Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions romantic-map-site/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>星光旅途 · 浪漫中国地图</title>
<link rel="stylesheet" href="styles.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Pacifico&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div class="aurora"></div>
<header class="hero">
<p class="hero__eyebrow">给 TA 的惊喜礼物</p>
<h1 class="hero__title">星光旅途 · 立体中国浪漫地图</h1>
<p class="hero__subtitle">点亮每一座我们去过的城市,让回忆成为永恒的星座。</p>
</header>

<main class="layout">
<section class="map-panel">
<div class="map-stage">
<div class="map-depth"></div>
<svg class="china-map" viewBox="0 0 600 480" aria-label="中国地图示意">
<defs>
<linearGradient id="mapGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#6f8dff" />
<stop offset="100%" stop-color="#ff8fb1" />
</linearGradient>
</defs>
<path
d="M78 245 L95 200 L138 180 L160 130 L220 110 L260 70 L315 80 L350 55 L380 90 L430 120 L470 150 L515 180 L530 230 L520 280 L495 320 L445 335 L420 370 L370 400 L310 410 L260 390 L210 380 L170 350 L120 320 L95 290 Z"
class="china-map__shape"
fill="url(#mapGradient)"
/>
<path
d="M120 260 L150 230 L200 210 L240 190 L300 190 L330 220 L360 235 L330 255 L295 270 L240 275 L190 285 L150 280 Z"
class="china-map__inner"
/>
</svg>
<div class="map-glow"></div>
<div id="city-markers" class="city-markers" aria-live="polite"></div>
</div>
<div class="map-footer">
<span>💗 你与我 · 北斗星的方向</span>
<button class="wish-button" id="open-letter">打开恋爱信笺</button>
</div>
</section>

<aside class="info-panel">
<div class="panel-card">
<h2>旅途进度</h2>
<div class="stats">
<div>
<p class="stats__label">点亮城市</p>
<p class="stats__value" id="visited-count">0</p>
</div>
<div>
<p class="stats__label">待探索</p>
<p class="stats__value" id="wishlist-count">0</p>
</div>
</div>
<p class="panel-note">每一颗星光都是我们携手走过的路。</p>
</div>

<div class="panel-card">
<h2>回忆图册</h2>
<p class="panel-note">选择一个城市,留下照片、旅行故事或 vlog 链接。</p>
<div class="memories" id="memories-preview"></div>
</div>

<div class="panel-card">
<h2>浪漫小语</h2>
<blockquote class="quote">“无论地图多大,你的手心就是我的归途。”</blockquote>
</div>
</aside>
</main>

<div class="modal" id="city-modal" aria-hidden="true" role="dialog">
<div class="modal__backdrop" data-close="true"></div>
<div class="modal__content" role="document">
<button class="modal__close" data-close="true" aria-label="关闭">×</button>
<div class="modal__header">
<h3 id="modal-title">城市</h3>
<span id="modal-status" class="status">已点亮</span>
</div>
<p id="modal-description"></p>
<div class="modal__gallery" id="modal-gallery"></div>
<div class="modal__actions">
<label class="upload">
<input type="file" id="upload-input" accept="image/*" multiple hidden />
上传我们的回忆
</label>
<button class="secondary" id="mark-visited">设为已去过</button>
</div>
</div>
</div>

<div class="modal" id="letter-modal" aria-hidden="true" role="dialog">
<div class="modal__backdrop" data-close="true"></div>
<div class="modal__content letter" role="document">
<button class="modal__close" data-close="true" aria-label="关闭">×</button>
<h3>致 TA 的恋爱信笺</h3>
<p>
亲爱的,我们走过的每一座城都写着你的名字。未来的旅途,我只想和你一起把地图点亮。
当你想念我时,就打开这张地图,那里有我们的故事、照片、和每一秒的心动。
</p>
<p class="signature">— 永远爱你的我</p>
</div>
</div>

<script src="script.js"></script>
</body>
</html>
199 changes: 199 additions & 0 deletions romantic-map-site/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
const cities = [
{
name: "北京",
province: "北京",
position: { x: 55, y: 30 },
status: "visited",
description: "在故宫的红墙下,我们许下了下一次旅行的约定。",
memories: ["https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=400&q=80"],
},
{
name: "上海",
province: "上海",
position: { x: 68, y: 45 },
status: "visited",
description: "外滩的灯光映着我们的笑,像城市的星河。",
memories: ["https://images.unsplash.com/photo-1505764706515-aa95265c5abc?auto=format&fit=crop&w=400&q=80"],
},
{
name: "成都",
province: "四川",
position: { x: 38, y: 50 },
status: "visited",
description: "宽窄巷子里,一杯盖碗茶,两颗心慢慢靠近。",
memories: ["https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?auto=format&fit=crop&w=400&q=80"],
},
{
name: "杭州",
province: "浙江",
position: { x: 66, y: 52 },
status: "wishlist",
description: "西湖的风还在等我们一起去看。",
memories: [],
},
{
name: "厦门",
province: "福建",
position: { x: 72, y: 63 },
status: "wishlist",
description: "海风与日落,是我们下一场浪漫。",
memories: [],
},
{
name: "西安",
province: "陕西",
position: { x: 47, y: 40 },
status: "wishlist",
description: "城墙上的夕阳,等待我们一起收集。",
memories: [],
},
];

