diff --git a/backend/include/csp/controllers/admin_controller.h b/backend/include/csp/controllers/admin_controller.h index 452084b..4520f70 100644 --- a/backend/include/csp/controllers/admin_controller.h +++ b/backend/include/csp/controllers/admin_controller.h @@ -13,6 +13,9 @@ class AdminController : public drogon::HttpController { 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 { void updateUserRating(const drogon::HttpRequestPtr& req, std::function&& cb, int64_t user_id); + void deleteUser(const drogon::HttpRequestPtr& req, + std::function&& cb, + int64_t user_id); void listRedeemItems(const drogon::HttpRequestPtr& req, std::function&& cb); diff --git a/backend/include/csp/services/user_service.h b/backend/include/csp/services/user_service.h index 7aaf791..35c4959 100644 --- a/backend/include/csp/services/user_service.h +++ b/backend/include/csp/services/user_service.h @@ -22,6 +22,7 @@ class UserService { std::vector 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_; diff --git a/backend/src/controllers/admin_controller.cc b/backend/src/controllers/admin_controller.cc index 865ee60..24b0b83 100644 --- a/backend/src/controllers/admin_controller.cc +++ b/backend/src/controllers/admin_controller.cc @@ -192,6 +192,42 @@ void AdminController::updateUserRating( } } +void AdminController::deleteUser( + const drogon::HttpRequestPtr& req, + std::function&& 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&& cb) { diff --git a/backend/src/controllers/meta_controller.cc b/backend/src/controllers/meta_controller.cc index 8ed7874..03adc16 100644 --- a/backend/src/controllers/meta_controller.cc +++ b/backend/src/controllers/meta_controller.cc @@ -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"] = "管理员修改积分兑换物品"; diff --git a/backend/src/controllers/problem_controller.cc b/backend/src/controllers/problem_controller.cc index 400e9f8..e7c479e 100644 --- a/backend/src/controllers/problem_controller.cc +++ b/backend/src/controllers/problem_controller.cc @@ -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())); diff --git a/backend/src/services/user_service.cc b/backend/src/services/user_service.cc index 613544a..4f96f27 100644 --- a/backend/src/services/user_service.cc +++ b/backend/src/services/user_service.cc @@ -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 diff --git a/frontend/src/app/api/image-cache/route.ts b/frontend/src/app/api/image-cache/route.ts index c10e83d..c4c97be 100644 --- a/frontend/src/app/api/image-cache/route.ts +++ b/frontend/src/app/api/image-cache/route.ts @@ -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 = { + ".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); } diff --git a/frontend/src/app/backend-logs/page.tsx b/frontend/src/app/backend-logs/page.tsx index e75a18d..3fa8b6c 100644 --- a/frontend/src/app/backend-logs/page.tsx +++ b/frontend/src/app/backend-logs/page.tsx @@ -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([]); const [triggerLoading, setTriggerLoading] = useState(false); const [triggerMsg, setTriggerMsg] = useState(""); + const [users, setUsers] = useState([]); + const [deleteUserId, setDeleteUserId] = useState(null); + const [userMsg, setUserMsg] = useState(""); const refresh = async () => { if (!isAdmin || !token) return; setLoading(true); setError(""); try { - const data = await apiFetch( - `/api/v1/backend/logs?limit=${limit}&running_limit=20&queued_limit=100`, - {}, - token - ); + const [data, usersData] = await Promise.all([ + apiFetch( + `/api/v1/backend/logs?limit=${limit}&running_limit=20&queued_limit=100`, + {}, + token + ), + apiFetch("/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 &&

{error}

} {triggerMsg &&

{triggerMsg}

} + {userMsg &&

{userMsg}

}

{tx( "系统已自动单线程异步处理待队列任务,无需手工点击;上方按钮仅用于立即手动补全。", "System auto-processes queued jobs in single-thread async mode; the button above is only for manual trigger." )}

+ +
+
+

{tx("用户管理(可删除)", "User Management (Delete Supported)")}

+

+ {tx("总用户", "Total users")} {users.length} +

+
+ +
+ {users.map((user) => ( +
+

+ #{user.id} · {user.username} +

+

+ Rating {user.rating} · {tx("创建", "Created")} {fmtTs(user.created_at)} +

+ +
+ ))} + {!loading && users.length === 0 && ( +

{tx("暂无用户数据", "No users found")}

+ )} +
+ +
+ + + + + + + + + + + + {users.map((user) => ( + + + + + + + + ))} + {!loading && users.length === 0 && ( + + + + )} + +
ID{tx("用户名", "Username")}Rating{tx("创建时间", "Created At")}{tx("操作", "Action")}
{user.id}{user.username}{user.rating}{fmtTs(user.created_at)} + +
+ {tx("暂无用户数据", "No users found")} +
+
+
+

{tx("正在处理(Running)", "Running Jobs")}

diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 5b334ca..538dfbc 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -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; } diff --git a/frontend/src/app/me/page.tsx b/frontend/src/app/me/page.tsx index c174466..213c98d 100644 --- a/frontend/src/app/me/page.tsx +++ b/frontend/src/app/me/page.tsx @@ -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 && (
-

ID: {profile.id}

-

{tx("用户名", "Username")}: {profile.username}

-

Rating: {profile.rating}

-

{tx("创建时间", "Created At")}: {fmtTs(profile.created_at)}

+
+ +
+

ID: {profile.id}

+

{tx("用户名", "Username")}: {profile.username}

+

Rating: {profile.rating}

+

{tx("创建时间", "Created At")}: {fmtTs(profile.created_at)}

+

+ {tx("默认像素头像按账号随机生成,可作为主题角色形象。", "Default pixel avatar is randomly generated by account as your theme character.")} +

+
+
)} diff --git a/frontend/src/app/problems/[id]/page.tsx b/frontend/src/app/problems/[id]/page.tsx index 5af4dd0..bf3f428 100644 --- a/frontend/src/app/problems/[id]/page.tsx +++ b/frontend/src/app/problems/[id]/page.tsx @@ -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(null); const [solutionMsg, setSolutionMsg] = useState(""); const [printAnswerMarkdown, setPrintAnswerMarkdown] = useState(""); + const draftLatestRef = useRef<{ code: string; stdin: string }>({ + code: starterCode, + stdin: defaultRunInput, + }); + const draftLastSavedSigRef = useRef( + buildDraftSignature(starterCode, defaultRunInput) + ); + const draftAutoSavingRef = useRef(false); const llmProfile = useMemo(() => { 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(`/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")} -
+                          
                             {normalizeCodeText(item.code_cpp)}
                           
diff --git a/frontend/src/components/app-nav.tsx b/frontend/src/components/app-nav.tsx index 5103452..856b183 100644 --- a/frontend/src/components/app-nav.tsx +++ b/frontend/src/components/app-nav.tsx @@ -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(readToken())); const [isAdmin, setIsAdmin] = useState(false); + const [meProfile, setMeProfile] = useState(null); const [menuOpen, setMenuOpen] = useState(false); const [desktopOpenGroup, setDesktopOpenGroup] = useState(null); const desktopMenuRef = useRef(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("/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 (
@@ -188,7 +210,11 @@ export function AppNav() { {group.label} {opened && ( -
+
{group.links.map((item) => { const linkActive = isActivePath(pathname, item.href); return ( @@ -207,6 +233,64 @@ export function AppNav() { ); })} + {group.key === "account" && ( +
+ + +
+
+ + {hasToken ? t("nav.logged_in") : t("nav.logged_out")} + + {hasToken && ( + <> + + {meProfile?.username && ( + {meProfile.username} + )} + + )} +
+ {hasToken && ( + + )} +
+
+ )}
)}
@@ -274,7 +358,7 @@ export function AppNav() { -
+
+
+ + +
+
+ + +
+
+ 1
2
3
4
5
6
7
8
9
10
11 +
+
+class Solution { +public: + vector<int> twoSum(vector<int>& nums, int target) { + unordered_map<int, int> hash; + + for (int i = 0; i < nums.size(); i++) { + int complement = target - nums[i]; + + if (hash.find(complement) != hash.end()) { + return {hash[complement], i}; + } + hash[nums[i]] = i; + } + return {}; + } +};
+
+ + +
+ Ready + + 0ms + 0KB +
+
COMBO x5
+
+ + +
+
+ + Test Console + +
+
+ + +
+
+
+
Case 1
+
Input
+
nums = [2,7,11,15], target = 9
+
Expected
+
[0,1]
+
+
+
Case 2
+
Input
+
nums = [3,2,4], target = 6
+
Expected
+
[1,2]
+
+
+
Case 3
+
Input
+
nums = [3,3], target = 6
+
Expected
+
[0,1]
+
+
+
+ + + + +
+50 XP
+ + + + \ No newline at end of file diff --git a/ref/CSP-Minecraft-UI-Kit/html/pages/contest.html b/ref/CSP-Minecraft-UI-Kit/html/pages/contest.html new file mode 100644 index 0000000..ec6d3cb --- /dev/null +++ b/ref/CSP-Minecraft-UI-Kit/html/pages/contest.html @@ -0,0 +1,905 @@ + + + + + + Minecraft Contest Page + + + + + + + + + +
+ + +
+
+
ONGOING
+
UPCOMING
+
FINISHED
+
+ +
+ +
+
+
+
+

Weekly Challenge #42

+
+ Ends in 2h + + 500 XP +
+
+
+
+ +
+
+ +
+
+
+
+

Bi-Weekly Rumble #15

+
+ Sat, 14:00 + + 350 XP +
+
+
+
+ +
+
+ +
+
+
+
+

Diamond League Qualifiers

+
+ Sun, 10:00 + + 1000 XP + BADGE +
+
+
+
+ +
+
+
+ + +
+
+

LEADERBOARD

+
+ UPDATING LIVE +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#PLAYERSCORETIMESTATUS
1Notch_Real40000:45:12AC
2Alex_Pro38000:52:30AC
3CreeperAwMan35001:05:00AC
4Enderman_tp32001:10:22AC
5RedstoneEng30001:15:45AC
6Miner6428001:20:10AC
7STEVE_DEV (YOU)25001:30:00WA (1)
8ZombieBoi20001:35:12AC
+
+ +
+ + +
+ + +
+
+ MY STATS + +
+
+ Participated: + 24 +
+
+ Best Rank: + #3 🥉 +
+
+ Total Points: + 1,250 +
+
+ Rating: + 1650 (Diamond II) +
+ +
+
+ +
2
+
+
+ +
5
+
+
+ +
8
+
+
+
+ + +
+
+ RULES + +
+
+
    +
  • Duration: 3 Hours
  • +
  • Penalty: +5 mins per WA
  • +
  • Languages: Java, C++, Python
  • +
  • Plagiarism check is ACTIVE
  • +
  • Do not break obsidian blocks
  • +
+
+
+ + +
+
+ HISTORY + +
+ +
+
+
+
+
+
2023-10-15
+
Weekly #41 - Rank #15
+
+
+
+
+
+
+
2023-10-08
+
Weekly #40 - Rank #2 🥈
+
+
+
+
+
+
+
2023-10-01
+
Weekly #39 - Rank #3 🥉
+
+
+
+
+
+
+
2023-09-24
+
Weekly #38 - Rank #45
+
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/ref/CSP-Minecraft-UI-Kit/html/pages/login.html b/ref/CSP-Minecraft-UI-Kit/html/pages/login.html new file mode 100644 index 0000000..b2d70c9 --- /dev/null +++ b/ref/CSP-Minecraft-UI-Kit/html/pages/login.html @@ -0,0 +1,605 @@ + + + + + + CSP Learning Platform - Minecraft Login + + + + + + + + + + + +
+ +
+ +
+ +
+
+ Online Players: 12,002 +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/ref/CSP-Minecraft-UI-Kit/html/pages/problem-library.html b/ref/CSP-Minecraft-UI-Kit/html/pages/problem-library.html new file mode 100644 index 0000000..e0c0ac6 --- /dev/null +++ b/ref/CSP-Minecraft-UI-Kit/html/pages/problem-library.html @@ -0,0 +1,849 @@ + + + + + + Minecraft Problem Library + + + + + + + +
+ + + + +
+ + +
+
+
TOTAL PROBLEMS
+
1,556
+
+
+
COMPLETED
+
128
+
+
+
PASS RATE
+
85%
+
+
+ +
+
+ + + +
+ +
+ + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
STSIDTITLERATEDIFFTAGSACT
1001A+B Problem95% +
+ + + +
+
MathSOLVE
1002Fibonacci Sequence45% +
+ + + +
+
DPSOLVE
+
+ + +
+ + + +
+ + + + \ No newline at end of file diff --git a/ref/CSP-Minecraft-UI-Kit/html/pages/profile-center.html b/ref/CSP-Minecraft-UI-Kit/html/pages/profile-center.html new file mode 100644 index 0000000..26d2d43 --- /dev/null +++ b/ref/CSP-Minecraft-UI-Kit/html/pages/profile-center.html @@ -0,0 +1,896 @@ + + + + + + Minecraft Profile Center + + + + + + + + + + +
+ +
+
+ + Avatar +
+ +
+
+

CodeMaster2024

+
+
Passionate problem solver
+
+
Lv.25
+
Algorithm Expert
+
+ +
+
+
8,450 / 10,000 XP
+
+
+ +
+
+
128
+
Problems
+
+
+
456
+
Submits
+
+
+
85%
+
Acceptance
+
+
+
15🔥
+
Streak
+
+
+
+ + +
+ +
+
+
+
Difficulty Stats
+
+
+ Easy + Med + Hard +
+
+
+ +
+
+
Activity Log
+
Last 30 Days
+
+ +
+
+
+ +
+
+
Timeline
+
+
+
Solved "Two Sum"
+
2 hours ago
+
+
+
Failed "Dijkstra"
+
5 hours ago
+
+
+
Badge Unlocked
+
1 day ago
+
+
+
+
+
+ + +
+
+
+
Achievement Wall
+
+ + +
+ +
+
Novice Slayer
+
Solve 10 Easy
+
+50 XP
+
+
+
+ +
+
Iron Will
+
Solve 50 Medium
+
+200 XP
+
+
+
+ +
LOCKED: Diamond Blade
+
+
+ +
+
Champion
+
Win a Contest
+
+1000 XP
+
+
+
+ +
+
Scholar
+
Read 100 Articles
+
+100 XP
+
+
+ + +
+ +
+
Alchemist
+
Optimize 10 times
+
+150 XP
+
+
+
+ +
LOCKED: Inferno
+
+
+ +
+
Speedster
+
Solve in 5 mins
+
+50 XP
+
+
+
+ +
LOCKED: Star Player
+
+
+ +
LOCKED: Explorer
+
+ + +
+ +
LOCKED: Guardian
+
+
+ +
LOCKED: Builder
+
+
+ +
LOCKED: Bug Hunter
+
+
+ +
LOCKED: Navigator
+
+
+ +
LOCKED: King
+
+
+
+
+ +
+
+
Learning Path
+
+
+ Basics + 100% +
+
+
+
+
+ Algorithms + 75% +
+
+
+
+
+ Data Structures + 60% +
+
+
+
+
+ Advanced DP + 40% +
+
+
+
+
+ Contest Prep + 20% +
+
+
+
+
+
+ + +
+
+
+
Leaderboard
+
+
1
+
Notch
+
99k
+
+
+
2
+
Jeb_
+
85k
+
+
+
3
+
Alex
+
72k
+
+
+
4
+
Herobrine
+
66k
+
+
+
5
+
CodeMaster
+
50k
+
+
+
+ +
+
+
Daily Quests
+
+
+
Login
+
10XP
+
+
+
+
Solve 1 Easy
+
20XP
+
+
+
+
Review Code
+
15XP
+
+
+
+
Solve 1 Hard
+
100XP
+
+
+
+
Forum Post
+
30XP
+
+
+
+ +
+
+
+
+ +
Daily Reward
+
+
+
+
+
+
+ + + + + + + \ No newline at end of file