diff --git a/public/solutions/agent-operations-final.png b/public/solutions/agent-operations-final.png new file mode 100644 index 0000000..e634991 Binary files /dev/null and b/public/solutions/agent-operations-final.png differ diff --git a/public/solutions/investment-research-final.png b/public/solutions/investment-research-final.png new file mode 100644 index 0000000..11da1fc Binary files /dev/null and b/public/solutions/investment-research-final.png differ diff --git a/public/solutions/media-distribution-final.png b/public/solutions/media-distribution-final.png new file mode 100644 index 0000000..249b413 Binary files /dev/null and b/public/solutions/media-distribution-final.png differ diff --git a/src/app/(site)/solutions/[slug]/page.tsx b/src/app/(site)/solutions/[slug]/page.tsx new file mode 100644 index 0000000..ff8ae1e --- /dev/null +++ b/src/app/(site)/solutions/[slug]/page.tsx @@ -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 { + 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 ( + + + + ); +} diff --git a/src/app/(site)/solutions/page.tsx b/src/app/(site)/solutions/page.tsx index 9aca5ba..c15db76 100644 --- a/src/app/(site)/solutions/page.tsx +++ b/src/app/(site)/solutions/page.tsx @@ -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 ( - + ); diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index dbdcd3f..3e866cc 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -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]; } diff --git a/src/components/scroll-reveal.tsx b/src/components/scroll-reveal.tsx index 91526f3..5a5867e 100644 --- a/src/components/scroll-reveal.tsx +++ b/src/components/scroll-reveal.tsx @@ -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
{children}
; } @@ -44,9 +56,9 @@ type ScrollRevealHeroProps = { /** 首屏进入页面时播放一次 */ export function ScrollRevealHero({ children, className }: ScrollRevealHeroProps) { - const reduceMotion = useReducedMotion(); + const canAnimate = useCanAnimate(); - if (reduceMotion) { + if (!canAnimate) { return
{children}
; } @@ -74,9 +86,9 @@ export function ScrollRevealItem({ className, index = 0, }: ScrollRevealItemProps) { - const reduceMotion = useReducedMotion(); + const canAnimate = useCanAnimate(); - if (reduceMotion) { + if (!canAnimate) { return
{children}
; } diff --git a/src/components/section-enterprise.tsx b/src/components/section-enterprise.tsx index 5b30e24..32e1e95 100644 --- a/src/components/section-enterprise.tsx +++ b/src/components/section-enterprise.tsx @@ -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 (
- + -
- {s.solutions.map((item) => ( -
-

{item.title}

-

{item.body}

+
+ {solutionCards.map((item) => ( +
+ + {item.imageAlt} + +
+

{item.eyebrow}

+

+ {item.title} +

+

{item.lead}

+
    + {item.tags.map((tag) => ( +
  • {tag}
  • + ))} +
+ + 查看方案 + +
))}
diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index 8fba7b6..e4a216a 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -73,16 +73,41 @@ export function SiteHeader() { aria-label="主导航" >
    - {site.nav.map((item) => ( -
  • + {site.nav.map((item) => { + const children = "children" in item ? item.children : undefined; + return ( +
  • {item.label} + {children ? ( +
    + {children.map((child) => ( + + {child.label} + + ))} +
    + ) : null}
  • - ))} + ); + })}
@@ -122,7 +147,9 @@ export function SiteHeader() { >
+ ); +} diff --git a/src/content/site.ts b/src/content/site.ts index e80a279..23b5fe0 100644 --- a/src/content/site.ts +++ b/src/content/site.ts @@ -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" }, ], }, diff --git a/src/content/solutions.ts b/src/content/solutions.ts new file mode 100644 index 0000000..5fb896d --- /dev/null +++ b/src/content/solutions.ts @@ -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); +} diff --git a/src/styles/components.css b/src/styles/components.css index bba93ac..dea499f 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -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;