chore: add project scripts, handover docs, and format guide

- guanghan_windows.py: Windows launcher script
- inject_lightbox: lightbox injection utilities
- handover docs: future missions proposal, vehicle creation guide, format guide

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-17 14:48:20 +08:00
co-authored by Claude
parent 5cbffa1386
commit 34072782ba
6 changed files with 879 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python
"""广寒计划发射窗口计算
广寒基地往返标准 LLO,每月有 2 个标准窗口。
地月转移耗时 5 天。给定任意时间,计算最近的地球出发发射窗口
及对应的月球着陆窗口。
"""
import math
import sys
from datetime import datetime, timedelta, timezone
MOON_PERIOD = timedelta(days=27.32166) # 月球自转周期
TRANSIT_DAYS = timedelta(days=5) # 地月转移耗时
# 参考着陆窗口(UTC)—— 发射窗口 = 着陆窗口 − 5 天地月转移
# 基于 2024 年 10 月精确窗口校准
REF_LANDING_A = datetime(2024, 10, 21, 4, 55, tzinfo=timezone.utc)
REF_LANDING_B = datetime(2024, 10, 29, 3, 15, tzinfo=timezone.utc)
REF_LAUNCH_A = REF_LANDING_A - TRANSIT_DAYS
REF_LAUNCH_B = REF_LANDING_B - TRANSIT_DAYS
def parse_dt(s: str) -> datetime:
"""解析用户输入的日期时间字符串,自动补全缺失部分。"""
s = s.strip()
formats = [
"%Y-%m-%d %H:%M",
"%Y-%m-%d %H",
"%Y-%m-%d",
"%Y/%m/%d %H:%M",
"%Y/%m/%d %H",
"%Y/%m/%d",
]
for fmt in formats:
try:
return datetime.strptime(s, fmt).replace(tzinfo=timezone.utc)
except ValueError:
continue
raise ValueError(f"无法解析日期: {s}(支持格式: YYYY-MM-DD HH:MM 或 YYYY/MM/DD")
def next_occurrences(ref: datetime, period: timedelta, after: datetime, count: int = 3):
"""计算从 after 之后最近的 count 个窗口(以 ref 为相位零点)。"""
n = math.floor((after - ref) / period) # 已完成周期数(floor确保负数时正确)
results = []
for i in range(1, count + 1):
results.append(ref + (n + i) * period)
return results
def guanghan_windows(target: datetime, count: int = 4):
"""返回最近 count 个广寒发射/着陆窗口。
以着陆窗口为相位零点,发射窗口 = 着陆窗口 − 5 天地月转移。
"""
landing_a = next_occurrences(REF_LANDING_A, MOON_PERIOD, target, count)
landing_b = next_occurrences(REF_LANDING_B, MOON_PERIOD, target, count)
# 合并着陆窗口,去重排序
all_landings = sorted(set(landing_a + landing_b))
lines = []
lines.append(f"查询时间: {target.strftime('%Y-%m-%d %H:%M UTC')}")
lines.append(f"参考着陆窗 A: {REF_LANDING_A.strftime('%Y-%m-%d %H:%M UTC')}")
lines.append(f"参考着陆窗 B: {REF_LANDING_B.strftime('%Y-%m-%d %H:%M UTC')}")
lines.append(f"月球周期: {MOON_PERIOD.days}.{MOON_PERIOD.seconds/3600:.2f}")
lines.append(f"地月转移: 5 天")
lines.append("")
header = f"{'#':<4} {'发射 (UTC)':<22} {'着陆 (UTC)':<22} {'距现在':>10}"
lines.append(header)
lines.append("-" * len(header))
for i, landing in enumerate(all_landings[:count], 1):
launch = landing - TRANSIT_DAYS
delta = launch - target
if delta.days >= 0:
delta_str = f"{delta.days}d {delta.seconds // 3600}h"
else:
delta_str = f"已过"
lines.append(f"{i:<4} {launch.strftime('%Y-%m-%d %H:%M'):<22} {landing.strftime('%Y-%m-%d %H:%M'):<22} {delta_str:>10}")
return "\n".join(lines)
def main():
if len(sys.argv) < 2:
print("用法: python guanghan_windows.py <日期时间>")
print("示例: python guanghan_windows.py '2024-07-20'")
print(" python guanghan_windows.py '2024-08-15 12:00'")
sys.exit(1)
target = parse_dt(sys.argv[1])
print(guanghan_windows(target))
if __name__ == "__main__":
main()
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""Inject lightbox CSS and JS into all Guanghan wiki pages."""
import os
import re
LIGHTBOX_CSS = """
.wiki-article img[data-wiki-asset] { cursor: zoom-in; }
.wiki-image-lightbox { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(32,33,34,0.84); z-index: 10000; box-sizing: border-box; }
.wiki-image-lightbox[hidden] { display: none; }
.wiki-image-lightbox-panel { position: relative; width: min(1120px, 96vw); max-height: 92vh; overflow: auto; margin: 0; padding: 10px; border: 1px solid #a2a9b1; background: #fff; box-shadow: 0 12px 38px rgba(0,0,0,0.35); box-sizing: border-box; }
.wiki-image-lightbox-panel img { display: block; width: 100%; height: auto; max-height: 82vh; object-fit: contain; background: #000; }
.wiki-image-lightbox-close { position: sticky; top: 0; float: right; width: 30px; height: 30px; margin: -2px -2px 8px 10px; border: 1px solid #a2a9b1; background: #eaecf0; color: #202122; font-weight: bold; line-height: 26px; cursor: pointer; }
.wiki-image-lightbox-close:hover, .wiki-image-lightbox-close:focus { background: #fff; }
body.wiki-image-lightbox-open { overflow: hidden; }
@media (max-width: 820px) { .wiki-image-lightbox { padding: 12px; } .wiki-image-lightbox-panel { width: 98vw; max-height: 94vh; } }"""
LIGHTBOX_JS = """<script>
(function () {
function ready(fn) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', fn, { once: true });
} else {
fn();
}
}
function captionFor(image) {
var container = image.closest('figure, td, tr, .infobox-image, .gallery-item');
if (!container) {
return image.alt || '';
}
var caption = container.querySelector('.thumbcaption');
return caption ? caption.textContent.trim() : (image.alt || '');
}
function install(article) {
if (!article || article.dataset.lightboxReady === '1') {
return;
}
article.dataset.lightboxReady = '1';
var overlay = document.createElement('div');
overlay.className = 'wiki-image-lightbox';
overlay.hidden = true;
overlay.innerHTML = '<figure class="wiki-image-lightbox-panel" role="dialog" aria-modal="true"><button class="wiki-image-lightbox-close" type="button" aria-label="Close image preview">x</button><img alt=""><figcaption class="thumbcaption"></figcaption></figure>';
document.body.appendChild(overlay);
var previewImage = overlay.querySelector('img');
var previewCaption = overlay.querySelector('figcaption');
var closeButton = overlay.querySelector('.wiki-image-lightbox-close');
function closePreview() {
overlay.hidden = true;
previewImage.removeAttribute('src');
previewCaption.textContent = '';
document.body.classList.remove('wiki-image-lightbox-open');
}
function openPreview(image, event) {
event.preventDefault();
event.stopPropagation();
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
}
previewImage.src = image.currentSrc || image.src;
previewImage.alt = image.alt || '';
previewCaption.textContent = captionFor(image);
overlay.hidden = false;
document.body.classList.add('wiki-image-lightbox-open');
closeButton.focus();
}
article.querySelectorAll('img[data-wiki-asset]').forEach(function (image) {
if (image.dataset.lightboxBound === '1') {
return;
}
image.dataset.lightboxBound = '1';
image.addEventListener('click', function (event) {
openPreview(image, event);
}, true);
});
article.addEventListener('click', function (event) {
var image = event.target.closest && event.target.closest('img[data-wiki-asset]');
if (!image || !article.contains(image) || image.closest('.wiki-image-lightbox')) {
return;
}
openPreview(image, event);
});
closeButton.addEventListener('click', closePreview);
overlay.addEventListener('click', function (event) {
if (event.target === overlay) {
closePreview();
}
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape' && !overlay.hidden) {
closePreview();
}
});
}
function installAll() {
document.querySelectorAll('.wiki-article, .vulture-article').forEach(install);
}
ready(function () {
installAll();
if (window.MutationObserver) {
var observer = new MutationObserver(installAll);
observer.observe(document.body, { childList: true, subtree: true });
}
window.setTimeout(installAll, 500);
window.setTimeout(installAll, 1500);
window.setTimeout(installAll, 3000);
});
}());
</script>"""
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
WIKI_DIR = os.path.join(BASE_DIR, 'data', 'wiki')
files = [
'guanghan_bl01_zh.html', 'guanghan_bl02_zh.html', 'guanghan_bl03_zh.html',
'guanghan_ghc01_zh.html', 'guanghan_ghc02_zh.html', 'guanghan_ghc03_zh.html',
'guanghan_ghc04_zh.html', 'guanghan_jc01_zh.html',
'guanghan_bl01_gallery_zh.html', 'guanghan_bl02_gallery_zh.html',
'guanghan_bl03_gallery_zh.html', 'guanghan_ghc01_gallery_zh.html',
'guanghan_ghc02_gallery_zh.html', 'guanghan_ghc03_gallery_zh.html',
'guanghan_ghc04_gallery_zh.html', 'guanghan_jc01_gallery_zh.html',
]
for fname in files:
fpath = os.path.join(WIKI_DIR, fname)
with open(fpath, 'r', encoding='utf-8') as f:
content = f.read()
if 'wiki-image-lightbox' in content:
print(f'SKIP (already done): {fname}')
continue
# Inject CSS before </style>
content = content.replace('</style>', LIGHTBOX_CSS + '\n</style>')
# Inject JS before </article>
content = content.replace('</article>', LIGHTBOX_JS + '\n</article>')
with open(fpath, 'w', encoding='utf-8') as f:
f.write(content)
print(f'DONE: {fname}')
print('=== Injection complete ===')
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
cd "d:/My Coding Project/KSP_Project"
# Lightbox CSS to add before </style>
LB_CSS='
.wiki-article img[data-wiki-asset] { cursor: zoom-in; }
.wiki-image-lightbox { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(32,33,34,0.84); z-index: 10000; box-sizing: border-box; }
.wiki-image-lightbox[hidden] { display: none; }
.wiki-image-lightbox-panel { position: relative; width: min(1120px, 96vw); max-height: 92vh; overflow: auto; margin: 0; padding: 10px; border: 1px solid #a2a9b1; background: #fff; box-shadow: 0 12px 38px rgba(0,0,0,0.35); box-sizing: border-box; }
.wiki-image-lightbox-panel img { display: block; width: 100%; height: auto; max-height: 82vh; object-fit: contain; background: #000; }
.wiki-image-lightbox-close { position: sticky; top: 0; float: right; width: 30px; height: 30px; margin: -2px -2px 8px 10px; border: 1px solid #a2a9b1; background: #eaecf0; color: #202122; font-weight: bold; line-height: 26px; cursor: pointer; }
.wiki-image-lightbox-close:hover, .wiki-image-lightbox-close:focus { background: #fff; }
body.wiki-image-lightbox-open { overflow: hidden; }
@media (max-width: 820px) { .wiki-image-lightbox { padding: 12px; } .wiki-image-lightbox-panel { width: 98vw; max-height: 94vh; } }'
for f in data/wiki/guanghan_bl01_zh.html data/wiki/guanghan_bl02_zh.html data/wiki/guanghan_bl03_zh.html data/wiki/guanghan_ghc01_zh.html data/wiki/guanghan_ghc02_zh.html data/wiki/guanghan_ghc03_zh.html data/wiki/guanghan_ghc04_zh.html data/wiki/guanghan_jc01_zh.html data/wiki/guanghan_bl01_gallery_zh.html data/wiki/guanghan_bl02_gallery_zh.html data/wiki/guanghan_bl03_gallery_zh.html data/wiki/guanghan_ghc01_gallery_zh.html data/wiki/guanghan_ghc02_gallery_zh.html data/wiki/guanghan_ghc03_gallery_zh.html data/wiki/guanghan_ghc04_gallery_zh.html data/wiki/guanghan_jc01_gallery_zh.html; do
if grep -q 'wiki-image-lightbox' "$f" 2>/dev/null; then
echo "SKIP (already done): $f"
continue
fi
# Add CSS before </style>
sed -i "s|</style>|${LB_CSS}\n</style>|" "$f"
echo "DONE: $f"
done
echo "=== CSS injection complete ==="