feat: ship minecraft theme updates and platform workflow improvements

这个提交包含在:
Codex CLI
2026-02-15 17:36:56 +08:00
父节点 cd7540ab9d
当前提交 37266bb846
修改 32 个文件,包含 5297 行新增119 行删除

查看文件

@@ -13,6 +13,9 @@ class AdminController : public drogon::HttpController<AdminController> {
ADD_METHOD_TO(AdminController::updateUserRating,
"/api/v1/admin/users/{1}/rating",
drogon::Patch);
ADD_METHOD_TO(AdminController::deleteUser,
"/api/v1/admin/users/{1}",
drogon::Delete);
ADD_METHOD_TO(AdminController::listRedeemItems, "/api/v1/admin/redeem-items", drogon::Get);
ADD_METHOD_TO(AdminController::createRedeemItem, "/api/v1/admin/redeem-items", drogon::Post);
ADD_METHOD_TO(AdminController::updateRedeemItem,
@@ -32,6 +35,9 @@ class AdminController : public drogon::HttpController<AdminController> {
void updateUserRating(const drogon::HttpRequestPtr& req,
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
int64_t user_id);
void deleteUser(const drogon::HttpRequestPtr& req,
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
int64_t user_id);
void listRedeemItems(const drogon::HttpRequestPtr& req,
std::function<void(const drogon::HttpResponsePtr&)>&& cb);

查看文件

@@ -22,6 +22,7 @@ class UserService {
std::vector<domain::GlobalLeaderboardEntry> GlobalLeaderboard(int limit = 100);
UserListResult ListUsers(int page, int page_size);
void SetRating(int64_t user_id, int rating);
void DeleteUser(int64_t user_id);
private:
db::SqliteDb& db_;

查看文件

@@ -192,6 +192,42 @@ void AdminController::updateUserRating(
}
}
void AdminController::deleteUser(
const drogon::HttpRequestPtr& req,
std::function<void(const drogon::HttpResponsePtr&)>&& cb,
int64_t user_id) {
try {
const auto admin_user_id = RequireAdminUserId(req, cb);
if (!admin_user_id.has_value()) return;
if (*admin_user_id == user_id) {
cb(JsonError(drogon::k400BadRequest, "cannot delete current admin user"));
return;
}
services::UserService users(csp::AppState::Instance().db());
const auto target = users.GetById(user_id);
if (!target.has_value()) {
cb(JsonError(drogon::k404NotFound, "user not found"));
return;
}
if (target->username == "admin") {
cb(JsonError(drogon::k400BadRequest, "cannot delete reserved admin user"));
return;
}
users.DeleteUser(user_id);
Json::Value payload;
payload["id"] = Json::Int64(user_id);
payload["username"] = target->username;
payload["deleted"] = true;
cb(JsonOk(payload));
} catch (const std::exception& e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));
}
}
void AdminController::listRedeemItems(
const drogon::HttpRequestPtr& req,
std::function<void(const drogon::HttpResponsePtr&)>&& cb) {

查看文件

@@ -100,6 +100,7 @@ Json::Value BuildOpenApiSpec() {
paths["/api/v1/submissions/{id}/analysis"]["post"]["summary"] = "提交评测建议LLM";
paths["/api/v1/admin/users"]["get"]["summary"] = "管理员用户列表";
paths["/api/v1/admin/users/{id}/rating"]["patch"]["summary"] = "管理员修改用户积分";
paths["/api/v1/admin/users/{id}"]["delete"]["summary"] = "管理员删除用户";
paths["/api/v1/admin/redeem-items"]["get"]["summary"] = "管理员查看积分兑换物品";
paths["/api/v1/admin/redeem-items"]["post"]["summary"] = "管理员新增积分兑换物品";
paths["/api/v1/admin/redeem-items/{id}"]["patch"]["summary"] = "管理员修改积分兑换物品";

查看文件

@@ -138,7 +138,13 @@ void ProblemController::getDraft(
services::ProblemWorkspaceService svc(csp::AppState::Instance().db());
const auto draft = svc.GetDraft(*user_id, problem_id);
if (!draft.has_value()) {
cb(JsonError(drogon::k404NotFound, "draft not found"));
Json::Value payload;
payload["language"] = "cpp";
payload["code"] = "";
payload["stdin"] = "";
payload["updated_at"] = Json::nullValue;
payload["exists"] = false;
cb(JsonOk(payload));
return;
}
@@ -147,6 +153,7 @@ void ProblemController::getDraft(
payload["code"] = draft->code;
payload["stdin"] = draft->stdin_text;
payload["updated_at"] = Json::Int64(draft->updated_at);
payload["exists"] = true;
cb(JsonOk(payload));
} catch (const std::exception& e) {
cb(JsonError(drogon::k500InternalServerError, e.what()));

查看文件

@@ -126,4 +126,18 @@ void UserService::SetRating(int64_t user_id, int rating) {
if (sqlite3_changes(db) <= 0) throw std::runtime_error("user not found");
}
void UserService::DeleteUser(int64_t user_id) {
if (user_id <= 0) throw std::runtime_error("invalid user_id");
sqlite3* db = db_.raw();
sqlite3_stmt* stmt = nullptr;
const char* sql = "DELETE FROM users WHERE id=?";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare delete user");
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
CheckSqlite(sqlite3_step(stmt), db, "exec delete user");
sqlite3_finalize(stmt);
if (sqlite3_changes(db) <= 0) throw std::runtime_error("user not found");
}
} // namespace csp::services

查看文件

@@ -8,6 +8,16 @@ export const runtime = "nodejs";
const CACHE_DIR = process.env.CSP_IMAGE_CACHE_DIR ?? "/tmp/csp-image-cache";
const MAX_BYTES = 5 * 1024 * 1024;
const IMAGE_EXT_TO_TYPE: Record<string, string> = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".webp": "image/webp",
".gif": "image/gif",
".svg": "image/svg+xml",
".bmp": "image/bmp",
".ico": "image/x-icon",
};
function toArrayBuffer(view: Uint8Array): ArrayBuffer {
return view.buffer.slice(
@@ -28,6 +38,25 @@ function pickExt(urlObj: URL, contentType: string): string {
return ".img";
}
function inferImageType(urlObj: URL, contentType: string): string {
const raw = contentType.split(";")[0].trim().toLowerCase();
if (raw.startsWith("image/")) return raw;
const ext = path.extname(urlObj.pathname || "").toLowerCase();
return IMAGE_EXT_TO_TYPE[ext] ?? raw;
}
function looksLikeImage(urlObj: URL, contentType: string): boolean {
if (contentType.startsWith("image/")) return true;
const ext = path.extname(urlObj.pathname || "").toLowerCase();
return Boolean(IMAGE_EXT_TO_TYPE[ext]);
}
function redirectToTarget(target: URL): NextResponse {
const res = NextResponse.redirect(target.toString(), 307);
res.headers.set("Cache-Control", "no-store");
return res;
}
async function readCachedByKey(
key: string
): Promise<{ data: Uint8Array; contentType: string } | null> {
@@ -94,14 +123,12 @@ export async function GET(req: NextRequest) {
});
if (!resp.ok) {
return NextResponse.json(
{ ok: false, error: `fetch image failed: HTTP ${resp.status}` },
{ status: 502 }
);
return redirectToTarget(target);
}
const contentType = (resp.headers.get("content-type") ?? "").toLowerCase();
if (!contentType.startsWith("image/")) {
const headerType = (resp.headers.get("content-type") ?? "").toLowerCase();
const contentType = inferImageType(target, headerType);
if (!looksLikeImage(target, contentType)) {
return NextResponse.json({ ok: false, error: "url is not an image" }, { status: 400 });
}
@@ -125,11 +152,8 @@ export async function GET(req: NextRequest) {
"Cache-Control": "public, max-age=31536000, immutable",
},
});
} catch (e: unknown) {
return NextResponse.json(
{ ok: false, error: `fetch image failed: ${String(e)}` },
{ status: 502 }
);
} catch {
return redirectToTarget(target);
} finally {
clearTimeout(timer);
}

查看文件

@@ -59,6 +59,20 @@ type TriggerMissingResp = {
max_solutions: number;
};
type AdminUser = {
id: number;
username: string;
rating: number;
created_at: number;
};
type AdminUsersResp = {
items: AdminUser[];
total_count: number;
page: number;
page_size: number;
};
function fmtTs(v: number | null | undefined): string {
if (!v) return "-";
return new Date(v * 1000).toLocaleString();
@@ -81,17 +95,24 @@ export default function BackendLogsPage() {
const [queuedIds, setQueuedIds] = useState<number[]>([]);
const [triggerLoading, setTriggerLoading] = useState(false);
const [triggerMsg, setTriggerMsg] = useState("");
const [users, setUsers] = useState<AdminUser[]>([]);
const [deleteUserId, setDeleteUserId] = useState<number | null>(null);
const [userMsg, setUserMsg] = useState("");
const refresh = async () => {
if (!isAdmin || !token) return;
setLoading(true);
setError("");
try {
const data = await apiFetch<BackendLogsResp>(
`/api/v1/backend/logs?limit=${limit}&running_limit=20&queued_limit=100`,
{},
token
);
const [data, usersData] = await Promise.all([
apiFetch<BackendLogsResp>(
`/api/v1/backend/logs?limit=${limit}&running_limit=20&queued_limit=100`,
{},
token
),
apiFetch<AdminUsersResp>("/api/v1/admin/users?page=1&page_size=200", {}, token),
]);
setPendingJobs(data.pending_jobs ?? 0);
setMissingProblems(data.missing_problems ?? 0);
setItems(data.items ?? []);
@@ -99,6 +120,7 @@ export default function BackendLogsPage() {
setQueuedJobs(data.queued_jobs ?? []);
setRunningIds(data.running_problem_ids ?? []);
setQueuedIds(data.queued_problem_ids ?? []);
setUsers(usersData.items ?? []);
} catch (e: unknown) {
setError(String(e));
} finally {
@@ -185,6 +207,45 @@ export default function BackendLogsPage() {
}
};
const deleteUser = async (user: AdminUser) => {
if (!isAdmin || !token) return;
if (user.username === "admin") {
setError(tx("保留管理员账号不可删除", "Reserved admin account cannot be deleted"));
return;
}
const ok = window.confirm(
tx(
`确认删除用户 ${user.username}(#${user.id})?该用户提交记录、错题本、草稿、积分记录会被级联删除。`,
`Delete user ${user.username}(#${user.id})? Submissions, wrong-book, drafts, and points records will be removed by cascade.`
)
);
if (!ok) return;
setDeleteUserId(user.id);
setError("");
setUserMsg("");
try {
await apiFetch(
`/api/v1/admin/users/${user.id}`,
{
method: "DELETE",
},
token
);
setUserMsg(
tx(
`已删除用户 ${user.username}(#${user.id})`,
`Deleted user ${user.username}(#${user.id}).`
)
);
await refresh();
} catch (e: unknown) {
setError(String(e));
} finally {
setDeleteUserId(null);
}
};
useEffect(() => {
if (!isAdmin || !token) return;
const timer = setInterval(() => {
@@ -250,12 +311,94 @@ export default function BackendLogsPage() {
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
{triggerMsg && <p className="mt-3 text-sm text-emerald-700">{triggerMsg}</p>}
{userMsg && <p className="mt-3 text-sm text-emerald-700">{userMsg}</p>}
<p className="mt-3 text-xs text-zinc-500">
{tx(
"系统已自动单线程异步处理待队列任务,无需手工点击;上方按钮仅用于立即手动补全。",
"System auto-processes queued jobs in single-thread async mode; the button above is only for manual trigger."
)}
</p>
<section className="mt-4 rounded-xl border bg-white p-3">
<div className="flex flex-wrap items-center justify-between gap-2">
<h2 className="text-sm font-medium">{tx("用户管理(可删除)", "User Management (Delete Supported)")}</h2>
<p className="text-xs text-zinc-600">
{tx("总用户", "Total users")} {users.length}
</p>
</div>
<div className="mt-3 divide-y md:hidden">
{users.map((user) => (
<article key={user.id} className="space-y-1 py-2 text-xs">
<p className="font-medium">
#{user.id} · {user.username}
</p>
<p className="text-zinc-600">
Rating {user.rating} · {tx("创建", "Created")} {fmtTs(user.created_at)}
</p>
<button
className="rounded border px-2 py-1 text-xs text-red-700 hover:bg-red-50 disabled:opacity-60"
disabled={deleteUserId === user.id || user.username === "admin"}
onClick={() => void deleteUser(user)}
>
{user.username === "admin"
? tx("保留账号", "Reserved")
: deleteUserId === user.id
? tx("删除中...", "Deleting...")
: tx("删除用户", "Delete User")}
</button>
</article>
))}
{!loading && users.length === 0 && (
<p className="py-4 text-center text-sm text-zinc-500">{tx("暂无用户数据", "No users found")}</p>
)}
</div>
<div className="mt-2 hidden overflow-x-auto md:block">
<table className="min-w-full text-xs">
<thead className="bg-zinc-100 text-left">
<tr>
<th className="px-2 py-2">ID</th>
<th className="px-2 py-2">{tx("用户名", "Username")}</th>
<th className="px-2 py-2">Rating</th>
<th className="px-2 py-2">{tx("创建时间", "Created At")}</th>
<th className="px-2 py-2">{tx("操作", "Action")}</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id} className="border-t">
<td className="px-2 py-2">{user.id}</td>
<td className="px-2 py-2">{user.username}</td>
<td className="px-2 py-2">{user.rating}</td>
<td className="px-2 py-2 text-zinc-600">{fmtTs(user.created_at)}</td>
<td className="px-2 py-2">
<button
className="rounded border px-2 py-1 text-xs text-red-700 hover:bg-red-50 disabled:opacity-60"
disabled={deleteUserId === user.id || user.username === "admin"}
onClick={() => void deleteUser(user)}
>
{user.username === "admin"
? tx("保留账号", "Reserved")
: deleteUserId === user.id
? tx("删除中...", "Deleting...")
: tx("删除用户", "Delete User")}
</button>
</td>
</tr>
))}
{!loading && users.length === 0 && (
<tr>
<td className="px-2 py-4 text-center text-zinc-500" colSpan={5}>
{tx("暂无用户数据", "No users found")}
</td>
</tr>
)}
</tbody>
</table>
</div>
</section>
<section className="mt-4 grid gap-3 md:grid-cols-2">
<article className="rounded-xl border bg-white p-3">
<h2 className="text-sm font-medium">{tx("正在处理Running", "Running Jobs")}</h2>

查看文件

@@ -35,6 +35,11 @@ body {
font-size: 1em;
}
.solution-code-block {
font-size: calc(0.875rem * 0.7) !important;
line-height: 1.05rem !important;
}
.print-only {
display: none;
}

查看文件

@@ -2,6 +2,7 @@
import { useEffect, useMemo, useState } from "react";
import { PixelAvatar } from "@/components/pixel-avatar";
import { apiFetch } from "@/lib/api";
import { readToken } from "@/lib/auth";
import { useI18nText } from "@/lib/i18n";
@@ -218,10 +219,23 @@ export default function MePage() {
{profile && (
<section className="mt-4 rounded-xl border bg-white p-4 text-sm">
<p>ID: {profile.id}</p>
<p>{tx("用户名", "Username")}: {profile.username}</p>
<p>Rating: {profile.rating}</p>
<p>{tx("创建时间", "Created At")}: {fmtTs(profile.created_at)}</p>
<div className="flex flex-wrap items-center gap-4">
<PixelAvatar
seed={`${profile.username}-${profile.id}`}
size={72}
className="border-zinc-700"
alt={`${profile.username} avatar`}
/>
<div className="space-y-1">
<p>ID: {profile.id}</p>
<p>{tx("用户名", "Username")}: {profile.username}</p>
<p>Rating: {profile.rating}</p>
<p>{tx("创建时间", "Created At")}: {fmtTs(profile.created_at)}</p>
<p className="text-xs text-zinc-500">
{tx("默认像素头像按账号随机生成,可作为主题角色形象。", "Default pixel avatar is randomly generated by account as your theme character.")}
</p>
</div>
</div>
</section>
)}

查看文件

@@ -2,7 +2,7 @@
import Link from "next/link";
import { useParams } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { CodeEditor } from "@/components/code-editor";
import { MarkdownRenderer } from "@/components/markdown-renderer";
@@ -100,6 +100,12 @@ function scoreRatio(score: number): number {
return Math.max(0, Math.min(100, score));
}
function buildDraftSignature(code: string, stdinText: string): string {
const normCode = normalizeCodeText(code);
const normStdin = (stdinText ?? "").replace(/\r\n?/g, "\n");
return `${normCode}\n__STDIN__\n${normStdin}`;
}
type ResultTone = {
title: string;
icon: string;
@@ -154,7 +160,8 @@ type DraftResp = {
language: string;
code: string;
stdin: string;
updated_at: number;
updated_at: number | null;
exists?: boolean;
};
type SolutionItem = {
@@ -292,6 +299,14 @@ export default function ProblemDetailPage() {
const [solutionData, setSolutionData] = useState<SolutionResp | null>(null);
const [solutionMsg, setSolutionMsg] = useState("");
const [printAnswerMarkdown, setPrintAnswerMarkdown] = useState("");
const draftLatestRef = useRef<{ code: string; stdin: string }>({
code: starterCode,
stdin: defaultRunInput,
});
const draftLastSavedSigRef = useRef<string>(
buildDraftSignature(starterCode, defaultRunInput)
);
const draftAutoSavingRef = useRef(false);
const llmProfile = useMemo<LlmProfile | null>(() => {
if (!problem?.llm_profile_json) return null;
@@ -392,8 +407,14 @@ export default function ProblemDetailPage() {
setShowSolutions(false);
setSolutionData(null);
setSolutionMsg("");
draftLatestRef.current = { code: starterCode, stdin: defaultRunInput };
draftLastSavedSigRef.current = buildDraftSignature(starterCode, defaultRunInput);
}, [id]);
useEffect(() => {
draftLatestRef.current = { code, stdin: runInput };
}, [code, runInput]);
useEffect(() => {
const clearPrintCache = () => setPrintAnswerMarkdown("");
window.addEventListener("afterprint", clearPrintCache);
@@ -477,9 +498,21 @@ export default function ProblemDetailPage() {
if (!token) return;
try {
const draft = await apiFetch<DraftResp>(`/api/v1/problems/${id}/draft`, undefined, token);
if (draft.code) setCode(draft.code);
if (draft.stdin) setRunInput(draft.stdin);
setDraftMsg(tx("已自动加载草稿", "Draft auto-loaded"));
const hasDraft = Boolean(draft.exists) || Boolean(draft.code) || Boolean(draft.stdin);
let nextCode = draftLatestRef.current.code;
let nextStdin = draftLatestRef.current.stdin;
if (draft.code) {
nextCode = draft.code;
setCode(draft.code);
}
if (typeof draft.stdin === "string" && draft.stdin.length > 0) {
nextStdin = draft.stdin;
setRunInput(draft.stdin);
}
draftLastSavedSigRef.current = buildDraftSignature(nextCode, nextStdin);
if (hasDraft) {
setDraftMsg(tx("已自动加载草稿", "Draft auto-loaded"));
}
} catch {
// ignore empty draft / unauthorized
}
@@ -487,6 +520,39 @@ export default function ProblemDetailPage() {
void loadDraft();
}, [id, tx]);
useEffect(() => {
if (!Number.isFinite(id) || id <= 0) return;
const timer = window.setInterval(() => {
const token = readToken();
if (!token || draftAutoSavingRef.current) return;
const payload = draftLatestRef.current;
const nextSig = buildDraftSignature(payload.code, payload.stdin);
if (nextSig === draftLastSavedSigRef.current) return;
draftAutoSavingRef.current = true;
void apiFetch<{ saved: boolean }>(
`/api/v1/problems/${id}/draft`,
{
method: "PUT",
body: JSON.stringify({ language: "cpp", code: payload.code, stdin: payload.stdin }),
},
token
)
.then(() => {
draftLastSavedSigRef.current = nextSig;
setDraftMsg(tx("草稿已自动保存每60秒", "Draft auto-saved (every 60s)"));
})
.catch(() => {
// Keep silent to avoid noisy notifications on transient network errors.
})
.finally(() => {
draftAutoSavingRef.current = false;
});
}, 60000);
return () => window.clearInterval(timer);
}, [id, tx]);
useEffect(() => {
if (!problemId) return;
setRunInput((prev) => (prev.trim().length > 0 ? prev : sampleInput || defaultRunInput));
@@ -555,6 +621,7 @@ export default function ProblemDetailPage() {
},
token
);
draftLastSavedSigRef.current = buildDraftSignature(code, runInput);
setDraftMsg(tx("草稿已保存", "Draft saved"));
} catch (e: unknown) {
setError(String(e));
@@ -1084,7 +1151,7 @@ export default function ProblemDetailPage() {
{tx("写入并试运行", "Insert & Run")}
</button>
</div>
<pre className="overflow-x-auto whitespace-pre p-3 text-sm leading-6 text-zinc-100">
<pre className="solution-code-block overflow-x-auto whitespace-pre p-3 text-sm leading-6 text-zinc-100">
{normalizeCodeText(item.code_cpp)}
</pre>
</div>

查看文件

@@ -3,6 +3,7 @@
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useMemo, useRef, useState } from "react";
import { PixelAvatar } from "@/components/pixel-avatar";
import { useUiPreferences } from "@/components/ui-preference-provider";
import { apiFetch } from "@/lib/api";
import { clearToken, readToken } from "@/lib/auth";
@@ -19,6 +20,11 @@ type NavGroup = {
links: NavLink[];
};
type MeProfile = {
id?: number;
username?: string;
};
function buildNavGroups(t: (key: string) => string, isAdmin: boolean): NavGroup[] {
const groups: NavGroup[] = [
{
@@ -97,12 +103,13 @@ export function AppNav() {
const [hasToken, setHasToken] = useState<boolean>(() => Boolean(readToken()));
const [isAdmin, setIsAdmin] = useState(false);
const [meProfile, setMeProfile] = useState<MeProfile | null>(null);
const [menuOpen, setMenuOpen] = useState(false);
const [desktopOpenGroup, setDesktopOpenGroup] = useState<string | null>(null);
const desktopMenuRef = useRef<HTMLDivElement | null>(null);
const navGroups = useMemo(() => buildNavGroups(t, isAdmin), [isAdmin, t]);
const activeGroup = resolveActiveGroup(pathname, navGroups);
const usePopupSecondary = theme === "default";
const usePopupSecondary = theme === "default" || theme === "minecraft";
useEffect(() => {
let canceled = false;
@@ -112,15 +119,20 @@ export function AppNav() {
setHasToken(Boolean(token));
if (!token) {
setIsAdmin(false);
setMeProfile(null);
return;
}
try {
const me = await apiFetch<{ username?: string }>("/api/v1/me", {}, token);
const me = await apiFetch<MeProfile>("/api/v1/me", {}, token);
if (!canceled) {
setIsAdmin((me?.username ?? "") === "admin");
setMeProfile(me ?? null);
}
} catch {
if (!canceled) setIsAdmin(false);
if (!canceled) {
setIsAdmin(false);
setMeProfile(null);
}
}
};
const onRefresh = () => {
@@ -149,6 +161,16 @@ export function AppNav() {
}, [desktopOpenGroup, usePopupSecondary]);
const currentGroup = navGroups.find((g) => g.key === activeGroup) ?? navGroups[0];
const avatarSeed = meProfile
? `${meProfile.username ?? "user"}-${meProfile.id ?? ""}`
: "guest";
const handleLogout = () => {
clearToken();
setHasToken(false);
setIsAdmin(false);
setMeProfile(null);
setDesktopOpenGroup(null);
};
return (
<header className="print-hidden border-b bg-[color:var(--surface)]/95 backdrop-blur supports-[backdrop-filter]:bg-[color:var(--surface)]/85">
@@ -188,7 +210,11 @@ export function AppNav() {
{group.label}
</button>
{opened && (
<div className="absolute left-0 top-full z-50 mt-2 min-w-[11rem] rounded-md border bg-[color:var(--surface)] p-1 shadow-lg">
<div
className={`absolute left-0 top-full z-50 mt-2 rounded-md border bg-[color:var(--surface)] p-1 shadow-lg ${
group.key === "account" ? "min-w-[18rem]" : "min-w-[11rem]"
}`}
>
{group.links.map((item) => {
const linkActive = isActivePath(pathname, item.href);
return (
@@ -207,6 +233,64 @@ export function AppNav() {
</button>
);
})}
{group.key === "account" && (
<div className="mt-2 space-y-2 border-t border-zinc-200 px-2 pt-2">
<label className="block text-xs">
<span className="text-zinc-500">{t("prefs.theme")}</span>
<select
className="mt-1 w-full rounded-md border px-2 py-1 text-xs"
value={theme}
onChange={(e) => setTheme(e.target.value as ThemeId)}
>
{themes.map((item) => (
<option key={item.id} value={item.id}>
{item.labels[language]}
</option>
))}
</select>
</label>
<label className="block text-xs">
<span className="text-zinc-500">{t("prefs.language")}</span>
<select
className="mt-1 w-full rounded-md border px-2 py-1 text-xs"
value={language}
onChange={(e) => setLanguage(e.target.value === "zh" ? "zh" : "en")}
>
<option value="en">{t("prefs.lang.en")}</option>
<option value="zh">{t("prefs.lang.zh")}</option>
</select>
</label>
<div className="flex items-center justify-between gap-2 rounded-md border border-zinc-200 px-2 py-1.5">
<div className="flex min-w-0 items-center gap-2">
<span className={hasToken ? "text-emerald-700" : "text-zinc-500"}>
{hasToken ? t("nav.logged_in") : t("nav.logged_out")}
</span>
{hasToken && (
<>
<PixelAvatar
seed={avatarSeed}
size={20}
className="border-zinc-700"
alt={meProfile?.username ? `${meProfile.username} avatar` : "avatar"}
/>
{meProfile?.username && (
<span className="truncate text-xs text-zinc-500">{meProfile.username}</span>
)}
</>
)}
</div>
{hasToken && (
<button
type="button"
className="rounded-md border px-2 py-1 text-xs hover:bg-zinc-100"
onClick={handleLogout}
>
{t("nav.logout")}
</button>
)}
</div>
</div>
)}
</div>
)}
</div>
@@ -274,7 +358,7 @@ export function AppNav() {
</div>
</div>
<div className="mt-2 flex flex-wrap items-center justify-end gap-2 text-xs sm:text-sm">
<div className={`mt-2 flex flex-wrap items-center justify-end gap-2 text-xs sm:text-sm ${usePopupSecondary ? "md:hidden" : ""}`}>
<label className="inline-flex items-center gap-1">
<span className="text-zinc-500">{t("prefs.theme")}</span>
<select
@@ -306,13 +390,17 @@ export function AppNav() {
<span className={hasToken ? "text-emerald-700" : "text-zinc-500"}>
{hasToken ? t("nav.logged_in") : t("nav.logged_out")}
</span>
{hasToken && (
<PixelAvatar
seed={avatarSeed}
size={24}
className="border-zinc-700"
alt={meProfile?.username ? `${meProfile.username} avatar` : "avatar"}
/>
)}
{hasToken && (
<button
onClick={() => {
clearToken();
setHasToken(false);
setIsAdmin(false);
}}
onClick={handleLogout}
className="rounded-md border px-3 py-1 hover:bg-zinc-100"
>
{t("nav.logout")}

查看文件

@@ -4,11 +4,13 @@ import dynamic from "next/dynamic";
import { useCallback, useEffect, useRef } from "react";
import type { editor as MonacoEditorNS, IDisposable, MarkerSeverity, IPosition } from "monaco-editor";
import { useUiPreferences } from "@/components/ui-preference-provider";
import { analyzeCpp14Policy, type Cpp14PolicyIssue } from "@/lib/cpp14-policy";
import { useI18nText } from "@/lib/i18n";
const MonacoEditor = dynamic(() => import("@monaco-editor/react"), { ssr: false });
const POLICY_MARKER_OWNER = "csp-cpp14-policy";
const MINECRAFT_MONACO_THEME = "csp-minecraft-dark";
type Props = {
value: string;
@@ -94,6 +96,40 @@ function localizePolicyIssue(
return { ...issue, ...localized };
}
function defineMinecraftMonacoTheme(monaco: typeof import("monaco-editor")): void {
monaco.editor.defineTheme(MINECRAFT_MONACO_THEME, {
base: "vs-dark",
inherit: true,
rules: [
{ token: "", foreground: "ECECEC", background: "111317" },
{ token: "comment", foreground: "7F8A99", fontStyle: "italic" },
{ token: "keyword", foreground: "C792EA" },
{ token: "type", foreground: "82AAFF" },
{ token: "number", foreground: "F78C6C" },
{ token: "string", foreground: "C3E88D" },
{ token: "operator", foreground: "89DDFF" },
{ token: "delimiter.bracket", foreground: "D8DEE9" },
{ token: "predefined", foreground: "FFD580" },
],
colors: {
"editor.background": "#111317",
"editor.foreground": "#ECECEC",
"editorCursor.foreground": "#FFE082",
"editorLineNumber.foreground": "#768296",
"editorLineNumber.activeForeground": "#C7D0DE",
"editor.selectionBackground": "#2A3347",
"editor.inactiveSelectionBackground": "#232B3C",
"editor.lineHighlightBackground": "#171D26",
"editor.wordHighlightBackground": "#303A4E99",
"editorIndentGuide.background": "#2A3240",
"editorIndentGuide.activeBackground": "#3D485A",
"editorBracketMatch.background": "#2B364A",
"editorBracketMatch.border": "#89DDFF88",
"editorGutter.background": "#111317",
},
});
}
export function CodeEditor({
value,
onChange,
@@ -101,10 +137,12 @@ export function CodeEditor({
fontSize = 14,
onPolicyIssuesChange,
}: Props) {
const { theme } = useUiPreferences();
const { tx } = useI18nText();
const editorRef = useRef<MonacoEditorNS.IStandaloneCodeEditor | null>(null);
const monacoRef = useRef<typeof import("monaco-editor") | null>(null);
const completionRef = useRef<IDisposable | null>(null);
const editorTheme = theme === "minecraft" ? MINECRAFT_MONACO_THEME : "vs";
const updatePolicyIssues = useCallback(
(nextCode: string) => {
@@ -145,10 +183,20 @@ export function CodeEditor({
[]
);
useEffect(() => {
const monaco = monacoRef.current;
if (!monaco) return;
if (editorTheme === MINECRAFT_MONACO_THEME) {
defineMinecraftMonacoTheme(monaco);
}
monaco.editor.setTheme(editorTheme);
}, [editorTheme]);
return (
<MonacoEditor
height={height}
language="cpp"
theme={editorTheme}
value={value}
options={{
fontSize,
@@ -167,6 +215,10 @@ export function CodeEditor({
onMount={(editor, monaco) => {
editorRef.current = editor;
monacoRef.current = monaco;
if (editorTheme === MINECRAFT_MONACO_THEME) {
defineMinecraftMonacoTheme(monaco);
}
monaco.editor.setTheme(editorTheme);
if (!completionRef.current) {
completionRef.current = monaco.languages.registerCompletionItemProvider("cpp", {

查看文件

@@ -0,0 +1,39 @@
"use client";
import Image from "next/image";
import { useMemo } from "react";
import { buildAvatarSeed, generatePixelAvatarDataUri } from "@/lib/pixel-avatar";
type PixelAvatarProps = {
seed: string;
size?: number;
className?: string;
alt?: string;
};
export function PixelAvatar({
seed,
size = 40,
className = "",
alt = "pixel avatar",
}: PixelAvatarProps) {
const resolvedSeed = useMemo(() => buildAvatarSeed(seed), [seed]);
const src = useMemo(() => generatePixelAvatarDataUri(resolvedSeed), [resolvedSeed]);
return (
<span
className={`pixel-avatar-frame inline-flex items-center justify-center overflow-hidden rounded border ${className}`.trim()}
style={{ width: size, height: size }}
>
<Image
src={src}
alt={alt}
width={size}
height={size}
unoptimized
className="pixel-avatar-image h-full w-full object-cover"
/>
</span>
);
}

查看文件

@@ -3,9 +3,9 @@ export const API_BASE =
(process.env.NODE_ENV === "development" ? "http://localhost:8080" : "/admin139");
function uiText(zhText: string, enText: string): string {
if (typeof window === "undefined") return enText;
if (typeof window === "undefined") return zhText;
const lang = window.localStorage.getItem("csp.ui.language");
return lang === "zh" ? zhText : enText;
return lang === "en" ? enText : zhText;
}
type ApiEnvelope<T> =

查看文件

@@ -186,21 +186,5 @@ export function analyzeCpp14Policy(code: string): Cpp14PolicyIssue[] {
);
}
if (!/\bfreopen\s*\(/.test(text) && /\bint\s+main\s*\(/.test(text)) {
const idx = text.search(/\bint\s+main\s*\(/);
const pos = offsetToPosition(lineStarts, Math.max(0, idx));
pushIssue(
issues,
"freopen-tip",
"hint",
"未检测到 freopen福建二轮常见文件读写要求",
"若考场题面要求 *.in/*.out,请按官方文件名补上 freopen。",
pos.line,
pos.column,
pos.line,
pos.column + 3
);
}
return issues;
}

查看文件

@@ -1,15 +1,20 @@
"use client";
import { useCallback } from "react";
import { useUiPreferences } from "@/components/ui-preference-provider";
export function readUiLanguage(): "en" | "zh" {
if (typeof window === "undefined") return "en";
return window.localStorage.getItem("csp.ui.language") === "zh" ? "zh" : "en";
if (typeof window === "undefined") return "zh";
return window.localStorage.getItem("csp.ui.language") === "en" ? "en" : "zh";
}
export function useI18nText() {
const { language } = useUiPreferences();
const isZh = language === "zh";
const tx = (zhText: string, enText: string) => (isZh ? zhText : enText);
const tx = useCallback(
(zhText: string, enText: string) => (isZh ? zhText : enText),
[isZh]
);
return { language, isZh, tx };
}

查看文件

@@ -0,0 +1,74 @@
function hashSeed(seed: string): number {
let h = 2166136261 >>> 0;
for (let i = 0; i < seed.length; i += 1) {
h ^= seed.charCodeAt(i);
h = Math.imul(h, 16777619) >>> 0;
}
return h >>> 0;
}
function mulberry32(seed: number): () => number {
let t = seed >>> 0;
return () => {
t += 0x6d2b79f5;
let x = Math.imul(t ^ (t >>> 15), t | 1);
x ^= x + Math.imul(x ^ (x >>> 7), x | 61);
return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
};
}
const SKIN_PALETTE = ["#f6d3b3", "#e6be95", "#d7a980", "#f8dec6", "#c99264"];
const HAIR_PALETTE = ["#2f1b12", "#4a2f1d", "#6d4c41", "#212121", "#4e342e", "#1b5e20"];
const ACCENT_PALETTE = ["#7cb342", "#00b0d6", "#ffb300", "#8d6e63", "#ab47bc", "#ef5350"];
export function buildAvatarSeed(...parts: Array<string | number | null | undefined>): string {
const cleaned = parts
.map((part) => String(part ?? "").trim())
.filter((part) => part.length > 0);
return cleaned.length > 0 ? cleaned.join("|") : "guest|pixel";
}
export function generatePixelAvatarDataUri(seedInput: string, pixelSize = 10): string {
const seed = buildAvatarSeed(seedInput);
const rng = mulberry32(hashSeed(seed));
const skin = SKIN_PALETTE[Math.floor(rng() * SKIN_PALETTE.length)] ?? SKIN_PALETTE[0];
const hair = HAIR_PALETTE[Math.floor(rng() * HAIR_PALETTE.length)] ?? HAIR_PALETTE[0];
const accent = ACCENT_PALETTE[Math.floor(rng() * ACCENT_PALETTE.length)] ?? ACCENT_PALETTE[0];
const bg = rng() > 0.5 ? "#1f1f1f" : "#2b2b2b";
const border = rng() > 0.5 ? "#000000" : "#3e2723";
const width = 8 * pixelSize;
const height = 8 * pixelSize;
const rects: string[] = [
`<rect width="${width}" height="${height}" fill="${bg}"/>`,
`<rect x="0" y="0" width="${width}" height="${pixelSize}" fill="${accent}" opacity="0.4"/>`,
];
for (let y = 0; y < 8; y += 1) {
for (let x = 0; x < 4; x += 1) {
const fillFace = y >= 1 && y <= 6 && x >= 1;
const threshold = fillFace ? 0.2 : 0.52;
if (rng() < threshold) continue;
const color = y <= 1 ? hair : y >= 6 ? accent : skin;
const leftX = x * pixelSize;
const rightX = (7 - x) * pixelSize;
const yPos = y * pixelSize;
rects.push(`<rect x="${leftX}" y="${yPos}" width="${pixelSize}" height="${pixelSize}" fill="${color}"/>`);
if (rightX !== leftX) {
rects.push(`<rect x="${rightX}" y="${yPos}" width="${pixelSize}" height="${pixelSize}" fill="${color}"/>`);
}
}
}
const eyeY = 3 * pixelSize;
rects.push(`<rect x="${2 * pixelSize}" y="${eyeY}" width="${pixelSize}" height="${pixelSize}" fill="#111"/>`);
rects.push(`<rect x="${5 * pixelSize}" y="${eyeY}" width="${pixelSize}" height="${pixelSize}" fill="#111"/>`);
const mouthY = 5 * pixelSize;
rects.push(`<rect x="${3 * pixelSize}" y="${mouthY}" width="${2 * pixelSize}" height="${pixelSize}" fill="${hair}" opacity="0.8"/>`);
rects.push(`<rect x="${0}" y="${0}" width="${width}" height="${height}" fill="none" stroke="${border}" stroke-width="${Math.max(2, Math.floor(pixelSize / 4))}"/>`);
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" shape-rendering="crispEdges">${rects.join("")}</svg>`;
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
}

查看文件

@@ -247,6 +247,8 @@ labels: {
来源参考:
- `ref/CSP-Minecraft-UI-Kit/docs/Design-Delivery-Document.html`
- `ref/CSP-Minecraft-UI-Kit/html/index.html`
- `ref/CSP-Minecraft-UI-Kit/html/pages/*.html`
已落地核心规范:
@@ -256,6 +258,7 @@ labels: {
- 输入控件像素化风格。
- 背景纹理与图片 `image-rendering: pixelated`
- 标题字体与文本阴影风格。
- 像素默认头像(按账号随机种子生成)。
---
@@ -291,4 +294,3 @@ labels: {
- 将“按钮、卡片、输入框、徽章”收敛为主题化组件(而非纯 class 覆盖)。
- 增加主题视觉回归截图(关键页面自动快照)。
- 建立对比度检测脚本,避免低对比度文本上线。

查看文件

@@ -1,49 +1,51 @@
@font-face {
font-family: "DelaGothicOne";
src: url("https://assets-persist.lovart.ai/agent-static-assets/DelaGothicOne-Regular.ttf");
font-family: "PressStart2P";
src: url("https://fonts.gstatic.com/s/pressstart2p/v15/e3t4euO8T-267oIAQAu6jDQyK3nVivM.woff2")
format("woff2");
font-display: swap;
}
@font-face {
font-family: "MiSans";
src: url("https://assets-persist.lovart.ai/agent-static-assets/MiSans-Regular.ttf");
font-display: swap;
}
@font-face {
font-family: "MiSansBold";
src: url("https://assets-persist.lovart.ai/agent-static-assets/MiSans-Bold.ttf");
font-family: "VT323";
src: url("https://fonts.gstatic.com/s/vt323/v17/pxiKyp0ihIEF2hsY.woff2") format("woff2");
font-display: swap;
}
:root[data-theme="minecraft"] {
--mc-grass-top: #5cb85c;
--mc-grass-side: #4cae4c;
--mc-grass-top: #7cb342;
--mc-grass-dark: #558b2f;
--mc-dirt: #795548;
--mc-wood-dark: #5d4037;
--mc-wood: #8d6e63;
--mc-plank: #c69c6d;
--mc-plank-light: #efebe9;
--mc-stone: #9e9e9e;
--mc-stone-dark: #616161;
--mc-obsidian: #212121;
--mc-wood: #8d6e63;
--mc-wood-dark: #5d4037;
--mc-gold: #ffd700;
--mc-diamond: #40e0d0;
--mc-redstone: #f44336;
--mc-obsidian: #1f1f1f;
--mc-diamond: #00b0d6;
--mc-gold: #ffb300;
--mc-red: #ef5350;
--mc-shadow: #000000;
--background: #1a1a1a;
--background: #161616;
--foreground: #f5f5f5;
--surface: #2d2d2d;
--surface-soft: #242424;
--surface: #262626;
--surface-soft: #1f1f1f;
--border: #000000;
}
:root[data-theme="minecraft"] body {
background-color: var(--background);
background-image:
linear-gradient(rgba(26, 26, 26, 0.9), rgba(26, 26, 26, 0.9)),
url("https://a.lovart.ai/artifacts/agent/W1iXxVdg3xIm5fP9.png");
background-size: 320px 320px;
linear-gradient(45deg, #1f1f1f 25%, transparent 25%),
linear-gradient(-45deg, #1f1f1f 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #1f1f1f 75%),
linear-gradient(-45deg, transparent 75%, #1f1f1f 75%);
background-size: 40px 40px;
background-position: 0 0, 0 20px, 20px -20px, -20px 0;
color: var(--foreground);
font-family: "MiSans", "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
font-family: "VT323", "MiSans", "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 1.06rem;
}
:root[data-theme="minecraft"] h1,
@@ -52,9 +54,9 @@
:root[data-theme="minecraft"] h4,
:root[data-theme="minecraft"] h5,
:root[data-theme="minecraft"] h6 {
color: #ffffff;
font-family: "DelaGothicOne", "MiSansBold", "MiSans", sans-serif;
font-family: "PressStart2P", "VT323", sans-serif;
letter-spacing: 0.04em;
line-height: 1.5;
text-shadow: 2px 2px 0 #000000;
}
@@ -65,19 +67,35 @@
}
:root[data-theme="minecraft"] ::-webkit-scrollbar-thumb {
background: var(--mc-stone);
border: 2px solid var(--mc-obsidian);
background: var(--mc-wood);
border: 2px solid #000;
}
:root[data-theme="minecraft"] .print-hidden {
image-rendering: pixelated;
}
:root[data-theme="minecraft"] header.print-hidden {
background: rgba(33, 33, 33, 0.96) !important;
border-bottom: 4px solid var(--mc-stone-dark);
box-shadow: 0 4px 0 rgba(0, 0, 0, 0.35);
background: linear-gradient(180deg, #1d1d1d 0%, #2b2b2b 100%) !important;
border-bottom: 4px solid #000 !important;
box-shadow: 0 6px 0 rgba(0, 0, 0, 0.4);
position: relative;
}
:root[data-theme="minecraft"] header.print-hidden::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 8px;
background: linear-gradient(180deg, var(--mc-grass-top) 0%, var(--mc-grass-dark) 100%);
border-bottom: 2px solid #2e7d32;
}
:root[data-theme="minecraft"] nav.print-hidden.fixed {
background: rgba(33, 33, 33, 0.96) !important;
border-top: 4px solid var(--mc-stone-dark);
background: linear-gradient(180deg, #1d1d1d 0%, #2b2b2b 100%) !important;
border-top: 4px solid #000;
}
:root[data-theme="minecraft"] .rounded-xl.border,
@@ -85,28 +103,39 @@
:root[data-theme="minecraft"] .rounded-md.border,
:root[data-theme="minecraft"] .rounded.border {
border-radius: 0 !important;
border-color: #000000 !important;
border-width: 3px !important;
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.45);
border: 3px solid #000 !important;
box-shadow:
5px 5px 0 rgba(0, 0, 0, 0.48),
inset 2px 2px 0 rgba(255, 255, 255, 0.08);
}
:root[data-theme="minecraft"] main section.rounded-xl.border.bg-white,
:root[data-theme="minecraft"] main article.rounded-xl.border.bg-white {
background: linear-gradient(180deg, #2a2a2a 0%, #232323 100%) !important;
}
:root[data-theme="minecraft"] .bg-white {
background-color: #2d2d2d !important;
background-color: #292929 !important;
}
:root[data-theme="minecraft"] .bg-zinc-50 {
background-color: #252525 !important;
background-color: #242424 !important;
}
:root[data-theme="minecraft"] .bg-zinc-100 {
background-color: #343434 !important;
background-color: #323232 !important;
}
:root[data-theme="minecraft"] .text-zinc-400,
:root[data-theme="minecraft"] .text-zinc-500,
:root[data-theme="minecraft"] .text-zinc-600,
:root[data-theme="minecraft"] .text-zinc-700 {
color: #d0d0d0 !important;
color: #d7d7d7 !important;
}
:root[data-theme="minecraft"] .text-zinc-800,
:root[data-theme="minecraft"] .text-zinc-900 {
color: #ececec !important;
}
:root[data-theme="minecraft"] .text-blue-600,
@@ -125,24 +154,26 @@
:root[data-theme="minecraft"] .text-red-600,
:root[data-theme="minecraft"] .text-red-700 {
color: var(--mc-redstone) !important;
color: var(--mc-red) !important;
}
:root[data-theme="minecraft"] button {
background: linear-gradient(180deg, var(--mc-stone) 0%, var(--mc-stone-dark) 100%) !important;
border: 3px solid #000000 !important;
background: linear-gradient(180deg, var(--mc-wood) 0%, var(--mc-wood-dark) 100%) !important;
border: 3px solid #000 !important;
border-bottom-width: 7px !important;
border-radius: 0 !important;
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.45);
color: #ffffff !important;
font-family: "DelaGothicOne", "MiSansBold", "MiSans", sans-serif;
letter-spacing: 0.03em;
text-shadow: 1px 1px 0 #000000;
color: #fff !important;
font-family: "PressStart2P", "VT323", sans-serif !important;
font-size: 0.62rem !important;
letter-spacing: 0.04em;
line-height: 1.4;
text-shadow: 1px 1px 0 #000;
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.48);
transition: transform 0.08s ease, filter 0.08s ease;
}
:root[data-theme="minecraft"] button:hover:not(:disabled) {
filter: brightness(1.08);
filter: brightness(1.07);
transform: translateY(-1px);
}
@@ -152,23 +183,25 @@
}
:root[data-theme="minecraft"] button:disabled {
filter: saturate(0.25);
opacity: 0.7;
opacity: 0.68;
filter: saturate(0.28);
}
:root[data-theme="minecraft"] input,
:root[data-theme="minecraft"] textarea,
:root[data-theme="minecraft"] select {
background: #1f1f1f !important;
border: 3px solid #000000 !important;
border: 3px solid #000 !important;
border-radius: 0 !important;
box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.1);
color: #f2f2f2 !important;
color: #f4f4f4 !important;
box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.12);
font-family: "VT323", "MiSans", sans-serif;
font-size: 1rem;
}
:root[data-theme="minecraft"] input::placeholder,
:root[data-theme="minecraft"] textarea::placeholder {
color: #acacac;
color: #adadad;
}
:root[data-theme="minecraft"] a {
@@ -180,22 +213,80 @@
}
:root[data-theme="minecraft"] table thead {
background: #333333 !important;
background: #353535 !important;
}
:root[data-theme="minecraft"] table tr {
border-color: #000000 !important;
border-color: #000 !important;
}
:root[data-theme="minecraft"] pre {
border: 2px solid #000000;
border: 2px solid #000;
box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.08);
}
:root[data-theme="minecraft"] code {
font-family: "VT323", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
:root[data-theme="minecraft"] .monaco-editor,
:root[data-theme="minecraft"] .monaco-editor .margin,
:root[data-theme="minecraft"] .monaco-editor .monaco-editor-background,
:root[data-theme="minecraft"] .monaco-editor-background,
:root[data-theme="minecraft"] .monaco-editor .inputarea.ime-input {
background-color: #111317 !important;
}
:root[data-theme="minecraft"] .monaco-editor .current-line {
border-color: #232b3c !important;
}
:root[data-theme="minecraft"] .monaco-editor .suggest-widget,
:root[data-theme="minecraft"] .monaco-editor .parameter-hints-widget {
border: 2px solid #000 !important;
background: #161a22 !important;
color: #ececec !important;
}
:root[data-theme="minecraft"] .monaco-editor .suggest-widget .monaco-list-row.focused {
background: #2a3347 !important;
}
:root[data-theme="minecraft"] img {
image-rendering: pixelated;
}
:root[data-theme="minecraft"] .pixel-avatar-frame {
border: 3px solid #000 !important;
border-radius: 0 !important;
background: linear-gradient(180deg, var(--mc-plank) 0%, var(--mc-dirt) 100%);
box-shadow:
3px 3px 0 rgba(0, 0, 0, 0.45),
inset 1px 1px 0 rgba(255, 255, 255, 0.18);
}
:root[data-theme="minecraft"] .pixel-avatar-image {
image-rendering: pixelated;
}
:root[data-theme="minecraft"] .problem-markdown-compact {
font-size: 66%;
}
:root[data-theme="minecraft"] .problem-markdown-compact article {
color: #ececec !important;
}
:root[data-theme="minecraft"] .problem-markdown-compact blockquote {
color: #d9d9d9 !important;
border-left-color: #8d6e63 !important;
}
:root[data-theme="minecraft"] .problem-markdown-compact th,
:root[data-theme="minecraft"] .problem-markdown-compact td {
color: #ececec !important;
}
:root[data-theme="minecraft"] .problem-markdown-compact th {
background: #3a3a3a !important;
}

查看文件

@@ -2,7 +2,7 @@ export type ThemeId = "default" | "minecraft";
export type UiLanguage = "en" | "zh";
export const DEFAULT_THEME: ThemeId = "default";
export const DEFAULT_LANGUAGE: UiLanguage = "en";
export const DEFAULT_LANGUAGE: UiLanguage = "zh";
export type ThemeMessages = Record<string, string>;

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 5.5 MiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 5.6 MiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 5.2 MiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 6.0 MiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 5.7 MiB

查看文件

@@ -0,0 +1,651 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1920, initial-scale=1.0">
<title>CSP Learning Platform - Minecraft Edition</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.6.0/remixicon.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap" rel="stylesheet">
<style>
:root {
--grass-top: #7CB342;
--dirt-side: #795548;
--wood-frame: #5D4037;
--wood-bg: #A1887F;
--plank-light: #D7CCC8;
--gold-btn: #FFB300;
--gold-shadow: #BFA002;
--btn-shadow: #33691E;
--text-main: #3E2723;
--diamond-blue: #00B0D6;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
width: 1920px;
margin: 0 auto;
background-color: #1a1a1a;
font-family: 'VT323', monospace;
color: white;
overflow-x: hidden;
background-image:
linear-gradient(45deg, #1f1f1f 25%, transparent 25%),
linear-gradient(-45deg, #1f1f1f 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #1f1f1f 75%),
linear-gradient(-45deg, transparent 75%, #1f1f1f 75%);
background-size: 40px 40px;
background-position: 0 0, 0 20px, 20px -20px, -20px 0px;
}
/* Utility Classes */
.pixel-text {
font-family: 'Press Start 2P', cursive;
line-height: 1.5;
}
.container {
width: 1400px;
margin: 0 auto;
position: relative;
z-index: 2;
}
/* Hero Section */
.hero {
height: 400px;
background-color: var(--grass-top);
position: relative;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 20px solid var(--dirt-side);
box-shadow: inset 0 -10px 0 rgba(0,0,0,0.2);
overflow: hidden;
}
/* Minecraft Grass Pattern via CSS */
.hero::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(255,255,255,0.1) 2px, transparent 2px),
linear-gradient(90deg, rgba(255,255,255,0.1) 2px, transparent 2px);
background-size: 32px 32px;
opacity: 0.3;
pointer-events: none;
}
.hero-content {
text-align: center;
text-shadow: 4px 4px 0px rgba(0,0,0,0.5);
z-index: 3;
}
.hero-title {
font-size: 48px;
margin-bottom: 16px;
color: white;
letter-spacing: 2px;
text-transform: uppercase;
}
.hero-subtitle {
font-size: 24px;
color: #FFEB3B;
margin-bottom: 24px;
}
.hero-desc {
font-size: 28px; /* VT323 is smaller visually */
margin-bottom: 30px;
color: #E0E0E0;
}
.features-list {
display: flex;
justify-content: center;
gap: 24px;
margin-bottom: 40px;
font-size: 20px;
}
.feature-item {
display: flex;
align-items: center;
gap: 8px;
}
.feature-item i {
color: #FFEB3B;
}
/* 3D Pixel Button */
.btn-pixel {
display: inline-block;
padding: 20px 40px;
font-family: 'Press Start 2P', cursive;
font-size: 16px;
text-decoration: none;
color: white;
text-transform: uppercase;
position: relative;
cursor: pointer;
transition: transform 0.1s;
border: 4px solid rgba(0,0,0,0.5);
}
.btn-green {
background-color: #558B2F;
box-shadow:
inset 4px 4px 0 rgba(255,255,255,0.3),
inset -4px -4px 0 rgba(0,0,0,0.3),
4px 4px 0 #1B5E20;
}
.btn-green:active {
transform: translate(4px, 4px);
box-shadow: none;
}
.btn-gold {
background-color: var(--gold-btn);
color: #3E2723;
box-shadow:
inset 4px 4px 0 rgba(255,255,255,0.4),
inset -4px -4px 0 rgba(0,0,0,0.2),
4px 4px 0 var(--gold-shadow);
}
.btn-gold:active {
transform: translate(4px, 4px);
box-shadow: none;
}
/* Showcase Section */
.showcase {
padding: 60px 0;
}
.grid-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 40px;
margin-bottom: 60px;
}
.card {
background-color: #C69C6D; /* Plank color */
border: 4px solid #3E2723;
padding: 8px;
position: relative;
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.27);
cursor: pointer;
box-shadow:
8px 8px 0 rgba(0,0,0,0.5),
inset 4px 4px 0 rgba(255,255,255,0.2);
}
.card:hover {
transform: translateY(-10px) rotateX(5deg);
}
.card:last-child {
grid-column: span 2;
width: 60%;
margin: 0 auto;
}
.card-inner {
border: 2px solid #795548;
background: #EFEBE9;
padding: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
.card-img-wrapper {
width: 100%;
height: 300px;
overflow: hidden;
border: 4px solid #3E2723;
margin-bottom: 20px;
position: relative;
}
.card-img {
width: 100%;
height: 100%;
object-fit: cover;
image-rendering: pixelated;
}
.card-content {
color: var(--text-main);
flex: 1;
}
.card-title {
font-family: 'Press Start 2P', cursive;
font-size: 20px;
margin-bottom: 12px;
color: #3E2723;
}
.card-desc {
font-size: 22px;
line-height: 1.4;
margin-bottom: 16px;
}
.card-features {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
}
.tag {
background: #8D6E63;
color: white;
padding: 4px 8px;
font-size: 18px;
border: 2px solid #5D4037;
}
.card-actions {
margin-top: auto;
text-align: right;
}
.btn-view {
background: #795548;
color: white;
border: none;
padding: 8px 16px;
font-family: 'VT323', monospace;
font-size: 22px;
cursor: pointer;
border: 2px solid #3E2723;
box-shadow: 2px 2px 0 #3E2723;
}
.btn-view:hover {
background: #8D6E63;
}
/* Download Section */
.download-section {
background: rgba(0,0,0,0.8);
border: 4px solid #795548;
padding: 40px;
text-align: center;
margin-bottom: 60px;
position: relative;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
.file-list {
margin: 20px auto;
display: inline-block;
text-align: left;
font-size: 20px;
color: #ccc;
background: rgba(255,255,255,0.05);
padding: 20px;
border: 2px dashed #795548;
}
.file-list div {
margin-bottom: 8px;
}
/* Floating Orbs Animation */
.orb {
position: absolute;
width: 10px;
height: 10px;
background: #99ff33;
border: 2px solid #66cc00;
box-shadow: 0 0 5px #99ff33;
animation: float 4s infinite ease-in-out;
pointer-events: none;
}
@keyframes float {
0% { transform: translateY(0px) rotate(0deg); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateY(-100px) rotate(360deg); opacity: 0; }
}
/* Footer */
.footer {
background: #000;
padding: 20px 0;
text-align: center;
border-top: 4px solid #795548;
font-size: 18px;
color: #888;
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background: #C69C6D;
border: 4px solid white;
padding: 40px;
width: 600px;
text-align: center;
color: #3E2723;
box-shadow: 0 0 0 4px #000;
position: relative;
}
.modal-title {
font-family: 'Press Start 2P', cursive;
margin-bottom: 20px;
font-size: 24px;
}
.modal-close {
position: absolute;
top: 10px;
right: 10px;
background: #d32f2f;
color: white;
border: 2px solid #000;
width: 30px;
height: 30px;
cursor: pointer;
font-family: sans-serif;
font-weight: bold;
line-height: 26px;
}
/* Particle decoration on hero */
.hero-particle {
position: absolute;
width: 40px;
height: 40px;
background: rgba(255,255,255,0.1);
animation: pixelFloat 10s infinite linear;
}
@keyframes pixelFloat {
0% { transform: translateY(100vh); }
100% { transform: translateY(-100px); }
}
</style>
</head>
<body>
<!-- Hero Section -->
<header class="hero">
<!-- Floating particles -->
<div class="hero-particle" style="left: 10%; animation-delay: 0s; width: 20px; height: 20px;"></div>
<div class="hero-particle" style="left: 80%; animation-delay: 2s; width: 30px; height: 30px;"></div>
<div class="hero-particle" style="left: 40%; animation-delay: 5s; width: 15px; height: 15px;"></div>
<div class="hero-content">
<h1 class="hero-title">
<i class="ri-box-3-fill" style="vertical-align: middle; color: #5D4037;"></i>
CSP Learning Platform
</h1>
<div class="hero-subtitle">Minecraft Pixel Art Style Edition</div>
<p class="hero-desc">Complete HTML package with 5 fully functional pages</p>
<div class="features-list">
<div class="feature-item"><i class="ri-check-line"></i> Pure HTML/CSS/JS</div>
<div class="feature-item"><i class="ri-wifi-off-line"></i> Offline Ready</div>
<div class="feature-item"><i class="ri-device-line"></i> Responsive Design</div>
<div class="feature-item"><i class="ri-grid-fill"></i> Pixel Perfect</div>
</div>
<a href="#showcase" class="btn-pixel btn-green" id="startBtn">Start Exploring</a>
</div>
</header>
<!-- Main Content -->
<div class="container" id="showcase">
<div class="showcase">
<div class="grid-container">
<!-- Card 1: Login -->
<div class="card" onclick="window.location.href='#'">
<div class="card-inner">
<div class="card-img-wrapper">
<img src="https://a.lovart.ai/artifacts/agent/4jJnMhBDygFtkHFr.png" alt="Login Page" class="card-img">
</div>
<div class="card-content">
<h3 class="card-title">Login Page</h3>
<p class="card-desc">Minecraft-themed authentication with form validation.</p>
<div class="card-features">
<span class="tag">Tab Switching</span>
<span class="tag">Password Strength</span>
<span class="tag">Mock Login</span>
</div>
<div class="card-actions">
<button class="btn-view">View Page <i class="ri-arrow-right-line"></i></button>
</div>
</div>
</div>
</div>
<!-- Card 2: Problem Library -->
<div class="card" onclick="window.location.href='#'">
<div class="card-inner">
<div class="card-img-wrapper">
<img src="https://a.lovart.ai/artifacts/agent/j6E654EFXn3NxJb4.png" alt="Problem Library" class="card-img">
</div>
<div class="card-content">
<h3 class="card-title">Problem Library</h3>
<p class="card-desc">Browse 1,556 problems with advanced filtering and search.</p>
<div class="card-features">
<span class="tag">Sidebar Menu</span>
<span class="tag">Search & Filter</span>
<span class="tag">Pagination</span>
</div>
<div class="card-actions">
<button class="btn-view">View Page <i class="ri-arrow-right-line"></i></button>
</div>
</div>
</div>
</div>
<!-- Card 3: Code Editor -->
<div class="card" onclick="window.location.href='#'">
<div class="card-inner">
<div class="card-img-wrapper">
<img src="https://a.lovart.ai/artifacts/agent/OWMy8aA6hD9v2JYA.png" alt="Code Editor" class="card-img">
</div>
<div class="card-content">
<h3 class="card-title">Code Editor</h3>
<p class="card-desc">Split-panel editor with syntax highlighting and output.</p>
<div class="card-features">
<span class="tag">Multi-language</span>
<span class="tag">Theme Toggle</span>
<span class="tag">Test Runner</span>
</div>
<div class="card-actions">
<button class="btn-view">View Page <i class="ri-arrow-right-line"></i></button>
</div>
</div>
</div>
</div>
<!-- Card 4: Profile Center -->
<div class="card" onclick="window.location.href='#'">
<div class="card-inner">
<div class="card-img-wrapper">
<img src="https://a.lovart.ai/artifacts/agent/zDDM5HzNib6if6te.png" alt="Profile Center" class="card-img">
</div>
<div class="card-content">
<h3 class="card-title">Profile Center</h3>
<p class="card-desc">User dashboard with achievements, stats, and history.</p>
<div class="card-features">
<span class="tag">XP System</span>
<span class="tag">Badges</span>
<span class="tag">Leaderboard</span>
</div>
<div class="card-actions">
<button class="btn-view">View Page <i class="ri-arrow-right-line"></i></button>
</div>
</div>
</div>
</div>
<!-- Card 5: Contest Page (Wide) -->
<div class="card" onclick="window.location.href='#'">
<div class="card-inner" style="flex-direction: row; gap: 20px;">
<div class="card-img-wrapper" style="width: 50%; height: 250px; margin-bottom: 0;">
<img src="https://a.lovart.ai/artifacts/agent/KTP6hqvX7SCjbsuf.png" alt="Contest Page" class="card-img">
</div>
<div class="card-content" style="width: 50%;">
<h3 class="card-title">Contest Page</h3>
<p class="card-desc">Live contests with real-time leaderboard and arena mode.</p>
<div class="card-features">
<span class="tag">Countdown Timer</span>
<span class="tag">Live Updates</span>
<span class="tag">Rankings</span>
</div>
<div class="card-actions" style="margin-top: 20px;">
<button class="btn-view">View Page <i class="ri-arrow-right-line"></i></button>
</div>
</div>
</div>
</div>
</div>
<!-- Download Section -->
<div class="download-section">
<h2 class="pixel-text" style="color: white; margin-bottom: 20px; font-size: 28px;">Ready to Craft Your Code?</h2>
<div class="file-list">
<div><i class="ri-file-zip-line"></i> csp-platform-v1.0.zip (12.5 MB)</div>
<div><i class="ri-file-list-line"></i> README.txt (2 KB)</div>
<div><i class="ri-folder-line"></i> /assets (images, css, js)</div>
</div>
<div style="margin-top: 20px;">
<button class="btn-pixel btn-gold" id="downloadBtn">
<i class="ri-download-cloud-2-line"></i> Download Package
</button>
</div>
<p style="margin-top: 20px; color: #aaa; font-size: 18px;">Installation: Simply unzip and open index.html in any browser.</p>
</div>
</div>
</div>
<!-- Footer -->
<footer class="footer">
<p>Crafted with <i class="ri-heart-fill" style="color: #d32f2f;"></i> by HTML Generator | Version 1.19.2</p>
<p style="margin-top: 10px; font-size: 14px; opacity: 0.6;">Mock GitHub Link: github.com/user/csp-minecraft</p>
</footer>
<!-- Download Modal -->
<div class="modal" id="downloadModal">
<div class="modal-content">
<button class="modal-close" onclick="closeModal()">X</button>
<h3 class="modal-title">Generating World...</h3>
<p style="font-size: 20px; margin-bottom: 20px;">Your download is being prepared!</p>
<div style="background: #3E2723; padding: 10px; margin-top: 20px; position: relative; height: 30px;">
<div style="background: #76FF03; width: 0%; height: 100%; transition: width 2s;" id="progressBar"></div>
</div>
<p id="progressText" style="margin-top: 10px;">0%</p>
</div>
</div>
<script>
// Smooth scroll
document.getElementById('startBtn').addEventListener('click', function(e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
// Download Modal Logic
const modal = document.getElementById('downloadModal');
const downloadBtn = document.getElementById('downloadBtn');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
downloadBtn.addEventListener('click', () => {
modal.style.display = 'flex';
progressBar.style.width = '0%';
progressText.innerText = '0%';
setTimeout(() => {
progressBar.style.width = '100%';
let progress = 0;
const interval = setInterval(() => {
progress += 5;
progressText.innerText = progress + '%';
if (progress >= 100) {
clearInterval(interval);
setTimeout(() => {
progressText.innerText = 'Download Complete!';
// Normally trigger download here
setTimeout(closeModal, 1500);
}, 500);
}
}, 100);
}, 500);
});
function closeModal() {
modal.style.display = 'none';
}
// XP Orbs Generation
function createOrb() {
const orb = document.createElement('div');
orb.className = 'orb';
// Random position
const x = Math.random() * window.innerWidth;
orb.style.left = x + 'px';
orb.style.bottom = '-20px'; // Start from bottom
// Random size variation
const size = 5 + Math.random() * 10;
orb.style.width = size + 'px';
orb.style.height = size + 'px';
document.body.appendChild(orb);
// Remove after animation
setTimeout(() => {
orb.remove();
}, 4000);
}
setInterval(createOrb, 800);
</script>
</body>
</html>

查看文件

@@ -0,0 +1,619 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minecraft Code Editor</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.6.0/remixicon.min.css" rel="stylesheet">
<style>
@font-face {
font-family: 'DelaGothicOne';
src: url('https://assets-persist.lovart.ai/agent-static-assets/DelaGothicOne-Regular.ttf');
}
@font-face {
font-family: 'MiSans';
src: url('https://assets-persist.lovart.ai/agent-static-assets/MiSans-Regular.ttf');
}
@font-face {
font-family: 'PixelCode';
src: local('Courier New'), monospace; /* Fallback for code */
}
:root {
--mc-wood-light: #A07F53;
--mc-wood-dark: #765637;
--mc-stone: #757575;
--mc-stone-dark: #3b3b3b;
--mc-obsidian: #18181F;
--mc-grass: #7CB342;
--mc-grass-dark: #558B2F;
--mc-gold: #FFB300;
--mc-gold-dark: #FF8F00;
--mc-diamond: #00BCD4;
--mc-redstone: #F44336;
--mc-text-shadow: 2px 2px 0px #000;
--mc-border-light: rgba(255, 255, 255, 0.4);
--mc-border-dark: rgba(0, 0, 0, 0.4);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
width: 1920px;
height: 1080px; /* Fixed height for preview, though body usually auto */
background-color: #121212;
font-family: 'MiSans', sans-serif;
color: white;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Minecraft Texture Classes */
.bg-wood {
background-color: var(--mc-wood-dark);
background-image:
linear-gradient(90deg, rgba(0,0,0,0.1) 50%, transparent 50%),
linear-gradient(rgba(0,0,0,0.1) 50%, transparent 50%);
background-size: 4px 4px;
box-shadow: inset 0 0 40px rgba(0,0,0,0.5);
}
.bg-stone {
background-color: var(--mc-stone);
background-image:
radial-gradient(circle at 2px 2px, rgba(255,255,255,0.1) 1px, transparent 1px),
radial-gradient(circle at 10px 10px, rgba(0,0,0,0.1) 2px, transparent 2px);
background-size: 20px 20px;
}
.bg-obsidian {
background-color: #1a1a24;
background-image:
repeating-linear-gradient(45deg, rgba(255,255,255,0.02) 0px, rgba(255,255,255,0.02) 1px, transparent 1px, transparent 10px),
repeating-linear-gradient(-45deg, rgba(255,255,255,0.02) 0px, rgba(255,255,255,0.02) 1px, transparent 1px, transparent 10px);
}
/* Typography */
h1, h2, h3, .mc-font {
font-family: 'DelaGothicOne', cursive;
letter-spacing: 1px;
text-shadow: 2px 2px 0 rgba(0,0,0,0.5);
}
/* 3D Button Style */
.mc-btn {
border: 4px solid #000;
border-top-color: rgba(255,255,255,0.5);
border-left-color: rgba(255,255,255,0.5);
border-bottom-color: rgba(0,0,0,0.5);
border-right-color: rgba(0,0,0,0.5);
padding: 8px 16px;
font-family: 'DelaGothicOne', cursive;
cursor: pointer;
text-transform: uppercase;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 8px;
transition: transform 0.1s;
image-rendering: pixelated;
}
.mc-btn:active {
border-top-color: rgba(0,0,0,0.5);
border-left-color: rgba(0,0,0,0.5);
border-bottom-color: rgba(255,255,255,0.5);
border-right-color: rgba(255,255,255,0.5);
transform: translateY(2px);
}
.btn-green { background-color: var(--mc-grass); color: white; text-shadow: 1px 1px 0 #000; }
.btn-gold { background-color: var(--mc-gold); color: #3e2723; text-shadow: none; }
.btn-stone { background-color: var(--mc-stone); color: white; }
.btn-icon { padding: 8px; }
/* Scrollbar */
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: #2b2b2b; }
::-webkit-scrollbar-thumb {
background: #555;
border: 2px solid #2b2b2b;
border-top-color: #777;
border-left-color: #777;
}
/* Navigation */
.navbar {
height: 60px;
background-color: #212121;
border-bottom: 4px solid #000;
display: flex;
align-items: center;
padding: 0 24px;
gap: 20px;
position: relative;
z-index: 10;
}
.nav-logo { font-size: 24px; color: var(--mc-grass); }
.nav-item { color: #aaa; text-decoration: none; font-family: 'DelaGothicOne'; font-size: 14px; }
.nav-item:hover { color: white; text-decoration: underline; }
/* Main Layout */
.main-container {
display: flex;
height: calc(100vh - 60px);
position: relative;
}
/* Left Panel */
.left-panel {
width: 40%;
height: 100%;
display: flex;
flex-direction: column;
border-right: 4px solid #000;
position: relative;
}
.problem-content {
flex: 1;
overflow-y: auto;
padding: 32px;
}
.paper-card {
background-color: #FDF5E6; /* Old Lace */
color: #333;
padding: 24px;
border: 4px solid #333;
box-shadow: 8px 8px 0 rgba(0,0,0,0.3);
margin-bottom: 24px;
position: relative;
}
.paper-card::before {
content: '';
position: absolute;
top: -4px; left: -4px; right: -4px; bottom: -4px;
border: 2px solid #5d4037;
pointer-events: none;
}
.difficulty-badge {
background-color: var(--mc-grass);
color: white;
padding: 4px 8px;
font-size: 12px;
border: 2px solid #33691E;
display: inline-block;
margin-left: 10px;
font-family: 'DelaGothicOne';
vertical-align: middle;
}
.code-block-display {
background-color: #263238;
color: #eceff1;
padding: 12px;
border-left: 4px solid var(--mc-grass);
font-family: 'PixelCode', monospace;
margin: 10px 0;
white-space: pre-wrap;
}
.panel-footer {
height: 70px;
background-color: rgba(0,0,0,0.3);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
border-top: 4px solid #3e2723;
}
/* Right Panel */
.right-panel {
width: 60%;
display: flex;
flex-direction: column;
background-color: #1e1e1e;
}
.toolbar {
height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
border-bottom: 4px solid #000;
}
.toolbar-group {
display: flex;
gap: 12px;
align-items: center;
}
.code-editor-wrapper {
flex: 1;
display: flex;
overflow: hidden;
position: relative;
}
.line-numbers {
width: 48px;
background-color: #121217;
color: #546e7a;
text-align: right;
padding: 16px 8px;
font-family: 'PixelCode', monospace;
font-size: 16px;
line-height: 1.5;
user-select: none;
border-right: 2px solid #333;
}
.code-area {
flex: 1;
background-color: transparent;
color: #d4d4d4;
padding: 16px;
font-family: 'PixelCode', monospace;
font-size: 16px;
line-height: 1.5;
overflow: auto;
white-space: pre;
outline: none;
}
/* Syntax Highlighting Colors */
.token-keyword { color: #cc7832; font-weight: bold; }
.token-type { color: #e0c46c; }
.token-string { color: #6a8759; }
.token-comment { color: #808080; font-style: italic; }
.token-number { color: #6897bb; }
.status-bar {
height: 32px;
background-color: #2d2d2d;
border-top: 2px solid #444;
display: flex;
align-items: center;
padding: 0 16px;
font-size: 12px;
gap: 20px;
color: #aaa;
font-family: 'PixelCode', monospace;
}
.combo-counter {
color: var(--mc-gold);
font-weight: bold;
display: flex;
align-items: center;
gap: 4px;
}
/* Test Panel */
.test-panel {
height: 220px;
border-top: 4px solid #000;
display: flex;
flex-direction: column;
transition: height 0.3s;
}
.test-panel.collapsed { height: 40px; }
.test-header {
height: 40px;
display: flex;
align-items: center;
padding: 0 16px;
background-color: rgba(0,0,0,0.2);
border-bottom: 2px solid #444;
cursor: pointer;
}
.tab-btn {
padding: 6px 16px;
background: none;
border: none;
color: #aaa;
cursor: pointer;
font-family: 'DelaGothicOne';
font-size: 12px;
}
.tab-btn.active { color: white; border-bottom: 2px solid var(--mc-grass); }
.test-content {
flex: 1;
padding: 16px;
display: flex;
gap: 16px;
overflow: hidden;
}
.test-case-item {
flex: 1;
background-color: rgba(0,0,0,0.3);
border: 2px solid #444;
padding: 12px;
font-family: 'PixelCode', monospace;
font-size: 14px;
}
.test-case-status { display: flex; justify-content: space-between; margin-bottom: 8px; font-weight: bold; }
.status-pass { color: var(--mc-grass); }
.status-fail { color: var(--mc-redstone); }
/* XP Overlay */
.xp-overlay {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
font-size: 64px;
color: #B9F6CA;
text-shadow: 4px 4px 0 #1B5E20;
font-family: 'DelaGothicOne';
opacity: 0;
pointer-events: none;
z-index: 100;
}
@keyframes xpFloat {
0% { opacity: 0; transform: translate(-50%, -20%); }
20% { opacity: 1; transform: translate(-50%, -50%); }
80% { opacity: 1; transform: translate(-50%, -60%); }
100% { opacity: 0; transform: translate(-50%, -100%); }
}
.animate-xp { animation: xpFloat 2s ease-out forwards; }
/* Confetti */
.confetti {
position: absolute;
width: 8px; height: 8px;
background: var(--mc-diamond);
animation: fall 3s linear forwards;
z-index: 99;
}
@keyframes fall {
to { transform: translateY(100vh) rotate(720deg); }
}
/* Select styling */
.mc-select {
background: var(--mc-stone-dark);
border: 2px solid #000;
color: white;
padding: 4px 8px;
font-family: 'PixelCode', monospace;
cursor: pointer;
outline: none;
}
</style>
</head>
<body>
<!-- Top Navigation -->
<nav class="navbar bg-stone">
<div class="nav-logo mc-font"><i class="ri-box-3-fill"></i> MINECODE</div>
<a href="#" class="nav-item">Problems</a>
<a href="#" class="nav-item">Contest</a>
<a href="#" class="nav-item">Discuss</a>
<div style="flex:1"></div>
<div class="nav-item" style="color:var(--mc-gold)">LVL 24</div>
<img src="https://api.dicebear.com/7.x/pixel-art/svg?seed=Steve" alt="User" style="width: 32px; height: 32px; border: 2px solid white;">
</nav>
<div class="main-container">
<!-- Left Panel: Problem -->
<aside class="left-panel bg-wood">
<div class="problem-content">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 20px;">
<h1 style="font-size: 28px; color: #3E2723;">1. Two Sum</h1>
<span class="difficulty-badge">EASY</span>
</div>
<div class="paper-card">
<p style="margin-bottom: 16px; line-height: 1.6;">
Given an array of integers <code>nums</code> and an integer <code>target</code>, return <i>indices</i> of the two numbers such that they add up to <code>target</code>.
</p>
<p style="line-height: 1.6;">
You may assume that each input would have <strong>exactly one solution</strong>, and you may not use the same element twice.
</p>
</div>
<div style="margin-bottom: 24px;">
<h3 style="color:#3E2723; margin-bottom:10px;">Example 1:</h3>
<div class="code-block-display">Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Explanation: Because nums[0] + nums[1] == 9, we return [0, 1].</div>
</div>
<div style="margin-bottom: 24px;">
<h3 style="color:#3E2723; margin-bottom:10px;">Example 2:</h3>
<div class="code-block-display">Input: nums = [3,2,4], target = 6
Output: [1,2]</div>
</div>
<div style="margin-bottom: 24px;">
<h3 style="color:#3E2723; margin-bottom:10px; display:flex; align-items:center;">
<i class="ri-book-mark-fill" style="margin-right:8px;"></i> Hints
</h3>
<div style="background: rgba(0,0,0,0.1); padding: 10px; border: 2px dashed #5D4037; color: #3E2723;">
Try using a Hash Map to store visited numbers.
</div>
</div>
</div>
<div class="panel-footer">
<button class="mc-btn btn-stone"><i class="ri-star-fill"></i> Bookmark</button>
<div style="display: flex; gap: 10px;">
<button class="mc-btn btn-stone btn-icon"><i class="ri-share-forward-fill"></i></button>
<button class="mc-btn btn-stone btn-icon"><i class="ri-file-list-3-fill"></i></button>
</div>
</div>
</aside>
<!-- Right Panel: Editor -->
<main class="right-panel">
<!-- Toolbar -->
<div class="toolbar bg-stone">
<div class="toolbar-group">
<select class="mc-select">
<option>C++</option>
<option>Java</option>
<option>Python</option>
</select>
<button class="mc-btn btn-stone btn-icon" style="padding:4px 8px;"><i class="ri-settings-3-fill"></i></button>
<div style="color:#bbb; font-size:12px;">Size: 14px</div>
</div>
<div class="toolbar-group">
<button class="mc-btn btn-green" onclick="runCode()"><i class="ri-play-fill"></i> Run</button>
<button class="mc-btn btn-gold" onclick="submitCode()"><i class="ri-upload-cloud-2-fill"></i> Submit</button>
</div>
</div>
<!-- Code Editor -->
<div class="code-editor-wrapper bg-obsidian">
<div class="line-numbers">
1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11
</div>
<div class="code-area" contenteditable="true" spellcheck="false">
<span class="token-keyword">class</span> Solution {
<span class="token-keyword">public</span>:
<span class="token-type">vector</span>&lt;<span class="token-type">int</span>&gt; twoSum(<span class="token-type">vector</span>&lt;<span class="token-type">int</span>&gt;& nums, <span class="token-type">int</span> target) {
<span class="token-type">unordered_map</span>&lt;<span class="token-type">int</span>, <span class="token-type">int</span>&gt; hash;
<span class="token-keyword">for</span> (<span class="token-type">int</span> i = <span class="token-number">0</span>; i < nums.size(); i++) {
<span class="token-type">int</span> complement = target - nums[i];
<span class="token-keyword">if</span> (hash.find(complement) != hash.end()) {
<span class="token-keyword">return</span> {hash[complement], i};
}
hash[nums[i]] = i;
}
<span class="token-keyword">return</span> {};
}
};</div>
</div>
<!-- Status Bar -->
<div class="status-bar">
<span>Ready</span>
<span style="border-left: 1px solid #555; height: 16px;"></span>
<span><i class="ri-time-fill"></i> 0ms</span>
<span><i class="ri-sd-card-mini-fill"></i> 0KB</span>
<div style="flex:1"></div>
<div class="combo-counter"><i class="ri-fire-fill"></i> COMBO x5</div>
</div>
<!-- Test Panel -->
<div class="test-panel bg-stone" id="testPanel">
<div class="test-header" onclick="toggleTestPanel()">
<i class="ri-terminal-box-fill" style="margin-right: 10px; color: #ccc;"></i>
<span class="mc-font" style="font-size: 14px; color: #eee; flex:1">Test Console</span>
<i class="ri-arrow-up-s-line" id="toggleIcon"></i>
</div>
<div style="background: rgba(0,0,0,0.4); padding: 0 16px; display: flex; border-bottom: 2px solid #444;">
<button class="tab-btn active">Test Cases</button>
<button class="tab-btn">Result</button>
</div>
<div class="test-content">
<div class="test-case-item">
<div class="test-case-status">Case 1 <i class="ri-checkbox-circle-fill status-pass"></i></div>
<div style="color: #888; font-size: 12px; margin-bottom: 4px;">Input</div>
<div style="color: #fff; margin-bottom: 8px;">nums = [2,7,11,15], target = 9</div>
<div style="color: #888; font-size: 12px; margin-bottom: 4px;">Expected</div>
<div style="color: #fff;">[0,1]</div>
</div>
<div class="test-case-item">
<div class="test-case-status">Case 2 <i class="ri-checkbox-circle-fill status-pass"></i></div>
<div style="color: #888; font-size: 12px; margin-bottom: 4px;">Input</div>
<div style="color: #fff; margin-bottom: 8px;">nums = [3,2,4], target = 6</div>
<div style="color: #888; font-size: 12px; margin-bottom: 4px;">Expected</div>
<div style="color: #fff;">[1,2]</div>
</div>
<div class="test-case-item" style="border-color: #F44336; opacity: 0.8;">
<div class="test-case-status">Case 3 <i class="ri-close-circle-fill status-fail"></i></div>
<div style="color: #888; font-size: 12px; margin-bottom: 4px;">Input</div>
<div style="color: #fff; margin-bottom: 8px;">nums = [3,3], target = 6</div>
<div style="color: #888; font-size: 12px; margin-bottom: 4px;">Expected</div>
<div style="color: #fff;">[0,1]</div>
</div>
</div>
</div>
</main>
</div>
<!-- XP Animation Overlay -->
<div id="xpDisplay" class="xp-overlay">+50 XP</div>
<script>
function toggleTestPanel() {
const panel = document.getElementById('testPanel');
const icon = document.getElementById('toggleIcon');
panel.classList.toggle('collapsed');
if(panel.classList.contains('collapsed')) {
icon.classList.remove('ri-arrow-down-s-line');
icon.classList.add('ri-arrow-up-s-line');
} else {
icon.classList.remove('ri-arrow-up-s-line');
icon.classList.add('ri-arrow-down-s-line');
}
}
function runCode() {
// Simulate running code
const btn = document.querySelector('.btn-green');
btn.innerHTML = '<i class="ri-loader-4-line ri-spin"></i> Running...';
setTimeout(() => {
btn.innerHTML = '<i class="ri-play-fill"></i> Run';
document.getElementById('testPanel').classList.remove('collapsed');
}, 1000);
}
function submitCode() {
const btn = document.querySelector('.btn-gold');
const originalContent = btn.innerHTML;
btn.innerHTML = '<i class="ri-loader-4-line ri-spin"></i> ...';
setTimeout(() => {
btn.innerHTML = originalContent;
showXP();
createConfetti();
}, 1500);
}
function showXP() {
const xp = document.getElementById('xpDisplay');
xp.classList.remove('animate-xp');
void xp.offsetWidth; // trigger reflow
xp.classList.add('animate-xp');
}
function createConfetti() {
const colors = ['#F44336', '#2196F3', '#FFEB3B', '#4CAF50', '#FF9800'];
for(let i=0; i<50; i++) {
const conf = document.createElement('div');
conf.className = 'confetti';
conf.style.left = Math.random() * 100 + 'vw';
conf.style.top = '-10px';
conf.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
conf.style.animationDuration = (Math.random() * 2 + 2) + 's';
document.body.appendChild(conf);
setTimeout(() => conf.remove(), 4000);
}
}
</script>
</body>
</html>

查看文件

@@ -0,0 +1,905 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1920, initial-scale=1.0">
<title>Minecraft Contest Page</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.6.0/remixicon.min.css" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
:root {
--mc-bg-dark: #1D1D1D;
--mc-obsidian: #141019;
--mc-stone: #757575;
--mc-wood: #8B6914; /* Oak wood plank approximation */
--mc-wood-dark: #5C4033;
--mc-grass: #7CB342;
--mc-grass-dark: #558B2F;
--mc-gold: #FFB300;
--mc-redstone: #E53935;
--mc-diamond: #40C4FF;
--mc-white: #FFFFFF;
--mc-text-shadow: 2px 2px 0px #000;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
font-family: 'Press Start 2P', cursive;
background-color: #121212;
background-image:
linear-gradient(45deg, #1a1a1a 25%, transparent 25%, transparent 75%, #1a1a1a 75%, #1a1a1a),
linear-gradient(45deg, #1a1a1a 25%, transparent 25%, transparent 75%, #1a1a1a 75%, #1a1a1a);
background-size: 40px 40px;
background-position: 0 0, 20px 20px;
color: var(--mc-white);
width: 1920px;
margin: 0 auto;
overflow-x: hidden;
font-size: 14px;
line-height: 1.5;
}
/* --- Utilities --- */
.pixel-border {
box-shadow:
-4px 0 0 0 black,
4px 0 0 0 black,
0 -4px 0 0 black,
0 4px 0 0 black,
inset -4px -4px 0 0 rgba(0,0,0,0.5),
inset 4px 4px 0 0 rgba(255,255,255,0.2);
border: 4px solid transparent;
}
.text-shadow {
text-shadow: var(--mc-text-shadow);
}
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
/* --- Navigation --- */
.navbar {
height: 80px;
background-color: #333;
border-bottom: 4px solid #000;
display: flex;
align-items: center;
padding: 0 40px;
justify-content: space-between;
}
.logo {
font-size: 24px;
color: var(--mc-grass);
text-transform: uppercase;
}
.nav-links a {
color: #ccc;
text-decoration: none;
margin-left: 30px;
padding: 10px;
transition: color 0.2s;
}
.nav-links a.active {
color: var(--mc-gold);
text-shadow: 2px 2px 0 #5C4033;
}
/* --- Banner --- */
.banner {
height: 350px;
width: 100%;
position: relative;
background: linear-gradient(180deg, #2a0e36 0%, #46142e 100%); /* Nether-ish */
background-image: url('https://images.unsplash.com/photo-1628151015968-3a4429e9ef04?q=80&w=2072&auto=format&fit=crop'); /* Pixel art background placeholder */
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-bottom: 6px solid #000;
overflow: hidden;
}
.banner-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.6);
z-index: 1;
}
.banner-content {
position: relative;
z-index: 2;
text-align: center;
width: 100%;
max-width: 1200px;
}
.live-badge {
background-color: var(--mc-redstone);
color: white;
padding: 8px 16px;
display: inline-block;
font-size: 16px;
margin-bottom: 20px;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(229, 57, 53, 0.7); }
70% { transform: scale(1.05); box-shadow: 0 0 0 10px rgba(229, 57, 53, 0); }
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(229, 57, 53, 0); }
}
.contest-title {
font-size: 42px;
color: var(--mc-gold);
margin-bottom: 20px;
-webkit-text-stroke: 2px #5C4033;
text-shadow: 4px 4px 0 #000;
}
.countdown {
font-size: 36px;
color: #fff;
margin-bottom: 30px;
letter-spacing: 4px;
text-shadow: 3px 3px 0 #000;
}
.join-btn {
background-color: var(--mc-grass);
color: white;
border: none;
padding: 20px 40px;
font-size: 20px;
cursor: pointer;
font-family: 'Press Start 2P', cursive;
transition: transform 0.1s, background-color 0.2s;
position: relative;
box-shadow: 0 6px 0 #558B2F, 0 10px 10px rgba(0,0,0,0.3);
text-transform: uppercase;
}
.join-btn:hover {
background-color: #8BC34A;
transform: translateY(-2px);
box-shadow: 0 8px 0 #558B2F, 0 12px 12px rgba(0,0,0,0.3);
}
.join-btn:active {
transform: translateY(4px);
box-shadow: 0 2px 0 #558B2F, 0 4px 4px rgba(0,0,0,0.3);
}
.participants-count {
margin-top: 20px;
font-size: 14px;
color: #ccc;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.player-head {
width: 24px;
height: 24px;
background: #555;
display: inline-block;
image-rendering: pixelated;
}
/* --- Main Layout --- */
.container {
display: flex;
width: 100%;
padding: 40px;
gap: 40px;
max-width: 1800px;
margin: 0 auto;
}
.left-col {
flex: 0 0 70%;
}
.right-col {
flex: 0 0 30%;
}
/* --- Tab Nav --- */
.tabs {
display: flex;
margin-bottom: 20px;
gap: 10px;
}
.tab {
background-color: #555;
padding: 15px 30px;
color: #aaa;
cursor: pointer;
border-bottom: none;
position: relative;
top: 4px;
transition: all 0.2s;
}
.tab.active {
background-color: var(--mc-wood);
color: #fff;
top: 0;
padding-bottom: 19px;
text-shadow: 2px 2px 0 #000;
box-shadow:
-4px 0 0 0 black,
4px 0 0 0 black,
0 -4px 0 0 black;
}
/* --- Wood Panel Style --- */
.wood-panel {
background-color: var(--mc-wood);
border: 4px solid #000;
padding: 20px;
margin-bottom: 30px;
box-shadow: inset 0 0 0 4px rgba(255,255,255,0.1), 8px 8px 0 rgba(0,0,0,0.5);
position: relative;
}
.wood-texture {
/* Simulating wood grain with linear gradients */
background-image:
linear-gradient(90deg, rgba(0,0,0,0.05) 1px, transparent 1px),
linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px);
background-size: 20px 20px;
}
/* --- Contest Cards --- */
.contest-card {
background-color: #6D4C41; /* Darker wood */
margin-bottom: 20px;
padding: 20px;
display: flex;
align-items: center;
justify-content: space-between;
transition: transform 0.2s;
position: relative;
border: 4px solid #3E2723;
}
.contest-card:hover {
transform: translateX(10px);
background-color: #795548;
}
.card-icon {
width: 60px;
height: 60px;
background-color: #3E2723;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
color: var(--mc-gold);
border: 4px solid #000;
margin-right: 20px;
}
.card-info h3 {
color: #fff;
margin-bottom: 10px;
font-size: 18px;
text-shadow: 2px 2px 0 #000;
}
.card-details {
font-size: 12px;
color: #D7CCC8;
display: flex;
gap: 15px;
}
.stars { color: var(--mc-gold); }
.card-action .btn-small {
background-color: var(--mc-stone);
border: 2px solid #000;
padding: 10px 20px;
color: white;
cursor: pointer;
font-family: inherit;
font-size: 12px;
box-shadow: 0 4px 0 #424242;
}
.card-action .btn-small:hover {
background-color: #9E9E9E;
margin-top: -2px;
box-shadow: 0 6px 0 #424242;
}
/* --- Leaderboard --- */
.leaderboard-title {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.live-dot {
width: 12px;
height: 12px;
background-color: var(--mc-grass);
border-radius: 50%; /* Minecraft has no circles, but for indicator */
display: inline-block;
margin-right: 8px;
box-shadow: 0 0 10px var(--mc-grass);
animation: blink 1s infinite;
}
@keyframes blink { 50% { opacity: 0.5; } }
.leaderboard-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
}
.leaderboard-table th {
text-align: left;
padding: 15px;
color: #3E2723;
font-size: 14px;
border-bottom: 4px solid #3E2723;
}
.leaderboard-table td {
background-color: #5D4037;
padding: 15px;
color: #fff;
border-top: 4px solid #3E2723;
border-bottom: 4px solid #3E2723;
}
.leaderboard-table tr td:first-child { border-left: 4px solid #3E2723; }
.leaderboard-table tr td:last-child { border-right: 4px solid #3E2723; }
.rank-1 td { background-color: #FFECB3; color: #5D4037; border-color: #FFB300 !important; }
.rank-2 td { background-color: #F5F5F5; color: #5D4037; border-color: #BDBDBD !important; }
.rank-3 td { background-color: #D7CCC8; color: #5D4037; border-color: #8D6E63 !important; }
.current-user td {
background-color: #DCEDC8;
color: #33691E;
border-color: #7CB342 !important;
}
.medal-icon { margin-right: 5px; }
/* --- Right Column --- */
.info-card {
background-color: #424242;
padding: 20px;
margin-bottom: 20px;
border: 4px solid #000;
box-shadow: 8px 8px 0 rgba(0,0,0,0.5);
}
.info-header {
font-size: 18px;
margin-bottom: 20px;
color: var(--mc-gold);
text-shadow: 2px 2px 0 #000;
border-bottom: 4px solid #000;
padding-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.stat-row {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
font-size: 13px;
}
.stat-val { color: var(--mc-diamond); }
.medals-display {
display: flex;
gap: 15px;
margin-top: 15px;
justify-content: center;
background: #212121;
padding: 10px;
border: 2px solid #000;
}
.timeline-item {
display: flex;
margin-bottom: 15px;
position: relative;
}
.timeline-line {
position: absolute;
left: 7px;
top: 20px;
bottom: -20px;
width: 2px;
background: #666;
z-index: 0;
}
.timeline-item:last-child .timeline-line { display: none; }
.timeline-dot {
width: 16px;
height: 16px;
background: var(--mc-gold);
border: 2px solid #000;
z-index: 1;
margin-right: 15px;
margin-top: 4px;
}
.timeline-content {
font-size: 12px;
}
.timeline-date { color: #888; font-size: 10px; margin-bottom: 4px; }
.timeline-rank { color: #fff; }
.collapsible-content {
max-height: 500px; /* arbitrary large */
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.collapsed .collapsible-content {
max-height: 0;
}
.toggle-btn {
cursor: pointer;
font-size: 20px;
}
/* --- Animations --- */
@keyframes shine {
0% { background-position: -100px; }
40%, 100% { background-position: 140px; }
}
.rank-1 {
position: relative;
overflow: hidden;
}
/* Light ray effect using psuedo elements not easy on tr, applied to td */
.rank-1 td {
background: linear-gradient(120deg, #FFECB3 0%, #FFECB3 40%, #FFF9C4 50%, #FFECB3 60%, #FFECB3 100%);
background-size: 200% 100%;
animation: shine-gold 3s infinite linear;
}
@keyframes shine-gold {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Responsive */
@media (max-width: 768px) {
.container { flex-direction: column; }
.left-col, .right-col { flex: 1 1 100%; }
}
</style>
</head>
<body>
<nav class="navbar pixel-border">
<div class="logo text-shadow"><i class="ri-sword-fill"></i> ALGO CRAFT</div>
<div class="nav-links">
<a href="#" class="active">CONTESTS</a>
<a href="#">PROBLEMS</a>
<a href="#">DISCUSS</a>
<a href="#">STORE</a>
</div>
<div style="display:flex; align-items:center; gap:10px;">
<div style="width:32px; height:32px; background:#ddd; border:2px solid #000;">
<img src="https://api.dicebear.com/7.x/pixel-art/svg?seed=Felix" alt="User" style="width:100%; height:100%;">
</div>
<span>STEVE_DEV</span>
</div>
</nav>
<div class="banner">
<div class="banner-overlay"></div>
<div class="banner-content">
<div class="live-badge pixel-border">LIVE NOW</div>
<h1 class="contest-title">WEEKLY ALGO CHALLENGE #42</h1>
<div class="countdown" id="countdown">02:45:30</div>
<button class="join-btn pixel-border">
JOIN CONTEST
</button>
<div class="participants-count">
<div class="player-head"></div>
<div class="player-head"></div>
<div class="player-head"></div>
<span>1,234 Crafters Online</span>
</div>
</div>
</div>
<div class="container">
<!-- Left Column -->
<div class="left-col">
<div class="tabs">
<div class="tab active pixel-border" onclick="switchTab('ongoing')">ONGOING</div>
<div class="tab pixel-border" onclick="switchTab('upcoming')">UPCOMING</div>
<div class="tab pixel-border" onclick="switchTab('finished')">FINISHED</div>
</div>
<div class="wood-panel pixel-border wood-texture" id="contest-list">
<!-- Contest Cards -->
<div class="contest-card pixel-border">
<div class="flex-center">
<div class="card-icon pixel-border"><i class="ri-trophy-fill"></i></div>
<div class="card-info">
<h3>Weekly Challenge #42</h3>
<div class="card-details">
<span><i class="ri-time-line"></i> Ends in 2h</span>
<span><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i></span>
<span style="color:#4DB6AC">500 XP</span>
</div>
</div>
</div>
<div class="card-action">
<button class="btn-small">ENTER</button>
</div>
</div>
<div class="contest-card pixel-border">
<div class="flex-center">
<div class="card-icon pixel-border"><i class="ri-sword-fill" style="color:#CFD8DC"></i></div>
<div class="card-info">
<h3>Bi-Weekly Rumble #15</h3>
<div class="card-details">
<span><i class="ri-calendar-line"></i> Sat, 14:00</span>
<span><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i><i class="ri-star-line"></i></span>
<span style="color:#4DB6AC">350 XP</span>
</div>
</div>
</div>
<div class="card-action">
<button class="btn-small">REGISTER</button>
</div>
</div>
<div class="contest-card pixel-border">
<div class="flex-center">
<div class="card-icon pixel-border"><i class="ri-vip-diamond-fill" style="color:var(--mc-diamond)"></i></div>
<div class="card-info">
<h3>Diamond League Qualifiers</h3>
<div class="card-details">
<span><i class="ri-calendar-line"></i> Sun, 10:00</span>
<span><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i></span>
<span style="color:#4DB6AC">1000 XP + BADGE</span>
</div>
</div>
</div>
<div class="card-action">
<button class="btn-small">REGISTER</button>
</div>
</div>
</div>
<!-- Leaderboard -->
<div class="wood-panel pixel-border wood-texture">
<div class="leaderboard-title">
<h2 class="text-shadow" style="color:#3E2723">LEADERBOARD</h2>
<div style="font-size: 12px; color: #3E2723; display: flex; align-items: center;">
<span class="live-dot"></span> UPDATING LIVE
</div>
</div>
<table class="leaderboard-table">
<thead>
<tr>
<th>#</th>
<th>PLAYER</th>
<th>SCORE</th>
<th>TIME</th>
<th>STATUS</th>
</tr>
</thead>
<tbody id="leaderboard-body">
<!-- JS will populate -->
<tr class="rank-1 pixel-border">
<td><i class="ri-vip-crown-fill" style="color:#F57F17"></i> 1</td>
<td>Notch_Real</td>
<td>400</td>
<td>00:45:12</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr class="rank-2">
<td><i class="ri-medal-fill" style="color:#757575"></i> 2</td>
<td>Alex_Pro</td>
<td>380</td>
<td>00:52:30</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr class="rank-3">
<td><i class="ri-medal-fill" style="color:#8D6E63"></i> 3</td>
<td>CreeperAwMan</td>
<td>350</td>
<td>01:05:00</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr>
<td>4</td>
<td>Enderman_tp</td>
<td>320</td>
<td>01:10:22</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr>
<td>5</td>
<td>RedstoneEng</td>
<td>300</td>
<td>01:15:45</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr>
<td>6</td>
<td>Miner64</td>
<td>280</td>
<td>01:20:10</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr class="current-user">
<td>7</td>
<td>STEVE_DEV (YOU)</td>
<td>250</td>
<td>01:30:00</td>
<td><span style="color:orange">WA (1)</span></td>
</tr>
<tr>
<td>8</td>
<td>ZombieBoi</td>
<td>200</td>
<td>01:35:12</td>
<td><span style="color:green">AC</span></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Right Column -->
<div class="right-col">
<!-- Stats Card -->
<div class="info-card pixel-border">
<div class="info-header">
<span>MY STATS</span>
<i class="ri-bar-chart-fill"></i>
</div>
<div class="stat-row">
<span>Participated:</span>
<span class="stat-val">24</span>
</div>
<div class="stat-row">
<span>Best Rank:</span>
<span class="stat-val">#3 🥉</span>
</div>
<div class="stat-row">
<span>Total Points:</span>
<span class="stat-val">1,250</span>
</div>
<div class="stat-row">
<span>Rating:</span>
<span class="stat-val" style="color:var(--mc-gold)">1650 (Diamond II)</span>
</div>
<div class="medals-display pixel-border">
<div style="text-align:center">
<i class="ri-medal-fill" style="color:var(--mc-gold); font-size:20px"></i>
<div style="font-size:10px">2</div>
</div>
<div style="text-align:center">
<i class="ri-medal-fill" style="color:#BDBDBD; font-size:20px"></i>
<div style="font-size:10px">5</div>
</div>
<div style="text-align:center">
<i class="ri-medal-fill" style="color:#8D6E63; font-size:20px"></i>
<div style="font-size:10px">8</div>
</div>
</div>
</div>
<!-- Rules Card -->
<div class="info-card pixel-border" id="rules-card">
<div class="info-header">
<span>RULES</span>
<i class="ri-book-open-fill toggle-btn" onclick="toggleRules()"></i>
</div>
<div class="collapsible-content" id="rules-content">
<ul style="padding-left: 20px; font-size: 12px; line-height: 1.8; color:#ccc;">
<li>Duration: 3 Hours</li>
<li>Penalty: +5 mins per WA</li>
<li>Languages: Java, C++, Python</li>
<li>Plagiarism check is ACTIVE</li>
<li>Do not break obsidian blocks</li>
</ul>
</div>
</div>
<!-- Past Results -->
<div class="info-card pixel-border">
<div class="info-header">
<span>HISTORY</span>
<i class="ri-history-line"></i>
</div>
<div class="timeline">
<div class="timeline-item">
<div class="timeline-line"></div>
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="timeline-date">2023-10-15</div>
<div class="timeline-rank">Weekly #41 - Rank #15</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-line"></div>
<div class="timeline-dot" style="background:#BDBDBD"></div>
<div class="timeline-content">
<div class="timeline-date">2023-10-08</div>
<div class="timeline-rank">Weekly #40 - Rank #2 🥈</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-line"></div>
<div class="timeline-dot" style="background:#8D6E63"></div>
<div class="timeline-content">
<div class="timeline-date">2023-10-01</div>
<div class="timeline-rank">Weekly #39 - Rank #3 🥉</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-line"></div>
<div class="timeline-dot" style="background:#555"></div>
<div class="timeline-content">
<div class="timeline-date">2023-09-24</div>
<div class="timeline-rank">Weekly #38 - Rank #45</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// --- Countdown Timer ---
let totalSeconds = 2 * 3600 + 45 * 60 + 30; // 2h 45m 30s
const countdownEl = document.getElementById('countdown');
function updateCountdown() {
const h = Math.floor(totalSeconds / 3600);
const m = Math.floor((totalSeconds % 3600) / 60);
const s = totalSeconds % 60;
countdownEl.textContent =
`${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
if (totalSeconds > 0) {
totalSeconds--;
}
}
setInterval(updateCountdown, 1000);
updateCountdown();
// --- Tabs Functionality ---
function switchTab(tabName) {
const tabs = document.querySelectorAll('.tab');
tabs.forEach(t => t.classList.remove('active'));
// Find the clicked tab (simple logic for this demo)
event.target.classList.add('active');
// In a real app, this would filter the card list
const list = document.getElementById('contest-list');
list.style.opacity = '0.5';
setTimeout(() => {
list.style.opacity = '1';
// Mock content change
if(tabName === 'finished') {
// Just a visual cue that something changed
list.innerHTML = `<div style="text-align:center; padding:40px; color:#aaa">Loading Archive...</div>`;
setTimeout(() => {
list.innerHTML = `
<div class="contest-card pixel-border" style="opacity:0.7">
<div class="flex-center">
<div class="card-icon pixel-border" style="background:#555; color:#aaa"><i class="ri-trophy-line"></i></div>
<div class="card-info">
<h3 style="color:#aaa">Weekly Challenge #41</h3>
<div class="card-details">
<span>Ended: 2 days ago</span>
<span>Winner: Herobrine</span>
</div>
</div>
</div>
<div class="card-action">
<button class="btn-small">VIEW</button>
</div>
</div>
`;
}, 500);
} else if(tabName === 'ongoing') {
// Reset to initial HTML (simplified)
location.reload();
}
}, 200);
}
// --- Toggle Rules ---
function toggleRules() {
const card = document.getElementById('rules-card');
card.classList.toggle('collapsed');
}
// --- Real-time Leaderboard Simulation ---
function simulateLeaderboard() {
const rows = document.querySelectorAll('#leaderboard-body tr:not(.current-user)');
// Randomly swap two rows to simulate rank changes
const idx1 = Math.floor(Math.random() * 3); // Only top 3 change mostly
const idx2 = Math.floor(Math.random() * 3);
if (idx1 !== idx2) {
// Flash effect
rows[idx1].style.backgroundColor = '#fff';
setTimeout(() => {
rows[idx1].style.backgroundColor = ''; // revert to CSS class style
}, 200);
}
}
setInterval(simulateLeaderboard, 10000);
// --- LocalStorage Logic ---
// Save visit timestamp
localStorage.setItem('last_visit_mc_contest', new Date().toISOString());
// --- Firework/Hover Effects for Top 3 ---
const topRanks = document.querySelectorAll('.rank-1, .rank-2, .rank-3');
topRanks.forEach(row => {
row.addEventListener('mouseenter', () => {
// Add a subtle scale effect
row.style.transform = "scale(1.02)";
row.style.transition = "transform 0.2s";
row.style.zIndex = "10";
row.style.boxShadow = "0 0 15px rgba(255, 215, 0, 0.5)";
});
row.addEventListener('mouseleave', () => {
row.style.transform = "scale(1)";
row.style.boxShadow = "none";
row.style.zIndex = "1";
});
});
</script>
</body>
</html>

查看文件

@@ -0,0 +1,605 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSP Learning Platform - Minecraft Login</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.6.0/remixicon.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<style>
:root {
--mc-dirt-side: #79553a;
--mc-dirt-top: #5c3f2b;
--mc-grass-top: #70b348;
--mc-grass-side: #5a9139;
--mc-stone: #7d7d7d;
--mc-wood: #a0744b;
--mc-wood-dark: #6e4e34;
--mc-wood-light: #bca07e;
--mc-diamond: #29B6F6;
--mc-redstone: #E53935;
--mc-gold: #FFD700;
--mc-sky: #cceeff;
--bg-sky-top: #87CEEB;
--bg-sky-bottom: #E0F7FA;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Press Start 2P', cursive;
}
body {
width: 1920px;
min-height: 1080px;
background: linear-gradient(to bottom, var(--bg-sky-top), var(--bg-sky-bottom));
overflow-x: hidden;
position: relative;
color: white;
display: flex;
flex-direction: column;
align-items: center;
}
/* Pixel Cloud Background Decoration */
.clouds {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
background-image:
radial-gradient(circle at 15% 20%, white 20px, transparent 21px),
radial-gradient(circle at 85% 30%, white 30px, transparent 31px),
radial-gradient(circle at 50% 10%, rgba(255,255,255,0.8) 40px, transparent 41px);
background-size: 800px 400px;
opacity: 0.6;
}
/* Top Nav */
.navbar {
width: 100%;
height: 80px;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
padding: 0 40px;
border-bottom: 4px solid rgba(0, 0, 0, 0.2);
backdrop-filter: blur(4px);
position: fixed;
top: 0;
z-index: 100;
}
.logo {
font-size: 20px;
color: #fff;
text-shadow: 4px 4px 0px #000;
display: flex;
align-items: center;
gap: 16px;
}
.logo i {
color: var(--mc-grass-top);
font-size: 28px;
}
/* Main Container */
.main-container {
margin-top: 140px;
position: relative;
z-index: 10;
}
/* Wooden Card */
.login-card {
width: 480px;
background-color: var(--mc-wood);
border: 4px solid #000;
box-shadow:
8px 8px 0px rgba(0,0,0,0.5),
inset 4px 4px 0px var(--mc-wood-light),
inset -4px -4px 0px var(--mc-wood-dark);
padding: 32px;
position: relative;
}
/* Wood Grain Texture CSS Pattern */
.login-card::before {
content: "";
position: absolute;
top: 4px;
left: 4px;
right: 4px;
bottom: 4px;
background-image:
repeating-linear-gradient(45deg,
rgba(0,0,0,0.05) 0px,
rgba(0,0,0,0.05) 2px,
transparent 2px,
transparent 8px
);
pointer-events: none;
z-index: 0;
}
.content-layer {
position: relative;
z-index: 1;
}
/* Tabs */
.tabs {
display: flex;
margin-bottom: 24px;
border-bottom: 4px solid var(--mc-wood-dark);
}
.tab-btn {
flex: 1;
padding: 12px 0;
text-align: center;
cursor: pointer;
color: rgba(255, 255, 255, 0.6);
background: none;
border: none;
font-size: 14px;
transition: all 0.2s;
text-shadow: 2px 2px 0 #000;
}
.tab-btn.active {
color: #fff;
background-color: rgba(0, 0, 0, 0.1);
border-top: 4px solid var(--mc-wood-light);
border-left: 4px solid var(--mc-wood-light);
border-right: 4px solid var(--mc-wood-dark);
}
/* Form Elements */
.form-group {
margin-bottom: 24px;
position: relative;
}
.input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.input-icon {
position: absolute;
left: 16px;
color: #fff;
font-size: 16px;
z-index: 2;
text-shadow: 2px 2px 0 #000;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 16px 16px 16px 48px;
background-color: #333;
border: 4px solid #000;
border-right-color: #555;
border-bottom-color: #555;
color: #fff;
font-size: 12px;
outline: none;
box-shadow: inset 4px 4px 0px #000;
}
input::placeholder {
color: #888;
}
input:focus {
background-color: #444;
}
.toggle-password {
position: absolute;
right: 16px;
cursor: pointer;
color: #aaa;
}
/* Checkbox */
.checkbox-group {
display: flex;
align-items: center;
margin-bottom: 24px;
font-size: 10px;
cursor: pointer;
}
.custom-checkbox {
width: 20px;
height: 20px;
background-color: #333;
border: 2px solid #000;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: inset 2px 2px 0 #000;
}
.custom-checkbox i {
display: none;
font-size: 14px;
color: var(--mc-grass-top);
}
.checkbox-group.checked .custom-checkbox i {
display: block;
}
/* Password Strength */
.strength-meter {
height: 8px;
background-color: #000;
margin-top: 8px;
display: flex;
padding: 2px;
}
.strength-bar {
height: 100%;
width: 0%;
transition: width 0.3s, background-color 0.3s;
}
.strength-text {
font-size: 8px;
margin-top: 4px;
text-align: right;
color: #ddd;
}
/* Button */
.btn-minecraft {
width: 100%;
padding: 16px;
background-color: var(--mc-grass-top);
border: 4px solid #000;
color: #fff;
font-size: 16px;
cursor: pointer;
text-transform: uppercase;
text-shadow: 2px 2px 0px #000;
box-shadow:
inset 4px 4px 0px rgba(255,255,255,0.3),
inset -4px -4px 0px rgba(0,0,0,0.3),
4px 4px 0px #000;
transition: transform 0.1s, box-shadow 0.1s;
}
.btn-minecraft:active {
transform: translate(4px, 4px);
box-shadow: none;
}
.btn-minecraft:hover {
background-color: #7bc453;
}
/* Links */
.links {
margin-top: 24px;
text-align: center;
font-size: 10px;
}
.link-diamond {
color: var(--mc-diamond);
text-decoration: none;
text-shadow: 1px 1px 0 #000;
border-bottom: 2px solid transparent;
}
.link-diamond:hover {
border-bottom-color: var(--mc-diamond);
}
/* Error Message */
.error-msg {
color: var(--mc-redstone);
font-size: 10px;
margin-top: 8px;
text-shadow: 1px 1px 0 #000;
display: none;
}
/* Floating XP Orbs */
.xp-orb {
position: absolute;
width: 24px;
height: 24px;
background-color: #bcfc03;
border: 2px solid #6c8f02;
transform: rotate(45deg);
box-shadow: 0 0 10px #bcfc03;
animation: float 3s ease-in-out infinite;
}
.orb-1 { top: -40px; left: -40px; animation-delay: 0s; background-color: #bcfc03; }
.orb-2 { bottom: -30px; right: -30px; animation-delay: 1.5s; background-color: #64ffda; border-color: #009688; }
.orb-3 { top: 50%; right: -60px; animation-delay: 0.8s; width: 16px; height: 16px; }
@keyframes float {
0%, 100% { transform: rotate(45deg) translateY(0); }
50% { transform: rotate(45deg) translateY(-20px); }
}
/* Footer */
.footer {
position: fixed;
bottom: 0;
width: 100%;
height: 60px;
background-color: rgba(0,0,0,0.6);
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 12px;
border-top: 4px solid rgba(0,0,0,0.4);
}
.online-players {
display: flex;
align-items: center;
gap: 12px;
}
.player-head {
width: 24px;
height: 24px;
background-color: #333;
border: 2px solid #fff;
image-rendering: pixelated;
}
.player-head:nth-child(2) { background-color: #AA0000; }
.player-head:nth-child(3) { background-color: #00AA00; }
.player-head:nth-child(4) { background-color: #0000AA; }
/* Grass Block Floor Decoration */
.grass-floor {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 40px;
background-image:
linear-gradient(to right, var(--mc-grass-side) 50%, var(--mc-grass-top) 50%),
linear-gradient(to bottom, transparent 50%, var(--mc-dirt-side) 50%);
background-size: 40px 40px, 100% 40px;
z-index: 5;
display: none; /* Hidden to keep clean look, or show for immersion */
}
</style>
</head>
<body>
<nav class="navbar">
<div class="logo">
<i class="ri-box-3-fill"></i>
CSP Learning Platform
</div>
</nav>
<div class="clouds"></div>
<div class="main-container">
<div class="login-card">
<!-- Decorative Orbs -->
<div class="xp-orb orb-1"></div>
<div class="xp-orb orb-2"></div>
<div class="xp-orb orb-3"></div>
<div class="content-layer">
<div class="tabs">
<button class="tab-btn active" onclick="switchTab('login')">Login</button>
<button class="tab-btn" onclick="switchTab('register')">Register</button>
</div>
<form id="authForm" onsubmit="handleLogin(event)">
<div class="form-group">
<div class="input-wrapper">
<i class="ri-user-smile-fill input-icon"></i>
<input type="text" id="username" placeholder="Username" autocomplete="off">
</div>
<div id="username-error" class="error-msg">Username must be at least 3 chars!</div>
</div>
<div class="form-group">
<div class="input-wrapper">
<i class="ri-lock-fill input-icon"></i>
<input type="password" id="password" placeholder="Password" oninput="checkStrength()">
<i class="ri-eye-off-fill toggle-password" onclick="togglePassword()"></i>
</div>
<div class="strength-meter">
<div class="strength-bar" id="strength-bar"></div>
</div>
<div class="strength-text" id="strength-text"></div>
<div id="password-error" class="error-msg">Password must be at least 6 chars!</div>
</div>
<div class="checkbox-group" onclick="toggleRemember(this)">
<div class="custom-checkbox">
<i class="ri-check-fill"></i>
</div>
<span>Remember me</span>
</div>
<button type="submit" class="btn-minecraft" id="submit-btn">Login</button>
</form>
<div class="links">
<a href="#" class="link-diamond">Forgot password?</a>
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="online-players">
<span>Online Players: <span style="color: var(--mc-gold);">12,002</span></span>
<div class="player-head"></div>
<div class="player-head"></div>
<div class="player-head"></div>
</div>
</footer>
<script>
let isLogin = true;
let rememberMe = false;
function switchTab(tab) {
const btns = document.querySelectorAll('.tab-btn');
const submitBtn = document.getElementById('submit-btn');
const form = document.getElementById('authForm');
// Clear errors
document.querySelectorAll('.error-msg').forEach(el => el.style.display = 'none');
if (tab === 'login') {
isLogin = true;
btns[0].classList.add('active');
btns[1].classList.remove('active');
submitBtn.innerText = 'Login';
} else {
isLogin = false;
btns[0].classList.remove('active');
btns[1].classList.add('active');
submitBtn.innerText = 'Register';
}
}
function togglePassword() {
const pwdInput = document.getElementById('password');
const icon = document.querySelector('.toggle-password');
if (pwdInput.type === 'password') {
pwdInput.type = 'text';
icon.classList.remove('ri-eye-off-fill');
icon.classList.add('ri-eye-fill');
} else {
pwdInput.type = 'password';
icon.classList.remove('ri-eye-fill');
icon.classList.add('ri-eye-off-fill');
}
}
function toggleRemember(el) {
el.classList.toggle('checked');
rememberMe = !rememberMe;
}
function checkStrength() {
const pwd = document.getElementById('password').value;
const bar = document.getElementById('strength-bar');
const text = document.getElementById('strength-text');
if (pwd.length === 0) {
bar.style.width = '0%';
bar.style.backgroundColor = 'transparent';
text.innerText = '';
return;
}
let strength = 0;
if (pwd.length >= 6) strength++;
if (pwd.match(/[a-z]/) && pwd.match(/[0-9]/)) strength++;
if (pwd.length > 8 && pwd.match(/[^a-zA-Z0-9]/)) strength++;
if (strength === 1) {
bar.style.width = '33%';
bar.style.backgroundColor = '#E53935'; // Weak
text.innerText = 'Weak';
text.style.color = '#E53935';
} else if (strength === 2) {
bar.style.width = '66%';
bar.style.backgroundColor = '#FFD700'; // Medium
text.innerText = 'Medium';
text.style.color = '#FFD700';
} else {
bar.style.width = '100%';
bar.style.backgroundColor = '#7CB342'; // Strong
text.innerText = 'Strong';
text.style.color = '#7CB342';
}
}
function handleLogin(e) {
e.preventDefault();
const user = document.getElementById('username').value;
const pass = document.getElementById('password').value;
const userErr = document.getElementById('username-error');
const passErr = document.getElementById('password-error');
let isValid = true;
// Reset errors
userErr.style.display = 'none';
passErr.style.display = 'none';
if (user.length < 3) {
userErr.style.display = 'block';
isValid = false;
}
if (pass.length < 6) {
passErr.style.display = 'block';
isValid = false;
}
if (!isValid) return;
// Mock login check
if (isLogin) {
if (user === 'test' && pass === 'whoami139') {
// Success
const token = 'mock_token_' + Date.now();
localStorage.setItem('authToken', token);
if(rememberMe) localStorage.setItem('savedUser', user);
// Simulate redirect with a nice alert or console log since we are in a static file context
// In a real app: window.location.href = 'problem-library.html';
const btn = document.getElementById('submit-btn');
btn.innerText = 'Connecting...';
btn.style.backgroundColor = '#FFD700'; // Gold color loading
setTimeout(() => {
// We just reload or show success for this demo
btn.innerText = 'Success!';
btn.style.backgroundColor = '#7CB342';
// Simulating redirect
// window.location.href = 'problem-library.html';
console.log("Redirecting to problem-library.html");
}, 1000);
} else {
// Wrong credentials
// Create a generic error message for demo
passErr.innerText = 'Invalid username or password!';
passErr.style.display = 'block';
}
} else {
// Register logic mock
alert(`Registered user: ${user}`);
}
}
</script>
</body>
</html>

查看文件

@@ -0,0 +1,849 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minecraft Problem Library</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.6.0/remixicon.min.css" rel="stylesheet">
<style>
/* Font Loading */
@font-face {
font-family: 'Press Start 2P';
src: url('https://fonts.gstatic.com/s/pressstart2p/v15/e3t4euO8T-267oIAQAu6jDQyK3nVivM.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: 'MiSans-Regular';
src: url('https://assets-persist.lovart.ai/agent-static-assets/MiSans-Regular.ttf');
}
:root {
--mc-dirt-dark: #5e4032;
--mc-dirt-light: #866043;
--mc-grass-side: #7CB342;
--mc-grass-top: #558B2F;
--mc-stone: #757575;
--mc-stone-light: #9E9E9E;
--mc-wood-dark: #4E342E;
--mc-wood-light: #795548;
--mc-plank: #A1887F;
--mc-text-shadow: 2px 2px 0 #000;
--mc-bg: #1e1e1e;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'MiSans-Regular', sans-serif;
background-color: #121212;
color: white;
width: 1920px;
min-height: 100vh;
overflow-x: hidden;
background-image:
linear-gradient(rgba(0,0,0,0.7), rgba(0,0,0,0.7)),
url('https://images.unsplash.com/photo-1587573088697-b4f9d17102dd?q=80&w=2669&auto=format&fit=crop'); /* Minecraft-like texture bg */
background-size: cover;
background-attachment: fixed;
display: flex;
flex-direction: column;
}
/* Minecraft Button Utility */
.mc-btn {
font-family: 'Press Start 2P', cursive;
font-size: 12px;
padding: 12px 20px;
border: 4px solid #000;
background: #9E9E9E;
color: #fff;
text-shadow: 2px 2px #333;
cursor: pointer;
box-shadow: inset -4px -4px 0px #555, inset 4px 4px 0px #DDD;
text-decoration: none;
display: inline-block;
transition: transform 0.1s;
text-transform: uppercase;
}
.mc-btn:active {
box-shadow: inset -4px -4px 0px #DDD, inset 4px 4px 0px #555;
transform: translateY(2px);
}
.mc-btn.green {
background: #7CB342;
box-shadow: inset -4px -4px 0px #33691E, inset 4px 4px 0px #AED581;
}
.mc-btn.green:active {
box-shadow: inset -4px -4px 0px #AED581, inset 4px 4px 0px #33691E;
}
/* Top Navigation */
.top-nav {
height: 70px;
background-color: rgba(0,0,0,0.8);
border-bottom: 4px solid #3e3e3e;
display: flex;
align-items: center;
padding: 0 40px;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 100;
}
.nav-brand {
font-family: 'Press Start 2P', cursive;
font-size: 20px;
color: #FFEB3B;
text-shadow: var(--mc-text-shadow);
margin-right: 60px;
display: flex;
align-items: center;
gap: 10px;
}
.nav-links {
display: flex;
gap: 10px;
}
.nav-item {
font-family: 'Press Start 2P', cursive;
font-size: 12px;
color: #ccc;
text-decoration: none;
padding: 10px 20px;
border: 2px solid transparent;
transition: all 0.2s;
}
.nav-item:hover, .nav-item.active {
color: #fff;
background: rgba(255,255,255,0.1);
border: 2px solid #fff;
text-shadow: 2px 2px 0 #000;
}
.user-info {
display: flex;
align-items: center;
gap: 15px;
}
.user-avatar {
width: 40px;
height: 40px;
border: 2px solid #fff;
image-rendering: pixelated;
}
/* Layout Container */
.container {
display: flex;
flex: 1;
max-width: 1920px;
}
/* Sidebar */
.sidebar {
width: 280px;
background-color: #5D4037; /* Dark Wood */
border-right: 4px solid #3E2723;
display: flex;
flex-direction: column;
padding: 20px;
background-image: repeating-linear-gradient(
45deg,
rgba(255,255,255,0.05) 0px,
rgba(255,255,255,0.05) 2px,
transparent 2px,
transparent 10px
);
}
.sidebar-menu {
list-style: none;
margin-bottom: auto;
}
.menu-item {
margin-bottom: 12px;
}
.menu-link {
display: block;
padding: 15px;
background: #8D6E63; /* Plank color */
color: #fff;
text-decoration: none;
font-family: 'Press Start 2P', cursive;
font-size: 10px;
border: 4px solid #3E2723;
box-shadow: inset 2px 2px 0 rgba(255,255,255,0.2);
transition: transform 0.1s;
}
.menu-link:hover {
transform: scale(1.02);
background: #A1887F;
}
.menu-link.active {
background: #7CB342;
border-color: #33691E;
}
.daily-quest-panel {
background: #263238;
border: 4px solid #000;
padding: 15px;
margin-top: 20px;
}
.quest-title {
font-family: 'Press Start 2P', cursive;
font-size: 12px;
color: #FFEB3B;
margin-bottom: 10px;
text-shadow: 1px 1px #000;
}
.quest-progress {
height: 20px;
background: #37474F;
border: 2px solid #000;
position: relative;
}
.quest-bar {
height: 100%;
width: 60%;
background: #29B6F6;
box-shadow: inset 0 -2px 0 rgba(0,0,0,0.2);
}
.quest-text {
font-family: 'Press Start 2P', cursive;
font-size: 10px;
margin-top: 8px;
text-align: right;
color: #B0BEC5;
}
/* Main Content */
.main-content {
flex: 1;
padding: 40px;
background: rgba(0,0,0,0.4);
backdrop-filter: blur(5px);
}
.page-header {
display: flex;
align-items: center;
margin-bottom: 30px;
gap: 15px;
}
.page-title {
font-family: 'Press Start 2P', cursive;
font-size: 32px;
color: #fff;
text-shadow: 4px 4px 0 #000;
}
/* Stats Cards */
.stats-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: #546E7A; /* Stone Blue */
border: 4px solid #263238;
padding: 20px;
position: relative;
box-shadow: 6px 6px 0 rgba(0,0,0,0.5);
}
.stat-card::after {
content: '';
position: absolute;
top: 4px;
left: 4px;
right: 4px;
bottom: 4px;
border: 2px dashed rgba(255,255,255,0.1);
pointer-events: none;
}
.stat-label {
font-family: 'Press Start 2P', cursive;
font-size: 10px;
color: #CFD8DC;
margin-bottom: 10px;
}
.stat-value {
font-family: 'Press Start 2P', cursive;
font-size: 24px;
color: #fff;
text-shadow: 2px 2px 0 #000;
}
/* Filters */
.filter-section {
background: #3E2723;
border: 4px solid #000;
padding: 15px;
margin-bottom: 20px;
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
}
.category-tabs {
display: flex;
gap: 8px;
margin-right: auto;
}
.tab-btn {
font-family: 'Press Start 2P', cursive;
font-size: 10px;
padding: 10px 15px;
background: #5D4037;
border: 2px solid #8D6E63;
color: #D7CCC8;
cursor: pointer;
text-transform: uppercase;
}
.tab-btn.active {
background: #7CB342;
color: white;
border-color: #AED581;
box-shadow: 0 4px 0 #33691E;
transform: translateY(-2px);
}
.filter-controls {
display: flex;
gap: 10px;
}
.mc-select {
font-family: 'MiSans-Regular', sans-serif; /* Readable font for dropdown */
font-size: 14px;
padding: 8px;
background: #D7CCC8;
border: 3px solid #5D4037;
color: #3E2723;
outline: none;
cursor: pointer;
min-width: 120px;
}
.mc-input {
font-family: 'MiSans-Regular', sans-serif;
font-size: 14px;
padding: 8px 12px;
background: #212121;
border: 3px solid #616161;
color: #fff;
width: 200px;
}
.mc-search-btn {
background: #FFB74D;
border: 3px solid #E65100;
color: #3E2723;
padding: 0 15px;
cursor: pointer;
font-family: 'Press Start 2P', cursive;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
}
/* Problem Table */
.problem-table-container {
background: #8D6E63; /* Plank */
border: 4px solid #3E2723;
padding: 10px;
box-shadow: 8px 8px 0 rgba(0,0,0,0.5);
}
.problem-table {
width: 100%;
border-collapse: collapse;
}
.problem-table th {
font-family: 'Press Start 2P', cursive;
font-size: 10px;
text-align: left;
padding: 15px;
background: #5D4037;
color: #FFCC80;
border-bottom: 4px solid #3E2723;
}
.problem-table td {
padding: 12px 15px;
border-bottom: 2px solid #5D4037;
color: #3E2723;
font-size: 14px;
font-weight: 500;
}
.problem-table tr:hover td {
background-color: rgba(255,255,255,0.1);
}
.problem-table tr:last-child td {
border-bottom: none;
}
.status-icon {
font-size: 18px;
}
.status-solved { color: #2E7D32; }
.status-locked { color: #616161; }
.status-attempted { color: #F57F17; }
.difficulty-stars {
display: flex;
gap: 2px;
}
.star {
font-size: 12px;
}
.tag-badge {
background: #3E2723;
color: #D7CCC8;
padding: 2px 6px;
font-size: 12px;
border-radius: 2px;
display: inline-block;
margin-right: 4px;
}
.table-btn {
font-family: 'Press Start 2P', cursive;
font-size: 8px;
padding: 6px 10px;
border: 2px solid #000;
background: #FFB74D;
color: #000;
text-decoration: none;
display: inline-block;
box-shadow: inset -2px -2px 0 #E65100, inset 2px 2px 0 #FFE0B2;
}
.table-btn:hover {
transform: scale(1.05);
}
/* Pagination */
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
gap: 15px;
}
.page-info {
font-family: 'Press Start 2P', cursive;
font-size: 10px;
color: #fff;
}
/* Right Floating Panel */
.right-panel {
width: 260px;
padding: 20px;
margin-right: 20px;
}
.task-card {
background: #F5F5F5; /* Paper */
border: 4px solid #424242;
padding: 15px;
box-shadow: 4px 4px 0 rgba(0,0,0,0.5);
position: sticky;
top: 100px;
}
.task-card::before {
content: '';
display: block;
height: 10px;
background: #B71C1C; /* Red tape */
width: 40px;
margin: -25px auto 15px;
box-shadow: 1px 1px 2px rgba(0,0,0,0.3);
}
.task-header {
font-family: 'Press Start 2P', cursive;
font-size: 12px;
color: #212121;
margin-bottom: 15px;
text-align: center;
border-bottom: 2px dashed #9E9E9E;
padding-bottom: 10px;
}
.task-list {
list-style: none;
}
.task-item {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
font-size: 13px;
color: #424242;
}
.mc-checkbox {
appearance: none;
width: 16px;
height: 16px;
border: 2px solid #000;
background: #fff;
cursor: pointer;
position: relative;
}
.mc-checkbox:checked {
background: #7CB342;
}
.mc-checkbox:checked::after {
content: '✔';
position: absolute;
top: -3px;
left: 1px;
font-size: 12px;
color: #fff;
}
.xp-reward {
font-family: 'Press Start 2P', cursive;
font-size: 8px;
color: #F57F17;
margin-left: auto;
}
/* Responsive */
@media (max-width: 1200px) {
.stats-row {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<nav class="top-nav">
<div class="nav-brand">
<i class="ri-book-3-fill" style="color: #8D6E63; font-size: 24px;"></i>
CRAFT CODE
</div>
<div class="nav-links">
<a href="#" class="nav-item">HOME</a>
<a href="#" class="nav-item active">PROBLEMS</a>
<a href="#" class="nav-item">CONTEST</a>
<a href="#" class="nav-item">LEADERBOARD</a>
</div>
<div class="user-info">
<img src="https://api.dicebear.com/7.x/pixel-art/svg?seed=Felix" alt="User" class="user-avatar">
<div style="font-family: 'Press Start 2P'; font-size: 10px; color: #fff;">Steve</div>
</div>
</nav>
<div class="container">
<!-- Sidebar -->
<aside class="sidebar">
<ul class="sidebar-menu">
<li class="menu-item"><a href="#" class="menu-link active">ALL PROBLEMS</a></li>
<li class="menu-item"><a href="#" class="menu-link">ALGORITHMS</a></li>
<li class="menu-item"><a href="#" class="menu-link">DATA STRUCTURES</a></li>
<li class="menu-item"><a href="#" class="menu-link">MATH</a></li>
<li class="menu-item"><a href="#" class="menu-link">STRINGS</a></li>
<li class="menu-item"><a href="#" class="menu-link">DYNAMIC PROG</a></li>
</ul>
<div class="daily-quest-panel">
<div class="quest-title">DAILY QUEST</div>
<div class="quest-progress">
<div class="quest-bar"></div>
</div>
<div class="quest-text">3/5 COMPLETED</div>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<div class="page-header">
<i class="ri-book-read-line" style="font-size: 32px; color: #FFD54F;"></i>
<h1 class="page-title">PROBLEM LIBRARY</h1>
</div>
<div class="stats-row">
<div class="stat-card">
<div class="stat-label">TOTAL PROBLEMS</div>
<div class="stat-value">1,556</div>
</div>
<div class="stat-card">
<div class="stat-label">COMPLETED</div>
<div class="stat-value" style="color: #7CB342;">128</div>
</div>
<div class="stat-card">
<div class="stat-label">PASS RATE</div>
<div class="stat-value" style="color: #FFB74D;">85%</div>
</div>
</div>
<div class="filter-section">
<div class="category-tabs">
<button class="tab-btn active" onclick="switchTab(this)">CSP-J</button>
<button class="tab-btn" onclick="switchTab(this)">CSP-S</button>
<button class="tab-btn" onclick="switchTab(this)">NOIP</button>
</div>
<div class="filter-controls">
<select class="mc-select">
<option>All Difficulty</option>
<option>1 Star</option>
<option>2 Stars</option>
<option>3 Stars</option>
<option>4 Stars</option>
<option>5 Stars</option>
</select>
<select class="mc-select">
<option>All Tags</option>
<option>Array</option>
<option>Stack</option>
<option>Queue</option>
</select>
<input type="text" class="mc-input" id="searchInput" placeholder="Search problems...">
<button class="mc-search-btn" onclick="filterTable()">
<i class="ri-search-2-line"></i>
</button>
</div>
</div>
<div class="problem-table-container">
<table class="problem-table" id="problemTable">
<thead>
<tr>
<th width="50">STS</th>
<th width="60">ID</th>
<th>TITLE</th>
<th width="100">RATE</th>
<th width="100">DIFF</th>
<th width="150">TAGS</th>
<th width="80">ACT</th>
</tr>
</thead>
<tbody>
<!-- Data rows will be populated by JS -->
<tr>
<td><i class="ri-checkbox-circle-fill status-solved status-icon"></i></td>
<td>1001</td>
<td>A+B Problem</td>
<td>95%</td>
<td>
<div class="difficulty-stars">
<i class="ri-star-fill star" style="color:#7CB342"></i>
<i class="ri-star-line star" style="color:#757575"></i>
<i class="ri-star-line star" style="color:#757575"></i>
</div>
</td>
<td><span class="tag-badge">Math</span></td>
<td><a href="#" class="table-btn">SOLVE</a></td>
</tr>
<tr>
<td><i class="ri-lock-fill status-locked status-icon"></i></td>
<td>1002</td>
<td>Fibonacci Sequence</td>
<td>45%</td>
<td>
<div class="difficulty-stars">
<i class="ri-star-fill star" style="color:#FFB74D"></i>
<i class="ri-star-fill star" style="color:#FFB74D"></i>
<i class="ri-star-line star" style="color:#757575"></i>
</div>
</td>
<td><span class="tag-badge">DP</span></td>
<td><a href="#" class="table-btn">SOLVE</a></td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<button class="mc-btn">PREV</button>
<div class="page-info">PAGE 1 / 32</div>
<button class="mc-btn green">NEXT</button>
</div>
</main>
<!-- Right Panel -->
<aside class="right-panel">
<div class="task-card">
<div class="task-header">DAILY TASKS</div>
<ul class="task-list">
<li class="task-item">
<input type="checkbox" class="mc-checkbox" checked>
<span>Login Daily</span>
<span class="xp-reward">+10XP</span>
</li>
<li class="task-item">
<input type="checkbox" class="mc-checkbox" checked>
<span>Solve 1 Easy</span>
<span class="xp-reward">+20XP</span>
</li>
<li class="task-item">
<input type="checkbox" class="mc-checkbox">
<span>Solve 1 Medium</span>
<span class="xp-reward">+50XP</span>
</li>
<li class="task-item">
<input type="checkbox" class="mc-checkbox">
<span>Submit 5 times</span>
<span class="xp-reward">+15XP</span>
</li>
<li class="task-item">
<input type="checkbox" class="mc-checkbox">
<span>Review Code</span>
<span class="xp-reward">+30XP</span>
</li>
</ul>
</div>
</aside>
</div>
<script>
// Sample Data Generation
const problems = [
{ id: '1001', title: 'A+B Problem', rate: '98%', diff: 1, tags: ['Math'], status: 'solved' },
{ id: '1002', title: 'Knapsack Problem', rate: '45%', diff: 3, tags: ['DP', 'Math'], status: 'attempted' },
{ id: '1003', title: 'Binary Tree Traversal', rate: '60%', diff: 2, tags: ['Tree', 'DFS'], status: 'locked' },
{ id: '1004', title: 'Shortest Path (Dijkstra)', rate: '32%', diff: 4, tags: ['Graph'], status: 'locked' },
{ id: '1005', title: 'String Matching (KMP)', rate: '28%', diff: 5, tags: ['String'], status: 'locked' },
{ id: '1006', title: 'Counting Sort', rate: '75%', diff: 2, tags: ['Sort'], status: 'solved' },
{ id: '1007', title: 'N-Queens', rate: '40%', diff: 3, tags: ['Backtrack'], status: 'locked' },
{ id: '1008', title: 'Union Find', rate: '55%', diff: 3, tags: ['DS'], status: 'attempted' },
{ id: '1009', title: 'Segment Tree Range Sum', rate: '20%', diff: 5, tags: ['Tree'], status: 'locked' },
{ id: '1010', title: 'Prim MST', rate: '35%', diff: 4, tags: ['Graph'], status: 'locked' }
];
function getStatusIcon(status) {
if (status === 'solved') return '<i class="ri-checkbox-circle-fill status-solved status-icon"></i>';
if (status === 'attempted') return '<i class="ri-record-circle-fill status-attempted status-icon"></i>';
return '<i class="ri-lock-fill status-locked status-icon"></i>';
}
function getStars(count) {
let html = '<div class="difficulty-stars">';
const colors = ['#7CB342', '#29B6F6', '#FFB74D', '#FF7043', '#D32F2F']; // Color coding
const color = colors[count - 1] || '#757575';
for(let i=0; i<5; i++) {
if(i < count) {
html += `<i class="ri-star-fill star" style="color:${color}"></i>`;
} else {
html += `<i class="ri-star-line star" style="color:#757575"></i>`;
}
}
html += '</div>';
return html;
}
function renderTable(data) {
const tbody = document.querySelector('#problemTable tbody');
tbody.innerHTML = '';
data.forEach(p => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${getStatusIcon(p.status)}</td>
<td>${p.id}</td>
<td style="cursor:pointer;" onclick="window.location.href='code-editor.html?id=${p.id}'">${p.title}</td>
<td>${p.rate}</td>
<td>${getStars(p.diff)}</td>
<td>${p.tags.map(t => `<span class="tag-badge">${t}</span>`).join('')}</td>
<td><a href="code-editor.html?id=${p.id}" class="table-btn">SOLVE</a></td>
`;
tbody.appendChild(tr);
});
}
// Tab Switching Logic
function switchTab(element) {
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
element.classList.add('active');
// Mock data change
const shuffled = [...problems].sort(() => 0.5 - Math.random());
renderTable(shuffled);
}
// Filter Logic
function filterTable() {
const term = document.getElementById('searchInput').value.toLowerCase();
const filtered = problems.filter(p =>
p.title.toLowerCase().includes(term) ||
p.id.includes(term) ||
p.tags.some(t => t.toLowerCase().includes(term))
);
renderTable(filtered);
}
// Real-time search
document.getElementById('searchInput').addEventListener('input', filterTable);
// Sidebar Active State
document.querySelectorAll('.menu-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
document.querySelectorAll('.menu-link').forEach(l => l.classList.remove('active'));
link.classList.add('active');
});
});
// Initialize
renderTable(problems);
// Save checkbox state to localStorage
document.querySelectorAll('.mc-checkbox').forEach((box, index) => {
const saved = localStorage.getItem(`task-${index}`);
if(saved !== null) {
box.checked = saved === 'true';
}
box.addEventListener('change', () => {
localStorage.setItem(`task-${index}`, box.checked);
});
});
</script>
</body>
</html>

查看文件

@@ -0,0 +1,896 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minecraft Profile Center</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.6.0/remixicon.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<style>
:root {
--bg-color: #1a1a1a;
--text-color: #ffffff;
--minecraft-green: #59a228;
--minecraft-dark-green: #376318;
--wood-light: #a07449;
--wood-dark: #634329;
--wood-border: #3d2919;
--gold: #fcc201;
--purple: #aa00aa;
--iron: #c6c6c6;
--diamond: #3fd2ea;
--red: #aa0000;
}
* {
box-sizing: border-box;
image-rendering: pixelated;
}
body {
margin: 0;
padding: 0;
background-color: #2c2c2c;
background-image:
linear-gradient(45deg, #252525 25%, transparent 25%),
linear-gradient(-45deg, #252525 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #252525 75%),
linear-gradient(-45deg, transparent 75%, #252525 75%);
background-size: 40px 40px;
font-family: 'Press Start 2P', cursive;
color: var(--text-color);
width: 1920px;
overflow-x: hidden;
min-height: 100vh;
}
/* Utility Classes */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: 8px; }
.gap-4 { gap: 16px; }
.w-full { width: 100%; }
.h-full { height: 100%; }
/* Minecraft UI Components */
.mc-btn {
background-color: #7d7d7d;
border: 4px solid #000;
border-top-color: #dedede;
border-left-color: #dedede;
border-right-color: #555;
border-bottom-color: #555;
padding: 10px 20px;
cursor: pointer;
text-transform: uppercase;
font-size: 14px;
color: white;
text-decoration: none;
position: relative;
}
.mc-btn:active, .mc-btn.active {
background-color: #555;
border-top-color: #333;
border-left-color: #333;
border-right-color: #dedede;
border-bottom-color: #dedede;
color: #aaa;
}
.mc-card {
background-color: #c6c6c6;
border: 4px solid #000;
border-top-color: #fff;
border-left-color: #fff;
border-right-color: #555;
border-bottom-color: #555;
padding: 4px;
position: relative;
}
.mc-card-inner {
background-color: #8b8b8b;
border: 4px solid #373737;
border-right-color: #fff;
border-bottom-color: #fff;
padding: 16px;
height: 100%;
}
.mc-wood-panel {
background-color: var(--wood-light);
background-image:
linear-gradient(90deg, rgba(0,0,0,0.1) 50%, transparent 50%),
linear-gradient(rgba(0,0,0,0.1) 50%, transparent 50%);
background-size: 8px 8px;
border: 4px solid var(--wood-border);
box-shadow: inset 4px 4px 0 var(--wood-dark), inset -4px -4px 0 var(--wood-dark);
padding: 20px;
}
/* Top Nav */
nav {
background: rgba(0,0,0,0.8);
padding: 20px 40px;
position: sticky;
top: 0;
z-index: 100;
border-bottom: 4px solid #555;
}
.nav-brand { font-size: 24px; color: var(--diamond); text-shadow: 2px 2px #000; }
.nav-links { gap: 20px; }
/* Main Container */
.container {
padding: 40px;
width: 100%;
}
/* User Info Card */
.user-hero {
display: grid;
grid-template-columns: 200px 1fr 400px;
gap: 40px;
margin-bottom: 40px;
position: relative;
}
.avatar-container {
width: 160px;
height: 160px;
background: #000;
border: 4px solid #fff;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
image-rendering: pixelated;
}
.user-details h1 {
font-size: 32px;
margin: 0 0 10px 0;
text-shadow: 3px 3px 0 #000;
}
.badge-lv {
display: inline-block;
background: var(--gold);
color: #000;
padding: 8px 12px;
border: 2px solid #fff;
margin-right: 10px;
box-shadow: 4px 4px 0 rgba(0,0,0,0.5);
}
.title-banner {
display: inline-block;
background: var(--purple);
padding: 8px 16px;
border: 2px solid #d369d3;
text-shadow: 2px 2px #000;
margin-top: 10px;
}
.xp-bar-container {
width: 100%;
height: 24px;
background: #333;
border: 2px solid #fff;
margin-top: 15px;
position: relative;
}
.xp-bar-fill {
height: 100%;
background: var(--minecraft-green);
width: 0%; /* JS will animate */
transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.xp-bar-fill::after {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-image: linear-gradient(
45deg,
rgba(255, 255, 255, 0.2) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.2) 50%,
rgba(255, 255, 255, 0.2) 75%,
transparent 75%,
transparent
);
background-size: 20px 20px;
}
.xp-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 10px;
color: #fff;
text-shadow: 1px 1px #000;
z-index: 2;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.stat-card {
background: rgba(0,0,0,0.3);
border: 2px solid rgba(255,255,255,0.2);
padding: 10px;
text-align: center;
}
.stat-val { font-size: 20px; color: var(--gold); margin-bottom: 5px; }
.stat-label { font-size: 10px; color: #ccc; }
/* 3 Columns Layout */
.main-content {
display: grid;
grid-template-columns: 25% 50% 25%;
gap: 20px;
}
/* Column Styles */
.col-card {
background: #c6c6c6;
border: 4px solid #555;
padding: 4px;
margin-bottom: 20px;
}
.col-card-inner {
background: #222;
border: 2px solid #000;
padding: 15px;
min-height: 100px;
}
.card-header {
font-size: 14px;
color: #fff;
border-bottom: 2px solid #555;
padding-bottom: 10px;
margin-bottom: 15px;
text-transform: uppercase;
}
/* Pie Chart Simulation */
.pie-chart-wrap {
position: relative;
width: 140px;
height: 140px;
margin: 20px auto;
border-radius: 50%;
background: conic-gradient(
var(--minecraft-green) 0% 50%,
var(--gold) 50% 80%,
var(--red) 80% 100%
);
border: 4px solid #000;
box-shadow: 4px 4px 0 rgba(0,0,0,0.5);
}
/* Activity Grid */
.activity-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 4px;
margin-top: 15px;
}
.activity-cell {
width: 100%;
padding-top: 100%;
background: #333;
position: relative;
}
.activity-cell[data-level="1"] { background: #1a4d1a; }
.activity-cell[data-level="2"] { background: #2b7a2b; }
.activity-cell[data-level="3"] { background: #40b340; }
.activity-cell[data-level="4"] { background: #5cd65c; }
/* Achievement Wall */
.achievement-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 15px;
padding: 20px;
background: url('https://www.transparenttextures.com/patterns/wood-pattern.png') #634329;
border: 8px solid #3d2919;
box-shadow: inset 0 0 20px #000;
}
.achievement-slot {
aspect-ratio: 1;
background: rgba(0,0,0,0.3);
border: 2px solid #3d2919;
display: flex;
justify-content: center;
align-items: center;
position: relative;
transition: transform 0.2s;
cursor: pointer;
}
.achievement-slot:hover {
transform: scale(1.1);
background: rgba(255,255,255,0.1);
z-index: 10;
}
.achievement-icon {
font-size: 32px;
filter: drop-shadow(2px 2px 0 #000);
}
.locked { filter: grayscale(1) brightness(0.5); }
.tooltip {
position: absolute;
bottom: 120%;
left: 50%;
transform: translateX(-50%);
background: #111;
border: 2px solid #fff;
padding: 8px;
font-size: 10px;
width: 150px;
text-align: center;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
z-index: 20;
}
.achievement-slot:hover .tooltip { opacity: 1; }
/* Learning Path */
.path-row { margin-bottom: 15px; }
.path-label { font-size: 12px; margin-bottom: 5px; display: flex; justify-content: space-between; }
.path-track {
height: 16px;
background: #000;
border: 2px solid #555;
position: relative;
}
.path-fill {
height: 100%;
background: var(--diamond);
width: 0;
transition: width 1s ease-out;
position: relative;
}
.path-fill::after {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0;
width: 4px;
background: #fff;
opacity: 0.5;
}
/* Leaderboard */
.leaderboard-item {
display: flex;
align-items: center;
padding: 8px;
border-bottom: 2px solid #333;
font-size: 12px;
}
.leaderboard-item.highlight {
background: rgba(255, 215, 0, 0.2);
border: 2px solid var(--gold);
}
.rank { width: 30px; text-align: center; font-weight: bold; }
.user { flex-grow: 1; padding-left: 10px; }
.score { color: var(--gold); }
/* Daily Tasks */
.task-item {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px dashed #444;
font-size: 11px;
cursor: pointer;
}
.checkbox-custom {
width: 20px;
height: 20px;
border: 2px solid #777;
background: #000;
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.checked .checkbox-custom::after {
content: '✓';
color: var(--minecraft-green);
font-size: 14px;
}
.task-xp { margin-left: auto; color: #888; font-size: 10px; }
/* Treasure Chest */
.treasure-box {
text-align: center;
padding: 20px;
cursor: pointer;
position: relative;
}
.chest-icon {
font-size: 64px;
color: var(--gold);
filter: drop-shadow(0 0 10px var(--gold));
animation: bounce 2s infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.chest-glow {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 100px; height: 100px;
background: radial-gradient(circle, rgba(255,215,0,0.4) 0%, transparent 70%);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.5; }
50% { transform: translate(-50%, -50%) scale(1.5); opacity: 0.8; }
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.5; }
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0; left: 0; w-full; h-full;
width: 100%; height: 100%;
background: rgba(0,0,0,0.8);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background: #c6c6c6;
border: 4px solid #fff;
padding: 4px;
width: 400px;
text-align: center;
}
.modal-inner {
background: #222;
border: 4px solid #555;
padding: 30px;
color: #fff;
}
.reward-icon { font-size: 48px; margin: 20px 0; display: block; }
.close-btn {
background: var(--red);
border: 2px solid #fff;
color: #fff;
padding: 10px 20px;
margin-top: 20px;
cursor: pointer;
font-family: inherit;
}
</style>
</head>
<body>
<nav class="flex justify-between items-center">
<div class="nav-brand"><i class="ri-code-box-line"></i> CRAFTCODE</div>
<div class="nav-links flex">
<a href="#" class="mc-btn">Home</a>
<a href="#" class="mc-btn">Problems</a>
<a href="#" class="mc-btn active">Profile</a>
<a href="#" class="mc-btn">Shop</a>
</div>
</nav>
<div class="container">
<!-- User Hero Section -->
<div class="mc-wood-panel user-hero">
<div class="avatar-container">
<!-- Using a pixelated avatar placeholder -->
<img src="https://api.dicebear.com/7.x/pixel-art/svg?seed=Steve&backgroundColor=b6e3f4" alt="Avatar" class="avatar-img">
</div>
<div class="user-details flex flex-col justify-center">
<div class="flex items-center">
<h1>CodeMaster2024</h1>
</div>
<div style="color: #ddd; font-size: 14px; margin-bottom: 10px;">Passionate problem solver</div>
<div class="flex items-center">
<div class="badge-lv"><i class="ri-shield-star-fill"></i> Lv.25</div>
<div class="title-banner">Algorithm Expert</div>
</div>
<div class="xp-bar-container">
<div class="xp-bar-fill" id="mainXP" data-width="84.5%"></div>
<div class="xp-text">8,450 / 10,000 XP</div>
</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-val">128</div>
<div class="stat-label">Problems</div>
</div>
<div class="stat-card">
<div class="stat-val">456</div>
<div class="stat-label">Submits</div>
</div>
<div class="stat-card">
<div class="stat-val">85%</div>
<div class="stat-label">Acceptance</div>
</div>
<div class="stat-card">
<div class="stat-val">15🔥</div>
<div class="stat-label">Streak</div>
</div>
</div>
</div>
<!-- Three Column Content -->
<div class="main-content">
<!-- Left Column -->
<div class="left-col">
<div class="col-card">
<div class="col-card-inner">
<div class="card-header">Difficulty Stats</div>
<div class="pie-chart-wrap"></div>
<div class="flex justify-center gap-4" style="font-size: 10px; margin-top: 10px;">
<span style="color:var(--minecraft-green)">Easy</span>
<span style="color:var(--gold)">Med</span>
<span style="color:var(--red)">Hard</span>
</div>
</div>
</div>
<div class="col-card">
<div class="col-card-inner">
<div class="card-header">Activity Log</div>
<div style="font-size: 10px; color: #aaa; text-align: center; margin-bottom: 5px;">Last 30 Days</div>
<div class="activity-grid" id="activityGrid">
<!-- Generated by JS -->
</div>
</div>
</div>
<div class="col-card">
<div class="col-card-inner">
<div class="card-header">Timeline</div>
<div class="flex flex-col gap-2" style="font-size: 10px; color: #ccc;">
<div style="border-left: 2px solid #555; padding-left: 8px;">
<div style="color: var(--minecraft-green)">Solved "Two Sum"</div>
<div style="font-size: 8px; color: #888;">2 hours ago</div>
</div>
<div style="border-left: 2px solid #555; padding-left: 8px;">
<div style="color: var(--red)">Failed "Dijkstra"</div>
<div style="font-size: 8px; color: #888;">5 hours ago</div>
</div>
<div style="border-left: 2px solid #555; padding-left: 8px;">
<div style="color: var(--gold)">Badge Unlocked</div>
<div style="font-size: 8px; color: #888;">1 day ago</div>
</div>
</div>
</div>
</div>
</div>
<!-- Center Column -->
<div class="center-col">
<div class="col-card">
<div class="col-card-inner">
<div class="card-header">Achievement Wall</div>
<div class="achievement-grid">
<!-- 15 Slots: 5x3 -->
<!-- Row 1 -->
<div class="achievement-slot">
<i class="ri-sword-fill achievement-icon" style="color: #cd7f32;"></i>
<div class="tooltip">
<div style="color: var(--gold)">Novice Slayer</div>
<div style="margin-top:4px; color:#aaa">Solve 10 Easy</div>
<div style="color: var(--minecraft-green)">+50 XP</div>
</div>
</div>
<div class="achievement-slot">
<i class="ri-sword-fill achievement-icon" style="color: #eee;"></i>
<div class="tooltip">
<div style="color: var(--gold)">Iron Will</div>
<div style="margin-top:4px; color:#aaa">Solve 50 Medium</div>
<div style="color: var(--minecraft-green)">+200 XP</div>
</div>
</div>
<div class="achievement-slot locked">
<i class="ri-sword-fill achievement-icon" style="color: var(--diamond);"></i>
<div class="tooltip">LOCKED: Diamond Blade</div>
</div>
<div class="achievement-slot">
<i class="ri-trophy-fill achievement-icon" style="color: var(--gold);"></i>
<div class="tooltip">
<div style="color: var(--gold)">Champion</div>
<div style="margin-top:4px; color:#aaa">Win a Contest</div>
<div style="color: var(--minecraft-green)">+1000 XP</div>
</div>
</div>
<div class="achievement-slot">
<i class="ri-book-3-fill achievement-icon" style="color: #a0522d;"></i>
<div class="tooltip">
<div style="color: var(--gold)">Scholar</div>
<div style="margin-top:4px; color:#aaa">Read 100 Articles</div>
<div style="color: var(--minecraft-green)">+100 XP</div>
</div>
</div>
<!-- Row 2 -->
<div class="achievement-slot">
<i class="ri-flask-fill achievement-icon" style="color: #ff69b4;"></i>
<div class="tooltip">
<div style="color: var(--gold)">Alchemist</div>
<div style="margin-top:4px; color:#aaa">Optimize 10 times</div>
<div style="color: var(--minecraft-green)">+150 XP</div>
</div>
</div>
<div class="achievement-slot locked">
<i class="ri-fire-fill achievement-icon" style="color: orange;"></i>
<div class="tooltip">LOCKED: Inferno</div>
</div>
<div class="achievement-slot">
<i class="ri-flashlight-fill achievement-icon" style="color: yellow;"></i>
<div class="tooltip">
<div style="color: var(--gold)">Speedster</div>
<div style="margin-top:4px; color:#aaa">Solve in 5 mins</div>
<div style="color: var(--minecraft-green)">+50 XP</div>
</div>
</div>
<div class="achievement-slot locked">
<i class="ri-star-fill achievement-icon" style="color: cyan;"></i>
<div class="tooltip">LOCKED: Star Player</div>
</div>
<div class="achievement-slot locked">
<i class="ri-map-pin-user-fill achievement-icon" style="color: var(--minecraft-green);"></i>
<div class="tooltip">LOCKED: Explorer</div>
</div>
<!-- Row 3 -->
<div class="achievement-slot locked">
<i class="ri-shield-fill achievement-icon" style="color: gray;"></i>
<div class="tooltip">LOCKED: Guardian</div>
</div>
<div class="achievement-slot locked">
<i class="ri-hammer-fill achievement-icon" style="color: #ddd;"></i>
<div class="tooltip">LOCKED: Builder</div>
</div>
<div class="achievement-slot locked">
<i class="ri-bug-fill achievement-icon" style="color: var(--red);"></i>
<div class="tooltip">LOCKED: Bug Hunter</div>
</div>
<div class="achievement-slot locked">
<i class="ri-compass-3-fill achievement-icon" style="color: gold;"></i>
<div class="tooltip">LOCKED: Navigator</div>
</div>
<div class="achievement-slot locked">
<i class="ri-vip-crown-fill achievement-icon" style="color: var(--purple);"></i>
<div class="tooltip">LOCKED: King</div>
</div>
</div>
</div>
</div>
<div class="col-card">
<div class="col-card-inner">
<div class="card-header">Learning Path</div>
<div class="path-row">
<div class="path-label">
<span>Basics</span>
<span>100%</span>
</div>
<div class="path-track"><div class="path-fill" data-width="100%"></div></div>
</div>
<div class="path-row">
<div class="path-label">
<span>Algorithms</span>
<span>75%</span>
</div>
<div class="path-track"><div class="path-fill" data-width="75%"></div></div>
</div>
<div class="path-row">
<div class="path-label">
<span>Data Structures</span>
<span>60%</span>
</div>
<div class="path-track"><div class="path-fill" data-width="60%"></div></div>
</div>
<div class="path-row">
<div class="path-label">
<span>Advanced DP</span>
<span>40%</span>
</div>
<div class="path-track"><div class="path-fill" data-width="40%"></div></div>
</div>
<div class="path-row">
<div class="path-label">
<span>Contest Prep</span>
<span>20%</span>
</div>
<div class="path-track"><div class="path-fill" data-width="20%"></div></div>
</div>
</div>
</div>
</div>
<!-- Right Column -->
<div class="right-col">
<div class="col-card">
<div class="col-card-inner">
<div class="card-header">Leaderboard</div>
<div class="leaderboard-item">
<div class="rank">1</div>
<div class="user">Notch <i class="ri-vip-crown-fill" style="color:gold"></i></div>
<div class="score">99k</div>
</div>
<div class="leaderboard-item">
<div class="rank">2</div>
<div class="user">Jeb_ <i class="ri-medal-fill" style="color:silver"></i></div>
<div class="score">85k</div>
</div>
<div class="leaderboard-item">
<div class="rank">3</div>
<div class="user">Alex <i class="ri-medal-fill" style="color:#cd7f32"></i></div>
<div class="score">72k</div>
</div>
<div class="leaderboard-item">
<div class="rank">4</div>
<div class="user">Herobrine</div>
<div class="score">66k</div>
</div>
<div class="leaderboard-item highlight">
<div class="rank">5</div>
<div class="user">CodeMaster</div>
<div class="score">50k</div>
</div>
</div>
</div>
<div class="col-card">
<div class="col-card-inner">
<div class="card-header">Daily Quests</div>
<div class="task-item checked">
<div class="checkbox-custom"></div>
<div>Login</div>
<div class="task-xp">10XP</div>
</div>
<div class="task-item checked">
<div class="checkbox-custom"></div>
<div>Solve 1 Easy</div>
<div class="task-xp">20XP</div>
</div>
<div class="task-item checked">
<div class="checkbox-custom"></div>
<div>Review Code</div>
<div class="task-xp">15XP</div>
</div>
<div class="task-item">
<div class="checkbox-custom"></div>
<div>Solve 1 Hard</div>
<div class="task-xp">100XP</div>
</div>
<div class="task-item">
<div class="checkbox-custom"></div>
<div>Forum Post</div>
<div class="task-xp">30XP</div>
</div>
</div>
</div>
<div class="col-card">
<div class="col-card-inner" style="display:flex; justify-content:center; align-items:center;">
<div class="treasure-box" id="chestBtn">
<div class="chest-glow"></div>
<i class="ri-treasure-map-line chest-icon"></i>
<div style="margin-top:10px; font-size: 10px; color: var(--gold);">Daily Reward</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Reward Modal -->
<div class="modal" id="rewardModal">
<div class="modal-content">
<div class="modal-inner">
<h2>CHEST OPENED!</h2>
<i class="ri-diamond-fill reward-icon" style="color: var(--diamond);"></i>
<p>You found <span style="color:var(--gold); font-size: 20px;">500 XP</span></p>
<p style="font-size: 10px; color: #aaa; margin-top: 10px;">Rare Item Found: Diamond Boots</p>
<button class="close-btn" id="closeModal">CLAIM</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Animate XP Bar
const xpBar = document.getElementById('mainXP');
setTimeout(() => {
xpBar.style.width = xpBar.getAttribute('data-width');
}, 500);
// Animate Learning Paths
const paths = document.querySelectorAll('.path-fill');
paths.forEach(path => {
setTimeout(() => {
path.style.width = path.getAttribute('data-width');
}, 800);
});
// Activity Grid Generator
const activityGrid = document.getElementById('activityGrid');
for(let i=0; i<30; i++) {
const cell = document.createElement('div');
cell.className = 'activity-cell';
// Random activity level 0-4
const level = Math.random() > 0.3 ? Math.floor(Math.random() * 5) : 0;
if(level > 0) cell.setAttribute('data-level', level);
// Add tooltip on hover
cell.title = `Day ${30-i}: ${level*2} submissions`;
activityGrid.appendChild(cell);
}
// Task Checkbox Logic
const tasks = document.querySelectorAll('.task-item');
tasks.forEach(task => {
task.addEventListener('click', () => {
task.classList.toggle('checked');
// Add simple sound effect logic here if audio was allowed
});
});
// Treasure Chest Logic
const chestBtn = document.getElementById('chestBtn');
const modal = document.getElementById('rewardModal');
const closeBtn = document.getElementById('closeModal');
chestBtn.addEventListener('click', () => {
modal.style.display = 'flex';
});
closeBtn.addEventListener('click', () => {
modal.style.display = 'none';
});
// Close modal on outside click
modal.addEventListener('click', (e) => {
if(e.target === modal) {
modal.style.display = 'none';
}
});
});
</script>
</body>
</html>