diff --git a/docs/css/style.css b/docs/css/style.css index b2c0e90..461676b 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -40,8 +40,15 @@ body { -moz-osx-font-smoothing: grayscale; font-feature-settings: "cv11", "ss01"; text-rendering: optimizeLegibility; + /* 中西文之间自动留出间距、行首行尾标点挤压、避免标点出现在行首 */ + text-autospace: normal; + text-spacing-trim: normal; + line-break: strict; } +/* 段落最后一行不要只剩一两个字 */ +p, li, td, dd, figcaption { text-wrap: pretty; } + /* 全站颗粒质感:纯装饰的噪点层,不含文字,不接收指针事件 */ body::after { content: ""; @@ -486,6 +493,8 @@ html.motion:not(.motion-ready) .hero :is(h1, .lede, .actions, .hero-badge, .spec letter-spacing: -0.02em; } .split > div { min-width: 0; } +.split > .split-head { position: sticky; top: calc(var(--pg-header-h) + var(--pg-space-5)); } +.split-head h2 { font-size: var(--pg-text-xl); line-height: var(--pg-leading-tight); letter-spacing: -0.02em; } .stack > * + * { margin-top: var(--pg-space-4); } .stack > h3 { margin-top: var(--pg-space-7); } @@ -613,7 +622,49 @@ html.motion:not(.motion-ready) .hero :is(h1, .lede, .actions, .hero-badge, .spec .rows > li:hover .key { color: var(--pg-accent); } .rows .key a { color: inherit; } .rows p { color: var(--pg-fg-2); } -.rows-wide > li { grid-template-columns: 12rem minmax(0, 1fr); } +/* 特性列表:带编号的卡片网格,标题在上、说明在下 */ +.rows-wide { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--pg-space-4); + counter-reset: feat; +} +.rows-wide > li, +.rows-wide > li:last-child { + display: flex; + flex-direction: column; + gap: var(--pg-space-2); + padding: var(--pg-space-5); + background: var(--pg-bg-card); + border: var(--pg-hairline) solid var(--pg-line-glass); + border-radius: var(--pg-radius-lg); + box-shadow: inset 0 1px 0 var(--pg-line-hi); + counter-increment: feat; + transition: border-color var(--pg-dur-2) ease, translate var(--pg-dur-3) var(--pg-ease-out), + box-shadow var(--pg-dur-2) ease; +} +.rows-wide > li:hover { border-color: var(--pg-line-strong); translate: 0 -0.1875rem; box-shadow: var(--pg-shadow); } +/* 编号:01、02……,等宽字体、品牌渐变 */ +.rows-wide > li::before, +.rows-wide > li:hover::before { + content: counter(feat, decimal-leading-zero); + position: static; + width: auto; + height: auto; + font-family: var(--pg-font-mono); + font-size: var(--pg-text-xs); + font-weight: 700; + letter-spacing: var(--pg-tracking-label); + line-height: 1; + background: var(--pg-grad-brand); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + -webkit-text-fill-color: transparent; + transform: none; +} +.rows-wide .key { font-size: var(--pg-text-lg); } +.rows-wide p { font-size: var(--pg-text-sm); line-height: 1.8; } .rows p + p { margin-top: var(--pg-space-2); } /* ---------- 代码 ---------- */ @@ -622,11 +673,15 @@ code, kbd, pre { font-size: 0.875em; } code { - padding: 0.1em 0.4em; + padding: 0.08em 0.38em; + font-size: 0.84em; background: var(--pg-accent-soft); border: var(--pg-hairline) solid var(--pg-line-glass); border-radius: var(--pg-radius-sm); overflow-wrap: anywhere; + /* 折行时每一段都保留完整的圆角和内边距 */ + -webkit-box-decoration-break: clone; + box-decoration-break: clone; } pre { position: relative; @@ -1235,7 +1290,10 @@ html.vt-theme::view-transition-new(root) { animation: none; mix-blend-mode: norm .container { padding-inline: var(--pg-space-4); } .hero { padding-block: var(--pg-space-8) var(--pg-space-7); } .hero-split, .split, .release { grid-template-columns: minmax(0, 1fr); gap: var(--pg-space-5); } - .split > h2 { position: static; } + .split > h2, .split > .split-head { position: static; } + .rows-wide { grid-template-columns: minmax(0, 1fr); gap: var(--pg-space-3); } + .rows-wide > li, .rows-wide > li:last-child { padding: var(--pg-space-4) var(--pg-space-5); } + .lede { font-size: 1.0625rem; } .section { padding-block: var(--pg-space-8); } .rows > li, .rows-wide > li { grid-template-columns: minmax(0, 1fr); gap: var(--pg-space-1); } .points { grid-template-columns: minmax(0, 1fr); } @@ -1273,6 +1331,219 @@ html.vt-theme::view-transition-new(root) { animation: none; mix-blend-mode: norm .site-nav .menu.is-current > summary::after { left: var(--pg-space-3); margin-left: 0; } } + +/* ---------- 长内容:折叠按钮、横滑卡片、本页目录 ---------- */ +/* 折叠:表格只露前 4 行、特性卡片在电脑上只露前 4 张,底部渐隐提示下面还有 */ +table.is-collapsed tbody > tr:nth-child(n+5) { display: none; } +.rows-wide.is-collapsed > li:nth-child(n+5) { display: none; } +.table-wrap:has(table.is-collapsed), +.rows-wide.is-collapsed { + -webkit-mask-image: linear-gradient(180deg, black 65%, transparent); + mask-image: linear-gradient(180deg, black 65%, transparent); +} +.fold-btn { + display: flex; + align-items: center; + justify-content: center; + gap: var(--pg-space-2); + width: 100%; + margin-top: var(--pg-space-3); + padding: var(--pg-space-3) var(--pg-space-4); + font: inherit; + font-size: var(--pg-text-sm); + font-weight: 600; + color: var(--pg-fg-1); + background: var(--pg-bg-card); + border: var(--pg-hairline) dashed var(--pg-line-strong); + border-radius: var(--pg-radius-lg); + cursor: pointer; + transition: border-color var(--pg-dur-2) ease, background-color var(--pg-dur-2) ease; +} +.fold-btn:hover { border-color: var(--pg-accent); border-style: solid; background: var(--pg-accent-soft); } +.fold-btn .icon { transition: transform var(--pg-dur-2) var(--pg-ease-spring); } +.fold-btn[aria-expanded="true"] .icon { transform: rotate(180deg); } +.swipe-meta { display: none; } +/* 列表直接放在两栏 .split 里时,按钮跟着列表待在右栏,并收紧和列表之间的行距 */ +@media (min-width: 56.01rem) { + .split > .fold-btn { grid-column: 2; margin-top: calc(var(--pg-space-7) * -1 + var(--pg-space-3)); } +} + +/* 本页目录:首屏下方吸顶;页眉收起时贴到最顶上 */ +.page-toc { + position: sticky; + top: var(--pg-header-h); + z-index: 40; + background: var(--pg-bg-glass); + border-block: var(--pg-hairline) solid var(--pg-line-glass); + -webkit-backdrop-filter: saturate(1.6) blur(var(--pg-blur)); + backdrop-filter: saturate(1.6) blur(var(--pg-blur)); + transition: top var(--pg-dur-3) var(--pg-ease-out); +} +.site-header.is-hidden ~ main .page-toc { top: 0; } +html.has-toc { scroll-padding-top: calc(var(--pg-header-h) + 3.5rem); } +.page-toc ul { + display: flex; + gap: var(--pg-space-1); + max-width: var(--pg-container); + margin: 0 auto; + padding: var(--pg-space-2) var(--pg-space-5); + overflow-x: auto; + list-style: none; + scrollbar-width: none; +} +.page-toc ul::-webkit-scrollbar { display: none; } +.page-toc li { flex: none; margin: 0; } +.page-toc a { + display: block; + padding: var(--pg-space-1) var(--pg-space-3); + font-size: var(--pg-text-xs); + font-weight: 500; + white-space: nowrap; + color: var(--pg-fg-2); + text-decoration: none; + border: var(--pg-hairline) solid transparent; + border-radius: var(--pg-radius-pill); + transition: color var(--pg-dur-1) ease, background-color var(--pg-dur-2) ease, border-color var(--pg-dur-2) ease; +} +.page-toc a:hover { color: var(--pg-fg-1); background: var(--pg-accent-soft); } +.page-toc a[aria-current] { + color: var(--pg-fg-1); + font-weight: 650; + background: var(--pg-accent-soft); + border-color: var(--pg-line-strong); +} +.page-toc + .section::before { display: none; } + +/* 手机:特性卡片改成横向滑动,露出下一张的边缘提示还能滑 */ +@media (max-width: 48rem) { + .rows-wide, + .rows-wide.is-collapsed { + grid-template-columns: none; + grid-auto-flow: column; + grid-auto-columns: 84%; + margin-inline: calc(var(--pg-space-4) * -1); + padding: 0 var(--pg-space-4) var(--pg-space-1); + overflow-x: auto; + overscroll-behavior-x: contain; + scroll-snap-type: x mandatory; + scroll-padding-inline: var(--pg-space-4); + scrollbar-width: none; + -webkit-mask-image: none; + mask-image: none; + } + .rows-wide::-webkit-scrollbar { display: none; } + .rows-wide > li { scroll-snap-align: start; align-self: start; } + .split > .swipe-meta { margin-top: calc(var(--pg-space-5) * -1 + var(--pg-space-3)); } + .rows-wide.is-collapsed > li:nth-child(n+5) { display: flex; } + .fold-btn.fold-cards { display: none; } + .swipe-meta { + display: flex; + align-items: center; + gap: var(--pg-space-3); + margin-top: var(--pg-space-3); + font-family: var(--pg-font-mono); + font-size: var(--pg-text-2xs); + color: var(--pg-fg-3); + } + .swipe-meta[hidden] { display: none; } + .swipe-track { + position: relative; + flex: 1; + height: 0.1875rem; + overflow: hidden; + background: var(--pg-line-2); + border-radius: var(--pg-radius-pill); + } + .swipe-track i { + position: absolute; + top: 0; + bottom: 0; + left: 0; + background: var(--pg-grad-brand); + border-radius: inherit; + } + .page-toc ul { padding-inline: var(--pg-space-4); } + /* 窄屏代码块:复制按钮放到代码上方一条,不再压住长命令 */ + pre:has(.copy-btn) { padding-top: calc(var(--pg-space-7) + var(--pg-space-1)); padding-right: var(--pg-space-5); } +} + +/* 手机上表格改成一行一张卡片:行标题在上,每个值前面补上列名(由 main.js 写进 data-label)。 + 这些表每一行彼此独立,不需要跨行横向比较,所以堆叠比横向滚动更好读。 */ +@media (max-width: 48rem) { + .table-wrap { + padding: 0; + overflow: visible; + background: none; + border: 0; + border-radius: 0; + box-shadow: none; + } + .table-wrap :is(table, tbody, tr, th, td) { display: block; width: auto; } + .table-wrap thead { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + } + .table-wrap tr { + padding: var(--pg-space-4) var(--pg-space-5); + background: var(--pg-bg-card); + border: var(--pg-hairline) solid var(--pg-line-glass); + border-radius: var(--pg-radius-lg); + box-shadow: inset 0 1px 0 var(--pg-line-hi); + } + .table-wrap tr + tr { margin-top: var(--pg-space-3); } + .table-wrap tbody tr:hover { background: var(--pg-bg-card); } + .table-wrap :is(th, td), + .table-wrap tbody tr:last-child > * { padding: 0; border: 0; text-align: left; white-space: normal; } + .table-wrap tbody th { font-size: var(--pg-text-md); line-height: var(--pg-leading-tight); } + .table-wrap tbody th + td, + .table-wrap td + td { margin-top: var(--pg-space-2); } + .table-wrap td:empty { display: none; } + .table-wrap td[data-label]::before { + content: attr(data-label); + display: block; + margin-bottom: 0.125rem; + font-family: var(--pg-font-mono); + font-size: var(--pg-text-2xs); + font-weight: 600; + letter-spacing: var(--pg-tracking-label); + text-transform: uppercase; + color: var(--pg-fg-3); + } + .table-wrap .badge { white-space: normal; } + /* 只有一个状态徽标的格子不再单占一行:挪到卡片标题右边,也不再显示列名 */ + .table-wrap tbody tr:has(> th) { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + column-gap: var(--pg-space-3); + align-items: start; + } + .table-wrap tbody tr:has(> th) > * { grid-column: 1 / -1; } + .table-wrap tbody tr:has(> th) > th { grid-column: 1; grid-row: 1; } + .table-wrap tbody tr > td:has(> .badge:only-child) { + grid-column: 2; + grid-row: 1; + margin-top: 0; + } + .table-wrap td:has(> .badge:only-child)::before { display: none; } + /* 数字列(行数等)同样放到标题右边,条形图缩短后跟在数字上方 */ + .table-wrap tbody tr > td.num { + grid-column: 2; + grid-row: 1; + margin-top: 0; + text-align: right; + } + .table-wrap td.num::before { display: none; } + .table-wrap td.num .bar { width: 4.5rem; margin-left: auto; } + .table-wrap tbody tr > th + td + td.num, + .table-wrap tbody tr > th + td:has(+ td.num) { margin-top: var(--pg-space-2); } + .table-wrap tbody tr > th + td:has(> .badge:only-child) + td { margin-top: var(--pg-space-2); } + .table-wrap td.num { text-align: left; } +} + /* 用户要求减少动态效果时,关掉所有装饰性动画与过渡 */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { diff --git a/docs/index.html b/docs/index.html index 2b32ed3..7506733 100644 --- a/docs/index.html +++ b/docs/index.html @@ -7,7 +7,7 @@ GoutouStdio - + @@ -239,12 +239,12 @@ - + - + diff --git a/docs/js/main.js b/docs/js/main.js index d9013cb..bcfb01d 100644 --- a/docs/js/main.js +++ b/docs/js/main.js @@ -132,10 +132,165 @@ }); } + // 手机上表格会堆叠成卡片,每个单元格前要显示列名;这里把表头文字抄到 data-label。 + function initTableLabels() { + document.querySelectorAll("main table").forEach(function (table) { + var heads = table.querySelectorAll("thead th"); + if (!heads.length) return; + var rows = table.querySelectorAll("tbody tr"); + // 文字最长的那一列就是说明本身,不需要再标列名,免得每张卡片都多一行「说明」「作用」 + var totals = []; + rows.forEach(function (tr) { + Array.prototype.forEach.call(tr.children, function (cell, i) { + totals[i] = (totals[i] || 0) + cell.textContent.trim().length; + }); + }); + var main = totals.indexOf(Math.max.apply(null, totals)); + rows.forEach(function (tr) { + Array.prototype.forEach.call(tr.children, function (cell, i) { + var label = heads[i] && heads[i].textContent.trim(); + if (cell.tagName === "TD" && label && i !== main) cell.setAttribute("data-label", label); + }); + }); + }); + } + + + /* ---------- 长内容:折叠、横滑、本页目录 ---------- + 三项都只在有脚本时生效;没有脚本时内容全部平铺,一个字都不会被藏起来。 */ + + var FOLD_ROWS = 4; // 表格超过 FOLD_ROWS + 1 行时,先显示 FOLD_ROWS 行 + var FOLD_CARDS = 4; // 特性卡片超过 FOLD_CARDS 张时,电脑上先显示 FOLD_CARDS 张 + var uid = 0; + + function makeFold(list, items, keep, extraClass) { + if (items.length <= keep + 1) return; + if (!list.id) list.id = "fold-" + (++uid); + var rest = items.length - keep; + var btn = document.createElement("button"); + btn.type = "button"; + btn.className = "fold-btn" + (extraClass ? " " + extraClass : ""); + btn.setAttribute("aria-controls", list.id); + function set(open) { + list.classList.toggle("is-collapsed", !open); + btn.setAttribute("aria-expanded", String(open)); + btn.innerHTML = (open ? "收起" : "展开其余 " + rest + " 项") + + ''; + } + set(false); + btn.addEventListener("click", function () { + var open = list.classList.contains("is-collapsed"); + set(open); + if (open) { + if (window.pgMotion) window.pgMotion.show(items.slice(keep)); + } else { + // 收起时把按钮带回视野,免得停在一大片空白里 + var top = list.getBoundingClientRect().top; + if (top < 0) list.scrollIntoView({ block: "start" }); + } + if (window.ScrollTrigger) window.ScrollTrigger.refresh(); + }); + var anchor = list.closest(".table-wrap") || list; + anchor.insertAdjacentElement("afterend", btn); + } + + function initFold() { + document.querySelectorAll("main .table-wrap:not([data-nofold]) table").forEach(function (table) { + var rows = Array.prototype.slice.call(table.querySelectorAll("tbody > tr")); + makeFold(table, rows, FOLD_ROWS); + }); + document.querySelectorAll("main .rows-wide:not([data-nofold])").forEach(function (list) { + var items = Array.prototype.slice.call(list.children); + // 手机上这类列表改为横滑卡片,不需要折叠按钮(CSS 在窄屏隐藏 .fold-cards) + makeFold(list, items, FOLD_CARDS, "fold-cards"); + }); + } + + // 手机上特性卡片横向滑动:下方一条进度条和「当前 / 总数」 + function initCarousel() { + document.querySelectorAll("main .rows-wide").forEach(function (list) { + var total = list.children.length; + if (total < 2) return; + var meta = document.createElement("div"); + meta.className = "swipe-meta"; + meta.setAttribute("aria-hidden", "true"); + meta.innerHTML = ''; + list.insertAdjacentElement("afterend", meta); + var thumb = meta.querySelector("i"); + var count = meta.querySelector(".swipe-count"); + function update() { + var max = list.scrollWidth - list.clientWidth; + if (max <= 1) { meta.hidden = true; return; } + meta.hidden = false; + var ratio = list.clientWidth / list.scrollWidth; + var p = list.scrollLeft / max; + thumb.style.width = (ratio * 100) + "%"; + thumb.style.transform = "translateX(" + (p * (1 / ratio - 1) * 100) + "%)"; + var card = list.children[0].getBoundingClientRect().width || 1; + var idx = Math.min(total, Math.round(list.scrollLeft / card) + 1); + count.textContent = "左右滑动 · " + idx + " / " + total; + } + list.addEventListener("scroll", update, { passive: true }); + window.addEventListener("resize", update); + update(); + }); + } + + // 本页目录:小节多的页面,在首屏下方放一条吸顶的目录,滚动时高亮当前小节 + function initToc() { + var main = document.querySelector("main"); + if (!main) return; + var sections = Array.prototype.slice.call(main.querySelectorAll(":scope > section.section[id]")).filter(function (s) { + return s.querySelector("h2"); + }); + if (sections.length < 4) return; + var nav = document.createElement("nav"); + nav.className = "page-toc"; + nav.setAttribute("aria-label", "本页目录"); + var ul = document.createElement("ul"); + var links = sections.map(function (s) { + var li = document.createElement("li"); + var a = document.createElement("a"); + a.href = "#" + s.id; + a.textContent = s.querySelector("h2").textContent.trim(); + li.appendChild(a); + ul.appendChild(li); + return a; + }); + nav.appendChild(ul); + var hero = main.querySelector(":scope > .hero"); + if (hero) hero.insertAdjacentElement("afterend", nav); + else main.prepend(nav); + document.documentElement.classList.add("has-toc"); + + if (!("IntersectionObserver" in window)) return; + var current = null; + function activate(a) { + if (current === a) return; + if (current) current.removeAttribute("aria-current"); + current = a; + if (!a) return; + a.setAttribute("aria-current", "true"); + // 只横向滚动目录本身,不带动页面 + var left = a.offsetLeft - (ul.clientWidth - a.offsetWidth) / 2; + ul.scrollTo({ left: Math.max(0, left), behavior: "smooth" }); + } + var io = new IntersectionObserver(function (entries) { + entries.forEach(function (e) { + if (e.isIntersecting) activate(links[sections.indexOf(e.target)]); + }); + }, { rootMargin: "-40% 0px -55% 0px" }); + sections.forEach(function (s) { io.observe(s); }); + } + function init() { initTheme(); initMenu(); initCopy(); + initTableLabels(); + initToc(); + initFold(); + initCarousel(); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); else init(); diff --git a/docs/js/motion.js b/docs/js/motion.js index 6961ee5..1a9ccf5 100644 --- a/docs/js/motion.js +++ b/docs/js/motion.js @@ -449,6 +449,16 @@ reveal($$(".footer-grid > *", footer), { y: 24 }, { stagger: 0.07, start: "top 95%" }); } + // main.js 展开折叠内容后调用:新露出的条目依次浮现 + window.pgMotion = { + show: function (items) { + gsap.fromTo(items, { y: 24, autoAlpha: 0 }, { + y: 0, autoAlpha: 1, duration: 0.7, stagger: 0.05, ease: "expo.out", overwrite: true, + clearProps: "transform,opacity,visibility" + }); + } + }; + function init() { initLenis(); initChrome(); diff --git a/docs/ota/index.html b/docs/ota/index.html index dbfe393..ad9e692 100644 --- a/docs/ota/index.html +++ b/docs/ota/index.html @@ -7,7 +7,7 @@ PySpOS 更新包 | GoutouStdio - + @@ -224,12 +224,12 @@ - + - + + - + + - + diff --git a/docs/requirements.html b/docs/requirements.html index 4500231..549df5e 100644 --- a/docs/requirements.html +++ b/docs/requirements.html @@ -7,7 +7,7 @@ 配置要求 | GoutouStdio - + @@ -88,7 +88,7 @@

配置要求

PySpOS 3.2.0

纯用户态,不直接操作硬件,能跑 Python 的机器就能跑。

-
+
@@ -110,7 +110,7 @@

配置要求

SpaceOS 9 公开预览

目前只在 QEMU 上验证过。

-
+
Python3.10 或更高。CI 覆盖 3.10、3.12、3.14。
@@ -139,7 +139,7 @@

配置要求

表里“已验证”是实际跑过的组合,“默认关闭”的需要自己在 make menuconfig 里打开。

-
+
虚拟机x86_64 QEMU,推荐 Q35 机型和 VirtIO GPU 2D。
@@ -223,12 +223,12 @@ - + - + diff --git a/docs/spaceos6.html b/docs/spaceos6.html index 8b84347..c5ea896 100644 --- a/docs/spaceos6.html +++ b/docs/spaceos6.html @@ -7,7 +7,7 @@ SpaceOS 6(已停更) | GoutouStdio - + @@ -189,12 +189,12 @@ - + - + diff --git a/docs/spaceos7.html b/docs/spaceos7.html index e75453f..b56f7cc 100644 --- a/docs/spaceos7.html +++ b/docs/spaceos7.html @@ -7,7 +7,7 @@ SpaceOS 7(已停更) | GoutouStdio - + @@ -198,12 +198,12 @@ - + - + diff --git a/docs/spaceos8-download.html b/docs/spaceos8-download.html index fa88f9b..0284059 100644 --- a/docs/spaceos8-download.html +++ b/docs/spaceos8-download.html @@ -7,7 +7,7 @@ 下载 SpaceOS 8 | GoutouStdio - + @@ -239,12 +239,12 @@ - + - + diff --git a/docs/spaceos8.html b/docs/spaceos8.html index af7baa9..a9e16a2 100644 --- a/docs/spaceos8.html +++ b/docs/spaceos8.html @@ -7,7 +7,7 @@ SpaceOS 8 | GoutouStdio - + @@ -343,12 +343,12 @@ - + - + diff --git a/docs/spaceos9-download.html b/docs/spaceos9-download.html index d954d72..cc763ae 100644 --- a/docs/spaceos9-download.html +++ b/docs/spaceos9-download.html @@ -7,7 +7,7 @@ 下载 SpaceOS 9 | GoutouStdio - + @@ -232,12 +232,12 @@ - + - + diff --git a/docs/spaceos9.html b/docs/spaceos9.html index ff00511..61436be 100644 --- a/docs/spaceos9.html +++ b/docs/spaceos9.html @@ -7,7 +7,7 @@ SpaceOS 9 | GoutouStdio - + @@ -245,12 +245,12 @@ - + - +
硬件要求状态