diff --git a/assets/css/main.css b/assets/css/main.css index 4db0dc3..c0cd8a9 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -1,170 +1,162 @@ -/* interimm.org page styles. The shared tokens, header, footer, buttons, cards - and page headers live in the kit (assets/kit/interimm.css); this file only - holds layouts used by the main site's own pages. */ +/* interimm.org page styles. The shared tokens, header, signal line, footer, + buttons, cards and page headers live in the kit (assets/kit/interimm.css); + this file only holds layouts used by the main site's own pages. */ /* ---------- home hero ---------- */ -.hero { padding-bottom: clamp(2rem, 5vw, 3.5rem); } -.hero-inner { - display: grid; align-items: center; gap: 2.5rem 3rem; - grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); - min-height: min(44rem, calc(100svh - 9rem)); - padding-block: clamp(3rem, 8vw, 6rem) clamp(2rem, 5vw, 3.5rem); -} +.hero { padding-block: clamp(3.5rem, 9vw, 7rem) clamp(2.5rem, 6vw, 4.5rem); } .hero h1 { - font-size: clamp(3rem, 1rem + 7.5vw, 6.75rem); font-weight: 700; - letter-spacing: -0.045em; line-height: 0.95; margin-bottom: 0.35em; - background: linear-gradient(180deg, #fff 30%, #ffc2a8 120%); - -webkit-background-clip: text; background-clip: text; color: transparent; + max-width: 14ch; margin-bottom: 0.4em; + font-size: clamp(2.75rem, 1rem + 6.5vw, 5.5rem); font-weight: 300; line-height: 1.02; letter-spacing: -0.025em; } -.hero h1:lang(zh) { letter-spacing: 0; line-height: 1.1; } -.hero .lede { max-width: 34rem; font-size: clamp(1.1rem, 1rem + 0.6vw, 1.4rem); color: var(--muted); margin-bottom: 1.5rem; } +.hero h1:lang(zh), :lang(zh) .hero h1 { max-width: 12em; font-weight: 400; line-height: 1.15; letter-spacing: 0.04em; } +.hero .lede { max-width: var(--measure); margin-bottom: 1.5rem; color: var(--muted); font-size: clamp(1.1rem, 1rem + 0.35vw, 1.25rem); } .hero .chips { margin-bottom: 2rem; } -.hero-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0; } - -.hero-visual { position: relative; display: grid; justify-items: center; gap: 1.5rem; } -.planet-wrap { position: relative; width: min(100%, 24rem); aspect-ratio: 1; display: grid; place-items: center; } -.planet { - width: 62%; aspect-ratio: 1; border-radius: 50%; - background: - radial-gradient(circle at 64% 68%, rgb(80 20 6 / 0.3), transparent 9%), - radial-gradient(circle at 30% 60%, rgb(90 25 8 / 0.25), transparent 13%), - radial-gradient(ellipse 24% 8% at 40% 18%, rgb(255 236 225 / 0.35), transparent 70%), - radial-gradient(ellipse 40% 12% at 50% 44%, rgb(120 35 12 / 0.5), transparent 70%), - radial-gradient(ellipse 30% 9% at 58% 30%, rgb(255 210 180 / 0.25), transparent 70%), - radial-gradient(circle at 34% 30%, #ffc09a 0%, #f2703f 28%, #b63c1b 58%, #4a1307 88%); - box-shadow: - inset -2.2rem -1.4rem 3.5rem rgb(0 0 0 / 0.65), - inset 0.4rem 0.3rem 1rem rgb(255 220 200 / 0.25), - 0 0 6rem rgb(255 107 61 / 0.3); -} -.orbit { position: absolute; inset: 0; border: 1px solid rgb(255 255 255 / 0.12); border-radius: 50%; animation: spin 40s linear infinite; } -.orbit-2 { inset: 9%; border-style: dashed; border-color: rgb(255 255 255 / 0.08); animation-duration: 26s; animation-direction: reverse; } -.moon { position: absolute; top: 50%; left: -4px; width: 8px; height: 8px; margin-top: -4px; border-radius: 50%; background: #e9e4dc; box-shadow: 0 0 12px rgb(255 255 255 / 0.6); } -.moon-2 { left: auto; right: -3px; width: 6px; height: 6px; margin-top: -3px; background: #c9b8a6; } -@keyframes spin { to { transform: rotate(360deg); } } - -.readout { - display: grid; grid-template-columns: repeat(3, auto); gap: 0 1.5rem; margin: 0; - padding: 0.9rem 1.25rem; border: 1px solid var(--border); border-radius: 0.9rem; - background: rgb(10 14 23 / 0.6); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); -} -.readout dt { font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); white-space: nowrap; } -.readout dd { margin: 0; font-family: var(--font-mono); font-size: 1.05rem; font-variant-numeric: tabular-nums; color: var(--text); } -.readout div:first-child dd { color: #ffb08f; } - -.latest { - display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.9rem; - padding: 0.85rem 1.1rem; border: 1px solid var(--border); border-radius: 0.9rem; - background: var(--surface); color: var(--text); text-decoration: none; font-size: 0.95rem; - transition: border-color 0.15s, background 0.15s; -} -.latest:hover { border-color: var(--border-strong); background: var(--surface-2); } -.latest time { font-family: var(--font-mono); font-size: 0.82rem; color: var(--muted); font-variant-numeric: tabular-nums; } -.latest-title { flex: 1; min-width: 12rem; font-weight: 500; } -.latest-arrow { color: var(--accent); transition: transform 0.15s; } -.latest:hover .latest-arrow { transform: translateX(4px); } -.badge { - padding: 0.1rem 0.6rem; border-radius: 999px; - background: var(--accent); color: var(--accent-text); - font-family: var(--font-mono); font-size: 0.7rem; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; -} +.hero-actions { display: flex; flex-wrap: wrap; gap: 0.75rem 2rem; margin: 0; } +.text-link-quiet { color: var(--muted); border-color: var(--border); } -@media (max-width: 54rem) { - .hero-inner { grid-template-columns: 1fr; min-height: 0; } - .hero-visual { grid-row: 1; } - .planet-wrap { width: min(70%, 15rem); } -} -@media (max-width: 30rem) { - .readout { grid-template-columns: 1fr 1fr; gap: 0.6rem 1.25rem; } - .readout div:last-child { grid-column: span 2; } +/* ---------- instrument: orbit and readings ---------- */ +.instrument { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(2rem, 5vw, 4.5rem); align-items: center; } +@media (max-width: 52rem) { .instrument { grid-template-columns: 1fr; } } +.orbit-figure { margin: 0; justify-self: center; width: 100%; max-width: 30rem; } +.orbit { display: block; width: 100%; aspect-ratio: 1; } +.orbit-path { fill: none; stroke: var(--border); stroke-width: 1; } +.orbit-sun, .orbit-earth { fill: var(--text); } +.orbit-mars { fill: var(--accent); } +.orbit-beam { fill: none; stroke: var(--accent); stroke-width: 1; stroke-dasharray: 2 4; } +.orbit text { fill: var(--muted); font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.06em; text-transform: uppercase; } +.orbit-figure figcaption { margin: 0.5rem 0 0; text-align: center; } +.readings { display: grid; gap: 1.75rem; } +.reading .kicker { margin-bottom: 0.4rem; } +.reading-value { + margin: 0; font-family: var(--font-serif); font-weight: 300; font-size: clamp(2.25rem, 1.5rem + 2.5vw, 3.25rem); + line-height: 1.05; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; } +.reading-note { max-width: 30rem; margin: 0.4rem 0 0; color: var(--muted); } +.reading-note b { color: var(--text); font-weight: 500; font-variant-numeric: tabular-nums; } +.instrument-note { margin: 0; font-size: 0.9375rem; } + +/* ---------- what we do ---------- */ +.pillars { display: grid; gap: 2rem 2.5rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); } +.pillar { display: flex; flex-direction: column; padding-top: 0.9rem; border-top: 1px solid var(--border-strong); } +.pillar-title { margin-bottom: 0.75rem; font-family: var(--font-mono); font-size: 0.75rem; font-weight: 400; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); } +.pillar-title:lang(zh), :lang(zh) .pillar-title { letter-spacing: 0.12em; } +.pillar p { margin-bottom: 1rem; font-family: var(--font-serif); font-size: 1.3rem; line-height: 1.35; } +.pillar p:first-letter { text-transform: uppercase; } +.pillar-action { margin: auto 0 0; } +.pillar .pillar-action { font-family: var(--font); font-size: 1rem; } -/* home: featured archive tiles, two of them wide */ -@media (min-width: 64rem) { - .bento-featured:has(> :nth-child(6):last-child) { grid-template-columns: repeat(4, 1fr); } - .bento-featured:has(> :nth-child(6):last-child) > :is(:first-child, :last-child) { grid-column: span 2; } +/* ---------- activity log ---------- */ +.log-list { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--border); } +.log-item { display: grid; grid-template-columns: 10rem 7rem minmax(0, 1fr); gap: 0.25rem 1.5rem; padding-block: 1.125rem; border-bottom: 1px solid var(--border); } +.log-date, .log-sol { padding-top: 0.3rem; font-family: var(--font-mono); font-size: 0.8rem; color: var(--muted); font-variant-numeric: tabular-nums; } +.log-title { margin: 0; font-size: 1.25rem; line-height: 1.3; } +.log-title a { color: var(--text); text-decoration: none; } +.log-title a:hover { text-decoration: underline; text-decoration-color: var(--border-strong); } +.log-body p { margin: 0.3rem 0 0; color: var(--muted); font-size: 0.97rem; } +@media (max-width: 40rem) { + .log-item { grid-template-columns: auto 1fr; gap: 0.1rem 1rem; } + .log-body { grid-column: 1 / -1; } } -/* what we do: three numbered columns */ -.pillars { display: grid; gap: 2rem 3rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); } -.pillar { display: flex; flex-direction: column; padding-top: 1.5rem; border-top: 1px solid var(--border-strong); } -.pillar-num { margin: 0 0 1.5rem; font-family: var(--font-mono); font-size: 0.85rem; color: var(--accent); } -.pillar-title { font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem); margin-bottom: 0.6rem; } -.pillar p { color: var(--muted); } -.pillar p:first-letter { text-transform: uppercase; } -.pillar-action { margin: auto 0 0; padding-top: 0.5rem; } +/* ---------- archive index and topics ---------- */ +.index-list { columns: 2 18rem; column-gap: 2.5rem; margin: 0; padding: 0; list-style: none; } +.index-list li { break-inside: avoid; display: flex; flex-direction: column; padding-block: 0.8rem; border-top: 1px solid var(--border); } +.index-list a { font-family: var(--font-serif); font-size: 1.2rem; line-height: 1.3; text-decoration: none; } +.index-list a:hover { text-decoration: underline; text-decoration-color: var(--border-strong); } +.index-list span { margin-top: 0.15rem; color: var(--muted); font-size: 0.9rem; line-height: 1.5; } -/* research topics: numbered link rows */ -.topic-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr)); column-gap: 3rem; margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--border); } +.topic-list { columns: 2 18rem; column-gap: 2.5rem; margin: 0; padding: 0; list-style: none; } +.topic-list li { break-inside: avoid; } .topic-list a { - display: flex; align-items: baseline; gap: 1rem; padding: 1rem 0.25rem; - border-bottom: 1px solid var(--border); color: var(--text); text-decoration: none; transition: color 0.15s, padding 0.2s; + display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding-block: 0.8rem; + border-top: 1px solid var(--border); color: var(--text); text-decoration: none; } -.topic-num { font-family: var(--font-mono); font-size: 0.8rem; color: var(--muted); } -.topic-name { flex: 1; font-family: var(--font-display); font-size: 1.1rem; font-weight: 500; } +.topic-name { font-family: var(--font-serif); font-size: 1.15rem; line-height: 1.35; } .topic-arrow { color: var(--muted); transition: transform 0.2s, color 0.15s; } -.topic-list a:hover { color: var(--link); padding-left: 0.75rem; } +.topic-list a:hover .topic-name { text-decoration: underline; text-decoration-color: var(--border-strong); } .topic-list a:hover .topic-arrow { color: var(--accent); transform: translate(2px, -2px); } -/* support band */ -.support-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); border-top: 1px solid var(--border); } -.support-item { padding: 1.5rem 1.5rem 0.5rem 0; } -.support-item + .support-item { padding-left: 1.5rem; border-left: 1px solid var(--border); } -.support-item h3 { font-size: 1.2rem; margin-bottom: 0.5rem; } -.support-item p { color: var(--muted); font-size: 0.97rem; } -@media (max-width: 58rem) { - .support-item, .support-item + .support-item { padding: 1.25rem 0 0.25rem; border-left: 0; border-bottom: 1px solid var(--border); } -} +/* ---------- support ---------- */ +.support-grid { display: grid; gap: 1.5rem 2.5rem; grid-template-columns: repeat(2, minmax(0, 1fr)); } +@media (max-width: 40rem) { .support-grid { grid-template-columns: 1fr; } } +.support-item { padding-top: 0.9rem; border-top: 1px solid var(--border); } +.support-item h3 { margin-bottom: 0.4rem; font-size: 1.2rem; } +.support-item p { margin: 0; color: var(--muted); font-size: 0.97rem; } + +/* ---------- note from outside the story ---------- */ +.outside p:first-child { max-width: 34rem; font-family: var(--font-serif); font-size: clamp(1.25rem, 1.1rem + 0.6vw, 1.5rem); line-height: 1.45; } +.outside p:last-child { margin: 0; } + +/* inner pages: the first section sits right under the page header */ +.section-flush { padding-top: 0; } -/* about page: media + text side by side */ +/* ---------- about page: media + text side by side ---------- */ .split { display: grid; gap: 2rem 3rem; align-items: center; grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); margin-top: 2rem; } .split-media { margin: 0; } -.split-media video { display: block; width: 100%; aspect-ratio: 16 / 9; background: #000; border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); } -.lede-strong { padding-left: 1rem; border-left: 3px solid var(--accent); font-family: var(--font-display); font-size: 1.3rem; font-weight: 600; line-height: 1.4; } - -.embed { aspect-ratio: 16 / 9; min-height: 18rem; } -.embed iframe { width: 100%; height: 100%; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-alt); } - -/* ---------- activity timeline ---------- */ -.timeline { position: relative; max-width: 50rem; margin: 0 auto; padding: 0 0 0 3.5rem; list-style: none; } -.timeline::before { content: ""; position: absolute; left: 1.3rem; top: 0.5rem; bottom: 0.5rem; width: 1px; background: linear-gradient(var(--accent), var(--border) 30%); } -.timeline-item { position: relative; padding-bottom: 2.5rem; } -.timeline-item:last-child { padding-bottom: 0; } -.timeline-year { - margin: 0 0 1rem; font-family: var(--font-display); font-size: 2rem; font-weight: 700; letter-spacing: -0.03em; - line-height: 1; color: color-mix(in srgb, var(--muted) 55%, transparent); -} -.timeline-item:not(:first-child) .timeline-year { padding-top: 1rem; } -.timeline-icon { - position: absolute; left: -3.5rem; width: 2.6rem; height: 2.6rem; - display: grid; place-items: center; color: var(--accent); - background: var(--bg); border: 1px solid var(--border-strong); border-radius: 50%; +.split-media video { display: block; width: 100%; aspect-ratio: 16 / 9; background: #000; border: 1px solid var(--border); } +.lede-strong { font-family: var(--font-serif); font-size: 1.4rem; line-height: 1.45; } +.prose .split-body p { font-size: 1.0625rem; } + +.embed { width: 100%; aspect-ratio: 16 / 9; margin-block: 1.5rem; } +.embed iframe { width: 100%; height: 100%; border: 1px solid var(--border); background: var(--bg-alt); } + +/* ---------- city record: registered places ---------- */ +.entry-list { max-width: var(--measure); margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--border); } +.entry-list li { display: grid; grid-template-columns: 2.5rem minmax(0, 1fr); gap: 1rem; padding-block: 1.25rem; border-bottom: 1px solid var(--border); } +.entry-num { padding-top: 0.25rem; font-family: var(--font-mono); font-size: 0.8rem; color: var(--accent); } +.entry-title { margin: 0; font-size: 1.25rem; line-height: 1.35; } +.entry-title a { color: var(--text); text-decoration: none; } +.entry-title a:hover { text-decoration: underline; text-decoration-color: var(--border-strong); } +.entry-list p { margin: 0.375rem 0 0; color: var(--muted); font-size: 0.97rem; } + +/* ---------- city 3D still, linking to the explorer ---------- */ +.city3d { margin-block: 2rem 2.5rem; } +.plate-link { position: relative; display: block; text-decoration: none; color: #17171a; } +.plate-action { + position: absolute; right: 1rem; bottom: 1rem; padding: 0.45rem 0.9rem; + background: #17171a; color: #f5f5f1; font-size: 0.9rem; font-weight: 500; transition: background 0.15s; } -.timeline-icon .icon { width: 1.2rem; height: 1.2rem; } -.timeline-body { padding: 1.1rem 1.35rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); } -.timeline-date { margin: 0; font-family: var(--font-mono); font-size: 0.8rem; color: var(--muted); font-variant-numeric: tabular-nums; } -.timeline-title { margin: 0.2rem 0 0.35rem; font-size: 1.2rem; } -.timeline-title a { color: var(--text); text-decoration-color: var(--border-strong); } -.timeline-title a:hover { color: var(--link); text-decoration-color: currentColor; } -.timeline-body p:last-child { margin-bottom: 0; color: var(--muted); } - -/* ---------- image map (city plans) ---------- */ -.imagemap { margin: 0 0 2rem; } -.imagemap-canvas { height: min(70vh, 34rem); border: 1px solid var(--border); border-radius: var(--radius); background: #05070d; } -.imagemap figcaption { margin-top: 0.5rem; color: var(--muted); font-size: 0.9rem; text-align: center; } +.plate-link:hover .plate-action { background: #b0441c; } +.plate figcaption a { color: var(--muted); } + +/* ---------- city facilities (Mars Open Facilities) ---------- */ +.facility-list { display: grid; gap: 0; margin: 1.5rem 0 0; padding: 0; list-style: none; border-top: 1px solid var(--border); } +.facility { padding-block: 1.25rem 1.5rem; border-bottom: 1px solid var(--border); } +.facility-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0.25rem 1.5rem; } +.facility-name { margin: 0; font-size: 1.35rem; line-height: 1.3; } +.facility-name a { color: var(--text); text-decoration: none; } +.facility-name a:hover { text-decoration: underline; text-decoration-color: var(--border-strong); } +.facility-status { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); } +.facility-status::before { content: ""; display: inline-block; width: 0.45rem; height: 0.45rem; margin-right: 0.45rem; border-radius: 50%; background: var(--muted); vertical-align: 0.05em; } +.facility-status[data-status="operating"]::before { background: #3f8a4f; } +.facility-status[data-status="reduced"]::before { background: #c28a1b; } +.facility-status[data-status="maintenance"]::before { background: var(--accent); } +.facility-summary { max-width: var(--measure); margin: 0.5rem 0 0; color: var(--muted); font-size: 0.97rem; } +.facility-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 0.75rem 1.5rem; margin: 1rem 0 0; } +.facility-kpis dt { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); } +.facility-kpis dd { margin: 0.15rem 0 0; font-variant-numeric: tabular-nums; } +.facility-kpis b { font-family: var(--font-serif); font-size: 1.6rem; font-weight: 300; } +.facility-design { color: var(--muted); font-size: 0.85rem; } +.facility-link { margin: 1rem 0 0; } + +/* ---------- image map (city plans), shown as a plate ---------- */ +.imagemap { margin: 0 0 2.5rem; } +.imagemap-canvas { height: min(70vh, 36rem); border: 1px solid var(--border); background: #fff; } +.imagemap figcaption { margin-top: 0.75rem; color: var(--muted); font-size: 0.875rem; } .map-pin { background: none; border: 0; } -.map-pin svg { filter: drop-shadow(0 2px 3px rgb(0 0 0 / 0.5)); } -.leaflet-popup-content { font-family: var(--font); line-height: 1.5; } -.leaflet-popup-content h3 { margin: 0 0 0.35rem; font-size: 1rem; color: #15171d; } -.leaflet-popup-content p { margin: 0 0 0.5rem; color: #2b364d; } -.leaflet-popup-content a { color: #b93d12; word-break: break-all; } +.map-pin svg { filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.35)); } +.leaflet-container { font-family: var(--font); } +.leaflet-popup-content { line-height: 1.5; } +.leaflet-popup-content h3 { margin: 0 0 0.35rem; font-size: 1rem; color: #17171a; } +.leaflet-popup-content p { margin: 0 0 0.5rem; color: #3b3a36; } +.leaflet-popup-content a { color: #b0441c; word-break: break-all; } /* ---------- 404 ---------- */ -.not-found { min-height: 60vh; display: grid; place-items: center; text-align: center; border: 0; } +.not-found { min-height: 60vh; display: grid; place-items: center; text-align: center; } .not-found .lede { margin-inline: auto; } -.not-found .code { margin: 0; font-family: var(--font-display); font-size: clamp(5rem, 20vw, 11rem); font-weight: 700; letter-spacing: -0.06em; line-height: 1; color: var(--accent); } +.not-found h1 { margin-inline: auto; } +.not-found .code { margin: 0 0 0.5rem; font-family: var(--font-mono); font-size: 0.85rem; letter-spacing: 0.1em; color: var(--accent); } @media print { - .latest, .hero-visual { display: none; } - .hero h1 { color: #000; background: none; } + .instrument-section { display: none; } } diff --git a/assets/js/facilities.js b/assets/js/facilities.js new file mode 100644 index 0000000..d1887d6 --- /dev/null +++ b/assets/js/facilities.js @@ -0,0 +1,36 @@ +// City pages: refresh the facility readings from the live Mars Open Facilities +// index. The section lists the data URLs to try in order (data-facilities). +(() => { + const root = document.querySelector('[data-facilities]'); + if (!root) return; + const urls = JSON.parse(root.dataset.facilities || '[]'); + const fmt = (v) => (v == null ? '–' : Number(v).toLocaleString(document.documentElement.lang, { maximumFractionDigits: 1, minimumFractionDigits: 1 })); + + const apply = (index) => { + index.facilities.forEach((f) => { + const item = root.querySelector(`[data-facility="${CSS.escape(f.id)}"]`); + if (!item) return; + const status = item.querySelector('[data-status]'); + if (status) { + status.dataset.status = f.status; + status.textContent = status.getAttribute(`data-label-${f.status}`) || f.status; + } + (f.kpis || []).forEach((k) => { + const v = item.querySelector(`[data-kpi="${CSS.escape(k.tag)}"] [data-value]`); + if (v) v.textContent = fmt(k.value); + }); + }); + }; + + const load = async () => { + for (const url of urls) { + try { + const r = await fetch(url, { cache: 'no-cache' }); + if (r.ok) return apply(await r.json()); + } catch { /* try the next source */ } + } + return undefined; + }; + load(); + setInterval(load, 10 * 60 * 1000); +})(); diff --git a/assets/js/imagemap.js b/assets/js/imagemap.js index 9b9afce..27a719a 100644 --- a/assets/js/imagemap.js +++ b/assets/js/imagemap.js @@ -22,7 +22,7 @@ const pin = L.divIcon({ className: 'map-pin', - html: '', + html: '', iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -14], diff --git a/assets/js/main.js b/assets/js/main.js index fb3503a..0cc1c26 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,24 +1,51 @@ -// Live Mars clock on the home page (NASA Mars24 algorithm, Allison & McEwen 2000). +// Home page instrument: today's positions of Earth and Mars and when a message +// sent now reaches Isidis. The astronomy lives in the kit (window.InterImm.astro), +// which also keeps the distance and delay readings live. (() => { - const el = document.querySelector('[data-mars-clock]'); - if (!el) return; - const mtc = el.querySelector('[data-mtc]'); - const msd = el.querySelector('[data-msd]'); - const utc = el.querySelector('[data-utc]'); - const TT_MINUS_UTC = 37 + 32.184; // TAI-UTC (leap seconds since 2017) + TT-TAI, in seconds - const pad = (n) => String(Math.floor(n)).padStart(2, '0'); - const hms = (hours) => { - const s = hours * 3600; - return `${pad(s / 3600)}:${pad((s % 3600) / 60)}:${pad(s % 60)}`; - }; - const tick = () => { + const root = document.querySelector('[data-instrument]'); + const astro = window.InterImm && window.InterImm.astro; + if (!root || !astro) return; + + const SCALE = 100; // svg units per AU; y is flipped so ecliptic north is up + const pt = ([x, y]) => [x * SCALE, -y * SCALE]; + + // Orbits: trace one full revolution through the mean anomaly. + ['earth', 'mars'].forEach((planet) => { + const now = Date.now(); + let d = ''; + for (let k = 0; k <= 180; k += 1) { + const [x, y] = pt(astro.position(planet, now, k * 2 - 180)); + d += `${k ? 'L' : 'M'}${x.toFixed(2)} ${y.toFixed(2)}`; + } + root.querySelector(`[data-orbit="${planet}"]`).setAttribute('d', `${d}Z`); + }); + + const beam = root.querySelector('[data-beam]'); + const utc = root.querySelector('[data-arrive-utc]'); + const mtc = root.querySelector('[data-arrive-mtc]'); + + const draw = () => { const now = Date.now(); - const jdTT = now / 86400000 + 2440587.5 + TT_MINUS_UTC / 86400; - const sol = (jdTT - 2405522.0028779) / 1.0274912517; - mtc.textContent = hms((((sol % 1) + 1) % 1) * 24); - msd.textContent = sol.toFixed(5); - utc.textContent = new Date(now).toISOString().slice(11, 19); + const pos = { earth: pt(astro.position('earth', now)), mars: pt(astro.position('mars', now)) }; + Object.entries(pos).forEach(([planet, [x, y]]) => { + const dot = root.querySelector(`[data-planet="${planet}"]`); + dot.setAttribute('cx', x); + dot.setAttribute('cy', y); + const r = Math.hypot(x, y) || 1; + const label = root.querySelector(`[data-planet-label="${planet}"]`); + label.setAttribute('x', x + (x / r) * 10); + label.setAttribute('y', y + (y / r) * 10 + 3); + label.setAttribute('text-anchor', x >= 0 ? 'start' : 'end'); + }); + beam.setAttribute('x1', pos.earth[0]); + beam.setAttribute('y1', pos.earth[1]); + beam.setAttribute('x2', pos.mars[0]); + beam.setAttribute('y2', pos.mars[1]); + + const arrive = now + astro.distance(now) * astro.AU_LIGHT_S * 1000; + utc.textContent = new Date(arrive).toISOString().slice(11, 19); + mtc.textContent = astro.mtc(arrive); }; - tick(); - setInterval(tick, 1000); + draw(); + setInterval(draw, 1000); })(); diff --git a/assets/kit/interimm.css b/assets/kit/interimm.css index bece0f9..f600cf3 100644 --- a/assets/kit/interimm.css +++ b/assets/kit/interimm.css @@ -5,21 +5,47 @@ https://interimm.org/kit/interimm.js. Other sites link those URLs instead of copying them, so they pick up changes automatically. See kit/README.md. - Hand-written, no framework. Colours are tokens on :root and switch with the - visitor's system light/dark preference. Anything with the `space` class is - always dark. Keep changes additive: other sites depend on these class names. */ + The idea: every page is a public document of the Interplanetary Immigration + Center. Paper and ink, serif headings, thin rules, one Mars-rust colour, and + one live element (the signal line under the header) that always knows how far + away Mars is. Colours are tokens on :root and follow the visitor's light or + dark setting. Keep changes additive: other sites depend on these class names. */ /* ---------- fonts (self-hosted, SIL OFL; see static/kit/fonts) ---------- */ @font-face { - font-family: "Space Grotesk"; - src: url("fonts/space-grotesk.woff2") format("woff2"); - font-weight: 300 700; + font-family: "Source Serif 4"; + src: url("fonts/source-serif-4.woff2") format("woff2"); + font-weight: 300 600; + font-style: normal; font-display: swap; - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2190-2199, U+2212, U+2215; + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { - font-family: "JetBrains Mono"; - src: url("fonts/jetbrains-mono-500.woff2") format("woff2"); + font-family: "Source Serif 4"; + src: url("fonts/source-serif-4-italic.woff2") format("woff2"); + font-weight: 300 600; + font-style: italic; + font-display: swap; + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +@font-face { + font-family: "IBM Plex Sans"; + src: url("fonts/ibm-plex-sans.woff2") format("woff2"); + font-weight: 400 600; + font-style: normal; + font-display: swap; + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +@font-face { + font-family: "IBM Plex Mono"; + src: url("fonts/ibm-plex-mono-400.woff2") format("woff2"); + font-weight: 400; + font-display: swap; + unicode-range: U+0000-00FF, U+2000-206F, U+2190-2199; +} +@font-face { + font-family: "IBM Plex Mono"; + src: url("fonts/ibm-plex-mono-500.woff2") format("woff2"); font-weight: 500; font-display: swap; unicode-range: U+0000-00FF, U+2000-206F, U+2190-2199; @@ -29,94 +55,58 @@ :root { color-scheme: light dark; - --bg: #f6f5f1; - --bg-alt: #eeece6; - --surface: #ffffff; - --surface-2: #faf9f6; - --text: #15171d; - --muted: #5a6070; - --border: #e2dfd7; - --border-strong: #cfcac0; - --link: #b93d12; - --focus: #d0461a; - --accent: #ff6b3d; - --accent-hover: #ff855e; - --accent-text: #1c0700; - --accent-soft: rgb(255 107 61 / 0.12); - --shadow: 0 1px 2px rgb(20 20 30 / 0.05), 0 12px 32px rgb(20 20 30 / 0.06); - - --radius: 1.1rem; - --wrap: 76rem; + --bg: #f5f5f1; /* paper */ + --bg-alt: #efeee8; + --surface: #fbfbf8; + --surface-2: #efeee8; + --text: #17171a; /* ink */ + --muted: #64635d; + --border: #dddcd4; /* rules */ + --border-strong: #17171a; + --link: #17171a; + --focus: #b0441c; + --accent: #b0441c; /* Mars rust, used sparingly */ + --accent-hover: #93381a; + --accent-text: #ffffff; + --accent-soft: rgb(176 68 28 / 0.08); + --shadow: 0 1px 2px rgb(23 23 26 / 0.04), 0 12px 32px rgb(23 23 26 / 0.06); + + --radius: 0.25rem; + --wrap: 74rem; + --measure: 40rem; --cjk: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC"; + --cjk-serif: "Songti SC", "STSong", "Noto Serif CJK SC", "Noto Serif SC", "Source Han Serif SC", "SimSun"; --emoji: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji"; - --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", var(--cjk), sans-serif, var(--emoji); - --font-display: "Space Grotesk", system-ui, -apple-system, "Segoe UI", Roboto, var(--cjk), sans-serif, var(--emoji); - --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, var(--cjk), monospace; + --font: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, var(--cjk), sans-serif, var(--emoji); + --font-serif: "Source Serif 4", Georgia, var(--cjk-serif), serif; + --font-display: var(--font-serif); + --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, var(--cjk), monospace; } -/* dark values, shared by the dark scheme and every `.space` block */ @media (prefers-color-scheme: dark) { :root { - --bg: #07090f; - --bg-alt: #0b0f18; - --surface: #0f141f; - --surface-2: #131a27; - --text: #e8ebf2; - --muted: #98a1b4; - --border: rgb(255 255 255 / 0.08); - --border-strong: rgb(255 255 255 / 0.16); - --link: #ff9b75; - --focus: #ff9b75; - --accent-soft: rgb(255 107 61 / 0.14); - --shadow: 0 1px 2px rgb(0 0 0 / 0.5), 0 16px 40px rgb(0 0 0 / 0.35); + --bg: #131315; + --bg-alt: #19191c; + --surface: #1b1b1e; + --surface-2: #232327; + --text: #ebeae4; + --muted: #9b9a93; + --border: #2d2d31; + --border-strong: #ebeae4; + --link: #ebeae4; + --focus: #e27a50; + --accent: #e27a50; + --accent-hover: #ec916c; + --accent-text: #1c0700; + --accent-soft: rgb(226 122 80 / 0.12); + --shadow: 0 1px 2px rgb(0 0 0 / 0.4), 0 16px 40px rgb(0 0 0 / 0.3); } - .site-header .brand-mark, .site-footer .brand-mark { filter: invert(1); } + .brand-mark { filter: invert(1); } } -.space { - --bg: #05070d; - --bg-alt: #0a0e17; - --surface: rgb(255 255 255 / 0.04); - --surface-2: rgb(255 255 255 / 0.07); - --text: #eef1f7; - --muted: #a3acc0; - --border: rgb(255 255 255 / 0.1); - --border-strong: rgb(255 255 255 / 0.2); - --link: #ff9b75; - --focus: #ff9b75; - color: var(--text); - position: relative; - isolation: isolate; - overflow: hidden; - background: - radial-gradient(60rem 32rem at 92% -10%, rgb(255 107 61 / 0.22), transparent 60%), - radial-gradient(48rem 36rem at -10% 110%, rgb(88 92 230 / 0.22), transparent 62%), - var(--bg); -} -/* starfield + faint coordinate grid, both static */ -.space::before, .space::after { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; } -.space::before { - opacity: 0.75; - background-image: - radial-gradient(1.2px 1.2px at 12% 22%, #fff, transparent), - radial-gradient(1px 1px at 31% 71%, #fff, transparent), - radial-gradient(1.5px 1.5px at 52% 14%, #fff, transparent), - radial-gradient(1px 1px at 68% 58%, #fff, transparent), - radial-gradient(1.2px 1.2px at 83% 33%, #fff, transparent), - radial-gradient(1px 1px at 93% 82%, #fff, transparent), - radial-gradient(1px 1px at 22% 92%, #fff, transparent), - radial-gradient(0.8px 0.8px at 44% 44%, #fff, transparent), - radial-gradient(0.8px 0.8px at 76% 8%, #fff, transparent); - background-size: 420px 340px; -} -.space::after { - background-image: - linear-gradient(rgb(255 255 255 / 0.035) 1px, transparent 1px), - linear-gradient(90deg, rgb(255 255 255 / 0.035) 1px, transparent 1px); - background-size: 64px 64px; - -webkit-mask-image: radial-gradient(ellipse at 70% 30%, #000, transparent 70%); - mask-image: radial-gradient(ellipse at 70% 30%, #000, transparent 70%); -} +/* `.space` used to be an always-dark starfield band. It is now a quiet header + band on paper, kept so pages that use it still get the right spacing. */ +.space { position: relative; color: var(--text); } /* ---------- base ---------- */ *, *::before, *::after { box-sizing: border-box; } @@ -131,7 +121,7 @@ body { flex-direction: column; font-family: var(--font); font-size: 1.0625rem; - line-height: 1.7; + line-height: 1.65; color: var(--text); background: var(--bg); -webkit-font-smoothing: antialiased; @@ -139,17 +129,24 @@ body { main { flex: 1; } h1, h2, h3 { - font-family: var(--font-display); font-weight: 600; - line-height: 1.15; letter-spacing: -0.02em; margin: 0 0 0.6em; text-wrap: balance; + font-family: var(--font-display); font-weight: 400; + line-height: 1.15; letter-spacing: -0.01em; margin: 0 0 0.6em; text-wrap: balance; } +:lang(zh) :is(h1, h2, h3), :is(h1, h2, h3):lang(zh) { letter-spacing: 0.02em; } p { margin: 0 0 1em; text-wrap: pretty; } img, video, svg { max-width: 100%; height: auto; } -a { color: var(--link); text-underline-offset: 0.2em; } -a:hover { text-decoration-thickness: 2px; } +a { + color: var(--link); + text-decoration-line: underline; text-decoration-thickness: 1px; + text-decoration-color: color-mix(in srgb, currentColor 30%, transparent); + text-underline-offset: 0.22em; + transition: text-decoration-color 0.15s, color 0.15s; +} +a:hover { text-decoration-color: currentColor; } ul, ol { margin: 0 0 1em; } ::selection { background: var(--accent); color: var(--accent-text); } -:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; border-radius: 0.3rem; } +:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; border-radius: 2px; } .wrap { width: min(100% - 2.5rem, var(--wrap)); margin-inline: auto; } @@ -160,81 +157,73 @@ ul, ol { margin: 0 0 1em; } .skip-link { position: absolute; left: 1rem; top: -4rem; z-index: 100; - padding: 0.6rem 1rem; border-radius: 0.6rem; - background: var(--accent); color: var(--accent-text); font-weight: 600; text-decoration: none; + padding: 0.6rem 1rem; background: var(--text); color: var(--bg); font-weight: 500; text-decoration: none; } .skip-link:focus { top: 1rem; } .text-center { text-align: center; } +.prose .text-center { text-align: start; } +/* Small uppercase label in the mono face: section names, metadata, captions. */ .kicker { - display: inline-flex; align-items: center; gap: 0.6rem; margin: 0 0 1rem; - font-family: var(--font-mono); font-size: 0.78rem; font-weight: 500; - letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); -} -.kicker::before { content: ""; width: 1.5rem; height: 1px; background: var(--accent); } -.kicker:has(.pulse)::before { display: none; } -.pulse { - width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--accent); - box-shadow: 0 0 0 0 rgb(255 107 61 / 0.6); animation: pulse 2.4s infinite; + display: block; margin: 0 0 1rem; + font-family: var(--font-mono); font-size: 0.75rem; font-weight: 400; + letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); } -@keyframes pulse { 70% { box-shadow: 0 0 0 0.6rem rgb(255 107 61 / 0); } 100% { box-shadow: 0 0 0 0 rgb(255 107 61 / 0); } } +.kicker:lang(zh), :lang(zh) .kicker { letter-spacing: 0.12em; } +.mono { font-family: var(--font-mono); font-size: 0.8125rem; font-variant-numeric: tabular-nums; } +.pulse { display: none; } -.icon { width: 1.5rem; height: 1.5rem; flex: none; } -.icon-emoji { display: inline-grid; place-items: center; font-size: 1.25rem; line-height: 1; } +.icon { width: 1.25rem; height: 1.25rem; flex: none; } +.icon-emoji { display: inline-grid; place-items: center; font-size: 1.1rem; line-height: 1; } /* ---------- header ---------- */ -.site-header { - position: sticky; top: 0; z-index: 50; - background: color-mix(in srgb, var(--bg) 78%, transparent); - -webkit-backdrop-filter: saturate(1.6) blur(14px); - backdrop-filter: saturate(1.6) blur(14px); - border-bottom: 1px solid var(--border); -} -.header-inner { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem 1.25rem; min-height: 4rem; padding-block: 0.5rem; } +.site-header { position: relative; z-index: 50; background: var(--bg); } +.header-inner { display: flex; align-items: center; flex-wrap: wrap; gap: 0.75rem 2rem; min-height: 4.75rem; padding-block: 1rem; } .brand { - display: inline-flex; align-items: center; gap: 0.65rem; - font-family: var(--font-display); font-weight: 700; font-size: 1.15rem; letter-spacing: -0.01em; + display: inline-flex; align-items: center; gap: 0.75rem; + font-family: var(--font-serif); font-weight: 400; font-size: 1.125rem; line-height: 1.2; color: var(--text); text-decoration: none; } -.brand-mark { border-radius: 50%; } +.brand-mark { flex: none; width: 2.25rem; height: 2.25rem; } +.brand-name { display: flex; flex-direction: column; } +.brand-sub { margin-top: 0.2rem; line-height: 1.2; font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); } -.site-nav { display: flex; flex: 1; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.25rem 1rem; } -.nav-list { display: flex; flex-wrap: wrap; align-items: center; gap: 0.1rem; list-style: none; margin: 0; padding: 0; } +.site-nav { display: flex; flex: 1; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0.25rem 1.5rem; } +.nav-list { display: flex; flex-wrap: wrap; align-items: center; gap: 0.1rem 1.25rem; list-style: none; margin: 0; padding: 0; } .nav-link, .dropdown > summary { display: inline-flex; align-items: center; gap: 0.4rem; - padding: 0.4rem 0.8rem; border-radius: 999px; - color: var(--muted); font-size: 0.95rem; font-weight: 500; text-decoration: none; cursor: pointer; - transition: color 0.15s, background 0.15s; + padding: 0.35rem 0; color: var(--muted); font-size: 0.9375rem; font-weight: 400; text-decoration: none; cursor: pointer; + transition: color 0.15s; } -.nav-link:hover, .dropdown > summary:hover, .dropdown[open] > summary { color: var(--text); background: var(--surface-2); } -.nav-link[aria-current="page"], .dropdown[data-current] > summary { color: var(--text); background: var(--accent-soft); } -.lang-link { border: 1px solid var(--border-strong); color: var(--text); } +.nav-link:hover, .dropdown > summary:hover, .dropdown[open] > summary { color: var(--text); } +.nav-link[aria-current="page"], .dropdown[data-current] > summary { color: var(--text); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.5em; } +.lang-link { color: var(--text); } +.nav-tools .nav-link svg { width: 1rem; height: 1rem; } .dropdown { position: relative; } .dropdown > summary { list-style: none; } .dropdown > summary::-webkit-details-marker { display: none; } .dropdown > summary::after { - content: ""; width: 0.35em; height: 0.35em; margin-top: -0.2em; - border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(45deg); + content: ""; width: 0.3em; height: 0.3em; margin-top: -0.2em; + border-right: 1px solid currentColor; border-bottom: 1px solid currentColor; transform: rotate(45deg); } .dropdown-menu { - position: absolute; top: calc(100% + 0.5rem); left: 0; z-index: 60; - min-width: min(19rem, 90vw); margin: 0; padding: 0.4rem; list-style: none; - background: var(--surface); border: 1px solid var(--border-strong); border-radius: 0.9rem; box-shadow: var(--shadow); + position: absolute; top: calc(100% + 0.6rem); left: -1rem; z-index: 60; + min-width: min(19rem, 90vw); margin: 0; padding: 0.5rem 0; list-style: none; + background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow); } -@media (prefers-color-scheme: dark) { .dropdown-menu { background: #121826; } } .dropdown-menu a { - display: block; padding: 0.5rem 0.75rem; border-radius: 0.55rem; - color: var(--text); font-size: 0.95rem; text-decoration: none; + display: block; padding: 0.45rem 1rem; + color: var(--text); font-size: 0.9375rem; text-decoration: none; } .dropdown-menu a:hover { background: var(--surface-2); } -.dropdown-menu a[aria-current="page"] { color: var(--link); font-weight: 600; } +.dropdown-menu a[aria-current="page"] { color: var(--accent); } .nav-toggle { - display: none; margin-left: auto; padding: 0.45rem; border: 1px solid var(--border-strong); border-radius: 999px; + display: none; margin-left: auto; padding: 0.4rem; border: 1px solid var(--border); border-radius: var(--radius); background: transparent; color: var(--text); cursor: pointer; } @@ -243,122 +232,160 @@ ul, ol { margin: 0 0 1em; } .js .site-nav { display: none; flex-basis: 100%; padding-bottom: 0.75rem; } .js .site-nav.is-open { display: flex; flex-direction: column; align-items: stretch; } .nav-list { flex-direction: column; align-items: stretch; } - .nav-link, .dropdown > summary { border-radius: 0.6rem; font-size: 1rem; } - .nav-tools { flex-direction: row; flex-wrap: wrap; padding-top: 0.5rem; border-top: 1px solid var(--border); } - .dropdown-menu { position: static; min-width: 0; margin: 0.2rem 0 0.4rem 0.75rem; box-shadow: none; } + .nav-link, .dropdown > summary { padding: 0.55rem 0; font-size: 1rem; border-bottom: 1px solid var(--border); width: 100%; } + .nav-tools { flex-direction: row; flex-wrap: wrap; gap: 0 1.5rem; padding-top: 0.25rem; } + .nav-tools .nav-link { width: auto; border-bottom: 0; } + .dropdown-menu { position: static; min-width: 0; margin: 0 0 0.5rem; padding: 0.25rem 0; border: 0; box-shadow: none; background: transparent; } + .dropdown-menu a { padding-inline: 1rem 0; } } -/* ---------- buttons, chips ---------- */ +/* ---------- signal line: distance, delay and Mars time, filled by interimm.js ---------- */ +.signal { border-block: 1px solid var(--border); color: var(--muted); } +.signal-inner { + display: flex; flex-wrap: wrap; gap: 0.2rem 2rem; padding-block: 0.6rem; + font-family: var(--font-mono); font-size: 0.78rem; letter-spacing: 0.01em; font-variant-numeric: tabular-nums; +} +.signal b { font-weight: 500; color: var(--text); } +.signal-item:first-child::before { + content: ""; display: inline-block; width: 0.4rem; height: 0.4rem; margin-right: 0.55rem; + border-radius: 50%; background: var(--accent); vertical-align: 0.08em; +} +@media (max-width: 30rem) { .signal-inner { gap: 0.1rem 1.25rem; font-size: 0.72rem; } } + +/* ---------- buttons, chips, links ---------- */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.6ch; - padding: 0.55rem 1.15rem; border: 1px solid var(--border-strong); border-radius: 999px; - background: var(--surface); color: var(--text); font: inherit; font-size: 0.95rem; font-weight: 600; line-height: 1.4; - white-space: nowrap; text-decoration: none; transition: background 0.15s, border-color 0.15s, transform 0.15s, box-shadow 0.15s; + padding: 0.55rem 1.1rem; border: 1px solid var(--border-strong); border-radius: var(--radius); + background: transparent; color: var(--text); font: inherit; font-size: 0.9375rem; font-weight: 500; line-height: 1.4; + white-space: nowrap; text-decoration: none; transition: background 0.15s, color 0.15s, border-color 0.15s; } -.btn:hover { border-color: var(--accent); } +.btn:hover { background: var(--text); color: var(--bg); } .btn span { transition: transform 0.15s; } .btn:hover span { transform: translateX(3px); } -.btn-primary { background: var(--accent); border-color: transparent; color: var(--accent-text); } -.btn-primary:hover { background: var(--accent-hover); border-color: transparent; box-shadow: 0 8px 30px rgb(255 107 61 / 0.35); } -.btn-ghost { background: transparent; } -.btn-ghost:hover { background: var(--surface-2); border-color: var(--border-strong); } -.btn-lg { padding: 0.8rem 1.6rem; font-size: 1.05rem; } - -.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; list-style: none; margin: 0 0 1.25rem; padding: 0; } -.chips li { - padding: 0.2rem 0.85rem; border: 1px solid var(--border-strong); border-radius: 999px; - background: var(--surface); color: var(--muted); font-size: 0.85rem; line-height: 1.6; -} -.chips-small li { padding: 0.05rem 0.65rem; font-size: 0.78rem; } - -.text-link { color: var(--text); font-weight: 600; text-decoration: none; border-bottom: 1px solid var(--accent); padding-bottom: 0.1rem; } +.btn-primary { background: var(--text); border-color: var(--text); color: var(--bg); } +.btn-primary:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-text); } +.btn-ghost { border-color: var(--border); } +.btn-ghost:hover { background: transparent; color: var(--text); border-color: var(--border-strong); } +.btn-lg { padding: 0.75rem 1.5rem; font-size: 1rem; } + +.chips { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; list-style: none; margin: 0 0 1.25rem; padding: 0; } +.chips li { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); } +.chips li:lang(zh), :lang(zh) .chips li { letter-spacing: 0.1em; } +.chips-small li { font-size: 0.7rem; } + +.text-link { color: var(--text); font-weight: 500; text-decoration: none; border-bottom: 1px solid var(--border-strong); padding-bottom: 0.1rem; } .text-link span { display: inline-block; transition: transform 0.15s; } +.text-link:hover { color: var(--accent); border-color: var(--accent); } .text-link:hover span { transform: translateX(3px); } /* ---------- sections ---------- */ -.section { padding-block: clamp(3.5rem, 8vw, 6rem); } -.section-alt { background: var(--bg-alt); border-block: 1px solid var(--border); } -.section-head { margin-bottom: clamp(1.75rem, 4vw, 2.75rem); } +.section { padding-block: clamp(3rem, 7vw, 5.5rem); } +.section + .section, .section-alt { border-top: 1px solid var(--border); } +.section-head { margin-bottom: clamp(1.75rem, 4vw, 2.5rem); } .section-head-split { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 1.25rem 2rem; } -.section-title { font-size: clamp(1.9rem, 1.2rem + 2.4vw, 3rem); margin-bottom: 0.4rem; } -.section-lede { max-width: 36rem; margin: 0; color: var(--muted); font-size: 1.1rem; } +.section-title { font-size: clamp(1.75rem, 1.2rem + 1.8vw, 2.5rem); line-height: 1.12; margin-bottom: 0.4rem; } +.section-lede { max-width: var(--measure); margin: 0; color: var(--muted); font-size: 1.0625rem; } + +/* A side label column and a main column, the document grid. */ +.doc-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 1rem 2rem; } +.doc-side { grid-column: span 3; } +.doc-main { grid-column: span 9; min-width: 0; } +@media (max-width: 52rem) { .doc-side, .doc-main { grid-column: 1 / -1; } } -/* tiles (archives and tools) */ -.bento { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(min(100%, 16.5rem), 1fr)); } +/* tiles (archives and tools): an index of entries divided by rules */ +.bento { display: grid; gap: 0 2.5rem; grid-template-columns: repeat(auto-fill, minmax(min(100%, 16.5rem), 1fr)); border-top: 1px solid var(--border); } .tile { position: relative; display: flex; flex-direction: column; - padding: 1.5rem; border: 1px solid var(--border); border-radius: var(--radius); - background: linear-gradient(180deg, var(--surface-2), var(--surface)); + padding: 1.5rem 0 1.25rem; border-bottom: 1px solid var(--border); } .tile > :last-child { margin-bottom: 0; } -.tile-icon { - display: inline-grid; place-items: center; width: 2.75rem; height: 2.75rem; margin-bottom: 1.25rem; - border-radius: 0.75rem; background: var(--accent-soft); color: var(--accent); -} -.tile-title { font-size: 1.2rem; margin-bottom: 0.4rem; } +.tile-icon { display: inline-flex; margin-bottom: 0.9rem; color: var(--accent); } +.tile-title { font-size: 1.3rem; line-height: 1.25; margin-bottom: 0.4rem; } .tile p { color: var(--muted); font-size: 0.97rem; } .tile .card-cta { color: var(--text); } -/* generic cards (inner pages) */ -.card-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); } +/* generic cards (inner pages): a framed surface for forms, results and longer content */ +.card-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); } .card { position: relative; display: flex; flex-direction: column; padding: 1.5rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); } .card > :last-child { margin-bottom: 0; } -.card-title { font-size: 1.25rem; margin-bottom: 0.5rem; } -.card-cta { margin: auto 0 0; padding-top: 0.75rem; color: var(--link); font-size: 0.92rem; font-weight: 600; } +.card-title { font-size: 1.35rem; line-height: 1.25; margin-bottom: 0.5rem; } +.card-cta { margin: auto 0 0; padding-top: 0.75rem; color: var(--text); font-size: 0.9rem; font-weight: 500; } .card-cta span { display: inline-block; color: var(--accent); transition: transform 0.15s; } .card-wide { max-width: 54rem; margin-inline: auto; width: 100%; } /* whole card is clickable through its title link */ -.card-link-wrap { transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s; } -.card-link-wrap:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--accent) 55%, transparent); box-shadow: var(--shadow); } +.card-link-wrap { transition: border-color 0.2s; } .card-link-wrap:hover .card-cta span { transform: translateX(4px); } +.card-link-wrap:hover .card-link { text-decoration: underline; text-decoration-color: var(--border-strong); } +.card.card-link-wrap:hover { border-color: var(--border-strong); } .card-link { color: var(--text); text-decoration: none; } -.card-link::after { content: ""; position: absolute; inset: 0; border-radius: var(--radius); } +.card-link::after { content: ""; position: absolute; inset: 0; } .card-link:focus-visible { outline: none; } .card-link:focus-visible::after { outline: 2px solid var(--focus); outline-offset: 3px; } .card-media { padding: 0; overflow: hidden; } -.card-media .card-image { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; background: var(--bg-alt); } +.card-media .card-image { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; background: var(--bg-alt); border-bottom: 1px solid var(--border); } .card-media .card-body { padding: 1.25rem 1.5rem 1.5rem; } .stack { display: grid; gap: 1.5rem; } -.figure { margin: 1rem 0 0; padding: 1rem; background: #fff; border-radius: 0.75rem; text-align: center; } + +/* Figures are shown as plates: the image on white, a caption below. */ +.figure, .plate { margin: 1rem 0 0; } +.figure { padding: clamp(0.75rem, 3vw, 1.5rem); background: #fff; border: 1px solid var(--border); text-align: center; } .figure img { max-height: 32rem; width: auto; margin-inline: auto; } +.plate-frame { background: #fff; border: 1px solid var(--border); padding: clamp(0.75rem, 3vw, 2rem); } +.plate-frame img { display: block; width: 100%; height: auto; margin-inline: auto; } +.plate figcaption { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.25rem 2rem; margin-top: 0.75rem; color: var(--muted); font-size: 0.875rem; } -/* ---------- inner pages ---------- */ -.page-hero { padding-block: clamp(3.5rem, 9vw, 6.5rem) clamp(2.5rem, 6vw, 4rem); border-bottom: 1px solid var(--border); } -.page-hero h1 { font-size: clamp(2.4rem, 1.4rem + 4vw, 4.25rem); letter-spacing: -0.035em; margin-bottom: 0.3em; } -.page-hero h1:lang(zh) { letter-spacing: 0; } -.page-hero .lede { max-width: 44rem; margin: 0 0 1rem; color: var(--muted); font-size: clamp(1.05rem, 1rem + 0.4vw, 1.25rem); } -.page-hero .chips { margin: 1rem 0 0; } +/* A record's metadata: label over value, ruled. */ +.meta-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); margin: 2.5rem 0 0; border-top: 1px solid var(--border-strong); } +.meta-list > div { padding: 0.875rem 1.25rem 0.875rem 0; border-bottom: 1px solid var(--border); } +.meta-list dt { margin-bottom: 0.25rem; font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); } +.meta-list dd { margin: 0; } -.prose { max-width: 46rem; } +/* ---------- inner pages ---------- */ +.page-hero { padding-block: clamp(3rem, 8vw, 5.5rem) clamp(2rem, 5vw, 3rem); } +.page-hero h1 { font-size: clamp(2.5rem, 1.4rem + 4vw, 4.5rem); font-weight: 300; line-height: 1.04; letter-spacing: -0.02em; margin-bottom: 0.35em; max-width: 20ch; } +.page-hero h1:lang(zh), :lang(zh) .page-hero h1 { font-weight: 400; letter-spacing: 0.04em; line-height: 1.15; } +.page-hero .lede { max-width: var(--measure); margin: 0 0 1rem; color: var(--muted); font-size: clamp(1.05rem, 1rem + 0.3vw, 1.2rem); } +.page-hero .chips { margin: 1.5rem 0 0; } +.crumbs { margin: 0 0 1.75rem; } +.crumbs a { color: var(--muted); text-decoration: none; } +.crumbs a:hover { color: var(--text); } + +.prose { max-width: var(--measure); } .prose > * { margin-inline: 0; } +.prose p, .prose li { font-family: var(--font-serif); font-size: 1.1875rem; line-height: 1.8; } +.prose :is(p, li):lang(zh), :lang(zh) .prose :is(p, li) { font-size: 1.125rem; line-height: 2; letter-spacing: 0.02em; } .prose h2 { margin-top: 2em; font-size: 1.75rem; } .prose h3 { margin-top: 1.6em; font-size: 1.3rem; } -.prose img, .prose video { border-radius: 0.75rem; } +.prose img, .prose video { display: block; } .prose-wide { max-width: 66rem; } -.prose-wide > p, .prose-wide > h2, .prose-wide > h3, .prose-wide > ul, .prose-wide > ol { max-width: 46rem; margin-inline: auto; } -.wrap.prose { margin-inline: auto; } +.prose-wide > p, .prose-wide > h2, .prose-wide > h3, .prose-wide > ul, .prose-wide > ol { max-width: var(--measure); } +/* A page body inside .wrap lines up with the page header: text keeps the + reading measure, figures and embeds use the full width. */ +.wrap.prose { max-width: none; } +.wrap.prose > :is(p, h2, h3, h4, ul, ol, blockquote, dl) { max-width: var(--measure); } :lang(zh) { line-height: 1.85; } -:lang(zh) .tile p, :lang(zh) .card p, :lang(zh) .timeline p { line-height: 1.75; } /* ---------- footer ---------- */ -.site-footer { padding-block: 3.5rem 1.5rem; background: var(--bg-alt); border-top: 1px solid var(--border); color: var(--muted); font-size: 0.93rem; } -.footer-brand { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem; padding-bottom: 2rem; margin-bottom: 2rem; border-bottom: 1px solid var(--border); } +.site-footer { padding-block: 3rem 1.75rem; border-top: 1px solid var(--border); color: var(--muted); font-size: 0.9rem; } +.footer-brand { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem; padding-bottom: 2rem; } .footer-brand .kicker { margin: 0; } -.footer-grid { display: grid; gap: 2rem 3rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); } -.footer-col h2 { margin-bottom: 0.8rem; font-family: var(--font-mono); font-size: 0.75rem; font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text); } +.footer-grid { display: grid; gap: 2rem 3rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); padding-top: 2rem; border-top: 1px solid var(--border); } +.footer-col h2 { margin-bottom: 0.8rem; font-family: var(--font-mono); font-size: 0.72rem; font-weight: 400; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text); } +.footer-col p { max-width: 24rem; } .footer-col ul { margin: 0; padding: 0; list-style: none; } -.footer-col li { margin-bottom: 0.35rem; } +.footer-col li { margin-bottom: 0.3rem; } .footer-col a { color: var(--muted); text-decoration: none; } -.footer-col a:hover { color: var(--link); text-decoration: underline; } -.footer-legal { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; margin: 2.5rem 0 0; padding-top: 1.25rem; border-top: 1px solid var(--border); font-family: var(--font-mono); font-size: 0.78rem; } +.footer-col a:hover { color: var(--text); text-decoration: underline; } +.footer-legal { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.5rem 1.5rem; margin: 2.5rem 0 0; padding-top: 1.25rem; border-top: 1px solid var(--border); font-family: var(--font-mono); font-size: 0.75rem; } .footer-legal a { color: var(--muted); text-decoration: none; } -.footer-legal a:hover { color: var(--link); } +.footer-legal a:hover { color: var(--text); } +.footer-legal-links { display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; } /* ---------- motion and print ---------- */ @media (prefers-reduced-motion: reduce) { @@ -367,7 +394,6 @@ ul, ol { margin: 0 0 1em; } } @media print { - .site-header, .site-footer, .skip-link { display: none; } - body, .space { background: #fff; color: #000; } - .space::before, .space::after { display: none; } + .site-header, .site-footer, .skip-link, .signal { display: none; } + body { background: #fff; color: #000; } } diff --git a/assets/kit/interimm.js b/assets/kit/interimm.js index 2a60b8c..25cb964 100644 --- a/assets/kit/interimm.js +++ b/assets/kit/interimm.js @@ -10,17 +10,109 @@ // 2. On every page it wires up the header menu: the mobile toggle and one // dropdown open at a time. Without JavaScript the menu is simply always // visible and the dropdowns are native
elements. +// 3. It keeps the signal line under the header live: the current Earth to Mars +// distance, the one-way signal delay, Mars time (MTC) and the sol. Any +// element with data-signal-au, -delay, -mtc or -sol is filled in. The same +// calculations are exposed as window.InterImm.astro for page scripts. (() => { document.documentElement.classList.add('js'); const script = document.currentScript; const base = script ? new URL('.', script.src) : new URL('/kit/', location.href); + // ---------- astronomy ---------- + // Mars time: NASA Mars24 (Allison & McEwen 2000). Planet positions: JPL + // approximate Keplerian elements (Standish), accurate to a few thousandths + // of an AU over 1800-2050, which is plenty for a light-time readout. + const TT_MINUS_UTC = 37 + 32.184; // TAI-UTC (leap seconds since 2017) + TT-TAI, seconds + const AU_KM = 149597870.7; + const AU_LIGHT_S = 499.004784; + const RAD = Math.PI / 180; + const julian = (ms) => ms / 86400000 + 2440587.5; + const msd = (ms) => (julian(ms) + TT_MINUS_UTC / 86400 - 2405522.0028779) / 1.0274912517; + const pad = (n) => String(Math.floor(n)).padStart(2, '0'); + const mtc = (ms) => { + const s = Math.floor((((msd(ms) % 1) + 1) % 1) * 86400); + return `${pad(s / 3600)}:${pad((s % 3600) / 60)}:${pad(s % 60)}`; + }; + // a (AU), e, I, L, longitude of perihelion, longitude of node (deg), then rates per century + const ELEMENTS = { + earth: [1.00000261, 0.01671123, -0.00001531, 100.46457166, 102.93768193, 0, 0.00000562, -0.00004392, -0.01294668, 35999.37244981, 0.32327364, 0], + mars: [1.52371034, 0.09339410, 1.84969142, -4.55343205, -23.94362959, 49.55953891, 0.00001847, 0.00007882, -0.00813131, 19140.30268499, 0.44441088, -0.29257343], + }; + // Heliocentric ecliptic position in AU. Pass meanAnomaly (deg) to trace the orbit instead. + const position = (planet, ms, meanAnomaly) => { + const T = (julian(ms) - 2451545) / 36525; + const [a0, e0, i0, l0, p0, o0, da, de, di, dl, dp, dO] = ELEMENTS[planet]; + const a = a0 + da * T; + const e = e0 + de * T; + const inc = (i0 + di * T) * RAD; + const peri = p0 + dp * T; + const node = (o0 + dO * T) * RAD; + const w = peri * RAD - node; + const M = (meanAnomaly ?? ((((l0 + dl * T - peri) % 360) + 540) % 360) - 180) * RAD; + let E = M + e * Math.sin(M); + for (let k = 0; k < 8; k++) E -= (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E)); + const x1 = a * (Math.cos(E) - e); + const y1 = a * Math.sqrt(1 - e * e) * Math.sin(E); + const cw = Math.cos(w), sw = Math.sin(w), cn = Math.cos(node), sn = Math.sin(node), ci = Math.cos(inc); + return [ + (cw * cn - sw * sn * ci) * x1 + (-sw * cn - cw * sn * ci) * y1, + (cw * sn + sw * cn * ci) * x1 + (-sw * sn + cw * cn * ci) * y1, + Math.sin(w) * Math.sin(inc) * x1 + Math.cos(w) * Math.sin(inc) * y1, + ]; + }; + const distance = (ms) => { + const e = position('earth', ms); + const m = position('mars', ms); + return Math.hypot(m[0] - e[0], m[1] - e[1], m[2] - e[2]); + }; + const astro = { msd, mtc, position, distance, AU_KM, AU_LIGHT_S }; + window.InterImm = Object.assign(window.InterImm || {}, { astro }); + + // "13 min 42 s" / "13 分 42 秒", from the page's own data-unit-min / data-unit-s, else English. + const duration = (seconds, el) => { + const min = el.dataset.unitMin || 'min'; + const sec = el.dataset.unitS || 's'; + return `${Math.floor(seconds / 60)} ${min} ${String(Math.round(seconds % 60)).padStart(2, '0')} ${sec}`; + }; + const tickSignal = () => { + const now = Date.now(); + const au = distance(now); + const set = (attr, fn) => document.querySelectorAll(`[${attr}]`).forEach((el) => { el.textContent = fn(el); }); + set('data-signal-au', () => au.toFixed(3)); + set('data-signal-km', () => (au * AU_KM / 1e6).toFixed(1)); + set('data-signal-delay', (el) => duration(au * AU_LIGHT_S, el)); + set('data-signal-mtc', () => mtc(now)); + set('data-signal-sol', () => Math.floor(msd(now)).toLocaleString('en-US')); + }; + const startSignal = () => { + if (!document.querySelector('[data-signal-au],[data-signal-delay],[data-signal-mtc],[data-signal-sol]')) return; + tickSignal(); + setInterval(tickSignal, 1000); + }; + const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]); const GITHUB_ICON = ''; const MENU_ICON = ''; - const renderHeader = (nav, current) => { + // A site with its own page in another language names it on the header, e.g. + // data-lang-cn="https://cities.interimm.org/?lang=cn"; otherwise the language + // link goes to that language's interimm.org home page. + const langUrl = (header, l) => header.getAttribute(`data-lang-${l.lang}`) || l.url; + + const renderSignal = (nav) => { + const s = nav.labels.signal; + if (!s) return ''; + return `

+ ${esc(s.distance)} – AU + ${esc(s.delay)} – + ${esc(s.mars)} – MTC + ${esc(s.sol)} – +

`; + }; + + const renderHeader = (header, nav, current) => { const link = (item) => `${esc(item.name)}`; const items = nav.menu.map((item) => { if (item.children && item.children.length) { @@ -29,9 +121,9 @@ } return `