Add industry solution pages

This commit is contained in:
Jane Doe
2026-06-19 11:29:38 +08:00
parent 2b4f92a8d1
commit 18a93a2114
13 changed files with 987 additions and 30 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -0,0 +1,52 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { SubpageShell } from "@/components/subpage-shell";
import { SolutionDetail } from "@/components/solution-detail";
import {
getSolutionBySlug,
getSolutionSlugs,
} from "@/content/solutions";
import { pageMetadata } from "@/lib/site-metadata";
type SolutionRouteProps = {
params: Promise<{
slug: string;
}>;
};
export function generateStaticParams() {
return getSolutionSlugs().map((slug) => ({ slug }));
}
export async function generateMetadata({
params,
}: SolutionRouteProps): Promise<Metadata> {
const { slug } = await params;
const solution = getSolutionBySlug(slug);
if (!solution) {
return {};
}
return pageMetadata({
title: solution.title,
description: solution.lead,
path: solution.href,
keywords: [solution.title, "AI Agent", "行业解决方案", "海宇数科"],
});
}
export default async function SolutionPage({ params }: SolutionRouteProps) {
const { slug } = await params;
const solution = getSolutionBySlug(slug);
if (!solution) {
notFound();
}
return (
<SubpageShell>
<SolutionDetail solution={solution} />
</SubpageShell>
);
}

View File

@@ -2,22 +2,24 @@ import type { Metadata } from "next";
import { SubpageShell } from "@/components/subpage-shell";
import { PageHero } from "@/components/page-hero";
import { SectionEnterprise } from "@/components/section-enterprise";
import { site } from "@/content/site";
import { solutionOverview } from "@/content/solutions";
import { pageMetadata } from "@/lib/site-metadata";
const s = site.enterprise;
export const metadata: Metadata = pageMetadata({
title: s.title,
description: s.lead,
title: solutionOverview.title,
description: solutionOverview.lead,
path: "/solutions",
keywords: ["企业数字化", "解决方案", "大模型", "海宇数科"],
keywords: ["企业方案", "行业解决方案", "AI Agent", "海宇数科"],
});
export default function SolutionsPage() {
return (
<SubpageShell>
<PageHero eyebrow={s.eyebrow} title={s.title} lead={s.lead} />
<PageHero
eyebrow={solutionOverview.eyebrow}
title={solutionOverview.title}
lead={solutionOverview.lead}
/>
<SectionEnterprise />
</SubpageShell>
);

View File

@@ -1,5 +1,6 @@
import type { MetadataRoute } from "next";
import { getAllNewsSlugs, getNewsBySlug } from "@/content/news-articles";
import { getSolutionSlugs } from "@/content/solutions";
import { canonicalAbsoluteUrl } from "@/lib/site-url";
export default function sitemap(): MetadataRoute.Sitemap {
@@ -24,6 +25,13 @@ export default function sitemap(): MetadataRoute.Sitemap {
priority: path === "/" ? 1 : 0.9,
}));
const solutionEntries: MetadataRoute.Sitemap = getSolutionSlugs().map((slug) => ({
url: canonicalAbsoluteUrl(`/solutions/${slug}`),
lastModified: now,
changeFrequency: "weekly" as const,
priority: 0.85,
}));
const newsEntries: MetadataRoute.Sitemap = getAllNewsSlugs().map((slug) => {
const article = getNewsBySlug(slug);
const lastModified = article?.date
@@ -37,5 +45,5 @@ export default function sitemap(): MetadataRoute.Sitemap {
};
});
return [...staticEntries, ...newsEntries];
return [...staticEntries, ...solutionEntries, ...newsEntries];
}

View File

