Initial k1412 research blog

This commit is contained in:
wuyang6
2026-08-10 02:00:15 +08:00
commit 8bd96c892a
41 changed files with 6357 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
node_modules
dist
.astro
.git
.research-private
.env*
npm-debug.log*
+9
View File
@@ -0,0 +1,9 @@
node_modules/
dist/
.astro/
.research-private/
.env
.env.*
!.env.example
.DS_Store
npm-debug.log*
+14
View File
@@ -0,0 +1,14 @@
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.29-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
RUN find /usr/share/nginx/html -type d -exec chmod 0755 {} + \
&& find /usr/share/nginx/html -type f -exec chmod 0644 {} +
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD wget -q -O /dev/null http://127.0.0.1:8080/healthz || exit 1
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 wuyang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+28
View File
@@ -0,0 +1,28 @@
# K1412 Research Blog
The public source for [blog.k1412.top](https://blog.k1412.top): technical
research articles, experiment reports, and a catalog of standalone interactive
research sites.
## Local development
```bash
npm ci
npm run check
npm run dev
```
The production build is static:
```bash
npm run build
```
Public Markdown lives in `content/posts/` and `content/sites/`. Raw research,
private evidence, credentials, and internal material must remain outside this
repository or under the ignored `.research-private/` directory.
## License
Code and original public content in this repository are available under the MIT
License unless an individual asset or linked external site states otherwise.
+14
View File
@@ -0,0 +1,14 @@
import { defineConfig } from "astro/config";
export default defineConfig({
site: "https://blog.k1412.top",
output: "static",
build: {
format: "directory"
},
markdown: {
shikiConfig: {
theme: "github-dark"
}
}
});
@@ -0,0 +1,94 @@
---
{
"title": "技术研究如何形成可验证的公开成果",
"summary": "从问题地图、证据台账和边界实验出发,把资料收集转化为可理解、可复现、可维护的公开研究。",
"date": "2026-08-10",
"updated": "2026-08-10",
"topic": "research-method",
"tags": ["evidence", "experiments", "publishing", "research"],
"kind": "article",
"status": "published",
"visibility": "public",
"canonicalUrl": "https://blog.k1412.top/articles/research-publishing-method/",
"sourceRepo": "https://git.k1412.top/wuyang/research-blog"
}
---
# 技术研究如何形成可验证的公开成果
## 研究对象先于资料目录
一个技术主题通常同时包含历史、机制、工程实现、实验结果和产品判断。直接从搜索结果开始写,文章很容易变成来源列表:材料很多,读者却不知道它们共同回答了什么问题。
研究开始时应先建立问题地图。至少写清楚研究对象、时间快照、适用边界、预期读者,以及研究最终要支持的理解或决策。宽泛领域再拆成少量相互关联的专题,每个专题都有自己的核心问题和完成证据。
问题地图不是固定目录。新证据可以改变专题关系,也可以推翻最初假设;但每次改变都要能解释原因。
## 证据台账连接判断与来源
文章中的重要判断需要一条可追溯链路。证据台账为每条判断记录来源、版本、时间、直接支持范围、限制和公开状态。它还要区分不同证据类型:
| 证据类型 | 能说明什么 | 不能替代什么 |
|---|---|---|
| 论文、规范与官方文档 | 作者或机构正式声明的设计、方法和结果 | 当前实现与真实运行状态 |
| 源码与版本记录 | 某一版本实际实现的接口和路径 | 线上部署已启用该实现 |
| 运行与实验记录 | 特定环境、输入和配置下发生的结果 | 未覆盖场景和长期生产表现 |
| 二手分析 | 背景、线索和不同解释 | 关键事实的一手证明 |
| 推断与方案 | 基于证据形成的判断或下一步 | 已经验证的事实 |
这层区分能避免常见错位:把设计文档写成现网事实、把静态检查写成端到端结果、把一个配置的成绩推广成整个系统的能力。
## 机制解释建立因果模型
技术文章需要回答“为什么”,而不只回答“是什么”。有效的机制解释通常包含四部分:输入条件、内部变化、可观察结果和失败条件。
例如,讨论推理优化时,单独给出吞吐提升比例并不足够。还需要说明优化改变了计算、内存、调度还是量化路径;基线与候选使用什么硬件、模型和请求形态;延迟边界从哪里开始计算;质量是否使用同一数据与指标。只有这些条件固定后,读者才能判断结果能否迁移到自己的场景。
具体例子和图可以显著降低理解成本。架构图适合说明组件关系,时序图适合说明事件顺序,状态图适合说明恢复和边界,统计图适合说明规模与变化。概念插画可以建立直觉,但精确标签、数字和方向仍要由正文或确定性图形承载。
## 比较之前先统一口径
技术比较最容易在表格完成后失真。不同系统可能使用不同模型、数据、预算、重试次数、成功定义或统计分母。把这些数字放进同一排行,会产生一种并不存在的精确性。
比较前应固定以下维度:
1. 任务与成功定义;
2. 模型、工具和系统边界;
3. 数据版本与污染假设;
4. 提示、预算、重试和随机种子;
5. 指标、分母、聚合方式与不确定性;
6. 硬件、延迟边界和成本边界。
无法统一时,应解释差异并停止排名。承认“目前不可直接比较”比给出误导性的顺序更有价值。
## 小样本实验验证链路和评分
能实验的问题不应停在方案。第一次实验的目标通常不是取得最终成绩,而是确认输入、执行、记录、评分和归因链路都可信。
实验协议应在运行前定义:假设、基线、候选、控制变量、代表样本、指标、通过阈值、环境版本、停止条件和扩大规则。对评测或 Agent 任务,先运行少量能覆盖关键路径的案例;保留失败样本和原始过程,而不是只保留平均分。
结果写作也要保持边界。小样本只能证明该样本与环境下的现象;烟测只能证明链路工作;外部服务耗时要与系统自身耗时分开。未运行的部分继续标为计划或估计。
## 编辑把完整性转化为可读性
完整研究不等于把所有材料放进正文。正文应保留形成因果模型所需的证据、例子、比较和限制;搜索记录、长摘录、中间脚本和原始输出进入研究工作区。
深度文章可以很长,但每节只推进一个问题。标题直接描述内容,不使用教学口号或作者的内部工作提示。新术语在第一次出现时解释,关键数字带上单位、分母、版本和时间。读者不需要经历研究者的所有过程,但必须能复核研究者的关键判断。
## 发布是一个可验证的版本
公开发布至少要建立一条版本映射:
```text
公开源码提交 → 成功构建 → 不可变镜像 → 线上部署 → 页面内容标记
```
在这条链路之外,还需要内容边界审查、来源链接检查、桌面和移动端阅读检查、图形渲染检查、HTTPS 和关键资源检查。HTTP 状态码只能证明服务器返回了内容,不能证明文章、视觉和源码发布正确。
当研究需要独立的交互地图、课程或实验室时,可以使用单独站点;中央博客保留清晰目录和来源入口。普通文章则保持稳定的 Markdown 源和文章 URL,让后续证据更新可以在同一条研究脉络中继续发生。
## 限制与维护
公开文章永远是某个时间点的证据快照。软件版本、排行榜、价格和组织状态会变化;实验也可能只覆盖有限环境。文章应保留更新时间和适用条件,在证据改变时更新、补充新版本,或明确标记旧结论已经被取代。
一项研究真正完成的标志,不是材料被收集完,而是读者能理解判断如何形成、在哪些条件下成立、如何复核,以及下一项最有价值的不确定性是什么。
+18
View File
@@ -0,0 +1,18 @@
---
{
"title": "Agent 到底凭什么说“完成了”",
"summary": "从任务结果、执行轨迹和失败行为出发,解释 Agent 完成声明需要哪些可验证证据。",
"date": "2026-07-27",
"updated": "2026-08-10",
"topic": "agent-systems",
"tags": ["agents", "evaluation", "evidence"],
"kind": "site",
"status": "published",
"visibility": "public",
"canonicalUrl": "https://agent-eval.k1412.top",
"sourceRepo": "https://git.k1412.top/wuyang/agent",
"preview": "/previews/agent-eval.jpg"
}
---
独立专题站。中央博客保留索引,交互内容以原站为准。
+17
View File
@@ -0,0 +1,17 @@
---
{
"title": "Agent 系统综述 2026:边界、构件、前沿与落地",
"summary": "以系统边界、核心构件、研究前沿和工程落地为主线组织 Agent 领域全貌。",
"date": "2026-07-28",
"updated": "2026-08-10",
"topic": "agent-systems",
"tags": ["agents", "architecture", "landscape"],
"kind": "site",
"status": "published",
"visibility": "public",
"canonicalUrl": "https://agent-landscape-2026.k1412.top",
"preview": "/previews/agent-landscape-2026.jpg"
}
---
独立专题站。历史源码尚未迁入中央公开仓库。
+17
View File
@@ -0,0 +1,17 @@
---
{
"title": "Agent 系统研究综述 2026:系统、生态、前沿与实践",
"summary": "连接 Agent 系统架构、生态项目、研究方向和实践约束的长篇研究综述。",
"date": "2026-07-28",
"updated": "2026-08-10",
"topic": "agent-systems",
"tags": ["agents", "ecosystem", "research"],
"kind": "site",
"status": "published",
"visibility": "public",
"canonicalUrl": "https://agent-review-2026.k1412.top",
"preview": "/previews/agent-review-2026.jpg"
}
---
独立专题站。历史源码尚未迁入中央公开仓库。
+17
View File
@@ -0,0 +1,17 @@
---
{
"title": "Astra 的十项数学结果:答案、正确性与理解开始脱钩",
"summary": "从十项数学结果、验证流程和人类理解成本分析 AI 辅助数学研究的新阶段。",
"date": "2026-08-06",
"updated": "2026-08-10",
"topic": "ai-research",
"tags": ["ai-math", "astra", "verification"],
"kind": "site",
"status": "published",
"visibility": "public",
"canonicalUrl": "https://astra-math.k1412.top",
"preview": "/previews/astra-math.jpg"
}
---
独立研究文章站。中央博客保留统一入口。
+17
View File
@@ -0,0 +1,17 @@
---
{
"title": "27B 推理优化:从基线到生产配置",
"summary": "记录 27B 模型在 llama.cpp 路径上的方案变化、三项速度指标和生产配置取舍。",
"date": "2026-07-28",
"updated": "2026-08-10",
"topic": "llm-systems",
"tags": ["inference", "llama.cpp", "optimization"],
"kind": "site",
"status": "published",
"visibility": "public",
"canonicalUrl": "https://llama-cpp-report.k1412.top",
"preview": "/previews/llama-cpp-report.jpg"
}
---
独立报告站。历史源码尚未迁入中央公开仓库。
+18
View File
@@ -0,0 +1,18 @@
---
{
"title": "LLM 技术图谱:从发展脉络到关键机制",
"summary": "以 Kimi K3 技术报告为锚点,组织语言模型发展、训练、推理和 DeepSeek 系列的专题课程。",
"date": "2026-07-30",
"updated": "2026-08-10",
"topic": "llm-systems",
"tags": ["deepseek", "history", "llm"],
"kind": "site",
"status": "published",
"visibility": "public",
"canonicalUrl": "https://llm-atlas.k1412.top",
"sourceRepo": "https://git.k1412.top/wuyang/llm-atlas",
"preview": "/previews/llm-atlas.jpg"
}
---
独立课程站。源码在单独的公开仓库中持续维护。
+17
View File
@@ -0,0 +1,17 @@
---
{
"title": "模型记忆的分水岭,不是容量,而是写入权",
"summary": "从写入权、更新语义和长期状态治理重新理解模型记忆系统的关键边界。",
"date": "2026-08-07",
"updated": "2026-08-10",
"topic": "llm-systems",
"tags": ["agents", "architecture", "memory"],
"kind": "site",
"status": "published",
"visibility": "public",
"canonicalUrl": "https://llm-memory.k1412.top",
"preview": "/previews/llm-memory.jpg"
}
---
独立研究文章站。中央博客保留统一入口。
+26
View File
@@ -0,0 +1,26 @@
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
location = /healthz {
access_log off;
add_header Content-Type text/plain;
return 200 'ok\n';
}
location /_astro/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable";
}
location / {
try_files $uri $uri/ $uri/index.html =404;
add_header Cache-Control "public, max-age=300";
}
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header X-Frame-Options SAMEORIGIN always;
}
+5275
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
{
"name": "k1412-research-blog",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "astro dev --host 127.0.0.1 --port 4321",
"preview": "astro preview --host 127.0.0.1 --port 4321",
"validate": "node scripts/validate-content.mjs",
"check": "npm run validate && astro check",
"build": "npm run check && astro build && node scripts/verify-dist.mjs"
},
"dependencies": {
"astro": "7.2.0"
},
"devDependencies": {
"@astrojs/check": "0.9.10",
"typescript": "6.0.3"
}
}
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="32" fill="#172429"/>
<path d="M18 15v34h7V35l14 14h9L31 32l16-17h-9L25 29V15z" fill="#f0ede5"/>
<circle cx="47" cy="17" r="5" fill="#b2482d"/>
</svg>

