mirror of
https://github.com/Coldsmiles/infstarweb.git
synced 2026-04-23 02:30:41 +08:00
feat: Add facilities page with data, styles, and functionality for resource sharing
This commit is contained in:
474
css/pages/facilities.css
Normal file
474
css/pages/facilities.css
Normal file
@@ -0,0 +1,474 @@
|
||||
/* Page-Specific Styles for Facilities */
|
||||
|
||||
.facilities-hero {
|
||||
margin-top: 44px; /* Navbar height */
|
||||
padding: 100px 20px 60px;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%);
|
||||
color: #1d1d1f;
|
||||
}
|
||||
|
||||
.facilities-hero h1 {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.facilities-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
/* Controls - Redesigned */
|
||||
.controls-section {
|
||||
background: var(--card-bg);
|
||||
padding: 30px;
|
||||
border-radius: var(--radius-large);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.controls-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-box i {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 10px 16px 10px 44px;
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
border-radius: 12px;
|
||||
font-size: 15px;
|
||||
background: #f5f5f7;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
background: #fff;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1);
|
||||
}
|
||||
|
||||
.filters-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
border-top: 1px solid rgba(0,0,0,0.05);
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
min-width: 70px; /* Align labels */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.filter-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-tag {
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
padding: 6px 14px;
|
||||
border-radius: 18px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.filter-tag:hover {
|
||||
background: #f5f5f7;
|
||||
color: var(--text-primary);
|
||||
border-color: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.filter-tag.active {
|
||||
background: var(--text-primary); /* Use black/dark for active like Apple tags usually do, or accent */
|
||||
color: white;
|
||||
border-color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Facilities Grid & Cards */
|
||||
.facilities-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.facility-card {
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--radius-medium);
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.04); /* Softer shadow */
|
||||
transition: var(--transition);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
border: 1px solid rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.facility-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 32px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* Status Pill in Card */
|
||||
.status-indicator-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-indicator-badge.status-online { background-color: #e8fceb; color: #15803d; }
|
||||
.status-indicator-badge.status-maintenance { background-color: #fff8d6; color: #b45309; }
|
||||
.status-indicator-badge.status-offline { background-color: #feebeb; color: #b91c1c; }
|
||||
|
||||
.status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.status-online .status-dot { background-color: #22c55e; }
|
||||
.status-maintenance .status-dot { background-color: #f59e0b; }
|
||||
.status-offline .status-dot { background-color: #ef4444; }
|
||||
|
||||
|
||||
.card-intro {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.5;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.meta-tag {
|
||||
font-size: 11px;
|
||||
background: #f5f5f7;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.facility-modal-content {
|
||||
background-color: #fff;
|
||||
margin: 40px auto;
|
||||
border-radius: var(--radius-large);
|
||||
max-width: 720px;
|
||||
width: 90%;
|
||||
padding: 0; /* Remove padding to handle header separately */
|
||||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 24px;
|
||||
font-size: 24px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
z-index: 10;
|
||||
background: rgba(255,255,255,0.8);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.close-modal:hover {
|
||||
background: #f0f0f0;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 40px 40px 24px;
|
||||
background: linear-gradient(to bottom, #fff, #fafafa);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.modal-badges {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.large-badge {
|
||||
padding: 6px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.badge-status-online { background: #e8fceb; color: #15803d; }
|
||||
.badge-status-maintenance { background: #fff8d6; color: #b45309; }
|
||||
.badge-status-offline { background: #feebeb; color: #b91c1c; }
|
||||
.badge-type { background: #e0f2fe; color: #0369a1; }
|
||||
|
||||
.modal-body {
|
||||
padding: 30px 40px 50px;
|
||||
}
|
||||
|
||||
.modal-section {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.modal-section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-left: 4px solid var(--accent-color);
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.modal-section-title i {
|
||||
color: var(--accent-color);
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-intro {
|
||||
font-size: 18px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 30px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.map-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #fff;
|
||||
background: var(--accent-color);
|
||||
padding: 6px 16px;
|
||||
border-radius: 20px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
margin-left: 12px;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.map-link:hover {
|
||||
background: #005bb5;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.contributors-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.contributor-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eee;
|
||||
padding: 6px 14px;
|
||||
border-radius: 30px;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.02);
|
||||
}
|
||||
|
||||
.contributor-tag img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
/* Instructions & Notes */
|
||||
.instruction-content, .notes-content {
|
||||
background: #f9f9fa;
|
||||
padding: 24px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.instruction-content p, .notes-content p {
|
||||
font-size: 15px;
|
||||
margin-bottom: 12px;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.instruction-content p:last-child, .notes-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.instruction-content img, .notes-content img {
|
||||
max-width: 100%;
|
||||
border-radius: 12px;
|
||||
margin: 12px 0 20px;
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.no-results-message {
|
||||
text-align: center;
|
||||
padding: 60px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 16px;
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--radius-medium);
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.facilities-hero h1 {
|
||||
font-size: 32px;
|
||||
}
|
||||
.controls-header-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.search-box {
|
||||
max-width: 100%;
|
||||
}
|
||||
.filter-label {
|
||||
min-width: auto;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.facility-modal-content {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
.close-modal {
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
.modal-body {
|
||||
padding: 24px 24px 80px;
|
||||
}
|
||||
.modal-header {
|
||||
padding: 60px 24px 20px;
|
||||
}
|
||||
}
|
||||
106
data/facilities.json
Normal file
106
data/facilities.json
Normal file
@@ -0,0 +1,106 @@
|
||||
[
|
||||
{
|
||||
"id": "sand-duper",
|
||||
"title": "公共刷沙机",
|
||||
"intro": "位于末地的高效刷沙机,支持沙子、红沙、混凝土粉等重力方块的复制。",
|
||||
"type": "resource",
|
||||
"dimension": "end",
|
||||
"status": "online",
|
||||
"coordinates": {
|
||||
"x": 1200,
|
||||
"y": 64,
|
||||
"z": -500
|
||||
},
|
||||
"contributors": ["Alice", "Bob"],
|
||||
"instructions": [
|
||||
{"type": "text", "content": "1. 携带需要复制的重力方块(沙子、红沙、混凝土粉等)。"},
|
||||
{"type": "text", "content": "2. 前往末地主岛刷沙机位置。"},
|
||||
{"type": "text", "content": "3. 按照指示牌站在指定AFK点位。"},
|
||||
{"type": "text", "content": "4. 打开拉杆启动机器,确保不要移动。"},
|
||||
{"type": "text", "content": "5. 收集掉落到末地传送门的物品。"}
|
||||
],
|
||||
"notes": [
|
||||
{"type": "text", "content": "请勿在服务器卡顿严重时使用。"},
|
||||
{"type": "text", "content": "使用完毕后请务必关闭机器。"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gold-farm",
|
||||
"title": "基岩层猪人塔",
|
||||
"intro": "位于下界基岩上层的高效金粒与经验获取设施。",
|
||||
"type": "xp",
|
||||
"dimension": "nether",
|
||||
"status": "maintenance",
|
||||
"coordinates": {
|
||||
"x": 0,
|
||||
"y": 250,
|
||||
"z": 0
|
||||
},
|
||||
"contributors": ["Charlie", "David"],
|
||||
"instructions": [
|
||||
{"type": "text", "content": "1. 通过地狱交通前往0,0坐标并上到基岩层。"},
|
||||
{"type": "text", "content": "2. 站在中心挂机点。"},
|
||||
{"type": "text", "content": "3. 挥剑攻击猪人以获取经验。"}
|
||||
],
|
||||
"notes": [
|
||||
{"type": "text", "content": "目前收集系统正在改造中,只能获取经验,物品会丢失。"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "iron-farm",
|
||||
"title": "堆叠式刷铁机",
|
||||
"intro": "主世界出生点附近的高效铁锭生产设施。",
|
||||
"type": "resource",
|
||||
"dimension": "overworld",
|
||||
"status": "online",
|
||||
"coordinates": {
|
||||
"x": 100,
|
||||
"y": 150,
|
||||
"z": 100
|
||||
},
|
||||
"contributors": ["Eve"],
|
||||
"instructions": [
|
||||
{"type": "text", "content": "直接从箱子中拿取铁锭即可。"}
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"id": "nether-hub",
|
||||
"title": "地狱交通中心",
|
||||
"intro": "连接各个主要生物群系和资源点的冰道枢纽。",
|
||||
"type": "infrastructure",
|
||||
"dimension": "nether",
|
||||
"status": "online",
|
||||
"coordinates": {
|
||||
"x": 0,
|
||||
"y": 120,
|
||||
"z": 0
|
||||
},
|
||||
"contributors": ["Frank", "Grace", "Heidi"],
|
||||
"instructions": [
|
||||
{"type": "text", "content": "请携带船只以便快速移动。"},
|
||||
{"type": "text", "content": "请勿破坏冰道。"}
|
||||
],
|
||||
"notes": [
|
||||
{"type": "text", "content": "前往主岛的线路暂时封闭。"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "broken-farm",
|
||||
"title": "旧版刷怪塔",
|
||||
"intro": "已废弃的旧版本刷怪设施,不再维护。",
|
||||
"type": "xp",
|
||||
"dimension": "overworld",
|
||||
"status": "offline",
|
||||
"coordinates": {
|
||||
"x": -500,
|
||||
"y": 64,
|
||||
"z": -500
|
||||
},
|
||||
"contributors": ["OldPlayer"],
|
||||
"instructions": [],
|
||||
"notes": [
|
||||
{"type": "text", "content": "该设施已损坏,请前往新的刷怪塔。"}
|
||||
]
|
||||
}
|
||||
]
|
||||
122
facilities.html
Normal file
122
facilities.html
Normal file
@@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>共享资源 - 白鹿原 Minecraft 服务器</title>
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<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=Noto+Sans+SC:wght@400;500;700&family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="css/pages/facilities.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="navbar-component"></div>
|
||||
|
||||
<div class="facilities-hero">
|
||||
<h1>全服共享资源</h1>
|
||||
<p class="hero-subtitle">共同建设,共同分享,让生存更轻松。</p>
|
||||
</div>
|
||||
|
||||
<div class="facilities-container">
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="controls-section">
|
||||
<div class="controls-header-row">
|
||||
<h2 class="section-title">设施列表</h2>
|
||||
<div class="search-box">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="facility-search" placeholder="搜索设施名称...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filters-wrapper">
|
||||
<div class="filter-group">
|
||||
<div class="filter-label"><i class="fas fa-layer-group"></i> 类型</div>
|
||||
<div class="filter-tags" id="type-filters">
|
||||
<button class="filter-tag active" data-filter="all">全部</button>
|
||||
<button class="filter-tag" data-filter="resource"><i class="fas fa-cube"></i> 资源</button>
|
||||
<button class="filter-tag" data-filter="xp"><i class="fas fa-star"></i> 经验</button>
|
||||
<button class="filter-tag" data-filter="infrastructure"><i class="fas fa-road"></i> 基建</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<div class="filter-label"><i class="fas fa-globe"></i> 维度</div>
|
||||
<div class="filter-tags" id="dimension-filters">
|
||||
<button class="filter-tag active" data-filter="all">全部</button>
|
||||
<button class="filter-tag" data-filter="overworld">主世界</button>
|
||||
<button class="filter-tag" data-filter="nether">下界</button>
|
||||
<button class="filter-tag" data-filter="end">末地</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Facilities Grid -->
|
||||
<div class="facilities-grid" id="facilities-list">
|
||||
<!-- JS will inject cards here -->
|
||||
</div>
|
||||
<div id="no-results" class="no-results-message is-hidden">
|
||||
没有找到匹配的设施
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Facility Modal -->
|
||||
<div id="facility-modal" class="modal">
|
||||
<div class="modal-content facility-modal-content">
|
||||
<span class="close-modal">×</span>
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" id="modal-title">设施名称</h3>
|
||||
<div class="modal-badges" id="modal-badges">
|
||||
<!-- Badges injected by JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p class="modal-intro" id="modal-intro">设施简介...</p>
|
||||
|
||||
<div class="modal-section">
|
||||
<h4 class="modal-section-title"><i class="fas fa-map-marker-alt"></i> 位置信息</h4>
|
||||
<p>
|
||||
<span id="modal-dimension"></span>:
|
||||
<span id="modal-coords"></span>
|
||||
<a href="#" target="_blank" id="modal-map-link" class="map-link">
|
||||
<i class="fas fa-map-marked-alt"></i> 查看地图
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-section">
|
||||
<h4 class="modal-section-title"><i class="fas fa-users-cog"></i> 贡献/维护人员</h4>
|
||||
<div class="contributors-list" id="modal-contributors">
|
||||
<!-- Contributors injected by JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-section">
|
||||
<h4 class="modal-section-title"><i class="fas fa-book-open"></i> 使用说明</h4>
|
||||
<div class="instruction-content" id="modal-instructions">
|
||||
<!-- Instructions injected by JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-section">
|
||||
<h4 class="modal-section-title"><i class="fas fa-exclamation-triangle"></i> 注意事项</h4>
|
||||
<div class="notes-content" id="modal-notes">
|
||||
<!-- Notes injected by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer-component"></div>
|
||||
<script src="js/components.js"></script>
|
||||
<script src="js/facilities_script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -14,6 +14,7 @@ const Components = {
|
||||
<div class="nav-links desktop-only">
|
||||
<a href="/doc.html">文档</a>
|
||||
<a href="/map.html">地图</a>
|
||||
<a href="/facilities.html">设施</a>
|
||||
<a href="/photo.html">相册</a>
|
||||
<a href="/stats.html">数据</a>
|
||||
<a href="/sponsor.html">赞助</a>
|
||||
@@ -30,6 +31,7 @@ const Components = {
|
||||
<div class="mobile-menu-links">
|
||||
<a href="/doc.html">文档</a>
|
||||
<a href="/map.html">地图</a>
|
||||
<a href="/facilities.html">设施</a>
|
||||
<a href="/photo.html">相册</a>
|
||||
<a href="/stats.html">数据</a>
|
||||
<a href="/sponsor.html">赞助</a>
|
||||
|
||||
253
js/facilities_script.js
Normal file
253
js/facilities_script.js
Normal file
@@ -0,0 +1,253 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let facilitiesData = [];
|
||||
const grid = document.getElementById('facilities-list');
|
||||
const noResults = document.getElementById('no-results');
|
||||
const statusFilters = document.getElementById('type-filters'); // Wait, I named it type-filters in HTML
|
||||
const dimensionFilters = document.getElementById('dimension-filters');
|
||||
const searchInput = document.getElementById('facility-search');
|
||||
|
||||
// Modal Elements
|
||||
const modal = document.getElementById('facility-modal');
|
||||
const closeModal = document.querySelector('.close-modal');
|
||||
|
||||
// Initial State
|
||||
let currentFilters = {
|
||||
type: 'all',
|
||||
dimension: 'all',
|
||||
search: ''
|
||||
};
|
||||
|
||||
// 1. Fetch Data
|
||||
fetch('data/facilities.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
facilitiesData = data;
|
||||
renderGrid();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error loading facilities:', err);
|
||||
grid.innerHTML = '<p class="error">无法加载设施数据。</p>';
|
||||
});
|
||||
|
||||
// 2. Event Listeners
|
||||
|
||||
// Type Filter
|
||||
statusFilters.addEventListener('click', (e) => {
|
||||
if (e.target.tagName === 'BUTTON') {
|
||||
// Remove active class from siblings
|
||||
Array.from(statusFilters.children).forEach(btn => btn.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
|
||||
currentFilters.type = e.target.dataset.filter;
|
||||
renderGrid();
|
||||
}
|
||||
});
|
||||
|
||||
// Dimension Filter
|
||||
dimensionFilters.addEventListener('click', (e) => {
|
||||
if (e.target.tagName === 'BUTTON') {
|
||||
Array.from(dimensionFilters.children).forEach(btn => btn.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
|
||||
currentFilters.dimension = e.target.dataset.filter;
|
||||
renderGrid();
|
||||
}
|
||||
});
|
||||
|
||||
// Search
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
currentFilters.search = e.target.value.toLowerCase().trim();
|
||||
renderGrid();
|
||||
});
|
||||
|
||||
// Modal Close
|
||||
closeModal.addEventListener('click', () => {
|
||||
modal.style.display = 'none';
|
||||
document.body.style.overflow = 'auto'; // Enable scrolling
|
||||
});
|
||||
|
||||
window.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Render Functions
|
||||
function renderGrid() {
|
||||
grid.innerHTML = '';
|
||||
|
||||
const filtered = facilitiesData.filter(item => {
|
||||
const matchType = currentFilters.type === 'all' || item.type === currentFilters.type;
|
||||
const matchDim = currentFilters.dimension === 'all' || item.dimension === currentFilters.dimension;
|
||||
const matchSearch = !currentFilters.search ||
|
||||
item.title.toLowerCase().includes(currentFilters.search) ||
|
||||
item.intro.toLowerCase().includes(currentFilters.search);
|
||||
return matchType && matchDim && matchSearch;
|
||||
});
|
||||
|
||||
if (filtered.length === 0) {
|
||||
noResults.classList.remove('is-hidden');
|
||||
return;
|
||||
} else {
|
||||
noResults.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
filtered.forEach(item => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'facility-card';
|
||||
card.onclick = () => openModal(item);
|
||||
|
||||
const statusColor = getStatusColor(item.status);
|
||||
const statusText = getStatusText(item.status);
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">${item.title}</h3>
|
||||
<div class="status-indicator-badge status-${item.status}">
|
||||
<div class="status-dot"></div>
|
||||
<span>${statusText}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-intro">${item.intro}</p>
|
||||
<div class="card-meta">
|
||||
<span class="meta-tag">${getTypeText(item.type)}</span>
|
||||
<span class="meta-tag">${getDimensionText(item.dimension)}</span>
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function openModal(item) {
|
||||
// Populate specific fields
|
||||
document.getElementById('modal-title').innerText = item.title;
|
||||
document.getElementById('modal-intro').innerText = item.intro;
|
||||
|
||||
// Badges
|
||||
const badgesContainer = document.getElementById('modal-badges');
|
||||
badgesContainer.innerHTML = '';
|
||||
|
||||
// Status Badge
|
||||
const statusBadge = document.createElement('span');
|
||||
statusBadge.className = `badge badge-status-${item.status} large-badge`;
|
||||
statusBadge.innerHTML = `<i class="fas ${getStatusIcon(item.status)}"></i> ${getStatusText(item.status)}`;
|
||||
badgesContainer.appendChild(statusBadge);
|
||||
|
||||
// Type Badge
|
||||
const typeBadge = document.createElement('span');
|
||||
typeBadge.className = 'badge badge-type large-badge';
|
||||
typeBadge.innerHTML = `<i class="fas fa-cube"></i> ${getTypeText(item.type)}`;
|
||||
badgesContainer.appendChild(typeBadge);
|
||||
|
||||
// Location
|
||||
document.getElementById('modal-dimension').innerText = getDimensionText(item.dimension);
|
||||
const coords = item.coordinates;
|
||||
document.getElementById('modal-coords').innerText = `X: ${coords.x}, Y: ${coords.y}, Z: ${coords.z}`;
|
||||
|
||||
// Map Link
|
||||
const mapLink = document.getElementById('modal-map-link');
|
||||
const worldName = getMapWorldName(item.dimension);
|
||||
// Format: #world:X:Y:Z:88:0:0:0:1:flat
|
||||
mapLink.href = `https://mcmap.lunadeer.cn/#${worldName}:${coords.x}:${coords.y}:${coords.z}:500:0:0:0:1:flat`;
|
||||
|
||||
// Contributors
|
||||
const contribList = document.getElementById('modal-contributors');
|
||||
contribList.innerHTML = '';
|
||||
if (item.contributors && item.contributors.length > 0) {
|
||||
item.contributors.forEach(name => {
|
||||
const tag = document.createElement('div');
|
||||
tag.className = 'contributor-tag';
|
||||
// Using minotar for avatar
|
||||
tag.innerHTML = `<img src="https://minotar.net/avatar/${name}/20" alt="${name}">${name}`;
|
||||
contribList.appendChild(tag);
|
||||
});
|
||||
} else {
|
||||
contribList.innerHTML = '<span class="text-secondary">暂无记录</span>';
|
||||
}
|
||||
|
||||
// Instructions
|
||||
renderContentList(document.getElementById('modal-instructions'), item.instructions);
|
||||
|
||||
// Notes
|
||||
renderContentList(document.getElementById('modal-notes'), item.notes);
|
||||
|
||||
modal.style.display = 'block';
|
||||
document.body.style.overflow = 'hidden'; // Prevent scrolling background
|
||||
}
|
||||
|
||||
function renderContentList(container, list) {
|
||||
container.innerHTML = '';
|
||||
if (!list || list.length === 0) {
|
||||
container.innerHTML = '<p>无</p>';
|
||||
return;
|
||||
}
|
||||
list.forEach(block => {
|
||||
if (block.type === 'text') {
|
||||
const p = document.createElement('p');
|
||||
p.innerText = block.content;
|
||||
container.appendChild(p);
|
||||
} else if (block.type === 'image') {
|
||||
const img = document.createElement('img');
|
||||
img.src = block.content;
|
||||
img.loading = 'lazy';
|
||||
container.appendChild(img);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function getStatusText(status) {
|
||||
const map = {
|
||||
'online': '正常运行',
|
||||
'maintenance': '维护中',
|
||||
'offline': '暂时失效'
|
||||
};
|
||||
return map[status] || status;
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
const map = {
|
||||
'online': 'status-online',
|
||||
'maintenance': 'status-maintenance',
|
||||
'offline': 'status-offline'
|
||||
};
|
||||
return map[status] || '';
|
||||
}
|
||||
|
||||
function getStatusIcon(status) {
|
||||
const map = {
|
||||
'online': 'fa-check-circle',
|
||||
'maintenance': 'fa-wrench',
|
||||
'offline': 'fa-times-circle'
|
||||
};
|
||||
return map[status] || 'fa-info-circle';
|
||||
}
|
||||
|
||||
function getTypeText(type) {
|
||||
const map = {
|
||||
'resource': '资源类',
|
||||
'xp': '经验类',
|
||||
'infrastructure': '基础设施'
|
||||
};
|
||||
return map[type] || type;
|
||||
}
|
||||
|
||||
function getDimensionText(dim) {
|
||||
const map = {
|
||||
'overworld': '主世界',
|
||||
'nether': '下界',
|
||||
'end': '末地'
|
||||
};
|
||||
return map[dim] || dim;
|
||||
}
|
||||
|
||||
function getMapWorldName(dim) {
|
||||
const map = {
|
||||
'overworld': 'world',
|
||||
'nether': 'world_nether',
|
||||
'end': 'world_the_end'
|
||||
};
|
||||
return map[dim] || 'world';
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user