#!/usr/bin/env python3
"""Apply the shared gallery presentation and lightbox behavior to mission galleries."""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
WIKI_DIR = ROOT / "data" / "wiki"
GALLERY_FILES = sorted(WIKI_DIR.glob("guanghan_*_gallery_zh.html"))
STYLE_END_RE = re.compile(r"", re.IGNORECASE)
SCRIPT_RE = re.compile(r"\s*\s*", re.IGNORECASE | re.DOTALL)
ASSET_IMAGE_RE = re.compile(r"
]*\bdata-wiki-asset=)[^>]*>", re.IGNORECASE)
CSS_BLOCK_RE = re.compile(
r"\n?/\* mission-gallery-enhancements:start \*/.*?"
r"/\* mission-gallery-enhancements:end \*/\n?",
re.DOTALL,
)
LEGACY_LIGHTBOX_CSS_RE = re.compile(
r"\n?\.wiki-article img\[data-wiki-asset\][^\n]*\n"
r".*?@media \(max-width: 820px\) \{ \.wiki-image-lightbox \{.*?\}\s*\}\n?",
re.DOTALL,
)
GALLERY_CSS = r"""
/* mission-gallery-enhancements:start */
.wiki-article .gallery-grid {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
align-items: stretch;
}
.wiki-article .gallery-item {
display: grid;
grid-template-rows: auto 1fr;
overflow: hidden;
content-visibility: auto;
contain-intrinsic-size: 340px 300px;
}
.wiki-article .gallery-item img[data-wiki-asset] {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: contain;
background: #202122;
cursor: zoom-in;
}
.wiki-article .gallery-item .thumbcaption {
padding: 8px 7px 7px;
}
.wiki-article .gallery-notice {
margin: 1em 0 1.25em;
padding: 10px 12px;
border-left: 4px solid #36c;
background: #f1f4fd;
color: #202122;
}
.wiki-article .image-placeholder {
position: relative;
border: 1px dashed #72777d;
background: linear-gradient(135deg, #f8f9fa, #eaecf0);
color: #54595d;
}
.wiki-article.gallery-planning .gallery-grid {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr));
gap: 12px;
}
.wiki-article.gallery-planning .gallery-item {
display: block;
content-visibility: visible;
contain-intrinsic-size: none;
border-color: #a2a9b1;
background: #fff;
box-shadow: 0 1px 2px rgba(32, 33, 34, 0.08);
}
.wiki-article.gallery-planning .image-placeholder {
min-height: 0 !important;
display: block;
padding: 14px 14px 12px;
border: 0;
border-left: 4px solid #72777d;
background: #f8f9fa;
color: #202122;
text-align: left;
font-size: 13px;
font-weight: 600;
line-height: 1.65;
}
.wiki-article.gallery-planning .gallery-item .thumbcaption {
padding: 10px 14px 12px;
border-top: 1px solid #eaecf0;
color: #54595d;
}
.wiki-article.gallery-planning > .image-placeholder {
min-height: 0;
padding: 22px 18px;
border-left: 4px solid #72777d;
}
.wiki-image-lightbox {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(32, 33, 34, 0.88);
z-index: 10000;
box-sizing: border-box;
}
.wiki-image-lightbox[hidden] { display: none; }
.wiki-image-lightbox-panel {
position: relative;
display: grid;
grid-template-columns: 44px minmax(0, 1fr) 44px;
grid-template-areas: "close close close" "prev image next" "counter caption caption";
gap: 8px;
width: min(1320px, 96vw);
max-height: 94vh;
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 {
grid-area: image;
width: 100%;
height: min(78vh, 900px);
object-fit: contain;
background: #000;
}
.wiki-image-lightbox-close {
grid-area: close;
justify-self: end;
position: static;
float: none;
width: 34px;
height: 34px;
margin: 0;
border: 1px solid #a2a9b1;
border-radius: 3px;
background: #eaecf0;
color: #202122;
font-size: 22px;
line-height: 30px;
cursor: pointer;
}
.wiki-image-lightbox-close:hover,
.wiki-image-lightbox-close:focus { background: #fff; }
.wiki-image-lightbox-prev { grid-area: prev; }
.wiki-image-lightbox-next { grid-area: next; }
.wiki-image-lightbox-nav {
align-self: center;
width: 40px;
height: 56px;
border: 1px solid #a2a9b1;
border-radius: 3px;
background: #eaecf0;
color: #202122;
font-size: 26px;
cursor: pointer;
}
.wiki-image-lightbox-nav:hover,
.wiki-image-lightbox-nav:focus { background: #fff; }
.wiki-image-lightbox-counter { grid-area: counter; color: #54595d; white-space: nowrap; }
.wiki-image-lightbox-panel .thumbcaption { grid-area: caption; padding: 0 4px 3px; }
body.wiki-image-lightbox-open { overflow: hidden; }
@media (max-width: 820px) {
.wiki-article .gallery-grid { grid-template-columns: 1fr; }
.wiki-image-lightbox-panel {
grid-template-columns: 36px minmax(0, 1fr) 36px;
width: 98vw;
}
.wiki-image-lightbox-panel > img { height: 70vh; }
.wiki-image-lightbox-nav { width: 34px; height: 48px; }
}
/* mission-gallery-enhancements:end */
""".strip()
GALLERY_JS = r""""""
def add_image_loading_attributes(match: re.Match[str]) -> str:
tag = match.group(0)
additions = []
if not re.search(r"\bloading=", tag, re.IGNORECASE):
additions.append('loading="lazy"')
if not re.search(r"\bdecoding=", tag, re.IGNORECASE):
additions.append('decoding="async"')
if not re.search(r"\bfetchpriority=", tag, re.IGNORECASE):
additions.append('fetchpriority="low"')
if not additions:
return tag
return tag[:-1].rstrip() + " " + " ".join(additions) + ">"
def normalize(path: Path) -> bool:
original = path.read_text(encoding="utf-8")
updated = LEGACY_LIGHTBOX_CSS_RE.sub("\n", original)
updated = CSS_BLOCK_RE.sub("\n", updated)
updated = STYLE_END_RE.sub(GALLERY_CSS + "\n", updated, count=1)
updated = ASSET_IMAGE_RE.sub(add_image_loading_attributes, updated)
updated = SCRIPT_RE.sub("\n", updated).rstrip()
updated += "\n"
if updated == original:
return False
path.write_text(updated, encoding="utf-8", newline="\n")
return True
def main() -> int:
changed = 0
for path in GALLERY_FILES:
was_changed = normalize(path)
changed += int(was_changed)
print(f"gallery_normalized file={path.name} changed={str(was_changed).lower()}")
print(f"gallery_normalized_summary files={len(GALLERY_FILES)} changed={changed}")
return 0
if __name__ == "__main__":
raise SystemExit(main())