Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
41ff269
feat(projects): refresh Glassbox and Zhihu project presentation
lora-sys Sep 14, 2026
4e7b856
feat(projects): curate Lora Skills as representative work
lora-sys Sep 14, 2026
36bcdce
feat(projects): elevate Lora Skills in portfolio curation
lora-sys Sep 14, 2026
8769504
feat(projects): link Lora Skills collection evidence
lora-sys Sep 14, 2026
8a665b7
fix(links): publish the current v3 avatar
lora-sys Sep 14, 2026
48c401c
fix(profile): stop showing the September 13 event as upcoming
lora-sys Sep 14, 2026
e63860b
refactor(now): establish one curated current-focus source
lora-sys Sep 14, 2026
13610c9
refactor(now): use the shared current-focus source
lora-sys Sep 14, 2026
d0a931d
refactor(now): share the same focus data across locales
lora-sys Sep 14, 2026
9cd0a38
fix(privacy): remove stale GoatCounter claim
lora-sys Sep 14, 2026
9e448ed
fix(seo): localize fallback descriptions
lora-sys Sep 14, 2026
b20c513
fix(seo): dedupe titles and correct structured profile data
lora-sys Sep 14, 2026
4b1733a
refactor(home): share project and current-focus curation, remove load…
lora-sys Sep 14, 2026
36ebf54
refactor(home): share English project and current-focus data
lora-sys Sep 14, 2026
7ffebb7
refactor(about): separate narrative from resume and activity records
lora-sys Sep 14, 2026
f37b6e5
refactor(about): keep English about focused on narrative and work
lora-sys Sep 14, 2026
bc8f27c
feat(notes): assign stable permalinks to short notes
lora-sys Sep 14, 2026
1adb4df
feat(notes): expose stable anchors and localized dates
lora-sys Sep 14, 2026
f12ac2d
fix(i18n): label Chinese-only writing archive in English footer
lora-sys Sep 14, 2026
edaa4de
fix(a11y): only expose haptics where vibration is supported
lora-sys Sep 14, 2026
8ac4399
fix(mobile): compact homepage section navigation
lora-sys Sep 14, 2026
ccb9e27
fix(a11y): describe tri-state theme control accurately
lora-sys Sep 14, 2026
0c3b4b8
test(release): require secondary and English routes
lora-sys Sep 14, 2026
47f4b03
fix(search): make language scope explicit
lora-sys Sep 14, 2026
77b3c91
feat(brand): add current social sharing card
lora-sys Sep 14, 2026
ddb5593
feat(brand): use the current social sharing card
lora-sys Sep 14, 2026
7b20346
fix(mobile): keep section navigation CSS portable
lora-sys Sep 14, 2026
d52df12
fix(tags): clarify that taxonomy applies to long-form writing
lora-sys Sep 14, 2026
334ca75
fix(i18n): localize friend-link copy feedback
lora-sys Sep 14, 2026
e9a8a9d
fix(a11y): handle undefined theme-cycle result
lora-sys Sep 14, 2026
5066a07
fix(haptics): avoid no-op controls on desktop browsers
lora-sys Sep 14, 2026
f40932a
fix(haptics): honor hidden state in component CSS
lora-sys Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/images/lora-social-card.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions scripts/audit-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,30 @@ const required = [
'blog/index.html',
'notes/index.html',
'projects/index.html',
'projects/zhihu-threads/index.html',
'lab/index.html',
'talks/index.html',
'about/index.html',
'contact/index.html',
'now/index.html',
'links/index.html',
'search/index.html',
'archives/index.html',
'tags/index.html',
'guestbook/index.html',
'resume/index.html',
'terms/index.html',
'en/index.html',
'en/work/index.html',
'en/writing/index.html',
'en/now/index.html',
'en/links/index.html',
'en/about/index.html',
'en/contact/index.html',
'en/search/index.html',
'en/guestbook/index.html',
'en/resume/index.html',
'en/projects/zhihu-threads/index.html',
'404.html',
'rss.xml',
'robots.txt',
Expand Down Expand Up @@ -74,6 +91,9 @@ for (const file of files.filter((path) => textExtensions.has(extname(path)))) {
url.startsWith(`${base}/tags/`) ||
url.startsWith(`${base}/archives`) ||
url.startsWith(`${base}/terms`) ||
url.startsWith(`${base}/guestbook`) ||
url.startsWith(`${base}/resume`) ||
url.startsWith(`${base}/en/`) ||
url.startsWith(`${base}/404`)
) continue

Expand Down
4 changes: 3 additions & 1 deletion scripts/fetch-github-projects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ const buildingRepositories = new Set([
])

const summaryOverrides = new Map([
// Reviewed against Glassbox c191401 on 2026-09-10; roadmap capabilities are not shipped.
['glassbox-agent-harness', '正在演进为长期运行的个人 Agent 工作台。当前支持 Codex、Claude Code、本地会话、运行回放、审批与画布检查;微信、QQ、长期任务和学习沉淀仍在规划。'],
['zhihu-threads', '从知乎回答与专栏摘录中选择来源,生成可追问、自测和导出的学习线。'],
['lorasys', '基于 Astro 的个人站,收录项目、构建笔记和公开贡献,通过 GitHub Pages 发布。'],
[
Expand Down Expand Up @@ -127,7 +129,7 @@ const summaryOverrides = new Map([

const titleOverrides = new Map([
['zhihu-threads', 'Zhihu Threads'],
['glassbox-agent-harness', 'Glassbox Agent Harness'],
['glassbox-agent-harness', 'Glassbox'],
['ai-engineering-harness', 'AI Engineering Harness'],
['nano-vllm-interactive-guide', 'nano-vLLM Interactive Guide'],
['trandingos', 'TradingOS'],
Expand Down
15 changes: 11 additions & 4 deletions src/components/BaseHead.astro
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,25 @@ const alternateWithBase = alternatePath
? alternatePath
: `${basePath}/${alternatePath.replace(/^\/+/, '')}`
: undefined
const siteTitle = `${title} ${config.titleDelimiter} ${config.title}`
const hasBrandInTitle = title.toLocaleLowerCase().includes(config.title.toLocaleLowerCase())
const siteTitle = hasBrandInTitle ? title : `${title} ${config.titleDelimiter} ${config.title}`
const canonicalURL = new URL(Astro.url.pathname, Astro.site)
const socialImageURL = new URL(ogImage ? ogImage : config.socialCard, Astro.url).href
const alternateURL = alternateWithBase ? new URL(alternateWithBase, Astro.site) : undefined
const socialProfiles = socialLinks.filter((link) => link.href.startsWith('http')).map((link) => link.href)
const twitterCreator = socialLinks.find((link) => link.name === 'X')?.handle
const structuredSiteDescription =
locale === 'zh-CN'
? 'AI Agent 开发者与全栈工程师的个人网站,记录项目、技术写作、公开构建与正在进行的实验。'
: config.description
const structuredJobTitle = locale === 'zh-CN' ? 'AI Agent 开发者与全栈工程师' : 'AI Agent Developer & Full-stack Engineer'
const websiteJsonLd = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: config.title,
url: canonicalURL.origin + baseUrl,
description: config.description,
description: structuredSiteDescription,
inLanguage: locale,
publisher: { '@type': 'Person', name: config.author }
}
const webPageJsonLd = {
Expand All @@ -62,8 +69,8 @@ const profileJsonLd = {
'@type': 'Person',
name: config.author,
url: canonicalURL.origin + baseUrl,
jobTitle: config.description,
description: config.description,
jobTitle: structuredJobTitle,
description: structuredSiteDescription,
sameAs: socialProfiles
}
---
Expand Down
30 changes: 24 additions & 6 deletions src/components/layout/SiteHeader.astro
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const search = locale === 'en-US' ? '/en/search' : '/search'
<a class='contact-cta tactile-action' href={locale === 'en-US' ? '/en/contact' : '/contact'}>{copy.contactLabel}</a>
{alternate && <a class='locale-toggle' href={alternate} hreflang={otherLocale} lang={otherLocale}>{copy.switchLabel}</a>}
<a class='search-link' href={search} title={`${copy.searchLabel} /`} aria-label={copy.searchLabel} aria-keyshortcuts='/'><Icon name='search' class='size-4' /></a>
<button id='toggleDarkMode' type='button' aria-label={locale === 'en-US' ? 'Toggle dark theme' : '切换深色主题'} aria-pressed='false'>
<button id='toggleDarkMode' type='button' aria-label={locale === 'en-US' ? 'Theme: system. Activate to change theme.' : '主题:跟随系统。点击切换主题。'}>
<Icon class='system size-4' name='computer' />
<Icon class='light hidden size-4' name='sun' />
<Icon class='dark hidden size-4' name='moon' />
Expand All @@ -47,9 +47,17 @@ const search = locale === 'en-US' ? '/en/search' : '/search'
const themeButton = document.getElementById('toggleDarkMode')
if (themeButton) {
const savedTheme = localStorage.getItem('theme') || 'system'
const isEnglish = document.documentElement.lang.toLowerCase().startsWith('en')
const themeNames = isEnglish
? { system: 'system', light: 'light', dark: 'dark' }
: { system: '跟随系统', light: '浅色', dark: '深色' }
themeButton.dataset.theme = savedTheme
const appliedTheme = savedTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : savedTheme
themeButton.setAttribute('aria-pressed', String(appliedTheme === 'dark'))
themeButton.setAttribute(
'aria-label',
isEnglish
? `Theme: ${themeNames[savedTheme] || savedTheme}. Activate to change theme.`
: `主题:${themeNames[savedTheme] || savedTheme}。点击切换主题。`
)
}
</script>
<script>
Expand All @@ -64,6 +72,17 @@ const search = locale === 'en-US' ? '/en/search' : '/search'
const isEnglish = document.documentElement.lang.toLowerCase().startsWith('en')
let menuReturnFocus: HTMLElement | null = null

const updateThemeLabel = (theme: string) => {
if (!themeButton) return
const name = isEnglish
? ({ system: 'system', light: 'light', dark: 'dark' }[theme] ?? theme)
: ({ system: '跟随系统', light: '浅色', dark: '深色' }[theme] ?? theme)
themeButton.setAttribute(
'aria-label',
isEnglish ? `Theme: ${name}. Activate to change theme.` : `主题:${name}。点击切换主题。`
)
}

const updateScrollState = () => {
if (!header) return
header.toggleAttribute('data-scrolled', window.scrollY > 18)
Expand Down Expand Up @@ -122,10 +141,9 @@ const search = locale === 'en-US' ? '/en/search' : '/search'
root.style.setProperty('--theme-origin-y', `${rect.top + rect.height / 2}px`)

const applyTheme = () => {
const nextTheme = setTheme(undefined, true)
const nextTheme = setTheme(undefined, true) ?? 'system'
themeButton.dataset.theme = nextTheme
const appliedTheme = nextTheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : nextTheme
themeButton.setAttribute('aria-pressed', String(appliedTheme === 'dark'))
updateThemeLabel(nextTheme)
}

const finish = () => {
Expand Down
44 changes: 26 additions & 18 deletions src/components/lora/HapticsToggle.astro
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
// 体感开关:开启后项目卡解锁可玩 demo。默认关闭;reduced-motion 访客不渲染此开关
// 触感反馈只在触控/粗指针设备支持 vibration 且访客未请求 reduced-motion 时提供
interface Props {
locale?: 'zh-CN' | 'en-US'
}
const { locale = 'zh-CN' } = Astro.props
const labels = {
'zh-CN': { off: '开启体感', on: '体感已开启' },
'zh-CN': { off: '开启触感反馈', on: '触感反馈已开启' },
'en-US': { off: 'Enable haptics', on: 'Haptics on' }
}
const label = labels[locale]
Expand Down Expand Up @@ -38,6 +38,9 @@ const label = labels[locale]
font-weight: 650;
letter-spacing: 0.03em;
}
.haptics-toggle[hidden] {
display: none;
}
.haptics-toggle i {
width: 0.46rem;
height: 0.46rem;
Expand Down Expand Up @@ -73,22 +76,35 @@ const label = labels[locale]
<script>
const toggles = [...document.querySelectorAll<HTMLButtonElement>('[data-haptics-toggle]')]
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)')
const coarsePointer = matchMedia('(pointer: coarse)')
const touchCapable = navigator.maxTouchPoints > 0 || coarsePointer.matches
const supportsHaptics = touchCapable && 'vibrate' in navigator && typeof navigator.vibrate === 'function'

const apply = (on: boolean) => {
document.documentElement.dataset.haptics = on ? 'on' : 'off'
const enabled = supportsHaptics && !reducedMotion.matches && on
document.documentElement.dataset.haptics = enabled ? 'on' : 'off'
for (const toggle of toggles) {
toggle.setAttribute('aria-pressed', String(on))
toggle.setAttribute('aria-pressed', String(enabled))
const label = toggle.querySelector<HTMLElement>('[data-haptics-label]')
if (label) {
label.textContent = on
label.textContent = enabled
? toggle.dataset.labelOn ?? 'Haptics on'
: toggle.dataset.labelOff ?? 'Enable haptics'
}
}
}

if (toggles.length > 0 && !reducedMotion.matches) {
for (const toggle of toggles) toggle.hidden = false
const syncAvailability = () => {
const available = supportsHaptics && !reducedMotion.matches
for (const toggle of toggles) toggle.hidden = !available
if (!available) {
apply(false)
localStorage.removeItem('haptics')
}
return available
}

if (toggles.length > 0 && syncAvailability()) {
apply(localStorage.getItem('haptics') === 'on')
for (const toggle of toggles) {
toggle.addEventListener('click', () => {
Expand All @@ -97,30 +113,22 @@ const label = labels[locale]
localStorage.setItem('haptics', next ? 'on' : 'off')
})
}
// 触觉开启时,按压共享 tactile-action 语义的控件给一次 8ms 轻震;
// 仅在支持 vibrate 的设备上生效,失败静默。
document.addEventListener(
'pointerdown',
(event) => {
if (document.documentElement.dataset.haptics !== 'on') return
const target = event.target instanceof Element ? event.target : null
if (target?.closest('button, a, summary, [role=\'button\']')) {
try {
navigator.vibrate?.(8)
navigator.vibrate(8)
} catch {
/* 部分平台抛 NotAllowedError,静默即可 */
/* 部分平台会拒绝振动请求,静默即可 */
}
}
},
{ passive: true }
)
reducedMotion.addEventListener?.('change', (event) => {
if (event.matches) {
for (const toggle of toggles) toggle.hidden = true
apply(false)
localStorage.removeItem('haptics')
}
})
reducedMotion.addEventListener?.('change', () => syncAvailability())
} else {
document.documentElement.dataset.haptics = 'off'
}
Expand Down
43 changes: 42 additions & 1 deletion src/components/lora/HomeSectionNav.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
// 首页滚动叙事导航:大字区块入口 + IntersectionObserver 驱动的琥珀下划线活动态
// 首页滚动叙事导航:桌面作为大字目录;窄屏收紧为单行可横向滚动的区块索引
interface SpyItem {
id: string
label: string
Expand Down Expand Up @@ -92,6 +92,47 @@ const { items } = Astro.props
font-weight: 600;
letter-spacing: 0.1em;
}

@media (max-width: 760px) {
.section-nav {
width: min(100%, 70rem);
margin-top: 1.65rem;
overflow-x: auto;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}
.section-nav::-webkit-scrollbar { display: none; }
.section-nav ul {
width: max-content;
min-width: 100%;
flex-wrap: nowrap;
gap: 0.35rem;
padding: 0 0.15rem 0.45rem;
}
.section-nav li { flex: none; }
.section-nav a {
min-height: 2.75rem;
align-items: center;
gap: 0.3rem;
border: 1px solid hsl(var(--border));
border-radius: 999px;
padding: 0.45rem 0.72rem;
background: hsl(var(--background) / 0.72);
font-family: ui-sans-serif, system-ui, sans-serif;
font-size: 0.78rem;
font-weight: 640;
letter-spacing: 0;
white-space: nowrap;
}
.section-nav a::after {
right: 0.65rem;
bottom: 0.28rem;
left: 0.65rem;
height: 2px;
}
.section-nav-index { font-size: 0.52rem; }
}

@media (prefers-reduced-motion: reduce) {
.section-nav a,
.section-nav a::after {
Expand Down
18 changes: 9 additions & 9 deletions src/components/projects/ProjectDemo.astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const meta: Record<DemoKind, Record<'zh-CN' | 'en-US', { name: string; note: str
'en-US': { name: 'Harness · Closed loop', note: 'Illustrative engineering flow. Select a step to inspect.' }
},
terminal: {
'zh-CN': { name: 'Glassbox · 记录流', note: '示例日志,不是实时运行。点击可复制。' },
'en-US': { name: 'Glassbox · Capture stream', note: 'Illustrative logs, not a live run. Click to copy.' }
'zh-CN': { name: 'Glassbox · 能力概览', note: '仓库能力说明,规划项尚未实现。点击可复制。' },
'en-US': { name: 'Glassbox · Capabilities', note: 'Repository summary, not a live run. Planned items are not available.' }
}
}

Expand Down Expand Up @@ -54,14 +54,14 @@ const pipelineData = {

const terminalData = {
'zh-CN': [
{ line: '$ glassbox attach --run 42', tip: '已复制示例指令,不会启动运行' },
{ line: '» step 03 captured · context rebuilt', tip: '已复制第 03 步的示例上下文日志' },
{ line: '» evidence saved · verdict: pass', tip: '已复制 · 证据文件已就绪,判定结果:通过' }
{ line: '当前 · Codex / Claude Code', tip: '已复制当前支持的 Agent 适配器说明' },
{ line: '当前 · 回放、审批与画布检查', tip: '已复制当前工作台能力说明' },
{ line: '规划 · 微信、QQ、长期任务、学习沉淀', tip: '已复制规划说明,这些功能尚未实现' }
],
'en-US': [
{ line: '$ glassbox attach --run 42', tip: 'Copied illustrative command; no run was started' },
{ line: '» step 03 captured · context rebuilt', tip: 'Copied · Captured step 03 trace & context' },
{ line: '» evidence saved · verdict: pass', tip: 'Copied · Evidence saved with passing verdict' }
{ line: 'Available: Codex / Claude Code', tip: 'Copied the available provider adapters' },
{ line: 'Available: replay, approvals, canvas', tip: 'Copied the available workbench capabilities' },
{ line: 'Planned: channels, long tasks, learning', tip: 'Copied planned capabilities; these are not available yet' }
]
}

Expand Down Expand Up @@ -122,7 +122,7 @@ const copy = meta[kind][locale]
data-text={item.line}
data-tip={item.tip}
style={`--i:${index}`}
title={locale === 'zh-CN' ? '点击复制此行日志' : 'Click to copy log line'}
title={locale === 'zh-CN' ? '点击复制此项说明' : 'Click to copy capability note'}
>
<span class='demo-line-inner'>{item.line}</span>
<span class='demo-copy-badge' aria-hidden='true'>{locale === 'zh-CN' ? '复制' : 'Copy'}</span>
Expand Down
2 changes: 1 addition & 1 deletion src/components/search/SearchPage.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { locale } = Astro.props
const t = (zh: string, en: string) => text(locale, zh, en)
const work = locale === 'en-US' ? '/en/work' : '/projects'
const writing = locale === 'en-US' ? '/en/writing' : '/blog'
const intro = t('搜索项目、文章、短笔记与实验记录。按 / 可定位搜索框。', 'Search projects, articles, notes and experiments. Press / to focus search.')
const intro = t('搜索中文项目、文章、短笔记与实验记录。按 / 可定位搜索框。', 'Search English projects and writing. Press / to focus search.')
---
<PageLayout lang={locale} meta={{ title: t('搜索', 'Search'), description: intro }}>
<main>
Expand Down
Loading
Loading