diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index c75624e..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,68 +0,0 @@ -# Project Guidelines - -## Code Style -- This repo is framework-free: plain HTML + CSS + vanilla JavaScript — no frameworks, modules, or build tooling. -- Keep existing naming style: `camelCase` for JS functions/variables and descriptive DOM ids/classes (for example `fetchCrowdfunding`, `setupMobileMenu`, `#players-grid`). -- Preserve current formatting patterns: 4-space indentation in HTML/CSS, simple function-based JS. -- Shared JS utilities use the global-object pattern (e.g. `DataUtils` in `js/data_utils.js`) — do not convert to ES modules. -- Reuse shared tokens in `css/style.css` (`:root` variables such as `--bg-color`, `--accent-color`) instead of introducing new ad-hoc styles. -- Keep page-local style overrides scoped to their page blocks; avoid broad visual refactors unless explicitly requested. -- Keep user-facing copy in Chinese unless the surrounding section is already English. - -## Architecture -- Public pages are static entry points: - - `index.html` + `js/script.js`: landing page, sponsors, fundraising progress, live server status. - - `sponsor.html` + `js/sponsor_script.js`: donation total, sponsor list, search/filter, donation modal. - - `stats.html` + `js/stats_script.js`: player leaderboard + searchable player cards + modal details. - - `join.html` + `js/join_script.js`: 4-step wizard (convention → agree → device → tutorial). Uses `js/marked.min.js` to render `data/convention.md` as HTML. - - `facilities.html` + `js/facilities_script.js`: searchable/filterable catalog of server facilities from `data/facilities.json`. Supports Bilibili video embeds (BV IDs) in notes. - - `doc.html`, `map.html`, `photo.html`: iframe wrappers (navbar + fullscreen iframe to external hosts). No page-specific JS beyond `js/components.js`. -- Shared utilities: - - `js/components.js`: injected into `#navbar-component` / `#footer-component`, handles mobile menu & current-link highlighting. - - `js/data_utils.js`: `DataUtils.parseSponsorsText()` and `DataUtils.buildSponsorTotals()` — used by both `index.html` and `sponsor.html`. -- Shared visual system lives in `css/style.css`; page-specific styles live in `css/pages/` (`join.css`, `facilities.css`, `sponsor.css`, `stats.css`). -- Every page includes ` - - - - - - -
- -
- - -
-
-
-

公告列表

