fix: align website branding and spacing
The website used redrawn brand marks, generated icons retained opaque white edges, and adjacent download and Runtime sections accumulated excessive vertical space. It now uses synchronized official theme icons with transparent antialiased edges and a consistent responsive section rhythm. Release note: 官网与应用图标现统一使用官方 GoodBuddy 标识,并优化下载区与 Agent Runtime 区之间的页面间距。
@@ -66,6 +66,19 @@ npm run build
|
|||||||
|
|
||||||
中间构建输出位于 `out`。该目录为生成内容,应修改源文件后重新构建,不要直接编辑。
|
中间构建输出位于 `out`。该目录为生成内容,应修改源文件后重新构建,不要直接编辑。
|
||||||
|
|
||||||
|
## 图标生成
|
||||||
|
|
||||||
|
应用图标源文件位于 `icons`。修改源图或图标处理逻辑后运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run icons
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本会精确裁出亮色和深色圆角卡片,清理圆角外侧背景并使用适合主题的
|
||||||
|
边缘颜色生成透明抗锯齿,避免缩放后出现白边。它会统一更新 `build` 中的
|
||||||
|
PNG / ICO、Renderer 图标,以及官网使用的亮色和深色品牌图标。任务栏和
|
||||||
|
托盘图标保持透明背景。
|
||||||
|
|
||||||
## 平台打包
|
## 平台打包
|
||||||
|
|
||||||
### 当前平台默认包
|
### 当前平台默认包
|
||||||
|
|||||||
@@ -17,6 +17,25 @@ const darkSourcePath = join(
|
|||||||
'ChatGPT_qPkaIrLGsm.png'
|
'ChatGPT_qPkaIrLGsm.png'
|
||||||
)
|
)
|
||||||
const rendererAssetRoot = join(root, 'src', 'renderer', 'src', 'assets')
|
const rendererAssetRoot = join(root, 'src', 'renderer', 'src', 'assets')
|
||||||
|
const websiteAssetRoot = join(root, 'sites', 'assets')
|
||||||
|
|
||||||
|
const lightTile = {
|
||||||
|
left: 40,
|
||||||
|
top: 32,
|
||||||
|
right: 704,
|
||||||
|
bottom: 696,
|
||||||
|
radius: 126,
|
||||||
|
edgeColor: [255, 255, 255]
|
||||||
|
}
|
||||||
|
|
||||||
|
const darkTile = {
|
||||||
|
left: 20,
|
||||||
|
top: 16,
|
||||||
|
right: 684,
|
||||||
|
bottom: 680,
|
||||||
|
radius: 126,
|
||||||
|
edgeColor: [15, 21, 31]
|
||||||
|
}
|
||||||
|
|
||||||
function cropSquare(source, size) {
|
function cropSquare(source, size) {
|
||||||
const output = new PNG({ width: size, height: size })
|
const output = new PNG({ width: size, height: size })
|
||||||
@@ -24,6 +43,102 @@ function cropSquare(source, size) {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scaleTile(tile, sourceSize, targetSize) {
|
||||||
|
const scale = targetSize / sourceSize
|
||||||
|
return {
|
||||||
|
left: tile.left * scale,
|
||||||
|
top: tile.top * scale,
|
||||||
|
right: tile.right * scale,
|
||||||
|
bottom: tile.bottom * scale,
|
||||||
|
radius: tile.radius * scale,
|
||||||
|
edgeColor: tile.edgeColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cropTile(source, tile) {
|
||||||
|
const width = Math.round(tile.right - tile.left)
|
||||||
|
const height = Math.round(tile.bottom - tile.top)
|
||||||
|
if (width !== height || width <= 0) {
|
||||||
|
throw new Error('图标卡片裁剪区域必须是有效正方形')
|
||||||
|
}
|
||||||
|
const output = new PNG({ width, height })
|
||||||
|
PNG.bitblt(
|
||||||
|
source,
|
||||||
|
output,
|
||||||
|
Math.round(tile.left),
|
||||||
|
Math.round(tile.top),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
image: output,
|
||||||
|
tile: {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: width,
|
||||||
|
bottom: height,
|
||||||
|
radius: tile.radius,
|
||||||
|
edgeColor: tile.edgeColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function roundedRectangleDistance(x, y, tile) {
|
||||||
|
const centerX = (tile.left + tile.right) / 2
|
||||||
|
const centerY = (tile.top + tile.bottom) / 2
|
||||||
|
const halfWidth = (tile.right - tile.left) / 2
|
||||||
|
const halfHeight = (tile.bottom - tile.top) / 2
|
||||||
|
const offsetX = Math.abs(x - centerX) - (halfWidth - tile.radius)
|
||||||
|
const offsetY = Math.abs(y - centerY) - (halfHeight - tile.radius)
|
||||||
|
return (
|
||||||
|
Math.hypot(Math.max(offsetX, 0), Math.max(offsetY, 0)) +
|
||||||
|
Math.min(Math.max(offsetX, offsetY), 0) -
|
||||||
|
tile.radius
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyRoundedTransparency(image, tile) {
|
||||||
|
for (let y = 0; y < image.height; y += 1) {
|
||||||
|
for (let x = 0; x < image.width; x += 1) {
|
||||||
|
const offset = pixelOffset(image, x, y)
|
||||||
|
const distance = roundedRectangleDistance(x + 0.5, y + 0.5, tile)
|
||||||
|
const coverage = Math.min(Math.max(0.5 - distance, 0), 1)
|
||||||
|
if (coverage >= 1) {
|
||||||
|
if (distance > -2 && image.data[offset + 3] < 255) {
|
||||||
|
image.data[offset] = tile.edgeColor[0]
|
||||||
|
image.data[offset + 1] = tile.edgeColor[1]
|
||||||
|
image.data[offset + 2] = tile.edgeColor[2]
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
image.data[offset] = tile.edgeColor[0]
|
||||||
|
image.data[offset + 1] = tile.edgeColor[1]
|
||||||
|
image.data[offset + 2] = tile.edgeColor[2]
|
||||||
|
image.data[offset + 3] = Math.round(
|
||||||
|
image.data[offset + 3] * coverage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRoundedIcon(source, tile, size) {
|
||||||
|
const cropped = cropTile(source, tile)
|
||||||
|
applyRoundedTransparency(cropped.image, cropped.tile)
|
||||||
|
const output = resize(
|
||||||
|
cropped.image,
|
||||||
|
size,
|
||||||
|
size,
|
||||||
|
'bicubicInterpolation'
|
||||||
|
)
|
||||||
|
applyRoundedTransparency(
|
||||||
|
output,
|
||||||
|
scaleTile(cropped.tile, cropped.image.width, size)
|
||||||
|
)
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
function pixelOffset(image, x, y) {
|
function pixelOffset(image, x, y) {
|
||||||
return (y * image.width + x) * 4
|
return (y * image.width + x) * 4
|
||||||
}
|
}
|
||||||
@@ -119,6 +234,51 @@ function assertTaskbarIcon(image) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertTransparentCorners(image, label) {
|
||||||
|
const corners = [
|
||||||
|
pixelOffset(image, 0, 0),
|
||||||
|
pixelOffset(image, image.width - 1, 0),
|
||||||
|
pixelOffset(image, 0, image.height - 1),
|
||||||
|
pixelOffset(image, image.width - 1, image.height - 1)
|
||||||
|
]
|
||||||
|
if (corners.some((offset) => image.data[offset + 3] !== 0)) {
|
||||||
|
throw new Error(`${label} 的圆角外侧必须透明`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertDarkEdgeHasNoWhiteFringe(image) {
|
||||||
|
const edgeWidth = Math.max(4, Math.round(image.width * 0.08))
|
||||||
|
for (let y = 0; y < image.height; y += 1) {
|
||||||
|
for (let x = 0; x < image.width; x += 1) {
|
||||||
|
if (
|
||||||
|
x >= edgeWidth &&
|
||||||
|
x < image.width - edgeWidth &&
|
||||||
|
y >= edgeWidth &&
|
||||||
|
y < image.height - edgeWidth
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const offset = pixelOffset(image, x, y)
|
||||||
|
const alpha = image.data[offset + 3]
|
||||||
|
if (
|
||||||
|
alpha > 0 &&
|
||||||
|
alpha < 255 &&
|
||||||
|
Math.max(
|
||||||
|
image.data[offset],
|
||||||
|
image.data[offset + 1],
|
||||||
|
image.data[offset + 2]
|
||||||
|
) > 96
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`暗色图标的透明边缘仍包含白色像素:${x},${y} ` +
|
||||||
|
`rgba(${image.data[offset]},${image.data[offset + 1]},` +
|
||||||
|
`${image.data[offset + 2]},${alpha})`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function repairDarkCursor(dark, light) {
|
function repairDarkCursor(dark, light) {
|
||||||
for (let y = 570; y <= 606; y += 1) {
|
for (let y = 570; y <= 606; y += 1) {
|
||||||
const backgroundOffset = pixelOffset(dark, 640, y)
|
const backgroundOffset = pixelOffset(dark, 640, y)
|
||||||
@@ -197,22 +357,31 @@ async function main() {
|
|||||||
repairDarkCursor(darkSquare, lightSquare)
|
repairDarkCursor(darkSquare, lightSquare)
|
||||||
assertCursorRemoved(lightSquare, darkSquare)
|
assertCursorRemoved(lightSquare, darkSquare)
|
||||||
|
|
||||||
const light = resize(lightSquare, 512, 512, 'bicubicInterpolation')
|
|
||||||
const dark = resize(darkSquare, 512, 512, 'bicubicInterpolation')
|
|
||||||
const taskbar = createTaskbarIcon(lightSquare)
|
const taskbar = createTaskbarIcon(lightSquare)
|
||||||
|
const light = createRoundedIcon(lightSquare, lightTile, 512)
|
||||||
|
const dark = createRoundedIcon(darkSquare, darkTile, 512)
|
||||||
|
const rendererLight = createRoundedIcon(lightSquare, lightTile, 128)
|
||||||
|
const rendererDark = createRoundedIcon(darkSquare, darkTile, 128)
|
||||||
assertTaskbarIcon(taskbar)
|
assertTaskbarIcon(taskbar)
|
||||||
|
assertTransparentCorners(light, '亮色图标')
|
||||||
|
assertTransparentCorners(dark, '暗色图标')
|
||||||
|
assertTransparentCorners(rendererLight, '亮色界面图标')
|
||||||
|
assertTransparentCorners(rendererDark, '暗色界面图标')
|
||||||
|
assertTransparentCorners(taskbar, '任务栏图标')
|
||||||
|
assertDarkEdgeHasNoWhiteFringe(dark)
|
||||||
|
assertDarkEdgeHasNoWhiteFringe(rendererDark)
|
||||||
const tray = resize(taskbar, 32, 32, 'bicubicInterpolation')
|
const tray = resize(taskbar, 32, 32, 'bicubicInterpolation')
|
||||||
|
assertTransparentCorners(tray, '托盘图标')
|
||||||
const lightPng = PNG.sync.write(light)
|
const lightPng = PNG.sync.write(light)
|
||||||
const darkPng = PNG.sync.write(dark)
|
const darkPng = PNG.sync.write(dark)
|
||||||
const taskbarPng = PNG.sync.write(taskbar)
|
const taskbarPng = PNG.sync.write(taskbar)
|
||||||
const trayPng = PNG.sync.write(tray)
|
const trayPng = PNG.sync.write(tray)
|
||||||
const rendererLightPng = PNG.sync.write(
|
const rendererLightPng = PNG.sync.write(rendererLight)
|
||||||
resize(light, 128, 128, 'bicubicInterpolation')
|
const rendererDarkPng = PNG.sync.write(rendererDark)
|
||||||
)
|
await Promise.all([
|
||||||
const rendererDarkPng = PNG.sync.write(
|
mkdir(rendererAssetRoot, { recursive: true }),
|
||||||
resize(dark, 128, 128, 'bicubicInterpolation')
|
mkdir(websiteAssetRoot, { recursive: true })
|
||||||
)
|
])
|
||||||
await mkdir(rendererAssetRoot, { recursive: true })
|
|
||||||
|
|
||||||
const outputs = [
|
const outputs = [
|
||||||
[join(root, 'build', 'icon-light.png'), lightPng],
|
[join(root, 'build', 'icon-light.png'), lightPng],
|
||||||
@@ -221,7 +390,9 @@ async function main() {
|
|||||||
[join(root, 'build', 'icon-taskbar.png'), taskbarPng],
|
[join(root, 'build', 'icon-taskbar.png'), taskbarPng],
|
||||||
[join(root, 'build', 'icon-tray.png'), trayPng],
|
[join(root, 'build', 'icon-tray.png'), trayPng],
|
||||||
[join(rendererAssetRoot, 'goodbuddy-light.png'), rendererLightPng],
|
[join(rendererAssetRoot, 'goodbuddy-light.png'), rendererLightPng],
|
||||||
[join(rendererAssetRoot, 'goodbuddy-dark.png'), rendererDarkPng]
|
[join(rendererAssetRoot, 'goodbuddy-dark.png'), rendererDarkPng],
|
||||||
|
[join(websiteAssetRoot, 'goodbuddy-light.png'), rendererLightPng],
|
||||||
|
[join(websiteAssetRoot, 'goodbuddy-dark.png'), rendererDarkPng]
|
||||||
]
|
]
|
||||||
await Promise.all(outputs.map(([path, contents]) => writeFile(path, contents)))
|
await Promise.all(outputs.map(([path, contents]) => writeFile(path, contents)))
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 302 KiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 287 KiB |
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 287 KiB |
@@ -59,5 +59,5 @@ SHA-256 清单。
|
|||||||
- `index.html`:页面结构与简体中文内容
|
- `index.html`:页面结构与简体中文内容
|
||||||
- `styles.css`:语义令牌、浅深主题、焦点与响应式布局
|
- `styles.css`:语义令牌、浅深主题、焦点与响应式布局
|
||||||
- `app.js`:主题、移动导航和当前章节
|
- `app.js`:主题、移动导航和当前章节
|
||||||
- `assets/favicon.svg`:站点图标
|
- `assets/goodbuddy-light.png`、`assets/goodbuddy-dark.png`:由 `npm run icons` 与桌面应用同步生成的官方品牌图标
|
||||||
- `scripts/validate.mjs`:无依赖静态检查
|
- `scripts/validate.mjs`:无依赖静态检查
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="g" x1="8" y1="8" x2="56" y2="56" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#0877e8"/>
|
|
||||||
<stop offset="1" stop-color="#08b89b"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<rect width="64" height="64" rx="16" fill="#fff"/>
|
|
||||||
<path d="M9 34a14 14 0 1 1 28 0v12H23A14 14 0 0 1 9 34Z" fill="none" stroke="url(#g)" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M27 34a14 14 0 1 1 28 0 14 14 0 0 1-28 0Z" fill="none" stroke="url(#g)" stroke-width="7"/>
|
|
||||||
<path d="M32 20v-7M41 17l5-5M23 17l-5-5" fill="none" stroke="url(#g)" stroke-width="4" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 702 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -10,7 +10,18 @@
|
|||||||
<link rel="canonical" href="https://mesalogo.github.io/goodbuddy/" />
|
<link rel="canonical" href="https://mesalogo.github.io/goodbuddy/" />
|
||||||
<meta name="theme-color" content="#f6f8fb" />
|
<meta name="theme-color" content="#f6f8fb" />
|
||||||
<title>GoodBuddy|桌面助手与 AI 编程工具台</title>
|
<title>GoodBuddy|桌面助手与 AI 编程工具台</title>
|
||||||
<link rel="icon" href="./assets/favicon.svg" type="image/svg+xml" />
|
<link
|
||||||
|
rel="icon"
|
||||||
|
href="./assets/goodbuddy-light.png"
|
||||||
|
type="image/png"
|
||||||
|
media="(prefers-color-scheme: light)"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
href="./assets/goodbuddy-dark.png"
|
||||||
|
type="image/png"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
/>
|
||||||
<link rel="stylesheet" href="./styles.css" />
|
<link rel="stylesheet" href="./styles.css" />
|
||||||
<script>
|
<script>
|
||||||
(() => {
|
(() => {
|
||||||
@@ -35,11 +46,10 @@
|
|||||||
<header class="site-header" data-site-header>
|
<header class="site-header" data-site-header>
|
||||||
<div class="header-inner">
|
<div class="header-inner">
|
||||||
<a class="brand" href="#home" aria-label="GoodBuddy 首页">
|
<a class="brand" href="#home" aria-label="GoodBuddy 首页">
|
||||||
<svg class="brand-mark" viewBox="0 0 40 40" aria-hidden="true">
|
<span class="brand-icon" aria-hidden="true">
|
||||||
<path d="M6 21a9 9 0 1 1 18 0v8H15a9 9 0 0 1-9-8Z" />
|
<img class="brand-icon__image brand-icon__image--light" src="./assets/goodbuddy-light.png" alt="" />
|
||||||
<path d="M16 21a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" />
|
<img class="brand-icon__image brand-icon__image--dark" src="./assets/goodbuddy-dark.png" alt="" />
|
||||||
<path d="M20 13V8M25 10l3-3M15 10l-3-3" />
|
</span>
|
||||||
</svg>
|
|
||||||
<span>GoodBuddy</span>
|
<span>GoodBuddy</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -136,10 +146,10 @@
|
|||||||
<div class="app-layout">
|
<div class="app-layout">
|
||||||
<aside class="app-sidebar" aria-hidden="true">
|
<aside class="app-sidebar" aria-hidden="true">
|
||||||
<div class="mini-brand">
|
<div class="mini-brand">
|
||||||
<svg viewBox="0 0 40 40">
|
<span class="brand-icon" aria-hidden="true">
|
||||||
<path d="M6 21a9 9 0 1 1 18 0v8H15a9 9 0 0 1-9-8Z" />
|
<img class="brand-icon__image brand-icon__image--light" src="./assets/goodbuddy-light.png" alt="" />
|
||||||
<path d="M16 21a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" />
|
<img class="brand-icon__image brand-icon__image--dark" src="./assets/goodbuddy-dark.png" alt="" />
|
||||||
</svg>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="side-item is-active"><span></span>对话</div>
|
<div class="side-item is-active"><span></span>对话</div>
|
||||||
<div class="side-item"><span></span>知识库</div>
|
<div class="side-item"><span></span>知识库</div>
|
||||||
@@ -161,7 +171,12 @@
|
|||||||
<div class="message message--user">修复构建问题,并验证三个桌面系统。</div>
|
<div class="message message--user">修复构建问题,并验证三个桌面系统。</div>
|
||||||
<div class="message message--assistant">
|
<div class="message message--assistant">
|
||||||
<div class="assistant-label">
|
<div class="assistant-label">
|
||||||
<span class="assistant-avatar">G</span>
|
<span class="assistant-avatar" aria-hidden="true">
|
||||||
|
<span class="brand-icon">
|
||||||
|
<img class="brand-icon__image brand-icon__image--light" src="./assets/goodbuddy-light.png" alt="" />
|
||||||
|
<img class="brand-icon__image brand-icon__image--dark" src="./assets/goodbuddy-dark.png" alt="" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<strong>GoodBuddy</strong>
|
<strong>GoodBuddy</strong>
|
||||||
</div>
|
</div>
|
||||||
<p>已通过 Agent Runtime 载入项目、Skills 和受控工具。</p>
|
<p>已通过 Agent Runtime 载入项目、Skills 和受控工具。</p>
|
||||||
@@ -373,10 +388,10 @@
|
|||||||
<div class="section-inner assistant-grid">
|
<div class="section-inner assistant-grid">
|
||||||
<div class="assistant-intro">
|
<div class="assistant-intro">
|
||||||
<div class="assistant-mark" aria-hidden="true">
|
<div class="assistant-mark" aria-hidden="true">
|
||||||
<svg viewBox="0 0 48 48">
|
<span class="brand-icon">
|
||||||
<path d="M9 25a11 11 0 1 1 22 0v10H20A11 11 0 0 1 9 25Z" />
|
<img class="brand-icon__image brand-icon__image--light" src="./assets/goodbuddy-light.png" alt="" />
|
||||||
<path d="M21 25a11 11 0 1 1 22 0 11 11 0 0 1-22 0Z" />
|
<img class="brand-icon__image brand-icon__image--dark" src="./assets/goodbuddy-dark.png" alt="" />
|
||||||
</svg>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="kicker">桌面助手</p>
|
<p class="kicker">桌面助手</p>
|
||||||
<h2 id="assistant-title">对话、知识、笔记与任务,都在桌面上</h2>
|
<h2 id="assistant-title">对话、知识、笔记与任务,都在桌面上</h2>
|
||||||
@@ -418,11 +433,10 @@
|
|||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<div class="section-inner footer-inner">
|
<div class="section-inner footer-inner">
|
||||||
<a class="brand brand--footer" href="#home" aria-label="返回 GoodBuddy 首页">
|
<a class="brand brand--footer" href="#home" aria-label="返回 GoodBuddy 首页">
|
||||||
<svg class="brand-mark" viewBox="0 0 40 40" aria-hidden="true">
|
<span class="brand-icon" aria-hidden="true">
|
||||||
<path d="M6 21a9 9 0 1 1 18 0v8H15a9 9 0 0 1-9-8Z" />
|
<img class="brand-icon__image brand-icon__image--light" src="./assets/goodbuddy-light.png" alt="" />
|
||||||
<path d="M16 21a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" />
|
<img class="brand-icon__image brand-icon__image--dark" src="./assets/goodbuddy-dark.png" alt="" />
|
||||||
<path d="M20 13V8M25 10l3-3M15 10l-3-3" />
|
</span>
|
||||||
</svg>
|
|
||||||
<span>GoodBuddy</span>
|
<span>GoodBuddy</span>
|
||||||
</a>
|
</a>
|
||||||
<p>桌面助手|AI 编程工具台</p>
|
<p>桌面助手|AI 编程工具台</p>
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ const requiredFiles = [
|
|||||||
"index.html",
|
"index.html",
|
||||||
"styles.css",
|
"styles.css",
|
||||||
"app.js",
|
"app.js",
|
||||||
"assets/favicon.svg",
|
"assets/goodbuddy-light.png",
|
||||||
|
"assets/goodbuddy-dark.png",
|
||||||
"README.md",
|
"README.md",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -67,6 +68,15 @@ report(/class="skip-link"\s+href="#main-content"/.test(html), "缺少跳到主
|
|||||||
report(/<main\s+id="main-content">/.test(html), "缺少 main-content 主区域");
|
report(/<main\s+id="main-content">/.test(html), "缺少 main-content 主区域");
|
||||||
report(/aria-label="主导航"/.test(html), "主导航缺少可访问名称");
|
report(/aria-label="主导航"/.test(html), "主导航缺少可访问名称");
|
||||||
report(/data-theme-toggle/.test(html), "缺少主题切换控件");
|
report(/data-theme-toggle/.test(html), "缺少主题切换控件");
|
||||||
|
report(
|
||||||
|
(html.match(/src="\.\/assets\/goodbuddy-light\.png"/g) ?? []).length >= 5,
|
||||||
|
"品牌位置必须使用官方亮色图标",
|
||||||
|
);
|
||||||
|
report(
|
||||||
|
(html.match(/src="\.\/assets\/goodbuddy-dark\.png"/g) ?? []).length >= 5,
|
||||||
|
"品牌位置必须使用官方深色图标",
|
||||||
|
);
|
||||||
|
report(!/class="brand-mark"/.test(html), "官网不得使用自绘品牌标志");
|
||||||
report(/data-tilt-stage/.test(html), "首屏产品界面缺少倾斜交互区域");
|
report(/data-tilt-stage/.test(html), "首屏产品界面缺少倾斜交互区域");
|
||||||
report(/data-tilt-card/.test(html), "首屏产品界面缺少倾斜卡片");
|
report(/data-tilt-card/.test(html), "首屏产品界面缺少倾斜卡片");
|
||||||
report(/prefers-reduced-motion:\s*reduce/.test(css), "缺少减少动态效果规则");
|
report(/prefers-reduced-motion:\s*reduce/.test(css), "缺少减少动态效果规则");
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
--space-12: 48px;
|
--space-12: 48px;
|
||||||
--space-16: 64px;
|
--space-16: 64px;
|
||||||
--space-20: 80px;
|
--space-20: 80px;
|
||||||
|
--section-space: 96px;
|
||||||
--font-caption: 0.625rem;
|
--font-caption: 0.625rem;
|
||||||
--font-body: 0.875rem;
|
--font-body: 0.875rem;
|
||||||
--font-section-title: 1rem;
|
--font-section-title: 1rem;
|
||||||
@@ -212,7 +213,7 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
padding-block: 112px;
|
padding-block: var(--section-space);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-inner,
|
.section-inner,
|
||||||
@@ -262,23 +263,32 @@ p {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-mark {
|
.brand-icon {
|
||||||
|
position: relative;
|
||||||
|
display: inline-grid;
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
overflow: visible;
|
flex: 0 0 auto;
|
||||||
fill: none;
|
place-items: center;
|
||||||
stroke: var(--accent);
|
|
||||||
stroke-linecap: round;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
stroke-width: 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-mark path:nth-child(2) {
|
.brand-icon__image {
|
||||||
stroke: var(--accent-mint);
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-mark path:last-child {
|
.brand-icon__image--dark {
|
||||||
stroke-width: 2.5;
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .brand-icon__image--light {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .brand-icon__image--dark {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-navigation {
|
.site-navigation {
|
||||||
@@ -737,21 +747,11 @@ p {
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
margin: 0 0 var(--space-3) 6px;
|
margin: 0 0 var(--space-3) 6px;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
border-radius: var(--radius-control);
|
|
||||||
background: var(--accent-subtle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mini-brand svg {
|
.mini-brand .brand-icon {
|
||||||
width: 22px;
|
width: 30px;
|
||||||
fill: none;
|
height: 30px;
|
||||||
stroke: var(--accent);
|
|
||||||
stroke-linecap: round;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
stroke-width: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-brand path:last-child {
|
|
||||||
stroke: var(--accent-mint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-item {
|
.side-item {
|
||||||
@@ -852,11 +852,11 @@ p {
|
|||||||
width: 23px;
|
width: 23px;
|
||||||
height: 23px;
|
height: 23px;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
border-radius: 7px;
|
}
|
||||||
background: linear-gradient(135deg, var(--accent), var(--accent-mint));
|
|
||||||
color: var(--text-on-accent);
|
.assistant-avatar .brand-icon {
|
||||||
font-size: 9px;
|
width: 23px;
|
||||||
font-weight: 800;
|
height: 23px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message--assistant p {
|
.message--assistant p {
|
||||||
@@ -1225,7 +1225,11 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.download-section {
|
.download-section {
|
||||||
padding-top: var(--space-20);
|
padding-top: var(--section-space);
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-section + .features-section {
|
||||||
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-grid {
|
.download-grid {
|
||||||
@@ -1361,24 +1365,14 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.assistant-mark {
|
.assistant-mark {
|
||||||
display: grid;
|
|
||||||
width: 76px;
|
width: 76px;
|
||||||
height: 76px;
|
height: 76px;
|
||||||
margin-bottom: var(--space-8);
|
margin-bottom: var(--space-8);
|
||||||
place-items: center;
|
|
||||||
border: 1px solid color-mix(in srgb, var(--accent-cyan) 40%, transparent);
|
|
||||||
border-radius: 20px;
|
|
||||||
background: color-mix(in srgb, var(--accent-cyan) 10%, transparent);
|
|
||||||
color: var(--accent-cyan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.assistant-mark svg {
|
.assistant-mark .brand-icon {
|
||||||
width: 42px;
|
width: 76px;
|
||||||
fill: none;
|
height: 76px;
|
||||||
stroke: currentColor;
|
|
||||||
stroke-linecap: round;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
stroke-width: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-link {
|
.text-link {
|
||||||
@@ -1442,7 +1436,7 @@ p {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand--footer .brand-mark {
|
.brand--footer .brand-icon {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
@@ -1503,8 +1497,8 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 959px) {
|
@media (max-width: 959px) {
|
||||||
.section {
|
:root {
|
||||||
padding-block: 88px;
|
--section-space: var(--space-20);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-inner {
|
.header-inner {
|
||||||
@@ -1632,6 +1626,10 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 719px) {
|
@media (max-width: 719px) {
|
||||||
|
:root {
|
||||||
|
--section-space: var(--space-16);
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-padding-top: 72px;
|
scroll-padding-top: 72px;
|
||||||
}
|
}
|
||||||
@@ -1641,10 +1639,6 @@ p {
|
|||||||
width: calc(100% - 32px);
|
width: calc(100% - 32px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
|
||||||
padding-block: var(--space-16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-inner {
|
.header-inner {
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
min-height: 64px;
|
min-height: 64px;
|
||||||
@@ -1654,7 +1648,7 @@ p {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-mark {
|
.brand-icon {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
@@ -1830,6 +1824,11 @@ p {
|
|||||||
height: 64px;
|
height: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assistant-mark .brand-icon {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
.assistant-list article {
|
.assistant-list article {
|
||||||
grid-template-columns: 32px 1fr;
|
grid-template-columns: 32px 1fr;
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 18 KiB |