@@ -1,10 +1,22 @@
"use client";
import { motion, useReducedMotion } from "motion/react";
import { useEffect, useState } from "react";
import type { ReactNode } from "react";
const easeOut = [0.22, 1, 0.36, 1] as const;
function useCanAnimate() {
const reduceMotion = useReducedMotion();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
return mounted && !reduceMotion;
}
type ScrollRevealProps = {
children: ReactNode;
className?: string;
@@ -18,9 +30,9 @@ export function ScrollReveal({
className,
delay = 0,
}: ScrollRevealProps) {
const reduceMotion = useReducedMotion();
const canAnimate = useCanAnimate();
if (reduceMotion) {
if (!canAnimate) {
return <div className={className}>{children}</div>;
}
@@ -44,9 +56,9 @@ type ScrollRevealHeroProps = {
/** 首屏进入页面时播放一次 */
export function ScrollRevealHero({ children, className }: ScrollRevealHeroProps) {
const reduceMotion = useReducedMotion();
const canAnimate = useCanAnimate();
if (reduceMotion) {
if (!canAnimate) {
return <div className={className}>{children}</div>;
}
@@ -74,9 +86,9 @@ export function ScrollRevealItem({
className,
index = 0,
}: ScrollRevealItemProps) {
const reduceMotion = useReducedMotion();
const canAnimate = useCanAnimate();
if (reduceMotion) {
if (!canAnimate) {
return <div className={className}>{children}</div>;
}

View File

@@ -1,18 +1,46 @@
import { site } from "@/content/site";
import Image from "next/image";
import Link from "next/link";
import { SectionIntro } from "@/components/section-intro";
import { solutionCards, solutionOverview } from "@/content/solutions";
export function SectionEnterprise() {
const s = site.enterprise;
return (
<section className="band-light band-section" id="solutions">
<div className="container-site section-stack">
<SectionIntro eyebrow={s.eyebrow} title={s.title} lead={s.lead} />
<SectionIntro
eyebrow={solutionOverview.eyebrow}
title={solutionOverview.title}
lead={solutionOverview.lead}
/>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{s.solutions.map((item) => (
<article key={item.title} className="card-on-light flex flex-col gap-4">
<h3 className="type-display-md text-balance text-ink">{item.title}</h3>
<p className="type-body-md text-body">{item.body}</p>
<div className="solution-card-grid">
{solutionCards.map((item) => (
<article key={item.id} className="solution-card card-on-light">
<Link href={item.href} className="solution-card__media">
<Image
src={item.image}
alt={item.imageAlt}
fill
loading={item.id === "digital-enterprise" ? "eager" : "lazy"}
sizes="(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw"
className="solution-card__image"
/>
</Link>
<div className="solution-card__body">
<p className="type-caption-strong text-accent">{item.eyebrow}</p>
<h3 className="solution-card__title type-display-md text-balance text-ink">
<Link href={item.href}>{item.title}</Link>
</h3>
<p className="solution-card__lead type-body-md text-body">{item.lead}</p>
<ul className="solution-card__tags" aria-label={`${item.title}能力标签`}>
{item.tags.map((tag) => (
<li key={tag}>{tag}</li>
))}
</ul>
<Link href={item.href} className="solution-card__link">
</Link>
</div>
</article>
))}
</div>

View File

@@ -73,16 +73,41 @@ export function SiteHeader() {
aria-label="主导航"
>
<ul className="nav-island-menu__list">
{site.nav.map((item) => (
<li key={item.href}>
{site.nav.map((item) => {
const children = "children" in item ? item.children : undefined;
return (
<li
key={item.href}
className={
children
? "nav-island-menu__item nav-island-menu__item--has-menu"
: "nav-island-menu__item"
}
>
<Link
href={resolveSiteHref(item.href)}
className="nav-island-menu__link"
aria-haspopup={children ? "menu" : undefined}
>
{item.label}
</Link>
</li>
{children ? (
<div className="nav-dropdown" role="menu">
{children.map((child) => (
<Link
key={child.href}
href={resolveSiteHref(child.href)}
className="nav-dropdown__link"
role="menuitem"
>
{child.label}
</Link>
))}
</div>
) : null}
</li>
);
})}
</ul>
</nav>
@@ -122,7 +147,9 @@ export function SiteHeader() {
>
<nav aria-label="移动端主导航">
<ul className="nav-mobile-drawer__list">
{site.nav.map((item) => (
{site.nav.map((item) => {
const children = "children" in item ? item.children : undefined;
return (
<li key={item.href}>
<Link
href={resolveSiteHref(item.href)}
@@ -131,8 +158,24 @@ export function SiteHeader() {
>
{item.label}
</Link>
{children ? (
<ul className="nav-mobile-drawer__sublist">
{children.map((child) => (
<li key={child.href}>
<Link
href={resolveSiteHref(child.href)}
className="nav-mobile-drawer__sublink"
onClick={() => setMenuOpen(false)}
>
{child.label}
</Link>
</li>
))}
</ul>
) : null}
</li>
);
})}
<li className="nav-mobile-drawer__cta-row">
<a
href="/#contact"

View File

@@ -0,0 +1,131 @@
import Image from "next/image";
import Link from "next/link";
import type { SolutionPage } from "@/content/solutions";
type SolutionDetailProps = {
solution: SolutionPage;
};
export function SolutionDetail({ solution }: SolutionDetailProps) {
return (
<article className="solution-detail-page">
<section className="solution-detail-hero band-light band-section">
<div className="container-site solution-detail-hero__layout">
<div className="solution-detail-hero__copy">
<Link
href="/solutions"
className="page-hero__back type-caption-strong text-body no-underline hover:no-underline w-fit"
>
</Link>
<p className="section-eyebrow">{solution.eyebrow}</p>
<h1 className="solution-detail-hero__title type-display-xl text-balance text-ink">
{solution.title}
</h1>
<p className="type-lead text-body">{solution.lead}</p>
<p className="solution-detail-hero__summary type-body-md text-body">
{solution.summary}
</p>
<div className="solution-detail-hero__actions">
<a href="/#contact" className="btn-primary no-underline hover:no-underline">
</a>
<Link
href="/solutions"
className="btn-outline no-underline hover:no-underline"
>
</Link>
</div>
</div>
<div className="solution-detail-visual">
<div className="solution-detail-visual__image">
<Image
src={solution.image}
alt={solution.imageAlt}
fill
priority
sizes="(min-width: 992px) 48vw, 100vw"
className="solution-detail-visual__img"
/>
</div>
<dl className="solution-detail-stats">
{solution.stats.map((stat) => (
<div key={stat.label} className="solution-detail-stat">
<dt>{stat.label}</dt>
<dd>{stat.value}</dd>
</div>
))}
</dl>
</div>
</div>
</section>
<section className="band-muted band-section">
<div className="container-site section-stack">
<header className="measure-title flex flex-col gap-4">
<p className="type-eyebrow section-eyebrow">Workflow</p>
<h2 className="type-display-xl text-ink"></h2>
</header>
<ol className="solution-workflow">
{solution.workflow.map((step, index) => (
<li key={step} className="solution-workflow__item">
<span className="solution-workflow__no">
{String(index + 1).padStart(2, "0")}
</span>
<span className="solution-workflow__label">{step}</span>
</li>
))}
</ol>
</div>
</section>
<section className="band-light band-section">
<div className="container-site section-stack">
<header className="measure-title flex flex-col gap-4">
<p className="type-eyebrow section-eyebrow">Capability</p>
<h2 className="type-display-xl text-ink"></h2>
</header>
<div className="solution-capability-grid">
{solution.capabilities.map((item) => (
<article key={item.title} className="solution-capability card-on-light">
<h3 className="type-display-md text-ink">{item.title}</h3>
<p className="type-body-md text-body">{item.body}</p>
</article>
))}
</div>
</div>
</section>
<section className="band-muted band-section">
<div className="container-site solution-detail-columns">
<div className="solution-list-panel">
<p className="type-eyebrow section-eyebrow">Scenario</p>
<h2 className="type-display-md text-ink"></h2>
<ul>
{solution.scenarios.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
<div className="solution-list-panel">
<p className="type-eyebrow section-eyebrow">Output</p>
<h2 className="type-display-md text-ink"></h2>
<ul>
{solution.outputs.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
{solution.note ? (
<aside className="solution-note">
<p className="type-caption-strong"></p>
<p>{solution.note}</p>
</aside>
) : null}
</div>
</section>
</article>
);
}

View File

@@ -1,3 +1,5 @@
import { solutionNavItems } from "@/content/solutions";
export const site = {
brand: "海宇数科",
tagline: "企业级大模型服务与智能报告平台",
@@ -33,7 +35,7 @@ export const site = {
{ label: "智能报告", href: "/reports" },
{ label: "Agent 技能", href: "/agents" },
{ label: "模型能力", href: "/models" },
{ label: "企业方案", href: "/solutions" },
{ label: "企业方案", href: "/solutions", children: solutionNavItems },
{ label: "AI 资讯", href: "/news" },
],
hero: {
@@ -487,7 +489,10 @@ export const site = {
{
title: "方案",
links: [
{ label: "企业数字化", href: "/solutions" },
{ label: "企业数字化解决方案", href: "/solutions" },
{ label: "新媒体内容分发智能体", href: "/solutions/media-distribution" },
{ label: "股市投研情报智能体", href: "/solutions/investment-research" },
{ label: "多平台自动运营智能体", href: "/solutions/agent-operations" },
{ label: "控制台", href: "https://console.haiyushuke.com" },
],
},

226
src/content/solutions.ts Normal file
View File

@@ -0,0 +1,226 @@
export type SolutionNavItem = {
label: string;
href: string;
};
export type SolutionCard = {
id: string;
eyebrow: string;
title: string;
href: string;
lead: string;
image: string;
imageAlt: string;
tags: string[];
};
export type SolutionPage = SolutionCard & {
slug: string;
summary: string;
stats: Array<{
value: string;
label: string;
}>;
workflow: string[];
capabilities: Array<{
title: string;
body: string;
}>;
scenarios: string[];
outputs: string[];
note?: string;
};
export const solutionNavItems: SolutionNavItem[] = [
{ label: "企业数字化解决方案", href: "/solutions/digital-enterprise" },
{ label: "新媒体内容分发智能体", href: "/solutions/media-distribution" },
{ label: "股市投研情报智能体", href: "/solutions/investment-research" },
{ label: "多平台自动运营智能体", href: "/solutions/agent-operations" },
];
export const solutionOverview = {
eyebrow: "企业方案",
title: "AI Agent 行业解决方案",
lead: "从通用企业数字化底座,到内容分发、投研情报与自动运营,把可重复的信息处理流程封装成可运行、可审计、可接入的业务智能体。",
};
const deliveryOutputs = [
"一对一部署 Agent",
"对接大模型",
"一对一指导",
"按需定制",
"稳定交付",
"24 小时持续运行",
];
const digitalEnterprisePage: SolutionPage = {
id: "digital-enterprise",
slug: "digital-enterprise",
eyebrow: "通用方案",
title: "企业数字化解决方案",
href: "/solutions/digital-enterprise",
lead: "将大模型能力嵌入 ERP、数据中台与协同办公流程形成可落地的经营分析、合规风控与客户洞察能力。",
summary:
"面向企业已有系统和业务流程,将智能报告、知识问答、流程自动化和数据洞察能力接入日常经营,让 AI 成为可审计、可协同、可持续运行的企业能力底座。",
image: "/banner.png",
imageAlt: "企业数字化解决方案示意图",
tags: ["经营分析", "合规风控", "客户洞察"],
stats: [
{ value: "ERP", label: "业务系统接入" },
{ value: "BI", label: "经营分析看板" },
{ value: "Agent", label: "智能体流程编排" },
],
workflow: ["需求梳理", "系统接入", "模型编排", "流程上线", "持续优化"],
capabilities: [
{
title: "经营分析中枢",
body: "统一指标口径,自动生成经营看板、数据解读和管理层报告,减少人工汇总成本。",
},
{
title: "合规与风控助手",
body: "支持制度文档问答、合同要点提取、异常条款提示和风险复核流转。",
},
{
title: "客户成功洞察",
body: "从工单、通话记录和客户反馈中提炼风险信号、续约建议和跟进动作。",
},
{
title: "既有系统融合",
body: "围绕 ERP、数据中台、知识库和协同办公系统做轻量接入不替代已有系统。",
},
],
scenarios: ["经营数据分析", "合同与制度核验", "客户续约洞察", "企业知识库问答"],
outputs: deliveryOutputs,
};
export const solutionPages: SolutionPage[] = [
digitalEnterprisePage,
{
id: "media-distribution",
slug: "media-distribution",
eyebrow: "新媒体方案",
title: "新媒体内容分发智能体",
href: "/solutions/media-distribution",
lead: "面向多平台内容运营团队,统一完成选题排期、内容生成、平台适配、账号发布与异常追踪。",
summary:
"把公众号、知乎、头条、百家号、CSDN、掘金等平台的发布规则和账号流程集中管理让团队从反复搬运和格式调整中释放出来。",
image: "/solutions/media-distribution-final.png",
imageAlt: "新媒体内容分发智能体仪表盘示意图",
tags: ["内容编排", "多平台分发", "发布追踪"],
stats: [
{ value: "6+", label: "平台能力矩阵" },
{ value: "92%", label: "执行成功率看板" },
{ value: "7x24", label: "任务队列巡检" },
],
workflow: ["排期导入", "AI 生成", "平台适配", "安全发布", "数据回写"],
capabilities: [
{
title: "多平台内容适配",
body: "根据不同平台的标题、封面、标签、正文长度与链接规则,自动生成可发布版本。",
},
{
title: "账号与会话管理",
body: "统一管理多账号身份、登录状态、发布节奏与异常提醒,降低人工切换成本。",
},
{
title: "生成预览与人工确认",
body: "发布前集中预览标题、封面和正文,关键内容保留人工确认节点。",
},
{
title: "发布历史可追踪",
body: "沉淀发布记录、失败原因、平台反馈与任务日志,方便团队复盘和审计。",
},
],
scenarios: ["品牌内容矩阵运营", "知识型长文分发", "技术社区同步发布", "活动内容定时推送"],
outputs: deliveryOutputs,
},
{
id: "investment-research",
slug: "investment-research",
eyebrow: "投研方案",
title: "股市投研情报智能体",
href: "/solutions/investment-research",
lead: "面向投研、财经内容与股票社群场景,自动聚合公告、舆情、行情与信号,输出可追溯的机会卡片。",
summary:
"把公告快讯、社区讨论、行情异动和因子信号接入同一个情报中枢,帮助团队更快发现值得复核的信息线索。",
image: "/solutions/investment-research-final.png",
imageAlt: "股市投研情报智能体仪表盘示意图",
tags: ["公告雷达", "舆情热度", "信号复核"],
stats: [
{ value: "24h", label: "公告与舆情巡检" },
{ value: "S/A/B", label: "线索分层评分" },
{ value: "0 荐股", label: "合规定位" },
],
workflow: ["数据接入", "信息雷达", "因子判断", "机会卡片", "复盘反馈"],
capabilities: [
{
title: "信息雷达层",
body: "聚合公告、财经快讯、社区舆情、行情快照与回看因子,形成统一情报入口。",
},
{
title: "前置信号评分",
body: "按热度、时效、证据来源、利好利空与风险提示,对候选线索进行分层。",
},
{
title: "群消息与看板推送",
body: "把重要公告、异动线索和复核按钮推送到社群或内部看板,减少人工盯盘压力。",
},
{
title: "反馈闭环",
body: "沉淀用户反馈、关注股、误报原因与后续表现,用于优化下一轮信号判断。",
},
],
scenarios: ["股票社群信息推送", "财经内容选题发现", "投研线索初筛", "公告与舆情巡检"],
outputs: deliveryOutputs,
note: "本方案用于信息整理、线索发现与辅助分析,不提供荐股、收益承诺或证券投资建议。",
},
{
id: "agent-operations",
slug: "agent-operations",
eyebrow: "运营方案",
title: "多平台自动运营智能体",
href: "/solutions/agent-operations",
lead: "面向获客、曝光、招聘和私域运营场景,自动识别线索意图、生成回复、沉淀客户并触发人工复核。",
summary:
"把小红书、抖音、BOSS直聘、微信等平台的重复运营动作串成闭环让 AI 先完成扫描、判断、回复草稿和客户入库。",
image: "/solutions/agent-operations-final.png",
imageAlt: "多平台自动运营智能体流程示意图",
tags: ["线索发现", "自动回复", "客户沉淀"],
stats: [
{ value: "4 类", label: "平台运营场景" },
{ value: "83%", label: "任务进度追踪" },
{ value: "CSV", label: "客户数据导出" },
],
workflow: ["线索发现", "意图识别", "自动触达", "客户入库", "持续跟进"],
capabilities: [
{
title: "线索发现与意图判断",
body: "扫描评论区、私信、候选人沟通和互动线索,判断是否值得进入跟进队列。",
},
{
title: "自动回复与触达",
body: "结合上下文生成回复草稿,支持自动执行或人工确认后执行。",
},
{
title: "客户库沉淀",
body: "把平台来源、需求标签、沟通状态与后续动作写入客户库,避免线索流失。",
},
{
title: "风控与人工复核",
body: "关键动作保留人工审核,支持频率限制、失败恢复、日志追踪与一键导出。",
},
],
scenarios: ["小红书评论找客", "抖音互动曝光", "招聘候选筛选", "微信私域跟进"],
outputs: deliveryOutputs,
},
];
export const solutionCards: SolutionCard[] = solutionPages;
export function getSolutionBySlug(slug: string) {
return solutionPages.find((solution) => solution.slug === slug);
}
export function getSolutionSlugs() {
return solutionPages.map((solution) => solution.slug);
}

View File

@@ -332,6 +332,344 @@
white-space: normal;
}
.solution-card-grid {
display: grid;
gap: var(--spacing-xl);
grid-template-columns: minmax(0, 1fr);
}
@media (width >= 640px) {
.solution-card-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (width >= 1180px) {
.solution-card-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
.solution-card.card-on-light {
display: flex;
flex-direction: column;
gap: 0;
overflow: hidden;
padding: 0;
}
.solution-card__media {
position: relative;
display: block;
aspect-ratio: 16 / 9;
width: 100%;
overflow: hidden;
background-color: var(--color-canvas-muted);
}
.solution-card__image {
object-fit: cover;
transition: transform 0.3s ease;
}
.solution-card:hover .solution-card__image {
transform: scale(1.018);
}
.solution-card__body {
display: flex;
flex: 1;
flex-direction: column;
gap: var(--spacing-md);
padding: var(--spacing-xl);
min-width: 0;
}
.solution-card__title {
margin: 0;
}
.solution-card__title a {
color: inherit;
text-decoration: none;
}
.solution-card__title a:hover {
color: var(--color-accent-ink);
text-decoration: none;
}
.solution-card__lead {
flex: 1;
margin: 0;
}
.solution-card__tags {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-xs);
margin: 0;
padding: 0;
list-style: none;
}
.solution-card__tags li {
border-radius: var(--radius-sm);
background-color: var(--color-canvas-muted);
color: var(--color-accent-soft);
font-size: 12px;
line-height: 1;
padding: 6px var(--spacing-sm);
}
.solution-card__link {
align-self: flex-start;
color: var(--color-accent-ink);
font-size: 14px;
font-weight: 500;
text-decoration: none;
}
.solution-card__link:hover {
text-decoration: underline;
}
.solution-detail-page {
display: flex;
flex-direction: column;
}
.solution-detail-hero__layout {
display: grid;
gap: var(--spacing-4xl);
align-items: center;
}
@media (width >= 992px) {
.solution-detail-hero__layout {
grid-template-columns: minmax(0, 0.82fr) minmax(0, 1fr);
}
}
.solution-detail-hero__copy {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
min-width: 0;
}
.solution-detail-hero__title {
margin: 0;
max-width: 14em;
}
.solution-detail-hero__summary {
margin: 0;
max-width: 40rem;
}
.solution-detail-hero__actions {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-md);
margin-top: var(--spacing-sm);
}
.solution-detail-visual {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
min-width: 0;
}
.solution-detail-visual__image {
position: relative;
aspect-ratio: 16 / 9;
width: 100%;
overflow: hidden;
border-radius: var(--radius-md);
background-color: var(--color-canvas-muted);
box-shadow: var(--shadow-surface-hover);
}
.solution-detail-visual__img {
object-fit: cover;
}
.solution-detail-stats {
display: grid;
gap: var(--spacing-md);
grid-template-columns: repeat(3, minmax(0, 1fr));
margin: 0;
}
@media (width < 640px) {
.solution-detail-stats {
grid-template-columns: 1fr;
}
}
.solution-detail-stat {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
padding: var(--spacing-lg);
border-radius: var(--radius-md);
background-color: var(--color-canvas);
box-shadow: var(--shadow-field);
min-width: 0;
}
.solution-detail-stat dt {
color: var(--color-body-light);
font-size: 12px;
line-height: 1.2;
}
.solution-detail-stat dd {
margin: 0;
color: var(--color-ink);
font-family: var(--font-display);
font-size: 24px;
font-weight: 600;
line-height: 1.1;
}
.solution-workflow {
display: grid;
gap: var(--spacing-md);
grid-template-columns: minmax(0, 1fr);
margin: 0;
padding: 0;
list-style: none;
}
@media (width >= 768px) {
.solution-workflow {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
.solution-workflow__item {
position: relative;
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
min-height: 120px;
padding: var(--spacing-xl);
border-radius: var(--radius-md);
background-color: var(--color-canvas);
box-shadow: var(--shadow-field);
}
.solution-workflow__no {
color: var(--color-accent-ink);
font-family: var(--font-mono-caps);
font-size: 12px;
letter-spacing: 0.06em;
}
.solution-workflow__label {
color: var(--color-ink);
font-family: var(--font-display);
font-size: 16px;
font-weight: 500;
line-height: 1.35;
}
.solution-capability-grid {
display: grid;
gap: var(--spacing-xl);
grid-template-columns: minmax(0, 1fr);
}
@media (width >= 768px) {
.solution-capability-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.solution-capability {
min-height: 172px;
}
.solution-capability h3,
.solution-capability p {
margin: 0;
}
.solution-detail-columns {
display: grid;
gap: var(--spacing-xl);
align-items: stretch;
grid-template-columns: minmax(0, 1fr);
}
@media (width >= 900px) {
.solution-detail-columns {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.solution-detail-columns:has(.solution-note) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
.solution-list-panel,
.solution-note {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
padding: var(--spacing-2xl);
border-radius: var(--radius-md);
background-color: var(--color-canvas);
box-shadow: var(--shadow-surface);
min-width: 0;
}
.solution-list-panel h2,
.solution-note p {
margin: 0;
}
.solution-list-panel ul {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
margin: 0;
padding: 0;
list-style: none;
}
.solution-list-panel li {
position: relative;
padding-left: var(--spacing-lg);
color: var(--color-body);
font-size: 15px;
line-height: 1.55;
}
.solution-list-panel li::before {
position: absolute;
top: 0.62em;
left: 0;
width: 6px;
height: 6px;
border-radius: var(--radius-full);
background-color: var(--color-accent-ink);
content: "";
}
.solution-note {
background-color: var(--color-accent-wash);
}
.solution-note p:last-child {
color: var(--color-body);
font-size: 14px;
line-height: 1.7;
}
.news-list-card {
transition: box-shadow 0.2s ease;
}
@@ -1496,15 +1834,29 @@
list-style: none;
}
.nav-island-menu__list > li {
.nav-island-menu__item {
display: flex;
align-items: center;
}
.nav-island-menu__item--has-menu {
position: relative;
}
.nav-island-menu__item--has-menu::after {
position: absolute;
top: 100%;
right: -10px;
left: -10px;
height: 18px;
content: "";
}
.nav-island-menu__link {
display: inline-flex;
height: 100%;
align-items: center;
gap: 6px;
padding-inline: 12px;
font-size: 13px;
line-height: 1;
@@ -1514,11 +1866,81 @@
transition: color 0.2s ease;
}
.nav-island-menu__item--has-menu > .nav-island-menu__link::after {
width: 6px;
height: 6px;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
content: "";
opacity: 0.72;
transform: translateY(-2px) rotate(45deg);
transition: transform 0.18s ease, opacity 0.18s ease;
}
.nav-island-menu__item--has-menu:hover > .nav-island-menu__link::after,
.nav-island-menu__item--has-menu:focus-within > .nav-island-menu__link::after {
opacity: 1;
transform: translateY(1px) rotate(225deg);
}
.nav-island-menu__link:hover {
color: var(--color-accent-ink);
text-decoration: none;
}
.nav-dropdown {
position: absolute;
top: calc(100% + 8px);
left: 50%;
z-index: 10;
display: grid;
width: max-content;
min-width: 220px;
gap: 2px;
padding: var(--spacing-sm);
border-radius: var(--radius-md);
background-color: rgb(255 255 255 / 0.96);
box-shadow: var(--shadow-surface-hover);
opacity: 0;
pointer-events: none;
transform: translate(-50%, -4px);
visibility: hidden;
transition:
opacity 0.18s ease 0.12s,
transform 0.18s ease 0.12s,
visibility 0s linear 0.3s;
}
.nav-island-menu__item--has-menu:hover .nav-dropdown,
.nav-island-menu__item--has-menu:focus-within .nav-dropdown {
opacity: 1;
pointer-events: auto;
transform: translate(-50%, 0);
visibility: visible;
transition-delay: 0.04s, 0.04s, 0s;
}
.nav-dropdown__link {
display: block;
padding: 10px var(--spacing-lg);
border-radius: var(--radius-sm);
color: var(--color-ink);
font-size: 13px;
line-height: 1.25;
letter-spacing: 0.02em;
text-decoration: none;
white-space: nowrap;
transition:
background-color 0.15s ease,
color 0.15s ease;
}
.nav-dropdown__link:hover {
color: var(--color-accent-ink);
background-color: var(--color-accent-wash);
text-decoration: none;
}
.nav-island-actions {
flex-shrink: 0;
gap: 4px;
@@ -1638,6 +2060,34 @@
text-decoration: none;
}
.nav-mobile-drawer__sublist {
display: flex;
flex-direction: column;
gap: 2px;
margin: 0 0 var(--spacing-xs);
padding: 0 0 0 var(--spacing-lg);
list-style: none;
}
.nav-mobile-drawer__sublink {
display: block;
padding: 9px var(--spacing-lg);
border-radius: var(--radius-sm);
color: var(--color-body);
font-size: 14px;
line-height: 1.35;
text-decoration: none;
transition:
background-color 0.15s ease,
color 0.15s ease;
}
.nav-mobile-drawer__sublink:hover {
color: var(--color-accent-ink);
background-color: var(--color-accent-wash);
text-decoration: none;
}
.nav-mobile-drawer__cta-row {
display: flex;
flex-direction: column;