Compare commits

...

33 Commits

Author SHA1 Message Date
fb2a3466c4 Document npm registry mirror and Docker named volumes for builds
Per Issue #2, the static build workflow now consistently uses:
- npm_config_registry env var to switch to npmmirror (China mainland)
- duoji-node-modules named volume (replaces --tmpfs to avoid noexec)
- duoji-npm-cache named volume (faster subsequent installs)
- npm ci --no-audit --no-fund (faster CI/deploy)
- caddy reload note: bind mounts require docker-compose up -d caddy
- volume cleanup commands (docker volume rm ...)
2026-08-18 10:19:36 +08:00
Jacky Ser
4d25114923 Improve catalog cards and cart exports 2026-08-14 15:38:24 +08:00
Jacky Ser
5101493901 Add cart exports and sample request workflow 2026-08-13 11:07:56 +08:00
Jacky Ser
511dbcd0e8 Refine responsive product detail specs 2026-08-12 18:30:50 +08:00
Jacky Ser
62a3d6ed14 Add product card image saving 2026-08-12 16:23:15 +08:00
Jacky Ser
81098ea237 Refine responsive catalog and branding 2026-08-11 22:58:14 +08:00
Jacky Ser
45fbdffddb Align static deployment with existing Caddy gateway 2026-08-11 18:01:39 +08:00
Jacky Ser
42c21dd024 Add static deployment build and guide 2026-08-11 17:37:11 +08:00
Jacky Ser
775ccf82ab Add precision gear dimension images 2026-08-11 17:09:33 +08:00
Jacky Ser
05de730115 Place catalog gallery image below controls 2026-08-11 17:00:42 +08:00
Jacky Ser
c131483255 Move catalog gallery controls onto image 2026-08-11 16:56:38 +08:00
Jacky Ser
b7e32000bf Refine catalog modal gallery layout 2026-08-11 16:40:46 +08:00
Jacky Ser
a608faa298 Add precision gear product cards 2026-08-11 16:35:50 +08:00
Jacky Ser
e8759531b7 Add large image action to AI recommendations 2026-08-11 16:28:39 +08:00
Jacky Ser
7eed023539 Improve catalog detail modal with size images 2026-08-11 16:22:59 +08:00
Jacky Ser
ca423b3be7 Add steering wheel accessories to catalog 2026-08-11 15:54:35 +08:00
Jacky Ser
c9e20bdb1e Add precision gear navigation entry 2026-08-11 15:49:17 +08:00
Jacky Ser
6c37aa3d47 Revert ecommerce catalog rematch 2026-08-11 15:37:23 +08:00
Jacky Ser
b4d11e2f12 Rematch catalog data to ecommerce source 2026-08-11 15:27:26 +08:00
Jacky Ser
9627c5043e Adapt product image windows to source ratios 2026-08-11 15:12:57 +08:00
Jacky Ser
794857f465 Use ecommerce main images for catalog products 2026-08-11 15:05:28 +08:00
Jacky Ser
78f5ead63a Unify AI recommendation card sizes 2026-08-11 14:51:26 +08:00
Jacky Ser
e855d5aa64 Fix start selection navigation 2026-08-11 14:46:46 +08:00
Jacky Ser
718d7c6ccb Configure automatic repository workflow 2026-08-11 10:55:54 +08:00
Jacky Ser
9078f19194 Add g and kg catalog filters 2026-08-11 09:41:44 +08:00
Jacky Ser
7b9a56b7fe Reclassify 4.A.05.A0008 as special servo 2026-08-11 09:20:37 +08:00
Jacky Ser
66ef28bdeb Clarify official and feature categories 2026-08-11 00:14:20 +08:00
Jacky Ser
0351c47684 Add custom servo contact path 2026-08-11 00:00:14 +08:00
Jacky Ser
9ab7d7c195 Add silent servo category 2026-08-10 23:56:54 +08:00
Jacky Ser
f72de38fb7 Use rated voltage options in selector 2026-08-10 23:51:42 +08:00
Jacky Ser
99f9f079b3 Enlarge catalog product images 2026-08-10 16:27:18 +08:00
Jacky Ser
3f9202b315 Add initial order details to selector 2026-08-10 16:24:53 +08:00
Jacky Ser
596493a686 Expand selector to full servo catalog 2026-08-10 16:21:49 +08:00
1470 changed files with 15658 additions and 541 deletions

9
.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
.git
.gitignore
node_modules
dist
dist-static
.next
.vinext
.wrangler
*.log

1
.gitignore vendored
View File

@@ -35,6 +35,7 @@ yarn-error.log*
.vercel .vercel
/dist/ /dist/
/dist-static/
/.wrangler/ /.wrangler/
/outputs/ /outputs/
/work/ /work/

5
AGENTS.md Normal file
View File

@@ -0,0 +1,5 @@
# Repository workflow
- After completing and validating any requested code change, commit it to `main` and push it to `origin` automatically.
- Do not push local-only prototypes, unconfirmed visual experiments, or changes the user explicitly asks to keep local.
- Never commit credentials, deploy private keys, environment secrets, or generated dependency/build directories.

155
DEPLOY_STATIC.md Normal file
View File