- -
- -
- -
-
-
类别
-
- - - - -
-
-
-
- - -
- -
- - -
- - - - - - - - - - - - diff --git a/css/pages/announcements.css b/css/pages/announcements.css deleted file mode 100644 index aef3d31..0000000 --- a/css/pages/announcements.css +++ /dev/null @@ -1,1124 +0,0 @@ -/* Page-Specific Styles for Announcements */ - -.announcements-hero-bg { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/69267755e14e3.png'); -} - -/* Container */ -.announcements-container { - max-width: 900px; - margin: 0 auto; - padding: 40px 20px; -} - -/* Controls */ -.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; - flex-wrap: wrap; - gap: 20px; -} - -.title-with-action { - display: flex; - align-items: center; - gap: 16px; -} - -.section-title { - font-size: 24px; - font-weight: 600; - color: var(--text-primary); - margin: 0; -} - -.btn-add-announcement { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 8px 18px; - background: var(--accent-color); - color: #fff; - border: none; - border-radius: 20px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - white-space: nowrap; -} - -.btn-add-announcement:hover { - background: #005bb5; - transform: translateY(-1px); -} - -.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; - margin-top: 24px; -} - -.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; - 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); - color: white; - border-color: var(--text-primary); -} - -/* ========== Timeline ========== */ -.timeline { - position: relative; - padding-left: 32px; -} - -.timeline::before { - content: ''; - position: absolute; - left: 7px; - top: 0; - bottom: 0; - width: 2px; - background: linear-gradient(to bottom, var(--accent-color), rgba(0,113,227,0.1)); - border-radius: 2px; -} - -.timeline-item { - position: relative; - margin-bottom: 24px; -} - -.timeline-item::before { - content: ''; - position: absolute; - left: -32px; - top: 28px; - width: 16px; - height: 16px; - border-radius: 50%; - background: #fff; - border: 3px solid var(--accent-color); - z-index: 1; - transition: var(--transition); -} - -.timeline-item.category-activity::before { - border-color: var(--brand-green); -} - -.timeline-item.category-maintenance::before { - border-color: #f59e0b; -} - -.timeline-item.category-other::before { - border-color: #8b5cf6; -} - -/* Card */ -.announcement-card { - background: var(--card-bg); - border-radius: var(--radius-medium); - box-shadow: 0 2px 12px rgba(0,0,0,0.04); - border: 1px solid rgba(0,0,0,0.03); - overflow: hidden; - transition: var(--transition); - cursor: pointer; -} - -.announcement-card:hover { - box-shadow: 0 8px 28px rgba(0,0,0,0.08); - transform: translateY(-2px); -} - -.announcement-card.expanded { - cursor: default; - transform: none; - box-shadow: 0 8px 32px rgba(0,0,0,0.1); - border-color: rgba(0,0,0,0.06); -} - -.card-summary { - padding: 24px 28px; - display: flex; - align-items: center; - gap: 16px; -} - -.announcement-card.expanded .card-summary { - border-bottom: 1px solid rgba(0,0,0,0.05); - background: linear-gradient(to bottom, #fff, #fafafa); -} - -.card-summary-main { - flex: 1; - min-width: 0; -} - -.card-summary-top { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 6px; - flex-wrap: wrap; -} - -.category-badge { - display: inline-flex; - align-items: center; - gap: 5px; - font-size: 11px; - font-weight: 600; - padding: 3px 10px; - border-radius: 10px; - white-space: nowrap; - flex-shrink: 0; -} - -.category-badge.badge-activity { - background: #e8fceb; - color: #15803d; -} - -.category-badge.badge-maintenance { - background: #fff8d6; - color: #b45309; -} - -.category-badge.badge-other { - background: #f3f0ff; - color: #6d28d9; -} - -.announcement-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - line-height: 1.4; - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.announcement-card.expanded .announcement-title { - white-space: normal; - overflow: visible; -} - -.announcement-intro { - font-size: 14px; - color: var(--text-secondary); - line-height: 1.5; - margin: 4px 0 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.announcement-card.expanded .announcement-intro { - white-space: normal; - overflow: visible; -} - -.card-summary-time { - font-size: 13px; - color: var(--text-secondary); - white-space: nowrap; - flex-shrink: 0; - display: flex; - align-items: center; - gap: 6px; -} - -.expand-icon { - color: var(--text-secondary); - font-size: 14px; - transition: transform 0.3s ease; - flex-shrink: 0; - opacity: 0.4; -} - -.announcement-card.expanded .expand-icon { - transform: rotate(180deg); - opacity: 0.6; -} - -/* Expanded Detail */ -.card-detail { - max-height: 0; - overflow: hidden; - transition: max-height 0.45s cubic-bezier(0.25, 1, 0.5, 1), padding 0.35s ease; - padding: 0 28px; -} - -.announcement-card.expanded .card-detail { - max-height: 2000px; - padding: 28px 28px 32px; -} - -.detail-content { - line-height: 1.8; - font-size: 15px; - color: var(--text-primary); -} - -.detail-content p { - margin-bottom: 14px; -} - -.detail-content p:last-child { - margin-bottom: 0; -} - -.detail-content img { - max-width: 100%; - border-radius: 12px; - margin: 12px 0 16px; - border: 1px solid rgba(0,0,0,0.05); - box-shadow: 0 4px 12px rgba(0,0,0,0.05); -} - -.detail-content .video-embed-wrapper { - position: relative; - width: 100%; - padding-bottom: 56.25%; - margin: 12px 0 16px; - border-radius: 12px; - overflow: hidden; - background: #000; - box-shadow: 0 4px 12px rgba(0,0,0,0.08); -} - -.detail-content .video-embed-wrapper iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: none; -} - -.detail-action-btn-row { - margin-top: 20px; - padding-top: 16px; - border-top: 1px solid rgba(0,0,0,0.05); - display: flex; - justify-content: flex-end; - gap: 10px; -} - -.btn-share-announcement { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 6px 16px; - background: transparent; - color: var(--text-secondary); - border: 1.5px solid rgba(0,0,0,0.12); - border-radius: 18px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - white-space: nowrap; -} - -.btn-share-announcement:hover { - color: var(--accent-color); - border-color: var(--accent-color); - background: rgba(0,113,227,0.04); -} - -.btn-share-announcement.shared { - color: #15803d; - border-color: #34c759; - background: #e8fceb; -} - -.btn-edit-announcement { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 6px 16px; - background: transparent; - color: var(--accent-color); - border: 1.5px solid var(--accent-color); - border-radius: 18px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - white-space: nowrap; -} - -.btn-edit-announcement:hover { - background: var(--accent-color); - color: #fff; -} - -/* No results */ -.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; -} - -/* Edit mode hidden class */ -.edit-hidden { - display: none !important; -} - -/* ========== Editor 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); -} - -.editor-modal-content { - background-color: #fff; - margin: 20px auto; - border-radius: var(--radius-large); - max-width: 1100px; - width: 95%; - padding: 0; - box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3); - position: relative; - max-height: calc(100vh - 40px); - overflow: hidden; - display: flex; - flex-direction: column; -} - -.close-editor-modal, -.close-json-modal { - position: absolute; - top: 16px; - right: 20px; - font-size: 24px; - color: var(--text-secondary); - cursor: pointer; - transition: 0.2s; - z-index: 10; - background: rgba(255,255,255,0.9); - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; -} - -.close-editor-modal:hover, -.close-json-modal:hover { - background: #f0f0f0; - color: var(--text-primary); -} - -.editor-modal-header { - padding: 20px 28px; - border-bottom: 1px solid rgba(0,0,0,0.08); - background: linear-gradient(to bottom, #fff, #fafafa); - border-radius: var(--radius-large) var(--radius-large) 0 0; - flex-shrink: 0; -} - -.editor-modal-header h3 { - font-size: 22px; - font-weight: 700; - display: flex; - align-items: center; - gap: 10px; -} - -.editor-layout { - display: flex; - flex: 1; - overflow: hidden; - min-height: 0; -} - -.editor-preview { - flex: 0 0 45%; - display: flex; - flex-direction: column; - border-right: 1px solid rgba(0,0,0,0.08); - background: #f5f5f7; -} - -.editor-panel-title { - font-size: 12px; - font-weight: 700; - color: var(--text-secondary); - padding: 14px 24px; - border-bottom: 1px solid rgba(0,0,0,0.05); - display: flex; - align-items: center; - gap: 8px; - text-transform: uppercase; - letter-spacing: 0.6px; - flex-shrink: 0; - background: rgba(255,255,255,0.6); -} - -.editor-preview-content { - flex: 1; - overflow-y: auto; - padding: 20px; -} - -.editor-preview-content::-webkit-scrollbar, -.editor-form-scroll::-webkit-scrollbar { - width: 5px; -} - -.editor-preview-content::-webkit-scrollbar-track, -.editor-form-scroll::-webkit-scrollbar-track { - background: transparent; -} - -.editor-preview-content::-webkit-scrollbar-thumb, -.editor-form-scroll::-webkit-scrollbar-thumb { - background: rgba(0,0,0,0.12); - border-radius: 10px; -} - -/* Preview Card in Editor */ -.preview-announcement { - background: #fff; - border-radius: 16px; - box-shadow: 0 2px 12px rgba(0,0,0,0.06); - overflow: hidden; -} - -.preview-header { - padding: 24px 24px 16px; - background: linear-gradient(to bottom, #fff, #fafafa); - border-bottom: 1px solid rgba(0,0,0,0.05); -} - -.preview-title { - font-size: 22px; - font-weight: 700; - margin-bottom: 12px; - line-height: 1.2; -} - -.preview-meta { - display: flex; - align-items: center; - gap: 12px; - flex-wrap: wrap; -} - -.preview-body { - padding: 20px 24px 24px; -} - -.preview-body .detail-content p { - margin-bottom: 12px; -} - -.preview-intro-text { - font-size: 15px; - line-height: 1.6; - color: var(--text-secondary); - margin-bottom: 16px; - font-style: italic; -} - -/* Editor Form */ -.editor-form { - flex: 0 0 55%; - display: flex; - flex-direction: column; - min-width: 0; -} - -.editor-form-scroll { - flex: 1; - overflow-y: auto; - padding: 24px 28px 40px; -} - -.form-group { - margin-bottom: 22px; -} - -.form-group > label { - display: block; - font-size: 12px; - font-weight: 700; - color: var(--text-secondary); - margin-bottom: 8px; - text-transform: uppercase; - letter-spacing: 0.4px; -} - -.form-group input[type="text"], -.form-group input[type="date"], -.form-group textarea { - width: 100%; - padding: 12px 16px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - font-size: 14px; - font-family: inherit; - background-color: #f9f9fa; - transition: all 0.2s ease; - color: var(--text-primary); - box-sizing: border-box; -} - -.form-group input:focus, -.form-group textarea:focus { - outline: none; - border-color: var(--accent-color); - background-color: #fff; - box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1); -} - -.form-group textarea { - resize: vertical; - min-height: 60px; -} - -.form-row { - display: flex; - gap: 14px; -} - -.form-row .form-group { - flex: 1; -} - -/* Custom Select */ -.custom-select { - position: relative; - width: 100%; - user-select: none; - font-size: 14px; -} - -.custom-select-trigger { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - padding: 12px 16px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - background-color: #f9f9fa; - color: var(--text-primary); - cursor: pointer; - transition: all 0.2s ease; - box-sizing: border-box; -} - -.custom-select-trigger i { - color: var(--text-secondary); - font-size: 12px; - transition: transform 0.3s ease; -} - -.custom-select:hover .custom-select-trigger { - background-color: #fff; - border-color: rgba(0,0,0,0.2); -} - -.custom-select.open .custom-select-trigger { - border-color: var(--accent-color); - background-color: #fff; - box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1); -} - -.custom-select.open .custom-select-trigger i { - transform: rotate(180deg); -} - -.custom-select-options { - position: absolute; - top: calc(100% + 8px); - left: 0; - right: 0; - background: #fff; - border-radius: 12px; - box-shadow: 0 10px 40px rgba(0,0,0,0.15); - border: 1px solid rgba(0,0,0,0.08); - opacity: 0; - visibility: hidden; - transform: translateY(-10px); - transition: all 0.2s cubic-bezier(0.25, 1, 0.5, 1); - z-index: 100; - padding: 8px; -} - -.custom-select.open .custom-select-options { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.custom-option { - padding: 10px 14px; - border-radius: 8px; - cursor: pointer; - transition: background 0.2s, color 0.2s; - color: var(--text-primary); - margin-bottom: 2px; -} - -.custom-option:last-child { - margin-bottom: 0; -} - -.custom-option:hover { - background: #f5f5f7; -} - -.custom-option.selected { - background: #e0f2fe; - color: #0369a1; - font-weight: 600; -} - -/* Sortable Items */ -.sortable-list { - min-height: 8px; - margin-bottom: 10px; -} - -.sortable-item { - display: flex; - align-items: flex-start; - gap: 10px; - padding: 10px 12px; - margin-bottom: 8px; - background: #fff; - border: 1.5px solid rgba(0,0,0,0.08); - border-radius: 12px; - transition: box-shadow 0.2s, border-color 0.2s, opacity 0.2s; -} - -.sortable-item:last-child { - margin-bottom: 0; -} - -.sortable-item:hover { - border-color: rgba(0,0,0,0.15); - box-shadow: 0 2px 8px rgba(0,0,0,0.04); -} - -.sortable-item.dragging { - opacity: 0.4; - border-color: var(--accent-color); -} - -.sortable-item.drag-over { - border-color: var(--accent-color); - box-shadow: 0 0 0 2px rgba(0, 113, 227, 0.15); -} - -.drag-handle { - cursor: grab; - color: var(--text-secondary); - padding: 6px 2px; - font-size: 14px; - opacity: 0.35; - transition: 0.2s; - flex-shrink: 0; -} - -.drag-handle:active { - cursor: grabbing; -} - -.sortable-item:hover .drag-handle { - opacity: 0.7; -} - -.item-type-badge { - font-size: 10px; - font-weight: 700; - padding: 3px 8px; - border-radius: 6px; - white-space: nowrap; - flex-shrink: 0; - margin-top: 6px; - text-transform: uppercase; - letter-spacing: 0.3px; -} - -.badge-text { - background: #e8f5e9; - color: #2e7d32; -} - -.badge-image { - background: #e3f2fd; - color: #1565c0; -} - -.badge-video { - background: #fce4ec; - color: #c62828; -} - -.sortable-item .item-content { - flex: 1; - border: 1px solid rgba(0,0,0,0.06) !important; - border-radius: 8px !important; - padding: 8px 10px !important; - font-size: 13px !important; - background: #fafafa !important; - min-height: unset; - font-family: inherit; - resize: vertical; -} - -.sortable-item .item-content:focus { - border-color: var(--accent-color) !important; - background: #fff !important; - box-shadow: none !important; -} - -.remove-item-btn { - background: none; - border: none; - color: #ccc; - cursor: pointer; - padding: 6px; - border-radius: 8px; - transition: 0.2s; - flex-shrink: 0; - margin-top: 3px; - font-size: 13px; -} - -.remove-item-btn:hover { - color: #ef4444; - background: #fef2f2; -} - -.add-item-row { - display: flex; - gap: 8px; -} - -.add-item-btn { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 8px 14px; - background: #f5f5f7; - border: 1.5px dashed rgba(0,0,0,0.12); - border-radius: 10px; - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); - cursor: pointer; - transition: var(--transition); -} - -.add-item-btn:hover { - border-color: var(--accent-color); - color: var(--accent-color); - background: #f0f7ff; -} - -/* Editor Actions */ -.editor-actions { - margin-top: 28px; - padding-top: 20px; - border-top: 1px solid rgba(0,0,0,0.08); - display: flex; - justify-content: flex-end; -} - -.btn-save-announcement { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 12px 28px; - background: var(--brand-green); - color: #fff; - border: none; - border-radius: 14px; - font-size: 15px; - font-weight: 700; - cursor: pointer; - transition: var(--transition); -} - -.btn-save-announcement:hover { - background: #2db84d; - transform: translateY(-1px); - box-shadow: 0 4px 16px rgba(52, 199, 89, 0.3); -} - -/* JSON Output Modal */ -.json-output-content { - background-color: #fff; - margin: 60px auto; - border-radius: var(--radius-large); - max-width: 640px; - width: 90%; - padding: 36px; - box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3); - position: relative; -} - -.json-output-content h3 { - font-size: 20px; - font-weight: 700; - margin-bottom: 8px; - display: flex; - align-items: center; - gap: 10px; -} - -.json-output-hint { - font-size: 14px; - color: var(--text-secondary); - margin-bottom: 20px; - line-height: 1.5; -} - -#json-output { - width: 100%; - height: 300px; - padding: 16px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - font-family: 'Courier New', Courier, monospace; - font-size: 12px; - background: #f5f5f7; - color: var(--text-primary); - resize: vertical; - margin-bottom: 16px; -} - -#json-output:focus { - outline: none; - border-color: var(--accent-color); -} - -.btn-copy-json { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 24px; - background: var(--accent-color); - color: #fff; - border: none; - border-radius: 12px; - font-size: 14px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - width: 100%; - justify-content: center; -} - -.btn-copy-json:hover { - background: #005bb5; -} - -/* ========== Responsive ========== */ -@media (max-width: 900px) { - .editor-modal-content { - margin: 0; - width: 100%; - max-width: 100%; - max-height: 100%; - height: 100%; - border-radius: 0; - } - - .editor-layout { - flex-direction: column; - } - - .editor-preview { - flex: none; - max-height: 35vh; - border-right: none; - border-bottom: 1px solid rgba(0,0,0,0.08); - } - - .editor-form { - flex: 1; - min-height: 0; - } - - .form-row { - flex-direction: column; - gap: 0; - } -} - -@media (max-width: 768px) { - .announcements-container { - padding: 24px 16px; - } - - .controls-header-row { - flex-direction: column; - align-items: stretch; - } - - .search-box { - max-width: 100%; - } - - .title-with-action { - flex-wrap: wrap; - gap: 10px; - } - - .timeline { - padding-left: 24px; - } - - .timeline-item::before { - left: -24px; - width: 12px; - height: 12px; - top: 26px; - } - - .card-summary { - padding: 18px 20px; - flex-direction: column; - align-items: flex-start; - gap: 8px; - } - - .card-summary-time { - align-self: flex-end; - } - - .announcement-card.expanded .card-detail { - padding: 20px; - } - - .json-output-content { - margin: 0; - width: 100%; - height: 100%; - max-height: 100%; - border-radius: 0; - } - - .editor-modal-header { - padding: 16px 20px; - } - - .editor-form-scroll { - padding: 20px; - } - - .filter-label { - min-width: auto; - margin-bottom: 4px; - } -} diff --git a/css/pages/facilities.css b/css/pages/facilities.css deleted file mode 100644 index fba2b2f..0000000 --- a/css/pages/facilities.css +++ /dev/null @@ -1,1267 +0,0 @@ -/* Page-Specific Styles for Facilities */ - -.facilities-hero-bg { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/69267755e14e3.png'); -} - -/* 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; -} - -/* Custom Scrollbar for Modal */ -.facility-modal-content::-webkit-scrollbar { - width: 6px; -} - -.facility-modal-content::-webkit-scrollbar-track { - background: transparent; - margin: 10px 0; -} - -.facility-modal-content::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.15); - border-radius: 10px; -} - -.facility-modal-content::-webkit-scrollbar-thumb:hover { - background: rgba(0, 0, 0, 0.25); -} - -.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-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 16px; - flex-wrap: wrap; -} - -.modal-badges { - display: flex; - gap: 10px; - flex-wrap: wrap; -} - -.modal-actions { - display: flex; - gap: 8px; - align-items: center; - flex-shrink: 0; -} - -.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); -} - -/* Video Embed */ -.video-embed-wrapper { - position: relative; - width: 100%; - padding-bottom: 56.25%; /* 16:9 aspect ratio */ - margin: 12px 0 20px; - border-radius: 12px; - overflow: hidden; - background: #000; - box-shadow: 0 4px 12px rgba(0,0,0,0.08); -} - -.video-embed-wrapper iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: none; -} - -.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; - } -} - -/* ========== Editor Modal ========== */ - -.title-with-action { - display: flex; - align-items: center; - gap: 16px; -} - -.btn-add-facility { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 8px 18px; - background: var(--accent-color); - color: #fff; - border: none; - border-radius: 20px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - white-space: nowrap; -} - -.btn-add-facility:hover { - background: #005bb5; - transform: translateY(-1px); -} - -.btn-share-facility { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 6px 16px; - background: transparent; - color: var(--text-secondary); - border: 1.5px solid rgba(0,0,0,0.12); - border-radius: 18px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - white-space: nowrap; - flex-shrink: 0; -} - -.btn-share-facility:hover { - color: var(--accent-color); - border-color: var(--accent-color); - background: rgba(0,113,227,0.04); -} - -.btn-share-facility.shared { - color: #15803d; - border-color: #34c759; - background: #e8fceb; -} - -.btn-edit-facility { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 6px 16px; - background: transparent; - color: var(--accent-color); - border: 1.5px solid var(--accent-color); - border-radius: 18px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - white-space: nowrap; - flex-shrink: 0; -} - -.btn-edit-facility:hover { - background: var(--accent-color); - color: #fff; -} - -/* Editor Modal Layout */ -.editor-modal-content { - background-color: #fff; - margin: 20px auto; - border-radius: var(--radius-large); - max-width: 1280px; - width: 95%; - padding: 0; - box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3); - position: relative; - max-height: calc(100vh - 40px); - overflow: hidden; - display: flex; - flex-direction: column; -} - -.close-editor-modal, -.close-json-modal { - position: absolute; - top: 16px; - right: 20px; - font-size: 24px; - color: var(--text-secondary); - cursor: pointer; - transition: 0.2s; - z-index: 10; - background: rgba(255,255,255,0.9); - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; -} - -.close-editor-modal:hover, -.close-json-modal:hover { - background: #f0f0f0; - color: var(--text-primary); -} - -.editor-modal-header { - padding: 20px 28px; - border-bottom: 1px solid rgba(0,0,0,0.08); - background: linear-gradient(to bottom, #fff, #fafafa); - border-radius: var(--radius-large) var(--radius-large) 0 0; - flex-shrink: 0; -} - -.editor-modal-header h3 { - font-size: 22px; - font-weight: 700; - display: flex; - align-items: center; - gap: 10px; -} - -.editor-layout { - display: flex; - flex: 1; - overflow: hidden; - min-height: 0; -} - -.editor-preview { - flex: 0 0 45%; - display: flex; - flex-direction: column; - border-right: 1px solid rgba(0,0,0,0.08); - background: #f5f5f7; -} - -.editor-panel-title { - font-size: 12px; - font-weight: 700; - color: var(--text-secondary); - padding: 14px 24px; - border-bottom: 1px solid rgba(0,0,0,0.05); - display: flex; - align-items: center; - gap: 8px; - text-transform: uppercase; - letter-spacing: 0.6px; - flex-shrink: 0; - background: rgba(255,255,255,0.6); -} - -.editor-preview-content { - flex: 1; - overflow-y: auto; - padding: 20px; -} - -.editor-preview-content::-webkit-scrollbar, -.editor-form-scroll::-webkit-scrollbar { - width: 5px; -} - -.editor-preview-content::-webkit-scrollbar-track, -.editor-form-scroll::-webkit-scrollbar-track { - background: transparent; -} - -.editor-preview-content::-webkit-scrollbar-thumb, -.editor-form-scroll::-webkit-scrollbar-thumb { - background: rgba(0,0,0,0.12); - border-radius: 10px; -} - -/* Preview Card */ -.preview-facility { - background: #fff; - border-radius: 16px; - box-shadow: 0 2px 12px rgba(0,0,0,0.06); - overflow: hidden; -} - -.preview-header { - padding: 24px 24px 16px; - background: linear-gradient(to bottom, #fff, #fafafa); - border-bottom: 1px solid rgba(0,0,0,0.05); -} - -.preview-title { - font-size: 22px; - font-weight: 700; - margin-bottom: 12px; - line-height: 1.2; -} - -.preview-body { - padding: 20px 24px 24px; -} - -.preview-body .modal-section { - margin-top: 20px; -} - -.preview-body .modal-section-title { - font-size: 14px; -} - -.preview-intro { - font-size: 15px; - line-height: 1.6; - color: var(--text-primary); - margin-bottom: 20px; -} - -.text-secondary { - color: var(--text-secondary); -} - -/* Editor Form */ -.editor-form { - flex: 0 0 55%; - display: flex; - flex-direction: column; - min-width: 0; -} - -.editor-form-scroll { - flex: 1; - overflow-y: auto; - padding: 24px 28px 40px; -} - -.form-group { - margin-bottom: 22px; -} - -.form-group > label { - display: block; - font-size: 12px; - font-weight: 700; - color: var(--text-secondary); - margin-bottom: 8px; - text-transform: uppercase; - letter-spacing: 0.4px; -} - -.form-group input[type="text"], -.form-group input[type="number"], -.form-group textarea { - width: 100%; - padding: 12px 16px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - font-size: 14px; - font-family: inherit; - background-color: #f9f9fa; - transition: all 0.2s ease; - color: var(--text-primary); - box-sizing: border-box; -} - -.form-group input:focus, -.form-group textarea:focus { - outline: none; - border-color: var(--accent-color); - background-color: #fff; - box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1); -} - -/* Custom Select Dropdown */ -.custom-select { - position: relative; - width: 100%; - user-select: none; - font-size: 14px; -} - -.custom-select-trigger { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - padding: 12px 16px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - background-color: #f9f9fa; - color: var(--text-primary); - cursor: pointer; - transition: all 0.2s ease; - box-sizing: border-box; -} - -.custom-select-trigger i { - color: var(--text-secondary); - font-size: 12px; - transition: transform 0.3s ease; -} - -.custom-select:hover .custom-select-trigger { - background-color: #fff; - border-color: rgba(0,0,0,0.2); -} - -.custom-select.open .custom-select-trigger { - border-color: var(--accent-color); - background-color: #fff; - box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1); -} - -.custom-select.open .custom-select-trigger i { - transform: rotate(180deg); -} - -.custom-select-options { - position: absolute; - top: calc(100% + 8px); - left: 0; - right: 0; - background: #fff; - border-radius: 12px; - box-shadow: 0 10px 40px rgba(0,0,0,0.15); - border: 1px solid rgba(0,0,0,0.08); - opacity: 0; - visibility: hidden; - transform: translateY(-10px); - transition: all 0.2s cubic-bezier(0.25, 1, 0.5, 1); - z-index: 100; - padding: 8px; -} - -.custom-select.open .custom-select-options { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.custom-option { - padding: 10px 14px; - border-radius: 8px; - cursor: pointer; - transition: background 0.2s, color 0.2s; - color: var(--text-primary); - margin-bottom: 2px; -} - -.custom-option:last-child { - margin-bottom: 0; -} - -.custom-option:hover { - background: #f5f5f7; -} - -.custom-option.selected { - background: #e0f2fe; - color: #0369a1; - font-weight: 600; -} - -.form-group textarea { - resize: vertical; - min-height: 60px; -} - -.form-row { - display: flex; - gap: 14px; -} - -.form-row .form-group { - flex: 1; -} - -/* Tags Input */ -.tags-input-wrapper { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px; - padding: 8px 12px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - background: #f9f9fa; - transition: border-color 0.2s, background 0.2s, box-shadow 0.2s; - cursor: text; -} - -.tags-input-wrapper:focus-within { - border-color: var(--accent-color); - background: #fff; - box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1); -} - -.tags-list { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.editor-tag { - display: inline-flex; - align-items: center; - gap: 6px; - background: var(--accent-color); - color: #fff; - padding: 4px 10px; - border-radius: 14px; - font-size: 12px; - font-weight: 600; -} - -.editor-tag-remove { - cursor: pointer; - opacity: 0.7; - transition: 0.2s; - font-size: 10px; -} - -.editor-tag-remove:hover { - opacity: 1; -} - -.tags-input-wrapper input { - border: none !important; - background: transparent !important; - padding: 4px 0 !important; - font-size: 13px; - flex: 1; - min-width: 140px; - box-shadow: none !important; -} - -.tags-input-wrapper input:focus { - outline: none; -} - -/* Sortable List */ -.sortable-list { - min-height: 8px; - margin-bottom: 10px; -} - -.sortable-item { - display: flex; - align-items: flex-start; - gap: 10px; - padding: 10px 12px; - margin-bottom: 8px; - background: #fff; - border: 1.5px solid rgba(0,0,0,0.08); - border-radius: 12px; - transition: box-shadow 0.2s, border-color 0.2s, opacity 0.2s; -} - -.sortable-item:last-child { - margin-bottom: 0; -} - -.sortable-item:hover { - border-color: rgba(0,0,0,0.15); - box-shadow: 0 2px 8px rgba(0,0,0,0.04); -} - -.sortable-item.dragging { - opacity: 0.4; - border-color: var(--accent-color); -} - -.sortable-item.drag-over { - border-color: var(--accent-color); - box-shadow: 0 0 0 2px rgba(0, 113, 227, 0.15); -} - -.drag-handle { - cursor: grab; - color: var(--text-secondary); - padding: 6px 2px; - font-size: 14px; - opacity: 0.35; - transition: 0.2s; - flex-shrink: 0; -} - -.drag-handle:active { - cursor: grabbing; -} - -.sortable-item:hover .drag-handle { - opacity: 0.7; -} - -.item-type-badge { - font-size: 10px; - font-weight: 700; - padding: 3px 8px; - border-radius: 6px; - white-space: nowrap; - flex-shrink: 0; - margin-top: 6px; - text-transform: uppercase; - letter-spacing: 0.3px; -} - -.badge-text { - background: #e8f5e9; - color: #2e7d32; -} - -.badge-image { - background: #e3f2fd; - color: #1565c0; -} - -.badge-video { - background: #fce4ec; - color: #c62828; -} - -.sortable-item .item-content { - flex: 1; - border: 1px solid rgba(0,0,0,0.06) !important; - border-radius: 8px !important; - padding: 8px 10px !important; - font-size: 13px !important; - background: #fafafa !important; - min-height: unset; - font-family: inherit; - resize: vertical; -} - -.sortable-item .item-content:focus { - border-color: var(--accent-color) !important; - background: #fff !important; - box-shadow: none !important; -} - -.remove-item-btn { - background: none; - border: none; - color: #ccc; - cursor: pointer; - padding: 6px; - border-radius: 8px; - transition: 0.2s; - flex-shrink: 0; - margin-top: 3px; - font-size: 13px; -} - -.remove-item-btn:hover { - color: #ef4444; - background: #fef2f2; -} - -.add-item-row { - display: flex; - gap: 8px; -} - -.add-item-btn { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 8px 14px; - background: #f5f5f7; - border: 1.5px dashed rgba(0,0,0,0.12); - border-radius: 10px; - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); - cursor: pointer; - transition: var(--transition); -} - -.add-item-btn:hover { - border-color: var(--accent-color); - color: var(--accent-color); - background: #f0f7ff; -} - -/* Editor Actions */ -.editor-actions { - margin-top: 28px; - padding-top: 20px; - border-top: 1px solid rgba(0,0,0,0.08); - display: flex; - justify-content: flex-end; -} - -.btn-save-facility { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 12px 28px; - background: var(--brand-green); - color: #fff; - border: none; - border-radius: 14px; - font-size: 15px; - font-weight: 700; - cursor: pointer; - transition: var(--transition); -} - -.btn-save-facility:hover { - background: #2db84d; - transform: translateY(-1px); - box-shadow: 0 4px 16px rgba(52, 199, 89, 0.3); -} - -/* JSON Output Modal */ -.json-output-content { - background-color: #fff; - margin: 60px auto; - border-radius: var(--radius-large); - max-width: 640px; - width: 90%; - padding: 36px; - box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3); - position: relative; -} - -.json-output-content h3 { - font-size: 20px; - font-weight: 700; - margin-bottom: 8px; - display: flex; - align-items: center; - gap: 10px; -} - -.json-output-hint { - font-size: 14px; - color: var(--text-secondary); - margin-bottom: 20px; - line-height: 1.5; -} - -#json-output { - width: 100%; - height: 300px; - padding: 16px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - font-family: 'Courier New', Courier, monospace; - font-size: 12px; - background: #f5f5f7; - color: var(--text-primary); - resize: vertical; - margin-bottom: 16px; -} - -#json-output:focus { - outline: none; - border-color: var(--accent-color); -} - -.btn-copy-json { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 24px; - background: var(--accent-color); - color: #fff; - border: none; - border-radius: 12px; - font-size: 14px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - width: 100%; - justify-content: center; -} - -.btn-copy-json:hover { - background: #005bb5; -} - -/* ========== Editor Responsive ========== */ - -@media (max-width: 900px) { - .editor-modal-content { - margin: 0; - width: 100%; - max-width: 100%; - max-height: 100%; - height: 100%; - border-radius: 0; - } - - .editor-layout { - flex-direction: column; - } - - .editor-preview { - flex: none; - max-height: 35vh; - border-right: none; - border-bottom: 1px solid rgba(0,0,0,0.08); - } - - .editor-form { - flex: 1; - min-height: 0; - } - - .form-row { - flex-direction: column; - gap: 0; - } - - .close-editor-modal { - top: 12px; - right: 14px; - } -} - -@media (max-width: 768px) { - .title-with-action { - flex-wrap: wrap; - gap: 10px; - } - - .json-output-content { - margin: 0; - width: 100%; - height: 100%; - max-height: 100%; - border-radius: 0; - } - - .editor-modal-header { - padding: 16px 20px; - } - - .editor-form-scroll { - padding: 20px; - } -} diff --git a/css/pages/join.css b/css/pages/join.css deleted file mode 100644 index f49fe1f..0000000 --- a/css/pages/join.css +++ /dev/null @@ -1,1070 +0,0 @@ -:root { - --step-active-bg: #0071e3; - --sidebar-bg: #f5f5f7; - --sidebar-width: 280px; - --content-padding: 60px; - --card-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - --transition-smooth: all 0.4s cubic-bezier(0.2, 0.8, 0.2, 1); -} - -body { - background-color: #fbfbfd; - color: #1d1d1f; -} - -/* Minimal Header */ -.join-header-minimal { - padding: 120px 20px 40px; - text-align: center; - background: transparent; -} - -.join-header-minimal h1 { - font-size: 3rem; - font-weight: 700; - letter-spacing: -0.02em; - margin-bottom: 10px; - background: linear-gradient(135deg, #1d1d1f 0%, #434344 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; -} - -.join-header-minimal p { - color: #86868b; - font-size: 1.25rem; - font-weight: 400; -} - -/* Modern Wizard Container */ -.wizard-container-modern { - display: flex; - max-width: 1200px; - margin: 20px auto 80px; - background: #fff; - border-radius: 24px; - box-shadow: 0 20px 40px rgba(0,0,0,0.08); - overflow: hidden; - min-height: 600px; - border: 1px solid rgba(0,0,0,0.04); -} - -/* Sidebar */ -.wizard-sidebar { - width: var(--sidebar-width); - background: var(--sidebar-bg); - padding: 60px 40px; - border-right: 1px solid rgba(0,0,0,0.05); - display: flex; - flex-direction: column; -} - -.wizard-progress-vertical { - position: relative; - display: flex; - flex-direction: column; - gap: 40px; -} - -.progress-step { - padding-left: 20px; - position: relative; - color: #86868b; - transition: var(--transition-smooth); - opacity: 0.6; - cursor: default; -} - -.progress-step::before { - content: ''; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); - width: 4px; - height: 4px; - background: #d2d2d7; - border-radius: 50%; - transition: var(--transition-smooth); -} - -.progress-step.active, -.progress-step.completed { - opacity: 1; - color: #1d1d1f; -} - -.progress-step.active::before { - background: var(--step-active-bg); - height: 24px; - border-radius: 2px; -} - -.progress-step.completed::before { - background: #34c759; -} - -.step-label { - display: block; - font-weight: 600; - font-size: 1.1rem; - margin-bottom: 4px; -} - -.step-sub { - font-size: 0.85rem; - color: #86868b; -} - -/* Content Area */ -.wizard-content-area { - flex: 1; - padding: var(--content-padding); - background: #fff; - display: flex; - flex-direction: column; - position: relative; -} - -.step-content { - display: none; - animation: fadeIn 0.5s ease; - flex: 1; - flex-direction: column; -} - -.step-content.active { - display: flex; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } -} - -.content-header { - margin-bottom: 40px; -} - -.content-header h2 { - font-size: 2rem; - font-weight: 700; - margin-bottom: 8px; -} - -.content-header p { - color: #86868b; - font-size: 1.1rem; -} - -/* Footer & Buttons */ -.step-footer { - margin-top: auto; - padding-top: 40px; - display: flex; - justify-content: space-between; - align-items: center; - border-top: 1px solid rgba(0,0,0,0.05); -} - -.btn-clean { - background: none; - border: none; - color: #86868b; - font-size: 1rem; - cursor: pointer; - transition: color 0.2s; - font-weight: 500; - padding: 10px 20px; - text-decoration: none; - display: inline-flex; - align-items: center; - gap: 8px; -} - -.btn-clean:hover { - color: #1d1d1f; -} - -.btn-clean:disabled { - opacity: 0.3; - cursor: not-allowed; -} - -.btn-primary-large { - background: #0071e3; - color: white; - border: none; - padding: 14px 32px; - border-radius: 980px; - font-size: 1rem; - font-weight: 600; - cursor: pointer; - transition: var(--transition-smooth); - display: inline-flex; - align-items: center; - gap: 8px; - text-decoration: none; -} - -.btn-primary-large:hover { - background: #0077ed; - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 113, 227, 0.3); -} - -.step-action-group { - display: flex; - gap: 16px; - align-items: center; -} - -.step-action-group .hidden { - display: none !important; -} - -.step-action-group .final-actions { - display: flex; - align-items: center; - gap: 12px; -} - -/* Device Cards */ -.card-content { - flex: 1; -} - -.device-selection-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 20px; -} - -.device-card { - border: 2px solid #f5f5f7; - border-radius: 16px; - padding: 30px 20px; - text-align: center; - cursor: pointer; - transition: var(--transition-smooth); - position: relative; - background: #fff; -} - -.device-card:hover { - border-color: #d2d2d7; - transform: translateY(-2px); -} - -.device-card.selected { - border-color: #0071e3; - background: rgba(0, 113, 227, 0.03); -} - -.icon-circle { - width: 60px; - height: 60px; - background: #f5f5f7; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; - margin: 0 auto 16px; - color: #1d1d1f; - transition: var(--transition-smooth); -} - -.device-card.selected .icon-circle { - background: #0071e3; - color: white; -} - -.check-mark { - position: absolute; - top: 16px; - right: 16px; - color: #0071e3; - opacity: 0; - transform: scale(0.5); - transition: var(--transition-smooth); -} - -.device-card.selected .check-mark { - opacity: 1; - transform: scale(1); - font-size: 1.2rem; -} - -.device-info h3 { - margin: 0 0 4px; - font-size: 1.1rem; -} -.device-info p { - margin: 0; - font-size: 0.9rem; - color: #86868b; -} - -/* Playstyle Cards */ -.playstyle-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 20px; -} - -.playstyle-card { - display: flex; - align-items: center; - padding: 24px; - border-radius: 16px; - background: #f5f5f7; - gap: 20px; - transition: var(--transition-smooth); - border: 1px solid transparent; -} - -.playstyle-card:hover { - background: #fff; - box-shadow: 0 4px 12px rgba(0,0,0,0.08); /* Fixed shadow var */ - transform: translateY(-2px); -} - -.playstyle-visual { - width: 64px; - height: 64px; - border-radius: 12px; - background: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; - color: #1d1d1f; - flex-shrink: 0; -} - -/* Convention */ -.convention-wrapper { - background: #f5f5f7; - border-radius: 12px; - padding: 6px; - margin-bottom: 24px; -} - -.markdown-content { - background: #fff; - border-radius: 8px; - padding: 30px; - height: 400px; - overflow-y: auto; - font-size: 0.95rem; - line-height: 1.6; - color: #333; -} - -/* Custom Checkbox */ -.agreement-area { - display: flex; - align-items: center; - gap: 12px; - padding: 12px; - cursor: pointer; - border-radius: 8px; - transition: background 0.2s; -} - -.agreement-area:hover { - background: #f5f5f7; -} - -.custom-checkbox { - position: relative; - width: 24px; - height: 24px; - border: 2px solid #d2d2d7; - border-radius: 6px; - display: flex; /* Flex to center content */ - align-items: center; - justify-content: center; - transition: var(--transition-smooth); - background: #fff; -} - -.custom-checkbox input { - position: absolute; - opacity: 0; - width: 0; - height: 0; -} - -.checkmark { - display: none; - color: white; - font-size: 14px; -} - -/* When the checkbox input inside is checked */ -.custom-checkbox:has(input:checked) { - background-color: #0071e3; - border-color: #0071e3; -} - -.custom-checkbox:has(input:checked) .checkmark { - display: block; -} - -/* Launcher Recommendation */ -.launcher-box { - margin-top: 30px; - padding: 24px; - background: #f0f9ff; - border-radius: 16px; - border: 1px solid #bae6fd; -} - -.hidden { - display: none; -} - -/* Final Message */ -.final-message { - text-align: center; - margin-top: 30px; - color: #86868b; - font-style: italic; -} - -@media (max-width: 800px) { - .join-header-minimal { - padding: 80px 20px 20px; - } - - .join-header-minimal h1 { - font-size: 2rem; - } - - .wizard-container-modern { - flex-direction: column; - margin: 0 10px 40px; - min-height: auto; - border-radius: 16px; - } - .wizard-sidebar { - width: 100%; - padding: 16px; - flex-direction: row; - overflow-x: auto; - border-right: none; - border-bottom: 1px solid rgba(0,0,0,0.05); - gap: 0; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; /* Firefox */ - } - .wizard-sidebar::-webkit-scrollbar { - display: none; /* Chrome/Safari */ - } - - .wizard-progress-vertical { - flex-direction: row; - width: auto; - gap: 24px; - flex-wrap: nowrap; - padding-right: 20px; /* End padding for scroll */ - } - .progress-step { - padding-left: 0; - white-space: nowrap; - opacity: 0.5; - flex-shrink: 0; - } - .progress-step.active { - opacity: 1; - color: #0071e3; - } - .progress-step::before { - display: none; - } - .step-sub { display: none; } - - .wizard-content-area { - padding: 24px 20px; - } - - .device-selection-grid, .playstyle-grid { - grid-template-columns: 1fr; - gap: 16px; - } - - .step-footer { - flex-direction: column-reverse; - gap: 16px; - padding-top: 30px; - } - - .btn-clean { - width: 100%; - justify-content: center; - padding: 12px; - } - - .step-action-group { - width: 100%; - flex-direction: column; - gap: 12px; - } - - .step-action-group .final-actions { - width: 100%; - flex-direction: column; - } - - .btn-primary-large { - width: 100%; - justify-content: center; - padding: 16px; - } - - /* Adjust specific card layouts for mobile */ - .device-card { - padding: 24px 16px; - display: flex; - align-items: center; - text-align: left; - gap: 20px; - } - - .icon-circle { - margin: 0; /* Reset auto margin */ - width: 48px; - height: 48px; - font-size: 20px; - } - - .device-info { - flex: 1; - } - - .check-mark { - position: static; - transform: scale(0.8); - opacity: 0; - width: 24px; - } - - .device-card.selected .check-mark { - opacity: 1; - transform: scale(1); - } - - /* Launcher cards */ - .launcher-grid { - grid-template-columns: 1fr; - } - - .launcher-card { - padding: 16px; - } - - .launcher-icon { - width: 44px; - height: 44px; - font-size: 20px; - margin-right: 16px; - } - - /* Playstyle cards adjustment */ - .playstyle-card { - padding: 16px; - } - - .playstyle-visual { - width: 48px; - height: 48px; - font-size: 20px; - } - - /* Modal/Details tweaks */ - .detail-grid-row { - grid-template-columns: 1fr; - gap: 20px; - } - - .details-card { - padding: 20px; - } - - .details-header h3 { - font-size: 1.25rem; - } - - /* Tutorial Step tweaks */ - .tutorial-step { - padding: 16px; - gap: 12px; - } - .step-badge { - width: 28px; - height: 28px; - font-size: 0.9rem; - } - - /* Server address box responsiveness */ - .server-address-box { - width: 100%; - box-sizing: border-box; - flex-direction: column; - align-items: flex-start; - gap: 8px; - } - - .server-address-box code { - word-break: break-all; - } - - .btn-copy { - width: 100%; - justify-content: center; - } -} - -.step-dot { display: none; } - -/* Tutorial Steps */ -.tutorial-steps { - display: flex; - flex-direction: column; - gap: 20px; -} - -.tutorial-step { - display: flex; - gap: 20px; - padding: 20px; - background: #f5f5f7; - border-radius: 12px; - align-items: flex-start; -} - -.step-badge { - width: 32px; - height: 32px; - background: #0071e3; - color: white; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - flex-shrink: 0; -} - -.step-text h4 { - margin: 0 0 4px; - font-size: 1.1rem; - color: #1d1d1f; -} - -.step-text p { - margin: 0; - color: #86868b; - font-size: 0.95rem; - line-height: 1.5; -} - -.step-text code { - background: #e8e8ed; - padding: 2px 6px; - border-radius: 4px; - font-family: monospace; - color: #1d1d1f; -} - -/* Enhanced Device Selection Styles */ -.launcher-box { - margin-top: 40px; - padding-top: 30px; - border-top: 1px solid rgba(0,0,0,0.05); - transition: opacity 0.4s ease, transform 0.4s ease; -} - -.recommendation-header { - text-align: center; - margin-bottom: 30px; -} - -.recommendation-header h3 { - font-size: 1.4rem; - color: #1d1d1f; - margin-bottom: 8px; -} - -.recommendation-header p { - color: #86868b; - font-size: 1rem; -} - -.launcher-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 20px; -} - -.launcher-card { - display: flex; - align-items: center; - padding: 24px; - background: #fbfbfd; - border: 1px solid rgba(0,0,0,0.05); - border-radius: 16px; - text-decoration: none; - color: inherit; - transition: all 0.3s cubic-bezier(0.2, 0.8, 0.2, 1); - position: relative; - overflow: hidden; -} - -.launcher-card:hover { - background: #fff; - box-shadow: 0 8px 24px rgba(0,0,0,0.06); - transform: translateY(-2px); - border-color: rgba(0,0,0,0.08); -} - -.launcher-card.primary { - background: #fff; - border: 1px solid rgba(0, 113, 227, 0.2); - box-shadow: 0 4px 12px rgba(0, 113, 227, 0.05); -} - -.launcher-card.primary:hover { - box-shadow: 0 8px 24px rgba(0, 113, 227, 0.12); - border-color: rgba(0, 113, 227, 0.4); -} - -.launcher-icon { - width: 54px; - height: 54px; - background: #ffffff; - border-radius: 14px; - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; - color: #1d1d1f; - margin-right: 20px; - box-shadow: 0 2px 8px rgba(0,0,0,0.05); - flex-shrink: 0; -} - -.launcher-card.primary .launcher-icon { - background: #0071e3; - color: white; -} - -.launcher-details { - flex: 1; -} - -.launcher-details h4 { - margin: 0 0 4px; - font-size: 1.1rem; - font-weight: 600; - display: flex; - align-items: center; - gap: 8px; -} - -.badge-rec { - font-size: 0.75rem; - background: #e1f0ff; - color: #0071e3; - padding: 2px 8px; - border-radius: 6px; - font-weight: 500; -} - -.launcher-details p { - margin: 0; - font-size: 0.9rem; - color: #86868b; - line-height: 1.4; -} - -.launcher-action { - color: #0071e3; - opacity: 0; - transform: translateX(-10px); - transition: all 0.3s ease; -} - -.launcher-card:hover .launcher-action { - opacity: 1; - transform: translateX(0); -} - -/* Server Address Box */ -.server-address-box { - background: #e8e8ed; - padding: 8px 12px; - border-radius: 8px; - display: inline-flex; - align-items: center; - gap: 12px; - margin: 8px 0; - font-family: monospace; - color: #1d1d1f; - border: 1px solid rgba(0,0,0,0.05); -} - -.server-address-box code { - background: none; - padding: 0; - font-size: 1rem; - font-weight: 600; -} - -.btn-copy { - background: #fff; - border: 1px solid #d2d2d7; - border-radius: 6px; - padding: 4px 10px; - font-size: 0.8rem; - cursor: pointer; - transition: all 0.2s; - color: #1d1d1f; - display: flex; - align-items: center; - gap: 4px; -} - -.btn-copy:hover { - background: #f5f5f7; - border-color: #86868b; -} - -.btn-copy:active { - transform: scale(0.95); -} - -.highlight-text { - color: #0071e3; - font-weight: 600; -} - -/* --- Added for Step 4 Interactive Enhancement --- */ - -/* Interactive Playstyle Cards */ -.playstyle-card { - cursor: pointer; - transition: var(--transition-smooth); - border: 2px solid transparent; /* Prepare for border change */ - /* Ensure existing styles don't conflict, but we assume they are basic cards */ -} - -.playstyle-card:hover { - transform: translateY(-5px); - box-shadow: 0 8px 24px rgba(0,0,0,0.12); -} - -.playstyle-card.selected { - border-color: #0071e3; - background-color: rgba(0, 113, 227, 0.04); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 113, 227, 0.2); -} - -.icon-check-wrapper { - position: absolute; - top: 10px; - right: 10px; - color: #0071e3; - opacity: 0; - transform: scale(0.5); - transition: var(--transition-smooth); -} - -.playstyle-card.selected .icon-check-wrapper { - opacity: 1; - transform: scale(1); -} - -/* Details Section */ -.playstyle-details-container { - margin-top: 40px; - opacity: 0; - max-height: 0; - overflow: hidden; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); -} - -.playstyle-details-container.visible { - opacity: 1; - max-height: 1000px; /* Arbitrary large height */ - margin-bottom: 20px; -} - -.details-card { - background: #fbfbfd; - border-radius: 16px; - padding: 30px; - border: 1px solid rgba(0,0,0,0.06); - box-shadow: inset 0 0 20px rgba(0,0,0,0.02); -} - -.details-header { - margin-bottom: 24px; - padding-bottom: 16px; - border-bottom: 1px solid rgba(0,0,0,0.08); - display: flex; - align-items: center; - gap: 12px; - flex-wrap: wrap; -} - -.details-header h3 { - margin: 0; - font-size: 1.5rem; - color: #1d1d1f; -} - -.badge-subtitle { - background: #e8e8ed; - color: #6e6e73; - padding: 4px 12px; - border-radius: 980px; - font-size: 0.85rem; - font-weight: 500; -} - -.details-body { - display: flex; - flex-direction: column; - gap: 24px; -} - -.detail-section h4 { - margin: 0 0 8px 0; - font-size: 1rem; - color: #1d1d1f; - display: flex; - align-items: center; - gap: 8px; -} - -.detail-section h4 i { - color: #0071e3; -} - -.detail-section p { - margin: 0; - color: #424245; - line-height: 1.6; -} - -.detail-grid-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 30px; -} - -@media (max-width: 768px) { - .detail-grid-row { - grid-template-columns: 1fr; - } -} - -.detail-section ul { - margin: 0; - padding-left: 0; - list-style: none; -} - -.detail-section ul li { - padding-left: 24px; - position: relative; - margin-bottom: 8px; - color: #424245; - line-height: 1.5; -} - -.detail-section ul li::before { - content: "•"; - position: absolute; - left: 8px; - color: #86868b; -} - -.detail-section.check-list ul li::before { - content: "✓"; - color: #34c759; /* Green */ - font-weight: bold; -} - -.detail-section.warn-list ul li::before { - content: "!"; - color: #ff9f0a; /* Orange */ - font-weight: bold; - left: 8px; /* Adjust if needed */ -} - -/* --- Added for Step 4 Interactive Enhancement - Fix --- */ - -/* Details Section */ -.playstyle-details-container { - margin-top: 0; - opacity: 0; - max-height: 0; - overflow: hidden; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); -} - -.playstyle-details-container.visible { - opacity: 1; - max-height: 1000px; - margin-top: 40px; - margin-bottom: 20px; -} - -/* Edition Selector */ -.edition-selector { - margin-top: 24px; - text-align: center; -} - -.edition-toggle { - display: inline-flex; - background: #f5f5f7; - border-radius: 12px; - padding: 4px; - gap: 4px; -} - -.edition-btn { - padding: 10px 28px; - border: none; - background: transparent; - border-radius: 10px; - cursor: pointer; - font-size: 0.95rem; - font-weight: 500; - color: #86868b; - transition: all 0.3s ease; - display: inline-flex; - align-items: center; - gap: 8px; -} - -.edition-btn.active { - background: #fff; - color: #1d1d1f; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); -} - -.edition-btn:hover:not(.active) { - color: #1d1d1f; -} - -.server-address-box span { - color: #86868b; - font-size: 0.9rem; - white-space: nowrap; -} diff --git a/css/pages/sponsor.css b/css/pages/sponsor.css deleted file mode 100644 index 4348380..0000000 --- a/css/pages/sponsor.css +++ /dev/null @@ -1,599 +0,0 @@ -.sponsor-hero { - padding: 140px 20px 50px; - text-align: center; - background: radial-gradient(circle at center, rgba(0,113,227,0.08) 0%, rgba(255,255,255,0) 70%); - position: relative; - overflow: hidden; -} - -.sponsor-hero h1 { - font-size: 56px; - font-weight: 800; - margin-bottom: 24px; - background: linear-gradient(135deg, #1d1d1f 0%, #434344 100%); - background-clip: text; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - letter-spacing: -0.02em; -} - -.hero-subtitle { - font-size: 18px; - color: var(--text-secondary); - margin-top: 16px; - max-width: 600px; - margin-left: auto; - margin-right: auto; -} - -.total-donations { - display: inline-flex; - flex-direction: column; - align-items: center; - background: rgba(255, 255, 255, 0.8); - backdrop-filter: blur(20px); - padding: 20px 40px; - border-radius: var(--radius-large); - box-shadow: 0 10px 40px rgba(0,0,0,0.06); - border: 1px solid rgba(255,255,255,0.6); - transform: translateY(0); - transition: transform 0.3s ease; -} - -.total-donations:hover { - transform: translateY(-5px); -} - -.counter-label { - font-size: 14px; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--text-secondary); - margin-bottom: 8px; - font-weight: 600; -} - -.counter-value { - font-size: 42px; - font-weight: 800; - color: var(--brand-green); - font-feature-settings: "tnum"; - font-variant-numeric: tabular-nums; -} - -.sponsor-container { - max-width: 1100px; - margin: 0 auto; - padding: 40px 20px; -} - -.section-title { - font-size: 28px; - font-weight: 700; - margin-bottom: 30px; - text-align: center; -} - -.sponsor-list-title { - margin-bottom: 10px; -} - -/* Top Donors Podium */ -.top-donors-section { - margin-bottom: 60px; -} - -.podium-container { - display: flex; - justify-content: center; - align-items: flex-end; - gap: 20px; - padding: 40px 0; - min-height: 300px; -} - -.podium-spot { - background: white; - border-radius: var(--radius-medium); - padding: 20px; - width: 220px; - display: flex; - flex-direction: column; - align-items: center; - box-shadow: 0 4px 20px rgba(0,0,0,0.06); - position: relative; - transition: transform 0.3s ease; - text-align: center; -} - -.podium-spot:hover { - transform: translateY(-10px); - z-index: 2; -} - -.podium-rank { - width: 40px; - height: 40px; - background: #f0f0f0; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: 800; - margin-bottom: 12px; - position: absolute; - top: -20px; - border: 4px solid var(--bg-color); -} - -/* Gold */ -.podium-spot.rank-1 { - height: 260px; - border: 2px solid rgba(255, 215, 0, 0.3); - background: linear-gradient(180deg, rgba(255,215,0,0.05) 0%, rgba(255,255,255,1) 100%); - order: 2; -} - -.podium-spot.rank-1 .podium-rank { - background: #FFD700; - color: #fff; - box-shadow: 0 4px 10px rgba(255, 215, 0, 0.4); -} - -.podium-spot.rank-1 .donor-avatar { - border-color: #FFD700; - width: 80px; - height: 80px; -} - -/* Silver */ -.podium-spot.rank-2 { - height: 220px; - border: 2px solid rgba(192, 192, 192, 0.3); - order: 1; -} - -.podium-spot.rank-2 .podium-rank { - background: #C0C0C0; - color: #fff; -} - -/* Bronze */ -.podium-spot.rank-3 { - height: 200px; - border: 2px solid rgba(205, 127, 50, 0.3); - order: 3; -} - -.podium-spot.rank-3 .podium-rank { - background: #CD7F32; - color: #fff; -} - -.donor-avatar { - width: 64px; - height: 64px; - border-radius: 50%; - border: 3px solid transparent; - margin-bottom: 12px; - background-color: #f0f0f0; - object-fit: cover; -} - -.podium-name { - font-weight: 700; - font-size: 18px; - margin-bottom: 4px; - word-break: break-all; -} - -.podium-total { - color: var(--brand-green); - font-weight: 600; - font-size: 16px; -} - -/* Controls */ -.controls-section { - display: flex; - flex-direction: column; - align-items: center; - gap: 20px; - margin-bottom: 40px; - max-width: 800px; - margin-left: auto; - margin-right: auto; -} - -.controls-header { - display: flex; - width: 100%; - justify-content: center; - align-items: center; - flex-wrap: wrap; - gap: 12px; - margin-bottom: 24px; -} - -/* Mobile adjustment for header */ -@media (max-width: 600px) { - .controls-header { - flex-direction: column; - } -} - -.cta-button { - height: 48px; - padding: 0 24px; - background-color: var(--text-primary); - color: white; - border-radius: 99px; - text-decoration: none; - font-weight: 600; - font-size: 14px; - display: inline-flex; - align-items: center; - gap: 8px; - transition: all 0.2s; - cursor: pointer; - border: 1px solid transparent; - white-space: nowrap; - box-sizing: border-box; -} - -.cta-button:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); -} - -.cta-button.outline { - background-color: transparent; - color: var(--text-primary); - border: 1px solid rgba(0,0,0,0.1); -} - -.cta-button.outline:hover { - border-color: var(--text-primary); - background-color: white; -} - -/* Modal Styles */ -.modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: rgba(0,0,0,0.5); - backdrop-filter: blur(5px); - opacity: 0; - transition: opacity 0.3s ease; -} - -.modal.show { - display: flex; - opacity: 1; - align-items: center; - justify-content: center; -} - -.modal-content { - background-color: white; - margin: auto; - padding: 40px; - border-radius: 24px; - width: 90%; - max-width: 400px; - position: relative; - box-shadow: 0 20px 60px rgba(0,0,0,0.2); - transform: scale(0.9); - transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); - text-align: center; -} - -.modal.show .modal-content { - transform: scale(1); -} - -.close-modal { - position: absolute; - top: 20px; - right: 20px; - color: #aaa; - font-size: 24px; - cursor: pointer; - width: 32px; - height: 32px; - background: #f5f5f7; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s; -} - -.close-modal:hover { - color: #000; - background: #e5e5e7; -} - -.modal-title { - font-size: 24px; - font-weight: 700; - margin-bottom: 8px; -} - -.modal-gift-icon { - width: 50px; - height: 50px; - background: rgba(52, 199, 89, 0.1); - border-radius: 50%; - color: var(--brand-green); - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; - margin: 0 auto 20px; -} - -.modal-subtitle { - color: var(--text-secondary); - font-size: 14px; - margin-bottom: 30px; - line-height: 1.5; -} - -.qr-placeholder { - background: #f9f9f9; - padding: 20px; - border-radius: 16px; - display: inline-block; - margin-bottom: 20px; -} - -.qr-img { - width: 180px; - height: 180px; - display: block; -} - -.desktop-qr-hint { - font-size: 13px; - color: #999; -} - -.alipay-btn { - background: #1677FF; - color: white; - display: inline-block; - width: 100%; - padding: 14px; - border-radius: 12px; - font-weight: 600; - text-decoration: none; - margin-top: 10px; -} - -#mobile-btn-view { - display: none; -} - -.mobile-pay-hint { - font-size: 12px; - color: #999; - margin-top: 16px; -} - -.search-box { - position: relative; - flex-grow: 0; - width: 100%; - max-width: 320px; -} - -.search-box input { - width: 100%; - height: 48px; - padding: 0 20px 0 44px; - border-radius: 99px; - border: 1px solid rgba(0,0,0,0.1); - background: white; - font-size: 15px; - outline: none; - transition: all 0.2s; -} - -.search-box input:focus { - border-color: var(--accent-color); - box-shadow: 0 0 0 3px rgba(0,113,227,0.1); -} - -.search-box i { - position: absolute; - left: 16px; - top: 50%; - transform: translateY(-50%); - color: var(--text-secondary); -} - -.filter-tags { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 10px; -} - -.filter-tag { - padding: 8px 16px; - border-radius: 99px; - border: none; - background: white; - color: var(--text-secondary); - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s; - box-shadow: 0 2px 8px rgba(0,0,0,0.03); -} - -.filter-tag:hover { - transform: translateY(-1px); - background: #fafafa; -} - -.filter-tag.active { - background: var(--text-primary); - color: white; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); -} - -.donation-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 20px; -} - -.no-results-message { - text-align: center; - color: var(--text-secondary); - padding: 40px; -} - -.sponsor-load-error { - text-align: center; - padding: 40px; - color: var(--text-secondary); - grid-column: 1 / -1; -} - -.donation-card-body { - flex-grow: 1; - display: flex; - flex-direction: column; -} - -.donation-date-icon { - margin-right: 4px; -} - -.is-hidden { - display: none; -} - -.donation-card { - background: white; - padding: 24px; - border-radius: var(--radius-medium); - transition: var(--transition); - display: flex; - flex-direction: column; - justify-content: space-between; - position: relative; - overflow: hidden; - border: 1px solid rgba(0,0,0,0.03); - animation: fadeInUp 0.5s ease backwards; -} - -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.donation-card:hover { - transform: translateY(-5px); - box-shadow: 0 10px 30px rgba(0,0,0,0.08); - border-color: transparent; -} - -.donation-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 16px; -} - -.donor-info { - display: flex; - align-items: center; - gap: 12px; -} - -.mini-avatar { - width: 40px; - height: 40px; - border-radius: 50%; - background: #f0f0f0; -} - -.donor-name { - font-weight: 700; - font-size: 16px; - line-height: 1.2; -} - -.donation-amount { - color: var(--brand-green); - font-weight: 800; - font-size: 18px; - background: rgba(52, 199, 89, 0.1); - padding: 4px 10px; - border-radius: 8px; -} - -.donation-purpose { - font-size: 13px; - color: var(--text-primary); - background: var(--bg-color); - padding: 6px 12px; - border-radius: 6px; - display: inline-block; - margin-bottom: 12px; - align-self: flex-start; -} - -.donation-date { - font-size: 12px; - color: #999; - text-align: right; - margin-top: auto; - border-top: 1px solid rgba(0,0,0,0.05); - padding-top: 12px; -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .sponsor-hero h1 { - font-size: 32px; - } - - .counter-value { - font-size: 32px; - } - - .podium-container { - flex-direction: column; - align-items: center; - gap: 30px; - } - - .podium-spot { - width: 100%; - max-width: 300px; - order: initial !important; - height: auto !important; - } - - .podium-rank { - position: relative; - top: 0; - margin-top: -30px; - } -} diff --git a/css/pages/stats.css b/css/pages/stats.css deleted file mode 100644 index 9d0e9ab..0000000 --- a/css/pages/stats.css +++ /dev/null @@ -1,568 +0,0 @@ -/* Specific styles for stats page override/additions */ -.stats-hero-bg { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/69267755e14e3.png'); -} - -.stats-main-section { - background: var(--bg-color); -} - -.stats-updated-at { - text-align: center; - color: var(--text-secondary); - font-size: 14px; - margin-top: -45px; - margin-bottom: 40px; -} - -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 20px; - margin-top: 30px; -} - -.player-card { - background: var(--card-bg); - border-radius: var(--radius-medium); - padding: 20px; - text-align: center; - transition: var(--transition); - cursor: pointer; - box-shadow: 0 4px 6px rgba(0,0,0,0.02); - position: relative; - overflow: hidden; -} - -.player-card:hover { - transform: translateY(-5px); - box-shadow: 0 10px 20px rgba(0,0,0,0.05); -} - -.player-card img { - width: 80px; - height: 80px; - border-radius: 10px; - margin-bottom: 15px; - box-shadow: 0 2px 10px rgba(0,0,0,0.1); -} - -.player-card h3 { - font-size: 16px; - margin-bottom: 5px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.player-card .p-uuid { - font-size: 12px; - color: var(--text-secondary); - margin-bottom: 10px; - font-family: monospace; -} - -/* Leaderboard Cards */ -.leaderboard-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 20px; - margin-bottom: 60px; -} - -.lb-card { - background: white; - border-radius: var(--radius-medium); - padding: 25px; - box-shadow: 0 4px 20px rgba(0,0,0,0.05); - position: relative; - overflow: hidden; -} - -.lb-card.gold { - border-top: 4px solid #FFD700; -} - -.lb-card.silver { - border-top: 4px solid #C0C0C0; -} - -.lb-card.bronze { - border-top: 4px solid #CD7F32; -} - -.lb-card.red { - border-top: 4px solid #ff3b30; -} - -.lb-card.purple { - border-top: 4px solid #9b59b6; -} - -.lb-card.kill-red { - border-top: 4px solid #e74c3c; -} - -.lb-header { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 20px; -} - -.lb-icon { - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 10px; - background: var(--bg-color); - color: var(--text-primary); - font-size: 20px; -} - -.lb-title { - font-weight: 600; - font-size: 18px; -} - -.lb-top-player { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 20px; - padding-bottom: 20px; - border-bottom: 1px solid #f0f0f0; -} - -.lb-top-player img { - width: 64px; - height: 64px; - border-radius: 8px; - margin-bottom: 10px; -} - -.lb-top-data { - font-size: 24px; - font-weight: 700; - color: var(--accent-color); -} - -.lb-top-name { - font-weight: 700; - margin-bottom: 4px; -} - -.lb-list { - list-style: none; -} - -.lb-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px 0; - font-size: 14px; - border-bottom: 1px dashed #eee; -} - -.lb-item-main { - display: flex; - align-items: center; -} - -.lb-item-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 100px; -} - -.lb-item:last-child { - border-bottom: none; -} - -.lb-rank { - width: 24px; - height: 24px; - background: #eee; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - font-weight: 700; - margin-right: 8px; -} - -/* Search Box */ -.search-container { - max-width: 600px; - margin: 0 auto 40px; - position: relative; -} - -.search-input { - width: 100%; - padding: 15px 20px 15px 45px; - border: 1px solid rgba(0,0,0,0.1); - border-radius: 15px; - font-size: 16px; - outline: none; - transition: var(--transition); - background: white; -} - -.search-input:focus { - border-color: var(--accent-color); - box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.1); -} - -.search-icon { - position: absolute; - left: 15px; - top: 50%; - transform: translateY(-50%); - color: #ccc; -} - -/* Stats Modal Override */ -.stat-row { - display: flex; - justify-content: space-between; - padding: 10px 0; - border-bottom: 1px solid #eee; -} - -.stat-label { - color: var(--text-secondary); -} - -.stat-value { - font-weight: 600; -} - -.loading-text { - text-align: center; - padding: 40px; - color: var(--text-secondary); - font-size: 18px; -} - -.loading-details-text { - text-align: center; - padding: 20px; - color: #888; -} - -.load-more-container { - text-align: center; - margin-top: 40px; -} - -.load-more-btn { - background: white; - border: 1px solid #ddd; - padding: 12px 30px; - border-radius: 20px; - cursor: pointer; - font-weight: 600; - transition: var(--transition); -} - -.load-more-btn:hover { - background: #f9f9f9; - transform: translateY(-2px); -} - -/* Modal Redesign */ -.modal-content.expanded-modal { - max-width: 800px; - width: 90%; - display: flex; - flex-direction: column; - max-height: 90vh; - overflow-y: auto; -} - -/* Custom Scrollbar for Modal */ -.modal-content.expanded-modal::-webkit-scrollbar { - width: 6px; -} - -.modal-content.expanded-modal::-webkit-scrollbar-track { - background: transparent; - margin: 10px 0; -} - -.modal-content.expanded-modal::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.15); - border-radius: 10px; -} - -.modal-content.expanded-modal::-webkit-scrollbar-thumb:hover { - background: rgba(0, 0, 0, 0.25); -} - -.modal-top-section { - display: flex; - flex-wrap: wrap; - gap: 20px; - margin-bottom: 20px; -} - -.modal-identity { - flex: 1; - min-width: 200px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; -} - -.modal-identity img { - width: 100px; - height: 100px; - border-radius: 12px; - box-shadow: 0 4px 15px rgba(0,0,0,0.1); - margin-bottom: 15px; -} - -#modal-name { - margin: 5px 0; - font-size: 24px; -} - -#modal-uuid { - font-size: 12px; - color: #999; - font-family: monospace; - word-break: break-all; -} - -.stats-list-container.compact-stats { - flex: 1; - min-width: 250px; - display: flex; - flex-direction: column; - justify-content: center; - background: #f9f9f9; - padding: 15px; - border-radius: 12px; -} - -.modal-divider { - border: 0; - border-top: 1px solid #eee; - margin: 10px 0 20px; -} - -/* Accordion Styles */ -.accordion { - display: flex; - flex-direction: column; - gap: 10px; -} - -.accordion-item { - border: 1px solid #eee; - border-radius: 8px; - overflow: hidden; -} - -.accordion-header { - background: #fdfdfd; - padding: 12px 15px; - cursor: pointer; - font-weight: 600; - display: flex; - justify-content: space-between; - align-items: center; - transition: background 0.2s; -} - -.accordion-header:hover { - background: #f0f0f0; -} - -.accordion-header .icon { - margin-right: 8px; - width: 20px; - text-align: center; - color: var(--text-secondary); -} - -.accordion-header .arrow { - transition: transform 0.3s; -} - -.accordion-header.active .arrow { - transform: rotate(180deg); -} - -.accordion-header.active { - background: #f0f0f0; - color: var(--accent-color); -} - -.accordion-content { - display: none; - padding: 15px; - background: white; - border-top: 1px solid #eee; -} - -.accordion-content.show { - display: block; -} - -/* Item count badge in accordion header */ -.accordion-header .item-count { - font-size: 11px; - font-weight: 500; - color: var(--text-secondary); - background: var(--bg-color); - padding: 2px 8px; - border-radius: 10px; - margin-left: 8px; -} - -.accordion-header.active .item-count { - background: rgba(0, 113, 227, 0.08); - color: var(--accent-color); -} - -/* Search filter inside accordion content */ -.detail-search-wrapper { - position: relative; - margin-bottom: 12px; -} - -.detail-search-wrapper i { - position: absolute; - left: 10px; - top: 50%; - transform: translateY(-50%); - color: #c0c0c0; - font-size: 12px; - pointer-events: none; -} - -.detail-search { - width: 100%; - padding: 8px 12px 8px 30px; - border: 1px solid #eee; - border-radius: 8px; - font-size: 13px; - outline: none; - transition: border-color 0.2s, box-shadow 0.2s; - background: #fafafa; -} - -.detail-search:focus { - border-color: var(--accent-color); - box-shadow: 0 0 0 2px rgba(0, 113, 227, 0.08); - background: white; -} - -.detail-no-results { - text-align: center; - color: var(--text-secondary); - font-size: 13px; - padding: 20px 0; - display: none; -} - -/* Grid for stats inside accordion */ -.detail-stats-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 8px; -} - -.detail-stat-item { - display: flex; - flex-direction: column; - background: #fafafa; - border-radius: 8px; - padding: 10px 12px; - border-left: 3px solid transparent; - transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; - cursor: default; -} - -.detail-stat-item:hover { - background: #f0f4ff; - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -/* Top 3 ranking indicators */ -.detail-stat-item.rank-1 { - border-left-color: #FFD700; - background: linear-gradient(135deg, #fffdf0, #fafafa); -} - -.detail-stat-item.rank-2 { - border-left-color: #C0C0C0; - background: linear-gradient(135deg, #fafafa, #f8f8f8); -} - -.detail-stat-item.rank-3 { - border-left-color: #CD7F32; - background: linear-gradient(135deg, #fdf8f4, #fafafa); -} - -.detail-stat-item.rank-1:hover, -.detail-stat-item.rank-2:hover, -.detail-stat-item.rank-3:hover { - background: #f0f4ff; -} - -.detail-stat-value { - font-size: 15px; - font-weight: 700; - font-family: 'Inter', monospace; - color: var(--text-primary, #1d1d1f); - line-height: 1.3; -} - -.detail-stat-label { - font-size: 12px; - color: var(--text-secondary); - margin-top: 2px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.detail-stat-item.hidden { - display: none; -} - -/* Mobile Adjustments */ -@media (max-width: 600px) { - .modal-top-section { - flex-direction: column; - } - - .modal-identity, - .stats-list-container.compact-stats { - width: 100%; - } - - .detail-stats-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 6px; - } - - .detail-stat-item { - padding: 8px 10px; - } - - .detail-stat-value { - font-size: 14px; - } -} diff --git a/css/pages/towns.css b/css/pages/towns.css deleted file mode 100644 index a3b0c78..0000000 --- a/css/pages/towns.css +++ /dev/null @@ -1,1699 +0,0 @@ -/* Page-Specific Styles for Towns */ - -.towns-hero-bg { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/69267755e14e3.png'); -} - -/* Container */ -.towns-container { - max-width: 1200px; - margin: 0 auto; - padding: 40px 20px; -} - -/* Controls - same as facilities */ -.towns-container .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; -} - -.towns-container .controls-header-row { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 24px; - flex-wrap: wrap; - gap: 20px; -} - -.towns-container .section-title { - font-size: 24px; - font-weight: 600; - color: var(--text-primary); - margin: 0; -} - -.towns-container .search-box { - position: relative; - max-width: 400px; - width: 100%; -} - -.towns-container .search-box i { - position: absolute; - left: 16px; - top: 50%; - transform: translateY(-50%); - color: var(--text-secondary); -} - -.towns-container .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); -} - -.towns-container .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); -} - -.towns-container .filters-wrapper { - display: flex; - flex-direction: column; - gap: 16px; - border-top: 1px solid rgba(0,0,0,0.05); - padding-top: 20px; -} - -.towns-container .filter-group { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 12px; -} - -.towns-container .filter-label { - font-weight: 600; - font-size: 14px; - color: var(--text-secondary); - min-width: 70px; - display: flex; - align-items: center; - gap: 6px; -} - -.towns-container .filter-tags { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.towns-container .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; -} - -.towns-container .filter-tag:hover { - background: #f5f5f7; - color: var(--text-primary); - border-color: rgba(0,0,0,0.2); -} - -.towns-container .filter-tag.active { - background: var(--text-primary); - color: white; - border-color: var(--text-primary); -} - -/* ========== Town Cards Grid ========== */ -.towns-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - gap: 24px; -} - -.town-card { - background: var(--card-bg); - border-radius: var(--radius-medium); - box-shadow: 0 2px 12px rgba(0,0,0,0.04); - transition: var(--transition); - cursor: pointer; - display: flex; - flex-direction: column; - border: 1px solid rgba(0,0,0,0.03); - overflow: hidden; - position: relative; -} - -.town-card:hover { - transform: translateY(-4px); - box-shadow: 0 12px 32px rgba(0,0,0,0.08); -} - -/* Card logo background */ -.town-card-bg { - height: 140px; - background-size: cover; - background-position: center; - background-color: #e8ecf1; - position: relative; - display: flex; - align-items: center; - justify-content: center; -} - -.town-card-bg.no-logo { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -} - -.town-card-bg::after, -.town-modal-banner::after, -.town-preview-banner::after, -.town-preview-card-cover::after { - content: ""; - position: absolute; - inset: 0; - background: linear-gradient(to top, rgba(15, 23, 42, 0.28), transparent 55%); - pointer-events: none; -} - -.town-card-bg .town-logo-img { - max-height: 80px; - max-width: 80%; - object-fit: contain; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); -} - -.town-card-bg .town-logo-placeholder { - font-size: 48px; - color: rgba(255,255,255,0.6); - position: relative; - z-index: 1; -} - -/* Card icon badges overlay */ -.town-card-icons { - position: absolute; - bottom: -14px; - left: 16px; - display: flex; - gap: 8px; - z-index: 2; -} - -.town-icon-badge { - width: 28px; - height: 28px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - color: #fff; - box-shadow: 0 2px 8px rgba(0,0,0,0.15); - border: 2px solid #fff; -} - -/* Scale icons */ -.icon-scale-small { background: #60a5fa; } -.icon-scale-medium { background: #f59e0b; } -.icon-scale-large { background: #ef4444; } - -/* Town type icons */ -.icon-type-building { background: #8b5cf6; } -.icon-type-adventure { background: #10b981; } -.icon-type-industry { background: #f97316; } - -/* Recruitment icons */ -.icon-recruit-welcome { background: #22c55e; } -.icon-recruit-closed { background: #ef4444; } -.icon-recruit-maybe { background: #eab308; } - -.town-card-body { - padding: 24px 20px 20px; - flex: 1; - display: flex; - flex-direction: column; -} - -.town-card-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 10px; - line-height: 1.3; -} - -.town-card-meta { - display: flex; - gap: 8px; - flex-wrap: wrap; - margin-top: auto; - padding-top: 12px; - border-top: 1px solid #f0f0f0; -} - -.town-meta-tag { - font-size: 11px; - background: #f5f5f7; - padding: 4px 10px; - border-radius: 6px; - color: var(--text-secondary); - font-weight: 500; - display: flex; - align-items: center; - gap: 4px; -} - -/* ========== Town Detail Modal ========== */ -.town-modal-content { - background-color: #fff; - margin: 40px auto; - border-radius: var(--radius-large); - max-width: 720px; - width: 90%; - padding: 0; - box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3); - position: relative; - max-height: 90vh; - overflow-y: auto; - display: flex; - flex-direction: column; -} - -.town-modal-content::-webkit-scrollbar { - width: 6px; -} - -.town-modal-content::-webkit-scrollbar-track { - background: transparent; - margin: 10px 0; -} - -.town-modal-content::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.15); - border-radius: 10px; -} - -.town-modal-content::-webkit-scrollbar-thumb:hover { - background: rgba(0, 0, 0, 0.25); -} - -/* Modal hero banner */ -.town-modal-banner { - height: 180px; - background-size: cover; - background-position: center; - background-color: #e8ecf1; - position: relative; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-large) var(--radius-large) 0 0; -} - -.town-modal-banner.no-logo { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -} - -.town-modal-banner .town-banner-logo { - max-height: 100px; - max-width: 80%; - object-fit: contain; - border-radius: 12px; - box-shadow: 0 4px 16px rgba(0,0,0,0.2); -} - -.town-modal-banner .town-banner-placeholder { - font-size: 64px; - color: rgba(255,255,255,0.5); - position: relative; - z-index: 1; -} - -.town-modal .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%; -} - -.town-modal .close-modal:hover { - background: #f0f0f0; - color: var(--text-primary); -} - -.town-modal-header { - padding: 24px 40px 20px; - background: linear-gradient(to bottom, #fff, #fafafa); - border-bottom: 1px solid rgba(0,0,0,0.05); -} - -.town-modal-title { - font-size: 32px; - font-weight: 700; - margin-bottom: 16px; - line-height: 1.2; -} - -.town-modal-badges-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 16px; - flex-wrap: wrap; -} - -.town-modal-badges { - display: flex; - gap: 10px; - flex-wrap: wrap; -} - -.town-modal-actions { - display: flex; - gap: 8px; - align-items: center; - flex-shrink: 0; -} - -.town-badge { - padding: 6px 14px; - border-radius: 8px; - font-size: 13px; - font-weight: 600; - display: flex; - align-items: center; - gap: 6px; -} - -.badge-scale-small { background: #dbeafe; color: #1d4ed8; } -.badge-scale-medium { background: #fef3c7; color: #b45309; } -.badge-scale-large { background: #fee2e2; color: #b91c1c; } - -.badge-type-building { background: #ede9fe; color: #6d28d9; } -.badge-type-adventure { background: #d1fae5; color: #047857; } -.badge-type-industry { background: #ffedd5; color: #c2410c; } - -.badge-recruit-welcome { background: #e8fceb; color: #15803d; } -.badge-recruit-closed { background: #feebeb; color: #b91c1c; } -.badge-recruit-maybe { background: #fef9c3; color: #a16207; } - -.town-modal-body { - padding: 30px 40px 50px; -} - -.town-modal-body .modal-section { - margin-top: 32px; -} - -.town-modal-body .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; -} - -.town-modal-body .modal-section-title i { - color: var(--accent-color); - width: 20px; - text-align: center; -} - -/* Btn styles (share/edit) - reuse from facilities */ -.btn-share-town { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 6px 16px; - background: transparent; - color: var(--text-secondary); - border: 1.5px solid rgba(0,0,0,0.12); - border-radius: 18px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - white-space: nowrap; - flex-shrink: 0; -} - -.btn-share-town:hover { - color: var(--accent-color); - border-color: var(--accent-color); - background: rgba(0,113,227,0.04); -} - -.btn-share-town.shared { - color: #15803d; - border-color: #34c759; - background: #e8fceb; -} - -.btn-edit-town { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 6px 16px; - background: transparent; - color: var(--accent-color); - border: 1.5px solid var(--accent-color); - border-radius: 18px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - white-space: nowrap; - flex-shrink: 0; -} - -.btn-edit-town:hover { - background: var(--accent-color); - color: #fff; -} - -.town-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; -} - -.town-map-link:hover { - background: #005bb5; - transform: translateY(-1px); -} - -/* Contributors list */ -.town-modal-body .contributors-list { - display: flex; - flex-wrap: wrap; - gap: 10px; -} - -.town-modal-body .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); -} - -.town-modal-body .contributor-tag img { - width: 24px; - height: 24px; - border-radius: 50%; - margin-right: 10px; - background: #eee; -} - -/* Instructions & Notes */ -.town-modal-body .instruction-content, -.town-modal-body .notes-content { - background: #f9f9fa; - padding: 24px; - border-radius: 16px; - border: 1px solid rgba(0,0,0,0.03); -} - -.town-modal-body .instruction-content p, -.town-modal-body .notes-content p { - font-size: 15px; - margin-bottom: 12px; - color: var(--text-primary); - line-height: 1.7; -} - -.town-modal-body .instruction-content p:last-child, -.town-modal-body .notes-content p:last-child { - margin-bottom: 0; -} - -.town-modal-body .instruction-content img, -.town-modal-body .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); -} - -/* Video Embed */ -.town-modal-body .video-embed-wrapper { - position: relative; - width: 100%; - padding-bottom: 56.25%; - margin: 12px 0 20px; - border-radius: 12px; - overflow: hidden; - background: #000; - box-shadow: 0 4px 12px rgba(0,0,0,0.08); -} - -.town-modal-body .video-embed-wrapper iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: none; -} - -/* No results */ -.towns-container .no-results-message { - text-align: center; - padding: 60px; - color: var(--text-secondary); - font-size: 16px; - background: var(--card-bg); - border-radius: var(--radius-medium); -} - -.towns-container .is-hidden { - display: none !important; -} - -/* ========== Add & Editor shared buttons ========== */ -.towns-container .title-with-action { - display: flex; - align-items: center; - gap: 16px; -} - -.btn-add-town { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 8px 18px; - background: var(--accent-color); - color: #fff; - border: none; - border-radius: 20px; - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - white-space: nowrap; -} - -.btn-add-town:hover { - background: #005bb5; - transform: translateY(-1px); -} - -/* ========== Editor Modal ========== */ -.town-editor-modal-content { - background-color: #fff; - margin: 20px auto; - border-radius: var(--radius-large); - max-width: 1280px; - width: 95%; - padding: 0; - box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3); - position: relative; - max-height: calc(100vh - 40px); - overflow: hidden; - display: flex; - flex-direction: column; -} - -.town-editor-modal-content .close-editor-modal { - position: absolute; - top: 16px; - right: 20px; - font-size: 24px; - color: var(--text-secondary); - cursor: pointer; - transition: 0.2s; - z-index: 10; - background: rgba(255,255,255,0.9); - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; -} - -.town-editor-modal-content .close-editor-modal:hover { - background: #f0f0f0; - color: var(--text-primary); -} - -.town-editor-modal-content .editor-modal-header { - padding: 20px 28px; - border-bottom: 1px solid rgba(0,0,0,0.08); - background: linear-gradient(to bottom, #fff, #fafafa); - border-radius: var(--radius-large) var(--radius-large) 0 0; - flex-shrink: 0; -} - -.town-editor-modal-content .editor-modal-header h3 { - font-size: 22px; - font-weight: 700; - display: flex; - align-items: center; - gap: 10px; -} - -.town-editor-modal-content .editor-layout { - display: flex; - flex: 1; - overflow: hidden; - min-height: 0; -} - -.town-editor-modal-content .editor-preview { - flex: 0 0 45%; - display: flex; - flex-direction: column; - border-right: 1px solid rgba(0,0,0,0.08); - background: #f5f5f7; -} - -.town-editor-modal-content .editor-panel-title { - font-size: 12px; - font-weight: 700; - color: var(--text-secondary); - padding: 14px 24px; - border-bottom: 1px solid rgba(0,0,0,0.05); - display: flex; - align-items: center; - gap: 8px; - text-transform: uppercase; - letter-spacing: 0.6px; - flex-shrink: 0; - background: rgba(255,255,255,0.6); -} - -.town-editor-modal-content .editor-preview-content { - flex: 1; - overflow-y: auto; - padding: 20px; -} - -.town-editor-modal-content .preview-stack { - display: flex; - flex-direction: column; - gap: 20px; -} - -.town-editor-modal-content .editor-preview-content::-webkit-scrollbar, -.town-editor-modal-content .editor-form-scroll::-webkit-scrollbar { - width: 5px; -} - -.town-editor-modal-content .editor-preview-content::-webkit-scrollbar-track, -.town-editor-modal-content .editor-form-scroll::-webkit-scrollbar-track { - background: transparent; -} - -.town-editor-modal-content .editor-preview-content::-webkit-scrollbar-thumb, -.town-editor-modal-content .editor-form-scroll::-webkit-scrollbar-thumb { - background: rgba(0,0,0,0.12); - border-radius: 10px; -} - -/* Preview Card in editor */ -.town-editor-modal-content .preview-facility { - background: #fff; - border-radius: 16px; - box-shadow: 0 2px 12px rgba(0,0,0,0.06); - overflow: hidden; -} - -.town-editor-modal-content .preview-card-shell { - background: #fff; - border-radius: 18px; - overflow: hidden; - box-shadow: 0 16px 36px rgba(15, 23, 42, 0.08); - border: 1px solid rgba(15, 23, 42, 0.06); -} - -.town-editor-modal-content .preview-card-cover { - height: 164px; - position: relative; - display: flex; - align-items: center; - justify-content: center; - background-size: cover; - background-position: center; - background-color: #e8ecf1; -} - -.town-editor-modal-content .preview-card-cover.no-logo { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -} - -.town-editor-modal-content .preview-card-cover .town-logo-placeholder { - font-size: 52px; - color: rgba(255,255,255,0.62); - position: relative; - z-index: 1; -} - -.town-editor-modal-content .preview-card-body { - padding: 24px 20px 20px; -} - -.town-editor-modal-content .preview-card-title { - font-size: 19px; - font-weight: 700; - color: var(--text-primary); - margin: 0 0 12px; -} - -.town-editor-modal-content .preview-card-icons { - position: absolute; - left: 16px; - bottom: -14px; - display: flex; - gap: 8px; - z-index: 2; -} - -.town-editor-modal-content .preview-card-meta { - display: flex; - gap: 8px; - flex-wrap: wrap; - padding-top: 14px; - border-top: 1px solid #eef2f7; -} - -.town-editor-modal-content .preview-card-tag { - font-size: 11px; - background: #f5f5f7; - padding: 4px 10px; - border-radius: 6px; - color: var(--text-secondary); - font-weight: 500; - display: inline-flex; - align-items: center; - gap: 4px; -} - -.town-editor-modal-content .preview-detail-shell { - background: #fff; - border-radius: 18px; - overflow: hidden; - box-shadow: 0 16px 36px rgba(15, 23, 42, 0.08); - border: 1px solid rgba(15, 23, 42, 0.06); -} - -.town-editor-modal-content .preview-detail-header { - padding: 24px 24px 20px; - background: linear-gradient(to bottom, #fff, #fafafa); - border-bottom: 1px solid rgba(0,0,0,0.05); -} - -.town-editor-modal-content .preview-detail-title { - font-size: 24px; - font-weight: 700; - margin: 0 0 14px; - line-height: 1.2; -} - -.town-editor-modal-content .preview-detail-body { - padding: 22px 24px 24px; -} - -.town-editor-modal-content .town-preview-banner { - height: 156px; - position: relative; - display: flex; - align-items: center; - justify-content: center; - background-size: cover; - background-position: center; - background-color: #e8ecf1; -} - -.town-editor-modal-content .town-preview-banner.no-logo { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -} - -.town-editor-modal-content .town-preview-banner .town-banner-placeholder { - font-size: 60px; - color: rgba(255,255,255,0.56); - position: relative; - z-index: 1; -} - -.town-editor-modal-content .preview-section { - margin-top: 22px; -} - -.town-editor-modal-content .preview-section:first-child { - margin-top: 0; -} - -.town-editor-modal-content .preview-section-title { - font-size: 14px; - font-weight: 700; - margin-bottom: 12px; - color: var(--text-primary); - display: flex; - align-items: center; - gap: 10px; - border-left: 4px solid var(--accent-color); - padding-left: 12px; -} - -.town-editor-modal-content .preview-inline-text { - font-size: 14px; - color: var(--text-primary); - line-height: 1.7; -} - -.town-editor-modal-content .preview-badges { - display: flex; - gap: 10px; - flex-wrap: wrap; -} - -.town-editor-modal-content .contributors-list { - display: flex; - flex-wrap: wrap; - gap: 10px; -} - -.town-editor-modal-content .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); -} - -.town-editor-modal-content .contributor-tag img { - width: 24px; - height: 24px; - border-radius: 50%; - margin-right: 10px; - background: #eee; -} - -.town-editor-modal-content .instruction-content { - background: #f9f9fa; - padding: 20px; - border-radius: 16px; - border: 1px solid rgba(0,0,0,0.03); -} - -.town-editor-modal-content .instruction-content p { - font-size: 14px; - margin-bottom: 12px; - color: var(--text-primary); - line-height: 1.7; -} - -.town-editor-modal-content .instruction-content p:last-child { - margin-bottom: 0; -} - -.town-editor-modal-content .instruction-content img { - max-width: 100%; - border-radius: 12px; - margin: 12px 0 16px; - border: 1px solid rgba(0,0,0,0.05); - box-shadow: 0 4px 12px rgba(0,0,0,0.05); -} - -.town-editor-modal-content .video-embed-wrapper { - position: relative; - width: 100%; - padding-bottom: 56.25%; - margin: 12px 0 16px; - border-radius: 12px; - overflow: hidden; - background: #000; - box-shadow: 0 4px 12px rgba(0,0,0,0.08); -} - -.town-editor-modal-content .video-embed-wrapper iframe { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - border: none; -} - -.town-editor-modal-content .preview-header { - padding: 24px 24px 16px; - background: linear-gradient(to bottom, #fff, #fafafa); - border-bottom: 1px solid rgba(0,0,0,0.05); -} - -.town-editor-modal-content .preview-title { - font-size: 22px; - font-weight: 700; - margin-bottom: 12px; - line-height: 1.2; -} - -.town-editor-modal-content .preview-body { - padding: 20px 24px 24px; -} - -.town-editor-modal-content .preview-body .modal-section { - margin-top: 20px; -} - -.town-editor-modal-content .preview-body .modal-section-title { - font-size: 14px; -} - -.town-editor-modal-content .preview-intro { - font-size: 15px; - line-height: 1.6; - color: var(--text-primary); - margin-bottom: 20px; -} - -.town-editor-modal-content .text-secondary { - color: var(--text-secondary); -} - -/* Editor Form */ -.town-editor-modal-content .editor-form { - flex: 0 0 55%; - display: flex; - flex-direction: column; - min-width: 0; -} - -.town-editor-modal-content .editor-form-scroll { - flex: 1; - overflow-y: auto; - padding: 24px 28px 40px; -} - -.town-editor-modal-content .form-group { - margin-bottom: 22px; -} - -.town-editor-modal-content .form-group > label { - display: block; - font-size: 12px; - font-weight: 700; - color: var(--text-secondary); - margin-bottom: 8px; - text-transform: uppercase; - letter-spacing: 0.4px; -} - -.town-editor-modal-content .form-group input[type="text"], -.town-editor-modal-content .form-group input[type="number"], -.town-editor-modal-content .form-group textarea { - width: 100%; - padding: 12px 16px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - font-size: 14px; - font-family: inherit; - background-color: #f9f9fa; - transition: all 0.2s ease; - color: var(--text-primary); - box-sizing: border-box; -} - -.town-editor-modal-content .form-group input:focus, -.town-editor-modal-content .form-group textarea:focus { - outline: none; - border-color: var(--accent-color); - background-color: #fff; - box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1); -} - -/* Custom Select Dropdown */ -.town-editor-modal-content .custom-select { - position: relative; - width: 100%; - user-select: none; - font-size: 14px; -} - -.town-editor-modal-content .custom-select-trigger { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - padding: 12px 16px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - background-color: #f9f9fa; - color: var(--text-primary); - cursor: pointer; - transition: all 0.2s ease; - box-sizing: border-box; -} - -.town-editor-modal-content .custom-select-trigger i { - color: var(--text-secondary); - font-size: 12px; - transition: transform 0.3s ease; -} - -.town-editor-modal-content .custom-select:hover .custom-select-trigger { - background-color: #fff; - border-color: rgba(0,0,0,0.2); -} - -.town-editor-modal-content .custom-select.open .custom-select-trigger { - border-color: var(--accent-color); - background-color: #fff; - box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1); -} - -.town-editor-modal-content .custom-select.open .custom-select-trigger i { - transform: rotate(180deg); -} - -.town-editor-modal-content .custom-select-options { - position: absolute; - top: calc(100% + 8px); - left: 0; - right: 0; - background: #fff; - border-radius: 12px; - box-shadow: 0 10px 40px rgba(0,0,0,0.15); - border: 1px solid rgba(0,0,0,0.08); - opacity: 0; - visibility: hidden; - transform: translateY(-10px); - transition: all 0.2s cubic-bezier(0.25, 1, 0.5, 1); - z-index: 100; - padding: 8px; -} - -.town-editor-modal-content .custom-select.open .custom-select-options { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.town-editor-modal-content .custom-option { - padding: 10px 14px; - border-radius: 8px; - cursor: pointer; - transition: background 0.2s, color 0.2s; - color: var(--text-primary); - margin-bottom: 2px; -} - -.town-editor-modal-content .custom-option:last-child { - margin-bottom: 0; -} - -.town-editor-modal-content .custom-option:hover { - background: #f5f5f7; -} - -.town-editor-modal-content .custom-option.selected { - background: #e0f2fe; - color: #0369a1; - font-weight: 600; -} - -.town-editor-modal-content .form-group textarea { - resize: vertical; - min-height: 60px; -} - -.town-editor-modal-content .form-row { - display: flex; - gap: 14px; -} - -.town-editor-modal-content .form-row .form-group { - flex: 1; -} - -.town-editor-modal-content .gradient-picker-row { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 12px; -} - -.town-editor-modal-content .color-picker-field { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 10px 12px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - background: #f9f9fa; -} - -.town-editor-modal-content .color-picker-field span { - font-size: 13px; - font-weight: 600; - color: var(--text-primary); -} - -.town-editor-modal-content .color-picker-field input[type="color"] { - width: 48px; - height: 36px; - padding: 0; - border: none; - border-radius: 10px; - background: transparent; - cursor: pointer; -} - -.town-editor-modal-content .color-picker-field input[type="color"]::-webkit-color-swatch-wrapper { - padding: 0; -} - -.town-editor-modal-content .color-picker-field input[type="color"]::-webkit-color-swatch { - border: none; - border-radius: 10px; -} - -.town-editor-modal-content .field-hint { - margin: 10px 0 0; - font-size: 12px; - line-height: 1.5; - color: var(--text-secondary); -} - -/* Toggle Switch */ -.town-editor-modal-content .toggle-label { - display: flex; - align-items: center; - justify-content: space-between; - font-weight: 600; - font-size: 14px; - color: var(--text-primary); - cursor: pointer; -} - -.toggle-switch { - position: relative; - width: 44px; - height: 24px; - flex-shrink: 0; -} - -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; - position: absolute; -} - -.toggle-slider { - position: absolute; - inset: 0; - background: #ccc; - border-radius: 24px; - transition: 0.25s; - cursor: pointer; -} - -.toggle-slider::before { - content: ""; - position: absolute; - width: 18px; - height: 18px; - left: 3px; - bottom: 3px; - background: #fff; - border-radius: 50%; - transition: 0.25s; - box-shadow: 0 1px 3px rgba(0,0,0,0.2); -} - -.toggle-switch input:checked + .toggle-slider { - background: var(--accent-color, #0071e3); -} - -.toggle-switch input:checked + .toggle-slider::before { - transform: translateX(20px); -} - -/* Tags Input */ -.town-editor-modal-content .tags-input-wrapper { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px; - padding: 8px 12px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - background: #f9f9fa; - transition: border-color 0.2s, background 0.2s, box-shadow 0.2s; - cursor: text; -} - -.town-editor-modal-content .tags-input-wrapper:focus-within { - border-color: var(--accent-color); - background: #fff; - box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1); -} - -.town-editor-modal-content .tags-list { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.town-editor-modal-content .editor-tag { - display: inline-flex; - align-items: center; - gap: 6px; - background: var(--accent-color); - color: #fff; - padding: 4px 10px; - border-radius: 14px; - font-size: 12px; - font-weight: 600; -} - -.town-editor-modal-content .editor-tag-remove { - cursor: pointer; - opacity: 0.7; - transition: 0.2s; - font-size: 10px; -} - -.town-editor-modal-content .editor-tag-remove:hover { - opacity: 1; -} - -.town-editor-modal-content .tags-input-wrapper input { - border: none !important; - background: transparent !important; - padding: 4px 0 !important; - font-size: 13px; - flex: 1; - min-width: 140px; - box-shadow: none !important; -} - -.town-editor-modal-content .tags-input-wrapper input:focus { - outline: none; -} - -/* Sortable List */ -.town-editor-modal-content .sortable-list { - min-height: 8px; - margin-bottom: 10px; -} - -.town-editor-modal-content .sortable-item { - display: flex; - align-items: flex-start; - gap: 10px; - padding: 10px 12px; - margin-bottom: 8px; - background: #fff; - border: 1.5px solid rgba(0,0,0,0.08); - border-radius: 12px; - transition: box-shadow 0.2s, border-color 0.2s, opacity 0.2s; -} - -.town-editor-modal-content .sortable-item:last-child { - margin-bottom: 0; -} - -.town-editor-modal-content .sortable-item:hover { - border-color: rgba(0,0,0,0.15); - box-shadow: 0 2px 8px rgba(0,0,0,0.04); -} - -.town-editor-modal-content .sortable-item.dragging { - opacity: 0.4; - border-color: var(--accent-color); -} - -.town-editor-modal-content .sortable-item.drag-over { - border-color: var(--accent-color); - box-shadow: 0 0 0 2px rgba(0, 113, 227, 0.15); -} - -.town-editor-modal-content .drag-handle { - cursor: grab; - color: var(--text-secondary); - padding: 6px 2px; - font-size: 14px; - opacity: 0.35; - transition: 0.2s; - flex-shrink: 0; -} - -.town-editor-modal-content .drag-handle:active { - cursor: grabbing; -} - -.town-editor-modal-content .sortable-item:hover .drag-handle { - opacity: 0.7; -} - -.town-editor-modal-content .item-type-badge { - font-size: 10px; - font-weight: 700; - padding: 3px 8px; - border-radius: 6px; - white-space: nowrap; - flex-shrink: 0; - margin-top: 6px; - text-transform: uppercase; - letter-spacing: 0.3px; -} - -.town-editor-modal-content .badge-text { - background: #e8f5e9; - color: #2e7d32; -} - -.town-editor-modal-content .badge-image { - background: #e3f2fd; - color: #1565c0; -} - -.town-editor-modal-content .badge-video { - background: #fce4ec; - color: #c62828; -} - -.town-editor-modal-content .sortable-item .item-content { - flex: 1; - border: 1px solid rgba(0,0,0,0.06) !important; - border-radius: 8px !important; - padding: 8px 10px !important; - font-size: 13px !important; - background: #fafafa !important; - min-height: unset; - font-family: inherit; - resize: vertical; -} - -.town-editor-modal-content .sortable-item .item-content:focus { - border-color: var(--accent-color) !important; - background: #fff !important; - box-shadow: none !important; -} - -.town-editor-modal-content .remove-item-btn { - background: none; - border: none; - color: #ccc; - cursor: pointer; - padding: 6px; - border-radius: 8px; - transition: 0.2s; - flex-shrink: 0; - margin-top: 3px; - font-size: 13px; -} - -.town-editor-modal-content .remove-item-btn:hover { - color: #ef4444; - background: #fef2f2; -} - -.town-editor-modal-content .add-item-row { - display: flex; - gap: 8px; -} - -.town-editor-modal-content .add-item-btn { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 8px 14px; - background: #f5f5f7; - border: 1.5px dashed rgba(0,0,0,0.12); - border-radius: 10px; - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); - cursor: pointer; - transition: var(--transition); -} - -.town-editor-modal-content .add-item-btn:hover { - border-color: var(--accent-color); - color: var(--accent-color); - background: #f0f7ff; -} - -/* Editor Actions */ -.town-editor-modal-content .editor-actions { - margin-top: 28px; - padding-top: 20px; - border-top: 1px solid rgba(0,0,0,0.08); - display: flex; - justify-content: flex-end; -} - -.town-editor-modal-content .btn-save-town { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 12px 28px; - background: var(--brand-green); - color: #fff; - border: none; - border-radius: 14px; - font-size: 15px; - font-weight: 700; - cursor: pointer; - transition: var(--transition); -} - -.town-editor-modal-content .btn-save-town:hover { - background: #2db84d; - transform: translateY(-1px); - box-shadow: 0 4px 16px rgba(52, 199, 89, 0.3); -} - -/* JSON Output Modal */ -.town-json-output-content { - background-color: #fff; - margin: 60px auto; - border-radius: var(--radius-large); - max-width: 640px; - width: 90%; - padding: 36px; - box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3); - position: relative; -} - -.town-json-output-content .close-json-modal { - position: absolute; - top: 16px; - right: 20px; - font-size: 24px; - color: var(--text-secondary); - cursor: pointer; - transition: 0.2s; - z-index: 10; - background: rgba(255,255,255,0.9); - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; -} - -.town-json-output-content .close-json-modal:hover { - background: #f0f0f0; - color: var(--text-primary); -} - -.town-json-output-content h3 { - font-size: 20px; - font-weight: 700; - margin-bottom: 8px; - display: flex; - align-items: center; - gap: 10px; -} - -.town-json-output-content .json-output-hint { - font-size: 14px; - color: var(--text-secondary); - margin-bottom: 20px; - line-height: 1.5; -} - -.town-json-output-content #town-json-output { - width: 100%; - height: 300px; - padding: 16px; - border: 1.5px solid rgba(0,0,0,0.1); - border-radius: 12px; - font-family: 'Courier New', Courier, monospace; - font-size: 12px; - background: #f5f5f7; - color: var(--text-primary); - resize: vertical; - margin-bottom: 16px; -} - -.town-json-output-content #town-json-output:focus { - outline: none; - border-color: var(--accent-color); -} - -.town-json-output-content .btn-copy-json { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 24px; - background: var(--accent-color); - color: #fff; - border: none; - border-radius: 12px; - font-size: 14px; - font-weight: 600; - cursor: pointer; - transition: var(--transition); - width: 100%; - justify-content: center; -} - -.town-json-output-content .btn-copy-json:hover { - background: #005bb5; -} - -/* ========== Responsive ========== */ - -@media (max-width: 900px) { - .town-editor-modal-content { - margin: 0; - width: 100%; - max-width: 100%; - max-height: 100%; - height: 100%; - border-radius: 0; - } - - .town-editor-modal-content .editor-layout { - flex-direction: column; - } - - .town-editor-modal-content .editor-preview { - flex: none; - max-height: 35vh; - border-right: none; - border-bottom: 1px solid rgba(0,0,0,0.08); - } - - .town-editor-modal-content .editor-form { - flex: 1; - min-height: 0; - } - - .town-editor-modal-content .form-row { - flex-direction: column; - gap: 0; - } - - .town-editor-modal-content .gradient-picker-row { - grid-template-columns: 1fr; - } - - .town-editor-modal-content .close-editor-modal { - top: 12px; - right: 14px; - } -} - -@media (max-width: 768px) { - .towns-container .controls-header-row { - flex-direction: column; - align-items: stretch; - } - - .towns-container .search-box { - max-width: 100%; - } - - .towns-container .filter-label { - min-width: auto; - margin-bottom: 4px; - } - - .town-modal-content { - margin: 0; - width: 100%; - height: 100%; - max-height: 100%; - border-radius: 0; - } - - .town-modal-banner { - border-radius: 0; - } - - .town-modal .close-modal { - top: 15px; - right: 15px; - } - - .town-modal-body { - padding: 24px 24px 80px; - } - - .town-modal-header { - padding: 20px 24px; - } - - .towns-container .title-with-action { - flex-wrap: wrap; - gap: 10px; - } - - .town-json-output-content { - margin: 0; - width: 100%; - height: 100%; - max-height: 100%; - border-radius: 0; - } - - .town-editor-modal-content .editor-modal-header { - padding: 16px 20px; - } - - .town-editor-modal-content .editor-form-scroll { - padding: 20px; - } -} diff --git a/css/style.css b/css/style.css deleted file mode 100644 index 2373c15..0000000 --- a/css/style.css +++ /dev/null @@ -1,968 +0,0 @@ -:root { - --bg-color: #f5f5f7; - --card-bg: #ffffff; - --text-primary: #1d1d1f; - --text-secondary: #86868b; - --accent-color: #0071e3; /* Apple Blue, but we might want Green */ - --brand-green: #34c759; /* Apple Green */ - --radius-large: 30px; - --radius-medium: 20px; - --transition: all 0.4s cubic-bezier(0.25, 1, 0.5, 1); -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: "Inter", "Noto Sans SC", -apple-system, BlinkMacSystemFont, sans-serif; - background-color: var(--bg-color); - color: var(--text-primary); - -webkit-font-smoothing: antialiased; -} - -.skip-to-main { - position: absolute; - left: -9999px; - top: 0; - z-index: 999; -} - -.home-hidden { - display: none; -} - -/* Navbar */ -.navbar { - position: fixed; - top: 0; - width: 100%; - height: 44px; - background: rgba(255, 255, 255, 0.8); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - z-index: 1000; - border-bottom: 1px solid rgba(0,0,0,0.05); - display: flex; - justify-content: center; -} - -.nav-content { - width: 100%; - max-width: 1000px; - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 20px; - font-size: 12px; -} - -.logo a { - display: block; -} - -.logo img { - height: 32px; - width: auto; - display: block; -} - -.nav-links { - display: flex; - align-items: center; -} - -.nav-links a { - text-decoration: none; - color: var(--text-primary); - margin-left: 24px; - opacity: 0.8; - transition: opacity 0.2s; -} - -.nav-links a:hover { - opacity: 1; -} - -.nav-cta-container { - display: flex; - align-items: center; - margin-left: 24px; -} - -.nav-cta { - background: #0071e3; - color: white !important; - padding: 6px 14px; - border-radius: 980px; - font-size: 12px; - font-weight: 500; - opacity: 1 !important; - transition: all 0.2s; - text-decoration: none; -} - -.nav-cta:hover { - background: #0077ed; - transform: scale(1.05); -} - -/* Hero */ -.hero { - height: 80vh; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - padding-top: 44px; /* Navbar height */ - background-color: #000; /* Fallback */ - background-size: cover; - background-position: center; - position: relative; - color: white; -} - -.page-hero { - height: 35vh; - min-height: 300px; -} - -.home-hero { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/69267755e14e3.png'); -} - -.hero-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.4); /* Dark overlay for text readability */ - z-index: 1; -} - -.hero-content { - position: relative; - z-index: 2; -} - -.hero-title { - font-size: 56px; - line-height: 1.07143; - font-weight: 700; - letter-spacing: -0.005em; - margin-bottom: 10px; - color: white; - text-shadow: 0 2px 10px rgba(0,0,0,0.3); - /* Remove gradient text for image background */ - background: none; - -webkit-text-fill-color: white; -} - -.hero-subtitle { - font-size: 28px; - line-height: 1.10722; - font-weight: 400; - letter-spacing: .004em; - color: rgba(255, 255, 255, 0.9); - margin-bottom: 15px; - text-shadow: 0 2px 5px rgba(0,0,0,0.3); -} - -.hero-subtitle-container { - margin-bottom: 15px; -} - -.subtitle-prefix, .subtitle-suffix { - white-space: nowrap; -} - -.subtitle-dynamic { - white-space: nowrap; - margin: 0 8px; - position: relative; - display: inline-block; - min-width: 40px; - text-align: center; -} - -/* Fade animation for dynamic subtitle */ -.fade-enter { - opacity: 0; - transform: translateY(10px); -} - -.fade-enter-active { - opacity: 1; - transform: translateY(0); - transition: opacity 0.5s ease, transform 0.5s ease; -} - -.fade-exit { - opacity: 1; - transform: translateY(0); -} - -.fade-exit-active { - opacity: 0; - transform: translateY(-10px); - transition: opacity 0.5s ease, transform 0.5s ease; -} - -.server-runtime { - font-size: 18px; - color: rgba(255, 255, 255, 0.85); - margin-bottom: 40px; - font-weight: 500; - text-shadow: 0 1px 3px rgba(0,0,0,0.3); -} - -.hero-actions { - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; -} - -.server-ip-box { - background: rgba(255, 255, 255, 0.2); - backdrop-filter: blur(10px); - border: 1px solid rgba(255, 255, 255, 0.3); - color: white; - padding: 12px 24px; - border-radius: 980px; - font-size: 17px; - font-weight: 400; - cursor: pointer; - transition: var(--transition); - display: flex; - align-items: center; - gap: 8px; - position: relative; -} - -.server-ip-box:hover { - background: rgba(255, 255, 255, 0.3); - transform: scale(1.02); -} - -.ip-hint { - font-size: 12px; - color: rgba(255, 255, 255, 0.7); -} - -.tooltip { - position: absolute; - top: -30px; - background: #333; - color: #fff; - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - opacity: 0; - transition: opacity 0.2s; -} -.tooltip.show { opacity: 1; } - -/* Online Status */ -.online-status-box { - margin-top: 15px; - position: relative; - cursor: default; -} - -.status-indicator { - background: rgba(0, 0, 0, 0.4); - backdrop-filter: blur(10px); - padding: 8px 16px; - border-radius: 20px; - color: rgba(255, 255, 255, 0.9); - font-size: 14px; - display: flex; - align-items: center; - gap: 8px; - transition: var(--transition); -} - -.status-dot { - width: 8px; - height: 8px; - background-color: #34c759; /* Green */ - border-radius: 50%; - box-shadow: 0 0 8px rgba(52, 199, 89, 0.6); -} - -.status-dot.offline { - background-color: #ff3b30; /* Red */ - box-shadow: 0 0 8px rgba(255, 59, 48, 0.6); -} - -.players-tooltip { - position: absolute; - top: 100%; - left: 50%; - transform: translateX(-50%); - margin-top: 10px; - background: rgba(255, 255, 255, 0.95); - color: #1d1d1f; - padding: 10px; - border-radius: 12px; - width: 200px; - max-height: 300px; - overflow-y: auto; - box-shadow: 0 4px 20px rgba(0,0,0,0.15); - opacity: 0; - visibility: hidden; - transition: all 0.2s ease; - z-index: 10; - text-align: left; -} - -.online-status-box:hover .players-tooltip { - opacity: 1; - visibility: visible; - margin-top: 15px; -} - -.players-tooltip::before { - content: ''; - position: absolute; - top: -6px; - left: 50%; - transform: translateX(-50%); - border-width: 0 6px 6px 6px; - border-style: solid; - border-color: transparent transparent rgba(255, 255, 255, 0.95) transparent; -} - -.player-item { - padding: 6px 8px; - font-size: 13px; - border-bottom: 1px solid rgba(0,0,0,0.05); - display: flex; - align-items: center; - gap: 8px; -} - -.player-item-center { - justify-content: center; -} - -.player-item-muted { - justify-content: center; - color: #86868b; -} - -.player-item-error { - justify-content: center; - color: #ff3b30; -} - -.player-item:last-child { - border-bottom: none; -} - -.player-avatar { - width: 16px; - height: 16px; - border-radius: 2px; -} - -/* Features Section */ -.features-section { - padding: 100px 0; - background: var(--bg-color); -} - -.container { - max-width: 1000px; - margin: 0 auto; - padding: 0 20px; -} - -.section-header { - font-size: 40px; - font-weight: 700; - text-align: center; - margin-bottom: 60px; -} - -/* Bento Grid */ -.bento-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - grid-auto-rows: 180px; - gap: 20px; -} - -.bento-item { - background: var(--card-bg); - border-radius: var(--radius-large); - padding: 30px; - display: flex; - flex-direction: column; - justify-content: flex-end; /* Align content to bottom for image cards */ - align-items: flex-start; /* Align left */ - text-align: left; - transition: var(--transition); - box-shadow: 2px 4px 12px rgba(0,0,0,0.02); - overflow: hidden; - position: relative; - background-size: cover; - background-position: center; -} - -.bento-item:hover { - transform: scale(1.02); - box-shadow: 2px 8px 24px rgba(0,0,0,0.06); -} - -/* Overlay for Bento Items with Images */ -.bento-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%); - z-index: 1; -} - -.bento-content { - position: relative; - z-index: 2; - width: 100%; -} - -/* Grid Spans */ -.large-item { - grid-column: span 2; - grid-row: span 2; -} - -.medium-item { - grid-column: span 2; - grid-row: span 1; -} - -.small-item { - grid-column: span 1; - grid-row: span 1; -} - -.feature-pure { - background-image: url('https://img.lunadeer.cn/i/2024/02/21/65d592eb4afad.jpg'); -} - -.feature-dev { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/6926982718ba8.png'); -} - -.feature-params { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/6926775006dea.jpg'); -} - -.feature-land { - background-image: url('https://img.lunadeer.cn/i/2024/02/21/65d592ea6faa1.jpg'); -} - -.feature-bedrock { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/692677560db46.png'); -} - -.feature-hardware { - background-image: url('https://img.lunadeer.cn/i/2024/02/21/65d592e248066.jpg'); -} - -.feature-fun { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/692677566b07b.png'); -} - -.feature-update { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/692697b71431b.png'); -} - -.feature-guide { - background-image: url('https://img.lunadeer.cn/i/2025/11/26/692697b7376c7.png'); -} - -/* Hardware Card (No Image) */ -.hardware-card { - background: linear-gradient(135deg, #2c3e50 0%, #000000 100%); - justify-content: center; - align-items: center; - text-align: center; - position: relative; -} - -/* Add a subtle tech grid pattern overlay */ -.hardware-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-image: - linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px), - linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px); - background-size: 20px 20px; - z-index: 1; -} - -.hardware-card .bento-content { - color: white; - z-index: 2; -} - -.hardware-card .icon { - color: white; - font-size: 32px; - margin-bottom: 10px; -} - -.hardware-card h4 { - color: white; - font-size: 17px; - margin: 10px 0 5px; -} - -.hardware-card p { - color: rgba(255, 255, 255, 0.8); - font-size: 13px; -} - -/* Content Styling for Image Cards */ -.bento-item:not(.hardware-card) .icon { - color: white; - font-size: 32px; - margin-bottom: 10px; -} - -.bento-item:not(.hardware-card) h3, -.bento-item:not(.hardware-card) h4 { - color: white; - text-shadow: 0 2px 4px rgba(0,0,0,0.3); -} - -.bento-item:not(.hardware-card) p { - color: rgba(255, 255, 255, 0.9); - font-size: 14px; - text-shadow: 0 1px 2px rgba(0,0,0,0.3); -} - -.bento-item h3 { - font-size: 24px; - margin-bottom: 10px; -} - -.small-item h4 { - font-size: 17px; - margin: 10px 0 5px; -} - -.small-item p { - font-size: 13px; -} - -/* Details Section */ -.details-section { - padding: 100px 0; - background: #fff; -} - -.detail-row { - margin-bottom: 80px; - text-align: center; -} - -.detail-text h3 { - font-size: 32px; - margin-bottom: 15px; -} - -.detail-text p { - font-size: 19px; - color: var(--text-secondary); - max-width: 600px; - margin: 0 auto; -} - -/* Footer */ -footer { - background: var(--bg-color); - padding: 40px 0; - border-top: 1px solid #e5e5e5; - font-size: 12px; - color: var(--text-secondary); -} - -/* Responsive */ -@media (max-width: 900px) { - .bento-grid { - grid-template-columns: 1fr; - grid-auto-rows: auto; - } - - .large-item, .medium-item, .small-item { - grid-column: span 1; - grid-row: auto; - min-height: 250px; - } - - .hero-title { - font-size: 40px; - } -} - -/* Sponsors Section */ -.sponsors-section { - padding: 80px 0; - background: #fff; - text-align: center; -} - -.top-sponsors-grid { - display: flex; - justify-content: center; - gap: 30px; - margin-bottom: 40px; - flex-wrap: wrap; -} - -.sponsor-card { - background: var(--bg-color); - border-radius: var(--radius-medium); - padding: 30px; - width: 250px; - box-shadow: 0 4px 12px rgba(0,0,0,0.05); - transition: var(--transition); - display: flex; - flex-direction: column; - align-items: center; -} - -.sponsor-card:hover { - transform: translateY(-5px); - box-shadow: 0 8px 24px rgba(0,0,0,0.1); -} - -.sponsor-rank { - font-size: 48px; - margin-bottom: 10px; -} - -.sponsor-name { - font-size: 20px; - font-weight: 600; - margin-bottom: 5px; - color: var(--text-primary); -} - -.sponsor-amount { - font-size: 16px; - color: var(--accent-color); - font-weight: 500; -} - -.view-sponsors-btn { - background: var(--text-primary); - color: white; - border: none; - padding: 12px 30px; - border-radius: 980px; - font-size: 16px; - cursor: pointer; - transition: var(--transition); -} - -.view-sponsors-btn:hover { - background: #000; - transform: scale(1.05); -} - -/* Crowdfunding Section */ -.crowdfunding-section { - padding: 80px 0; - background: var(--bg-color); /* Or white, depending on contrast preference */ -} - -.crowdfunding-grid { - display: flex; - flex-direction: column; - gap: 30px; - max-width: 800px; - margin: 0 auto; -} - -.fund-card { - background: #fff; - border-radius: var(--radius-medium); - padding: 30px; - box-shadow: 0 4px 12px rgba(0,0,0,0.05); - transition: var(--transition); -} - -.fund-card:hover { - transform: translateY(-5px); - box-shadow: 0 8px 24px rgba(0,0,0,0.1); -} - -.fund-header { - display: flex; - justify-content: space-between; - align-items: flex-end; - margin-bottom: 15px; -} - -.fund-title { - font-size: 20px; - font-weight: 600; - color: var(--text-primary); -} - -.fund-stats { - font-size: 14px; - color: var(--text-secondary); -} - -.fund-stats span { - font-weight: 600; - color: var(--text-primary); -} - -.progress-bar-bg { - width: 100%; - height: 12px; - background-color: #f0f0f0; - border-radius: 6px; - overflow: hidden; -} - -.progress-bar-fill { - height: 100%; - background: linear-gradient(90deg, #0071e3, #34c759); - border-radius: 6px; - width: 0%; /* Will be set by JS */ - transition: width 1s ease-out; -} - -.fund-percentage { - text-align: right; - font-size: 12px; - color: var(--text-secondary); - margin-top: 8px; -} - -/* Modal */ -.modal { - display: none; - position: fixed; - z-index: 2000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: rgba(0,0,0,0.5); - backdrop-filter: blur(5px); - justify-content: center; - align-items: center; -} - -.modal-content { - background-color: #fff; - padding: 40px; - border-radius: var(--radius-large); - width: 90%; - max-width: 800px; - max-height: 80vh; - position: relative; - display: flex; - flex-direction: column; -} - -.close-modal { - position: absolute; - top: 20px; - right: 30px; - color: #aaa; - font-size: 28px; - font-weight: bold; - cursor: pointer; - transition: 0.2s; -} - -.close-modal:hover { - color: #000; -} - -.modal-content h2 { - margin-bottom: 20px; - text-align: center; -} - -.sponsors-list-container { - overflow-y: auto; - flex-grow: 1; -} - -#sponsors-table { - width: 100%; - border-collapse: collapse; - font-size: 14px; -} - -#sponsors-table th, #sponsors-table td { - padding: 12px 15px; - text-align: left; - border-bottom: 1px solid #eee; -} - -#sponsors-table th { - background-color: #f9f9f9; - font-weight: 600; - position: sticky; - top: 0; -} - -#sponsors-table tr:hover { - background-color: #f5f5f7; -} - -/* Mobile Navbar Optimization */ -.mobile-toggle { - display: none; - background: none; - border: none; - color: var(--text-primary); - font-size: 20px; - cursor: pointer; - padding: 0; - width: 40px; - height: 40px; - align-items: center; - justify-content: center; - border-radius: 50%; - transition: background 0.2s; -} - -.mobile-toggle:hover { - background: rgba(0,0,0,0.05); -} - -.mobile-menu { - position: fixed; - top: 44px; - left: 0; - width: 100%; - height: 0; - background: rgba(255, 255, 255, 0.98); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - overflow: hidden; - transition: height 0.5s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.3s ease; - opacity: 0; - visibility: hidden; - z-index: 998; -} - -.mobile-menu.active { - height: calc(100vh - 44px); - opacity: 1; - visibility: visible; -} - -.mobile-menu-links { - padding: 24px 40px; - display: flex; - flex-direction: column; - gap: 0; - max-width: 600px; - margin: 0 auto; -} - -.mobile-menu-links a { - display: block; - font-size: 24px; - font-weight: 600; - text-decoration: none; - color: var(--text-primary); - padding: 16px 0; - border-bottom: 1px solid rgba(0,0,0,0.05); - opacity: 0; - transform: translateY(-20px); - transition: all 0.4s ease; -} - -.mobile-menu.active .mobile-menu-links a { - opacity: 1; - transform: translateY(0); -} - -/* Stagger animation */ -.mobile-menu.active .mobile-menu-links a:nth-child(1) { transition-delay: 0.1s; } -.mobile-menu.active .mobile-menu-links a:nth-child(2) { transition-delay: 0.15s; } -.mobile-menu.active .mobile-menu-links a:nth-child(3) { transition-delay: 0.2s; } -.mobile-menu.active .mobile-menu-links a:nth-child(4) { transition-delay: 0.25s; } -.mobile-menu.active .mobile-menu-links a:nth-child(5) { transition-delay: 0.3s; } - -/* Desktop Adjustment to keep alignment with split items */ -@media (min-width: 769px) { - .nav-links.desktop-only { - margin-left: auto; /* Push links and CTA to right */ - } - .nav-cta-container { - margin-left: 24px; - } -} - -/* Responsive Navbar Adjustments */ -@media (max-width: 768px) { - .nav-content { - justify-content: space-between; - padding: 0 15px; - } - - .desktop-only { - display: none !important; - } - - .mobile-toggle { - display: flex; - order: 1; /* Left */ - } - - .logo { - position: absolute; - left: 50%; - transform: translateX(-50%); - order: 2; /* Center */ - margin: 0; - } - - .nav-cta-container { - order: 3; /* Right */ - margin: 0; - } - - .nav-cta { - padding: 6px 12px; - font-size: 11px; - } - - /* Prevent body scroll when menu is open */ - body.menu-open { - overflow: hidden; - } -} - -/* Active State for Navbar Links */ -.nav-links a.active, -.mobile-menu-links a.active { - opacity: 1; - font-weight: 600; - color: var(--text-primary); -} diff --git a/doc.html b/doc.html deleted file mode 100644 index 0a8ce8d..0000000 --- a/doc.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - 文档 - 白鹿原 Minecraft 服务器 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - diff --git a/facilities.html b/facilities.html deleted file mode 100644 index 5a22071..0000000 --- a/facilities.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - - 共享资源 - 白鹿原 Minecraft 服务器 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - -
-
-
-

设施列表

- -
- -
- -
-
-
类型
-
- - - - -
-
- -
-
维度
-
- - - - -
-
-
-
- - -
- -
- - -
- - - - - - - - - - - - - - - diff --git a/index.html b/index.html index 8f21b8e..ee9ecdf 100644 --- a/index.html +++ b/index.html @@ -1,258 +1,13 @@ - + - - - - 白鹿原 Minecraft 服务器 - 永不换档的纯净原版生存Minecraft服务器 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 跳转到主内容 - - - - - - - - -
-
-
- - -
- -
-
-
- -

纯净原版

-

无纷繁复杂的 Mod,无破坏平衡的插件。一切简单的就像是单机模式的共享一般

-
-
- - -
-
-
- -

深度自研

-

全栈自研核心,拒绝卡脖子,保证可持续发展

-
-
- - -
-
-
- -

原汁原味

-

生物生成、红石参数与单机高度一致

-
-
- - -
-
-
- -

免费圈地

-

2048*2048 超大领地

-
-
-
-
-
- -

基岩互通

-

手机电脑随时畅玩

-
-
-
-
-
- -

自有硬件

-

物理工作站,永不跑路

-
-
-
-
-
- -

娱乐玩法

-

空岛、跑酷、小游戏

-
-
- - -
-
- -

紧跟新版

-

紧跟 Paper 核心版本更新,始终保持在版本前列。第一时间体验 Minecraft 的最新内容

-
-
- - -
-
- -

新手指南

-

完善的服务器文档与活跃的社区,帮助你快速上手,加入白鹿原大家庭

-
-
-
-
-
- - -
-
-

特别鸣谢

-
- -
- -
-
- - -
-
-

众筹进度

-
- -
-
-
-
- - - - - - - - + + + + + 白鹿原 Minecraft 服务器 + + +
+ + + \ No newline at end of file diff --git a/join.html b/join.html deleted file mode 100644 index df4c804..0000000 --- a/join.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - - 加入游戏指引 - 白鹿原 Minecraft 服务器 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-

开启您的冒险

-
- -
- -
-
-
-
-
- 公约 - 阅读须知 -
-
-
-
-
- 设备 - 选择平台 -
-
-
-
-
- 启动 - 配置教程 -
-
-
-
-
- 出发 - 选择玩法 -
-
-
-
- -
- -
-
-

服务器公约

-

为了维护良好的游戏环境,请务必阅读并遵守以下规则。

-
- -
-
- -
- - 正在加载公约内容... -
-
-
- -
-
- - -
- -
-
- - -
-
-

选择您的设备

-

工欲善其事,必先利其器。

-
- -
-
-
-
-
-

电脑版

-

Win / Mac / Linux

-
-
-
-
-
-
-

iOS 设备

-

iPhone / iPad

-
-
-
-
-
-
-

安卓设备

-

Android 手机 / 平板

-
-
-
-
- - - - -
-
- - -
-
-

配置与启动

-

只需几步简单操作即可进入游戏。

-
-
- -
-
- - -
-
-

选择您的玩法

-

这里有无限可能,你想成为什么样的玩家?(点击卡片查看详情)

-
- -
-
- -
-
- -
-
-

融入大型城镇

-

快速启航,共建繁华

-
-
- - -
-
- -
-
-

加入小型城镇

-

共同成长,见证历史

-
-
- - -
-
- -
-
-

与朋友共建

-

白手起家,开创时代

-
-
- - -
-
- -
-
-

独狼求生

-

自力更生,隐于山林

-
-
-
- - -
-
-
-

玩法标题

- 副标题 -
-
-
-

适合人群

-

...

-
-
-
-

优势

-
    -
    -
    -

    注意事项

    -
      -
      -
      -
      -
      -
      - -
      -

      无论你选择哪条路,都别忘了多探索世界,结识他人。我们期待看到你的故事在这里展开!

      -
      -
      -
      - - - -
      -
      -
      - - - - - - - - diff --git a/js/announcements_script.js b/js/announcements_script.js deleted file mode 100644 index e19f42c..0000000 --- a/js/announcements_script.js +++ /dev/null @@ -1,605 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - let announcementsData = []; - const timeline = document.getElementById('announcements-timeline'); - const noResults = document.getElementById('no-results'); - const categoryFilters = document.getElementById('category-filters'); - const searchInput = document.getElementById('announcement-search'); - - let currentFilters = { - category: 'all', - search: '' - }; - - let editModeEnabled = false; - let currentEditItem = null; - - // ========== Secret "edit" keyboard shortcut ========== - let secretBuffer = ''; - document.addEventListener('keydown', (e) => { - // Ignore if user is typing in an input/textarea - if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) return; - secretBuffer += e.key.toLowerCase(); - // Keep only last 4 characters - if (secretBuffer.length > 4) { - secretBuffer = secretBuffer.slice(-4); - } - if (secretBuffer === 'edit') { - editModeEnabled = !editModeEnabled; - secretBuffer = ''; - toggleEditButtons(editModeEnabled); - if (editModeEnabled) { - console.log('%c[公告管理] 编辑模式已启用', 'color: #34c759; font-weight: bold; font-size: 14px;'); - console.log('%c再次输入 "edit" 可隐藏编辑按钮', 'color: #86868b;'); - } else { - console.log('%c[公告管理] 编辑模式已隐藏', 'color: #f59e0b; font-weight: bold; font-size: 14px;'); - } - } - }); - - // Log hint on page load - console.log('%c[公告管理] 提示:在页面中键入 "edit" 可显示编辑按钮', 'color: #0071e3; font-weight: bold; font-size: 13px;'); - - function toggleEditButtons(show) { - document.querySelectorAll('.edit-hidden').forEach(el => { - if (show) { - el.classList.remove('edit-hidden'); - el.classList.add('edit-visible'); - } else { - el.classList.remove('edit-visible'); - el.classList.add('edit-hidden'); - } - }); - } - - // ========== Fetch Data ========== - fetch('data/announcements.json') - .then(response => response.json()) - .then(data => { - announcementsData = data; - // Sort by time descending (newest first) - announcementsData.sort((a, b) => new Date(b.time) - new Date(a.time)); - renderTimeline(); - handleHashNavigation(); - }) - .catch(err => { - console.error('Error loading announcements:', err); - timeline.innerHTML = '

      无法加载公告数据。

      '; - }); - - // ========== Event Listeners ========== - categoryFilters.addEventListener('click', (e) => { - if (e.target.tagName === 'BUTTON') { - Array.from(categoryFilters.children).forEach(btn => btn.classList.remove('active')); - e.target.classList.add('active'); - currentFilters.category = e.target.dataset.filter; - renderTimeline(); - } - }); - - searchInput.addEventListener('input', (e) => { - currentFilters.search = e.target.value.toLowerCase().trim(); - renderTimeline(); - }); - - // ========== Render ========== - function renderTimeline() { - timeline.innerHTML = ''; - - const filtered = announcementsData.filter(item => { - const matchCategory = currentFilters.category === 'all' || item.category === currentFilters.category; - const matchSearch = !currentFilters.search || - item.title.toLowerCase().includes(currentFilters.search) || - item.intro.toLowerCase().includes(currentFilters.search); - return matchCategory && matchSearch; - }); - - if (filtered.length === 0) { - noResults.classList.remove('is-hidden'); - return; - } else { - noResults.classList.add('is-hidden'); - } - - filtered.forEach((item, index) => { - const anchorId = generateAnchorId(item); - const timelineItem = document.createElement('div'); - timelineItem.className = 'timeline-item category-' + item.category; - timelineItem.id = anchorId; - - const card = document.createElement('div'); - card.className = 'announcement-card'; - // Expand the first (newest) item by default - if (index === 0) { - card.classList.add('expanded'); - } - - const categoryBadgeClass = getCategoryBadgeClass(item.category); - const categoryText = getCategoryText(item.category); - const categoryIcon = getCategoryIcon(item.category); - - // Summary - const summary = document.createElement('div'); - summary.className = 'card-summary'; - summary.innerHTML = ` -
      -
      - - ${categoryText} - -

      ${escapeHtml(item.title)}

      -
      -

      ${escapeHtml(item.intro)}

      -
      - ${escapeHtml(item.time)} - - `; - - summary.addEventListener('click', () => { - const wasExpanded = card.classList.contains('expanded'); - // Collapse all - timeline.querySelectorAll('.announcement-card.expanded').forEach(c => c.classList.remove('expanded')); - // Toggle current - if (!wasExpanded) { - card.classList.add('expanded'); - } - }); - - // Detail - const detail = document.createElement('div'); - detail.className = 'card-detail'; - const detailInner = document.createElement('div'); - detailInner.className = 'detail-content'; - renderContentBlocks(detailInner, item.content); - - // Action buttons row inside detail - const actionRow = document.createElement('div'); - actionRow.className = 'detail-action-btn-row'; - - // Share button - const shareBtn = document.createElement('button'); - shareBtn.className = 'btn-share-announcement'; - shareBtn.innerHTML = ' 分享'; - shareBtn.addEventListener('click', (e) => { - e.stopPropagation(); - var url = location.origin + location.pathname + '#' + anchorId; - navigator.clipboard.writeText(url).then(() => { - shareBtn.innerHTML = ' 已复制链接'; - shareBtn.classList.add('shared'); - setTimeout(() => { - shareBtn.innerHTML = ' 分享'; - shareBtn.classList.remove('shared'); - }, 2000); - }).catch(() => { - // Fallback: use a temporary input - var tmp = document.createElement('input'); - tmp.value = url; - document.body.appendChild(tmp); - tmp.select(); - document.execCommand('copy'); - document.body.removeChild(tmp); - shareBtn.innerHTML = ' 已复制链接'; - setTimeout(() => { - shareBtn.innerHTML = ' 分享'; - }, 2000); - }); - }); - actionRow.appendChild(shareBtn); - - // Edit button (hidden by default) - const editBtn = document.createElement('button'); - editBtn.className = 'btn-edit-announcement ' + (editModeEnabled ? 'edit-visible' : 'edit-hidden'); - editBtn.innerHTML = ' 编辑'; - editBtn.addEventListener('click', (e) => { - e.stopPropagation(); - openEditor(item); - }); - actionRow.appendChild(editBtn); - - detail.appendChild(detailInner); - detail.appendChild(actionRow); - - card.appendChild(summary); - card.appendChild(detail); - timelineItem.appendChild(card); - timeline.appendChild(timelineItem); - }); - } - - function renderContentBlocks(container, blocks) { - container.innerHTML = ''; - if (!blocks || blocks.length === 0) { - container.innerHTML = '

      暂无内容

      '; - return; - } - blocks.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); - } else if (block.type === 'video') { - const bv = parseBVNumber(block.content); - if (bv) { - const wrapper = document.createElement('div'); - wrapper.className = 'video-embed-wrapper'; - const iframe = document.createElement('iframe'); - iframe.src = 'https://player.bilibili.com/player.html?bvid=' + bv + '&autoplay=0&high_quality=1'; - iframe.allowFullscreen = true; - iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups'); - iframe.loading = 'lazy'; - wrapper.appendChild(iframe); - container.appendChild(wrapper); - } else { - const p = document.createElement('p'); - p.className = 'text-secondary'; - p.innerText = '无效的视频 BV 号'; - container.appendChild(p); - } - } - }); - } - - // ========== Generate stable ID for announcement ========== - function generateAnchorId(item) { - // Use title + time to create a stable hash-friendly ID - var raw = (item.time || '') + '_' + (item.title || ''); - var hash = 0; - for (var i = 0; i < raw.length; i++) { - hash = ((hash << 5) - hash) + raw.charCodeAt(i); - hash |= 0; - } - return 'a' + Math.abs(hash).toString(36); - } - - // ========== Handle URL hash on load ========== - function handleHashNavigation() { - var hash = location.hash.replace('#', ''); - if (!hash) return; - var target = document.getElementById(hash); - if (!target) return; - // Expand this card - var card = target.querySelector('.announcement-card'); - if (card) { - timeline.querySelectorAll('.announcement-card.expanded').forEach(function(c) { c.classList.remove('expanded'); }); - card.classList.add('expanded'); - } - // Scroll into view with a small delay for layout - setTimeout(function() { - target.scrollIntoView({ behavior: 'smooth', block: 'center' }); - }, 100); - } - - // ========== Helpers ========== - function getCategoryText(cat) { - const map = { 'activity': '活动', 'maintenance': '维护', 'other': '其他' }; - return map[cat] || cat; - } - - function getCategoryIcon(cat) { - const map = { 'activity': 'fa-calendar-check', 'maintenance': 'fa-wrench', 'other': 'fa-info-circle' }; - return map[cat] || 'fa-info-circle'; - } - - function getCategoryBadgeClass(cat) { - const map = { 'activity': 'badge-activity', 'maintenance': 'badge-maintenance', 'other': 'badge-other' }; - return map[cat] || 'badge-other'; - } - - function parseBVNumber(input) { - if (!input) return null; - input = input.trim(); - var bvPattern = /^(BV[A-Za-z0-9]+)$/; - var directMatch = input.match(bvPattern); - if (directMatch) return directMatch[1]; - var urlPattern = /bilibili\.com\/video\/(BV[A-Za-z0-9]+)/; - var urlMatch = input.match(urlPattern); - if (urlMatch) return urlMatch[1]; - var generalPattern = /(BV[A-Za-z0-9]{10,})/; - var generalMatch = input.match(generalPattern); - if (generalMatch) return generalMatch[1]; - return null; - } - - function escapeHtml(text) { - if (!text) return ''; - var div = document.createElement('div'); - div.appendChild(document.createTextNode(text)); - return div.innerHTML; - } - - // ========== Editor Modal ========== - const editorModal = document.getElementById('editor-modal'); - const jsonOutputModal = document.getElementById('json-output-modal'); - const closeEditorModal = document.querySelector('.close-editor-modal'); - const closeJsonModal = document.querySelector('.close-json-modal'); - - document.getElementById('btn-add-announcement').addEventListener('click', () => { - openEditor(null); - }); - - closeEditorModal.addEventListener('click', () => { - editorModal.style.display = 'none'; - document.body.style.overflow = 'auto'; - }); - - window.addEventListener('click', (e) => { - if (e.target === editorModal) { - editorModal.style.display = 'none'; - document.body.style.overflow = 'auto'; - } - if (e.target === jsonOutputModal) { - jsonOutputModal.style.display = 'none'; - } - }); - - closeJsonModal.addEventListener('click', () => { - jsonOutputModal.style.display = 'none'; - }); - - let editorContentBlocks = []; - - // Custom select init - document.querySelectorAll('.custom-select').forEach(select => { - const trigger = select.querySelector('.custom-select-trigger'); - const options = select.querySelectorAll('.custom-option'); - const input = select.querySelector('input[type="hidden"]'); - const text = select.querySelector('.custom-select-text'); - - trigger.addEventListener('click', (e) => { - e.stopPropagation(); - const isOpen = select.classList.contains('open'); - document.querySelectorAll('.custom-select').forEach(s => s.classList.remove('open')); - if (!isOpen) { - select.classList.add('open'); - } - }); - - options.forEach(option => { - option.addEventListener('click', (e) => { - e.stopPropagation(); - options.forEach(opt => opt.classList.remove('selected')); - option.classList.add('selected'); - text.innerText = option.innerText; - input.value = option.dataset.value; - input.dispatchEvent(new Event('change')); - select.classList.remove('open'); - }); - }); - }); - - document.addEventListener('click', () => { - document.querySelectorAll('.custom-select').forEach(s => s.classList.remove('open')); - }); - - function setCustomSelectValue(id, value) { - var input = document.getElementById(id); - if (!input) return; - var select = input.closest('.custom-select'); - var option = select.querySelector('.custom-option[data-value="' + value + '"]'); - if (option) { - input.value = value; - select.querySelector('.custom-select-text').innerText = option.innerText; - select.querySelectorAll('.custom-option').forEach(opt => opt.classList.remove('selected')); - option.classList.add('selected'); - } - } - - function openEditor(item) { - currentEditItem = item; - editorContentBlocks = item ? item.content.map(c => ({...c})) : []; - - document.getElementById('editor-title').value = item ? item.title : ''; - document.getElementById('editor-intro').value = item ? item.intro : ''; - document.getElementById('editor-time').value = item ? item.time : new Date().toISOString().slice(0, 10); - setCustomSelectValue('editor-category', item ? item.category : 'activity'); - - renderSortableList('editor-content-list', editorContentBlocks); - updatePreview(); - - editorModal.style.display = 'block'; - document.body.style.overflow = 'hidden'; - } - - // ========== Sortable List (drag-and-drop) ========== - let dragState = { listId: null, fromIdx: null }; - - function renderSortableList(listId, items) { - var container = document.getElementById(listId); - container.innerHTML = ''; - items.forEach((item, idx) => { - var div = document.createElement('div'); - div.className = 'sortable-item'; - div.draggable = true; - div.dataset.idx = idx; - div.dataset.listId = listId; - - var typeBadgeClass = item.type === 'text' ? 'badge-text' : item.type === 'image' ? 'badge-image' : 'badge-video'; - var typeBadgeLabel = item.type === 'text' ? '文字' : item.type === 'image' ? '图片' : '视频'; - var contentHtml; - if (item.type === 'text') { - contentHtml = ''; - } else if (item.type === 'image') { - contentHtml = ''; - } else { - contentHtml = ''; - } - - div.innerHTML = - '' + - '' + typeBadgeLabel + '' + - contentHtml + - ''; - container.appendChild(div); - - div.addEventListener('dragstart', onDragStart); - div.addEventListener('dragover', onDragOver); - div.addEventListener('dragenter', onDragEnter); - div.addEventListener('dragleave', onDragLeave); - div.addEventListener('drop', onDrop); - div.addEventListener('dragend', onDragEnd); - - var contentEl = div.querySelector('.item-content'); - contentEl.addEventListener('input', () => { - items[idx].content = contentEl.value; - updatePreview(); - }); - - div.querySelector('.remove-item-btn').addEventListener('click', () => { - items.splice(idx, 1); - renderSortableList(listId, items); - updatePreview(); - }); - }); - } - - function onDragStart(e) { - var item = e.target.closest('.sortable-item'); - if (!item) return; - dragState.listId = item.dataset.listId; - dragState.fromIdx = parseInt(item.dataset.idx); - item.classList.add('dragging'); - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/plain', ''); - } - function onDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } - function onDragEnter(e) { - var item = e.target.closest('.sortable-item'); - if (item && item.dataset.listId === dragState.listId) item.classList.add('drag-over'); - } - function onDragLeave(e) { - var item = e.target.closest('.sortable-item'); - if (item) item.classList.remove('drag-over'); - } - function onDrop(e) { - e.preventDefault(); - var item = e.target.closest('.sortable-item'); - if (!item || item.dataset.listId !== dragState.listId) return; - var toIdx = parseInt(item.dataset.idx); - var fromIdx = dragState.fromIdx; - if (fromIdx === toIdx) return; - var moved = editorContentBlocks.splice(fromIdx, 1)[0]; - editorContentBlocks.splice(toIdx, 0, moved); - renderSortableList('editor-content-list', editorContentBlocks); - updatePreview(); - } - function onDragEnd() { - document.querySelectorAll('.sortable-item').forEach(el => el.classList.remove('dragging', 'drag-over')); - dragState = { listId: null, fromIdx: null }; - } - - // Add content buttons - document.querySelectorAll('.add-item-btn').forEach(btn => { - btn.addEventListener('click', () => { - var type = btn.dataset.type; - editorContentBlocks.push({ type: type, content: '' }); - renderSortableList('editor-content-list', editorContentBlocks); - updatePreview(); - }); - }); - - // Live Preview - ['editor-title', 'editor-intro', 'editor-time', 'editor-category'].forEach(id => { - document.getElementById(id).addEventListener('input', updatePreview); - document.getElementById(id).addEventListener('change', updatePreview); - }); - - function updatePreview() { - var preview = document.getElementById('editor-preview-area'); - var title = document.getElementById('editor-title').value || '未命名公告'; - var intro = document.getElementById('editor-intro').value || '暂无简介'; - var time = document.getElementById('editor-time').value || '未设定'; - var category = document.getElementById('editor-category').value; - - var categoryText = getCategoryText(category); - var categoryIcon = getCategoryIcon(category); - var badgeClass = getCategoryBadgeClass(category); - - var html = '
      '; - html += '
      '; - html += '
      ' + escapeHtml(title) + '
      '; - html += '
      '; - html += ' ' + categoryText + ''; - html += ' ' + escapeHtml(time) + ''; - html += '
      '; - html += '
      '; - - html += '
      '; - html += '

      ' + escapeHtml(intro) + '

      '; - html += '
      '; - if (editorContentBlocks.length > 0) { - editorContentBlocks.forEach(block => { - if (block.type === 'text') { - html += '

      ' + (escapeHtml(block.content) || '空文字') + '

      '; - } else if (block.type === 'image') { - html += block.content ? '' : '

      空图片

      '; - } else if (block.type === 'video') { - html += renderVideoPreviewHtml(block.content); - } - }); - } else { - html += '

      暂无正文内容

      '; - } - html += '
      '; - preview.innerHTML = html; - } - - function renderVideoPreviewHtml(content) { - var bv = parseBVNumber(content); - if (bv) { - return '
      '; - } - return '

      请输入有效的 BV 号或 bilibili 视频地址

      '; - } - - // ========== Save / Generate JSON ========== - document.getElementById('btn-save-announcement').addEventListener('click', () => { - var title = document.getElementById('editor-title').value.trim(); - if (!title) { - alert('请填写公告标题'); - document.getElementById('editor-title').focus(); - return; - } - - var announcementObj = { - title: title, - intro: document.getElementById('editor-intro').value.trim(), - time: document.getElementById('editor-time').value, - category: document.getElementById('editor-category').value, - content: editorContentBlocks.filter(i => i.content.trim() !== '').map(i => { - if (i.type === 'video') { - return { type: 'video', content: parseBVNumber(i.content) || i.content }; - } - return { ...i }; - }) - }; - - var jsonStr = JSON.stringify(announcementObj, null, 4); - document.getElementById('json-output').value = jsonStr; - jsonOutputModal.style.display = 'block'; - }); - - // Copy JSON - document.getElementById('btn-copy-json').addEventListener('click', () => { - var textArea = document.getElementById('json-output'); - textArea.select(); - textArea.setSelectionRange(0, 99999); - - navigator.clipboard.writeText(textArea.value).then(() => { - var btn = document.getElementById('btn-copy-json'); - var originalHTML = btn.innerHTML; - btn.innerHTML = ' 已复制!'; - btn.style.background = '#34c759'; - setTimeout(() => { - btn.innerHTML = originalHTML; - btn.style.background = ''; - }, 2000); - }).catch(() => { - document.execCommand('copy'); - alert('已复制到剪贴板'); - }); - }); -}); diff --git a/js/components.js b/js/components.js deleted file mode 100644 index 6291612..0000000 --- a/js/components.js +++ /dev/null @@ -1,151 +0,0 @@ - -const Components = { - navbarHTML: ` - - - -
      - -
      - `, - - footerHTML: ` - - `, - - initPageHero: function() { - const heroContainer = document.getElementById('hero-component'); - if (heroContainer) { - const title = heroContainer.dataset.title || ''; - const subtitle = heroContainer.dataset.subtitle || ''; - const extraClass = heroContainer.dataset.class || ''; - heroContainer.className = `hero page-hero ${extraClass}`; - heroContainer.innerHTML = ` -
      -
      -

      ${title}

      -

      ${subtitle}

      -
      `; - } - }, - - init: function() { - // Inject Navbar - const navContainer = document.getElementById('navbar-component'); - if (navContainer) { - navContainer.innerHTML = this.navbarHTML; - } - - // Inject Page Hero - this.initPageHero(); - - // Inject Footer - const footerContainer = document.getElementById('footer-component'); - if (footerContainer) { - footerContainer.innerHTML = this.footerHTML; - } - - // Setup Mobile Menu Logic - this.setupMobileMenu(); - - // Highlight current page - this.highlightCurrentPage(); - }, - - setupMobileMenu: function() { - const toggle = document.getElementById('mobile-toggle'); - const menu = document.getElementById('mobile-menu'); - - if (toggle && menu) { - const icon = toggle.querySelector('i'); - // Remove old listeners if any to avoid duplicates? - // Since we just injected the HTML, there are no listeners. - - toggle.addEventListener('click', () => { - menu.classList.toggle('active'); - document.body.classList.toggle('menu-open'); - - if (menu.classList.contains('active')) { - if(icon) { - icon.classList.remove('fa-bars'); - icon.classList.add('fa-times'); - } - } else { - if(icon) { - icon.classList.remove('fa-times'); - icon.classList.add('fa-bars'); - } - } - }); - - menu.querySelectorAll('a').forEach(link => { - link.addEventListener('click', () => { - menu.classList.remove('active'); - document.body.classList.remove('menu-open'); - if(icon) { - icon.classList.remove('fa-times'); - icon.classList.add('fa-bars'); - } - }); - }); - } - }, - - highlightCurrentPage: function() { - const currentPath = window.location.pathname; - const links = document.querySelectorAll('.nav-links a, .mobile-menu-links a'); - - links.forEach(link => { - if (link.getAttribute('href') === currentPath) { - link.classList.add('active'); // You might need to add CSS for .active - } - }); - } -}; - -document.addEventListener('DOMContentLoaded', () => { - Components.init(); -}); diff --git a/js/data_utils.js b/js/data_utils.js deleted file mode 100644 index de46c14..0000000 --- a/js/data_utils.js +++ /dev/null @@ -1,42 +0,0 @@ -const DataUtils = { - parseSponsorsText: function(text) { - const sponsors = []; - - if (!text) { - return sponsors; - } - - const lines = text.trim().split('\n'); - lines.forEach(line => { - const parts = line.split(','); - if (parts.length < 3) { - return; - } - - const name = parts[0].trim(); - const project = parts[1].trim(); - const amountStr = parts[2].trim().replace('¥', ''); - const amount = parseFloat(amountStr); - const date = parts[3] ? parts[3].trim() : ''; - - if (!isNaN(amount)) { - sponsors.push({ name, project, amount, date }); - } - }); - - return sponsors; - }, - - buildSponsorTotals: function(sponsors) { - const totals = {}; - - sponsors.forEach(item => { - if (!totals[item.name]) { - totals[item.name] = 0; - } - totals[item.name] += item.amount; - }); - - return totals; - } -}; diff --git a/js/facilities_script.js b/js/facilities_script.js deleted file mode 100644 index 64a13ab..0000000 --- a/js/facilities_script.js +++ /dev/null @@ -1,825 +0,0 @@ -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: '' - }; - - let currentDetailItem = null; - - // Generate stable anchor ID for a facility - function generateFacilityId(item) { - var raw = (item.title || ''); - var hash = 0; - for (var i = 0; i < raw.length; i++) { - hash = ((hash << 5) - hash) + raw.charCodeAt(i); - hash |= 0; - } - return 'f' + Math.abs(hash).toString(36); - } - - // Handle URL hash: auto-open facility modal - function handleHashNavigation() { - var hash = location.hash.replace('#', ''); - if (!hash) return; - for (var i = 0; i < facilitiesData.length; i++) { - if (generateFacilityId(facilitiesData[i]) === hash) { - openModal(facilitiesData[i]); - return; - } - } - } - - // 1. Fetch Data - fetch('data/facilities.json') - .then(response => response.json()) - .then(data => { - facilitiesData = data; - renderGrid(); - handleHashNavigation(); - }) - .catch(err => { - console.error('Error loading facilities:', err); - grid.innerHTML = '

      无法加载设施数据。

      '; - }); - - // 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 - history.replaceState(null, '', location.pathname + location.search); - }); - - window.addEventListener('click', (e) => { - if (e.target === modal) { - modal.style.display = 'none'; - document.body.style.overflow = 'auto'; - history.replaceState(null, '', location.pathname + location.search); - } - }); - - // 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 = ` -
      -

      ${item.title}

      -
      -
      - ${statusText} -
      -
      -

      ${item.intro}

      -
      - ${getTypeText(item.type)} - ${getDimensionText(item.dimension)} -
      - `; - grid.appendChild(card); - }); - } - - function openModal(item) { - currentDetailItem = 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 = ` ${getStatusText(item.status)}`; - badgesContainer.appendChild(statusBadge); - - // Type Badge - const typeBadge = document.createElement('span'); - typeBadge.className = 'badge badge-type large-badge'; - typeBadge.innerHTML = ` ${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 = `${name}${name}`; - contribList.appendChild(tag); - }); - } else { - contribList.innerHTML = '暂无记录'; - } - - // 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 - - // Update URL hash - var anchorId = generateFacilityId(item); - history.replaceState(null, '', '#' + anchorId); - } - - function renderContentList(container, list) { - container.innerHTML = ''; - if (!list || list.length === 0) { - container.innerHTML = '

      '; - 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); - } else if (block.type === 'video') { - const bv = parseBVNumber(block.content); - if (bv) { - const wrapper = document.createElement('div'); - wrapper.className = 'video-embed-wrapper'; - const iframe = document.createElement('iframe'); - iframe.src = `https://player.bilibili.com/player.html?bvid=${bv}&autoplay=0&high_quality=1`; - iframe.allowFullscreen = true; - iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups'); - iframe.loading = 'lazy'; - wrapper.appendChild(iframe); - container.appendChild(wrapper); - } else { - const p = document.createElement('p'); - p.className = 'text-secondary'; - p.innerText = '无效的视频 BV 号'; - container.appendChild(p); - } - } - }); - } - - function parseBVNumber(input) { - if (!input) return null; - input = input.trim(); - // Match BV number directly (e.g. BV1qPhWzdEwU) - const bvPattern = /^(BV[A-Za-z0-9]+)$/; - const directMatch = input.match(bvPattern); - if (directMatch) return directMatch[1]; - // Match from bilibili URL (e.g. https://www.bilibili.com/video/BV1qPhWzdEwU/...) - const urlPattern = /bilibili\.com\/video\/(BV[A-Za-z0-9]+)/; - const urlMatch = input.match(urlPattern); - if (urlMatch) return urlMatch[1]; - // Match from b23.tv short URL or other formats containing BV - const generalPattern = /(BV[A-Za-z0-9]{10,})/; - const generalMatch = input.match(generalPattern); - if (generalMatch) return generalMatch[1]; - return null; - } - - // 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'; - } - - // ========== Editor Modal Logic ========== - - const editorModal = document.getElementById('editor-modal'); - const jsonOutputModal = document.getElementById('json-output-modal'); - const closeEditorModal = document.querySelector('.close-editor-modal'); - const closeJsonModal = document.querySelector('.close-json-modal'); - - // Open empty editor for new facility - document.getElementById('btn-add-facility').addEventListener('click', () => { - openEditor(null); - }); - - // Share facility link - document.getElementById('btn-share-facility').addEventListener('click', () => { - if (!currentDetailItem) return; - var anchorId = generateFacilityId(currentDetailItem); - var url = location.origin + location.pathname + '#' + anchorId; - var btn = document.getElementById('btn-share-facility'); - navigator.clipboard.writeText(url).then(() => { - btn.innerHTML = ' 已复制链接'; - btn.classList.add('shared'); - setTimeout(() => { - btn.innerHTML = ' 分享'; - btn.classList.remove('shared'); - }, 2000); - }).catch(() => { - var tmp = document.createElement('input'); - tmp.value = url; - document.body.appendChild(tmp); - tmp.select(); - document.execCommand('copy'); - document.body.removeChild(tmp); - btn.innerHTML = ' 已复制链接'; - setTimeout(() => { - btn.innerHTML = ' 分享'; - }, 2000); - }); - }); - - // Open editor from detail modal - document.getElementById('btn-edit-facility').addEventListener('click', () => { - if (currentDetailItem) { - modal.style.display = 'none'; - document.body.style.overflow = 'auto'; - openEditor(currentDetailItem); - } - }); - - // Close editor modal - closeEditorModal.addEventListener('click', () => { - editorModal.style.display = 'none'; - document.body.style.overflow = 'auto'; - }); - window.addEventListener('click', (e) => { - if (e.target === editorModal) { - editorModal.style.display = 'none'; - document.body.style.overflow = 'auto'; - } - if (e.target === jsonOutputModal) { - jsonOutputModal.style.display = 'none'; - } - }); - closeJsonModal.addEventListener('click', () => { - jsonOutputModal.style.display = 'none'; - }); - - // State for editor - let editorContributors = []; - let editorInstructions = []; - let editorNotes = []; - - // Initialize custom selects - document.querySelectorAll('.custom-select').forEach(select => { - const trigger = select.querySelector('.custom-select-trigger'); - const options = select.querySelectorAll('.custom-option'); - const input = select.querySelector('input[type="hidden"]'); - const text = select.querySelector('.custom-select-text'); - - trigger.addEventListener('click', (e) => { - e.stopPropagation(); - const isOpen = select.classList.contains('open'); - // Close all others - document.querySelectorAll('.custom-select').forEach(s => s.classList.remove('open')); - if (!isOpen) { - select.classList.add('open'); - } - }); - - options.forEach(option => { - option.addEventListener('click', (e) => { - e.stopPropagation(); - - // Update selection visually - options.forEach(opt => opt.classList.remove('selected')); - option.classList.add('selected'); - text.innerText = option.innerText; - - // Update hidden input and trigger change - input.value = option.dataset.value; - input.dispatchEvent(new Event('change')); - - // Close dropdown - select.classList.remove('open'); - }); - }); - }); - - // Close custom selects on outside click - document.addEventListener('click', () => { - document.querySelectorAll('.custom-select').forEach(s => s.classList.remove('open')); - }); - - function setCustomSelectValue(id, value) { - const input = document.getElementById(id); - if (!input) return; - const select = input.closest('.custom-select'); - const option = select.querySelector(`.custom-option[data-value="${value}"]`); - - if (option) { - input.value = value; - select.querySelector('.custom-select-text').innerText = option.innerText; - select.querySelectorAll('.custom-option').forEach(opt => opt.classList.remove('selected')); - option.classList.add('selected'); - } - } - - function openEditor(item) { - // Reset state - editorContributors = item ? [...item.contributors] : []; - editorInstructions = item ? item.instructions.map(i => ({...i})) : []; - editorNotes = item ? item.notes.map(n => ({...n})) : []; - - // Populate form fields - document.getElementById('editor-title').value = item ? item.title : ''; - document.getElementById('editor-intro').value = item ? item.intro : ''; - - setCustomSelectValue('editor-type', item ? item.type : 'resource'); - setCustomSelectValue('editor-status', item ? item.status : 'online'); - setCustomSelectValue('editor-dimension', item ? item.dimension : 'overworld'); - - document.getElementById('editor-x').value = item ? item.coordinates.x : ''; - document.getElementById('editor-y').value = item ? item.coordinates.y : ''; - document.getElementById('editor-z').value = item ? item.coordinates.z : ''; - - renderContributorTags(); - renderSortableList('editor-instructions-list', editorInstructions); - renderSortableList('editor-notes-list', editorNotes); - updatePreview(); - - editorModal.style.display = 'block'; - document.body.style.overflow = 'hidden'; - } - - // --- Contributors tags --- - function renderContributorTags() { - const container = document.getElementById('editor-contributors-tags'); - container.innerHTML = ''; - editorContributors.forEach((name, idx) => { - const tag = document.createElement('span'); - tag.className = 'editor-tag'; - tag.innerHTML = `${name} `; - container.appendChild(tag); - }); - } - - function commitContributorInput() { - const contributorInput = document.getElementById('editor-contributor-input'); - const value = contributorInput.value.trim(); - - if (value && !editorContributors.includes(value)) { - editorContributors.push(value); - renderContributorTags(); - updatePreview(); - } - - contributorInput.value = ''; - } - - document.getElementById('editor-contributors-tags').addEventListener('click', (e) => { - const removeBtn = e.target.closest('.editor-tag-remove'); - if (removeBtn) { - const idx = parseInt(removeBtn.dataset.idx); - editorContributors.splice(idx, 1); - renderContributorTags(); - updatePreview(); - } - }); - - document.getElementById('editor-contributor-input').addEventListener('keydown', (e) => { - if (e.isComposing) { - return; - } - - if (e.key === 'Enter' || e.key === ' ' || e.code === 'Space') { - e.preventDefault(); - commitContributorInput(); - } - }); - - document.getElementById('editor-contributor-input').addEventListener('blur', () => { - commitContributorInput(); - }); - - // Click on wrapper focuses input - document.getElementById('editor-contributors-wrapper').addEventListener('click', () => { - document.getElementById('editor-contributor-input').focus(); - }); - - // --- Sortable Lists (drag-and-drop) --- - let dragState = { listId: null, fromIdx: null }; - - function renderSortableList(listId, items) { - const container = document.getElementById(listId); - container.innerHTML = ''; - items.forEach((item, idx) => { - const div = document.createElement('div'); - div.className = 'sortable-item'; - div.draggable = true; - div.dataset.idx = idx; - div.dataset.listId = listId; - - const typeBadgeClass = item.type === 'text' ? 'badge-text' : item.type === 'image' ? 'badge-image' : 'badge-video'; - const typeBadgeLabel = item.type === 'text' ? '文字' : item.type === 'image' ? '图片' : '视频'; - let contentHtml; - if (item.type === 'text') { - contentHtml = ``; - } else if (item.type === 'image') { - contentHtml = ``; - } else { - contentHtml = ``; - } - div.innerHTML = ` - - ${typeBadgeLabel} - ${contentHtml} - - `; - container.appendChild(div); - - // Drag events - div.addEventListener('dragstart', onDragStart); - div.addEventListener('dragover', onDragOver); - div.addEventListener('dragenter', onDragEnter); - div.addEventListener('dragleave', onDragLeave); - div.addEventListener('drop', onDrop); - div.addEventListener('dragend', onDragEnd); - - // Content change - const contentEl = div.querySelector('.item-content'); - contentEl.addEventListener('input', () => { - items[idx].content = contentEl.value; - updatePreview(); - }); - - // Remove - div.querySelector('.remove-item-btn').addEventListener('click', () => { - items.splice(idx, 1); - renderSortableList(listId, items); - updatePreview(); - }); - }); - } - - function onDragStart(e) { - const item = e.target.closest('.sortable-item'); - if (!item) return; - dragState.listId = item.dataset.listId; - dragState.fromIdx = parseInt(item.dataset.idx); - item.classList.add('dragging'); - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/plain', ''); // required for Firefox - } - - function onDragOver(e) { - e.preventDefault(); - e.dataTransfer.dropEffect = 'move'; - } - - function onDragEnter(e) { - const item = e.target.closest('.sortable-item'); - if (item && item.dataset.listId === dragState.listId) { - item.classList.add('drag-over'); - } - } - - function onDragLeave(e) { - const item = e.target.closest('.sortable-item'); - if (item) { - item.classList.remove('drag-over'); - } - } - - function onDrop(e) { - e.preventDefault(); - const item = e.target.closest('.sortable-item'); - if (!item || item.dataset.listId !== dragState.listId) return; - const toIdx = parseInt(item.dataset.idx); - const fromIdx = dragState.fromIdx; - if (fromIdx === toIdx) return; - - const listId = dragState.listId; - const items = listId === 'editor-instructions-list' ? editorInstructions : editorNotes; - - // Reorder - const [moved] = items.splice(fromIdx, 1); - items.splice(toIdx, 0, moved); - - renderSortableList(listId, items); - updatePreview(); - } - - function onDragEnd(e) { - document.querySelectorAll('.sortable-item').forEach(el => { - el.classList.remove('dragging', 'drag-over'); - }); - dragState = { listId: null, fromIdx: null }; - } - - // --- Add item buttons --- - document.querySelectorAll('.add-item-btn').forEach(btn => { - btn.addEventListener('click', () => { - const target = btn.dataset.target; // 'instructions' or 'notes' - const type = btn.dataset.type; // 'text' or 'image' - const newItem = { type: type, content: '' }; - - if (target === 'instructions') { - editorInstructions.push(newItem); - renderSortableList('editor-instructions-list', editorInstructions); - } else { - editorNotes.push(newItem); - renderSortableList('editor-notes-list', editorNotes); - } - updatePreview(); - }); - }); - - // --- Live Preview --- - // Listen for form field changes to update preview - ['editor-title', 'editor-intro', 'editor-type', 'editor-status', - 'editor-dimension', 'editor-x', 'editor-y', 'editor-z'].forEach(id => { - document.getElementById(id).addEventListener('input', updatePreview); - document.getElementById(id).addEventListener('change', updatePreview); - }); - - function updatePreview() { - const preview = document.getElementById('editor-preview-area'); - const title = document.getElementById('editor-title').value || '未命名设施'; - const intro = document.getElementById('editor-intro').value || '暂无简介'; - const type = document.getElementById('editor-type').value; - const status = document.getElementById('editor-status').value; - const dimension = document.getElementById('editor-dimension').value; - const x = document.getElementById('editor-x').value || '0'; - const y = document.getElementById('editor-y').value || '64'; - const z = document.getElementById('editor-z').value || '0'; - - const statusText = getStatusText(status); - const statusIcon = getStatusIcon(status); - const typeText = getTypeText(type); - const dimensionText = getDimensionText(dimension); - - let html = `
      `; - html += `
      `; - html += `
      ${escapeHtml(title)}
      `; - html += ``; - html += `
      `; - - html += `
      `; - html += `

      ${escapeHtml(intro)}

      `; - - // Location - html += ``; - - // Contributors - html += ``; - - // Instructions - html += ``; - - // Notes - html += ``; - - html += `
      `; - preview.innerHTML = html; - } - - // --- Save / Generate JSON --- - document.getElementById('btn-save-facility').addEventListener('click', () => { - const title = document.getElementById('editor-title').value.trim(); - if (!title) { - alert('请填写设施名称'); - document.getElementById('editor-title').focus(); - return; - } - - const facilityObj = { - title: title, - intro: document.getElementById('editor-intro').value.trim(), - type: document.getElementById('editor-type').value, - dimension: document.getElementById('editor-dimension').value, - status: document.getElementById('editor-status').value, - coordinates: { - x: parseInt(document.getElementById('editor-x').value) || 0, - y: parseInt(document.getElementById('editor-y').value) || 64, - z: parseInt(document.getElementById('editor-z').value) || 0 - }, - contributors: [...editorContributors], - instructions: editorInstructions.filter(i => i.content.trim() !== '').map(i => i.type === 'video' ? { type: 'video', content: parseBVNumber(i.content) || i.content } : {...i}), - notes: editorNotes.filter(n => n.content.trim() !== '').map(n => n.type === 'video' ? { type: 'video', content: parseBVNumber(n.content) || n.content } : {...n}) - }; - - const jsonStr = JSON.stringify(facilityObj, null, 4); - document.getElementById('json-output').value = jsonStr; - - jsonOutputModal.style.display = 'block'; - }); - - // --- Copy JSON --- - document.getElementById('btn-copy-json').addEventListener('click', () => { - const textArea = document.getElementById('json-output'); - textArea.select(); - textArea.setSelectionRange(0, 99999); - - navigator.clipboard.writeText(textArea.value).then(() => { - const btn = document.getElementById('btn-copy-json'); - const originalHTML = btn.innerHTML; - btn.innerHTML = ' 已复制!'; - btn.style.background = '#34c759'; - setTimeout(() => { - btn.innerHTML = originalHTML; - btn.style.background = ''; - }, 2000); - }).catch(() => { - // Fallback - document.execCommand('copy'); - alert('已复制到剪贴板'); - }); - }); - - function renderVideoPreviewHtml(content) { - const bv = parseBVNumber(content); - if (bv) { - return `
      `; - } - return '

      请输入有效的 BV 号或 bilibili 视频地址

      '; - } - - // --- Utility --- - function escapeHtml(text) { - if (!text) return ''; - const div = document.createElement('div'); - div.appendChild(document.createTextNode(text)); - return div.innerHTML; - } -}); diff --git a/js/join_script.js b/js/join_script.js deleted file mode 100644 index c2bf3a8..0000000 --- a/js/join_script.js +++ /dev/null @@ -1,634 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - // State - let currentStep = 1; - let selectedDevice = null; - let selectedEdition = 'java'; - const totalSteps = 4; - - // Elements - const prevBtn = document.getElementById('btn-prev'); - const nextBtn = document.getElementById('btn-next'); - // Updated selector for new sidebar structure - const stepIndicators = document.querySelectorAll('.progress-step'); - const stepContents = document.querySelectorAll('.step-content'); - const conventionContent = document.getElementById('convention-content'); - - if (!conventionContent) { - console.error('Critical Error: Element #convention-content not found in DOM'); - return; - } - - const agreeCheckbox = document.getElementById('agree-checkbox'); - const deviceCards = document.querySelectorAll('.device-card'); - const recommendationSection = document.getElementById('launcher-recommendation'); - const recommendationContent = document.getElementById('recommendation-content'); - const tutorialContent = document.getElementById('tutorial-content'); - const step4Buttons = document.getElementById('step4-buttons'); - const mainWizardActions = document.querySelector('.wizard-actions'); - const editionSelector = document.getElementById('edition-selector'); - const editionBtns = document.querySelectorAll('.edition-btn'); - - console.log('DOM Elements loaded. Step contents found:', stepContents.length); - - // Fallback if marked is not loading correctly - if (typeof marked === 'undefined') { - console.warn("Marked not defined globally, checking for window.marked"); - if (window.marked) { - console.log("Found window.marked"); - // Assign to local variable if needed or just use window.marked - } - } - - // --- Step 1: Convention Loading --- - console.log('Fetching convention from data/convention.md ...'); - fetch('data/convention.md') - .then(response => { - console.log('Response status:', response.status); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - return response.text(); - }) - .then(markdown => { - console.log('Convention loaded, length:', markdown.length); - - let parser = null; - if (typeof marked !== 'undefined') { - if (typeof marked.parse === 'function') parser = marked.parse; - else if (typeof marked === 'function') parser = marked; - } else if (window.marked) { - if (typeof window.marked.parse === 'function') parser = window.marked.parse; - else if (typeof window.marked === 'function') parser = window.marked; - } - - if (parser) { - try { - const result = parser(markdown); - if (result instanceof Promise) { - result.then(html => conventionContent.innerHTML = html); - } else { - conventionContent.innerHTML = result; - } - } catch (e) { - console.error('Parse error:', e); - conventionContent.innerHTML = '
      ' + markdown + '
      '; - } - } else { - console.error('No markdown parser found'); - conventionContent.innerHTML = '
      ' + markdown + '
      '; - } - }) - .catch(error => { - console.error('Convention fetch error:', error); - conventionContent.innerHTML = `

      无法加载公约内容: ${error.message}

      `; - }); - - // --- Navigation Logic --- - function updateWizard() { - console.log('UpdateWizard called, step:', currentStep); - - // Update Indicators - stepIndicators.forEach(indicator => { - const step = parseInt(indicator.dataset.step); - if (step === currentStep) { - indicator.classList.add('active'); - indicator.classList.remove('completed'); - - // Scroll into view on mobile - if (window.innerWidth <= 800) { - indicator.scrollIntoView({ - behavior: 'smooth', - block: 'nearest', - inline: 'center' - }); - } - } else if (step < currentStep) { - indicator.classList.add('completed'); - indicator.classList.remove('active'); - } else { - indicator.classList.remove('active', 'completed'); - } - }); - - // Update Progress Bar Fill - const progressFill = document.getElementById('progress-fill'); - if (progressFill) { - const progress = ((currentStep - 1) / (totalSteps - 1)) * 100; - progressFill.style.width = `${progress}%`; - } - - // Update Content visibility with Animation timeout - stepContents.forEach(content => { - if (content.id === `step-${currentStep}`) { - content.classList.add('active'); - // Optional: ensure display block if handled by CSS alone or JS - } else { - content.classList.remove('active'); - } - }); - - // Buttons State - updateButtons(); - } - - function updateButtons() { - // Prev Button - if (prevBtn) prevBtn.disabled = currentStep === 1; - - // Next Button logic - if (currentStep === 1) { - // Step 1: Checkbox required - if (nextBtn) nextBtn.disabled = !agreeCheckbox.checked; - } else if (currentStep === 2) { - // Step 2: Device selection required - if (nextBtn) nextBtn.disabled = !selectedDevice; - } else { - if (nextBtn) nextBtn.disabled = false; - } - - // Step 4 special buttons visibility - if (currentStep === totalSteps) { - if (nextBtn) nextBtn.style.display = 'none'; - if (step4Buttons) step4Buttons.classList.remove('hidden'); - } else { - if (nextBtn) nextBtn.style.display = 'inline-flex'; - if (step4Buttons) step4Buttons.classList.add('hidden'); - } - } - - if (prevBtn) { - prevBtn.addEventListener('click', () => { - if (currentStep > 1) { - currentStep--; - updateWizard(); - } - }); - } - - if (nextBtn) { - nextBtn.addEventListener('click', () => { - if (currentStep < totalSteps) { - if (currentStep === 2) { - renderTutorial(); // Generate step 3 content before showing - // Also scroll to top for better ux - window.scrollTo({ top: 0, behavior: 'smooth' }); - } - currentStep++; - updateWizard(); - window.scrollTo({ top: 0, behavior: 'smooth' }); - } - }); - } - - // Step 1 Checkbox - if (agreeCheckbox) { - agreeCheckbox.addEventListener('change', updateButtons); - } - - // --- Step 2: Device Selection --- - const deviceData = { - pc: { - title: "电脑版 (Java Edition)", - recommendations: [ - { - name: "PCL2", - icon: "fas fa-cube", - desc: "界面精美,功能强大的现代化启动器(仅Win)", - url: "https://afdian.net/p/0164034c016c11ebafcb52540025c377", - primary: true - }, - { - name: "HMCL", - icon: "fas fa-horse-head", - desc: "历史悠久,跨平台支持好 (Win/Mac/Linux)", - url: "https://hmcl.huangyuhui.net/", - primary: false - } - ], - note: "推荐使用 PCL2 或 HMCL,均支持极大改善游戏体验。" - }, - ios: { - title: "iOS 设备", - recommendations: [ - { - name: "PojavLauncher", - icon: "fab fa-app-store-ios", - desc: "iOS 上运行 Java 版的唯一选择", - url: "https://apps.apple.com/us/app/pojavlauncher/id6443526546", - primary: true - } - ], - note: "需要 iOS 14.0 或更高版本。若未越狱,请保持 JIT 开启以获得最佳性能(部分版本可能需要)。" - }, - android: { - title: "安卓设备", - recommendations: [ - { - name: "FCL 启动器", - icon: "fab fa-android", - desc: "基于 FoldCraft 的高性能启动器", - url: "https://github.com/FoldCraftLauncher/FoldCraftLauncher/releases", - primary: true - }, - { - name: "PojavLauncher", - icon: "fas fa-gamepad", - desc: "经典的移动端 Java 版启动器", - url: "https://play.google.com/store/apps/details?id=net.kdt.pojavlaunch", - primary: false - } - ], - note: "建议设备拥有至少 4GB 运存以流畅运行 1.21 版本。" - } - }; - - const bedrockDeviceData = { - pc: { - title: "电脑版 (Bedrock Edition)", - recommendations: [ - { - name: "Minecraft 基岩版", - icon: "fas fa-cube", - desc: "从 Microsoft Store 获取 Minecraft(需 Windows 10/11)", - url: "https://www.xbox.com/games/store/minecraft/9NBLGGH2JHXJ", - primary: true - } - ], - note: "基岩版通过 Microsoft Store 购买,使用 Xbox / Microsoft 账号登录即可游玩。" - }, - ios: { - title: "iOS 基岩版", - recommendations: [ - { - name: "Minecraft", - icon: "fas fa-cube", - desc: "从 App Store 购买并下载 Minecraft", - url: "https://apps.apple.com/app/minecraft/id479516143", - primary: true - } - ], - note: "基岩版是 iOS 上的原生 Minecraft,性能最佳、操作体验最好。" - }, - android: { - title: "安卓基岩版", - recommendations: [ - { - name: "Minecraft", - icon: "fas fa-cube", - desc: "从 Google Play 购买并下载 Minecraft", - url: "https://play.google.com/store/apps/details?id=com.mojang.minecraftpe", - primary: true - } - ], - note: "基岩版是安卓上的原生 Minecraft,性能最佳、操作体验最好。" - } - }; - - deviceCards.forEach(card => { - card.addEventListener('click', () => { - // UI Update - deviceCards.forEach(c => c.classList.remove('selected')); - card.classList.add('selected'); - - const deviceType = card.dataset.device; - selectedDevice = deviceType; - selectedEdition = 'java'; - - // Show edition selector and reset to Java - if (editionSelector) { - editionSelector.classList.remove('hidden'); - editionBtns.forEach(btn => { - btn.classList.toggle('active', btn.dataset.edition === 'java'); - }); - } - - // Show Recommendation - showRecommendation(deviceType); - - // Re-enable next button - updateButtons(); - }); - }); - - // Edition toggle handlers - editionBtns.forEach(btn => { - btn.addEventListener('click', () => { - editionBtns.forEach(b => b.classList.remove('active')); - btn.classList.add('active'); - selectedEdition = btn.dataset.edition; - if (selectedDevice) { - showRecommendation(selectedDevice); - } - }); - }); - - function showRecommendation(device) { - const data = selectedEdition === 'bedrock' ? bedrockDeviceData[device] : deviceData[device]; - if (!data) return; - - if (recommendationSection) { - recommendationSection.classList.remove('hidden'); - // Little fade-in effect - recommendationSection.style.opacity = '0'; - setTimeout(() => recommendationSection.style.opacity = '1', 50); - } - - let cardsHtml = data.recommendations.map(req => ` - -
      - -
      -
      -

      ${req.name} ${req.primary ? '推荐' : ''}

      -

      ${req.desc}

      -
      -
      - -
      -
      - `).join(''); - - const html = ` -
      -

      为 ${data.title} 准备启动器

      -

      ${data.note}

      -
      -
      - ${cardsHtml} -
      - `; - - if (recommendationContent) recommendationContent.innerHTML = html; - } - - // --- Step 3: Tutorial Rendering --- - const deviceTutorials = { - pc: [ - { - title: '登录账号', - desc: '打开启动器(PCL2/HMCL),选择“添加账号”。推荐使用 Microsoft 账号登录拥有正版 Minecraft 的账户。' - }, - { - title: '安装游戏', - desc: '在启动器中创建一个新游戏配置,选择游戏版本 1.21.x。强烈建议安装 Fabric 加载器以获得更好的模组支持和性能优化。' - }, - { - title: '启动游戏', - desc: '等待游戏资源文件下载完成,点击启动游戏直到看到 Minecraft 主界面。' - }, - { - title: '加入服务器', - desc: `点击“多人游戏” -> “添加服务器”。
      - 服务器名称:白鹿原
      - 输入以下服务器地址,点击“完成”并双击服务器即可加入。 -
      - mcpure.lunadeer.cn - -
      - ` - } - ], - ios: [ - { - title: '准备环境', - desc: '打开 PojavLauncher。若您的设备未越狱,请确保已启用 JIT(Just-In-Time)以获得可玩的帧率。' - }, - { - title: '登录账号', - desc: '点击“添加账户”,选择“Microsoft 账户”并完成登录流程。' - }, - { - title: '下载并启动', - desc: '点击“创建新配置”,选择版本 1.21.x。建议调整内存分配至设备总内存的 50% 左右,然后点击“启动”。' - }, - { - title: '加入服务器', - desc: `进入主界面后,选择 Multiplayer(多人游戏) -> Add Server(添加服务器)。
      - Address(地址)填写以下内容,点击 Done 并加入。 -
      - mcpure.lunadeer.cn - -
      - ` - } - ], - android: [ - { - title: '配置启动器', - desc: '打开 FCL 或 PojavLauncher。给予必要的存储权限。' - }, - { - title: '登录账号', - desc: '在账户设置中添加 Microsoft 账户。' - }, - { - title: '安装版本', - desc: '下载 1.21.x 游戏核心。FCL 用户可直接使用内置下载源加速下载。建议安装 OptiFine 或 Fabric+Sodium 以提升帧率。' - }, - { - title: '加入服务器', - desc: `启动游戏后,点击 Multiplayer(多人游戏) -> Add Server(添加服务器)。
      - Address(地址)填写以下内容,点击 Done 并加入。 -
      - mcpure.lunadeer.cn - -
      - ` - } - ] - }; - - const bedrockTutorials = { - pc: [ - { - title: '获取游戏', - desc: '从 Microsoft Store 购买并下载 Minecraft(基岩版/Bedrock Edition),需要 Windows 10 或 Windows 11。' - }, - { - title: '登录账号', - desc: '打开 Minecraft,使用 Microsoft / Xbox 账号登录。' - }, - { - title: '加入服务器', - desc: `点击"游戏" → 切换到"服务器"标签页 → 滚动到底部点击"添加服务器"。
      - 服务器名称:白鹿原
      - 填写以下服务器信息: -
      - 地址:mcbe.lunadeer.cn - -
      -
      - 端口:15337 - -
      - 填写完成后点击"保存",然后选中该服务器加入即可。` - } - ], - ios: [ - { - title: '获取游戏', - desc: '从 App Store 购买并下载 Minecraft。' - }, - { - title: '登录账号', - desc: '打开 Minecraft,使用 Microsoft / Xbox 账号登录。' - }, - { - title: '加入服务器', - desc: `点击"游戏" → 切换到"服务器"标签页 → 滚动到底部点击"添加服务器"。
      - 服务器名称:白鹿原
      - 填写以下服务器信息: -
      - 地址:mcbe.lunadeer.cn - -
      -
      - 端口:15337 - -
      - 填写完成后点击"保存",然后选中该服务器加入即可。` - } - ], - android: [ - { - title: '获取游戏', - desc: '从 Google Play 购买并下载 Minecraft。' - }, - { - title: '登录账号', - desc: '打开 Minecraft,使用 Microsoft / Xbox 账号登录。' - }, - { - title: '加入服务器', - desc: `点击"游戏" → 切换到"服务器"标签页 → 滚动到底部点击"添加服务器"。
      - 服务器名称:白鹿原
      - 填写以下服务器信息: -
      - 地址:mcbe.lunadeer.cn - -
      -
      - 端口:15337 - -
      - 填写完成后点击"保存",然后选中该服务器加入即可。` - } - ] - }; - - function renderTutorial() { - const device = selectedDevice || 'pc'; - let steps; - if (selectedEdition === 'bedrock') { - steps = bedrockTutorials[device] || bedrockTutorials['pc']; - } else { - steps = deviceTutorials[device] || deviceTutorials['pc']; - } - - let content = '
      '; - - steps.forEach((step, index) => { - content += ` -
      -
      ${index + 1}
      -
      -

      ${step.title}

      -

      ${step.desc}

      -
      -
      - `; - }); - - content += '
      '; - if (tutorialContent) { - tutorialContent.innerHTML = content; - } - } - - // --- Step 4: Playstyle Selection --- - const playstyleCards = document.querySelectorAll('.playstyle-card'); - const playstyleDetails = document.getElementById('playstyle-details'); - - const playstyleData = { - 'large-town': { - title: '融入大型城镇', - subtitle: '快速启航,共建繁华 (10+人)', - target: '希望跳过艰难的初期积累,快速投入大规模建造与合作的玩家。', - pros: ['资源无忧:可直接从公共仓库获取建材与工具。', '工业完善:享受成熟的自动化生产带来的便利。'], - cons: ['为了整体美观与规划,可能需要遵守城镇的建筑风格与管理安排,自由度相对受限。'] - }, - 'small-town': { - title: '加入小型城镇', - subtitle: '共同成长,见证历史 (3-10人)', - target: '喜欢参与从零到一的建设过程,享受亲手打造家园成就感的玩家。', - pros: ['发展参与感:亲身参与城镇的规划与扩张。', '自由度较高:在发展初期通常有更多的个人发挥空间。'], - cons: ['初期资源相对有限,需要与同伴共同努力。'] - }, - 'friends': { - title: '与朋友共建家园', - subtitle: '白手起家,开创时代 (1-3人)', - target: '拥有固定小团体,渴望一片完全属于自己的领地的玩家。', - pros: ['绝对自由:从选址到规划,一切由你定义。', '纯粹体验:体验最原始的协作与创造乐趣。'], - cons: ['这是一条充满挑战的道路,但从无到有建立的一切都将格外珍贵。'] - }, - 'solo': { - title: '独狼求生', - subtitle: '自力更生,隐于山林', - target: '享受孤独,崇尚一切亲力亲为的硬核生存玩家。', - pros: ['极致的自由与独立,你的世界只属于你。', '可尝试与其他玩家进行贸易换取无法独自获得的资源。'], - cons: ['一切都需要亲力亲为,生存挑战较大。'] - } - }; - - if (playstyleCards.length > 0 && playstyleDetails) { - playstyleCards.forEach(card => { - card.addEventListener('click', () => { - // UI Visual Selection - playstyleCards.forEach(c => c.classList.remove('selected')); - card.classList.add('selected'); - - // Get Data - const styleKey = card.dataset.style; - const data = playstyleData[styleKey]; - - if (data) { - // Populate Details - document.getElementById('detail-title').textContent = data.title; - document.getElementById('detail-subtitle').textContent = data.subtitle; - document.getElementById('detail-target').textContent = data.target; - - const prosList = document.getElementById('detail-pros'); - const consList = document.getElementById('detail-cons'); - - prosList.innerHTML = data.pros.map(p => `
    • ${p}
    • `).join(''); - consList.innerHTML = data.cons.map(c => `
    • ${c}
    • `).join(''); - - // Show Details - playstyleDetails.classList.add('visible'); - - // Optional: scroll into view gently if needed, or stick to bottom - playstyleDetails.scrollIntoView({ behavior: 'smooth', block: 'end' }); - } - }); - }); - } - - // Initial check - updateWizard(); -}); diff --git a/js/marked.min.js b/js/marked.min.js deleted file mode 100644 index b4e0d73..0000000 --- a/js/marked.min.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * marked v15.0.12 - a markdown parser - * Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed) - * https://github.com/markedjs/marked - */ - -/** - * DO NOT EDIT THIS FILE - * The code in this file is generated from files in ./src/ - */ -(function(g,f){if(typeof exports=="object"&&typeof module<"u"){module.exports=f()}else if("function"==typeof define && define.amd){define("marked",f)}else {g["marked"]=f()}}(typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : this,function(){var exports={};var __exports=exports;var module={exports}; -"use strict";var H=Object.defineProperty;var be=Object.getOwnPropertyDescriptor;var Te=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var ye=(l,e)=>{for(var t in e)H(l,t,{get:e[t],enumerable:!0})},Re=(l,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Te(e))!we.call(l,s)&&s!==t&&H(l,s,{get:()=>e[s],enumerable:!(n=be(e,s))||n.enumerable});return l};var Se=l=>Re(H({},"__esModule",{value:!0}),l);var kt={};ye(kt,{Hooks:()=>L,Lexer:()=>x,Marked:()=>E,Parser:()=>b,Renderer:()=>$,TextRenderer:()=>_,Tokenizer:()=>S,defaults:()=>w,getDefaults:()=>z,lexer:()=>ht,marked:()=>k,options:()=>it,parse:()=>pt,parseInline:()=>ct,parser:()=>ut,setOptions:()=>ot,use:()=>lt,walkTokens:()=>at});module.exports=Se(kt);function z(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var w=z();function N(l){w=l}var I={exec:()=>null};function h(l,e=""){let t=typeof l=="string"?l:l.source,n={replace:(s,i)=>{let r=typeof i=="string"?i:i.source;return r=r.replace(m.caret,"$1"),t=t.replace(s,r),n},getRegex:()=>new RegExp(t,e)};return n}var m={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:l=>new RegExp(`^( {0,3}${l})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}#`),htmlBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}<(?:[a-z].*>|!--)`,"i")},$e=/^(?:[ \t]*(?:\n|$))+/,_e=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Le=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,O=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,ze=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,F=/(?:[*+-]|\d{1,9}[.)])/,ie=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,oe=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Me=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),Q=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,Pe=/^[^\n]+/,U=/(?!\s*\])(?:\\.|[^\[\]\\])+/,Ae=h(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",U).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Ee=h(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,F).getRegex(),v="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",K=/|$))/,Ce=h("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",K).replace("tag",v).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),le=h(Q).replace("hr",O).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),Ie=h(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",le).getRegex(),X={blockquote:Ie,code:_e,def:Ae,fences:Le,heading:ze,hr:O,html:Ce,lheading:oe,list:Ee,newline:$e,paragraph:le,table:I,text:Pe},re=h("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",O).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3} )[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),Oe={...X,lheading:Me,table:re,paragraph:h(Q).replace("hr",O).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",re).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex()},Be={...X,html:h(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",K).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:I,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:h(Q).replace("hr",O).replace("heading",` *#{1,6} *[^ -]`).replace("lheading",oe).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},qe=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,ve=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,ae=/^( {2,}|\\)\n(?!\s*$)/,De=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\]*?>/g,ue=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,je=h(ue,"u").replace(/punct/g,D).getRegex(),Fe=h(ue,"u").replace(/punct/g,pe).getRegex(),he="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",Qe=h(he,"gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Ue=h(he,"gu").replace(/notPunctSpace/g,He).replace(/punctSpace/g,Ge).replace(/punct/g,pe).getRegex(),Ke=h("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Xe=h(/\\(punct)/,"gu").replace(/punct/g,D).getRegex(),We=h(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),Je=h(K).replace("(?:-->|$)","-->").getRegex(),Ve=h("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",Je).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),q=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Ye=h(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",q).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),ke=h(/^!?\[(label)\]\[(ref)\]/).replace("label",q).replace("ref",U).getRegex(),ge=h(/^!?\[(ref)\](?:\[\])?/).replace("ref",U).getRegex(),et=h("reflink|nolink(?!\\()","g").replace("reflink",ke).replace("nolink",ge).getRegex(),J={_backpedal:I,anyPunctuation:Xe,autolink:We,blockSkip:Ne,br:ae,code:ve,del:I,emStrongLDelim:je,emStrongRDelimAst:Qe,emStrongRDelimUnd:Ke,escape:qe,link:Ye,nolink:ge,punctuation:Ze,reflink:ke,reflinkSearch:et,tag:Ve,text:De,url:I},tt={...J,link:h(/^!?\[(label)\]\((.*?)\)/).replace("label",q).getRegex(),reflink:h(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",q).getRegex()},j={...J,emStrongRDelimAst:Ue,emStrongLDelim:Fe,url:h(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},fe=l=>st[l];function R(l,e){if(e){if(m.escapeTest.test(l))return l.replace(m.escapeReplace,fe)}else if(m.escapeTestNoEncode.test(l))return l.replace(m.escapeReplaceNoEncode,fe);return l}function V(l){try{l=encodeURI(l).replace(m.percentDecode,"%")}catch{return null}return l}function Y(l,e){let t=l.replace(m.findPipe,(i,r,o)=>{let a=!1,c=r;for(;--c>=0&&o[c]==="\\";)a=!a;return a?"|":" |"}),n=t.split(m.splitPipe),s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length0?-2:-1}function me(l,e,t,n,s){let i=e.href,r=e.title||null,o=l[1].replace(s.other.outputLinkReplace,"$1");n.state.inLink=!0;let a={type:l[0].charAt(0)==="!"?"image":"link",raw:t,href:i,title:r,text:o,tokens:n.inlineTokens(o)};return n.state.inLink=!1,a}function rt(l,e,t){let n=l.match(t.other.indentCodeCompensation);if(n===null)return e;let s=n[1];return e.split(` -`).map(i=>{let r=i.match(t.other.beginningSpace);if(r===null)return i;let[o]=r;return o.length>=s.length?i.slice(s.length):i}).join(` -`)}var S=class{options;rules;lexer;constructor(e){this.options=e||w}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:A(n,` -`)}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],s=rt(n,t[3]||"",this.rules);return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:s}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let s=A(n,"#");(this.options.pedantic||!s||this.rules.other.endingSpaceChar.test(s))&&(n=s.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:A(t[0],` -`)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=A(t[0],` -`).split(` -`),s="",i="",r=[];for(;n.length>0;){let o=!1,a=[],c;for(c=0;c1,i={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");let r=this.rules.other.listItemRegex(n),o=!1;for(;e;){let c=!1,p="",u="";if(!(t=r.exec(e))||this.rules.block.hr.test(e))break;p=t[0],e=e.substring(p.length);let d=t[2].split(` -`,1)[0].replace(this.rules.other.listReplaceTabs,Z=>" ".repeat(3*Z.length)),g=e.split(` -`,1)[0],T=!d.trim(),f=0;if(this.options.pedantic?(f=2,u=d.trimStart()):T?f=t[1].length+1:(f=t[2].search(this.rules.other.nonSpaceChar),f=f>4?1:f,u=d.slice(f),f+=t[1].length),T&&this.rules.other.blankLine.test(g)&&(p+=g+` -`,e=e.substring(g.length+1),c=!0),!c){let Z=this.rules.other.nextBulletRegex(f),te=this.rules.other.hrRegex(f),ne=this.rules.other.fencesBeginRegex(f),se=this.rules.other.headingBeginRegex(f),xe=this.rules.other.htmlBeginRegex(f);for(;e;){let G=e.split(` -`,1)[0],C;if(g=G,this.options.pedantic?(g=g.replace(this.rules.other.listReplaceNesting," "),C=g):C=g.replace(this.rules.other.tabCharGlobal," "),ne.test(g)||se.test(g)||xe.test(g)||Z.test(g)||te.test(g))break;if(C.search(this.rules.other.nonSpaceChar)>=f||!g.trim())u+=` -`+C.slice(f);else{if(T||d.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||ne.test(d)||se.test(d)||te.test(d))break;u+=` -`+g}!T&&!g.trim()&&(T=!0),p+=G+` -`,e=e.substring(G.length+1),d=C.slice(f)}}i.loose||(o?i.loose=!0:this.rules.other.doubleBlankLine.test(p)&&(o=!0));let y=null,ee;this.options.gfm&&(y=this.rules.other.listIsTask.exec(u),y&&(ee=y[0]!=="[ ] ",u=u.replace(this.rules.other.listReplaceTask,""))),i.items.push({type:"list_item",raw:p,task:!!y,checked:ee,loose:!1,text:u,tokens:[]}),i.raw+=p}let a=i.items.at(-1);if(a)a.raw=a.raw.trimEnd(),a.text=a.text.trimEnd();else return;i.raw=i.raw.trimEnd();for(let c=0;cd.type==="space"),u=p.length>0&&p.some(d=>this.rules.other.anyLine.test(d.raw));i.loose=u}if(i.loose)for(let c=0;c({text:a,tokens:this.lexer.inline(a),header:!1,align:r.align[c]})));return r}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:t[2].charAt(0)==="="?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===` -`?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let r=A(n.slice(0,-1),"\\");if((n.length-r.length)%2===0)return}else{let r=de(t[2],"()");if(r===-2)return;if(r>-1){let a=(t[0].indexOf("!")===0?5:4)+t[1].length+r;t[2]=t[2].substring(0,r),t[0]=t[0].substring(0,a).trim(),t[3]=""}}let s=t[2],i="";if(this.options.pedantic){let r=this.rules.other.pedanticHrefTitle.exec(s);r&&(s=r[1],i=r[3])}else i=t[3]?t[3].slice(1,-1):"";return s=s.trim(),this.rules.other.startAngleBracket.test(s)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?s=s.slice(1):s=s.slice(1,-1)),me(t,{href:s&&s.replace(this.rules.inline.anyPunctuation,"$1"),title:i&&i.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let s=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),i=t[s.toLowerCase()];if(!i){let r=n[0].charAt(0);return{type:"text",raw:r,text:r}}return me(n,i,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s||s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){let r=[...s[0]].length-1,o,a,c=r,p=0,u=s[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(u.lastIndex=0,t=t.slice(-1*e.length+r);(s=u.exec(t))!=null;){if(o=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!o)continue;if(a=[...o].length,s[3]||s[4]){c+=a;continue}else if((s[5]||s[6])&&r%3&&!((r+a)%3)){p+=a;continue}if(c-=a,c>0)continue;a=Math.min(a,a+c+p);let d=[...s[0]][0].length,g=e.slice(0,r+s.index+d+a);if(Math.min(r,a)%2){let f=g.slice(1,-1);return{type:"em",raw:g,text:f,tokens:this.lexer.inlineTokens(f)}}let T=g.slice(2,-2);return{type:"strong",raw:g,text:T,tokens:this.lexer.inlineTokens(T)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),s=this.rules.other.nonSpaceChar.test(n),i=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return s&&i&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){let t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,s;return t[2]==="@"?(n=t[1],s="mailto:"+n):(n=t[1],s=n),{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,s;if(t[2]==="@")n=t[0],s="mailto:"+n;else{let i;do i=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(i!==t[0]);n=t[0],t[1]==="www."?s="http://"+t[0]:s=t[0]}return{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:n}}}};var x=class l{tokens;options;state;tokenizer;inlineQueue;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||w,this.options.tokenizer=this.options.tokenizer||new S,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:m,block:B.normal,inline:P.normal};this.options.pedantic?(t.block=B.pedantic,t.inline=P.pedantic):this.options.gfm&&(t.block=B.gfm,this.options.breaks?t.inline=P.breaks:t.inline=P.gfm),this.tokenizer.rules=t}static get rules(){return{block:B,inline:P}}static lex(e,t){return new l(t).lex(e)}static lexInline(e,t){return new l(t).inlineTokens(e)}lex(e){e=e.replace(m.carriageReturn,` -`),this.blockTokens(e,this.tokens);for(let t=0;t(s=r.call({lexer:this},e,t))?(e=e.substring(s.raw.length),t.push(s),!0):!1))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);let r=t.at(-1);s.raw.length===1&&r!==void 0?r.raw+=` -`:t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=` -`+s.raw,r.text+=` -`+s.text,this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=` -`+s.raw,r.text+=` -`+s.raw,this.inlineQueue.at(-1).src=r.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let i=e;if(this.options.extensions?.startBlock){let r=1/0,o=e.slice(1),a;this.options.extensions.startBlock.forEach(c=>{a=c.call({lexer:this},o),typeof a=="number"&&a>=0&&(r=Math.min(r,a))}),r<1/0&&r>=0&&(i=e.substring(0,r+1))}if(this.state.top&&(s=this.tokenizer.paragraph(i))){let r=t.at(-1);n&&r?.type==="paragraph"?(r.raw+=` -`+s.raw,r.text+=` -`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s),n=i.length!==e.length,e=e.substring(s.raw.length);continue}if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="text"?(r.raw+=` -`+s.raw,r.text+=` -`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(e){let r="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(r);break}else throw new Error(r)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){let o=Object.keys(this.tokens.links);if(o.length>0)for(;(s=this.tokenizer.rules.inline.reflinkSearch.exec(n))!=null;)o.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(s=this.tokenizer.rules.inline.anyPunctuation.exec(n))!=null;)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;(s=this.tokenizer.rules.inline.blockSkip.exec(n))!=null;)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);let i=!1,r="";for(;e;){i||(r=""),i=!1;let o;if(this.options.extensions?.inline?.some(c=>(o=c.call({lexer:this},e,t))?(e=e.substring(o.raw.length),t.push(o),!0):!1))continue;if(o=this.tokenizer.escape(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.tag(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.link(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(o.raw.length);let c=t.at(-1);o.type==="text"&&c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(o=this.tokenizer.emStrong(e,n,r)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.codespan(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.br(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.del(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.autolink(e)){e=e.substring(o.raw.length),t.push(o);continue}if(!this.state.inLink&&(o=this.tokenizer.url(e))){e=e.substring(o.raw.length),t.push(o);continue}let a=e;if(this.options.extensions?.startInline){let c=1/0,p=e.slice(1),u;this.options.extensions.startInline.forEach(d=>{u=d.call({lexer:this},p),typeof u=="number"&&u>=0&&(c=Math.min(c,u))}),c<1/0&&c>=0&&(a=e.substring(0,c+1))}if(o=this.tokenizer.inlineText(a)){e=e.substring(o.raw.length),o.raw.slice(-1)!=="_"&&(r=o.raw.slice(-1)),i=!0;let c=t.at(-1);c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(e){let c="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(c);break}else throw new Error(c)}}return t}};var $=class{options;parser;constructor(e){this.options=e||w}space(e){return""}code({text:e,lang:t,escaped:n}){let s=(t||"").match(m.notSpaceStart)?.[0],i=e.replace(m.endingNewline,"")+` -`;return s?'
      '+(n?i:R(i,!0))+`
      -`:"
      "+(n?i:R(i,!0))+`
      -`}blockquote({tokens:e}){return`
      -${this.parser.parse(e)}
      -`}html({text:e}){return e}heading({tokens:e,depth:t}){return`${this.parser.parseInline(e)} -`}hr(e){return`
      -`}list(e){let t=e.ordered,n=e.start,s="";for(let o=0;o -`+s+" -`}listitem(e){let t="";if(e.task){let n=this.checkbox({checked:!!e.checked});e.loose?e.tokens[0]?.type==="paragraph"?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&e.tokens[0].tokens[0].type==="text"&&(e.tokens[0].tokens[0].text=n+" "+R(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`
    • ${t}
    • -`}checkbox({checked:e}){return"'}paragraph({tokens:e}){return`

      ${this.parser.parseInline(e)}

      -`}table(e){let t="",n="";for(let i=0;i${s}`),` - -`+t+` -`+s+`
      -`}tablerow({text:e}){return` -${e} -`}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+` -`}strong({tokens:e}){return`${this.parser.parseInline(e)}`}em({tokens:e}){return`${this.parser.parseInline(e)}`}codespan({text:e}){return`${R(e,!0)}`}br(e){return"
      "}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:t,tokens:n}){let s=this.parser.parseInline(n),i=V(e);if(i===null)return s;e=i;let r='
      ",r}image({href:e,title:t,text:n,tokens:s}){s&&(n=this.parser.parseInline(s,this.parser.textRenderer));let i=V(e);if(i===null)return R(n);e=i;let r=`${n}{let o=i[r].flat(1/0);n=n.concat(this.walkTokens(o,t))}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)))}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let s={...n};if(s.async=this.defaults.async||s.async||!1,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let r=t.renderers[i.name];r?t.renderers[i.name]=function(...o){let a=i.renderer.apply(this,o);return a===!1&&(a=r.apply(this,o)),a}:t.renderers[i.name]=i.renderer}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let r=t[i.level];r?r.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]))}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens)}),s.extensions=t),n.renderer){let i=this.defaults.renderer||new $(this.defaults);for(let r in n.renderer){if(!(r in i))throw new Error(`renderer '${r}' does not exist`);if(["options","parser"].includes(r))continue;let o=r,a=n.renderer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u||""}}s.renderer=i}if(n.tokenizer){let i=this.defaults.tokenizer||new S(this.defaults);for(let r in n.tokenizer){if(!(r in i))throw new Error(`tokenizer '${r}' does not exist`);if(["options","rules","lexer"].includes(r))continue;let o=r,a=n.tokenizer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.tokenizer=i}if(n.hooks){let i=this.defaults.hooks||new L;for(let r in n.hooks){if(!(r in i))throw new Error(`hook '${r}' does not exist`);if(["options","block"].includes(r))continue;let o=r,a=n.hooks[o],c=i[o];L.passThroughHooks.has(r)?i[o]=p=>{if(this.defaults.async)return Promise.resolve(a.call(i,p)).then(d=>c.call(i,d));let u=a.call(i,p);return c.call(i,u)}:i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.hooks=i}if(n.walkTokens){let i=this.defaults.walkTokens,r=n.walkTokens;s.walkTokens=function(o){let a=[];return a.push(r.call(this,o)),i&&(a=a.concat(i.call(this,o))),a}}this.defaults={...this.defaults,...s}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return x.lex(e,t??this.defaults)}parser(e,t){return b.parse(e,t??this.defaults)}parseMarkdown(e){return(n,s)=>{let i={...s},r={...this.defaults,...i},o=this.onError(!!r.silent,!!r.async);if(this.defaults.async===!0&&i.async===!1)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);let a=r.hooks?r.hooks.provideLexer():e?x.lex:x.lexInline,c=r.hooks?r.hooks.provideParser():e?b.parse:b.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(n):n).then(p=>a(p,r)).then(p=>r.hooks?r.hooks.processAllTokens(p):p).then(p=>r.walkTokens?Promise.all(this.walkTokens(p,r.walkTokens)).then(()=>p):p).then(p=>c(p,r)).then(p=>r.hooks?r.hooks.postprocess(p):p).catch(o);try{r.hooks&&(n=r.hooks.preprocess(n));let p=a(n,r);r.hooks&&(p=r.hooks.processAllTokens(p)),r.walkTokens&&this.walkTokens(p,r.walkTokens);let u=c(p,r);return r.hooks&&(u=r.hooks.postprocess(u)),u}catch(p){return o(p)}}}onError(e,t){return n=>{if(n.message+=` -Please report this to https://github.com/markedjs/marked.`,e){let s="

      An error occurred:

      "+R(n.message+"",!0)+"
      ";return t?Promise.resolve(s):s}if(t)return Promise.reject(n);throw n}}};var M=new E;function k(l,e){return M.parse(l,e)}k.options=k.setOptions=function(l){return M.setOptions(l),k.defaults=M.defaults,N(k.defaults),k};k.getDefaults=z;k.defaults=w;k.use=function(...l){return M.use(...l),k.defaults=M.defaults,N(k.defaults),k};k.walkTokens=function(l,e){return M.walkTokens(l,e)};k.parseInline=M.parseInline;k.Parser=b;k.parser=b.parse;k.Renderer=$;k.TextRenderer=_;k.Lexer=x;k.lexer=x.lex;k.Tokenizer=S;k.Hooks=L;k.parse=k;var it=k.options,ot=k.setOptions,lt=k.use,at=k.walkTokens,ct=k.parseInline,pt=k,ut=b.parse,ht=x.lex; - -if(__exports != exports)module.exports = exports;return module.exports})); diff --git a/js/script.js b/js/script.js deleted file mode 100644 index 3d06596..0000000 --- a/js/script.js +++ /dev/null @@ -1,247 +0,0 @@ -function copyIp() { - const ipText = document.getElementById('server-ip').innerText; - const tooltip = document.getElementById('copy-tooltip'); - - navigator.clipboard.writeText(ipText).then(() => { - tooltip.innerText = "已复制!"; - tooltip.classList.add('show'); - - setTimeout(() => { - tooltip.classList.remove('show'); - setTimeout(() => { - tooltip.innerText = "点击复制 IP"; - }, 200); // Wait for fade out - }, 2000); - }).catch(err => { - console.error('无法复制文本: ', err); - tooltip.innerText = "复制失败"; - tooltip.classList.add('show'); - setTimeout(() => { - tooltip.classList.remove('show'); - }, 2000); - }); -} - -// Dynamic Subtitle Rotation -const SUBTITLES = [ - '纯净', - '原版', - '生存', - '养老', - '休闲' -]; - -let currentSubtitleIndex = 0; - -function initDynamicSubtitle() { - const dynamicElement = document.getElementById('dynamic-subtitle'); - if (!dynamicElement) return; - - // Set initial subtitle - dynamicElement.textContent = SUBTITLES[0]; - dynamicElement.classList.add('fade-enter-active'); - - // Start rotation - setInterval(() => { - // Fade out - dynamicElement.classList.remove('fade-enter-active'); - dynamicElement.classList.add('fade-exit-active'); - - setTimeout(() => { - // Change text - currentSubtitleIndex = (currentSubtitleIndex + 1) % SUBTITLES.length; - dynamicElement.textContent = SUBTITLES[currentSubtitleIndex]; - - // Fade in - dynamicElement.classList.remove('fade-exit-active'); - dynamicElement.classList.add('fade-enter-active'); - }, 500); - }, 4000); // Change every 4 seconds -} - -// Sponsors Logic -document.addEventListener('DOMContentLoaded', () => { - initDynamicSubtitle(); - fetchSponsors(); - fetchCrowdfunding(); - // setupModal(); // Removed, modal is gone - fetchServerStatus(); - startRuntimeTimer(); -}); - - - -function startRuntimeTimer() { - const startTime = new Date("2021-09-14T09:57:59").getTime(); - - function update() { - const now = new Date().getTime(); - const diff = now - startTime; - - const days = Math.floor(diff / (1000 * 60 * 60 * 24)); - const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((diff % (1000 * 60)) / 1000); - - const daysEl = document.getElementById("runtime-days"); - const hoursEl = document.getElementById("runtime-hours"); - const minutesEl = document.getElementById("runtime-minutes"); - const secondsEl = document.getElementById("runtime-seconds"); - - if (daysEl) daysEl.innerText = days; - if (hoursEl) hoursEl.innerText = hours; - if (minutesEl) minutesEl.innerText = minutes; - if (secondsEl) secondsEl.innerText = seconds; - } - - update(); - setInterval(update, 1000); -} - -async function fetchServerStatus() { - const countElement = document.getElementById('online-count'); - const listElement = document.getElementById('players-list'); - const dotElement = document.querySelector('.status-dot'); - - try { - const response = await fetch('https://api.mcstatus.io/v2/status/java/mcpure.lunadeer.cn'); - const data = await response.json(); - - if (data.online) { - countElement.innerText = `在线人数: ${data.players.online} / ${data.players.max}`; - dotElement.classList.remove('offline'); - - if (data.players.list && data.players.list.length > 0) { - listElement.innerHTML = data.players.list.map(player => ` -
      - - ${player.name_raw} -
      - `).join(''); - } else { - listElement.innerHTML = '
      暂无玩家在线
      '; - } - } else { - countElement.innerText = '服务器离线'; - dotElement.classList.add('offline'); - listElement.innerHTML = '
      服务器离线
      '; - } - } catch (error) { - console.error('Error fetching server status:', error); - countElement.innerText = '无法获取状态'; - dotElement.classList.add('offline'); - listElement.innerHTML = '
      获取失败
      '; - } -} - -async function fetchCrowdfunding() { - try { - console.log('Fetching crowdfunding data...'); - const response = await fetch('data/fund_progress.txt'); - if (!response.ok) { - console.error('Failed to fetch data/fund_progress.txt:', response.status, response.statusText); - return; - } - - const text = await response.text(); - console.log('Crowdfunding data received:', text); - const lines = text.trim().split('\n'); - - const funds = []; - lines.forEach(line => { - // Replace Chinese comma with English comma just in case - const normalizedLine = line.replace(/,/g, ','); - const parts = normalizedLine.split(','); - if (parts.length >= 3) { - const name = parts[0].trim(); - const current = parseFloat(parts[1].trim()); - const target = parseFloat(parts[2].trim()); - - if (name && !isNaN(current) && !isNaN(target)) { - funds.push({ name, current, target }); - } - } - }); - console.log('Parsed funds:', funds); - - if (funds.length > 0) { - renderCrowdfunding(funds); - const section = document.getElementById('crowdfunding-section'); - if (section) { - section.style.display = 'block'; - console.log('Crowdfunding section displayed'); - } else { - console.error('Crowdfunding section element not found'); - } - } else { - console.warn('No valid crowdfunding data found'); - } - - } catch (error) { - console.error('Error loading crowdfunding data:', error); - } -} - -function renderCrowdfunding(funds) { - const container = document.getElementById('crowdfunding-grid'); - - container.innerHTML = funds.map(fund => { - const percentage = Math.min(100, Math.max(0, (fund.current / fund.target) * 100)); - return ` -
      -
      -
      ${fund.name}
      -
      - ¥${fund.current} / ¥${fund.target} -
      -
      -
      -
      -
      -
      ${percentage.toFixed(1)}%
      -
      - `; - }).join(''); - - container.querySelectorAll('.progress-bar-fill').forEach(bar => { - const percentage = bar.dataset.percentage || '0'; - bar.style.width = `${percentage}%`; - }); -} - -async function fetchSponsors() { - try { - const response = await fetch('data/sponsors.txt'); - const text = await response.text(); - const sponsors = DataUtils.parseSponsorsText(text); - const userTotals = DataUtils.buildSponsorTotals(sponsors); - - // Sort users by total amount for Top 3 - const sortedUsers = Object.keys(userTotals).map(name => ({ - name, - total: userTotals[name] - })).sort((a, b) => b.total - a.total); - - renderTopSponsors(sortedUsers.slice(0, 3)); - // renderSponsorsTable(sponsors); // Removed as table is now on sponsor.html - - } catch (error) { - console.error('Error loading sponsors:', error); - } -} - -function renderTopSponsors(topUsers) { - const container = document.getElementById('top-sponsors'); - const medals = ['🥇', '🥈', '🥉']; - - container.innerHTML = topUsers.map((user, index) => ` - - `).join(''); -} - -/* Removed renderSponsorsTable and setupModal as they are no longer used on index page */ - diff --git a/js/sponsor_script.js b/js/sponsor_script.js deleted file mode 100644 index 466f03b..0000000 --- a/js/sponsor_script.js +++ /dev/null @@ -1,223 +0,0 @@ -let allSponsors = []; -let grandTotal = 0; -let filterState = { search: '', project: 'all' }; - -document.addEventListener('DOMContentLoaded', () => { - try { - setupUI(); - } catch (e) { - console.error("UI Setup failed", e); - } - fetchSponsorsData(); - setupListeners(); -}); - -function setupUI() { - // Mobile menu toggle handled by components.js - - // Modal Logic - const modal = document.getElementById('sponsor-modal'); - const btn = document.getElementById('open-sponsor-modal'); - const span = document.querySelector('.close-modal'); - const desktopView = document.getElementById('desktop-qr-view'); - const mobileView = document.getElementById('mobile-btn-view'); - - // Detect Mobile - const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth < 768; - - if (isMobile) { - if(desktopView) desktopView.style.display = 'none'; - if(mobileView) mobileView.style.display = 'block'; - } else { - if(desktopView) desktopView.style.display = 'block'; - if(mobileView) mobileView.style.display = 'none'; - } - - if (modal && btn) { - btn.addEventListener('click', (e) => { - e.preventDefault(); - modal.style.display = "flex"; - // Trigger reflow - void modal.offsetWidth; - requestAnimationFrame(() => { - modal.classList.add('show'); - }); - }); - } - - if (span && modal) { - span.addEventListener('click', () => { - modal.classList.remove('show'); - setTimeout(() => { - modal.style.display = "none"; - }, 300); - }); - } - - window.addEventListener('click', (event) => { - if (modal && event.target == modal) { - modal.classList.remove('show'); - setTimeout(() => { - modal.style.display = "none"; - }, 300); - } - }); -} - - -function setupListeners() { - const searchInput = document.getElementById('sponsor-search'); - const filterContainer = document.getElementById('project-filters'); - - if (searchInput) { - searchInput.addEventListener('input', (e) => { - filterState.search = e.target.value.toLowerCase().trim(); - applyFilters(); - }); - } - - if (filterContainer) { - filterContainer.addEventListener('click', (e) => { - if (e.target.classList.contains('filter-tag')) { - // Update active class - document.querySelectorAll('.filter-tag').forEach(btn => btn.classList.remove('active')); - e.target.classList.add('active'); - - // Update filter state - filterState.project = e.target.dataset.project; - applyFilters(); - } - }); - } -} - -async function fetchSponsorsData() { - try { - const response = await fetch('data/sponsors.txt'); - if (!response.ok) { - throw new Error('Failed to fetch data/sponsors.txt'); - } - const text = await response.text(); - const sponsors = DataUtils.parseSponsorsText(text); - const projects = new Set(); - grandTotal = 0; - - sponsors.forEach(item => { - grandTotal += item.amount; - projects.add(item.project); - }); - - allSponsors = [...sponsors].reverse(); // Start with newest - - // Animate Total - animateValue(grandTotal); - - // Render everything - renderFilters(Array.from(projects)); - applyFilters(); // Renders the grid initially - - } catch (error) { - console.error('Error loading sponsors:', error); - const grid = document.getElementById('donation-list'); - if(grid) grid.innerHTML = ''; - } -} - -function animateValue(end) { - const obj = document.getElementById('total-amount-display'); - if (!obj) return; - - // Simple count up - let startTimestamp = null; - const duration = 2000; - const start = 0; - - const step = (timestamp) => { - if (!startTimestamp) startTimestamp = timestamp; - const progress = Math.min((timestamp - startTimestamp) / duration, 1); - // Easing out Quart - const easeProgress = 1 - Math.pow(1 - progress, 4); - - const current = Math.floor(easeProgress * (end - start) + start); - - obj.innerHTML = `¥${current.toLocaleString('en-US')}`; - - if (progress < 1) { - window.requestAnimationFrame(step); - } else { - obj.innerHTML = `¥${end.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2})}`; - } - }; - window.requestAnimationFrame(step); -} - -function renderFilters(projects) { - const container = document.getElementById('project-filters'); - if (!container) return; - - // Remove existing project buttons, keep "All" - const existingButtons = container.querySelectorAll('button:not([data-project="all"])'); - existingButtons.forEach(btn => btn.remove()); - - // Add project buttons - projects.forEach(proj => { - if (!proj) return; - const btn = document.createElement('button'); - btn.className = 'filter-tag'; - btn.textContent = proj; - btn.dataset.project = proj; - container.appendChild(btn); - }); -} - -function applyFilters() { - const { search, project } = filterState; - const grid = document.getElementById('donation-list'); - const noResults = document.getElementById('no-results'); - - if (!grid) return; - - const filtered = allSponsors.filter(item => { - const matchesProject = project === 'all' || item.project === project; - const matchesSearch = item.name.toLowerCase().includes(search); - return matchesProject && matchesSearch; - }); - - grid.innerHTML = ''; - - if (filtered.length === 0) { - if (noResults) noResults.style.display = 'block'; - return; - } - - if (noResults) noResults.style.display = 'none'; - - filtered.forEach((item, index) => { - const card = document.createElement('div'); - card.className = 'donation-card'; - // Max delay 1s to prevent long waits on huge lists - const delay = Math.min(index * 0.05, 1); - card.style.animationDelay = `${delay}s`; - - const avatarUrl = `https://minotar.net/helm/${item.name}/64.png`; - - card.innerHTML = ` -
      -
      - ${item.name} -
      ${item.name}
      -
      -
      ¥${item.amount}
      -
      - -
      -
      ${item.project}
      -
      - ${item.date} -
      -
      - `; - grid.appendChild(card); - }); -} - diff --git a/js/stats_script.js b/js/stats_script.js deleted file mode 100644 index 470a4b8..0000000 --- a/js/stats_script.js +++ /dev/null @@ -1,408 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - fetchStats(); - setupModal(); - setupSearch(); - setupLoadMore(); -}); - - - -let allPlayers = []; -let displayedPlayers = []; -let currentPage = 1; -const pageSize = 24; - -async function fetchStats() { - try { - const response = await fetch('stats/summary.json'); - if (!response.ok) throw new Error('Failed to load stats'); - - const data = await response.json(); - allPlayers = data.players; - - // Show update time - if (data.updated_at) { - document.getElementById('stats-updated-at').textContent = '数据更新于 ' + data.updated_at; - } - - // Hide loading - document.getElementById('loading-indicator').style.display = 'none'; - - // Render things - renderLeaderboards(); - - // Initial Grid Render - displayedPlayers = allPlayers; // Start with all - renderPlayerGrid(true); // reset - - } catch (error) { - console.error('Error:', error); - document.getElementById('loading-indicator').innerText = "加载数据失败,请稍后重试。"; - } -} - -function renderLeaderboards() { - // Helper to sort and slice - const getTop = (key, subKey) => { - return [...allPlayers] - .sort((a, b) => { - let valA = subKey ? a.stats[key] : a.stats[key]; // if structure allows direct access - let valB = subKey ? b.stats[key] : b.stats[key]; - - // Special case for walk which has raw sorting value - if (key === 'walk_fmt') valA = a.stats.walk_raw; - if (key === 'walk_fmt') valB = b.stats.walk_raw; - - // Special case for play_time which has raw sorting value - if (key === 'play_time_fmt') valA = a.stats.play_time_raw; - if (key === 'play_time_fmt') valB = b.stats.play_time_raw; - - return valB - valA; - }) - .slice(0, 4); // Top 4 - }; - - const renderCard = (elementId, players, valueFormatter) => { - const container = document.getElementById(elementId); - if (!players || players.length === 0) { - container.innerHTML = '
      暂无数据
      '; - return; - } - - const top1 = players[0]; - let html = ` -
      - -
      ${top1.name}
      -
      ${valueFormatter(top1)}
      -
      -
      - `; - - for (let i = 1; i < players.length; i++) { - const p = players[i]; - html += ` -
      -
      - ${i+1} - ${p.name} -
      - ${valueFormatter(p)} -
      - `; - } - html += '
      '; - container.innerHTML = html; - }; - - // 1. Walk (stats.walk_raw) - const topWalkers = getTop('walk_fmt'); // uses walk_raw internally - renderCard('lb-walk', topWalkers, p => p.stats.walk_fmt); - - // 2. Placed (stats.placed) - const topPlacers = getTop('placed'); - renderCard('lb-placed', topPlacers, p => p.stats.placed.toLocaleString()); - - // 3. Mined (stats.mined) - const topMiners = getTop('mined'); - renderCard('lb-mined', topMiners, p => p.stats.mined.toLocaleString()); - - // 4. Deaths (stats.deaths) - const topDeaths = getTop('deaths'); - renderCard('lb-deaths', topDeaths, p => p.stats.deaths.toLocaleString()); - - // 5. Play Time (stats.play_time_raw) - const topPlayTime = getTop('play_time_fmt'); // uses play_time_raw internally - renderCard('lb-playtime', topPlayTime, p => p.stats.play_time_fmt); - - // 6. Kills (stats.kills) - const topKills = getTop('kills'); - renderCard('lb-kills', topKills, p => p.stats.kills.toLocaleString()); -} - -function renderPlayerGrid(reset = false) { - const grid = document.getElementById('players-grid'); - const loadMoreBtn = document.getElementById('load-more-btn'); - - if (reset) { - grid.innerHTML = ''; - currentPage = 1; - } - - const start = (currentPage - 1) * pageSize; - const end = start + pageSize; - const items = displayedPlayers.slice(start, end); - - items.forEach(p => { - const card = document.createElement('div'); - card.className = 'player-card'; - card.onclick = () => openModal(p); - - card.innerHTML = ` - -

      ${p.name}

      - - `; - grid.appendChild(card); - }); - - if (end >= displayedPlayers.length) { - loadMoreBtn.style.display = 'none'; - } else { - loadMoreBtn.style.display = 'inline-block'; - } -} - -function setupLoadMore() { - document.getElementById('load-more-btn').addEventListener('click', () => { - currentPage++; - renderPlayerGrid(false); - }); -} - -function setupSearch() { - const input = document.getElementById('player-search'); - input.addEventListener('input', (e) => { - const term = e.target.value.toLowerCase().trim(); - if (!term) { - displayedPlayers = allPlayers; - } else { - displayedPlayers = allPlayers.filter(p => - p.name.toLowerCase().includes(term) || - p.uuid.toLowerCase().includes(term) - ); - } - renderPlayerGrid(true); - }); -} - -// Modal Logic -const modal = document.getElementById("player-modal"); -const span = document.getElementsByClassName("close-modal")[0]; - -function setupModal() { - span.onclick = function() { - modal.style.display = "none"; - } - window.onclick = function(event) { - if (event.target == modal) { - modal.style.display = "none"; - } - } -} - -function openModal(player) { - const modal = document.getElementById("player-modal"); - - // Top Section: Identity - document.getElementById('modal-name').innerText = player.name; - document.getElementById('modal-uuid').innerText = player.uuid; - const avatar = document.getElementById('modal-avatar'); - avatar.src = player.avatar; - avatar.onerror = () => { avatar.src = `https://crafatar.com/avatars/${player.uuid}?size=64&overlay`; }; - - // Top Section: Summary Stats - // These are from summary.json which is already formatted - document.getElementById('modal-walk').innerText = player.stats.walk_fmt; - document.getElementById('modal-placed').innerText = player.stats.placed.toLocaleString(); - document.getElementById('modal-mined').innerText = player.stats.mined.toLocaleString(); - document.getElementById('modal-deaths').innerText = player.stats.deaths; - document.getElementById('modal-kills').innerText = player.stats.kills; - document.getElementById('modal-playtime').innerText = player.stats.play_time_fmt; - - // Bottom Section: Reset and Load Details - const accordion = document.getElementById('stats-accordion'); - accordion.innerHTML = ''; - document.getElementById('loading-details').style.display = 'block'; - document.getElementById('loading-details').innerText = '正在加载详细数据...'; - - modal.style.display = "flex"; - - // Load existing details - loadPlayerDetails(player.uuid); -} - -async function loadPlayerDetails(uuid) { - const accordion = document.getElementById('stats-accordion'); - const loading = document.getElementById('loading-details'); - - try { - const response = await fetch(`stats/${uuid}.json`); - if (!response.ok) throw new Error('Stats file not found'); - - const data = await response.json(); - - if (!data.stats) { - loading.innerText = '暂无详细统计数据。'; - return; - } - - loading.style.display = 'none'; - renderDetailsAccordion(data.stats); - - } catch (error) { - console.error('Error loading details:', error); - loading.innerText = '无法加载详细数据。'; - } -} - -function renderDetailsAccordion(statsObj) { - const accordion = document.getElementById('stats-accordion'); - - // Define category mappings for better display names - const categoryMap = { - 'minecraft:custom': { name: '通用统计', icon: 'fa-chart-bar' }, - 'minecraft:mined': { name: '挖掘统计', icon: 'fa-hammer' }, - 'minecraft:used': { name: '使用统计', icon: 'fa-hand-paper' }, - 'minecraft:crafted': { name: '合成统计', icon: 'fa-tools' }, - 'minecraft:broken': { name: '破坏统计', icon: 'fa-heart-broken' }, - 'minecraft:picked_up': { name: '拾取统计', icon: 'fa-box-open' }, - 'minecraft:dropped': { name: '丢弃统计', icon: 'fa-trash' }, - 'minecraft:killed': { name: '击杀统计', icon: 'fa-crosshairs' }, - 'minecraft:killed_by': { name: '死亡统计', icon: 'fa-skull' } - }; - - // Helper to format keys (minecraft:stone -> Stone) - const formatKey = (key) => { - if (key.includes(':')) key = key.split(':')[1]; - return key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); - }; - - // Sort categories to put 'custom' first, then others alphabetically - const sortedKeys = Object.keys(statsObj).sort((a, b) => { - if (a === 'minecraft:custom') return -1; - if (b === 'minecraft:custom') return 1; - return a.localeCompare(b); - }); - - // Threshold for showing search filter - const SEARCH_THRESHOLD = 20; - - sortedKeys.forEach(catKey => { - const subStats = statsObj[catKey]; - const itemCount = Object.keys(subStats).length; - if (itemCount === 0) return; - - const catInfo = categoryMap[catKey] || { name: formatKey(catKey), icon: 'fa-folder' }; - - // Create Accordion Item - const item = document.createElement('div'); - item.className = 'accordion-item'; - - // Header with item count badge - const header = document.createElement('div'); - header.className = 'accordion-header'; - header.innerHTML = ` - ${catInfo.name}${itemCount} - - `; - - // Content - const content = document.createElement('div'); - content.className = 'accordion-content'; - - // Search filter for large categories - let searchInput = null; - let noResults = null; - if (itemCount >= SEARCH_THRESHOLD) { - const searchWrapper = document.createElement('div'); - searchWrapper.className = 'detail-search-wrapper'; - searchWrapper.innerHTML = ''; - searchInput = document.createElement('input'); - searchInput.type = 'text'; - searchInput.className = 'detail-search'; - searchInput.placeholder = '搜索条目...'; - searchWrapper.appendChild(searchInput); - content.appendChild(searchWrapper); - - noResults = document.createElement('div'); - noResults.className = 'detail-no-results'; - noResults.textContent = '没有匹配的条目'; - content.appendChild(noResults); - } - - // Grid for stats - const grid = document.createElement('div'); - grid.className = 'detail-stats-grid'; - - // Sort sub-items by value descending - const subItems = Object.entries(subStats).sort((a, b) => b[1] - a[1]); - - subItems.forEach(([k, v], index) => { - let label = formatKey(k); - let val = v.toLocaleString(); - - // Special formatting for time/distance in 'custom' - if (catKey === 'minecraft:custom') { - if (k.includes('time') || k.includes('minute')) { - if (k.includes('play_time') || k.includes('time_since')) { - const sec = v / 20; - if (sec > 3600) val = (sec/3600).toFixed(1) + ' h'; - else if (sec > 60) val = (sec/60).toFixed(1) + ' m'; - else val = sec.toFixed(0) + ' s'; - } - } - if (k.includes('cmt') || k.includes('one_cm')) { - const m = v / 100; - if (m > 1000) val = (m/1000).toFixed(2) + ' km'; - else val = m.toFixed(1) + ' m'; - } - } - - // Rank class for top 3 - let rankClass = ''; - if (index === 0) rankClass = ' rank-1'; - else if (index === 1) rankClass = ' rank-2'; - else if (index === 2) rankClass = ' rank-3'; - - const statDiv = document.createElement('div'); - statDiv.className = 'detail-stat-item' + rankClass; - statDiv.dataset.label = label.toLowerCase(); - statDiv.innerHTML = ` - ${val} - ${label} - `; - grid.appendChild(statDiv); - }); - - // Wire up search filter - if (searchInput) { - searchInput.addEventListener('input', () => { - const query = searchInput.value.toLowerCase().trim(); - const items = grid.querySelectorAll('.detail-stat-item'); - let visible = 0; - items.forEach(el => { - const match = !query || el.dataset.label.includes(query); - el.classList.toggle('hidden', !match); - if (match) visible++; - }); - noResults.style.display = visible === 0 ? 'block' : 'none'; - }); - } - - content.appendChild(grid); - item.appendChild(header); - item.appendChild(content); - accordion.appendChild(item); - - // Click Event - header.addEventListener('click', () => { - const isActive = header.classList.contains('active'); - - // Close all others? Optional. Let's keep multiple openable. - // But if we want accordion behavior: - // document.querySelectorAll('.accordion-header').forEach(h => { - // h.classList.remove('active'); - // h.nextElementSibling.classList.remove('show'); - // }); - - if (!isActive) { - header.classList.add('active'); - content.classList.add('show'); - } else { - header.classList.remove('active'); - content.classList.remove('show'); - } - }); - }); -} diff --git a/js/towns_script.js b/js/towns_script.js deleted file mode 100644 index 635af17..0000000 --- a/js/towns_script.js +++ /dev/null @@ -1,931 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const DEFAULT_GRADIENT = { - from: '#667eea', - to: '#764ba2' - }; - - let townsData = []; - const grid = document.getElementById('towns-list'); - const noResults = document.getElementById('no-results'); - const scaleFilters = document.getElementById('scale-filters'); - const typeFilters = document.getElementById('type-filters'); - const recruitFilters = document.getElementById('recruit-filters'); - const searchInput = document.getElementById('town-search'); - - // Modal Elements - const modal = document.getElementById('town-modal'); - const closeModalBtn = modal.querySelector('.close-modal'); - - // Initial State - let currentFilters = { - scale: 'all', - townType: 'all', - recruitment: 'all', - search: '' - }; - - let currentDetailItem = null; - - // Generate stable anchor ID for a town - function generateTownId(item) { - var raw = (item.title || ''); - var hash = 0; - for (var i = 0; i < raw.length; i++) { - hash = ((hash << 5) - hash) + raw.charCodeAt(i); - hash |= 0; - } - return 't' + Math.abs(hash).toString(36); - } - - // Handle URL hash: auto-open town modal - function handleHashNavigation() { - var hash = location.hash.replace('#', ''); - if (!hash) return; - for (var i = 0; i < townsData.length; i++) { - if (generateTownId(townsData[i]) === hash) { - openModal(townsData[i]); - return; - } - } - } - - // 1. Fetch Data - fetch('data/towns.json') - .then(response => response.json()) - .then(data => { - townsData = data; - renderGrid(); - handleHashNavigation(); - }) - .catch(err => { - console.error('Error loading towns:', err); - grid.innerHTML = '

      无法加载城镇数据。

      '; - }); - - // 2. Event Listeners - - // Scale Filter - scaleFilters.addEventListener('click', (e) => { - if (e.target.tagName === 'BUTTON') { - Array.from(scaleFilters.children).forEach(btn => btn.classList.remove('active')); - e.target.classList.add('active'); - currentFilters.scale = e.target.dataset.filter; - renderGrid(); - } - }); - - // Type Filter - typeFilters.addEventListener('click', (e) => { - if (e.target.tagName === 'BUTTON') { - Array.from(typeFilters.children).forEach(btn => btn.classList.remove('active')); - e.target.classList.add('active'); - currentFilters.townType = e.target.dataset.filter; - renderGrid(); - } - }); - - // Recruit Filter - recruitFilters.addEventListener('click', (e) => { - if (e.target.tagName === 'BUTTON') { - Array.from(recruitFilters.children).forEach(btn => btn.classList.remove('active')); - e.target.classList.add('active'); - currentFilters.recruitment = e.target.dataset.filter; - renderGrid(); - } - }); - - // Search - searchInput.addEventListener('input', (e) => { - currentFilters.search = e.target.value.toLowerCase().trim(); - renderGrid(); - }); - - // Modal Close - closeModalBtn.addEventListener('click', () => { - modal.style.display = 'none'; - document.body.style.overflow = 'auto'; - history.replaceState(null, '', location.pathname + location.search); - }); - - window.addEventListener('click', (e) => { - if (e.target === modal) { - modal.style.display = 'none'; - document.body.style.overflow = 'auto'; - history.replaceState(null, '', location.pathname + location.search); - } - }); - - // 3. Render Functions - function renderGrid() { - grid.innerHTML = ''; - - const filtered = townsData.filter(item => { - const matchScale = currentFilters.scale === 'all' || item.scale === currentFilters.scale; - const matchType = currentFilters.townType === 'all' || item.townType === currentFilters.townType; - const matchRecruit = currentFilters.recruitment === 'all' || item.recruitment === currentFilters.recruitment; - const matchSearch = !currentFilters.search || - item.title.toLowerCase().includes(currentFilters.search); - return matchScale && matchType && matchRecruit && 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 = 'town-card'; - card.onclick = () => openModal(item); - - const hasLogo = item.logo && item.logo.trim() !== ''; - const gradient = getTownGradient(item); - - // Build card icon badges (scale + type + recruitment) - let iconsHtml = ''; - iconsHtml += '
      '; - iconsHtml += ''; - iconsHtml += ''; - iconsHtml += ''; - iconsHtml += '
      '; - - card.innerHTML = - '' + - '
      ' + - '

      ' + escapeHtml(item.title) + '

      ' + - '
      ' + - ' ' + getScaleText(item.scale) + '' + - ' ' + getTownTypeText(item.townType) + '' + - ' ' + getRecruitText(item.recruitment) + '' + - '
      ' + - '
      '; - - grid.appendChild(card); - }); - } - - function openModal(item) { - currentDetailItem = item; - - // Banner - var banner = document.getElementById('town-modal-banner'); - var hasLogo = item.logo && item.logo.trim() !== ''; - var gradient = getTownGradient(item); - banner.className = 'town-modal-banner' + (hasLogo ? '' : ' no-logo'); - if (hasLogo) { - banner.style.background = ''; - banner.style.backgroundImage = "url('" + item.logo + "')"; - banner.innerHTML = ''; - } else { - banner.style.backgroundImage = ''; - banner.style.background = buildGradientBackgroundValue(gradient); - banner.innerHTML = ''; - } - - // Title - document.getElementById('town-modal-title').innerText = item.title; - - // Badges - var badgesContainer = document.getElementById('town-modal-badges'); - badgesContainer.innerHTML = ''; - - var scaleBadge = document.createElement('span'); - scaleBadge.className = 'town-badge badge-scale-' + item.scale; - scaleBadge.innerHTML = ' ' + getScaleText(item.scale); - badgesContainer.appendChild(scaleBadge); - - var typeBadge = document.createElement('span'); - typeBadge.className = 'town-badge badge-type-' + item.townType; - typeBadge.innerHTML = ' ' + getTownTypeText(item.townType); - badgesContainer.appendChild(typeBadge); - - var recruitBadge = document.createElement('span'); - recruitBadge.className = 'town-badge badge-recruit-' + item.recruitment; - recruitBadge.innerHTML = ' ' + getRecruitText(item.recruitment); - badgesContainer.appendChild(recruitBadge); - - // Coordinates & Dimension - var coords = item.coordinates || { x: 0, y: 64, z: 0 }; - var dimension = item.dimension || 'overworld'; - var isSecret = item.coordinatesSecret === true; - var locationTitleEl = document.getElementById('town-modal-location-title'); - var dimensionEl = document.getElementById('town-modal-dimension'); - var coordsEl = document.getElementById('town-modal-coords'); - var mapLink = document.getElementById('town-modal-map-link'); - - locationTitleEl.innerHTML = ' 位置信息'; - if (isSecret) { - dimensionEl.innerText = ''; - coordsEl.innerText = '保密'; - mapLink.style.display = 'none'; - } else { - dimensionEl.innerText = getDimensionText(dimension) + ': '; - coordsEl.innerText = 'X: ' + coords.x + ', Y: ' + coords.y + ', Z: ' + coords.z; - mapLink.style.display = ''; - mapLink.href = 'https://mcmap.lunadeer.cn/#' + getDimensionMapWorld(dimension) + ':' + coords.x + ':' + coords.y + ':' + coords.z + ':500:0:0:0:1:flat'; - } - - // Founders - var foundersContainer = document.getElementById('town-modal-founders'); - foundersContainer.innerHTML = ''; - if (item.founders && item.founders.length > 0) { - item.founders.forEach(function(name) { - var tag = document.createElement('div'); - tag.className = 'contributor-tag'; - tag.innerHTML = '' + escapeHtml(name) + '' + escapeHtml(name); - foundersContainer.appendChild(tag); - }); - } else { - foundersContainer.innerHTML = '暂无记录'; - } - - // Members - var membersContainer = document.getElementById('town-modal-members'); - membersContainer.innerHTML = ''; - if (item.members && item.members.length > 0) { - item.members.forEach(function(name) { - var tag = document.createElement('div'); - tag.className = 'contributor-tag'; - tag.innerHTML = '' + escapeHtml(name) + '' + escapeHtml(name); - membersContainer.appendChild(tag); - }); - } else { - membersContainer.innerHTML = '暂无记录'; - } - - // Introduction - renderContentList(document.getElementById('town-modal-introduction'), item.introduction); - - modal.style.display = 'block'; - document.body.style.overflow = 'hidden'; - - // Update URL hash - var anchorId = generateTownId(item); - history.replaceState(null, '', '#' + anchorId); - } - - function renderContentList(container, list) { - container.innerHTML = ''; - if (!list || list.length === 0) { - container.innerHTML = '

      '; - return; - } - list.forEach(function(block) { - if (block.type === 'text') { - var p = document.createElement('p'); - p.innerText = block.content; - container.appendChild(p); - } else if (block.type === 'image') { - var img = document.createElement('img'); - img.src = block.content; - img.loading = 'lazy'; - container.appendChild(img); - } else if (block.type === 'video') { - var bv = parseBVNumber(block.content); - if (bv) { - var wrapper = document.createElement('div'); - wrapper.className = 'video-embed-wrapper'; - var iframe = document.createElement('iframe'); - iframe.src = 'https://player.bilibili.com/player.html?bvid=' + bv + '&autoplay=0&high_quality=1'; - iframe.allowFullscreen = true; - iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups'); - iframe.loading = 'lazy'; - wrapper.appendChild(iframe); - container.appendChild(wrapper); - } else { - var p = document.createElement('p'); - p.className = 'text-secondary'; - p.innerText = '无效的视频 BV 号'; - container.appendChild(p); - } - } - }); - } - - function parseBVNumber(input) { - if (!input) return null; - input = input.trim(); - var bvPattern = /^(BV[A-Za-z0-9]+)$/; - var directMatch = input.match(bvPattern); - if (directMatch) return directMatch[1]; - var urlPattern = /bilibili\.com\/video\/(BV[A-Za-z0-9]+)/; - var urlMatch = input.match(urlPattern); - if (urlMatch) return urlMatch[1]; - var generalPattern = /(BV[A-Za-z0-9]{10,})/; - var generalMatch = input.match(generalPattern); - if (generalMatch) return generalMatch[1]; - return null; - } - - // Helpers - function getScaleText(scale) { - var map = { 'small': '小型(5人以下)', 'medium': '中型(2-10人)', 'large': '大型(10人以上)' }; - return map[scale] || scale; - } - - function getScaleIcon(scale) { - var map = { 'small': 'fa-user', 'medium': 'fa-users', 'large': 'fa-city' }; - return map[scale] || 'fa-users'; - } - - function getTownTypeText(type) { - var map = { 'building': '建筑', 'adventure': '冒险', 'industry': '工业' }; - return map[type] || type; - } - - function getTownTypeIcon(type) { - var map = { 'building': 'fa-building', 'adventure': 'fa-dragon', 'industry': 'fa-industry' }; - return map[type] || 'fa-building'; - } - - function getRecruitText(recruitment) { - var map = { 'welcome': '欢迎加入', 'closed': '暂不招人', 'maybe': '可以考虑' }; - return map[recruitment] || recruitment; - } - - function getRecruitIcon(recruitment) { - var map = { 'welcome': 'fa-door-open', 'closed': 'fa-door-closed', 'maybe': 'fa-question-circle' }; - return map[recruitment] || 'fa-info-circle'; - } - - function getDimensionText(dimension) { - var map = { 'overworld': '主世界', 'nether': '下界', 'the_end': '末地' }; - return map[dimension] || '主世界'; - } - - function getDimensionMapWorld(dimension) { - var map = { 'overworld': 'world', 'nether': 'world_nether', 'the_end': 'world_the_end' }; - return map[dimension] || 'world'; - } - - function normalizeHexColor(value, fallback) { - if (!value || typeof value !== 'string') return fallback; - var trimmed = value.trim(); - if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) return trimmed; - return fallback; - } - - function getTownGradient(item) { - var gradient = item && item.gradient ? item.gradient : {}; - return { - from: normalizeHexColor(gradient.from, DEFAULT_GRADIENT.from), - to: normalizeHexColor(gradient.to, DEFAULT_GRADIENT.to) - }; - } - - function buildGradientBackgroundValue(gradient) { - return 'linear-gradient(135deg, ' + gradient.from + ' 0%, ' + gradient.to + ' 100%)'; - } - - function buildGradientBackgroundStyle(gradient) { - return 'background:' + buildGradientBackgroundValue(gradient) + ';'; - } - - // Share town link - document.getElementById('btn-share-town').addEventListener('click', function() { - if (!currentDetailItem) return; - var anchorId = generateTownId(currentDetailItem); - var url = location.origin + location.pathname + '#' + anchorId; - var btn = document.getElementById('btn-share-town'); - navigator.clipboard.writeText(url).then(function() { - btn.innerHTML = ' 已复制链接'; - btn.classList.add('shared'); - setTimeout(function() { - btn.innerHTML = ' 分享'; - btn.classList.remove('shared'); - }, 2000); - }).catch(function() { - var tmp = document.createElement('input'); - tmp.value = url; - document.body.appendChild(tmp); - tmp.select(); - document.execCommand('copy'); - document.body.removeChild(tmp); - btn.innerHTML = ' 已复制链接'; - setTimeout(function() { - btn.innerHTML = ' 分享'; - }, 2000); - }); - }); - - // Open editor from detail modal - document.getElementById('btn-edit-town').addEventListener('click', function() { - if (currentDetailItem) { - modal.style.display = 'none'; - document.body.style.overflow = 'auto'; - openEditor(currentDetailItem); - } - }); - - // ========== Editor Modal Logic ========== - - var editorModal = document.getElementById('town-editor-modal'); - var jsonOutputModal = document.getElementById('town-json-output-modal'); - var closeEditorModalBtn = editorModal.querySelector('.close-editor-modal'); - var closeJsonModalBtn = jsonOutputModal.querySelector('.close-json-modal'); - - // Open empty editor for new town - document.getElementById('btn-add-town').addEventListener('click', function() { - openEditor(null); - }); - - // Close editor modal - closeEditorModalBtn.addEventListener('click', function() { - editorModal.style.display = 'none'; - document.body.style.overflow = 'auto'; - }); - window.addEventListener('click', function(e) { - if (e.target === editorModal) { - editorModal.style.display = 'none'; - document.body.style.overflow = 'auto'; - } - if (e.target === jsonOutputModal) { - jsonOutputModal.style.display = 'none'; - } - }); - closeJsonModalBtn.addEventListener('click', function() { - jsonOutputModal.style.display = 'none'; - }); - - // State for editor - var editorFounders = []; - var editorMembers = []; - var editorIntroduction = []; - - // Initialize custom selects - editorModal.querySelectorAll('.custom-select').forEach(function(select) { - var trigger = select.querySelector('.custom-select-trigger'); - var options = select.querySelectorAll('.custom-option'); - var input = select.querySelector('input[type="hidden"]'); - var text = select.querySelector('.custom-select-text'); - - trigger.addEventListener('click', function(e) { - e.stopPropagation(); - var isOpen = select.classList.contains('open'); - editorModal.querySelectorAll('.custom-select').forEach(function(s) { s.classList.remove('open'); }); - if (!isOpen) { - select.classList.add('open'); - } - }); - - options.forEach(function(option) { - option.addEventListener('click', function(e) { - e.stopPropagation(); - options.forEach(function(opt) { opt.classList.remove('selected'); }); - option.classList.add('selected'); - text.innerText = option.innerText; - input.value = option.dataset.value; - input.dispatchEvent(new Event('change')); - select.classList.remove('open'); - }); - }); - }); - - document.addEventListener('click', function() { - editorModal.querySelectorAll('.custom-select').forEach(function(s) { s.classList.remove('open'); }); - }); - - function setCustomSelectValue(id, value) { - var input = document.getElementById(id); - if (!input) return; - var select = input.closest('.custom-select'); - var option = select.querySelector('.custom-option[data-value="' + value + '"]'); - if (option) { - input.value = value; - select.querySelector('.custom-select-text').innerText = option.innerText; - select.querySelectorAll('.custom-option').forEach(function(opt) { opt.classList.remove('selected'); }); - option.classList.add('selected'); - } - } - - function openEditor(item) { - var gradient = getTownGradient(item || {}); - var coordinates = item && item.coordinates ? item.coordinates : { x: '', y: '', z: '' }; - editorFounders = item && Array.isArray(item.founders) ? item.founders.slice() : []; - editorMembers = item && Array.isArray(item.members) ? item.members.slice() : []; - editorIntroduction = item && Array.isArray(item.introduction) ? item.introduction.map(function(i) { return {type: i.type, content: i.content}; }) : []; - - document.getElementById('editor-town-title').value = item ? item.title : ''; - document.getElementById('editor-town-logo').value = item ? (item.logo || '') : ''; - document.getElementById('editor-town-gradient-from').value = gradient.from; - document.getElementById('editor-town-gradient-to').value = gradient.to; - - setCustomSelectValue('editor-town-scale', item ? item.scale : 'small'); - setCustomSelectValue('editor-town-type', item ? item.townType : 'building'); - setCustomSelectValue('editor-town-recruit', item ? item.recruitment : 'welcome'); - setCustomSelectValue('editor-town-dimension', item && item.dimension ? item.dimension : 'overworld'); - - var secretCheckbox = document.getElementById('editor-town-secret'); - secretCheckbox.checked = item ? (item.coordinatesSecret === true) : false; - toggleCoordsRow(); - - document.getElementById('editor-town-x').value = item ? coordinates.x : ''; - document.getElementById('editor-town-y').value = item ? coordinates.y : ''; - document.getElementById('editor-town-z').value = item ? coordinates.z : ''; - - renderTagsList('editor-founders-tags', editorFounders); - renderTagsList('editor-members-tags', editorMembers); - renderSortableList('editor-introduction-list', editorIntroduction); - updatePreview(); - - editorModal.style.display = 'block'; - document.body.style.overflow = 'hidden'; - } - - // --- Tags input helpers --- - function renderTagsList(containerId, list) { - var container = document.getElementById(containerId); - container.innerHTML = ''; - list.forEach(function(name, idx) { - var tag = document.createElement('span'); - tag.className = 'editor-tag'; - tag.innerHTML = escapeHtml(name) + ' '; - container.appendChild(tag); - }); - } - - function commitTagInput(inputId, list, tagsContainerId) { - var input = document.getElementById(inputId); - var value = input.value.trim(); - if (value && list.indexOf(value) === -1) { - list.push(value); - renderTagsList(tagsContainerId, list); - updatePreview(); - } - input.value = ''; - } - - // Founders tags - document.getElementById('editor-founders-tags').addEventListener('click', function(e) { - var removeBtn = e.target.closest('.editor-tag-remove'); - if (removeBtn) { - var idx = parseInt(removeBtn.dataset.idx); - editorFounders.splice(idx, 1); - renderTagsList('editor-founders-tags', editorFounders); - updatePreview(); - } - }); - - document.getElementById('editor-founder-input').addEventListener('keydown', function(e) { - if (e.isComposing) return; - if (e.key === 'Enter' || e.key === ' ' || e.code === 'Space') { - e.preventDefault(); - commitTagInput('editor-founder-input', editorFounders, 'editor-founders-tags'); - } - }); - document.getElementById('editor-founder-input').addEventListener('blur', function() { - commitTagInput('editor-founder-input', editorFounders, 'editor-founders-tags'); - }); - document.getElementById('editor-founders-wrapper').addEventListener('click', function() { - document.getElementById('editor-founder-input').focus(); - }); - - // Members tags - document.getElementById('editor-members-tags').addEventListener('click', function(e) { - var removeBtn = e.target.closest('.editor-tag-remove'); - if (removeBtn) { - var idx = parseInt(removeBtn.dataset.idx); - editorMembers.splice(idx, 1); - renderTagsList('editor-members-tags', editorMembers); - updatePreview(); - } - }); - - document.getElementById('editor-member-input').addEventListener('keydown', function(e) { - if (e.isComposing) return; - if (e.key === 'Enter' || e.key === ' ' || e.code === 'Space') { - e.preventDefault(); - commitTagInput('editor-member-input', editorMembers, 'editor-members-tags'); - } - }); - document.getElementById('editor-member-input').addEventListener('blur', function() { - commitTagInput('editor-member-input', editorMembers, 'editor-members-tags'); - }); - document.getElementById('editor-members-wrapper').addEventListener('click', function() { - document.getElementById('editor-member-input').focus(); - }); - - // --- Sortable Lists (drag-and-drop) --- - var dragState = { listId: null, fromIdx: null }; - - function renderSortableList(listId, items) { - var container = document.getElementById(listId); - container.innerHTML = ''; - items.forEach(function(item, idx) { - var div = document.createElement('div'); - div.className = 'sortable-item'; - div.draggable = true; - div.dataset.idx = idx; - div.dataset.listId = listId; - - var typeBadgeClass = item.type === 'text' ? 'badge-text' : item.type === 'image' ? 'badge-image' : 'badge-video'; - var typeBadgeLabel = item.type === 'text' ? '文字' : item.type === 'image' ? '图片' : '视频'; - var contentHtml; - if (item.type === 'text') { - contentHtml = ''; - } else if (item.type === 'image') { - contentHtml = ''; - } else { - contentHtml = ''; - } - div.innerHTML = - '' + - '' + typeBadgeLabel + '' + - contentHtml + - ''; - container.appendChild(div); - - div.addEventListener('dragstart', onDragStart); - div.addEventListener('dragover', onDragOver); - div.addEventListener('dragenter', onDragEnter); - div.addEventListener('dragleave', onDragLeave); - div.addEventListener('drop', onDrop); - div.addEventListener('dragend', onDragEnd); - - var contentEl = div.querySelector('.item-content'); - contentEl.addEventListener('input', function() { - items[idx].content = contentEl.value; - updatePreview(); - }); - - div.querySelector('.remove-item-btn').addEventListener('click', function() { - items.splice(idx, 1); - renderSortableList(listId, items); - updatePreview(); - }); - }); - } - - function onDragStart(e) { - var item = e.target.closest('.sortable-item'); - if (!item) return; - dragState.listId = item.dataset.listId; - dragState.fromIdx = parseInt(item.dataset.idx); - item.classList.add('dragging'); - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/plain', ''); - } - - function onDragOver(e) { - e.preventDefault(); - e.dataTransfer.dropEffect = 'move'; - } - - function onDragEnter(e) { - var item = e.target.closest('.sortable-item'); - if (item && item.dataset.listId === dragState.listId) { - item.classList.add('drag-over'); - } - } - - function onDragLeave(e) { - var item = e.target.closest('.sortable-item'); - if (item) { - item.classList.remove('drag-over'); - } - } - - function onDrop(e) { - e.preventDefault(); - var item = e.target.closest('.sortable-item'); - if (!item || item.dataset.listId !== dragState.listId) return; - var toIdx = parseInt(item.dataset.idx); - var fromIdx = dragState.fromIdx; - if (fromIdx === toIdx) return; - - var listId = dragState.listId; - var items = editorIntroduction; - - var moved = items.splice(fromIdx, 1)[0]; - items.splice(toIdx, 0, moved); - - renderSortableList(listId, items); - updatePreview(); - } - - function onDragEnd() { - document.querySelectorAll('.sortable-item').forEach(function(el) { - el.classList.remove('dragging', 'drag-over'); - }); - dragState = { listId: null, fromIdx: null }; - } - - // --- Add item buttons --- - editorModal.querySelectorAll('.add-item-btn').forEach(function(btn) { - btn.addEventListener('click', function() { - var type = btn.dataset.type; - var newItem = { type: type, content: '' }; - editorIntroduction.push(newItem); - renderSortableList('editor-introduction-list', editorIntroduction); - updatePreview(); - }); - }); - - // --- Live Preview --- - ['editor-town-title', 'editor-town-logo', 'editor-town-scale', 'editor-town-type', - 'editor-town-recruit', 'editor-town-dimension', 'editor-town-x', 'editor-town-y', 'editor-town-z'].forEach(function(id) { - var el = document.getElementById(id); - el.addEventListener('input', updatePreview); - el.addEventListener('change', updatePreview); - }); - - ['editor-town-gradient-from', 'editor-town-gradient-to'].forEach(function(id) { - var el = document.getElementById(id); - el.addEventListener('input', updatePreview); - el.addEventListener('change', updatePreview); - }); - - // Secret coordinates toggle - function toggleCoordsRow() { - var secret = document.getElementById('editor-town-secret').checked; - var dimensionGroup = document.getElementById('editor-dimension-group'); - var coordsRow = document.getElementById('editor-coords-row'); - dimensionGroup.style.display = secret ? 'none' : ''; - coordsRow.style.display = secret ? 'none' : ''; - } - - document.getElementById('editor-town-secret').addEventListener('change', function() { - toggleCoordsRow(); - updatePreview(); - }); - - function updatePreview() { - var preview = document.getElementById('town-editor-preview-area'); - var title = document.getElementById('editor-town-title').value || '未命名城镇'; - var logo = document.getElementById('editor-town-logo').value.trim(); - var scale = document.getElementById('editor-town-scale').value; - var townType = document.getElementById('editor-town-type').value; - var recruit = document.getElementById('editor-town-recruit').value; - var x = document.getElementById('editor-town-x').value || '0'; - var y = document.getElementById('editor-town-y').value || '64'; - var z = document.getElementById('editor-town-z').value || '0'; - var dimension = document.getElementById('editor-town-dimension').value || 'overworld'; - var isSecret = document.getElementById('editor-town-secret').checked; - var gradient = { - from: normalizeHexColor(document.getElementById('editor-town-gradient-from').value, DEFAULT_GRADIENT.from), - to: normalizeHexColor(document.getElementById('editor-town-gradient-to').value, DEFAULT_GRADIENT.to) - }; - var hasLogo = logo !== ''; - - var html = '
      '; - - html += '
      '; - html += ''; - html += '
      '; - html += '

      ' + escapeHtml(title) + '

      '; - html += '
      '; - html += ' ' + getScaleText(scale) + ''; - html += ' ' + getTownTypeText(townType) + ''; - html += ' ' + getRecruitText(recruit) + ''; - html += '
      '; - html += '
      '; - html += '
      '; - - html += '
      '; - html += '

      ' + (isSecret ? '位置信息(保密)' : '位置信息') + '

      '; - if (isSecret) { - html += '

      坐标保密

      '; - } else { - html += '

      ' + escapeHtml(getDimensionText(dimension)) + ': X: ' + escapeHtml(x) + ', Y: ' + escapeHtml(y) + ', Z: ' + escapeHtml(z) + '

      '; - } - html += '
      '; - - html += '
      '; - html += '

      创始人

      '; - if (editorFounders.length > 0) { - html += '
      '; - editorFounders.forEach(function(name) { - html += '
      ' + escapeHtml(name) + '' + escapeHtml(name) + '
      '; - }); - html += '
      '; - } else { - html += '暂无记录'; - } - html += '
      '; - - html += '
      '; - html += '

      主要成员

      '; - if (editorMembers.length > 0) { - html += '
      '; - editorMembers.forEach(function(name) { - html += '
      ' + escapeHtml(name) + '' + escapeHtml(name) + '
      '; - }); - html += '
      '; - } else { - html += '暂无记录'; - } - html += '
      '; - - html += '
      '; - html += '

      城镇介绍

      '; - html += '
      '; - if (editorIntroduction.length > 0) { - editorIntroduction.forEach(function(block) { - if (block.type === 'text') { - html += '

      ' + (escapeHtml(block.content) || '空文字') + '

      '; - } else if (block.type === 'image') { - html += block.content ? '' : '

      空图片

      '; - } else if (block.type === 'video') { - html += renderVideoPreviewHtml(block.content); - } - }); - } else { - html += '

      '; - } - html += '
      '; - - html += '
      '; - html += '
      '; - preview.innerHTML = html; - } - - // --- Save / Generate JSON --- - document.getElementById('btn-save-town').addEventListener('click', function() { - var title = document.getElementById('editor-town-title').value.trim(); - if (!title) { - alert('请填写城镇名称'); - document.getElementById('editor-town-title').focus(); - return; - } - - var isSecret = document.getElementById('editor-town-secret').checked; - var townObj = { - title: title, - logo: document.getElementById('editor-town-logo').value.trim(), - gradient: { - from: normalizeHexColor(document.getElementById('editor-town-gradient-from').value, DEFAULT_GRADIENT.from), - to: normalizeHexColor(document.getElementById('editor-town-gradient-to').value, DEFAULT_GRADIENT.to) - }, - dimension: document.getElementById('editor-town-dimension').value || 'overworld', - coordinatesSecret: isSecret, - scale: document.getElementById('editor-town-scale').value, - townType: document.getElementById('editor-town-type').value, - recruitment: document.getElementById('editor-town-recruit').value, - founders: editorFounders.slice(), - members: editorMembers.slice(), - introduction: editorIntroduction.filter(function(i) { return i.content.trim() !== ''; }).map(function(i) { - return i.type === 'video' ? { type: 'video', content: parseBVNumber(i.content) || i.content } : { type: i.type, content: i.content }; - }) - }; - - if (!isSecret) { - townObj.coordinates = { - x: parseInt(document.getElementById('editor-town-x').value) || 0, - y: parseInt(document.getElementById('editor-town-y').value) || 64, - z: parseInt(document.getElementById('editor-town-z').value) || 0 - }; - } - - var jsonStr = JSON.stringify(townObj, null, 4); - document.getElementById('town-json-output').value = jsonStr; - jsonOutputModal.style.display = 'block'; - }); - - // --- Copy JSON --- - document.getElementById('btn-copy-town-json').addEventListener('click', function() { - var textArea = document.getElementById('town-json-output'); - textArea.select(); - textArea.setSelectionRange(0, 99999); - - navigator.clipboard.writeText(textArea.value).then(function() { - var btn = document.getElementById('btn-copy-town-json'); - var originalHTML = btn.innerHTML; - btn.innerHTML = ' 已复制!'; - btn.style.background = '#34c759'; - setTimeout(function() { - btn.innerHTML = originalHTML; - btn.style.background = ''; - }, 2000); - }).catch(function() { - document.execCommand('copy'); - alert('已复制到剪贴板'); - }); - }); - - function renderVideoPreviewHtml(content) { - var bv = parseBVNumber(content); - if (bv) { - return '
      '; - } - return '

      请输入有效的 BV 号或 bilibili 视频地址

      '; - } - - // --- Utility --- - function escapeHtml(text) { - if (!text) return ''; - var div = document.createElement('div'); - div.appendChild(document.createTextNode(text)); - return div.innerHTML; - } -}); diff --git a/map.html b/map.html deleted file mode 100644 index aba213b..0000000 --- a/map.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - 在线地图 - 白鹿原 Minecraft 服务器 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      - -
      - - - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..da7483b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1207 @@ +{ + "name": "bailuyuan-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bailuyuan-web", + "version": "0.1.0", + "dependencies": { + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^5.4.14" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz", + "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.30", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz", + "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz", + "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.30", + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz", + "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz", + "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz", + "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz", + "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/runtime-core": "3.5.30", + "@vue/shared": "3.5.30", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz", + "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "vue": "3.5.30" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz", + "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz", + "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-sfc": "3.5.30", + "@vue/runtime-dom": "3.5.30", + "@vue/server-renderer": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9e7da60 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "bailuyuan-web", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "update:stats": "python scripts/statsprocess.py" + }, + "dependencies": { + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^5.4.14" + } +} \ No newline at end of file diff --git a/photo.html b/photo.html deleted file mode 100644 index ad963f4..0000000 --- a/photo.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - 服务器相册 - 白鹿原 Minecraft 服务器 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      - -
      - - - diff --git a/public/CNAME b/public/CNAME new file mode 100644 index 0000000..68e8bda --- /dev/null +++ b/public/CNAME @@ -0,0 +1 @@ +bailuyuan.lunadeer.cn \ No newline at end of file diff --git a/data/announcements.json b/public/data/announcements.json similarity index 100% rename from data/announcements.json rename to public/data/announcements.json diff --git a/data/convention.md b/public/data/convention.md similarity index 100% rename from data/convention.md rename to public/data/convention.md diff --git a/data/facilities.json b/public/data/facilities.json similarity index 100% rename from data/facilities.json rename to public/data/facilities.json diff --git a/data/fund_progress.txt b/public/data/fund_progress.txt similarity index 100% rename from data/fund_progress.txt rename to public/data/fund_progress.txt diff --git a/data/sponsors.txt b/public/data/sponsors.txt similarity index 100% rename from data/sponsors.txt rename to public/data/sponsors.txt diff --git a/data/towns.json b/public/data/towns.json similarity index 100% rename from data/towns.json rename to public/data/towns.json diff --git a/favicon.ico b/public/favicon.ico similarity index 100% rename from favicon.ico rename to public/favicon.ico diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..34d374c --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / +Disallow: /stats/*.json + +Sitemap: https://bailuyuan.lunadeer.cn/sitemap.xml \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..d41b630 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,9 @@ + + + + https://bailuyuan.lunadeer.cn/ + 2026-03-18 + weekly + 1.0 + + \ No newline at end of file diff --git a/robots.txt b/robots.txt deleted file mode 100644 index 7d577c8..0000000 --- a/robots.txt +++ /dev/null @@ -1,27 +0,0 @@ -# robots.txt for 白鹿原 Minecraft 服务器 - -User-agent: * -Allow: / -Disallow: /stats/*.json - -# Sitemap -Sitemap: https://bailuyuan.lunadeer.cn/sitemap.xml - -# 主要搜索引擎爬虫 -User-agent: Googlebot -Allow: / - -User-agent: Baiduspider -Allow: / - -User-agent: Bingbot -Allow: / - -User-agent: Sogou web spider -Allow: / - -User-agent: 360Spider -Allow: / - -# 爬取频率建议(仅供参考) -Crawl-delay: 1 diff --git a/scripts/statsprocess.py b/scripts/statsprocess.py new file mode 100644 index 0000000..30fe563 --- /dev/null +++ b/scripts/statsprocess.py @@ -0,0 +1,234 @@ +import json +import os +import re +import threading +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime +from pathlib import Path + +import requests +from requests.adapters import HTTPAdapter +from tqdm import tqdm +from urllib3.util.retry import Retry + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +STATS_DIR = PROJECT_ROOT / "public" / "stats" +MAX_WORKERS = max(4, min(16, int(os.environ.get("STATS_MAX_WORKERS", (os.cpu_count() or 4) * 2)))) + +BASE_URL = os.environ.get("STATS_BASE_URL", "").rstrip("/") +if BASE_URL: + BASE_URL += "/" + +STATS_USER = os.environ.get("STATS_USER", "") +STATS_PASS = os.environ.get("STATS_PASS", "") +BASE_AUTH = (STATS_USER, STATS_PASS) if STATS_USER else None + +retry_strategy = Retry( + total=3, + backoff_factor=1, + status_forcelist=[429, 500, 502, 503, 504], +) +thread_local = threading.local() + + +def create_session(): + session = requests.Session() + session.trust_env = False + adapter = HTTPAdapter( + max_retries=retry_strategy, + pool_connections=MAX_WORKERS, + pool_maxsize=MAX_WORKERS, + ) + session.mount("http://", adapter) + session.mount("https://", adapter) + return session + + +def get_session(): + session = getattr(thread_local, "session", None) + if session is None: + session = create_session() + thread_local.session = session + return session + + +def load_name_cache(): + summary_path = STATS_DIR / "summary.json" + if not summary_path.exists(): + return {} + + try: + with summary_path.open("r", encoding="utf-8") as file_handle: + summary = json.load(file_handle) + except Exception: + return {} + + return { + player.get("uuid"): player.get("name") + for player in summary.get("players", []) + if player.get("uuid") and player.get("name") and player.get("name") != "Unknown" + } + + +def get_player_name(uuid): + try: + response = get_session().get(f"https://api.ashcon.app/mojang/v2/user/{uuid}", timeout=5) + if response.status_code == 200: + return response.json().get("username") + except Exception: + pass + + try: + response = get_session().get( + f"https://sessionserver.mojang.com/session/minecraft/profile/{uuid}", + timeout=5, + ) + if response.status_code == 200: + return response.json().get("name") + except Exception: + pass + + return "Unknown" + + +def format_dist(cm): + meters = cm / 100 + if meters < 1000: + return f"{meters:.1f} m" + return f"{meters / 1000:.2f} km" + + +def format_time(ticks): + seconds = ticks / 20 + if seconds < 60: + return f"{seconds:.3f} 秒" + + minutes = seconds / 60 + if minutes < 60: + return f"{minutes:.3f} 分钟" + + hours = minutes / 60 + if hours < 24: + return f"{hours:.3f} 小时" + + return f"{hours / 24:.3f} 天" + + +def process_player(filename, name_cache): + uuid = filename.replace(".json", "") + json_path = STATS_DIR / filename + + try: + response = get_session().get(f"{BASE_URL}{filename}", timeout=10, auth=BASE_AUTH) + response.raise_for_status() + data = response.json() + except Exception as exc: + print(f"Error downloading {filename}: {exc}") + return None + + player_name = name_cache.get(uuid, "Unknown") + if player_name == "Unknown": + player_name = get_player_name(uuid) + + stats = data.get("stats", {}) + custom = stats.get("minecraft:custom", {}) + walk_cm = custom.get("minecraft:walk_one_cm", 0) + play_time_ticks = custom.get("minecraft:play_time", 0) + total_mined = sum(stats.get("minecraft:mined", {}).values()) + total_placed = sum(stats.get("minecraft:used", {}).values()) + total_deaths = sum(stats.get("minecraft:killed_by", {}).values()) + total_kills = sum(stats.get("minecraft:killed", {}).values()) + + data["extra"] = { + "player_name": player_name, + "formatted_walk": format_dist(walk_cm), + "walk_cm": walk_cm, + "total_mined": total_mined, + "total_placed": total_placed, + "total_deaths": total_deaths, + "total_kills": total_kills, + "play_time_fmt": format_time(play_time_ticks), + "play_time_ticks": play_time_ticks, + } + + with json_path.open("w", encoding="utf-8") as file_handle: + json.dump(data, file_handle, ensure_ascii=False, indent=4) + + return { + "uuid": uuid, + "name": player_name, + "avatar": f"https://minotar.net/avatar/{player_name}/64" + if player_name != "Unknown" + else f"https://minotar.net/avatar/{uuid}/64", + "stats": { + "walk_fmt": format_dist(walk_cm), + "walk_raw": walk_cm, + "mined": total_mined, + "placed": total_placed, + "deaths": total_deaths, + "kills": total_kills, + "play_time_fmt": format_time(play_time_ticks), + "play_time_raw": play_time_ticks, + }, + } + + +def main(): + STATS_DIR.mkdir(parents=True, exist_ok=True) + + if BASE_AUTH: + print(f"Using authentication for BASE_URL (user: {STATS_USER})") + else: + print("No STATS_USER/STATS_PASS set, accessing BASE_URL without auth.") + + if not BASE_URL: + raise SystemExit("STATS_BASE_URL is not set.") + + print("Fetching file list...") + fetch_failed = False + files = [] + + try: + response = get_session().get(BASE_URL, timeout=10, auth=BASE_AUTH) + response.raise_for_status() + files = sorted(set(re.findall(r'href="([0-9a-f-]{36}\.json)"', response.text))) + print(f"Found {len(files)} player stats files.") + except Exception as exc: + print(f"Error fetching file list: {exc}") + fetch_failed = True + + if fetch_failed: + raise SystemExit(1) + + name_cache = load_name_cache() + results = [] + + with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: + future_map = { + executor.submit(process_player, filename, name_cache): filename + for filename in files + } + for future in tqdm(as_completed(future_map), total=len(future_map), desc="Processing players"): + try: + result = future.result() + except Exception as exc: + print(f"Worker failed for {future_map[future]}: {exc}") + continue + + if result is not None: + results.append(result) + + results.sort(key=lambda item: item["name"]) + summary = { + "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "players": results, + } + + with (STATS_DIR / "summary.json").open("w", encoding="utf-8") as file_handle: + json.dump(summary, file_handle, ensure_ascii=False, indent=4) + + print(f"Processing complete. Summary saved to {STATS_DIR / 'summary.json'}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml deleted file mode 100644 index 309325f..0000000 --- a/sitemap.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - https://mcpure.lunadeer.cn/ - 2026-02-11 - weekly - 1.0 - - - - - https://mcpure.lunadeer.cn/stats.html - 2026-02-11 - daily - 0.8 - - - - - https://mcpure.lunadeer.cn/sponsor.html - 2026-03-05 - weekly - 0.7 - - - - - https://mcpure.lunadeer.cn/join.html - 2026-03-05 - monthly - 0.9 - - - - - https://mcpure.lunadeer.cn/facilities.html - 2026-03-05 - weekly - 0.7 - - - - - https://mcpure.lunadeer.cn/doc.html - 2026-03-05 - monthly - 0.6 - - - - - https://mcpure.lunadeer.cn/map.html - 2026-03-05 - monthly - 0.5 - - - - - https://mcpure.lunadeer.cn/photo.html - 2026-03-05 - weekly - 0.5 - - - - - https://mcpure.lunadeer.cn/announcements.html - 2026-03-10 - weekly - 0.7 - - - diff --git a/sponsor.html b/sponsor.html deleted file mode 100644 index 265cd2e..0000000 --- a/sponsor.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - 赞助榜 - 白鹿原 Minecraft 服务器 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      - - - - - - - diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..c51c252 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..90f82b2 --- /dev/null +++ b/src/main.js @@ -0,0 +1,5 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import './styles.css'; + +createApp(App).mount('#app'); \ No newline at end of file diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..ca261c2 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,114 @@ +:root { + color-scheme: light; + font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; + line-height: 1.5; + font-weight: 400; + color: #172033; + background: #f4f7fb; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; + background: + radial-gradient(circle at top left, rgba(81, 146, 255, 0.18), transparent 32%), + radial-gradient(circle at bottom right, rgba(48, 196, 141, 0.16), transparent 28%), + #f4f7fb; +} + +button, +input, +textarea, +select { + font: inherit; +} + +#app { + min-height: 100vh; +} + +.app-shell { + width: min(1100px, calc(100% - 32px)); + margin: 0 auto; + padding: 56px 0 72px; +} + +.hero-card, +.status-grid article { + border: 1px solid rgba(23, 32, 51, 0.08); + border-radius: 24px; + background: rgba(255, 255, 255, 0.88); + box-shadow: 0 20px 60px rgba(40, 62, 98, 0.08); + backdrop-filter: blur(16px); +} + +.hero-card { + padding: 36px; +} + +.eyebrow { + margin: 0 0 12px; + font-size: 0.85rem; + font-weight: 700; + letter-spacing: 0.16em; + text-transform: uppercase; + color: #2d6cdf; +} + +.hero-card h1 { + margin: 0; + font-size: clamp(2rem, 5vw, 3.4rem); + line-height: 1.05; +} + +.intro { + max-width: 680px; + margin: 18px 0 0; + font-size: 1.05rem; + color: #49556b; +} + +.status-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; + margin-top: 24px; +} + +.status-grid article { + padding: 24px; +} + +.status-grid h2 { + margin: 0 0 10px; + font-size: 1.1rem; +} + +.status-grid p { + margin: 0; + color: #5b6780; +} + +@media (max-width: 840px) { + .app-shell { + width: min(100% - 24px, 1100px); + padding-top: 28px; + } + + .hero-card { + padding: 24px; + } + + .status-grid { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/stats.html b/stats.html deleted file mode 100644 index 8fcfc0e..0000000 --- a/stats.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - 玩家数据统计 - 白鹿原 Minecraft 服务器 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      - - -
      -
      - - -

      排行榜

      -

      -
      - -
      -
      -
      -
      旅行者
      -
      -
      -
      加载中...
      -
      -
      - - -
      -
      -
      -
      搬石大师
      -
      -
      -
      加载中...
      -
      -
      - - -
      -
      -
      -
      挖挖机
      -
      -
      -
      加载中...
      -
      -
      - - -
      -
      -
      -
      亡灵
      -
      -
      -
      加载中...
      -
      -
      - - -
      -
      -
      -
      尊者
      -
      -
      -
      加载中...
      -
      -
      - - -
      -
      -
      -
      屠夫
      -
      -
      -
      加载中...
      -
      -
      -
      - - -

      玩家档案

      - -
      - - -
      - -
      - -
      - -
      - -
      - -
      正在从服务器获取数据...
      -
      -
      - - - - - - - - - - diff --git a/statsprocess.py b/statsprocess.py deleted file mode 100644 index 2ee1a27..0000000 --- a/statsprocess.py +++ /dev/null @@ -1,261 +0,0 @@ -import os -import json -import threading -import requests -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry -import re -from concurrent.futures import ThreadPoolExecutor, as_completed -from datetime import datetime -from tqdm import tqdm # Add tqdm for progress bars - -STATS_DIR = "stats" -MAX_WORKERS = max(4, min(16, int(os.environ.get("STATS_MAX_WORKERS", (os.cpu_count() or 4) * 2)))) - -# HTTP Basic Auth for BASE_URL (from environment variables) -BASE_URL = os.environ.get("STATS_BASE_URL", "") -STATS_USER = os.environ.get("STATS_USER", "") -STATS_PASS = os.environ.get("STATS_PASS", "") -BASE_AUTH = (STATS_USER, STATS_PASS) if STATS_USER else None - -retry_strategy = Retry( - total=3, - backoff_factor=1, - status_forcelist=[429, 500, 502, 503, 504], -) -thread_local = threading.local() - - -def create_session(): - session = requests.Session() - session.trust_env = False # Ignore HTTP_PROXY / HTTPS_PROXY env vars - adapter = HTTPAdapter( - max_retries=retry_strategy, - pool_connections=MAX_WORKERS, - pool_maxsize=MAX_WORKERS, - ) - session.mount("http://", adapter) - session.mount("https://", adapter) - return session - - -def get_session(): - session = getattr(thread_local, "session", None) - if session is None: - session = create_session() - thread_local.session = session - return session - -if BASE_AUTH: - print(f"Using authentication for BASE_URL (user: {STATS_USER})") -else: - print("No STATS_USER/STATS_PASS set, accessing BASE_URL without auth.") - -# Ensure directories exist -os.makedirs(STATS_DIR, exist_ok=True) - -print("Fetching file list...") -fetch_failed = False -try: - response = get_session().get(BASE_URL, timeout=10, auth=BASE_AUTH) - response.raise_for_status() - content = response.text - # Regex for UUID.json - files = sorted(set(re.findall(r'href="([0-9a-f-]{36}\.json)"', content))) - print(f"Found {len(files)} player stats files.") -except Exception as e: - print(f"Error fetching file list: {e}") - files = [] - fetch_failed = True - - -def load_name_cache(): - summary_path = os.path.join(STATS_DIR, 'summary.json') - if not os.path.exists(summary_path): - return {} - - try: - with open(summary_path, 'r', encoding='utf-8') as f: - summary = json.load(f) - except Exception: - return {} - - return { - player.get('uuid'): player.get('name') - for player in summary.get('players', []) - if player.get('uuid') and player.get('name') and player.get('name') != "Unknown" - } - - -def get_player_name(uuid): - # Try Ashcon first - try: - r = get_session().get(f"https://api.ashcon.app/mojang/v2/user/{uuid}", timeout=5) - if r.status_code == 200: - return r.json().get('username') - except Exception: - pass - - # Try Mojang Session - try: - r = get_session().get(f"https://sessionserver.mojang.com/session/minecraft/profile/{uuid}", timeout=5) - if r.status_code == 200: - return r.json().get('name') - except Exception: - pass - - return "Unknown" - - -def format_dist(cm): - m = cm / 100 - if m < 1000: - return f"{m:.1f} m" - return f"{m / 1000:.2f} km" - - -def format_time(ticks): - seconds = ticks / 20 - if seconds < 60: - return f"{seconds:.3f} 秒" - minutes = seconds / 60 - if minutes < 60: - return f"{minutes:.3f} 分钟" - hours = minutes / 60 - if hours < 24: - return f"{hours:.3f} 小时" - days = hours / 24 - return f"{days:.3f} 天" - - -def process_player(filename, name_cache): - uuid = filename.replace(".json", "") - json_path = os.path.join(STATS_DIR, filename) - - # 1. Download/Load JSON - data = None - try: - r = get_session().get(BASE_URL + filename, timeout=10, auth=BASE_AUTH) - if r.status_code == 200: - data = r.json() - else: - print(f"Failed to download {filename}") - return None - except Exception as e: - print(f"Error downloading {filename}: {e}") - return None - - if not data: - return None - - # 2. Get Name - player_name = name_cache.get(uuid, "Unknown") - - if player_name == "Unknown": - player_name = get_player_name(uuid) - - # 3. Download Avatar - SKIPPED to avoid rate limits - # The frontend will handle dynamic loading of avatars using Minotar/Crafatar URLs. - - # 4. Calculate Stats - stats = data.get('stats', {}) - - # Walk - # Handle both modern ':' and potentially flattened or different versions if necessary, - # but usually proper JSON has "minecraft:custom" - # "minecraft:walk_one_cm" - - custom = stats.get('minecraft:custom', {}) - walk_cm = custom.get('minecraft:walk_one_cm', 0) - - walk_fmt = format_dist(walk_cm) - - # Play Time (1 tick = 1/20 second) - play_time_ticks = custom.get('minecraft:play_time', 0) - - play_time_fmt = format_time(play_time_ticks) - - # Mined - mined = stats.get('minecraft:mined', {}) - total_mined = sum(mined.values()) - - # Placed (Used) - used = stats.get('minecraft:used', {}) - total_placed = sum(used.values()) - - # Deaths (Killed By) - killed_by = stats.get('minecraft:killed_by', {}) - total_deaths = sum(killed_by.values()) - - # Kills (Killed) - killed = stats.get('minecraft:killed', {}) - total_kills = sum(killed.values()) - - # Inject into JSON - data['extra'] = { - 'player_name': player_name, - 'formatted_walk': walk_fmt, - 'walk_cm': walk_cm, - 'total_mined': total_mined, - 'total_placed': total_placed, - 'total_deaths': total_deaths, - 'total_kills': total_kills, - 'play_time_fmt': play_time_fmt, - 'play_time_ticks': play_time_ticks - } - - # Save - with open(json_path, 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False, indent=4) - - return { - 'uuid': uuid, - 'name': player_name, - 'avatar': f"https://minotar.net/avatar/{player_name}/64" if player_name != "Unknown" else f"https://minotar.net/avatar/{uuid}/64", - 'stats': { - 'walk_fmt': walk_fmt, - 'walk_raw': walk_cm, - 'mined': total_mined, - 'placed': total_placed, - 'deaths': total_deaths, - 'kills': total_kills, - 'play_time_fmt': play_time_fmt, - 'play_time_raw': play_time_ticks - } - } - - -name_cache = load_name_cache() - -results = [] -if files: - with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: - future_map = { - executor.submit(process_player, filename, name_cache): filename - for filename in files - } - for future in tqdm(as_completed(future_map), total=len(future_map), desc="Processing players"): - try: - result = future.result() - except Exception as e: - print(f"Worker failed for {future_map[future]}: {e}") - continue - if result is not None: - results.append(result) - -if fetch_failed: - print("Skipping summary update because file list fetch failed.") - raise SystemExit(1) - -# Sort by name perhaps? Or just raw list. -results.sort(key=lambda x: x['name']) - -summary = { - 'updated_at': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - 'players': results -} - -with open(os.path.join(STATS_DIR, 'summary.json'), 'w', encoding='utf-8') as f: - json.dump(summary, f, ensure_ascii=False, indent=4) - -print("Processing complete. Summary saved to stats/summary.json") diff --git a/towns.html b/towns.html deleted file mode 100644 index 666f3f8..0000000 --- a/towns.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - 城镇介绍 - 白鹿原 Minecraft 服务器 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      - -
      - - -
      -
      -
      -

      城镇列表

      - -
      - -
      - -
      -
      -
      规模
      -
      - - - - -
      -
      - -
      -
      类型
      -
      - - - - -
      -
      - -
      -
      招募
      -
      - - - - -
      -
      -
      -
      - - -
      - -
      - - -
      - - - - - - - - - - - - - - - diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..7fe948f --- /dev/null +++ b/vite.config.js @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; + +export default defineConfig({ + plugins: [vue()], +}); \ No newline at end of file