Compare commits
33 Commits
09a99d5e48
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| fb2a3466c4 | |||
|
|
4d25114923 | ||
|
|
5101493901 | ||
|
|
511dbcd0e8 | ||
|
|
62a3d6ed14 | ||
|
|
81098ea237 | ||
|
|
45fbdffddb | ||
|
|
42c21dd024 | ||
|
|
775ccf82ab | ||
|
|
05de730115 | ||
|
|
c131483255 | ||
|
|
b7e32000bf | ||
|
|
a608faa298 | ||
|
|
e8759531b7 | ||
|
|
7eed023539 | ||
|
|
ca423b3be7 | ||
|
|
c9e20bdb1e | ||
|
|
6c37aa3d47 | ||
|
|
b4d11e2f12 | ||
|
|
9627c5043e | ||
|
|
794857f465 | ||
|
|
78f5ead63a | ||
|
|
e855d5aa64 | ||
|
|
718d7c6ccb | ||
|
|
9078f19194 | ||
|
|
7b9a56b7fe | ||
|
|
66ef28bdeb | ||
|
|
0351c47684 | ||
|
|
9ab7d7c195 | ||
|
|
f72de38fb7 | ||
|
|
99f9f079b3 | ||
|
|
3f9202b315 | ||
|
|
596493a686 |
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
||||
.git
|
||||
.gitignore
|
||||
node_modules
|
||||
dist
|
||||
dist-static
|
||||
.next
|
||||
.vinext
|
||||
.wrangler
|
||||
*.log
|
||||
1
.gitignore
vendored
@@ -35,6 +35,7 @@ yarn-error.log*
|
||||
.vercel
|
||||
|
||||
/dist/
|
||||
/dist-static/
|
||||
/.wrangler/
|
||||
/outputs/
|
||||
/work/
|
||||
|
||||
5
AGENTS.md
Normal 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
@@ -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 配置,修改后先校验配置,再只重载 Caddy;bind 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 秒,仅重启 Caddy,galderma-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
@@ -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
|
||||
@@ -16,6 +16,10 @@ npm run dev
|
||||
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`.
|
||||
|
||||
## Included Shape
|
||||
|
||||
137
app/catalog-accessories.ts
Normal 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,
|
||||
},
|
||||
];
|
||||
@@ -7,6 +7,7 @@ export type CatalogProduct = {
|
||||
name: string;
|
||||
code: string;
|
||||
image: string;
|
||||
dimensionImage?: string;
|
||||
page: number;
|
||||
specs: Record<string, string>;
|
||||
torqueValue: number | null;
|
||||
@@ -64,7 +65,7 @@ export const CATALOG_PRODUCTS: CatalogProduct[] = [
|
||||
},
|
||||
{
|
||||
"id": "p01-5-4-a-05-a0008",
|
||||
"category": "标准舵机",
|
||||
"category": "异形舵机",
|
||||
"name": "1.7g舵机(异形款/塑胶齿/三线)",
|
||||
"code": "4.A.05.A0008",
|
||||
"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> = {
|
||||
"标准舵机": 118,
|
||||
"异形舵机": 13,
|
||||
"标准舵机": 117,
|
||||
"异形舵机": 14,
|
||||
"全金属舵机": 66,
|
||||
"大扭矩舵机": 26,
|
||||
"积木舵机": 20,
|
||||
|
||||
153
app/catalog-image-map.ts
Normal 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[];
|
||||
438
app/globals.css
@@ -39,24 +39,26 @@ a { color: inherit; text-decoration: none; }
|
||||
border-bottom: 1px solid rgba(16,24,43,.08);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
.brand { display: inline-flex; align-items: center; gap: 11px; }
|
||||
.brand-mark {
|
||||
width: 38px; height: 38px; border-radius: 11px;
|
||||
display: grid; place-items: center;
|
||||
color: white; font-size: 21px; font-weight: 900; font-style: italic;
|
||||
background: linear-gradient(145deg, #2d75ff, #0e48b9);
|
||||
box-shadow: 0 7px 18px rgba(24,94,232,.25);
|
||||
.brand { display: inline-flex; align-items: center; }
|
||||
.brand-wordmark { display: grid; gap: 3px; }
|
||||
.brand-wordmark strong {
|
||||
color: var(--ink); font-family: Arial, "Helvetica Neue", sans-serif;
|
||||
font-size: 18px; font-style: italic; font-weight: 900; letter-spacing: .045em; line-height: .9;
|
||||
}
|
||||
.brand > span:last-child { display: grid; gap: 1px; }
|
||||
.brand strong { font-size: 14px; letter-spacing: .08em; line-height: 1; }
|
||||
.brand small { color: var(--muted); font-size: 10px; letter-spacing: .04em; }
|
||||
.brand-wordmark small { color: var(--blue); font-size: 12px; font-weight: 850; letter-spacing: .16em; line-height: 1; }
|
||||
.site-header nav { display: flex; gap: 34px; font-size: 14px; color: #536078; }
|
||||
.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 {
|
||||
border: 0; border-radius: 10px; padding: 11px 17px;
|
||||
background: var(--ink); color: white; cursor: pointer; font-weight: 650;
|
||||
}
|
||||
.header-cta span { margin-left: 8px; color: #82aaff; }
|
||||
.mobile-menu-toggle { display: none; }
|
||||
|
||||
.hero {
|
||||
min-height: 720px;
|
||||
@@ -92,6 +94,7 @@ a { color: inherit; text-decoration: none; }
|
||||
|
||||
.selector-shell {
|
||||
min-height: 585px;
|
||||
scroll-margin-top: 94px;
|
||||
background: white;
|
||||
border: 1px solid rgba(73,98,142,.15);
|
||||
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; }
|
||||
.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; }
|
||||
.choice-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
.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-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; }
|
||||
.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.selected { border-color: var(--blue); background: var(--blue-soft); box-shadow: inset 0 0 0 1px var(--blue); }
|
||||
.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; }
|
||||
.next-button { min-width: 150px; padding: 13px 20px; }
|
||||
.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-label { display: flex; justify-content: space-between; color: #657187; font-size: 12px; }
|
||||
.field-stack { margin-top: 13px; padding: 15px 16px 13px; border-radius: 12px; background: #f6f8fc; }
|
||||
.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; }
|
||||
.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; }
|
||||
.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; }
|
||||
.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 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: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 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; }
|
||||
.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; }
|
||||
@@ -157,35 +169,40 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
|
||||
|
||||
.result-panel { padding: 24px 28px 28px; }
|
||||
.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); }
|
||||
.match-image-wrap { position: relative; min-height: 270px; background: #eff4fb; }
|
||||
.match-image-wrap img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.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; }
|
||||
.match-content { position: relative; padding: 19px 19px 16px; }
|
||||
.match-score { position: absolute; right: 16px; top: 15px; display: grid; text-align: right; color: #7c889a; font-size: 9px; }
|
||||
.match-score strong { color: var(--blue); font-size: 18px; }
|
||||
.recommendation-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: stretch; gap: 10px; }
|
||||
.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); }
|
||||
.recommendation-card.is-primary { border-color: #cfdaf0; box-shadow: 0 8px 24px rgba(38,70,125,.1); }
|
||||
.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; }
|
||||
.recommendation-image-wrap img { display: block; width: 100%; height: auto; object-fit: contain; mix-blend-mode: multiply; transition: transform .25s ease; }
|
||||
.recommendation-image-wrap:hover img { transform: scale(1.04); }
|
||||
.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; }
|
||||
.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; }
|
||||
.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 span { color: #8994a6; font-size: 8px; }
|
||||
.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; }
|
||||
.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 span { color: #7c8798; font-size: 9px; }
|
||||
.price-line strong { color: #e55726; font-size: 20px; }
|
||||
.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 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; }
|
||||
.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 .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; }
|
||||
@@ -205,6 +222,22 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
|
||||
.success-card h3 { margin: 0; }
|
||||
.success-card > p:not(.step-kicker) { color: #66776f; font-size: 10px; }
|
||||
.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 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; }
|
||||
.catalog-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 9px;
|
||||
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 strong { color: var(--blue); font-size: 19px; }
|
||||
.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 {
|
||||
position: sticky;
|
||||
top: 74px;
|
||||
@@ -271,6 +305,7 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
|
||||
background: #e9edf3;
|
||||
cursor: pointer;
|
||||
}
|
||||
.catalog-mobile-filter-toggle { display: none; }
|
||||
.category-pills { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
|
||||
.category-pills button {
|
||||
padding: 8px 12px;
|
||||
@@ -282,9 +317,16 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
|
||||
font-size: 10px;
|
||||
}
|
||||
.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 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 {
|
||||
min-width: 0;
|
||||
border: 1px solid #dce3ed;
|
||||
@@ -293,24 +335,26 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
|
||||
background: white;
|
||||
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 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 190px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: auto;
|
||||
min-height: 220px;
|
||||
display: block;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #edf0f4;
|
||||
background: linear-gradient(145deg, #f8fafe, #eef3fa);
|
||||
cursor: pointer;
|
||||
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 {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
top: 44px;
|
||||
right: 10px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 6px;
|
||||
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-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 h3 { height: 42px; margin: 0 0 12px; 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 div { display: flex; justify-content: space-between; gap: 8px; padding: 3px 0; font-size: 9px; }
|
||||
.catalog-card-body dt { color: #8a95a7; }
|
||||
.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 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: 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 { min-width: 0; display: grid; align-content: start; gap: 4px; padding: 3px 0; font-size: 9px; }
|
||||
.catalog-card-body dt { overflow: hidden; color: #8a95a7; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.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 {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
@@ -340,6 +384,160 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
|
||||
font-size: 10px;
|
||||
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 p { color: #8792a4; font-size: 12px; }
|
||||
.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 {
|
||||
position: relative;
|
||||
width: min(760px, 100%);
|
||||
width: min(1120px, 100%);
|
||||
max-height: min(820px, 92vh);
|
||||
padding: 28px;
|
||||
padding: 30px;
|
||||
overflow-y: auto;
|
||||
border-radius: 18px;
|
||||
background: white;
|
||||
@@ -391,18 +589,30 @@ select:focus, input:focus, textarea:focus { border-color: var(--blue); box-shado
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
}
|
||||
.catalog-modal-product { display: grid; grid-template-columns: 210px 1fr; align-items: center; gap: 28px; }
|
||||
.catalog-modal-image { height: 180px; display: grid; place-items: center; border-radius: 13px; overflow: hidden; background: #f2f5fa; }
|
||||
.catalog-modal-image img { width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; }
|
||||
.catalog-modal-product h2 { margin: 4px 0 10px; font-size: 27px; line-height: 1.3; letter-spacing: -.025em; }
|
||||
.catalog-modal-product code { color: #5d6b81; font-size: 11px; }
|
||||
.catalog-source-note { color: #96a0af; font-size: 9px; }
|
||||
.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-spec-table div { min-height: 48px; padding: 10px 12px; display: grid; align-content: center; gap: 4px; background: white; }
|
||||
.catalog-spec-table span { color: #8b96a7; font-size: 9px; }
|
||||
.catalog-spec-table strong { font-size: 11px; }
|
||||
.catalog-modal-product { display: block; }
|
||||
.catalog-modal-visual-card { min-width: 0; display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 9px; }
|
||||
.catalog-modal-visual-toolbar { display: flex; align-items: center; justify-content: flex-start; gap: 16px; }
|
||||
.catalog-modal-gallery-switch { display: inline-flex; gap: 3px; padding: 3px; border: 1px solid #dce5f2; border-radius: 8px; background: #f5f8fd; }
|
||||
.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-modal-gallery-switch button.active { color: var(--blue); background: white; box-shadow: 0 2px 7px rgba(31,71,139,.09); }
|
||||
.catalog-modal-gallery-switch button:disabled { cursor: not-allowed; opacity: .4; }
|
||||
.catalog-modal-inline-info { min-width: 0; display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; }
|
||||
.catalog-modal-inline-info > span { color: var(--blue); font-size: 10px; font-weight: 800; }
|
||||
.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 .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; }
|
||||
.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; }
|
||||
@@ -425,12 +635,31 @@ footer > span { color: #9aa4b2; font-size: 10px; }
|
||||
.hero-copy { max-width: 760px; }
|
||||
.selector-shell { width: min(100%, 720px); }
|
||||
.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) {
|
||||
.site-header { height: 64px; padding: 0 18px; }
|
||||
.site-header nav { display: none; }
|
||||
.header-cta { padding: 9px 12px; font-size: 12px; }
|
||||
.site-header { min-height: 64px; height: auto; padding: 10px 14px; }
|
||||
.brand-wordmark { gap: 2px; }
|
||||
.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 h1 { font-size: 48px; }
|
||||
.hero-lead { font-size: 15px; }
|
||||
@@ -439,35 +668,90 @@ footer > span { color: #9aa4b2; font-size: 10px; }
|
||||
.selector-shell { border-radius: 16px; }
|
||||
.question-panel, .result-panel { padding: 20px 16px; }
|
||||
.welcome-panel { padding: 55px 20px 35px; }
|
||||
.choice-grid, .two-fields, .form-grid, .alternative-list { grid-template-columns: 1fr; }
|
||||
.best-match { grid-template-columns: 1fr; }
|
||||
.match-image-wrap { height: 240px; }
|
||||
.two-fields, .parameter-grid, .form-grid, .recommendation-grid { grid-template-columns: 1fr; }
|
||||
.choice-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
|
||||
.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 div:nth-child(2) { border-right: 0; }
|
||||
.section-heading { align-items: flex-start; flex-direction: column; gap: 18px; }
|
||||
.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-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
.catalog-image-button { height: 150px; }
|
||||
.catalog-card-body { padding: 12px; }
|
||||
.catalog-card-body h3 { height: 60px; font-size: 13px; }
|
||||
.catalog-card-body dl { min-height: 90px; }
|
||||
.catalog-toolbar { display: block; }
|
||||
.catalog-toolbar.is-hidden { visibility: hidden; opacity: 0; pointer-events: none; }
|
||||
.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-toolbar.is-compact { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px; padding: 7px 8px; margin-bottom: 14px; border-radius: 12px; }
|
||||
.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 { padding: 22px 16px; }
|
||||
.catalog-modal-product { grid-template-columns: 1fr; gap: 16px; }
|
||||
.catalog-modal-image { height: 220px; }
|
||||
.catalog-spec-table { grid-template-columns: 1fr; }
|
||||
.catalog-modal-visual-toolbar { align-items: flex-start; flex-direction: column; gap: 10px; }
|
||||
.catalog-modal-inline-info { width: 100%; }
|
||||
.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 .modal-cart-button { width: 100%; }
|
||||
.products-section { padding: 75px 16px; }
|
||||
.how-section { grid-template-columns: 1fr; padding: 75px 20px; }
|
||||
footer { grid-template-columns: 1fr; justify-items: center; gap: 15px; }
|
||||
.footer-brand { justify-self: center; }
|
||||
}
|
||||
|
||||
@media print {
|
||||
.site-header, .hero-copy, .capability-strip, .products-section, .how-section, footer, .selector-topbar, .result-actions, .restart-button { display: none !important; }
|
||||
.hero { display: block; min-height: auto; padding: 0; background: white; }
|
||||
.selector-shell { box-shadow: none; border: 0; }
|
||||
.result-panel { padding: 20px; }
|
||||
@media (max-width: 380px) {
|
||||
.catalog-spec-table { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.cart-drawer-footer > div { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.cart-sample-form-actions { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
@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; }
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
metadataBase,
|
||||
title: "高徳乐 AI 舵机选型助手",
|
||||
description: "高徳乐280个产品型号完整目录,支持搜索筛选、AI舵机初选与样品申请。",
|
||||
description: "高徳乐243款舵机+配件,共286个产品型号,支持搜索筛选、AI舵机初选与样品申请。",
|
||||
openGraph: {
|
||||
title: "高徳乐 AI 舵机选型助手",
|
||||
description: "280个型号完整目录。3分钟找到适合你的舵机,AI初选,工程师复核。",
|
||||
description: "243款舵机+配件,共286个型号。3分钟找到适合你的舵机,AI初选,工程师复核。",
|
||||
images: [{ url: new URL("/og.png", metadataBase).toString(), width: 1200, height: 630 }],
|
||||
type: "website",
|
||||
},
|
||||
|
||||
2393
app/page.tsx
11603
app/precision-gear-data.ts
Normal file
6
deploy/Caddyfile.duoji
Normal 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
@@ -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
@@ -8,13 +8,16 @@
|
||||
"scripts": {
|
||||
"dev": "WRANGLER_LOG_PATH=.wrangler/wrangler.log vinext dev",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"drizzle-orm": "0.45.2",
|
||||
"exceljs": "^4.4.0",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6"
|
||||
},
|
||||
|
||||
BIN
public/catalog-products/precision-gears-size/1000.jpg
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
public/catalog-products/precision-gears-size/260.jpg
Normal file
|
After Width: | Height: | Size: 206 KiB |
BIN
public/catalog-products/precision-gears-size/329.jpg
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
public/catalog-products/precision-gears-size/330.jpg
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
public/catalog-products/precision-gears-size/331.jpg
Normal file
|
After Width: | Height: | Size: 232 KiB |
BIN
public/catalog-products/precision-gears-size/332.jpg
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
public/catalog-products/precision-gears-size/333.jpg
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
public/catalog-products/precision-gears-size/334.jpg
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
public/catalog-products/precision-gears-size/335.jpg
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
public/catalog-products/precision-gears-size/336.jpg
Normal file
|
After Width: | Height: | Size: 232 KiB |
BIN
public/catalog-products/precision-gears-size/337.jpg
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
public/catalog-products/precision-gears-size/338.jpg
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
public/catalog-products/precision-gears-size/339.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
public/catalog-products/precision-gears-size/340.jpg
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
public/catalog-products/precision-gears-size/341.jpg
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
public/catalog-products/precision-gears-size/342.jpg
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
public/catalog-products/precision-gears-size/343.jpg
Normal file
|
After Width: | Height: | Size: 206 KiB |
BIN
public/catalog-products/precision-gears-size/344.jpg
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
public/catalog-products/precision-gears-size/345.jpg
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
public/catalog-products/precision-gears-size/346.jpg
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
public/catalog-products/precision-gears-size/347.jpg
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
public/catalog-products/precision-gears-size/348.jpg
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
public/catalog-products/precision-gears-size/349.jpg
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
public/catalog-products/precision-gears-size/350.jpg
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
public/catalog-products/precision-gears-size/351.jpg
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
public/catalog-products/precision-gears-size/352.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
public/catalog-products/precision-gears-size/353.jpg
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
public/catalog-products/precision-gears-size/354.jpg
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
public/catalog-products/precision-gears-size/355.jpg
Normal file
|
After Width: | Height: | Size: 211 KiB |
BIN
public/catalog-products/precision-gears-size/356.jpg
Normal file
|
After Width: | Height: | Size: 186 KiB |
BIN
public/catalog-products/precision-gears-size/358.jpg
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
public/catalog-products/precision-gears-size/359.jpg
Normal file
|
After Width: | Height: | Size: 238 KiB |
BIN
public/catalog-products/precision-gears-size/360.jpg
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
public/catalog-products/precision-gears-size/361.jpg
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
public/catalog-products/precision-gears-size/362.jpg
Normal file
|
After Width: | Height: | Size: 263 KiB |
BIN
public/catalog-products/precision-gears-size/363.jpg
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
public/catalog-products/precision-gears-size/364.jpg
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
public/catalog-products/precision-gears-size/365.jpg
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
public/catalog-products/precision-gears-size/366.jpg
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
public/catalog-products/precision-gears-size/367.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
public/catalog-products/precision-gears-size/368.jpg
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
public/catalog-products/precision-gears-size/369.jpg
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
public/catalog-products/precision-gears-size/370.jpg
Normal file
|
After Width: | Height: | Size: 263 KiB |
BIN
public/catalog-products/precision-gears-size/371.jpg
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
public/catalog-products/precision-gears-size/372.jpg
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
public/catalog-products/precision-gears-size/373.jpg
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
public/catalog-products/precision-gears-size/374.jpg
Normal file
|
After Width: | Height: | Size: 206 KiB |
BIN
public/catalog-products/precision-gears-size/375.jpg
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
public/catalog-products/precision-gears-size/376.jpg
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
public/catalog-products/precision-gears-size/377.jpg
Normal file
|
After Width: | Height: | Size: 237 KiB |
BIN
public/catalog-products/precision-gears-size/378.jpg
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
public/catalog-products/precision-gears-size/379.jpg
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
public/catalog-products/precision-gears-size/380.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
public/catalog-products/precision-gears-size/381.jpg
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
public/catalog-products/precision-gears-size/382.jpg
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
public/catalog-products/precision-gears-size/383.jpg
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
public/catalog-products/precision-gears-size/384.jpg
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
public/catalog-products/precision-gears-size/385.jpg
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
public/catalog-products/precision-gears-size/386.jpg
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
public/catalog-products/precision-gears-size/387.jpg
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
public/catalog-products/precision-gears-size/388.jpg
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
public/catalog-products/precision-gears-size/389.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
public/catalog-products/precision-gears-size/390.jpg
Normal file
|
After Width: | Height: | Size: 244 KiB |
BIN
public/catalog-products/precision-gears-size/391.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
public/catalog-products/precision-gears-size/392.jpg
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
public/catalog-products/precision-gears-size/393.jpg
Normal file
|
After Width: | Height: | Size: 244 KiB |
BIN
public/catalog-products/precision-gears-size/394.jpg
Normal file
|
After Width: | Height: | Size: 272 KiB |
BIN
public/catalog-products/precision-gears-size/395.jpg
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
public/catalog-products/precision-gears-size/396.jpg
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
public/catalog-products/precision-gears-size/397.jpg
Normal file
|
After Width: | Height: | Size: 235 KiB |
BIN
public/catalog-products/precision-gears-size/398.jpg
Normal file
|
After Width: | Height: | Size: 263 KiB |
BIN
public/catalog-products/precision-gears-size/399.jpg
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
public/catalog-products/precision-gears-size/400.jpg
Normal file
|
After Width: | Height: | Size: 262 KiB |
BIN
public/catalog-products/precision-gears-size/401.jpg
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
public/catalog-products/precision-gears-size/402.jpg
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
public/catalog-products/precision-gears-size/403.jpg
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
public/catalog-products/precision-gears-size/404.jpg
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
public/catalog-products/precision-gears-size/405.jpg
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
public/catalog-products/precision-gears-size/406.jpg
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
public/catalog-products/precision-gears-size/407.jpg
Normal file
|
After Width: | Height: | Size: 211 KiB |
BIN
public/catalog-products/precision-gears-size/408.jpg
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
public/catalog-products/precision-gears-size/409.jpg
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
public/catalog-products/precision-gears-size/410.jpg
Normal file
|
After Width: | Height: | Size: 208 KiB |