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">
|
<div class="nav-links desktop-only">
|
||||||
<a href="/doc.html">文档</a>
|
<a href="/doc.html">文档</a>
|
||||||
<a href="/map.html">地图</a>
|
<a href="/map.html">地图</a>
|
||||||
|
<a href="/facilities.html">设施</a>
|
||||||
<a href="/photo.html">相册</a>
|
<a href="/photo.html">相册</a>
|
||||||
<a href="/stats.html">数据</a>
|
<a href="/stats.html">数据</a>
|
||||||
<a href="/sponsor.html">赞助</a>
|
<a href="/sponsor.html">赞助</a>
|
||||||
@@ -30,6 +31,7 @@ const Components = {
|
|||||||
<div class="mobile-menu-links">
|
<div class="mobile-menu-links">
|
||||||
<a href="/doc.html">文档</a>
|
<a href="/doc.html">文档</a>
|
||||||
<a href="/map.html">地图</a>
|
<a href="/map.html">地图</a>
|
||||||
|
<a href="/facilities.html">设施</a>
|
||||||
<a href="/photo.html">相册</a>
|
<a href="/photo.html">相册</a>
|
||||||
<a href="/stats.html">数据</a>
|
<a href="/stats.html">数据</a>
|
||||||
<a href="/sponsor.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