@@ -0,0 +1,155 @@
# 高徳乐 AI 舵机选型助手:静态部署说明
本项目提供纯静态构建方式。产品数据、图片、选型计算、筛选和弹窗交互都在浏览器中运行;网站运行时不需要 Node.js、数据库、API 或 Cloudflare Worker。
## 当前服务器的推荐方案
服务器已经有 Docker Compose 管理的 Caddy 网关,并且 Caddy 占用 80/443 端口。因此本项目应当:
1. 使用 `npm run build:static` 生成 `dist-static/`
2.`dist-static/` 同步到服务器静态目录。
3. 把静态目录以只读方式挂载到现有 Caddy 容器。
4. 在现有 Caddyfile 中增加 `duoji.dbpi.com.cn` 的站点配置。
不要为本项目另起 Nginx不要让新容器绑定 80/443也不要改动现有 `galderma-api``galderma-db` 容器。
## 给 Git AI 的部署任务
可以直接把下面这段话发给仓库里的 AI
> 请把本仓库部署为 `duoji.dbpi.com.cn` 的纯静态网站。不要执行 `npm run build`,不要启动 Vinext Server、Node.js 后端或 Cloudflare Worker请执行 `npm run build:static`,发布生成的 `dist-static/` 目录。服务器已有 Docker Compose 管理的 Caddy 网关80/443 端口不能抢占,也不要启动独立 Nginx 或新的 80 端口容器。请先检查现有 `/root/galderma/docker-compose.yml` 和 Caddyfile在 Caddy 服务中增加只读挂载 `/var/www/duoji:/srv/duoji:ro`,并增加仓库 `deploy/Caddyfile.duoji` 中的站点配置。构建时服务器没有 Node.js使用一次性 `node:22-alpine` 容器构建,并通过环境变量 `npm_config_registry=https://registry.npmmirror.com` 切换到中国大陆 npm 镜像(官方源经常连不上);用两个 Docker named volume 分别隔离 `node_modules``duoji-node-modules`,解决 `tmpfs` 默认 `noexec` 导致 esbuild 执行失败的问题)和 npm 缓存(`duoji-npm-cache`,加快后续依赖安装)。修改前备份 Caddy 配置,修改后先校验配置,再只重载 Caddybind mount 改动必须重启 caddy 容器才生效(`caddy reload` 不会重挂 volume实际影响约 3 秒。不要改动 galderma-api、galderma-db 和现有站点不要把密码、SSH 密钥或 token 写入仓库。最后用 curl 检查首页、产品图片和 HTTPS。
## 构建静态文件
如果服务器有 Node.js
```bash
npm ci --no-audit --no-fund
npm run build:static
```
如果服务器没有 Node.js使用一次性 Docker 构建容器。该方式不会在宿主机留下 `node_modules`,并通过环境变量切换 npm 镜像源以适配中国大陆网络环境:
```bash
docker volume create duoji-node-modules
docker volume create duoji-npm-cache
docker run --rm \
-v "$PWD":/src \
-v duoji-node-modules:/src/node_modules \
-v duoji-npm-cache:/root/.npm \
-w /src \
-e npm_config_registry=https://registry.npmmirror.com \
node:22-alpine \
sh -lc 'npm ci --no-audit --no-fund && npm run build:static'
```
### 为什么这样设计
- **`npm_config_registry` 环境变量** 而不是 `npm config set`:避免在仓库或容器里写死镜像配置;非中国大陆环境不传这个环境变量就自动走官方源。
- **named volume `duoji-node-modules`** 替代 `--tmpfs`tmpfs 默认 `noexec`,会导致 esbuild 的 postinstall 可执行文件无法运行named volume 既隔离宿主机又支持可执行权限。
- **named volume `duoji-npm-cache`** 缓存 `/root/.npm`:第二次及以后的构建会复用缓存,`npm ci` 显著加快(从几十秒降到几秒)。注意:`npm ci` 仍会重建 `node_modules`(不修改 package-lock加速主要来自缓存元数据`node_modules` volume 主要用来隔离宿主机并解决执行权限问题。
- **`--no-audit --no-fund`**:跳过 npm 审计和赞助信息,加快 CI/部署速度。
### 清理构建缓存
需要彻底清理时执行:
```bash
docker volume rm duoji-node-modules
docker volume rm duoji-npm-cache
```
构建完成后,网站文件位于 `dist-static/`,入口是 `dist-static/index.html`。当前静态入口不依赖 `app/layout.tsx` 的动态 metadata因此不要用 `npm run build` 代替 `npm run build:static`
## 与现有 Caddy 共存
### 1. 同步静态文件
以下路径是示例,实际执行时让部署 AI 先确认服务器目录和权限:
```bash
sudo mkdir -p /var/www/duoji
sudo rsync -a --delete dist-static/ /var/www/duoji/
```
### 2. 给 Caddy 服务增加只读目录挂载
在现有 Docker Compose 的 Caddy 服务中增加:
```yaml
volumes:
- /var/www/duoji:/srv/duoji:ro
```
不要覆盖原有 volumes只追加这一项。容器内的 `/srv/duoji` 对应本项目的静态文件目录。
### 3. 增加 Caddy 站点
将 [`deploy/Caddyfile.duoji`](deploy/Caddyfile.duoji) 的内容追加到现有 Caddyfile。配置核心如下
```caddy
duoji.dbpi.com.cn {
root * /srv/duoji
encode gzip zstd
try_files {path} /index.html
file_server
}
```
修改前先备份 Caddyfile并确认没有同名的 `duoji.dbpi.com.cn` 配置。修改后先验证:
```bash
docker exec galderma-caddy-1 caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
```
由于新增了 bind mount `/var/www/duoji:/srv/duoji:ro`**必须重启 Caddy 容器才会挂载**`caddy reload` 不会重新挂载 volume执行
```bash
cd /root/galderma
docker-compose up -d caddy
```
实际影响约 3 秒,仅重启 Caddygalderma-api / galderma-db 容器不受影响。如果现有容器内的 Caddyfile 路径不同,以 Docker Compose 的实际挂载路径为准。不要为了部署本项目执行整个 Compose 栈的重建或清理操作。
### 4. 验证
```bash
curl -I https://duoji.dbpi.com.cn/
curl -I https://duoji.dbpi.com.cn/catalog-products/precision-gears/418.jpg
curl -I https://duoji.dbpi.com.cn/catalog-products/precision-gears-size/418.jpg
```
浏览器中再检查首页、`#selector``#products``#precision-gears`,并打开任意精密齿轮产品确认"产品尺寸"可以切换。
## 后续更新
首次部署后已经创建了 `duoji-node-modules``duoji-npm-cache` 两个 named volume后续更新直接复用
```bash
git pull --ff-only origin main
docker run --rm \
-v "$PWD":/src \
-v duoji-node-modules:/src/node_modules \
-v duoji-npm-cache:/root/.npm \
-w /src \
-e npm_config_registry=https://registry.npmmirror.com \
node:22-alpine \
sh -lc 'npm ci --no-audit --no-fund && npm run build:static'
sudo rsync -a --delete dist-static/ /var/www/duoji/
```
只更新静态文件时不需要重新构建或清理其他业务容器,也不需要重启 Caddy 容器Caddy 的 `file_server` 每次请求都会读磁盘,新文件一上来就生效)。仅当 `docker-compose.yml` 或 Caddyfile 本身有改动时才需要 `docker-compose up -d caddy`
## 其他部署方式
仓库中的 [`Dockerfile.static`](Dockerfile.static) 和 [`deploy/nginx-static.conf`](deploy/nginx-static.conf) 可用于独立服务器或独立容器场景,但不适用于当前已经由 Caddy 占用 80/443 的服务器。当前服务器优先使用"静态文件 + 现有 Caddy"方案。
## 注意事项
- 静态版的"申请样品"和"联系我们"表单目前只在浏览器中生成演示编号,不会将线索写入 CRM 或数据库。
- 如果后续要保存客户线索,需要另行接入表单 API、企业微信、CRM 或数据库,不要把密钥写进前端代码。
- 页面使用根路径资源,建议部署在域名根路径,不建议直接部署到 `/servo/` 子目录。
- 目前尺寸图仅在产品详情弹窗切换到"产品尺寸"时加载,图片体积主要影响磁盘和用户主动查看尺寸图时的下载,不会首屏一次性加载全部图片。

12
Dockerfile.static Normal file
View File

@@ -0,0 +1,12 @@
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build:static
FROM nginx:1.27-alpine
COPY deploy/nginx-static.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist-static /usr/share/nginx/html
EXPOSE 80

View File

@@ -16,6 +16,10 @@ npm run dev
npm run build npm run build
``` ```
## Static Deployment
This project also supports a no-backend static build. Run `npm run build:static` and publish the generated `dist-static/` directory to Nginx, object storage, or another static file server. See [DEPLOY_STATIC.md](DEPLOY_STATIC.md) for instructions that can be handed directly to a Git AI deployment agent.
This starter does not use `wrangler.jsonc`. This starter does not use `wrangler.jsonc`.
## Included Shape ## Included Shape

137
app/catalog-accessories.ts Normal file
View File

@@ -0,0 +1,137 @@
import type { CatalogProduct } from "./catalog-data";
// 舵盘配件来自高徳乐商城产品资料,作为官方目录的独立配件分类展示。
export const STEERING_WHEEL_ACCESSORIES: CatalogProduct[] = [
{
id: "shop-563",
category: "舵盘配件",
name: "9克舵机舵盘配件",
code: "2.A.LS009.001A",
image: "/catalog-products/steering-wheel/563.png",
page: 0,
specs: {
"花键大小": "∅5.0x21T",
"类别": "四叶舵盘",
"产品名称": "四叶舵盘_花键直径∅5.0_21T",
"材质": "POM",
"长mm": "30.30",
"宽mm": "30.30",
"高mm": "3.80",
"重量": "0.415",
"可选编码": "2.A.LS009.001A、2.A.LS009.002A、2.A.LS009.003A、2.A.LS009.004A、2.A.LS120.003A、2.A.LS009.006A、2.A.LS009.007A、2.A.LS009.008A、2.A.LS009.009A",
"参考单价": "¥0.096",
},
torqueValue: null,
ratedVoltage: null,
},
{
id: "shop-565",
category: "舵盘配件",
name: "17克舵机舵盘配件",
code: "2.A.LS018.001A",
image: "/catalog-products/steering-wheel/565.png",
page: 0,
specs: {
"花键大小": "∅6.0x25T",
"类别": "六叶舵盘",
"产品名称": "六叶舵盘_花键直径∅6.0_25T",
"材质": "POM",
"长mm": "31.10",
"宽mm": "27.54",
"高mm": "5.48",
"重量": "1.1",
"可选编码": "2.A.LS018.001A、2.A.LS018.002A、2.A.LS018.003A、2.A.LS018.004A、2.A.LS018.005A、2.A.LS018.006A、2.A.LS018.007A、2.A.LS018.008A",
"参考单价": "¥0.124",
},
torqueValue: null,
ratedVoltage: null,
},
{
id: "shop-566",
category: "舵盘配件",
name: "3.7克舵机舵盘配件",
code: "2.A.LS028.001A",
image: "/catalog-products/steering-wheel/566.png",
page: 0,
specs: {
"花键大小": "∅3.91x28T",
"类别": "长二叶舵盘",
"产品名称": "长二叶舵盘_花键直径∅4.0_28T",
"材质": "POM",
"长mm": "26.79",
"宽mm": "5.50",
"高mm": "3.30",
"重量": "0.18",
"可选编码": "2.A.LS028.001A、2.A.LS028.002A、2.A.LS028.003A、2.A.LS028.004A、2.A.LS028.005A、2.A.LS120.004A",
"参考单价": "¥0.089",
},
torqueValue: null,
ratedVoltage: null,
},
{
id: "shop-567",
category: "舵盘配件",
name: "37克舵机舵盘配件",
code: "2.A.LS018.001A",
image: "/catalog-products/steering-wheel/567.png",
page: 0,
specs: {
"花键大小": "∅6.0x25T",
"类别": "六叶舵盘",
"产品名称": "六叶舵盘_花键直径∅6.0_25T",
"材质": "POM",
"长mm": "31.10",
"宽mm": "27.54",
"高mm": "5.48",
"重量": "1.1",
"可选编码": "2.A.LS018.001A、2.A.LS018.002A、2.A.LS018.003A、2.A.LS018.004A、2.A.LS018.005A、2.A.LS018.006A、2.A.LS018.007A、2.A.LS018.008A、2.A.LS120.001A",
"参考单价": "¥0.124",
},
torqueValue: null,
ratedVoltage: null,
},
{
id: "shop-568",
category: "舵盘配件",
name: "2克舵机舵盘配件",
code: "2.A.LS028.001A",
image: "/catalog-products/steering-wheel/568.png",
page: 0,
specs: {
"花键大小": "∅3.91x28T",
"类别": "长二叶舵盘",
"产品名称": "长二叶舵盘_花键直径∅4.0_28T",
"材质": "POM",
"长mm": "26.79",
"宽mm": "5.50",
"高mm": "3.30",
"重量": "0.18",
"可选编码": "2.A.LS028.001A、2.A.LS028.002A、2.A.LS028.003A、2.A.LS028.004A、2.A.LS028.005A、2.A.LS120.004A",
"参考单价": "¥0.089",
},
torqueValue: null,
ratedVoltage: null,
},
{
id: "shop-572",
category: "舵盘配件",
name: "25克舵机舵盘配件",
code: "2.A.LS018.001A",
image: "/catalog-products/steering-wheel/572.png",
page: 0,
specs: {
"花键大小": "∅6.0x25T",
"类别": "六叶舵盘",
"产品名称": "六叶舵盘_花键直径∅6.0_25T",
"材质": "POM",
"长mm": "31.10",
"宽mm": "27.54",
"高mm": "5.48",
"重量": "1.1",
"可选编码": "2.A.LS018.001A、2.A.LS018.002A、2.A.LS018.003A、2.A.LS018.004A、2.A.LS018.005A、2.A.LS018.006A、2.A.LS018.007A、2.A.LS018.008A",
"参考单价": "¥0.124",
},
torqueValue: null,
ratedVoltage: null,
},
];

View File

@@ -7,6 +7,7 @@ export type CatalogProduct = {
name: string; name: string;
code: string; code: string;
image: string; image: string;
dimensionImage?: string;
page: number; page: number;
specs: Record<string, string>; specs: Record<string, string>;
torqueValue: number | null; torqueValue: number | null;
@@ -64,7 +65,7 @@ export const CATALOG_PRODUCTS: CatalogProduct[] = [
}, },
{ {
"id": "p01-5-4-a-05-a0008", "id": "p01-5-4-a-05-a0008",
"category": "标准舵机", "category": "异形舵机",
"name": "1.7g舵机(异形款/塑胶齿/三线)", "name": "1.7g舵机(异形款/塑胶齿/三线)",
"code": "4.A.05.A0008", "code": "4.A.05.A0008",
"image": "/catalog-products/p01-4-a-05-a0008.webp", "image": "/catalog-products/p01-4-a-05-a0008.webp",
@@ -7170,8 +7171,8 @@ export const CATALOG_PRODUCTS: CatalogProduct[] = [
]; ];
export const CATALOG_COUNTS: Record<string, number> = { export const CATALOG_COUNTS: Record<string, number> = {
"标准舵机": 118, "标准舵机": 117,
"异形舵机": 13, "异形舵机": 14,
"全金属舵机": 66, "全金属舵机": 66,
"大扭矩舵机": 26, "大扭矩舵机": 26,
"积木舵机": 20, "积木舵机": 20,

153
app/catalog-image-map.ts Normal file
View File

@@ -0,0 +1,153 @@
import type { CatalogProduct } from "./catalog-data";
// The ecommerce image archive groups products by family rather than by the
// individual model code used in the PDF catalog. These are the corresponding
// family-level main images copied into public/catalog-products/source-main.
const SOURCE_MAIN_IMAGES = {
"1004": "/catalog-products/source-main/1004-main.png",
"258": "/catalog-products/source-main/258-main.jpg",
"259": "/catalog-products/source-main/259-main.jpg",
"266": "/catalog-products/source-main/266-main.jpg",
"267": "/catalog-products/source-main/267-main.jpg",
"268": "/catalog-products/source-main/268-main.jpg",
"270": "/catalog-products/source-main/270-main.jpg",
"271": "/catalog-products/source-main/271-main.jpg",
"272": "/catalog-products/source-main/272-main.jpg",
"273": "/catalog-products/source-main/273-main.jpg",
"274": "/catalog-products/source-main/274-main.jpg",
"275": "/catalog-products/source-main/275-main.jpg",
"276": "/catalog-products/source-main/276-main.jpg",
"308": "/catalog-products/source-main/308-main.png",
"309": "/catalog-products/source-main/309-main.png",
"310": "/catalog-products/source-main/310-main.png",
"311": "/catalog-products/source-main/311-main.png",
"313": "/catalog-products/source-main/313-main.png",
} as const;
const SOURCE_SIZE_IMAGES = {
"1004": "/catalog-products/size-reference/1004-size.png",
"258": "/catalog-products/size-reference/258-size.jpg",
"259": "/catalog-products/size-reference/259-size.jpg",
"266": "/catalog-products/size-reference/266-size.jpg",
"267": "/catalog-products/size-reference/267-size.jpg",
"268": "/catalog-products/size-reference/268-size.jpg",
"270": "/catalog-products/size-reference/270-size.jpg",
"271": "/catalog-products/size-reference/271-size.jpg",
"272": "/catalog-products/size-reference/272-size.jpg",
"273": "/catalog-products/size-reference/273-size.jpg",
"274": "/catalog-products/size-reference/274-size.jpg",
"275": "/catalog-products/size-reference/275-size.jpg",
"276": "/catalog-products/size-reference/276-size.jpg",
"308": "/catalog-products/size-reference/308-size.png",
"309": "/catalog-products/size-reference/309-size.png",
"310": "/catalog-products/size-reference/310-size.png",
"311": "/catalog-products/size-reference/311-size.png",
"313": "/catalog-products/size-reference/313-size.png",
"563": "/catalog-products/size-reference/563-size.png",
"565": "/catalog-products/size-reference/565-size.png",
"566": "/catalog-products/size-reference/566-size.png",
"567": "/catalog-products/size-reference/567-size.png",
"568": "/catalog-products/size-reference/568-size.png",
"572": "/catalog-products/size-reference/572-size.png",
} as const;
type ProductImageSet = Record<string, string>;
type SourceImageKey = keyof typeof SOURCE_MAIN_IMAGES;
function hasWeight(name: string, grams: string) {
return new RegExp(`${grams}\\s*(?:g|克)`, "i").test(name);
}
function getGearSeries(name: string) {
if (/半金属/.test(name)) return "semi";
if (/全金属|金属/.test(name)) return "metal";
if (/塑胶|塑料/.test(name)) return "plastic";
return null;
}
function getSmallServoImage(name: string, images: ProductImageSet): string | null {
if (hasWeight(name, "1\\.7")) return images["1004"];
const gearSeries = getGearSeries(name);
if (hasWeight(name, "2") && gearSeries === "plastic") return images["272"];
if (hasWeight(name, "3\\.7") && gearSeries === "plastic") return images["271"];
if (hasWeight(name, "9")) {
if (gearSeries === "metal") return images["266"];
if (gearSeries === "semi") return images["259"];
if (gearSeries === "plastic") return images["258"];
}
if (hasWeight(name, "17")) {
if (gearSeries === "metal") return images["274"];
if (gearSeries === "plastic") return images["273"];
}
if (hasWeight(name, "25")) {
if (gearSeries === "metal") return images["276"];
if (gearSeries === "plastic") return images["275"];
}
if (hasWeight(name, "37")) {
if (gearSeries === "metal") return images["270"];
if (gearSeries === "semi") return images["268"];
if (gearSeries === "plastic") return images["267"];
}
return null;
}
function getBrickServoImage(name: string, images: ProductImageSet): string | null {
if (name.includes("3x3x3")) return images["311"];
if (name.includes("3x3x7")) return images["310"];
if (name.includes("3x7x5")) return images["309"];
if (name.includes("6x6x5")) return images["308"];
if (name.includes("11x5x4")) return images["313"];
return null;
}
export function getCatalogProductImage(product: CatalogProduct) {
if (product.category === "积木舵机") {
return getBrickServoImage(product.name, SOURCE_MAIN_IMAGES) ?? product.image;
}
if (product.category === "标准舵机" || product.category === "异形舵机") {
return getSmallServoImage(product.name, SOURCE_MAIN_IMAGES) ?? product.image;
}
return product.image;
}
export function getCatalogProductThumbnail(src: string) {
if (src.includes("/catalog-products/precision-gears/")) {
return src.replace("/catalog-products/precision-gears/", "/catalog-products/precision-gears-thumb/").replace(/\.(?:jpe?g|png)$/i, ".webp");
}
if (src.includes("/catalog-products/source-main/")) {
return src.replace("/catalog-products/source-main/", "/catalog-products/source-main-thumb/").replace(/\.(?:jpe?g|png)$/i, ".webp");
}
return src;
}
export function getCatalogProductDimensionImage(product: CatalogProduct) {
if (product.category === "精密齿轮配件") {
return product.dimensionImage ?? null;
}
if (product.category === "舵盘配件") {
return SOURCE_SIZE_IMAGES[product.id.replace("shop-", "")] ?? null;
}
if (product.category === "积木舵机") {
return getBrickServoImage(product.name, SOURCE_SIZE_IMAGES);
}
if (product.category === "标准舵机" || product.category === "异形舵机") {
return getSmallServoImage(product.name, SOURCE_SIZE_IMAGES);
}
return null;
}
export const CATALOG_SOURCE_IMAGE_KEYS: SourceImageKey[] = Object.keys(SOURCE_MAIN_IMAGES) as SourceImageKey[];

View File

@@ -39,24 +39,26 @@ a { color: inherit; text-decoration: none; }
border-bottom: 1px solid rgba(16,24,43,.08); border-bottom: 1px solid rgba(16,24,43,.08);
backdrop-filter: blur(14px); backdrop-filter: blur(14px);
} }
.brand { display: inline-flex; align-items: center; gap: 11px; } .brand { display: inline-flex; align-items: center; }
.brand-mark { .brand-wordmark { display: grid; gap: 3px; }
width: 38px; height: 38px; border-radius: 11px; .brand-wordmark strong {
display: grid; place-items: center; color: var(--ink); font-family: Arial, "Helvetica Neue", sans-serif;
color: white; font-size: 21px; font-weight: 900; font-style: italic; font-size: 18px; font-style: italic; font-weight: 900; letter-spacing: .045em; line-height: .9;
background: linear-gradient(145deg, #2d75ff, #0e48b9);
box-shadow: 0 7px 18px rgba(24,94,232,.25);
} }
.brand > span:last-child { display: grid; gap: 1px; } .brand-wordmark small { color: var(--blue); font-size: 12px; font-weight: 850; letter-spacing: .16em; line-height: 1; }
.brand strong { font-size: 14px; letter-spacing: .08em; line-height: 1; }
.brand small { color: var(--muted); font-size: 10px; letter-spacing: .04em; }
.site-header nav { display: flex; gap: 34px; font-size: 14px; color: #536078; } .site-header nav { display: flex; gap: 34px; font-size: 14px; color: #536078; }
.site-header nav a:hover { color: var(--blue); } .site-header nav a:hover { color: var(--blue); }
.header-actions { display: flex; align-items: center; gap: 8px; }
.cart-header-button { position: relative; min-height: 38px; padding: 7px 12px 7px 9px; display: inline-flex; align-items: center; gap: 5px; border: 1px solid #dbe4f1; border-radius: 10px; color: #536078; background: white; cursor: pointer; font-size: 10px; font-weight: 750; }
.cart-header-button:hover { border-color: #a9c4f2; color: var(--blue); background: #f7faff; }
.cart-header-button > span { font-size: 15px; line-height: 1; }
.cart-header-button b { min-width: 17px; height: 17px; padding: 0 4px; display: inline-grid; place-items: center; border-radius: 999px; color: white; background: var(--blue); font-size: 9px; }
.header-cta { .header-cta {
border: 0; border-radius: 10px; padding: 11px 17px; border: 0; border-radius: 10px; padding: 11px 17px;
background: var(--ink); color: white; cursor: pointer; font-weight: 650; background: var(--ink); color: white; cursor: pointer; font-weight: 650;
} }
.header-cta span { margin-left: 8px; color: #82aaff; } .header-cta span { margin-left: 8px; color: #82aaff; }
.mobile-menu-toggle { display: none; }
.hero { .hero {
min-height: 720px; min-height: 720px;
@@ -92,6 +94,7 @@ a { color: inherit; text-decoration: none; }
.selector-shell { .selector-shell {
min-height: 585px; min-height: 585px;
scroll-margin-top: 94px;
background: white; background: white;
border: 1px solid rgba(73,98,142,.15); border: 1px solid rgba(73,98,142,.15);
border-radius: 22px; border-radius: 22px;
@@ -125,8 +128,8 @@ a { color: inherit; text-decoration: none; }
.step-kicker { margin: 0 0 7px; color: var(--blue); font-size: 11px; letter-spacing: .08em; font-weight: 800; text-transform: uppercase; } .step-kicker { margin: 0 0 7px; color: var(--blue); font-size: 11px; letter-spacing: .08em; font-weight: 800; text-transform: uppercase; }
.question-step h2, .result-heading h2 { margin: 0; font-size: 23px; letter-spacing: -.025em; } .question-step h2, .result-heading h2 { margin: 0; font-size: 23px; letter-spacing: -.025em; }
.step-help { margin: 7px 0 19px; color: #7c8799; font-size: 12px; } .step-help { margin: 7px 0 19px; color: #7c8799; font-size: 12px; }
.choice-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .choice-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; }
.choice-card { position: relative; min-height: 76px; padding: 14px 35px 14px 15px; display: grid; gap: 5px; text-align: left; border: 1px solid #dfe5ee; border-radius: 11px; background: white; cursor: pointer; } .choice-card { position: relative; min-height: 68px; padding: 11px 30px 11px 13px; display: grid; align-content: center; gap: 3px; text-align: left; border: 1px solid #dfe5ee; border-radius: 11px; background: white; cursor: pointer; }
.choice-card:hover { border-color: #a8c3f4; background: #fbfdff; } .choice-card:hover { border-color: #a8c3f4; background: #fbfdff; }
.choice-card.selected { border-color: var(--blue); background: var(--blue-soft); box-shadow: inset 0 0 0 1px var(--blue); } .choice-card.selected { border-color: var(--blue); background: var(--blue-soft); box-shadow: inset 0 0 0 1px var(--blue); }
.choice-card b { font-size: 13px; } .choice-card b { font-size: 13px; }
@@ -137,19 +140,28 @@ a { color: inherit; text-decoration: none; }
.back-button, .restart-button { padding: 11px 2px; border: 0; color: #798599; background: transparent; cursor: pointer; font-size: 12px; } .back-button, .restart-button { padding: 11px 2px; border: 0; color: #798599; background: transparent; cursor: pointer; font-size: 12px; }
.next-button { min-width: 150px; padding: 13px 20px; } .next-button { min-width: 150px; padding: 13px 20px; }
.next-button:disabled { cursor: not-allowed; opacity: .35; transform: none; box-shadow: none; } .next-button:disabled { cursor: not-allowed; opacity: .35; transform: none; box-shadow: none; }
.field-stack { padding: 18px 18px 15px; border-radius: 12px; background: #f6f8fc; } .field-stack { margin-top: 13px; padding: 15px 16px 13px; border-radius: 12px; background: #f6f8fc; }
.field-label { display: flex; justify-content: space-between; color: #657187; font-size: 12px; } .field-label { display: flex; align-items: center; justify-content: space-between; gap: 12px; color: #657187; font-size: 12px; }
.field-label strong { color: var(--blue); font-size: 15px; } .field-label strong { color: var(--blue); font-size: 15px; }
.range-input { width: 100%; margin: 24px 0 6px; accent-color: var(--blue); } .torque-entry { display: flex; align-items: center; gap: 6px; }
.torque-entry input { width: 86px; height: 34px; padding: 0 9px; text-align: right; font-weight: 750; }
.torque-entry strong { font-size: 10px; white-space: nowrap; }
.range-input { width: 100%; margin: 16px 0 5px; accent-color: var(--blue); }
.range-scale { display: flex; justify-content: space-between; color: #9aa3b1; font-size: 9px; } .range-scale { display: flex; justify-content: space-between; color: #9aa3b1; font-size: 9px; }
.torque-presets { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; margin-top: 10px; }
.torque-presets button { min-width: 0; padding: 6px 2px; border: 1px solid #d8e0eb; border-radius: 7px; color: #657187; background: white; cursor: pointer; font-size: 9px; }
.torque-presets button.active { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); font-weight: 750; }
.two-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 15px; } .two-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 15px; }
.compact-fields { margin-top: 0; }
.parameter-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; margin-top: 13px; }
.select-field, .text-field { display: grid; gap: 7px; } .select-field, .text-field { display: grid; gap: 7px; }
.select-field span, .text-field span, .field-title, .sample-form label span { color: #68758a; font-size: 11px; font-weight: 650; } .select-field span, .text-field span, .field-title, .sample-form label span { color: #68758a; font-size: 11px; font-weight: 650; }
select, input, textarea { outline: none; border: 1px solid #dbe2ec; border-radius: 9px; color: var(--ink); background: white; } select, input, textarea { outline: none; border: 1px solid #dbe2ec; border-radius: 9px; color: var(--ink); background: white; }
select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(24,94,232,.1); } select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(24,94,232,.1); }
.select-field select { height: 46px; padding: 0 12px; } .select-field select, .parameter-input { width: 100%; height: 43px; padding: 0 12px; }
.text-field { margin-top: 13px; } .text-field { margin-top: 13px; }
.text-field input { height: 44px; padding: 0 13px; } .text-field input { height: 44px; padding: 0 13px; }
.project-notes textarea { min-height: 78px; padding: 11px 13px; resize: vertical; line-height: 1.55; }
.field-group { margin: 17px 0 13px; } .field-group { margin: 17px 0 13px; }
.pill-row { display: flex; gap: 7px; margin-top: 8px; } .pill-row { display: flex; gap: 7px; margin-top: 8px; }
.pill-row button { flex: 1; padding: 10px 7px; border: 1px solid #dce3ed; border-radius: 9px; background: white; cursor: pointer; font-size: 11px; } .pill-row button { flex: 1; padding: 10px 7px; border: 1px solid #dce3ed; border-radius: 9px; background: white; cursor: pointer; font-size: 11px; }
@@ -157,35 +169,40 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
.result-panel { padding: 24px 28px 28px; } .result-panel { padding: 24px 28px 28px; }
.result-heading { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; } .result-heading { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; }
.best-match { display: grid; grid-template-columns: 180px 1fr; border: 1px solid #cfdaf0; border-radius: 15px; overflow: hidden; box-shadow: 0 8px 24px rgba(38,70,125,.08); } .recommendation-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: stretch; gap: 10px; }
.match-image-wrap { position: relative; min-height: 270px; background: #eff4fb; } .recommendation-card { min-width: 0; min-height: 442px; display: flex; flex-direction: column; overflow: hidden; border: 1px solid #dce3ed; border-radius: 14px; background: white; box-shadow: 0 8px 24px rgba(38,70,125,.06); }
.match-image-wrap img { width: 100%; height: 100%; object-fit: cover; } .recommendation-card.is-primary { border-color: #cfdaf0; box-shadow: 0 8px 24px rgba(38,70,125,.1); }
.match-badge { position: absolute; left: 12px; top: 12px; padding: 6px 9px; border-radius: 7px; color: white; background: var(--orange); font-size: 9px; font-weight: 800; } .recommendation-image-wrap { position: relative; width: 100%; padding: 0; display: grid; place-items: center; overflow: hidden; border: 0; color: inherit; background: #eff4fb; cursor: pointer; text-align: left; }
.match-content { position: relative; padding: 19px 19px 16px; } .recommendation-image-wrap img { display: block; width: 100%; height: auto; object-fit: contain; mix-blend-mode: multiply; transition: transform .25s ease; }
.match-score { position: absolute; right: 16px; top: 15px; display: grid; text-align: right; color: #7c889a; font-size: 9px; } .recommendation-image-wrap:hover img { transform: scale(1.04); }
.match-score strong { color: var(--blue); font-size: 18px; } .recommendation-image-wrap:focus-visible { outline: 3px solid rgba(24,94,232,.28); outline-offset: -3px; }
.match-badge { position: absolute; left: 12px; bottom: 12px; padding: 6px 9px; border-radius: 7px; color: white; background: var(--orange); font-size: 9px; font-weight: 800; box-shadow: 0 5px 13px rgba(80,45,13,.2); }
.recommendation-zoom-hint { position: absolute; right: 12px; bottom: 12px; padding: 5px 8px; border: 1px solid rgba(255,255,255,.7); border-radius: 6px; color: #53637b; background: rgba(255,255,255,.9); font-size: 8px; font-weight: 700; }
.recommendation-content { position: relative; flex: 1; display: flex; flex-direction: column; min-width: 0; padding: 14px 13px 12px; }
.product-code { margin: 0 0 4px; color: #8b96a8; font-size: 9px; } .product-code { margin: 0 0 4px; color: #8b96a8; font-size: 9px; }
.match-content h3 { max-width: 250px; margin: 0; font-size: 19px; } .recommendation-content h3 { min-height: 38px; max-width: 100%; margin: 0; overflow: hidden; font-size: 14px; line-height: 1.35; }
.product-subtitle { margin: 5px 0 13px; color: #7b8799; font-size: 10px; } .product-subtitle { margin: 5px 0 13px; color: #7b8799; font-size: 10px; }
.metric-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; } .metric-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.metric { padding: 8px 9px; border-radius: 8px; background: #f4f7fb; display: grid; gap: 3px; } .metric { padding: 8px 9px; border-radius: 8px; background: #f4f7fb; display: grid; gap: 3px; }
.metric span { color: #8994a6; font-size: 8px; } .metric span { color: #8994a6; font-size: 8px; }
.metric strong { font-size: 10px; } .metric strong { font-size: 10px; }
.reason-list { display: flex; gap: 10px; flex-wrap: wrap; margin: 12px 0 10px; padding: 0; list-style: none; color: #2d6e55; font-size: 9px; } .reason-list { display: flex; gap: 10px; flex-wrap: wrap; margin: 12px 0 10px; padding: 0; list-style: none; color: #2d6e55; font-size: 9px; }
.recommendation-insights { display: grid; gap: 7px; margin: 9px 0 10px; }
.recommendation-insights > div { min-width: 0; padding: 7px 8px; border: 1px solid #e6edf6; border-radius: 8px; background: #fbfcfe; }
.recommendation-insights > div > strong { display: block; color: #68778e; font-size: 8px; }
.recommendation-insights .reason-list { margin: 5px 0 0; gap: 7px; font-size: 8px; }
.recommendation-insights p { margin: 5px 0 0; color: #53637b; font-size: 8px; line-height: 1.45; }
.price-line { display: flex; align-items: baseline; gap: 6px; padding-top: 9px; border-top: 1px solid #edf0f4; } .price-line { display: flex; align-items: baseline; gap: 6px; padding-top: 9px; border-top: 1px solid #edf0f4; }
.price-line span { color: #7c8798; font-size: 9px; } .price-line span { color: #7c8798; font-size: 9px; }
.price-line strong { color: #e55726; font-size: 20px; } .price-line strong { color: #e55726; font-size: 20px; }
.price-line small { color: #9aa4b3; font-size: 8px; } .price-line small { color: #9aa4b3; font-size: 8px; }
.source-line { display: flex; align-items: center; gap: 7px; margin-top: auto; padding-top: 9px; border-top: 1px solid #edf0f4; }
.source-line span { color: #7c8798; font-size: 9px; }
.source-line strong { color: var(--blue); font-size: 10px; }
.source-line small { margin-left: auto; color: #9aa4b3; font-size: 8px; }
.engineer-alert { display: flex; align-items: center; gap: 10px; margin-top: 10px; padding: 10px 12px; border: 1px solid #f0d29e; border-radius: 9px; background: #fff9ef; } .engineer-alert { display: flex; align-items: center; gap: 10px; margin-top: 10px; padding: 10px 12px; border: 1px solid #f0d29e; border-radius: 9px; background: #fff9ef; }
.engineer-alert span { flex: 0 0 auto; padding: 4px 7px; border-radius: 5px; color: #9b5a0a; background: #ffe7bc; font-size: 8px; font-weight: 800; } .engineer-alert span { flex: 0 0 auto; padding: 4px 7px; border-radius: 5px; color: #9b5a0a; background: #ffe7bc; font-size: 8px; font-weight: 800; }
.engineer-alert p { margin: 0; color: #815e2f; font-size: 9px; } .engineer-alert p { margin: 0; color: #815e2f; font-size: 9px; }
.alternative-list { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin-top: 10px; }
.alternative-card { min-width: 0; display: grid; grid-template-columns: 58px 1fr; align-items: center; border: 1px solid #e1e6ee; border-radius: 10px; overflow: hidden; }
.alternative-card img { width: 58px; height: 58px; object-fit: cover; background: #f0f3f7; }
.alternative-card div { min-width: 0; padding: 7px 9px; }
.alternative-card span { color: var(--blue); font-size: 8px; font-weight: 700; }
.alternative-card h4 { margin: 2px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; }
.alternative-card p { margin: 0; color: #8a95a6; font-size: 8px; }
.result-actions { display: flex; gap: 9px; margin-top: 14px; } .result-actions { display: flex; gap: 9px; margin-top: 14px; }
.result-actions .panel-primary { flex: 1; } .result-actions .panel-primary { flex: 1; }
.outline-button { border: 1px solid #cfd7e4; border-radius: 10px; padding: 0 17px; background: white; cursor: pointer; font-size: 11px; font-weight: 650; } .outline-button { border: 1px solid #cfd7e4; border-radius: 10px; padding: 0 17px; background: white; cursor: pointer; font-size: 11px; font-weight: 650; }
@@ -205,6 +222,22 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
.success-card h3 { margin: 0; } .success-card h3 { margin: 0; }
.success-card > p:not(.step-kicker) { color: #66776f; font-size: 10px; } .success-card > p:not(.step-kicker) { color: #66776f; font-size: 10px; }
.success-card .outline-button { min-height: 38px; } .success-card .outline-button { min-height: 38px; }
.custom-result-card {
padding: 22px;
display: grid;
grid-template-columns: 58px 1fr;
gap: 10px 16px;
border: 1px solid #bdd0f2;
border-radius: 15px;
background: linear-gradient(145deg, #f7faff, #edf4ff);
}
.custom-result-mark { width: 58px; height: 58px; display: grid; place-items: center; border-radius: 16px; color: white; background: linear-gradient(145deg, #397fff, #1454d0); font-size: 13px; font-weight: 850; box-shadow: 0 10px 22px rgba(24,94,232,.2); }
.custom-result-card > div:nth-child(2) > span { color: var(--blue); font-size: 9px; font-weight: 800; letter-spacing: .08em; }
.custom-result-card h3 { margin: 4px 0 7px; font-size: 18px; line-height: 1.35; }
.custom-result-card > div:nth-child(2) > p { margin: 0; color: #6d7b91; font-size: 10px; line-height: 1.65; }
.custom-requirement-grid { grid-column: 1 / -1; margin-top: 8px; }
.custom-notes { grid-column: 1 / -1; margin: 2px 0 0; padding: 10px 12px; border-radius: 8px; color: #5d6c82; background: rgba(255,255,255,.8); font-size: 9px; line-height: 1.6; }
.custom-contact-form { border-color: #afc8f2; }
.capability-strip { padding: 28px max(32px, calc((100vw - 1120px) / 2)); display: grid; grid-template-columns: repeat(4, 1fr); background: var(--ink); color: white; } .capability-strip { padding: 28px max(32px, calc((100vw - 1120px) / 2)); display: grid; grid-template-columns: repeat(4, 1fr); background: var(--ink); color: white; }
.capability-strip div { display: grid; gap: 4px; text-align: center; border-right: 1px solid rgba(255,255,255,.13); } .capability-strip div { display: grid; gap: 4px; text-align: center; border-right: 1px solid rgba(255,255,255,.13); }
@@ -217,7 +250,7 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
.section-heading > p { max-width: 480px; margin: 0; color: #6e7a8e; line-height: 1.8; font-size: 13px; } .section-heading > p { max-width: 480px; margin: 0; color: #6e7a8e; line-height: 1.8; font-size: 13px; }
.catalog-summary { .catalog-summary {
display: grid; display: grid;
grid-template-columns: repeat(7, 1fr); grid-template-columns: repeat(8, 1fr);
gap: 9px; gap: 9px;
margin-bottom: 22px; margin-bottom: 22px;
} }
@@ -237,6 +270,7 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
.catalog-summary button.active { border-color: var(--blue); background: var(--blue-soft); } .catalog-summary button.active { border-color: var(--blue); background: var(--blue-soft); }
.catalog-summary strong { color: var(--blue); font-size: 19px; } .catalog-summary strong { color: var(--blue); font-size: 19px; }
.catalog-summary span { color: #748196; font-size: 10px; } .catalog-summary span { color: #748196; font-size: 10px; }
.catalog-category-note { margin: -8px 2px 18px; color: #8792a3; font-size: 10px; line-height: 1.6; }
.catalog-toolbar { .catalog-toolbar {
position: sticky; position: sticky;
top: 74px; top: 74px;
@@ -271,6 +305,7 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
background: #e9edf3; background: #e9edf3;
cursor: pointer; cursor: pointer;
} }
.catalog-mobile-filter-toggle { display: none; }
.category-pills { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; } .category-pills { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
.category-pills button { .category-pills button {
padding: 8px 12px; padding: 8px 12px;
@@ -282,9 +317,16 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
font-size: 10px; font-size: 10px;
} }
.category-pills button.active { border-color: var(--blue); color: white; background: var(--blue); } .category-pills button.active { border-color: var(--blue); color: white; background: var(--blue); }
.category-pills button.feature-filter:not(.active) { border-color: #f1bf92; color: #a75a19; background: #fff9f3; }
.catalog-scale-filter { display: grid; grid-template-columns: minmax(240px, 330px) 1fr; align-items: end; gap: 14px; margin-top: 13px; padding-top: 13px; border-top: 1px solid #e7ebf1; }
.catalog-scale-filter label { display: grid; gap: 6px; }
.catalog-scale-filter label > span { color: #68758a; font-size: 10px; font-weight: 700; }
.catalog-scale-filter select { width: 100%; height: 42px; padding: 0 12px; }
.catalog-scale-filter p { margin: 0 0 4px; color: #8792a3; font-size: 9px; line-height: 1.55; }
.catalog-scale-filter p strong { color: #657187; }
.catalog-result-count { margin: 11px 2px 0; color: #8792a3; font-size: 10px; } .catalog-result-count { margin: 11px 2px 0; color: #8792a3; font-size: 10px; }
.catalog-result-count strong { color: var(--blue); font-size: 12px; } .catalog-result-count strong { color: var(--blue); font-size: 12px; }
.catalog-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; } .catalog-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.catalog-card { .catalog-card {
min-width: 0; min-width: 0;
border: 1px solid #dce3ed; border: 1px solid #dce3ed;
@@ -293,24 +335,26 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
background: white; background: white;
transition: transform .22s, border-color .22s, box-shadow .22s; transition: transform .22s, border-color .22s, box-shadow .22s;
} }
.catalog-card:hover { transform: translateY(-3px); border-color: #b7c9e7; box-shadow: 0 15px 34px rgba(30,53,91,.1); } .catalog-card:hover { transform: translateY(-3px); border-color: #dce3ed; box-shadow: 0 15px 34px rgba(30,53,91,.2); }
.catalog-image-button { .catalog-image-button {
position: relative; position: relative;
width: 100%; width: 100%;
height: 190px; height: auto;
display: grid; min-height: 220px;
place-items: center; display: block;
padding: 0;
border: 0; border: 0;
border-bottom: 1px solid #edf0f4; border-bottom: 1px solid #edf0f4;
background: linear-gradient(145deg, #f8fafe, #eef3fa); background: linear-gradient(145deg, #f8fafe, #eef3fa);
cursor: pointer; cursor: pointer;
overflow: hidden; overflow: hidden;
} }
.catalog-image-button img { width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; } .catalog-image-button img { display: block; width: 100%; height: auto; object-fit: contain; mix-blend-mode: multiply; transition: transform .25s ease; }
.catalog-image-button:hover img { transform: none; }
.catalog-image-button small { .catalog-image-button small {
position: absolute; position: absolute;
top: 10px; top: 44px;
left: 10px; right: 10px;
padding: 5px 8px; padding: 5px 8px;
border-radius: 6px; border-radius: 6px;
color: #315686; color: #315686;
@@ -322,11 +366,11 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
.catalog-placeholder { color: #9eb3d3; font-size: 14px; font-weight: 800; letter-spacing: .12em; } .catalog-placeholder { color: #9eb3d3; font-size: 14px; font-weight: 800; letter-spacing: .12em; }
.catalog-card-body { padding: 16px; } .catalog-card-body { padding: 16px; }
.catalog-card-body > p { margin: 0 0 5px; color: #8793a5; font-size: 9px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } .catalog-card-body > p { margin: 0 0 5px; color: #8793a5; font-size: 9px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.catalog-card-body h3 { height: 42px; margin: 0 0 12px; overflow: hidden; font-size: 14px; line-height: 1.5; } .catalog-card-body h3 { min-height: 30px; max-height: 42px; height: auto; margin: 0 0 4px; overflow: hidden; font-size: 14px; line-height: 1.5; }
.catalog-card-body dl { min-height: 83px; margin: 0; padding: 10px; border-radius: 9px; background: #f6f8fb; } .catalog-card-body dl { min-height: 0; margin: 0; padding: 10px; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; border-radius: 9px; background: #f6f8fb; }
.catalog-card-body dl div { display: flex; justify-content: space-between; gap: 8px; padding: 3px 0; font-size: 9px; } .catalog-card-body dl div { min-width: 0; display: grid; align-content: start; gap: 4px; padding: 3px 0; font-size: 9px; }
.catalog-card-body dt { color: #8a95a7; } .catalog-card-body dt { overflow: hidden; color: #8a95a7; text-overflow: ellipsis; white-space: nowrap; }
.catalog-card-body dd { max-width: 58%; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; font-weight: 650; } .catalog-card-body dd { max-width: 100%; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; font-weight: 650; }
.catalog-card-body > button { .catalog-card-body > button {
width: 100%; width: 100%;
margin-top: 12px; margin-top: 12px;
@@ -340,6 +384,160 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
font-size: 10px; font-size: 10px;
font-weight: 700; font-weight: 700;
} }
.add-cart-button { width: 100%; min-height: 33px; margin-top: 10px; padding: 8px 10px; display: inline-flex; align-items: center; justify-content: space-between; border: 1px solid #c9dafa; border-radius: 8px; color: #185ee8; background: #f4f8ff; cursor: pointer; font-size: 10px; font-weight: 750; }
.add-cart-button:hover { border-color: #8eb2f2; color: #104ec6; background: #eaf2ff; }
.add-cart-button span { font-size: 14px; line-height: 1; }
.catalog-card-body > .add-cart-button { margin-top: 5px; }
.precision-gear-card-body .add-cart-button { margin-top: 10px; }
.recommendation-content .add-cart-button { margin-top: 10px; }
.spec-sheet-button { width: 100%; min-height: 33px; margin-top: 5px; padding: 8px 10px; display: inline-flex; align-items: center; justify-content: space-between; border: 1px solid #f1c68f; border-radius: 8px; color: #a76219; background: #fffaf2; cursor: pointer; font-size: 10px; font-weight: 750; }
.spec-sheet-button:hover { border-color: #e6a95d; color: #8d4e0e; background: #fff3df; }
.spec-sheet-button span { font-size: 14px; line-height: 1; }
.catalog-card-body > .spec-sheet-button { margin-top: 5px; }
.precision-gear-card-body .spec-sheet-button { margin-top: 5px; }
.recommendation-content .spec-sheet-button { margin-top: 5px; }
.cart-toast { position: fixed; left: 50%; bottom: 24px; z-index: 120; transform: translateX(-50%); max-width: min(90vw, 380px); padding: 10px 15px; border: 1px solid #cbdaf2; border-radius: 999px; color: #174baf; background: rgba(248,251,255,.96); box-shadow: 0 12px 32px rgba(24,64,126,.18); font-size: 11px; font-weight: 750; text-align: center; backdrop-filter: blur(10px); }
.cart-drawer-backdrop { position: fixed; inset: 0; z-index: 110; display: flex; justify-content: flex-end; background: rgba(7,14,29,.45); backdrop-filter: blur(4px); }
.cart-drawer { width: min(430px, 100%); height: 100%; padding: 28px 24px 22px; display: flex; flex-direction: column; overflow: hidden; background: white; box-shadow: -18px 0 50px rgba(20,43,82,.2); }
.cart-drawer-header { display: flex; align-items: flex-start; justify-content: space-between; padding-bottom: 17px; border-bottom: 1px solid #e5eaf1; }
.cart-drawer-header .step-kicker { margin-bottom: 4px; }
.cart-drawer-header h2 { margin: 0; color: var(--ink); font-size: 24px; letter-spacing: -.03em; }
.cart-drawer-header h2 span { color: var(--blue); font-size: 16px; }
.cart-close-button { width: 34px; height: 34px; border: 0; border-radius: 50%; color: #627086; background: #eef2f7; cursor: pointer; font-size: 20px; }
.cart-list { flex: 1; display: grid; align-content: start; gap: 10px; padding: 16px 2px; overflow-y: auto; }
.cart-item { min-width: 0; padding: 10px; display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: 11px; border: 1px solid #dce5f2; border-radius: 11px; background: #f9fbfe; }
.cart-item-image { width: 64px; height: 64px; display: grid; place-items: center; overflow: hidden; border-radius: 8px; background: #eef3fa; }
.cart-item-image img { display: block; width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; }
.cart-item-image span { color: #7ea1dd; font-weight: 800; }
.cart-item-copy { min-width: 0; display: grid; align-content: start; gap: 3px; }
.cart-item-copy small { color: var(--blue); font-size: 8px; font-weight: 750; }
.cart-item-copy strong { overflow: hidden; color: var(--ink); font-size: 11px; line-height: 1.35; text-overflow: ellipsis; white-space: nowrap; }
.cart-item-copy code { overflow: hidden; color: #8995a6; font-size: 8px; text-overflow: ellipsis; white-space: nowrap; }
.cart-item-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 5px; }
.quantity-control { display: inline-flex; align-items: center; border: 1px solid #d7e1ee; border-radius: 7px; overflow: hidden; background: white; }
.quantity-control button { width: 25px; height: 24px; padding: 0; border: 0; color: var(--blue); background: white; cursor: pointer; font-size: 15px; }
.quantity-control span { min-width: 24px; color: #52617a; font-size: 10px; text-align: center; }
.cart-remove-button { padding: 3px 0; border: 0; color: #a27b7b; background: transparent; cursor: pointer; font-size: 9px; }
.cart-remove-button:hover { color: #d34f4f; }
.cart-drawer-footer { padding-top: 14px; border-top: 1px solid #e5eaf1; }
.cart-drawer-footer p { margin: 0 0 12px; color: #7f8b9e; font-size: 9px; line-height: 1.45; }
.cart-drawer-footer > div { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }
.cart-drawer-footer .outline-button, .cart-drawer-footer .panel-primary { width: 100%; min-width: 0; min-height: 39px; padding: 0 6px; flex: none; font-size: 10px; line-height: 1; white-space: nowrap; }
.cart-drawer-footer .panel-primary span { margin-left: 5px; font-size: 9px; }
.cart-drawer-footer .cart-sample-button { grid-column: 1 / -1; color: white; background: #185ee8; }
.cart-drawer-footer .cart-sample-button:hover { color: white; background: #124fc9; }
.cart-drawer-footer .cart-sample-button:disabled, .cart-empty-actions .cart-sample-button:disabled { color: #aeb9c9; border-color: #dfe5ee; background: #f6f8fb; box-shadow: none; cursor: not-allowed; }
.cart-export-button { color: var(--blue); border-color: #b9cdf1; }
.cart-export-button:hover { color: #124ac0; border-color: var(--blue); background: #f4f8ff; }
.cart-export-button:disabled { color: #aeb9c9; border-color: #dfe5ee; background: #f6f8fb; cursor: not-allowed; }
.cart-pdf-button { color: #a85d2a; border-color: #edc9ac; }
.cart-pdf-button:hover { color: #874317; border-color: #d99565; background: #fff8f2; }
.cart-pdf-button:disabled { color: #bdb4ad; border-color: #e9e2dc; background: #faf8f6; cursor: not-allowed; }
.cart-empty { margin: auto 0; padding: 30px 12px; display: grid; justify-items: center; gap: 9px; text-align: center; }
.cart-empty > span { font-size: 36px; }
.cart-empty strong { color: var(--ink); font-size: 16px; }
.cart-empty p { max-width: 270px; margin: 0 0 8px; color: #7f8b9e; font-size: 10px; line-height: 1.6; }
.cart-empty-actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); width: 100%; gap: 7px; }
.cart-empty-actions .panel-primary, .cart-empty-actions .cart-export-button { min-height: 42px; }
.cart-empty-actions .cart-pdf-button { min-height: 42px; }
.cart-empty-actions .outline-button, .cart-empty-actions .panel-primary { min-width: 0; padding: 0 6px; font-size: 10px; line-height: 1; white-space: nowrap; }
.cart-empty-actions .cart-sample-button { grid-column: 1 / -1; }
.cart-sample-scroll { flex: 1; overflow-y: auto; padding: 16px 2px; }
.cart-sample-form { margin-top: 0; }
.cart-sample-products { display: grid; gap: 6px; margin: 10px 0 14px; padding: 9px; border: 1px solid #dbe5f2; border-radius: 9px; background: #f8faff; }
.cart-sample-products > div { min-width: 0; display: grid; gap: 2px; padding-bottom: 6px; border-bottom: 1px solid #e8eef6; }
.cart-sample-products > div:last-child { padding-bottom: 0; border-bottom: 0; }
.cart-sample-products span { overflow: hidden; color: var(--ink); font-size: 10px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.cart-sample-products code { overflow: hidden; color: #8995a6; font-size: 8px; text-overflow: ellipsis; white-space: nowrap; }
.cart-sample-form-actions { display: grid; grid-template-columns: 1fr 1.4fr; gap: 8px; margin-top: 10px; }
.cart-sample-form-actions .outline-button, .cart-sample-form-actions .panel-primary { width: 100%; min-width: 0; min-height: 39px; padding: 0 8px; font-size: 10px; white-space: nowrap; }
.cart-sample-success { margin-top: 18px; }
.cart-print-sheet { display: none; }
.cart-print-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; padding-bottom: 18px; border-bottom: 2px solid #185ee8; }
.cart-print-header p { margin: 0 0 7px; color: #185ee8; font-size: 10pt; font-weight: 800; letter-spacing: .08em; }
.cart-print-header h1 { margin: 0 0 6px; color: #17233a; font-size: 24pt; line-height: 1.1; }
.cart-print-header span, .cart-print-header strong { color: #71809a; font-size: 9pt; }
.cart-print-header strong { padding-top: 25px; font-weight: 600; }
.cart-print-summary { width: 100%; margin-top: 18px; border-collapse: collapse; table-layout: fixed; color: #17233a; font-size: 8.5pt; }
.cart-print-summary th, .cart-print-summary td { padding: 6px; border: 1px solid #dbe4ef; vertical-align: middle; overflow-wrap: anywhere; }
.cart-print-summary th { color: white; background: #185ee8; font-weight: 800; text-align: center; }
.cart-print-summary th:nth-child(1) { width: 12%; }
.cart-print-summary th:nth-child(2) { width: 19%; }
.cart-print-summary th:nth-child(3) { width: 14%; }
.cart-print-summary th:nth-child(4) { width: 12%; }
.cart-print-summary th:nth-child(5) { width: 7%; }
.cart-print-summary th:nth-child(6) { width: 24%; }
.cart-print-summary th:nth-child(7) { width: 12%; }
.cart-print-summary td:nth-child(5) { text-align: center; font-weight: 800; }
.cart-print-image-cell { height: 66px; text-align: center; }
.cart-print-image-cell img { width: 78px; height: 54px; object-fit: contain; }
.cart-print-preline { white-space: pre-line; }
.cart-print-sheet > h2 { margin: 25px 0 10px; padding-bottom: 6px; border-bottom: 1px solid #cbd8ea; color: #185ee8; font-size: 15pt; }
.cart-print-details { display: grid; gap: 12px; }
.cart-print-product { break-inside: avoid; page-break-inside: avoid; border: 1px solid #dbe4ef; }
.cart-print-product-heading { padding: 9px 11px; background: #f3f7fd; }
.cart-print-product-heading h3 { margin: 0 0 4px; color: #17233a; font-size: 11pt; }
.cart-print-product-heading span { color: #71809a; font-size: 8pt; }
.cart-print-spec-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.cart-print-spec-grid div { min-height: 32px; padding: 6px 10px; border-top: 1px solid #e3eaf3; }
.cart-print-spec-grid div:nth-child(4n + 1), .cart-print-spec-grid div:nth-child(4n + 2), .cart-print-spec-grid div:nth-child(4n + 3) { border-right: 1px solid #e3eaf3; }
.cart-print-spec-grid span, .cart-print-spec-grid strong { display: block; overflow-wrap: anywhere; }
.cart-print-spec-grid span { margin-bottom: 3px; color: #8492a7; font-size: 7.5pt; }
.cart-print-spec-grid strong { color: #17233a; font-size: 8.5pt; line-height: 1.35; }
.cart-print-footer { margin: 20px 0 0; padding-top: 10px; border-top: 1px solid #dbe4ef; color: #8492a7; font-size: 7.5pt; }
.precision-gear-panel {
display: grid;
grid-template-columns: minmax(260px, .8fr) 1.2fr;
gap: 28px;
align-items: center;
margin-top: 42px;
padding: 28px;
border: 1px solid #d8e3f5;
border-radius: 18px;
background: linear-gradient(135deg, #f5f9ff, #eef5ff 62%, #f8fbff);
}
.precision-gear-copy h3 { margin: 9px 0 9px; color: var(--ink); font-size: 25px; letter-spacing: -.03em; }
.precision-gear-copy > p:not(.eyebrow) { max-width: 410px; margin: 0; color: #6d7c92; font-size: 12px; line-height: 1.75; }
.precision-gear-overview { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.precision-gear-stat { display: flex; align-items: baseline; gap: 7px; padding: 16px 17px; border: 1px solid rgba(176, 201, 239, .72); border-radius: 12px; background: rgba(255,255,255,.78); }
.precision-gear-stat strong { color: var(--blue); font-size: 25px; letter-spacing: -.04em; }
.precision-gear-stat span { color: #73829a; font-size: 11px; }
.precision-gear-subcats { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 7px; margin-top: 3px; padding-top: 13px; border-top: 1px solid rgba(176, 201, 239, .58); }
.precision-gear-subcats span { padding: 6px 9px; border: 1px solid #d6e2f5; border-radius: 999px; color: #60718b; background: rgba(255,255,255,.72); font-size: 10px; }
.precision-gear-subcats b { margin-left: 3px; color: var(--blue); font-weight: 800; }
.precision-gear-section-label { margin: 34px 0 0; }
.precision-gear-catalog { margin-top: 16px; }
.precision-gear-catalog-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }
.precision-gear-catalog-heading h4 { margin: 5px 0 0; color: var(--ink); font-size: 22px; letter-spacing: -.03em; }
.precision-gear-catalog-heading > p { max-width: 430px; margin: 0; color: #7b8799; font-size: 11px; line-height: 1.65; text-align: right; }
.precision-gear-controls { position: sticky; top: 74px; z-index: 19; margin-top: 16px; padding: 12px; border: 1px solid #dbe4f1; border-radius: 13px; background: rgba(251,252,254,.96); box-shadow: 0 8px 20px rgba(30,53,91,.07); backdrop-filter: blur(10px); }
.precision-gear-mobile-filter-toggle { display: none; }
.precision-gear-controls > input { width: 100%; height: 40px; padding: 0 12px; font-size: 11px; background: white; }
.precision-gear-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.precision-gear-pills button { padding: 7px 10px; border: 1px solid #dce4ef; border-radius: 999px; color: #68778e; background: white; cursor: pointer; font-size: 9px; }
.precision-gear-pills button:hover { border-color: #aec6ed; color: var(--blue); }
.precision-gear-pills button.active { border-color: var(--blue); color: white; background: var(--blue); }
.precision-gear-result { margin: 10px 2px 0; color: #8a96a7; font-size: 9px; }
.precision-gear-result strong { color: var(--blue); font-size: 12px; }
.precision-gear-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-top: 15px; }
.precision-gear-card { min-width: 0; overflow: hidden; border: 1px solid #dfe6f0; border-radius: 12px; background: white; transition: transform .2s, border-color .2s, box-shadow .2s; }
.precision-gear-card:hover { transform: translateY(-2px); border-color: #b9cbea; box-shadow: 0 10px 22px rgba(30,53,91,.08); }
.precision-gear-image { position: relative; width: 100%; aspect-ratio: 1.18; padding: 0; display: grid; place-items: center; overflow: hidden; border: 0; background: #f4f7fb; cursor: pointer; }
.precision-gear-image img { display: block; width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; transition: transform .25s ease; }
.precision-gear-image:hover img { transform: scale(1.05); }
.precision-gear-image:focus-visible { outline: 3px solid rgba(24,94,232,.28); outline-offset: -3px; }
.precision-gear-image > span { position: absolute; right: 8px; bottom: 8px; padding: 4px 6px; border: 1px solid rgba(255,255,255,.75); border-radius: 5px; color: #61708a; background: rgba(255,255,255,.9); font-size: 8px; font-weight: 700; }
.precision-gear-card-body { min-width: 0; padding: 11px 11px 12px; }
.precision-gear-card-body > p { margin: 0 0 4px; color: var(--blue); font-size: 8px; font-weight: 800; }
.precision-gear-card-body h5 { height: 32px; margin: 0 0 5px; overflow: hidden; color: var(--ink); font-size: 11px; line-height: 1.45; }
.precision-gear-card-body code { display: block; overflow: hidden; color: #8a95a7; font-size: 8px; text-overflow: ellipsis; white-space: nowrap; }
.precision-gear-card-body dl { min-height: 42px; margin: 9px 0 0; padding-top: 7px; border-top: 1px solid #edf0f4; }
.precision-gear-card-body dl div { display: flex; justify-content: space-between; gap: 5px; padding: 2px 0; font-size: 8px; }
.precision-gear-card-body dt { color: #8a95a7; }
.precision-gear-card-body dd { max-width: 62%; margin: 0; overflow: hidden; color: #52617a; text-overflow: ellipsis; white-space: nowrap; text-align: right; font-weight: 700; }
.precision-gear-empty { margin-top: 15px; }
.precision-gear-load-more { margin-top: 22px; }
.catalog-empty { padding: 60px 20px; text-align: center; border: 1px dashed #cbd5e3; border-radius: 15px; background: white; } .catalog-empty { padding: 60px 20px; text-align: center; border: 1px dashed #cbd5e3; border-radius: 15px; background: white; }
.catalog-empty p { color: #8792a4; font-size: 12px; } .catalog-empty p { color: #8792a4; font-size: 12px; }
.catalog-empty button { border: 0; color: var(--blue); background: transparent; cursor: pointer; font-weight: 700; } .catalog-empty button { border: 0; color: var(--blue); background: transparent; cursor: pointer; font-weight: 700; }
@@ -369,9 +567,9 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
} }
.catalog-modal { .catalog-modal {
position: relative; position: relative;
width: min(760px, 100%); width: min(1120px, 100%);
max-height: min(820px, 92vh); max-height: min(820px, 92vh);
padding: 28px; padding: 30px;
overflow-y: auto; overflow-y: auto;
border-radius: 18px; border-radius: 18px;
background: white; background: white;
@@ -391,18 +589,30 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
cursor: pointer; cursor: pointer;
font-size: 20px; font-size: 20px;
} }
.catalog-modal-product { display: grid; grid-template-columns: 210px 1fr; align-items: center; gap: 28px; } .catalog-modal-product { display: block; }
.catalog-modal-image { height: 180px; display: grid; place-items: center; border-radius: 13px; overflow: hidden; background: #f2f5fa; } .catalog-modal-visual-card { min-width: 0; display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 9px; }
.catalog-modal-image img { width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; } .catalog-modal-visual-toolbar { display: flex; align-items: center; justify-content: flex-start; gap: 16px; }
.catalog-modal-product h2 { margin: 4px 0 10px; font-size: 27px; line-height: 1.3; letter-spacing: -.025em; } .catalog-modal-gallery-switch { display: inline-flex; gap: 3px; padding: 3px; border: 1px solid #dce5f2; border-radius: 8px; background: #f5f8fd; }
.catalog-modal-product code { color: #5d6b81; font-size: 11px; } .catalog-modal-gallery-switch button { padding: 6px 9px; border: 0; border-radius: 6px; color: #7c899c; background: transparent; cursor: pointer; font-size: 9px; font-weight: 700; }
.catalog-source-note { color: #96a0af; font-size: 9px; } .catalog-modal-gallery-switch button.active { color: var(--blue); background: white; box-shadow: 0 2px 7px rgba(31,71,139,.09); }
.catalog-spec-table { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; margin-top: 25px; overflow: hidden; border: 1px solid #e0e5ed; border-radius: 11px; background: #e0e5ed; } .catalog-modal-gallery-switch button:disabled { cursor: not-allowed; opacity: .4; }
.catalog-spec-table div { min-height: 48px; padding: 10px 12px; display: grid; align-content: center; gap: 4px; background: white; } .catalog-modal-inline-info { min-width: 0; display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; }
.catalog-spec-table span { color: #8b96a7; font-size: 9px; } .catalog-modal-inline-info > span { color: var(--blue); font-size: 10px; font-weight: 800; }
.catalog-spec-table strong { font-size: 11px; } .catalog-modal-inline-info h2 { max-width: min(560px, 100%); margin: 0; color: var(--ink); font-size: 20px; line-height: 1.3; letter-spacing: -.025em; }
.catalog-modal-inline-info code { color: #5d6b81; font-size: 10px; }
.catalog-modal-inline-info small { color: #96a0af; font-size: 8px; }
.catalog-modal-image { min-height: min(56vh, 520px); display: flex; align-items: center; justify-content: center; border: 1px solid #e1e7f0; border-radius: 13px; overflow: hidden; background: #f5f7fb; }
.catalog-modal-image img { display: block; max-width: 100%; max-height: min(56vh, 510px); width: 100%; height: auto; object-fit: contain; mix-blend-mode: multiply; }
.catalog-spec-table { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1px; margin-top: 18px; overflow: hidden; border: 1px solid #e0e5ed; border-radius: 11px; background: white; }
.catalog-spec-table div { min-width: 0; min-height: 43px; padding: 5px 7px; display: grid; align-content: center; gap: 1px; background: white; box-shadow: inset 0 0 0 1px #e0e5ed; }
.catalog-spec-table span,
.catalog-spec-table strong { min-width: 0; overflow-wrap: anywhere; word-break: break-word; }
.catalog-spec-table span { color: #8b96a7; font-size: 8.5px; line-height: 1.15; }
.catalog-spec-table strong { font-size: 10.5px; line-height: 1.2; }
.catalog-modal-actions { display: flex; align-items: center; gap: 18px; margin-top: 20px; } .catalog-modal-actions { display: flex; align-items: center; gap: 18px; margin-top: 20px; }
.catalog-modal-actions .panel-primary { flex: 0 0 auto; } .catalog-modal-actions .panel-primary { flex: 0 0 auto; }
.catalog-modal-actions .modal-cart-button { width: auto; min-width: 124px; margin-top: 0; flex: 0 0 auto; }
.catalog-modal-actions .spec-sheet-button { width: auto; min-width: 124px; margin-top: 0; flex: 0 0 auto; }
.catalog-modal-actions p { margin: 0; color: #8b95a5; font-size: 9px; line-height: 1.6; } .catalog-modal-actions p { margin: 0; color: #8b95a5; font-size: 9px; line-height: 1.6; }
.how-section { padding: 100px max(32px, calc((100vw - 1180px) / 2)); display: grid; grid-template-columns: .9fr 1.1fr; gap: 100px; color: white; background: linear-gradient(135deg, #0c1528, #14284e); } .how-section { padding: 100px max(32px, calc((100vw - 1180px) / 2)); display: grid; grid-template-columns: .9fr 1.1fr; gap: 100px; color: white; background: linear-gradient(135deg, #0c1528, #14284e); }
.eyebrow.light { color: #77a5ff; } .eyebrow.light { color: #77a5ff; }
@@ -425,12 +635,31 @@ footer > span { color: #9aa4b2; font-size: 10px; }
.hero-copy { max-width: 760px; } .hero-copy { max-width: 760px; }
.selector-shell { width: min(100%, 720px); } .selector-shell { width: min(100%, 720px); }
.how-section { gap: 45px; } .how-section { gap: 45px; }
.catalog-summary { grid-template-columns: repeat(4, 1fr); }
.catalog-grid { grid-template-columns: repeat(2, 1fr); }
.precision-gear-panel { grid-template-columns: 1fr; }
.precision-gear-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.catalog-spec-table { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.choice-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
} }
@media (max-width: 720px) { @media (max-width: 720px) {
.site-header { height: 64px; padding: 0 18px; } .site-header { min-height: 64px; height: auto; padding: 10px 14px; }
.site-header nav { display: none; } .brand-wordmark { gap: 2px; }
.header-cta { padding: 9px 12px; font-size: 12px; } .brand-wordmark strong { font-size: 14px; }
.brand-wordmark small { font-size: 10px; letter-spacing: .12em; }
.site-header nav { display: none; position: absolute; top: calc(100% + 8px); left: 12px; right: 12px; z-index: 35; gap: 0; padding: 8px; border: 1px solid #dce5f2; border-radius: 16px; background: rgba(255,255,255,.98); box-shadow: 0 18px 40px rgba(27,54,99,.18); }
.site-header nav.is-open { display: grid; }
.site-header nav a { padding: 14px 13px; border-radius: 10px; font-size: 14px; }
.site-header nav a:hover { background: var(--blue-soft); }
.header-cta { padding: 9px 11px; font-size: 11px; }
.header-cta span { margin-left: 4px; }
.cart-header-button { min-height: 36px; padding: 7px 7px; }
.cart-header-button small { display: none; }
.cart-header-button > span { font-size: 16px; }
.mobile-menu-toggle { min-width: 52px; padding: 7px 8px; display: inline-flex; align-items: center; justify-content: center; gap: 5px; border: 1px solid #dce5f2; border-radius: 10px; color: var(--blue); background: white; cursor: pointer; }
.mobile-menu-toggle span { font-size: 18px; line-height: 1; }
.mobile-menu-toggle small { font-size: 10px; font-weight: 700; }
.hero { min-height: 0; padding: 45px 16px 56px; } .hero { min-height: 0; padding: 45px 16px 56px; }
.hero h1 { font-size: 48px; } .hero h1 { font-size: 48px; }
.hero-lead { font-size: 15px; } .hero-lead { font-size: 15px; }
@@ -439,35 +668,90 @@ footer > span { color: #9aa4b2; font-size: 10px; }
.selector-shell { border-radius: 16px; } .selector-shell { border-radius: 16px; }
.question-panel, .result-panel { padding: 20px 16px; } .question-panel, .result-panel { padding: 20px 16px; }
.welcome-panel { padding: 55px 20px 35px; } .welcome-panel { padding: 55px 20px 35px; }
.choice-grid, .two-fields, .form-grid, .alternative-list { grid-template-columns: 1fr; } .two-fields, .parameter-grid, .form-grid, .recommendation-grid { grid-template-columns: 1fr; }
.best-match { grid-template-columns: 1fr; } .choice-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
.match-image-wrap { height: 240px; } .choice-card { min-height: 62px; padding: 9px 27px 9px 10px; border-radius: 9px; }
.choice-card b { font-size: 11px; }
.choice-card small { font-size: 8px; line-height: 1.3; }
.choice-check { right: 8px; top: 8px; width: 16px; height: 16px; font-size: 9px; }
.torque-presets { grid-template-columns: repeat(4, 1fr); }
.recommendation-card { min-height: 0; }
.capability-strip { grid-template-columns: 1fr 1fr; gap: 24px 0; padding: 28px 16px; } .capability-strip { grid-template-columns: 1fr 1fr; gap: 24px 0; padding: 28px 16px; }
.capability-strip div:nth-child(2) { border-right: 0; } .capability-strip div:nth-child(2) { border-right: 0; }
.section-heading { align-items: flex-start; flex-direction: column; gap: 18px; } .section-heading { align-items: flex-start; flex-direction: column; gap: 18px; }
.catalog-summary { grid-template-columns: repeat(2, 1fr); } .catalog-summary { grid-template-columns: repeat(2, 1fr); }
.catalog-summary button:last-child { grid-column: 1 / -1; } .catalog-summary button:last-child { grid-column: auto; }
.catalog-toolbar { top: 64px; } .catalog-toolbar { top: 64px; }
.catalog-grid { grid-template-columns: 1fr 1fr; gap: 10px; } .catalog-toolbar { display: block; }
.catalog-image-button { height: 150px; } .catalog-toolbar.is-hidden { visibility: hidden; opacity: 0; pointer-events: none; }
.catalog-card-body { padding: 12px; } .catalog-mobile-filter-toggle { display: none; min-height: 36px; align-items: center; justify-content: center; padding: 0 13px; border: 1px solid #dce3ed; border-radius: 9px; color: var(--blue); background: white; cursor: pointer; font-size: 11px; font-weight: 750; }
.catalog-card-body h3 { height: 60px; font-size: 13px; } .catalog-toolbar.is-compact { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px; padding: 7px 8px; margin-bottom: 14px; border-radius: 12px; }
.catalog-card-body dl { min-height: 90px; } .catalog-toolbar.is-compact .catalog-mobile-filter-toggle { display: inline-flex; }
.catalog-toolbar.is-compact .catalog-search { grid-column: 1; }
.catalog-toolbar.is-compact .catalog-search input { height: 36px; padding: 0 38px; font-size: 12px; }
.catalog-toolbar.is-compact .catalog-search > span { left: 12px; top: 7px; font-size: 19px; }
.catalog-toolbar.is-compact .catalog-search > button { right: 6px; width: 25px; height: 25px; }
.catalog-toolbar.is-compact:not(.filters-open) .category-pills,
.catalog-toolbar.is-compact:not(.filters-open) .catalog-scale-filter,
.catalog-toolbar.is-compact:not(.filters-open) .catalog-result-count { display: none; }
.catalog-toolbar.is-compact.filters-open .category-pills,
.catalog-toolbar.is-compact.filters-open .catalog-scale-filter,
.catalog-toolbar.is-compact.filters-open .catalog-result-count { grid-column: 1 / -1; }
.catalog-toolbar.is-compact.filters-open .catalog-mobile-filter-toggle { color: white; background: var(--blue); }
.catalog-scale-filter { grid-template-columns: 1fr; align-items: stretch; }
.catalog-grid { grid-template-columns: 1fr; gap: 15px; }
.precision-gear-panel { gap: 20px; margin-top: 30px; padding: 22px 18px; }
.precision-gear-catalog-heading { align-items: flex-start; flex-direction: column; gap: 12px; }
.precision-gear-catalog-heading > p { text-align: left; }
.precision-gear-controls { position: sticky; top: 64px; z-index: 19; }
.precision-gear-mobile-filter-toggle { display: none; min-height: 36px; align-items: center; justify-content: center; padding: 0 13px; border: 1px solid #dce3ed; border-radius: 9px; color: var(--blue); background: white; cursor: pointer; font-size: 11px; font-weight: 750; }
.precision-gear-controls.is-compact { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px; padding: 7px 8px; border-radius: 12px; }
.precision-gear-controls.is-compact > input { grid-column: 1; height: 36px; min-width: 0; font-size: 12px; }
.precision-gear-controls.is-compact .precision-gear-mobile-filter-toggle { display: inline-flex; }
.precision-gear-controls.is-compact:not(.filters-open) .precision-gear-pills,
.precision-gear-controls.is-compact:not(.filters-open) .precision-gear-result { display: none; }
.precision-gear-controls.is-compact.filters-open .precision-gear-pills,
.precision-gear-controls.is-compact.filters-open .precision-gear-result { grid-column: 1 / -1; }
.precision-gear-controls.is-compact.filters-open .precision-gear-mobile-filter-toggle { color: white; background: var(--blue); }
.precision-gear-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 9px; }
.precision-gear-card-body { padding: 9px; }
.catalog-card-body { padding: 15px; }
.catalog-card-body h3 { height: auto; min-height: 30px; max-height: 42px; margin-bottom: 4px; font-size: 14px; }
.catalog-card-body dl { min-height: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 5px; padding: 9px 7px; }
.catalog-card-body dl div { min-width: 0; display: grid; align-content: start; justify-content: initial; gap: 3px; padding: 2px 0; font-size: 8px; }
.catalog-card-body dt { overflow: hidden; font-size: 8px; text-overflow: ellipsis; white-space: nowrap; }
.catalog-card-body dd { max-width: 100%; overflow: hidden; font-size: 9px; text-align: left; text-overflow: ellipsis; white-space: nowrap; }
.catalog-modal-backdrop { padding: 10px; } .catalog-modal-backdrop { padding: 10px; }
.catalog-modal { padding: 22px 16px; } .catalog-modal { padding: 22px 16px; }
.catalog-modal-product { grid-template-columns: 1fr; gap: 16px; } .catalog-modal-visual-toolbar { align-items: flex-start; flex-direction: column; gap: 10px; }
.catalog-modal-image { height: 220px; } .catalog-modal-inline-info { width: 100%; }
.catalog-spec-table { grid-template-columns: 1fr; } .catalog-modal-inline-info h2 { font-size: 18px; }
.catalog-modal-image { min-height: 290px; }
.catalog-modal-image img { max-height: 290px; }
.catalog-spec-table { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.catalog-spec-table div { min-height: 39px; padding: 4px 5px; gap: 1px; }
.catalog-spec-table span { font-size: 7.5px; }
.catalog-spec-table strong { font-size: 8.5px; line-height: 1.15; }
.catalog-modal-actions { align-items: stretch; flex-direction: column; } .catalog-modal-actions { align-items: stretch; flex-direction: column; }
.catalog-modal-actions .modal-cart-button { width: 100%; }
.products-section { padding: 75px 16px; } .products-section { padding: 75px 16px; }
.how-section { grid-template-columns: 1fr; padding: 75px 20px; } .how-section { grid-template-columns: 1fr; padding: 75px 20px; }
footer { grid-template-columns: 1fr; justify-items: center; gap: 15px; } footer { grid-template-columns: 1fr; justify-items: center; gap: 15px; }
.footer-brand { justify-self: center; } .footer-brand { justify-self: center; }
} }
@media print { @media (max-width: 380px) {
.site-header, .hero-copy, .capability-strip, .products-section, .how-section, footer, .selector-topbar, .result-actions, .restart-button { display: none !important; } .catalog-spec-table { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.hero { display: block; min-height: auto; padding: 0; background: white; } .cart-drawer-footer > div { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.selector-shell { box-shadow: none; border: 0; } .cart-sample-form-actions { grid-template-columns: 1fr; }
.result-panel { padding: 20px; } }
@media print {
@page { size: A4; margin: 12mm 10mm; }
body.printing-cart { margin: 0; background: white; }
body.printing-cart > * { visibility: hidden !important; }
body.printing-cart .cart-print-sheet,
body.printing-cart .cart-print-sheet * { visibility: visible !important; }
body.printing-cart .cart-print-sheet { position: absolute; inset: 0; display: block !important; width: 100%; color: #17233a; background: white; font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif; }
body.printing-cart .site-header, body.printing-cart .hero-copy, body.printing-cart .capability-strip, body.printing-cart .products-section, body.printing-cart .how-section, body.printing-cart footer, body.printing-cart .selector-topbar, body.printing-cart .result-actions, body.printing-cart .restart-button { display: none !important; }
} }

View File

@@ -11,10 +11,10 @@ export async function generateMetadata(): Promise<Metadata> {
return { return {
metadataBase, metadataBase,
title: "高徳乐 AI 舵机选型助手", title: "高徳乐 AI 舵机选型助手",
description: "高徳乐280个产品型号完整目录支持搜索筛选、AI舵机初选与样品申请。", description: "高徳乐243款舵机配件共286个产品型号支持搜索筛选、AI舵机初选与样品申请。",
openGraph: { openGraph: {
title: "高徳乐 AI 舵机选型助手", title: "高徳乐 AI 舵机选型助手",
description: "280个型号完整目录。3分钟找到适合你的舵机AI初选工程师复核。", description: "243款舵机配件共286个型号。3分钟找到适合你的舵机AI初选工程师复核。",
images: [{ url: new URL("/og.png", metadataBase).toString(), width: 1200, height: 630 }], images: [{ url: new URL("/og.png", metadataBase).toString(), width: 1200, height: 630 }],
type: "website", type: "website",
}, },

File diff suppressed because it is too large Load Diff

11603
app/precision-gear-data.ts Normal file

File diff suppressed because it is too large Load Diff

6
deploy/Caddyfile.duoji Normal file
View File

@@ -0,0 +1,6 @@
duoji.dbpi.com.cn {
root * /srv/duoji
encode gzip zstd
try_files {path} /index.html
file_server
}

17
deploy/nginx-static.conf Normal file
View File

@@ -0,0 +1,17 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(?:js|css|png|jpg|jpeg|webp|gif|svg|ico|woff2)$ {
try_files $uri =404;
expires 1d;
add_header Cache-Control "public, max-age=86400";
}
}

985
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,13 +8,16 @@
"scripts": { "scripts": {
"dev": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext dev", "dev": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext dev",
"build": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext build", "build": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext build",
"build:static": "vite build --config vite.static.config.ts",
"images:optimize": "node scripts/optimize_catalog_images.mjs",
"start": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext start", "start": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext start",
"test": "npm run build && node --test tests/rendered-html.test.mjs", "test": "npm run build && node --test tests/rendered-html.test.mjs",
"lint": "eslint . --ignore-pattern dist --ignore-pattern .next", "lint": "eslint . --ignore-pattern dist --ignore-pattern dist-static --ignore-pattern .next",
"db:generate": "drizzle-kit generate" "db:generate": "drizzle-kit generate"
}, },
"dependencies": { "dependencies": {
"drizzle-orm": "0.45.2", "drizzle-orm": "0.45.2",
"exceljs": "^4.4.0",
"react": "19.2.6", "react": "19.2.6",
"react-dom": "19.2.6" "react-dom": "19.2.6"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Some files were not shown because too many files have changed in this diff Show More