Add project source with .gitignore, remove node_modules and dist from tracking
This commit is contained in:
+38
@@ -0,0 +1,38 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
dist-ssr/
|
||||
|
||||
# Vite cache
|
||||
.vite/
|
||||
|
||||
# TypeScript build info
|
||||
*.tsbuildinfo
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Editor / IDE
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea/
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
@@ -0,0 +1,87 @@
|
||||
# 🌍 MCLIVE — 在方块世界中,重建真实文明
|
||||
|
||||
> *「这不只是一个服务器,而是一个持续进化的平行世界。」*
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ 硬核基建 · 极致流畅体验
|
||||
|
||||
| 项目 | 详情 |
|
||||
|:---:|:---|
|
||||
| 📍 **数据中心** | 华东苏州 T3+ 级机房,电信骨干网接入 |
|
||||
| 🌐 **网络质量** | 三网直连优化,全国低延迟畅玩 |
|
||||
| ⚡ **核心性能** | 搭载 **Intel i9-13900HK**,单核睿频高达 **5.4GHz** |
|
||||
| 🛡️ **运维模式** | 自托管独立服务器,拒绝共享卡顿 |
|
||||
|
||||
*无论是海量实体运算还是复杂红石电路,都能保持丝滑帧率。*
|
||||
|
||||
---
|
||||
|
||||
## 🎮 核心玩法 · 你的第二人生
|
||||
|
||||
### 🗺️ 超大世界地图 — 等比例还原真实地球
|
||||
> **73,728 × 36,864** 格的广袤世界
|
||||
|
||||
按真实比例还原地球生态地貌,从雪原到沙漠、从平原到山脉,在方块世界中探索我们赖以生存的星球。
|
||||
|
||||
### 🏰 自建城镇系统 — 从拓荒者到一城之主
|
||||
基于 **HuskTown** 深度定制的城镇玩法:
|
||||
- 🧱 **一步一步建设**:从荒野小屋到繁华都市,亲手规划每一寸土地
|
||||
- 🤝 **外交与联盟**:与其他城镇缔结盟约、开展贸易
|
||||
- ⚔️ **战争与征服**:当外交破裂,用剑与盾捍卫你的领土
|
||||
|
||||
### 🌐 网页联动平台 — 游戏内外,无缝衔接
|
||||
**完全自研**的前端联动系统,打破游戏与浏览器的边界:
|
||||
- 📊 **实时排行榜**:领地榜、经济榜,让你的成就被所有人看见
|
||||
- 🗺️ **在线卫星地图**:实时渲染游戏内建筑与地貌,随时随地浏览世界
|
||||
- 🚩 **个性化定制**:上传专属旗帜、自定义城镇名称
|
||||
- 📌 **身份标注**:在网页地图上标明你的领地与身份,宣告主权
|
||||
|
||||
---
|
||||
|
||||
## ✨ 特色系统 · 永不无聊的世界
|
||||
|
||||
### 🎲 随机事件引擎 [🚧 开发中]
|
||||
这个世界拥有自己的"意志"——
|
||||
- 🐉 **世界BOSS降临**:史诗级怪物入侵,全服玩家协力讨伐
|
||||
- ☄️ **天降陨石灾害**:随机灾难事件,考验城镇的应急能力
|
||||
- 🎁 **更多惊喜**:每一次登录都可能遇到意想不到的奇遇
|
||||
|
||||
### 🎨 定制材质包 [🚧 开发中]
|
||||
使用 **BlockBench** 精心打造的专属模型:
|
||||
- 以材质包形式呈现,**无需安装任何额外模组**
|
||||
- 原版客户端即可体验精美的自定义物品与方块
|
||||
- 让建筑与装备拥有独一无二的视觉风格
|
||||
|
||||
---
|
||||
|
||||
## 🚀 无限进化 · AI 驱动的未来
|
||||
|
||||
> *「我们的服务器不只是在运行,它在成长。」*
|
||||
|
||||
依托前沿 AI 技术,MCLIVE 拥有持续进化的能力:
|
||||
|
||||
| 工具链 | 用途 |
|
||||
|:---|:---|
|
||||
| 🤖 **AI Agent 工具** | OpenCode、MCP、Skills 等智能体开发框架 |
|
||||
| 🧠 **大语言模型** | GPT、GLM、DeepSeek 等多模型协同 |
|
||||
|
||||
**这意味着什么?**
|
||||
- 💡 你的每一个脑洞、每一条吐槽,都可能被 AI 快速转化为实际功能
|
||||
- 🔄 服务器以远超传统团队的速度迭代更新
|
||||
- 🌱 这不是一成不变的世界,而是一个生生不息、持续进化的游乐场
|
||||
|
||||
**欢迎贡献你的想法——下一个改变世界的功能,可能就来自你的一句话。**
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
### 🎉 加入 MCLIVE,书写你的故事
|
||||
|
||||
*「在这里,每一个方块都是你留下的印记。」*
|
||||
|
||||
**🔗 服务器地址:** `play.mclive.xxx`
|
||||
**💬 玩家交流群:** `XXXXXXXX`
|
||||
|
||||
</div>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#071a24" />
|
||||
<meta name="description" content="MCLIVE Minecraft 服务器:在方块世界中重建真实文明。" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<title>MCLIVE | 重建真实文明</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2607
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "mclive-website",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-three/drei": "^10.7.6",
|
||||
"@react-three/fiber": "^9.4.0",
|
||||
"lucide-react": "^0.468.0",
|
||||
"motion": "^12.23.26",
|
||||
"react": "^19.2.1",
|
||||
"react-dom": "^19.2.1",
|
||||
"three": "^0.182.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/three": "^0.182.0",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.2.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<rect width="64" height="64" fill="#071a24"/>
|
||||
<path d="M32 6 55 19v26L32 58 9 45V19Z" fill="none" stroke="#72aa86" stroke-width="4"/>
|
||||
<path d="m32 18 12 7v14l-12 7-12-7V25Z" fill="#c6a15b"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 265 B |
+217
@@ -0,0 +1,217 @@
|
||||
import { ArrowDown, ArrowUpRight, Box, Cpu, Crosshair, Flag, HardDrive, Menu, Network, Radio, Satellite, Server, Shield, Sparkles, Swords, X } from 'lucide-react'
|
||||
import { lazy, Suspense, useEffect, useState } from 'react'
|
||||
import Reveal from './components/Reveal'
|
||||
import { civilizationSteps, events, infrastructure, platformFeatures } from './data/server'
|
||||
|
||||
const VoxelEarth = lazy(() => import('./components/VoxelEarth'))
|
||||
const infrastructureIcons = [Server, Network, Cpu, HardDrive]
|
||||
|
||||
function App() {
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const [activeStep, setActiveStep] = useState(0)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const update = () => setScrolled(window.scrollY > 20)
|
||||
window.addEventListener('scroll', update, { passive: true })
|
||||
return () => window.removeEventListener('scroll', update)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<main>
|
||||
<header className={`site-header ${scrolled ? 'is-scrolled' : ''}`}>
|
||||
<a className="brand" href="#top" aria-label="MCLIVE 首页">
|
||||
<span className="brand-mark"><span /></span>
|
||||
<span>MCLIVE</span>
|
||||
<small>文明实验服</small>
|
||||
</a>
|
||||
<nav className={menuOpen ? 'open' : ''} aria-label="主导航">
|
||||
<a href="#world" onClick={() => setMenuOpen(false)}>世界</a>
|
||||
<a href="#civilization" onClick={() => setMenuOpen(false)}>文明</a>
|
||||
<a href="#systems" onClick={() => setMenuOpen(false)}>系统</a>
|
||||
<a href="#join" onClick={() => setMenuOpen(false)}>加入计划</a>
|
||||
</nav>
|
||||
<button className="menu-button" onClick={() => setMenuOpen(!menuOpen)} aria-label={menuOpen ? '关闭菜单' : '打开菜单'}>
|
||||
{menuOpen ? <X /> : <Menu />}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<section className="hero" id="top">
|
||||
<div className="hero-grid" aria-hidden="true" />
|
||||
<div className="hero-copy">
|
||||
<p className="eyebrow"><span>WORLD INSTANCE 001</span> 地球文明观测站</p>
|
||||
<h1>在方块世界中,<br /><em>重建真实文明</em></h1>
|
||||
<p className="hero-lead">一颗等比例重构的方块地球。这里没有预设的故事,只有由玩家建立、结盟、冲突与延续的真实历史。</p>
|
||||
<div className="hero-actions">
|
||||
<a className="button primary" href="#world">进入世界档案 <ArrowDown size={17} /></a>
|
||||
<span className="status"><i /> 服务器筹备中</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hero-earth">
|
||||
<Suspense fallback={<div className="earth-loading">LOADING WORLD INSTANCE...</div>}>
|
||||
<VoxelEarth />
|
||||
</Suspense>
|
||||
</div>
|
||||
<div className="hero-scale">
|
||||
<span>EARTH RECONSTRUCTION</span>
|
||||
<strong>73,728 <i>×</i> 36,864</strong>
|
||||
<small>BLOCKS / 等比例地貌世界</small>
|
||||
</div>
|
||||
<div className="scroll-note"><span /> 向下滚动,读取世界档案</div>
|
||||
</section>
|
||||
|
||||
<section className="manifesto" id="world">
|
||||
<Reveal className="section-heading wide">
|
||||
<p className="section-label">FILE / 01 — THE WORLD</p>
|
||||
<h2>这不只是一张地图。<br />这是文明发生的<span>真实尺度。</span></h2>
|
||||
</Reveal>
|
||||
<div className="world-layout">
|
||||
<Reveal className="world-map">
|
||||
<div className="map-grid" />
|
||||
<div className="map-land land-one" /><div className="map-land land-two" /><div className="map-land land-three" />
|
||||
<div className="map-route"><i /><i /><i /></div>
|
||||
<div className="map-compass"><Crosshair /><span>N</span></div>
|
||||
<span className="map-label label-one">新大陆 · 未命名领地</span>
|
||||
<span className="map-label label-two">定居点 / 024</span>
|
||||
<div className="map-legend"><span><i className="owned" /> 玩家领地</span><span><i className="wild" /> 荒野</span></div>
|
||||
</Reveal>
|
||||
<Reveal className="world-copy">
|
||||
<p>从雪原到沙漠,从海岸到山脉,世界按真实地球生态地貌重构。距离重新拥有意义,每一次远征都需要计划。</p>
|
||||
<blockquote>“你看到的地平线,不是服务器边界,而是下一段历史的起点。”</blockquote>
|
||||
<dl>
|
||||
<div><dt>世界类型</dt><dd>等比例地球地貌</dd></div>
|
||||
<div><dt>城镇核心</dt><dd>HuskTown 深度定制</dd></div>
|
||||
<div><dt>客户端</dt><dd>原版即可进入</dd></div>
|
||||
</dl>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="civilization" id="civilization">
|
||||
<Reveal className="section-heading">
|
||||
<p className="section-label">FILE / 02 — CIVILIZATION</p>
|
||||
<h2>从第一个方块,<br />到一座城的意志。</h2>
|
||||
<p>文明不是菜单中的选项,而是每一位玩家共同留下的结果。</p>
|
||||
</Reveal>
|
||||
<div className="civilization-console">
|
||||
<div className="step-tabs" role="tablist" aria-label="文明发展阶段">
|
||||
{civilizationSteps.map((step, index) => (
|
||||
<button key={step.key} className={activeStep === index ? 'active' : ''} onClick={() => setActiveStep(index)} role="tab" aria-selected={activeStep === index}>
|
||||
<span>{step.key}</span><strong>{step.title}</strong><small>{step.tag}</small>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className={`territory stage-${activeStep}`}>
|
||||
<div className="territory-grid" />
|
||||
<div className="territory-zone zone-a" /><div className="territory-zone zone-b" />
|
||||
<div className="city city-a"><Box /><span>中心城</span></div>
|
||||
<div className="city city-b"><Flag /><span>北境哨站</span></div>
|
||||
<div className="city city-c"><Shield /><span>盟友领地</span></div>
|
||||
{activeStep === 3 && <div className="conflict"><Swords /><span>边境冲突</span></div>}
|
||||
</div>
|
||||
<div className="step-description" role="tabpanel">
|
||||
<span>{civilizationSteps[activeStep].tag} / PHASE {civilizationSteps[activeStep].key}</span>
|
||||
<h3>{civilizationSteps[activeStep].title}</h3>
|
||||
<p>{civilizationSteps[activeStep].text}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="platform" id="systems">
|
||||
<Reveal className="section-heading platform-title">
|
||||
<p className="section-label">FILE / 03 — WORLD INTERFACE</p>
|
||||
<h2>离开游戏,<br />世界仍在运转。</h2>
|
||||
<p>自研+二次开发的网页联动平台,让城镇、身份与地图在浏览器里持续在线。</p>
|
||||
</Reveal>
|
||||
<Reveal className="browser-frame">
|
||||
<div className="browser-bar"><i /><i /><i /><span>map.mclive / 世界观测终端</span><Radio size={14} /></div>
|
||||
<div className="browser-body">
|
||||
<aside>
|
||||
<div className="platform-logo"><Satellite /> MCLIVE MAP</div>
|
||||
{platformFeatures.map((feature, index) => <button className={index === 1 ? 'active' : ''} key={feature.label}><span>{feature.label}</span><small>{feature.value}</small></button>)}
|
||||
</aside>
|
||||
<div className="satellite-map">
|
||||
<div className="scan-line" />
|
||||
<div className="map-river" />
|
||||
<div className="claim claim-a"><span>曙光城</span></div>
|
||||
<div className="claim claim-b"><span>北海联盟</span></div>
|
||||
<div className="claim claim-c"><span>自由领</span></div>
|
||||
<div className="player-marker marker-one"><Flag /><span>LIN_042</span></div>
|
||||
<div className="player-marker marker-two"><Flag /><span>MARS_17</span></div>
|
||||
<span className="map-preview-tag">功能概念预览 / PREVIEW</span>
|
||||
</div>
|
||||
<div className="rank-panel">
|
||||
<span>城镇领地榜</span>
|
||||
{[['01', '曙光城', '18,420'], ['02', '北海联盟', '14,917'], ['03', '自由领', '9,284']].map(row => <div key={row[0]}><b>{row[0]}</b><strong>{row[1]}</strong><small>{row[2]} 格</small></div>)}
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<section className="event-section">
|
||||
<Reveal className="section-heading">
|
||||
<p className="section-label">FILE / 04 — ANOMALY ENGINE</p>
|
||||
<h2>世界拥有自己的意志。</h2>
|
||||
<p>随机事件打破恒定秩序。每一次登录,世界都可能发生新的变化。</p>
|
||||
</Reveal>
|
||||
<div className="event-list">
|
||||
{events.map((event, index) => (
|
||||
<Reveal className="event-row" key={event.code}>
|
||||
<span className="event-index">0{index + 1}</span>
|
||||
<div><small>{event.code}</small><h3>{event.title}</h3></div>
|
||||
<p>{event.text}</p><span className="dev-tag">{event.status}</span><ArrowUpRight />
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="infrastructure">
|
||||
<Reveal className="infra-intro">
|
||||
<p className="section-label">SYSTEM / INFRASTRUCTURE</p>
|
||||
<h2>复杂世界,<br />不该被卡顿打断。</h2>
|
||||
<p>从实体运算到红石机器,MCLIVE 以独立硬件和低延迟网络承载持续生长的文明。</p>
|
||||
</Reveal>
|
||||
<div className="infra-grid">
|
||||
{infrastructure.map((item, index) => {
|
||||
const Icon = infrastructureIcons[index]
|
||||
return <Reveal className="infra-card" key={item.value}><span>SYS.0{index + 1}</span><Icon /><strong>{item.value}</strong><h3>{item.label}</h3><p>{item.detail}</p></Reveal>
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="evolution">
|
||||
<div className="evolution-glow" />
|
||||
<Reveal className="evolution-copy">
|
||||
<p className="section-label">FILE / 05 — EVOLUTION</p>
|
||||
<h2>它不只是在运行,<br /><span>它在持续成长。</span></h2>
|
||||
<p>AI Agent 与多模型协作,将玩家的脑洞、反馈与吐槽更快转化为真实功能。这个世界的下一次更新,也许就来自你的一句话。</p>
|
||||
<div className="ai-tools"><span>OPENCODE</span><span>MCP</span><span>SKILLS</span><span>GPT</span><span>GLM</span><span>DEEPSEEK</span></div>
|
||||
</Reveal>
|
||||
<Reveal className="change-log">
|
||||
<div><span>WORLD CHANGELOG</span><small>持续同步</small></div>
|
||||
<ol>
|
||||
<li><time>NOW</time><p><strong>玩家提案进入分析队列</strong><small>AI Agent 正在整理需求与实现路径</small></p><i /></li>
|
||||
<li><time>NEXT</time><p><strong>随机事件引擎</strong><small>世界 BOSS 与动态灾害原型</small></p><i /></li>
|
||||
<li><time>SOON</time><p><strong>定制材质资产</strong><small>原版客户端兼容的专属视觉</small></p><i /></li>
|
||||
</ol>
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<section className="join" id="join">
|
||||
<div className="join-grid" />
|
||||
<Reveal>
|
||||
<Sparkles className="join-icon" />
|
||||
<p className="section-label">THE ARCHIVE IS WAITING</p>
|
||||
<h2>下一块历史,<br />由你放下。</h2>
|
||||
<p>服务器目前仍在筹备。地址与玩家交流群将在开放时公布。</p>
|
||||
<div className="join-status"><span><i /> SERVER ADDRESS</span><strong>待开放</strong><span><i /> COMMUNITY</span><strong>待开放</strong></div>
|
||||
<a className="button primary" href="#top">返回观测站 <ArrowUpRight size={17} /></a>
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
<footer><div className="brand"><span className="brand-mark"><span /></span><span>MCLIVE</span></div><p>在方块世界中,重建真实文明。</p><span>WORLD INSTANCE 001 · 2026</span></footer>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,17 @@
|
||||
import { motion, useReducedMotion } from 'motion/react'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
|
||||
export default function Reveal({ children, className = '' }: PropsWithChildren<{ className?: string }>) {
|
||||
const reduceMotion = useReducedMotion()
|
||||
return (
|
||||
<motion.div
|
||||
className={className}
|
||||
initial={reduceMotion ? false : { opacity: 0, y: 28 }}
|
||||
whileInView={reduceMotion ? undefined : { opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.18 }}
|
||||
transition={{ duration: 0.72, ease: [0.22, 1, 0.36, 1] }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { Canvas, useFrame } from '@react-three/fiber'
|
||||
import { OrbitControls, Stars } from '@react-three/drei'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import * as THREE from 'three'
|
||||
|
||||
const LAND = new THREE.Color('#4f8f78')
|
||||
const LAND_LIGHT = new THREE.Color('#78ad83')
|
||||
const OCEAN = new THREE.Color('#163f51')
|
||||
const ICE = new THREE.Color('#d7f1e8')
|
||||
|
||||
function terrainValue(lat: number, lon: number) {
|
||||
const waves =
|
||||
Math.sin(lon * 1.7 + Math.cos(lat * 2.3)) +
|
||||
Math.sin(lon * 3.1 - lat * 1.8) * 0.55 +
|
||||
Math.cos(lat * 4.2 + lon * 0.7) * 0.42
|
||||
const continentalBias = Math.sin(lon * 0.48 - 0.8) * Math.cos(lat * 1.4)
|
||||
return waves + continentalBias
|
||||
}
|
||||
|
||||
function VoxelSphere({ reduceMotion }: { reduceMotion: boolean }) {
|
||||
const mesh = useRef<THREE.InstancedMesh>(null)
|
||||
const group = useRef<THREE.Group>(null)
|
||||
const dummy = useMemo(() => new THREE.Object3D(), [])
|
||||
const points = useMemo(() => {
|
||||
const values: { position: THREE.Vector3; scale: number; color: THREE.Color }[] = []
|
||||
const latitudeBands = 31
|
||||
for (let y = 0; y < latitudeBands; y += 1) {
|
||||
const v = y / (latitudeBands - 1)
|
||||
const phi = v * Math.PI
|
||||
const ringSize = Math.max(8, Math.round(Math.sin(phi) * 62))
|
||||
for (let x = 0; x < ringSize; x += 1) {
|
||||
const theta = (x / ringSize) * Math.PI * 2
|
||||
const lat = Math.PI / 2 - phi
|
||||
const terrain = terrainValue(lat, theta)
|
||||
const polar = Math.abs(lat) > 1.25
|
||||
const isLand = terrain > 0.42
|
||||
const radius = isLand ? 2.24 : 2.15
|
||||
const position = new THREE.Vector3(
|
||||
radius * Math.sin(phi) * Math.cos(theta),
|
||||
radius * Math.cos(phi),
|
||||
radius * Math.sin(phi) * Math.sin(theta),
|
||||
)
|
||||
const color = polar ? ICE : isLand ? (terrain > 1.2 ? LAND_LIGHT : LAND) : OCEAN
|
||||
values.push({ position, scale: isLand ? 0.15 : 0.115, color })
|
||||
}
|
||||
}
|
||||
return values
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!mesh.current) return
|
||||
points.forEach((point, index) => {
|
||||
dummy.position.copy(point.position)
|
||||
dummy.lookAt(0, 0, 0)
|
||||
dummy.rotateX(Math.PI / 2)
|
||||
const startScale = reduceMotion ? point.scale : 0
|
||||
dummy.scale.set(startScale, startScale, startScale)
|
||||
dummy.updateMatrix()
|
||||
mesh.current?.setMatrixAt(index, dummy.matrix)
|
||||
mesh.current?.setColorAt(index, point.color)
|
||||
})
|
||||
mesh.current.instanceMatrix.needsUpdate = true
|
||||
if (mesh.current.instanceColor) mesh.current.instanceColor.needsUpdate = true
|
||||
}, [dummy, points, reduceMotion])
|
||||
|
||||
useFrame((state, delta) => {
|
||||
if (!mesh.current || !group.current) return
|
||||
if (!reduceMotion) group.current.rotation.y += delta * 0.055
|
||||
const progress = reduceMotion ? 1 : Math.min(1, state.clock.elapsedTime / 1.8)
|
||||
const eased = 1 - Math.pow(1 - progress, 3)
|
||||
points.forEach((point, index) => {
|
||||
const s = point.scale * eased
|
||||
dummy.position.copy(point.position).multiplyScalar(0.8 + eased * 0.2)
|
||||
dummy.lookAt(0, 0, 0)
|
||||
dummy.rotateX(Math.PI / 2)
|
||||
dummy.scale.set(s, s, s)
|
||||
dummy.updateMatrix()
|
||||
mesh.current?.setMatrixAt(index, dummy.matrix)
|
||||
})
|
||||
mesh.current.instanceMatrix.needsUpdate = true
|
||||
})
|
||||
|
||||
return (
|
||||
<group ref={group} rotation={[0.12, -0.55, -0.08]}>
|
||||
<instancedMesh ref={mesh} args={[undefined, undefined, points.length]}>
|
||||
<boxGeometry args={[1, 1, 1]} />
|
||||
<meshStandardMaterial roughness={0.72} metalness={0.05} />
|
||||
</instancedMesh>
|
||||
<mesh>
|
||||
<sphereGeometry args={[2.02, 48, 48]} />
|
||||
<meshBasicMaterial color="#082938" transparent opacity={0.65} depthWrite={false} />
|
||||
</mesh>
|
||||
<Beacon position={[1.35, 1.58, 1.15]} delay={0.6} />
|
||||
<Beacon position={[-1.72, 0.75, 1.25]} delay={1} />
|
||||
<Beacon position={[1.7, -0.15, -1.28]} delay={1.35} />
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
function Beacon({ position, delay }: { position: [number, number, number]; delay: number }) {
|
||||
const beacon = useRef<THREE.Group>(null)
|
||||
const { surfacePosition, orientation } = useMemo(() => {
|
||||
const normal = new THREE.Vector3(...position).normalize()
|
||||
return {
|
||||
surfacePosition: normal.clone().multiplyScalar(2.3),
|
||||
orientation: new THREE.Quaternion().setFromUnitVectors(
|
||||
new THREE.Vector3(0, 1, 0),
|
||||
normal,
|
||||
),
|
||||
}
|
||||
}, [position])
|
||||
|
||||
useFrame((state) => {
|
||||
if (!beacon.current) return
|
||||
const p = Math.max(0, Math.min(1, (state.clock.elapsedTime - delay) * 2))
|
||||
beacon.current.scale.y = p
|
||||
beacon.current.visible = p > 0
|
||||
})
|
||||
return (
|
||||
<group ref={beacon} position={surfacePosition} quaternion={orientation}>
|
||||
<mesh position={[0, 0.3, 0]} renderOrder={1}>
|
||||
<boxGeometry args={[0.035, 0.6, 0.035]} />
|
||||
<meshBasicMaterial color="#e7b85b" transparent opacity={0.75} depthWrite={false} />
|
||||
</mesh>
|
||||
<mesh position={[0, 0.65, 0]} renderOrder={1}>
|
||||
<octahedronGeometry args={[0.09]} />
|
||||
<meshBasicMaterial color="#ffd680" transparent opacity={1} depthWrite={false} />
|
||||
</mesh>
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
export default function VoxelEarth() {
|
||||
const [reduceMotion, setReduceMotion] = useState(false)
|
||||
useEffect(() => {
|
||||
const query = window.matchMedia('(prefers-reduced-motion: reduce)')
|
||||
const update = () => setReduceMotion(query.matches)
|
||||
update()
|
||||
query.addEventListener('change', update)
|
||||
return () => query.removeEventListener('change', update)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="earth-canvas" aria-label="可拖拽旋转的方块地球模型">
|
||||
<Canvas camera={{ position: [0, 0.15, 6.9], fov: 39 }} dpr={[1, 1.65]}>
|
||||
<ambientLight intensity={1.8} />
|
||||
<directionalLight position={[4, 6, 5]} intensity={3.2} color="#e8fff5" />
|
||||
<pointLight position={[-5, -2, -4]} intensity={8} color="#287b8b" />
|
||||
<VoxelSphere reduceMotion={reduceMotion} />
|
||||
{!reduceMotion && <Stars radius={45} depth={15} count={420} factor={1.4} saturation={0} fade speed={0.15} />}
|
||||
<OrbitControls enableZoom={false} enablePan={false} rotateSpeed={0.38} minPolarAngle={0.7} maxPolarAngle={2.4} />
|
||||
</Canvas>
|
||||
<div className="earth-orbit earth-orbit-one" />
|
||||
<div className="earth-orbit earth-orbit-two" />
|
||||
<span className="earth-coordinate earth-coordinate-a">31.30° N / 120.58° E</span>
|
||||
<span className="earth-coordinate earth-coordinate-b">WORLD SCALE · 73,728 × 36,864</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export const infrastructure = [
|
||||
{ value: 'SUZHOU', label: '华东 T3+ 数据中心', detail: '电信骨干网接入' },
|
||||
{ value: '3-NET', label: '全国三网直连优化', detail: '低延迟网络路径' },
|
||||
{ value: '5.4 GHz', label: 'Intel i9-13900HK', detail: '高单核性能' },
|
||||
{ value: 'BARE METAL', label: '自托管独立服务器', detail: '拒绝共享资源卡顿' },
|
||||
]
|
||||
|
||||
export const civilizationSteps = [
|
||||
{ key: '01', title: '拓荒', tag: 'EXPLORE', text: '跨越雪原、沙漠与山脉,在等比例地球上找到属于你的坐标。' },
|
||||
{ key: '02', title: '筑城', tag: 'SETTLE', text: '从荒野小屋开始,规划道路、建筑与边界,让聚落成长为都市。' },
|
||||
{ key: '03', title: '结盟', tag: 'ALLY', text: '建立贸易关系、缔结外交盟约,形成跨越大陆的文明网络。' },
|
||||
{ key: '04', title: '捍卫', tag: 'DEFEND', text: '当外交破裂,用资源、策略与剑盾守护每一寸共同建设的领土。' },
|
||||
]
|
||||
|
||||
export const platformFeatures = [
|
||||
{ label: '领地排行', value: '实时同步' },
|
||||
{ label: '卫星地图', value: '在线浏览' },
|
||||
{ label: '城镇旗帜', value: '自由上传' },
|
||||
{ label: '领主身份', value: '地图标注' },
|
||||
]
|
||||
|
||||
export const events = [
|
||||
{ code: 'EVT-DRAGON', title: '世界 BOSS 降临', text: '史诗级怪物入侵,全服玩家共同响应世界级威胁。', status: '开发中' },
|
||||
{ code: 'EVT-METEOR', title: '天降陨石灾害', text: '随机灾难改变局部地貌,考验城镇储备与应急协作。', status: '开发中' },
|
||||
{ code: 'ASSET-CUSTOM', title: '定制材质与模型', text: '无需额外模组,原版客户端即可体验专属物品与方块。', status: '制作中' },
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './styles.css'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Noto+Sans+SC:wght@300;400;500;600;700;900&family=ZCOOL+QingKe+HuangYou&display=swap');
|
||||
|
||||
:root {
|
||||
--night: #071a24;
|
||||
--night-deep: #041117;
|
||||
--night-soft: #0d2a35;
|
||||
--land: #4f8f78;
|
||||
--land-bright: #72aa86;
|
||||
--ice: #d7f1e8;
|
||||
--mist: #8fafb2;
|
||||
--gold: #c6a15b;
|
||||
--signal: #e05a47;
|
||||
--line: rgba(166, 209, 203, 0.18);
|
||||
--sans: 'Noto Sans SC', sans-serif;
|
||||
--display: 'ZCOOL QingKe HuangYou', sans-serif;
|
||||
--mono: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body { margin: 0; background: var(--night); color: var(--ice); font-family: var(--sans); overflow-x: hidden; }
|
||||
button, a { font: inherit; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
button { color: inherit; }
|
||||
::selection { background: var(--gold); color: var(--night); }
|
||||
|
||||
.site-header { position: fixed; z-index: 50; top: 0; left: 0; right: 0; height: 78px; padding: 0 clamp(24px, 5vw, 80px); display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid transparent; transition: .35s ease; }
|
||||
.site-header.is-scrolled { height: 66px; background: rgba(4, 17, 23, .86); border-color: var(--line); backdrop-filter: blur(16px); }
|
||||
.brand { display: flex; align-items: center; gap: 11px; font-family: var(--mono); font-weight: 600; letter-spacing: .08em; }
|
||||
.brand small { color: var(--mist); font: 400 10px var(--sans); letter-spacing: .12em; padding-left: 10px; border-left: 1px solid var(--line); }
|
||||
.brand-mark { position: relative; display: grid; place-items: center; width: 24px; height: 24px; border: 1px solid var(--land-bright); transform: rotate(30deg) skew(-5deg); }
|
||||
.brand-mark span { width: 8px; height: 8px; background: var(--gold); }
|
||||
.site-header nav { display: flex; gap: 38px; font-size: 13px; color: var(--mist); }
|
||||
.site-header nav a { position: relative; transition: color .2s; }
|
||||
.site-header nav a::after { content: ''; position: absolute; left: 0; bottom: -8px; width: 0; height: 1px; background: var(--gold); transition: width .25s; }
|
||||
.site-header nav a:hover { color: var(--ice); }
|
||||
.site-header nav a:hover::after { width: 100%; }
|
||||
.menu-button { display: none; border: 0; background: transparent; padding: 8px; }
|
||||
|
||||
.hero { min-height: 100svh; position: relative; display: grid; grid-template-columns: minmax(0, .92fr) minmax(420px, 1.08fr); align-items: center; padding: 120px clamp(24px, 7vw, 118px) 75px; overflow: hidden; background: radial-gradient(circle at 72% 48%, rgba(34, 104, 110, .24), transparent 32%), linear-gradient(135deg, #061923, #071a24 54%, #06151c); }
|
||||
.hero::before { content: ''; position: absolute; width: 55vw; height: 55vw; border: 1px solid rgba(143, 175, 178, .1); border-radius: 50%; top: 50%; left: 72%; transform: translate(-50%, -50%); box-shadow: 0 0 0 7vw rgba(120, 190, 184, .025), 0 0 0 14vw rgba(120, 190, 184, .018); }
|
||||
.hero-grid, .join-grid { position: absolute; inset: 0; opacity: .16; background-image: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px); background-size: 54px 54px; mask-image: linear-gradient(to bottom, transparent, black 25%, black 75%, transparent); }
|
||||
.hero-copy { position: relative; z-index: 5; padding-bottom: 45px; }
|
||||
.eyebrow, .section-label { margin: 0 0 24px; color: var(--gold); font: 500 11px var(--mono); letter-spacing: .16em; text-transform: uppercase; }
|
||||
.eyebrow span { color: var(--mist); margin-right: 18px; }
|
||||
.hero h1 { margin: 0; font: 400 clamp(56px, 6.4vw, 106px)/.94 var(--display); letter-spacing: -.025em; }
|
||||
.hero h1 em { color: var(--gold); font-style: normal; text-shadow: 0 0 50px rgba(198, 161, 91, .14); }
|
||||
.hero-lead { max-width: 590px; color: var(--mist); font-size: 16px; line-height: 1.9; margin: 35px 0; }
|
||||
.hero-actions { display: flex; align-items: center; gap: 30px; }
|
||||
.button { min-height: 48px; padding: 0 21px; display: inline-flex; align-items: center; justify-content: center; gap: 24px; position: relative; font-size: 13px; font-weight: 600; letter-spacing: .05em; border: 1px solid var(--gold); transition: .25s ease; }
|
||||
.button::before, .button::after { content: ''; position: absolute; width: 6px; height: 6px; border-color: var(--ice); transition: .25s; }
|
||||
.button::before { top: -4px; left: -4px; border-top: 1px solid; border-left: 1px solid; }
|
||||
.button::after { right: -4px; bottom: -4px; border-right: 1px solid; border-bottom: 1px solid; }
|
||||
.button.primary { color: var(--night-deep); background: var(--gold); }
|
||||
.button:hover { transform: translateY(-2px); filter: brightness(1.08); }
|
||||
.button:hover::before, .button:hover::after { width: 11px; height: 11px; }
|
||||
.status { display: flex; align-items: center; gap: 10px; color: var(--mist); font: 11px var(--mono); }
|
||||
.status i, .join-status i { width: 7px; height: 7px; display: inline-block; background: var(--signal); box-shadow: 0 0 12px var(--signal); animation: pulse 1.8s infinite; }
|
||||
.hero-earth { height: min(680px, 70vw); position: relative; z-index: 2; }
|
||||
.earth-loading { height: 100%; display: grid; place-items: center; color: var(--mist); font: 9px var(--mono); letter-spacing: .14em; }
|
||||
.earth-canvas { height: 100%; position: relative; cursor: grab; }
|
||||
.earth-canvas:active { cursor: grabbing; }
|
||||
.earth-orbit { position: absolute; pointer-events: none; border: 1px solid rgba(215, 241, 232, .15); border-radius: 50%; left: 50%; top: 50%; transform: translate(-50%, -50%) rotate(-12deg); }
|
||||
.earth-orbit-one { width: 73%; height: 31%; }
|
||||
.earth-orbit-two { width: 56%; height: 82%; transform: translate(-50%, -50%) rotate(56deg); }
|
||||
.earth-coordinate { position: absolute; color: rgba(215, 241, 232, .55); font: 9px var(--mono); letter-spacing: .1em; }
|
||||
.earth-coordinate-a { right: 5%; top: 27%; }
|
||||
.earth-coordinate-b { left: 6%; bottom: 18%; }
|
||||
.hero-scale { position: absolute; z-index: 5; bottom: 44px; right: clamp(24px, 7vw, 118px); display: grid; text-align: right; }
|
||||
.hero-scale span, .hero-scale small { color: var(--mist); font: 9px var(--mono); letter-spacing: .13em; }
|
||||
.hero-scale strong { font: 500 22px var(--mono); margin: 5px 0; }
|
||||
.hero-scale strong i { color: var(--gold); font-style: normal; }
|
||||
.scroll-note { position: absolute; left: clamp(24px, 7vw, 118px); bottom: 34px; color: rgba(143,175,178,.7); font: 9px var(--mono); letter-spacing: .14em; display: flex; align-items: center; gap: 12px; }
|
||||
.scroll-note span { width: 30px; height: 1px; background: var(--gold); }
|
||||
|
||||
.manifesto, .civilization, .platform, .event-section, .infrastructure, .evolution { padding: clamp(90px, 11vw, 170px) clamp(24px, 7vw, 118px); position: relative; }
|
||||
.section-heading { max-width: 650px; margin-bottom: 70px; }
|
||||
.section-heading.wide { max-width: 920px; }
|
||||
.section-heading h2, .infra-intro h2, .evolution h2, .join h2 { margin: 0; font: 400 clamp(42px, 5vw, 76px)/1.06 var(--display); letter-spacing: -.02em; }
|
||||
.section-heading h2 span, .evolution h2 span { color: var(--gold); }
|
||||
.section-heading > p:last-child, .infra-intro > p:last-child, .evolution-copy > p { color: var(--mist); line-height: 1.9; max-width: 580px; }
|
||||
.manifesto { background: #e3e1d7; color: var(--night); }
|
||||
.manifesto .section-label { color: #7c663a; }
|
||||
.world-layout { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(300px, .55fr); gap: clamp(45px, 7vw, 110px); align-items: center; }
|
||||
.world-map { aspect-ratio: 1.55; position: relative; overflow: hidden; border: 1px solid rgba(7, 26, 36, .25); background: #cdd8ce; }
|
||||
.map-grid, .territory-grid { position: absolute; inset: 0; background-image: linear-gradient(rgba(7, 26, 36, .11) 1px, transparent 1px), linear-gradient(90deg, rgba(7, 26, 36, .11) 1px, transparent 1px); background-size: 40px 40px; }
|
||||
.map-land { position: absolute; background: #768f6f; filter: url('#none'); opacity: .9; }
|
||||
.land-one { width: 46%; height: 48%; left: 5%; top: 8%; clip-path: polygon(6% 22%, 31% 1%, 58% 14%, 83% 7%, 100% 30%, 84% 51%, 95% 79%, 69% 100%, 48% 78%, 16% 89%, 0 62%); }
|
||||
.land-two { width: 38%; height: 58%; right: 3%; bottom: -7%; clip-path: polygon(31% 0, 75% 7%, 100% 32%, 79% 53%, 83% 92%, 45% 100%, 24% 72%, 0 53%, 13% 19%); background: #849b75; }
|
||||
.land-three { width: 20%; height: 19%; left: 39%; bottom: 3%; clip-path: polygon(17% 0, 87% 10%, 100% 65%, 64% 100%, 0 76%); }
|
||||
.map-route { position: absolute; left: 18%; top: 29%; width: 63%; height: 38%; border: 2px dashed rgba(116, 73, 42, .7); border-left: 0; border-bottom: 0; border-radius: 50%; transform: rotate(8deg); }
|
||||
.map-route i { position: absolute; width: 9px; height: 9px; background: #8d4c35; border: 2px solid #e3e1d7; box-shadow: 0 0 0 1px #8d4c35; }
|
||||
.map-route i:nth-child(1) { left: 2%; top: 41%; }.map-route i:nth-child(2) { left: 53%; top: -3%; }.map-route i:nth-child(3) { right: 0; bottom: 23%; }
|
||||
.map-compass { position: absolute; right: 24px; top: 24px; display: grid; place-items: center; color: rgba(7,26,36,.7); }.map-compass svg { width: 35px; height: 35px; }.map-compass span { font: 11px var(--mono); }
|
||||
.map-label { position: absolute; padding: 5px 8px; color: #e3e1d7; background: rgba(7, 26, 36, .77); font: 9px var(--mono); }.label-one { left: 12%; bottom: 15%; }.label-two { right: 12%; top: 36%; }
|
||||
.map-legend { position: absolute; right: 20px; bottom: 18px; display: flex; gap: 16px; padding: 9px; background: rgba(227,225,215,.72); font: 8px var(--mono); }.map-legend span { display: flex; align-items: center; gap: 5px; }.map-legend i { width: 8px; height: 8px; }.map-legend .owned { background: #768f6f; }.map-legend .wild { border: 1px dashed #53616a; }
|
||||
.world-copy > p { font-size: 16px; line-height: 2; color: #43545b; }
|
||||
.world-copy blockquote { margin: 45px 0; padding: 0 0 0 20px; border-left: 2px solid var(--gold); font: 28px/1.4 var(--display); }
|
||||
.world-copy dl { border-top: 1px solid rgba(7,26,36,.2); }
|
||||
.world-copy dl div { display: flex; justify-content: space-between; padding: 16px 0; border-bottom: 1px solid rgba(7,26,36,.2); font-size: 12px; }.world-copy dt { color: #6f7c7f; }.world-copy dd { margin: 0; font-weight: 600; }
|
||||
|
||||
.civilization { background: var(--night-deep); overflow: hidden; }
|
||||
.civilization::after { content: 'CIVILIZATION'; position: absolute; top: 100px; right: -35px; color: rgba(215,241,232,.025); font: 170px var(--display); writing-mode: vertical-rl; }
|
||||
.civilization-console { position: relative; z-index: 1; display: grid; grid-template-columns: 190px 1fr 300px; min-height: 520px; border: 1px solid var(--line); background: #071b24; }
|
||||
.step-tabs { display: flex; flex-direction: column; border-right: 1px solid var(--line); }
|
||||
.step-tabs button { flex: 1; cursor: pointer; text-align: left; border: 0; border-bottom: 1px solid var(--line); padding: 20px; background: transparent; display: grid; grid-template-columns: 35px 1fr; align-content: center; transition: .3s; }
|
||||
.step-tabs button span, .step-tabs button small { color: var(--mist); font: 9px var(--mono); }.step-tabs button strong { font: 24px var(--display); font-weight: 400; }.step-tabs button small { grid-column: 2; margin-top: 4px; }.step-tabs button.active { background: var(--gold); color: var(--night); }.step-tabs button.active span, .step-tabs button.active small { color: #685832; }
|
||||
.territory { position: relative; overflow: hidden; background: radial-gradient(circle, rgba(79,143,120,.2), transparent 60%), #09232d; transition: .4s; }
|
||||
.territory::before { content: ''; position: absolute; width: 66%; height: 63%; top: 18%; left: 18%; border: 1px dashed rgba(215,241,232,.25); border-radius: 41% 59% 36% 64%; transition: 1s; }.territory.stage-0::before { transform: scale(.1); opacity: 0; }.territory.stage-1::before { transform: scale(.6); }.territory.stage-2::before { transform: scale(.9); border-color: var(--gold); }.territory.stage-3::before { transform: scale(1.05); border-color: var(--signal); }
|
||||
.territory-zone { position: absolute; background: rgba(79,143,120,.22); transition: .7s; }.zone-a { width: 45%; height: 43%; left: 16%; top: 26%; clip-path: polygon(20% 0, 90% 9%, 100% 77%, 54% 100%, 0 71%); }.zone-b { width: 37%; height: 39%; right: 10%; bottom: 10%; clip-path: polygon(40% 0, 100% 20%, 87% 87%, 13% 100%, 0 27%); opacity: 0; }.stage-2 .zone-b, .stage-3 .zone-b { opacity: 1; background: rgba(198,161,91,.2); }
|
||||
.city, .conflict { position: absolute; display: grid; justify-items: center; gap: 5px; color: var(--ice); font: 8px var(--mono); transition: .5s; }.city svg, .conflict svg { width: 21px; padding: 4px; height: 21px; color: var(--night); background: var(--gold); }.city-a { left: 37%; top: 43%; }.city-b { right: 27%; top: 24%; opacity: 0; }.city-c { right: 19%; bottom: 21%; opacity: 0; }.stage-1 .city-b, .stage-2 .city-b, .stage-2 .city-c, .stage-3 .city-b, .stage-3 .city-c { opacity: 1; }.conflict { left: 59%; top: 52%; color: #ff9788; }.conflict svg { background: var(--signal); }
|
||||
.step-description { border-left: 1px solid var(--line); padding: 45px 30px; display: flex; flex-direction: column; justify-content: flex-end; }.step-description > span { font: 9px var(--mono); color: var(--gold); }.step-description h3 { font: 54px var(--display); font-weight: 400; margin: 10px 0 20px; }.step-description p { color: var(--mist); line-height: 1.85; font-size: 13px; }
|
||||
|
||||
.platform { background: #102e38; }.platform-title { margin-left: auto; text-align: right; }.platform-title > p:last-child { margin-left: auto; }
|
||||
.browser-frame { border: 1px solid rgba(215,241,232,.25); background: #071a24; box-shadow: 0 40px 100px rgba(0,0,0,.28); }
|
||||
.browser-bar { height: 42px; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--line); padding: 0 15px; }.browser-bar > i { width: 7px; height: 7px; border-radius: 50%; background: #47626a; }.browser-bar span { margin-left: 12px; color: var(--mist); font: 9px var(--mono); flex: 1; }.browser-bar svg { color: var(--land-bright); }
|
||||
.browser-body { min-height: 550px; display: grid; grid-template-columns: 185px 1fr 230px; }
|
||||
.browser-body aside { border-right: 1px solid var(--line); padding: 18px 12px; }.platform-logo { height: 65px; display: flex; align-items: center; gap: 10px; color: var(--ice); font: 11px var(--mono); }.platform-logo svg { color: var(--gold); }
|
||||
.browser-body aside button { width: 100%; background: transparent; border: 0; border-left: 2px solid transparent; padding: 15px 10px; text-align: left; display: grid; gap: 3px; cursor: default; }.browser-body aside button span { font-size: 11px; }.browser-body aside button small { color: var(--mist); font: 8px var(--mono); }.browser-body aside button.active { background: rgba(79,143,120,.14); border-left-color: var(--land-bright); }
|
||||
.satellite-map { position: relative; overflow: hidden; background-color: #183944; background-image: linear-gradient(30deg, rgba(108,143,116,.11) 12%, transparent 12.5%, transparent 87%, rgba(108,143,116,.11) 87.5%), linear-gradient(150deg, rgba(108,143,116,.11) 12%, transparent 12.5%, transparent 87%, rgba(108,143,116,.11) 87.5%), linear-gradient(30deg, rgba(108,143,116,.11) 12%, transparent 12.5%, transparent 87%, rgba(108,143,116,.11) 87.5%); background-size: 80px 140px; }
|
||||
.satellite-map::before { content: ''; position: absolute; inset: 0; opacity: .2; background-image: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px); background-size: 30px 30px; }
|
||||
.scan-line { position: absolute; z-index: 5; left: 0; right: 0; height: 1px; background: rgba(215,241,232,.5); box-shadow: 0 0 20px var(--land-bright); animation: scan 5s linear infinite; }
|
||||
.map-river { position: absolute; background: #0d697b; width: 12%; height: 110%; left: 49%; top: -5%; transform: rotate(21deg); clip-path: polygon(30% 0, 100% 0, 59% 30%, 90% 58%, 37% 100%, 0 100%, 39% 58%, 6% 30%); }
|
||||
.claim { position: absolute; display: grid; place-items: center; border: 1px solid; font: 8px var(--mono); }.claim span { padding: 4px 6px; background: rgba(4,17,23,.7); }.claim-a { width: 37%; height: 36%; left: 9%; top: 11%; color: #82cba6; background: rgba(79,143,120,.14); clip-path: polygon(10% 0, 90% 5%, 100% 83%, 25% 100%, 0 55%); }.claim-b { width: 34%; height: 31%; right: 5%; top: 20%; color: #dcb76c; background: rgba(198,161,91,.14); transform: rotate(-4deg); }.claim-c { width: 39%; height: 35%; right: 16%; bottom: 6%; color: #99bec5; border-style: dashed; transform: rotate(5deg); }
|
||||
.player-marker { position: absolute; z-index: 3; display: flex; gap: 5px; align-items: center; padding: 4px; background: var(--night); font: 7px var(--mono); }.player-marker svg { width: 12px; height: 12px; color: var(--gold); }.marker-one { left: 27%; top: 34%; }.marker-two { right: 22%; bottom: 24%; }.map-preview-tag { position: absolute; bottom: 10px; left: 10px; color: rgba(215,241,232,.55); font: 8px var(--mono); }
|
||||
.rank-panel { border-left: 1px solid var(--line); padding: 25px 16px; }.rank-panel > span { color: var(--mist); font: 9px var(--mono); letter-spacing: .1em; }.rank-panel > div { display: grid; grid-template-columns: 28px 1fr; padding: 20px 0; border-bottom: 1px solid var(--line); }.rank-panel b { color: var(--gold); font: 10px var(--mono); }.rank-panel strong { font-size: 12px; }.rank-panel small { grid-column: 2; color: var(--mist); margin-top: 4px; font: 8px var(--mono); }
|
||||
|
||||
.event-section { background: #d7d5cb; color: var(--night); }.event-section .section-label { color: #856b3e; }
|
||||
.event-list { border-top: 1px solid rgba(7,26,36,.25); }
|
||||
.event-row { display: grid; grid-template-columns: 65px 1fr 1.25fr 90px 24px; gap: 25px; align-items: center; min-height: 145px; border-bottom: 1px solid rgba(7,26,36,.25); transition: .25s; }.event-row:hover { padding-left: 12px; background: rgba(255,255,255,.2); }.event-index { font: 11px var(--mono); color: #697b7c; }.event-row small { color: #748385; font: 8px var(--mono); }.event-row h3 { margin: 7px 0 0; font: 28px var(--display); font-weight: 400; }.event-row p { color: #536368; font-size: 13px; line-height: 1.8; }.dev-tag { justify-self: start; color: #8a493c; border: 1px solid #a76b5e; padding: 5px 8px; font: 8px var(--mono); }
|
||||
|
||||
.infrastructure { display: grid; grid-template-columns: .7fr 1.3fr; gap: 8vw; background: var(--night); }.infra-intro { align-self: center; }.infra-grid { display: grid; grid-template-columns: 1fr 1fr; border-top: 1px solid var(--line); border-left: 1px solid var(--line); }.infra-card { min-height: 280px; padding: 25px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); display: flex; flex-direction: column; }.infra-card > span { color: var(--mist); font: 8px var(--mono); }.infra-card svg { width: 22px; margin: 35px 0 auto; color: var(--gold); }.infra-card strong { font: 26px var(--mono); color: var(--ice); }.infra-card h3 { margin: 8px 0; font-size: 13px; }.infra-card p { color: var(--mist); font-size: 11px; margin: 0; }
|
||||
|
||||
.evolution { display: grid; grid-template-columns: 1fr .8fr; gap: 10vw; background: #081d27; overflow: hidden; }.evolution-glow { position: absolute; width: 500px; height: 500px; left: -220px; top: 15%; background: rgba(61,139,122,.13); filter: blur(60px); border-radius: 50%; }.evolution-copy, .change-log { position: relative; z-index: 1; }.ai-tools { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 35px; }.ai-tools span { border: 1px solid var(--line); padding: 7px 10px; color: var(--mist); font: 8px var(--mono); }
|
||||
.change-log { border: 1px solid var(--line); align-self: center; background: rgba(4,17,23,.35); }.change-log > div { padding: 17px 20px; display: flex; justify-content: space-between; border-bottom: 1px solid var(--line); font: 9px var(--mono); }.change-log > div small { color: var(--land-bright); }.change-log ol { margin: 0; padding: 0 20px; list-style: none; }.change-log li { min-height: 105px; display: grid; grid-template-columns: 50px 1fr 10px; align-items: center; border-bottom: 1px solid var(--line); }.change-log li:last-child { border: 0; }.change-log time { color: var(--gold); font: 8px var(--mono); }.change-log p { display: grid; gap: 5px; margin: 0; }.change-log strong { font-size: 12px; }.change-log small { color: var(--mist); font-size: 9px; }.change-log li > i { width: 6px; height: 6px; background: var(--land-bright); box-shadow: 0 0 12px var(--land-bright); }
|
||||
|
||||
.join { min-height: 750px; padding: 140px 24px; display: grid; place-items: center; position: relative; text-align: center; overflow: hidden; background: radial-gradient(circle at center, #123943 0, var(--night-deep) 52%); }.join::before { content: ''; position: absolute; width: 430px; height: 430px; border-radius: 50%; border: 1px solid var(--line); box-shadow: 0 0 0 55px rgba(79,143,120,.025), 0 0 0 110px rgba(79,143,120,.018); }.join > div:not(.join-grid) { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; }.join-icon { color: var(--gold); margin-bottom: 28px; }.join p:not(.section-label) { color: var(--mist); margin: 28px 0; }.join-status { width: min(540px, 90vw); display: grid; grid-template-columns: 1fr 1fr; margin: 5px 0 35px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }.join-status span, .join-status strong { padding: 13px; }.join-status span { color: var(--mist); font: 8px var(--mono); display: flex; align-items: center; justify-content: center; gap: 8px; }.join-status strong { color: var(--gold); font: 12px var(--mono); }
|
||||
footer { min-height: 90px; padding: 0 clamp(24px, 7vw, 118px); display: flex; align-items: center; gap: 40px; border-top: 1px solid var(--line); background: var(--night-deep); color: var(--mist); font-size: 10px; }footer p { flex: 1; }footer > span { font: 8px var(--mono); }
|
||||
|
||||
@keyframes pulse { 50% { opacity: .4; box-shadow: 0 0 3px var(--signal); } }
|
||||
@keyframes scan { from { top: 0; } to { top: 100%; } }
|
||||
|
||||
@media (max-width: 1050px) {
|
||||
.hero { grid-template-columns: .85fr 1.15fr; padding-left: 5vw; padding-right: 5vw; }.hero h1 { font-size: clamp(52px, 7vw, 78px); }
|
||||
.civilization-console { grid-template-columns: 145px 1fr 240px; }.browser-body { grid-template-columns: 155px 1fr; }.rank-panel { display: none; }
|
||||
.infrastructure { grid-template-columns: 1fr; }.infra-intro { max-width: 600px; }
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.site-header { height: 66px; padding: 0 20px; }.brand small { display: none; }.site-header nav { display: none; position: absolute; top: 66px; left: 0; right: 0; padding: 30px 24px; flex-direction: column; gap: 25px; background: rgba(4,17,23,.96); border-bottom: 1px solid var(--line); }.site-header nav.open { display: flex; }.menu-button { display: block; }
|
||||
.hero { min-height: 920px; display: block; padding: 120px 22px 80px; }.hero-copy { z-index: 4; }.hero h1 { font-size: clamp(48px, 14vw, 68px); }.hero-lead { font-size: 14px; line-height: 1.75; }.hero-actions { align-items: flex-start; flex-direction: column; gap: 18px; }.hero-earth { position: absolute; height: 520px; width: 115%; left: -7.5%; bottom: 40px; opacity: .8; }.hero-scale { left: 22px; right: auto; bottom: 33px; text-align: left; }.scroll-note { display: none; }.earth-coordinate { display: none; }
|
||||
.manifesto, .civilization, .platform, .event-section, .infrastructure, .evolution { padding: 90px 22px; }.section-heading { margin-bottom: 48px; }.section-heading h2, .infra-intro h2, .evolution h2, .join h2 { font-size: 44px; }
|
||||
.world-layout { grid-template-columns: 1fr; }.world-map { aspect-ratio: 1.15; }.map-label { display: none; }
|
||||
.civilization-console { display: flex; flex-direction: column; min-height: 0; }.step-tabs { flex-direction: row; border-right: 0; overflow-x: auto; }.step-tabs button { min-width: 104px; display: block; padding: 15px; border-right: 1px solid var(--line); }.step-tabs button strong, .step-tabs button small { display: block; }.step-tabs button strong { font-size: 20px; }.territory { height: 340px; }.step-description { min-height: 230px; border-left: 0; border-top: 1px solid var(--line); padding: 28px; }.step-description h3 { font-size: 42px; }
|
||||
.platform-title { text-align: left; }.platform-title > p:last-child { margin-left: 0; }.browser-body { display: block; min-height: 520px; }.browser-body aside { height: 70px; display: flex; overflow-x: auto; border: 0; border-bottom: 1px solid var(--line); padding: 7px; }.platform-logo { display: none; }.browser-body aside button { min-width: 115px; }.satellite-map { height: 450px; }
|
||||
.event-row { grid-template-columns: 38px 1fr 70px; gap: 12px; padding: 22px 0; }.event-row > p { grid-column: 2 / 4; margin: 0; }.event-row > svg { display: none; }.event-row h3 { font-size: 24px; }.event-row:hover { padding-left: 0; }
|
||||
.infra-grid { grid-template-columns: 1fr; }.infra-card { min-height: 230px; }.evolution { grid-template-columns: 1fr; }.change-log { margin-top: 20px; }
|
||||
.join { min-height: 700px; }.join-status { grid-template-columns: 1fr 1fr; }.join-status span, .join-status strong { padding: 10px 5px; }
|
||||
footer { padding: 30px 22px; flex-wrap: wrap; gap: 12px; }footer p { order: 3; flex-basis: 100%; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
|
||||
}
|
||||
|
||||
:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
HOST="${HOST:-0.0.0.0}"
|
||||
PORT="${PORT:-5173}"
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if [[ ! -d node_modules ]]; then
|
||||
npm install
|
||||
fi
|
||||
|
||||
exec npm run dev -- --host "$HOST" --port "$PORT"
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
declare const _default: import("vite").UserConfig;
|
||||
export default _default;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
three: ['three', '@react-three/fiber', '@react-three/drei'],
|
||||
motion: ['motion'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
three: ['three', '@react-three/fiber', '@react-three/drei'],
|
||||
motion: ['motion'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user