Refine responsive catalog and branding

This commit is contained in:
Jacky Ser
2026-08-11 22:58:14 +08:00
parent 45fbdffddb
commit 81098ea237
2 changed files with 138 additions and 67 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { FormEvent, useMemo, useState } from "react";
import { FormEvent, useEffect, useMemo, useState } from "react";
import {
CATALOG_COUNTS,
CATALOG_PRODUCTS as RAW_CATALOG_PRODUCTS,
@@ -352,6 +352,7 @@ function Metric({ label, value }: { label: string; value: string }) {
export default function Home() {
const [stage, setStage] = useState(0);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [requirements, setRequirements] = useState(DEFAULT_REQUIREMENTS);
const [showSampleForm, setShowSampleForm] = useState(false);
const [submitted, setSubmitted] = useState(false);
@@ -360,9 +361,14 @@ export default function Home() {
const [catalogCategory, setCatalogCategory] = useState("全部产品");
const [catalogScale, setCatalogScale] = useState("all");
const [catalogLimit, setCatalogLimit] = useState(24);
const [catalogToolbarCompact, setCatalogToolbarCompact] = useState(false);
const [catalogToolbarFiltersOpen, setCatalogToolbarFiltersOpen] = useState(false);
const [catalogToolbarHidden, setCatalogToolbarHidden] = useState(false);
const [precisionGearQuery, setPrecisionGearQuery] = useState("");
const [precisionGearCategory, setPrecisionGearCategory] = useState("全部子类");
const [precisionGearLimit, setPrecisionGearLimit] = useState(24);
const [precisionGearToolbarCompact, setPrecisionGearToolbarCompact] = useState(false);
const [precisionGearFiltersOpen, setPrecisionGearFiltersOpen] = useState(false);
const [selectedCatalogProduct, setSelectedCatalogProduct] = useState<CatalogProduct | null>(null);
const [catalogVisualMode, setCatalogVisualMode] = useState<CatalogVisualMode>("main");
@@ -419,12 +425,41 @@ export default function Home() {
? getCatalogProductDimensionImage(selectedCatalogProduct)
: null;
useEffect(() => {
function updateCatalogToolbarState() {
const toolbar = document.querySelector(".catalog-toolbar");
const shouldCompact = window.innerWidth <= 720 && Boolean(toolbar) && toolbar.getBoundingClientRect().top <= 72 && window.scrollY > 0;
setCatalogToolbarCompact((current) => (current === shouldCompact ? current : shouldCompact));
if (!shouldCompact) setCatalogToolbarFiltersOpen(false);
const precisionHeading = document.querySelector(".precision-gear-catalog-heading");
const shouldHideCatalogToolbar = window.innerWidth <= 720 && Boolean(precisionHeading) && precisionHeading.getBoundingClientRect().top <= 72;
setCatalogToolbarHidden((current) => (current === shouldHideCatalogToolbar ? current : shouldHideCatalogToolbar));
const precisionToolbar = document.querySelector(".precision-gear-controls");
const shouldCompactPrecision = window.innerWidth <= 720 && Boolean(precisionToolbar) && precisionToolbar.getBoundingClientRect().top <= 72 && window.scrollY > 0;
setPrecisionGearToolbarCompact((current) => (current === shouldCompactPrecision ? current : shouldCompactPrecision));
if (!shouldCompactPrecision) setPrecisionGearFiltersOpen(false);
}
updateCatalogToolbarState();
window.addEventListener("scroll", updateCatalogToolbarState, { passive: true });
document.addEventListener("scroll", updateCatalogToolbarState, { passive: true, capture: true });
window.addEventListener("resize", updateCatalogToolbarState);
return () => {
window.removeEventListener("scroll", updateCatalogToolbarState);
document.removeEventListener("scroll", updateCatalogToolbarState, true);
window.removeEventListener("resize", updateCatalogToolbarState);
};
}, []);
function openCatalogProduct(product: CatalogProduct) {
setSelectedCatalogProduct(product);
setCatalogVisualMode("main");
}
function startSelection() {
setMobileMenuOpen(false);
setStage(1);
requestAnimationFrame(() => {
document.getElementById("selector")?.scrollIntoView({ behavior: "smooth", block: "start" });
@@ -454,22 +489,33 @@ export default function Home() {
return (
<main>
<header className="site-header">
<a className="brand" href="#top" aria-label="高乐 AI选型首页">
<span className="brand-mark">G</span>
<span>
<a className="brand" href="#top" aria-label="高乐 AI选型首页">
<span className="brand-wordmark">
<strong>GAUDELOT</strong>
<small> · </small>
<small></small>
</span>
</a>
<nav aria-label="主导航">
<a href="#selector">AI选型</a>
<a href="#products"></a>
<a href="#precision-gears">齿</a>
<a href="#how"></a>
<nav id="mobile-primary-nav" className={mobileMenuOpen ? "is-open" : ""} aria-label="主导航">
<a href="#selector" onClick={() => setMobileMenuOpen(false)}>AI选型</a>
<a href="#products" onClick={() => setMobileMenuOpen(false)}></a>
<a href="#precision-gears" onClick={() => setMobileMenuOpen(false)}>齿</a>
<a href="#how" onClick={() => setMobileMenuOpen(false)}></a>
</nav>
<button className="header-cta" type="button" onClick={startSelection}>
<span></span>
</button>
<div className="header-actions">
<button className="header-cta" type="button" onClick={startSelection}>
<span></span>
</button>
<button
className="mobile-menu-toggle"
type="button"
aria-expanded={mobileMenuOpen}
aria-controls="mobile-primary-nav"
onClick={() => setMobileMenuOpen((open) => !open)}
>
<span aria-hidden="true">{mobileMenuOpen ? "×" : "☰"}</span>
<small>{mobileMenuOpen ? "关闭" : "菜单"}</small>
</button>
</div>
</header>
<section className="hero" id="top">
@@ -962,6 +1008,7 @@ export default function Home() {
<p>{CATALOG_PRODUCTS.length}</p>
</div>
<div className="catalog-directory">
<div className="catalog-summary" aria-label="产品分类统计">
{PRIMARY_CATALOG_CATEGORIES.map((category) => (
<button
@@ -980,7 +1027,7 @@ export default function Home() {
</div>
<p className="catalog-category-note"> · {CATALOG_PRODUCTS.length} · 6 · 11</p>
<div className="catalog-toolbar">
<div className={`catalog-toolbar ${catalogToolbarCompact ? "is-compact" : ""} ${catalogToolbarFiltersOpen ? "filters-open" : ""} ${catalogToolbarHidden ? "is-hidden" : ""}`}>
<label className="catalog-search">
<span></span>
<input
@@ -996,6 +1043,14 @@ export default function Home() {
<button type="button" onClick={() => setCatalogQuery("")} aria-label="清空搜索">×</button>
)}
</label>
<button
className="catalog-mobile-filter-toggle"
type="button"
aria-expanded={catalogToolbarFiltersOpen}
onClick={() => setCatalogToolbarFiltersOpen((open) => !open)}
>
{catalogToolbarFiltersOpen ? "收起" : "筛选"}
</button>
<div className="category-pills" aria-label="产品类别筛选">
{CATALOG_FILTER_CATEGORIES.map((category) => (
<button
@@ -1056,7 +1111,6 @@ export default function Home() {
<span className="catalog-placeholder">GAUDELOT</span>
)}
<small>{product.category}</small>
<span className="catalog-zoom-hint"></span>
</button>
<div className="catalog-card-body">
<p>{product.code}</p>
@@ -1090,25 +1144,10 @@ export default function Home() {
</button>
</div>
)}
</div>
<aside className="precision-gear-panel" id="precision-gears" aria-labelledby="precision-gears-title">
<div className="precision-gear-copy">
<p className="eyebrow"><span /> 齿</p>
<h3 id="precision-gears-title">齿</h3>
<p> 511 11 齿</p>
</div>
<div className="precision-gear-overview">
<div className="precision-gear-stat"><strong>511</strong><span></span></div>
<div className="precision-gear-stat"><strong>11</strong><span></span></div>
<div className="precision-gear-subcats" aria-label="精密齿轮配件子类">
{PRECISION_GEAR_SUBCATEGORIES.map(([name, count]) => (
<span key={name}>{name} <b>{count}</b></span>
))}
</div>
</div>
</aside>
<div className="precision-gear-catalog" aria-label="精密齿轮配件产品卡片">
<p className="eyebrow precision-gear-section-label"><span /> 齿</p>
<div className="precision-gear-catalog" id="precision-gears" aria-label="精密齿轮配件产品卡片">
<div className="precision-gear-catalog-heading">
<div>
<p className="step-kicker"></p>
@@ -1116,7 +1155,7 @@ export default function Home() {
</div>
<p></p>
</div>
<div className="precision-gear-controls">
<div className={`precision-gear-controls ${precisionGearToolbarCompact ? "is-compact" : ""} ${precisionGearFiltersOpen ? "filters-open" : ""}`}>
<input
value={precisionGearQuery}
onChange={(event) => {
@@ -1126,6 +1165,14 @@ export default function Home() {
placeholder="搜索齿轮名称、编码、材质或规格"
aria-label="搜索精密齿轮配件"
/>
<button
className="precision-gear-mobile-filter-toggle"
type="button"
aria-expanded={precisionGearFiltersOpen}
onClick={() => setPrecisionGearFiltersOpen((open) => !open)}
>
{precisionGearFiltersOpen ? "收起" : "筛选"}
</button>
<div className="precision-gear-pills" aria-label="精密齿轮子类筛选">
<button
type="button"
@@ -1302,8 +1349,7 @@ export default function Home() {
<footer>
<div className="brand footer-brand">
<span className="brand-mark">G</span>
<span><strong>GAUDELOT</strong><small> · </small></span>
<span className="brand-wordmark"><strong>GAUDELOT</strong><small></small></span>
</div>
<p>AI选型结果仅供初步筛选</p>
<span> · V1</span>