const markersContainer = document.getElementById("city-markers");
const visitedCount = document.getElementById("visited-count");
const wishlistCount = document.getElementById("wishlist-count");
const previewContainer = document.getElementById("memories-preview");
const modal = document.getElementById("city-modal");
const modalTitle = document.getElementById("modal-title");
const modalStatus = document.getElementById("modal-status");
const modalDescription = document.getElementById("modal-description");
const modalGallery = document.getElementById("modal-gallery");
const uploadInput = document.getElementById("upload-input");
const markVisitedButton = document.getElementById("mark-visited");
const letterModal = document.getElementById("letter-modal");

let activeCity = null;

const statusLabel = {
visited: "已点亮",
wishlist: "待探索",
};

const updateStats = () => {
const visited = cities.filter((city) => city.status === "visited").length;
const wishlist = cities.filter((city) => city.status === "wishlist").length;
visitedCount.textContent = visited;
wishlistCount.textContent = wishlist;
};

const renderMarkers = () => {
markersContainer.innerHTML = "";
cities.forEach((city, index) => {
const marker = document.createElement("button");
marker.className = "city-marker";
marker.dataset.status = city.status;
marker.style.left = `${city.position.x}%`;
marker.style.top = `${city.position.y}%`;
marker.textContent = `${city.name}`;
marker.addEventListener("click", () => openCityModal(index));
markersContainer.appendChild(marker);
});
};

const renderPreview = () => {
previewContainer.innerHTML = "";
cities
.filter((city) => city.status === "visited")
.slice(0, 3)
.forEach((city) => {
const card = document.createElement("div");
card.className = "memory-card";
card.textContent = `${city.name} · ${city.description}`;
previewContainer.appendChild(card);
});
};

const openCityModal = (index) => {
activeCity = cities[index];
modalTitle.textContent = `${activeCity.name} · ${activeCity.province}`;
modalStatus.textContent = statusLabel[activeCity.status];
modalDescription.textContent = activeCity.description;
modalStatus.style.background =
activeCity.status === "visited" ? "rgba(255, 143, 177, 0.2)" : "rgba(255, 255, 255, 0.1)";
modalStatus.style.color = activeCity.status === "visited" ? "var(--accent)" : "var(--muted)";
markVisitedButton.style.display = activeCity.status === "wishlist" ? "inline-flex" : "none";
renderGallery();
modal.setAttribute("aria-hidden", "false");
};

const renderGallery = () => {
modalGallery.innerHTML = "";
if (activeCity.memories.length === 0) {
const empty = document.createElement("div");
empty.className = "memory-card";
empty.textContent = "待探索 · 期待我们一起写下故事";
modalGallery.appendChild(empty);
return;
}

activeCity.memories.forEach((url) => {
const img = document.createElement("img");
img.src = url;
img.alt = `${activeCity.name} 回忆`;
modalGallery.appendChild(img);
});
};

const closeModal = (targetModal) => {
targetModal.setAttribute("aria-hidden", "true");
};

const handleFiles = (files) => {
if (!activeCity) return;
Array.from(files).forEach((file) => {
const reader = new FileReader();
reader.onload = (event) => {
if (event.target?.result) {
activeCity.memories.push(event.target.result.toString());
activeCity.status = "visited";
renderMarkers();
updateStats();
renderPreview();
renderGallery();
modalStatus.textContent = statusLabel[activeCity.status];
markVisitedButton.style.display = "none";
}
};
reader.readAsDataURL(file);
});
};

uploadInput.addEventListener("change", (event) => {
if (event.target.files) {
handleFiles(event.target.files);
}
});

markVisitedButton.addEventListener("click", () => {
if (!activeCity) return;
activeCity.status = "visited";
renderMarkers();
updateStats();
renderPreview();
renderGallery();
modalStatus.textContent = statusLabel.visited;
markVisitedButton.style.display = "none";
});

window.addEventListener("click", (event) => {
const target = event.target;
if (target instanceof HTMLElement && target.dataset.close === "true") {
closeModal(target.closest(".modal"));
}
});

window.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closeModal(modal);
closeModal(letterModal);
}
});

const openLetterButton = document.getElementById("open-letter");
openLetterButton.addEventListener("click", () => {
letterModal.setAttribute("aria-hidden", "false");
});

renderMarkers();
updateStats();
renderPreview();
Loading