After

Width:  |  Height:  |  Size: 250 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630">
<rect width="1200" height="630" fill="#f0ede5"/>
<path d="M0 505C260 380 340 590 603 426s372-155 597-77v281H0z" fill="#172429"/>
<circle cx="970" cy="150" r="93" fill="none" stroke="#b2482d" stroke-width="2"/>
<circle cx="970" cy="150" r="42" fill="#b2482d"/>
<path d="M79 93h315" stroke="#172429" opacity=".25"/>
<text x="80" y="205" fill="#b2482d" font-family="Arial, sans-serif" font-size="25" letter-spacing="7">K1412 / OPEN RESEARCH</text>
<text x="80" y="310" fill="#172429" font-family="Georgia, serif" font-size="86">K1412 Research</text>
<text x="80" y="379" fill="#627077" font-family="Arial, sans-serif" font-size="30">技术研究、实验记录与可验证的公开成果</text>
</svg>

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://blog.k1412.top/sitemap.xml
+51
View File
@@ -0,0 +1,51 @@
import { access, readFile, readdir } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { join } from "node:path";
const root = new URL("../", import.meta.url);
const contentRoot = new URL("../content/", import.meta.url);
const required = ["title", "summary", "date", "updated", "topic", "kind", "status", "visibility", "canonicalUrl"];
const forbidden = [
[/\/(?:Users|home|mnt)\/[^\s)\]>'"]+/, "private absolute path"],
[/\b(?:10\.(?:\d{1,3}\.){2}\d{1,3}|192\.168\.(?:\d{1,3}\.)\d{1,3}|100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.(?:\d{1,3}\.)\d{1,3})\b/, "private IPv4"],
[/(?:git\.n\.xiaomi\.com|coro\.doc|\.codex\/sessions|<codex_internal_context)/, "internal material"],
[/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, "private key"],
[/"?sourcePaths"?\s*:/, "Knowledge Hub private metadata"]
];
async function files(dir) {
const entries = await readdir(dir, { withFileTypes: true });
const base = fileURLToPath(dir);
return entries.flatMap((entry) => entry.isFile() && entry.name.endsWith(".md") ? [join(base, entry.name)] : []);
}
function parse(text, path) {
const match = text.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!match) throw new Error(`${path}: invalid frontmatter delimiters`);
return { data: JSON.parse(match[1]), body: match[2].trim() };
}
const paths = [...await files(new URL("posts/", contentRoot)), ...await files(new URL("sites/", contentRoot))];
const errors = [];
const urls = new Set();
for (const path of paths) {
const text = await readFile(path, "utf8");
let parsed;
try { parsed = parse(text, path); } catch (error) { errors.push(error.message); continue; }
for (const key of required) if (!parsed.data[key]) errors.push(`${path}: missing ${key}`);
if (parsed.data.visibility !== "public") errors.push(`${path}: visibility must be public`);
if (!/^https:\/\//.test(parsed.data.canonicalUrl ?? "")) errors.push(`${path}: canonicalUrl must use HTTPS`);
if (urls.has(parsed.data.canonicalUrl)) errors.push(`${path}: duplicate canonicalUrl`);
urls.add(parsed.data.canonicalUrl);
if (parsed.data.kind === "article" && !parsed.body) errors.push(`${path}: empty article body`);
if (parsed.data.preview?.startsWith("/")) {
try { await access(new URL(`../public${parsed.data.preview}`, import.meta.url)); }
catch { errors.push(`${path}: missing preview asset ${parsed.data.preview}`); }
}
for (const [pattern, label] of forbidden) if (pattern.test(text)) errors.push(`${path}: forbidden ${label}`);
}
if (errors.length) {
console.error(errors.join("\n"));
process.exit(1);
}
console.log(`valid: ${paths.length} public content entries at ${root.pathname}`);
+27
View File
@@ -0,0 +1,27 @@
import { access, readFile, readdir } from "node:fs/promises";
import { join } from "node:path";
const root = new URL("../dist/", import.meta.url);
const required = ["index.html", "archive/index.html", "method/index.html", "articles/research-publishing-method/index.html", "rss.xml", "sitemap.xml"];
for (const path of required) await access(new URL(path, root));
async function walk(path) {
const entries = await readdir(path, { withFileTypes: true });
const found = [];
for (const entry of entries) {
const child = join(path, entry.name);
if (entry.isDirectory()) found.push(...await walk(child));
else if (/\.(?:html|xml|css|js|svg)$/.test(entry.name)) found.push(child);
}
return found;
}
const files = await walk(root.pathname);
const forbidden = /(?:git\.n\.xiaomi\.com|coro\.doc|\.codex\/sessions|<codex_internal_context|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----)/;
for (const file of files) {
const text = await readFile(file, "utf8");
if (forbidden.test(text)) throw new Error(`private material found in ${file}`);
}
const index = await readFile(new URL("index.html", root), "utf8");
if (!index.includes("K1412-RESEARCH-BLOG-20260810")) throw new Error("release marker missing");
console.log(`verified: ${files.length} rendered text assets and release marker`);
+93
View File
@@ -0,0 +1,93 @@
---
interface Item {
id: string;
data: {
title: string;
summary: string;
topic: string;
date: string;
canonicalUrl: string;
preview?: string;
sourceRepo?: string;
kind: "article" | "site";
};
}
interface Props { items: Item[] }
const { items } = Astro.props;
const topicLabels: Record<string, string> = {
"agent-systems": "Agent 系统",
"llm-systems": "LLM 系统",
"ai-research": "AI 研究",
"research-method": "研究方法"
};
const topics = [...new Set(items.map((item) => item.data.topic))];
const linkFor = (item: Item) => item.data.kind === "article" ? `/articles/${item.id}/` : item.data.canonicalUrl;
---
<section class="research-shell" id="research">
<aside class="research-rail">
<p class="eyebrow">INDEX / 2026</p>
<h2>研究索引</h2>
<p>文章、实验报告与独立交互专题按同一条研究脉络组织。</p>
<div class="topic-filters" role="group" aria-label="按主题筛选">
<button class="topic-filter is-active" data-topic="all" type="button">全部 <span>{items.length}</span></button>
{topics.map((topic) => (
<button class="topic-filter" data-topic={topic} type="button">
{topicLabels[topic] ?? topic}
<span>{items.filter((item) => item.data.topic === topic).length}</span>
</button>
))}
</div>
</aside>
<div class="research-list" aria-live="polite">
{items.map((item, index) => (
<article class="research-entry" data-entry data-topic={item.data.topic}>
<span class="entry-number">{String(index + 1).padStart(2, "0")}</span>
<div class="entry-copy">
<div class="entry-meta">
<span>{topicLabels[item.data.topic] ?? item.data.topic}</span>
<span>{item.data.date}</span>
<span>{item.data.kind === "site" ? "独立专题" : "文章"}</span>
</div>
<h3><a href={linkFor(item)}>{item.data.title}</a></h3>
<p>{item.data.summary}</p>
<div class="entry-links">
<a class="text-link" href={linkFor(item)}>打开研究 <span aria-hidden="true">↗</span></a>
{item.data.sourceRepo && <a class="muted-link" href={item.data.sourceRepo}>源码</a>}
</div>
</div>
{item.data.preview ? (
<a class="entry-preview" href={linkFor(item)} aria-label={`打开 ${item.data.title}`}>
<img src={item.data.preview} alt="" loading="lazy" width="640" height="360" />
</a>
) : (
<a class={`entry-abstract topic-${item.data.topic}`} href={linkFor(item)} aria-hidden="true" tabindex="-1">
<span></span><span></span><span></span>
</a>
)}
</article>
))}
<p class="empty-state" hidden data-empty>这个主题暂时没有公开内容。</p>
</div>
</section>
<script>
const buttons = Array.from(document.querySelectorAll<HTMLButtonElement>("[data-topic].topic-filter"));
const entries = Array.from(document.querySelectorAll<HTMLElement>("[data-entry]"));
const empty = document.querySelector<HTMLElement>("[data-empty]");
for (const button of buttons) {
button.addEventListener("click", () => {
const topic = button.dataset.topic ?? "all";
for (const candidate of buttons) candidate.classList.toggle("is-active", candidate === button);
let visible = 0;
for (const entry of entries) {
const show = topic === "all" || entry.dataset.topic === topic;
entry.hidden = !show;
if (show) visible += 1;
}
if (empty) empty.hidden = visible !== 0;
});
}
</script>
+47
View File
@@ -0,0 +1,47 @@
<div class="orbit" aria-label="研究从问题、证据、实验到公开成果的闭环示意图">
<svg viewBox="0 0 720 520" role="img" aria-labelledby="orbit-title orbit-desc">
<title id="orbit-title">研究闭环</title>
<desc id="orbit-desc">问题地图连接来源核验、机制解释、实验比较和公开成果,验证结果再回到问题地图。</desc>
<defs>
<linearGradient id="arc" x1="0" x2="1">
<stop offset="0" stop-color="#b2482d" />
<stop offset="1" stop-color="#183c49" />
</linearGradient>
<filter id="glow" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="8" />
</filter>
</defs>
<circle class="orbit-glow" cx="360" cy="258" r="150" />
<ellipse class="orbit-path path-one" cx="360" cy="258" rx="272" ry="160" />
<ellipse class="orbit-path path-two" cx="360" cy="258" rx="168" ry="264" transform="rotate(58 360 258)" />
<path class="feedback" d="M150 360 C155 458 315 486 398 430" />
<path class="feedback-arrow" d="m392 418 9 13-15 4" />
<g class="orbit-core">
<circle cx="360" cy="258" r="92" />
<text x="360" y="247" text-anchor="middle">可验证的</text>
<text x="360" y="278" text-anchor="middle">公开研究</text>
</g>
<g class="orbit-node node-a" transform="translate(104 124)">
<circle r="7" />
<text x="17" y="6">问题地图</text>
</g>
<g class="orbit-node node-b" transform="translate(564 117)">
<circle r="7" />
<text x="-17" y="6" text-anchor="end">来源核验</text>
</g>
<g class="orbit-node node-c" transform="translate(628 327)">
<circle r="7" />
<text x="-17" y="6" text-anchor="end">机制解释</text>
</g>
<g class="orbit-node node-d" transform="translate(400 474)">
<circle r="7" />
<text x="17" y="6">实验比较</text>
</g>
<g class="orbit-node node-e" transform="translate(122 373)">
<circle r="7" />
<text x="17" y="6">边界与反例</text>
</g>
</svg>
</div>
+29
View File
@@ -0,0 +1,29 @@
import { defineCollection } from "astro:content";
import { glob } from "astro/loaders";
import { z } from "astro/zod";
const common = z.object({
title: z.string(),
summary: z.string(),
date: z.string(),
updated: z.string(),
topic: z.string(),
tags: z.array(z.string()).default([]),
status: z.enum(["draft", "published", "archived"]),
visibility: z.literal("public"),
canonicalUrl: z.url(),
sourceRepo: z.url().optional(),
preview: z.string().optional()
});
const posts = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./content/posts" }),
schema: common.extend({ kind: z.literal("article") })
});
const sites = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./content/sites" }),
schema: common.extend({ kind: z.literal("site") })
});
export const collections = { posts, sites };
+62
View File
@@ -0,0 +1,62 @@
---
import "../styles/global.css";
interface Props {
title?: string;
description?: string;
canonical?: string;
article?: boolean;
}
const {
title = "K1412 Research",
description = "技术研究、实验记录与可验证的公开成果。",
canonical = Astro.url.href,
article = false
} = Astro.props;
const pageTitle = title === "K1412 Research" ? title : `${title} · K1412 Research`;
---
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<meta name="description" content={description} />
<meta name="theme-color" content="#f0ede5" />
<meta name="generator" content={Astro.generator} />
<link rel="canonical" href={canonical} />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="alternate" type="application/rss+xml" title="K1412 Research" href="/rss.xml" />
<meta property="og:type" content={article ? "article" : "website"} />
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical} />
<meta property="og:image" content="https://blog.k1412.top/og-default.svg" />
<title>{pageTitle}</title>
</head>
<body>
<div class="site-grain" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/" aria-label="K1412 Research 首页">
<span class="brand-mark" aria-hidden="true">K</span>
<span>K1412 <b>Research</b></span>
</a>
<nav aria-label="主导航">
<a href="/#research">研究</a>
<a href="/archive/">索引</a>
<a href="/method/">方法</a>
<a href="https://git.k1412.top/wuyang/research-blog">源码</a>
</nav>
</header>
<main>
<slot />
</main>
<footer class="site-footer">
<p>K1412 Research</p>
<p>公开研究、可复现实验与独立专题站的统一入口。</p>
<p><a href="https://git.k1412.top/wuyang/research-blog">Source on git.k1412.top</a></p>
</footer>
<div class="release-marker" data-release="K1412-RESEARCH-BLOG-20260810" hidden></div>
</body>
</html>
+28
View File
@@ -0,0 +1,28 @@
---
import { getCollection } from "astro:content";
import BaseLayout from "../layouts/BaseLayout.astro";
const posts = await getCollection("posts");
const sites = await getCollection("sites");
const rows = [...posts, ...sites].sort((a, b) => b.data.date.localeCompare(a.data.date));
---
<BaseLayout title="公开研究索引" description="K1412 Research 的文章、实验报告与独立专题站索引。">
<section class="archive-page">
<header class="page-intro">
<p class="eyebrow">ARCHIVE</p>
<h1>公开研究索引</h1>
<p>按发布日期记录文章与独立专题站。草稿保留在源码中,不进入公开索引。</p>
</header>
<div class="archive-table">
{rows.filter((row) => row.data.status === "published").map((row) => (
<a class="archive-row" href={row.data.canonicalUrl}>
<time>{row.data.date}</time>
<strong>{row.data.title}</strong>
<span>{row.data.kind === "site" ? "独立专题" : "文章"}</span>
<i aria-hidden="true">↗</i>
</a>
))}
</div>
</section>
</BaseLayout>
+36
View File
@@ -0,0 +1,36 @@
---
import { getCollection, render, type CollectionEntry } from "astro:content";
import BaseLayout from "../../layouts/BaseLayout.astro";
export async function getStaticPaths() {
const posts = await getCollection("posts", ({ data }) => data.status === "published");
return posts.map((post) => ({ params: { id: post.id }, props: { post } }));
}
interface Props { post: CollectionEntry<"posts"> }
const { post } = Astro.props;
const { Content, headings } = await render(post);
---
<BaseLayout title={post.data.title} description={post.data.summary} canonical={post.data.canonicalUrl} article>
<article class="article-page">
<header class="article-header">
<a class="back-link" href="/">← 返回研究索引</a>
<div class="article-kicker"><span>{post.data.topic}</span><span>{post.data.date}</span><span>更新 {post.data.updated}</span></div>
<h1>{post.data.title}</h1>
<p>{post.data.summary}</p>
<div class="tag-row">{post.data.tags.map((tag) => <span>{tag}</span>)}</div>
</header>
<div class="article-grid">
<aside class="article-outline" aria-label="文章目录">
<p>本文目录</p>
<ol>
{headings.filter((heading) => heading.depth === 2).map((heading) => (
<li><a href={`#${heading.slug}`}>{heading.text}</a></li>
))}
</ol>
</aside>
<div class="prose"><Content /></div>
</div>
</article>
</BaseLayout>
+47
View File
@@ -0,0 +1,47 @@
---
import { getCollection } from "astro:content";
import BaseLayout from "../layouts/BaseLayout.astro";
import ResearchOrbit from "../components/ResearchOrbit.astro";
import ResearchIndex from "../components/ResearchIndex.astro";
const posts = await getCollection("posts", ({ data }) => data.status === "published");
const sites = await getCollection("sites", ({ data }) => data.status === "published");
const items = [...posts, ...sites].sort((a, b) => b.data.date.localeCompare(a.data.date));
---
<BaseLayout>
<section class="hero">
<div class="hero-copy">
<p class="eyebrow">K1412 / OPEN RESEARCH ARCHIVE</p>
<h1>技术研究,<br /><em>从证据走向理解。</em></h1>
<p class="hero-intro">这里收录经过来源核验、机制梳理与实验检验的技术文章,也保留需要独立交互形态的专题研究站。</p>
<div class="hero-actions">
<a class="primary-action" href="#research">浏览研究</a>
<a class="secondary-action" href="/method/">研究方法</a>
</div>
<dl class="hero-stats">
<div><dt>{items.length}</dt><dd>公开成果</dd></div>
<div><dt>{new Set(items.map((item) => item.data.topic)).size}</dt><dd>研究主题</dd></div>
<div><dt>2026</dt><dd>当前快照</dd></div>
</dl>
</div>
<ResearchOrbit />
</section>
<section class="thesis-band" aria-label="研究原则">
<p>问题地图</p><span>→</span><p>一手来源</p><span>→</span><p>机制解释</p><span>→</span><p>边界实验</p><span>→</span><p>公开成果</p>
</section>
<ResearchIndex items={items} />
<section class="method-callout">
<div>
<p class="eyebrow">METHOD / EVIDENCE</p>
<h2>文章不是搜索结果的重排。</h2>
</div>
<div>
<p>每项研究先建立问题与证据台账,再区分来源声称、源码观察、运行证据、实验结果和推断。能实验的问题先做小样本验证;不能验证的部分保留边界和不确定性。</p>
<a class="text-link" href="/articles/research-publishing-method/">阅读全文 <span aria-hidden="true">→</span></a>
</div>
</section>
</BaseLayout>
+22
View File
@@ -0,0 +1,22 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="研究与发布方法" description="K1412 Research 的证据、实验、编辑、可视化与公开发布方法。">
<article class="method-page">
<header class="page-intro">
<p class="eyebrow">METHOD</p>
<h1>研究与发布方法</h1>
<p>公开内容从问题地图开始,经来源核验、机制解释、边界实验和编辑审查后发布。</p>
</header>
<div class="method-steps">
<section><span>01</span><h2>问题地图</h2><p>先定义要支持的理解或决策,再划分主题、边界、时间快照和成功证据。</p></section>
<section><span>02</span><h2>证据台账</h2><p>为关键判断记录来源、版本、支持范围、限制和公开状态。搜索结果不直接成为文章结构。</p></section>
<section><span>03</span><h2>机制与比较</h2><p>先用普通语言建立因果模型,再在统一任务、版本、分母和指标下比较方案。</p></section>
<section><span>04</span><h2>边界实验</h2><p>能实验的问题先做小规模代表验证,保存环境、命令、原始结果、失败样本和归因。</p></section>
<section><span>05</span><h2>编辑与视觉</h2><p>正文完整但易读;图分别承担拓扑、顺序、状态、比较、数量或原理直觉中的一种任务。</p></section>
<section><span>06</span><h2>公开发布</h2><p>内容审查、构建、桌面与移动验收、公开源码、不可变镜像、HTTPS 和内容标记共同组成发布证据。</p></section>
</div>
<a class="primary-action" href="/articles/research-publishing-method/">阅读完整方法文章</a>
</article>
</BaseLayout>
+15
View File
@@ -0,0 +1,15 @@
import { getCollection } from "astro:content";
function escapeXml(value: string) {
return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
}
export async function GET() {
const posts = await getCollection("posts", ({ data }) => data.status === "published");
const items = posts
.sort((a, b) => b.data.date.localeCompare(a.data.date))
.map((post) => `<item><title>${escapeXml(post.data.title)}</title><link>${escapeXml(post.data.canonicalUrl)}</link><guid>${escapeXml(post.data.canonicalUrl)}</guid><pubDate>${new Date(`${post.data.date}T00:00:00+08:00`).toUTCString()}</pubDate><description>${escapeXml(post.data.summary)}</description></item>`)
.join("");
const xml = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>K1412 Research</title><link>https://blog.k1412.top</link><description>技术研究、实验记录与可验证的公开成果。</description>${items}</channel></rss>`;
return new Response(xml, { headers: { "Content-Type": "application/rss+xml; charset=utf-8" } });
}
+18
View File
@@ -0,0 +1,18 @@
import { getCollection } from "astro:content";
const localPages = ["/", "/archive/", "/method/"];
function escapeXml(value: string) {
return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
}
export async function GET() {
const posts = await getCollection("posts", ({ data }) => data.status === "published");
const urls = [
...localPages.map((path) => ({ loc: new URL(path, "https://blog.k1412.top").href, lastmod: "2026-08-10" })),
...posts.map((post) => ({ loc: post.data.canonicalUrl, lastmod: post.data.updated }))
];
const entries = urls.map(({ loc, lastmod }) => `<url><loc>${escapeXml(loc)}</loc><lastmod>${escapeXml(lastmod)}</lastmod></url>`).join("");
const xml = `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${entries}</urlset>`;
return new Response(xml, { headers: { "Content-Type": "application/xml; charset=utf-8" } });
}
+229
View File
@@ -0,0 +1,229 @@
:root {
color-scheme: light;
--paper: #f0ede5;
--paper-deep: #e4dfd2;
--ink: #172429;
--muted: #627077;
--line: rgba(23, 36, 41, 0.18);
--rust: #b2482d;
--teal: #183c49;
--acid: #d3e2a4;
--max: 1440px;
--serif: Iowan Old Style, Songti SC, STSong, Noto Serif CJK SC, Georgia, serif;
--sans: Inter, SF Pro Display, PingFang SC, Microsoft YaHei, system-ui, sans-serif;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; background: var(--paper); }
body { margin: 0; color: var(--ink); background: var(--paper); font-family: var(--sans); line-height: 1.65; }
a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
img, svg { display: block; max-width: 100%; }
button, input { font: inherit; }
::selection { background: var(--acid); color: var(--ink); }
.site-grain { position: fixed; inset: 0; pointer-events: none; opacity: .18; z-index: 10; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.18'/%3E%3C/svg%3E"); mix-blend-mode: multiply; }
.site-header { width: min(calc(100% - 48px), var(--max)); margin: 0 auto; min-height: 88px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); }
.brand { display: inline-flex; align-items: center; gap: 12px; font-size: 13px; letter-spacing: .12em; text-decoration: none; text-transform: uppercase; }
.brand b { color: var(--rust); font-weight: 600; }
.brand-mark { display: grid; place-items: center; width: 32px; height: 32px; border: 1px solid var(--ink); border-radius: 50%; font-family: var(--serif); font-size: 19px; font-style: italic; }
.site-header nav { display: flex; gap: 28px; }
.site-header nav a { color: var(--muted); font-size: 13px; text-decoration: none; }
.site-header nav a:hover, .site-header nav a:focus-visible { color: var(--rust); }
.eyebrow { margin: 0 0 20px; color: var(--rust); font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; }
.hero { width: min(calc(100% - 48px), var(--max)); min-height: 720px; margin: 0 auto; display: grid; grid-template-columns: minmax(0, 1fr) minmax(420px, .9fr); align-items: center; gap: 36px; }
.hero-copy { padding: 76px 0 64px; }
.hero h1 { max-width: 780px; margin: 0; font-family: var(--serif); font-size: clamp(62px, 7vw, 112px); font-weight: 400; line-height: .94; letter-spacing: -.055em; }
.hero h1 em { color: var(--rust); font-weight: 400; }
.hero h1 em { white-space: nowrap; }
.hero-intro { max-width: 610px; margin: 42px 0 0; color: var(--muted); font-size: 18px; }
.hero-actions { display: flex; gap: 14px; margin-top: 40px; }
.primary-action, .secondary-action { display: inline-flex; min-height: 48px; align-items: center; justify-content: center; padding: 0 24px; border: 1px solid var(--ink); text-decoration: none; font-size: 13px; font-weight: 700; letter-spacing: .08em; }
.primary-action { color: var(--paper); background: var(--ink); }
.primary-action:hover { background: var(--rust); border-color: var(--rust); }
.secondary-action:hover { color: var(--paper); background: var(--teal); border-color: var(--teal); }
.hero-stats { display: flex; gap: 42px; margin: 66px 0 0; }
.hero-stats div { min-width: 92px; }
.hero-stats dt { font-family: var(--serif); font-size: 34px; line-height: 1; }
.hero-stats dd { margin: 8px 0 0; color: var(--muted); font-size: 12px; }
.orbit { position: relative; min-height: 580px; display: grid; place-items: center; }
.orbit svg { width: min(100%, 680px); overflow: visible; }
.orbit-glow { fill: rgba(178, 72, 45, .12); filter: url(#glow); }
.orbit-path { fill: none; stroke: url(#arc); stroke-width: 1.2; stroke-dasharray: 3 8; opacity: .65; }
.path-two { stroke-dasharray: 1 9; }
.feedback, .feedback-arrow { fill: none; stroke: var(--rust); stroke-width: 1.6; }
.orbit-core circle { fill: var(--ink); }
.orbit-core text { fill: var(--paper); font-family: var(--serif); font-size: 22px; }
.orbit-node circle { fill: var(--rust); }
.orbit-node text { fill: var(--ink); font-size: 15px; letter-spacing: .04em; }
@media (prefers-reduced-motion: no-preference) {
.path-one { animation: dash 28s linear infinite; }
.path-two { animation: dash 34s linear infinite reverse; }
.orbit-core { transform-origin: 360px 258px; animation: pulse 7s ease-in-out infinite; }
}
@keyframes dash { to { stroke-dashoffset: -220; } }
@keyframes pulse { 50% { transform: scale(1.035); } }
.thesis-band { min-height: 100px; display: flex; align-items: center; justify-content: center; gap: clamp(12px, 3vw, 42px); padding: 24px; color: var(--paper); background: var(--ink); overflow: hidden; }
.thesis-band p { margin: 0; white-space: nowrap; font-family: var(--serif); font-size: clamp(15px, 1.5vw, 21px); }
.thesis-band span { color: var(--rust); }
.research-shell { width: min(calc(100% - 48px), var(--max)); margin: 0 auto; padding: 120px 0 140px; display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: clamp(40px, 7vw, 112px); }
.research-rail, .research-list { min-width: 0; }
.research-rail { position: sticky; top: 34px; align-self: start; }
.research-rail h2 { margin: 0; font-family: var(--serif); font-size: 46px; font-weight: 400; }
.research-rail > p:not(.eyebrow) { color: var(--muted); font-size: 14px; }
.topic-filters { margin-top: 36px; border-top: 1px solid var(--line); }
.topic-filter { width: 100%; display: flex; justify-content: space-between; gap: 16px; padding: 13px 0; border: 0; border-bottom: 1px solid var(--line); color: var(--muted); background: transparent; text-align: left; cursor: pointer; }
.topic-filter span { font-family: var(--serif); }
.topic-filter:hover, .topic-filter.is-active { color: var(--rust); }
.research-entry { display: grid; grid-template-columns: 44px minmax(0, 1fr) minmax(220px, 34%); gap: 24px; align-items: center; min-height: 270px; padding: 32px 0; border-top: 1px solid var(--line); }
.research-entry:last-of-type { border-bottom: 1px solid var(--line); }
.entry-number { align-self: start; color: var(--rust); font-family: var(--serif); font-size: 18px; }
.entry-meta { display: flex; flex-wrap: wrap; gap: 12px; color: var(--muted); font-size: 10px; letter-spacing: .12em; text-transform: uppercase; }
.entry-meta span:not(:last-child)::after { content: "/"; padding-left: 12px; color: var(--line); }
.entry-copy h3 { max-width: 720px; margin: 15px 0 10px; font-family: var(--serif); font-size: clamp(28px, 3vw, 44px); font-weight: 400; line-height: 1.08; letter-spacing: -.025em; }
.entry-copy h3 a { text-decoration: none; }
.entry-copy h3 a:hover { color: var(--rust); }
.entry-copy > p { max-width: 690px; margin: 0; color: var(--muted); font-size: 14px; }
.entry-links { display: flex; align-items: center; gap: 22px; margin-top: 22px; }
.text-link, .muted-link { font-size: 12px; font-weight: 700; letter-spacing: .06em; }
.text-link { color: var(--rust); }
.muted-link { color: var(--muted); }
.entry-preview, .entry-abstract { min-height: 180px; border: 1px solid var(--line); overflow: hidden; text-decoration: none; }
.entry-preview img { width: 100%; height: 100%; min-height: 180px; object-fit: cover; filter: saturate(.78) contrast(.96); transition: transform .5s ease, filter .5s ease; }
.entry-preview:hover img { transform: scale(1.025); filter: saturate(1); }
.entry-abstract { position: relative; display: block; background: var(--paper-deep); }
.entry-abstract span { position: absolute; display: block; border: 1px solid currentColor; border-radius: 50%; opacity: .7; }
.entry-abstract span:nth-child(1) { width: 160px; height: 160px; left: 15%; top: 12%; }
.entry-abstract span:nth-child(2) { width: 100px; height: 100px; right: 12%; bottom: 8%; }
.entry-abstract span:nth-child(3) { width: 1px; height: 130%; left: 56%; top: -15%; background: currentColor; transform: rotate(28deg); }
.topic-agent-systems { color: var(--rust); background: #dfd6c7; }
.topic-llm-systems { color: var(--teal); background: #cbd6d1; }
.topic-ai-research { color: #6d6548; background: #dad5bd; }
.topic-research-method { color: #4e5962; background: #d8d9d5; }
.empty-state { color: var(--muted); }
.method-callout { width: min(calc(100% - 48px), var(--max)); margin: 0 auto 130px; padding: 70px; display: grid; grid-template-columns: 1fr 1fr; gap: 80px; color: var(--paper); background: var(--teal); }
.method-callout .eyebrow { color: var(--acid); }
.method-callout h2 { max-width: 560px; margin: 0; font-family: var(--serif); font-size: clamp(40px, 5vw, 72px); font-weight: 400; line-height: 1; }
.method-callout > div:last-child { align-self: end; }
.method-callout > div:last-child p { max-width: 560px; color: rgba(240, 237, 229, .76); }
.method-callout .text-link { color: var(--acid); }
.site-footer { width: min(calc(100% - 48px), var(--max)); min-height: 150px; margin: 0 auto; display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 24px; align-items: center; border-top: 1px solid var(--line); color: var(--muted); font-size: 12px; }
.site-footer p:last-child { text-align: right; }
.page-intro { max-width: 900px; padding: 110px 0 72px; }
.page-intro h1 { margin: 0; font-family: var(--serif); font-size: clamp(56px, 7vw, 100px); font-weight: 400; line-height: .95; }
.page-intro > p:last-child { max-width: 700px; margin-top: 30px; color: var(--muted); font-size: 18px; }
.archive-page, .method-page { width: min(calc(100% - 48px), 1180px); margin: 0 auto 130px; }
.archive-table { border-top: 1px solid var(--line); }
.archive-row { display: grid; grid-template-columns: 130px 1fr 100px 24px; gap: 24px; align-items: center; min-height: 92px; border-bottom: 1px solid var(--line); text-decoration: none; }
.archive-row time, .archive-row span { color: var(--muted); font-size: 12px; }
.archive-row strong { font-family: var(--serif); font-size: 22px; font-weight: 400; }
.archive-row:hover strong { color: var(--rust); }
.method-steps { display: grid; grid-template-columns: repeat(2, 1fr); border-top: 1px solid var(--line); border-left: 1px solid var(--line); margin-bottom: 50px; }
.method-steps section { min-height: 280px; padding: 40px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.method-steps section > span { color: var(--rust); font-family: var(--serif); }
.method-steps h2 { margin: 48px 0 12px; font-family: var(--serif); font-size: 32px; font-weight: 400; }
.method-steps p { color: var(--muted); }
.article-page { width: min(calc(100% - 48px), 1320px); margin: 0 auto; }
.article-header { max-width: 1060px; padding: 80px 0 90px; }
.back-link { color: var(--muted); font-size: 12px; }
.article-kicker { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 60px; color: var(--rust); font-size: 10px; font-weight: 700; letter-spacing: .15em; text-transform: uppercase; }
.article-header h1 { margin: 26px 0 24px; font-family: var(--serif); font-size: clamp(54px, 7vw, 104px); font-weight: 400; line-height: .96; letter-spacing: -.045em; }
.article-header > p { max-width: 800px; color: var(--muted); font-size: 20px; }
.tag-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 30px; }
.tag-row span { padding: 6px 10px; border: 1px solid var(--line); font-size: 10px; }
.article-grid { display: grid; grid-template-columns: 220px minmax(0, 820px); gap: clamp(50px, 8vw, 110px); justify-content: center; padding: 78px 0 140px; border-top: 1px solid var(--line); }
.article-outline { position: sticky; top: 34px; align-self: start; }
.article-outline > p { color: var(--rust); font-size: 11px; font-weight: 700; letter-spacing: .12em; }
.article-outline ol { margin: 20px 0 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
.article-outline li { border-bottom: 1px solid var(--line); }
.article-outline a { display: block; padding: 12px 0; color: var(--muted); font-size: 12px; text-decoration: none; }
.article-outline a:hover { color: var(--rust); }
.prose { min-width: 0; font-family: var(--serif); font-size: 18px; line-height: 1.9; }
.prose h1 { display: none; }
.prose h2 { margin: 3.2em 0 .9em; font-size: 40px; font-weight: 400; line-height: 1.2; letter-spacing: -.025em; }
.prose h2:first-child { margin-top: 0; }
.prose h3 { margin-top: 2.3em; font-size: 25px; font-weight: 500; }
.prose p, .prose li { color: #2f3c41; }
.prose strong { color: var(--ink); }
.prose blockquote { margin: 2.2em 0; padding: 20px 26px; border-left: 3px solid var(--rust); background: rgba(178, 72, 45, .06); }
.prose pre { overflow-x: auto; padding: 22px; border-radius: 2px; font-size: 13px; line-height: 1.6; }
.prose code:not(pre code) { padding: .12em .35em; background: var(--paper-deep); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .88em; }
.prose table { width: 100%; margin: 2.2em 0; border-collapse: collapse; font-family: var(--sans); font-size: 13px; line-height: 1.55; }
.prose th, .prose td { padding: 12px 10px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
.prose th { color: var(--rust); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; }
.prose img, .prose svg { width: min(1100px, calc(100vw - 48px)); max-width: none; margin: 2.6em 0 2.6em 50%; transform: translateX(-50%); }
@media (max-width: 980px) {
.hero { grid-template-columns: 1fr; min-height: auto; }
.hero-copy { padding-bottom: 0; }
.orbit { min-height: 460px; }
.research-shell { grid-template-columns: minmax(0, 1fr); }
.research-rail { position: static; }
.topic-filters { display: flex; gap: 8px; overflow-x: auto; border: 0; padding-bottom: 8px; }
.topic-filter { width: auto; flex: 0 0 auto; gap: 10px; padding: 9px 12px; border: 1px solid var(--line); }
.research-entry { grid-template-columns: 36px minmax(0, 1fr); }
.entry-preview, .entry-abstract { grid-column: 2; }
.method-callout { grid-template-columns: 1fr; gap: 34px; padding: 50px; }
.article-grid { grid-template-columns: 1fr; }
.article-outline { position: static; }
.article-outline ol { display: grid; grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 981px) and (max-width: 1180px) {
.hero h1 { font-size: clamp(60px, 6.3vw, 74px); }
}
@media (max-width: 680px) {
.site-header { width: min(calc(100% - 28px), var(--max)); min-height: 72px; }
.site-header nav { gap: 14px; }
.site-header nav a:nth-child(2), .site-header nav a:nth-child(4) { display: none; }
.brand { letter-spacing: .05em; }
.hero, .research-shell, .method-callout, .site-footer, .article-page, .archive-page, .method-page { width: min(calc(100% - 28px), var(--max)); }
.hero-copy { padding-top: 62px; }
.hero h1 { font-size: clamp(51px, 16vw, 74px); }
.hero h1 em { white-space: normal; }
.hero-intro { margin-top: 28px; font-size: 16px; }
.hero-actions { flex-direction: column; align-items: stretch; }
.hero-stats { gap: 20px; margin-top: 44px; }
.hero-stats div { min-width: 0; }
.orbit { min-height: 370px; margin: 0 -14px; }
.thesis-band { justify-content: flex-start; overflow-x: auto; }
.research-shell { padding: 82px 0 96px; gap: 42px; }
.research-rail h2 { font-size: 40px; }
.research-entry { grid-template-columns: 26px minmax(0, 1fr); min-height: 0; padding: 28px 0; gap: 14px; }
.entry-copy h3 { font-size: 30px; }
.entry-preview, .entry-abstract { min-height: 150px; }
.method-callout { margin-bottom: 90px; padding: 36px 28px; }
.site-footer { grid-template-columns: 1fr; gap: 2px; padding: 36px 0; }
.site-footer p { margin: 2px 0; }
.site-footer p:last-child { text-align: left; }
.page-intro { padding: 72px 0 50px; }
.page-intro h1 { font-size: 54px; }
.archive-row { grid-template-columns: 80px 1fr 20px; gap: 12px; padding: 16px 0; }
.archive-row span { display: none; }
.archive-row strong { font-size: 18px; line-height: 1.25; }
.method-steps { grid-template-columns: 1fr; }
.method-steps section { min-height: 230px; padding: 28px; }
.article-header { padding: 54px 0 64px; }
.article-kicker { margin-top: 40px; }
.article-header h1 { font-size: 48px; }
.article-header > p { font-size: 17px; }
.article-grid { padding-top: 48px; gap: 40px; }
.article-outline ol { grid-template-columns: 1fr; }
.prose { font-size: 17px; line-height: 1.85; }
.prose h2 { font-size: 32px; }
.prose table { display: block; overflow-x: auto; white-space: nowrap; }
.prose img, .prose svg { width: calc(100vw - 28px); }
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
}
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}