diff --git a/design-qa.md b/design-qa.md new file mode 100644 index 0000000..0fb79cc --- /dev/null +++ b/design-qa.md @@ -0,0 +1,57 @@ +# Homepage Three-Screen Prototype — Design QA + +- Source visual truth: `docs/homepage-prototype-qa/source-direction.jpg` plus the two supplied Douyin design references (bold business matrix and three-screen narrative funnel). +- Implementation: `http://127.0.0.1:5173/` +- Browser-rendered evidence: + - `docs/homepage-prototype-qa/implementation-hero.png` + - `docs/homepage-prototype-qa/implementation-themes.png` + - `docs/homepage-prototype-qa/implementation-story.png` + - `docs/homepage-prototype-qa/implementation-mobile.png` +- Desktop viewport: 1440 × 1000 CSS px; screenshot 1440 × 872 px after app browser chrome, density 1×. +- Mobile viewport: 390 × 844 CSS px; screenshot 390 × 838 px after app browser chrome, density 1×. +- Source pixels: 2358 × 891. The source is directional rather than a 1:1 frame; comparison normalized by composition, hierarchy, density, palette, imagery, and narrative rhythm rather than pixel position. +- State: default English homepage; hero slide 1; desktop and mobile initial view; second and third screens after scroll. + +## Full-view comparison evidence + +- Hero now uses a full-bleed, low-saturation travel image, oversized sans-serif statement, acid-green accent, concise explanation, and an explicit planning action. It is structurally distinct from the former split image/copy travel template. +- The second screen uses an eight-item edge-to-edge business/experience matrix with consistent numbered cells and responsive 4/2/1-column behavior. +- The third screen creates a clear narrative funnel: point of view → method → three-step explanation → trust statement. + +## Focused-region comparison evidence + +- Hero typography/crop checked independently at desktop and mobile widths. +- Theme-card image crop, label contrast, and 2-row alignment checked at 1440 px. +- Third-screen portrait crop, headline wrapping, supporting copy, and step borders checked at 1440 px. + +## Required fidelity surfaces + +- Fonts and typography: bold sans-serif display type deliberately replaces the previous classic serif-first homepage hierarchy; line breaks, weight, and mobile wrapping remain readable. Card and step headings use the same sans family to preserve the new direction. +- Spacing and layout rhythm: three distinct near-viewport chapters; desktop margins align to a 1500 px max canvas; responsive layouts collapse cleanly without horizontal overflow. +- Colors and tokens: deep forest, warm paper, off-white and acid green form a controlled four-color palette with adequate foreground contrast on the checked states. +- Image quality: all visible assets are supplied high-resolution client imagery; crops are intentional, no placeholders, broken images, fake illustration, or stretched assets were observed. +- Copy and content: English is the default. New headline and explanatory copy are specific to WanderQ, while the existing customer-approved content and downstream routes remain in place. + +## Primary interactions tested + +- Hero slideshow controls render and remain operable. +- Primary “Design your journey” CTA resolves to `/contact/`. +- Eight theme cards resolve to existing experience/contact routes. +- Scroll-triggered entrances and hero parallax run without console warnings or errors. +- Broken-image check returned zero failures. + +## Comparison history + +1. P1: global heading CSS overrode the intended bold display face, leaving hero and matrix headings in serif. Fixed with scoped, higher-specificity homepage type rules. Post-fix evidence: all four implementation screenshots. +2. P2: theme-card and story-step headings still inherited the classic serif family. Fixed by explicitly applying the homepage sans family. Post-fix evidence: `implementation-themes.png` and `implementation-story.png`. +3. P2: mobile hero needed separate type and spacing constraints. Added 820 px and 540 px responsive rules; verified at 390 × 844 with no clipping of the main statement or CTA. + +## Findings + +No actionable P0/P1/P2 issues remain in the three-screen prototype. The existing full-site header is intentionally retained so the concept can be judged inside the real product rather than as an isolated mock. + +## Follow-up polish + +- P3: if this direction is approved, tune the remaining homepage sections to continue the same display typography, dark/light cadence, and tighter content density. + +final result: passed diff --git a/docs/WanderQ官网客户验收与资料确认清单.docx b/docs/WanderQ官网客户验收与资料确认清单.docx new file mode 100644 index 0000000..78080ed Binary files /dev/null and b/docs/WanderQ官网客户验收与资料确认清单.docx differ diff --git a/docs/final-audit/01-home.png b/docs/final-audit/01-home.png new file mode 100644 index 0000000..dedc94c Binary files /dev/null and b/docs/final-audit/01-home.png differ diff --git a/docs/final-audit/02-routes.png b/docs/final-audit/02-routes.png new file mode 100644 index 0000000..453c108 Binary files /dev/null and b/docs/final-audit/02-routes.png differ diff --git a/docs/final-audit/03-experiences.png b/docs/final-audit/03-experiences.png new file mode 100644 index 0000000..d917cbc Binary files /dev/null and b/docs/final-audit/03-experiences.png differ diff --git a/docs/final-audit/04-transport.png b/docs/final-audit/04-transport.png new file mode 100644 index 0000000..4408729 Binary files /dev/null and b/docs/final-audit/04-transport.png differ diff --git a/docs/final-audit/05-hotels.png b/docs/final-audit/05-hotels.png new file mode 100644 index 0000000..7390f91 Binary files /dev/null and b/docs/final-audit/05-hotels.png differ diff --git a/docs/final-audit/06-contact.png b/docs/final-audit/06-contact.png new file mode 100644 index 0000000..566b112 Binary files /dev/null and b/docs/final-audit/06-contact.png differ diff --git a/docs/final-audit/07-route-detail.png b/docs/final-audit/07-route-detail.png new file mode 100644 index 0000000..d39784d Binary files /dev/null and b/docs/final-audit/07-route-detail.png differ diff --git a/docs/final-audit/08-experience-detail.png b/docs/final-audit/08-experience-detail.png new file mode 100644 index 0000000..2b14876 Binary files /dev/null and b/docs/final-audit/08-experience-detail.png differ diff --git a/docs/final-audit/09-contact-validation.png b/docs/final-audit/09-contact-validation.png new file mode 100644 index 0000000..a27ba37 Binary files /dev/null and b/docs/final-audit/09-contact-validation.png differ diff --git a/docs/final-audit/10-mobile-home.png b/docs/final-audit/10-mobile-home.png new file mode 100644 index 0000000..bc468fe Binary files /dev/null and b/docs/final-audit/10-mobile-home.png differ diff --git a/docs/final-audit/11-mobile-routes.png b/docs/final-audit/11-mobile-routes.png new file mode 100644 index 0000000..3cc75e8 Binary files /dev/null and b/docs/final-audit/11-mobile-routes.png differ diff --git a/docs/final-audit/12-mobile-experience.png b/docs/final-audit/12-mobile-experience.png new file mode 100644 index 0000000..a22eca4 Binary files /dev/null and b/docs/final-audit/12-mobile-experience.png differ diff --git a/docs/final-audit/13-mobile-contact.png b/docs/final-audit/13-mobile-contact.png new file mode 100644 index 0000000..5d2ffc6 Binary files /dev/null and b/docs/final-audit/13-mobile-contact.png differ diff --git a/docs/final-audit/14-menu.png b/docs/final-audit/14-menu.png new file mode 100644 index 0000000..cc86ece Binary files /dev/null and b/docs/final-audit/14-menu.png differ diff --git a/docs/final-audit/15-search.png b/docs/final-audit/15-search.png new file mode 100644 index 0000000..baa76d8 Binary files /dev/null and b/docs/final-audit/15-search.png differ diff --git a/docs/final-audit/验收报告.md b/docs/final-audit/验收报告.md new file mode 100644 index 0000000..5412916 --- /dev/null +++ b/docs/final-audit/验收报告.md @@ -0,0 +1,60 @@ +# WanderQ 官网全站最终验收报告 + +日期:2026-08-12 +环境:`http://127.0.0.1:5173/` +范围:首页、6 个主要栏目、线路/体验/美食/交通/酒店详情、咨询页、全屏菜单、站内搜索、中英文切换及移动端。 + +## 验收结论 + +当前版本已达到提交客户进行整站内容与视觉验收的条件。页面默认英文,中文切换正常;核心导航、详情页、咨询入口、图片加载与移动端布局均可用。生产构建、ESLint 与空白差异检查通过。 + +仍不建议直接对外正式发布:客户尚需补齐酒店实景与授权、供应商和人员资质、正式合作背书、真实评价、实时价格与取消条款。这些属于业务真实性和交易条件,不应由开发侧推测或代填。 + +## 验收步骤与健康度 + +1. 页面覆盖与路由:通过。遍历 40 个内部路径,均存在主内容、页脚与英文页面状态。 +2. 视觉一致性:通过。首页、线路、体验、交通、酒店、咨询及代表详情页保持统一的低饱和绿色、编辑式排版和图片语言。 +3. 响应式布局:通过。桌面端与 390px 手机端无页面水平溢出;修复了移动端线路标题与主视觉层叠问题。 +4. 导航与链接:通过。主导航、全屏菜单和详情锚点可用;酒店菜单已由旧版酒店更新为客户确认的 8 家候选。 +5. 搜索与语言:通过。站内搜索可按中英文标题筛选线路、体验、酒店、美食等内容;中英文切换后页面语言属性同步,验收结束已恢复英文。 +6. 咨询表单:通过。姓名、联系方式与体验类型等必填项使用浏览器原生校验;本次未发送真实咨询数据。 +7. 图片与媒体:通过。可见图片均正常加载;批量检查中未发现完成加载后 `naturalWidth = 0` 的实际裂图。离屏懒加载图片不计为错误。 +8. 可访问性基础项:通过。页面使用主内容区、标题层级、对话框语义和可聚焦控件;全屏菜单打开后焦点进入关闭按钮,菜单与搜索可关闭并恢复触发点焦点。 +9. 工程检查:通过。TypeScript/Vite 生产构建、ESLint、`git diff --check` 均成功。当前仅有主 JavaScript 包超过 500 kB 的非阻断构建警告,后续可按页面拆包优化首屏性能。 + +## 本轮修复 + +- 修复线路详情标签在窄桌面宽度下越界。 +- 修复手机端线路页标题被主视觉遮罩覆盖。 +- 修复交通页、酒店页重复一级标题。 +- 将全屏菜单酒店入口更新为客户新版 8 家候选,并连接到对应页面卡片。 +- 为全屏菜单补充打开焦点、关闭后焦点恢复和 Escape 键处理。 +- 将搜索框由静态输入升级为可筛选的站内内容搜索,并增加无结果提示。 +- 补充搜索对话框标题关联和搜索输入类型。 + +## 客户确认前必须补齐 + +1. 8 家特色酒店的真实原图、拍摄主体与商业使用授权,以及图片与酒店/房型的准确对应。 +2. 酒店正式中英文名、经营和预订主体、准确地址、现行营业状态、境外客人登记方式、消防与重点安全信息。 +3. 户外与文化体验的具名运营方、老师/向导/领队资质、装备、保险、应急与撤离方案、当前可约日期。 +4. 合作机构、媒体推荐、行业会员或政府合作的正式文件和 Logo 使用授权;未提供前不展示暗示性背书。 +5. 可公开的真实英文评价,以及照片、国籍、人数、线路、原始评价和授权之间的对应关系。 +6. 当前价格、房态或活动名额、报价包含/不含、税费、预付、支付、取消、退款和变更条款。 + +## 截图证据 + +- `01-home.png`:桌面首页 +- `02-routes.png`:线路栏目 +- `03-experiences.png`:体验栏目 +- `04-transport.png`:交通栏目 +- `05-hotels.png`:特色酒店 +- `06-contact.png`:咨询页 +- `07-route-detail.png`:线路详情 +- `08-experience-detail.png`:体验详情 +- `09-contact-validation.png`:咨询表单校验 +- `10-mobile-home.png`:手机端首页 +- `11-mobile-routes.png`:手机端线路 +- `12-mobile-experience.png`:手机端体验详情 +- `13-mobile-contact.png`:手机端咨询 +- `14-menu.png`:全屏菜单与新版酒店入口 +- `15-search.png`:站内搜索结果 diff --git a/docs/homepage-prototype-qa/implementation-hero.png b/docs/homepage-prototype-qa/implementation-hero.png new file mode 100644 index 0000000..8bb48ce Binary files /dev/null and b/docs/homepage-prototype-qa/implementation-hero.png differ diff --git a/docs/homepage-prototype-qa/implementation-mobile.png b/docs/homepage-prototype-qa/implementation-mobile.png new file mode 100644 index 0000000..5a73905 Binary files /dev/null and b/docs/homepage-prototype-qa/implementation-mobile.png differ diff --git a/docs/homepage-prototype-qa/implementation-story.png b/docs/homepage-prototype-qa/implementation-story.png new file mode 100644 index 0000000..bbaaad2 Binary files /dev/null and b/docs/homepage-prototype-qa/implementation-story.png differ diff --git a/docs/homepage-prototype-qa/implementation-themes.png b/docs/homepage-prototype-qa/implementation-themes.png new file mode 100644 index 0000000..280a705 Binary files /dev/null and b/docs/homepage-prototype-qa/implementation-themes.png differ diff --git a/docs/homepage-prototype-qa/source-direction.jpg b/docs/homepage-prototype-qa/source-direction.jpg new file mode 100644 index 0000000..9db136c Binary files /dev/null and b/docs/homepage-prototype-qa/source-direction.jpg differ diff --git a/docs/客户修改意见实施清单.md b/docs/客户修改意见实施清单.md new file mode 100644 index 0000000..76fdd83 --- /dev/null +++ b/docs/客户修改意见实施清单.md @@ -0,0 +1,201 @@ +# WanderQ 官网客户修改意见实施清单(V2) + +更新时间:2026-08-12 +需求来源:客户《飞书文档》32 页版本 +当前阶段:首页、三条线路、新奇体验栏目页、四项户外体验详情页、高桥专项核验页,以及洞穴餐叙、山野观景咖啡、原生村寨沉浸、节庆民俗盛典、喀斯特野奢星空露营、非遗与轻户外融合体验详情页、民族人文栏目页、“苗族蜡染・苗绣”“侗族草木染・木构建筑”“水族马尾绣”与“布依族古法技艺”详情页第一版已完成;下一步进入“民族美食文化”系列。底部第三方合作与资质标识仍等待正式文件和公开授权。 + +## 一、已经确认的执行原则 + +1. 客户文档中的中英文文案与原始图片是本轮开发的首要内容依据。 +2. 不一次性重做整站,按“顶部导航 → 首屏 → 首页下方模块 → 各栏目页 → 详情页”的顺序逐块确认、逐块开发。 +3. 企业团建与会务暂不加入官网。本轮相关文案、案例和服务范围全部排除,除非业务后续重新确认。 +4. 客户已明确的文案不擅自改写;仅修正明显的排版、标点、编号和中英文显示问题。 +5. “高级、欧美极简、低饱和、大量留白”等主观视觉要求,需要转换成统一的网页设计规范后再实施。 +6. 任何涉及资质、媒体推荐、协会会员、合作单位、保险、安全承诺和量化服务数据的内容,必须使用业务确认过的正式名称和可公开素材。 + +## 二、32 页文档内容归位 + +| 文档页码 | 主要内容 | 网站归属 | 本轮状态 | +|---|---|---|---| +| 1–2 | 三条代表线路的中英文独家亮点;户外、洞穴、峡谷、瀑布资源说明 | 首页线路卡片、线路列表、线路详情 | 纳入 | +| 3–4 上半 | 民族村寨、非遗、美食、峰林、瀑布、梯田、古镇基础内容 | 体验、风光、人文、目的地栏目 | 纳入,但后续优先使用更完整的第 9–25 页版本 | +| 4 下半 | 企业团建、会务、政企案例、团队拓展 | 企业业务栏目 | 本轮排除 | +| 5 | 首屏视觉、第三方背书、Logo、极简版式、实拍客片与评价 | 全站视觉与首页 | 纳入,优先级最高 | +| 6–7 | 24 小时在地服务、业务团队、品牌价值、团队照片、线路状态 | 首页品牌、团队与信任模块 | 纳入;部分信息待确认 | +| 8–9 上半 | 线路详情结构:地图、信息总览、亮点、日程、费用、酒店、安全、评价 | 线路详情页模板 | 纳入 | +| 9 下半–14 | Signature Guizhou Experiences;户外探险与在地新奇体验 | 新奇体验栏目及体验详情 | 纳入 | +| 15–17 | 民族人文、非遗工坊、村寨沉浸、节庆、美食文化 | 民族人文栏目及体验详情 | 纳入 | +| 18–19 | 世界级瀑布群与独特风光 | 风光地貌栏目及目的地详情 | 纳入 | +| 20–21 | 原生民族村寨、历史古镇古村 | 村寨与古镇栏目及目的地详情 | 纳入 | +| 22–24 | 青岩、娄山关实景沉浸历史体验 | 历史体验详情 | 纳入;团建适用标签不展示 | +| 25 | 在地市集与街头美食 | 探索美食栏目 | 纳入 | +| 26–30 上半 | 私家用车、接送、跨城接驳、特殊车型、服务标准与承诺 | 交通服务栏目 | 纳入;企业团用车表述需删减 | +| 30 下半–32 | 新增特色酒店及线路联动 | 精选酒店栏目 | 纳入;图片素材待补齐 | + +## 三、文档图片核对结论 + +文档内共嵌入 145 个图片文件,但其中同时包含官网截图、竞品参考截图、Logo 示例、旅行照片、活动照片、景点照片、餐饮照片和车辆照片。因此不能把 145 个文件全部直接作为网站正式图片。 + +### 可以作为正式素材候选 + +- 户外探洞、瀑降、溯溪、漂流、徒步与高桥体验照片。 +- 山野咖啡、洞穴晚宴、村寨与非遗体验照片。 +- 民族节庆、村寨、古镇、瀑布与风光照片。 +- 美食和车辆照片。 +- 两个 WanderQ Logo 文件,以箭头版新 Logo 为准。 +- 若业务确认人物授权,可评估文档中的团队合照和游客合照。 + +### 只能作为设计参考,不能直接当网站内容 + +- 当前 WanderQ 官网截图及红框标注图。 +- Visit Croatia、LPS Beijing 等第三方网站截图。 +- 客户用于说明版式、团队介绍、评价结构的示例页面。 + +### 文档中未找到完整正式素材 + +- 《国家地理》推荐的正式标识、报道链接或授权文件。 +- 贵州文旅官方合作地接标识。 +- 贵州户外探险协会会员单位标识或证书。 +- 本地非遗工坊合作证书。 +- 可直接发布的境外游客英文评价原文及评价人与照片对应关系。 +- 定制师个人照片、姓名/称呼、从业年限、语言能力和量化服务数据的完整对应表。 +- 第 30–32 页新增特色酒店对应的酒店原始照片。 + +## 四、首页从上到下的实施结构 + +| 序号 | 首页部分 | 客户要求 | 当前处理 | +|---|---|---|---| +| 01 | 顶部导航栏 | 文档未提出导航结构调整;只明确 Logo 错误 | 保留导航结构,使用新 Logo,检查移动端和链接 | +| 02 | 首屏主视觉 | 浅色、低饱和、高级质感、欧美极简、文字清晰 | 需要确定单图/轮播、最终主图、默认语言 | +| 03 | 顶部信任背书 | 增加媒体、官方合作与协会等第三方背书 | 正式素材与名称确认后实施,不使用假 Logo | +| 04 | 品牌主张与核心价值 | 打破旅游泡泡、真实在地生活、一人一行程、透明无套路 | 第一版已完成:客户原文核心意思、真实村寨人物图、五项编号价值说明 | +| 05 | 三大体验入口 | 户外探险、民族人文、独特风光 | 第一版已完成:洞穴桨板、非遗工坊、瀑布群实拍图与双语入口短文 | +| 06 | 三条代表线路 | 三条线路增加各 3 个独家亮点 | 第一版已完成:首页卡片已使用第 1–2 页的 9 个中英文亮点;详情页后续按第 8–9 页模板处理 | +| 07 | 团队与在地服务 | 团队照片、定制师信息、24 小时在地服务 | 第一版已完成:真实团队/旅行现场照片与三项服务说明;个人姓名、年限及量化数据待业务补充 | +| 08 | 定制流程 | 客户没有明确要求删除现有三步流程 | 已复核并保留三步流程,继续使用极简分栏呈现 | +| 09 | 真实客片与英文评价 | 国籍、人数、线路、英文短评和行程照片 | 真实现场影集第一版已完成;文档未提供照片与国籍、人数、线路、英文原评的一一对应关系,评价卡待补资料 | +| 10 | 出行方式对比 | 万趣私家定制 vs 大巴团 vs 国际 OTA | 第一版已完成:按 7 个相同维度进行克制对比,并标明不同运营商和产品存在差异 | +| 11 | 最终咨询行动区 | 引导用户开始定制 | 第一版已完成:单一主行动按钮,并提示日期人数、兴趣、节奏与住宿三类有效咨询信息 | +| 12 | 底部合作与资质 | 官方合作、协会会员、工坊证书 | 暂不上线;正式文件、Logo 原件与公开授权确认后实施 | +| 13 | 页脚 | 客户没有提出结构重做 | 已复核并保留精简结构,统一品牌名、服务定位、邮箱和法律链接 | + +## 五、栏目页与详情页实施矩阵 + +### 1. 线路栏目与线路详情 + +- 首页三条线路补充第 1–2 页提供的 9 个中英文独家亮点。 +- 线路栏目页已改为三条代表线路的大图编辑式结构:经典全景线、喀斯特户外探险线、均衡探索线;取消仅有三条主线时不必要的复杂筛选。 +- 列表卡片直接说明路线方向、节奏、适合人群和三项客户原文独家亮点,不展示缺少报价依据的宽泛价格区间。 +- 线路详情统一采用:动态地图、核心信息、Why This Trip、时间轴、每日行程、费用包含/不含、住宿参考、安全贴士、关联评价。 +- 经典全景线详情页第一版已完成:核心信息总览、3 项 Why This Trip、6 节点路线示意、8 天上午/下午/晚上拆分、车程参考、餐食住宿字段、费用包含/不含和 6 项安全实用贴士均已落地。 +- 经典全景线不展示未经核验的酒店涉外资质和不对应酒店的图片;真实酒店 2–3 档与关联客评继续等待正式资料。 +- 喀斯特户外探险线详情页第一版已完成:使用客户户外实拍素材,补齐 7 天逐日骨架、三级活动强度、四项 Go / No-go 安全门槛、恢复日、地面替代方案、费用边界和 6 项安全说明。 +- 户外线暂不发布未经核验的 370 米蹦极、260 米翼装、瀑降高度、CE 装备、向导证书、最低年龄等强承诺;具体运营方、人员、装备、通信、保险和撤离机制需进入正式方案。 +- 均衡探索线详情页第一版已完成:使用客户村寨、非遗、餐食和山野实拍素材,补齐 8 天逐日方案、慢住/跟随/轻探三层结构,以及“提前锁定/当天可调/可能替换”的弹性边界。 +- 均衡线对客户“每日完全灵活”做了可执行化处理:村寨住宿、主人、农民带路人、非遗老师与车辆需提前确认;开放时间和安全条件允许时再调整停留时长与轻量活动。 +- 当前文档同时出现“线路暂无”和“成熟定制线路合集”,需要确认“暂无”指暂无成品页、暂无价格,还是暂无可售线路。 + +### 2. 新奇体验与户外探险 + +- 使用 `Signature Guizhou Experiences` 作为栏目头部内容。 +- 分为 `Karst Outdoor Adventures` 和 `Unique Local Experiences` 两大系列。 +- 每项体验详情至少展示:英文名称、地理依托、核心体验、适合人群、难度/体力、安全保障、可加入的线路。 +- 370 米蹦极、260 米翼装飞行、CE 装备、持证向导等事实和安全表述需业务确认后上线。 +- 新奇体验栏目页第一版已完成:取消原有“类型 + 地点”的平铺筛选墙,改为两大系列的编辑式选择结构;户外系列公开 4 个方向,在地系列公开 5 个方向。 +- 栏目页已增加活动强度、执行条件、组合线路和公开边界;370 米蹦极、翼装体验及未核验的高度、世界纪录、证书、年龄承诺继续不进入公开可售清单。 +- “地下喀斯特探索”详情页第一版已完成:以客户第 10 页中清镇、安顺与绥阳洞穴区域为地理依托,建立轻量 / 活力 / 进阶候选三档分级、五步现场流程、四项 Go / No-go 门槛和三条可组合线路。 +- 洞穴详情页未将“全年 18°C”、“6 岁以上”、CE 装备、认证向导和进阶洞段写成全区域固定承诺;以公开边界模块明确标记为“按具体点位与运营方复核”。 +- “瀑降与峡谷绳索”详情页第一版已完成:提取并使用客户文档中 4 张瀑降实拍原图,建立初次体验候选 / 活力 / 进阶候选三档分级、五步执行流程、四项 Go / No-go 门槛和三条线路组合边界。 +- 瀑降详情页未将 20–86 米、36 米入门、86 米进阶、双绳保护、向导/教练资料、实时水量监测、救生装备和保险覆盖写成默认承诺;全部列为具体点位与运营方的复核项。 +- “峡谷溯溪与白水漂流”详情页第一版已完成:提取并使用客户文档中 5 张河流实拍原图,把平缓亲水 / 活力溯溪 / 白水候选拆分为三档独立决策,建立五步水上流程、四项 Go / No-go 门槛与三条线路组合。 +- 水上详情页未将“全年龄”、平缓段亲子、专业救生衣与护具、持证溯溪向导、全程水位/天气实时监测写成默认承诺;客户图片只证明平缓水域场景,不用于证明白水漂流已可售。 + +### 3. 民族人文、风光、村寨与古镇 + +- 以客户英文文案为主要内容,中文作为切换语言内容。 +- 非遗、村寨、节庆、瀑布、古镇采用统一卡片和详情结构,不把全部长文堆在列表页。 +- 节庆日期按文档保留“每年随农历变化”的提示。 +- 青岩和娄山关沉浸体验中的“企业团建”适配标签本轮删除。 + +### 4. 探索美食 + +- 使用客户提供的 `Local Market & Street Food Tour` 内容和 5 类代表小吃。 +- 首页/列表页使用短文案,详细英文介绍放美食详情页。 + +### 5. 交通服务 + +- 页面英文为主展示文案,中文随语言切换。 +- 服务结构调整为:全程私家用车、机场/高铁接送、跨城景观接驳、特殊场景车型、服务标准、服务承诺。 +- 企业团、研学团相关表述本轮删除或改为中性的小团/同业接待描述。 +- “车龄 5 年以内、司机 5 年以上经验、基础英文、免费安全座椅、足额营运保险、延误免费等候 60 分钟”等承诺需逐项确认。 + +### 6. 精选酒店 + +- 按野奢地貌、民族村寨、田园山野、城市/主题四类组织。 +- 使用文档中的中英文酒店介绍和线路联动建议。 +- 第 30–32 页没有嵌入对应酒店原始照片,不能用无关风景图替代。 +- “所有酒店均具备涉外资质”只能在每家酒店资质核验后展示。 + +## 六、第一批必须确认的问题(顶部部分) + +确认结果(2026-08-11): + +1. 首屏保留自动轮播,并提供分页选择与暂停/继续控制。 +2. 首屏图片由开发侧从客户文档素材中筛选和制作。 +3. 官网首次打开默认显示英文,用户仍可切换为中文。 +4. 不伪造《国家地理》推荐、政府合作或协会会员等第三方身份。正式文件补充前,顶部使用“贵州本地团队、纯玩私家小团、英文旅行支持、24/7 在地支持”四项真实服务信号;未来可替换或补充正式背书。 + +## 七、当前执行顺序 + +1. 顶部导航和新 Logo:已完成。 +2. 首屏主视觉与客户素材轮播:已完成第一版。 +3. 默认英文:已完成。 +4. 真实服务信任栏:已完成第一版。 +5. 桌面端、移动端、轮播暂停/继续和图片加载:已检查通过。 +6. 品牌主张与核心价值:已完成第一版;使用客户第 6–7 页内容与文档原图,桌面端、移动端及中英文排版已检查通过。 +7. 三大体验入口:已完成第一版;使用客户第 9–19 页中的实拍素材与核心表述,桌面端、移动端及中英文排版已检查通过。 +8. 三条代表线路:首页第一版已完成;使用客户第 1–2 页线路名称、9 个独家亮点和文档实拍图,移除过宽且误导性强的价格区间。 +9. 团队与在地服务:已完成不含虚构个人资料的第一版;使用客户文档团队照片,突出贵州本地规划、英文文化沟通和 24 小时在地协调。 +10. 定制流程:已复核,客户未要求删除,保留现有三步极简流程。 +11. 真实客片:已使用客户文档中的餐食、户外、洞穴、山野、非遗和瀑布照片完成第一版静态影集。 +12. 英文评价:文档仅提出结构要求,未提供可核验的“照片—国籍—人数—线路—英文原评”对应数据,暂不上线虚构评价。 +13. 出行方式对比:已完成第一版;覆盖行程设计、节奏、在地深度、语言、现场响应、价格方式和适合人群,不使用贬低同行的绝对化表述。 +14. 最终咨询行动区:已完成第一版;使用单一“开始规划”入口,并通过三项提示降低用户咨询门槛。 +15. 底部合作与资质:文档未提供可核验的正式标识与授权,继续保留为待补项,不使用文字或占位 Logo 暗示合作关系。 +16. 页脚:已完成复核;统一 WanderQ 品牌名、贵州私家定制旅行定位、联系邮箱与法律链接。 +17. 线路栏目页:已完成第一版;三条代表线路使用大图模块、决策信息和客户第 1–2 页的 9 项中英文亮点,复杂筛选与宽泛价格区间已移除。 +18. 经典全景线详情页:已完成第一版;使用客户实拍素材与 3 项独家亮点,补齐 8 天行程、路线示意、费用边界和实用贴士。 +19. 待补资料:经典线 2–3 档真实酒店原图、当前涉外接待状态、对应线路的真实英文评价。 +20. 喀斯特户外探险线详情页:已完成第一版;按客户第 10–14 页内容建立三级强度、四项出发门槛、7 天日程、费用边界和天气/水位替代方案。 +21. 户外线待补资料:具体合作运营方、领队/向导资料、装备清单、通信与撤离方案、专项保险说明,以及“8 年以上本地经验”证明。 +22. 均衡探索线详情页:已完成第一版;按客户第 1–2 页和第 14–18 页内容建立 8 天日程、三层体验结构、弹性边界、费用说明和 6 项实用提示。 +23. 均衡线待补资料:可接待境外客人的村寨住宿清单及真实图片、主人/农民带路人/非遗老师资料、体验价格和可预约档期。 +24. 新奇体验栏目页:已完成第一版;按客户第 9–15 页拆分为 `Karst Outdoor Adventures` 与 `Unique Local Experiences`,使用 4 + 5 个策展式体验方向、风险边界和三条组合线路入口。 +25. “地下喀斯特探索”详情页:已完成第一版;使用客户洞穴实拍图,补齐地理依托、三档强度、适合人群、五步流程、装备/天气/水位/退出门槛和可加入线路,并通过英文默认、移动端、裂图与控制台检查。 +26. “瀑降与峡谷绳索”详情页:已完成第一版;使用客户文档的 4 张实拍图,补齐地理依托、三档分级、适合人群、五步流程、水量/岩面/锚点/绳索/人员/保险/撤离门槛和可加入线路,并通过英文默认、移动端、裂图与控制台检查。 +27. “峡谷溯溪与白水漂流”详情页:已完成第一版;使用客户文档的 5 张实拍图,补齐地理依托、三档水上模式、适合人群、五步流程、水文/河段/运营方/人员/装备/保险/退出门槛和可加入线路,并通过英文默认、移动端、裂图与控制台检查。 +28. “喀斯特峰林徒步与骑行”详情页:已完成第一版;使用客户文档的 4 张峰林徒步/景观实拍图,拆分万峰林田园慢骑或步行、茂兰森林徒步与赤水河谷进阶骑行三种独立产品,补齐五步流程、路线/天气/人员/装备/保险/退出门槛,并把“两万座峰林、160 公里专用自行车道、全年龄段”转为具名产品核验项。 +29. “高桥极限挑战”专项核验页:已完成第一版;采用“桥梁观景候选 + 坝陵河大桥蹦极高风险候选 + ‘破茧之翼’项目性质待确认”的三层结构。公开使用坝陵河位置、客户 3 张现场图和 2019-01-12 认证的 370.25 米商业蹦极设施历史事实;不把“世界最高”、370 米实际自由落差、低空跳伞/滑翔伞日常开放或“普通人翼装飞行”写成当前可售承诺。第 4 张合成宣传海报未作为真实客片使用。 +30. “洞穴野奢体验・地下咖啡与烛光晚宴”详情页:已完成第一版;从客户 6 张素材中采用 3 张能支撑洞穴接待空间的图片,未使用大型探洞团图片及带企业品牌和大量可识别人物的活动记录。将洞穴咖啡茶点、私家晚宴、轻量探洞后落座拆为三个等级,补齐场地授权、洞穴保护、消防/电力/照明、餐饮卫生/过敏原/酒水、人员/接驳/保险/撤离门槛;“18℃恒温”、真实烛火、专属英文人员和完整配套只按具名场地与报价确认。 +31. “悬崖与山野云端咖啡”详情页:已完成第一版;从客户 6 张素材中采用 4 张能够支撑公开观景咖啡馆、带护栏平台和山林咖啡空间的图片,未使用悬挂座椅“侠客咖啡”及专业洞穴活动图片。将具名观景咖啡馆、顺路山林/梯田/村寨咖啡停靠、私家户外冲煮拆为三个等级,补齐场地身份/授权、步道/临崖/天气、饮品/热水/火源/卫生/垃圾、客人/接驳/应急门槛;“小众、无人拥挤、独享景观”和图片地理归属只按具体点位与日期确认。 +32. “原生村寨深度生活体验”详情页:已完成第一版;核对黄岗村、掌坳苗寨的官方地理口径与 6 张客户素材,采用 4 张村落、木楼生活空间和田野图片,未使用无人物身份/授权说明的持枪人物及近距离老人肖像。把公开村寨慢行、具名主人半日与一至两晚住宿候选拆为三档,补齐主人/社区同意、住宿/消防/境外客人登记、餐饮/农事/儿童、隐私/拍摄/收益门槛;“原生态、完整民俗、传承人一对一、全部收益直达村民”不作统一承诺。 +33. “喀斯特野奢星空露营”详情页:已完成第一版;客户本组 3 张图片实际为山地景观住宿/观景平台,没有帐篷、夜景或营地服务证据,因此仅作为舒适度和景观参考,并补充采用一张客户此前提供的喀斯特谷地普通帐篷图片说明营位类型。将具名经营型营地、固定山地住宿 + 夜观和私家临时搭建拆成三档,补齐用地/经营、森林防火/篝火、雷电/风雨/山洪、固定避难空间、卫浴饮水/食品、夜间值守、儿童临边、撤离与垃圾污水门槛;银河、日出、无人打扰、24 小时值守和卫浴保障均不作无条件承诺。 +34. “非遗 + 户外融合体验”详情页:已完成第一版;客户本组 3 张图片均为扎染打卡空间概念拼图,没有展示梯田徒步、植物观察、古法造纸、茶园骑行或制茶,因此仅采用 1 张并在页面明确标注“客户概念参考 / 非真实场地证明”,正式主视觉和主体画面采用客户此前提供的田野与手作素材。将梯田慢行 + 具名蜡染工坊、植物观察 + 石桥古法造纸、茶园步行/骑行 + 制茶拆为三组独立产品,补齐两端具名主体、路线/接驳/季节、老师/材料/作品、野生植物不默认采集、骑行道路/头盔/保障车、儿童工具/热蜡/染料与天气备选门槛。 +35. “民族人文”栏目与核心体验入口:已完成第一版;采用客户第 15–17 页英文引言的核心意图,但把 `17 indigenous ethnic groups` 调整为有官方依据、语义更准确的 `17 long-established ethnic minority groups`。页面以客户 4 张工坊/村寨实拍构建低饱和编辑式视觉,设置苗族蜡染・苗绣、侗族草木染・木构建筑、水族马尾绣、布依传统织染技艺四个入口,并明确具名老师、真实时长、拍摄同意、作品带走和收益路径等接待原则。客户图片主要证明蜡染/手作氛围,未冒充水族马尾绣或布依技艺实拍;村寨和节庆仅做下一层入口与边界提示。 +36. “苗族蜡染・苗绣”详情页:已完成第一版;采用客户本组 5 张蜡染/手作实拍中的 4 张,主视觉使用无人物的工坊空间,人物近景明确标注“客户提供实拍,身份与公开使用授权需归档”。将蜡染主题、苗绣主题和双主题半日拆为三种策划方式,补齐具名工坊与老师、单一真实课程目标、蜡材/染料/针具/热源、儿童与皮肤敏感、英文人员、拍摄同意、费用结算、作品完成/干燥/带走或后寄五步流程及四类确认门槛;“第三代传承人、天然蓝靛、全程英文、所有作品当天带走”均改为按工坊与日期核验。 +37. “侗族草木染・木构建筑”详情页:已完成第一版;客户素材能够提供村寨、木构和蓝染氛围,但不能证明具体侗布工坊、老师与建筑身份,因此所有图片均标注具体地点/工坊待确认,其中蓝染图明确写明“非侗布工坊或课程证明”。将具名侗布染制工坊、鼓楼/风雨桥公共建筑观察、工坊 + 木构村寨完整一日拆为三种方式,补齐具名侗寨、工坊与建筑讲解人、染料/亮布整理材料、具体建筑结构来源、公共空间与社区活动优先、居民拍摄、木桥湿滑/临水临边、真实距离、英文支持和费用结算五步流程及四类确认门槛;不把公开参观包装成传承人私享课,也不把“纯天然草木染”“所有建筑不用一钉一铁”作统一承诺。 +38. “水族马尾绣”详情页:已完成第一版;客户提供的两张本组图片只能证明通用手作桌面、线材和操作氛围,不能证明三都水族马尾绣工坊、老师、材料或针法,因此页面主视觉、影集和流程图均主动标注“非马尾绣工序证明”,未把普通刺绣、蜡染或通用手作冒充马尾绣实拍。依据文化和旅游部、国家民委及贵州公开资料,将体验拆为 45–75 分钟工艺演示、2–3 小时马尾芯缠线与基础小样、4–5 小时或多次深度学习三种方式,补齐三都具名工坊与老师、官方传承人称号依据、马尾来源/清洁/预处理、丝线缠裹与盘绣关系、针具与儿童、拍摄同意、英文支持、费用结算、作品完成/老师收尾/后寄五步流程及四类确认门槛;复杂背带/服饰作品不作短课完成承诺,“刺绣活化石”只作为有来源的文化称谓。 +39. “布依族古法技艺”详情页:已完成第一版;客户原文提出“古法织造 + 葫芦彩绘 + 600 年石头寨”,但现有图片组只能证明通用贵州蜡染/手作氛围,无法证明布依工坊、石头寨、织机、葫芦彩绘或授课老师,因此页面所有图片均明确标注“非布依工坊或具体课程证明”。公开资料可以支持安顺石头寨有 600 多年历史及当地蜡染传统,也能支持黔西南布依纺织包含绞纱、上浆、排线、织布与服饰制作等复杂工序,但未找到“石头寨葫芦彩绘”对应的地方与传承依据;因此将体验拆为安顺石头寨具名蜡染、黔西南具名织造/服饰、葫芦彩绘待核验候选三种方向,补齐地区与技艺分开归属、具名工坊/老师/课程、单一可完成工序、蜡材/染液/织机/颜料或热针、儿童与工具安全、拍摄同意、英文支持、作品完成/干燥/后寄和费用结算五步流程及四类确认门槛;不把不同地区、材料和老师拼成“一站式布依古法课”。 +40. “原生村寨沉浸”系列归位:已完成;将原详情页从笼统的“公开慢行 / 主人半日 / 住宿候选”升级为客户明确提出的苗族吊脚楼住家、侗族鼓楼社区生活与布依石头寨慢居三条方向,并保留半日、一晚、最长 3 天 2 晚的逐级核验逻辑。掌坳、黄岗、安顺石头寨/高荡仅作地理候选,公开资料只能证明文化与建筑背景,不能证明当前可售房间、主人或涉外接待能力;页面因此补齐具名经营主体、真实房型与卫浴、现行卫生与消防、实名登记、境外客人入住非旅馆住所后 24 小时内登记、楼梯/火源/夜路、餐食过敏、家庭与居民拍摄、英文支持、费用与收益路径等门槛。侗族大歌、农事、家宴、火塘故事和手作均按社区、主人、季节与日期确认,不承诺每日发生;4 张客户图片新增逐图用途说明,不再暗示具体村寨、客房或住宿主体。 +41. “节庆民俗盛典”系列:已完成第一版;新增独立详情页并在民族人文栏目增加入口,保留客户提出的苗年、苗族姊妹节、侗族大歌节/黄岗喊天节、水族端节四项方向,但将传统历法日期、当年政府公共活动与村寨/家庭内部仪式拆开表达。页面写明 2026 年黔东南苗年州级假日为 11 月 15 日、2026 年台江姊妹节公共系列活动为 4 月 30 日至 5 月 10 日且现已结束;黄岗喊天节按农历六月十五核验,从江侗族大歌节历史上曾在 11 月 28 日前后举办但不作每年固定公历日期承诺;水族端节“49 天”解释为不同氏族/村寨批次的整体跨度,而非同一地点连续 49 天游客活动。补齐当年主办与通知、公开活动/仪式边界、交通管制/拥挤/烟火/斗牛/赛马、摄影与未成年人、无人机、具名住宿/涨价/预付退款、天气和取消替代等门槛。采用客户 4 张苗族节庆图片并逐图标注地点、年份、人物和公开授权待归档,明确不得用于证明侗族喊天节或水族端节。 +42. “民族美食文化”系列:已完成第一版;新增独立详情页并在民族人文栏目增加入口,保留客户提出的民族家宴烹饪课、原生长桌宴、在地市集风味探访三项方向,但将家庭厨房、私房餐叙、持证餐厅、节庆/社区宴席、景区商业表演与公共市场拆开表达。家宴课按具名主人、当天营业市场、实际厨房/场地与一至两道真实菜单确认;酸汤鱼、糯米饭和双语食谱只作候选,不承诺每位主人均可提供。长桌宴须核对具名组织者、村寨或场地、最低人数、菜单、座席、天气备选、敬酒/酒歌、无酒精替代、交通、洗手间、价格与取消,不把商业场次写成家庭宴,也不承诺临时到村即开席。市集探访按具名市场、真实营业时段与 3–5 份小份试吃设计,丝娃娃、牛肉粉、烤香猪仅作地区性线索,不承诺每个乡村早市都有。依据《中华人民共和国食品安全法》、市场监管总局《餐饮服务食品安全操作规范》和现行《贵州省食品安全条例》,补齐接待主体、原料采购/保存、生熟分离、饮水、清洁消毒、熟度、严重过敏原与交叉接触、辣度、素食/清真需求、刀具/热源、未成年人看护、酒精与驾车、紧急联系、拍摄同意、费用、最低人数和取消退款等门槛;无法安全满足时默认替换为具名餐厅地方风味分享餐。客户本组 90–94 为带品牌字样的商业宣传海报,未采用;采用 95–98 四张真实菜品/餐桌图片,并逐图标注菜名、餐厅、制作人、组织者、地点、年份、商业属性及使用授权待归档。 +43. “世界级瀑布群”系列:已完成第一版;新增独立详情页并在新奇体验栏目新增 `Iconic Landscapes` 景观系列入口,将黄果树成熟景区、滴水滩山谷多级瀑布候选和赤水丹霞瀑布拆成三套独立游览逻辑。黄果树依据公开资料保留大瀑布高 77.8 米、宽 101 米,以及陡坡塘—大瀑布—天星桥完整一日的分区概念,但入园时段、实名预约、观光车、水帘洞/步道开放、客流与真实步行按日期确认;不以“亚洲最大”、固定丰水、固定彩虹或水帘洞必开作承诺。滴水滩依据贵州公开资料保留七级、约 410 米总落差,但明确其不是黄果树核心景区同一套成熟动线,入口、道路、停车、步道、观景台、洗手间、通信和当前开放必须单独核验;不使用“隐秘、无人、无遮挡、秘境直达”话术。赤水明确为世界自然遗产“中国丹霞”六个组成地之一,核心以赤水丹霞旅游区・大瀑布(原十丈洞,公开资料约高 76 米、宽 80 米)规划,四洞沟等其他景区按独立门票、接驳和时间确认,不写成“几十处瀑布一次看完”,也不与安顺黄果树做同日往返。补齐官方开放/票务/入口、上游降雨/涨水/雷电/山洪/落石/水雾湿滑、总步行/台阶/排队/电梯、儿童与长辈、轮椅或行动不便替代、接驳车安全带、摄影/三脚架/无人机、警戒线和关闭替代等门槛;2026 年价格只采用省发改委政府定价清单作规划参考,最终以当日官方渠道为准。客户本组 100–108 共 9 张瀑布图片,采用 100、102、105、108 四张无近距离人物且能支持主瀑、水幕、多级瀑布与丹霞地貌的画面;103、106、107 因人物靠近水边/非标准边缘可能暗示越界机位,104 因具名住宿和具体瀑布关系不清,均未采用;109 已进入下一章旧版页面截图。所有采用图片逐图标注具体瀑布、日期、机位与公开授权待归档,不把不同地点或不同水期混作同一处。 +44. “原生民族村寨”系列:已完成第一版;新增独立详情页,并将其与原“原生村寨沉浸”页分工为“比较去哪座代表村寨”与“说明半日/过夜怎样被接待”两层。页面保留西江、肇兴/黄岗、岜沙、安顺布依石寨四组方向,但取消“越不商业越原生态”的真实性排名:西江明确为服务成熟、客流与商业经营集中的景区型村寨,依据公开资料采用 1,300 余栋传统木质吊脚楼表述,预约、观光车、行李、台阶、木构消防、住宿和境外客人登记按日期确认,不承诺“私家避人机位”;肇兴保留仁义礼智信五座鼓楼,黄岗作为黎平县双江镇另一处具名社区单独说明,喊天节按农历六月十五与当年通知核验,侗族大歌按节庆、集体交往和接待语境确认,不写成每天随到随唱;岜沙使用正式村名,不以“枪手部落”为产品标题,将镰刀剃头、迎宾和火枪元素明确为可能进入景区演艺的表达,只按当日公开节目与官方现场管理,不保证鸣枪、剃头或成人礼,更不安排游客接触/操作枪械与镰刀;布依方向明确扁担山石头寨与高荡是两个不同村寨,不把两地历史、建筑与课程合并为“600 年全石头村”,家访、织染和慢行须落实具体村名、主人/老师、材料、费用与同意。补齐村寨/景区/主人身份、开放与收费边界、门票取消、交通接驳、真实房型/卫浴/消防/行李/住宿登记、节目与工具安全、儿童长者、住宅/仪式/成人/未成年人肖像/商业拍摄/无人机及图片版权等门槛;居民日常、农事、家宴、歌唱和人生礼俗均不作自动开放承诺。客户本组 image110–119 共 10 张图片采用 110、116、118、119 四张,用于西江暮色/全景和侗寨山谷/鼓楼氛围;其中侗寨两图的具体村名仍待确认,未拿西江或侗寨图片冒充岜沙、布依石头寨实拍。image120 起已核准进入下一章“历史古镇古村”。默认英文、中文切换、桌面与移动端、图片加载、控制台和水平溢出已完成检查。 +45. “历史古镇古村”系列:已完成第一版;新增独立详情页并纳入体验归档,将青岩古镇、镇远古城与天龙屯堡拆成明代石城、㵲阳河商贸古城和安顺屯堡村落三条不同历史线索,不用“古色古香”或江南水乡模板笼统合并。青岩依据贵州公开资料采用明洪武年间设堡、六百余年历史积累的口径,具体从石城墙、定广门、石牌坊、宗教建筑、街巷与小吃进入;公共古镇空间与收费景点/套票分开,2026 年政府定价清单所列景区门票和套票不直接硬编码为永久价格,正式报价须写明包含景点。镇远依据公开资料采用公元前 202 年置县、2,200 余年建置史及明清㵲阳河—湘黔滇驿道商贸节点表述,明确古城仍是居民与商业并存的县城;青龙洞、石屏山、下㵲阳河游船等为不同收费项目,灯光、游船、沿河噪声、水位/暴雨和住宿位置按日期确认。天龙明确为安顺屯堡文化的一处具名村落,明代军屯、后续移民屯田与各民族交往共同塑造其语言、服饰、石木建筑、饮食和地戏传统,不写成“六百年从未改变”的静态遗址;安顺地戏 2006 年列入首批国家级非遗,景区演武堂每天定时的约 10 分钟折子戏与春节、农历七月等传统节令中的村寨地戏分开说明,均不承诺随到随演。补齐建置史与具体遗存来源、公共空间/单点门票/联票、预约与开放、维修、演出、游船/接驳、停车、城墙/山体台阶、湿滑石板、临河和夜路、老人儿童、文物保护、宗教/仪式/住宅/地戏后台、成人与未成年人肖像、商业拍摄、三脚架、无人机及图片版权等门槛。客户本组 image120–124 共 5 张图片采用 120–123 四张,其中 120–121 仅标注为客户文档对应青岩的古镇/石城墙氛围,具体建筑和墙段待确认;122–123 对应镇远夜景并明确不保证同等亮灯、游船和客流;124 与 123 内容重复未采用。客户没有提供可明确证明天龙屯堡的图片,未拿青岩或镇远照片冒充天龙。image125 起已核准进入下一章“实景沉浸式历史演绎”。 +46. “青岩战斗・古镇实景沉浸演绎”专项页:已完成第一版;核验过程发现客户构想已经出现可对应的现有产品,但两者不能直接画等号。中国国防报 2026 年 5 月报道确认花溪区文旅部门与景区运营方以“青岩狮子山阻击战”为素材打造《战青岩》,用实景实战与角色扮演开展国防教育;贵阳网 2026 年 7 月进一步确认项目由贵阳青岩古镇景区管理有限公司与贵州创新文化旅游开发有限公司于 2026 年 1 月合力打造,4 月 29 日起全面运营,落地定广湖畔三处实景战场区域,截至 7 月 5 日累计接待超过 3 万人次,公开角色包括侦察兵、医务员、担架手、机枪手、士兵和物资小队。页面因此将状态修正为“已有运营产品 / 境外私家接待待专项确认”,不再写成纯概念。与此同时,客户文档所写的石巷、城墙、历史院落、线索追踪、情报传递、真人 NPC、剧情解锁、城墙声光电战斗和史实复盘,尚无证据证明全部属于当前《战青岩》的既有玩法,故作为二期共创方向单独标注。历史层面依据贵州省政协公开资料与贵阳市文旅规划,保留 1935 年 4 月青岩一带战斗及红军狮子山战斗遗址线索,但要求具名历史顾问将狮子山/簸箕山等史迹与现今动线逐点对应,不把整座古镇或所有院落称为原战场。补齐两家运营主体、当前场次/容量/散客或包团/价格/取消、私家与境外客接待、英文主持或翻译、真实时长、年龄/体能/健康与陪同、服装尺码清洁、仿真道具、爆破烟雾、消防急救通讯、雨天泥泞与障碍退出、医疗转运、保险、肖像同意、文物及公共空间许可、史实与戏剧化区分等门槛;客户提出的 3–4 小时、12+、全程英文、专业演员、私家包场、固定场次和城墙声光电均不作无条件承诺。后续检查客户插图锚点发现,紧跟娄山关标题的 image126 画面文字明确写着“战青岩”,实际属于青岩而非娄山关,已将其补入青岩页主视觉、图库和流程画面,同时保留人物同意、拍摄日期与商业使用授权待归档;未把娄山关 image125 冒充青岩。页面提供现有《战青岩》实景体验、青岩史迹步行和二期古镇任务剧本三种强度,允许不接受战争模拟的客人退出角色扮演并改为历史步行。 +47. “娄山关战役・山道沉浸行军演绎”专项页:已完成第一版;公开资料能够确认娄山关红军战斗遗址、《娄山关大捷》实景演出及游客参与型产品,但客户所写的完整山道移动剧场尚不能视为现有产品。依据党史资料采用 1935 年 2 月 25 日至 26 日主要战斗、遵义会议后首次大捷的口径,并将战斗遗址陈列馆、关口、大尖山/小尖山/点金山等公开遗址线索与演艺景区分开;纠正客户“登顶品读娄山关摩崖石刻”的表述,今天大尖山一侧展示的是后建的大理石《忆秦娥・娄山关》手迹碑,不写成 1935 年原始摩崖石刻。人民日报、人民网与贵州媒体可以证明《娄山关大捷》实景演出长期存在,2026 年“五一”由原演出升级推出“跟着团长攻打娄山关”游客参与项目,公开报道出现游客换装、跟随百余名演员进入战壕/密林及 13:30 场次;但场次、票价和每日运营均不直接固化,仍以出行日期运营方书面信息为准。客户要求的沿古驿道连续 4–5 小时、每个历史地标分段演员演绎、登顶剧情高潮、全员年代服装道具、完整英文剧本、专业英文历史向导、门票与演员协调全部包含,未找到对应的现行路线、供应商和英文接待证据,因此作为二期共创方案。页面将产品拆为现有参与型演出、战斗遗址与诗词半日线、二期 4–5 小时移动山道剧场三档,补齐具名运营主体、景区/演出/互动票种、容量场次、私家团、取消、演艺场与遗址地图、路线距离/爬升/坡度/路面/补水/退出点实测、历史审校、服装与仿真道具、火焰/烟雾/强声/风向/消防隔离/急停、山地高温暴雨雷电大雾湿滑落石森林火险、年龄与心肺/骨关节/听力/呼吸/孕期/创伤敏感、知情自愿、前后领队、通信急救医疗转运和保险等门槛。客户同组 image125–126 实际在同一图片段落中:image125 文字明确标注“攻打娄山关”,可用于支持演员、制服、烟雾与大型火焰的演出氛围,但拍摄日期、场次、制作方、人物同意和商业使用授权待归档;image126 明确标注“战青岩”,已移回青岩页,未拿来证明娄山关。企业团建适配标签继续按客户已确认边界删除。 +48. “在地市集・街头风味巡礼”内容归位:已完成;客户本段与已完成的“民族美食文化”详情页属于同一产品体系,因此未重复新建同质页面,而是在原页面新增独立 `Local Market / Street-Food Tour` 板块,完整保留客户指定的花溪牛肉粉、贵阳肠旺面、贵阳丝娃娃、青岩豆腐与玫瑰冰粉、水城烙锅五类内容。依据贵州“黔菜师傅”菜品遴选、传统优势食品产业方案及地方公开资料,明确花溪牛肉粉、肠旺面和丝娃娃属于贵阳核心早餐/小吃;肠旺面英文说明直译猪大肠、血旺、脆哨与红油,不用模糊“特色肉面”规避食材;丝娃娃不固定承诺十种以上蔬菜,并提示小麦、花生、芝麻、酱油、折耳根和辣椒;青岩风味炒豆腐与贵阳玫瑰冰粉分别有公开名录依据,但未证明是固定传统套餐,因此只作为一咸一甜的路线组合,分别核验豆制品、油炸交叉接触、冰品用水和糖量;水城烙锅明确归属六盘水/水城,更适合作为共享正餐,不为了凑齐“贵阳五种小吃”跨城搬运或算作一口式试吃。客户提出的英文向导带领 5–6 种小吃试吃,调整为通常 3–5 份,只有步行距离、客人食量与实际摊位都合适时扩展至 5–6 份;不承诺在同一市场一次吃齐五类。贵阳城市版拆为早餐线与夜市线二选一,青云市集可作为成熟夜间商业街区候选,但依据贵州省政府公开资料明确它是餐饮、文创与娱乐混合的现代综合市集,不包装成传统农贸早市;具名门店、真实营业、拥挤度、摊位身份、熟制状态、生熟分离、饮水、严重过敏原、辣度、支付、座位、洗手间和天气按日期确认。页面继续沿用客户已提供的真实菜品/餐桌素材,因为本段没有新增图片;桌面、移动、中英文、图片和构建完成验证。 +49. “用车服务”栏目页:已完成第一版;完整保留客户提出的全程私家团专属用车、机场/高铁专车接送、跨城景观专线和特殊场景车型四类结构,并采用客户文档中 5 座、7 座与 9 座车型组的 4 张车辆实图替换原来的无关风景图。页面将额定座位与建议客人数/行李容量分开,补齐日期与路线、成人/儿童、行李/设备、车型匹配、承运资质与保险、司机工时、报价/等候/变更条款、出发前车辆与接站信息五步确认流程;英文支持明确由英文司导、随车讲解或远程旅行顾问按订单确认,不假设每位司机会英语。依据现行旅游经营与道路客运要求加入具名合格供应商、营运资质、车辆技术状态、承运人责任险、司机连续驾驶与休息、山区夜间道路、座位/安全带和儿童座椅等门槛。“全部车龄 5 年以内、司机均有 5 年山路经验且能基础英文、免费儿童座椅、足额营运保险、延误免费等候 60 分钟、同一司机全程、所有合理改线不加价、无隐藏费用/全包价”等改为报价和派车确认单逐项锁定,不作无条件官网承诺;企业团建、会议与研学标签已删除或改为亲友/户外/同业小团。默认英文、中文切换、桌面与移动端、4 类服务、8 类车型、5 项服务标准、图片加载、水平溢出及构建均完成检查。 +50. “新增特色酒店”栏目页:已完成第一版;按野奢地貌、民族村寨、田园山野、城市/主题四类整理客户列出的 8 家住宿候选,并补回客户原文缺失的“民族风情村寨美宿”分区标题。页面完整纳入兴义峰兮・洞穴民宿、朱砂古镇・放下云居、雷公山树蛙部落、榕江加宜梯田民宿、台江拾仓来隐、云屯・星空野奢树屋、枕宿・枕山河和兴义蘑菇野奢酒店,逐家提供位置线索、路线适配、客户卖点的安全改写和预订前核验项;新增万峰林/万峰湖、雷公山/榕江/台江、铜仁/梵净山、六盘水四组线路联动,以及经营与预订主体、真实房型与行李动线、境外客人住宿登记、消防/临崖/木构/泳池与儿童设施、活动运营与保险、价格/预付/取消/备选六项统一确认流程。客户文档没有嵌入这 8 家酒店的对应原图,因此新版主动删除旧式无关风景图卡片,采用清晰标注“客户推荐・待确认”的无图编辑式候选卡,不冒充酒店实景。峰兮可由贵州公开报道和正规预订平台支持品牌与万峰湖方向,但具体悬崖高度、房量、房型、泳池和餐饮仍按日期确认;放下云居可由铜仁公开资料支持其与朱砂古镇的关系;加宜村梯田和月亮山地理可由榕江县政府资料支持。树蛙部落具体地址及是否位于保护区、其他酒店正式中英文名和当前运营状态均继续作为待核验项。依据国家移民管理局 2026 年外国人旅馆以外住宿网上登记政策、贵州民宿高质量发展指导意见及现行贵州民宿消防标准,在页面加入住宿登记、治安、消防、食品、卫生、防灾、房屋与隐私门槛;不再把设计奖项、500 米悬崖、15 栋/12 间、270°视野、保护区位置、萤火虫、智能设施、泳池、儿童乐园或具体文化/户外活动写成统一保证。默认英文、中文切换、桌面与移动端、4 个分区、8 家候选、6 项核验、图片加载、水平溢出及生产构建均完成检查。 +51. 全站最终验收:已完成;遍历首页、栏目页、线路/体验/美食/交通/酒店详情和咨询页共 40 个内部路径,并抽查桌面与 390px 手机断点。修复移动端线路标题被主视觉遮挡、线路详情标签横向溢出、交通与酒店页重复 H1、全屏菜单酒店入口仍指向旧酒店、菜单键盘焦点/关闭,以及搜索框不产生筛选结果等问题。确认默认英文、中英文切换、咨询表单必填校验、内部图片、锚点、主导航、全屏菜单与搜索交互正常;生产构建、ESLint 和 `git diff --check` 通过。完整结果与截图见 `docs/final-audit/验收报告.md`。 +52. 下一步:进入客户验收与真实资料补齐。优先确认 8 家酒店原图及商业使用授权、每家经营/预订主体与境外客人登记方式、户外和文化体验的具名供应商/老师/向导/保险与应急资料、正式合作或资质文件、可公开的英文真实评价,以及最终价格、包含/不含、预付、取消和变更条款;客户确认后再做发布前内容定版与部署。 diff --git a/docs/首页版本管理说明.md b/docs/首页版本管理说明.md new file mode 100644 index 0000000..fd72741 --- /dev/null +++ b/docs/首页版本管理说明.md @@ -0,0 +1,24 @@ +# WanderQ 首页版本管理 + +从 2026-08-12 起,首页设计采用“只新增、不覆盖”的版本管理方式。 + +## 固定预览入口 + +| 版本 | 用途 | 本地地址 | +| --- | --- | --- | +| Classic | 客户意见实施完成后的原始首页,作为永久基准版 | `/preview/classic/` | +| V1 | 参考业务矩阵与三屏叙事完成的高级版 | `/preview/v1/` | +| Current | 当前准备继续开发或展示的版本 | `/` | + +## 规则 + +1. `Classic` 只用于回看和对比,不在其上直接开发。 +2. 新方向依次增加为 `V2`、`V3`、`V4`,旧版本不得改名或覆盖。 +3. 根地址 `/` 可以指向当前推荐方案,但所有历史版本继续保留独立入口。 +4. 每个确认版本都同时创建 Git 提交和版本标签,确保代码可以完整恢复。 +5. 给客户展示前,先内部确认要展示的版本入口,避免把实验中的页面误当成最终版。 + +## 当前代码存档 + +- 工作分支:`codex/homepage-version-lab` +- 基准与 V1 的固定标签会在首次版本存档提交后创建。 diff --git a/docs/首页结构确认表.md b/docs/首页结构确认表.md new file mode 100644 index 0000000..041c1b2 --- /dev/null +++ b/docs/首页结构确认表.md @@ -0,0 +1,105 @@ +# WanderQ 官网首页结构确认表(第一版,已由 V2 实施清单取代) + +> 本文件保留为早期讨论记录。客户已确认文档内含大量中英文内容和高清图片,且企业团建会务暂不纳入官网;后续以 `docs/客户修改意见实施清单.md` 为准。 + +更新时间:2026-08-11 +依据:当前首页代码、客户《飞书文档》32 页修改意见 +当前阶段:只确认结构、内容职责与素材需求,暂不继续做视觉和前端细节修改。 + +## 一、先给结论 + +1. 首页应继续聚焦“面向境外游客的贵州高端私家定制旅行”,不把所有线路、景点、团建、酒店和交通详情都堆进首页。 +2. 客户文档中的“团建与会务”属于另一类 B2B 用户需求,建议单独做栏目或落地页,不与境外私家旅行首页混排。 +3. 首页建议由 13 个部分组成,其中真正的主体内容模块为 9 个;当前首页的多数内容可以重组,不需要全部推倒重写。 +4. 首屏建议最终只使用 1 张具有代表性的主图,而不是继续保留 4 张质量与风格不统一的轮播图片。其余优质图片分配给下方具体板块。 +5. 第三方背书、合作标识、客人评价和服务数据必须有真实证明、原始素材与使用授权,不能仅按文案制作。 + +## 二、建议的首页结构(从上到下) + +| 序号 | 板块 | 核心作用 | 建议展示内容 | 素材要求 | 当前处理建议 | 需要业务确认 | +|---|---|---|---|---|---|---| +| 01 | 顶部导航栏 | 帮助用户快速理解网站提供什么,并进入线路、体验、酒店、交通和品牌页面 | Logo、中英文切换、6 个一级导航、菜单、搜索、开始规划 | 已有 Logo;无需新增大图 | 保留现有结构,后续只检查导航名称和链接 | 一级导航名称是否最终确定;英文是否为默认语言 | +| 02 | 首屏主视觉 | 在 5 秒内说清“谁、在哪里、提供什么服务” | 贵州私家定制定位、主标题、1 句价值说明、开始规划按钮 | 推荐 1 张横向主图;真实贵州场景;低饱和;画面干净;可用于桌面和手机裁切 | 当前方案作为结构参考,最终主图待重新选择;建议取消 4 图轮播 | 单图还是轮播;最终主标题;客户能否提供可商用主图 | +| 03 | 顶部信任背书栏 | 立即降低境外游客对资质、可靠性和当地执行能力的顾虑 | 3–4 项真实背书:正规入境接待、本地运营、协会会员、官方合作或真实媒体推荐 | 官方 Logo 矢量文件、证书扫描件、授权说明 | 新增;用简洁单色标识,不做花哨卡片 | 《国家地理》推荐是否真实且可证明;贵州文旅、户外协会、非遗工坊合作是否有授权 | +| 04 | 品牌主张与核心价值 | 解释万趣为什么不同,以及用户为什么愿意为私家定制支付溢价 | “打破旅游泡泡”“不赶场”“接触真实贵州”;精选 4–5 个核心价值 | 1 张有真实人物和贵州环境的品牌照片,或简洁文字版式 | 合并当前品牌引语和部分“为什么选择万趣”内容 | 最终品牌主张;是否以“创造改变生命的旅行体验”为核心表达 | +| 05 | 三大贵州体验方向 | 让用户快速选择自己感兴趣的旅行类型 | 户外探险、民族人文、自然风光;每类只保留标题、短说明和入口 | 3 张同一摄影风格的代表图;建议竖图或统一比例 | 保留当前三类体验,但减少文字、统一图片风格 | 三大分类是否最终确定;“极限户外”是否要改成更适合境外高端客群的称呼 | +| 06 | 三条代表性定制线路 | 把抽象体验转化为可以咨询的旅行方案 | 经典全景线、喀斯特户外探险线、均衡探索线;天数、适合人群、3 个独家亮点、查看详情 | 每条线路 1 张主图;路线地图后续放详情页,不放首页 | 保留现有 3 张线路卡片,但按客户文档补充独家亮点,暂不展示未经确认的价格 | 三条线路是否已成熟;名称、天数、价格区间、独家资源是否真实可交付 | +| 07 | 团队与在地服务能力 | 用真人和量化信息建立信任,证明不是匿名旅行产品网站 | 团队合照、2–4 位定制师/司导简介、语言、年限、服务国家数、24 小时在地支持 | 必须提供真实团队合照与职业/户外实拍;不能用图库人物 | 重做当前“为什么选择万趣”板块,把抽象优势变成人物和真实能力 | 团队成员是否愿意公开;可公开的姓名、照片、语言能力和服务数据 | +| 08 | 定制流程 | 告诉用户咨询之后会发生什么,降低联系门槛 | 01 提交需求、02 设计方案、03 确认与出发;可补充响应时间 | 不需要大图,可用极简步骤排版 | 将当前紧贴首屏的三步骤移动到团队之后、评价之前 | 是否承诺具体响应时间,例如 24 小时内回复 | +| 09 | 真实客片与英文评价 | 证明真实服务过境外游客,并展示实际旅行氛围 | 3 个精选案例;游客国籍、人数、线路、英文短评、旅行照片 | 每个案例至少 2–4 张真实照片和评价授权;需要原图 | 升级当前“贵州现场影集”,不再只是无说明的图片轮播 | 是否已有境外游客评价、照片和公开授权;是否可展示国籍与人数 | +| 10 | 万趣与其他出行方式对比 | 帮助用户理解私家定制的价值,而不是只比较价格 | 万趣私家定制 vs 普通大巴团 vs 国际 OTA 套餐;比较灵活性、语言、在地深度、价格透明、安全支持 | 不需要照片;使用克制的对比表或对比列表 | 新增,但保持简短,避免攻击性表达 | 对比维度和事实是否可验证;是否需要法务或业务审核 | +| 11 | 最终咨询行动区 | 在用户理解价值和看到证据后推动咨询 | 一句总结、开始规划按钮、预计沟通内容:日期、人数、预算、偏好 | 可使用纯色背景,或 1 张极低干扰的横图 | 新增明确的页面收口,不让首页以图片轮播突然结束 | 联系方式、表单字段、响应时效是否确定 | +| 12 | 底部合作与资质标识 | 在页面结束前再次强化可信度 | 贵州文旅合作、协会会员、非遗工坊合作、保险或接待资质等 | 官方原始 Logo、证书缩略图、授权证明 | 新增;只放确认过的真实标识 | 哪些标识可以公开;名称和 Logo 使用规范 | +| 13 | 页脚 | 提供基础公司、联系、法律和社交信息 | 公司名称、邮箱、社交媒体、服务协议、隐私政策、备案信息 | 已有 Logo 和社交图标 | 保留并补齐真实公司与法律信息;社交媒体入口移入页脚 | 公司主体、备案、地址、电话及隐私政策是否最终确定 | + +## 三、当前首页内容如何处理 + +| 当前首页板块 | 新结构中的位置 | 处理方式 | +|---|---|---| +| 顶部导航 | 01 顶部导航栏 | 保留,不作为本轮重点修改 | +| 首屏四图轮播 | 02 首屏主视觉 | 保留结构方向,建议改为单图;重新建立选图标准 | +| 三步定制流程 | 08 定制流程 | 保留内容,向下移动,不再紧贴首屏 | +| 三大体验方向 | 05 三大贵州体验方向 | 保留,精简文字并统一图片风格 | +| 三条线路卡片 | 06 三条代表性线路 | 保留,补充真实独家亮点;价格未确认前不展示具体数字 | +| 为什么选择万趣 | 04 品牌主张 + 07 团队能力 | 拆成两个板块:品牌价值负责讲理念,团队板块负责讲真人与能力 | +| 品牌引语 | 04 品牌主张 | 合并,避免单独占用一个内容重复的板块 | +| 社交媒体横条 | 13 页脚 | 不再单独占一整段,合并进入页脚或客片评价区 | +| 贵州现场影集 | 09 真实客片与英文评价 | 重做为有国籍、人数、线路和评价的真实案例 | +| 当前缺少的背书 | 03 + 12 | 页面顶部放简洁信任条,底部放完整合作与资质标识 | +| 当前缺少的对比 | 10 对比板块 | 新增一组简短、可核验的比较内容 | +| 当前缺少的收口 | 11 最终咨询行动区 | 新增明确咨询入口 | + +## 四、图片与素材清单(确认结构后再统一收集) + +| 用途 | 建议数量 | 建议比例 | 必须满足的条件 | 当前状态 | +|---|---:|---|---|---| +| 首屏主图 | 1 张,最多 2 张备选 | 桌面约 16:7;同时保留手机裁切空间 | 贵州真实场景、低饱和、主体明确、无杂乱文字、具备商用权 | 现有 4 张均需重新评估 | +| 品牌主张照片 | 1 张 | 3:2 或横向 | 真人与真实环境,有“在地旅行”感,不使用摆拍图库人物 | 缺失 | +| 三大体验图 | 3 张 | 统一 4:5 或 3:4 | 户外、人文、风光各 1 张;摄影语言统一 | 现有图片风格不统一 | +| 三条线路图 | 3 张 | 统一 3:2 | 每张能代表对应线路,而不是通用贵州风景 | 部分可用,需业务确认 | +| 团队素材 | 1 张合照 + 2–4 张个人照 | 合照 3:2;个人 4:5 | 真人、清晰、统一拍摄风格、可公开 | 缺失 | +| 客片案例 | 3 组,每组 2–4 张 | 横竖图均可,但每组需统一编排 | 真实境外游客、评价授权、可标注国籍/人数/线路 | 未确认 | +| 合作与资质 | 3–6 项 | SVG/PNG 原文件 | 真实、可核验、可公开、符合 Logo 使用规范 | 未提供原件 | + +## 五、当前最需要业务回答的问题 + +### A. 定位与受众 + +1. 首页第一目标是否确定为“境外游客贵州高端私家定制”? +2. 团建与会务是否同样是核心业务?如果是,建议独立做“企业团建/会务”页面,而不是放入当前首页主线。 +3. 官网默认展示语言是英文还是中文?从客户文档看,境外用户应当以英文为主。 + +### B. 可信度与法律风险 + +4. 是否真实获得《国家地理》推荐?可以提供链接、证书、文章或正式授权吗? +5. “贵州文旅官方合作地接”“贵州户外探险协会会员”“非遗工坊合作”分别有哪些可公开证明? +6. 现有网站使用的所有图片是否有版权或客户授权?客户文档中的参考图不能默认直接用于官网。 + +### C. 产品与内容 + +7. 三条代表线路是否已经可以稳定交付?名称、天数、价格、独家资源是否已经确认? +8. 是否有真实境外游客评价、客片和公开授权? +9. 团队成员是否愿意展示真人照片、姓名、语言能力、从业年限和服务数据? +10. “24 小时在地服务”是否可以公开承诺具体响应标准? + +## 六、进入 Figma 前的确认门槛 + +满足以下条件后,再进入高保真视觉设计: + +- 首页主受众确定。 +- 上述 13 个板块确定保留、删除或合并。 +- 三条代表线路的名称和定位确定。 +- 第三方背书真实性确定。 +- 团队、客片和合作标识能否提供确定。 +- 首屏采用单图还是轮播确定。 + +在此之前,Figma 只做黑白或低保真结构稿;确认后再制作完整首页视觉稿、字体与颜色系统、图片风格规范和组件规范。 + +## 七、建议的下一步顺序 + +1. 先由业务回答第五部分的 10 个问题。 +2. 根据答案删减或确认首页 13 个板块。 +3. 制作首页低保真结构稿。 +4. 收集并审核图片、团队、评价和背书素材。 +5. 制作完整高保真 Figma 视觉稿。 +6. 视觉稿确认后,再按板块修改前端。 diff --git a/hero-comparison-final.jpg b/hero-comparison-final.jpg new file mode 100644 index 0000000..9db136c Binary files /dev/null and b/hero-comparison-final.jpg differ diff --git a/implementation-hero-final-visible.png b/implementation-hero-final-visible.png new file mode 100644 index 0000000..c53bd8a Binary files /dev/null and b/implementation-hero-final-visible.png differ diff --git a/implementation-hero-mobile-final.png b/implementation-hero-mobile-final.png new file mode 100644 index 0000000..2495a11 Binary files /dev/null and b/implementation-hero-mobile-final.png differ diff --git a/index.html b/index.html index 4ee9759..60eaa40 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + 万趣旅行|贵州秘境定制旅行 diff --git a/public/client-assets/ancient-towns-qingyan-overview.jpg b/public/client-assets/ancient-towns-qingyan-overview.jpg new file mode 100644 index 0000000..71ba10d Binary files /dev/null and b/public/client-assets/ancient-towns-qingyan-overview.jpg differ diff --git a/public/client-assets/ancient-towns-stone-wall.jpg b/public/client-assets/ancient-towns-stone-wall.jpg new file mode 100644 index 0000000..157bdb0 Binary files /dev/null and b/public/client-assets/ancient-towns-stone-wall.jpg differ diff --git a/public/client-assets/ancient-towns-zhenyuan-night.jpg b/public/client-assets/ancient-towns-zhenyuan-night.jpg new file mode 100644 index 0000000..32f46f3 Binary files /dev/null and b/public/client-assets/ancient-towns-zhenyuan-night.jpg differ diff --git a/public/client-assets/ancient-towns-zhenyuan-river.jpg b/public/client-assets/ancient-towns-zhenyuan-river.jpg new file mode 100644 index 0000000..e17a2f6 Binary files /dev/null and b/public/client-assets/ancient-towns-zhenyuan-river.jpg differ diff --git a/public/client-assets/brand-real-guizhou.webp b/public/client-assets/brand-real-guizhou.webp new file mode 100644 index 0000000..90cef70 Binary files /dev/null and b/public/client-assets/brand-real-guizhou.webp differ diff --git a/public/client-assets/camp-cloud-deck.jpg b/public/client-assets/camp-cloud-deck.jpg new file mode 100644 index 0000000..18abb7b Binary files /dev/null and b/public/client-assets/camp-cloud-deck.jpg differ diff --git a/public/client-assets/camp-mountain-portrait.jpg b/public/client-assets/camp-mountain-portrait.jpg new file mode 100644 index 0000000..1f60adf Binary files /dev/null and b/public/client-assets/camp-mountain-portrait.jpg differ diff --git a/public/client-assets/camp-view-lounge.jpg b/public/client-assets/camp-view-lounge.jpg new file mode 100644 index 0000000..6527dac Binary files /dev/null and b/public/client-assets/camp-view-lounge.jpg differ diff --git a/public/client-assets/cave-luxury-dining.jpg b/public/client-assets/cave-luxury-dining.jpg new file mode 100644 index 0000000..c323221 Binary files /dev/null and b/public/client-assets/cave-luxury-dining.jpg differ diff --git a/public/client-assets/cave-luxury-lounge.jpg b/public/client-assets/cave-luxury-lounge.jpg new file mode 100644 index 0000000..89109f0 Binary files /dev/null and b/public/client-assets/cave-luxury-lounge.jpg differ diff --git a/public/client-assets/cave-luxury-waterwalk.jpg b/public/client-assets/cave-luxury-waterwalk.jpg new file mode 100644 index 0000000..a47fd3b Binary files /dev/null and b/public/client-assets/cave-luxury-waterwalk.jpg differ diff --git a/public/client-assets/culinary-long-table-banquet.jpg b/public/client-assets/culinary-long-table-banquet.jpg new file mode 100644 index 0000000..ed23fa7 Binary files /dev/null and b/public/client-assets/culinary-long-table-banquet.jpg differ diff --git a/public/client-assets/culinary-shared-griddle.jpg b/public/client-assets/culinary-shared-griddle.jpg new file mode 100644 index 0000000..5a5b716 Binary files /dev/null and b/public/client-assets/culinary-shared-griddle.jpg differ diff --git a/public/client-assets/culinary-sour-soup-pot.jpg b/public/client-assets/culinary-sour-soup-pot.jpg new file mode 100644 index 0000000..af77734 Binary files /dev/null and b/public/client-assets/culinary-sour-soup-pot.jpg differ diff --git a/public/client-assets/culinary-sour-soup-table.jpg b/public/client-assets/culinary-sour-soup-table.jpg new file mode 100644 index 0000000..9f37cde Binary files /dev/null and b/public/client-assets/culinary-sour-soup-table.jpg differ diff --git a/public/client-assets/culture-batik-gallery.jpg b/public/client-assets/culture-batik-gallery.jpg new file mode 100644 index 0000000..c2fc504 Binary files /dev/null and b/public/client-assets/culture-batik-gallery.jpg differ diff --git a/public/client-assets/culture-batik-making.jpg b/public/client-assets/culture-batik-making.jpg new file mode 100644 index 0000000..67a7e21 Binary files /dev/null and b/public/client-assets/culture-batik-making.jpg differ diff --git a/public/client-assets/culture-batik-wax-detail.jpg b/public/client-assets/culture-batik-wax-detail.jpg new file mode 100644 index 0000000..73304d3 Binary files /dev/null and b/public/client-assets/culture-batik-wax-detail.jpg differ diff --git a/public/client-assets/culture-craft-hands.jpg b/public/client-assets/culture-craft-hands.jpg new file mode 100644 index 0000000..db52460 Binary files /dev/null and b/public/client-assets/culture-craft-hands.jpg differ diff --git a/public/client-assets/culture-craft-table.jpg b/public/client-assets/culture-craft-table.jpg new file mode 100644 index 0000000..99e8215 Binary files /dev/null and b/public/client-assets/culture-craft-table.jpg differ diff --git a/public/client-assets/culture-village-dong-view.jpg b/public/client-assets/culture-village-dong-view.jpg new file mode 100644 index 0000000..2e45f55 Binary files /dev/null and b/public/client-assets/culture-village-dong-view.jpg differ diff --git a/public/client-assets/culture-village-timber-homes.jpg b/public/client-assets/culture-village-timber-homes.jpg new file mode 100644 index 0000000..6ff3102 Binary files /dev/null and b/public/client-assets/culture-village-timber-homes.jpg differ diff --git a/public/client-assets/experience-entry-culture.webp b/public/client-assets/experience-entry-culture.webp new file mode 100644 index 0000000..449e752 Binary files /dev/null and b/public/client-assets/experience-entry-culture.webp differ diff --git a/public/client-assets/experience-entry-landscape.webp b/public/client-assets/experience-entry-landscape.webp new file mode 100644 index 0000000..14aad53 Binary files /dev/null and b/public/client-assets/experience-entry-landscape.webp differ diff --git a/public/client-assets/experience-entry-outdoor.webp b/public/client-assets/experience-entry-outdoor.webp new file mode 100644 index 0000000..b9c1b63 Binary files /dev/null and b/public/client-assets/experience-entry-outdoor.webp differ diff --git a/public/client-assets/festival-leishan-procession.jpg b/public/client-assets/festival-leishan-procession.jpg new file mode 100644 index 0000000..c8802f3 Binary files /dev/null and b/public/client-assets/festival-leishan-procession.jpg differ diff --git a/public/client-assets/festival-miao-blue-procession.jpg b/public/client-assets/festival-miao-blue-procession.jpg new file mode 100644 index 0000000..2e40868 Binary files /dev/null and b/public/client-assets/festival-miao-blue-procession.jpg differ diff --git a/public/client-assets/festival-miao-gathering.jpg b/public/client-assets/festival-miao-gathering.jpg new file mode 100644 index 0000000..ebde38a Binary files /dev/null and b/public/client-assets/festival-miao-gathering.jpg differ diff --git a/public/client-assets/festival-miao-silver-parade.jpg b/public/client-assets/festival-miao-silver-parade.jpg new file mode 100644 index 0000000..1558bc7 Binary files /dev/null and b/public/client-assets/festival-miao-silver-parade.jpg differ diff --git a/public/client-assets/fusion-indigo-concept.jpg b/public/client-assets/fusion-indigo-concept.jpg new file mode 100644 index 0000000..f70c4ab Binary files /dev/null and b/public/client-assets/fusion-indigo-concept.jpg differ diff --git a/public/client-assets/gallery-cave-dinner.webp b/public/client-assets/gallery-cave-dinner.webp new file mode 100644 index 0000000..59bf148 Binary files /dev/null and b/public/client-assets/gallery-cave-dinner.webp differ diff --git a/public/client-assets/gallery-heritage-hands.webp b/public/client-assets/gallery-heritage-hands.webp new file mode 100644 index 0000000..8be0ea8 Binary files /dev/null and b/public/client-assets/gallery-heritage-hands.webp differ diff --git a/public/client-assets/gallery-karst-adventure.webp b/public/client-assets/gallery-karst-adventure.webp new file mode 100644 index 0000000..10b468f Binary files /dev/null and b/public/client-assets/gallery-karst-adventure.webp differ diff --git a/public/client-assets/gallery-local-table.webp b/public/client-assets/gallery-local-table.webp new file mode 100644 index 0000000..5c5e9e2 Binary files /dev/null and b/public/client-assets/gallery-local-table.webp differ diff --git a/public/client-assets/gallery-mountain-pause.webp b/public/client-assets/gallery-mountain-pause.webp new file mode 100644 index 0000000..e72dd15 Binary files /dev/null and b/public/client-assets/gallery-mountain-pause.webp differ diff --git a/public/client-assets/gallery-waterfall-traveler.webp b/public/client-assets/gallery-waterfall-traveler.webp new file mode 100644 index 0000000..dbc3b14 Binary files /dev/null and b/public/client-assets/gallery-waterfall-traveler.webp differ diff --git a/public/client-assets/hero-miao-home-meal.webp b/public/client-assets/hero-miao-home-meal.webp new file mode 100644 index 0000000..12ff3e9 Binary files /dev/null and b/public/client-assets/hero-miao-home-meal.webp differ diff --git a/public/client-assets/hero-real-travelers.webp b/public/client-assets/hero-real-travelers.webp new file mode 100644 index 0000000..52cc915 Binary files /dev/null and b/public/client-assets/hero-real-travelers.webp differ diff --git a/public/client-assets/hero-river-adventure.webp b/public/client-assets/hero-river-adventure.webp new file mode 100644 index 0000000..88e3f6c Binary files /dev/null and b/public/client-assets/hero-river-adventure.webp differ diff --git a/public/client-assets/high-bridge-bungee.jpg b/public/client-assets/high-bridge-bungee.jpg new file mode 100644 index 0000000..4b1555d Binary files /dev/null and b/public/client-assets/high-bridge-bungee.jpg differ diff --git a/public/client-assets/high-bridge-cocoon.jpg b/public/client-assets/high-bridge-cocoon.jpg new file mode 100644 index 0000000..f5550ac Binary files /dev/null and b/public/client-assets/high-bridge-cocoon.jpg differ diff --git a/public/client-assets/high-bridge-record.jpg b/public/client-assets/high-bridge-record.jpg new file mode 100644 index 0000000..469f2bc Binary files /dev/null and b/public/client-assets/high-bridge-record.jpg differ diff --git a/public/client-assets/karst-hike-ridge.jpg b/public/client-assets/karst-hike-ridge.jpg new file mode 100644 index 0000000..a27f66b Binary files /dev/null and b/public/client-assets/karst-hike-ridge.jpg differ diff --git a/public/client-assets/karst-hike-river-sunset.jpg b/public/client-assets/karst-hike-river-sunset.jpg new file mode 100644 index 0000000..9965772 Binary files /dev/null and b/public/client-assets/karst-hike-river-sunset.jpg differ diff --git a/public/client-assets/karst-hike-sunset.jpg b/public/client-assets/karst-hike-sunset.jpg new file mode 100644 index 0000000..178d047 Binary files /dev/null and b/public/client-assets/karst-hike-sunset.jpg differ diff --git a/public/client-assets/karst-valley-camp.jpg b/public/client-assets/karst-valley-camp.jpg new file mode 100644 index 0000000..99448bc Binary files /dev/null and b/public/client-assets/karst-valley-camp.jpg differ diff --git a/public/client-assets/loushanguan-immersive-march-01.png b/public/client-assets/loushanguan-immersive-march-01.png new file mode 100644 index 0000000..dfef33b Binary files /dev/null and b/public/client-assets/loushanguan-immersive-march-01.png differ diff --git a/public/client-assets/qingyan-battle-client-scene.png b/public/client-assets/qingyan-battle-client-scene.png new file mode 100644 index 0000000..a982ef3 Binary files /dev/null and b/public/client-assets/qingyan-battle-client-scene.png differ diff --git a/public/client-assets/river-calm-rafting.jpg b/public/client-assets/river-calm-rafting.jpg new file mode 100644 index 0000000..3fdf62a Binary files /dev/null and b/public/client-assets/river-calm-rafting.jpg differ diff --git a/public/client-assets/river-canyon-pool.jpg b/public/client-assets/river-canyon-pool.jpg new file mode 100644 index 0000000..cae4c2e Binary files /dev/null and b/public/client-assets/river-canyon-pool.jpg differ diff --git a/public/client-assets/river-forest-rest.jpg b/public/client-assets/river-forest-rest.jpg new file mode 100644 index 0000000..31f5364 Binary files /dev/null and b/public/client-assets/river-forest-rest.jpg differ diff --git a/public/client-assets/river-karst-aerial.jpg b/public/client-assets/river-karst-aerial.jpg new file mode 100644 index 0000000..cd864e9 Binary files /dev/null and b/public/client-assets/river-karst-aerial.jpg differ diff --git a/public/client-assets/river-packraft-shore.jpg b/public/client-assets/river-packraft-shore.jpg new file mode 100644 index 0000000..7aa329b Binary files /dev/null and b/public/client-assets/river-packraft-shore.jpg differ diff --git a/public/client-assets/route-entry-balanced.webp b/public/client-assets/route-entry-balanced.webp new file mode 100644 index 0000000..bf75956 Binary files /dev/null and b/public/client-assets/route-entry-balanced.webp differ diff --git a/public/client-assets/route-entry-classic.webp b/public/client-assets/route-entry-classic.webp new file mode 100644 index 0000000..ff145c2 Binary files /dev/null and b/public/client-assets/route-entry-classic.webp differ diff --git a/public/client-assets/route-entry-outdoor.webp b/public/client-assets/route-entry-outdoor.webp new file mode 100644 index 0000000..3ce300a Binary files /dev/null and b/public/client-assets/route-entry-outdoor.webp differ diff --git a/public/client-assets/team-field-day.webp b/public/client-assets/team-field-day.webp new file mode 100644 index 0000000..9e5984a Binary files /dev/null and b/public/client-assets/team-field-day.webp differ diff --git a/public/client-assets/team-wanderq-group.webp b/public/client-assets/team-wanderq-group.webp new file mode 100644 index 0000000..46e0b71 Binary files /dev/null and b/public/client-assets/team-wanderq-group.webp differ diff --git a/public/client-assets/transport-light-van.jpg b/public/client-assets/transport-light-van.jpg new file mode 100644 index 0000000..bba280b Binary files /dev/null and b/public/client-assets/transport-light-van.jpg differ diff --git a/public/client-assets/transport-premium-mpv.jpg b/public/client-assets/transport-premium-mpv.jpg new file mode 100644 index 0000000..12f2f0f Binary files /dev/null and b/public/client-assets/transport-premium-mpv.jpg differ diff --git a/public/client-assets/transport-sedan-suv.jpg b/public/client-assets/transport-sedan-suv.jpg new file mode 100644 index 0000000..2068b08 Binary files /dev/null and b/public/client-assets/transport-sedan-suv.jpg differ diff --git a/public/client-assets/transport-van-interior.jpg b/public/client-assets/transport-van-interior.jpg new file mode 100644 index 0000000..23ea5b3 Binary files /dev/null and b/public/client-assets/transport-van-interior.jpg differ diff --git a/public/client-assets/village-balcony-life.jpg b/public/client-assets/village-balcony-life.jpg new file mode 100644 index 0000000..5da16bb Binary files /dev/null and b/public/client-assets/village-balcony-life.jpg differ diff --git a/public/client-assets/village-drumtower-roofs.jpg b/public/client-assets/village-drumtower-roofs.jpg new file mode 100644 index 0000000..45e3945 Binary files /dev/null and b/public/client-assets/village-drumtower-roofs.jpg differ diff --git a/public/client-assets/village-forest-homes.jpg b/public/client-assets/village-forest-homes.jpg new file mode 100644 index 0000000..c8eb4c4 Binary files /dev/null and b/public/client-assets/village-forest-homes.jpg differ diff --git a/public/client-assets/village-valley-fields.jpg b/public/client-assets/village-valley-fields.jpg new file mode 100644 index 0000000..7ec4049 Binary files /dev/null and b/public/client-assets/village-valley-fields.jpg differ diff --git a/public/client-assets/villages-dong-drumtowers.jpg b/public/client-assets/villages-dong-drumtowers.jpg new file mode 100644 index 0000000..d84a789 Binary files /dev/null and b/public/client-assets/villages-dong-drumtowers.jpg differ diff --git a/public/client-assets/villages-dong-valley.jpg b/public/client-assets/villages-dong-valley.jpg new file mode 100644 index 0000000..f4ce6b8 Binary files /dev/null and b/public/client-assets/villages-dong-valley.jpg differ diff --git a/public/client-assets/villages-xijiang-dusk.jpg b/public/client-assets/villages-xijiang-dusk.jpg new file mode 100644 index 0000000..c0cfd71 Binary files /dev/null and b/public/client-assets/villages-xijiang-dusk.jpg differ diff --git a/public/client-assets/villages-xijiang-panorama.jpg b/public/client-assets/villages-xijiang-panorama.jpg new file mode 100644 index 0000000..8a9d4be Binary files /dev/null and b/public/client-assets/villages-xijiang-panorama.jpg differ diff --git a/public/client-assets/waterfall-abseil-canyon.jpg b/public/client-assets/waterfall-abseil-canyon.jpg new file mode 100644 index 0000000..ea53886 Binary files /dev/null and b/public/client-assets/waterfall-abseil-canyon.jpg differ diff --git a/public/client-assets/waterfall-abseil-descent.jpg b/public/client-assets/waterfall-abseil-descent.jpg new file mode 100644 index 0000000..8eaf72b Binary files /dev/null and b/public/client-assets/waterfall-abseil-descent.jpg differ diff --git a/public/client-assets/waterfall-abseil-rope.jpg b/public/client-assets/waterfall-abseil-rope.jpg new file mode 100644 index 0000000..5024bff Binary files /dev/null and b/public/client-assets/waterfall-abseil-rope.jpg differ diff --git a/public/client-assets/waterfall-abseil-spray.jpg b/public/client-assets/waterfall-abseil-spray.jpg new file mode 100644 index 0000000..45bff58 Binary files /dev/null and b/public/client-assets/waterfall-abseil-spray.jpg differ diff --git a/public/client-assets/waterfall-chishui-danxia.jpg b/public/client-assets/waterfall-chishui-danxia.jpg new file mode 100644 index 0000000..f4fbdbb Binary files /dev/null and b/public/client-assets/waterfall-chishui-danxia.jpg differ diff --git a/public/client-assets/waterfall-dishuitan-tiered.jpg b/public/client-assets/waterfall-dishuitan-tiered.jpg new file mode 100644 index 0000000..626c9b4 Binary files /dev/null and b/public/client-assets/waterfall-dishuitan-tiered.jpg differ diff --git a/public/client-assets/waterfall-huangguoshu-curtain.jpg b/public/client-assets/waterfall-huangguoshu-curtain.jpg new file mode 100644 index 0000000..404de04 Binary files /dev/null and b/public/client-assets/waterfall-huangguoshu-curtain.jpg differ diff --git a/public/client-assets/waterfall-huangguoshu-main.jpg b/public/client-assets/waterfall-huangguoshu-main.jpg new file mode 100644 index 0000000..2fb6a63 Binary files /dev/null and b/public/client-assets/waterfall-huangguoshu-main.jpg differ diff --git a/public/client-assets/wild-coffee-cliff-cup.jpg b/public/client-assets/wild-coffee-cliff-cup.jpg new file mode 100644 index 0000000..83852d5 Binary files /dev/null and b/public/client-assets/wild-coffee-cliff-cup.jpg differ diff --git a/public/client-assets/wild-coffee-forest-cafe.jpg b/public/client-assets/wild-coffee-forest-cafe.jpg new file mode 100644 index 0000000..d8ed8c7 Binary files /dev/null and b/public/client-assets/wild-coffee-forest-cafe.jpg differ diff --git a/public/client-assets/wild-coffee-rain-deck.jpg b/public/client-assets/wild-coffee-rain-deck.jpg new file mode 100644 index 0000000..6560b65 Binary files /dev/null and b/public/client-assets/wild-coffee-rain-deck.jpg differ diff --git a/public/client-assets/wild-coffee-terrace-view.jpg b/public/client-assets/wild-coffee-terrace-view.jpg new file mode 100644 index 0000000..8bfca52 Binary files /dev/null and b/public/client-assets/wild-coffee-terrace-view.jpg differ diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..df54c14 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/guizhou-assets/destination-fanjing.webp b/public/guizhou-assets/destination-fanjing.webp new file mode 100644 index 0000000..c6ca108 Binary files /dev/null and b/public/guizhou-assets/destination-fanjing.webp differ diff --git a/public/guizhou-assets/destination-huangguoshu.webp b/public/guizhou-assets/destination-huangguoshu.webp new file mode 100644 index 0000000..27fb0df Binary files /dev/null and b/public/guizhou-assets/destination-huangguoshu.webp differ diff --git a/public/guizhou-assets/destination-liboxqk.webp b/public/guizhou-assets/destination-liboxqk.webp new file mode 100644 index 0000000..dfcdbb8 Binary files /dev/null and b/public/guizhou-assets/destination-liboxqk.webp differ diff --git a/public/guizhou-assets/destination-wanfenglin.webp b/public/guizhou-assets/destination-wanfenglin.webp new file mode 100644 index 0000000..22021c6 Binary files /dev/null and b/public/guizhou-assets/destination-wanfenglin.webp differ diff --git a/public/guizhou-assets/destination-xijiang.webp b/public/guizhou-assets/destination-xijiang.webp new file mode 100644 index 0000000..675c77e Binary files /dev/null and b/public/guizhou-assets/destination-xijiang.webp differ diff --git a/public/guizhou-assets/destination-zhenyuan.webp b/public/guizhou-assets/destination-zhenyuan.webp new file mode 100644 index 0000000..11e1b22 Binary files /dev/null and b/public/guizhou-assets/destination-zhenyuan.webp differ diff --git a/public/guizhou-assets/experience-cave.webp b/public/guizhou-assets/experience-cave.webp new file mode 100644 index 0000000..c841fc0 Binary files /dev/null and b/public/guizhou-assets/experience-cave.webp differ diff --git a/public/guizhou-assets/experience-coffee-village.webp b/public/guizhou-assets/experience-coffee-village.webp new file mode 100644 index 0000000..e414144 Binary files /dev/null and b/public/guizhou-assets/experience-coffee-village.webp differ diff --git a/public/guizhou-assets/experience-miao-craft.webp b/public/guizhou-assets/experience-miao-craft.webp new file mode 100644 index 0000000..46a1f9c Binary files /dev/null and b/public/guizhou-assets/experience-miao-craft.webp differ diff --git a/public/guizhou-assets/experience-rafting.webp b/public/guizhou-assets/experience-rafting.webp new file mode 100644 index 0000000..a971601 Binary files /dev/null and b/public/guizhou-assets/experience-rafting.webp differ diff --git a/public/guizhou-assets/faq-outdoor.webp b/public/guizhou-assets/faq-outdoor.webp new file mode 100644 index 0000000..e6b40bf Binary files /dev/null and b/public/guizhou-assets/faq-outdoor.webp differ diff --git a/public/guizhou-assets/footer-cta.webp b/public/guizhou-assets/footer-cta.webp new file mode 100644 index 0000000..ffb7ba6 Binary files /dev/null and b/public/guizhou-assets/footer-cta.webp differ diff --git a/public/guizhou-assets/hero-fanjing.webp b/public/guizhou-assets/hero-fanjing.webp new file mode 100644 index 0000000..a38cf93 Binary files /dev/null and b/public/guizhou-assets/hero-fanjing.webp differ diff --git a/public/guizhou-assets/hero-huangguoshu.webp b/public/guizhou-assets/hero-huangguoshu.webp new file mode 100644 index 0000000..355b43b Binary files /dev/null and b/public/guizhou-assets/hero-huangguoshu.webp differ diff --git a/public/guizhou-assets/hero-wanfenglin.webp b/public/guizhou-assets/hero-wanfenglin.webp new file mode 100644 index 0000000..6608258 Binary files /dev/null and b/public/guizhou-assets/hero-wanfenglin.webp differ diff --git a/public/guizhou-assets/hero-xijiang.webp b/public/guizhou-assets/hero-xijiang.webp new file mode 100644 index 0000000..5c8b5d0 Binary files /dev/null and b/public/guizhou-assets/hero-xijiang.webp differ diff --git a/public/guizhou-assets/hotel-bolian-spa.webp b/public/guizhou-assets/hotel-bolian-spa.webp new file mode 100644 index 0000000..0a3810d Binary files /dev/null and b/public/guizhou-assets/hotel-bolian-spa.webp differ diff --git a/public/guizhou-assets/hotel-reference.webp b/public/guizhou-assets/hotel-reference.webp new file mode 100644 index 0000000..50fde51 Binary files /dev/null and b/public/guizhou-assets/hotel-reference.webp differ diff --git a/public/guizhou-assets/menu-culture.webp b/public/guizhou-assets/menu-culture.webp new file mode 100644 index 0000000..ab702e4 Binary files /dev/null and b/public/guizhou-assets/menu-culture.webp differ diff --git a/public/guizhou-assets/menu-food.webp b/public/guizhou-assets/menu-food.webp new file mode 100644 index 0000000..0db0fa9 Binary files /dev/null and b/public/guizhou-assets/menu-food.webp differ diff --git a/public/guizhou-assets/menu-hotels.webp b/public/guizhou-assets/menu-hotels.webp new file mode 100644 index 0000000..dba71fa Binary files /dev/null and b/public/guizhou-assets/menu-hotels.webp differ diff --git a/public/guizhou-assets/menu-landscape.webp b/public/guizhou-assets/menu-landscape.webp new file mode 100644 index 0000000..3886dc1 Binary files /dev/null and b/public/guizhou-assets/menu-landscape.webp differ diff --git a/public/guizhou-assets/menu-outdoor.webp b/public/guizhou-assets/menu-outdoor.webp new file mode 100644 index 0000000..9511379 Binary files /dev/null and b/public/guizhou-assets/menu-outdoor.webp differ diff --git a/public/guizhou-assets/menu-transport.webp b/public/guizhou-assets/menu-transport.webp new file mode 100644 index 0000000..2a5a038 Binary files /dev/null and b/public/guizhou-assets/menu-transport.webp differ diff --git a/public/guizhou-assets/quote-guests.webp b/public/guizhou-assets/quote-guests.webp new file mode 100644 index 0000000..bed2237 Binary files /dev/null and b/public/guizhou-assets/quote-guests.webp differ diff --git a/public/guizhou-assets/route-classic.webp b/public/guizhou-assets/route-classic.webp new file mode 100644 index 0000000..d27a00c Binary files /dev/null and b/public/guizhou-assets/route-classic.webp differ diff --git a/public/guizhou-assets/route-mixed.webp b/public/guizhou-assets/route-mixed.webp new file mode 100644 index 0000000..c335aab Binary files /dev/null and b/public/guizhou-assets/route-mixed.webp differ diff --git a/public/guizhou-assets/route-outdoor.webp b/public/guizhou-assets/route-outdoor.webp new file mode 100644 index 0000000..b87bae8 Binary files /dev/null and b/public/guizhou-assets/route-outdoor.webp differ diff --git a/public/wanderq-logo.png b/public/wanderq-logo.png new file mode 100644 index 0000000..9899da3 Binary files /dev/null and b/public/wanderq-logo.png differ diff --git a/scripts/build_client_acceptance_doc.py b/scripts/build_client_acceptance_doc.py new file mode 100644 index 0000000..eccc5cf --- /dev/null +++ b/scripts/build_client_acceptance_doc.py @@ -0,0 +1,423 @@ +from pathlib import Path + +from docx import Document +from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +from docx.shared import Inches, Pt, RGBColor + + +OUT = Path(__file__).resolve().parents[1] / "docs" / "WanderQ官网客户验收与资料确认清单.docx" + +GREEN = "315A46" +DEEP_GREEN = "18382A" +LIGHT_GREEN = "EAF2ED" +PALE_GREEN = "F5F8F6" +GOLD = "A77A3F" +INK = "26332C" +MUTED = "68736C" +LINE = "CCD8D0" +WHITE = "FFFFFF" + + +def set_cell_shading(cell, fill): + tc_pr = cell._tc.get_or_add_tcPr() + shd = tc_pr.find(qn("w:shd")) + if shd is None: + shd = OxmlElement("w:shd") + tc_pr.append(shd) + shd.set(qn("w:fill"), fill) + + +def set_cell_margins(cell, top=100, start=120, bottom=100, end=120): + tc = cell._tc + tc_pr = tc.get_or_add_tcPr() + tc_mar = tc_pr.first_child_found_in("w:tcMar") + if tc_mar is None: + tc_mar = OxmlElement("w:tcMar") + tc_pr.append(tc_mar) + for margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)): + node = tc_mar.find(qn(f"w:{margin}")) + if node is None: + node = OxmlElement(f"w:{margin}") + tc_mar.append(node) + node.set(qn("w:w"), str(value)) + node.set(qn("w:type"), "dxa") + + +def set_cell_width(cell, width_dxa): + tc_pr = cell._tc.get_or_add_tcPr() + tc_w = tc_pr.find(qn("w:tcW")) + if tc_w is None: + tc_w = OxmlElement("w:tcW") + tc_pr.append(tc_w) + tc_w.set(qn("w:w"), str(width_dxa)) + tc_w.set(qn("w:type"), "dxa") + + +def set_table_geometry(table, widths, indent=120): + table.autofit = False + tbl_pr = table._tbl.tblPr + tbl_w = tbl_pr.find(qn("w:tblW")) + if tbl_w is None: + tbl_w = OxmlElement("w:tblW") + tbl_pr.append(tbl_w) + tbl_w.set(qn("w:w"), str(sum(widths))) + tbl_w.set(qn("w:type"), "dxa") + tbl_ind = tbl_pr.find(qn("w:tblInd")) + if tbl_ind is None: + tbl_ind = OxmlElement("w:tblInd") + tbl_pr.append(tbl_ind) + tbl_ind.set(qn("w:w"), str(indent)) + tbl_ind.set(qn("w:type"), "dxa") + grid = table._tbl.tblGrid + for child in list(grid): + grid.remove(child) + for width in widths: + col = OxmlElement("w:gridCol") + col.set(qn("w:w"), str(width)) + grid.append(col) + for row in table.rows: + for index, cell in enumerate(row.cells): + set_cell_width(cell, widths[index]) + set_cell_margins(cell) + cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER + + +def set_repeat_table_header(row): + tr_pr = row._tr.get_or_add_trPr() + tbl_header = OxmlElement("w:tblHeader") + tbl_header.set(qn("w:val"), "true") + tr_pr.append(tbl_header) + + +def set_cant_split(row): + tr_pr = row._tr.get_or_add_trPr() + cant_split = OxmlElement("w:cantSplit") + cant_split.set(qn("w:val"), "true") + tr_pr.append(cant_split) + + +def set_run(run, size=11, bold=False, color=INK, font="Noto Sans CJK SC"): + run.font.name = font + run._element.get_or_add_rPr().rFonts.set(qn("w:ascii"), font) + run._element.get_or_add_rPr().rFonts.set(qn("w:hAnsi"), font) + run._element.get_or_add_rPr().rFonts.set(qn("w:eastAsia"), font) + run.font.size = Pt(size) + run.bold = bold + run.font.color.rgb = RGBColor.from_string(color) + + +def add_text(paragraph, text, size=11, bold=False, color=INK): + run = paragraph.add_run(text) + set_run(run, size=size, bold=bold, color=color) + return run + + +def add_header_footer(section): + header = section.header + p = header.paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.RIGHT + p.paragraph_format.space_after = Pt(0) + add_text(p, "WANDERQ · CLIENT ACCEPTANCE", size=8.5, bold=True, color=MUTED) + + footer = section.footer + p = footer.paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + p.paragraph_format.space_before = Pt(0) + add_text(p, "WanderQ 官网客户验收与资料确认清单 · 2026-08-12", size=8.5, color=MUTED) + + +def add_title_block(doc): + p = doc.add_paragraph() + p.paragraph_format.space_before = Pt(10) + p.paragraph_format.space_after = Pt(2) + add_text(p, "WANDERQ / 万趣旅行", size=10.5, bold=True, color=GOLD) + + p = doc.add_paragraph() + p.paragraph_format.space_before = Pt(0) + p.paragraph_format.space_after = Pt(6) + add_text(p, "官网客户验收与资料确认清单", size=27, bold=True, color=DEEP_GREEN) + + p = doc.add_paragraph() + p.paragraph_format.space_after = Pt(16) + add_text(p, "用于整站内容确认、真实资料补齐与正式发布前定版", size=12.5, color=MUTED) + + table = doc.add_table(rows=2, cols=2) + set_table_geometry(table, [4680, 4680], indent=120) + info = [ + ("当前阶段", "已完成第一版开发与全站技术验收"), + ("确认方式", "可在本文勾选、批注,或按编号逐项回复"), + ("本轮范围", "贵州私家定制旅行官网(不含企业团建会务)"), + ("建议顺序", "先确认整站方向,再分批补齐酒店与供应资料"), + ] + for index, (label, value) in enumerate(info): + cell = table.cell(index // 2, index % 2) + set_cell_shading(cell, LIGHT_GREEN if index < 2 else PALE_GREEN) + p = cell.paragraphs[0] + p.paragraph_format.space_after = Pt(1) + add_text(p, label + "\n", size=8.5, bold=True, color=GREEN) + add_text(p, value, size=10.5, color=INK) + doc.add_paragraph() + + +def add_heading(doc, number, title, intro=None): + p = doc.add_paragraph(style="Heading 1") + p.paragraph_format.keep_with_next = True + add_text(p, f"{number} {title}", size=16, bold=True, color=DEEP_GREEN) + if intro: + p = doc.add_paragraph() + p.paragraph_format.space_after = Pt(8) + add_text(p, intro, size=10.5, color=MUTED) + + +def add_subheading(doc, title): + p = doc.add_paragraph(style="Heading 2") + p.paragraph_format.keep_with_next = True + add_text(p, title, size=12.5, bold=True, color=GREEN) + + +def add_checkbox_line(doc, text, indent=0): + p = doc.add_paragraph() + p.paragraph_format.left_indent = Inches(indent) + p.paragraph_format.space_after = Pt(4) + p.paragraph_format.keep_together = True + add_text(p, "☐ ", size=11.5, color=GREEN) + add_text(p, text, size=10.5) + + +def add_response_box(doc, prompt="客户意见 / 补充说明:", lines=2): + table = doc.add_table(rows=1, cols=1) + set_table_geometry(table, [9360], indent=120) + cell = table.cell(0, 0) + set_cell_shading(cell, PALE_GREEN) + set_cell_margins(cell, top=120, start=160, bottom=120, end=160) + p = cell.paragraphs[0] + p.paragraph_format.space_after = Pt(3) + add_text(p, prompt, size=9, bold=True, color=GREEN) + for _ in range(lines): + p = cell.add_paragraph() + p.paragraph_format.space_after = Pt(3) + add_text(p, "________________________________________________________________________________", size=9, color=LINE) + doc.add_paragraph().paragraph_format.space_after = Pt(2) + + +def add_status_table(doc): + rows = [ + ("整站视觉", "浅色、低饱和、欧美极简与编辑式排版", "请确认方向"), + ("首屏主视觉", "保留自动轮播,并支持手动切换与暂停", "已按前期确认执行"), + ("默认语言", "首次打开默认英文,可切换中文", "已按前期确认执行"), + ("业务范围", "暂不加入企业团建会务", "已按前期确认执行"), + ("第三方背书", "无正式文件前,不展示政府、媒体、协会等暗示性 Logo", "需资料后再上线"), + ("英文评价", "无可核验原始评价和授权前,不编造评价", "需资料后再上线"), + ] + table = doc.add_table(rows=1, cols=3) + set_table_geometry(table, [1900, 5060, 2400], indent=120) + for i, text in enumerate(("项目", "当前处理", "本轮确认")): + cell = table.rows[0].cells[i] + set_cell_shading(cell, GREEN) + p = cell.paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + add_text(p, text, size=9.5, bold=True, color=WHITE) + set_repeat_table_header(table.rows[0]) + for row_index, row_data in enumerate(rows): + row = table.add_row() + set_cant_split(row) + cells = row.cells + for i, text in enumerate(row_data): + set_cell_shading(cells[i], WHITE if row_index % 2 == 0 else PALE_GREEN) + p = cells[i].paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.LEFT if i == 1 else WD_ALIGN_PARAGRAPH.CENTER + add_text(p, text, size=9.5, bold=(i == 0), color=INK) + set_table_geometry(table, [1900, 5060, 2400], indent=120) + + +def add_hotel_table(doc): + hotels = [ + "兴义峰兮・洞穴民宿", + "朱砂古镇・放下云居", + "雷公山树蛙部落", + "榕江加宜梯田民宿", + "台江拾仓来隐", + "云屯・星空野奢树屋", + "枕宿・枕山河", + "兴义蘑菇野奢酒店", + ] + table = doc.add_table(rows=1, cols=4) + widths = [700, 2800, 3960, 1900] + set_table_geometry(table, widths, indent=120) + headers = ("序号", "酒店候选", "请提供 / 确认", "状态") + for i, text in enumerate(headers): + cell = table.rows[0].cells[i] + set_cell_shading(cell, GREEN) + p = cell.paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + add_text(p, text, size=9.2, bold=True, color=WHITE) + set_repeat_table_header(table.rows[0]) + detail = "正式中英文名、准确地址/地图点位、经营或预订主体、可接待境外客人方式、4–6 张原图及授权、当前房型与价格渠道" + for index, hotel in enumerate(hotels, 1): + row = table.add_row() + set_cant_split(row) + cells = row.cells + values = (f"{index:02d}", hotel, detail, "☐ 待提供\n☐ 已确认") + for i, text in enumerate(values): + set_cell_shading(cells[i], WHITE if index % 2 else PALE_GREEN) + p = cells[i].paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.CENTER if i in (0, 3) else WD_ALIGN_PARAGRAPH.LEFT + add_text(p, text, size=8.8, bold=(i == 1), color=INK) + set_table_geometry(table, widths, indent=120) + + +def add_material_block(doc, title, items): + table = doc.add_table(rows=1, cols=1) + set_table_geometry(table, [9360], indent=120) + cell = table.cell(0, 0) + set_cell_shading(cell, PALE_GREEN) + set_cell_margins(cell, top=130, start=170, bottom=130, end=170) + p = cell.paragraphs[0] + p.paragraph_format.space_after = Pt(5) + add_text(p, title, size=11, bold=True, color=DEEP_GREEN) + for item in items: + p = cell.add_paragraph() + p.paragraph_format.space_after = Pt(3) + add_text(p, "☐ ", size=10.5, color=GREEN) + add_text(p, item, size=9.7) + doc.add_paragraph().paragraph_format.space_after = Pt(2) + + +def build(): + doc = Document() + section = doc.sections[0] + section.page_width = Inches(8.5) + section.page_height = Inches(11) + section.top_margin = Inches(0.78) + section.bottom_margin = Inches(0.75) + section.left_margin = Inches(0.9) + section.right_margin = Inches(0.9) + section.header_distance = Inches(0.35) + section.footer_distance = Inches(0.35) + add_header_footer(section) + + normal = doc.styles["Normal"] + normal.font.name = "Noto Sans CJK SC" + normal._element.rPr.rFonts.set(qn("w:ascii"), "Noto Sans CJK SC") + normal._element.rPr.rFonts.set(qn("w:hAnsi"), "Noto Sans CJK SC") + normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Noto Sans CJK SC") + normal.font.size = Pt(10.5) + normal.font.color.rgb = RGBColor.from_string(INK) + normal.paragraph_format.space_after = Pt(6) + normal.paragraph_format.line_spacing = 1.25 + + for name, size, before, after in (("Heading 1", 16, 16, 8), ("Heading 2", 12.5, 12, 6)): + style = doc.styles[name] + style.font.name = "Noto Sans CJK SC" + style._element.rPr.rFonts.set(qn("w:ascii"), "Noto Sans CJK SC") + style._element.rPr.rFonts.set(qn("w:hAnsi"), "Noto Sans CJK SC") + style._element.rPr.rFonts.set(qn("w:eastAsia"), "Noto Sans CJK SC") + style.font.size = Pt(size) + style.font.bold = True + style.font.color.rgb = RGBColor.from_string(DEEP_GREEN if name == "Heading 1" else GREEN) + style.paragraph_format.space_before = Pt(before) + style.paragraph_format.space_after = Pt(after) + + add_title_block(doc) + + add_heading(doc, "01", "客户如何使用本清单", "请先完成整站方向确认;具体酒店、供应商和交易资料可以分批补充。任何暂时无法确认的项目,官网都会继续使用“待确认”或安全边界表达,不会替客户作无依据承诺。") + add_checkbox_line(doc, "整站方向无重大问题,可继续进入真实资料补齐和发布前定版。") + add_checkbox_line(doc, "整体方向认可,但需要按本文编号进行局部修改。") + add_checkbox_line(doc, "需要重新讨论某个栏目结构;请注明栏目和原因。") + add_response_box(doc, "请填写本轮总意见:", 3) + + add_heading(doc, "02", "整站方向确认", "以下项目是此前沟通结果与当前官网的执行方式,请客户确认是否继续沿用。") + add_status_table(doc) + add_subheading(doc, "请客户确认") + add_checkbox_line(doc, "认可当前整站视觉方向,不需要大规模重做。") + add_checkbox_line(doc, "认可当前首页到详情页的栏目结构和内容顺序。") + add_checkbox_line(doc, "认可英文为默认语言,中文作为切换语言。") + add_checkbox_line(doc, "认可暂不加入企业团建会务。") + add_checkbox_line(doc, "认可未提供证明的卖点、资质和背书暂不上线。") + add_response_box(doc, "如有页面级修改,请写“页面名称 + 位置 + 修改内容”:", 3) + + add_heading(doc, "03", "8 家特色酒店资料确认", "官网已经按照客户提出的 8 家住宿候选完成栏目结构,但目前不使用无关风景图冒充酒店实景。请按酒店逐家提供下列资料。") + add_hotel_table(doc) + add_subheading(doc, "每家酒店图片最低要求") + add_checkbox_line(doc, "外观或环境 1–2 张、代表房型 2–3 张、公共空间或餐饮 1–2 张,尽量提供原始高清图。") + add_checkbox_line(doc, "明确图片对应的酒店、房型、拍摄时间和是否允许官网商业使用。") + add_checkbox_line(doc, "如图片来自酒店官方或第三方平台,请提供授权依据,不能仅提供网络截图。") + add_response_box(doc, "酒店资料负责人 / 预计提供日期:", 2) + + add_heading(doc, "04", "体验供应与安全资料", "客户文档中的体验方向已完整进入网站。正式对外销售前,需要把“概念内容”落实到具名运营方、具名人员和可执行条件。") + add_material_block(doc, "户外体验", [ + "洞穴、瀑降、溯溪、漂流、徒步/骑行、高桥挑战等项目的具名运营方与联系人。", + "领队、向导、教练或救援人员的相关资质、经验与可公开介绍。", + "适用年龄、体能/健康限制、装备清单、天气/水位取消条件与替代方案。", + "保险责任、应急通信、撤离路线、最近医疗资源和事故处理流程。", + "真实产品时长、人数上下限、当前价格、可约日期和取消规则。", + ]) + add_material_block(doc, "文化与村寨体验", [ + "苗绣/蜡染、侗族技艺、水族马尾绣、布依技艺等具名老师、工坊和地点。", + "村寨拜访、家庭接待、住宿、家宴、节庆参与的主人/社区/运营主体同意。", + "课程可完成内容、材料与工具、儿童参与边界、拍摄同意和作品带走/后寄方式。", + "英文支持由谁提供,以及费用如何分配给老师、主人或社区。", + ]) + add_material_block(doc, "特别场景与历史演绎", [ + "洞穴咖啡/晚宴、山野咖啡、露营的场地许可、餐饮、消防、电力、天气与撤离资料。", + "《战青岩》《娄山关大捷》等现有产品的运营主体、当前场次、票价、英文接待与包团条件。", + "客户提出但尚未成为现有产品的二期共创内容,需单独确认开发主体、预算和周期。", + ]) + add_response_box(doc, "供应商与安全资料负责人 / 预计提供日期:", 2) + + add_heading(doc, "05", "合作背书与真实评价", "这一部分能够显著增强官网信任感,但必须保证来源真实、关联准确并获得公开使用许可。") + add_subheading(doc, "合作、媒体与资质") + add_checkbox_line(doc, "合作机构或政府单位:正式名称、合作内容、有效期、证明文件及 Logo 使用授权。") + add_checkbox_line(doc, "媒体报道或推荐:原始报道链接/文件、准确标题和允许引用的内容。") + add_checkbox_line(doc, "协会会员、行业认证或获奖:证书、有效期、认证主体和允许展示的标识。") + add_subheading(doc, "真实英文客户评价") + add_checkbox_line(doc, "英文原评(不由开发侧翻译成“原评”)、客户姓名或允许公开的署名方式。") + add_checkbox_line(doc, "客户照片、国籍/地区、人数、实际参加线路或体验、出行时间。") + add_checkbox_line(doc, "照片和评价公开用于官网及营销材料的授权。") + add_response_box(doc, "如暂时没有完整资料,可注明“本次不上线”:", 2) + + add_heading(doc, "06", "价格、预订与法律信息", "官网进入正式发布和接单阶段前,请确认以下交易信息,避免页面卖点与实际报价、履约条件不一致。") + for item in [ + "各线路、体验、酒店和用车是展示参考价、起价、询价制,还是暂不公开价格。", + "报价包含与不包含:门票、住宿、餐饮、车辆、司导、装备、保险、税费和服务费。", + "预订金比例、尾款时间、支付方式、汇率或跨境支付费用。", + "取消、退款、日期变更、人数变化、天气取消和供应商取消条款。", + "公司/经营主体正式名称、注册地址、联系人、客服邮箱及适用的隐私与服务条款。", + "客户咨询信息由谁接收、如何跟进、保存多久,以及是否需要单独的隐私同意。", + ]: + add_checkbox_line(doc, item) + add_response_box(doc, "价格与合同资料负责人 / 预计提供日期:", 2) + + add_heading(doc, "07", "回复优先级与最终确认", "为了不影响当前设计确认,资料可以按以下顺序分批提供。") + priorities = [ + ("P0 / 本轮确认", "整站视觉、结构、默认英文、业务范围,以及需要立即修改的页面。"), + ("P1 / 发布前必须", "酒店原图与授权、具名供应商与安全资料、价格/取消条款、经营与法律信息。"), + ("P2 / 可后补增强", "合作背书、媒体材料、真实英文评价和新增内容。"), + ] + table = doc.add_table(rows=1, cols=2) + set_table_geometry(table, [2200, 7160], indent=120) + for index, (level, detail) in enumerate(priorities): + row = table.rows[0] if index == 0 else table.add_row() + set_cell_shading(row.cells[0], GREEN if index == 0 else LIGHT_GREEN) + set_cell_shading(row.cells[1], PALE_GREEN) + p = row.cells[0].paragraphs[0] + add_text(p, level, size=10, bold=True, color=WHITE if index == 0 else GREEN) + p = row.cells[1].paragraphs[0] + add_text(p, detail, size=10) + set_table_geometry(table, [2200, 7160], indent=120) + doc.add_paragraph() + add_checkbox_line(doc, "确认当前官网可作为客户整站验收版本继续推进。") + add_checkbox_line(doc, "确认按本清单分批补充资料;未提供或未核验内容不作为官网承诺。") + add_checkbox_line(doc, "确认客户提供的文字、图片、Logo、评价与证明材料拥有相应公开和商业使用权限。") + add_response_box(doc, "客户代表 / 日期 / 其他说明:", 4) + + OUT.parent.mkdir(parents=True, exist_ok=True) + doc.save(OUT) + print(OUT) + + +if __name__ == "__main__": + build() diff --git a/src/TravelSite.css b/src/TravelSite.css index cd537b0..107cddd 100644 --- a/src/TravelSite.css +++ b/src/TravelSite.css @@ -21,6 +21,4022 @@ font-family: 'Assistant', Arial, sans-serif; } +/* Homepage concept 2026 — bold destination matrix + narrative funnel */ +.oc-prototype-hero { + position: relative; + min-height: calc(100svh - 91px); + overflow: hidden; + isolation: isolate; + color: #f7f5ed; + background: #17221b; +} + +.oc-prototype-hero-image, +.oc-prototype-hero-shade { + position: absolute; + inset: 0; + width: 100%; + height: 112%; +} + +.oc-prototype-hero-image { + z-index: -3; + object-fit: cover; + object-position: center 48%; + filter: saturate(.72) contrast(1.04) brightness(.8); + animation: oc-prototype-image-in 1.2s cubic-bezier(.22, 1, .36, 1) both; +} + +@keyframes oc-prototype-image-in { + from { opacity: .35; transform: scale(1.055); } + to { opacity: 1; transform: scale(1); } +} + +.oc-prototype-hero-shade { + z-index: -2; + height: 100%; + background: + linear-gradient(90deg, rgba(8, 14, 10, .62) 0%, rgba(8, 14, 10, .13) 61%, rgba(8, 14, 10, .31) 100%), + linear-gradient(0deg, rgba(8, 14, 10, .73) 0%, rgba(8, 14, 10, .02) 52%, rgba(8, 14, 10, .22) 100%); +} + +.oc-prototype-hero-content { + width: min(calc(100% - 96px), 1500px); + min-height: calc(100svh - 91px); + display: grid; + grid-template-columns: minmax(0, 1fr) 340px; + grid-template-rows: auto 1fr; + column-gap: 58px; + align-items: end; + margin: 0 auto; + padding: 47px 0 150px; +} + +.oc-prototype-hero-kicker { + grid-column: 1 / -1; + align-self: start; + margin: 0; + color: rgba(255, 255, 255, .76); + font-size: 11px; + font-weight: 700; + letter-spacing: .2em; + text-transform: uppercase; +} + +.oc-site .oc-prototype-hero-content h1 { + max-width: 1050px; + margin: 0; + color: #fff; + font-family: 'Assistant', Arial, sans-serif; + font-size: clamp(68px, 8.1vw, 132px); + font-weight: 650; + line-height: .82; + letter-spacing: -.072em; +} + +.oc-prototype-hero-title-row { + display: block; + overflow: hidden; + padding: 0 .08em .08em 0; +} + +.oc-prototype-hero-title-line { + display: block; +} + +.oc-prototype-hero-title-line.is-accent { + color: #d9ff57; +} + +.oc-prototype-hero-side { + align-self: end; + padding-bottom: 10px; +} + +.oc-prototype-hero-side p { + margin: 0 0 29px; + color: rgba(255, 255, 255, .79); + font-size: 16px; + line-height: 1.65; +} + +.oc-prototype-hero-side a, +.oc-prototype-story-copy > a { + display: inline-flex; + align-items: center; + gap: 18px; + padding-bottom: 9px; + color: currentColor; + border-bottom: 1px solid currentColor; + font-size: 12px; + font-weight: 700; + letter-spacing: .08em; + text-transform: uppercase; +} + +.oc-prototype-hero-footer { + position: absolute; + right: 0; + bottom: 0; + left: 0; + min-height: 80px; + display: grid; + grid-template-columns: 1fr auto auto; + gap: 48px; + align-items: center; + padding: 0 max(48px, calc((100vw - 1500px) / 2)); + border-top: 1px solid rgba(255, 255, 255, .24); + color: rgba(255, 255, 255, .72); + font-size: 10px; + font-weight: 700; + letter-spacing: .12em; + text-transform: uppercase; +} + +.oc-prototype-hero-assurances, +.oc-prototype-hero-controls { + display: flex; + align-items: center; +} + +.oc-prototype-hero-assurances { gap: 26px; } +.oc-prototype-hero-assurances span + span::before { content: '·'; margin-right: 26px; } +.oc-prototype-hero-controls { gap: 11px; } + +.oc-prototype-hero-dot { + width: 26px; + height: 1px; + padding: 7px 0; + border: 0; + background: linear-gradient(rgba(255,255,255,.42), rgba(255,255,255,.42)) center / 100% 1px no-repeat; + cursor: pointer; +} + +.oc-prototype-hero-dot.is-active { + background-image: linear-gradient(#d9ff57, #d9ff57); +} + +.oc-prototype-hero-playback { + width: 27px; + height: 27px; + display: grid; + place-items: center; + padding: 0; + border: 0; + color: #fff; + background: transparent; + cursor: pointer; +} + +.oc-prototype-hero :is(button, a):focus-visible, +.oc-prototype-themes a:focus-visible, +.oc-prototype-story a:focus-visible { + outline: 2px solid #d9ff57; + outline-offset: 5px; +} + +.oc-prototype-themes { + min-height: 100svh; + padding: 118px max(48px, calc((100vw - 1500px) / 2)) 104px; + color: #f3f1e8; + background: #142119; +} + +.oc-prototype-theme-head { + display: grid; + grid-template-columns: 160px minmax(0, 1fr) 320px; + gap: 45px; + align-items: end; + margin-bottom: 64px; +} + +.oc-prototype-theme-head > span, +.oc-prototype-story-copy > span { + align-self: start; + color: #d9ff57; + font-size: 10px; + font-weight: 700; + line-height: 1.5; + letter-spacing: .17em; + text-transform: uppercase; +} + +.oc-site .oc-prototype-theme-head h2, +.oc-site .oc-prototype-story-copy h2 { + margin: 0; + color: inherit; + font-family: 'Assistant', Arial, sans-serif; + font-size: clamp(52px, 6.2vw, 96px); + font-weight: 620; + line-height: .9; + letter-spacing: -.065em; + white-space: pre-line; +} + +.oc-prototype-theme-head p { + margin: 0 0 5px; + color: rgba(243, 241, 232, .58); + font-size: 15px; + line-height: 1.7; +} + +.oc-prototype-theme-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + border-top: 1px solid rgba(255, 255, 255, .19); + border-left: 1px solid rgba(255, 255, 255, .19); +} + +.oc-prototype-theme-card { + position: relative; + min-height: clamp(230px, 21vw, 330px); + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: 1fr auto; + align-items: end; + padding: 25px 27px 27px; + overflow: hidden; + border-right: 1px solid rgba(255, 255, 255, .19); + border-bottom: 1px solid rgba(255, 255, 255, .19); + color: #f8f6ee; + isolation: isolate; +} + +.oc-prototype-theme-card::after { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + background: linear-gradient(0deg, rgba(5, 10, 7, .88), rgba(5, 10, 7, .05) 70%); + transition: background-color .45s ease; +} + +.oc-prototype-theme-card img { + position: absolute; + inset: 0; + z-index: -2; + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.62) brightness(.73); + transition: transform .7s cubic-bezier(.2,.75,.3,1), filter .5s ease; +} + +.oc-prototype-theme-card > span { + grid-column: 1 / -1; + align-self: start; + color: rgba(255, 255, 255, .66); + font-size: 10px; + letter-spacing: .15em; +} + +.oc-site .oc-prototype-theme-card h3 { + margin: 0; + color: inherit; + font-size: clamp(23px, 2vw, 34px); + font-weight: 600; + line-height: 1; + letter-spacing: -.035em; + font-family: 'Assistant', Arial, sans-serif; +} + +.oc-prototype-theme-card:hover img { transform: scale(1.065); filter: saturate(.9) brightness(.9); } +.oc-prototype-theme-card:hover::after { background-color: rgba(45, 75, 52, .25); } +.oc-prototype-theme-card:hover svg { transform: translate(5px, -5px); } +.oc-prototype-theme-card svg { transition: transform .25s ease; } + +.oc-prototype-story { + min-height: 100svh; + display: grid; + grid-template-columns: minmax(340px, .92fr) minmax(0, 1.08fr); + grid-template-rows: auto 1fr auto; + column-gap: clamp(70px, 8vw, 130px); + padding: 112px max(48px, calc((100vw - 1500px) / 2)) 82px; + color: #1d2b21; + background: #f1efe5; +} + +.oc-prototype-story-image { + position: relative; + grid-row: 1 / 4; + min-height: 690px; + overflow: hidden; + background: #d8ddd4; +} + +.oc-prototype-story-image::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(0deg, rgba(10, 20, 13, .48), transparent 44%); +} + +.oc-prototype-story-image img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.75) contrast(.95); +} + +.oc-prototype-story-image span { + position: absolute; + bottom: 29px; + left: 31px; + z-index: 1; + color: #fff; + font-size: 10px; + font-weight: 700; + letter-spacing: .16em; + text-transform: uppercase; +} + +.oc-prototype-story-copy { + display: grid; + grid-template-columns: 125px 1fr; + column-gap: 35px; + align-content: start; +} + +.oc-prototype-story-copy h2 { grid-column: 2; } + +.oc-prototype-story-copy > p { + grid-column: 2; + max-width: 620px; + margin: 31px 0 0; + color: #5d695f; + font-size: 16px; + line-height: 1.75; +} + +.oc-prototype-story-copy > a { + grid-column: 2; + justify-self: start; + margin-top: 28px; + color: #263a2c; +} + +.oc-prototype-story-steps { + align-self: end; + margin-top: 68px; + border-top: 1px solid rgba(25, 42, 30, .22); +} + +.oc-prototype-story-step { + display: grid; + grid-template-columns: 70px 1fr; + gap: 25px; + padding: 22px 0; + border-bottom: 1px solid rgba(25, 42, 30, .18); +} + +.oc-prototype-story-step > span { + color: #66816c; + font-size: 11px; + font-weight: 700; + letter-spacing: .15em; +} + +.oc-site .oc-prototype-story-step h3 { + margin: 0 0 7px; + color: #253429; + font-size: 18px; + font-weight: 650; + line-height: 1.25; + font-family: 'Assistant', Arial, sans-serif; +} + +.oc-prototype-story-step p { + max-width: 680px; + margin: 0; + color: #667069; + font-size: 13px; + line-height: 1.6; +} + +.oc-prototype-story-proof { + margin: 30px 0 0; + color: #68816d; + font-size: 10px; + font-weight: 700; + letter-spacing: .12em; + text-transform: uppercase; +} + +@media (max-width: 1100px) { + .oc-prototype-hero-content { grid-template-columns: 1fr; align-content: end; } + .oc-prototype-hero-side { max-width: 520px; margin-top: 28px; } + .oc-prototype-hero-assurances { display: none; } + .oc-prototype-hero-footer { grid-template-columns: 1fr auto; } + .oc-prototype-theme-head { grid-template-columns: 120px 1fr; } + .oc-prototype-theme-head p { grid-column: 2; } + .oc-prototype-theme-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .oc-prototype-story { grid-template-columns: minmax(300px, .8fr) 1.2fr; column-gap: 54px; } + .oc-prototype-story-copy { grid-template-columns: 1fr; } + .oc-prototype-story-copy > :is(h2, p, a) { grid-column: 1; } + .oc-prototype-story-copy > span { margin-bottom: 24px; } +} + +@media (max-width: 820px) { + .oc-prototype-hero { min-height: calc(100svh - 76px); } + .oc-prototype-hero-content { + width: min(100% - 40px, 700px); + min-height: calc(100svh - 76px); + padding: 33px 0 122px; + } + .oc-prototype-hero-content h1 { font-size: clamp(54px, 13vw, 86px); line-height: .87; } + .oc-prototype-hero-footer { min-height: 64px; padding: 0 20px; } + .oc-prototype-hero-footer > span { display: none; } + .oc-prototype-hero-footer { grid-template-columns: 1fr; } + .oc-prototype-hero-controls { justify-self: start; } + .oc-prototype-themes { padding: 84px 20px 74px; } + .oc-prototype-theme-head { grid-template-columns: 1fr; gap: 22px; margin-bottom: 42px; } + .oc-prototype-theme-head p { grid-column: 1; max-width: 480px; } + .oc-prototype-theme-card { min-height: 250px; } + .oc-prototype-story { grid-template-columns: 1fr; padding: 78px 20px 68px; } + .oc-prototype-story-image { grid-row: auto; min-height: 540px; margin-bottom: 60px; } +} + +@media (max-width: 540px) { + .oc-prototype-hero-content h1 { font-size: 52px; } + .oc-prototype-hero-side p { font-size: 14px; } + .oc-prototype-theme-grid { grid-template-columns: 1fr; } + .oc-prototype-theme-card { min-height: 235px; } + .oc-prototype-theme-head h2, + .oc-prototype-story-copy h2 { font-size: 48px; } + .oc-prototype-story-image { min-height: 440px; } + .oc-prototype-story-step { grid-template-columns: 42px 1fr; gap: 12px; } +} +/* Underground karst experience detail */ +.oc-cave-detail { + --oc-cave-ink: #172019; + --oc-cave-moss: #253a2f; + --oc-cave-sage: #a9b8a9; + --oc-cave-paper: #f3f1e9; + color: var(--oc-cave-ink); + background: #f8f7f2; +} + +.oc-cave-detail :is(section, nav)[id] { + scroll-margin-top: 96px; +} + +.oc-cave-detail-hero { + position: relative; + min-height: 760px; + display: grid; + overflow: hidden; + color: #fff; + background: #172019; +} + +.oc-cave-detail-hero > img, +.oc-cave-detail-shade { + position: absolute; + inset: 0; + width: 100%; + height: 100%; +} + +.oc-cave-detail-hero > img { + object-fit: cover; + object-position: center 54%; + filter: saturate(.72) contrast(.94) brightness(.82); +} + +.oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(9, 16, 12, .82) 0%, rgba(9, 16, 12, .57) 38%, rgba(9, 16, 12, .08) 73%), + linear-gradient(0deg, rgba(9, 16, 12, .48) 0%, transparent 48%); +} + +.oc-cave-detail-hero-content { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + justify-content: flex-end; + min-height: 760px; + padding-top: 120px; + padding-bottom: 90px; +} + +.oc-cave-detail-hero-copy > .oc-breadcrumb.is-light { + position: static; + margin: 0 0 23px; + padding: 0; +} + +.oc-cave-detail-hero-copy { + max-width: 790px; +} + +.oc-cave-detail-hero-copy > span, +.oc-cave-detail-section-head > span, +.oc-cave-detail-intro > span { + display: block; + margin-bottom: 22px; + color: #b9c8b9; + font-size: 11px; + font-weight: 700; + line-height: 1.3; + letter-spacing: .2em; + text-transform: uppercase; +} + +.oc-cave-detail-hero-copy h1 { + max-width: 760px; + margin: 0; + font-family: var(--oc-serif); + font-size: clamp(54px, 7.1vw, 108px); + font-weight: 400; + line-height: .92; + letter-spacing: -.055em; +} + +.oc-cave-detail-hero-copy p { + max-width: 650px; + margin: 31px 0 0; + color: rgba(255, 255, 255, .78); + font-size: clamp(16px, 1.45vw, 20px); + line-height: 1.65; +} + +.oc-cave-detail-hero-copy > a { + display: inline-flex; + align-items: center; + gap: 11px; + margin-top: 32px; + padding-bottom: 7px; + color: #fff; + border-bottom: 1px solid rgba(255, 255, 255, .55); + font-size: 12px; + font-weight: 700; + letter-spacing: .08em; + text-transform: uppercase; +} + +.oc-cave-detail-nav { + position: sticky; + z-index: 4; + top: 0; + display: flex; + justify-content: center; + gap: clamp(22px, 5vw, 68px); + min-height: 69px; + align-items: center; + background: rgba(248, 247, 242, .94); + border-bottom: 1px solid rgba(32, 49, 39, .13); + backdrop-filter: blur(14px); +} + +.oc-cave-detail-nav a { + color: #48544c; + font-size: 11px; + font-weight: 700; + letter-spacing: .08em; + text-transform: uppercase; +} + +.oc-cave-detail-overview { + display: grid; + grid-template-columns: minmax(0, 1.16fr) minmax(380px, .84fr); + gap: clamp(70px, 10vw, 150px); + padding-top: 145px; + padding-bottom: 145px; +} + +.oc-cave-detail-intro h2, +.oc-cave-detail-section-head h2 { + margin: 0; + font-family: var(--oc-serif); + font-size: clamp(38px, 4.6vw, 68px); + font-weight: 400; + line-height: 1.05; + letter-spacing: -.045em; +} + +.oc-cave-detail-intro > span { + color: #647468; +} + +.oc-cave-detail-intro p { + max-width: 760px; + margin: 35px 0 0; + color: #5a635d; + font-size: 16px; + line-height: 1.9; +} + +.oc-cave-detail-facts { + margin: 0; + border-top: 1px solid rgba(32, 49, 39, .2); +} + +.oc-cave-detail-facts > div { + display: grid; + grid-template-columns: 122px minmax(0, 1fr); + gap: 22px; + padding: 18px 0; + border-bottom: 1px solid rgba(32, 49, 39, .14); +} + +.oc-cave-detail-facts dt { + color: #788078; + font-size: 10px; + font-weight: 700; + letter-spacing: .08em; + text-transform: uppercase; +} + +.oc-cave-detail-facts dd { + margin: 0; + color: #26352c; + font-size: 13px; + line-height: 1.55; +} + +.oc-cave-detail-levels { + padding: 132px 0 142px; + color: #f5f3eb; + background: var(--oc-cave-moss); +} + +.oc-cave-detail-levels .oc-cave-detail-section-head { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(300px, .58fr); + column-gap: 100px; + align-items: end; +} + +.oc-cave-detail-levels .oc-cave-detail-section-head > span { + grid-column: 1 / -1; +} + +.oc-cave-detail-section-head > p { + margin: 0; + color: #67726a; + font-size: 14px; + line-height: 1.85; +} + +.oc-cave-detail-levels .oc-cave-detail-section-head > p { + color: rgba(245, 243, 235, .63); +} + +.oc-cave-detail-level-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 76px; + border-top: 1px solid rgba(255, 255, 255, .18); + border-bottom: 1px solid rgba(255, 255, 255, .18); +} + +.oc-cave-detail-level-grid article { + min-height: 480px; + padding: 35px 38px 38px; + border-left: 1px solid rgba(255, 255, 255, .18); +} + +.oc-cave-detail-level-grid article:first-child { + border-left: 0; +} + +.oc-cave-detail-level-grid article > div { + display: flex; + align-items: center; + justify-content: space-between; +} + +.oc-cave-detail-level-grid article > div span { + color: rgba(255, 255, 255, .48); + font-family: var(--oc-serif); + font-size: 29px; +} + +.oc-cave-detail-level-grid article > div b { + padding: 7px 12px; + border: 1px solid rgba(255, 255, 255, .28); + border-radius: 999px; + font-size: 9px; + letter-spacing: .13em; + text-transform: uppercase; +} + +.oc-cave-detail-level-grid h3 { + min-height: 116px; + margin: 64px 0 25px; + font-family: var(--oc-serif); + font-size: 31px; + font-weight: 400; + line-height: 1.12; +} + +.oc-cave-detail-level-grid p { + margin: 0; + color: rgba(245, 243, 235, .67); + font-size: 13px; + line-height: 1.85; +} + +.oc-cave-detail-level-grid small { + display: block; + margin-top: 30px; + padding-top: 18px; + color: #d4ded3; + border-top: 1px solid rgba(255, 255, 255, .13); + font-size: 10px; + line-height: 1.65; +} + +.oc-cave-detail-flow { + display: grid; + grid-template-columns: minmax(0, .94fr) minmax(0, 1.06fr); + gap: clamp(60px, 8vw, 120px); + padding-top: 150px; + padding-bottom: 150px; +} + +.oc-cave-detail-flow figure { + position: sticky; + top: 98px; + height: 660px; + margin: 0; + overflow: hidden; + background: #d9ddd6; +} + +.oc-cave-detail-flow figure img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.8); +} + +.oc-cave-detail-flow-copy .oc-cave-detail-section-head > span, +.oc-cave-detail-routes .oc-cave-detail-section-head > span { + color: #657469; +} + +.oc-cave-detail-flow ol { + margin: 66px 0 0; + padding: 0; + list-style: none; + border-top: 1px solid rgba(32, 49, 39, .18); +} + +.oc-cave-detail-flow li { + display: grid; + grid-template-columns: 52px minmax(0, 1fr); + gap: 22px; + padding: 27px 0; + border-bottom: 1px solid rgba(32, 49, 39, .14); +} + +.oc-cave-detail-flow li > span { + color: #8c968e; + font-family: var(--oc-serif); + font-size: 22px; +} + +.oc-cave-detail-flow h3 { + margin: 0; + font-family: var(--oc-serif); + font-size: 22px; + font-weight: 400; +} + +.oc-cave-detail-flow li p { + margin: 9px 0 0; + color: #69716c; + font-size: 12px; + line-height: 1.75; +} + +.oc-cave-detail-safety { + padding: 135px 0 140px; + background: #e7e9e1; +} + +.oc-cave-detail-safety .oc-cave-detail-section-head { + max-width: 930px; +} + +.oc-cave-detail-safety .oc-cave-detail-section-head > span { + color: #647468; +} + +.oc-cave-detail-safety .oc-cave-detail-section-head > p { + max-width: 690px; + margin-top: 29px; +} + +.oc-cave-detail-gate-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + margin-top: 72px; + border-top: 1px solid rgba(32, 49, 39, .2); +} + +.oc-cave-detail-gate-grid article { + position: relative; + min-height: 310px; + padding: 31px 28px 34px; + border-right: 1px solid rgba(32, 49, 39, .16); +} + +.oc-cave-detail-gate-grid article:first-child { + border-left: 1px solid rgba(32, 49, 39, .16); +} + +.oc-cave-detail-gate-grid article > span { + color: #849087; + font-family: var(--oc-serif); + font-size: 21px; +} + +.oc-cave-detail-gate-grid article > svg { + position: absolute; + top: 30px; + right: 27px; + color: #617768; +} + +.oc-cave-detail-gate-grid h3 { + margin: 79px 0 18px; + font-family: var(--oc-serif); + font-size: 23px; + font-weight: 400; + line-height: 1.2; +} + +.oc-cave-detail-gate-grid p { + margin: 0; + color: #68726b; + font-size: 12px; + line-height: 1.75; +} + +.oc-cave-detail-boundary { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1px; + margin-top: 82px; + background: rgba(255, 255, 255, .52); + border: 1px solid rgba(32, 49, 39, .12); +} + +.oc-cave-detail-boundary > div { + padding: 44px 48px; + background: rgba(255, 255, 255, .52); +} + +.oc-cave-detail-boundary > div:last-child { + background: rgba(36, 55, 44, .94); + color: #f4f2e9; +} + +.oc-cave-detail-boundary span { + display: block; + margin-bottom: 27px; + font-size: 10px; + font-weight: 700; + letter-spacing: .14em; + text-transform: uppercase; +} + +.oc-cave-detail-boundary ul { + display: grid; + gap: 13px; + margin: 0; + padding-left: 18px; +} + +.oc-cave-detail-boundary li { + color: #526057; + font-size: 12px; + line-height: 1.7; +} + +.oc-cave-detail-boundary > div:last-child li { + color: rgba(244, 242, 233, .71); +} + +.oc-cave-detail-routes { + padding-top: 140px; + padding-bottom: 146px; +} + +.oc-cave-detail-routes .oc-cave-detail-section-head { + max-width: 950px; +} + +.oc-cave-detail-route-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 72px; + border-top: 1px solid rgba(32, 49, 39, .2); + border-bottom: 1px solid rgba(32, 49, 39, .2); +} + +.oc-cave-detail-route-grid a { + position: relative; + min-height: 275px; + padding: 29px 34px 34px; + color: var(--oc-cave-ink); + border-left: 1px solid rgba(32, 49, 39, .16); + transition: color .25s ease, background .25s ease; +} + +.oc-cave-detail-route-grid a:first-child { + border-left: 0; +} + +.oc-cave-detail-route-grid a:hover { + color: #fff; + background: var(--oc-cave-moss); +} + +.oc-cave-detail-route-grid a > span { + color: #8a958d; + font-family: var(--oc-serif); + font-size: 22px; +} + +.oc-cave-detail-route-grid h3 { + max-width: 320px; + margin: 70px 0 14px; + font-family: var(--oc-serif); + font-size: 25px; + font-weight: 400; +} + +.oc-cave-detail-route-grid p { + max-width: 340px; + margin: 0; + color: #6c756f; + font-size: 11px; + line-height: 1.75; + transition: color .25s ease; +} + +.oc-cave-detail-route-grid a:hover p { + color: rgba(255, 255, 255, .68); +} + +.oc-cave-detail-route-grid svg { + position: absolute; + top: 33px; + right: 30px; +} + +.oc-cave-detail > .oc-menu-bottom-prompt { + margin-bottom: 120px; +} + +@media (max-width: 1024px) { + .oc-cave-detail-hero, + .oc-cave-detail-hero-content { + min-height: 680px; + } + + .oc-cave-detail-overview, + .oc-cave-detail-levels .oc-cave-detail-section-head, + .oc-cave-detail-flow { + grid-template-columns: 1fr; + } + + .oc-cave-detail-overview { + gap: 64px; + } + + .oc-cave-detail-levels .oc-cave-detail-section-head > span { + grid-column: auto; + } + + .oc-cave-detail-levels .oc-cave-detail-section-head > p { + margin-top: 26px; + } + + .oc-cave-detail-level-grid { + grid-template-columns: 1fr; + } + + .oc-cave-detail-level-grid article, + .oc-cave-detail-level-grid article:first-child { + min-height: 0; + border-top: 1px solid rgba(255, 255, 255, .15); + border-left: 0; + } + + .oc-cave-detail-level-grid article:first-child { + border-top: 0; + } + + .oc-cave-detail-level-grid h3 { + min-height: 0; + margin-top: 45px; + } + + .oc-cave-detail-flow figure { + position: relative; + top: auto; + height: 580px; + } + + .oc-cave-detail-gate-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .oc-cave-detail-gate-grid article:nth-child(3) { + border-left: 1px solid rgba(32, 49, 39, .16); + } +} + +@media (max-width: 720px) { + .oc-cave-detail-hero, + .oc-cave-detail-hero-content { + min-height: 650px; + } + + .oc-cave-detail-hero > img { + object-position: 58% center; + } + + .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(9, 16, 12, .88) 0%, rgba(9, 16, 12, .2) 86%); + } + + .oc-cave-detail-hero-content { + padding-top: 92px; + padding-bottom: 58px; + } + + .oc-cave-detail-hero-copy h1 { + font-size: clamp(49px, 15vw, 72px); + } + + .oc-cave-detail-hero-copy p { + font-size: 15px; + } + + .oc-cave-detail-nav { + justify-content: flex-start; + gap: 26px; + min-height: 61px; + overflow-x: auto; + padding-right: 22px; + padding-left: 22px; + scrollbar-width: none; + } + + .oc-cave-detail-nav::-webkit-scrollbar { + display: none; + } + + .oc-cave-detail-nav a { + flex: 0 0 auto; + } + + .oc-cave-detail-overview, + .oc-cave-detail-flow, + .oc-cave-detail-levels, + .oc-cave-detail-safety, + .oc-cave-detail-routes { + padding-top: 82px; + padding-bottom: 88px; + } + + .oc-cave-detail-intro h2, + .oc-cave-detail-section-head h2 { + font-size: clamp(37px, 11vw, 52px); + } + + .oc-cave-detail-facts > div { + grid-template-columns: 96px minmax(0, 1fr); + gap: 15px; + } + + .oc-cave-detail-level-grid { + margin-top: 50px; + } + + .oc-cave-detail-level-grid article { + padding: 29px 4px 32px; + } + + .oc-cave-detail-level-grid h3 { + margin-top: 38px; + font-size: 27px; + } + + .oc-cave-detail-flow { + gap: 50px; + } + + .oc-cave-detail-flow figure { + height: 430px; + } + + .oc-cave-detail-flow ol { + margin-top: 48px; + } + + .oc-cave-detail-flow li { + grid-template-columns: 39px minmax(0, 1fr); + gap: 13px; + } + + .oc-cave-detail-gate-grid, + .oc-cave-detail-boundary, + .oc-cave-detail-route-grid { + grid-template-columns: 1fr; + } + + .oc-cave-detail-gate-grid article, + .oc-cave-detail-gate-grid article:first-child, + .oc-cave-detail-gate-grid article:nth-child(3) { + min-height: 250px; + border-top: 1px solid rgba(32, 49, 39, .16); + border-right: 1px solid rgba(32, 49, 39, .16); + border-left: 1px solid rgba(32, 49, 39, .16); + } + + .oc-cave-detail-gate-grid h3 { + margin-top: 54px; + } + + .oc-cave-detail-boundary { + margin-top: 55px; + } + + .oc-cave-detail-boundary > div { + padding: 34px 26px; + } + + .oc-cave-detail-route-grid a, + .oc-cave-detail-route-grid a:first-child { + min-height: 235px; + padding-right: 24px; + padding-left: 24px; + border-top: 1px solid rgba(32, 49, 39, .16); + border-left: 0; + } + + .oc-cave-detail-route-grid h3 { + margin-top: 52px; + } + + .oc-cave-detail > .oc-menu-bottom-prompt { + margin-bottom: 70px; + } +} + +/* Waterfall abseiling detail reuses the outdoor-detail grammar with a colder, wetter field palette. */ +.oc-waterfall-detail { + --oc-cave-ink: #142326; + --oc-cave-moss: #18383a; + --oc-cave-sage: #9cb9b8; + --oc-cave-paper: #eef2ee; + background: #f3f5f1; +} + +.oc-waterfall-detail .oc-cave-detail-hero > img { + object-position: center 47%; + filter: saturate(.78) contrast(.98) brightness(.79); +} + +.oc-waterfall-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(8, 22, 24, .84) 0%, rgba(8, 22, 24, .57) 42%, rgba(8, 22, 24, .08) 76%), + linear-gradient(0deg, rgba(8, 22, 24, .52) 0%, transparent 52%); +} + +.oc-waterfall-detail .oc-cave-detail-hero-copy { + max-width: 980px; +} + +.oc-waterfall-detail .oc-cave-detail-hero-copy h1 { + max-width: 960px; + font-size: clamp(50px, 6.25vw, 94px); +} + +.oc-waterfall-detail .oc-cave-detail-levels { + background: #18383a; +} + +.oc-waterfall-detail .oc-cave-detail-safety { + background: #e1e9e5; +} + +.oc-waterfall-detail-gallery { + display: grid; + grid-template-columns: minmax(0, 1.08fr) minmax(0, .92fr); + grid-template-rows: repeat(2, 330px); + gap: 14px; + padding-bottom: 145px; +} + +.oc-waterfall-detail-gallery figure { + margin: 0; + overflow: hidden; + background: #cdd9d5; +} + +.oc-waterfall-detail-gallery figure.is-tall { + grid-row: 1 / 3; +} + +.oc-waterfall-detail-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.84) contrast(.97); + transition: transform .7s ease; +} + +.oc-waterfall-detail-gallery figure:hover img { + transform: scale(1.018); +} + +.oc-waterfall-detail-gallery figure:nth-child(2) img { + object-position: center 47%; +} + +.oc-waterfall-detail-gallery figure:nth-child(3) img { + object-position: center 56%; +} + +.oc-waterfall-detail .oc-cave-detail-flow figure img { + object-position: center 54%; +} + +@media (max-width: 720px) { + .oc-waterfall-detail .oc-cave-detail-hero > img { + object-position: 55% center; + } + + .oc-waterfall-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(45px, 13.6vw, 62px); + } + + .oc-waterfall-detail-gallery { + grid-template-columns: 1fr; + grid-template-rows: 430px 300px 300px; + gap: 10px; + padding-bottom: 88px; + } + + .oc-waterfall-detail-gallery figure.is-tall { + grid-row: auto; + } +} + +/* Canyon river detail separates calm water, tracing, and white water within one consistent decision system. */ +.oc-river-detail { + --oc-cave-ink: #132b2c; + --oc-cave-moss: #173c3b; + --oc-cave-sage: #9fc5bd; + --oc-cave-paper: #eef4ef; + background: #f3f6f1; +} + +.oc-river-detail .oc-cave-detail-hero > img { + object-position: center 52%; + filter: saturate(.76) contrast(.96) brightness(.76); +} + +.oc-river-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(7, 25, 26, .88) 0%, rgba(7, 25, 26, .58) 44%, rgba(7, 25, 26, .08) 78%), + linear-gradient(0deg, rgba(7, 25, 26, .48) 0%, transparent 52%); +} + +.oc-river-detail .oc-cave-detail-hero-copy { + max-width: 1060px; +} + +.oc-river-detail .oc-cave-detail-hero-copy h1 { + max-width: 1030px; + font-size: clamp(48px, 5.55vw, 84px); + line-height: .95; +} + +.oc-river-detail .oc-cave-detail-hero-copy p { + max-width: 750px; +} + +.oc-river-detail .oc-cave-detail-levels { + background: #173c3b; +} + +.oc-river-detail .oc-cave-detail-safety { + background: #e3ece7; +} + +.oc-river-detail-gallery { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-rows: repeat(2, 320px); + gap: 12px; + padding-bottom: 145px; +} + +.oc-river-detail-gallery figure { + margin: 0; + overflow: hidden; + background: #ceddd7; +} + +.oc-river-detail-gallery figure:first-child { + grid-column: 1 / 3; +} + +.oc-river-detail-gallery figure:nth-child(2) { + grid-column: 3; + grid-row: 1 / 3; +} + +.oc-river-detail-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.82) contrast(.96); + transition: transform .7s ease; +} + +.oc-river-detail-gallery figure:hover img { + transform: scale(1.018); +} + +.oc-river-detail-gallery figure:first-child img { + object-position: center 54%; +} + +.oc-river-detail-gallery figure:nth-child(2) img { + object-position: center 54%; +} + +.oc-river-detail-gallery figure:nth-child(3) img { + object-position: center 64%; +} + +.oc-river-detail-gallery figure:nth-child(4) img { + object-position: center 58%; +} + +.oc-river-detail .oc-cave-detail-flow figure img { + object-position: center 60%; +} + +@media (max-width: 720px) { + .oc-river-detail .oc-cave-detail-hero > img { + object-position: 58% center; + } + + .oc-river-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(7, 25, 26, .9) 0%, rgba(7, 25, 26, .45) 62%, rgba(7, 25, 26, .08) 100%); + } + + .oc-river-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(41px, 11.8vw, 55px); + line-height: .96; + } + + .oc-river-detail-gallery { + grid-template-columns: 1fr; + grid-template-rows: 300px 430px 330px 330px; + gap: 10px; + padding-bottom: 88px; + } + + .oc-river-detail-gallery figure:first-child, + .oc-river-detail-gallery figure:nth-child(2) { + grid-column: auto; + grid-row: auto; + } +} + +/* Karst hiking and cycling uses a dry-land palette and separates three distant regions into distinct products. */ +.oc-hike-detail { + --oc-cave-ink: #263126; + --oc-cave-moss: #34452f; + --oc-cave-sage: #c2c997; + --oc-cave-paper: #f3f0e6; + background: #f6f3e9; +} + +.oc-hike-detail .oc-cave-detail-hero > img { + object-position: center 48%; + filter: saturate(.78) contrast(.96) brightness(.8); +} + +.oc-hike-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(24, 30, 20, .88) 0%, rgba(24, 30, 20, .55) 45%, rgba(24, 30, 20, .06) 78%), + linear-gradient(0deg, rgba(24, 30, 20, .5) 0%, transparent 54%); +} + +.oc-hike-detail .oc-cave-detail-hero-copy { + max-width: 1060px; +} + +.oc-hike-detail .oc-cave-detail-hero-copy h1 { + max-width: 1050px; + font-size: clamp(48px, 5.35vw, 82px); + line-height: .96; +} + +.oc-hike-detail .oc-cave-detail-hero-copy p { + max-width: 740px; +} + +.oc-hike-detail .oc-cave-detail-levels { + background: #34452f; +} + +.oc-hike-detail .oc-cave-detail-safety { + background: #ebe8da; +} + +.oc-hike-detail-gallery { + display: grid; + grid-template-columns: 1.1fr .9fr 1fr; + grid-template-rows: 640px; + gap: 12px; + padding-bottom: 145px; +} + +.oc-hike-detail-gallery figure { + margin: 0; + overflow: hidden; + background: #d8d7c1; +} + +.oc-hike-detail-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.82) contrast(.96); + transition: transform .7s ease; +} + +.oc-hike-detail-gallery figure:hover img { + transform: scale(1.018); +} + +.oc-hike-detail-gallery figure:first-child img { + object-position: center 55%; +} + +.oc-hike-detail-gallery figure:nth-child(2) img { + object-position: center 48%; +} + +.oc-hike-detail-gallery figure:nth-child(3) img { + object-position: center 56%; +} + +.oc-hike-detail .oc-cave-detail-flow figure img { + object-position: center 60%; +} + +@media (max-width: 720px) { + .oc-hike-detail .oc-cave-detail-hero > img { + object-position: 58% center; + } + + .oc-hike-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(24, 30, 20, .9) 0%, rgba(24, 30, 20, .42) 62%, rgba(24, 30, 20, .05) 100%); + } + + .oc-hike-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(40px, 11.4vw, 53px); + line-height: .98; + } + + .oc-hike-detail-gallery { + grid-template-columns: 1fr; + grid-template-rows: 410px 410px 460px; + gap: 10px; + padding-bottom: 88px; + } +} + +/* High-bridge review page is intentionally evidentiary rather than a direct-booking product page. */ +.oc-bridge-detail { + --oc-cave-ink: #2f2b27; + --oc-cave-moss: #4a2f27; + --oc-cave-sage: #d9a47e; + --oc-cave-paper: #f4eee8; + background: #f6f1eb; +} + +.oc-bridge-detail .oc-cave-detail-hero > img { + object-position: center 56%; + filter: saturate(.72) contrast(.98) brightness(.73); +} + +.oc-bridge-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(27, 20, 18, .91) 0%, rgba(37, 24, 20, .62) 47%, rgba(37, 24, 20, .08) 80%), + linear-gradient(0deg, rgba(28, 20, 18, .55) 0%, transparent 55%); +} + +.oc-bridge-detail .oc-cave-detail-hero-copy { + max-width: 1110px; +} + +.oc-bridge-detail .oc-cave-detail-hero-copy h1 { + max-width: 1080px; + font-size: clamp(47px, 5.25vw, 81px); + line-height: .96; +} + +.oc-bridge-detail .oc-cave-detail-hero-copy p { + max-width: 780px; +} + +.oc-bridge-detail .oc-cave-detail-levels { + background: #4a2f27; +} + +.oc-bridge-detail .oc-cave-detail-safety { + background: #eee5dc; +} + +.oc-bridge-detail-gallery { + display: grid; + grid-template-columns: .9fr 1.1fr 1fr; + grid-template-rows: 660px; + gap: 12px; + padding-bottom: 145px; +} + +.oc-bridge-detail-gallery figure { + margin: 0; + overflow: hidden; + background: #d8cbc2; +} + +.oc-bridge-detail-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.82) contrast(.98); + transition: transform .7s ease; +} + +.oc-bridge-detail-gallery figure:hover img { + transform: scale(1.018); +} + +.oc-bridge-detail-gallery figure:first-child img { + object-position: center 46%; +} + +.oc-bridge-detail-gallery figure:nth-child(2) img { + object-position: center 58%; +} + +.oc-bridge-detail-gallery figure:nth-child(3) img { + object-position: center 50%; +} + +.oc-bridge-detail .oc-cave-detail-flow figure img { + object-position: center 52%; +} + +.oc-bridge-detail-source { + max-width: 930px; + margin: 34px auto 0; + padding-top: 22px; + border-top: 1px solid rgba(78, 53, 43, .16); + color: #776960; + font-size: 10px; + line-height: 1.75; + text-align: center; +} + +@media (max-width: 720px) { + .oc-bridge-detail .oc-cave-detail-hero > img { + object-position: 51% center; + } + + .oc-bridge-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(27, 20, 18, .92) 0%, rgba(37, 24, 20, .52) 64%, rgba(37, 24, 20, .08) 100%); + } + + .oc-bridge-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(38px, 10.8vw, 51px); + line-height: .99; + } + + .oc-bridge-detail-gallery { + grid-template-columns: 1fr; + grid-template-rows: 430px 480px 430px; + gap: 10px; + padding-bottom: 88px; + } +} + +/* Cave hospitality uses warm mineral tones while keeping venue and operating boundaries explicit. */ +.oc-cave-luxury-detail { + --oc-cave-ink: #302923; + --oc-cave-moss: #4a392c; + --oc-cave-sage: #d1ad79; + --oc-cave-paper: #f4eee5; + background: #f7f2e9; +} + +.oc-cave-luxury-detail .oc-cave-detail-hero > img { + object-position: center 58%; + filter: saturate(.76) contrast(.98) brightness(.74); +} + +.oc-cave-luxury-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(24, 18, 14, .92) 0%, rgba(36, 27, 20, .59) 48%, rgba(36, 27, 20, .08) 82%), + linear-gradient(0deg, rgba(25, 19, 15, .55) 0%, transparent 55%); +} + +.oc-cave-luxury-detail .oc-cave-detail-hero-copy { + max-width: 1120px; +} + +.oc-cave-luxury-detail .oc-cave-detail-hero-copy h1 { + max-width: 1080px; + font-size: clamp(45px, 5.1vw, 78px); + line-height: .97; +} + +.oc-cave-luxury-detail .oc-cave-detail-hero-copy p { + max-width: 760px; +} + +.oc-cave-luxury-detail .oc-cave-detail-levels { + background: #4a392c; +} + +.oc-cave-luxury-detail .oc-cave-detail-safety { + background: #eee5d8; +} + +.oc-cave-luxury-gallery { + display: grid; + grid-template-columns: .95fr .9fr 1.15fr; + grid-template-rows: 650px; + gap: 12px; + padding-bottom: 145px; +} + +.oc-cave-luxury-gallery figure { + margin: 0; + overflow: hidden; + background: #d7cbbb; +} + +.oc-cave-luxury-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.82) contrast(.98); + transition: transform .7s ease; +} + +.oc-cave-luxury-gallery figure:hover img { + transform: scale(1.018); +} + +.oc-cave-luxury-gallery figure:first-child img { + object-position: center 58%; +} + +.oc-cave-luxury-gallery figure:nth-child(2) img { + object-position: center 52%; +} + +.oc-cave-luxury-gallery figure:nth-child(3) img { + object-position: center 58%; +} + +.oc-cave-luxury-detail .oc-cave-detail-flow figure img { + object-position: center 54%; +} + +@media (max-width: 720px) { + .oc-cave-luxury-detail .oc-cave-detail-hero > img { + object-position: 58% center; + } + + .oc-cave-luxury-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(24, 18, 14, .93) 0%, rgba(36, 27, 20, .5) 64%, rgba(36, 27, 20, .08) 100%); + } + + .oc-cave-luxury-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(37px, 10.5vw, 50px); + line-height: .99; + } + + .oc-cave-luxury-gallery { + grid-template-columns: 1fr; + grid-template-rows: 460px 420px 460px; + gap: 10px; + padding-bottom: 88px; + } +} + +/* Mountain-view coffee keeps a grounded café palette and makes site authority more important than exclusivity claims. */ +.oc-wild-coffee-detail { + --oc-cave-ink: #283229; + --oc-cave-moss: #344438; + --oc-cave-sage: #c7b78e; + --oc-cave-paper: #f2efe5; + background: #f6f3eb; +} + +.oc-wild-coffee-detail .oc-cave-detail-hero > img { + object-position: center 48%; + filter: saturate(.78) contrast(.97) brightness(.77); +} + +.oc-wild-coffee-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(19, 27, 21, .9) 0%, rgba(27, 37, 29, .58) 47%, rgba(27, 37, 29, .07) 80%), + linear-gradient(0deg, rgba(20, 29, 22, .51) 0%, transparent 56%); +} + +.oc-wild-coffee-detail .oc-cave-detail-hero-copy { + max-width: 1100px; +} + +.oc-wild-coffee-detail .oc-cave-detail-hero-copy h1 { + max-width: 1030px; + font-size: clamp(48px, 5.35vw, 82px); + line-height: .96; +} + +.oc-wild-coffee-detail .oc-cave-detail-hero-copy p { + max-width: 760px; +} + +.oc-wild-coffee-detail .oc-cave-detail-levels { + background: #344438; +} + +.oc-wild-coffee-detail .oc-cave-detail-safety { + background: #e7e8df; +} + +.oc-wild-coffee-gallery { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-rows: repeat(2, 315px); + gap: 12px; + padding-bottom: 145px; +} + +.oc-wild-coffee-gallery figure { + margin: 0; + overflow: hidden; + background: #ccd2c5; +} + +.oc-wild-coffee-gallery figure:first-child { + grid-row: 1 / 3; +} + +.oc-wild-coffee-gallery figure:nth-child(2) { + grid-column: 2 / 4; +} + +.oc-wild-coffee-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.82) contrast(.97); + transition: transform .7s ease; +} + +.oc-wild-coffee-gallery figure:hover img { + transform: scale(1.018); +} + +.oc-wild-coffee-gallery figure:first-child img { + object-position: center 54%; +} + +.oc-wild-coffee-gallery figure:nth-child(2) img { + object-position: center 50%; +} + +.oc-wild-coffee-gallery figure:nth-child(3) img { + object-position: center 42%; +} + +.oc-wild-coffee-gallery figure:nth-child(4) img { + object-position: center 52%; +} + +.oc-wild-coffee-detail .oc-cave-detail-flow figure img { + object-position: center 58%; +} + +@media (max-width: 720px) { + .oc-wild-coffee-detail .oc-cave-detail-hero > img { + object-position: 54% center; + } + + .oc-wild-coffee-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(19, 27, 21, .92) 0%, rgba(27, 37, 29, .48) 64%, rgba(27, 37, 29, .06) 100%); + } + + .oc-wild-coffee-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(40px, 11.2vw, 53px); + line-height: .98; + } + + .oc-wild-coffee-gallery { + grid-template-columns: 1fr; + grid-template-rows: 450px 420px 390px 450px; + gap: 10px; + padding-bottom: 88px; + } + + .oc-wild-coffee-gallery figure:first-child, + .oc-wild-coffee-gallery figure:nth-child(2) { + grid-column: auto; + grid-row: auto; + } +} + +/* Village immersion uses timber, indigo, and field tones while keeping consent and hosting evidence visible. */ +.oc-village-immersion-detail { + --oc-cave-ink: #2b2e2b; + --oc-cave-moss: #343d39; + --oc-cave-sage: #b9aa87; + --oc-cave-paper: #f1ede3; + background: #f5f1e9; +} + +.oc-village-immersion-detail .oc-cave-detail-hero > img { + object-position: center 48%; + filter: saturate(.68) contrast(.99) brightness(.68); +} + +.oc-village-immersion-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(24, 26, 25, .9) 0%, rgba(32, 34, 32, .6) 47%, rgba(32, 34, 32, .08) 81%), + linear-gradient(0deg, rgba(28, 27, 24, .5) 0%, transparent 56%); +} + +.oc-village-immersion-detail .oc-cave-detail-hero-copy { + max-width: 1160px; +} + +.oc-village-immersion-detail .oc-cave-detail-hero-copy h1 { + max-width: 1110px; + font-size: clamp(46px, 5.15vw, 80px); + line-height: .97; +} + +.oc-village-immersion-detail .oc-cave-detail-hero-copy p { + max-width: 780px; +} + +.oc-village-immersion-detail .oc-cave-detail-levels { + background: #343d39; +} + +.oc-village-immersion-detail .oc-cave-detail-safety { + background: #e9e3d8; +} + +.oc-village-immersion-gallery { + display: grid; + grid-template-columns: 1.04fr .96fr .96fr; + grid-template-rows: repeat(2, 315px); + gap: 12px; + padding-bottom: 145px; +} + +.oc-village-immersion-gallery figure { + position: relative; + margin: 0; + overflow: hidden; + background: #cac2b5; +} + +.oc-village-immersion-gallery figcaption { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + padding: 34px 18px 15px; + background: linear-gradient(0deg, rgba(26, 29, 27, .84), rgba(26, 29, 27, 0)); + color: rgba(255, 255, 255, .88); + font-size: 10px; + line-height: 1.55; + letter-spacing: .035em; +} + +.oc-village-immersion-gallery figure:first-child { + grid-row: 1 / 3; +} + +.oc-village-immersion-gallery figure:nth-child(2) { + grid-column: 2 / 4; +} + +.oc-village-immersion-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.76) contrast(.98); + transition: transform .7s ease; +} + +.oc-village-immersion-gallery figure:hover img { + transform: scale(1.018); +} + +.oc-village-immersion-gallery figure:first-child img { + object-position: center 49%; +} + +.oc-village-immersion-gallery figure:nth-child(2) img { + object-position: center 55%; +} + +.oc-village-immersion-gallery figure:nth-child(3) img { + object-position: center 54%; +} + +.oc-village-immersion-gallery figure:nth-child(4) img { + object-position: center 61%; +} + +.oc-village-immersion-detail .oc-cave-detail-flow figure img { + object-position: center 52%; +} + +.oc-village-detail-source { + max-width: 940px; + margin: 34px auto 0; + padding-top: 22px; + border-top: 1px solid rgba(57, 52, 44, .16); + color: #756d62; + font-size: 10px; + line-height: 1.75; + text-align: center; +} + +@media (max-width: 720px) { + .oc-village-immersion-detail .oc-cave-detail-hero > img { + object-position: 51% center; + } + + .oc-village-immersion-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(24, 26, 25, .94) 0%, rgba(32, 34, 32, .5) 65%, rgba(32, 34, 32, .07) 100%); + } + + .oc-village-immersion-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(37px, 10.3vw, 49px); + line-height: .99; + } + + .oc-village-immersion-gallery { + grid-template-columns: 1fr; + grid-template-rows: 470px 420px 430px 460px; + gap: 10px; + padding-bottom: 88px; + } + + .oc-village-immersion-gallery figure:first-child, + .oc-village-immersion-gallery figure:nth-child(2) { + grid-column: auto; + grid-row: auto; + } +} + +/* Festival detail keeps ceremonial colour restrained and foregrounds annual verification. */ +.oc-festival-detail { + --oc-cave-ink: #302720; + --oc-cave-moss: #49342d; + --oc-cave-sage: #c3a57d; + --oc-cave-paper: #f3eee4; + background: #f7f2e9; +} + +.oc-festival-detail .oc-cave-detail-hero > img { + object-position: center 42%; + filter: saturate(.66) contrast(1.02) brightness(.62); +} + +.oc-festival-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(35, 24, 20, .92) 0%, rgba(47, 31, 26, .64) 48%, rgba(47, 31, 26, .1) 82%), + linear-gradient(0deg, rgba(36, 24, 20, .56) 0%, transparent 58%); +} + +.oc-festival-detail .oc-cave-detail-hero-copy { + max-width: 1120px; +} + +.oc-festival-detail .oc-cave-detail-hero-copy h1 { + max-width: 1080px; + font-size: clamp(49px, 6vw, 92px); +} + +.oc-festival-detail .oc-cave-detail-levels { + background: #49342d; +} + +.oc-festival-detail .oc-cave-detail-level-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.oc-festival-detail .oc-cave-detail-level-grid article { + min-height: 440px; + border-bottom: 1px solid rgba(255, 255, 255, .18); +} + +.oc-festival-detail .oc-cave-detail-level-grid article:nth-child(odd) { + border-left: 0; +} + +.oc-festival-detail .oc-cave-detail-level-grid h3 { + min-height: 76px; +} + +.oc-festival-detail .oc-cave-detail-safety { + background: #ece3d7; +} + +.oc-festival-gallery figure:first-child img { + object-position: center 34%; +} + +.oc-festival-gallery figure:nth-child(2) img { + object-position: center 48%; +} + +.oc-festival-gallery figure:nth-child(3) img, +.oc-festival-gallery figure:nth-child(4) img { + object-position: center 34%; +} + +.oc-festival-detail .oc-cave-detail-flow figure img { + object-position: center 28%; +} + +@media (max-width: 1024px) { + .oc-festival-detail .oc-cave-detail-level-grid { + grid-template-columns: 1fr; + } + + .oc-festival-detail .oc-cave-detail-level-grid article, + .oc-festival-detail .oc-cave-detail-level-grid article:nth-child(odd) { + min-height: 0; + border-left: 0; + } +} + +@media (max-width: 720px) { + .oc-festival-detail .oc-cave-detail-hero > img { + object-position: 47% center; + } + + .oc-festival-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(35, 24, 20, .96) 0%, rgba(47, 31, 26, .48) 68%, rgba(47, 31, 26, .1) 100%); + } + + .oc-festival-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(40px, 11vw, 54px); + } +} + +/* Culinary detail uses warm clay and tea tones while keeping food and hosting checks explicit. */ +.oc-culinary-detail { + --oc-cave-ink: #302a24; + --oc-cave-moss: #443229; + --oc-cave-sage: #c59b68; + --oc-cave-paper: #f3eee5; + background: #f8f3ea; +} + +.oc-culinary-detail .oc-cave-detail-hero > img { + object-position: center 58%; + filter: saturate(.72) contrast(1.02) brightness(.62); +} + +.oc-culinary-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(38, 26, 20, .94) 0%, rgba(50, 35, 27, .68) 48%, rgba(50, 35, 27, .08) 84%), + linear-gradient(0deg, rgba(39, 26, 20, .5) 0%, transparent 58%); +} + +.oc-culinary-detail .oc-cave-detail-hero-copy { + max-width: 1160px; +} + +.oc-culinary-detail .oc-cave-detail-hero-copy h1 { + max-width: 1120px; + font-size: clamp(48px, 6vw, 90px); +} + +.oc-culinary-detail .oc-cave-detail-levels { + background: #443229; +} + +.oc-culinary-detail .oc-cave-detail-level-grid article { + min-height: 510px; +} + +.oc-culinary-detail .oc-cave-detail-safety { + background: #eee3d4; +} + +.oc-culinary-gallery figure:first-child img, +.oc-culinary-gallery figure:nth-child(2) img, +.oc-culinary-gallery figure:nth-child(3) img { + object-position: center 52%; +} + +.oc-culinary-gallery figure:nth-child(4) img, +.oc-culinary-detail .oc-cave-detail-flow figure img { + object-position: center 42%; +} + +.oc-culinary-street-food { + background: #e9ddcd; + padding: 135px 0 145px; +} + +.oc-culinary-street-food .oc-cave-detail-section-head { + max-width: 930px; +} + +.oc-culinary-street-food .oc-cave-detail-section-head > span { + color: #8b6748; +} + +.oc-culinary-street-food-grid { + border-bottom: 1px solid rgba(69, 48, 36, .16); + border-top: 1px solid rgba(69, 48, 36, .16); + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 70px; +} + +.oc-culinary-street-food-grid article { + display: flex; + flex-direction: column; + min-height: 475px; + padding: 48px clamp(28px, 4.5vw, 64px) 44px; +} + +.oc-culinary-street-food-grid article:nth-child(even) { + border-left: 1px solid rgba(69, 48, 36, .16); +} + +.oc-culinary-street-food-grid article:nth-child(n+3) { + border-top: 1px solid rgba(69, 48, 36, .16); +} + +.oc-culinary-street-food-grid article:last-child { + grid-column: 1 / -1; + min-height: 390px; +} + +.oc-culinary-street-food-grid article > div { + align-items: center; + color: #806c5d; + display: flex; + font-size: 10px; + font-weight: 800; + gap: 18px; + letter-spacing: .08em; + text-transform: uppercase; +} + +.oc-culinary-street-food-grid article > div span { + color: #b07a51; +} + +.oc-culinary-street-food-grid h3 { + color: #342b25; + font-family: var(--oc-serif); + font-size: clamp(32px, 3.2vw, 48px); + font-weight: 400; + letter-spacing: -.035em; + line-height: 1.04; + margin: 44px 0 0; +} + +.oc-culinary-street-food-grid p { + color: #65594f; + font-size: 15px; + line-height: 1.72; + margin: 25px 0 0; +} + +.oc-culinary-street-food-grid small { + border-top: 1px solid rgba(69, 48, 36, .14); + color: #7a6657; + display: block; + font-size: 11px; + line-height: 1.6; + margin-top: auto; + padding-top: 23px; +} + +.oc-culinary-market-note { + align-items: start; + background: #443229; + color: #fff; + display: grid; + gap: clamp(38px, 7vw, 100px); + grid-template-columns: minmax(260px, .72fr) minmax(0, 1.28fr); + margin-top: 70px; + padding: clamp(42px, 5vw, 70px); +} + +.oc-culinary-market-note span { + color: #d0a87d; + display: block; + font-size: 10px; + font-weight: 800; + letter-spacing: .16em; + text-transform: uppercase; +} + +.oc-culinary-market-note h3 { + font-family: var(--oc-serif); + font-size: clamp(32px, 3vw, 46px); + font-weight: 400; + letter-spacing: -.035em; + line-height: 1.05; + margin: 24px 0 0; +} + +.oc-culinary-market-note p { + color: rgba(255, 255, 255, .7); + font-size: 15px; + line-height: 1.75; + margin: 0; +} + +@media (max-width: 900px) { + .oc-culinary-street-food-grid, + .oc-culinary-market-note { + grid-template-columns: 1fr; + } + + .oc-culinary-street-food-grid article, + .oc-culinary-street-food-grid article:last-child { + grid-column: auto; + min-height: 0; + } + + .oc-culinary-street-food-grid article:nth-child(even) { + border-left: 0; + } + + .oc-culinary-street-food-grid article:nth-child(n+2) { + border-top: 1px solid rgba(69, 48, 36, .16); + } +} + +@media (max-width: 720px) { + .oc-culinary-detail .oc-cave-detail-hero > img { + object-position: 58% center; + } + + .oc-culinary-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(38, 26, 20, .97) 0%, rgba(50, 35, 27, .52) 70%, rgba(50, 35, 27, .1) 100%); + } + + .oc-culinary-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(38px, 10.5vw, 52px); + } + + .oc-culinary-street-food { + padding: 88px 0; + } + + .oc-culinary-street-food-grid { + margin-top: 48px; + } + + .oc-culinary-street-food-grid article { + padding: 40px 8px; + } + + .oc-culinary-street-food-grid h3 { + margin-top: 34px; + } + + .oc-culinary-street-food-grid small { + margin-top: 28px; + } + + .oc-culinary-market-note { + gap: 28px; + margin-top: 48px; + padding: 38px 24px 42px; + } +} + +/* Waterfall clusters use a cool mineral palette and foreground live access over fixed postcard promises. */ +.oc-iconic-waterfall-detail { + --oc-cave-ink: #20302e; + --oc-cave-moss: #24413d; + --oc-cave-sage: #9cc5bd; + --oc-cave-paper: #edf3f0; + background: #f2f6f3; +} + +.oc-iconic-waterfall-detail .oc-cave-detail-hero > img { + object-position: center 43%; + filter: saturate(.78) contrast(1.04) brightness(.64); +} + +.oc-iconic-waterfall-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(16, 31, 30, .94) 0%, rgba(22, 48, 45, .65) 50%, rgba(22, 48, 45, .08) 85%), + linear-gradient(0deg, rgba(14, 31, 29, .52) 0%, transparent 60%); +} + +.oc-iconic-waterfall-detail .oc-cave-detail-hero-copy { + max-width: 1180px; +} + +.oc-iconic-waterfall-detail .oc-cave-detail-hero-copy h1 { + max-width: 1140px; + font-size: clamp(48px, 6vw, 90px); +} + +.oc-iconic-waterfall-detail .oc-cave-detail-levels { + background: #24413d; +} + +.oc-iconic-waterfall-detail .oc-cave-detail-level-grid article { + min-height: 530px; +} + +.oc-iconic-waterfall-detail .oc-cave-detail-safety { + background: #e2ece8; +} + +.oc-iconic-waterfall-gallery figure:first-child img, +.oc-iconic-waterfall-gallery figure:nth-child(2) img { + object-position: center 40%; +} + +.oc-iconic-waterfall-gallery figure:nth-child(3) img { + object-position: center 48%; +} + +.oc-iconic-waterfall-gallery figure:nth-child(4) img, +.oc-iconic-waterfall-detail .oc-cave-detail-flow figure img { + object-position: center 35%; +} + +@media (max-width: 720px) { + .oc-iconic-waterfall-detail .oc-cave-detail-hero > img { + object-position: 52% center; + } + + .oc-iconic-waterfall-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(16, 31, 30, .97) 0%, rgba(22, 48, 45, .5) 70%, rgba(22, 48, 45, .08) 100%); + } + + .oc-iconic-waterfall-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(38px, 10.5vw, 52px); + } +} + +/* Village comparison uses timber, indigo, and rice-field tones while separating public visits from community hosting. */ +.oc-indigenous-villages-detail { + --oc-cave-ink: #2f342d; + --oc-cave-moss: #344a40; + --oc-cave-sage: #b9b17f; + --oc-cave-paper: #f1efe7; + background: #f6f3ec; +} + +.oc-indigenous-villages-detail .oc-cave-detail-hero > img { + object-position: center 48%; + filter: saturate(.72) contrast(1.02) brightness(.63); +} + +.oc-indigenous-villages-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(22, 29, 25, .94) 0%, rgba(33, 50, 43, .65) 52%, rgba(34, 50, 43, .08) 85%), + linear-gradient(0deg, rgba(20, 28, 24, .52) 0%, transparent 60%); +} + +.oc-indigenous-villages-detail .oc-cave-detail-hero-copy { + max-width: 1180px; +} + +.oc-indigenous-villages-detail .oc-cave-detail-hero-copy h1 { + max-width: 1180px; + font-size: clamp(47px, 5.8vw, 88px); +} + +.oc-indigenous-villages-detail .oc-cave-detail-levels { + background: #344a40; +} + +.oc-indigenous-villages-detail .oc-cave-detail-level-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.oc-indigenous-villages-detail .oc-cave-detail-level-grid article { + min-height: 510px; +} + +.oc-indigenous-villages-detail .oc-cave-detail-level-grid article:nth-child(3) { + border-top: 1px solid rgba(255, 255, 255, .18); + border-left: 0; +} + +.oc-indigenous-villages-detail .oc-cave-detail-level-grid article:nth-child(4) { + border-top: 1px solid rgba(255, 255, 255, .18); +} + +.oc-indigenous-villages-detail .oc-cave-detail-safety { + background: #e8e4d8; +} + +.oc-indigenous-villages-detail .oc-cave-detail-route-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.oc-indigenous-villages-detail .oc-cave-detail-route-grid a:nth-child(3) { + border-top: 1px solid rgba(32, 49, 39, .16); + border-left: 0; +} + +.oc-indigenous-villages-detail .oc-cave-detail-route-grid a:nth-child(4) { + border-top: 1px solid rgba(32, 49, 39, .16); +} + +.oc-indigenous-villages-gallery figure:first-child img { + object-position: center 58%; +} + +.oc-indigenous-villages-gallery figure:nth-child(2) img { + object-position: center 47%; +} + +.oc-indigenous-villages-gallery figure:nth-child(3) img, +.oc-indigenous-villages-gallery figure:nth-child(4) img, +.oc-indigenous-villages-detail .oc-cave-detail-flow figure img { + object-position: center 44%; +} + +@media (max-width: 1024px) { + .oc-indigenous-villages-detail .oc-cave-detail-level-grid { + grid-template-columns: 1fr; + } + + .oc-indigenous-villages-detail .oc-cave-detail-level-grid article { + min-height: 0; + } + + .oc-indigenous-villages-detail .oc-cave-detail-level-grid article:nth-child(4) { + border-left: 0; + } +} + +@media (max-width: 720px) { + .oc-indigenous-villages-detail .oc-cave-detail-hero > img { + object-position: 50% center; + } + + .oc-indigenous-villages-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(22, 29, 25, .97) 0%, rgba(33, 50, 43, .53) 70%, rgba(33, 50, 43, .08) 100%); + } + + .oc-indigenous-villages-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(37px, 10.2vw, 50px); + } + + .oc-indigenous-villages-detail .oc-cave-detail-route-grid { + grid-template-columns: 1fr; + } + + .oc-indigenous-villages-detail .oc-cave-detail-route-grid a:nth-child(4) { + border-left: 0; + } +} + +/* Karst glamping uses a dusk palette while making shelter and operations more prominent than star promises. */ +.oc-historic-towns-detail { + --oc-cave-ink: #332f2a; + --oc-cave-moss: #493b32; + --oc-cave-sage: #c4aa79; + --oc-cave-paper: #f1ece3; + background: #f6f1e9; +} + +.oc-historic-towns-detail .oc-cave-detail-hero > img { + object-position: center 42%; + filter: saturate(.64) contrast(1.04) brightness(.61); +} + +.oc-historic-towns-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(24, 21, 19, .95) 0%, rgba(53, 39, 31, .66) 52%, rgba(53, 39, 31, .08) 84%), + linear-gradient(0deg, rgba(27, 21, 18, .58) 0%, transparent 62%); +} + +.oc-historic-towns-detail .oc-cave-detail-hero-copy { + max-width: 1200px; +} + +.oc-historic-towns-detail .oc-cave-detail-hero-copy h1 { + max-width: 1180px; + font-size: clamp(46px, 5.7vw, 86px); +} + +.oc-historic-towns-detail .oc-cave-detail-levels { + background: #493b32; +} + +.oc-historic-towns-detail .oc-cave-detail-level-grid article { + min-height: 550px; +} + +.oc-historic-towns-detail .oc-cave-detail-safety { + background: #e9e0d3; +} + +.oc-historic-towns-gallery figure:first-child img { + object-position: center 50%; +} + +.oc-historic-towns-gallery figure:nth-child(2) img, +.oc-historic-towns-detail .oc-cave-detail-flow figure img { + object-position: center 44%; +} + +.oc-historic-towns-gallery figure:nth-child(3) img, +.oc-historic-towns-gallery figure:nth-child(4) img { + object-position: center 48%; +} + +@media (max-width: 720px) { + .oc-historic-towns-detail .oc-cave-detail-hero > img { + object-position: 48% center; + } + + .oc-historic-towns-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(24, 21, 19, .97) 0%, rgba(53, 39, 31, .54) 70%, rgba(53, 39, 31, .08) 100%); + } + + .oc-historic-towns-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(37px, 10vw, 50px); + } +} + +/* Qingyan's battle page separates a live 2026 field product from the brief's unbuilt old-town roleplay. */ +.oc-qingyan-battle-detail { + --oc-cave-ink: #302b25; + --oc-cave-moss: #42362d; + --oc-cave-sage: #bd9d67; + --oc-cave-paper: #f1ece2; + background: #f5f1e8; +} + +.oc-qingyan-battle-detail .oc-cave-detail-hero > img { + object-position: center 43%; + filter: saturate(.58) contrast(1.05) brightness(.56); +} + +.oc-qingyan-battle-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(24, 21, 18, .96) 0%, rgba(49, 39, 31, .7) 54%, rgba(49, 39, 31, .1) 86%), + linear-gradient(0deg, rgba(23, 19, 16, .62) 0%, transparent 62%); +} + +.oc-qingyan-battle-detail .oc-cave-detail-hero-copy { + max-width: 1210px; +} + +.oc-qingyan-battle-detail .oc-cave-detail-hero-copy h1 { + max-width: 1200px; + font-size: clamp(45px, 5.45vw, 84px); + line-height: .96; +} + +.oc-qingyan-battle-detail .oc-cave-detail-hero-copy p { + max-width: 810px; +} + +.oc-qingyan-battle-detail .oc-cave-detail-levels { + background: #42362d; +} + +.oc-qingyan-battle-detail .oc-cave-detail-level-grid article { + min-height: 590px; +} + +.oc-qingyan-battle-detail .oc-cave-detail-safety { + background: #e9e0d2; +} + +.oc-qingyan-battle-context { + display: grid; + grid-template-columns: 1.08fr .92fr; + grid-template-rows: 680px; + gap: 12px; + padding-bottom: 145px; +} + +.oc-qingyan-battle-context figure { + background: #d8cdbf; + margin: 0; + overflow: hidden; + position: relative; +} + +.oc-qingyan-battle-context img { + display: block; + filter: saturate(.7) contrast(1.02); + height: 100%; + object-fit: cover; + transition: transform .7s ease; + width: 100%; +} + +.oc-qingyan-battle-context figure:first-child img { + object-position: center 35%; +} + +.oc-qingyan-battle-context figure:nth-child(2) img, +.oc-qingyan-battle-detail .oc-cave-detail-flow figure img { + object-position: center 30%; +} + +.oc-qingyan-battle-context figure:hover img { + transform: scale(1.018); +} + +.oc-qingyan-battle-context figcaption { + background: linear-gradient(0deg, rgba(21, 18, 15, .86), rgba(21, 18, 15, .02)); + bottom: 0; + color: rgba(255, 255, 255, .86); + font-size: 11px; + left: 0; + line-height: 1.55; + padding: 70px 26px 22px; + position: absolute; + right: 0; +} + +.oc-qingyan-battle-source { + border-top: 1px solid rgba(72, 55, 43, .17); + color: #75685e; + font-size: 10px; + line-height: 1.75; + margin: 34px auto 0; + max-width: 990px; + padding-top: 22px; + text-align: center; +} + +@media (max-width: 720px) { + .oc-qingyan-battle-detail .oc-cave-detail-hero > img { + object-position: 48% center; + } + + .oc-qingyan-battle-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(24, 21, 18, .97) 0%, rgba(49, 39, 31, .58) 70%, rgba(49, 39, 31, .08) 100%); + } + + .oc-qingyan-battle-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(36px, 9.8vw, 49px); + } + + .oc-qingyan-battle-context { + grid-template-columns: 1fr; + grid-template-rows: 410px 430px; + gap: 10px; + padding-bottom: 88px; + } +} + +/* Loushan Pass pairs a vivid live show with a restrained evidence-first mountain route review. */ +.oc-loushanguan-detail { + --oc-cave-ink: #2f2a24; + --oc-cave-moss: #433229; + --oc-cave-sage: #c29a67; + --oc-cave-paper: #f2ede3; + background: #f6f1e8; +} + +.oc-loushanguan-detail .oc-cave-detail-hero > img { + object-position: center 26%; + filter: saturate(.64) contrast(1.08) brightness(.52); +} + +.oc-loushanguan-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(25, 20, 17, .97) 0%, rgba(55, 37, 29, .72) 54%, rgba(55, 37, 29, .1) 86%), + linear-gradient(0deg, rgba(25, 19, 16, .68) 0%, transparent 64%); +} + +.oc-loushanguan-detail .oc-cave-detail-hero-copy { + max-width: 1240px; +} + +.oc-loushanguan-detail .oc-cave-detail-hero-copy h1 { + max-width: 1210px; + font-size: clamp(45px, 5.35vw, 82px); + line-height: .96; +} + +.oc-loushanguan-detail .oc-cave-detail-hero-copy p { + max-width: 835px; +} + +.oc-loushanguan-detail .oc-cave-detail-levels { + background: #433229; +} + +.oc-loushanguan-detail .oc-cave-detail-level-grid article { + min-height: 605px; +} + +.oc-loushanguan-detail .oc-cave-detail-safety { + background: #e9dfd1; +} + +.oc-loushanguan-evidence { + display: grid; + grid-template-columns: minmax(0, 1.08fr) minmax(420px, .92fr); + min-height: 720px; + padding-bottom: 145px; +} + +.oc-loushanguan-evidence figure { + margin: 0; + min-height: 720px; + overflow: hidden; + position: relative; +} + +.oc-loushanguan-evidence figure img { + display: block; + filter: saturate(.68) contrast(1.04); + height: 100%; + object-fit: cover; + object-position: center 23%; + width: 100%; +} + +.oc-loushanguan-evidence figcaption { + background: linear-gradient(0deg, rgba(23, 18, 15, .9), rgba(23, 18, 15, .02)); + bottom: 0; + color: rgba(255, 255, 255, .86); + font-size: 11px; + left: 0; + line-height: 1.55; + padding: 80px 28px 24px; + position: absolute; + right: 0; +} + +.oc-loushanguan-evidence aside { + align-content: center; + background: #e8ddce; + display: grid; + padding: clamp(48px, 6.5vw, 98px); +} + +.oc-loushanguan-evidence aside > span { + color: #82684e; + font-size: 10px; + font-weight: 800; + letter-spacing: .18em; + margin-bottom: 25px; + text-transform: uppercase; +} + +.oc-loushanguan-evidence aside h2 { + color: #342b24; + font-family: var(--oc-serif); + font-size: clamp(35px, 3.8vw, 57px); + font-weight: 400; + letter-spacing: -.04em; + line-height: 1.03; + margin: 0; +} + +.oc-loushanguan-evidence aside p { + color: #665a50; + font-size: 15px; + line-height: 1.75; + margin: 30px 0 0; +} + +.oc-loushanguan-detail .oc-cave-detail-flow figure img { + object-position: center 24%; +} + +.oc-loushanguan-source { + border-top: 1px solid rgba(72, 51, 39, .17); + color: #75675d; + font-size: 10px; + line-height: 1.75; + margin: 34px auto 0; + max-width: 1000px; + padding-top: 22px; + text-align: center; +} + +@media (max-width: 720px) { + .oc-loushanguan-detail .oc-cave-detail-hero > img { + object-position: 38% 18%; + } + + .oc-loushanguan-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(25, 20, 17, .98) 0%, rgba(55, 37, 29, .62) 72%, rgba(55, 37, 29, .12) 100%); + } + + .oc-loushanguan-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(35px, 9.55vw, 48px); + } + + .oc-loushanguan-evidence { + grid-template-columns: 1fr; + min-height: 0; + padding-bottom: 88px; + } + + .oc-loushanguan-evidence figure { + min-height: 520px; + } + + .oc-loushanguan-evidence aside { + padding: 56px 26px 62px; + } +} + +/* Karst glamping uses a dusk palette while making shelter and operations more prominent than star promises. */ +.oc-karst-glamping-detail { + --oc-cave-ink: #222c32; + --oc-cave-moss: #25343d; + --oc-cave-sage: #b9a979; + --oc-cave-paper: #edf0ed; + background: #f2f3f0; +} + +.oc-karst-glamping-detail .oc-cave-detail-hero > img { + object-position: center 55%; + filter: saturate(.7) contrast(1.02) brightness(.66); +} + +.oc-karst-glamping-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(13, 22, 28, .91) 0%, rgba(20, 31, 38, .62) 48%, rgba(20, 31, 38, .09) 82%), + linear-gradient(0deg, rgba(14, 25, 31, .57) 0%, transparent 58%); +} + +.oc-karst-glamping-detail .oc-cave-detail-hero-copy { + max-width: 1160px; +} + +.oc-karst-glamping-detail .oc-cave-detail-hero-copy h1 { + max-width: 1120px; + font-size: clamp(45px, 5.05vw, 78px); + line-height: .97; +} + +.oc-karst-glamping-detail .oc-cave-detail-hero-copy p { + max-width: 760px; +} + +.oc-karst-glamping-detail .oc-cave-detail-levels { + background: #25343d; +} + +.oc-karst-glamping-detail .oc-cave-detail-safety { + background: #e5e8e5; +} + +.oc-karst-glamping-gallery { + display: grid; + grid-template-columns: 1.02fr .98fr .98fr; + grid-template-rows: repeat(2, 315px); + gap: 12px; + padding-bottom: 145px; +} + +.oc-karst-glamping-gallery figure { + margin: 0; + overflow: hidden; + background: #bcc5c8; +} + +.oc-karst-glamping-gallery figure:first-child { + grid-row: 1 / 3; +} + +.oc-karst-glamping-gallery figure:nth-child(2) { + grid-column: 2 / 4; +} + +.oc-karst-glamping-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.78) contrast(.98); + transition: transform .7s ease; +} + +.oc-karst-glamping-gallery figure:hover img { + transform: scale(1.018); +} + +.oc-karst-glamping-gallery figure:first-child img { + object-position: center 54%; +} + +.oc-karst-glamping-gallery figure:nth-child(2) img { + object-position: center 46%; +} + +.oc-karst-glamping-gallery figure:nth-child(3) img { + object-position: center 53%; +} + +.oc-karst-glamping-gallery figure:nth-child(4) img { + object-position: center 45%; +} + +.oc-karst-glamping-detail .oc-cave-detail-flow figure img { + object-position: center 54%; +} + +.oc-glamping-detail-source { + max-width: 940px; + margin: 34px auto 0; + padding-top: 22px; + border-top: 1px solid rgba(39, 53, 60, .16); + color: #68747a; + font-size: 10px; + line-height: 1.75; + text-align: center; +} + +@media (max-width: 720px) { + .oc-karst-glamping-detail .oc-cave-detail-hero > img { + object-position: 51% center; + } + + .oc-karst-glamping-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(13, 22, 28, .95) 0%, rgba(20, 31, 38, .53) 66%, rgba(20, 31, 38, .08) 100%); + } + + .oc-karst-glamping-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(36px, 9.9vw, 48px); + line-height: .99; + } + + .oc-karst-glamping-gallery { + grid-template-columns: 1fr; + grid-template-rows: 470px 420px 430px 460px; + gap: 10px; + padding-bottom: 88px; + } + + .oc-karst-glamping-gallery figure:first-child, + .oc-karst-glamping-gallery figure:nth-child(2) { + grid-column: auto; + grid-row: auto; + } +} + +/* Heritage-outdoor pairings balance indigo craft detail with green field context and label concept imagery explicitly. */ +.oc-heritage-outdoor-detail { + --oc-cave-ink: #28302d; + --oc-cave-moss: #293a36; + --oc-cave-sage: #b8a273; + --oc-cave-paper: #f1eee6; + background: #f5f2eb; +} + +.oc-heritage-outdoor-detail .oc-cave-detail-hero > img { + object-position: center 58%; + filter: saturate(.72) contrast(.99) brightness(.66); +} + +.oc-heritage-outdoor-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(18, 28, 25, .91) 0%, rgba(27, 39, 35, .61) 47%, rgba(27, 39, 35, .08) 81%), + linear-gradient(0deg, rgba(20, 31, 27, .55) 0%, transparent 58%); +} + +.oc-heritage-outdoor-detail .oc-cave-detail-hero-copy { + max-width: 1160px; +} + +.oc-heritage-outdoor-detail .oc-cave-detail-hero-copy h1 { + max-width: 1120px; + font-size: clamp(45px, 5.05vw, 78px); + line-height: .97; +} + +.oc-heritage-outdoor-detail .oc-cave-detail-hero-copy p { + max-width: 790px; +} + +.oc-heritage-outdoor-detail .oc-cave-detail-levels { + background: #293a36; +} + +.oc-heritage-outdoor-detail .oc-cave-detail-safety { + background: #e9e5dc; +} + +.oc-heritage-outdoor-gallery { + display: grid; + grid-template-columns: 1.02fr .98fr .98fr; + grid-template-rows: repeat(2, 315px); + gap: 12px; + padding-bottom: 145px; +} + +.oc-heritage-outdoor-gallery figure { + position: relative; + margin: 0; + overflow: hidden; + background: #c8c3b8; +} + +.oc-heritage-outdoor-gallery figure:first-child { + grid-row: 1 / 3; +} + +.oc-heritage-outdoor-gallery figure:nth-child(2) { + grid-column: 2 / 4; +} + +.oc-heritage-outdoor-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(.78) contrast(.98); + transition: transform .7s ease; +} + +.oc-heritage-outdoor-gallery figure:hover img { + transform: scale(1.018); +} + +.oc-heritage-outdoor-gallery figure:first-child img { + object-position: center 59%; +} + +.oc-heritage-outdoor-gallery figure:nth-child(2) img { + object-position: center 52%; +} + +.oc-heritage-outdoor-gallery figure:nth-child(3) img { + object-position: center 48%; +} + +.oc-heritage-outdoor-gallery figure:nth-child(4) img { + object-position: center 57%; +} + +.oc-heritage-outdoor-gallery figcaption { + position: absolute; + right: 14px; + bottom: 14px; + left: 14px; + padding: 11px 13px; + background: rgba(25, 31, 34, .78); + color: rgba(255, 255, 255, .86); + font-size: 9px; + font-weight: 700; + line-height: 1.4; + letter-spacing: .08em; + text-transform: uppercase; + backdrop-filter: blur(7px); +} + +.oc-heritage-outdoor-detail .oc-cave-detail-flow figure img { + object-position: center 53%; +} + +.oc-heritage-outdoor-source { + max-width: 950px; + margin: 34px auto 0; + padding-top: 22px; + border-top: 1px solid rgba(45, 54, 50, .16); + color: #716d65; + font-size: 10px; + line-height: 1.75; + text-align: center; +} + +@media (max-width: 720px) { + .oc-heritage-outdoor-detail .oc-cave-detail-hero > img { + object-position: 53% center; + } + + .oc-heritage-outdoor-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(18, 28, 25, .95) 0%, rgba(27, 39, 35, .53) 66%, rgba(27, 39, 35, .08) 100%); + } + + .oc-heritage-outdoor-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(36px, 9.8vw, 48px); + line-height: .99; + } + + .oc-heritage-outdoor-gallery { + grid-template-columns: 1fr; + grid-template-rows: 470px 420px 450px 470px; + gap: 10px; + padding-bottom: 88px; + } + + .oc-heritage-outdoor-gallery figure:first-child, + .oc-heritage-outdoor-gallery figure:nth-child(2) { + grid-column: auto; + grid-row: auto; + } +} + +/* Miao craft detail keeps the proven information architecture while shifting the visual language to indigo, cloth, and studio light. */ +.oc-miao-craft-detail { + --oc-cave-ink: #27302d; + --oc-cave-moss: #29444a; + --oc-cave-sage: #c8ae76; + --oc-cave-paper: #f2eee5; + background: #f7f3eb; +} + +.oc-miao-craft-detail .oc-cave-detail-hero > img { + object-position: center 49%; + filter: saturate(.7) contrast(.98) brightness(.64); +} + +.oc-miao-craft-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(20, 30, 30, .93) 0%, rgba(26, 44, 46, .67) 47%, rgba(26, 44, 46, .1) 82%), + linear-gradient(0deg, rgba(20, 30, 30, .54) 0%, transparent 59%); +} + +.oc-miao-craft-detail .oc-cave-detail-hero-copy { + max-width: 1060px; +} + +.oc-miao-craft-detail .oc-cave-detail-hero-copy h1 { + max-width: 1010px; + font-size: clamp(48px, 5.7vw, 86px); + line-height: .96; +} + +.oc-miao-craft-detail .oc-cave-detail-hero-copy p { + max-width: 760px; +} + +.oc-miao-craft-detail .oc-cave-detail-levels { + background: #29444a; +} + +.oc-miao-craft-detail .oc-cave-detail-safety { + background: #ebe6dc; +} + +.oc-miao-craft-gallery figure:first-child img { + object-position: center 42%; +} + +.oc-miao-craft-gallery figure:nth-child(2) img { + object-position: center 55%; +} + +.oc-miao-craft-gallery figure:nth-child(3) img { + object-position: center 43%; +} + +.oc-miao-craft-gallery figure:nth-child(4) img { + object-position: center 50%; +} + +.oc-miao-craft-detail .oc-cave-detail-flow figure img { + object-position: center 42%; +} + +@media (max-width: 720px) { + .oc-miao-craft-detail .oc-cave-detail-hero > img { + object-position: 52% center; + } + + .oc-miao-craft-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(19, 31, 32, .96) 0%, rgba(26, 44, 46, .58) 68%, rgba(26, 44, 46, .08) 100%); + } + + .oc-miao-craft-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(38px, 10.2vw, 52px); + line-height: .99; + } +} + +/* Dong craft detail uses dark timber, mist green, and muted indigo while keeping every pictured place explicitly provisional. */ +.oc-dong-craft-detail { + --oc-cave-ink: #292f2b; + --oc-cave-moss: #2f3d37; + --oc-cave-sage: #b7a675; + --oc-cave-paper: #f0ede5; + background: #f5f2eb; +} + +.oc-dong-craft-detail .oc-cave-detail-hero > img { + object-position: center 52%; + filter: saturate(.67) contrast(.99) brightness(.63); +} + +.oc-dong-craft-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(18, 28, 24, .93) 0%, rgba(28, 43, 36, .68) 48%, rgba(28, 43, 36, .08) 82%), + linear-gradient(0deg, rgba(17, 28, 23, .57) 0%, transparent 60%); +} + +.oc-dong-craft-detail .oc-cave-detail-hero-copy { + max-width: 1120px; +} + +.oc-dong-craft-detail .oc-cave-detail-hero-copy h1 { + max-width: 1100px; + font-size: clamp(43px, 5.05vw, 76px); + line-height: .97; +} + +.oc-dong-craft-detail .oc-cave-detail-hero-copy p { + max-width: 820px; +} + +.oc-dong-craft-detail .oc-cave-detail-levels { + background: #2f3d37; +} + +.oc-dong-craft-detail .oc-cave-detail-safety { + background: #e9e5dc; +} + +.oc-dong-craft-gallery figure:first-child img { + object-position: center 54%; +} + +.oc-dong-craft-gallery figure:nth-child(2) img { + object-position: center 48%; +} + +.oc-dong-craft-gallery figure:nth-child(3) img { + object-position: center 45%; +} + +.oc-dong-craft-gallery figure:nth-child(4) img { + object-position: center 51%; +} + +.oc-dong-craft-detail .oc-cave-detail-flow figure img { + object-position: center 49%; +} + +@media (max-width: 720px) { + .oc-dong-craft-detail .oc-cave-detail-hero > img { + object-position: 55% center; + } + + .oc-dong-craft-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(18, 29, 24, .96) 0%, rgba(30, 45, 38, .6) 69%, rgba(30, 45, 38, .08) 100%); + } + + .oc-dong-craft-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(34px, 9.2vw, 46px); + line-height: 1; + } +} + +/* Shui horsetail embroidery detail uses a quiet silk-and-earth palette while explicitly treating the available photography as general craft reference. */ +.oc-shui-embroidery-detail { + --oc-cave-ink: #332e31; + --oc-cave-moss: #45383d; + --oc-cave-sage: #b89c72; + --oc-cave-paper: #f1ece4; + background: #f6f2eb; +} + +.oc-shui-embroidery-detail .oc-cave-detail-hero > img { + object-position: center 55%; + filter: saturate(.58) contrast(.97) brightness(.61); +} + +.oc-shui-embroidery-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(31, 24, 28, .94) 0%, rgba(58, 45, 51, .69) 47%, rgba(58, 45, 51, .08) 82%), + linear-gradient(0deg, rgba(34, 26, 30, .57) 0%, transparent 60%); +} + +.oc-shui-embroidery-detail .oc-cave-detail-hero-copy { + max-width: 1060px; +} + +.oc-shui-embroidery-detail .oc-cave-detail-hero-copy h1 { + max-width: 1040px; + font-size: clamp(47px, 5.45vw, 82px); + line-height: .96; +} + +.oc-shui-embroidery-detail .oc-cave-detail-hero-copy p { + max-width: 770px; +} + +.oc-shui-embroidery-detail .oc-cave-detail-levels { + background: #45383d; +} + +.oc-shui-embroidery-detail .oc-cave-detail-safety { + background: #eae3da; +} + +.oc-shui-embroidery-gallery { + grid-template-columns: 1.08fr .92fr; + grid-template-rows: 590px; +} + +.oc-shui-embroidery-gallery figure:first-child, +.oc-shui-embroidery-gallery figure:nth-child(2) { + grid-column: auto; + grid-row: auto; +} + +.oc-shui-embroidery-gallery figure:first-child img { + object-position: center 43%; +} + +.oc-shui-embroidery-gallery figure:nth-child(2) img { + object-position: center 52%; +} + +.oc-shui-embroidery-detail .oc-cave-detail-flow figure img { + object-position: center 43%; +} + +@media (max-width: 720px) { + .oc-shui-embroidery-detail .oc-cave-detail-hero > img { + object-position: 47% center; + } + + .oc-shui-embroidery-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(32, 24, 29, .96) 0%, rgba(61, 47, 54, .61) 69%, rgba(61, 47, 54, .08) 100%); + } + + .oc-shui-embroidery-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(37px, 9.8vw, 49px); + line-height: .99; + } + + .oc-shui-embroidery-gallery { + grid-template-columns: 1fr; + grid-template-rows: 470px 500px; + gap: 10px; + padding-bottom: 88px; + } +} + +/* Buyi craft detail pairs stone grey, muted indigo, and warm fibre tones while keeping all available imagery explicitly provisional. */ +.oc-buyi-craft-detail { + --oc-cave-ink: #303235; + --oc-cave-moss: #3c4547; + --oc-cave-sage: #b6a075; + --oc-cave-paper: #f0ede7; + background: #f5f2ec; +} + +.oc-buyi-craft-detail .oc-cave-detail-hero > img { + object-position: center 48%; + filter: saturate(.55) contrast(.98) brightness(.6); +} + +.oc-buyi-craft-detail .oc-cave-detail-shade { + background: + linear-gradient(90deg, rgba(25, 29, 31, .94) 0%, rgba(46, 56, 59, .7) 48%, rgba(46, 56, 59, .08) 82%), + linear-gradient(0deg, rgba(28, 32, 34, .58) 0%, transparent 60%); +} + +.oc-buyi-craft-detail .oc-cave-detail-hero-copy { + max-width: 1080px; +} + +.oc-buyi-craft-detail .oc-cave-detail-hero-copy h1 { + max-width: 1040px; + font-size: clamp(45px, 5.25vw, 80px); + line-height: .97; +} + +.oc-buyi-craft-detail .oc-cave-detail-hero-copy p { + max-width: 780px; +} + +.oc-buyi-craft-detail .oc-cave-detail-levels { + background: #3c4547; +} + +.oc-buyi-craft-detail .oc-cave-detail-safety { + background: #e7e4de; +} + +.oc-buyi-craft-gallery figure:first-child img { + object-position: center 51%; +} + +.oc-buyi-craft-gallery figure:nth-child(2) img { + object-position: center 48%; +} + +.oc-buyi-craft-gallery figure:nth-child(3) img { + object-position: center 52%; +} + +.oc-buyi-craft-gallery figure:nth-child(4) img { + object-position: center 43%; +} + +.oc-buyi-craft-detail .oc-cave-detail-flow figure img { + object-position: center 48%; +} + +@media (max-width: 720px) { + .oc-buyi-craft-detail .oc-cave-detail-hero > img { + object-position: 53% center; + } + + .oc-buyi-craft-detail .oc-cave-detail-shade { + background: linear-gradient(0deg, rgba(25, 29, 31, .96) 0%, rgba(47, 57, 60, .62) 69%, rgba(47, 57, 60, .08) 100%); + } + + .oc-buyi-craft-detail .oc-cave-detail-hero-copy h1 { + font-size: clamp(35px, 9.25vw, 47px); + line-height: 1; + } +} + +/* Signature Experiences archive — curated series instead of a flat filter wall. */ +.oc-experience-editorial { + background: #f2f0e9; +} + +.oc-experience-editorial .oc-menu-page-hero { + padding-bottom: 118px; +} + +.oc-experience-series-nav { + position: relative; + z-index: 3; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 18px; + margin-top: -54px; + padding-bottom: 112px; +} + +.oc-experience-series-nav > a { + position: relative; + min-height: 310px; + display: grid; + grid-template-columns: 44px minmax(0, 1fr) 24px; + gap: 18px; + align-items: end; + overflow: hidden; + padding: 30px; + color: #fff; +} + +.oc-experience-series-nav > a::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(180deg, rgba(18, 35, 24, 0.04) 20%, rgba(17, 31, 22, 0.9) 100%); +} + +.oc-experience-series-nav img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(0.76) contrast(0.96); + transition: transform 0.7s ease; +} + +.oc-experience-series-nav > a:hover img { + transform: scale(1.025); +} + +.oc-experience-series-nav > a > span, +.oc-experience-series-nav > a > div, +.oc-experience-series-nav > a > svg { + position: relative; + z-index: 1; +} + +.oc-experience-series-nav > a > span { + color: rgba(255, 255, 255, 0.7); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.12em; +} + +.oc-experience-series-nav b, +.oc-experience-series-nav small { + display: block; +} + +.oc-experience-series-nav b { + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(23px, 2.2vw, 32px); + font-weight: 400; + line-height: 1.2; +} + +.oc-experience-series-nav small { + margin-top: 8px; + color: rgba(255, 255, 255, 0.68); + font-size: 11px; + line-height: 1.5; +} + +.oc-experience-series-nav svg { + align-self: center; +} + +.oc-experience-series-head { + display: grid; + grid-template-columns: minmax(0, 1.15fr) minmax(280px, 0.7fr); + gap: 72px; + align-items: end; + margin-bottom: 54px; +} + +.oc-experience-series-head > div { + display: grid; + gap: 16px; +} + +.oc-experience-series-head span { + color: #78857b; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.oc-experience-series-head h2 { + max-width: 720px; + color: #304037; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(34px, 4.2vw, 58px); + font-weight: 400; + line-height: 1.08; +} + +.oc-experience-series-head > p { + color: #667069; + font-size: 13px; + line-height: 1.75; +} + +.oc-experience-outdoor { + padding: 118px 0 124px; + background: #172a1e; +} + +.oc-experience-outdoor .oc-experience-series-head span, +.oc-experience-outdoor .oc-experience-series-head > p { + color: rgba(229, 238, 225, 0.62); +} + +.oc-experience-outdoor .oc-experience-series-head h2 { + color: #f0f4ec; +} + +.oc-experience-outdoor-grid { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 18px; +} + +.oc-experience-outdoor-grid article { + min-width: 0; + grid-column: span 2; + display: grid; + grid-template-rows: 260px minmax(0, 1fr); + background: #23362a; +} + +.oc-experience-outdoor-grid article.is-featured { + grid-column: 1 / -1; + grid-template-columns: minmax(0, 1.28fr) minmax(330px, 0.72fr); + grid-template-rows: minmax(440px, auto); +} + +.oc-experience-card-media { + display: block; + min-height: 0; + overflow: hidden; +} + +.oc-experience-card-media img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(0.7) contrast(1.02); + transition: transform 0.7s ease; +} + +.oc-experience-card-media:hover img { + transform: scale(1.025); +} + +.oc-experience-card-copy { + display: flex; + flex-direction: column; + min-width: 0; + padding: 28px 26px 30px; +} + +.oc-experience-card-copy > span { + color: #a9c19f; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.12em; +} + +.oc-experience-card-copy > small { + margin-top: 20px; + color: rgba(232, 240, 227, 0.48); + font-size: 9px; + line-height: 1.5; + text-transform: uppercase; +} + +.oc-experience-card-copy h3 { + margin-top: 18px; + color: #f1f4ed; + font-family: Georgia, 'Times New Roman', serif; + font-size: 25px; + font-weight: 400; + line-height: 1.25; +} + +.oc-experience-card-copy h4 { + margin-top: 10px; + color: #a9bca6; + font-size: 11px; + font-weight: 500; + line-height: 1.5; +} + +.oc-experience-card-copy p { + margin-top: 17px; + color: rgba(232, 240, 227, 0.67); + font-size: 12px; + line-height: 1.7; +} + +.oc-experience-card-copy > a { + width: fit-content; + display: inline-flex; + align-items: center; + gap: 8px; + margin-top: auto; + padding-top: 28px; + color: #dce9d7; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.oc-experience-verification-note { + display: grid; + grid-template-columns: 48px minmax(0, 1fr); + gap: 24px; + margin-top: 56px; + padding: 34px 36px; + border: 1px solid rgba(225, 238, 219, 0.17); + color: #e7eee3; +} + +.oc-experience-verification-note > svg { + color: #aac3a1; +} + +.oc-experience-verification-note span { + color: #a9c19f; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.13em; + text-transform: uppercase; +} + +.oc-experience-verification-note h3 { + margin-top: 12px; + font-family: Georgia, 'Times New Roman', serif; + font-size: 23px; + font-weight: 400; +} + +.oc-experience-verification-note p { + max-width: 880px; + margin-top: 10px; + color: rgba(232, 240, 227, 0.6); + font-size: 11px; + line-height: 1.66; +} + +.oc-experience-verification-note a { + width: fit-content; + display: inline-flex; + align-items: center; + gap: 8px; + margin-top: 18px; + color: #dce9d7; + font-size: 10px; + font-weight: 700; + letter-spacing: .08em; + text-transform: uppercase; +} + +.oc-experience-local { + padding-top: 122px; + padding-bottom: 126px; +} + +.oc-experience-local-grid { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + gap: 18px; +} + +.oc-experience-local-grid article { + min-width: 0; + grid-column: span 5; + display: grid; + grid-template-rows: 330px minmax(0, 1fr); + background: #e8e2d6; +} + +.oc-experience-local-grid article.is-wide { + grid-column: span 7; +} + +.oc-experience-local-grid article:last-child { + grid-column: 1 / -1; + grid-template-columns: minmax(0, 1.2fr) minmax(330px, 0.8fr); + grid-template-rows: minmax(390px, auto); +} + +.oc-experience-local-grid article > img { + width: 100%; + height: 100%; + object-fit: cover; + filter: saturate(0.76) contrast(0.96); +} + +.oc-experience-local-grid article > div { + display: flex; + flex-direction: column; + padding: 28px 27px 30px; +} + +.oc-experience-local-grid article span { + color: #7a806f; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.12em; +} + +.oc-experience-local-grid article small { + margin-top: 20px; + color: #828177; + font-size: 9px; + line-height: 1.5; + text-transform: uppercase; +} + +.oc-experience-local-grid article h3 { + margin-top: 18px; + color: #38443b; + font-family: Georgia, 'Times New Roman', serif; + font-size: 25px; + font-weight: 400; + line-height: 1.25; +} + +.oc-experience-local-grid article p { + margin-top: 16px; + color: #66685f; + font-size: 12px; + line-height: 1.7; +} + +.oc-experience-local-grid article a { + width: fit-content; + display: inline-flex; + align-items: center; + gap: 8px; + margin-top: auto; + padding-top: 26px; + color: #3d5b45; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.07em; + text-transform: uppercase; +} + +.oc-experience-landscapes { + border-top: 1px solid rgba(38, 64, 57, .12); + background: #e5ece7; +} + +.oc-experience-landscapes .oc-experience-local-grid article { + background: #d7e2dc; +} + +.oc-experience-landscapes .oc-experience-local-grid article > img { + object-position: center 42%; +} + +.oc-experience-route-pairing { + padding: 104px 0 112px; + background: #dfe5db; +} + +.oc-experience-pairing-head { + display: grid; + grid-template-columns: minmax(180px, 0.45fr) minmax(0, 1.5fr); + gap: 46px; + align-items: end; + margin-bottom: 45px; +} + +.oc-experience-pairing-head span { + color: #748078; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.13em; + text-transform: uppercase; +} + +.oc-experience-pairing-head h2 { + color: #35443a; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(31px, 3.5vw, 48px); + font-weight: 400; + line-height: 1.15; +} + +.oc-experience-pairing-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + border-top: 1px solid rgba(48, 68, 55, 0.22); + border-left: 1px solid rgba(48, 68, 55, 0.15); +} + +.oc-experience-pairing-grid a { + min-height: 235px; + display: grid; + grid-template-rows: auto auto minmax(0, 1fr) auto; + padding: 26px 24px 28px; + border-right: 1px solid rgba(48, 68, 55, 0.15); + border-bottom: 1px solid rgba(48, 68, 55, 0.15); + color: #334238; +} + +.oc-experience-pairing-grid a > span { + color: #78867b; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.1em; +} + +.oc-experience-pairing-grid h3 { + margin-top: 24px; + font-family: Georgia, 'Times New Roman', serif; + font-size: 22px; + font-weight: 400; +} + +.oc-experience-pairing-grid p { + margin-top: 13px; + color: #667069; + font-size: 11px; + line-height: 1.65; +} + +.oc-experience-pairing-grid svg { + margin-top: 20px; +} + +@media (max-width: 900px) { + .oc-experience-series-head { + grid-template-columns: 1fr; + gap: 24px; + } + + .oc-experience-outdoor-grid article, + .oc-experience-outdoor-grid article.is-featured { + grid-column: span 3; + grid-template-columns: 1fr; + grid-template-rows: 290px minmax(0, 1fr); + } + + .oc-experience-local-grid article, + .oc-experience-local-grid article.is-wide { + grid-column: span 6; + } + + .oc-experience-local-grid article:last-child { + grid-column: 1 / -1; + } +} + +@media (max-width: 640px) { + .oc-experience-editorial .oc-menu-page-hero { + padding-bottom: 88px; + } + + .oc-experience-series-nav { + grid-template-columns: 1fr; + margin-top: -30px; + padding-bottom: 76px; + } + + .oc-experience-series-nav > a { + min-height: 245px; + grid-template-columns: 28px minmax(0, 1fr) 20px; + gap: 12px; + padding: 24px 20px; + } + + .oc-experience-outdoor, + .oc-experience-local { + padding-top: 78px; + padding-bottom: 82px; + } + + .oc-experience-series-head { + margin-bottom: 36px; + } + + .oc-experience-outdoor-grid, + .oc-experience-local-grid, + .oc-experience-pairing-grid { + grid-template-columns: 1fr; + } + + .oc-experience-outdoor-grid article, + .oc-experience-outdoor-grid article.is-featured, + .oc-experience-local-grid article, + .oc-experience-local-grid article.is-wide, + .oc-experience-local-grid article:last-child { + grid-column: auto; + grid-template-columns: 1fr; + grid-template-rows: 245px minmax(0, 1fr); + } + + .oc-experience-verification-note { + grid-template-columns: 1fr; + padding: 28px 24px; + } + + .oc-experience-route-pairing { + padding: 76px 0 82px; + } + + .oc-experience-pairing-head { + grid-template-columns: 1fr; + gap: 16px; + margin-bottom: 32px; + } + + .oc-experience-pairing-grid a { + min-height: 0; + } +} + .oc-site a { color: inherit; text-decoration: none; @@ -461,7 +4477,7 @@ a.oc-route-service-panel, .oc-home-safari-image, .oc-info-band-image, -.oc-experience-band > img, +.oc-experience-band-media img, .oc-special-menu-image img, .oc-safari-card-image img, .oc-archive-card-image > img, @@ -478,7 +4494,7 @@ a.oc-route-service-panel, .oc-home-safari:hover .oc-home-safari-image, .oc-info-band:hover .oc-info-band-image, -.oc-experience-band:hover > img, +.oc-experience-band:hover .oc-experience-band-media img, .oc-special-menu-card:hover .oc-special-menu-image img, .oc-safari-card:hover .oc-safari-card-image img, .oc-archive-card:hover .oc-archive-card-image > img, @@ -921,17 +4937,15 @@ a.oc-route-service-panel, .oc-home-hero { position: relative; - height: 80vh; + height: calc(100svh - 91px); min-height: 720px; - background-position: center; - background-size: cover; display: grid; - align-items: end; - text-align: center; - color: #fff; + grid-template-rows: minmax(420px, 59%) minmax(300px, 41%); + overflow: hidden; + background: #f2f0e8; + color: #20241f; } -.oc-home-hero::after, .oc-image-hero::after { content: ''; position: absolute; @@ -940,6 +4954,29 @@ a.oc-route-service-panel, pointer-events: none; } +.oc-hero-media { + grid-row: 1; + width: 100%; + height: 100%; + min-height: 0; + object-fit: cover; + object-position: center 52%; + background-color: #dfe3dd; + filter: saturate(0.7) contrast(0.93) brightness(1.04); + animation: oc-hero-image-in 700ms ease both; +} + +@keyframes oc-hero-image-in { + from { + opacity: 0.65; + transform: scale(1.012); + } + to { + opacity: 1; + transform: scale(1); + } +} + .oc-awards { position: absolute; top: 62px; @@ -958,26 +4995,51 @@ a.oc-route-service-panel, .oc-hero-dots { position: absolute; - top: 76px; - left: 54px; + right: clamp(44px, 5vw, 76px); + bottom: 60px; z-index: 1; display: flex; align-items: center; - gap: 10px; + gap: 16px; } -.oc-hero-dots button { +.oc-hero-dot { width: 10px; height: 10px; - border: 1px solid rgba(255, 255, 255, 0.86); + padding: 0; + border: 1px solid #667668; border-radius: 999px; background: transparent; cursor: pointer; + transition: background-color 180ms ease, border-color 180ms ease, transform 180ms ease; } -.oc-hero-dots button.is-active { - width: 30px; - background: #fff; +.oc-hero-dot.is-active { + background: #48624c; + border-color: #48624c; +} + +.oc-hero-dot:hover { + transform: scale(1.18); +} + +.oc-hero-dot:focus-visible, +.oc-hero-playback:focus-visible { + outline: 2px solid #48624c; + outline-offset: 4px; +} + +.oc-hero-playback { + width: 28px; + height: 28px; + display: grid; + place-items: center; + margin-left: 4px; + padding: 0; + border: 0; + background: transparent; + color: #48624c; + cursor: pointer; } .oc-trip { @@ -992,30 +5054,243 @@ a.oc-route-service-panel, .oc-hero-copy { position: relative; z-index: 1; - padding: 0 24px 88px; + grid-row: 2; + display: grid; + align-content: start; + justify-items: start; + padding: 32px clamp(44px, 5vw, 76px) 38px; + background: #f2f0e8; + color: #20241f; + text-align: left; } .oc-hero-copy p { - font-size: 16px; - line-height: 28.8px; - letter-spacing: 1px; - margin-bottom: 19px; - opacity: 0.9; + margin-bottom: 18px; + color: #48624c; + font-size: 14px; + font-weight: 500; + line-height: 1.5; + letter-spacing: 0.08em; } .oc-hero-copy h1 { - font-size: 50px; - line-height: 60px; - text-shadow: 0 3px 16px rgba(0, 0, 0, 0.22); + max-width: min(1000px, calc(100% - 180px)); + margin-bottom: 16px; + color: #20241f; + font-size: clamp(46px, 3.9vw, 60px); + line-height: 1.16; + letter-spacing: 0.01em; + text-wrap: balance; +} + +.oc-hero-copy p.oc-hero-summary { margin-bottom: 26px; + color: #3f4740; + font-size: 17px; + font-weight: 400; + line-height: 1.65; + letter-spacing: 0.02em; } .oc-hero-copy .oc-orange-btn { - min-width: 168px; - height: 45px; + min-width: 176px; + height: 48px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 12px; + padding: 0 24px; + background: #48624c; + box-shadow: none; font-size: 16px; } +.oc-hero-copy .oc-orange-btn:hover { + background: #394f3d; + box-shadow: none; +} + +.oc-home-trust { + border-top: 1px solid #e2e3dc; + border-bottom: 1px solid #e2e3dc; + background: #faf9f4; +} + +.oc-home-trust-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + padding-top: 28px; + padding-bottom: 28px; +} + +.oc-home-trust-item { + min-width: 0; + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 14px; + align-items: start; + padding: 0 26px; + color: #48624c; +} + +.oc-home-trust-item:first-child { + padding-left: 0; +} + +.oc-home-trust-item:last-child { + padding-right: 0; +} + +.oc-home-trust-item + .oc-home-trust-item { + border-left: 1px solid #dedfd7; +} + +.oc-home-trust-item strong, +.oc-home-trust-item span { + display: block; +} + +.oc-home-trust-item strong { + margin-bottom: 6px; + color: #243128; + font-family: Georgia, 'Times New Roman', serif; + font-size: 17px; + font-weight: 500; + line-height: 1.25; +} + +.oc-home-trust-item span { + color: #69716a; + font-size: 12px; + line-height: 1.55; +} + +.oc-home-brand-story { + padding: 112px 0 104px; + background: #f2f0e9; +} + +.oc-brand-story-layout { + display: grid; + grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); + gap: clamp(64px, 8vw, 112px); + align-items: center; +} + +.oc-brand-story-media { + position: relative; + aspect-ratio: 4 / 5; + margin: 0; + overflow: hidden; + background: #d8dbd2; +} + +.oc-brand-story-media::after { + content: ''; + position: absolute; + inset: 0; + border: 1px solid rgba(37, 49, 40, 0.1); + pointer-events: none; +} + +.oc-brand-story-media img { + width: 100%; + height: 100%; + display: block; + object-fit: cover; + object-position: center; + filter: saturate(0.72) contrast(0.94); +} + +.oc-brand-story-copy { + max-width: 570px; +} + +.oc-brand-eyebrow { + display: inline-flex; + align-items: center; + gap: 14px; + margin-bottom: 28px; + color: #55705b; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.18em; + text-transform: uppercase; +} + +.oc-brand-eyebrow::before { + content: ''; + width: 44px; + height: 1px; + background: #718275; +} + +.oc-brand-story-copy h2 { + max-width: 560px; + color: #263229; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(48px, 4.4vw, 66px); + line-height: 1.08; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.oc-brand-story-copy > p { + max-width: 540px; + margin-top: 34px; + color: #5c645e; + font-size: 17px; + line-height: 1.82; +} + +.oc-brand-story-copy blockquote { + margin: 38px 0 0; + padding: 0 0 0 24px; + border-left: 1px solid #6f8573; + color: #354a3a; + font-family: Georgia, 'Times New Roman', serif; + font-size: 22px; + font-style: italic; + line-height: 1.65; +} + +.oc-brand-values { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 28px; + margin-top: 88px; +} + +.oc-brand-value { + min-width: 0; + padding-top: 20px; + border-top: 1px solid rgba(37, 49, 40, 0.25); +} + +.oc-brand-value > span { + display: block; + margin-bottom: 24px; + color: #628069; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.16em; +} + +.oc-brand-value h3 { + min-height: 52px; + color: #2b392f; + font-family: Georgia, 'Times New Roman', serif; + font-size: 19px; + line-height: 1.35; +} + +.oc-brand-value p { + margin-top: 14px; + color: #687069; + font-size: 13px; + line-height: 1.65; +} + .oc-plan-panel { min-height: 275px; display: grid; @@ -1203,13 +5478,72 @@ a.oc-route-service-panel, } .oc-home-experiences { - margin-top: 60px; + margin-top: 104px; min-height: 0; } +.oc-home-experiences .oc-section-title { + max-width: 820px; + margin: 0 0 48px; + text-align: left; +} + +.oc-home-experiences .oc-section-title h2 { + color: #28362d; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(48px, 5vw, 66px); + line-height: 1.08; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.oc-home-experiences .oc-section-title p { + max-width: 700px; + margin: 24px 0 0; + color: #626c65; + font-size: 16px; + line-height: 1.75; +} + .oc-home-safaris-section { margin-top: 120px; + position: relative; + isolation: isolate; min-height: 0; + padding: 108px 0 104px; +} + +.oc-home-safaris-section::before { + content: ''; + position: absolute; + inset: 0 50%; + z-index: -1; + width: 100vw; + transform: translateX(-50%); + background: #203529; +} + +.oc-home-safaris-section .oc-section-title { + max-width: 820px; + margin: 0 0 48px; + text-align: left; +} + +.oc-home-safaris-section .oc-section-title h2 { + color: #f2f1e9; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(48px, 5vw, 66px); + line-height: 1.08; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.oc-home-safaris-section .oc-section-title p { + max-width: 710px; + margin: 24px 0 0; + color: rgba(239, 242, 235, 0.72); + font-size: 16px; + line-height: 1.75; } .oc-section-title { @@ -1298,154 +5632,386 @@ a.oc-route-service-panel, .oc-experience-stack { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px; + gap: 20px; } .oc-experience-band { - min-height: 520px; + min-height: 720px; display: grid; - grid-template-rows: 300px minmax(220px, 1fr); - text-align: center; - color: #fff; + grid-template-rows: 448px minmax(272px, 1fr); + text-align: left; + color: #2c3930; + background: #ecebe5; + border: 1px solid rgba(39, 52, 43, 0.1); overflow: hidden; - border-radius: 10px; + border-radius: 0; + box-shadow: none; } .oc-experience-band.tan { - background: #9dbf73; + background: #edece5; } .oc-experience-band.clay { - background: #48a850; + background: #ebe9e2; } .oc-experience-band.charcoal { - background: #193523; + background: #e8e9e3; +} + +.oc-experience-band-media { + min-width: 0; + overflow: hidden; + background: #d8dcd3; } .oc-experience-band img { width: 100%; - height: 300px; + height: 100%; + display: block; object-fit: cover; + filter: saturate(0.78) contrast(0.95); } .oc-experience-band-content { - display: grid; - align-content: center; - justify-items: center; - gap: 24px; - padding: 28px 28px 30px; + min-width: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 26px 28px 28px; } -.oc-experience-band span, -.oc-experience-band small { - color: #f4fbef; - font-size: 16px; - font-weight: 400; - line-height: 24px; - letter-spacing: 1.5px; +.oc-experience-band-meta { + width: 100%; + display: flex; + justify-content: space-between; + gap: 18px; + margin-bottom: 24px; + color: #607466; +} + +.oc-experience-band-meta span { + color: inherit; + font-size: 10px; + font-weight: 700; + line-height: 1.35; + letter-spacing: 0.12em; text-transform: uppercase; } +.oc-experience-band-meta span:last-child { + text-align: right; +} + .oc-experience-band h3 { - max-width: 320px; - color: #fff; - font-size: 36px; - line-height: 43.2px; + max-width: 300px; + color: #2b382f; + font-family: Georgia, 'Times New Roman', serif; + font-size: 31px; + line-height: 1.18; + letter-spacing: -0.02em; +} + +.oc-experience-band p { + margin-top: 16px; + color: #68716a; + font-size: 13px; + line-height: 1.65; } .oc-experience-band small { + width: 100%; display: inline-flex; align-items: center; + justify-content: space-between; gap: 16px; + margin-top: auto; + padding-top: 20px; + border-top: 1px solid rgba(39, 52, 43, 0.16); + color: #38513f; + font-size: 10px; + font-weight: 700; + line-height: 1.3; + letter-spacing: 0.12em; + text-transform: uppercase; } .oc-home-safaris { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 32px; + gap: 20px; } .oc-home-safari { position: relative; - min-height: 560px; + min-height: 820px; display: flex; flex-direction: column; - background: var(--oc-panel); - border-radius: 4px; + background: #f0efe8; + border: 1px solid rgba(239, 242, 235, 0.14); + border-radius: 0; padding: 0; overflow: hidden; + box-shadow: none; +} + +.oc-home-safari-media { + position: relative; + height: 290px; + overflow: hidden; + background: #cfd5cd; } .oc-home-safari-image { width: 100%; - height: 200px; + height: 100%; + display: block; object-fit: cover; + filter: saturate(0.76) contrast(0.95); +} + +.oc-home-safari-media > span { + position: absolute; + left: 18px; + bottom: 16px; + min-width: 38px; + height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(242, 241, 233, 0.92); + color: #2d4534; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.12em; } .oc-home-safari-body { flex: 1; display: flex; flex-direction: column; - padding: 24px 20px 27px; + padding: 26px 28px 28px; } .oc-home-safari::after { - content: 'English'; - position: absolute; - top: 13px; - right: 18px; - background: #9fbf79; - color: #fff; - padding: 6px 10px; - font-size: 10px; + display: none; } html[data-oc-language='zh'] .oc-home-safari::after { - content: '英语'; + display: none; } -.oc-home-safari span { - color: #6a766b; - font-size: 12px; +.oc-home-safari .oc-home-safari-meta { + color: #667269; + font-size: 10px; font-weight: 700; + line-height: 1.45; + letter-spacing: 0.08em; + text-transform: uppercase; } .oc-home-safari h3 { - margin: 18px 0 12px; - font-family: 'Tenor Sans', Georgia, serif; - color: #223127; - font-size: 18px; - font-weight: 600; - line-height: 25.2px; -} - -.oc-home-safari b { - display: block; - border-top: 1px solid #c4d4bc; - border-bottom: 1px solid #c4d4bc; - color: #243328; - font-size: 11px; - padding: 9px 0; + margin: 20px 0 0; + color: #27362d; + font-family: Georgia, 'Times New Roman', serif; + font-size: 31px; + font-weight: 400; + line-height: 1.18; + letter-spacing: -0.02em; } .oc-home-safari p { - color: #5e6c61; - font-size: 11px; + margin: 16px 0 0; + color: #626d65; + font-size: 13px; + line-height: 1.65; +} + +.oc-home-safari-highlights { + display: grid; + gap: 0; + margin: 24px 0 28px; + padding: 0; + list-style: none; + border-top: 1px solid rgba(39, 54, 45, 0.16); +} + +.oc-home-safari-highlights li { + position: relative; + padding: 12px 0 12px 18px; + border-bottom: 1px solid rgba(39, 54, 45, 0.12); + color: #445248; + font-size: 12px; line-height: 1.5; - margin: 12px 0 18px; +} + +.oc-home-safari-highlights li::before { + content: ''; + position: absolute; + top: 19px; + left: 1px; + width: 5px; + height: 5px; + border-radius: 50%; + background: #728676; +} + +.oc-home-safari-footer { + display: grid; + gap: 18px; + margin-top: auto; +} + +.oc-home-safari-footer b { + color: #516057; + font-size: 11px; + font-weight: 600; + line-height: 1.4; } .oc-home-safari .oc-card-action { + width: 100%; display: flex; align-items: center; - justify-content: center; - height: 31px; - margin-top: auto; - border: 1px solid var(--oc-clay); - color: var(--oc-clay-dark); + justify-content: space-between; + height: auto; + margin: 0; + padding-top: 17px; + border: 0; + border-top: 1px solid rgba(39, 54, 45, 0.2); + color: #314c39; font-size: 10px; - font-weight: 800; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.oc-home-safaris-section .oc-center { + text-align: center; + margin-top: 42px; +} + +.oc-home-safaris-section .oc-center .oc-clay-btn { + border: 1px solid rgba(239, 242, 235, 0.54); + background: transparent; + color: #f2f1e9; + box-shadow: none; +} + +.oc-home-safaris-section .oc-center .oc-clay-btn:hover { + border-color: #f2f1e9; + background: #f2f1e9; + color: #203529; +} + +.oc-home-team { + margin-top: 124px; + min-height: 0; +} + +.oc-home-team-layout { + display: grid; + grid-template-columns: minmax(0, 1.04fr) minmax(0, 0.96fr); + gap: clamp(62px, 7vw, 98px); + align-items: center; +} + +.oc-home-team-media { + position: relative; + min-height: 720px; +} + +.oc-home-team-media img { + display: block; + object-fit: cover; + filter: saturate(0.76) contrast(0.95); +} + +.oc-home-team-main-image { + width: 88%; + height: 520px; + object-position: center; +} + +.oc-home-team-field-image { + position: absolute; + right: 0; + bottom: 0; + width: 68%; + height: 300px; + border: 12px solid var(--oc-bg); + object-position: center; +} + +.oc-home-team-copy { + min-width: 0; +} + +.oc-home-team-copy h2 { + color: #29372e; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(46px, 4.4vw, 62px); + line-height: 1.08; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.oc-home-team-intro { + margin-top: 28px; + color: #626d65; + font-size: 16px; + line-height: 1.78; +} + +.oc-home-team-services { + margin-top: 42px; + border-top: 1px solid rgba(39, 54, 45, 0.22); +} + +.oc-home-team-service { + display: grid; + grid-template-columns: 40px minmax(0, 1fr); + gap: 14px; + padding: 22px 0; + border-bottom: 1px solid rgba(39, 54, 45, 0.16); +} + +.oc-home-team-service > span { + padding-top: 4px; + color: #67806d; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.12em; +} + +.oc-home-team-service h3 { + color: #304036; + font-family: Georgia, 'Times New Roman', serif; + font-size: 20px; + line-height: 1.35; +} + +.oc-home-team-service p { + margin-top: 8px; + color: #6b746d; + font-size: 13px; + line-height: 1.65; +} + +.oc-home-team-cta { + min-width: 218px; + min-height: 48px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 12px; + margin-top: 34px; + padding: 0 22px; + color: #38523e; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.06em; text-transform: uppercase; } @@ -1541,86 +6107,314 @@ html[data-oc-language='zh'] .oc-home-safari::after { } .oc-live { - width: min(100%, 1440px); + width: min(1116px, calc(100% - 120px)); text-align: left; min-height: 0; - padding-bottom: 26px; - overflow: hidden; + margin-top: 118px; + padding-bottom: 84px; +} + +.oc-live-heading { + max-width: 760px; + margin-bottom: 48px; } .oc-live h2 { - color: var(--oc-clay); - font-size: 48px; - line-height: 62.4px; - margin: 0 0 28px; - text-align: center; + color: #29372e; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(48px, 5vw, 66px); + line-height: 1.08; + letter-spacing: -0.035em; + text-wrap: balance; } -.oc-live-carousel { - overflow: hidden; - width: 100%; - -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%); - mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%); +.oc-live-heading > p { + max-width: 680px; + margin-top: 24px; + color: #626d65; + font-size: 16px; + line-height: 1.75; } -.oc-live-track { - display: flex; - width: max-content; - animation: oc-live-scroll 44s linear infinite; +.oc-live-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-auto-rows: 255px; + gap: 18px; } -.oc-live-carousel:hover .oc-live-track { - animation-play-state: paused; -} - -.oc-live-set { - display: flex; - flex: 0 0 auto; - gap: 14px; - padding-right: 14px; -} - -.oc-live-slide { - flex: 0 0 292px; - width: 292px; +.oc-live-photo { + position: relative; + min-width: 0; margin: 0; - border-radius: 6px; overflow: hidden; - background: var(--oc-panel); - box-shadow: 0 14px 28px rgba(24, 50, 34, 0.12); + background: #d8dcd3; } -.oc-live-slide img { +.oc-live-photo:first-child { + grid-column: span 2; + grid-row: span 2; +} + +.oc-live-photo img { width: 100%; - height: 214px; - object-fit: cover; + height: 100%; display: block; + object-fit: cover; + filter: saturate(0.78) contrast(0.95); + transition: transform 0.4s ease, filter 0.4s ease; } -@keyframes oc-live-scroll { - from { - transform: translateX(0); - } +.oc-live-photo:hover img { + transform: scale(1.025); + filter: saturate(0.92) contrast(0.98); +} - to { - transform: translateX(-50%); - } +.oc-live-photo figcaption { + position: absolute; + right: 0; + bottom: 0; + left: 0; + display: flex; + align-items: center; + gap: 12px; + padding: 28px 18px 16px; + background: linear-gradient(180deg, transparent, rgba(20, 31, 24, 0.72)); + color: #f7f5ef; + font-size: 12px; + line-height: 1.4; +} + +.oc-live-photo figcaption span { + color: rgba(247, 245, 239, 0.66); + font-size: 9px; + font-weight: 700; + letter-spacing: 0.12em; } @media (prefers-reduced-motion: reduce) { - .oc-live-carousel { - overflow-x: auto; - -webkit-mask-image: none; - mask-image: none; + .oc-live-photo img { + transition: none; } +} - .oc-live-track { - animation: none; - } +.oc-home-comparison { + padding: 108px 0 112px; + background: #ebe9e1; +} - .oc-live-set[aria-hidden='true'] { - display: none; - } +.oc-comparison-heading { + max-width: 790px; + margin-bottom: 48px; +} + +.oc-comparison-heading h2 { + color: #29372e; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(48px, 5vw, 66px); + line-height: 1.08; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.oc-comparison-heading > p { + max-width: 680px; + margin-top: 24px; + color: #626d65; + font-size: 16px; + line-height: 1.75; +} + +.oc-comparison-scroll { + overflow-x: auto; + border-top: 1px solid rgba(39, 54, 45, 0.28); + border-bottom: 1px solid rgba(39, 54, 45, 0.28); + scrollbar-color: #708176 transparent; + scrollbar-width: thin; +} + +.oc-comparison-scroll:focus-visible { + outline: 3px solid rgba(72, 168, 80, 0.44); + outline-offset: 5px; +} + +.oc-comparison-table { + width: 100%; + min-width: 980px; + border-collapse: collapse; + table-layout: fixed; +} + +.oc-comparison-table th, +.oc-comparison-table td { + padding: 22px 20px; + border-right: 1px solid rgba(39, 54, 45, 0.13); + border-bottom: 1px solid rgba(39, 54, 45, 0.13); + color: #566159; + font-size: 13px; + font-weight: 400; + line-height: 1.55; + text-align: left; + vertical-align: top; +} + +.oc-comparison-table th:last-child, +.oc-comparison-table td:last-child { + border-right: 0; +} + +.oc-comparison-table tbody tr:last-child th, +.oc-comparison-table tbody tr:last-child td { + border-bottom: 0; +} + +.oc-comparison-table thead th { + padding-top: 19px; + padding-bottom: 19px; + color: #33433a; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + vertical-align: middle; +} + +.oc-comparison-table thead th:first-child, +.oc-comparison-table tbody th { + width: 150px; + color: #33433a; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.oc-comparison-table .is-wanderq { + background: rgba(212, 226, 207, 0.6); + color: #2f4736; +} + +.oc-comparison-table thead .is-wanderq { + color: #2c5837; +} + +.oc-comparison-note { + max-width: 820px; + margin-top: 22px; + color: #717971; + font-size: 11px; + line-height: 1.6; +} + +.oc-home-final-cta { + position: relative; + overflow: hidden; + padding: 116px 0 120px; + background: #183222; + color: #f3f5ed; +} + +.oc-home-final-cta::before { + content: ''; + position: absolute; + top: -280px; + right: -170px; + width: 680px; + height: 680px; + border: 1px solid rgba(217, 234, 211, 0.12); + border-radius: 50%; + box-shadow: + 0 0 0 72px rgba(217, 234, 211, 0.025), + 0 0 0 144px rgba(217, 234, 211, 0.018); + pointer-events: none; +} + +.oc-home-final-cta-layout { + position: relative; + z-index: 1; + display: grid; + grid-template-columns: minmax(0, 1.05fr) minmax(360px, 0.78fr); + gap: clamp(64px, 8vw, 116px); + align-items: center; +} + +.oc-home-final-cta-copy { + max-width: 690px; +} + +.oc-home-final-eyebrow, +.oc-home-inquiry-prompts-label { + color: #b7d3af; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.13em; + text-transform: uppercase; +} + +.oc-home-final-cta h2 { + max-width: 720px; + margin-top: 22px; + color: #f7f5ef; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(48px, 5vw, 68px); + font-weight: 400; + line-height: 1.06; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.oc-home-final-cta-copy > p { + max-width: 650px; + margin-top: 28px; + color: rgba(239, 245, 235, 0.72); + font-size: 15px; + line-height: 1.78; +} + +.oc-home-final-cta-button { + min-width: 258px; + margin-top: 36px; + gap: 12px; +} + +.oc-home-inquiry-prompts { + border-top: 1px solid rgba(217, 234, 211, 0.32); +} + +.oc-home-inquiry-prompts-label { + display: block; + padding: 18px 0 20px; +} + +.oc-home-inquiry-prompt { + display: grid; + grid-template-columns: 40px minmax(0, 1fr); + gap: 15px; + padding: 22px 0; + border-top: 1px solid rgba(217, 234, 211, 0.16); +} + +.oc-home-inquiry-prompt > span { + padding-top: 4px; + color: rgba(183, 211, 175, 0.72); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.12em; +} + +.oc-home-inquiry-prompt h3 { + color: #f1f5ec; + font-family: Georgia, 'Times New Roman', serif; + font-size: 19px; + font-weight: 400; + line-height: 1.35; +} + +.oc-home-inquiry-prompt p { + margin-top: 7px; + color: rgba(239, 245, 235, 0.64); + font-size: 12px; + line-height: 1.62; } .oc-breadcrumb { @@ -2411,6 +7205,356 @@ html[data-oc-language='zh'] .oc-home-safari::after { padding: 0 0 72px; } +.oc-transport-editorial { + position: relative; + z-index: 1; + display: grid; + grid-template-columns: minmax(0, 0.54fr) minmax(360px, 0.46fr); + gap: clamp(42px, 6vw, 96px); + align-items: center; + padding: 52px 0 108px; +} + +.oc-transport-editorial-copy > span, +.oc-transport-section-head > span { + color: var(--oc-clay-dark); + font-size: 11px; + font-weight: 800; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.oc-transport-editorial-copy h1, +.oc-transport-section-head h2 { + color: var(--oc-ink); + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(40px, 4.4vw, 66px); + font-weight: 400; + line-height: 1.04; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.oc-transport-editorial-copy h1 { + max-width: 850px; + margin-top: 20px; +} + +.oc-transport-editorial-copy > p, +.oc-transport-section-head > p { + color: var(--oc-muted); + font-size: 16px; + line-height: 1.78; +} + +.oc-transport-editorial-copy > p { + max-width: 720px; + margin-top: 26px; +} + +.oc-transport-editorial-signals { + display: flex; + flex-wrap: wrap; + gap: 12px 22px; + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid rgba(36, 51, 40, 0.14); +} + +.oc-transport-editorial-signals span { + display: inline-flex; + align-items: center; + gap: 7px; + color: #405645; + font-size: 13px; + font-weight: 700; +} + +.oc-transport-editorial-signals svg { + color: var(--oc-logo-green); +} + +.oc-transport-editorial-media { + position: relative; + margin: 0; + padding: 0 0 24px 24px; +} + +.oc-transport-editorial-media::before { + content: ''; + position: absolute; + inset: 44px 28px 0 0; + border: 1px solid rgba(51, 88, 60, 0.25); +} + +.oc-transport-editorial-media img { + position: relative; + z-index: 1; + width: 100%; + height: clamp(460px, 47vw, 680px); + object-fit: cover; + object-position: center 44%; +} + +.oc-transport-editorial-media figcaption { + position: relative; + z-index: 2; + max-width: 420px; + margin: -30px 0 0 auto; + padding: 15px 18px; + background: #f4f8ef; + color: #677368; + font-size: 11px; + line-height: 1.55; +} + +.oc-transport-services { + position: relative; + z-index: 1; + padding: 0 0 110px; +} + +.oc-transport-section-head { + display: grid; + grid-template-columns: minmax(0, 0.64fr) minmax(280px, 0.36fr); + gap: 20px 64px; + align-items: end; + padding-bottom: 40px; + border-bottom: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-transport-section-head > span { + grid-column: 1 / -1; +} + +.oc-transport-section-head h2 { + font-size: clamp(36px, 3.8vw, 56px); +} + +.oc-transport-section-head.is-compact { + display: block; + padding-bottom: 32px; +} + +.oc-transport-section-head.is-compact h2 { + max-width: 900px; + margin-top: 14px; +} + +.oc-transport-section-head.is-compact > p { + max-width: 780px; + margin-top: 18px; +} + +.oc-transport-service-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.oc-transport-service-grid article { + display: grid; + grid-template-columns: 58px minmax(0, 1fr); + gap: 15px 24px; + align-content: start; + min-height: 310px; + padding: 42px 46px 38px 0; + border-bottom: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-transport-service-grid article:nth-child(odd) { + padding-right: 52px; + border-right: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-transport-service-grid article:nth-child(even) { + padding-left: 52px; +} + +.oc-transport-service-grid article > span { + grid-row: 1 / span 3; + color: rgba(63, 86, 66, 0.42); + font-family: Georgia, 'Times New Roman', serif; + font-size: 30px; +} + +.oc-transport-service-grid h3, +.oc-transport-standard-grid h3 { + color: var(--oc-ink); + font-family: Georgia, 'Times New Roman', serif; + font-size: 27px; + font-weight: 400; + line-height: 1.18; +} + +.oc-transport-service-grid p, +.oc-transport-standard-grid p { + color: var(--oc-muted); + font-size: 14px; + line-height: 1.72; +} + +.oc-transport-service-grid small { + align-self: end; + color: var(--oc-clay-dark); + font-size: 11px; + font-weight: 800; + letter-spacing: 0.045em; + text-transform: uppercase; +} + +.oc-transport-fleet { + position: relative; + z-index: 1; + padding-bottom: 108px; +} + +.oc-transport-fleet .oc-transport-grid { + padding: 36px 0 0; +} + +.oc-transport-standards { + position: relative; + z-index: 1; + padding: 104px 0 96px; + background: #203328; +} + +.oc-transport-section-head.is-light { + border-color: rgba(220, 233, 216, 0.2); +} + +.oc-transport-section-head.is-light > span { + color: #a8c69f; +} + +.oc-transport-section-head.is-light h2 { + color: #f5f4eb; +} + +.oc-transport-standard-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + border-bottom: 1px solid rgba(220, 233, 216, 0.2); +} + +.oc-transport-standard-grid article { + min-height: 310px; + padding: 34px 27px 32px; + border-left: 1px solid rgba(220, 233, 216, 0.2); +} + +.oc-transport-standard-grid article:last-child { + border-right: 1px solid rgba(220, 233, 216, 0.2); +} + +.oc-transport-standard-grid article > span { + display: block; + color: #8fb488; + font-size: 11px; + font-weight: 800; + letter-spacing: 0.12em; +} + +.oc-transport-standard-grid h3 { + min-height: 74px; + margin-top: 28px; + color: #f5f4eb; + font-size: 22px; +} + +.oc-transport-standard-grid p { + margin-top: 18px; + color: rgba(239, 245, 235, 0.68); + font-size: 13px; +} + +.oc-transport-promise-strip { + display: flex; + flex-wrap: wrap; + gap: 12px 28px; + align-items: center; + padding-top: 30px; + color: rgba(239, 245, 235, 0.72); + font-size: 12px; +} + +.oc-transport-promise-strip strong { + margin-right: auto; + color: #f5f4eb; + font-family: Georgia, 'Times New Roman', serif; + font-size: 18px; + font-weight: 400; +} + +.oc-transport-promise-strip span::before { + content: '✓'; + margin-right: 7px; + color: #95bd8d; +} + +.oc-transport-dispatch { + position: relative; + z-index: 1; + display: grid; + grid-template-columns: minmax(360px, 0.47fr) minmax(0, 0.53fr); + gap: clamp(42px, 6vw, 92px); + align-items: stretch; + padding: 110px 0; +} + +.oc-transport-dispatch figure { + min-height: 680px; + margin: 0; + overflow: hidden; +} + +.oc-transport-dispatch figure img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.oc-transport-dispatch > div { + align-self: center; +} + +.oc-transport-dispatch ol { + display: grid; + margin: 6px 0 0; + padding: 0; + list-style: none; +} + +.oc-transport-dispatch li { + display: grid; + grid-template-columns: 42px minmax(0, 1fr); + gap: 18px; + align-items: center; + padding: 18px 0; + border-bottom: 1px solid rgba(36, 51, 40, 0.14); +} + +.oc-transport-dispatch li span { + color: var(--oc-clay-dark); + font-size: 11px; + font-weight: 800; +} + +.oc-transport-dispatch li p { + color: var(--oc-ink); + font-size: 15px; + line-height: 1.5; +} + +.oc-transport-dispatch-note { + margin-top: 26px; + padding: 18px 20px; + background: #e7eee1; + color: #5d6c60; + font-size: 12px; + line-height: 1.65; +} + .oc-transport-card { display: grid; grid-template-columns: 210px minmax(0, 1fr); @@ -2456,6 +7600,327 @@ html[data-oc-language='zh'] .oc-home-safari::after { color: var(--oc-ink); } +.oc-hotel-editorial { + position: relative; + z-index: 1; + display: grid; + grid-template-columns: minmax(0, 0.59fr) minmax(320px, 0.41fr); + gap: clamp(46px, 7vw, 112px); + align-items: end; + padding: 48px 0 112px; +} + +.oc-hotel-editorial > div:first-child > span, +.oc-hotel-route-links header > span, +.oc-hotel-booking-checks > div > span { + color: var(--oc-clay-dark); + font-size: 11px; + font-weight: 800; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.oc-hotel-editorial h1, +.oc-hotel-group h2, +.oc-hotel-route-links h2, +.oc-hotel-booking-checks h2 { + color: var(--oc-ink); + font-family: Georgia, 'Times New Roman', serif; + font-weight: 400; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.oc-hotel-editorial h1 { + max-width: 900px; + margin-top: 20px; + font-size: clamp(43px, 5vw, 74px); + line-height: 1.02; +} + +.oc-hotel-editorial > div:last-child > p { + color: var(--oc-muted); + font-size: 15px; + line-height: 1.78; +} + +.oc-hotel-editorial-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 24px; +} + +.oc-hotel-editorial-tags span { + padding: 8px 11px; + border: 1px solid rgba(56, 86, 62, 0.18); + color: #52634f; + font-size: 11px; + font-weight: 700; +} + +.oc-hotel-groups { + position: relative; + z-index: 1; +} + +.oc-hotel-group { + padding-bottom: 112px; +} + +.oc-hotel-group > header { + display: grid; + grid-template-columns: 72px minmax(0, 1fr); + gap: 26px; + align-items: start; + padding-bottom: 34px; + border-bottom: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-hotel-group > header > span { + color: rgba(63, 86, 66, 0.4); + font-family: Georgia, 'Times New Roman', serif; + font-size: 33px; + line-height: 1; +} + +.oc-hotel-group h2 { + font-size: clamp(37px, 4vw, 58px); + line-height: 1.04; +} + +.oc-hotel-group header p { + max-width: 820px; + margin-top: 16px; + color: var(--oc-muted); + font-size: 14px; + line-height: 1.72; +} + +.oc-hotel-candidate-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.oc-hotel-group:nth-child(2) .oc-hotel-candidate-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.oc-hotel-candidate { + display: grid; + grid-template-rows: auto auto auto minmax(0, 1fr) auto auto; + min-height: 480px; + padding: 36px 42px 38px; + border-bottom: 1px solid rgba(36, 51, 40, 0.16); + border-left: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-hotel-candidate:last-child { + border-right: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-hotel-candidate-topline { + display: flex; + justify-content: space-between; + gap: 16px; + color: var(--oc-clay-dark); + font-size: 10px; + font-weight: 800; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.oc-hotel-candidate-topline b { + color: #6f7d70; + font-size: inherit; +} + +.oc-hotel-candidate h3 { + max-width: 540px; + margin-top: 34px; + color: var(--oc-ink); + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(26px, 2.3vw, 35px); + font-weight: 400; + line-height: 1.12; +} + +.oc-hotel-candidate-location { + display: flex; + align-items: center; + gap: 7px; + margin-top: 15px; + color: #637166; + font-size: 12px; + font-weight: 700; +} + +.oc-hotel-candidate-location svg { + flex: 0 0 auto; + color: var(--oc-logo-green); +} + +.oc-hotel-candidate > p:not(.oc-hotel-candidate-location) { + margin-top: 22px; + color: var(--oc-muted); + font-size: 14px; + line-height: 1.72; +} + +.oc-hotel-candidate dl { + display: grid; + gap: 9px; + margin: 26px 0 0; + padding-top: 20px; + border-top: 1px solid rgba(36, 51, 40, 0.12); +} + +.oc-hotel-candidate dl div { + display: grid; + grid-template-columns: 58px minmax(0, 1fr); + gap: 12px; +} + +.oc-hotel-candidate dt { + color: var(--oc-clay-dark); + font-size: 10px; + font-weight: 800; + text-transform: uppercase; +} + +.oc-hotel-candidate dd { + margin: 0; + color: #667368; + font-size: 11px; + line-height: 1.55; +} + +.oc-hotel-candidate > a { + display: inline-flex; + align-items: center; + gap: 7px; + width: fit-content; + margin-top: 28px; + color: var(--oc-ink); + font-size: 11px; + font-weight: 800; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.oc-hotel-route-links { + position: relative; + z-index: 1; + padding: 108px 0 102px; + background: #203328; +} + +.oc-hotel-route-links .oc-container-wide { + display: grid; + grid-template-columns: minmax(310px, 0.36fr) minmax(0, 0.64fr); + gap: clamp(44px, 7vw, 112px); +} + +.oc-hotel-route-links header > span { + color: #9abf92; +} + +.oc-hotel-route-links h2 { + margin-top: 18px; + color: #f5f4eb; + font-size: clamp(38px, 4.2vw, 59px); + line-height: 1.06; +} + +.oc-hotel-route-links .oc-container-wide > div { + border-top: 1px solid rgba(220, 233, 216, 0.2); +} + +.oc-hotel-route-links article { + display: grid; + grid-template-columns: 42px minmax(170px, 0.35fr) minmax(170px, 0.35fr) minmax(220px, 0.3fr); + gap: 18px; + align-items: center; + padding: 24px 0; + border-bottom: 1px solid rgba(220, 233, 216, 0.2); +} + +.oc-hotel-route-links article > span { + color: #8fb488; + font-size: 10px; + font-weight: 800; +} + +.oc-hotel-route-links h3 { + color: #f5f4eb; + font-family: Georgia, 'Times New Roman', serif; + font-size: 19px; + font-weight: 400; +} + +.oc-hotel-route-links article b { + color: #bad0b6; + font-size: 11px; + line-height: 1.5; +} + +.oc-hotel-route-links article p { + color: rgba(239, 245, 235, 0.62); + font-size: 11px; + line-height: 1.58; +} + +.oc-hotel-booking-checks { + position: relative; + z-index: 1; + display: grid; + grid-template-columns: minmax(320px, 0.43fr) minmax(0, 0.57fr); + gap: clamp(48px, 7vw, 108px); + padding: 112px 0; +} + +.oc-hotel-booking-checks h2 { + max-width: 620px; + margin-top: 18px; + font-size: clamp(39px, 4.4vw, 62px); + line-height: 1.05; +} + +.oc-hotel-booking-checks > div > p { + max-width: 600px; + margin-top: 25px; + color: var(--oc-muted); + font-size: 14px; + line-height: 1.75; +} + +.oc-hotel-booking-checks ol { + margin: 0; + padding: 0; + border-top: 1px solid rgba(36, 51, 40, 0.16); + list-style: none; +} + +.oc-hotel-booking-checks li { + display: grid; + grid-template-columns: 42px minmax(0, 1fr); + gap: 18px; + padding: 21px 0; + border-bottom: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-hotel-booking-checks li span { + color: var(--oc-clay-dark); + font-size: 10px; + font-weight: 800; +} + +.oc-hotel-booking-checks li p { + color: var(--oc-ink); + font-size: 14px; + line-height: 1.55; +} + .oc-archive-layout.is-collective { padding-bottom: 105px; } @@ -2496,7 +7961,7 @@ html[data-oc-language='zh'] .oc-home-safari::after { gap: clamp(18px, 2.4vw, 30px); width: min(1116px, calc(100% - 80px)); margin: 0 auto; - padding: 22px 0 26px; + padding: 28px 0 30px; border-top: 1px solid rgba(74, 110, 70, 0.24); color: #6a756d; font-size: 13px; @@ -2507,7 +7972,7 @@ html[data-oc-language='zh'] .oc-home-safari::after { } .oc-footer-legal-logo img { - width: 92px; + width: 104px; height: auto; object-fit: contain; } @@ -2676,13 +8141,17 @@ html[data-oc-language='zh'] .oc-home-safari::after { } .oc-tabs { + box-sizing: border-box; + max-width: 100%; min-height: 72px; background: #183222; color: #dcebd5; display: flex; align-items: center; - justify-content: center; - gap: 54px; + justify-content: safe center; + gap: clamp(18px, 2.8vw, 54px); + overflow-x: auto; + overflow-y: hidden; padding: 0 24px; position: sticky; top: 84px; @@ -5534,16 +11003,51 @@ body.oc-lightbox-open { min-height: 560px; } + .oc-home-hero { + height: auto; + min-height: 670px; + grid-template-rows: minmax(300px, 43vh) minmax(370px, auto); + } + + .oc-hero-media { + object-position: center; + } + + .oc-home-trust-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + row-gap: 24px; + } + + .oc-home-trust-item:nth-child(odd) { + padding-left: 0; + border-left: 0; + } + + .oc-home-trust-item:nth-child(even) { + padding-right: 0; + } + .oc-awards, .oc-trip { display: none; } .oc-hero-dots { - top: 70px; + top: auto; + right: auto; + bottom: 30px; left: 24px; } + .oc-hero-copy { + padding: 34px 24px 82px; + } + + .oc-hero-copy h1 { + max-width: 700px; + font-size: clamp(38px, 7vw, 48px); + } + .oc-plan-panel, .oc-detail-overview, .oc-detail-strip, @@ -6098,7 +11602,61 @@ body.oc-lightbox-open { @media (max-width: 540px) { .oc-home-hero { - min-height: 515px; + min-height: 650px; + grid-template-rows: minmax(270px, 40vh) minmax(380px, auto); + } + + .oc-hero-copy { + padding: 30px 20px 76px; + } + + .oc-hero-copy p { + margin-bottom: 14px; + font-size: 13px; + } + + .oc-hero-copy h1 { + max-width: 100%; + margin-bottom: 14px; + font-size: 36px; + line-height: 1.18; + } + + .oc-hero-copy p.oc-hero-summary { + margin-bottom: 24px; + font-size: 15px; + } + + .oc-hero-copy .oc-orange-btn { + min-width: 154px; + height: 46px; + font-size: 15px; + } + + .oc-hero-dots { + bottom: 26px; + left: 20px; + gap: 14px; + } + + .oc-home-trust-grid { + grid-template-columns: 1fr; + gap: 0; + padding-top: 8px; + padding-bottom: 8px; + } + + .oc-home-trust-item, + .oc-home-trust-item:first-child, + .oc-home-trust-item:last-child, + .oc-home-trust-item:nth-child(even), + .oc-home-trust-item:nth-child(odd) { + padding: 18px 0; + border-left: 0; + } + + .oc-home-trust-item + .oc-home-trust-item { + border-top: 1px solid #dedfd7; } .oc-footer-legal { @@ -6391,6 +11949,46 @@ body.oc-lightbox-open { } @media (max-width: 980px) { + .oc-hotel-editorial, + .oc-hotel-route-links .oc-container-wide, + .oc-hotel-booking-checks { + grid-template-columns: 1fr; + } + + .oc-hotel-group:nth-child(2) .oc-hotel-candidate-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .oc-hotel-route-links article { + grid-template-columns: 42px minmax(170px, 0.35fr) minmax(170px, 0.35fr) minmax(0, 0.3fr); + } + + .oc-transport-editorial, + .oc-transport-dispatch { + grid-template-columns: 1fr; + } + + .oc-transport-editorial-media { + width: min(720px, 100%); + justify-self: end; + } + + .oc-transport-section-head { + grid-template-columns: 1fr; + } + + .oc-transport-standard-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .oc-transport-standard-grid article:nth-child(even) { + border-right: 1px solid rgba(220, 233, 216, 0.2); + } + + .oc-transport-dispatch figure { + min-height: 540px; + } + .oc-food-intro, .oc-transport-intro { grid-template-columns: 1fr; @@ -6406,6 +12004,248 @@ body.oc-lightbox-open { } @media (max-width: 640px) { + .oc-hotel-editorial { + gap: 26px; + padding: 36px 0 74px; + } + + .oc-hotel-editorial h1, + .oc-hotel-group h2, + .oc-hotel-route-links h2, + .oc-hotel-booking-checks h2 { + font-size: 32px; + line-height: 1.08; + } + + .oc-hotel-editorial > div:last-child > p, + .oc-hotel-group header p, + .oc-hotel-booking-checks > div > p { + font-size: 12px; + line-height: 1.62; + } + + .oc-hotel-group { + padding-bottom: 72px; + } + + .oc-hotel-group > header { + grid-template-columns: 42px minmax(0, 1fr); + gap: 12px; + padding-bottom: 24px; + } + + .oc-hotel-group > header > span { + font-size: 22px; + } + + .oc-hotel-candidate-grid, + .oc-hotel-group:nth-child(2) .oc-hotel-candidate-grid { + grid-template-columns: 1fr; + } + + .oc-hotel-candidate, + .oc-hotel-candidate:last-child { + min-height: 0; + padding: 28px 0; + border-right: 0; + border-left: 0; + } + + .oc-hotel-candidate h3 { + margin-top: 24px; + font-size: 26px; + } + + .oc-hotel-candidate > p:not(.oc-hotel-candidate-location) { + margin-top: 17px; + font-size: 12px; + line-height: 1.62; + } + + .oc-hotel-route-links { + padding: 72px 0 66px; + } + + .oc-hotel-route-links .oc-container-wide { + gap: 34px; + } + + .oc-hotel-route-links article { + grid-template-columns: 28px minmax(0, 1fr); + gap: 8px 12px; + align-items: start; + padding: 20px 0; + } + + .oc-hotel-route-links article > span { + grid-row: 1 / span 3; + } + + .oc-hotel-route-links h3, + .oc-hotel-route-links article b, + .oc-hotel-route-links article p { + grid-column: 2; + } + + .oc-hotel-route-links h3 { + font-size: 18px; + } + + .oc-hotel-booking-checks { + gap: 34px; + padding: 72px 0; + } + + .oc-hotel-booking-checks li { + grid-template-columns: 30px minmax(0, 1fr); + gap: 10px; + padding: 16px 0; + } + + .oc-hotel-booking-checks li p { + font-size: 12px; + } + + .oc-transport-editorial { + gap: 34px; + padding: 36px 0 72px; + } + + .oc-transport-editorial-copy h1, + .oc-transport-section-head h2 { + font-size: 32px; + line-height: 1.09; + } + + .oc-transport-editorial-copy > p, + .oc-transport-section-head > p { + font-size: 13px; + line-height: 1.62; + } + + .oc-transport-editorial-signals { + display: grid; + gap: 10px; + margin-top: 24px; + padding-top: 20px; + } + + .oc-transport-editorial-media { + padding: 0 0 15px 14px; + } + + .oc-transport-editorial-media img { + height: 420px; + } + + .oc-transport-editorial-media figcaption { + margin-top: -18px; + padding: 11px 13px; + font-size: 9px; + } + + .oc-transport-services, + .oc-transport-fleet { + padding-bottom: 72px; + } + + .oc-transport-section-head { + gap: 14px; + padding-bottom: 24px; + } + + .oc-transport-service-grid { + grid-template-columns: 1fr; + } + + .oc-transport-service-grid article, + .oc-transport-service-grid article:nth-child(odd), + .oc-transport-service-grid article:nth-child(even) { + grid-template-columns: 38px minmax(0, 1fr); + min-height: 0; + padding: 28px 0; + border-right: 0; + } + + .oc-transport-service-grid article > span { + font-size: 21px; + } + + .oc-transport-service-grid h3 { + font-size: 22px; + } + + .oc-transport-service-grid p { + font-size: 12px; + line-height: 1.58; + } + + .oc-transport-service-grid small { + font-size: 9px; + line-height: 1.45; + } + + .oc-transport-fleet .oc-transport-grid { + padding-top: 24px; + } + + .oc-transport-standards { + padding: 72px 0 64px; + } + + .oc-transport-standard-grid { + grid-template-columns: 1fr; + } + + .oc-transport-standard-grid article, + .oc-transport-standard-grid article:nth-child(even), + .oc-transport-standard-grid article:last-child { + min-height: 0; + padding: 28px 0; + border-right: 0; + border-left: 0; + border-bottom: 1px solid rgba(220, 233, 216, 0.2); + } + + .oc-transport-standard-grid h3 { + min-height: 0; + margin-top: 14px; + font-size: 21px; + } + + .oc-transport-standard-grid p { + margin-top: 10px; + font-size: 12px; + } + + .oc-transport-promise-strip { + display: grid; + gap: 10px; + } + + .oc-transport-promise-strip strong { + margin-bottom: 6px; + } + + .oc-transport-dispatch { + gap: 36px; + padding: 72px 0; + } + + .oc-transport-dispatch figure { + min-height: 440px; + } + + .oc-transport-dispatch li { + grid-template-columns: 32px minmax(0, 1fr); + gap: 12px; + padding: 14px 0; + } + + .oc-transport-dispatch li p { + font-size: 12px; + } + .oc-about-page { padding-bottom: 54px; } @@ -6864,3 +12704,2507 @@ body.oc-lightbox-open { gap: 6px; } } + +@media (max-width: 980px) { + .oc-home-brand-story { + padding: 88px 0 82px; + } + + .oc-brand-story-layout { + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); + gap: 46px; + } + + .oc-brand-story-copy h2 { + font-size: clamp(40px, 5.5vw, 52px); + } + + .oc-brand-story-copy > p { + margin-top: 26px; + font-size: 15px; + } + + .oc-brand-story-copy blockquote { + margin-top: 28px; + font-size: 19px; + } + + .oc-brand-values { + grid-template-columns: repeat(3, minmax(0, 1fr)); + row-gap: 42px; + margin-top: 68px; + } +} + +@media (max-width: 640px) { + .oc-home-brand-story { + padding: 64px 0 66px; + } + + .oc-brand-story-layout { + grid-template-columns: 1fr; + gap: 42px; + } + + .oc-brand-story-media { + aspect-ratio: 4 / 4.8; + } + + .oc-brand-eyebrow { + margin-bottom: 22px; + } + + .oc-brand-story-copy h2 { + font-size: 38px; + line-height: 1.12; + } + + .oc-brand-story-copy > p { + font-size: 15px; + line-height: 1.72; + } + + .oc-brand-story-copy blockquote { + padding-left: 18px; + font-size: 18px; + line-height: 1.6; + } + + .oc-brand-values { + grid-template-columns: 1fr; + gap: 0; + margin-top: 56px; + } + + .oc-brand-value { + display: grid; + grid-template-columns: 42px minmax(0, 1fr); + column-gap: 10px; + padding: 22px 0; + } + + .oc-brand-value > span { + grid-row: 1 / span 2; + margin: 4px 0 0; + } + + .oc-brand-value h3 { + min-height: 0; + font-size: 18px; + } + + .oc-brand-value p { + margin-top: 8px; + } +} + +@media (max-width: 980px) { + .oc-home-experiences { + margin-top: 86px; + } + + .oc-home-experiences .oc-section-title h2 { + font-size: 52px; + } + + .oc-experience-band { + min-height: 660px; + grid-template-rows: 380px minmax(280px, 1fr); + } + + .oc-experience-band-content { + padding: 24px 22px 25px; + } + + .oc-experience-band h3 { + font-size: 27px; + } +} + +@media (max-width: 820px) { + .oc-home-experiences .oc-section-title { + margin-bottom: 38px; + } + + .oc-experience-stack { + grid-template-columns: 1fr; + gap: 24px; + } + + .oc-experience-band, + .oc-experience-band.tan, + .oc-experience-band.clay, + .oc-experience-band.charcoal { + min-height: 390px; + display: grid; + grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr); + grid-template-rows: 1fr; + } + + .oc-experience-band-media, + .oc-experience-band img { + width: 100%; + height: 100%; + min-height: 390px; + } + + .oc-experience-band-content { + min-width: 0; + align-items: flex-start; + justify-items: initial; + padding: 34px 32px 30px; + } + + .oc-experience-band-meta { + margin-bottom: 28px; + } + + .oc-experience-band p { + font-size: 14px; + } +} + +@media (max-width: 640px) { + .oc-home-experiences { + margin-top: 68px; + } + + .oc-home-experiences .oc-section-title h2 { + font-size: 38px; + line-height: 1.12; + } + + .oc-home-experiences .oc-section-title p { + margin-top: 18px; + font-size: 14px; + line-height: 1.65; + } + + .oc-experience-band, + .oc-experience-band.tan, + .oc-experience-band.clay, + .oc-experience-band.charcoal { + min-height: 0; + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto auto; + text-align: left; + } + + .oc-experience-band-media { + aspect-ratio: 4 / 4.6; + min-height: 0; + } + + .oc-experience-band img { + width: 100%; + height: 100%; + min-height: 0; + } + + .oc-experience-band-content { + min-height: 286px; + padding: 24px 22px 24px; + } + + .oc-experience-band-meta { + margin-bottom: 22px; + } + + .oc-experience-band-meta span { + font-size: 9px; + } + + .oc-experience-band h3 { + margin: 0; + font-size: 28px; + line-height: 1.18; + } + + .oc-experience-band p { + margin-top: 14px; + font-size: 13px; + } + + .oc-experience-band small { + font-size: 9px; + } +} + +@media (max-width: 980px) { + .oc-home-safaris-section { + padding: 90px 0 88px; + } + + .oc-home-safaris-section .oc-section-title h2 { + font-size: 52px; + } + + .oc-home-safaris { + gap: 16px; + } + + .oc-home-safari { + min-height: 790px; + } + + .oc-home-safari-body { + padding: 24px 21px 24px; + } + + .oc-home-safari h3 { + font-size: 27px; + } +} + +@media (max-width: 820px) { + .oc-home-safaris { + grid-template-columns: 1fr; + gap: 24px; + } + + .oc-home-safari { + min-height: 560px; + display: grid; + grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr); + } + + .oc-home-safari-media, + .oc-home-safari-image { + width: 100%; + height: 100%; + min-height: 560px; + } + + .oc-home-safari-body { + min-width: 0; + padding: 34px 32px 30px; + } + + .oc-home-safari p { + font-size: 14px; + } + + .oc-home-safari-highlights li { + font-size: 13px; + } +} + +@media (max-width: 640px) { + .oc-home-safaris-section { + margin-top: 84px; + padding: 68px 0 70px; + } + + .oc-home-safaris-section .oc-section-title { + margin-bottom: 38px; + } + + .oc-home-safaris-section .oc-section-title h2 { + font-size: 38px; + line-height: 1.12; + } + + .oc-home-safaris-section .oc-section-title p { + margin-top: 18px; + font-size: 14px; + line-height: 1.65; + } + + .oc-home-safari { + min-height: 0; + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto auto; + } + + .oc-home-safari-media { + aspect-ratio: 4 / 3.35; + height: auto; + min-height: 0; + } + + .oc-home-safari-image { + width: 100%; + height: 100%; + min-height: 0; + } + + .oc-home-safari-body { + min-width: 0; + padding: 25px 22px 24px; + } + + .oc-home-safari .oc-home-safari-meta { + font-size: 9px; + line-height: 1.5; + } + + .oc-home-safari h3 { + margin: 18px 0 0; + font-size: 29px; + line-height: 1.18; + } + + .oc-home-safari p { + display: block; + margin-top: 14px; + overflow: visible; + font-size: 13px; + line-height: 1.65; + -webkit-line-clamp: unset; + line-clamp: unset; + } + + .oc-home-safari-highlights { + margin: 22px 0 26px; + } + + .oc-home-safari-highlights li { + padding-top: 11px; + padding-bottom: 11px; + font-size: 12px; + } + + .oc-home-safari-highlights li::before { + top: 18px; + } + + .oc-home-safari-footer b { + margin: 0; + padding: 0; + border: 0; + font-size: 10px; + } + + .oc-home-safari .oc-card-action { + justify-content: space-between; + margin: 0; + padding-top: 16px; + border-top: 1px solid rgba(39, 54, 45, 0.2); + } +} + +@media (max-width: 980px) { + .oc-home-team-layout { + gap: 48px; + } + + .oc-home-team-media { + min-height: 650px; + } + + .oc-home-team-main-image { + height: 470px; + } + + .oc-home-team-field-image { + height: 260px; + } + + .oc-home-team-copy h2 { + font-size: 48px; + } +} + +@media (max-width: 820px) { + .oc-home-team { + margin-top: 98px; + } + + .oc-home-team-layout { + grid-template-columns: 1fr; + gap: 58px; + } + + .oc-home-team-media { + min-height: 680px; + } + + .oc-home-team-main-image { + height: 520px; + } + + .oc-home-team-field-image { + width: 62%; + height: 270px; + } + + .oc-home-team-copy { + max-width: 650px; + } +} + +@media (max-width: 640px) { + .oc-home-team { + margin-top: 76px; + } + + .oc-home-team-layout { + gap: 46px; + } + + .oc-home-team-media { + min-height: 448px; + } + + .oc-home-team-main-image { + width: 100%; + height: 320px; + } + + .oc-home-team-field-image { + width: 70%; + height: 190px; + border-width: 8px; + } + + .oc-home-team-copy h2 { + font-size: 38px; + line-height: 1.12; + } + + .oc-home-team-intro { + margin-top: 22px; + font-size: 14px; + line-height: 1.7; + } + + .oc-home-team-services { + margin-top: 34px; + } + + .oc-home-team-service { + grid-template-columns: 34px minmax(0, 1fr); + gap: 10px; + padding: 20px 0; + } + + .oc-home-team-service h3 { + font-size: 18px; + } + + .oc-home-team-service p { + font-size: 12px; + } + + .oc-home-team-cta { + width: 100%; + margin-top: 28px; + } +} + +@media (max-width: 820px) { + .oc-live { + margin-top: 94px; + padding-bottom: 72px; + } + + .oc-live h2 { + font-size: 50px; + } + + .oc-live-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-auto-rows: 250px; + } + + .oc-live-photo:first-child { + grid-column: span 2; + grid-row: span 2; + } +} + +@media (max-width: 540px) { + .oc-live { + width: min(100% - 32px, 700px); + margin-top: 76px; + padding-bottom: 58px; + } + + .oc-live-heading { + margin-bottom: 34px; + } + + .oc-live h2 { + font-size: 38px; + line-height: 1.12; + } + + .oc-live-heading > p { + margin-top: 18px; + font-size: 14px; + line-height: 1.65; + } + + .oc-live-grid { + grid-template-columns: 1fr; + grid-auto-rows: 300px; + gap: 14px; + } + + .oc-live-photo:first-child { + grid-column: span 1; + grid-row: span 1; + } + + .oc-live-photo figcaption { + padding: 28px 15px 14px; + font-size: 11px; + } +} + +@media (max-width: 820px) { + .oc-home-comparison { + padding: 88px 0 92px; + } + + .oc-comparison-heading h2 { + font-size: 50px; + } + + .oc-comparison-table { + min-width: 900px; + } + + .oc-comparison-table thead th:first-child, + .oc-comparison-table tbody th { + position: sticky; + left: 0; + z-index: 2; + background: #ebe9e1; + box-shadow: 12px 0 18px rgba(36, 51, 40, 0.06); + } + + .oc-comparison-table thead th:first-child { + z-index: 3; + } + + .oc-home-final-cta { + padding: 92px 0 96px; + } + + .oc-home-final-cta-layout { + grid-template-columns: 1fr; + gap: 58px; + } + + .oc-home-final-cta-copy { + max-width: 680px; + } + + .oc-home-inquiry-prompts { + max-width: 680px; + } +} + +@media (max-width: 540px) { + .oc-home-comparison { + padding: 70px 0 74px; + } + + .oc-comparison-heading { + margin-bottom: 36px; + } + + .oc-comparison-heading h2 { + font-size: 38px; + line-height: 1.12; + } + + .oc-comparison-heading > p { + margin-top: 18px; + font-size: 14px; + line-height: 1.65; + } + + .oc-comparison-table { + min-width: 820px; + } + + .oc-comparison-table th, + .oc-comparison-table td { + padding: 18px 15px; + font-size: 12px; + } + + .oc-comparison-table thead th:first-child, + .oc-comparison-table tbody th { + width: 112px; + font-size: 10px; + } + + .oc-home-final-cta { + padding: 72px 0 76px; + } + + .oc-home-final-cta::before { + top: -210px; + right: -300px; + width: 560px; + height: 560px; + } + + .oc-home-final-cta-layout { + gap: 48px; + } + + .oc-home-final-cta h2 { + margin-top: 18px; + font-size: 38px; + line-height: 1.12; + } + + .oc-home-final-cta-copy > p { + margin-top: 20px; + font-size: 14px; + line-height: 1.68; + } + + .oc-home-final-cta-button { + width: 100%; + min-width: 0; + margin-top: 30px; + padding-right: 18px; + padding-left: 18px; + } + + .oc-home-inquiry-prompt { + grid-template-columns: 34px minmax(0, 1fr); + gap: 10px; + padding: 20px 0; + } + + .oc-home-inquiry-prompt h3 { + font-size: 18px; + } +} + +/* Route archive — three editorial starting points from the customer route brief. */ +.oc-routes-editorial { + position: relative; + z-index: 1; + padding: 70px 0 112px; +} + +.oc-routes-editorial-heading { + max-width: 820px; + margin-bottom: 58px; +} + +.oc-routes-editorial-heading h2 { + color: #29372e; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(48px, 5vw, 66px); + font-weight: 400; + line-height: 1.08; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.oc-routes-editorial-heading > p { + max-width: 700px; + margin-top: 24px; + color: #626d65; + font-size: 16px; + line-height: 1.76; +} + +.oc-routes-editorial .oc-safari-grid { + display: grid; + grid-template-columns: 1fr; + gap: 28px; +} + +.oc-routes-editorial .oc-safari-card { + display: grid; + grid-template-columns: minmax(340px, 0.88fr) minmax(0, 1.12fr); + width: 100%; + max-width: none; + min-height: 590px; + overflow: hidden; + border: 1px solid rgba(39, 54, 45, 0.14); + border-radius: 0; + background: #f0efe8; + color: #29372e; + box-shadow: none; +} + +.oc-routes-editorial .oc-safari-card:nth-child(even) { + grid-template-columns: minmax(0, 1.12fr) minmax(340px, 0.88fr); +} + +.oc-routes-editorial .oc-safari-card:nth-child(even) .oc-safari-card-image { + order: 2; +} + +.oc-routes-editorial .oc-safari-card-image { + height: 100%; + min-height: 590px; + overflow: hidden; +} + +.oc-routes-editorial .oc-safari-card-image::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(180deg, rgba(24, 50, 34, 0.02), rgba(24, 50, 34, 0.18)); + pointer-events: none; +} + +.oc-routes-editorial .oc-safari-card-image img { + width: 100%; + height: 100%; + display: block; + object-fit: cover; + filter: saturate(0.74) contrast(0.95); + transition: transform 0.45s ease, filter 0.45s ease; +} + +.oc-routes-editorial .oc-safari-card:hover .oc-safari-card-image img, +.oc-routes-editorial .oc-safari-card:focus-visible .oc-safari-card-image img { + transform: scale(1.025); + filter: saturate(0.88) contrast(0.98); +} + +.oc-routes-editorial .oc-safari-card-image > span { + top: 22px; + left: 22px; + z-index: 2; + min-width: 42px; + height: 42px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + background: rgba(242, 241, 233, 0.9); + color: #2d4534; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.12em; +} + +.oc-routes-editorial .oc-safari-card-body { + min-height: 0; + display: flex; + flex-direction: column; + gap: 0; + padding: clamp(38px, 5vw, 64px); +} + +.oc-routes-editorial .oc-safari-card-body > p:first-child, +.oc-routes-editorial .oc-safari-card-kicker { + margin: 0; + color: #667269; + font-size: 10px; + font-weight: 700; + line-height: 1.45; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.oc-routes-editorial .oc-safari-card-body h3 { + max-width: 650px; + margin: 18px 0 0; + color: #27362d; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(36px, 3.7vw, 50px); + font-weight: 400; + line-height: 1.08; + letter-spacing: -0.03em; + text-wrap: balance; +} + +.oc-routes-editorial .oc-safari-card-summary { + max-width: 660px; + margin-top: 20px; + color: #626d65; + font-size: 14px; + line-height: 1.72; +} + +.oc-safari-card-route-line { + margin-top: 18px; + padding-left: 15px; + border-left: 1px solid #738679; + color: #425148; + font-size: 12px; + line-height: 1.62; +} + +.oc-safari-card-decision { + display: grid; + grid-template-columns: 0.72fr 0.72fr 1.56fr; + margin-top: 28px; + border-top: 1px solid rgba(39, 54, 45, 0.16); + border-bottom: 1px solid rgba(39, 54, 45, 0.16); +} + +.oc-safari-card-decision > div { + min-width: 0; + padding: 15px 14px 16px 0; +} + +.oc-safari-card-decision > div + div { + padding-left: 14px; + border-left: 1px solid rgba(39, 54, 45, 0.12); +} + +.oc-safari-card-decision span, +.oc-safari-card-highlights > span { + display: block; + color: #728077; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.oc-safari-card-decision strong { + display: block; + margin-top: 6px; + color: #37483d; + font-size: 11px; + font-weight: 600; + line-height: 1.45; +} + +.oc-safari-card-highlights { + margin-top: 24px; +} + +.oc-safari-card-highlights ol { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; + margin: 13px 0 0; + padding: 0; + list-style: none; + counter-reset: route-highlight; +} + +.oc-safari-card-highlights li { + position: relative; + min-width: 0; + padding: 10px 0 0 24px; + border-top: 1px solid rgba(39, 54, 45, 0.14); + color: #3e4c43; + font-size: 11px; + line-height: 1.5; + counter-increment: route-highlight; +} + +.oc-safari-card-highlights li::before { + content: counter(route-highlight, decimal-leading-zero); + position: absolute; + top: 11px; + left: 0; + color: #738679; + font-size: 8px; + font-weight: 700; + letter-spacing: 0.08em; +} + +.oc-routes-editorial .oc-safari-card-body > .oc-card-action { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-top: auto; + padding-top: 25px; + border-top: 1px solid rgba(39, 54, 45, 0.2); + color: #31503a; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.11em; + text-transform: uppercase; +} + +@media (prefers-reduced-motion: reduce) { + .oc-routes-editorial .oc-safari-card-image img { + transition: none; + } +} + +@media (max-width: 900px) { + .oc-routes-editorial { + padding-top: 54px; + padding-bottom: 92px; + } + + .oc-routes-editorial .oc-safari-card, + .oc-routes-editorial .oc-safari-card:nth-child(even) { + grid-template-columns: 1fr; + min-height: 0; + } + + .oc-routes-editorial .oc-safari-card:nth-child(even) .oc-safari-card-image { + order: 0; + } + + .oc-routes-editorial .oc-safari-card-image { + height: 370px; + min-height: 0; + } + + .oc-routes-editorial .oc-safari-card-body { + padding: 42px; + } +} + +@media (max-width: 540px) { + .oc-routes-editorial { + width: min(100% - 32px, 700px); + padding: 42px 0 72px; + } + + .oc-routes-editorial-heading { + margin-bottom: 36px; + } + + .oc-routes-editorial-heading h2 { + font-size: 38px; + line-height: 1.12; + } + + .oc-routes-editorial-heading > p { + margin-top: 18px; + font-size: 14px; + line-height: 1.68; + } + + .oc-routes-editorial .oc-safari-grid { + display: grid; + grid-template-columns: 1fr; + gap: 18px; + } + + .oc-routes-editorial .oc-safari-card, + .oc-routes-editorial .oc-safari-card:nth-child(even) { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto auto; + width: 100%; + min-height: 0; + text-align: left; + } + + .oc-routes-editorial .oc-safari-card-image { + width: 100%; + height: 255px; + min-height: 255px; + } + + .oc-routes-editorial .oc-safari-card-body { + min-height: 0; + display: flex; + padding: 27px 22px 24px; + } + + .oc-routes-editorial .oc-safari-card-body h3 { + margin-top: 13px; + font-size: 30px; + line-height: 1.1; + } + + .oc-routes-editorial .oc-safari-card-summary { + display: block; + margin-top: 16px; + font-size: 13px; + line-height: 1.64; + -webkit-line-clamp: unset; + line-clamp: unset; + } + + .oc-safari-card-route-line { + margin-top: 15px; + font-size: 11px; + } + + .oc-safari-card-decision { + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 22px; + } + + .oc-safari-card-decision > div:nth-child(3) { + grid-column: 1 / -1; + padding-left: 0; + border-top: 1px solid rgba(39, 54, 45, 0.12); + border-left: 0; + } + + .oc-safari-card-highlights ol { + grid-template-columns: 1fr; + gap: 0; + } + + .oc-safari-card-highlights li { + padding-top: 11px; + padding-bottom: 11px; + } + + .oc-routes-editorial .oc-safari-card-body > .oc-card-action { + margin-top: 22px; + padding-top: 18px; + } +} + +/* Classic Panorama detail — route brief implementation. */ +.is-classic-panorama .oc-route-summary, +.is-outdoor-adventure .oc-route-summary, +.is-balanced-discovery .oc-route-summary { + padding-top: 52px; + padding-bottom: 92px; +} + +.oc-classic-fact-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin: 34px 0 28px; + border-top: 1px solid rgba(39, 54, 45, 0.2); + border-bottom: 1px solid rgba(39, 54, 45, 0.2); +} + +.oc-classic-fact-grid > div { + min-width: 0; + padding: 18px 16px 18px 0; +} + +.oc-classic-fact-grid > div:not(:nth-child(3n + 1)) { + padding-left: 16px; + border-left: 1px solid rgba(39, 54, 45, 0.12); +} + +.oc-classic-fact-grid > div:nth-child(n + 4) { + border-top: 1px solid rgba(39, 54, 45, 0.12); +} + +.oc-classic-fact-grid dt { + color: #718077; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.11em; + text-transform: uppercase; +} + +.oc-classic-fact-grid dd { + margin: 7px 0 0; + color: #34473a; + font-size: 12px; + font-weight: 600; + line-height: 1.52; +} + +.oc-classic-why { + padding: 110px 0 118px; + border-top: 1px solid rgba(39, 54, 45, 0.14); +} + +.oc-classic-why .oc-route-section-head { + margin-bottom: 52px; +} + +.oc-classic-why .oc-route-section-head > div, +.oc-classic-practical .oc-route-section-head > div { + display: grid; + gap: 14px; +} + +.oc-classic-why-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 18px; +} + +.oc-classic-why-grid article { + min-width: 0; + background: #efeee7; +} + +.oc-classic-why-grid img { + width: 100%; + height: 320px; + display: block; + object-fit: cover; + filter: saturate(0.76) contrast(0.95); +} + +.oc-classic-why-grid article > div { + min-height: 230px; + padding: 28px 26px 30px; +} + +.oc-classic-why-grid article span { + color: #718077; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.12em; +} + +.oc-classic-why-grid h3 { + margin-top: 16px; + color: #2f4035; + font-family: Georgia, 'Times New Roman', serif; + font-size: 24px; + font-weight: 400; + line-height: 1.3; +} + +.oc-classic-why-grid p { + margin-top: 14px; + color: #626d65; + font-size: 13px; + line-height: 1.68; +} + +.is-classic-panorama .oc-route-flow, +.is-outdoor-adventure .oc-route-flow, +.is-balanced-discovery .oc-route-flow { + padding-top: 110px; + border-top: 1px solid rgba(39, 54, 45, 0.14); +} + +.oc-route-flow figure.oc-classic-route-map { + background: #183222; +} + +.oc-classic-route-map-head { + display: grid; + gap: 8px; + padding: 24px 24px 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.14); +} + +.oc-classic-route-map-head span { + color: #b7d3af; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.13em; + text-transform: uppercase; +} + +.oc-classic-route-map-head strong { + color: #f1f5ec; + font-family: Georgia, 'Times New Roman', serif; + font-size: 22px; + font-weight: 400; + line-height: 1.3; +} + +.oc-classic-route-map ol { + margin: 0; + padding: 10px 24px 16px; + list-style: none; +} + +.oc-classic-route-map li { + position: relative; + display: grid; + grid-template-columns: 36px minmax(0, 1fr); + gap: 12px; + align-items: start; + padding: 16px 0; +} + +.oc-classic-route-map li:not(:last-child)::after { + content: ''; + position: absolute; + top: 44px; + bottom: -4px; + left: 16px; + width: 1px; + background: rgba(183, 211, 175, 0.26); +} + +.oc-classic-route-map li > span { + position: relative; + z-index: 1; + width: 33px; + height: 33px; + display: inline-grid; + place-items: center; + border: 1px solid rgba(183, 211, 175, 0.42); + border-radius: 50%; + background: #183222; + color: #d9ead3; + font-size: 8px; + font-weight: 700; + letter-spacing: 0.08em; +} + +.oc-classic-route-map li b, +.oc-classic-route-map li small { + display: block; +} + +.oc-classic-route-map li b { + color: #f1f5ec; + font-family: Georgia, 'Times New Roman', serif; + font-size: 17px; + font-weight: 400; + line-height: 1.3; +} + +.oc-classic-route-map li small { + margin-top: 4px; + color: rgba(239, 245, 235, 0.62); + font-size: 10px; + line-height: 1.45; +} + +.oc-route-day-schedule { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0; + margin: 18px 0 14px; + border-top: 1px solid rgba(39, 54, 45, 0.14); + border-bottom: 1px solid rgba(39, 54, 45, 0.14); +} + +.oc-route-day-schedule > div { + display: grid; + grid-template-columns: 62px minmax(0, 1fr); + gap: 10px; + padding: 11px 12px 11px 0; +} + +.oc-route-day-schedule > div:nth-child(even) { + padding-right: 0; + padding-left: 12px; + border-left: 1px solid rgba(39, 54, 45, 0.1); +} + +.oc-route-day-schedule > div:nth-child(n + 3) { + border-top: 1px solid rgba(39, 54, 45, 0.1); +} + +.oc-route-day-schedule dt { + color: #708077; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.oc-route-day-schedule dd { + margin: 0; + color: #526159; + font-size: 11px; + line-height: 1.55; +} + +.oc-classic-practical { + margin-bottom: 96px; + padding: 104px 0 110px; + background: #ebe9e1; +} + +.oc-classic-practical .oc-route-section-head { + margin-bottom: 48px; +} + +.oc-classic-practical-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + border-top: 1px solid rgba(39, 54, 45, 0.2); + border-left: 1px solid rgba(39, 54, 45, 0.14); +} + +.oc-classic-practical-grid article { + min-height: 210px; + padding: 26px 24px 28px; + border-right: 1px solid rgba(39, 54, 45, 0.14); + border-bottom: 1px solid rgba(39, 54, 45, 0.14); +} + +.oc-classic-practical-grid article > span { + color: #718077; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.12em; +} + +.oc-classic-practical-grid h3 { + margin-top: 22px; + color: #304036; + font-family: Georgia, 'Times New Roman', serif; + font-size: 21px; + font-weight: 400; + line-height: 1.32; +} + +.oc-classic-practical-grid p { + margin-top: 13px; + color: #626d65; + font-size: 12px; + line-height: 1.68; +} + +/* Karst Outdoor detail — graded adventure and operating gates. */ +.is-outdoor-adventure .oc-detail-strip img, +.is-outdoor-adventure .oc-classic-why-grid img { + filter: saturate(0.72) contrast(0.98); +} + +.oc-outdoor-why { + background: #f5f3ed; +} + +.oc-outdoor-grades { + padding: 112px 0 120px; + background: #1a2e22; + color: #edf2e9; +} + +.oc-outdoor-grades .oc-route-section-head { + margin-bottom: 54px; +} + +.oc-outdoor-grades .oc-route-section-head > div { + display: grid; + gap: 14px; +} + +.oc-outdoor-grades .oc-route-section-head span, +.oc-outdoor-grades .oc-route-section-head > p { + color: rgba(232, 241, 229, 0.64); +} + +.oc-outdoor-grades .oc-route-section-head h2 { + color: #f2f5ee; +} + +.oc-outdoor-grade-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + border-top: 1px solid rgba(226, 239, 220, 0.2); + border-left: 1px solid rgba(226, 239, 220, 0.14); +} + +.oc-outdoor-grade-grid article { + min-height: 310px; + padding: 30px 28px 32px; + border-right: 1px solid rgba(226, 239, 220, 0.14); + border-bottom: 1px solid rgba(226, 239, 220, 0.14); +} + +.oc-outdoor-grade-grid article > span { + color: #a9c69f; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.oc-outdoor-grade-grid h3 { + margin-top: 28px; + color: #f1f5ed; + font-family: Georgia, 'Times New Roman', serif; + font-size: 24px; + font-weight: 400; + line-height: 1.3; +} + +.oc-outdoor-grade-grid p { + margin-top: 17px; + color: rgba(235, 242, 231, 0.74); + font-size: 13px; + line-height: 1.7; +} + +.oc-outdoor-grade-grid small { + display: block; + margin-top: 28px; + padding-top: 20px; + border-top: 1px solid rgba(226, 239, 220, 0.13); + color: rgba(235, 242, 231, 0.52); + font-size: 10px; + line-height: 1.65; +} + +.oc-outdoor-gate { + display: grid; + grid-template-columns: minmax(250px, 0.72fr) minmax(0, 1.5fr); + gap: 54px; + margin-top: 56px; + padding-top: 48px; + border-top: 1px solid rgba(226, 239, 220, 0.22); +} + +.oc-outdoor-gate-intro > span { + color: #b7d3ad; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.15em; +} + +.oc-outdoor-gate-intro h3 { + margin-top: 18px; + color: #f1f5ed; + font-family: Georgia, 'Times New Roman', serif; + font-size: 28px; + font-weight: 400; + line-height: 1.28; +} + +.oc-outdoor-gate-intro p { + margin-top: 17px; + color: rgba(235, 242, 231, 0.62); + font-size: 12px; + line-height: 1.7; +} + +.oc-outdoor-gate ol { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin: 0; + padding: 0; + list-style: none; + border-top: 1px solid rgba(226, 239, 220, 0.16); + border-left: 1px solid rgba(226, 239, 220, 0.12); +} + +.oc-outdoor-gate li { + display: grid; + grid-template-columns: 28px minmax(0, 1fr); + gap: 13px; + padding: 22px 20px; + border-right: 1px solid rgba(226, 239, 220, 0.12); + border-bottom: 1px solid rgba(226, 239, 220, 0.12); +} + +.oc-outdoor-gate li > span { + color: #9fbb96; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.08em; +} + +.oc-outdoor-gate li b { + color: #eff4eb; + font-family: Georgia, 'Times New Roman', serif; + font-size: 16px; + font-weight: 400; +} + +.oc-outdoor-gate li p { + margin-top: 8px; + color: rgba(235, 242, 231, 0.58); + font-size: 10px; + line-height: 1.62; +} + +.is-outdoor-adventure .oc-route-flow figure.oc-classic-route-map { + background: #14281d; +} + +.is-outdoor-adventure .oc-classic-route-map li > span { + background: #14281d; +} + +.oc-outdoor-practical { + background: #e7ebe3; +} + +/* Balanced Discovery detail — village depth, local-led days and visible flexibility. */ +.is-balanced-discovery .oc-detail-strip img, +.is-balanced-discovery .oc-classic-why-grid img { + filter: saturate(0.78) contrast(0.96); +} + +.oc-balanced-why { + background: #f6f0e6; +} + +.oc-balanced-framework { + padding: 112px 0 120px; + background: #eee6d8; +} + +.oc-balanced-framework .oc-route-section-head { + margin-bottom: 54px; +} + +.oc-balanced-framework .oc-route-section-head > div { + display: grid; + gap: 14px; +} + +.oc-balanced-layer-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 18px; +} + +.oc-balanced-layer-grid article { + min-height: 300px; + padding: 30px 28px 32px; + background: #faf7f0; + border-top: 3px solid #64765d; +} + +.oc-balanced-layer-grid article:nth-child(2) { + border-top-color: #9a7754; +} + +.oc-balanced-layer-grid article:nth-child(3) { + border-top-color: #768b81; +} + +.oc-balanced-layer-grid article > span { + color: #7b7b6d; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.13em; + text-transform: uppercase; +} + +.oc-balanced-layer-grid h3 { + margin-top: 30px; + color: #3b463d; + font-family: Georgia, 'Times New Roman', serif; + font-size: 24px; + font-weight: 400; + line-height: 1.32; +} + +.oc-balanced-layer-grid p { + margin-top: 18px; + color: #66685f; + font-size: 13px; + line-height: 1.72; +} + +.oc-balanced-flex { + display: grid; + grid-template-columns: minmax(250px, 0.78fr) minmax(0, 1.5fr); + gap: 56px; + margin-top: 54px; + padding: 48px 42px; + background: #34463a; +} + +.oc-balanced-flex > div > span { + color: #bdcdb7; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.oc-balanced-flex > div > h3 { + margin-top: 19px; + color: #f1f2ec; + font-family: Georgia, 'Times New Roman', serif; + font-size: 27px; + font-weight: 400; + line-height: 1.3; +} + +.oc-balanced-flex dl { + margin: 0; + border-top: 1px solid rgba(236, 241, 233, 0.18); +} + +.oc-balanced-flex dl > div { + display: grid; + grid-template-columns: 130px minmax(0, 1fr); + gap: 20px; + padding: 17px 0; + border-bottom: 1px solid rgba(236, 241, 233, 0.14); +} + +.oc-balanced-flex dt { + color: #b9cbb4; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.oc-balanced-flex dd { + margin: 0; + color: rgba(240, 244, 237, 0.76); + font-size: 11px; + line-height: 1.65; +} + +.is-balanced-discovery .oc-route-flow figure.oc-classic-route-map { + background: #3d493d; +} + +.is-balanced-discovery .oc-classic-route-map li > span { + background: #3d493d; +} + +.oc-balanced-practical { + background: #eee9df; +} + +@media (max-width: 900px) { + .oc-classic-fact-grid, + .oc-classic-why-grid, + .oc-classic-practical-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .oc-classic-fact-grid > div:not(:nth-child(3n + 1)) { + padding-left: 0; + border-left: 0; + } + + .oc-classic-fact-grid > div:nth-child(even) { + padding-left: 16px; + border-left: 1px solid rgba(39, 54, 45, 0.12); + } + + .oc-classic-fact-grid > div:nth-child(n + 3) { + border-top: 1px solid rgba(39, 54, 45, 0.12); + } + + .oc-classic-why-grid article:last-child { + grid-column: 1 / -1; + } + + .oc-outdoor-grade-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .oc-outdoor-grade-grid article:last-child { + grid-column: 1 / -1; + } + + .oc-outdoor-gate { + grid-template-columns: 1fr; + } + + .oc-balanced-layer-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .oc-balanced-layer-grid article:last-child { + grid-column: 1 / -1; + } + + .oc-balanced-flex { + grid-template-columns: 1fr; + } +} + +@media (max-width: 540px) { + .is-classic-panorama .oc-route-summary, + .is-outdoor-adventure .oc-route-summary, + .is-balanced-discovery .oc-route-summary { + padding-top: 40px; + padding-bottom: 68px; + } + + .oc-classic-fact-grid { + grid-template-columns: 1fr; + margin-top: 26px; + } + + .oc-classic-fact-grid > div, + .oc-classic-fact-grid > div:nth-child(even) { + padding: 14px 0; + border-top: 1px solid rgba(39, 54, 45, 0.1); + border-left: 0; + } + + .oc-classic-fact-grid > div:first-child { + border-top: 0; + } + + .oc-classic-why { + padding: 76px 0 80px; + } + + .oc-classic-why .oc-route-section-head, + .oc-classic-practical .oc-route-section-head { + margin-bottom: 34px; + } + + .oc-classic-why-grid, + .oc-classic-practical-grid { + grid-template-columns: 1fr; + } + + .oc-classic-why-grid article:last-child { + grid-column: auto; + } + + .oc-classic-why-grid img { + height: 260px; + } + + .oc-classic-why-grid article > div { + min-height: 0; + padding: 24px 22px 27px; + } + + .is-classic-panorama .oc-route-flow, + .is-outdoor-adventure .oc-route-flow, + .is-balanced-discovery .oc-route-flow { + padding-top: 74px; + } + + .oc-route-day-schedule { + grid-template-columns: 1fr; + } + + .oc-route-day-schedule > div, + .oc-route-day-schedule > div:nth-child(even) { + grid-template-columns: 54px minmax(0, 1fr); + padding: 10px 0; + border-top: 1px solid rgba(39, 54, 45, 0.1); + border-left: 0; + } + + .oc-route-day-schedule > div:first-child { + border-top: 0; + } + + .oc-classic-practical { + margin-bottom: 72px; + padding: 74px 0 78px; + } + + .oc-classic-practical-grid article { + min-height: 0; + padding: 24px 22px 26px; + } + + .oc-outdoor-grades { + padding: 76px 0 82px; + } + + .oc-outdoor-grades .oc-route-section-head { + margin-bottom: 36px; + } + + .oc-outdoor-grade-grid, + .oc-outdoor-gate ol { + grid-template-columns: 1fr; + } + + .oc-outdoor-grade-grid article, + .oc-outdoor-grade-grid article:last-child { + min-height: 0; + grid-column: auto; + padding: 25px 22px 27px; + } + + .oc-outdoor-gate { + gap: 32px; + margin-top: 42px; + padding-top: 38px; + } + + .oc-balanced-framework { + padding: 76px 0 82px; + } + + .oc-balanced-framework .oc-route-section-head { + margin-bottom: 36px; + } + + .oc-balanced-layer-grid { + grid-template-columns: 1fr; + } + + .oc-balanced-layer-grid article, + .oc-balanced-layer-grid article:last-child { + min-height: 0; + grid-column: auto; + padding: 25px 22px 27px; + } + + .oc-balanced-flex { + gap: 30px; + margin-top: 38px; + padding: 32px 24px; + } + + .oc-balanced-flex dl > div { + grid-template-columns: 1fr; + gap: 7px; + } +} + +/* Culture / living heritage landing */ +.oc-culture-page { + --culture-ink: #1f3028; + --culture-muted: #657069; + --culture-blue: #294f59; + --culture-paper: #f5f2e9; + --culture-line: rgba(31, 48, 40, 0.16); + overflow: clip; + color: var(--culture-ink); + background: var(--culture-paper); +} + +.oc-culture-hero { + display: grid; + grid-template-columns: minmax(0, 0.94fr) minmax(480px, 1.06fr); + min-height: min(820px, calc(100vh - 30px)); + background: #ecebe3; +} + +.oc-culture-hero-copy { + display: flex; + flex-direction: column; + justify-content: center; + padding: 82px clamp(42px, 5.2vw, 92px) 64px max(28px, calc((100vw - 1320px) / 2)); +} + +.oc-culture-hero-copy .oc-breadcrumb { + margin: 0 0 50px; +} + +.oc-culture-kicker, +.oc-culture-intro-copy > span, +.oc-culture-village-grid > div > span, +.oc-culture-section-head > span { + color: #527064; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.18em; + text-transform: uppercase; +} + +.oc-culture-hero-copy h1 { + max-width: 780px; + margin: 20px 0 24px; + font-size: clamp(2.9rem, 5vw, 5.8rem); + font-weight: 430; + line-height: 0.98; + letter-spacing: -0.055em; +} + +.oc-culture-hero-copy h1 em { + display: block; + margin-top: 0.12em; + color: var(--culture-blue); + font-family: Georgia, 'Times New Roman', serif; + font-weight: 400; +} + +.oc-culture-hero-copy > p { + max-width: 670px; + margin: 0; + color: #4e5d55; + font-size: clamp(1rem, 1.25vw, 1.18rem); + line-height: 1.78; +} + +.oc-culture-hero-actions { + display: flex; + align-items: center; + gap: 24px; + margin-top: 34px; +} + +.oc-culture-hero-actions a { + color: var(--culture-ink); + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.07em; + text-transform: uppercase; +} + +.oc-culture-hero-actions .oc-culture-primary { + display: inline-flex; + align-items: center; + gap: 15px; + min-height: 50px; + padding: 0 20px; + color: #fff; + background: var(--culture-blue); +} + +.oc-culture-hero-actions svg, +.oc-culture-card-link svg, +.oc-culture-village-grid > div > a svg { + width: 17px; + height: 17px; + flex: 0 0 auto; +} + +.oc-culture-hero-facts { + display: grid; + grid-template-columns: 0.75fr 0.75fr 1.5fr; + gap: 22px; + margin: 52px 0 0; + padding-top: 22px; + border-top: 1px solid var(--culture-line); +} + +.oc-culture-hero-facts > div { + display: grid; + gap: 6px; +} + +.oc-culture-hero-facts dt { + color: var(--culture-blue); + font-family: Georgia, 'Times New Roman', serif; + font-size: 1.65rem; +} + +.oc-culture-hero-facts dd { + margin: 0; + color: var(--culture-muted); + font-size: 0.76rem; + line-height: 1.45; +} + +.oc-culture-hero-media { + position: relative; + min-height: 720px; + overflow: hidden; + background: #d6d8d0; +} + +.oc-culture-hero-media > img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: 50% 50%; + filter: saturate(0.76) contrast(0.96); +} + +.oc-culture-hero-media > figure { + position: absolute; + right: clamp(22px, 3.5vw, 54px); + bottom: clamp(22px, 3.5vw, 54px); + width: min(31%, 235px); + margin: 0; + padding: 8px; + color: #fff; + background: rgba(23, 39, 33, 0.88); +} + +.oc-culture-hero-media > figure img { + display: block; + width: 100%; + aspect-ratio: 0.78; + object-fit: cover; + filter: saturate(0.8); +} + +.oc-culture-hero-media figcaption, +.oc-culture-intro-image figcaption, +.oc-culture-village figure figcaption { + font-size: 0.67rem; + line-height: 1.5; +} + +.oc-culture-hero-media figcaption { + padding: 9px 4px 2px; + color: rgba(255, 255, 255, 0.72); +} + +.oc-culture-subnav { + position: sticky; + z-index: 8; + top: 74px; + display: grid; + grid-template-columns: repeat(3, 1fr); + border-top: 1px solid rgba(255, 255, 255, 0.08); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(31, 48, 40, 0.96); + backdrop-filter: blur(12px); +} + +.oc-culture-subnav a { + padding: 18px 22px; + border-left: 1px solid rgba(255, 255, 255, 0.12); + color: rgba(255, 255, 255, 0.82); + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.08em; + text-align: center; + text-transform: uppercase; +} + +.oc-culture-subnav a:first-child { + border-left: 0; +} + +.oc-culture-intro { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(340px, 0.62fr); + gap: clamp(54px, 8vw, 126px); + align-items: end; + padding-top: 122px; + padding-bottom: 84px; +} + +.oc-culture-intro-copy h2, +.oc-culture-village-grid h2, +.oc-culture-section-head h2 { + margin: 18px 0 24px; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(2.25rem, 4.2vw, 4.7rem); + font-weight: 400; + line-height: 1.05; + letter-spacing: -0.042em; +} + +.oc-culture-intro-copy p, +.oc-culture-village-grid > div > p { + max-width: 760px; + margin: 0; + color: var(--culture-muted); + font-size: 1rem; + line-height: 1.82; +} + +.oc-culture-intro-image { + margin: 0; +} + +.oc-culture-intro-image img { + display: block; + width: 100%; + aspect-ratio: 0.8; + object-fit: cover; + filter: saturate(0.76); +} + +.oc-culture-intro-image figcaption { + padding-top: 12px; + color: #7d827e; +} + +.oc-culture-crafts { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + padding-bottom: 126px; + border-top: 1px solid var(--culture-line); + border-left: 1px solid var(--culture-line); +} + +.oc-culture-craft-card { + display: flex; + min-height: 390px; + flex-direction: column; + padding: clamp(30px, 4vw, 58px); + border-right: 1px solid var(--culture-line); + border-bottom: 1px solid var(--culture-line); + color: var(--culture-ink); + transition: color 180ms ease, background 180ms ease; +} + +.oc-culture-craft-card:hover { + color: #fff; + background: var(--culture-blue); +} + +.oc-culture-craft-card > div { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.oc-culture-craft-card > div > span { + color: #6a8076; + font-family: Georgia, 'Times New Roman', serif; + font-size: 1.55rem; +} + +.oc-culture-craft-card > div > small { + color: #7d8580; + font-size: 0.66rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.oc-culture-craft-card h3 { + max-width: 580px; + margin: 62px 0 12px; + font-family: Georgia, 'Times New Roman', serif; + font-size: clamp(1.8rem, 3vw, 3.1rem); + font-weight: 400; + line-height: 1.08; + letter-spacing: -0.035em; +} + +.oc-culture-craft-card > strong { + color: #607068; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.04em; +} + +.oc-culture-craft-card > p { + max-width: 650px; + margin: 22px 0 30px; + color: var(--culture-muted); + font-size: 0.9rem; + line-height: 1.72; +} + +.oc-culture-card-link { + display: inline-flex; + align-items: center; + gap: 12px; + margin-top: auto; + color: var(--culture-blue); + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.oc-culture-craft-card:hover :is(div > span, div > small, strong, p, .oc-culture-card-link) { + color: rgba(255, 255, 255, 0.82); +} + +.oc-culture-village { + padding: 116px 0; + color: #f4f0e5; + background: #20332c; +} + +.oc-culture-village-grid { + display: grid; + grid-template-columns: minmax(360px, 0.92fr) minmax(0, 1.08fr); + gap: clamp(54px, 8vw, 126px); + align-items: center; +} + +.oc-culture-village figure { + margin: 0; +} + +.oc-culture-village figure img { + display: block; + width: 100%; + aspect-ratio: 0.9; + object-fit: cover; + filter: saturate(0.74) brightness(0.92); +} + +.oc-culture-village figure figcaption { + padding-top: 12px; + color: rgba(255, 255, 255, 0.48); +} + +.oc-culture-village-grid > div > span { + color: #a9c1b5; +} + +.oc-culture-village-grid h2 { + color: #f5f1e7; +} + +.oc-culture-village-grid > div > p { + color: rgba(255, 255, 255, 0.68); +} + +.oc-culture-village-grid ul { + display: grid; + gap: 13px; + margin: 32px 0 36px; + padding: 0; + list-style: none; +} + +.oc-culture-village-grid li { + display: flex; + align-items: center; + gap: 12px; + color: rgba(255, 255, 255, 0.82); + font-size: 0.86rem; +} + +.oc-culture-village-grid li svg { + width: 18px; + height: 18px; + color: #9dbaa9; +} + +.oc-culture-village-grid > div > a { + display: inline-flex; + align-items: center; + gap: 13px; + color: #f5f1e7; + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.oc-culture-village-actions { + display: flex; + flex-wrap: wrap; + gap: 18px 30px; +} + +.oc-culture-village-actions > a { + display: inline-flex; + align-items: center; + gap: 13px; + color: #f5f1e7; + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.oc-culture-village-actions > a:last-child { + color: rgba(245, 241, 231, .68); +} + +.oc-culture-festival { + border-top: 1px solid rgba(255, 255, 255, .12); + background: #4a332b; +} + +.oc-culture-festival .oc-culture-village-grid { + grid-template-columns: minmax(0, 1.08fr) minmax(360px, .92fr); +} + +.oc-culture-festival figure img { + object-position: center 36%; +} + +.oc-culture-culinary { + border-top: 1px solid rgba(255, 255, 255, .12); + background: #5a4537; +} + +.oc-culture-culinary figure img { + object-position: center 42%; +} + +.oc-culture-principles { + padding-top: 122px; + padding-bottom: 126px; +} + +.oc-culture-section-head { + display: grid; + grid-template-columns: 0.36fr 1fr; + gap: 42px; + align-items: start; + margin-bottom: 62px; +} + +.oc-culture-section-head h2 { + max-width: 980px; + margin: 0; +} + +.oc-culture-principle-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + border-top: 1px solid var(--culture-line); + border-left: 1px solid var(--culture-line); +} + +.oc-culture-principle-grid article { + min-height: 278px; + padding: 30px 27px; + border-right: 1px solid var(--culture-line); + border-bottom: 1px solid var(--culture-line); +} + +.oc-culture-principle-grid article > span { + color: #789087; + font-family: Georgia, 'Times New Roman', serif; + font-size: 1.15rem; +} + +.oc-culture-principle-grid h3 { + margin: 52px 0 18px; + font-family: Georgia, 'Times New Roman', serif; + font-size: 1.55rem; + font-weight: 400; +} + +.oc-culture-principle-grid p { + margin: 0; + color: var(--culture-muted); + font-size: 0.84rem; + line-height: 1.68; +} + +.oc-culture-page > .oc-menu-bottom-prompt { + margin-bottom: 110px; + border-top-color: var(--culture-line); +} + +@media (max-width: 1080px) { + .oc-culture-hero { + grid-template-columns: 1fr 0.9fr; + } + + .oc-culture-hero-copy { + padding-left: 34px; + } + + .oc-culture-hero-facts { + grid-template-columns: repeat(2, 1fr); + } + + .oc-culture-hero-facts > div:last-child { + grid-column: 1 / -1; + } + + .oc-culture-principle-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 760px) { + .oc-culture-hero { + display: flex; + min-height: 0; + flex-direction: column-reverse; + } + + .oc-culture-hero-media { + min-height: 480px; + } + + .oc-culture-hero-media > img { + object-position: 50% 47%; + } + + .oc-culture-hero-media > figure { + right: 14px; + bottom: 14px; + width: 31%; + padding: 5px; + } + + .oc-culture-hero-media figcaption { + display: none; + } + + .oc-culture-hero-copy { + padding: 42px 20px 52px; + } + + .oc-culture-hero-copy .oc-breadcrumb { + margin-bottom: 34px; + } + + .oc-culture-hero-copy h1 { + margin-top: 16px; + font-size: clamp(2.55rem, 12vw, 4rem); + } + + .oc-culture-hero-actions { + align-items: stretch; + flex-direction: column; + gap: 18px; + } + + .oc-culture-hero-actions .oc-culture-primary { + justify-content: space-between; + } + + .oc-culture-hero-facts { + grid-template-columns: repeat(2, 1fr); + gap: 18px; + margin-top: 38px; + } + + .oc-culture-subnav { + top: 66px; + display: flex; + overflow-x: auto; + } + + .oc-culture-subnav a { + min-width: max-content; + flex: 1 0 auto; + padding: 15px 18px; + } + + .oc-culture-intro { + grid-template-columns: 1fr; + gap: 42px; + padding-top: 78px; + padding-bottom: 58px; + } + + .oc-culture-intro-copy h2, + .oc-culture-village-grid h2, + .oc-culture-section-head h2 { + font-size: clamp(2.05rem, 10vw, 3.2rem); + } + + .oc-culture-intro-image { + width: 78%; + margin-left: auto; + } + + .oc-culture-crafts { + grid-template-columns: 1fr; + padding-bottom: 78px; + } + + .oc-culture-craft-card { + min-height: 0; + padding: 28px 22px 30px; + } + + .oc-culture-craft-card h3 { + margin-top: 46px; + } + + .oc-culture-card-link { + margin-top: 10px; + } + + .oc-culture-village { + padding: 78px 0; + } + + .oc-culture-village-grid { + grid-template-columns: 1fr; + gap: 46px; + } + + .oc-culture-festival .oc-culture-village-grid { + grid-template-columns: 1fr; + } + + .oc-culture-village figure { + width: calc(100% - 32px); + } + + .oc-culture-principles { + padding-top: 78px; + padding-bottom: 82px; + } + + .oc-culture-section-head { + grid-template-columns: 1fr; + gap: 22px; + margin-bottom: 42px; + } + + .oc-culture-principle-grid { + grid-template-columns: 1fr; + } + + .oc-culture-principle-grid article { + min-height: 0; + padding: 26px 22px 29px; + } + + .oc-culture-principle-grid h3 { + margin-top: 34px; + } + + .oc-culture-page > .oc-menu-bottom-prompt { + margin-bottom: 72px; + } +} diff --git a/src/TravelSite.tsx b/src/TravelSite.tsx index 643e8c9..dbf66c5 100644 --- a/src/TravelSite.tsx +++ b/src/TravelSite.tsx @@ -1,13 +1,19 @@ -import { Children, cloneElement, createContext, isValidElement, useContext, useEffect, useMemo, useRef, useState, type CSSProperties, type FormEvent, type ReactElement, type ReactNode } from 'react'; +import { createContext, useContext, useEffect, useMemo, useRef, useState, type CSSProperties, type FormEvent, type ReactNode } from 'react'; import { gsap } from 'gsap'; +import { ScrollTrigger } from 'gsap/ScrollTrigger'; import { useGSAP } from '@gsap/react'; import { ArrowLeft, ArrowRight, CheckCircle, + Globe, + Headset, List, MagnifyingGlass, + MapPin, + PauseCircle, PlayCircle, + UsersThree, X, } from '@phosphor-icons/react'; import { @@ -38,7 +44,7 @@ import { createInquiry } from './api'; import type { CmsContent, Destination, DetailContentBlock, Product, ProductRoutePlan } from './cms'; import './TravelSite.css'; -gsap.registerPlugin(useGSAP); +gsap.registerPlugin(useGSAP, ScrollTrigger); function asset(source: string) { return source; @@ -52,7 +58,7 @@ function applySiteLanguage(_root: Element, _language: SiteLanguage) { const bg = (source: string) => ({ backgroundImage: `url(${asset(source)})` }); const moduleHeroBg = (source: string) => ({ '--oc-module-hero-image': `url(${asset(source)})` }) as CSSProperties; -const languageStorageKey = 'wanqu-travel-language'; +const languageStorageKey = 'wanqu-travel-language-en-default-v1'; const videoSourcePattern = /\.(mp4|webm|ogv|ogg|mov)(?:[?#].*)?$/i; function isVideoSource(source: string) { @@ -118,9 +124,22 @@ type MegaMenu = { type DetailIconSize = 'tab' | 'amenity' | 'live'; -type FilterSelections = Record; type SafariRouteHighlight = { title: string; copy: string; image: string }; -type SafariRouteDay = { day: string; title: string; place: string; copy: string; meta: string[] }; +type SafariRouteDay = { + day: string; + title: string; + place: string; + copy: string; + meta: string[]; + schedule?: { + morning: string; + afternoon: string; + evening: string; + drive: string; + meals: string; + stay: string; + }; +}; type RouteDetailPillarId = 'route-flow' | 'route-stay' | 'route-food' | 'route-transport'; type RouteDetailPillar = { id: RouteDetailPillarId; @@ -259,6 +278,7 @@ function localizeSafariCard(safari: SafariCardItem, language: SiteLanguage): Saf place: day.place, copy: day.copy, meta: [...day.meta], + schedule: 'schedule' in day ? { ...day.schedule } : undefined, })), inclusions: [...override.inclusions], exclusions: [...override.exclusions], @@ -266,24 +286,6 @@ function localizeSafariCard(safari: SafariCardItem, language: SiteLanguage): Saf }; } -function localizeExperienceCard(card: ArchiveCardItem, language: SiteLanguage): ArchiveCardItem { - if (language === 'zh') return card; - const slug = archiveCardSlug(card); - const override = experienceEnglish[slug as keyof typeof experienceEnglish]; - return override - ? { ...card, title: override.title, meta: override.meta, type: override.type, copy: override.copy } - : { ...card, title: translateUi(card.title, language), meta: translateUi(card.meta, language), type: card.type ? translateUi(card.type, language) : card.type, copy: translateUi(card.copy, language) }; -} - -function localizeHotelCard(card: ArchiveCardItem, language: SiteLanguage): ArchiveCardItem { - if (language === 'zh') return card; - const slug = archiveCardSlug(card); - const override = hotelEnglish[slug as keyof typeof hotelEnglish]; - return override - ? { ...card, title: override.title, meta: override.meta, copy: override.copy } - : { ...card, title: translateUi(card.title, language), meta: translateUi(card.meta, language), copy: translateUi(card.copy, language) }; -} - function localizeFoodCard(card: FoodCardItem, language: SiteLanguage): FoodCardItem { if (language === 'zh') return card; const override = foodEnglish[card.id as keyof typeof foodEnglish]; @@ -482,76 +484,6 @@ function localizePoiDetail(detail: PoiDetailContent, language: SiteLanguage, slu }; } -function normalizeFilterValue(value: string) { - return value.trim().toLowerCase(); -} - -function selectedFilterValues(filters: FilterSelections, title: string) { - return filters[title] || []; -} - -function nextFilterSelections(filters: FilterSelections, title: string, item: string) { - const next = { ...filters }; - if (item === 'All' || item === '全部') { - delete next[title]; - return next; - } - - const current = selectedFilterValues(filters, title); - const selected = current.includes(item) ? current.filter((value) => value !== item) : [...current, item]; - if (selected.length) { - next[title] = selected; - } else { - delete next[title]; - } - return next; -} - -function setSingleFilterSelection(filters: FilterSelections, title: string, item: string) { - const next = { ...filters }; - if (item === 'All' || item === '全部') { - delete next[title]; - } else { - next[title] = [item]; - } - return next; -} - -function matchesAnySelected(selected: string[], matcher: (value: string) => boolean) { - return selected.length === 0 || selected.some(matcher); -} - -function safariDurationRange(safari: SafariCardItem) { - const [start, end] = (safari.duration.match(/\d+/g) || []).map(Number); - const min = start || 0; - const max = end || min; - return { min, max }; -} - -function safariMatchesDurationFilter(safari: SafariCardItem, value: string) { - const { min, max } = safariDurationRange(safari); - if (value === '1日游') return max <= 1; - if (value === '3天以内') return max <= 3; - if (value === '3-5天') return min >= 3 && min <= 5; - if (value === '5天以上') return max >= 5; - return safari.duration === value || safari.nights.includes(value); -} - -function safariMatchesFilters(safari: SafariCardItem, filters: FilterSelections) { - const categoryFilters = selectedFilterValues(filters, '线路分类'); - const focusFilters = selectedFilterValues(filters, '游玩重点'); - const paceFilters = selectedFilterValues(filters, '旅行节奏'); - const durationFilters = selectedFilterValues(filters, '天数'); - const destinations = safari.destinations.map(normalizeFilterValue); - - return ( - matchesAnySelected(categoryFilters, (value) => safari.category === value) && - matchesAnySelected(focusFilters, (value) => safari.focus === value) && - matchesAnySelected(paceFilters, (value) => safari.pace === value) && - matchesAnySelected(durationFilters, (value) => safariMatchesDurationFilter(safari, value)) && - destinations.length > 0 - ); -} function safariFromSlug(slug: string, cmsContent: CmsContent | undefined, language: SiteLanguage) { const cmsSafariCards = cmsRouteCards(cmsContent, language); @@ -794,47 +726,33 @@ function findCmsDestination(cmsContent: CmsContent | undefined, slug: string, ti )); } -function initialSafariFilters(): FilterSelections { - if (typeof window === 'undefined') return {}; - const params = new URLSearchParams(window.location.search); - const category = params.get('category'); - const focus = params.get('focus'); - return { - ...(category ? { 线路分类: [category] } : {}), - ...(focus ? { 游玩重点: [focus] } : {}), - }; -} - -function experienceMatchesFilters(card: ArchiveCardItem, filters: FilterSelections) { - const typeFilters = selectedFilterValues(filters, '体验类型'); - const locationFilters = selectedFilterValues(filters, '地点'); - const type = normalizeFilterValue(card.type || ''); - const meta = normalizeFilterValue(card.meta); - - return ( - matchesAnySelected(typeFilters, (value) => type === normalizeFilterValue(value)) && - matchesAnySelected(locationFilters, (value) => meta.includes(normalizeFilterValue(value))) - ); -} - -function collectiveMatchesFilters(card: ArchiveCardItem, filters: FilterSelections) { - const locationFilters = selectedFilterValues(filters, '地点'); - const meta = normalizeFilterValue(card.meta); - return matchesAnySelected(locationFilters, (value) => meta.includes(normalizeFilterValue(value))); -} - const experienceTitleZh: Record = { 'cave-expedition': '天然溶洞探险', - rafting: '峡谷漂流与亲水', + 'cave-luxury-dining': '洞穴咖啡与私家晚宴', + rafting: '峡谷溯溪与白水漂流', 'stream-tracing': '溯溪与瀑降', + 'karst-hiking-cycling': '喀斯特峰林徒步与骑行', 'miao-craft': '苗绣与蜡染非遗体验', + 'dong-indigo-architecture': '侗族草木染与木构建筑', + 'shui-horsetail-embroidery': '水族马尾绣工坊', + 'buyi-traditional-crafts': '布依族传统织染技艺', 'dong-song': '侗族大歌与鼓楼村寨', huangguoshu: '黄果树瀑布错峰玩法', 'libo-xiaoqikong': '荔波樟江·小七孔喀斯特水系', 'fanjing-mountain': '梵净山天气窗口', 'xijiang-miao-village': '西江与侗寨夜晚', wanfenglin: '兴义万峰林锥状喀斯特峰林', - 'wild-coffee': '山野咖啡停靠', + 'wild-coffee': '悬崖与山野观景咖啡', + 'village-immersion': '原生村寨沉浸', + 'ethnic-festivals': '节庆民俗盛典', + 'culinary-cultural-journeys': '民族美食文化', + 'iconic-waterfall-clusters': '世界级瀑布群', + 'indigenous-ethnic-villages': '原生民族村寨', + 'historic-ancient-towns': '历史古镇古村', + 'qingyan-battle-immersive-roleplay': '青岩战斗・实景沉浸演绎', + 'loushanguan-battle-immersive-march': '娄山关战役・山道沉浸行军', + 'karst-glamping': '喀斯特野奢星空露营', + 'heritage-outdoor-pairings': '非遗与轻户外融合体验', 'hot-spring-recovery': '温泉恢复日', }; @@ -1710,7 +1628,7 @@ function TravelSite({ content, cmsContent }: { content?: SiteContent; cmsContent const [language, setLanguage] = useState(() => { const stored = window.localStorage.getItem(languageStorageKey); if (stored === 'zh' || stored === 'en') return stored; - return cmsContent?.settings.defaultLanguage || 'zh'; + return cmsContent?.settings.defaultLanguage || 'en'; }); const activeSiteContent = useMemo(() => normalizeSiteContent(content), [content]); const path = normalizePath(window.location.pathname); @@ -1809,7 +1727,7 @@ const routeStayPlans: Record> = 'outdoor-coffee': { title: '户外活动前后用住宿做体力缓冲', summary: '高活动量线路的住宿不追求频繁换风景,而是服务装备整理、早出发、活动后洗浴恢复和山地咖啡停靠。', - image: img.routeOutdoor, + image: img.clientMountainPause, tags: ['装备整理', '早出发', '恢复夜'], items: ['贵阳集合日靠近补给和装备确认点。', '洞穴、峡谷或亲水活动后优先保证热水、洗衣和睡眠。', '山野咖啡可作为高强度后的轻恢复住宿节点。'], href: '/hotels/', @@ -1818,7 +1736,7 @@ const routeStayPlans: Record> = 'culture-outdoor-mix': { title: '村寨住宿和自然节点交替,避免体验断层', summary: '混搭线要让文化体验和轻户外互相支撑。村寨或古镇停留需要足够时间,酒店选择要兼顾翻译沟通、餐食和第二天转场。', - image: img.destinationXijiang, + image: img.clientRouteBalanced, tags: ['村寨慢住', '轻户外缓冲', '沟通便利'], items: ['黔东南段优先保证一晚以上慢停留。', '轻户外前后搭配更易休整的住宿节点。', '境外客人入住点要考虑英文沟通和餐食稳定性。'], href: '/hotels/', @@ -1866,7 +1784,7 @@ const routeTransportPlans: Record> = 'outdoor-coffee': { title: '户外补给、热食恢复和山野咖啡', summary: '高强度户外线的饮食先服务体力,再服务特色。活动日前后要安排热食、补水、轻便餐和山地咖啡恢复。', - image: img.experienceCoffee, + image: img.clientCaveDinner, tags: ['活动补给', '热食恢复', '野咖'], items: ['探洞、溯溪或漂流日前要准备补水和轻便能量食。', '活动后优先安排热汤、粉面或容易接受的恢复餐。', '山野咖啡适合作为轻松半日,不和高强度活动硬塞同一天。'], href: '/food/', @@ -1932,7 +1850,7 @@ const routeFoodPlans: Record> = 'culture-outdoor-mix': { title: '村寨主理人餐、酸汤风味和轻户外恢复餐', summary: '人文混搭线的餐食本身就是体验。需要解释礼仪、口味和食材,同时给不习惯酸辣的境外客人准备替代选择。', - image: img.destinationXijiang, + image: img.clientLocalTable, tags: ['村寨餐食', '酸汤', '替代菜单'], items: ['村寨家宴或长桌餐要提前说明礼仪和食材。', '酸汤、腌鱼、牛瘪等强地方风味需要确认可接受度。', '轻户外日前后用稳定正餐恢复体力。'], href: '/food/', @@ -2082,6 +2000,8 @@ function routeDetailPillars(safari: SafariCardItem, language: SiteLanguage = 'zh } function selectPage(path: string) { + if (path === '/preview/classic') return ; + if (path === '/preview/v1') return ; if (path === '/safari-faqs' || path === '/faqs') return ; if (path.startsWith('/our-guides')) return ; if (path.startsWith('/meet-the-team')) return ; @@ -2093,8 +2013,9 @@ function selectPage(path: string) { if (path.startsWith('/blog') || path.startsWith('/our-expert-guides') || path.startsWith('/live-from-camp')) return ; if (path.startsWith('/new-agents-sign-up') || path.startsWith('/trade-portal')) return ; if (path.startsWith('/expedition')) return ; - if (path.startsWith('/signature')) return ; + if (path.startsWith('/signature')) return ; if (path.startsWith('/icon')) return ; + if (path === '/culture' || path.startsWith('/culture/')) return ; if (path === '/landscapes') return ; if (path === '/safaris') return ; if (path.startsWith('/safaris/')) return ; @@ -2484,6 +2405,22 @@ function MegaMenu({ menu }: { menu: SiteMegaMenu }) { function FullMenu({ onClose }: { onClose: () => void }) { const { content, language } = useTravelSite(); const menuRef = useRef(null); + const closeRef = useRef(null); + + useEffect(() => { + const returnFocusTo = document.activeElement instanceof HTMLElement ? document.activeElement : null; + closeRef.current?.focus(); + + const handleKeyDown = (event: globalThis.KeyboardEvent) => { + if (event.key === 'Escape') onClose(); + }; + + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + returnFocusTo?.focus(); + }; + }, [onClose]); useGSAP( () => { @@ -2561,7 +2498,7 @@ function FullMenu({ onClose }: { onClose: () => void }) { return (
- @@ -2595,73 +2532,493 @@ function FullMenu({ onClose }: { onClose: () => void }) { function SearchOverlay({ onClose }: { onClose: () => void }) { const { content, language } = useTravelSite(); + const [query, setQuery] = useState(''); + const returnFocusRef = useRef(null); + const allSearchLinks = useMemo(() => { + const links = [ + ...enabledSiteLinks(content.search.suggestions), + ...enabledSiteLinks(content.header.nav), + ...content.megaMenus.flatMap((menu) => menu.groups.flatMap((group) => enabledSiteLinks(group.links))), + ]; + const uniqueLinks = new Map(); + links.forEach((link) => { + const key = `${link.href}|${oc(link.label, language)}`; + if (!uniqueLinks.has(key)) uniqueLinks.set(key, link); + }); + return Array.from(uniqueLinks.values()); + }, [content, language]); + const visibleSearchLinks = useMemo(() => { + const normalizedQuery = query.trim().toLocaleLowerCase(); + const source = normalizedQuery ? allSearchLinks : enabledSiteLinks(content.search.suggestions); + return source + .filter((link) => !normalizedQuery || oc(link.label, language).toLocaleLowerCase().includes(normalizedQuery)) + .slice(0, 12); + }, [allSearchLinks, content.search.suggestions, language, query]); + + useEffect(() => { + returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null; + const handleKeyDown = (event: globalThis.KeyboardEvent) => { + if (event.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + returnFocusRef.current?.focus(); + }; + }, [onClose]); + return ( -
+
-

{oc(content.search.title, language)}

+

{oc(content.search.title, language)}

- {enabledSiteLinks(content.search.suggestions).map((suggestion) => ( + {visibleSearchLinks.map((suggestion) => ( {oc(suggestion.label, language)} ))} + {query.trim() && visibleSearchLinks.length === 0 && ( +

{pickText('没有找到匹配内容,请尝试地点或体验名称。', 'No matching pages. Try a destination or experience name.', language)}

+ )}
); } -function HomePage() { +function HomePage({ variant = 'v1' }: { variant?: 'classic' | 'v1' }) { const { content, language } = useTravelSite(); + const prototypeRef = useRef(null); const home = content.home; const heroSlides = useMemo( () => [home.hero.imageUrl, ...home.hero.awardUrls].filter(Boolean), [home.hero.awardUrls, home.hero.imageUrl], ); const liveImages = home.liveGallery.imageUrls; + const liveGalleryCaptions = [ + pickText('在地餐桌,慢慢分享', 'A local table, shared slowly', language), + pickText('喀斯特户外日', 'A day in the karst', language), + pickText('洞穴里的特别晚餐', 'Dinner inside a cave', language), + pickText('山野间的一次停靠', 'A quiet mountain pause', language), + pickText('亲手体验非遗技艺', 'Heritage, made by hand', language), + pickText('走近瀑布', 'Waterfall, up close', language), + ]; const [heroIndex, setHeroIndex] = useState(0); + const [heroPaused, setHeroPaused] = useState(false); const activeHero = heroSlides[heroIndex % heroSlides.length] || home.hero.imageUrl; + useGSAP( + () => { + if (variant === 'classic') return; + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + + const heroTimeline = gsap.timeline({ defaults: { ease: 'power3.out' } }); + heroTimeline + .from('.oc-prototype-hero-kicker', { y: 18, autoAlpha: 0, duration: 0.6 }) + .from('.oc-prototype-hero-title-line', { yPercent: 115, duration: 0.95, stagger: 0.1 }, '-=0.3') + .from('.oc-prototype-hero-side', { y: 20, autoAlpha: 0, duration: 0.65 }, '-=0.45') + .from('.oc-prototype-hero-footer > *', { y: 14, autoAlpha: 0, duration: 0.45, stagger: 0.06 }, '-=0.3'); + + gsap.to('.oc-prototype-hero-image', { + yPercent: 8, + scale: 1.06, + ease: 'none', + scrollTrigger: { + trigger: '.oc-prototype-hero', + start: 'top top+=90', + end: 'bottom top', + scrub: true, + }, + }); + + gsap.from('.oc-prototype-theme-head > *', { + y: 60, + autoAlpha: 0, + duration: 0.85, + stagger: 0.08, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-prototype-themes', start: 'top 72%' }, + }); + + gsap.from('.oc-prototype-theme-card', { + y: 80, + autoAlpha: 0, + duration: 0.85, + stagger: 0.08, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-prototype-theme-grid', start: 'top 78%' }, + }); + + gsap.from('.oc-prototype-story-copy > *', { + y: 54, + autoAlpha: 0, + duration: 0.8, + stagger: 0.09, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-prototype-story', start: 'top 67%' }, + }); + + gsap.from('.oc-prototype-story-step', { + x: 54, + autoAlpha: 0, + duration: 0.7, + stagger: 0.12, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-prototype-story-steps', start: 'top 72%' }, + }); + }, + { scope: prototypeRef, dependencies: [language, variant] }, + ); + useEffect(() => { - if (heroSlides.length <= 1) return undefined; + if (heroSlides.length <= 1 || heroPaused) return undefined; const timer = window.setInterval(() => { setHeroIndex((current) => (current + 1) % heroSlides.length); - }, 5200); + }, 5600); return () => window.clearInterval(timer); - }, [heroSlides.length]); + }, [heroPaused, heroSlides.length]); + + const trustSignals = [ + { + icon: MapPin, + title: pickText('贵州本地团队', 'Guizhou-based team', language), + copy: pickText('熟悉山路、天气与在地节奏', 'Local knowledge of roads, weather and daily rhythms', language), + }, + { + icon: UsersThree, + title: pickText('纯玩私家小团', 'Private journeys only', language), + copy: pickText('按人数、兴趣与节奏定制', 'Designed around your people, interests and pace', language), + }, + { + icon: Globe, + title: pickText('英文旅行支持', 'English trip support', language), + copy: pickText('双语沟通与文化说明', 'Bilingual planning and cultural context', language), + }, + { + icon: Headset, + title: pickText('24 小时在地支持', '24/7 on-ground support', language), + copy: pickText('行程期间持续响应与协调', 'Responsive coordination throughout your journey', language), + }, + ]; + + const prototypeThemes = [ + { number: '01', title: pickText('喀斯特山野', 'Karst Wilds', language), image: '/client-assets/karst-hike-ridge.jpg', href: '/experiences/karst-hiking/' }, + { number: '02', title: pickText('村寨生活', 'Village Life', language), image: '/client-assets/villages-xijiang-dusk.jpg', href: '/experiences/indigenous-ethnic-villages/' }, + { number: '03', title: pickText('瀑布秘境', 'Water Worlds', language), image: '/client-assets/waterfall-huangguoshu-curtain.jpg', href: '/experiences/iconic-waterfall-clusters/' }, + { number: '04', title: pickText('贵州餐桌', 'Local Table', language), image: '/client-assets/culinary-long-table-banquet.jpg', href: '/experiences/culinary-cultural-journeys/' }, + { number: '05', title: pickText('非遗手作', 'Living Craft', language), image: '/client-assets/culture-batik-making.jpg', href: '/experiences/village-immersion/' }, + { number: '06', title: pickText('野趣冒险', 'Wild Adventure', language), image: '/client-assets/river-packraft-shore.jpg', href: '/experiences/river-adventure/' }, + { number: '07', title: pickText('古城时间', 'Ancient Time', language), image: '/client-assets/ancient-towns-zhenyuan-night.jpg', href: '/experiences/historic-ancient-towns/' }, + { number: '08', title: pickText('为你定制', 'Made for You', language), image: '/client-assets/hero-real-travelers.webp', href: '/contact/' }, + ]; + + const prototypeJourneySteps = [ + { + number: '01', + label: pickText('从你出发', 'Start with you', language), + copy: pickText('不是先卖一条线路。我们先了解同行的人、真正感兴趣的事,以及你想怎样度过时间。', 'Not with a package. We begin with who is travelling, what moves you and how you want your days to feel.', language), + }, + { + number: '02', + label: pickText('把贵州重新编排', 'Shape the journey', language), + copy: pickText('本地团队把山水、村寨、住宿与交通重新组合成只属于这一组客人的路线。', 'Our local team brings landscapes, villages, stays and transport into one private rhythm.', language), + }, + { + number: '03', + label: pickText('放心走进真实现场', 'Travel beyond the obvious', language), + copy: pickText('双语支持与在地协调始终在线,你只需要靠近那些真正值得记住的人和地方。', 'Bilingual, on-ground support stays close while you meet the people and places worth remembering.', language), + }, + ]; + + const brandValues = [ + { + number: '01', + title: pickText('合规入境接待', 'Compliant inbound reception', language), + copy: pickText('为境外游客提供清晰、正规的入境接待流程。', 'A clear, compliant reception process for international travelers.', language), + }, + { + number: '02', + title: pickText('懂贵州,也会讲贵州', 'Local stories, not scripts', language), + copy: pickText('讲清这里的文化与生活,不做背书式流水线导游。', 'Guizhou specialists who explain the culture, not a memorized script.', language), + }, + { + number: '03', + title: pickText('熟悉山路与天气', 'Locals who know the road', language), + copy: pickText('熟悉贵州山路、天气与现场变化,及时响应。', 'Local knowledge of mountain roads, weather and changing conditions.', language), + }, + { + number: '04', + title: pickText('不赶场的真实旅行', 'A slower, more human pace', language), + copy: pickText('拒绝赶场式打卡,把时间留给山水、村寨与真实生活。', 'No checklist travel—more time for landscapes, villages and daily life.', language), + }, + { + number: '05', + title: pickText('一人一行程,价格透明', 'One journey, clearly priced', language), + copy: pickText('按你的兴趣与节奏设计行程,清楚说明价格与服务。', 'A private itinerary shaped around you, with pricing explained clearly.', language), + }, + ]; + + const travelComparisonRows = [ + { + label: pickText('行程设计', 'Route design', language), + wanderq: pickText('一组客人一套方案,按兴趣、日期与体力设计', 'One plan per private group, shaped around interests, dates and energy', language), + coach: pickText('固定线路与统一集合时间,同团客人共享行程', 'A fixed program and meeting schedule shared by the group', language), + ota: pickText('标准化套餐为主,可选升级取决于具体供应商', 'Usually a standardized package; upgrades depend on the supplier', language), + }, + { + label: pickText('旅行节奏', 'Daily pace', language), + wanderq: pickText('在开放时间与安全条件允许时,可调整停留时长', 'Stay times can flex when opening hours and field conditions allow', language), + coach: pickText('按照全团时间推进,个人调整空间通常较少', 'The group moves together, so individual flexibility is usually limited', language), + ota: pickText('依据所购套餐执行,调整规则由平台与供应商决定', 'Follows the booked package; change rules vary by platform and supplier', language), + }, + { + label: pickText('在地深度', 'Local depth', language), + wanderq: pickText('可加入村寨家庭、手艺人和小众山野体验', 'Can include village homes, artisans and quieter field experiences', language), + coach: pickText('通常覆盖代表性景点,便于在有限时间看更多地方', 'Often covers major sights efficiently within a set timeframe', language), + ota: pickText('深度取决于套餐内容与实际承接团队', 'Depth depends on the package and the local operating team', language), + }, + { + label: pickText('语言与讲解', 'Language & context', language), + wanderq: pickText('英文旅行支持,并补充贵州文化与生活背景', 'English trip support with cultural and practical context', language), + coach: pickText('由具体团型和导游配置决定', 'Depends on the specific departure and guide assignment', language), + ota: pickText('平台沟通与现场语言能力可能来自不同服务方', 'Platform support and on-trip language may come from different providers', language), + }, + { + label: pickText('现场响应', 'On-trip response', language), + wanderq: pickText('贵州本地团队在行程期间提供 24 小时协调', 'A Guizhou-based team provides 24-hour coordination during the journey', language), + coach: pickText('主要由随团导游或领队统一协调', 'Usually coordinated by the accompanying guide or tour leader', language), + ota: pickText('通常由平台客服与当地供应商分工处理', 'Usually shared between platform support and the local supplier', language), + }, + { + label: pickText('价格方式', 'Pricing model', language), + wanderq: pickText('确认方案后逐项说明包含内容与定制报价', 'A custom quote with inclusions explained after the plan is aligned', language), + coach: pickText('团体套餐价清晰,需另外核对自费与可选项目', 'A clear group price; optional activities and extras should be checked', language), + ota: pickText('页面套餐价便于比较,包含项目因产品而异', 'Package prices are easy to compare; inclusions vary by product', language), + }, + { + label: pickText('更适合', 'A good fit for', language), + wanderq: pickText('重视私密、灵活与在地体验的家庭、朋友和境外小团', 'Families, friends and inbound small groups valuing privacy and flexibility', language), + coach: pickText('重视预算效率、固定安排和多人同行氛围的客人', 'Travelers who value budget efficiency, fixed plans and a social group', language), + ota: pickText('习惯线上比价,希望快速预订标准套餐的客人', 'Travelers who prefer online comparison and fast package booking', language), + }, + ]; + + const inquiryPrompts = [ + { + number: '01', + title: pickText('大致日期与同行人数', 'Rough dates & party size', language), + copy: pickText('日期还没完全确定也没关系,先给我们一个时间范围。', 'A date range is enough if your plans are not fixed yet.', language), + }, + { + number: '02', + title: pickText('你最想体验的贵州', 'What draws you to Guizhou', language), + copy: pickText('山水、户外、村寨、人文、美食,或者还没有答案。', 'Landscapes, adventure, villages, food—or simply an open mind.', language), + }, + { + number: '03', + title: pickText('节奏与住宿偏好', 'Pace & stay preferences', language), + copy: pickText('告诉我们你喜欢轻松还是充实,以及大致的住宿期待。', 'Tell us whether you prefer an easy or fuller pace and the stays you enjoy.', language), + }, + ]; return ( -
-
-
- {heroSlides.map((slide, index) => ( +
+ {variant === 'classic' ? ( + <> +
+ {language + {home.hero.tripAdvisorUrl ? Tripadvisor : null} +
+

{oc(home.hero.kicker, language)}

+

{oc(home.hero.title, language)}

+

+ {pickText('越过景点清单,跟随本地人走进山水、村寨与真实生活。', 'Go beyond the sights with local people, landscapes and everyday life.', language)} +

+ + {oc(home.hero.ctaLabel, language)} + +
+ {heroSlides.map((slide, index) => ( + +
+
+
+ +
+
+ {trustSignals.map(({ icon: Icon, title, copy }) => ( +
+
+ ))} +
+
+ +
+
+
+
+ {language +
+
+ {language === 'zh' ? '为什么选择万趣' : 'Why WanderQ'} +

{pickText('越过旅行泡泡,走进真实贵州', 'Travel beyond the bubble.', language)}

+

{pickText('万趣的核心不是“售卖旅游产品”,而是创造能够改变人与地方关系的旅行体验。我们拒绝景点清单式打卡,让旅行者真正靠近贵州的山水、日常生活与当地人。', 'WanderQ is not here to sell a checklist of sights. We create private journeys that bring you closer to the landscapes, everyday life and people of Guizhou.', language)}

+
{pickText('“把脚步留给山风、瀑声与村寨灯火,其余的交给我们。”', '“Leave your footsteps to mountain winds, waterfalls and village lights. Leave the rest to us.”', language)}
+
+
+
+ {brandValues.map((value) => ( +
+ {value.number}

{value.title}

{value.copy}

+
+ ))} +
+
+
+ + ) : ( + <> +
+ {language +
+
+
+ {pickText('八种方式,进入贵州', 'Eight ways into Guizhou', language)} +

{pickText('同一个贵州,\n不止一种打开方式。', 'One province.\nNever one way in.', language)}

+

{pickText('从你真正好奇的事开始,而不是从一张固定行程表开始。', 'Begin with what makes you curious—not with a fixed itinerary.', language)}

+
+
+ {prototypeThemes.map((theme) => ( + + + {theme.number} +

{theme.title}

+
+ ))} +
+
+ +
+
+ {language + {pickText('贵州 · 中国西南', 'Guizhou · Southwest China', language)} +
+
+ {pickText('万趣的方法', 'The WanderQ way', language)} +

{pickText('更少赶路,\n更多抵达。', 'See less.\nFeel more.', language)}

+

{pickText('我们不把贵州压缩成景点目录,而是把你的兴趣翻译成一段有节奏、有温度、有人照应的旅程。', 'We do not compress Guizhou into a list. We translate your interests into a journey with rhythm, warmth and local care.', language)}

+ {pickText('认识万趣', 'Meet WanderQ', language)} +
+
+ {prototypeJourneySteps.map((step) => ( +
+ {step.number} +

{step.label}

{step.copy}

+
+ ))} +
+

{pickText('贵州本地团队 · 英文旅行支持 · 24/7 在地协调', 'Guizhou-based team · English trip support · 24/7 local coordination', language)}

+
+ +
+
+ {trustSignals.map(({ icon: Icon, title, copy }) => ( +
+
+ ))} +
+
+ + )} +
{home.planning.steps.map((step) => (
@@ -2678,11 +3035,13 @@ function HomePage() { copy={oc(home.experiences.copy, language)} />
-
- -
- {home.journey.cards.map((item) => ( - - {oc(item.copy, language)} - - ))} +
+
+
+ {language + {language +
+ +
+ {language === 'zh' ? '贵州本地团队' : 'Guizhou-based team'} +

{oc(home.journey.title, language)}

+

+ {pickText( + '一段好的私家旅行,依赖的是离现场足够近的人。我们从贵州本地规划与协调行程,关注天气、道路、体力和每一天的真实节奏。', + 'A good private journey depends on people close enough to the field. We plan and coordinate from Guizhou, keeping an eye on weather, roads, energy levels and the real pace of each day.', + language, + )} +

+ +
+ {home.journey.cards.map((item, index) => ( +
+ {String(index + 1).padStart(2, '0')} +
+

{oc(item.title, language)}

+

{oc(item.copy, language)}

+
+
+ ))} +
+ + + {language === 'zh' ? '和本地团队聊聊' : 'Talk with our local team'} + +
@@ -2764,18 +3173,111 @@ function HomePage() {
-
-

{oc(home.liveGallery.title, language)}

-
-
- {[false, true].map((hiddenSet) => ( -
- {liveImages.map((image, index) => ( -
- {hiddenSet -
+
+
+ {language === 'zh' ? '贵州现场影集' : 'From the field'} +

{oc(home.liveGallery.title, language)}

+

+ {pickText( + '一起吃饭、扣好安全绳、坐在手作桌前,也在山水之间停一会儿——这些照片来自真实发生的贵州旅程。', + 'Meals shared, harnesses clipped, hands at the craft table and pauses in the mountains—photographed on journeys through Guizhou.', + language, + )} +

+
+ +
+ {liveImages.map((image, index) => ( +
+ {liveGalleryCaptions[index] +
+ {String(index + 1).padStart(2, '0')} + {liveGalleryCaptions[index]} +
+
+ ))} +
+
+ +
+
+
+ {language === 'zh' ? '选择适合你的方式' : 'Choose the model that fits'} +

+ {pickText('私家定制,区别在哪里?', 'How does private travel differ?', language)} +

+

+ {pickText( + '没有一种出行方式适合所有人。下面比较的是常见服务模型,帮助你根据预算、节奏和期望做选择。', + 'No travel model fits everyone. This comparison describes common service models so you can choose around budget, pace and expectations.', + language, + )} +

+
+ +
+ + + + + + + + + + + {travelComparisonRows.map((row) => ( + + + + + + ))} - + +
{language === 'zh' ? '对比维度' : 'Dimension'}WanderQ{language === 'zh' ? '普通大巴跟团' : 'Coach group tour'}{language === 'zh' ? '国际 OTA 套餐' : 'International OTA package'}
{row.label}{row.wanderq}{row.coach}{row.ota}
+
+ +

+ {pickText( + '注:不同旅行社、平台和具体产品可能存在差异,预订前请以实际行程、费用包含和退改规则为准。', + 'Note: Operators and individual products vary. Always check the final itinerary, inclusions and change terms before booking.', + language, + )} +

+
+
+ +
+
+
+ {oc(content.footerCta.eyebrowLabel, language)} +

{oc(content.footerCta.title, language)}

+

+ {pickText( + '不需要先准备一份完整计划。告诉我们大致时间、同行的人和你真正感兴趣的事,我们会从一条清楚、可讨论的贵州行程方向开始。', + 'You do not need a finished plan. Share your rough dates, who is traveling and what genuinely interests you. We will begin with a clear Guizhou journey direction you can discuss with us.', + language, + )} +

+ + {pickText('开始规划我的贵州之旅', 'Start planning my Guizhou journey', language)} + +
+ +
+ + {pickText('咨询时,可以先告诉我们', 'Helpful things to share', language)} + + {inquiryPrompts.map((prompt) => ( +
+ {prompt.number} +
+

{prompt.title}

+

{prompt.copy}

+
+
))}
@@ -2785,78 +3287,255 @@ function HomePage() { } function SafarisPage() { - const { language, cmsContent } = useTravelSite(); - const [filters, setFilters] = useState(initialSafariFilters); - const displaySafaris = useMemo(() => { - const cmsSafaris = cmsRouteCards(cmsContent, language); - return cmsSafaris.length ? cmsSafaris : safariCards.map((safari) => localizeSafariCard(safari, language)); - }, [cmsContent, language]); - const filteredSafaris = useMemo(() => displaySafaris.filter((safari) => safariMatchesFilters(safari, filters)), [displaySafaris, filters]); - const updateFilter = (title: string, item: string) => setFilters((current) => nextFilterSelections(current, title, item)); - const selectFilter = (title: string, item: string) => setFilters((current) => setSingleFilterSelection(current, title, item)); - const categoryFilters = selectedFilterValues(filters, '线路分类'); + const { language } = useTravelSite(); + const featuredRouteSlugs = ['classic-guizhou', 'outdoor-coffee', 'culture-outdoor-mix']; + const routeChoices = featuredRouteSlugs + .map((slug) => safariCards.find((safari) => safari.slug === slug || safari.aliases?.includes(slug))) + .filter((safari): safari is SafariCardItem => Boolean(safari)) + .map((safari) => localizeSafariCard(safari, language)); return (
- + {pickText( - '贵州的好玩不在单点打卡,而在瀑布、喀斯特、村寨和山路之间的节奏。这里按经典首访、户外探索、人文深度和慢旅行整理路线方向,方便你快速找到适合自己的贵州走法。', - 'Guizhou is not about isolated check-ins; the experience lives in the rhythm between waterfalls, karst, villages, and mountain roads. These route families help you quickly find the style that fits.', + '先从经典全景、喀斯特户外或人文与自然的均衡组合中选择一个方向,再按你的日期、体力和兴趣重新设计。这里展示的是可讨论的行程骨架,不是必须照走的固定团。', + 'Begin with a classic panorama, a karst adventure, or a balanced mix of culture and nature. Each is a starting framework, redesigned around your dates, energy and interests—not a fixed group itinerary.', language, )} -
- - - - - - +
+
+ {pickText('选择一个起点', 'Choose a starting point', language)} +

{pickText('路线有方向,行程没有模板。', 'A clear direction, never a template.', language)}

+

+ {pickText( + '三条线路各自解决一种旅行期待。进入详情页可以继续查看线路节奏、核心亮点和日程骨架;最终天数、酒店、交通与体验仍以沟通后的定制方案为准。', + 'Each route answers a different travel intention. Open a route to review its rhythm, defining moments and itinerary framework; final days, stays, transport and experiences are confirmed through planning.', + language, + )} +

+
+
- {filteredSafaris.map((safari) => ( - + {routeChoices.map((safari, index) => ( + ))} - {filteredSafaris.length === 0 && } + {routeChoices.length === 0 && }
+
); } function ExperiencesPage() { const { language } = useTravelSite(); - const [filters, setFilters] = useState({}); - const filteredExperiences = useMemo(() => experienceArchiveCards.filter((card) => experienceMatchesFilters(card, filters)), [filters]); - const updateFilter = (title: string, item: string) => setFilters((current) => nextFilterSelections(current, title, item)); - const selectFilter = (title: string, item: string) => setFilters((current) => setSingleFilterSelection(current, title, item)); + const outdoorExperiences = [ + { + number: '01', + title: pickText('地下喀斯特探索', 'Underground Karst Exploration', language), + subtitle: pickText('洞穴步行、平缓水域与洞穴桨板候选', 'Cave walking, calm water, and cave-paddleboarding options', language), + meta: pickText('轻量至进阶 · 洞穴区域按日期确认', 'Light to advanced · Cave area confirmed by date', language), + copy: pickText('从入门洞穴步行到洞穴桨板或进阶洞段,按客人经验、怕水程度、天气、水位、装备与退出路线选择。', 'From introductory cave walks to paddleboarding or advanced sections, selected around experience, water comfort, weather, equipment, and exit routes.', language), + image: img.clientOutdoorCave, + href: '/experiences/cave-expedition/', + }, + { + number: '02', + title: pickText('瀑降与峡谷绳索', 'Waterfall Abseiling & Canyoning', language), + subtitle: pickText('仅作为完整核验后的进阶候选', 'An advanced candidate after full validation only', language), + meta: pickText('进阶 · 运营方、绳索系统与保险需确认', 'Advanced · Operator, rope system, and insurance required', language), + copy: pickText('不直接发布客户文档中的具体高度。只有运营方、带队人员、装备、通信、救援和专项保险全部确认后,才写入正式方案。', 'Specific heights from the brief are not published. The activity enters a final plan only after operator, field team, equipment, communication, rescue, and specialist insurance are confirmed.', language), + image: img.clientWaterfallAbseilCanyon, + href: '/experiences/stream-tracing/', + }, + { + number: '03', + title: pickText('峡谷溯溪与亲水', 'Canyon River Tracing & Water Days', language), + subtitle: pickText('从平缓河段到更活跃的峡谷路线', 'From calm river sections to more active canyon routes', language), + meta: pickText('轻量至活力 · 天气与水位决定执行', 'Light to active · Weather and water decide operation', language), + copy: pickText('可在平缓亲水、溯溪、峡谷步行或漂流中选择一项;当天水位不合适时,切换森林步道、摄影或村寨体验。', 'Choose one calm-water, stream-tracing, canyon-walk, or rafting option; unsuitable levels trigger a forest, photography, or village alternative.', language), + image: img.clientRiverKarstAerial, + href: '/experiences/rafting/', + }, + { + number: '04', + title: pickText('峰林徒步与骑行', 'Karst Peak-Forest Hiking & Cycling', language), + subtitle: pickText('把喀斯特地貌做成可进入的风景', 'Enter the karst landscape at ground level', language), + meta: pickText('轻量至活力 · 路线和里程按体力确认', 'Light to active · Route and distance matched to ability', language), + copy: pickText('从万峰林田园慢骑、峰林步道到更长距离山地路线,优先匹配时间、体力、天气和返程条件,不使用未经核验的固定里程承诺。', 'From relaxed Wanfenglin cycling and peak-forest walks to longer mountain routes, matched to time, ability, weather, and return logistics without unverified distance claims.', language), + image: img.clientKarstHikeSunset, + href: '/experiences/karst-hiking-cycling/', + }, + ]; + const localExperiences = [ + { + number: '01', + title: pickText('洞穴咖啡与烛光晚宴', 'Underground Coffee & Candlelit Dinner', language), + meta: pickText('轻奢场景 · 私家预约', 'Private setup · Date-specific booking', language), + copy: pickText('在确认可安全接待的洞穴场景中安排咖啡、茶点或定制晚餐;餐食、灯光、接驳和应急方案需逐项报价。', 'Coffee, tea, or a private dinner in a cave setting approved for safe reception, with food, lighting, transfers, and contingency itemized in the quote.', language), + image: img.clientCaveLuxuryDining, + href: '/experiences/cave-luxury-dining/', + action: pickText('查看体验详情', 'View Experience', language), + }, + { + number: '02', + title: pickText('悬崖与山野咖啡', 'Cliffside & Mountain-Top Coffee', language), + meta: pickText('轻量 · 日出、日落或恢复半日', 'Light · Sunrise, sunset, or a recovery half-day', language), + copy: pickText('把现磨咖啡安排在峰林、峡谷或山地观景节点;具体点位、营业状态、步行距离和天气条件出发前复核。', 'Place freshly brewed coffee at a peak-forest, canyon, or mountain viewpoint, with site, opening status, walk length, and weather reconfirmed before departure.', language), + image: img.clientWildCoffeeCliffCup, + href: '/experiences/wild-coffee/', + action: pickText('查看体验详情', 'View Experience', language), + }, + { + number: '03', + title: pickText('原生村寨沉浸', 'Authentic Village Homestays', language), + meta: pickText('苗族 / 侗族 / 布依族 · 半日至 3 天 2 晚候选', 'Miao / Dong / Buyi · Half-day to 3 days / 2 nights', language), + copy: pickText('从苗族吊脚楼、侗族鼓楼社区与布依石头寨三条方向中选择一处;住宿、主人、活动与境外客人登记按日期逐项确认。', 'Choose one direction—Miao stilt-house life, a Dong drum-tower community, or a Buyi stone village—with the stay, host, activities, and international guest registration checked for the date.', language), + image: img.clientVillageDrumtowerRoofs, + href: '/experiences/village-immersion/', + action: pickText('查看体验详情', 'View Experience', language), + }, + { + number: '04', + title: pickText('喀斯特星空露营', 'Karst Glamping & Starry-Sky Camping', language), + meta: pickText('轻量度假 · 营地条件逐项确认', 'Gentle escape · Camp conditions checked', language), + copy: pickText('以舒适床品、卫浴、餐食、值守和天气备用为前提选择峰林、河谷或山地营地;具体营地资料补齐后开放预约。', 'Select a peak-forest, valley, or mountain camp only after bedding, bathrooms, meals, staffing, and weather backup are verified; booking opens once site records are complete.', language), + image: img.clientKarstValleyCamp, + href: '/experiences/karst-glamping/', + action: pickText('查看体验详情', 'View Experience', language), + }, + { + number: '05', + title: pickText('非遗与轻户外融合', 'Heritage & Gentle Outdoor Pairings', language), + meta: pickText('轻量至活力 · 一天只做一个组合', 'Light to active · One pairing per day', language), + copy: pickText('从“梯田徒步 + 蜡染工坊”“山林植物 + 古法造纸”“茶山骑行 + 制茶”中选择一组,避免把文化与户外都压缩成打卡。', 'Choose one pairing—terrace walk plus batik, forest plants plus papermaking, or tea-country cycling plus tea craft—without compressing both sides into quick stops.', language), + image: img.clientHeritageHands, + href: '/experiences/heritage-outdoor-pairings/', + action: pickText('查看体验详情', 'View Experience', language), + }, + ]; + const routePairings = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('适合加入一项村寨、手作或山野咖啡。', 'Add one village, craft, or mountain-coffee moment.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('用活动分级、恢复日和安全门槛组合洞穴与峡谷。', 'Combine caves and canyons through grading, recovery, and operating gates.', language), href: '/safaris/outdoor-coffee/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('把村寨慢住、非遗和一项轻户外放进同一条路线。', 'Blend village depth, heritage, and one gentle outdoor day.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; return ( -
+
- + {pickText( - '把贵州真正放进旅程里:进溶洞、漂峡谷、看峰林,也走进苗侗村寨、手作工坊、山野咖啡和温泉恢复日。这里按体验类型与地点整理灵感,方便你挑出想亲自参与的部分。', - 'Put Guizhou into the journey itself: caves, canyons, peak forests, Miao and Dong villages, craft studios, mountain coffee, and hot-spring recovery days organized by type and place.', + '喀斯特塑造冒险,在地生活留下记忆。先从两大系列中选择一种感受,再把一至两项体验编进私家线路,而不是把项目堆成清单。', + 'Adventure shaped by karst; memory shaped by local life. Choose a series, then weave one or two experiences into a private route instead of building a checklist.', language, )} -
- - - - -
- {filteredExperiences.map((card) => ( - + +
+
+
+
01 / Karst Outdoor Adventures

{pickText('先选择强度,再选择项目。', 'Choose the level before the activity.', language)}

+

{pickText('页面展示的是可讨论的体验方向,不是对所有日期与客人的固定承诺。天气、水位、运营方、人员、装备、保险和退出机制确认后,项目才进入正式行程。', 'These are experience directions, not blanket promises for every date or guest. Weather, water, operator, field team, equipment, insurance, and exit plan must be confirmed before an activity enters the itinerary.', language)}

+
+
+ {outdoorExperiences.map((item, index) => ( + + ))} +
+
+ +
{pickText('当前发布边界', 'Current Publication Boundary', language)}

{pickText('极限桥梁项目仍处于资料核验阶段。', 'Extreme bridge activities remain under verification.', language)}

{pickText('坝陵河大桥可作为桥梁观景候选;蹦极、“破茧之翼”及具体高度、当前纪录、证书、年龄与安全承诺暂不进入公开可售清单。专项页仅说明证据和上线门槛。', 'Balinghe Bridge can be considered for bridge viewing; bungee, the “Cocoon Wing,” and related height, current-record, certificate, age, or safety promises are not in the public sellable list. The review page documents evidence and launch gates only.', language)}

{pickText('查看专项核验页', 'View the verification review', language)}
+
+
+
+
+
+
02 / Unique Local Experiences

{pickText('把特别,放在更慢的地方。', 'Put the special moments in slower places.', language)}

+

{pickText('这些体验偏轻奢、慢节奏,可单独安排半日,也可以作为经典线或户外线的恢复节点。具体场地、主人、餐食和接待条件按日期复核。', 'These slower, more private ideas can fill half a day or become a recovery chapter inside a classic or outdoor route. Sites, hosts, meals, and reception conditions are reconfirmed by date.', language)}

+
+
+ {localExperiences.map((item, index) => ( +
+ +
+ {item.number} + {item.meta} +

{item.title}

+

{item.copy}

+ {item.action} +
+
))} - {filteredExperiences.length === 0 && } +
+
+
+
+
03 / Iconic Landscapes

{pickText('同一种水,在喀斯特与丹霞之间形成三种旅程。', 'One element, three journeys across karst and Danxia.', language)}

+

{pickText('黄果树成熟景区、滴水滩山谷候选和赤水丹霞瀑布需要完全不同的交通、步行和开放核验。先选一个区域,再按水量与当天条件决定动线。', 'Huangguoshu’s developed scenic area, a Dishuitan valley candidate, and Chishui Danxia require different transfers, walking, and access checks. Choose one region, then shape the route around flow and live conditions.', language)}

+
+
+
+ {pickText('丰水期的黄果树大瀑布', +
+ 01 + {pickText('黄果树 / 滴水滩 / 赤水丹霞 · 三选一', 'Huangguoshu / Dishuitan / Chishui Danxia · choose one', language)} +

{pickText('世界级瀑布群', 'Iconic Waterfall Clusters', language)}

+

{pickText('比较成熟景区、山谷多级瀑布和丹霞瀑布的真实差异,补齐预约、票务、接驳、水量、步行、天气、封闭与摄影边界,不以历史丰水照片保证当天景观。', 'Compare a developed scenic area, multi-tier valley falls, and a Danxia waterfall with honest differences in reservation, ticketing, shuttles, flow, walking, weather, closures, and photography—without using historic high-water photos as a promise.', language)}

+ {pickText('查看三个瀑布区域', 'Compare Three Waterfall Regions', language)} +
+
+
+
+
+
+
{pickText('放进哪条线路?', 'Where Does It Fit?', language)}

{pickText('一次旅程,选择一至两项真正记得住的体验。', 'Choose one or two experiences worth remembering.', language)}

+
+ {routePairings.map((route) => ( + {route.number}

{route.title}

{route.copy}

+ ))} +
@@ -2910,17 +3589,98 @@ function FoodPage() { function TransportPage() { const { language } = useTravelSite(); + const serviceTypes = [ + { + number: '01', + title: pickText('全程私家团专属用车', 'Full-itinerary private chauffeur', language), + copy: pickText('从抵达接站到返程送机,车辆按每日已确认行程提供门到门服务。停留点或时间可调整;涉及绕行、超时或新增里程时,先说明费用与司机工时影响再执行。', 'A dedicated vehicle follows the confirmed daily itinerary from arrival pickup to departure. Stops and timing can be adjusted; detours, overtime, and added mileage are priced and checked against driver hours before approval.', language), + meta: pickText('2-8 人 · 5 座 / 7 座 / 9 座', '2-8 guests · 5 / 7 / 9-seat options', language), + }, + { + number: '02', + title: pickText('机场・高铁专车接送', 'Airport & high-speed rail transfers', language), + copy: pickText('覆盖贵阳龙洞堡机场、主要高铁站与酒店间的单程接送。确认单会写明接站位置、联系人、航班或车次监测方式,以及延误等候窗口和超时规则。', 'Private one-way transfers connect Guiyang Longdongbao Airport, major rail stations, and hotels. The confirmation states the meeting point, contact, arrival monitoring method, included wait window, and overtime terms.', language), + meta: pickText('举牌可选 · 行李协助 · 直达酒店', 'Optional name sign · luggage help · direct drop-off', language), + }, + { + number: '03', + title: pickText('跨城景观专线接驳', 'Inter-city scenic transfers', language), + copy: pickText('连接贵阳、黄果树、兴义、凯里、肇兴与赤水等主要节点。可把合规、顺路且具备安全停车条件的短停纳入路线,让转场本身也有节奏。', 'Point-to-point transfers link Guiyang, Huangguoshu, Xingyi, Kaili, Zhaoxing, Chishui, and other major nodes. Short scenic stops can be built in when they are on-route, legally accessible, and safe for parking.', language), + meta: pickText('路线预排 · 合法停靠 · 时间边界清楚', 'Planned route · legal stops · clear timing', language), + }, + { + number: '04', + title: pickText('特殊场景定制车型', 'Special-scenario vehicles', language), + copy: pickText('按户外装备、道路条件、舒适度和小团规模匹配高通过性 SUV、高端商务 MPV 或中型客车。偏远路线并不等于越野通行,封闭或保护区域不会安排车辆进入。', 'Match high-clearance SUVs, premium MPVs, or minibuses to equipment, road conditions, comfort, and group size. Remote does not mean off-road access: vehicles do not enter closed or protected areas.', language), + meta: pickText('户外装备 · 高端度假 · 亲友 / 同业小团', 'Outdoor gear · premium travel · private/trade groups', language), + }, + ]; + const standards = [ + { title: pickText('合规车辆与承运方', 'Licensed vehicle & carrier', language), copy: pickText('派车前核验对应营运资质、车辆技术状态与承运人责任保险,不以“看起来新”代替合规文件。', 'Before dispatch, we check the relevant operating credentials, vehicle condition, and carrier liability insurance—not appearance alone.', language) }, + { title: pickText('座位、行李与儿童需求', 'Seats, luggage & children', language), copy: pickText('按成人、儿童年龄、行李尺寸和设备数量配车;儿童座椅的型号、固定方式、数量和费用需在确认单中锁定。', 'Vehicle matching uses adult count, children’s ages, luggage dimensions, and equipment. Child-seat type, installation, quantity, and fee are locked in the confirmation.', language) }, + { title: pickText('司机工时与山路安全', 'Driver hours & mountain safety', language), copy: pickText('每日路线受法定驾驶和休息时间、天气、能见度及山区道路条件约束;必要时调整出发时间、路线或司机配置。', 'Daily routes follow legal driving and rest limits plus weather, visibility, and mountain-road conditions. Departure time, routing, or driver allocation may change for safety.', language) }, + { title: pickText('语言与在地协调', 'Language & local coordination', language), copy: pickText('不假设每位司机都能英语沟通。需要英文服务时,明确由英文司导、随车讲解或旅行顾问远程支持中的哪一种负责。', 'We do not assume every driver speaks English. When English support is needed, we specify whether it comes from an English-speaking guide, onboard interpreter, or remote trip coordinator.', language) }, + { title: pickText('干净车辆与实用配置', 'Clean vehicle & practical amenities', language), copy: pickText('出车前完成清洁和基础检查;饮用水、充电接口、纸巾等配置按具体车型与报价确认,不以统一赠品制造落差。', 'Cleaning and basic checks are completed before service. Water, charging ports, tissues, and other amenities are confirmed by vehicle and quotation.', language) }, + ]; + const dispatchFlow = [ + pickText('提供日期、航班 / 车次和完整路线', 'Share dates, flight/train, and full route', language), + pickText('填写人数、儿童年龄、行李与特殊设备', 'List guests, child ages, luggage, and special gear', language), + pickText('匹配车型并核验道路、司机工时与承运资质', 'Match vehicle; check roads, driver hours, and carrier credentials', language), + pickText('确认价格、包含项、等候与变更规则', 'Confirm price, inclusions, wait time, and change terms', language), + pickText('出发前发送车辆、司机和接站信息', 'Receive vehicle, driver, and meeting details before service', language), + ]; return (
- + {pickText( - '贵州景点分散、山路多,车的选择会直接影响舒适度和行程节奏。这里按人数、行李、转场距离和道路条件整理车型,帮助你提前判断怎样移动更轻松。', - 'Guizhou sights are spread out across mountain roads, so vehicle choice directly shapes comfort and route rhythm. These options are organized by group size, luggage, transfer distance, and road conditions.', + '贵州景点分散、山路多。我们先把路线、人数、行李与道路条件放在一起,再匹配合规车辆、司机工时和英文旅行支持。', + 'Guizhou’s sights are spread across mountain roads. We match the route, group, luggage, legal vehicle, driver hours, and English travel support as one transport plan.', language, )} -
+
+
+ {pickText('贵州私家用车', 'Private transport in Guizhou', language)} +

{pickText('门到门的舒适,也要经得起每一项核验', 'Door-to-door comfort, built on details that can be verified', language)}

+

{pickText('第一次来贵州,不必自己拼接公共交通。用车可以覆盖整段行程、单次接送或跨城转场;真正的安心来自清楚的车辆类别、行李容量、接站方式、司机工时、费用边界和应急联系人。', 'First-time visitors do not need to piece together public transport. Service can cover a full itinerary, a single arrival, or an inter-city transfer. Confidence comes from a clear vehicle class, luggage plan, meeting method, driver hours, price boundaries, and a named support contact.', language)}

+
+ {pickText('私家团专用', 'Private to your group', language)} + {pickText('英文旅行支持', 'English travel support', language)} + {pickText('在地现场协调', 'Local trip coordination', language)} +
+
+
+ {pickText('客户提供的商务 +
{pickText('图片展示车型类别参考;品牌、型号、颜色与车龄以派车确认单为准。', 'Vehicle-class reference; make, model, color, and age are confirmed at dispatch.', language)}
+
+
+ +
+
+ {pickText('四种服务方式', 'Four service formats', language)} +

{pickText('用车跟着路线走,不让路线迁就一张车型表', 'Transport shaped around the route—not the other way around', language)}

+

{pickText('客户提出的四类服务全部保留,并把“灵活”“等候”“特殊道路”等容易产生理解差异的内容写清边界。', 'All four client-requested formats remain, with clearer boundaries around flexibility, waiting, and special-road access.', language)}

+
+
+ {serviceTypes.map((service) => ( +
+ {service.number} +

{service.title}

+

{service.copy}

+ {service.meta} +
+ ))} +
+
+ +
+
+ {pickText('车型匹配', 'Vehicle matching', language)} +

{pickText('先看舒适乘员与行李,再看额定座位', 'Guest comfort and luggage come before rated seats', language)}

+

{pickText('以下是车型类别,不承诺固定品牌或型号。实际派车会把儿童座椅、摄影器材、轮椅或大件行李占用的空间一并计算。', 'These are vehicle classes, not guaranteed makes or models. Child seats, camera gear, mobility aids, and oversized luggage are included in the space calculation.', language)}

+
+
{transportCards.map((rawVehicle) => { const vehicle = localizeTransportCard(rawVehicle, language); return ( @@ -2947,11 +3707,55 @@ function TransportPage() { ); })} +
+
+ +
+
+
+ {pickText('我们的服务标准', 'Our service standards', language)} +

{pickText('把“安心”变成出发前可以逐项确认的内容', 'Turn reassurance into items confirmed before departure', language)}

+
+
+ {standards.map((standard, index) => ( +
+ {String(index + 1).padStart(2, '0')} +

{standard.title}

+

{standard.copy}

+
+ ))} +
+
+ {pickText('确认后可以承诺', 'Once confirmed, we commit to', language)} + {pickText('专属本团用车', 'No shared ride', language)} + {pickText('透明报价与排除项', 'Transparent quote and exclusions', language)} + {pickText('接站状态更新', 'Pickup status updates', language)} + {pickText('行程中在地支持', 'On-ground trip support', language)} +
+
+
+ +
+
+ {pickText('客户提供的轻型商务车座舱照片', +
+
+
+ {pickText('派车确认流程', 'Dispatch confirmation', language)} +

{pickText('五步锁定一辆真正适合这段旅程的车', 'Five steps to lock in the right vehicle for this journey', language)}

+
+
    + {dispatchFlow.map((item, index) => ( +
  1. {String(index + 1).padStart(2, '0')}

    {item}

  2. + ))} +
+

{pickText('默认不会把“同一位司机全程、车龄 5 年内、司机英语、免费儿童座椅、延误免费等候 60 分钟、无限次免费改线”写成无条件保证;如某次报价确实包含,会明确落在书面确认中。', 'We do not automatically promise the same driver throughout, a vehicle under five years old, an English-speaking driver, a free child seat, 60 minutes of free delay waiting, or unlimited free route changes. When included, each item appears in writing.', language)}

+
@@ -2960,36 +3764,207 @@ function TransportPage() { function CollectivePage() { const { language } = useTravelSite(); - const [filters, setFilters] = useState({}); - const filteredCollective = useMemo(() => collectiveArchiveCards.filter((card) => collectiveMatchesFilters(card, filters)), [filters]); - const updateFilter = (title: string, item: string) => setFilters((current) => nextFilterSelections(current, title, item)); - const selectFilter = (title: string, item: string) => setFilters((current) => setSingleFilterSelection(current, title, item)); + const hotelGroups = [ + { + number: '01', + title: pickText('野奢地貌度假酒店', 'Wild Luxury Landscape Resorts', language), + intro: pickText('让住宿成为喀斯特、峡谷与矿业遗产路线的一部分;景观之外,更要核对进出道路、临崖安全与真实房型。', 'Stays that become part of a karst, canyon, or mining-heritage route—with road access, cliff safety, and actual room types checked alongside the view.', language), + hotels: [ + { + id: 'fengxi-cave', + title: pickText('兴义峰兮・洞穴民宿', 'Fengxi Cave Resort Xingyi', language), + location: pickText('兴义 / 万峰湖方向', 'Xingyi / Wanfeng Lake area', language), + copy: pickText('客户推荐的洞穴与悬崖景观住宿候选,适合万峰湖、万峰林线路留出一晚慢停留。洞穴客房、落地窗、泳池、下午茶与私家餐饮均按实际房型和日期确认。', 'A client-recommended cave-and-cliff stay candidate for a slower night around Wanfeng Lake or Wanfenglin. Cave rooms, full-height windows, pool access, afternoon tea, and private dining are confirmed by room type and date.', language), + verify: pickText('正式名称、房型景观、临崖防护、道路与行李接驳', 'Legal name, room view, cliff protection, road and luggage transfer', language), + route: pickText('万峰林 / 万峰湖线', 'Wanfenglin / Wanfeng Lake', language), + }, + { + id: 'fangxia-yunju', + title: pickText('朱砂古镇・放下云居', 'Zhusha Ancient Town · Fangxia Yunju', language), + location: pickText('铜仁万山 / 朱砂古镇', 'Wanshan, Tongren / Zhusha Ancient Town', language), + copy: pickText('以汞矿工业遗产和峡谷氛围为核心的住宿候选,适合铜仁或梵净山前后的延伸夜。独栋数量、设计奖项、露台视野及矿洞参观不能仅凭宣传文案确认。', 'A mining-heritage and canyon-atmosphere candidate for an extension before or after Tongren or Fanjing Mountain. Villa count, design awards, terrace views, and mine-tunnel visits require direct confirmation.', language), + verify: pickText('运营主体、奖项出处、房型数量、矿区开放与安全边界', 'Operator, award source, room count, mine access and safety boundary', language), + route: pickText('铜仁 / 梵净山延伸线', 'Tongren / Fanjing extension', language), + }, + ], + }, + { + number: '02', + title: pickText('民族风情村寨美宿', 'Ethnic Village Boutique Stays', language), + intro: pickText('把雷公山、榕江和台江的人文行程放慢一晚,但不把民宿包装成可以自动进入居民生活、仪式或私人空间的通行证。', 'Slow down a Leigongshan, Rongjiang, or Taijiang culture route—without presenting a stay as automatic access to residents’ homes, rituals, or private life.', language), + hotels: [ + { + id: 'tree-frog-leigongshan', + title: pickText('雷公山树蛙部落', 'Tree Frog Tribe Leigongshan', language), + location: pickText('雷公山区域 / 具体地址待确认', 'Leigongshan area / exact address to confirm', language), + copy: pickText('客户推荐的森林树屋候选,适合亲子、自然观察和轻量山林停留。其是否位于自然保护区范围、天窗观星、夏季萤火虫及林间活动都需按实际点位、季节与管理要求确认。', 'A client-recommended forest-treehouse candidate for families, nature observation, and gentle woodland stays. Reserve location, skylight stargazing, summer fireflies, and forest activities all depend on the exact site, season, and management rules.', language), + verify: pickText('精确地址、保护地关系、儿童临边、虫季与夜间照明', 'Exact address, protected-area status, child safety, insect season and night lighting', language), + route: pickText('雷公山深度人文线', 'Leigongshan culture route', language), + }, + { + id: 'jiayi-terrace', + title: pickText('榕江加宜梯田民宿', 'Jiayi Terrace Mountain Retreat', language), + location: pickText('榕江月亮山 / 加宜村方向', 'Moon Mountain, Rongjiang / Jiayi area', language), + copy: pickText('以梯田、云雾和苗族木楼氛围为核心的山脊住宿候选。房量、270°视野、山景泡浴、苗装、染色手作、徒步与瑜伽不作为所有房型或每日服务的统一承诺。', 'A ridge-stay candidate shaped by terraces, cloud, and Miao timber architecture. Room count, 270-degree views, view baths, dress-up, dyeing, hikes, and yoga are not assumed for every room or day.', language), + verify: pickText('经营主体、真实房型、道路台阶、活动老师与天气备选', 'Operator, actual rooms, road and steps, activity hosts and weather backup', language), + route: pickText('榕江 / 月亮山人文线', 'Rongjiang / Moon Mountain route', language), + }, + { + id: 'shicang-laiyin', + title: pickText('台江拾仓来隐', 'Shicang Laiyin Boutique Guesthouse', language), + location: pickText('台江红阳苗寨 / 具体村址待确认', 'Hongyang Miao Village, Taijiang / address to confirm', language), + copy: pickText('客户推荐的小体量村寨精品住宿候选,强调传统村落肌理与现代舒适的平衡。智能客房、苗绣、长者故事与时令家宴需要落实房型、主人、老师、菜单、费用和居民同意。', 'A client-recommended small village boutique candidate balancing traditional settlement texture with modern comfort. Smart-room features, embroidery, elder storytelling, and seasonal meals require named rooms, hosts, teachers, menus, fees, and resident consent.', language), + verify: pickText('村址与名称、住宿登记、消防、智能设施与文化活动主体', 'Village and legal name, registration, fire safety, smart features and culture hosts', language), + route: pickText('台江 / 苗族人文线', 'Taijiang / Miao culture route', language), + }, + ], + }, + { + number: '03', + title: pickText('田园山野精品民宿', 'Pastoral Mountain Boutique Stays', language), + intro: pickText('适合把森林、峡谷和乡村活动变成较轻松的一天;所有户外配套仍需和住宿本身分开核价、核时段、核保险。', 'Use a forest, canyon, or rural stay to create a gentler day—while pricing, timing, and insuring any outdoor activity separately from the room.', language), + hotels: [ + { + id: 'yuntun-treehouse', + title: pickText('云屯・星空野奢树屋', 'Yuntun Star Treehouse Resort', language), + location: pickText('兴义云屯生态公园方向', 'Yuntun Ecological Park area, Xingyi', language), + copy: pickText('森林树屋和独立露台概念适合万峰林后的亲子或情侣慢停留。万亩森林、绝佳观星、射箭与其他轻户外属于需复核的场地及运营描述,天气和光环境也会改变体验。', 'A forest-treehouse and private-terrace concept for a slower family or couples stay after Wanfenglin. Forest scale, stargazing, archery, and other activities are venue claims requiring verification and remain weather- and light-dependent.', language), + verify: pickText('正式店名、真实营位/房型、园区关系、活动运营方与儿童安全', 'Legal name, room type, park relationship, activity operator and child safety', language), + route: pickText('万峰林 / 亲子轻松线', 'Wanfenglin / easy family route', language), + }, + { + id: 'zhenshanhe', + title: pickText('枕宿・枕山河', 'Zhensu Zhenshanhe Canyon Retreat', language), + location: pickText('六盘水格所河峡谷方向', 'Gesuo River Canyon area, Liupanshui', language), + copy: pickText('客户推荐的布依老宅与峡谷住宿候选,适合六盘水山地路线。夯土、旧瓦和石作保留程度,以及识野菜、木榨油、刺梨酒和田野餐桌等活动,都按具名主人、季节和食品安全条件确认。', 'A client-recommended Buyi-house and canyon stay candidate for a Liupanshui mountain route. Preserved earth, tile, and stone features—plus foraging, oil pressing, roxburgh rose wine, and farm dining—depend on named hosts, season, and food-safety conditions.', language), + verify: pickText('具体村寨、房屋与消防、食品与酒水、农事活动和交通时间', 'Exact village, building and fire safety, food and alcohol, farm activities and transfer time', language), + route: pickText('六盘水峡谷 / 山野线', 'Liupanshui canyon / mountain route', language), + }, + ], + }, + { + number: '04', + title: pickText('城市与主题特色住宿', 'Urban & Special Themed Stays', language), + intro: pickText('主题感可以成为亲子路线的轻松收尾,但先确认它是否真的适合休息、接驳和孩子的年龄,而不是只看拍照效果。', 'A themed stay can be a playful family-route finale, but rest quality, transfers, and age fit come before photo appeal.', language), + hotels: [ + { + id: 'xingyi-mushroom', + title: pickText('兴义蘑菇野奢酒店', 'Xingyi Mushroom Luxury Hotel', language), + location: pickText('兴义近郊 / 具体地址待确认', 'Xingyi outskirts / exact address to confirm', language), + copy: pickText('以蘑菇造型独栋和童话主题为卖点的亲子候选,可作为严肃山野行程之外的轻松一晚。泳池、儿童乐园、主题房、开放季节和维护状态均需在预订日期重新确认。', 'A family candidate built around mushroom-shaped villas and a playful theme, useful as a lighter night between serious mountain stays. Pool, playground, themed rooms, opening season, and maintenance status require date-specific confirmation.', language), + verify: pickText('正式名称与地址、年龄适配、泳池/乐园开放、房型与夜间安静度', 'Legal name and address, age fit, pool/playground opening, room type and night noise', language), + route: pickText('兴义 / 亲子轻松线', 'Xingyi / easy family route', language), + }, + ], + }, + ]; + const routeLinks = [ + { route: pickText('万峰林・万峰湖', 'Wanfenglin · Wanfeng Lake', language), stays: pickText('峰兮洞穴 / 云屯树屋', 'Fengxi Cave / Yuntun Treehouse', language), note: pickText('景观慢住或亲子树屋,二选一更利于路线节奏。', 'Choose either a landscape slow stay or family treehouse for a cleaner route rhythm.', language) }, + { route: pickText('雷公山・榕江・台江', 'Leigongshan · Rongjiang · Taijiang', language), stays: pickText('树蛙部落 / 加宜梯田 / 拾仓来隐', 'Tree Frog Tribe / Jiayi Terrace / Shicang Laiyin', language), note: pickText('按具体村寨、交通时间和可接待活动选择,不建议为了“打卡”频繁换宿。', 'Choose by exact village, transfer time, and confirmed hosting; avoid changing stays just to collect check-ins.', language) }, + { route: pickText('铜仁・梵净山延伸', 'Tongren · Fanjing extension', language), stays: pickText('朱砂古镇・放下云居', 'Zhusha Ancient Town · Fangxia Yunju', language), note: pickText('适合工业遗产延伸,需与梵净山早出发节奏分开评估。', 'Works as a mining-heritage extension, assessed separately from an early Fanjing start.', language) }, + { route: pickText('六盘水山野', 'Liupanshui mountain route', language), stays: pickText('枕宿・枕山河', 'Zhensu Zhenshanhe', language), note: pickText('先核实峡谷道路、真实车程和雨季替代,再决定是否住进深山。', 'Check canyon roads, real transfer time, and wet-season backup before committing to a remote night.', language) }, + ]; + const bookingChecks = [ + pickText('正式店名、经营与预订主体、准确地址和当前位置', 'Legal name, operator, booking entity, exact address and map pin', language), + pickText('真实房型、床型、景观、卫浴、空调、早餐与行李动线', 'Actual room, bed, view, bathroom, climate control, breakfast and luggage access', language), + pickText('外国客人住宿登记方式,以及护照信息如何安全处理', 'Foreign-guest registration process and secure passport-data handling', language), + pickText('消防、临崖/临水、木结构、泳池、儿童设施与夜间值守', 'Fire, cliff/water, timber building, pool, child facilities and night staffing', language), + pickText('活动是否自营、是否另收费、老师/向导、天气取消和保险', 'Who operates activities, extra fees, host/guide, weather cancellation and insurance', language), + pickText('最终房价、税费、预付、取消、房态与同区域备选', 'Final rate, tax, deposit, cancellation, availability and local backup', language), + ]; return (
- + {pickText( - '贵州住宿的价值不只看星级,更看它是否顺路、好休息、能接住山地行程的节奏。这里把城市抵离、瀑布温泉、苗寨梯田、峰林度假等节点整理出来,方便你判断每一晚该住在哪里。', - 'In Guizhou, a stay is not only about star rating. It has to be on-route, restful, and able to support the mountain-travel rhythm.', + '贵州住宿的价值不只看星级。我们从客户提出的稀缺景观、村寨文化、田园山野与主题亲子四个方向建立候选清单,再按真实房型、路线、安全和境外客人登记逐家确认。', + 'A Guizhou stay is not only about star rating. We shortlist distinctive landscape, village, pastoral, and themed properties, then confirm actual rooms, route fit, safety, and foreign-guest registration one by one.', language, )} -
- - - -
- {filteredCollective.map((card) => ( - - ))} - {filteredCollective.length === 0 && } +
+
+ {pickText('贵州特色住宿候选', 'Distinctive Guizhou stay shortlist', language)} +

{pickText('先选一晚值得停留的地方,再核实它是否真的适合你', 'Choose a night worth slowing down for—then verify that it truly fits', language)}

+
+
+

{pickText('这 8 家来自客户修改意见。客户文档没有嵌入对应酒店原图,也没有提供经营证照、实时房态或境外客人接待证明,因此本页不会用别处风景图冒充酒店实景,也不会把宣传卖点直接写成保证。', 'These eight candidates come from the client brief. The document contains no matching hotel photography, operating documents, live availability, or evidence of foreign-guest handling, so this page does not substitute unrelated scenery or turn promotional claims into guarantees.', language)}

+
+ {pickText('8 家候选', '8 candidates', language)} + {pickText('4 类住宿', '4 stay types', language)} + {pickText('预订前逐家确认', 'Verified before booking', language)} +
+ +
+ {hotelGroups.map((group) => ( +
+
+ {group.number} +
+

{group.title}

+

{group.intro}

+
+
+
+ {group.hotels.map((hotel, index) => ( +
+
+ {group.number}.{String(index + 1).padStart(2, '0')} + {pickText('客户推荐・待确认', 'Client shortlist · To verify', language)} +
+

{hotel.title}

+

{hotel.location}

+

{hotel.copy}

+
+
{pickText('路线', 'Route fit', language)}
{hotel.route}
+
{pickText('确认', 'Verify', language)}
{hotel.verify}
+
+ {pickText('加入住宿比选', 'Add to Stay Shortlist', language)} +
+ ))} +
+
+ ))} +
+ +
+
+
+ {pickText('线路联动', 'Route pairing', language)} + +
+
+ {routeLinks.map((item, index) => ( +
+ {String(index + 1).padStart(2, '0')} +

{item.route}

+ {item.stays} +

{item.note}

+
+ ))} +
+
+
+ +
+
+ {pickText('正式预订前', 'Before booking', language)} +

{pickText('每一家,都要通过同一套六项确认', 'Every property passes the same six booking checks', language)}

+

{pickText('“可以在平台上看到”不等于适合这组客人,也不等于所有宣传设施在出行日期可用。确认结果会进入报价和入住说明。', 'Being listed online does not mean a property fits this group or that every advertised facility is available on the travel date. Confirmed details go into the quote and check-in notes.', language)}

+
+
    + {bookingChecks.map((check, index) => ( +
  1. {String(index + 1).padStart(2, '0')}

    {check}

  2. + ))} +
+
@@ -3020,7 +3995,2959 @@ function TransportDetailPage({ slug }: { slug: string }) { return ; } -function ExperienceDetailPage({ slug }: { slug: string }) { +function CaveExpeditionDetailPage() { + const { language } = useTravelSite(); + const levels = [ + { + number: '01', + label: pickText('轻量', 'Light', language), + title: pickText('洞穴步行与入门观察', 'Cave Walk & Introductory Exploration', language), + copy: pickText( + '以已确认可接待的洞段、明确路径和短时活动为主,不包含技术高度与深水。适合首次进洞、家庭小团或对黑暗与密闭感需要逐步适应的客人。', + 'A short, clearly defined route in a cave section approved for visitor access, without technical height or deep water. Best for first-time cave guests, families, and anyone who wants a gradual introduction to darkness and enclosed spaces.', + language, + ), + gate: pickText('年龄、路面与照明条件按具体洞穴复核', 'Age, terrain, and lighting are confirmed for the selected cave', language), + }, + { + number: '02', + label: pickText('活力', 'Active', language), + title: pickText('平缓水域洞穴桨板', 'Calm-Water Cave Paddleboarding', language), + copy: pickText( + '在运营方允许且水位适合的洞穴水域中进行,会结合不平整洞穴步行、上下板和基础平衡。需要对落水有基本心理准备,且能听从现场指令。', + 'Operated only in cave water approved by the local operator and at suitable water levels. It combines uneven cave walking, mounting and dismounting the board, and basic balance. Guests need reasonable water confidence and must be able to follow field instructions.', + language, + ), + gate: pickText('天气、水位、水温、上岸点与通信条件均须通过', 'Weather, water, landing points, and communication must all pass', language), + }, + { + number: '03', + label: pickText('进阶候选', 'Advanced Candidate', language), + title: pickText('非开发洞段、飞拉达与绳索场景', 'Wild Cave, Via Ferrata & Rope Elements', language), + copy: pickText( + '只面向经验、体能和心理状态匹配的客人,且仅在具体运营方、带队人员、绳索系统、保险和撤离机制完整核验后进入正式方案。', + 'For guests whose experience, fitness, and mental readiness match the terrain. It enters a final itinerary only after the exact operator, field team, rope system, insurance, and evacuation plan have been fully validated.', + language, + ), + gate: pickText('这是待核验的进阶方向,不是默认可售项目', 'This is a validation-dependent direction, not a default sellable activity', language), + }, + ]; + const flow = [ + { title: pickText('行前问卷与分级', 'Pre-trip Profile & Grading', language), copy: pickText('确认年龄、健康、水性、户外经验、怕黑/恐高和想要的强度。', 'Confirm age, health, water confidence, outdoor experience, fear of darkness or heights, and preferred intensity.', language) }, + { title: pickText('点位匹配与二次确认', 'Site Match & Final Reconfirmation', language), copy: pickText('按日期对接清镇、安顺或绥阳等洞穴区域候选,出发前再查天气、水位与场地状态。', 'Match a date-specific candidate in the Qingzhen, Anshun, or Suiyang cave regions, then recheck weather, water, and site status before departure.', language) }, + { title: pickText('现场说明与装备适配', 'Field Briefing & Gear Fit', language), copy: pickText('说明移动方式、手势指令、失衡处理、返回节点与通信方式,再进行头盔、照明或水上装备适配。', 'Review movement, hand signals, loss-of-balance response, return points, and communication before fitting helmets, lighting, or water equipment.', language) }, + { title: pickText('一项核心活动', 'One Core Activity', language), copy: pickText('洞穴日只保留一项核心活动,不把步行、桨板、飞拉达和绳降堆叠成打卡清单。', 'The cave day keeps one core activity instead of stacking walking, paddleboarding, via ferrata, and rope work into a checklist.', language) }, + { title: pickText('退出、恢复与复盘', 'Exit, Recovery & Debrief', language), copy: pickText('按预设节点退出,更换干衣、热饮补给,再决定后续行程是保留还是放缓。', 'Exit at the planned point, change into dry layers, warm up, and decide whether the rest of the day should continue or slow down.', language) }, + ]; + const gates = [ + { title: pickText('场地与自然条件', 'Site & Natural Conditions', language), copy: pickText('当日开放状态、天气、降雨、水位、路面与退出路线。', 'Opening status, weather, rainfall, water level, terrain, and exit route.', language) }, + { title: pickText('运营方与现场团队', 'Operator & Field Team', language), copy: pickText('具体运营主体、带队人员、客人比例、通信与事故处理流程。', 'Named operator, field leaders, guest ratio, communication, and incident procedure.', language) }, + { title: pickText('装备与保险', 'Equipment & Insurance', language), copy: pickText('逐项确认装备清单、检查记录、使用说明和保险对当日活动的覆盖范围。', 'Verify the equipment list, inspection record, use briefing, and insurance coverage for the exact activity.', language) }, + { title: pickText('客人状态与退出机制', 'Guest Readiness & Exit Plan', language), copy: pickText('健康、体力、心理反应和现场表现任一不匹配时,降级、中止或切换地面方案。', 'If health, fitness, mental response, or field performance does not match, downgrade, stop, or switch to a surface alternative.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('作为主力活动日,前后保留恢复与备选窗口。', 'Use it as the primary activity day, with recovery and fallback space around it.', language), href: '/safaris/outdoor-coffee/' }, + { number: '02', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('只加入轻量洞穴步行,与村寨慢住和非遗节奏保持平衡。', 'Add only the light cave-walk version to preserve the balance with village and heritage days.', language), href: '/safaris/culture-outdoor-mix/' }, + { number: '03', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('按日期与路线加一个轻量半日,不改变全景主线。', 'Add a light half-day when dates and routing allow, without changing the panoramic journey.', language), href: '/safaris/classic-guizhou/' }, + ]; + + return ( +
+
+ {pickText('贵州地下喀斯特探索现场', +
+
+
+ + Karst Outdoor Adventures / 01 +

{pickText('地下喀斯特探索', 'Underground Karst Exploration', language)}

+

{pickText('洞穴步行、平缓水域桨板与技术型候选项目——先完成分级,再确定进入哪一处洞穴。', 'Cave walking, calm-water paddleboarding, and technical candidates—graded before any cave site is chosen.', language)}

+ {pickText('先看体验分级', 'See the activity levels', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('不是选一张最刺激的图,而是选一个真正适合你的洞穴日。', 'Not the most dramatic photo—the cave day that genuinely fits you.', language)}

+

{pickText('贵州的喀斯特地貌造就了丰富的洞穴系统。客户文档提出清镇、安顺与绥阳的洞穴区域作为地理依托;实际执行时,我们按日期、开放状态、运营条件与客人能力再确定具体点位。', 'Guizhou’s karst landscape supports a rich cave system. The client brief anchors this experience in the cave regions of Qingzhen, Anshun, and Suiyang; the exact site is selected only after date, access status, operating conditions, and guest ability are checked.', language)}

+
+
+
{pickText('地理依托', 'Geographic Base', language)}
{pickText('清镇 / 安顺 / 绥阳洞穴区域候选', 'Qingzhen / Anshun / Suiyang cave regions', language)}
+
{pickText('活动形式', 'Format', language)}
{pickText('私家小团 / 一天一项核心活动', 'Private small group / one core activity', language)}
+
{pickText('强度', 'Level', language)}
{pickText('轻量 / 活力 / 进阶候选', 'Light / Active / Advanced candidate', language)}
+
{pickText('适合', 'Best For', language)}
{pickText('对洞穴地貌有好奇心,愿意接受行前评估的客人', 'Cave-curious guests open to pre-trip assessment', language)}
+
{pickText('执行方式', 'Operation', language)}
{pickText('场地、运营方、天气与水位逐项确认', 'Site, operator, weather, and water checked item by item', language)}
+
{pickText('备选', 'Fallback', language)}
{pickText('轻量洞穴步行 / 地面喀斯特步道 / 村寨与手作', 'Light cave walk / surface karst trail / village or craft', language)}
+
+
+ +
+
+
01 / Activity Grading

{pickText('先选强度,再选洞穴。', 'Choose the level before the cave.', language)}

{pickText('三档分级不是套餐名称,而是决定点位、装备、人员配置与退出机制的第一步。', 'These are not package names. They are the first decision for site, equipment, staffing, and exit planning.', language)}

+
+ {levels.map((level) =>
{level.number}{level.label}

{level.title}

{level.copy}

{level.gate}
)} +
+
+
+ +
+
{pickText('喀斯特户外现场',
+
+
02 / How the Day Works

{pickText('一个洞穴日,应该有清晰的开始和退出。', 'A cave day needs a clear beginning—and a clear exit.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Go or No-Go

{pickText('四道门槛,缺一项就降级或替换。', 'Four gates. If one fails, downgrade or replace.', language)}

{pickText('行程确认不等于无条件出发。现场决策权优先服务于客人安全和可撤回性。', 'An itinerary confirmation is not an unconditional go. Field decisions prioritize guest safety and a reliable exit.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('与客人匹配的活动分级', 'An activity grade matched to the guest', language)}
  • {pickText('一项核心活动与地面备选', 'One core activity and a surface fallback', language)}
  • {pickText('在正式方案中列明运营方、装备、保险与接驳', 'Named operator, equipment, insurance, and transfers in the final plan', language)}
+
{pickText('必须按具体点位复核', 'Claims that require site-specific proof', language)}
  • {pickText('全年 18°C 的固定温度表述', 'A blanket year-round 18°C statement', language)}
  • {pickText('CE 装备、认证向导与具体保险覆盖', 'CE equipment, certified guides, and exact insurance coverage', language)}
  • {pickText('6 岁以上等统一最低年龄与进阶洞段', 'A universal age minimum such as 6+, and any advanced cave section', language)}
+
+
+
+ +
+
04 / Route Pairing

{pickText('把它放进对的线路,而不是硬塞进每一次贵州之旅。', 'Place it in the right route—not every Guizhou trip.', language)}

+ +
+ + +
+ ); +} + +function WaterfallAbseilingDetailPage() { + const { language } = useTravelSite(); + const levels = [ + { + number: '01', + label: pickText('初次体验候选', 'First-Descent Candidate', language), + title: pickText('干地练习与可控短线下降', 'Dry Practice & a Controlled Short Descent', language), + copy: pickText( + '先在干地完成装备识别、下降姿势、手势与中止指令练习,再由已核验的运营方匹配较短、退出明确的线路。不将“36 米入门”当作所有新手的默认选项。', + 'Begin on dry ground with equipment recognition, body position, hand signals, and stop commands. A verified operator then matches a shorter line with a clear exit. The brief’s “36 m beginner” figure is not treated as a universal first-timer option.', + language, + ), + gate: pickText('只有干地练习、心理状态与现场条件全部通过才进入', 'Proceed only after dry practice, guest readiness, and field conditions all pass', language), + }, + { + number: '02', + label: pickText('活力', 'Active', language), + title: pickText('单项瀑布绳索下降', 'Single Waterfall Rope Descent', language), + copy: pickText( + '以一条经核验的瀑布下降线为当日核心,需要面对水雾、噪声、湿滑岩面和高度感。不在同一天再叠加长距离溯溪、跳潭或其他高强度项目。', + 'One verified waterfall line becomes the day’s core activity. Guests must manage spray, noise, wet rock, and exposure to height. Long stream tracing, pool jumps, or other high-intensity elements are not stacked onto the same day.', + language, + ), + gate: pickText('降雨、水量、岩面、锚点/绳索系统与下方出口均须通过', 'Rainfall, water, rock, anchor/rope system, and lower exit must all pass', language), + }, + { + number: '03', + label: pickText('进阶候选', 'Advanced Candidate', language), + title: pickText('瀑降与峡谷组合', 'Abseiling & Canyoning Combination', language), + copy: pickText( + '客户文档提出溯溪、跳潭、溜索与更大落差的组合方向。这些只面向经验、体能和心理状态匹配的客人,并需要具体运营方、人员、场地、保险与救援资料完整。', + 'The brief proposes combinations with stream tracing, pool jumps, zip lines, and larger drops. These remain for guests whose experience, fitness, and mental readiness match, and only after operator, team, site, insurance, and rescue records are complete.', + language, + ), + gate: pickText('高度与组合玩法必须逐点核验,不是默认可售项目', 'Height and combined elements require site-specific proof and are not default sellable activities', language), + }, + ]; + const flow = [ + { title: pickText('行前筛查与体验匹配', 'Pre-trip Screening & Match', language), copy: pickText('确认健康、体力、恐高程度、游泳/水性、绳索经验和对湿身环境的接受度。', 'Confirm health, fitness, height exposure, water confidence, rope experience, and comfort with a fully wet environment.', language) }, + { title: pickText('点位、天气与水量二次确认', 'Site, Weather & Water Recheck', language), copy: pickText('从归兰山、摆弓岩、滴水滩等客户文档候选区域中按当日可执行条件选择,不预先锁定高度。', 'Select from brief-listed candidates such as Guilanshan, Baigongyan, or Dishuitan only when the day’s operating conditions pass, without pre-promising a height.', language) }, + { title: pickText('干地说明与装备适配', 'Dry Briefing & Equipment Fit', language), copy: pickText('由具体运营方说明保护系统、下降动作、手势、中止指令、通信和退出路线,完成穿戴与干地练习。', 'The named operator explains the protection system, descent movement, signals, stop commands, communication, and exit route before fitting equipment and rehearsing on dry ground.', language) }, + { title: pickText('Go / No-go 与单项下降', 'Go / No-go & One Core Descent', language), copy: pickText('现场不通过就停止或替换;通过后也只执行正式方案列明的一项核心绳索活动。', 'Stop or replace if field checks fail; if they pass, operate only the single rope activity named in the final plan.', language) }, + { title: pickText('离场、保暖与恢复', 'Exit, Warm-up & Recovery', language), copy: pickText('按预设路线离场,更换干衣、补充热食与水分,当天不再安排赶机、长距离山路或第二项高强度活动。', 'Leave by the planned exit, change into dry layers, hydrate, and take warm food. Do not follow with a flight, long mountain transfer, or second high-intensity activity.', language) }, + ]; + const gates = [ + { title: pickText('降雨、水量与岩面', 'Rain, Water & Rock', language), copy: pickText('降雨趋势、上游来水、瀑布水量、能见度、岩面湿滑和水温。', 'Rain trend, upstream inflow, waterfall volume, visibility, rock traction, and water temperature.', language) }, + { title: pickText('场地、系统与退出', 'Site, System & Exit', language), copy: pickText('当日开放/授权状态、锚点与绳索方案、上下站位、通信与中途撤离可能性。', 'Day-specific access/authorization, anchor and rope plan, top and bottom stations, communication, and mid-route escape options.', language) }, + { title: pickText('运营方、人员与救援', 'Operator, Team & Rescue', language), copy: pickText('具体运营主体、带队/操作人员、客人比例、应急联络、救援路线与现场救生装备。', 'Named operator, field/rope staff, guest ratio, emergency contacts, rescue route, and on-site rescue equipment.', language) }, + { title: pickText('客人、装备与保险', 'Guest, Equipment & Insurance', language), copy: pickText('客人健康与现场表现、装备清单/检查记录、使用说明,以及保险是否覆盖该点位和活动。', 'Guest health and field performance, equipment list/inspection record, use briefing, and insurance coverage for the exact site and activity.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('最合适的主线:把瀑降作为唯一主活动日,前后保留备选和恢复。', 'The best-fit route: make abseiling the only core activity that day, with fallback and recovery space around it.', language), href: '/safaris/outdoor-coffee/' }, + { number: '02', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('只在安顺段整天替换时加入,不与黄果树全景观光同天满负荷叠加。', 'Add it only by replacing a full Anshun day, not by stacking it onto a full Huangguoshu sightseeing day.', language), href: '/safaris/classic-guizhou/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('默认保留瀑布观景或峡谷步行;只在全员能力与资料都匹配时单独定制绳索活动。', 'Default to waterfall viewing or a canyon walk; customize a rope activity only when every guest and operating record matches.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('贵州瀑布绳索下降现场', +
+
+
+ + Karst Outdoor Adventures / 02 +

{pickText('瀑降与峡谷绳索', 'Waterfall Abseiling & Canyoning', language)}

+

{pickText('在水雾、岩壁与绳索之间下降——但只有点位、系统、人员、水量与退出全部通过时才出发。', 'Descend through spray, rock, and rope—only when site, system, team, water, and exit checks all pass.', language)}

+ {pickText('先看强度分级', 'See the activity levels', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('它不是一个瀑布打卡点,而是一个高后果的湿绳索活动日。', 'Not a waterfall photo stop—a high-consequence wet-rope activity day.', language)}

+

{pickText('客户文档以贵州密集瀑布群为依托,提到归兰山、摆弓岩与滴水滩等候选区域。水量、岩面、锚点与撤离条件会随地点和当天变化,因此具体瀑布、高度与玩法只在核验后写入正式方案。', 'The client brief anchors this experience in Guizhou’s dense waterfall landscape, naming Guilanshan, Baigongyan, and Dishuitan as candidates. Water, rock, anchors, and escape conditions vary by place and day, so the exact waterfall, height, and format enter the final plan only after validation.', language)}

+
+
+
{pickText('地理依托', 'Geographic Base', language)}
{pickText('归兰山 / 摆弓岩 / 滴水滩等候选区域', 'Guilanshan / Baigongyan / Dishuitan candidate areas', language)}
+
{pickText('活动形式', 'Format', language)}
{pickText('私家小团 / 一天一项核心绳索活动', 'Private small group / one core rope activity', language)}
+
{pickText('强度', 'Level', language)}
{pickText('初次体验候选 / 活力 / 进阶候选', 'First-descent / Active / Advanced candidates', language)}
+
{pickText('适合', 'Best For', language)}
{pickText('具备基础体力,能接受湿身、噪声与高度感的客人', 'Guests with baseline fitness who accept full wetness, noise, and exposure', language)}
+
{pickText('执行方式', 'Operation', language)}
{pickText('点位、运营方、绳索系统、水量与保险逐项确认', 'Site, operator, rope system, water, and insurance checked item by item', language)}
+
{pickText('备选', 'Fallback', language)}
{pickText('瀑布观景 / 峡谷步行 / 山野咖啡或村寨', 'Waterfall viewing / canyon walk / mountain coffee or village', language)}
+
+
+ +
+
{pickText('瀑布峡谷绳索现场',
+
{pickText('瀑布水幕中的绳索下降',
+
{pickText('瀑布水雾中的下降者',
+
+ +
+
+
01 / Activity Grading

{pickText('先匹配下降等级,再谈瀑布高度。', 'Match the descent level before discussing height.', language)}

{pickText('对新手来说,高度数字不是难度的全部。水量、岩面、出口、心理反应和操作系统共同决定是否适合。', 'Height alone does not define difficulty. Water, rock, exit, mental response, and the operating system together decide whether a descent fits.', language)}

+
{levels.map((level) =>
{level.number}{level.label}

{level.title}

{level.copy}

{level.gate}
)}
+
+
+ +
+
{pickText('瀑降现场的水雾与绳索',
+
+
02 / How the Day Works

{pickText('真正的开始在干地,真正的结束在安全离场之后。', 'The real start is on dry ground. The real finish is a safe exit.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Go or No-Go

{pickText('四道门槛,任一不通过就不进入瀑布线。', 'Four gates. If one fails, do not enter the waterfall line.', language)}

{pickText('客户文档中的“双绳保护、前置探路、实时水量监测与应急救生装备”会成为运营方核验项,核验前不对外作事实承诺。', 'The brief’s double-rope, pre-scouting, live water monitoring, and emergency equipment claims become operator verification items, not public promises before proof.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具名的点位、运营方与活动等级', 'A named site, operator, and activity grade', language)}
  • {pickText('一项核心绳索活动与地面备选', 'One core rope activity and a surface fallback', language)}
  • {pickText('列明人员、绳索/装备、保险、通信、撤离与接驳', 'Named team, rope/equipment, insurance, communication, exit, and transfers', language)}
+
{pickText('必须按具体点位复核', 'Claims that require site-specific proof', language)}
  • {pickText('20–86 米、36 米入门与 86 米进阶等高度数字', 'The 20–86 m range and 36 m beginner / 86 m advanced figures', language)}
  • {pickText('双绳保护的具体系统、人员资料与装备检查记录', 'The exact double-rope system, staff records, and equipment inspections', language)}
  • {pickText('前置探路、实时水量监测、救生装备与保险覆盖', 'Pre-scouting, live water monitoring, rescue equipment, and insurance coverage', language)}
+
+
+
+ +
+
04 / Route Pairing

{pickText('给瀑降一个完整的活动日,而不是观光之后的加码项。', 'Give abseiling a complete activity day—not an add-on after sightseeing.', language)}

+ +
+ + +
+ ); +} + +function CanyonRiverDetailPage() { + const { language } = useTravelSite(); + const levels = [ + { + number: '01', + label: pickText('轻量亲水', 'Gentle Water', language), + title: pickText('平缓水域与短时水边体验', 'Calm Water & a Short Riverside Session', language), + copy: pickText( + '以已核验的平缓水面、稳定上下船点与短时体验为主,可结合水边步行、山野野餐或只观景不下水的陪同方案。亲子是候选客群,但不直接承诺“全年龄”。', + 'A short session on verified calm water with a stable launch and landing point, optionally paired with a riverside walk, wild picnic, or a dry companion plan. Families are a candidate audience, but “all ages” is not promised without operator rules.', + language, + ), + gate: pickText('年龄、身高/体重、救生衣适配、水温与岸线条件按具体运营方复核', 'Age, size, flotation fit, water temperature, and shoreline are operator-specific', language), + }, + { + number: '02', + label: pickText('活力溯溪', 'Active Tracing', language), + title: pickText('峡谷涉水、步行与岩石通过', 'Canyon Wading, Walking & Rock Passage', language), + copy: pickText( + '在具体河段中逆流或顺流行走,包含不平整河床、湿滑岩石、涉水与短距离攀爬。跳潭不默认包含,可绕行与中途退出必须先确认。', + 'Move upstream or downstream through an exact section with uneven riverbed, wet rock, wading, and short scrambling. Pool jumps are not included by default; bypasses and mid-route exits must be confirmed first.', + language, + ), + gate: pickText('需要基础体力、水性和湿滑地形通过能力,大雨或水位上升直接替换', 'Requires baseline fitness, water confidence, and wet-terrain mobility; rising water triggers replacement', language), + }, + { + number: '03', + label: pickText('白水候选', 'White-water Candidate', language), + title: pickText('较快河段的专业皮划艇/漂流艇', 'Rafting or Kayaking on Faster Water', language), + copy: pickText( + '真正的白水河段与平缓充气艇不是同一类产品。只有具体河段难度、运营许可、艇船与救生系统、操作人员、应急救援、保险和客人能力全部通过后,才进入正式方案。', + 'True white water is not the same product as calm-water packrafting. It enters a final plan only after river difficulty, operating permission, craft and rescue systems, field team, emergency response, insurance, and guest ability all pass.', + language, + ), + gate: pickText('客户图片主要展示平缓水域,不用来证明白水漂流已可售', 'The supplied images show mainly calm water and do not prove a sellable white-water product', language), + }, + ]; + const flow = [ + { title: pickText('行前能力与客群筛查', 'Guest Screening', language), copy: pickText('确认年龄范围、健康、水性、游泳能力、户外经验、对落水/翻船的心理准备和是否需要不下水的陪同方案。', 'Confirm age range, health, water confidence, swimming ability, outdoor experience, mental readiness for immersion or capsize, and whether a dry companion plan is needed.', language) }, + { title: pickText('只选一种当日水上模式', 'Choose One Water Mode', language), copy: pickText('在平缓亲水、活力溯溪与白水候选中选一项,不把溯溪、跳潭、漂流和长距离转场全部堆在一天。', 'Choose calm water, active tracing, or a white-water candidate—not stream tracing, pool jumps, rafting, and a long transfer all in one day.', language) }, + { title: pickText('河段、天气与水文二次确认', 'River, Weather & Hydrology Recheck', language), copy: pickText('按场地开放状态、上游降雨、水位趋势、流速、能见度、水温、上下岸点与退出路线作 Go / No-go。', 'Make the Go / No-go call from access status, upstream rain, water trend, flow, visibility, temperature, launch/landing points, and exit routes.', language) }, + { title: pickText('装备适配、陆地说明与入水', 'Fit, Shore Briefing & Entry', language), copy: pickText('由具体运营方完成救生衣、头盔或艇船等当日装备适配,说明划行、手势、落水处理、集合与退出。', 'The named operator fits the day’s flotation, helmet, or craft and briefs paddling, signals, immersion response, regrouping, and exit.', language) }, + { title: pickText('离水、保暖、野餐或热食', 'Exit, Warm-up & Food', language), copy: pickText('按计划点离水,更换干衣、补水与热食。野餐只在合法、安全且不损害环境的休息点安排。', 'Exit at the planned point, change into dry layers, hydrate, and take warm food. Picnics use only legal, safe, low-impact rest points.', language) }, + ]; + const gates = [ + { title: pickText('降雨、水文与水温', 'Rain, Hydrology & Temperature', language), copy: pickText('当地与上游降雨、水位变化、流速、能见度、水温和突发洪水风险。', 'Local and upstream rain, water trend, flow, visibility, temperature, and flash-flood risk.', language) }, + { title: pickText('河段、上下岸与退出', 'River, Access & Exit', language), copy: pickText('具体起止点、河段难度、岸线、障碍物、通信信号、中途退出点与车辆接回方案。', 'Exact start/finish, river difficulty, banks, hazards, communication, mid-route exits, and vehicle recovery.', language) }, + { title: pickText('运营方、人员与救援', 'Operator, Team & Rescue', language), copy: pickText('具名运营主体、带队/舟艇操作人员、客人比例、资质证明、急救、水上救援与应急联络。', 'Named operator, field/craft staff, guest ratio, credential records, first aid, water rescue, and emergency contact.', language) }, + { title: pickText('装备、客人与保险', 'Equipment, Guest & Insurance', language), copy: pickText('救生衣、头盔、护具或艇船的适配与检查记录,客人年龄/身高/体重/能力限制,以及保险对当日河段和活动的覆盖。', 'Fit and inspection records for flotation, helmet, protection, or craft; guest age/size/ability limits; and insurance coverage for the exact river and activity.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('最合适作为一个完整水上日,只选一档强度,前后保留备选与恢复。', 'Best as one complete water day at a single level, with fallback and recovery space around it.', language), href: '/safaris/outdoor-coffee/' }, + { number: '02', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('加入平缓亲水或短时溯溪,与村寨慢住、非遗和恢复节奏保持平衡。', 'Add calm water or a short tracing section while preserving the route’s village, heritage, and recovery rhythm.', language), href: '/safaris/culture-outdoor-mix/' }, + { number: '03', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('只在荔波段顺路加入轻量亲水,不与小七孔全天步行或长转场强行叠加。', 'Add only a light on-route water session in Libo, not on top of a full Xiaoqikong walk or long transfer.', language), href: '/safaris/classic-guizhou/' }, + ]; + + return ( +
+
+ {pickText('贵州喀斯特森林河流与充气艇', +
+
+
+ + Karst Outdoor Adventures / 03 +

{pickText('峡谷溯溪与白水漂流', 'Canyon River Tracing & White Water Rafting', language)}

+

{pickText('从平缓亲水到活力溯溪,再到待核验的白水河段——先选水上模式,再决定哪一条河适合。', 'From calm water to active tracing and validation-dependent white water—choose the water mode before the river.', language)}

+ {pickText('先看水上分级', 'See the water levels', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('亲水、溯溪与白水漂流,是三种不同的决策。', 'Calm water, stream tracing, and white water are three different decisions.', language)}

+

{pickText('客户文档以荔波喀斯特峡谷与黔南森林溪流为地理依托,提出溯溪、皮划艇/漂流艇、小众水潭与山野野餐。这些方向不能默认合并:具体河段、强度、船型、年龄条件、水文和运营方必须分开确认。', 'The brief anchors this experience in Libo’s karst canyons and Qiannan forest streams, proposing tracing, kayaks or rafts, quiet pools, and wild picnics. These do not automatically combine: river, intensity, craft, age rules, hydrology, and operator must be checked separately.', language)}

+
+
+
{pickText('地理依托', 'Geographic Base', language)}
{pickText('荔波喀斯特峡谷 / 黔南森林溪流候选', 'Libo karst canyons / Qiannan forest streams', language)}
+
{pickText('活动形式', 'Format', language)}
{pickText('私家小团 / 一天只选一档水上活动', 'Private small group / one water level per day', language)}
+
{pickText('强度', 'Level', language)}
{pickText('轻量亲水 / 活力溯溪 / 白水候选', 'Gentle water / Active tracing / White-water candidate', language)}
+
{pickText('适合', 'Best For', language)}
{pickText('愿意按水性、体力与年龄条件选择对应模式的客人', 'Guests open to a mode matched to water confidence, fitness, and age rules', language)}
+
{pickText('执行方式', 'Operation', language)}
{pickText('河段、水文、运营方、人员、装备、保险与退出逐项确认', 'River, hydrology, operator, team, equipment, insurance, and exit checked item by item', language)}
+
{pickText('备选', 'Fallback', language)}
{pickText('水森林步道 / 峡谷摄影 / 村寨 / 山野咖啡', 'Water-forest trail / canyon photography / village / mountain coffee', language)}
+
+
+ +
+
{pickText('水边的充气艇与船桨',
+
{pickText('森林溪流中的休息节点',
+
{pickText('平缓水域中的充气艇',
+
{pickText('喀斯特峡谷水潭',
+
+ +
+
+
01 / Water Grading

{pickText('先选水上模式,再选河段和装备。', 'Choose the water mode before the river and gear.', language)}

{pickText('轻量亲水、溯溪和白水漂流对客人能力、年龄条件、艇船、人员和救援系统的要求不同。', 'Gentle water, tracing, and white water require different guest ability, age rules, craft, staffing, and rescue systems.', language)}

+
{levels.map((level) =>
{level.number}{level.label}

{level.title}

{level.copy}

{level.gate}
)}
+
+
+ +
+
{pickText('森林平缓水域与充气艇',
+
+
02 / How the Day Works

{pickText('一个水上日,只做一个清晰的选择。', 'One water day. One clear choice.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Go or No-Go

{pickText('水不对、人不匹配或退出不清楚,就不下水。', 'Wrong water, wrong match, or unclear exit means no entry.', language)}

{pickText('客户文档中的“全年龄、专业救生衣与护具、持证溯溪向导、全程实时监测”会转为具体河段与运营方的核验项。', 'The brief’s all-ages, professional equipment, certified guide, and full-time live monitoring claims become river- and operator-specific verification items.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('明确选择平缓亲水、溯溪或已核验白水中的一项', 'One clearly selected mode: calm water, tracing, or validated white water', language)}
  • {pickText('具名河段、运营方、人员、装备与地面备选', 'Named river, operator, team, equipment, and surface fallback', language)}
  • {pickText('列明年龄/能力条件、保险、通信、撤离与接驳', 'Age/ability rules, insurance, communication, exit, and transfers', language)}
+
{pickText('必须按具体河段复核', 'Claims that require river-specific proof', language)}
  • {pickText('“全年龄”和平缓段亲子的最低年龄、身高/体重与陪同规则', '“All ages” and exact family minimum age, size, and companion rules', language)}
  • {pickText('持证溯溪向导、操作人员资料与水上救援方案', 'Certified tracing guide, staff records, and water-rescue plan', language)}
  • {pickText('救生衣/护具/艇船检查、实时水位与天气监测、白水河段难度与保险覆盖', 'Equipment/craft inspections, live monitoring, white-water difficulty, and insurance coverage', language)}
+
+
+
+ +
+
04 / Route Pairing

{pickText('把河流变成一段有节奏的旅程,而不是把所有水上玩法放在一天。', 'Make the river a journey chapter—not every water activity in one day.', language)}

+ +
+ + +
+ ); +} + +function KarstHikingCyclingDetailPage() { + const { language } = useTravelSite(); + const levels = [ + { + number: '01', + label: pickText('轻量', 'Gentle', language), + title: pickText('万峰林田园慢骑或步行', 'Wanfenglin Country Ride or Walk', language), + copy: pickText( + '在峰林与田园之间选择短程慢骑或步行,不默认把两万座峰林写成可验证的现场数量,也不把“全年龄”当作统一承诺。自行车供应、道路交通、头盔、身高适配、陪骑与接回车辆按日期确认。', + 'Choose a short country ride or walk through the peak-forest landscape. The “20,000 peaks” and “all ages” claims are not treated as universal promises. Bike supply, road traffic, helmets, sizing, ride support, and recovery vehicle are confirmed by date.', + language, + ), + gate: pickText('适合想看风景、拍照与保持舒缓节奏的客人', 'Best for scenery, photography, and an unhurried pace', language), + }, + { + number: '02', + label: pickText('活力', 'Active', language), + title: pickText('茂兰喀斯特森林徒步', 'Maolan Karst Forest Hike', language), + copy: pickText( + '以森林步道、湿润岩面、树根与喀斯特地形为主。溶洞停靠只有在具体开放状态、向导、照明、天气与退出条件通过后才加入;世界遗产地的具体进入范围也需按当日规则确认。', + 'A forest hike across damp rock, roots, and karst terrain. Any cave stop enters the plan only after access, guide, lighting, weather, and exit are cleared; the exact accessible area within the heritage landscape is also date-specific.', + language, + ), + gate: pickText('需要稳定步行能力,并接受湿滑、不平整与天气变化', 'Requires steady walking ability and comfort with wet, uneven ground and changing weather', language), + }, + { + number: '03', + label: pickText('进阶候选', 'Advanced Candidate', language), + title: pickText('赤水河谷长距离骑行', 'Chishui River Valley Distance Ride', language), + copy: pickText( + '客户文档中的 160 公里作为道路网络线索,而不是单日承诺里程。正式方案必须给出实际骑行距离、累计爬升、路况、车型、补给、维修、交通干扰、领骑人员、保障车、保险与中途退出点。', + 'The brief’s 160 km figure is treated as a route-network lead, not a promised day distance. A final plan must state actual distance, elevation gain, road surface, bike type, fuel stops, repairs, traffic exposure, ride leader, support vehicle, insurance, and exits.', + language, + ), + gate: pickText('只面向已确认骑行经验、体能和车辆适配的客人', 'Only for guests with confirmed riding experience, fitness, and bike fit', language), + }, + ]; + const flow = [ + { title: pickText('行前能力与身体筛查', 'Guest Profile', language), copy: pickText('确认徒步或骑行经验、舒适里程、膝踝情况、年龄范围、怕热程度、身高与自行车尺寸需求。', 'Confirm hiking or riding experience, comfortable distance, knee and ankle history, age range, heat tolerance, height, and bike-sizing needs.', language) }, + { title: pickText('只选一个区域和一种移动方式', 'Choose One Landscape and Mode', language), copy: pickText('万峰林、茂兰与赤水河谷不包装成一个活动日;根据旅行路线选择慢骑、徒步或进阶骑行中的一项。', 'Wanfenglin, Maolan, and Chishui River Valley are not packaged as one activity day; choose one walk, gentle ride, or advanced ride that fits the journey.', language) }, + { title: pickText('复核路线、天气与退出', 'Recheck Route, Weather & Exit', language), copy: pickText('在出发前确认开放状态、实际距离、累计爬升、路面或步道、降雨与高温、补给、通信、中途退出和车辆接回。', 'Before departure, confirm access, actual distance, elevation, surface, rain or heat, fuel stops, communication, mid-route exits, and vehicle recovery.', language) }, + { title: pickText('装备适配与现场说明', 'Fit and Briefing', language), copy: pickText('徒步确认鞋服、防滑与补水;骑行确认车架尺寸、刹车、变速、头盔、维修包、灯具与领骑手势。', 'For hiking, confirm footwear, grip, layers, and water; for cycling, confirm frame size, brakes, gears, helmet, repair kit, lights, and ride signals.', language) }, + { title: pickText('分段集合、退出与恢复', 'Regroup, Exit & Recover', language), copy: pickText('按分段节点集合,任何疼痛、天气或路况变化都可提前退出;结束后安排补水、热食、换衣和接回。', 'Regroup at planned points, allow early exit for pain, weather, or surface changes, then provide hydration, warm food, dry layers, and return transfer.', language) }, + ]; + const gates = [ + { title: pickText('路线、开放与天气', 'Route, Access & Weather', language), copy: pickText('具名起终点、真实里程与爬升、道路/步道状态、季节、降雨、高温、交通与开放规则。', 'Named start and finish, real distance and elevation, road or trail state, season, rain, heat, traffic, and access rules.', language) }, + { title: pickText('运营方与现场人员', 'Operator & Field Team', language), copy: pickText('具名供应方、向导或领骑、客人比例、急救、通信、车辆调度和应急联系人。', 'Named supplier, guide or ride leader, guest ratio, first aid, communication, vehicle dispatch, and emergency contact.', language) }, + { title: pickText('装备与车辆', 'Equipment & Bikes', language), copy: pickText('徒步防滑与补水要求;骑行车型、尺寸、头盔、刹车/变速检查、维修工具、备件与保障车。', 'Hiking grip and hydration requirements; bike type and size, helmet, brake and gear checks, tools, spares, and support vehicle.', language) }, + { title: pickText('客人、保险与退出', 'Guest, Insurance & Exit', language), copy: pickText('年龄、健康、膝踝、体力和经验限制,活动对应保险,以及中途退出、换乘和医疗转运路径。', 'Age, health, knee and ankle, fitness and experience limits, activity-specific insurance, mid-route exits, transfers, and medical escalation.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('安排一个完整徒步或骑行日,保留天气、路线和体力备选,不与高强度洞穴或水上项目叠加。', 'Use one complete hiking or riding day with weather, route, and ability fallbacks—not on top of a demanding cave or water activity.', language), href: '/safaris/outdoor-coffee/' }, + { number: '02', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('选择万峰林轻量慢骑或半日徒步,与村寨、非遗和恢复节奏保持平衡。', 'Choose a gentle Wanfenglin ride or half-day walk while preserving village, heritage, and recovery time.', language), href: '/safaris/culture-outdoor-mix/' }, + { number: '03', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('仅在兴义段顺路加入轻量峰林步行或短程慢骑,不强行加入长距离或高爬升。', 'Add only an easy peak-forest walk or short ride while in Xingyi, without forcing distance or major elevation.', language), href: '/safaris/classic-guizhou/' }, + ]; + + return ( +
+
+ {pickText('徒步者远眺喀斯特峰林与河谷', +
+
+
+ + Karst Outdoor Adventures / 04 +

{pickText('喀斯特峰林徒步与骑行', 'Karst Peak Forest Hiking & Cycling', language)}

+

{pickText('三种地貌、三种移动方式——先匹配地形、里程、天气与退出,再决定哪一条路线适合。', 'Three landscapes, three ways to move—match terrain, distance, weather, and exit before choosing the route.', language)}

+ {pickText('先看三种选择', 'See the three options', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('万峰林、茂兰、赤水河谷,不是同一个活动日。', 'Wanfenglin, Maolan, and Chishui River Valley are not one activity day.', language)}

+

{pickText('客户文档提供三个地理依托:万峰林田园、茂兰原始森林与赤水河谷自行车路线。它们距离远、地形和保障条件不同,因此页面把它们拆为三个独立产品方向,由旅行日期、客人能力与整条线路位置决定最终选择。', 'The brief names three geographic bases: Wanfenglin countryside, Maolan forest, and the Chishui River Valley cycle route. They are far apart and require different terrain and support systems, so this page treats them as three independent product directions selected by date, guest ability, and journey geography.', language)}

+
+
+
{pickText('地理依托', 'Geographic Base', language)}
{pickText('万峰林 / 茂兰 / 赤水河谷候选', 'Wanfenglin / Maolan / Chishui River Valley', language)}
+
{pickText('活动形式', 'Format', language)}
{pickText('私家小团 / 一天只选一个区域与一种方式', 'Private small group / one region and one mode per day', language)}
+
{pickText('强度', 'Level', language)}
{pickText('轻量慢骑或步行 / 活力徒步 / 进阶骑行候选', 'Gentle ride or walk / Active hike / Advanced ride candidate', language)}
+
{pickText('适合', 'Best For', language)}
{pickText('愿意按体力、经验、膝踝与天气选择对应模式的客人', 'Guests open to matching mode with fitness, experience, joint health, and weather', language)}
+
{pickText('执行方式', 'Operation', language)}
{pickText('路线、里程、爬升、装备、团队、保险与退出逐项确认', 'Route, distance, elevation, equipment, team, insurance, and exit checked item by item', language)}
+
{pickText('备选', 'Fallback', language)}
{pickText('峰林摄影 / 村寨慢行 / 山野咖啡 / 室内非遗', 'Peak-forest photography / village walk / mountain coffee / indoor craft', language)}
+
+
+ +
+
{pickText('日落下远眺峰林与河谷',
+
{pickText('徒步者站在喀斯特山脊',
+
{pickText('喀斯特山谷道路与露营场景',
+
+ +
+
+
01 / Choose Your Mode

{pickText('先选地貌与移动方式,再谈里程。', 'Choose the landscape and mode before the mileage.', language)}

{pickText('田园慢骑、森林徒步与进阶骑行对路线、装备、体力和保障系统的要求完全不同。', 'A country ride, forest hike, and advanced distance ride require completely different routes, gear, fitness, and support.', language)}

+
{levels.map((level) =>
{level.number}{level.label}

{level.title}

{level.copy}

{level.gate}
)}
+
+
+ +
+
{pickText('喀斯特山谷中的户外路线',
+
+
02 / How the Day Works

{pickText('一个户外日,只做一个清晰的选择。', 'One outdoor day. One clear choice.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Go or No-Go

{pickText('路线不明、装备不合或退出不清楚,就不出发。', 'Unclear route, poor fit, or no exit means no departure.', language)}

{pickText('客户文档中的“两万座峰林、160 公里专用自行车道、全年龄段”会转为具名路线与运营方的核验项,不直接成为统一营销承诺。', 'The brief’s “20,000 peaks,” “160 km dedicated cycle path,” and “all ages” claims become route- and operator-specific checks—not universal marketing promises.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('明确选择一个区域、一种移动方式与对应强度', 'One named region, mode, and matched intensity', language)}
  • {pickText('具名起终点、实际里程/爬升、供应方、装备与接驳', 'Named start/finish, actual distance/elevation, supplier, equipment, and transfers', language)}
  • {pickText('列明天气备选、能力条件、保险、通信与退出', 'Weather fallback, ability rules, insurance, communication, and exits', language)}
+
{pickText('必须按具体产品复核', 'Claims requiring product-specific proof', language)}
  • {pickText('“两万座峰林”的准确口径和可使用来源', 'The exact basis and usable source for “20,000 peaks”', language)}
  • {pickText('“160 公里专用自行车道”的实际路段、路权、开放和维护状态', 'The exact section, right-of-way, access, and maintenance behind “160 km dedicated cycle path”', language)}
  • {pickText('“全年龄”、世界遗产进入范围、车型照片、向导/领骑资料与保险覆盖', '“All ages,” heritage access, bike imagery, guide/ride-leader records, and insurance coverage', language)}
+
+
+
+ +
+
04 / Route Pairing

{pickText('让步行或骑行成为旅程里恰到好处的一天。', 'Make walking or riding the right-sized day in the journey.', language)}

+ +
+ + +
+ ); +} + +function CaveLuxuryDiningDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('轻量', 'Gentle', language), + title: pickText('洞穴咖啡与茶点', 'Underground Coffee & Tea', language), + copy: pickText( + '在已经具备安全步道、照明、座席与接待条件的洞穴空间中安排 60–90 分钟咖啡或茶点。菜单、热水、电力、杯具、饮用水、过敏原和垃圾回收按场地确认,不把临时野洞改造成餐厅。', + 'A 60–90 minute coffee or tea service inside a cave venue already equipped with safe paths, lighting, seating, and guest reception. Menu, hot water, power, drinkware, potable water, allergens, and waste removal are venue-specific; a wild cave is never improvised into a dining room.', + language, + ), + status: pickText('适合希望安静停留与拍摄、但不需要完整正餐的客人', 'For guests seeking a quiet, photogenic pause without a full meal', language), + }, + { + number: '02', + label: pickText('私家餐叙', 'Private Dining', language), + title: pickText('洞穴私家晚宴', 'Private Cave Dinner', language), + copy: pickText( + '以具名菜单、座位布置、服务人员、后厨/出餐路径、温控与卫生方案为基础设计 2–3 小时晚宴。默认使用无明火氛围灯;只有场地书面允许并完成消防复核时,才讨论真实蜡烛或其他火源。', + 'A 2–3 hour dinner built around a named menu, seating plan, service team, kitchen and delivery route, temperature control, and hygiene plan. Flame-free ambient lighting is the default; real candles or any open flame are considered only with written venue permission and fire review.', + language, + ), + status: pickText('餐食、酒水、花艺、灯光、独享范围与超时费用逐项报价', 'Food, drinks, florals, lighting, exclusivity, and overtime are quoted item by item', language), + }, + { + number: '03', + label: pickText('组合候选', 'Combined Candidate', language), + title: pickText('轻量探洞后落座', 'Light Cave Walk, Then Dine', language), + copy: pickText( + '先完成独立的轻量洞穴步行,再回到具备接待条件的空间落座。探洞段需要单独核验开放、水位、地面、向导、头盔、头灯、鞋服与退出;饮酒只安排在步行全部结束之后。', + 'Complete a separately managed light cave walk before returning to the hospitality-ready area. The walk has its own access, water, surface, guide, helmet, headlamp, footwear, and exit checks; alcohol is served only after all walking is complete.', + language, + ), + status: pickText('不是边走边吃,也不把餐饮场地照片当作探洞安全证明', 'Not a roaming meal, and dining imagery is not evidence of safe cave exploration', language), + }, + ]; + const flow = [ + { title: pickText('确认客人需求与限制', 'Guest Brief', language), copy: pickText('确认人数、日期、用餐时段、饮食禁忌与过敏、酒水、行动能力、怕黑/幽闭感、儿童安排和仪式需求。', 'Confirm group size, date, meal time, diet and allergies, alcohol, mobility, darkness or claustrophobia concerns, children, and occasion needs.', language) }, + { title: pickText('只从具名可接待场地中选择', 'Choose a Named Hospitality Venue', language), copy: pickText('确认场地授权、开放时间、入口与步道、地面、防坠、水体边界、座席、卫生间、通信、应急照明和双向撤离。', 'Confirm venue authority, hours, entrance and path, floor, fall and water edges, seating, toilets, communication, emergency lighting, and two-way evacuation.', language) }, + { title: pickText('锁定菜单、团队与接驳', 'Lock Menu, Team & Transfers', language), copy: pickText('确定菜单和过敏原表、厨师/供应商、饮用水、保温/冷链、服务人员、英文支持、酒水规则,以及客人和物资的往返接驳。', 'Fix the menu and allergen sheet, chef or caterer, potable water, hot/cold chain, service team, English support, alcohol rules, and guest and supply transfers.', language) }, + { title: pickText('现场复核与无痕布置', 'Site Check & Low-Impact Setup', language), copy: pickText('活动前复核天气、水位、湿滑、落石、照明、电力和消防;布置不打孔、不触碰洞穴沉积物,电线防水防绊,垃圾与污水全部带离。', 'Before service, recheck weather, water, slip and rockfall, lighting, power, and fire controls. No drilling or touching formations; cables are water- and trip-managed, and all waste and wastewater leave the cave.', language) }, + { title: pickText('迎宾、用餐与安全离场', 'Host, Dine & Exit', language), copy: pickText('入场先说明路线、卫生间、应急照明和集合点;结束后清点客人及物资,关闭临时设备、恢复场地,并由专车送回。', 'On arrival, brief paths, toilets, emergency lighting, and regroup points. Afterwards, count guests and equipment, shut temporary systems, restore the site, and return by private transfer.', language) }, + ]; + const gates = [ + { title: pickText('场地授权与洞穴保护', 'Venue Authority & Cave Protection', language), copy: pickText('具名场地、可开展餐饮/活动的书面边界、开放状态、容量、独享范围,以及不破坏岩体、沉积物、水体和生物的布置规则。', 'Named venue, written hospitality/activity boundary, access status, capacity, exclusive area, and setup rules protecting rock, formations, water, and cave life.', language) }, + { title: pickText('消防、电力、照明与撤离', 'Fire, Power, Lighting & Evacuation', language), copy: pickText('是否允许明火、用电负荷、漏保与防水、常规/应急照明、通风、通信、两条撤离路径、灭火器材和医疗转运。', 'Open-flame rule, power load, residual-current and waterproof protection, normal/emergency light, ventilation, communication, two exit routes, fire equipment, and medical transfer.', language) }, + { title: pickText('餐饮卫生与酒水', 'Food Hygiene & Alcohol', language), copy: pickText('具名餐饮供应方、制作与出餐位置、饮用水、冷热链、餐具消毒、手部卫生、过敏原、垃圾/污水处理与酒水许可及份量。', 'Named food supplier, preparation and service points, potable water, hot/cold chain, utensil hygiene, handwashing, allergens, waste/wastewater, and alcohol permission and quantities.', language) }, + { title: pickText('人员、客人、接驳与保险', 'Team, Guests, Transfers & Insurance', language), copy: pickText('场地负责人、服务/向导人员、英文支持、客人行动与健康限制、活动前后接驳、备用场地、责任边界与保险覆盖。', 'Venue lead, service or guide team, English support, guest mobility and health limits, pre/post transfers, fallback venue, responsibilities, and insurance coverage.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('在兴义、安顺或返程节点加入咖啡茶点或私家晚宴,以具名场地距离决定,不为一顿饭大幅绕路。', 'Add coffee or private dining near Xingyi, Anshun, or a return node, selected by named-venue geography rather than a major meal-only detour.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('作为洞穴活动后的恢复餐叙,必须与进阶探洞分开管理;晚宴当天不再叠加高强度项目。', 'Use as recovery after a cave day, managed separately from advanced exploration; no additional demanding activity on dinner day.', language), href: '/safaris/outdoor-coffee/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('适合安排轻量咖啡、纪念日晚宴或摄影停留,与村寨和非遗节奏保持平衡。', 'Works as gentle coffee, an occasion dinner, or a photography pause while preserving village and heritage pacing.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('天然洞穴中的私家餐桌布置', +
+
+
+ + Unique Local Experiences / 01 +

{pickText('洞穴野奢体验・地下咖啡与私家晚宴', 'Cave Luxury Experience: Underground Coffee & Private Dining', language)}

+

{pickText('浪漫来自安静与尺度,安心来自具名场地、菜单、团队、照明和撤离。', 'Atmosphere comes from quiet and restraint; confidence comes from a named venue, menu, team, lighting, and exit.', language)}

+ {pickText('查看三种体验方式', 'See the three formats', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('不是在任意溶洞里摆一张桌子,而是在可接待场地里完成一场克制的餐叙。', 'Not a table placed in any cave—a restrained service inside a hospitality-ready venue.', language)}

+

{pickText('客户文档提出地下咖啡、烛光晚宴和轻量探洞。页面将三者拆开:餐饮只进入具备步道、照明、卫生和撤离条件的具名场地;探洞另行使用户外活动标准。所谓“18℃恒温”只在具体场地提供测量或官方资料后使用。', 'The brief proposes underground coffee, candlelit dinner, and a light cave walk. This page separates them: food service uses only a named venue with paths, lighting, hygiene, and exit; cave walking follows separate outdoor standards. The proposed “constant 18°C” is used only when supported by measurements or official venue evidence.', language)}

+
+
+
{pickText('地理依托', 'Geographic Base', language)}
{pickText('贵州具名、可接待的天然洞穴场地 / 按日期匹配', 'Named hospitality-ready natural cave in Guizhou / matched by date', language)}
+
{pickText('体验形式', 'Formats', language)}
{pickText('咖啡茶点 / 私家晚宴 / 轻量洞穴步行后落座', 'Coffee and tea / Private dinner / Light cave walk then dine', language)}
+
{pickText('建议时长', 'Suggested Time', language)}
{pickText('60–90 分钟至 2–3 小时,组合体验另计', '60–90 minutes to 2–3 hours; combined format quoted separately', language)}
+
{pickText('适合', 'Best For', language)}
{pickText('情侣纪念日、小型家庭、私家小团与摄影客人', 'Couples, small families, private groups, and photography-focused guests', language)}
+
{pickText('执行方式', 'Operation', language)}
{pickText('场地、消防、餐饮、人员、接驳、保险与撤离逐项确认', 'Venue, fire, food, team, transfers, insurance, and exit checked item by item', language)}
+
{pickText('室内备选', 'Fallback', language)}
{pickText('酒店私宴 / 山野餐厅 / 洞穴风格空间 / 下午茶', 'Hotel private room / mountain restaurant / cave-inspired space / afternoon tea', language)}
+
+
+ +
+
{pickText('带有步道与水体的洞穴接待空间',
+
{pickText('洞穴中的休憩与咖啡空间',
+
{pickText('洞穴中的私家餐桌',
+
+ +
+
+
01 / Service Formats

{pickText('先选择服务深度,再确认场地。', 'Choose the service depth before the venue.', language)}

{pickText('咖啡、晚宴与探洞后落座对餐饮、照明、团队、时长和退出的要求逐级增加。', 'Coffee, dinner, and a post-walk meal add progressively different food, lighting, staffing, timing, and exit requirements.', language)}

+
{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
+
+ +
+
{pickText('洞穴中的安静休憩空间',
+
+
02 / How Hosting Works

{pickText('一场晚宴,从客人离开洞穴时依然安心来倒推。', 'Design the dinner backwards from a calm, safe exit.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Hosting Gates

{pickText('好看只是结果,接待系统才是产品。', 'The atmosphere is the result; the hosting system is the product.', language)}

{pickText('“专属英文服务、精致餐食、氛围布置、往返接送”会在正式方案中拆成具名供应方、人员、菜单、设备和价格,而不是一句笼统承诺。', '“Dedicated English service, refined food, styling, and transfers” become named suppliers, people, menus, systems, and prices in the final proposal—not one generic promise.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具名洞穴场地、使用区域、真实开放时段与当日温湿度建议', 'Named cave venue, usable area, real hours, and date-specific temperature/humidity advice', language)}
  • {pickText('具名菜单、过敏原、餐饮团队、英文支持、灯光和往返接驳', 'Named menu, allergens, food team, English support, lighting, and return transfers', language)}
  • {pickText('明确消防、电力、卫生间、通信、保险、撤离和天气/水位备选', 'Clear fire, power, toilet, communication, insurance, exit, and weather/water fallback', language)}
+
{pickText('必须按场地复核', 'Claims requiring venue-specific proof', language)}
  • {pickText('所有贵州洞穴“18℃恒温”、绝对静谧或完全独享', 'A universal “constant 18°C,” absolute silence, or full exclusivity across Guizhou caves', language)}
  • {pickText('真实蜡烛/篝火、洞内烹饪、酒水服务与食品经营边界', 'Real candles or fire, in-cave cooking, alcohol service, and food-operation permissions', language)}
  • {pickText('专属英文人员、精致餐食、轻量探洞、往返接送与摄影布置默认包含', 'Dedicated English staff, refined food, light cave walk, transfers, or photography styling as default inclusions', language)}
+
+
+
+ +
+
04 / Route Pairing

{pickText('让餐叙成为旅程的停顿,而不是一次为了拍照的绕路。', 'Make the meal a pause in the journey—not a photography-only detour.', language)}

+ +
+ + +
+ ); +} + +function WildCoffeeDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('优先推荐', 'Preferred', language), + title: pickText('具名观景咖啡馆', 'Named Scenic Café', language), + copy: pickText( + '选择已有营业主体、固定步道、护栏、座席、卫生间与稳定出品的观景咖啡馆。正式方案写明店名、位置、营业时间、预约方式、停车/步行距离和雨天可用区域,不把照片中的某个场地默认套用到荔波或万峰林。', + 'Choose an established scenic café with a named operator, fixed paths, edge protection, seating, toilets, and consistent service. The final plan names the venue, location, hours, reservation, parking or walk distance, and wet-weather area—without assuming a pictured site is in Libo or Wanfenglin.', + language, + ), + status: pickText('风险最低,也最容易稳定安排咖啡、甜点、洗手间与车辆接回', 'The most reliable option for coffee, snacks, toilets, and vehicle recovery', language), + }, + { + number: '02', + label: pickText('顺路停靠', 'On-Route Pause', language), + title: pickText('山林、梯田或村寨咖啡', 'Forest, Terrace or Village Coffee', language), + copy: pickText( + '把一家山林木屋、梯田窗景或村寨咖啡馆放在实际行车路线中,用 45–90 分钟恢复体力。营业状态、咖啡供应、餐食、无障碍、室内座位和旺季客流按日期复核,不为了“网红视角”增加大幅绕行。', + 'Place a forest cabin, terrace-window, or village café on the actual drive for a 45–90 minute recovery pause. Recheck opening, coffee and food service, accessibility, indoor seating, and peak crowds by date—without a major detour for a social-media angle.', + language, + ), + status: pickText('最适合接在徒步、摄影、自驾或村寨体验之后', 'Best after hiking, photography, road travel, or a village experience', language), + }, + { + number: '03', + label: pickText('专项候选', 'Special Setup Candidate', language), + title: pickText('私家户外冲煮', 'Private Outdoor Brew', language), + copy: pickText( + '只有土地/景区/社区授权、稳定平台或安全地面、临崖隔离、天气、热水与火源规则、饮用水、食品卫生、垃圾与污水带离、通信和退出全部明确时才搭建。无许可的悬崖边、山顶或保护地不做私家布置。', + 'Set up only with land, scenic-area, or community authority; stable ground and edge separation; weather, hot-water and fire rules; potable water, food hygiene, waste and wastewater removal, communication, and exit. No private setup on an unauthorized cliff, summit, or protected site.', + language, + ), + status: pickText('日出/日落、独享和无人打扰只能按具体点位与日期申请,不能保证', 'Sunrise, sunset, exclusivity, and solitude are requested by site and date—not guaranteed', language), + }, + ]; + const flow = [ + { title: pickText('先确定这一杯咖啡在路线中的作用', 'Define the Pause', language), copy: pickText('确认是晨间出发、徒步后恢复、摄影停靠、日落时段还是纪念日布置,并确定可接受的绕行和步行时间。', 'Decide whether this is a morning start, post-hike recovery, photography pause, sunset window, or occasion setup, plus acceptable detour and walk time.', language) }, + { title: pickText('选择具名场地或授权点位', 'Choose a Named or Authorized Site', language), copy: pickText('先选公开咖啡馆;如需户外冲煮,则取得土地、景区或社区的明确授权,并确认入口、停车、步道、平台、护栏/边界和卫生间。', 'Prefer an established café; for outdoor brewing, obtain clear land, scenic-area, or community authority and confirm entrance, parking, path, platform, edge protection, and toilets.', language) }, + { title: pickText('复核天气、光线与临崖条件', 'Recheck Weather, Light & Edge', language), copy: pickText('按风、雨、雷电、雾、地面湿滑、落石和能见度决定执行。日出/日落还要确认营业时段、照明、返程路况和夜间步行。', 'Use wind, rain, lightning, fog, slippery ground, rockfall, and visibility for the go/no-go call. Sunrise or sunset also requires hours, lighting, return-road, and night-walk checks.', language) }, + { title: pickText('确认咖啡、热水与无痕服务', 'Confirm Coffee & Low-Impact Service', language), copy: pickText('写明咖啡豆/饮品、热水来源、用电或火源、饮用水、杯具、甜点、过敏原、洗手、垃圾分类和全部带离方式。', 'State beans and drinks, hot-water source, power or flame, potable water, cups, snacks, allergens, hand hygiene, waste sorting, and full pack-out.', language) }, + { title: pickText('停留、收整与车辆接回', 'Pause, Pack Down & Return', language), copy: pickText('控制客人靠近边缘的范围和停留时间;天气变化立即转入室内或取消。结束后恢复场地、清点垃圾与设备,由车辆在具名位置接回。', 'Control edge proximity and dwell time; move indoors or cancel when conditions change. Restore the site, count waste and equipment, and meet the vehicle at a named point.', language) }, + ]; + const gates = [ + { title: pickText('场地身份与授权', 'Venue Identity & Authority', language), copy: pickText('具名营业主体或土地/景区/社区许可、可使用区域、营业/进入时段、容量、预约、停车、步行入口与费用。', 'Named operator or land/scenic/community permission, usable area, hours, capacity, reservation, parking, walking access, and fee.', language) }, + { title: pickText('临崖、步道与天气', 'Edge, Path & Weather', language), copy: pickText('护栏或安全边界、平台承载、路面、落石、风雨雷电/雾、日出日落照明、通信和中途退出。', 'Edge barrier or safe boundary, platform capacity, surface, rockfall, wind/rain/lightning/fog, sunrise/sunset light, communication, and exit.', language) }, + { title: pickText('饮品、火源与卫生', 'Drinks, Fire & Hygiene', language), copy: pickText('饮用水、热水与电力/燃气规则、食品供应方、杯具/手部卫生、过敏原、垃圾和污水带离,以及保护地禁火要求。', 'Potable and hot water, power or gas rules, food supplier, cup and hand hygiene, allergens, waste and wastewater removal, and protected-area fire bans.', language) }, + { title: pickText('客人、接驳与应急', 'Guests, Transfers & Response', language), copy: pickText('行动能力、儿童看护、恐高、鞋服、司机与接回点、备用室内场地、急救、医疗转运和责任/保险边界。', 'Mobility, child supervision, height exposure, footwear and layers, driver and pickup point, indoor fallback, first aid, medical transfer, and responsibility or insurance boundary.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('在万峰林、黄果树或跨城路上加入一家顺路的具名咖啡馆,控制在 45–90 分钟。', 'Add one named on-route café near Wanfenglin, Huangguoshu, or an intercity drive, limited to 45–90 minutes.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('放在高活动量之后作为真正的恢复节点,优先选择有护栏、热食、卫生间和车辆直达的场地。', 'Use as real recovery after an active day, prioritizing edge protection, warm food, toilets, and vehicle access.', language), href: '/safaris/outdoor-coffee/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('与村寨、手作或摄影半日组合;私家布置只在不干扰社区与环境时加入。', 'Pair with a village, craft, or photography half-day; add a private setup only when it does not disrupt community or environment.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('喀斯特峡谷临崖观景咖啡馆', +
+
+
+ + Unique Local Experiences / 02 +

{pickText('悬崖与山野观景咖啡', 'Cliffside & Mountain-View Coffee', language)}

+

{pickText('先找到合法、安全、顺路的观景位置,再决定这一杯咖啡值得停多久。', 'Find a legal, safe, on-route viewpoint before deciding how long the coffee pause deserves.', language)}

+ {pickText('查看三种停靠方式', 'See the three formats', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('好风景不一定需要私搭,一家正确的咖啡馆往往更松弛。', 'A good view does not require a private setup—the right café is often more relaxed.', language)}

+

{pickText('客户文档提出荔波森林悬崖、万峰林山顶和峡谷观景台,并强调小众、无人拥挤和独享。客户图片实际展示了多种公开咖啡空间,但不足以证明所有图片的地理位置,也不能保证独享。因此第一版优先具名营业场地,私家搭建只作为经过授权的候选。', 'The brief proposes Libo forest cliffs, Wanfenglin summits, and canyon viewpoints, emphasizing privacy and no crowds. The supplied images show several established cafés but do not prove every location or guarantee exclusivity. This first version prioritizes named operating venues and treats private setup as an authority-dependent candidate.', language)}

+
+
+
{pickText('地理依托', 'Geographic Base', language)}
{pickText('贵州峰林 / 峡谷 / 梯田 / 山林沿线具名场地', 'Named venues along Guizhou peak forests, canyons, terraces, and forest routes', language)}
+
{pickText('体验形式', 'Formats', language)}
{pickText('观景咖啡馆 / 顺路咖啡停靠 / 私家户外冲煮候选', 'Scenic café / On-route coffee pause / Private outdoor brew candidate', language)}
+
{pickText('建议时长', 'Suggested Time', language)}
{pickText('45–90 分钟,日出/日落和户外布置另行确认', '45–90 minutes; sunrise, sunset, and private setup confirmed separately', language)}
+
{pickText('适合', 'Best For', language)}
{pickText('休闲度假、摄影、咖啡爱好者与户外后的恢复停靠', 'Leisure, photography, coffee lovers, and post-outdoor recovery', language)}
+
{pickText('执行方式', 'Operation', language)}
{pickText('场地、步道、临崖、天气、服务、火源、垃圾、接驳逐项确认', 'Venue, path, edge, weather, service, fire, waste, and transfer checked item by item', language)}
+
{pickText('备选', 'Fallback', language)}
{pickText('室内窗景咖啡馆 / 酒店下午茶 / 村寨茶歇', 'Indoor view café / hotel afternoon tea / village tea stop', language)}
+
+
+ +
+
{pickText('俯瞰梯田与河谷的室内咖啡座',
+
{pickText('带护栏的临崖咖啡平台',
+
{pickText('雨天峡谷观景平台',
+
{pickText('雨中的山林木屋咖啡馆',
+
+ +
+
+
01 / Coffee Formats

{pickText('先选择场地性质,再选择拍摄时段。', 'Choose the venue type before the photo window.', language)}

{pickText('营业咖啡馆、顺路停靠与私家户外冲煮对授权、临崖保护、服务和撤收有不同要求。', 'An operating café, on-route stop, and private outdoor brew require different authority, edge protection, service, and pack-down.', language)}

+
{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
+
+ +
+
{pickText('梯田河谷窗景与咖啡座',
+
+
02 / How the Stop Works

{pickText('真正的松弛感,来自顺路、可坐、能避雨,也能安全离开。', 'Real ease means on-route, seated, weather-ready, and easy to leave safely.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Operating Gates

{pickText('没有授权、边界不清或天气不对,就换一家室内咖啡馆。', 'No authority, unclear edge, or wrong weather means an indoor café instead.', language)}

{pickText('“小众私家点位、无游客拥挤、独享景观”会转换成具体点位、时段和预约状态,不作为统一承诺。', '“Hidden private site, no crowds, exclusive view” become site-, time-, and reservation-specific checks—not blanket promises.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具名咖啡馆/授权点位、实际营业或进入时间、预约与费用', 'Named café or authorized site, real hours or access time, reservation, and fee', language)}
  • {pickText('停车和步行距离、平台/护栏、座席、卫生间、饮品和雨天区域', 'Parking and walk length, platform or barrier, seating, toilets, drinks, and wet-weather area', language)}
  • {pickText('日出/日落时段、司机接回、天气备选与户外布置/撤收清单', 'Sunrise or sunset timing, driver recovery, weather fallback, and outdoor setup/pack-down list', language)}
+
{pickText('必须按点位复核', 'Claims requiring site-specific proof', language)}
  • {pickText('图片中的咖啡馆是否位于荔波、万峰林或客户所述目的地', 'Whether the pictured cafés are in Libo, Wanfenglin, or another named destination', language)}
  • {pickText('“小众、无人拥挤、独享”、日出/日落营业和专属摄影位', '“Hidden, uncrowded, exclusive,” sunrise/sunset operation, or private photo position', language)}
  • {pickText('临崖座椅/悬挂设施、户外用火、食物服务、土地许可与保险覆盖', 'Cliff seating or suspended structures, outdoor fire, food service, land permission, and insurance', language)}
+
+
+
+ +
+
04 / Route Pairing

{pickText('把咖啡放在真正需要停下来的地方。', 'Place the coffee where the journey actually needs a pause.', language)}

+ +
+ + +
+ ); +} + +function VillageImmersionDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('苗族方向', 'Miao Direction', language), + title: pickText('苗族吊脚楼住家候选', 'Miao Stilt-House Homestay Candidate', language), + copy: pickText( + '以雷山掌坳或其他具名苗寨为地理候选。半日可做公共村路与木楼建筑观察,再从家常饭、手作或当季农事中选一项;一至两晚只有在经营主体或留宿人、具体房间、卫浴、消防、夜间出入和住宿登记全部确认后才进入方案。', + 'Use Zhangao in Leishan or another named Miao village as a geographic candidate. A half-day can pair a public village and timber-house walk with one hosted meal, craft, or seasonal task. One or two nights enter the plan only after the operator or host, exact room, bathroom, fire conditions, night access, and accommodation registration are confirmed.', + language, + ), + status: pickText('农事、火塘故事与家庭空间按主人、季节和同意确认,不作每日承诺', 'Farm work, fireside stories, and family spaces depend on the host, season, and consent—not a daily promise', language), + }, + { + number: '02', + label: pickText('侗族方向', 'Dong Direction', language), + title: pickText('侗族鼓楼社区生活候选', 'Dong Drum-Tower Community Candidate', language), + copy: pickText( + '以黎平黄岗或其他具名侗寨为地理候选,围绕鼓楼、风雨桥、村路与稻田安排公共慢行,再按真实档期加入一顿主人餐、一项手作或一次歌唱交流。鼓楼首先是社区公共空间,不为客人临时布置“专场生活”。', + 'Use Huanggang in Liping or another named Dong village as a geographic candidate. Build a public walk around the drum tower, wind-and-rain bridge, lanes, and fields, then add one hosted meal, craft, or song exchange only when genuinely available. The drum tower remains a community space first—not a private set built for visitors.', + language, + ), + status: pickText('侗族大歌、夜间对歌与节庆不是每天固定发生,必须按社区与日期确认', 'Grand Song, evening singing, and festivals are not daily fixtures; confirm the community and date', language), + }, + { + number: '03', + label: pickText('布依族方向', 'Buyi Direction', language), + title: pickText('布依石头寨慢居候选', 'Buyi Stone-Village Slow-Stay Candidate', language), + copy: pickText( + '以安顺石头寨、高荡或其他具名布依村寨为地理候选,安排石构建筑慢行、村寨餐食与一项具名手作或安静夜晚。不能把整座村寨、每间客房或所有新旧建筑都写成“全石古寨”,住宿仍须落实到合法经营主体和真实房间。', + 'Use Shitouzhai, Gaodang, or another named Buyi village around Anshun as a geographic candidate. Pair a stone-architecture walk with a village meal and one named craft or a quiet evening. Do not describe every building or room as an all-stone ancient village; the stay still requires a lawful operator and a real room.', + language, + ), + status: pickText('现有客户图片只作山谷与村落氛围参考,不证明具体布依村寨或住宿', 'Current supplied imagery is atmosphere only—not proof of a particular Buyi village or stay', language), + }, + ]; + const flow = [ + { title: pickText('选择一条文化方向', 'Choose One Cultural Direction', language), copy: pickText('根据完整线路与客人兴趣,在苗族吊脚楼、侗族鼓楼社区或布依石头寨中选择一处,不把三座村寨压成同一天的民族打卡。', 'Choose one place—Miao stilt-house life, a Dong drum-tower community, or a Buyi stone village—based on the full route and guest interest, rather than compressing three villages into one ethnic checklist.', language) }, + { title: pickText('匹配具名主体与房间', 'Match a Named Host & Room', language), copy: pickText('确认村寨、经营主体或留宿人、真实客房、卫浴、消防、行李、夜间支持、英文协助与境外客人住宿登记路径;没有这些资料时只做半日。', 'Confirm the village, operator or host, real room, bathroom, fire conditions, luggage, night support, language help, and registration path for international guests. Without this evidence, keep it a half-day.', language) }, + { title: pickText('确定半日、一晚或两晚', 'Set Half-Day, One Night, or Two', language), copy: pickText('从公共慢行、家常饭、具名手作和当季农事中选择一至两项,提前约定可进入空间、可拍人物、翻译、报酬与取消条件。', 'Choose one or two elements from a public walk, home-style meal, named craft, or seasonal work, then agree access, photography, interpretation, compensation, and cancellation.', language) }, + { title: pickText('出发前交付核验资料', 'Share the Evidence Before Arrival', language), copy: pickText('住宿方案提供当期房间与卫浴照片、楼梯和出口、消防与夜间联系人、菜单/过敏原、接驳、登记方式和拍摄边界,不只发一张村寨风景图。', 'For an overnight plan, share current room and bathroom images, stairs and exits, fire arrangements and night contact, menu or allergens, transfer, registration, and photo boundaries—not only a scenic village image.', language) }, + { title: pickText('现场再确认并负责离开', 'Reconfirm On Site & Leave Responsibly', language), copy: pickText('抵达后再次说明礼仪和儿童安全,征得同意后再进入、拍摄或参与;按约定结算,不公开未获同意的人像与住址,并完成住宿登记与离店。', 'Brief etiquette and child safety again on arrival, ask before entering, photographing, or joining, settle as agreed, publish no unapproved portraits or addresses, and complete registration and departure.', language) }, + ]; + const gates = [ + { title: pickText('主人、社区与真实档期', 'Host, Community & Real Availability', language), copy: pickText('具名联系人、社区同意、可接待人数、实际活动、语言协助、服务费用与取消方式;节庆和日常不为镜头临时摆拍。', 'Named contact, community consent, capacity, actual activity, language help, service fee, and cancellation; festivals and daily life are not staged for the camera.', language) }, + { title: pickText('经营、卫生、消防与登记', 'Operation, Hygiene, Fire & Registration', language), copy: pickText('经营主体或留宿人、具体房间与卫浴、公共场所卫生许可、房屋与消防、夜间值守、旅客实名登记;境外客人住非旅馆住所时,按当地流程在入住后 24 小时内完成登记。', 'Operator or host, exact room and bathroom, public-place hygiene permit, building and fire conditions, night support, and real-name guest records. International guests in non-hotel accommodation must complete registration through the local process within 24 hours of arrival.', language) }, + { title: pickText('饮食、活动、楼梯与儿童', 'Food, Activities, Stairs & Children', language), copy: pickText('食材与饮水、厨房卫生、过敏原、辣度、酒精,以及木楼楼梯、火源、农具、水田、牲畜、夜路、儿童看护和医疗转运。', 'Ingredients and water, kitchen hygiene, allergens, spice, alcohol, plus timber-house stairs, heat, tools, fields, animals, night paths, child supervision, and medical transfer.', language) }, + { title: pickText('隐私、拍摄与收益', 'Privacy, Photos & Benefit', language), copy: pickText('住宅与私人区域、人物肖像同意、未成年人拍摄、素材用途、翻译准确性,以及主人、手艺人与社区实际获得的费用路径。', 'Home and private areas, portrait consent, images of minors, intended media use, interpretation accuracy, and the real payment path to hosts, artisans, and community.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('只加入一处顺路村寨:安顺段可选布依石头寨,黔东南段可选苗寨或侗寨;半日和住宿按真实条件决定。', 'Add only one village that fits the route: a Buyi stone village around Anshun, or a Miao or Dong village in Qiandongnan, with half-day or overnight depth based on real conditions.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('家庭人文慢行', 'Family Heritage Pacing', language), copy: pickText('优先半日或正规民宿一晚,提前核对木楼楼梯、火源、夜路、餐食、儿童拍摄与就医距离。', 'Prefer a half-day or one night in a registered guesthouse, checking stairs, heat, night paths, food, child photography, and medical distance first.', language), href: '/safaris/classic-guizhou/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('在一座具名村寨安排经核验的一至两晚,不用“苗侗布依三寨连住”制造表面丰富。', 'Use one or two verified nights in one named village rather than a superficial three-village Miao–Dong–Buyi sampler.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('木楼屋顶与鼓楼构成的侗寨村落景观', +
+
+
+ + Unique Local Experiences / 03 +

{pickText('原生村寨沉浸', 'Authentic Village Homestays', language)}

+

{pickText('从苗族吊脚楼、侗族鼓楼社区或布依石头寨中选择一处,以半日至 3 天 2 晚认真进入,而不是白天观光、夜间回城。', 'Choose one Miao stilt-house village, Dong drum-tower community, or Buyi stone village for a half-day to three days and two nights—beyond a daytime stop and return to the city.', language)}

+ {pickText('查看三条村寨方向', 'See the three village directions', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('先选对一座村寨,再决定住多久、和谁一起生活。', 'Choose the right village first—then decide how long to stay and with whom.', language)}

+

{pickText('客户文档明确提出苗族吊脚楼住家、侗族鼓楼社区生活与布依石头寨慢居,并希望提供半日至 3 天 2 晚的深度体验。本页保留这三条方向,但不把农事、家宴、火塘故事、侗族大歌或手作写成每天都有的套餐;最终内容由具名村寨、主人、季节、住宿条件与客人边界共同决定。', 'The brief clearly proposes Miao stilt-house homestays, Dong drum-tower community life, and slow stays in Buyi stone villages, from a half-day to three days and two nights. This page keeps all three directions without turning farm work, home meals, fireside stories, Grand Song, or craft into daily package items. The named village, host, season, room, and guest boundaries determine the final experience.', language)}

+
+
+
{pickText('三条方向', 'Three Directions', language)}
{pickText('苗族吊脚楼 / 侗族鼓楼社区 / 布依石头寨', 'Miao stilt houses / Dong drum-tower community / Buyi stone village', language)}
+
{pickText('地理候选', 'Geographic Candidates', language)}
{pickText('掌坳 / 黄岗 / 安顺石头寨或高荡;最终以接待主体为准', 'Zhangao / Huanggang / Shitouzhai or Gaodang; final choice follows the actual host', language)}
+
{pickText('建议时长', 'Suggested Time', language)}
{pickText('半日 / 经核验的一晚 / 最长 3 天 2 晚', 'Half-day / verified one night / up to 3 days and 2 nights', language)}
+
{pickText('适合', 'Best For', language)}
{pickText('文化爱好者、亲子、慢旅行与愿意尊重社区边界的客人', 'Culture lovers, families, slow travellers, and guests who respect community boundaries', language)}
+
{pickText('住宿前提', 'Stay Requirement', language)}
{pickText('具名房间 / 卫生与消防 / 实名及境外客人登记路径', 'Named room / hygiene and fire / real-name and international guest registration', language)}
+
{pickText('活动选择', 'Activity Choice', language)}
{pickText('按主人、社区、季节和日期选择一至两项,不保证全部出现', 'One or two items matched to host, community, season, and date—never all guaranteed', language)}
+
+
+ +
+
{pickText('鼓楼与木楼屋顶',
{pickText('侗寨建筑氛围参考 · 具体村寨、公共空间开放与住宿主体待确认', 'Dong village atmosphere · Exact village, public access, and stay operator to be confirmed', language)}
+
{pickText('山林中的木楼村寨',
{pickText('木楼村寨氛围参考 · 非苗族吊脚楼客房或可住宿证明', 'Timber-village atmosphere · Not proof of a Miao guest room or bookable stay', language)}
+
{pickText('木楼连廊中的村寨日常',
{pickText('客户提供人物实拍 · 身份、地点、拍摄及公开使用授权需归档', 'Client-supplied portrait · Identity, location, capture, and publication consent require records', language)}
+
{pickText('村寨周边的山谷与田野',
{pickText('山谷与村落氛围参考 · 非布依石头寨或具名住宿证明', 'Valley and village atmosphere · Not proof of a Buyi stone village or named stay', language)}
+
+ +
+
+
01 / Village Directions

{pickText('三条方向只选一处,半日到两晚逐级核验。', 'Choose one of three directions, then verify the depth from half-day to two nights.', language)}

{pickText('文化方向可以先确定,但住宿只有在具名主体、真实房间、卫生消防和登记路径都成立后才能出售。', 'The cultural direction can be chosen first, but an overnight stay is sellable only after the named operator, real room, hygiene, fire, and registration path all pass.', language)}

+
{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
+
+ +
+
{pickText('村寨木楼与居民生活空间',
+
+
02 / Hosted Flow

{pickText('主人不是演职人员,客人也不是旁观的人群。', 'The host is not a performer, and the guest is not a crowd.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Hosting Gates

{pickText('不能说明谁接待、住哪里、钱怎么分,就不叫深度体验。', 'If we cannot name the host, room, and payment path, it is not an immersion.', language)}

{pickText('“原生态、完整民俗、传承人一对一、全部收益直达村民”都要转换成具名人物、实际活动、书面同意和结算记录。', '“Authentic, complete traditions, one-to-one heritage bearer, all benefit direct to villagers” must become named people, real activities, consent, and settlement records.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具名村寨、主人/手艺人、实际时长、活动、费用与取消条件', 'Named village, host or artisan, real duration, activity, fee, and cancellation', language)}
  • {pickText('可进入和可拍摄范围、翻译、餐食菜单/过敏原及儿童看护安排', 'Agreed access and photo scope, interpretation, menu or allergens, and child supervision', language)}
  • {pickText('具名住宿的房间、卫浴、消防、接待/登记流程、接驳和天气备选', 'Named room, bathroom, fire conditions, reception or registration, transfer, and weather fallback', language)}
+
{pickText('必须按主人和日期复核', 'Claims requiring host-and-date proof', language)}
  • {pickText('“原生态、未商业化、完整保留民俗”以及图片的具体村寨与人物身份', '“Untouched, uncommercialised, complete traditions,” and the pictured village or identities', language)}
  • {pickText('非遗传承人一对一、鼓楼对歌、节庆、农事、抓鱼等当天一定发生', 'One-to-one heritage teaching, drum-tower songs, festivals, farming, or fish-catching on demand', language)}
  • {pickText('住家/禾仓合法接待境外客人、全部收益直达村民或任何固定分成比例', 'A home or granary stay’s international reception, all benefit direct to villagers, or a fixed revenue split', language)}
+
+

{pickText('公开文化与地理信息参考:贵州省民政厅黄岗村介绍、贵州省人大掌坳苗寨介绍、国家民委及贵州公开的布依石构建筑资料。住宿执行参考贵州省民宿指导意见、现行民宿卫生与消防要求及国家移民管理局住宿登记规则;贵州当前不在 2026 年首批旅馆以外住所网上登记试点范围内,具体登记方式需向当地公安机关确认。', 'Public cultural and location references include Guizhou Civil Affairs on Huanggang, the Guizhou People’s Congress on Zhangao, and official sources on Buyi stone architecture. Stay operation follows Guizhou guesthouse guidance, current hygiene and fire requirements, and National Immigration Administration registration rules. Guizhou is not in the first 2026 online pilot for non-hotel registration, so the exact local process must be confirmed with the public security authority.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('给村寨留出真实时间,也给社区保留自己的节奏。', 'Give the village real time—and let the community keep its own rhythm.', language)}

+ +
+ + +
+ ); +} + +function EthnicFestivalsDetailPage() { + const { language } = useTravelSite(); + const festivals = [ + { + number: '01', + label: pickText('农历十月', 'Lunar October', language), + title: pickText('苗年', 'Miao New Year', language), + copy: pickText( + '苗年是黔东南苗族重要年节,不同村寨的过年日期、节期和公开活动并不完全相同。2026 年黔东南州公布的苗年假日为 11 月 15 日;盛装巡游、芦笙、打糍粑、斗牛或家庭年饭是否举行,仍须按具体县、村寨和当年活动表确认。', + 'Miao New Year is a major celebration in Qiandongnan, but dates, duration, and public programmes differ by community. Qiandongnan’s official 2026 Miao New Year holiday is 15 November; costume parades, lusheng, ciba making, bullfighting, or family meals still require the exact county, village, and current programme.', + language, + ), + status: pickText('客户文档所写“持续 3–15 天”不能套用于每个村寨或每位游客', 'The brief’s “3–15 days” cannot be applied to every village or visitor', language), + }, + { + number: '02', + label: pickText('农历三月十五至十七', 'Lunar 3rd month, days 15–17', language), + title: pickText('苗族姊妹节', 'Miao Sisters’ Meal Festival', language), + copy: pickText( + '传统节期以台江施洞、老屯一带农历三月十五至十七为核心。2026 年台江县公共系列活动于 4 月 30 日至 5 月 10 日举行,现已结束;下一年度的县城活动、施洞活动、交通管制与观演安排必须等待新通知。', + 'The traditional window centres on lunar days 15–17 of the third month around Shidong and Laotun in Taijiang. Taijiang’s 2026 public programme ran from 30 April to 10 May and has ended; next year’s county and Shidong events, traffic controls, and viewing arrangements await a new notice.', + language, + ), + status: pickText('五色姊妹饭、盛装与对歌可作为文化线索,不承诺进入婚恋或家庭仪式', 'Coloured rice, dress, and antiphonal song are cultural anchors—not access to courtship or family rites', language), + }, + { + number: '03', + label: pickText('两个独立节庆', 'Two Separate Festivals', language), + title: pickText('侗族大歌节・黄岗喊天节', 'Dong Grand Song & Huanggang Heaven-Calling', language), + copy: pickText( + '黄岗喊天节传统日期为农历六月十五,祭祀、侗族大歌与长桌宴的公开范围由村寨当年安排决定。从江侗族大歌节过去曾在 11 月 28 日前后举办,但公开资料不足以证明这是每年固定公历日期,因此两项活动不能合并成一个全年可售产品。', + 'Huanggang’s Heaven-Calling Festival traditionally falls on lunar 6/15, while public access to rites, Grand Song, and banquets follows the village’s current plan. Congjiang Grand Song festivals have previously taken place around 28 November, but available evidence does not establish that as a fixed annual Gregorian date; the two events are not one year-round product.', + language, + ), + status: pickText('侗族大歌不是到场即唱;具名歌队、场次、费用和拍摄须单独确认', 'Grand Song is not performed on demand; confirm the named ensemble, session, fee, and photography', language), + }, + { + number: '04', + label: pickText('水历分批过节', 'Staggered Shui Calendar', language), + title: pickText('水族端节', 'Shui Duan Festival', language), + copy: pickText( + '端节按水历和不同氏族、村寨的亥日分批举行,大致落在公历秋季。公开资料中的“49 天”指各批次从头至尾的整体跨度,不是同一地点连续 49 天的游客活动;赛马、铜鼓、祭祖与家庭宴席也不在每一批次都向游客开放。', + 'Duan follows the Shui calendar and staggered Pig Days for different clans and villages, broadly in autumn. The published “49 days” describes the span across all batches—not a continuous 49-day visitor event in one place. Horse racing, bronze drums, ancestor rites, and family meals are not all public in every batch.', + language, + ), + status: pickText('必须先锁定县、村寨、氏族批次和公开活动,再安排交通与住宿', 'Confirm the county, village, clan batch, and public programme before transport and accommodation', language), + }, + ]; + const flow = [ + { title: pickText('先确定年份与节庆', 'Start with the Year & Festival', language), copy: pickText('客户先提供旅行年份、日期范围和最感兴趣的节庆;不使用多年以前的海报或历史日期替代当年通知。', 'Begin with the travel year, date range, and festival of interest; an old poster or historic date never substitutes for a current notice.', language) }, + { title: pickText('拆开传统日期与公共活动', 'Separate Tradition from Public Programme', language), copy: pickText('核对农历/水历传统日期、当年官方公共活动、村寨内部仪式三层信息,并标出游客能进入、只能远观和不可拍摄的部分。', 'Check three layers—the traditional lunar or Shui-calendar date, the current public programme, and internal village rites—then mark what visitors may enter, observe only, or not photograph.', language) }, + { title: pickText('锁定交通、住宿与缓冲', 'Lock Transport, Stay & Buffer', language), copy: pickText('节庆期间可能拥堵、封路、停车受限或住宿紧张;正式方案写明接驳、步行、集合点、房价、取消条件和至少一个时间缓冲。', 'Festivals may bring congestion, road controls, limited parking, and tight rooms. State transfers, walking, meeting point, room rate, cancellation, and at least one time buffer.', language) }, + { title: pickText('交付礼仪与拍摄说明', 'Brief Etiquette & Photography', language), copy: pickText('说明服饰、敬酒、对歌、祭祀、未成年人、无人机和商业拍摄边界;人物近景与家庭空间现场再次征得同意。', 'Brief dress, toasting, singing, ritual, minors, drones, and commercial-image boundaries; reconfirm close portraits and home spaces on site.', language) }, + { title: pickText('当天按现场通知执行', 'Follow the Live Programme', language), copy: pickText('天气、社区决定、安保和人流拥有最终调整权;节目变化时优先保证尊重与安全,不用临时摆拍补足宣传清单。', 'Weather, community decisions, security, and crowd conditions retain the final say. If the programme changes, protect respect and safety rather than staging replacement content.', language) }, + ]; + const gates = [ + { title: pickText('当年日期与主办信息', 'Current Date & Organiser', language), copy: pickText('官方或村寨当年通知、主办/联系人、地点、公开时段、门票/预约和变更渠道齐全后,才写入正式行程。', 'Only publish after the current official or village notice names the organiser, contact, venue, public hours, tickets or reservations, and update channel.', language) }, + { title: pickText('公开活动与仪式边界', 'Public Events & Ritual Boundaries', language), copy: pickText('区分巡游、舞台展演、公共对歌、宴席与祭祀/家庭环节;公开节庆不等于所有空间和人物均可进入拍摄。', 'Separate parades, staged shows, public singing, banquets, rites, and family moments; a public festival does not make every space or person open to cameras.', language) }, + { title: pickText('人流、道路与现场安全', 'Crowds, Roads & Field Safety', language), copy: pickText('交通管制、停车/接驳、拥挤踩踏、夜间照明、雨天湿滑、烟火/斗牛/赛马等区域隔离及儿童集合方案。', 'Traffic controls, parking or shuttle, crowd pressure, night lighting, wet ground, separation from fireworks, bullfighting, or horse racing, and a child regrouping plan.', language) }, + { title: pickText('住宿、费用与取消', 'Stay, Cost & Cancellation', language), copy: pickText('具名酒店/民宿、当前房价与涉外接待、最少入住、预付退款、节目取消后的替代和不退费用逐项写清。', 'State the named hotel or guesthouse, live rate and international reception, minimum stay, deposit refund, event-cancellation alternative, and non-refundable costs.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('黔东南苗族节庆线', 'Qiandongnan Miao Festival Route', language), copy: pickText('围绕苗年或姊妹节只选一项,在凯里/台江/雷山之间保留前后缓冲,不把两个不同季节拼在同一线路。', 'Build around either Miao New Year or Sisters’ Meal, with buffer around Kaili, Taijiang, or Leishan—never combining two different seasons into one route.', language), href: '/safaris/culture-outdoor-mix/' }, + { number: '02', title: pickText('侗寨节庆与慢住', 'Dong Festival & Village Stay', language), copy: pickText('以黄岗喊天节或当年从江侗族大歌活动为锚点,先核验公共场次,再安排一座具名侗寨住宿。', 'Anchor the route to Huanggang Heaven-Calling or a current Congjiang Grand Song event, verify the public programme, then add one named Dong village stay.', language), href: '/safaris/culture-outdoor-mix/' }, + { number: '03', title: pickText('三都水族端节窗口', 'Sandu Shui Duan Window', language), copy: pickText('先确定当年村寨批次,再从贵阳/都匀衔接三都;赛马或家庭宴席没有书面确认时不写入核心亮点。', 'Confirm the current village batch before connecting Sandu from Guiyang or Duyun; horse racing or a family feast is not a headline without written confirmation.', language), href: '/safaris/classic-guizhou/' }, + ]; + + return ( +
+
+ {pickText('雷山苗族节庆队伍与芦笙', +
+
+
+ + Living Heritage / Seasonal +

{pickText('节庆民俗盛典', 'Ethnic Festivals & Customs', language)}

+

{pickText('真正的节庆不是全年循环播放的节目。先确认当年日期、村寨和公开范围,再决定我们能看见什么、能参与什么。', 'A living festival is not year-round programming. Confirm the current date, village, and public boundary before deciding what may be witnessed or joined.', language)}

+ {pickText('查看四项节庆窗口', 'Explore four festival windows', language)} +
+
+
+ + + +
+
+ {pickText('节庆定义', 'Festival Definition', language)} +

{pickText('把节庆放进行程,先要接受它不为旅行者改变时间。', 'To place a festival in a journey, accept that it does not change its time for the traveller.', language)}

+

{pickText('客户文档提出苗年、苗族姊妹节、侗族大歌节与黄岗喊天节、水族端节。公开核验显示:传统日期、政府组织的公共活动和村寨内部仪式不是同一层信息;“11 月 28 日固定举办”“连续 49 天游客活动”等表述也不能直接发布。因此本页以当年确认窗口呈现,而不是常年可下单的节目单。', 'The brief proposes Miao New Year, Sisters’ Meal, Dong Grand Song and Huanggang Heaven-Calling, and Shui Duan. Public verification shows that traditional dates, government-led public programmes, and internal village rites are different layers. Claims such as a fixed 28 November event or a continuous 49-day visitor programme cannot be published as-is, so this page presents annual planning windows rather than year-round inventory.', language)}

+
+
+
{pickText('规划规则', 'Planning Rule', language)}
{pickText('每年重新核对官方与社区通知', 'Recheck official and community notices every year', language)}
+
{pickText('2026 苗年', '2026 Miao New Year', language)}
{pickText('州级假日 11 月 15 日;具体村寨节目待逐项确认', 'Prefecture holiday 15 Nov; village programmes verified separately', language)}
+
{pickText('2026 姊妹节', '2026 Sisters’ Meal', language)}
{pickText('公共系列活动 4 月 30 日至 5 月 10 日,现已结束', 'Public series ran 30 Apr–10 May and has ended', language)}
+
{pickText('侗族节庆', 'Dong Festivals', language)}
{pickText('喊天节与侗族大歌节分开核验,不共享固定日期', 'Heaven-Calling and Grand Song verified separately', language)}
+
{pickText('水族端节', 'Shui Duan', language)}
{pickText('按氏族与村寨分批,先确认具体亥日和公开活动', 'Staggered by clan and village; confirm the Pig Day and public programme', language)}
+
{pickText('默认深度', 'Default Depth', language)}
{pickText('公共活动半日至一日;家庭与仪式不默认进入', 'Half- to full-day public events; no default access to homes or rites', language)}
+
+
+ +
+
{pickText('大型苗族盛装聚会',
{pickText('客户提供苗族节庆氛围图 · 具体活动、年份、地点与公开使用授权待归档', 'Client-supplied Miao festival atmosphere · Event, year, location, and publication rights require records', language)}
+
{pickText('雷山节庆芦笙队伍',
{pickText('画面可见“中国・雷山”标识 · 不据此认定为某一届苗年或姊妹节', 'The image shows “Leishan, China” · Not proof of a particular Miao New Year or Sisters’ Meal edition', language)}
+
{pickText('苗族银饰盛装队伍',
{pickText('苗族盛装氛围参考 · 人物身份、拍摄同意与活动名称待确认', 'Miao ceremonial-dress reference · Identity, consent, and event name to be confirmed', language)}
+
{pickText('身着蓝色服饰的苗族节庆队伍',
{pickText('客户提供人物实拍 · 不用于证明侗族喊天节或水族端节', 'Client-supplied portrait · Not evidence of Dong Heaven-Calling or Shui Duan', language)}
+
+ +
+
+
01 / Annual Festival Windows

{pickText('四项节庆,四套时间逻辑。', 'Four festivals, four different calendars.', language)}

{pickText('页面保留客户提出的文化内容,但会主动标出已经结束、等待新通知、分批轮办或不能进入的部分。', 'The page retains the cultural intent of the brief while marking what has ended, awaits a new notice, rotates by batch, or remains closed to visitors.', language)}

+
{festivals.map((festival) =>
{festival.number}{festival.label}

{festival.title}

{festival.copy}

{festival.status}
)}
+
+
+ +
+
{pickText('苗族盛装人群与银饰细节',
+
+
02 / Festival Planning

{pickText('先找到当年的真实入口,再安排客人的位置。', 'Find this year’s real point of entry before placing the guest.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Festival Gates

{pickText('日期对,不等于所有内容都能看见。', 'The right date does not guarantee access to every moment.', language)}

{pickText('正式方案必须把“传统上会发生”“当年公共活动已经公布”和“旅行者获准进入”分开写。', 'The final plan separates what traditionally occurs, what the current public programme announces, and what the traveller is actually allowed to enter.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('当年官方或村寨确认的日期、地点、公开活动与开放时段', 'Current confirmed date, venue, public programme, and hours', language)}
  • {pickText('具名交通、集合点、酒店/民宿、房价、预约、门票和取消条件', 'Named transport, meeting point, stay, rate, reservation, ticket, and cancellation', language)}
  • {pickText('可进入区域、拍摄边界、英文说明、拥挤与天气备选', 'Access areas, photo boundaries, English briefing, crowd and weather fallback', language)}
+
{pickText('必须按当年重新核验', 'Claims requiring annual reconfirmation', language)}
  • {pickText('客户文档中的公历月份、持续天数和 11 月 28 日固定举办', 'Gregorian windows, durations, or a fixed 28 November from the brief', language)}
  • {pickText('斗牛、赛马、宰猪、祭祖、百家宴、夜间对歌或歌队一定出现', 'Bullfighting, horse racing, slaughter, ancestor rites, banquet, evening song, or ensemble guaranteed', language)}
  • {pickText('家庭邀请、近距离拍摄、无人机、商业摄影及内部仪式进入权', 'Home invitations, close portraits, drones, commercial shoots, or access to internal rites', language)}
+
+

{pickText('公开信息参考:黔东南州 2026 年节假日通知、台江县政府姊妹节介绍及 2026 年公共活动报道、贵州省政协黄岗喊天节资料、文化和旅游部历届从江侗族大歌节报道,以及贵州公开的水族端节资料。节庆页面每年更新,最终以当年政府、主办方与村寨通知为准。', 'Public references include Qiandongnan’s 2026 holiday notice, Taijiang government material and reporting on the 2026 Sisters’ Meal programme, Guizhou CPPCC material on Huanggang Heaven-Calling, Ministry of Culture and Tourism reports on past Congjiang Grand Song festivals, and official Guizhou sources on Shui Duan. This page is updated annually; current government, organiser, and village notices prevail.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('围绕一个节庆留出时间,不把节庆塞进景点清单。', 'Leave time around one festival instead of squeezing it into a sightseeing list.', language)}

+ +
+ + +
+ ); +} + +function CulinaryCulturalJourneysDetailPage() { + const { language } = useTravelSite(); + const streetFoods = [ + { + number: '01', + title: pickText('花溪牛肉粉', 'Huaxi Beef Rice Noodles', language), + place: pickText('贵阳・花溪代表风味', 'Guiyang · Huaxi signature', language), + copy: pickText('牛骨汤、米粉、牛肉与酸萝卜组成的贵阳代表性早餐。正式路线选择具名门店,并提前说明汤底、辣椒、牛肉部位、香菜/葱花与清真需求;“牛骨慢熬”只按具体门店核实。', 'A Guiyang breakfast built around beef broth, rice noodles, sliced beef, and pickled radish. The final walk names the shop and clarifies broth, chilli, cut of beef, herbs, and halal needs. “Slow-simmered bone broth” is verified with the actual kitchen rather than assumed.', language), + status: pickText('适合早餐或早午餐;不和夜市营业时间混写', 'Best at breakfast or brunch; not treated as a night-market item by default', language), + }, + { + number: '02', + title: pickText('贵阳肠旺面', 'Guiyang Changwang Noodles', language), + place: pickText('贵阳・城市早餐', 'Guiyang · city breakfast', language), + copy: pickText('以鸡蛋面、猪大肠、血旺和脆哨为核心的贵阳小吃,红油和动物内脏风味鲜明。英文说明必须直译主要食材,让客人在品尝前作出知情选择,并询问猪肉、内脏、血制品和辣度接受度。', 'A Guiyang noodle bowl centred on egg noodles, pork intestine, blood curd, crisp pork, and chilli oil. English interpretation names the ingredients plainly so guests can make an informed choice, including comfort with pork, offal, blood products, and heat.', language), + status: pickText('不使用模糊的“特色肉面”替代真实食材说明', 'Never hide the ingredients behind a vague “special meat noodle” label', language), + }, + { + number: '03', + title: pickText('贵阳丝娃娃', 'Guiyang Siwawa', language), + place: pickText('贵阳・蔬菜薄饼小吃', 'Guiyang · vegetable wrap', language), + copy: pickText('用薄面皮包裹多种切丝蔬菜,再加入酸辣蘸水。蔬菜数量和配料随门店变化,不固定承诺“十种以上”;花生、芝麻、酱油、小麦、折耳根及辣椒作为重点询问项。', 'Thin wrappers hold an assortment of shredded vegetables with a sour-spicy dipping sauce. The number and mix vary by shop rather than a guaranteed “ten-plus.” Ask specifically about peanut, sesame, soy, wheat, fish mint, and chilli.', language), + status: pickText('看似清淡不等于无过敏原,也不自动等于全素', 'Light-looking does not mean allergen-free or automatically vegan', language), + }, + { + number: '04', + title: pickText('青岩豆腐与玫瑰冰粉', 'Qingyan Tofu & Rose Ice Jelly', language), + place: pickText('青岩咸食 + 贵阳甜品组合', 'Qingyan savoury + Guiyang dessert pairing', language), + copy: pickText('客户希望用炸/炒豆腐与玫瑰冰粉形成咸甜收尾。公开名录分别支持青岩风味炒豆腐和贵阳玫瑰冰粉,但未证明两者是固定传统套餐;页面只把它们作为青岩或贵阳线路中可分开核验的组合。', 'The brief pairs fried or wok-cooked tofu with rose ice jelly as a savoury-sweet finish. Public lists support Qingyan-style tofu and Guiyang rose ice jelly separately, not as one fixed traditional set. They remain individually verified stops within a Qingyan or Guiyang route.', language), + status: pickText('豆制品、油炸交叉接触、冰品用水与糖量分别确认', 'Confirm soy, fryer cross-contact, ice-water handling, and sugar separately', language), + }, + { + number: '05', + title: pickText('水城烙锅', 'Shuicheng Luoguo Griddle', language), + place: pickText('六盘水・水城核心风味', 'Liupanshui · Shuicheng signature', language), + copy: pickText('食材在桌面铁锅上现烙,并搭配糊辣椒蘸料,是六盘水/水城具有明确地域归属的共享餐食。它更适合作为六盘水一餐,不为了凑齐“贵阳五种小吃”跨城搬运;肉类、豆制品、花生芝麻、辣椒和生熟夹具需要逐项确认。', 'Ingredients are cooked on a tabletop iron griddle and paired with toasted-chilli dip—a shared meal strongly associated with Shuicheng and Liupanshui. It belongs in a Liupanshui meal rather than being imported merely to complete a “five Guiyang snacks” checklist. Confirm meats, soy, peanut or sesame, chilli, and separate raw and cooked utensils.', language), + status: pickText('通常是一顿共享餐,不计作一口式街头试吃', 'Usually a shared meal, not a one-bite street-food tasting', language), + }, + ]; + const formats = [ + { + number: '01', + label: pickText('约 3–4 小时', 'Approx. 3–4 hours', language), + title: pickText('民族家宴烹饪课', 'Ethnic Home Cooking Class', language), + copy: pickText( + '与具名主人先逛一处当天营业的市场,再回到已经核验的家庭厨房、工作室或餐饮场所学习一至两道菜。酸汤鱼、糯米饭只作为菜单候选;主人身份、经营或家庭接待边界、食材来源、饮水、过敏原、刀具与热源、儿童参与、翻译、食谱和费用必须写进确认单。', + 'Shop at one operating market with a named host, then learn one or two dishes in a verified home kitchen, studio, or food venue. Sour-soup fish and glutinous rice are menu candidates, not defaults. The confirmation names the host and service type, sourcing, drinking water, allergens, knives and heat, children, interpretation, recipe notes, and cost.', + language, + ), + status: pickText('家庭厨房、私房菜与持证餐厅必须如实区分', 'Home kitchen, private dining, and licensed restaurant must be labelled accurately', language), + }, + { + number: '02', + label: pickText('约 2–3 小时', 'Approx. 2–3 hours', language), + title: pickText('原生长桌宴', 'Traditional Long-Table Banquet', language), + copy: pickText( + '先区分村寨真实节庆或社区宴席、预约型文化餐叙和景区/餐厅商业表演,再决定页面与行程如何命名。具名组织者、地点、最低人数、菜单、座位、天气备选、敬酒与酒歌、无酒精替代、交通、洗手间、价格和取消条件全部确认后才可预订。', + 'First distinguish a genuine village or festival gathering, a booked cultural meal, and a scenic-area or restaurant performance. Only book after confirming the named organiser, place, minimum group, menu, seating, weather fallback, toasts and songs, non-alcoholic alternatives, transport, toilets, price, and cancellation.', + language, + ), + status: pickText('不把商业场次写成家庭宴,也不承诺临时到村即开席', 'A commercial session is not a family feast, and no village banquet is promised on demand', language), + }, + { + number: '03', + label: pickText('约 2–3 小时', 'Approx. 2–3 hours', language), + title: pickText('在地市集风味探访', 'Local Market & Street-Food Tour', language), + copy: pickText( + '选择一处具名市场或一条紧凑街区,在真实营业时段安排 3–5 份小份试吃;只有步行、食量和摊位均合适时才扩展到客户建议的 5–6 份。花溪牛肉粉、肠旺面、丝娃娃、青岩豆腐/玫瑰冰粉与水城烙锅地域和用餐方式不同,不承诺在同一市场一次吃齐;摊位、熟制状态、生熟分离、过敏原、辣度、支付、座位、洗手间和拥挤天气逐项核验。', + 'Use one named market or compact food district in its real trading hours for three to five small tastings, expanding to the brief’s five or six only when distance, appetite, and vendors fit. Huaxi beef noodles, changwang noodles, siwawa, Qingyan tofu or rose ice jelly, and Shuicheng luoguo belong to different places and meal formats, so one market is never promised to contain them all. Verify vendor, cooking state, raw/cooked separation, allergens, spice, payment, seating, toilets, weather, and crowds.', + language, + ), + status: pickText('市场是公共商业空间,不自动等于家庭或少数民族私享体验', 'A market is a public commercial space, not automatically a private ethnic or home experience', language), + }, + ]; + const flow = [ + { title: pickText('先做饮食画像', 'Build the Food Profile', language), copy: pickText('记录严重过敏、饮食禁忌、素食/清真需求、辣度、酒精、儿童年龄与厨房操作舒适度;不能做到的需求在下单前说明。', 'Record severe allergies, restrictions, vegetarian or halal needs, spice, alcohol, children’s ages, and comfort in a kitchen. Any need that cannot be met is disclosed before booking.', language) }, + { title: pickText('只选择一种主体验', 'Choose One Anchor Experience', language), copy: pickText('家宴课、长桌宴或市集探访一天只设一个美食锚点,先锁定具名主人、经营场所、组织者或市场。', 'Use one food anchor per day—cooking class, banquet, or market—and first lock the named host, venue, organiser, or market.', language) }, + { title: pickText('核对菜单与卫生条件', 'Verify Menu & Hygiene', language), copy: pickText('确认菜单、食材采购与保存、饮用水、加工环境、餐具清洁、过敏原交叉接触、熟度和剩余食品处理。', 'Confirm menu, sourcing and storage, drinking water, preparation area, utensil cleaning, allergen cross-contact, cooking, and leftover handling.', language) }, + { title: pickText('交付礼仪与安全说明', 'Brief Etiquette & Safety', language), copy: pickText('提前说明厨房刀具与热源、未成年人看护、敬酒不劝酒、无酒精替代、人物与家庭空间拍摄,以及市场步行和支付方式。', 'Brief knives and heat, child supervision, no pressure to drink, non-alcoholic alternatives, photography in homes and of people, market walking, and payment.', language) }, + { title: pickText('当天按供应调整', 'Adapt to the Day’s Supply', language), copy: pickText('早市摊位、时令食材和主人菜单会变化;以新鲜、安全和真实营业为先,不为凑齐宣传菜名跨市场追赶。', 'Stalls, seasonal produce, and host menus change. Freshness, safety, and real opening hours come first—never race between markets to tick off promotional dish names.', language) }, + ]; + const gates = [ + { title: pickText('接待主体与产品身份', 'Host & Product Identity', language), copy: pickText('具名主人/组织者、准确地址、联系方式、家庭接待或餐饮经营属性、可接待人数、英语支持、服务费与取消方式齐全。', 'Name the host or organiser, address, contact, home-hosting or food-business status, capacity, English support, service fee, and cancellation.', language) }, + { title: pickText('食品、饮水与过敏原', 'Food, Water & Allergens', language), copy: pickText('核对原料来源与保存、生熟分开、饮用水、清洁消毒、熟度、严重过敏原和交叉接触;无法安全满足时改用具名餐厅方案。', 'Check sourcing and storage, raw/cooked separation, drinking water, cleaning, cooking, severe allergens, and cross-contact. Use a named restaurant fallback when needs cannot be met safely.', language) }, + { title: pickText('工具、儿童与酒精', 'Tools, Children & Alcohol', language), copy: pickText('刀具、明火/热油、湿滑地面、儿童一对一看护、酒精度与饮酒选择、驾车人员零酒精及紧急医疗联系均提前说明。', 'Brief knives, flame or hot oil, wet floors, one-to-one child supervision, alcohol strength and choice, zero alcohol for drivers, and emergency medical contact.', language) }, + { title: pickText('同意、价格与取消', 'Consent, Price & Cancellation', language), copy: pickText('主人、家庭空间、摊主和未成年人拍摄同意;菜单价格、服务费、小费、采购费、最低人数、天气/停市/主人取消与退款逐项写清。', 'Record consent for hosts, homes, vendors, and minors; state menu price, service and shopping fees, tips, minimum group, weather or market closure, host cancellation, and refunds.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('贵阳抵达日・市集小吃', 'Guiyang Arrival Food Walk', language), copy: pickText('把具名早市或城市小吃线放在抵达后或返程前,按真实营业时间安排 3–5 份小吃,不挤压正餐和转场。', 'Place a named market or city snack walk on arrival or before departure, with three to five tastings in real opening hours and room for transfers and meals.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('村寨慢住・家宴烹饪', 'Village Stay & Hosted Cooking', language), copy: pickText('在具名村寨住宿确认后,再匹配一位真实主人和一个可执行菜单;不把整个村寨都写成随时可预约的家庭厨房。', 'After a named village stay is confirmed, match one real host and one workable menu. The whole village is never presented as an on-demand home kitchen.', language), href: '/safaris/culture-outdoor-mix/' }, + { number: '03', title: pickText('节庆窗口・长桌宴候选', 'Festival Window & Banquet Candidate', language), copy: pickText('只有当年节庆或社区公开宴席已有书面安排时进入线路;否则替换为具名餐厅的地方风味分享餐。', 'Add a banquet only when a current festival or public community meal has written arrangements; otherwise use a named restaurant tasting meal.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('贵州酸汤与蘸水组成的餐桌', +
+
+
+ + Culture Through Food / One Table at a Time +

{pickText('民族美食文化', 'Culinary Cultural Journeys', language)}

+

{pickText('以美食为切口理解民族生活,全程拒绝流水线团餐。先确认是谁做、在哪里做、食材从哪里来,再决定这一餐如何进入旅程。', 'Understand local life through food, without assembly-line group meals. First confirm who cooks, where, and how ingredients are sourced—then decide how the table belongs in the journey.', language)}

+ {pickText('查看三种美食体验', 'Explore three food formats', language)} +
+
+
+ + + +
+
+ {pickText('美食体验定义', 'Food Experience Definition', language)} +

{pickText('不追求吃得最多,而是把一餐背后的生活关系说清楚。', 'The aim is not to eat the most, but to understand the relationships behind one meal.', language)}

+

{pickText('客户文档提出家宴烹饪课、原生长桌宴与在地市集探访,并指定花溪牛肉粉、肠旺面、丝娃娃、青岩豆腐/玫瑰冰粉、水城烙锅五类风味。实际落地时,家庭接待、经营餐厅、节庆宴席与公共市场属于不同场景;这五类风味也分属贵阳早餐、青岩停留和六盘水共享餐,不能用一条市集线强行吃齐。菜品、主人、地点和营业时间必须按日期逐项确认。', 'The brief proposes a home-cooking class, traditional long-table banquet, and local market tour, specifying Huaxi beef rice noodles, changwang noodles, siwawa, Qingyan tofu or rose ice jelly, and Shuicheng luoguo. A home visit, food business, festival banquet, and public market are different settings; the five tastes also span Guiyang breakfast, a Qingyan stop, and a shared Liupanshui meal rather than one market checklist. Dish, host, place, and operating hours are verified by date.', language)}

+
+
+
{pickText('体验数量', 'Formats', language)}
{pickText('三选一,一天一个美食锚点', 'Choose one; one food anchor per day', language)}
+
{pickText('建议时长', 'Suggested Duration', language)}
{pickText('市集 2–3 小时 / 烹饪 3–4 小时 / 宴席 2–3 小时', 'Market 2–3h / cooking 3–4h / banquet 2–3h', language)}
+
{pickText('市集试吃', 'Market Tastings', language)}
{pickText('通常 3–5 份;5–6 份只在距离、食量与摊位合适时', 'Usually 3–5; five or six only when distance, appetite, and vendors fit', language)}
+
{pickText('饮食需求', 'Dietary Needs', language)}
{pickText('过敏、素食与清真逐项书面确认', 'Allergy, vegetarian, and halal needs confirmed in writing', language)}
+
{pickText('酒精礼仪', 'Alcohol', language)}
{pickText('不劝酒,提供无酒精替代', 'No pressure to drink; non-alcoholic alternative', language)}
+
{pickText('默认备选', 'Default Fallback', language)}
{pickText('具名餐厅地方风味分享餐', 'A named-restaurant regional tasting meal', language)}
+
+
+ +
+
{pickText('陶锅酸汤菜品',
{pickText('客户提供菜品实拍 · 菜名、餐厅、制作人与使用授权待归档', 'Client-supplied dish photograph · dish, venue, maker, and publication rights require records', language)}
+
{pickText('酸汤锅与多种贵州蘸水',
{pickText('酸汤风味氛围参考 · 不证明为家庭厨房或民族家宴课程', 'Sour-flavour reference · not evidence of a home kitchen or ethnic cooking class', language)}
+
{pickText('多人共享的烙锅餐桌',
{pickText('客户提供餐桌实拍 · 场所与菜品身份待确认,不作长桌宴证明', 'Client-supplied table photograph · venue and dish identity pending; not evidence of a long-table banquet', language)}
+
{pickText('木构空间里的长桌宴席',
{pickText('客户提供长桌氛围图 · 组织者、地点、年份及是否商业场次待确认', 'Client-supplied banquet atmosphere · organiser, place, year, and commercial status require confirmation', language)}
+
+ +
+
+
Local Market / Street-Food Tour

{pickText('五类代表风味,不做一张“必须全部吃完”的清单。', 'Five signature tastes—not a mandatory one-walk checklist.', language)}

{pickText('客户给出的五类内容全部保留,但按真实地域、营业时段和用餐方式重新组织。贵阳早餐、青岩停留和六盘水共享烙锅可以分属不同天,最终一次城市步行通常只选 3–5 份小试。', 'All five items from the brief remain, reorganised by real place, opening time, and meal format. Guiyang breakfast, a Qingyan stop, and a shared Liupanshui griddle may belong on different days; one city walk normally selects only three to five small tastings.', language)}

+
{streetFoods.map((food) =>
{food.number}{food.place}

{food.title}

{food.copy}

{food.status}
)}
+
{pickText('贵阳城市版建议', 'Suggested Guiyang Edition', language)}

{pickText('早餐线与夜市线二选一。', 'Choose a breakfast route or an evening market route.', language)}

{pickText('早餐线围绕具名粉面店、丝娃娃或糯米饭等 3–4 站;夜间可选择青云市集等成熟商业街区,但它是餐饮、文创与娱乐混合的现代商业市集,不包装成传统农贸早市。具体门店、营业与拥挤度按当天确认。', 'A breakfast route uses three or four named noodle, wrap, or glutinous-rice stops. In the evening, an established commercial district such as Qingyun Market may work, but it is a modern mix of food, culture, and entertainment—not a traditional produce market. Named outlets, live opening, and crowd level are checked for the day.', language)}

+
+
+ +
+
+
01 / Culinary Formats

{pickText('三种体验,三套完全不同的接待关系。', 'Three formats, three different hosting relationships.', language)}

{pickText('保留客户希望传达的文化深度,同时把经营身份、食品安全、酒精礼仪和能否拍摄提前说清。', 'Keep the cultural depth of the brief while making venue identity, food safety, alcohol etiquette, and photography explicit first.', language)}

+
{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
+
+ +
+
{pickText('木构长廊中的宴席餐桌',
+
+
02 / Table Planning

{pickText('先把不能吃、不能做和不想被要求做的事说清楚。', 'Begin with what cannot be eaten, done, or socially expected.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Food & Hosting Gates

{pickText('好吃是结果,安全和尊重是前提。', 'Delicious is the outcome; safety and respect are the conditions.', language)}

{pickText('正式确认以国家餐饮食品安全要求和贵州现行食品安全条例为底线,并按实际服务主体落实;页面不以“家庭”“传统”替代卫生与经营核验。', 'Final confirmation uses current national catering food-safety requirements and Guizhou food-safety rules as the baseline, applied to the actual provider. “Home” or “traditional” never substitutes for hygiene and service-status checks.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具名主人、餐厅/场地、市场、营业时段与准确产品身份', 'Named host, venue, market, opening hours, and accurate product identity', language)}
  • {pickText('经确认的菜单、食材、过敏原、辣度、饮水、酒精与无酒精选择', 'Confirmed menu, ingredients, allergens, spice, water, alcohol, and non-alcoholic choice', language)}
  • {pickText('时长、人数、费用、翻译、拍摄、交通、洗手间和取消条件', 'Duration, group, cost, interpretation, photos, transport, toilets, and cancellation', language)}
+
{pickText('必须逐单重新核验', 'Claims requiring booking-specific proof', language)}
  • {pickText('“家庭秘方”“原生长桌宴”“非商业”“有机食材”或固定收益去向', '“Family secret,” “original banquet,” “non-commercial,” organic sourcing, or fixed benefit claims', language)}
  • {pickText('每个市场都有丝娃娃、牛肉粉、烤香猪,或任一主人都能教酸汤鱼', 'Every market has siwawa, beef noodles, roast pork, or any host can teach sour-soup fish', language)}
  • {pickText('绝对无过敏原、严格清真、全素无交叉接触或未成年人可独立使用刀火', 'Allergen-free, strict halal, fully vegan without cross-contact, or children using knives or heat alone', language)}
+
+

{pickText('公开信息与规则参考:贵州省“黔菜师傅”工程菜品遴选结果及传统优势食品产业方案对贵阳肠旺面、丝娃娃、花溪牛肉粉、青岩风味炒豆腐和贵阳玫瑰冰粉的归属;文化和旅游部及六盘水公开资料对水城烙锅的地域与技艺说明;贵州省政府对青云市集现代综合街区的介绍;以及《中华人民共和国食品安全法》、市场监管总局《餐饮服务食品安全操作规范》和《贵州省食品安全条例》。实际安排仍需核对具名经营主体、门店营业、市场管理和每位客人的饮食需求。', 'Public references include Guizhou’s Qian Cuisine Chef selection and traditional-food industry plan for Guiyang changwang noodles, siwawa, Huaxi beef noodles, Qingyan-style tofu, and Guiyang rose ice jelly; Ministry of Culture and Tourism and Liupanshui material on Shuicheng luoguo; the Guizhou government description of Qingyun Market as a modern mixed-use district; China’s Food Safety Law, SAMR’s catering code, and the Guizhou Food Safety Regulation. Each booking still checks the named provider, live opening, market management, and every guest’s dietary needs.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('让一餐成为当天的文化锚点,而不是景点之间的填空题。', 'Make one meal the day’s cultural anchor, not a gap between sights.', language)}

+ +
+ + +
+ ); +} + +function IconicWaterfallClustersDetailPage() { + const { language } = useTravelSite(); + const clusters = [ + { + number: '01', + label: pickText('安顺・成熟景区', 'Anshun · Developed Scenic Area', language), + title: pickText('黄果树瀑布群', 'Huangguoshu Waterfall Group', language), + copy: pickText( + '以高 77.8 米、宽 101 米的黄果树大瀑布为核心,通常把陡坡塘、大瀑布和天星桥分区安排为完整一日。陡坡塘的宽缓水幕、天星桥的水上石林和银链坠潭不能被简化成“同一个瀑布机位”;入园时段、观光车、水帘洞/步道开放、客流和步行量按当天确认。', + 'Centred on Huangguoshu Great Waterfall, officially described as 77.8 m high and 101 m wide, a full day usually separates Doupotang, the main falls, and Tianxingqiao. Doupotang’s broad curtain, Tianxingqiao’s water-stone landscape, and Yinlianzhuitan are not one photo stop. Entry time, shuttle, Water-Curtain Cave and trail access, crowds, and walking are checked for the day.', + language, + ), + status: pickText('适合首次来黔;夏季水势更强,但丰水、彩虹和水帘洞开放均不作保证', 'Best for a first Guizhou visit; stronger summer water, rainbows, and cave access are never guaranteed', language), + }, + { + number: '02', + label: pickText('关岭・山谷候选', 'Guanling · Valley Candidate', language), + title: pickText('滴水滩瀑布', 'Dishuitan Waterfall', language), + copy: pickText( + '官方公开资料支持其为坝陵河支流上的七级瀑布、总落差约 410 米,也是黄果树瀑布群中落差最大的瀑布之一;这不等于游客可沿全部落差连续步行。它与黄果树核心景区不是同一套成熟游览动线,入口、道路、停车、步道、观景台、洗手间、通信和当前开放必须单独核验。', + 'Official public material describes a seven-stage waterfall on a Baling River tributary with a total drop of about 410 m, among the greatest drops in the Huangguoshu group. That does not mean visitors can walk the full drop. It is not the same mature visitor circuit as core Huangguoshu, so entrance, road, parking, trail, viewpoint, toilets, signal, and live access require a separate check.', + language, + ), + status: pickText('不使用“隐秘、无人、无遮挡”作承诺;只有具名安全观景点才进入方案', 'No promise of “hidden, empty, unobstructed”; only a named safe viewpoint enters the plan', language), + }, + { + number: '03', + label: pickText('赤水・丹霞峡谷', 'Chishui · Danxia Gorge', language), + title: pickText('赤水丹霞瀑布', 'Chishui Danxia Waterfalls', language), + copy: pickText( + '赤水是世界自然遗产“中国丹霞”的六个组成地之一。核心可规划赤水丹霞旅游区・大瀑布(原十丈洞,公开资料约高 76 米、宽 80 米),再按时间选择四洞沟或其他具名景区;不同门票、接驳和步道不可笼统写成“几十处瀑布一次看完”。', + 'Chishui is one of the six components of the UNESCO World Heritage property China Danxia. The anchor can be Chishui Danxia Tourism Area · Great Waterfall, formerly Shizhangdong and publicly described as about 76 m high and 80 m wide, with Sidonggou or another named scenic area added only if time allows. Separate tickets, shuttles, and trails cannot be sold as “dozens of waterfalls in one visit.”', + language, + ), + status: pickText('与安顺黄果树分属贵州不同区域,建议独立安排赤水停留,不做同日往返', 'A different Guizhou region from Huangguoshu; plan a Chishui stay, not a same-day return', language), + }, + ]; + const flow = [ + { title: pickText('先选一个瀑布区域', 'Choose One Waterfall Region', language), copy: pickText('黄果树完整景区、滴水滩山谷候选和赤水丹霞是三种不同交通与体力结构;一天只设一个核心区域。', 'Huangguoshu’s full scenic area, a Dishuitan valley candidate, and Chishui Danxia have different transfer and exertion profiles. Use one core region per day.', language) }, + { title: pickText('核对当日开放与票务', 'Check Live Access & Ticketing', language), copy: pickText('使用官方渠道确认实名预约、入园时段、门票、观光车/电梯、水帘洞或步道开闭、停车与景区变更通知。', 'Use official channels for real-name reservation, entry slot, ticket, shuttle or lift, cave or trail access, parking, and change notices.', language) }, + { title: pickText('匹配水量、天气与客流', 'Match Water, Weather & Crowds', language), copy: pickText('丰水期更壮观,也可能带来暴雨、涨水、水雾、湿滑与局部关闭;枯水期更轻柔,不用历史照片保证当天水量。', 'High water can be dramatic but also brings rain, rising water, spray, slippery surfaces, and closures. Low water is gentler; historic photos never guarantee the day’s flow.', language) }, + { title: pickText('计算真实步行与退出', 'Map Real Walking & Exits', language), copy: pickText('按台阶、坡度、湿滑、候车、老人/儿童、膝踝和摄影停留计算;明确电梯、缩短动线、休息点和中途退出。', 'Plan around steps, grade, wet surfaces, queues, elders or children, knees and ankles, and photo stops; name lifts, shortened circuits, rests, and exits.', language) }, + { title: pickText('当天服从现场安全调整', 'Follow Live Safety Changes', language), copy: pickText('雷雨、洪水、落石、道路管制或景区限流优先于原计划;用具名室内文化点、古镇或恢复型住宿替代,不绕封闭线。', 'Thunderstorms, floods, rockfall, road controls, or capacity limits override the plan. Use a named indoor cultural stop, old town, or recovery stay—never bypass a closure.', language) }, + ]; + const gates = [ + { title: pickText('开放、票务与准确点位', 'Access, Ticket & Exact Site', language), copy: pickText('具名景区/入口、官方开放、预约时段、门票与接驳、停车、具体观景台和临时关闭渠道全部确认。', 'Confirm the named scenic area and entrance, official opening, reservation slot, ticket and shuttle, parking, exact viewpoint, and closure channel.', language) }, + { title: pickText('水文、天气与地质风险', 'Water, Weather & Geology', language), copy: pickText('上游降雨、河水上涨、雷电、水雾、湿滑、落石和山洪预警逐项检查;不进入警戒线、河道、瀑顶或非开放崖边。', 'Check upstream rain, rising water, lightning, spray, slippery ground, rockfall, and flash-flood alerts; never enter barriers, channels, waterfall tops, or closed edges.', language) }, + { title: pickText('步行、接驳与无障碍', 'Walking, Shuttle & Accessibility', language), copy: pickText('总步行、台阶、排队、观光车/电梯、洗手间、遮雨、饮水、儿童牵行、轮椅或行动不便替代动线写入行程。', 'State total walking, steps, queues, shuttle or lift, toilets, rain cover, water, child hand-holding, and wheelchair or reduced-mobility alternatives.', language) }, + { title: pickText('摄影、无人机与现场秩序', 'Photography, Drones & Site Order', language), copy: pickText('只在开放观景点停留;三脚架、商业拍摄、无人机和涉水机位按景区规定。乘坐接驳车主动系安全带并听从现场人员。', 'Stop only at open viewpoints; tripods, commercial shoots, drones, and water-edge positions follow site rules. Wear shuttle seatbelts and follow staff instructions.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线・黄果树整日', 'Classic Panorama · Full Huangguoshu Day', language), copy: pickText('贵阳或安顺衔接黄果树,按客流在陡坡塘、大瀑布、天星桥之间排序;老人和亲子版本主动缩短一段。', 'Connect from Guiyang or Anshun and sequence Doupotang, the main falls, and Tianxingqiao around crowds; shorten one section for elders and families.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('关岭峡谷线・滴水滩候选', 'Guanling Valley · Dishuitan Candidate', language), copy: pickText('与坝陵河/关岭停留组合,只有道路、入口、步道、具名观景台和当地接待全部通过才加入,不与黄果树完整日叠加。', 'Pair with a Baling River or Guanling stay only after road, entrance, trail, named viewpoint, and local support all pass; do not stack it onto a full Huangguoshu day.', language), href: '/safaris/outdoor-expedition/' }, + { number: '03', title: pickText('赤水丹霞线・两至三晚', 'Chishui Danxia · Two to Three Nights', language), copy: pickText('以赤水城区或具名景观住宿为基地,选择大瀑布与一处丹霞/竹林景区;为长距离转场和雨天留缓冲。', 'Base in Chishui city or a named landscape stay, combining the Great Waterfall with one Danxia or bamboo-forest site and leaving buffer for long transfers and rain.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('黄果树大瀑布与喀斯特山地', +
+
+
+ + Karst Water / Danxia Valleys / Live Conditions +

{pickText('世界级瀑布群', 'Iconic Waterfall Clusters', language)}

+

{pickText('从壮阔地标到山谷多级瀑布,先分清景区、地点和当天水况,再决定看多少、走多远。', 'From a monumental landmark to multi-tier valley falls, first separate the scenic area, exact place, and live water conditions—then decide how much to see and walk.', language)}

+ {pickText('比较三个瀑布区域', 'Compare three waterfall regions', language)} +
+
+
+ + + +
+
+ {pickText('瀑布群定义', 'Waterfall Cluster Definition', language)} +

{pickText('“瀑布很多”不是路线;景区边界、水量和脚下的路才是。', '“Many waterfalls” is not an itinerary; boundaries, water, and the path underfoot are.', language)}

+

{pickText('客户文档提出黄果树、滴水滩和赤水三个方向,并使用“亚洲最大瀑布群、410 米隐秘瀑布、几十处瀑布”等宣传语言。公开资料可以支持黄果树大瀑布尺寸、滴水滩总落差和赤水丹霞地貌,但三者的成熟度、距离、票务与安全条件完全不同。因此本页按三个独立区域呈现,不用一张历史丰水照片暗示全年水量,也不把非开放山谷包装成随时可到的“小众秘境”。', 'The brief proposes Huangguoshu, Dishuitan, and Chishui, using phrases such as “Asia’s largest cluster,” a “410 m hidden waterfall,” and “dozens of cascades.” Public material supports the main Huangguoshu dimensions, Dishuitan’s total drop, and Chishui’s Danxia setting, but their maturity, distance, ticketing, and safety differ. This page treats them as separate regions, never using a historic high-water image to imply year-round flow or presenting a non-open valley as an on-demand “hidden gem.”', language)}

+
+
+
{pickText('区域数量', 'Regions', language)}
{pickText('三选一,一天一个核心区域', 'Choose one; one core region per day', language)}
+
{pickText('黄果树主瀑', 'Huangguoshu Main Falls', language)}
{pickText('公开尺寸高 77.8 米、宽 101 米', 'Officially described as 77.8 m high and 101 m wide', language)}
+
{pickText('滴水滩', 'Dishuitan', language)}
{pickText('约 410 米七级总落差;开放动线单独核验', 'About 410 m over seven stages; access verified separately', language)}
+
{pickText('赤水大瀑布', 'Chishui Great Waterfall', language)}
{pickText('约高 76 米、宽 80 米;丹霞景区独立安排', 'About 76 m high and 80 m wide; separate Danxia visit', language)}
+
{pickText('丰水观赏', 'High-Water Viewing', language)}
{pickText('通常夏季更强;暴雨时也可能局部关闭', 'Usually stronger in summer; heavy rain may close sections', language)}
+
{pickText('影像承诺', 'Image Promise', language)}
{pickText('不保证固定水量、彩虹、无人或无遮挡', 'No guaranteed flow, rainbow, emptiness, or clear view', language)}
+
+
+ +
+
{pickText('丰水期黄果树大瀑布',
{pickText('客户提供黄果树主瀑实拍 · 拍摄日期、水量、机位与公开使用授权待归档', 'Client-supplied Huangguoshu main-falls image · date, flow, viewpoint, and publication rights require records', language)}
+
{pickText('带彩虹的宽幅瀑布水幕',
{pickText('黄果树瀑布群氛围参考 · 具体瀑布与日期待确认,不保证当天彩虹', 'Huangguoshu-group atmosphere · exact falls and date pending; no promise of a rainbow', language)}
+
{pickText('山谷中的多级瀑布',
{pickText('客户文档对应滴水滩多级瀑布 · 具体机位、道路与观景边界待现场核验', 'Client brief associates this with Dishuitan · exact viewpoint, road, and viewing boundary require field verification', language)}
+
{pickText('红色丹霞岩壁旁的瀑布水幕',
{pickText('客户文档对应赤水丹霞瀑布 · 具体景区/瀑布名称与授权待归档', 'Client brief associates this with Chishui Danxia · exact scenic area, waterfall, and rights require records', language)}
+
+ +
+
+
01 / Three Waterfall Regions

{pickText('三个区域,不是一张“瀑布打卡清单”。', 'Three regions, not one waterfall checklist.', language)}

{pickText('从成熟景区、山谷候选到丹霞目的地,分别决定时长、交通、体力和可承诺内容。', 'A developed scenic area, a valley candidate, and a Danxia destination each need their own time, transport, exertion, and promise.', language)}

+
{clusters.map((cluster) =>
{cluster.number}{cluster.label}

{cluster.title}

{cluster.copy}

{cluster.status}
)}
+
+
+ +
+
{pickText('森林山谷中的滴水滩多级瀑布参考',
+
+
02 / Waterfall-Day Planning

{pickText('先看天气和路,再看瀑布。', 'Check weather and the route before the waterfall.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Waterfall Gates

{pickText('水越大,边界越要清楚。', 'The bigger the water, the clearer the boundary must be.', language)}

{pickText('开放、上游降雨、步行和现场管理共同决定当天能到哪里;“小众”从来不是越过护栏或封闭线的理由。', 'Access, upstream rain, walking, and on-site management decide how far the day can go. “Off the beaten path” is never a reason to cross a barrier or closure.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具名景区、入口、预约时段、票价/接驳、步行与预计停留', 'Named scenic area, entrance, slot, ticket or shuttle, walking, and planned stay', language)}
  • {pickText('开放观景台、缩短动线、老人/儿童安排和明确退出点', 'Open viewpoints, shortened route, elder or child plan, and clear exits', language)}
  • {pickText('当天官方通知、天气/水情复核和关闭后的具名替代', 'Current notice, weather and water check, and a named closure fallback', language)}
+
{pickText('必须按日期重新核验', 'Claims requiring date-specific proof', language)}
  • {pickText('“亚洲最大”“完全无人”“无遮挡”“秘境直达”或“几十处一次看完”', '“Asia’s largest,” empty, unobstructed, hidden access, or dozens in one visit', language)}
  • {pickText('固定丰水、固定彩虹、水帘洞必开、无人机可飞或可站到瀑顶/水边', 'Guaranteed high flow, rainbow, cave access, drone flight, waterfall-top or water-edge access', language)}
  • {pickText('客户图片对应的具体瀑布、拍摄日期、合法机位和人物/图片授权', 'Exact falls, date, lawful viewpoint, and people or image rights in client photography', language)}
+
+

{pickText('公开信息参考:贵州省民政厅黄果树资料、贵州省政协与贵州公开资料中的滴水滩信息、联合国教科文组织“中国丹霞”遗产资料、贵州省 2026 年政府定价景区清单及文化和旅游部 2025 年景区安全暗访通报。最终开放、预约、价格与安全要求以旅行当日景区和属地通知为准。', 'Public references include Guizhou Civil Affairs material on Huangguoshu, Guizhou public material on Dishuitan, UNESCO’s China Danxia record, Guizhou’s 2026 government-priced scenic-area list, and the Ministry of Culture and Tourism’s 2025 safety inspection notice. Live opening, reservation, price, and safety requirements follow the scenic area and local notice on the travel date.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('一日看透一个水系,比跨区追赶三张照片更完整。', 'One water system in a day is richer than chasing three photos across regions.', language)}

+ +
+ + +
+ ); +} + +function IndigenousEthnicVillagesDetailPage() { + const { language } = useTravelSite(); + const villages = [ + { + number: '01', + label: pickText('雷山・成熟景区', 'Leishan · Developed Scenic Area', language), + title: pickText('西江千户苗寨', 'Xijiang Thousand-Household Miao Village', language), + copy: pickText( + '成片木质吊脚楼、白水河、风雨桥和山腰全景构成最完整的第一印象,官方公开资料记录传统木质吊脚楼 1,300 余栋。这里也是服务成熟、游客集中、商业经营充分的景区型村寨:预约入园、观光车、行李、台阶、木构消防、住宿接待与夜间噪声要一起核对。', + 'Timber stilt houses, the Baishui River, wind-and-rain bridges, and a hillside panorama create the strongest first impression; public official material records more than 1,300 traditional timber stilt houses. It is also a mature, busy scenic-area village with substantial commerce, so timed entry, shuttle, luggage, steps, timber-fire safety, accommodation, and night noise all need checking.', + language, + ), + status: pickText('只使用开放观景台和公共动线;不承诺“私家机位、完全无人或避开所有客流”', 'Use open viewpoints and public routes only; no promise of a private angle, empty village, or total crowd avoidance', language), + }, + { + number: '02', + label: pickText('黎平・两个不同村寨', 'Liping · Two Distinct Villages', language), + title: pickText('肇兴与黄岗侗寨', 'Zhaoxing & Huanggang Dong Villages', language), + copy: pickText( + '肇兴以仁、义、礼、智、信五座鼓楼和风雨桥形成清晰的公共游览骨架,适合第一次理解侗族木构公共空间。黄岗位于黎平县双江镇,是另一处具名社区;喊天节在传统上对应农历六月十五,侗族大歌有节庆、集体交往与接待语境,不能写成每天“村民自发为游客演唱”。', + 'Zhaoxing’s five drum towers—Ren, Yi, Li, Zhi, and Xin—and its wind-and-rain bridges form a legible public route for a first reading of Dong communal timber architecture. Huanggang is a separate named community in Shuangjiang, Liping. Its Heaven-Calling Festival traditionally falls on the fifteenth day of the sixth lunar month, while Dong Grand Song belongs to festival, collective, and hosting contexts—not an on-demand daily performance.', + language, + ), + status: pickText('先选肇兴或黄岗;若组合,必须分别确认日期、交通、主人/公开活动与可拍范围', 'Choose Zhaoxing or Huanggang first; a pairing needs separate dates, transport, host or public programme, and photography boundaries', language), + }, + { + number: '03', + label: pickText('从江・公开景区与社区', 'Congjiang · Scenic Area & Community', language), + title: pickText('岜沙苗寨', 'Basha Miao Village', language), + copy: pickText( + '岜沙由多个自然寨组成,木楼、古树、森林信仰与人生礼俗共同构成村寨语境。镰刀剃头、迎宾和火枪元素如今也进入景区演艺表达;正式方案只引用当日官方公开节目,把舞台呈现与居民日常分开,不保证火枪鸣放,更不安排游客接触或操作枪械、镰刀。', + 'Basha comprises several natural hamlets, where timber homes, old trees, forest beliefs, and life-cycle customs form the village context. Sickle haircutting, welcomes, and musket elements also appear in scenic-area performance. A final plan cites only the current official public programme, separates staged presentation from resident life, never guarantees a musket salute, and never offers visitors firearms or sickle handling.', + language, + ), + status: pickText('不用“枪手部落”作为产品标题;成人礼与居民肖像不是可购买的表演道具', 'No “gunmen tribe” product label; rites of passage and resident portraits are not purchasable props', language), + }, + { + number: '04', + label: pickText('安顺・先定具体村名', 'Anshun · Name the Village First', language), + title: pickText('布依石头寨', 'Buyi Stone Villages', language), + copy: pickText( + '客户文档的方向可落到镇宁扁担山石头寨,官方资料描述其以石块砌墙、石片盖顶;高荡则是另一处石木结构布依古寨,不能把两地历史、建筑和体验合并成一个“600 年全石头村”。慢行、织染或家访只有在村名、具名老师/主人、材料、费用与拍摄同意全部确认后进入方案。', + 'The brief can be anchored to Shitouzhai in Biandanshan, Zhenning, officially described with stone walls and stone-slab roofs. Gaodang is a separate Buyi village with stone-and-timber architecture; their histories, buildings, and experiences cannot be merged into one “600-year all-stone village.” A walk, textile session, or home visit enters the plan only after the village, named teacher or host, materials, fee, and photography consent are confirmed.', + language, + ), + status: pickText('用具体建筑和主人讲故事,不把居民日常包装成自动开放的“原生态体验”', 'Tell the story through a specific building and host, not by packaging daily life as automatically open “authenticity”', language), + }, + ]; + const flow = [ + { title: pickText('先选接待关系', 'Choose the Visitor Relationship', language), copy: pickText('成熟景区公共游览、公开演艺、具名主人接待和住宿是四种不同关系;先选适合客人的一种,不做“越不商业越真实”的排名。', 'A developed public visit, public performance, named-host visit, and overnight stay are different relationships. Choose one that suits the guest, without ranking “less commercial” as “more authentic.”', language) }, + { title: pickText('写清具体村寨与日期', 'Name the Village & Date', language), copy: pickText('西江、肇兴、黄岗、岜沙、扁担山石头寨或高荡逐一写明;节庆使用农历与当年公历双重核对,不以旧年海报推算。', 'State Xijiang, Zhaoxing, Huanggang, Basha, Biandanshan Shitouzhai, or Gaodang precisely. Check festivals against both the lunar calendar and current Gregorian notice, never an old poster.', language) }, + { title: pickText('确认交通、住宿与接待能力', 'Confirm Access, Stay & Hosting', language), copy: pickText('核对入寨/入园、预约、门票接驳、车辆落客、行李、坡路台阶、洗手间、房型消防,以及住宿能否完成境外客人登记。', 'Confirm entry, reservation, ticket and shuttle, drop-off, luggage, slopes and steps, toilets, room and fire safety, and whether the stay can register international guests.', language) }, + { title: pickText('区分公开节目与生活现场', 'Separate Programme from Daily Life', language), copy: pickText('公开演出只按当日节目表;农事、歌唱、家宴、成人礼和私人空间均需主人或社区明确同意,不能临时要求“来一个”。', 'Public performance follows the day’s programme. Farm work, singing, home meals, rites of passage, and private spaces require explicit host or community consent—never an impromptu demand.', language) }, + { title: pickText('当天让社区边界优先', 'Let Community Boundaries Lead', language), copy: pickText('活动取消、主人忙碌、天气变化或社区临时事务优先于游客清单;切换到公共建筑步行、具名博物馆或另一项已确认活动。', 'Cancellation, host commitments, weather, or community matters take priority over a visitor checklist. Switch to a public architecture walk, named museum, or another confirmed activity.', language) }, + ]; + const gates = [ + { title: pickText('村寨、主体与商业边界', 'Village, Operator & Commercial Boundary', language), copy: pickText('具体村名、景区/社区/主人身份、公开区域、收费项目、收益对象、门票与取消退款写清;不使用模糊的“当地人安排”。', 'State the village, scenic area, community or host identity, public area, paid items, beneficiary, ticket, and cancellation or refund. Avoid a vague “arranged by locals.”', language) }, + { title: pickText('住宿、消防与境外登记', 'Stay, Fire & Guest Registration', language), copy: pickText('具名房型、真实图片、卫浴、热水、供暖/空调、木构消防、夜间联系人、车辆/行李和境外客人住宿登记能力逐项确认。', 'Verify the named room, real images, bathroom, hot water, heating or cooling, timber-fire safety, night contact, vehicle or luggage support, and international-guest registration.', language) }, + { title: pickText('演艺、工具与人群安全', 'Performance, Tools & Crowd Safety', language), copy: pickText('当日节目、舞台/观众边界、火枪或镰刀元素的官方管理、噪声、拥挤、儿童与长者位置、下雨替代和医疗联系明确。', 'Confirm the day’s programme, stage and audience boundary, official control of any musket or sickle element, noise, crowds, child or elder position, rain fallback, and medical contact.', language) }, + { title: pickText('同意、隐私与影像使用', 'Consent, Privacy & Image Use', language), copy: pickText('公共建筑可拍范围、住宅/祭祀空间、成人与未成年人肖像、服饰租赁、商业拍摄、无人机和图片版权分别取得许可。', 'Obtain separate permission for public architecture, homes or ritual spaces, adult and minor portraits, costume rental, commercial shoots, drones, and image rights.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('西江一晚・景区型初识', 'One Night in Xijiang · Scenic Introduction', language), copy: pickText('以公开观景、木构步行和一间已核验住宿为主;用入园时段与早晚节奏管理客流,不承诺私家机位。', 'Use public viewpoints, a timber-architecture walk, and one verified stay. Manage crowds through entry timing and morning or evening rhythm, not a promised private angle.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('侗寨两晚・肇兴或黄岗为主', 'Two Dong-Village Nights · Zhaoxing or Huanggang', language), copy: pickText('肇兴适合建筑与公共动线;黄岗只在日期、主人或当年公开节庆成立时加入。两地组合要保留交通和社区时间。', 'Zhaoxing suits architecture and a public route. Add Huanggang only with a valid date, host, or current public festival, leaving time for transfer and community rhythm.', language), href: '/safaris/culture-outdoor-mix/' }, + { number: '03', title: pickText('从江南线・岜沙具名访问', 'Southern Congjiang · Named Basha Visit', language), copy: pickText('以公开景区动线和当日演艺通知为准,可与从江/肇兴住宿组合;不把火枪、剃头或成人礼做成游客参与项目。', 'Follow the public scenic route and current performance notice, paired with a Congjiang or Zhaoxing stay. Muskets, haircutting, and rites of passage are never visitor-participation products.', language), href: '/safaris/culture-outdoor-mix/' }, + { number: '04', title: pickText('安顺布依线・只选一座石寨', 'Anshun Buyi Route · One Stone Village', language), copy: pickText('在扁担山石头寨与高荡之间选一处,围绕具体建筑、具名工坊或主人安排半日;可与黄果树衔接但不压缩成停车拍照。', 'Choose either Biandanshan Shitouzhai or Gaodang for a half-day around a specific building, named workshop, or host. Pair with Huangguoshu without reducing it to a roadside photo stop.', language), href: '/safaris/classic-guizhou/' }, + ]; + + return ( +
+
+ {pickText('山谷梯田与木构村寨全景', +
+
+
+ + Miao / Dong / Buyi · One Named Village at a Time +

{pickText('原生民族村寨', 'Indigenous Ethnic Villages', language)}

+

{pickText('从西江、肇兴与黄岗,到岜沙和安顺布依石寨;不评判谁“更原生态”,只把每一处真正能进入的关系说明白。', 'From Xijiang, Zhaoxing, and Huanggang to Basha and Anshun’s Buyi stone villages, we do not rank who is “more authentic.” We clarify the relationship a visitor can honestly enter.', language)}

+ {pickText('比较四个村寨方向', 'Compare four village directions', language)} +
+
+
+ + + +
+
+ {pickText('不是“原生态排行榜”', 'Not an Authenticity Ranking', language)} +

{pickText('商业化程度不同,不等于文化价值有高低。', 'Different levels of commerce do not create a hierarchy of cultural value.', language)}

+

{pickText('客户文档希望按商业化梯度覆盖苗族、侗族与布依族代表村寨,同时使用了“世界最大、未受破坏、枪手部落、全石头村”等宣传表达。本页保留四组目的地方向,却把评判“真不真实”改为说明游客与村寨的实际关系:景区公共游览、按日期发生的节庆、公开演艺、具名主人接待或住宿。所有表演、住宿、家访、拍摄和费用都必须落到具体村名、具体日期与责任主体。', 'The brief asks for representative Miao, Dong, and Buyi villages along a commercialisation gradient, while using phrases such as “world’s largest,” “unspoiled,” “gunmen tribe,” and “entirely stone.” This page keeps the four destination directions but replaces a judgement of “realness” with the actual visitor relationship: public scenic visit, date-specific festival, public performance, named-host visit, or stay. Every performance, room, home visit, image, and fee must resolve to an exact village, date, and responsible party.', language)}

+
+
+
{pickText('目的地方向', 'Directions', language)}
{pickText('四组比较;一天只设一座主村寨', 'Four groups; one anchor village per day', language)}
+
{pickText('接待模式', 'Visitor Modes', language)}
{pickText('公共游览 / 具名接待 / 住宿', 'Public visit / named host / overnight', language)}
+
{pickText('节庆与大歌', 'Festival & Grand Song', language)}
{pickText('按传统语境与当年公开通知,不随到随演', 'Current notice and proper context, never on demand', language)}
+
{pickText('岜沙演艺', 'Basha Performance', language)}
{pickText('只按当日官方节目;不安排枪械或镰刀操作', 'Current official programme only; no weapon or sickle handling', language)}
+
{pickText('住宿', 'Accommodation', language)}
{pickText('房型、消防、行李与境外登记逐项确认', 'Room, fire, luggage, and international registration checked', language)}
+
{pickText('影像原则', 'Image Principle', language)}
{pickText('不偷拍住宅、仪式、儿童和居民日常', 'No covert images of homes, rites, children, or resident life', language)}
+
+
+ +
+
{pickText('暮色中的大型木构苗寨',
{pickText('客户文档对应西江千户苗寨 · 拍摄日期、机位与公开使用授权待归档', 'Client brief associates this with Xijiang · date, viewpoint, and publication rights require records', language)}
+
{pickText('山谷与梯田之间的西江木构村寨',
{pickText('西江村寨全景参考 · 只使用公开观景空间,不承诺图片中的客流与季节', 'Xijiang panorama reference · public viewpoints only; crowd level and season are not promised', language)}
+
{pickText('云雾山谷中的侗族鼓楼与村寨',
{pickText('客户提供侗寨氛围图 · 具体为肇兴或黄岗、日期与授权待确认', 'Client-supplied Dong-village atmosphere · exact village, date, and rights pending', language)}
+
{pickText('侗寨木构鼓楼群近景',
{pickText('鼓楼建筑参考 · 不用建筑照片证明当日必有侗族大歌或其他节目', 'Drum-tower architecture reference · the image does not prove a Grand Song or other programme will run', language)}
+
+ +
+
+
01 / Four Village Directions

{pickText('先选关系,再选村寨。', 'Choose the relationship, then the village.', language)}

{pickText('成熟景区、侗寨公共空间、景区演艺与具名社区接待各有价值,也各有不同的承诺边界。', 'A developed scenic area, Dong public space, scenic performance, and named-community hosting each have value—and different promise boundaries.', language)}

+
{villages.map((village) =>
{village.number}{village.label}

{village.title}

{village.copy}

{village.status}
)}
+
+
+ +
+
{pickText('山谷稻田与侗寨公共建筑',
+
+
02 / Village Planning

{pickText('把“想看什么”,先翻译成“被怎样接待”。', 'Translate “what to see” into “how to be hosted.”', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Hosting Gates

{pickText('进入一座村寨,也要知道何时停在门外。', 'Entering a village also means knowing when to remain outside.', language)}

{pickText('开放区域、主人同意与社区当日安排共同决定访问深度;游客兴趣不自动转化为进入住宅、仪式或人物镜头的权利。', 'Public space, host consent, and the community’s day determine visit depth. Visitor interest does not create a right to enter a home, rite, or portrait.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具体村寨、公共入口/动线、交通、预计步行、洗手间与可休息点', 'Exact village, public entrance or route, transport, walking, toilets, and rests', language)}
  • {pickText('具名住宿/主人/老师、接待时段、费用、包含内容、境外登记与取消规则', 'Named stay, host or teacher, time, fee, inclusions, international registration, and cancellation', language)}
  • {pickText('当日公开节目、拍摄范围、老人儿童安排,以及取消后的具名替代', 'Current public programme, image boundary, elder or child plan, and named fallback', language)}
+
{pickText('不能直接当作承诺', 'Claims that are not automatic promises', language)}
  • {pickText('“最原生态、完全未商业化、世界最大、私家避人机位”或居民随叫随到', '“Most authentic,” untouched by commerce, world’s largest, private crowd-free angle, or residents on demand', language)}
  • {pickText('黄岗随时唱大歌、喊天节全年可看、岜沙必鸣枪/必剃头或游客可参与工具操作', 'Huanggang song on demand, year-round Heaven-Calling, guaranteed Basha musket or haircut, or visitor tool handling', language)}
  • {pickText('“600 年全石头村”、未获同意的家访/儿童肖像,以及客户图片的未归档版权', 'A “600-year all-stone village,” unconsented home visit or child portrait, or undocumented client-image rights', language)}
+
+

{pickText('公开信息参考:贵州省政协与贵州省人大公开的西江吊脚楼、肇兴五座鼓楼和岜沙村资料,贵州省民政厅黄岗村资料及黄岗喊天节公开信息,镇宁县公开的扁担山石头寨与高荡资料,以及贵州省 2026 年政府定价景区清单。最终开放、节目、票价、住宿登记与社区接待以旅行日期的景区、属地和具名主人确认结果为准。', 'Public references include Guizhou CPPCC and People’s Congress material on Xijiang stilt houses, Zhaoxing’s five drum towers, and Basha; Guizhou Civil Affairs and public material on Huanggang and its Heaven-Calling Festival; Zhenning public material on Biandanshan Shitouzhai and Gaodang; and Guizhou’s 2026 government-priced scenic-area list. Live opening, programmes, price, guest registration, and hosting follow the scenic area, locality, and named host for the travel date.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('一座主村寨,一段可说清的关系。', 'One anchor village, one relationship we can explain.', language)}

+ +
+ + +
+ ); +} + +function HistoricAncientTownsDetailPage() { + const { language } = useTravelSite(); + const towns = [ + { + number: '01', + label: pickText('贵阳花溪・明代石城', 'Huaxi, Guiyang · Ming Stone Town', language), + title: pickText('青岩古镇', 'Qingyan Ancient Town', language), + copy: pickText( + '青岩堡设置于明洪武年间,今天可从石城墙、定广门、石牌坊、街巷、宗教建筑与地方小吃理解其六百余年积累。它离贵阳较近,适合半日,但“古镇公共空间”与收费景点/套票不是同一概念;城墙台阶、节假日客流、停车接驳和餐饮排队需要写进节奏。', + 'Qingyan Fort was established in the Hongwu period of the Ming dynasty. Its six centuries can be read through stone walls, Dingguang Gate, memorial arches, lanes, religious buildings, and local food. It suits a half-day from Guiyang, but public town space is not the same as ticketed attractions or a bundled pass. Wall steps, holiday crowds, parking or transfers, and food queues belong in the plan.', + language, + ), + status: pickText('2026 政府定价清单列景区门票与套票两种价格;正式报价说明具体包含景点,不只写“青岩门票”', 'The 2026 government-priced list separates entry and a bundled pass; the final quote names included sites, not just “Qingyan ticket”', language), + }, + { + number: '02', + label: pickText('黔东南・㵲阳河古城', 'Qiandongnan · Wuyang River City', language), + title: pickText('镇远古城', 'Zhenyuan Ancient Town', language), + copy: pickText( + '镇远自公元前 202 年置县,拥有 2,200 余年建置史;明清时期依托㵲阳河与湘黔滇驿道发展为商贸节点。古城本身仍是居民生活与商业并存的县城,青龙洞、石屏山、下㵲阳河游船等是不同收费项目,不能用“镇远一张票”或一张夜景图概括。', + 'Zhenyuan has more than 2,200 years of administrative history since its county was established in 202 BCE. In the Ming and Qing periods, the Wuyang River and Hunan-Guizhou-Yunnan routes supported a trading centre. The old town remains a lived-in county town where residents and commerce coexist; Qinglong Cave, Shiping Mountain, and Lower Wuyang cruises are separate paid products, not one “Zhenyuan ticket” or one night-view photograph.', + language, + ), + status: pickText('适合一晚慢住;灯光开启、游船、沿河噪声、洪水/暴雨与住宿位置均按当天确认', 'Best with one night; lighting, cruises, riverside noise, flood or rain, and the stay location are checked for the date', language), + }, + { + number: '03', + label: pickText('安顺平坝・屯堡村落', 'Pingba, Anshun · Tunbao Village', language), + title: pickText('天龙屯堡', 'Tianlong Tunbao Village', language), + copy: pickText( + '天龙属于安顺屯堡文化的一处具名村落。明代军屯与后续移民、屯田和各民族交往共同塑造了语言、服饰、石木建筑、饮食与地戏传统;它不是“六百年从未改变”的静态遗址。安顺地戏 2006 年列入首批国家级非遗,景区演武堂的定时短场与春节、农历七月等传统节令中的村寨地戏也应分开理解。', + 'Tianlong is one named village within Anshun’s Tunbao culture. Ming military settlement, later migration and farming, and exchange among communities shaped its language, dress, stone-and-timber architecture, food, and Dixi tradition; it is not a static relic “unchanged for 600 years.” Anshun Dixi entered the first national intangible-heritage list in 2006. Timed short scenic-area shows at Yanwutang should be understood separately from village Dixi in traditional periods such as Spring Festival and the seventh lunar month.', + language, + ), + status: pickText('地戏只按当日公开场次;面具、服饰、居民肖像、住宅与仪式空间分别征得同意', 'Dixi follows the day’s public schedule; masks, dress, portraits, homes, and ritual space require separate consent', language), + }, + ]; + const flow = [ + { title: pickText('先选一处历史主线', 'Choose One Historical Thread', language), copy: pickText('青岩看明代石城与宗教/市井层次;镇远看河运商贸与活态古城;天龙看屯堡聚落和地戏。一天不把三处压成同类打卡。', 'Use Qingyan for Ming stone-town and religious or street layers, Zhenyuan for river trade and a lived-in city, and Tianlong for Tunbao settlement and Dixi. Do not compress all three into one generic checklist.', language) }, + { title: pickText('拆开公共空间与收费项目', 'Separate Public Space & Tickets', language), copy: pickText('核对古镇入口、联票所含景点、城墙/寺庙/古建、山体、游船、演出和接驳,不用一个含糊票价代表所有内容。', 'Check town entry, bundled-pass sites, walls, temples or historic buildings, mountain access, cruises, performance, and transfer. One vague price cannot represent them all.', language) }, + { title: pickText('把真实步行与夜晚算进去', 'Plan Walking & the Night', language), copy: pickText('石板湿滑、城墙台阶、坡巷、夏季日晒、老人儿童、洗手间和休息点逐项说明;镇远过夜还要看河边住宿位置与夜间噪声。', 'State slick stone, wall steps, sloped lanes, summer sun, elders or children, toilets, and rests. A Zhenyuan night also depends on the riverside room and evening noise.', language) }, + { title: pickText('演出与居民生活分开', 'Separate Performance & Resident Life', language), copy: pickText('景区地戏按节目表,传统节令活动按社区通知;住宅、宗教空间、仪式、店铺经营和居民肖像不能因为位于景区就默认开放。', 'Scenic Dixi follows the programme; traditional-period activity follows community notice. Homes, religious spaces, rites, shops, and resident portraits are not automatically open because they lie inside a scenic area.', language) }, + { title: pickText('出发前确认当日变化', 'Confirm Live Changes', language), copy: pickText('复核开放、票务、维修、演出、游船、水位、暴雨、道路停车和夜景照明;不成立时切换为仍开放的具名古建、博物馆或城市慢行。', 'Recheck opening, tickets, restoration, performance, cruise, water level, rain, road or parking, and night lighting. If unavailable, switch to a named open building, museum, or town walk.', language) }, + ]; + const gates = [ + { title: pickText('历史表述与具体遗存', 'History & Specific Fabric', language), copy: pickText('建置史、堡城/城墙、驿道商贸、屯堡迁徙与具体建筑分别注明来源;不把整个现有街区都说成六百年原物。', 'Source administrative history, fort or wall, trade routes, Tunbao migration, and specific buildings separately. Do not call the entire present streetscape a 600-year-old original.', language) }, + { title: pickText('开放、门票与现场运营', 'Access, Tickets & Operations', language), copy: pickText('具名景区、收费项目、联票内容、预约、开放时间、演出场次、游船/接驳、维修关闭、价格与取消退款逐项确认。', 'Confirm the named site, paid item, pass inclusion, reservation, hours, performance, cruise or transfer, restoration closure, price, and cancellation or refund.', language) }, + { title: pickText('步行、水边与儿童长者', 'Walking, Waterfront & Families', language), copy: pickText('城墙和山体台阶、湿滑石板、临河护栏、夜路照明、拥挤、洗手间、儿童牵行、长者缩短路线和医疗联系写入方案。', 'Include wall and hill steps, slick stone, riverside barriers, night lighting, crowds, toilets, child hand-holding, shortened elder route, and medical contact.', language) }, + { title: pickText('文物、居民与影像边界', 'Heritage, Residents & Images', language), copy: pickText('不触摸攀爬文物;宗教/仪式/住宅空间、地戏后台、成人与未成年人肖像、商业拍摄、三脚架和无人机按许可。', 'Do not touch or climb heritage fabric. Religious, ritual, or home space, Dixi backstage, adult and minor portraits, commercial shoots, tripods, and drones follow permission.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('贵阳半日・青岩石城与小吃', 'Guiyang Half-Day · Qingyan Stone Town & Food', language), copy: pickText('用一条城门—石巷—具名历史建筑—地方小吃的短线完成,不把全部联票点位塞满;返贵阳前保留停车和客流缓冲。', 'Use a concise gate-lane-named-building-local-food route instead of filling every bundled-pass site, with parking and crowd buffer before returning to Guiyang.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('镇远一晚・河城日夜', 'One Night in Zhenyuan · River City by Day & Night', language), copy: pickText('白天选古巷、码头/祝圣桥与一处收费古建或山体,傍晚回到河岸;游船和夜景只作为按当天成立的加项。', 'By day, choose lanes, wharf or Zhusheng Bridge, and one ticketed historic building or hill. Return to the river at dusk; cruise and lighting remain date-specific additions.', language), href: '/safaris/classic-guizhou/' }, + { number: '03', title: pickText('安顺半日・天龙屯堡与地戏', 'Anshun Half-Day · Tianlong Tunbao & Dixi', language), copy: pickText('围绕村落公共动线、具名建筑讲解和当日演武堂短场安排;若地戏取消,保留建筑/移民史讲解,不临时要求村民开演。', 'Use the village public route, a named architecture interpretation, and the day’s short Yanwutang show. If Dixi is cancelled, keep the architecture and migration story—never ask residents for an impromptu show.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('㵲阳河穿过镇远古城的夜景', +
+
+ + Stone Fort / River Trade / Tunbao Village +

{pickText('历史古镇古村', 'Historic Towns & Villages', language)}

+

{pickText('青岩不是江南水乡,镇远不只是一张夜景,天龙屯堡也不是停住六百年的布景。沿石城、河运和军屯三条线读贵州。', 'Qingyan is not a Jiangnan water town, Zhenyuan is more than a night view, and Tianlong is not a set frozen for six centuries. Read Guizhou through stone fort, river trade, and military settlement.', language)}

+ {pickText('比较三处古镇古村', 'Compare three historic places', language)} +
+
+ +
+
{pickText('三条贵州历史线索', 'Three Guizhou Historical Threads', language)}

{pickText('山地古镇的价值,不在于复制一座江南水乡。', 'A mountain town is valuable without imitating a Jiangnan water town.', language)}

{pickText('客户文档把青岩、镇远与天龙屯堡放在同一章节,强调屯堡文化、盐运商贸和多民族融合。我们保留这三处,却不把它们做成同一种“古街夜景”:青岩从明代堡城与石构空间进入,镇远从 2,200 余年建置和㵲阳河商贸进入,天龙从安顺屯堡聚落与地戏进入。建筑年代、现存形态、演出、票务、夜景与居民生活分别说明,避免“整座古镇原样保存六百年”一类误导。', 'The brief groups Qingyan, Zhenyuan, and Tianlong around Tunbao culture, trade, and intercultural exchange. We keep the three without turning them into one generic old-street night scene: Qingyan begins with Ming fortification and stone space, Zhenyuan with more than 2,200 years of administration and Wuyang trade, and Tianlong with Anshun Tunbao settlement and Dixi. Building date, present fabric, performance, tickets, lighting, and resident life are stated separately, avoiding claims that an entire town remains unchanged for six centuries.', language)}

+
+
{pickText('三个方向', 'Three Places', language)}
{pickText('青岩 / 镇远 / 天龙屯堡', 'Qingyan / Zhenyuan / Tianlong Tunbao', language)}
+
{pickText('青岩', 'Qingyan', language)}
{pickText('明洪武年间设堡;半日石城线', 'Ming Hongwu-period fort; half-day stone-town route', language)}
+
{pickText('镇远', 'Zhenyuan', language)}
{pickText('公元前 202 年置县;建议一晚', 'County established in 202 BCE; one night preferred', language)}
+
{pickText('天龙', 'Tianlong', language)}
{pickText('安顺屯堡村落;地戏按当日场次', 'Anshun Tunbao village; Dixi by current schedule', language)}
+
{pickText('票务原则', 'Ticket Principle', language)}
{pickText('公共街区、单点门票与联票分别说明', 'Public streets, individual tickets, and passes separated', language)}
+
{pickText('夜景原则', 'Night Principle', language)}
{pickText('不保证固定亮灯、游船或同等机位', 'No guaranteed lighting, cruise, or identical viewpoint', language)}
+
+
+
+
{pickText('贵州山地古镇屋顶与寺院空间',
{pickText('客户文档对应青岩古镇氛围 · 具体机位、建筑名称、日期与授权待归档', 'Client brief associates this with Qingyan · exact viewpoint, building, date, and rights require records', language)}
+
{pickText('依山而建的石城墙与台阶',
{pickText('客户提供石城墙画面 · 是否为青岩定广门/具体墙段待确认,不作为全线开放证明', 'Client-supplied stone-wall image · exact Qingyan gate or wall section pending; not proof the full route is open', language)}
+
{pickText('镇远古城㵲阳河夜景全景',
{pickText('客户文档对应镇远夜景 · 灯光、季节、机位与公开使用授权待归档', 'Client brief associates this with Zhenyuan at night · lighting, season, viewpoint, and rights require records', language)}
+
{pickText('镇远古城沿河建筑与桥梁夜景',
{pickText('镇远河城氛围 · 不保证当日游船、同等亮灯或图片中的客流状态', 'Zhenyuan river-city atmosphere · no promise of cruise, identical lighting, or matching crowd level', language)}
+
+
01 / Three Historic Places

{pickText('一处古镇,一条历史主线。', 'One historic place, one legible historical thread.', language)}

{pickText('堡城、河城和屯堡村落分别决定停留方式,不用“古色古香”替代具体内容。', 'A fortified town, river city, and Tunbao village each require their own stay—specific content replaces a generic “ancient charm.”', language)}

{towns.map((town) =>
{town.number}{town.label}

{town.title}

{town.copy}

{town.status}
)}
+
{pickText('山地石城墙与陡峭台阶',
02 / Historic-Place Planning

{pickText('先读清楚历史,再走进街巷。', 'Read the history before entering the lanes.', language)}

    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
03 / Heritage & Visit Gates

{pickText('开放给游客的古城,也仍然是文物与生活现场。', 'A town open to visitors remains heritage and lived space.', language)}

{pickText('门票不能替代对文物、居民、宗教和演出的边界说明;任何“沉浸”都不意味着可进入未开放区域。', 'A ticket does not replace boundaries around heritage, residents, religion, and performance. “Immersion” never grants access to closed space.', language)}

{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具名古镇/村落、公共步行线、具体收费景点、开放时间与预计停留', 'Named town or village, public walk, specific paid sites, hours, and planned stay', language)}
  • {pickText('真实步行、台阶、停车/接驳、洗手间、休息点和老人儿童替代', 'Real walking, steps, parking or transfer, toilets, rests, and elder or child alternative', language)}
  • {pickText('当日地戏/游船/灯光核验,以及取消后的具名建筑或博物馆替代', 'Current Dixi, cruise, or lighting check and a named building or museum fallback', language)}
{pickText('不能直接当作承诺', 'Claims that are not automatic promises', language)}
  • {pickText('“整座六百年原样不变”“所有建筑都是明代原物”或“完全无商业化”', '“Unchanged for 600 years,” every building a Ming original, or entirely free of commerce', language)}
  • {pickText('镇远每天同等夜景/游船、天龙随到即演传统地戏或居民配合换装拍照', 'Identical Zhenyuan lighting or cruise daily, Tianlong Dixi on demand, or resident costume posing', language)}
  • {pickText('客户图片的具体机位、建筑、拍摄日期与未归档版权,以及无人机可飞', 'Exact client-image viewpoint, building, date, undocumented rights, or permission to fly a drone', language)}

{pickText('公开信息参考:贵州省人大公开的青岩、镇远、屯堡建筑与地戏资料,镇远县古城/古码头与城市保护规划,中国民族报 2026 年安顺地戏报道,以及贵州省 2026 年政府定价景区门票与景区内交通清单。最终开放、票价、联票内容、地戏场次、游船、夜景与交通以旅行当日景区和属地通知为准。', 'Public references include Guizhou People’s Congress material on Qingyan, Zhenyuan, Tunbao architecture, and Dixi; Zhenyuan county material on the old city, wharves, and conservation planning; a 2026 China Ethnic News report on Anshun Dixi; and Guizhou’s 2026 government-priced scenic-area and transport list. Live access, price, pass inclusions, Dixi, cruise, lighting, and transport follow the scenic area and local notice on the travel date.', language)}

+
04 / Route Pairing

{pickText('半日读一座石城,一晚读一座河城。', 'A half-day for a stone town; a night for a river city.', language)}

+ +
+ ); +} + +function QingyanBattleImmersiveDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('已有产品', 'Operating Product', language), + title: pickText('《战青岩》山地实景体验', 'Zhan Qingyan Field Experience', language), + copy: pickText( + '2026 年公开报道确认,项目由青岩古镇景区管理公司与贵州创新文化旅游开发公司共同打造,落地定广湖畔,设置三处实景战场区域。参与者可分配侦察、医务、担架、机枪、士兵或物资小队等角色,在山地小道、战场救护、障碍和模拟战斗中行进。', + 'Public reporting in 2026 confirms a joint project by the Qingyan scenic-area company and Guizhou Innovation Culture & Tourism. Beside Dingguang Lake, three field zones assign roles such as scout, medic, stretcher bearer, machine gunner, soldier, or supply team across mountain paths, rescue tasks, obstacles, and simulated battle.', + language, + ), + status: pickText('已运营不等于随时可订:日期、场次、组团方式、票价和个人游客入口仍按当期书面信息确认', 'Operating does not mean always bookable: date, session, group format, price, and individual access still require current written confirmation', language), + }, + { + number: '02', + label: pickText('客户构想', 'Brief Concept', language), + title: pickText('古镇街巷任务式剧本', 'Old-Town Mission Roleplay', language), + copy: pickText( + '客户文档提出在石巷、城墙和历史院落中,以红军战士、地下交通员、村民或守城者身份完成线索追踪、情报传递、NPC 互动和剧情解锁,并在结尾由讲解员区分史实与戏剧化处理。它与现有《战青岩》的场地、角色和玩法并不完全相同。', + 'The brief proposes clue trails, intelligence delivery, NPC interaction, and branching storylines through stone lanes, walls, and historic courtyards, with guests cast as soldiers, messengers, villagers, or defenders and a final debrief separating history from dramatization. This is not identical to the live product in venue, roles, or gameplay.', + language, + ), + status: pickText('目前作为二期共创方向,不把 3–4 小时、真人 NPC、年代服装和城墙战斗写成现成服务', 'A phase-two co-creation direction; 3–4 hours, live NPCs, period dress, and a wall battle are not presented as existing services', language), + }, + { + number: '03', + label: pickText('境外私家小团', 'Private International Group', language), + title: pickText('翻译与文化敏感版', 'Interpreted, Context-Led Format', language), + copy: pickText( + '面向境外客人时,优先确认是否可接受私家小团、是否有英文主持或随行翻译、服装与仿真道具规则,以及如何解释长征、交战方、牺牲和纪念语境。客人不愿角色扮演时,改为遗址讲解与历史步行,不强制进入战争模拟。', + 'For international guests, first confirm private-group access, English facilitation or an interpreter, dress and replica-prop rules, and how the Long March, opposing forces, loss, and commemoration are contextualised. Guests who decline roleplay receive a site-led historical walk rather than compulsory battle simulation.', + language, + ), + status: pickText('公开报道显示境外客人仅占少量;英文脚本、主持能力和跨文化说明尚无公开证据', 'Public reporting shows only a small international share; no public evidence yet confirms an English script, facilitation, or cross-cultural interpretation', language), + }, + ]; + const flow = [ + { title: pickText('确认当日可售版本', 'Confirm the Live Version', language), copy: pickText('向两家运营主体确认项目入口、场次、真实时长、最低/最高人数、散客或包团、价格、付款、取消和天气停运;不以新闻报道替代预约凭证。', 'Confirm entrance, session, real duration, group minimum and maximum, individual or private access, price, payment, cancellation, and weather closure with the two operating entities. A news report is not a booking voucher.', language) }, + { title: pickText('核对史实与空间', 'Map History to Place', language), copy: pickText('由具名历史顾问把 1935 年 4 月青岩相关战斗、狮子山/簸箕山等史迹与今天项目动线逐点对应;不能把整座古镇或所有院落笼统称为原战场。', 'A named historian maps the April 1935 Qingyan action and sites such as Lion and Boji hills against the present route. The entire town or every courtyard cannot be labelled the original battlefield.', language) }, + { title: pickText('匹配客人与角色', 'Match Guest & Role', language), copy: pickText('收集年龄、行动能力、心肺/骨关节情况、伤病与战争叙事接受度,再选择角色;儿童、长者和不适合匍匐/负重者需要观察或轻任务版本。', 'Collect age, mobility, cardio or joint conditions, injuries, and comfort with war narratives before assigning roles. Children, older guests, and those unsuited to crawling or load-carrying need an observer or gentle-task version.', language) }, + { title: pickText('确认语言、服装与道具', 'Confirm Language, Dress & Props', language), copy: pickText('明确英文主持、随行翻译、字幕/术语表、服装尺码与清洁、仿真枪械/爆破/烟雾接触规则、肖像拍摄和退出信号,不让翻译在现场临时猜测。', 'Confirm English facilitation, interpreter, glossary or captions, costume size and cleaning, replica-weapon, blast and smoke rules, image consent, and stop signals—without asking an interpreter to improvise terminology on site.', language) }, + { title: pickText('体验后做史实复盘', 'Close with a Historical Debrief', language), copy: pickText('结束后说明哪些角色、任务和场景属于戏剧化重构,哪些事件、人物与地点有史料或遗址依据,并给出选择退出、安静纪念或继续古镇步行的空间。', 'Close by distinguishing reconstructed roles, tasks, and scenes from sourced events, people, and places, while allowing guests to opt out, reflect quietly, or continue with a regular town walk.', language) }, + ]; + const gates = [ + { title: pickText('运营、票务与接待', 'Operation, Ticket & Hosting', language), copy: pickText('具名运营主体、当前产品说明、场次/容量、私家小团与境外客接待、价格、发票、取消退款、天气与景区关闭替代。', 'Named operator, current product sheet, session and capacity, private and international-group handling, price, invoice, cancellation or refund, weather, and scenic-area closure alternative.', language) }, + { title: pickText('历史与文物空间', 'History & Heritage Space', language), copy: pickText('剧本审校人、史料来源、遗址点位、真实与虚构标记,以及古城墙、院落、革命遗址和公共街巷的书面使用边界;不得攀爬、钉挂或损伤文物。', 'Script reviewer, historical sources, site mapping, fact-versus-fiction labels, and written limits for walls, courtyards, revolutionary sites, and public lanes; no climbing, fixing, or damage to heritage fabric.', language) }, + { title: pickText('爆破、烟雾与现场安全', 'Effects & Field Safety', language), copy: pickText('声效/爆破/烟雾的设备与物料、噪声和能见度控制、消防、急救、通讯、人员比例、雨天泥泞/山路、障碍退出、医疗转运和保险覆盖。', 'Equipment and materials for sound, blast, or smoke effects, noise and visibility control, fire, first aid, communication, staffing ratio, wet mountain terrain, obstacle exits, medical transfer, and insurance coverage.', language) }, + { title: pickText('年龄、语言与知情选择', 'Age, Language & Consent', language), copy: pickText('客户提出的“12 岁以上”不是已证实门槛;运营方应书面给出年龄、身高/体能、健康和陪同规则,并提供可理解的中英文风险说明、角色自愿选择、肖像同意和随时退出机制。', 'The brief’s “12+” is not a verified operating threshold. The operator must issue written age, size or fitness, health, and guardian rules, plus understandable Chinese and English risk information, voluntary role choice, image consent, and a stop-any-time mechanism.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('贵阳一日・实景体验版', 'Guiyang Day · Live Field Version', language), copy: pickText('仅在《战青岩》当日场次、语言支持与客人筛查通过后安排;前后保留换装、淋浴/清洁、用餐和情绪缓冲。', 'Use only when the live session, language support, and guest screening pass. Keep time around it for changing, washing, food, and emotional decompression.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('青岩半日・史迹步行版', 'Qingyan Half-Day · Historical Walk', language), copy: pickText('不参加战斗模拟时,以公开可进入的古镇与经核实红色史迹为主,由讲解员说明 1935 年事件和现今遗存,适合轻量人文客人。', 'Without battle simulation, use publicly accessible town space and verified revolutionary sites, with an interpreter explaining the 1935 events and surviving fabric. Suits gentler cultural travellers.', language), href: '/experiences/historic-ancient-towns/' }, + { number: '03', title: pickText('二期候选・古镇任务剧本', 'Phase Two · Old-Town Mission Script', language), copy: pickText('只有脚本审校、古镇/文物空间许可、演员与英文、居民和游客影响、声光电及保险全部成立后,才把客户构想制作成独立私家产品。', 'Develop the brief’s private old-town mission format only after script review, heritage and venue permission, actors and English, resident and visitor impact, effects, and insurance all pass.', language), href: '/contact/' }, + ]; + + return ( +
+
+ {pickText('客户提供并标注为战青岩的实景互动画面', +
+
+ + Operating Experience / Specialist Review +

{pickText('青岩战斗・实景沉浸演绎', 'Qingyan 1935: Immersive History', language)}

+

{pickText('《战青岩》已经落地运营;客户提出的古镇街巷剧本则仍是二期构想。先分清两种产品,再决定境外私家小团能预约哪一种。', 'Zhan Qingyan is now operating; the brief’s old-town mission roleplay remains a phase-two concept. We separate the two before deciding what a private international group can actually book.', language)}

+ {pickText('查看现有产品与构想边界', 'See live product vs concept', language)} +
+
+ +
+
{pickText('2026 年 8 月核验结论', 'Review as of August 2026', language)}

{pickText('它已经不是纸上概念,但也不是客户文档所写内容全部兑现。', 'It is no longer a paper concept—nor is every promise in the brief already delivered.', language)}

{pickText('官方与本地媒体已确认《战青岩》:2026 年 1 月联合打造,4 月 29 日起全面运营;截至 7 月 5 日报道累计接待超过 3 万人次,项目位于定广湖畔,使用山地地形、角色分工和模拟战场。客户文档描述的却是石巷、城墙、历史院落、线索追踪、情报传递、真人 NPC 与历史复盘。两者方向接近但不是同一个已被证明的产品,因此官网只把现有《战青岩》列为可询价候选,把古镇任务剧本列为二期共创。', 'Official and local reporting confirms Zhan Qingyan: jointly created in January 2026 and fully operating from 29 April. A 5 July report recorded more than 30,000 visits. It uses mountain terrain, assigned roles, and simulated battlefield zones beside Dingguang Lake. The brief instead describes stone lanes, walls, courtyards, clue trails, intelligence delivery, live NPCs, and a historical debrief. The directions overlap but are not the same evidenced product, so this page treats the live experience as an enquiry candidate and the old-town mission script as phase-two co-creation.', language)}

+
+
{pickText('史实基础', 'Historical Basis', language)}
{pickText('1935 年 4 月青岩一带红军战斗', 'Red Army action around Qingyan, April 1935', language)}
+
{pickText('现有产品', 'Live Product', language)}
{pickText('《战青岩》/ 定广湖畔三处实景区域', 'Zhan Qingyan / three field zones by Dingguang Lake', language)}
+
{pickText('公开运营', 'Public Operation', language)}
{pickText('2026-04-29 起全面运营;当日状态仍复核', 'Full operation from 29 Apr 2026; live status still rechecked', language)}
+
{pickText('客户方案', 'Brief Format', language)}
{pickText('3–4 小时古镇任务剧本 / 尚未证明为现有产品', '3–4-hour old-town roleplay / not evidenced as live', language)}
+
{pickText('年龄', 'Age', language)}
{pickText('客户建议 12+;运营方书面规则待取得', 'Brief suggests 12+; operator rule still required', language)}
+
{pickText('英文与私家团', 'English & Private Groups', language)}
{pickText('尚无公开证据;必须专项确认', 'No public evidence yet; specialist confirmation required', language)}
+
+
+
+
{pickText('战青岩角色互动与战术讲解画面',
{pickText('客户图片明确标注“战青岩” · 具体拍摄日期、场次、人物同意与公开使用授权仍待归档', 'Client image explicitly labelled Zhan Qingyan · date, session, participant consent, and publication rights still require records', language)}
+
{pickText('青岩山地石城墙与台阶氛围',
{pickText('客户提供石城氛围图 · 具体墙段、拍摄日期与公开使用授权待归档', 'Client-supplied stone-town context · exact wall, date, and publication rights require records', language)}
+
+
01 / Product Status

{pickText('现有实景项目、客户剧本构想、境外接待,分成三层。', 'Separate the live field product, brief concept, and international delivery.', language)}

{pickText('“已经有《战青岩》”不能自动证明客户文档里的每一项玩法都能预订。', 'The existence of Zhan Qingyan does not automatically make every activity in the brief bookable.', language)}

{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
{pickText('战青岩游客角色互动与现场讲解参考',
02 / Verification Flow

{pickText('先拿到当期产品资料,再为境外客人改编。', 'Obtain the current product file before adapting it for international guests.', language)}

    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
03 / History, Heritage & Safety Gates

{pickText('有真实战场感,更需要把历史、文物和风险说清楚。', 'A convincing battlefield makes historical, heritage, and safety clarity more important.', language)}

{pickText('角色扮演与国防教育可以增强理解,但不能模糊史实、强迫参与,也不能把爆破烟雾和山地障碍当成普通古镇步行。', 'Roleplay and defence education can deepen understanding, but must not blur facts, compel participation, or treat smoke, blast effects, and mountain obstacles as an ordinary town walk.', language)}

{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
{pickText('当前可以公开', 'Safe to publish now', language)}
  • {pickText('1935 年青岩相关战斗的史实基础与红军狮子山战斗遗址线索', 'The historical basis of the 1935 Qingyan action and the Lion Hill battle-site thread', language)}
  • {pickText('《战青岩》的具名联合运营方、定广湖畔场地、三处实景区域与公开角色类型', 'Named joint operators, Dingguang Lake setting, three field zones, and reported roles for Zhan Qingyan', language)}
  • {pickText('截至 2026 年 7 月公开报道的运营事实;正式方案继续做当日核验', 'Operating facts reported through July 2026, with live checks still required for a final plan', language)}
{pickText('尚不能直接承诺', 'Not an automatic promise', language)}
  • {pickText('客户构想中的石巷/城墙/院落任务剧本、线索追踪、情报传递与全部真人 NPC', 'The brief’s lane, wall, and courtyard mission script, clue trails, intelligence delivery, or full live-NPC format', language)}
  • {pickText('3–4 小时、12+、全程英文、私家包场、年代服装尺码齐全与每日固定场次', 'A 3–4-hour duration, age 12+, full English, private buyout, complete costume sizing, or fixed daily sessions', language)}
  • {pickText('城墙声光电战斗已获许可、所有游客可接触仿真道具、保险已覆盖境外客人', 'Permitted wall-based effects, universal replica-prop handling, or confirmed insurance for international guests', language)}

{pickText('公开信息参考:中国国防报 2026-05-20《贵州省贵阳市花溪区沉浸式体验,还原战火烽烟》;贵阳网 2026-07-06《累计接待游客超3万人次!〈战青岩〉沉浸式文旅项目掀起红色旅游热潮》;贵州省政协《青岩历史文化的三个视角》;贵阳市“十四五”文化和旅游发展规划。文物空间的使用还须遵守 2025 年 3 月 1 日起施行的修订《中华人民共和国文物保护法》及属地管理要求。', 'Public references: China National Defense News, 20 May 2026; Guiyang News, 6 July 2026; Guizhou CPPCC, “Three Perspectives on Qingyan History”; and Guiyang’s 14th Five-Year culture and tourism plan. Any heritage-space use must also follow the revised PRC Cultural Relics Protection Law effective 1 March 2025 and local management requirements.', language)}

+
04 / Route Pairing

{pickText('同一段历史,可以有三种强度。', 'One history, three levels of participation.', language)}

+ +
+ ); +} + +function LoushanguanBattleImmersiveMarchDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('现有互动演出', 'Operating Interactive Show', language), + title: pickText('“跟着团长攻打娄山关”', 'Follow the Commander at Loushan Pass', language), + copy: pickText( + '《娄山关大捷》实景演出已经运营多年。2026 年“五一”升级推出游客参与版本,公开报道显示参与者可穿演出服、跟随演员进入战壕与山林,在舞台烟火、声效和模拟战斗场景中完成互动。它位于娄山关大捷实景演艺景区,不等同于沿所有真实战斗点位徒步。', + 'The Loushan Pass Victory live show has operated for years. A participatory upgrade launched for May Day 2026, with public reporting showing guests in costume following actors through trenches and woodland amid stage effects, sound, and simulated combat. It operates in the live-performance scenic area and is not the same as hiking every original battle position.', + language, + ), + status: pickText('2026 年公开报道出现每日 13:30 场次;正式方案仍以运营方当期排期、容量与票种为准', 'A 13:30 daily session appeared in 2026 reporting; the final plan still follows current operator schedule, capacity, and ticket type', language), + }, + { + number: '02', + label: pickText('历史遗址参观', 'Battlefield Heritage Visit', language), + title: pickText('娄山关战斗遗址与诗词线', 'Battle Site & Poetry Route', language), + copy: pickText( + '以娄山关战斗遗址陈列馆、关口、大尖山/小尖山/点金山等公开点位和《忆秦娥・娄山关》展示为主,由讲解员说明 1935 年 2 月 25 日至 26 日战斗与遵义会议后的历史位置。今天山体上的诗词为大理石手迹碑,不应写成 1935 年留下的原始摩崖石刻。', + 'Use the battle-site exhibition hall, pass, publicly accessible Dajianshan, Xiaojian Mountain, and Dianjin Mountain positions, and the display of “Loushan Pass,” with interpretation of the 25–26 February 1935 battle and its place after the Zunyi Conference. The poem visible on the mountain is a later marble calligraphy panel, not an original 1935 cliff inscription.', + language, + ), + status: pickText('适合作为不参加战争模拟的默认版本;开放点位、接驳、步行与陈列馆状态按日期确认', 'The default for guests declining combat simulation; access, transfer, walking, and exhibition-hall status are checked by date', language), + }, + { + number: '03', + label: pickText('客户二期构想', 'Brief Phase-Two Concept', language), + title: pickText('4–5 小时山道行军剧本', '4–5-Hour Narrative Mountain March', language), + copy: pickText( + '客户提出从关下集结、沿“古驿道”上行、在历史点位观看演员分段演绎、登顶读诗并下山复盘。当前没有公开证据证明这条完整动线已经常态运营,也未证明现有演艺景区、战斗遗址和所谓古驿道属于同一条可连续执行路线。', + 'The brief proposes a foot-of-pass briefing, an ascent on an “ancient post road,” actor-led scenes at historical positions, summit poetry, and a descent debrief. No public evidence yet shows this complete route in regular operation or proves that the live-show area, battle heritage, and proposed old road form one continuous guest route.', + language, + ), + status: pickText('作为共创方案保留;先完成路线测绘、历史审校、场地许可、演员与英文、山地安全和撤离测试', 'Retained for co-creation only after route mapping, historical review, venue permission, actors and English, mountain safety, and evacuation testing', language), + }, + ]; + const flow = [ + { title: pickText('确认买的是哪一种产品', 'Define the Exact Product', language), copy: pickText('区分普通遗址参观、观看《娄山关大捷》、参与“跟着团长”以及尚未落地的山道剧本;分别取得入口、场次、时长、价格、容量、预约和取消资料。', 'Separate a heritage visit, watching the Loushan Pass Victory show, joining “Follow the Commander,” and the unbuilt narrative march. Obtain entrance, session, duration, price, capacity, booking, and cancellation for the exact choice.', language) }, + { title: pickText('把史实点与演艺场逐点对应', 'Map History Against the Set', language), copy: pickText('由具名历史顾问标明 1935 年战斗点位、现有遗址、陈列馆、纪念性诗词碑和演艺景区;舞台战壕、碉堡与爆破区不得自动表述为原始遗存。', 'A named historian maps 1935 battle positions, surviving sites, the exhibition hall, commemorative poetry panel, and performance area. Stage trenches, bunkers, and effects zones are not automatically original remains.', language) }, + { title: pickText('测出真实山地负荷', 'Measure the Real Mountain Load', language), copy: pickText('记录起终点、距离、累计爬升、台阶/坡度、路面、遮阴、洗手间、补水、信号、单向或折返和退出点;“铺装山道、中等强度”不能替代数据。', 'Record start and finish, distance, elevation gain, steps or gradient, surface, shade, toilets, water, signal, one-way or return pattern, and exits. “Paved, moderate trail” is not a substitute for measurements.', language) }, + { title: pickText('筛查客人与特效接受度', 'Screen Guests & Effects', language), copy: pickText('确认年龄、行动能力、心肺/骨关节、听力、呼吸系统、孕期、创伤或战争叙事敏感,以及对制服、仿真道具、烟雾、强声和火焰的接受度;参与必须自愿。', 'Confirm age, mobility, cardio or joint health, hearing, respiratory needs, pregnancy, trauma or war-narrative sensitivity, and comfort with uniforms, replicas, smoke, loud sound, and flame. Participation must be voluntary.', language) }, + { title: pickText('用史实复盘结束', 'End with Evidence, Not Spectacle', language), copy: pickText('演出后区分战斗史实、诗词意象、纪念设施与艺术重构;英文版本先经历史与语言双重审校,不把戏剧对白直接当作历史结论。', 'After the show, separate battle history, poetic imagery, commemorative installations, and artistic reconstruction. The English version receives historical and language review rather than treating dramatic dialogue as evidence.', language) }, + ]; + const gates = [ + { title: pickText('运营主体与当期票务', 'Operator & Live Ticketing', language), copy: pickText('具名经营公司、景区/演出/互动票种、每日场次、参与容量、服装与角色是否包含、私家小团、价格、付款、发票、迟到、取消退款和天气停演。', 'Named operator, scenic, show, and interactive ticket types, daily sessions, participation capacity, included costume or role, private-group handling, price, payment, invoice, late arrival, cancellation, and weather closure.', language) }, + { title: pickText('历史、遗址与文物边界', 'History, Site & Heritage', language), copy: pickText('剧本审校人、战斗点位来源、演艺场与遗址的空间关系、陈列馆和山体开放,以及战壕/弹坑/诗词碑的年代说明;不进入关闭区域,不触碰或布置文物本体。', 'Script reviewer, sources for battle positions, relationship between set and heritage, live access to the hall and mountains, and dating of trenches, shell holes, or poem panel; no closed-area entry or staging on heritage fabric.', language) }, + { title: pickText('火焰、烟雾、强声与道具', 'Flame, Smoke, Sound & Props', language), copy: pickText('特效物料和距离、点火/停机负责人、风向、烟雾成分、噪声、消防器材、观众隔离、仿真枪械管理、服装阻燃/清洁、急停口令、急救与保险逐项留档。', 'Document effect materials and distance, ignition and shutdown lead, wind, smoke composition, noise, fire equipment, audience separation, replica control, costume flame resistance and cleaning, stop commands, first aid, and insurance.', language) }, + { title: pickText('山地天气、体能与撤离', 'Mountain Weather, Fitness & Exit', language), copy: pickText('高温、暴雨、雷电、大雾、湿滑、落石和森林火险停运标准,真实爬升/路面、前后领队、补水休息、通信、退出接驳、医疗转运、儿童与长者替代,以及全程英文支持。', 'Closure thresholds for heat, rain, lightning, fog, slick ground, rockfall, and forest fire; real ascent and surface, lead and sweep, water and rests, communication, exit transfer, medical transport, child and older-guest alternatives, and full English support.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('互动演出半日', 'Participatory Show Half-Day', language), copy: pickText('只在当日互动票、场次、特效与客人筛查成立时参加;表演前后安排换装、听力/呼吸保护说明、清洁和恢复,不再叠加长距离徒步。', 'Join only when the live interactive ticket, session, effects, and guest screening pass. Allow changing, hearing and respiratory briefing, cleaning, and recovery, without adding a long hike.', language), href: '/contact/' }, + { number: '02', title: pickText('遗址与诗词半日', 'Battle Site & Poetry Half-Day', language), copy: pickText('陈列馆、公开战斗遗址、关口与诗词展示组成轻量历史线;避开模拟战斗,适合关注史实、摄影或不接受烟火特效的客人。', 'Use the exhibition hall, accessible battle sites, pass, and poetry display as a gentler history route, avoiding simulated combat for evidence-focused, photography, or effects-sensitive guests.', language), href: '/safaris/classic-guizhou/' }, + { number: '03', title: pickText('二期山道移动剧场', 'Phase Two Moving Mountain Theatre', language), copy: pickText('路线测绘、历史点位、演员调度、英文脚本、特效分区、公共游客影响和撤离演练全部通过后,才把客户的 4–5 小时构想做成私家产品。', 'Turn the brief’s 4–5-hour concept into a private product only after route mapping, historical positions, actor logistics, English script, effects zoning, public-visitor impact, and evacuation drill all pass.', language), href: '/contact/' }, + ]; + + return ( +
+
+ {pickText('客户提供的攻打娄山关实景演出画面', +
+
+ + Live Performance / Battlefield Heritage / Route Review +

{pickText('娄山关战役・山道沉浸行军', 'Loushan Pass: Battle & Immersive March', language)}

+

{pickText('现有游客参与演出已经成立;沿真实战斗遗址连续行军 4–5 小时的移动剧场仍待共创。先选“看遗址、看演出或参与演出”,再谈山道剧本。', 'A participatory battle show now exists; a 4–5-hour moving theatre across verified battlefield terrain does not yet. First choose heritage visit, show, or participation—then consider a scripted march.', language)}

+ {pickText('比较三种体验', 'Compare the three formats', language)} +
+
+ +
+
{pickText('2026 年 8 月核验结论', 'Review as of August 2026', language)}

{pickText('“实景演出”已经有,“山道移动剧场”还没有被证明。', 'The live show exists; the moving mountain theatre is not yet evidenced.', language)}

{pickText('娄山关战斗遗址和《娄山关大捷》实景演出均有长期公开记录;2026 年,现有演出升级出“跟着团长攻打娄山关”游客参与项目。客户原文进一步提出沿古驿道上行、在每个历史地标插入演员场景、登顶读诗、下山复盘,并承诺 4–5 小时、全员服装道具、完整英文剧本和专业英文历史向导。后半部分目前没有对应的运营、路线或英文接待证据,所以只作为二期产品蓝图。', 'Both the Loushan Pass battle heritage and live show have a long public record; in 2026, the show added the participatory “Follow the Commander” experience. The brief goes further: climb an old road, stage actors at each historical landmark, read poetry at the summit, and debrief on descent, with a 4–5-hour duration, costume and props for all, a full English script, and a specialist English history host. Those later promises lack matching operating, route, or English-delivery evidence and remain a phase-two blueprint.', language)}

+
+
{pickText('核心史实', 'Historical Core', language)}
{pickText('1935-02-25 至 26 娄山关战斗', 'Battle of Loushan Pass, 25–26 Feb 1935', language)}
+
{pickText('现有演出', 'Live Show', language)}
{pickText('《娄山关大捷》实景演出', 'Loushan Pass Victory live performance', language)}
+
{pickText('2026 新玩法', '2026 Addition', language)}
{pickText('“跟着团长攻打娄山关”游客参与版', 'Participatory “Follow the Commander” format', language)}
+
{pickText('客户构想', 'Brief Concept', language)}
{pickText('4–5 小时山道剧情徒步 / 待共创', '4–5-hour narrative mountain hike / under development', language)}
+
{pickText('体力口径', 'Physical Load', language)}
{pickText('距离、爬升、台阶和退出点尚待实测', 'Distance, gain, steps, and exits still require measurement', language)}
+
{pickText('英文与私家团', 'English & Private Groups', language)}
{pickText('客户需求成立;供应端能力尚待书面确认', 'Valid brief requirement; supplier capability unconfirmed', language)}
+
+
+
+
{pickText('攻打娄山关演出中的演员、烟火与冲锋场景',
{pickText('客户图片明确标注“攻打娄山关” · 可支持实景演出氛围,具体日期、场次、制作方、人物与商业使用授权仍待归档', 'Client image explicitly labelled “Attack Loushan Pass” · supports the live-show atmosphere; date, session, producer, participant consent, and commercial rights still require records', language)}
+ +
+
01 / Three Product Formats

{pickText('看演出、参与演出、走历史遗址,不是同一件事。', 'Watching, participating, and walking the heritage are different products.', language)}

{pickText('三种体验的时间、风险、票种和历史密度不同,报价前必须选清楚。', 'Each carries different timing, risk, ticketing, and historical depth; choose before quoting.', language)}

{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
{pickText('娄山关实景演出烟火与演员画面',
02 / Verification Flow

{pickText('先把演艺景区和真实战斗遗址画在同一张地图上。', 'Map the performance area and historic battlefield before combining them.', language)}

    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
03 / History, Effects & Mountain Gates

{pickText('火焰、烟雾和山路叠加时,不能只写“中等强度”。', 'Flame, smoke, and mountain terrain demand more than a “moderate” label.', language)}

{pickText('当前图片中的特效规模明显,客户又提出连续上山;产品上线必须同时通过演艺安全、游客筛查、山地天气、遗址保护和英文知情说明。', 'The supplied image shows substantial effects, while the brief adds a sustained climb. Launch requires performance safety, guest screening, mountain weather, heritage protection, and informed English communication to pass together.', language)}

{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
{pickText('当前可以公开', 'Safe to publish now', language)}
  • {pickText('娄山关战斗遗址、1935 年 2 月主要战斗与遵义会议后首次大捷的历史定位', 'Loushan Pass battle heritage, the February 1935 action, and its position as the first major victory after the Zunyi Conference', language)}
  • {pickText('《娄山关大捷》实景演出长期存在,以及 2026 年新增游客参与项目', 'The established live show and its 2026 participatory addition', language)}
  • {pickText('客户提供的一张“攻打娄山关”演出图,但图片权利与拍摄信息仍须归档', 'One client-supplied performance image labelled “Attack Loushan Pass,” subject to image-rights records', language)}
{pickText('尚不能直接承诺', 'Not an automatic promise', language)}
  • {pickText('沿原始古驿道连续 4–5 小时、每个历史地标都有演员和旁白、登顶剧情高潮', 'A continuous 4–5-hour original post-road hike, actors and narration at every landmark, or a summit climax', language)}
  • {pickText('全员服装道具、完整英文剧本、专业英文历史向导、景区门票和演员协调全部包含', 'Costume and props for all, full English script, specialist English history host, tickets, and actor coordination all included', language)}
  • {pickText('客户所称“铺装中等山道”已完成测绘,或大型火焰/烟雾/仿真枪械对所有客人安全', 'A surveyed “moderate paved mountain trail,” or large flame, smoke, and replicas being safe for every guest', language)}

{pickText('公开信息参考:人民日报海外版 2021-07-23 娄山关景区报道;人民网 2026-07-22 红色文旅报道;贵州广播电视台 2026 年“五一”活动与暑期项目报道;解放军报/国防部娄山关战斗史资料;中共贵州省委党史研究室相关史料。公开报道中的历史票价与场次不作为本官网现行报价,全部以出行日期的景区和运营方书面信息为准。', 'Public references: People’s Daily Overseas Edition on Loushan Pass, 23 July 2021; People.cn on red-culture tourism, 22 July 2026; Guizhou Broadcasting reports on 2026 May Day and summer products; PLA Daily / Ministry of National Defense history of the battle; and Guizhou Party History Institute material. Historical prices or sessions in reporting are not current quotes and must be replaced by written scenic-area and operator information for the travel date.', language)}

+
04 / Route Pairing

{pickText('先选择参与强度,再决定是否走山。', 'Choose participation intensity before adding the mountain.', language)}

+ +
+ ); +} + +function KarstGlampingDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('优先推荐', 'Preferred', language), + title: pickText('具名经营型营地', 'Named Serviced Camp', language), + copy: pickText( + '选择已有经营主体、固定营位或帐篷、床品、卫浴、饮水、电力、夜间值守和车辆/步行入口的营地。正式方案写明营地名称、房型、真实图片、容量、价格、服务清单、取消规则和天气备用。', + 'Choose a named operator with fixed pitches or tents, bedding, bathrooms, potable water, power, night support, and clear vehicle or walking access. The final plan states the camp, room type, real images, capacity, price, inclusions, cancellation, and weather fallback.', + language, + ), + status: pickText('最接近“野趣与舒适并存”,也最容易核验消防、食品和住宿责任', 'Closest to wild comfort while keeping fire, food, and accommodation duties verifiable', language), + }, + { + number: '02', + label: pickText('稳定备选', 'Reliable Fallback', language), + title: pickText('山地景观住宿 + 夜间观星', 'Mountain Stay + Stargazing', language), + copy: pickText( + '当风雨、雷电、低温或场地资料不足时,以有固定建筑、卫浴和室内空间的山地住宿承接度假感,再在安全平台安排短时夜观。客户本组3张图片更接近这一类型,而不是帐篷营地。', + 'When wind, rain, lightning, cold, or site evidence is weak, use a mountain stay with a fixed building, bathroom, and indoor shelter, then add a short sky watch from a safe platform. The three images supplied for this section resemble this format rather than a tent camp.', + language, + ), + status: pickText('不强求帐篷形式,优先保证睡眠、卫浴、避雨和随时回到室内', 'No forced tent aesthetic; prioritise sleep, bathrooms, rain shelter, and immediate indoor return', language), + }, + { + number: '03', + label: pickText('专项候选', 'Special Setup Candidate', language), + title: pickText('私家临时营地搭建', 'Private Pop-Up Camp', language), + copy: pickText( + '只有用地/景区/社区授权、生态承载、防火、雷电避险、抗风营帐、厕所与污水、饮水餐食、照明通信、夜间安保和撤离路线全部书面明确时才搭建。天坑底部、峡谷河畔和保护地不因“秘境”而降低门槛。', + 'Set up only when land, scenic-area, or community authority; ecological capacity; fire and lightning shelter; wind-rated tents; toilets and wastewater; water and food; lighting and communication; night security; and evacuation are all documented. A sinkhole, riverside canyon, or protected site gets no lower bar for being “hidden.”', + language, + ), + status: pickText('不具名点位、不清楚土地性质或没有恶劣天气固定避难空间时,不执行', 'No operation with an unnamed site, unclear land status, or no fixed severe-weather shelter', language), + }, + ]; + const flow = [ + { title: pickText('先定义舒适度与客人条件', 'Define Comfort & Guests', language), copy: pickText('确认日期、人数、儿童/长辈、行动能力、睡眠与床型、独立卫浴、饮食过敏、夜间活动、用电和对帐篷的真实接受度。', 'Confirm dates, group, children or elders, mobility, sleep and bed needs, private bathroom, diet and allergies, night activity, power, and real tolerance for tent living.', language) }, + { title: pickText('选择具名营地或固定住宿', 'Choose a Named Camp or Stay', language), copy: pickText('先取得经营主体、具体位置、房型/营位、设施、真实现场图、容量、价格和当季开放状态;图片不能替代场地资料。', 'Obtain the operator, exact site, room or pitch, facilities, real on-site images, capacity, price, and seasonal opening before relying on any mood image.', language) }, + { title: pickText('核对场地、火源与生活保障', 'Check Site, Fire & Living Support', language), copy: pickText('确认用地和可进入区域、森林/景区规则、明火与烹饪、电力燃气、饮用水、食物、厕所、洗浴、垃圾污水、虫害和夜间值守。', 'Confirm land and access, forest or scenic rules, flame and cooking, power or gas, potable water, food, toilets, showers, waste and wastewater, insects, and night staffing.', language) }, + { title: pickText('出发前两次天气决策', 'Two Weather Decisions', language), copy: pickText('出发前48小时复核趋势,当天下午按雷电、风、雨、温度、地表积水、山洪和能见度做最终 Go / No-Go;不适合时切换固定住宿。', 'Review the trend 48 hours out, then make the final Go / No-Go that afternoon using lightning, wind, rain, temperature, surface water, flash flood, and visibility; switch to fixed lodging when unsuitable.', language) }, + { title: pickText('入住、夜观与无痕离开', 'Stay, Sky Watch & Leave No Trace', language), copy: pickText('先做逃生、照明、通信、儿童和野生动物说明;夜观只在安全区域短时进行。次日清点人员和装备,分类带走垃圾并核对场地恢复。', 'Brief escape, lighting, communication, children, and wildlife first; sky watching stays short and within a safe area. Next morning count people and equipment, remove sorted waste, and confirm site restoration.', language) }, + ]; + const gates = [ + { title: pickText('场地、经营与住宿责任', 'Site, Operation & Accommodation', language), copy: pickText('具名主体、用地/景区/社区授权、经营与收费、真实营位/房型、容量、住宿登记、责任联系人和取消退款规则。', 'Named entity, land/scenic/community authority, operation and pricing, real pitch or room, capacity, guest registration, responsible contact, and cancellation or refund rules.', language) }, + { title: pickText('天气、防雷与撤离', 'Weather, Lightning & Evacuation', language), copy: pickText('雷电预警接收、抗风雨阈值、山洪/落石/积水、固定避难空间、照明通信、夜间路线、车辆接回和医疗转运。', 'Lightning warnings, wind and rain limits, flash flood, rockfall and ponding, fixed shelter, lighting and communication, night route, vehicle recovery, and medical transfer.', language) }, + { title: pickText('防火、电力与餐卫', 'Fire, Power, Food & Sanitation', language), copy: pickText('森林防火期/禁火令、明火和篝火许可、燃气与电力、灭火器材、饮用水、厨房/食品、过敏原、厕所、洗浴、垃圾和污水。', 'Forest fire season or bans, flame and campfire permission, gas and power, extinguishers, potable water, kitchen and food, allergens, toilets, showers, waste, and wastewater.', language) }, + { title: pickText('夜间值守、儿童与环境', 'Night Staff, Children & Environment', language), copy: pickText('24小时联系人和巡查、儿童临崖/临水/火源边界、野生动物与虫害、噪声照明控制、生态承载和离场恢复记录。', '24-hour contact and patrol, child boundaries near edges, water, or fire, wildlife and insects, noise and light control, ecological capacity, and restoration record.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('使用一晚具名经营营地或山地景观住宿,车辆可达、独立卫浴和天气备选优先。', 'Use one night at a named serviced camp or mountain-view stay, prioritising vehicle access, a private bathroom, and weather fallback.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('放在轻至中等活动日后,不与高风险水上、洞穴或绳索项目叠加;第二天保留较晚出发。', 'Place after a light or moderate activity day, not beside high-risk water, cave, or rope work, with a later departure next morning.', language), href: '/safaris/outdoor-coffee/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('与村寨、田园步行或山野咖啡组合;天气不佳时直接切换固定建筑住宿和室内晚餐。', 'Pair with a village, country walk, or mountain coffee; poor weather switches directly to fixed lodging and an indoor dinner.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('喀斯特峰林谷地中的帐篷营地', +
+
+
+ + Unique Local Experiences / 04 +

{pickText('喀斯特野奢星空露营', 'Karst Glamping: Starry Sky Camping', language)}

+

{pickText('先确认今晚能安全、舒适地睡在哪里,再把星空留给天气。', 'Secure a safe, comfortable night first—and leave the stars to the weather.', language)}

+ {pickText('查看三种住宿方式', 'See the three stay formats', language)} +
+
+
+ + + +
+
+ {pickText('当前定义', 'Current Definition', language)} +

{pickText('“野奢”不是一顶漂亮帐篷,而是一整夜有人负责。', 'Glamping is not a beautiful tent—it is a whole night with clear responsibility.', language)}

+

{pickText('客户文档提出峰林田园、峡谷河畔和天坑秘境三类露营,并承诺床品、卫浴、餐食、搭撤与24小时值守。本组3张图片实际展示的是山地景观酒店和观景平台,没有帐篷、夜景或营地设施。第一版保留这些图片作为舒适度与景观参考,不把它们认定为露营地,也不在具名场地资料补齐前开放直接预订。', 'The brief proposes peak-country, riverside-canyon, and sinkhole camping with bedding, bathrooms, food, setup, and 24-hour staffing. The three supplied images actually show a mountain-view lodge and viewing decks, with no tents, night scene, or camp facilities. This first version treats them as comfort and view references—not proof of a campground—and keeps direct booking closed until a named site is documented.', language)}

+
+
+
{pickText('当前状态', 'Current Status', language)}
{pickText('候选体验;具名营地与运营资料补齐后开放', 'Candidate experience; opens after named camp and operating records', language)}
+
{pickText('场景候选', 'Setting Candidates', language)}
{pickText('峰林田园 / 合规山地营地 / 固定景观住宿', 'Peak-forest countryside / compliant mountain camp / fixed view lodging', language)}
+
{pickText('建议时长', 'Suggested Time', language)}
{pickText('1 晚为主;连续住宿需复核洗浴、餐食与天气', 'Primarily one night; longer stays need bathroom, food, and weather review', language)}
+
{pickText('适合', 'Best For', language)}
{pickText('情侣、亲子、轻户外与重视睡眠舒适度的自然爱好者', 'Couples, families, gentle outdoor travellers, and nature lovers who value sleep comfort', language)}
+
{pickText('星空说明', 'Sky Note', language)}
{pickText('银河、无云、低光污染和日出均受日期与天气影响,不保证', 'Milky Way, clear sky, low light pollution, and sunrise depend on date and weather', language)}
+
{pickText('默认备选', 'Default Fallback', language)}
{pickText('固定山地住宿 / 室内晚餐 / 安全平台短时夜观', 'Fixed mountain stay / indoor dinner / short sky watch from a safe deck', language)}
+
+
+ +
+
{pickText('喀斯特谷地中的普通帐篷营位参考',
+
{pickText('云海景观平台与水面',
+
{pickText('固定山地住宿的室内休息空间',
+
{pickText('山地景观平台上的旅行者',
+
+ +
+
+
01 / Stay Formats

{pickText('先选经营与庇护等级,再选“秘境”视角。', 'Choose operation and shelter before the “hidden” view.', language)}

{pickText('经营型营地、固定住宿和私家临时搭建对用地、消防、卫浴、值守与撤离有不同证据要求。', 'A serviced camp, fixed stay, and private pop-up require different evidence for land, fire, bathrooms, staffing, and evacuation.', language)}

+
{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
+
+ +
+
{pickText('带固定室内庇护的山地景观住宿',
+
+
02 / Operating Flow

{pickText('星空可以落空,睡眠、卫浴和撤离不能。', 'Stars may not appear. Sleep, sanitation, and exit cannot fail.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Launch Gates

{pickText('没有固定避难空间和夜间责任人,就不在山里过夜。', 'No fixed shelter or accountable night lead means no mountain overnight.', language)}

{pickText('“远离光污染、篝火闲谈、银河星空、清晨鸟鸣、24小时值守和卫浴保障”都必须变成具体场地、日期、设施与人员证据。', '“No light pollution, campfire talk, Milky Way, birds at dawn, 24-hour staff, and bathroom support” must become site-, date-, facility-, and person-specific evidence.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具名营地/住宿、经营主体、实际房型/营位、现场图、容量与价格', 'Named camp or stay, operator, real room or pitch, on-site images, capacity, and price', language)}
  • {pickText('床型床品、独立/公共卫浴、饮水餐食、电力、值守、停车与步行距离', 'Bed and linen, private or shared bathroom, water and food, power, staffing, parking, and walk', language)}
  • {pickText('天气阈值、固定避难空间、取消/替代住宿、接驳与撤离方式', 'Weather limits, fixed shelter, cancellation or alternative stay, transfer, and evacuation', language)}
+
{pickText('必须按场地和日期复核', 'Claims requiring site-and-date proof', language)}
  • {pickText('图片是否为实际营地,以及峰林、天坑、峡谷河畔的具体地理归属', 'Whether images show the actual camp and the exact peak, sinkhole, or riverside location', language)}
  • {pickText('银河、无云日出、远离光污染、无人打扰、鸟鸣唤醒或“独享秘境”', 'Milky Way, cloudless sunrise, no light pollution, solitude, birds at dawn, or an “exclusive hidden site”', language)}
  • {pickText('篝火/野炊可用、24小时值守、卫浴保障、私家搭撤与天坑/河边过夜', 'Campfire or cooking, 24-hour staffing, bathroom guarantee, private setup, or sinkhole/riverside overnight', language)}
+
+

{pickText('执行参考:贵州省森林防火条例、贵州省农村消防管理规定及中国气象局《旅游景区雷电防护安全规范》。森林防火期、禁火令、雷电预警和实际开放状态仍按属地与日期复核。', 'Operating references: Guizhou Forest Fire Prevention Regulations, Guizhou Rural Fire Management Rules, and the China Meteorological Administration tourism lightning-safety standard. Fire season, bans, lightning alerts, and opening status remain local and date-specific.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('把一晚山野放进路线,不把整条路线押给天气。', 'Put one mountain night into the route—not the whole route at the mercy of weather.', language)}

+ +
+ + +
+ ); +} + +function MiaoCraftDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('蜡染主题', 'Batik Focus', language), + title: pickText('纹样理解 + 点蜡或染色', 'Motif Reading + Wax Drawing or Dyeing', language), + copy: pickText( + '由具名工坊先讲清当地纹样、工具和工序,再根据时间选择点蜡、染色、脱蜡或成品讲解中的可开放环节。短课以理解和完成小样为主,不承诺从白布到复杂成品的全流程。', + 'A named studio first explains local motifs, tools, and process, then opens whichever stage fits the day—wax drawing, dyeing, wax removal, or finished-work interpretation. A short session focuses on understanding and a small sample, not a full complex piece from blank cloth.', + language, + ), + status: pickText('建议 2–3 小时;热蜡、染料、通风、皮肤敏感和作品干燥单独确认', 'Allow 2–3 hours; confirm hot wax, dye, ventilation, skin sensitivity, and drying separately', language), + }, + { + number: '02', + label: pickText('苗绣主题', 'Embroidery Focus', language), + title: pickText('纹样故事 + 一种基础针法', 'Motif Story + One Foundational Stitch', language), + copy: pickText( + '不把苗绣简化成通用刺绣课。由具名老师选择一种适合初学者的纹样或针法,说明当地服饰与图案语境,再完成可在课程时间内收尾的小型练习。', + 'This is not a generic embroidery class. A named teacher selects one beginner-suitable motif or stitch, explains its local dress and visual context, and guides a small exercise that can honestly finish within the session.', + language, + ), + status: pickText('建议 1.5–2.5 小时;针具、视力/精细动作、儿童年龄和成品规格按老师确认', 'Allow 1.5–2.5 hours; needles, eyesight and fine-motor needs, child age, and piece size are teacher-confirmed', language), + }, + { + number: '03', + label: pickText('双主题半日', 'Combined Half-Day', language), + title: pickText('先读懂一块布,再选择动手重点', 'Read the Cloth First, Then Choose the Hands-On Focus', language), + copy: pickText( + '只有同一具名工坊能够真实提供两项内容、老师和材料时,才安排蜡染与苗绣半日组合。文化讲解可覆盖两项,动手部分仍只选择一个重点,避免课程变成两次匆忙打卡。', + 'Combine batik and embroidery only when one named studio genuinely has both teachers and materials. Interpretation may cover both, while the hands-on portion still chooses one focus so the half-day does not become two rushed check-ins.', + language, + ), + status: pickText('建议 4–5 小时;不默认一位老师同时代表所有地区、支系和技艺', 'Allow 4–5 hours; one teacher does not automatically represent every place, community, or technique', language), + }, + ]; + const flow = [ + { title: pickText('锁定具名工坊与老师', 'Name the Studio and Teacher', language), copy: pickText('确认地址、授课人真实身份与技艺范围、课程是否日常开放、容量、费用、取消和当天接待窗口。', 'Confirm address, teacher identity and actual craft scope, whether the lesson operates normally, capacity, fee, cancellation, and the date-specific hosting window.', language) }, + { title: pickText('选择一个真实课程目标', 'Choose One Honest Learning Goal', language), copy: pickText('在蜡染小样、苗绣基础针法或双主题文化讲解中选定重点;根据年龄、精细动作、皮肤敏感和可用时间调整。', 'Choose a small batik sample, one embroidery stitch, or combined cultural interpretation; adapt around age, fine-motor ability, skin sensitivity, and available time.', language) }, + { title: pickText('核对材料、工具与语言', 'Check Materials, Tools & Language', language), copy: pickText('写明使用蜂蜡或其他蜡材、天然蓝靛或其他染料、热源、针具、围裙/手套、通风与英文讲解由谁完成。', 'State the wax material, natural indigo or other dye, heat source, needles, apron or gloves, ventilation, and exactly who provides English interpretation.', language) }, + { title: pickText('现场再确认同意与安全', 'Reconfirm Consent & Safety On Site', language), copy: pickText('开课前说明热蜡、染料沾染、针具和儿童操作边界;人物、家庭空间、作品与制作过程拍摄均再次征得同意。', 'Before starting, explain hot wax, dye staining, needles, and child boundaries; reconfirm consent for portraits, homes, works, and the making process.', language) }, + { title: pickText('完成、干燥或后寄', 'Finish, Dry or Ship Later', language), copy: pickText('离开前说明作品完成度、是否需要脱蜡/清洗/晾干、当天带走或后寄、包装、邮费和破损责任。', 'Before departure, state the finish level, any wax removal, washing, or drying needed, same-day collection or later shipping, packaging, postage, and damage responsibility.', language) }, + ]; + const gates = [ + { title: pickText('身份与课程真实性', 'Identity & Lesson Reality', language), copy: pickText('具名工坊、老师姓名与实际技艺、传承人称号的官方名录依据、课程内容、日常接待能力、容量、费用和取消。', 'Named studio, teacher and actual craft, official-list basis for any heritage-bearer title, lesson content, normal hosting ability, capacity, fee, and cancellation.', language) }, + { title: pickText('材料与作品边界', 'Material & Piece Boundary', language), copy: pickText('蜡材、染料是否为天然蓝靛、布料与线材、可操作工序、成品尺寸、干燥/脱蜡/清洗、当天带走或后寄。', 'Wax, whether the dye is natural indigo, cloth and thread, available process, piece size, drying, wax removal, washing, same-day collection, or shipping.', language) }, + { title: pickText('热蜡、针具与儿童', 'Hot Wax, Needles & Children', language), copy: pickText('热源稳定、防烫桌面、通风、染料接触、围裙/手套、针具回收、洗手与急救;儿童年龄和成人陪同由老师确认。', 'Stable heat, burn-safe surface, ventilation, dye contact, aprons or gloves, needle control, handwashing, and first aid; teacher confirms child age and adult supervision.', language) }, + { title: pickText('英文、拍摄与结算', 'English, Photography & Payment', language), copy: pickText('英文由老师、译员或司导谁来完成;哪些人物/空间可拍、图片可否公开;工坊费、老师费、材料、翻译和寄送分别结算。', 'Who provides English—the teacher, interpreter, or guide; who and what may be photographed and published; separate settlement for studio, teacher, materials, interpretation, and shipping.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('把一项 2–3 小时具名工坊放在安顺、丹寨或黔东南转场中,避免为了手作单独往返。', 'Place one named 2–3 hour studio inside an Anshun, Danzhai, or Qiandongnan transfer rather than making a separate return journey.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('亲子人文慢游', 'Family Heritage Slow Travel', language), copy: pickText('优先选择低温蜡材或苗绣入门,把儿童年龄、成人陪同、工具分配和作品预期提前写清。', 'Prefer lower-risk wax handling or introductory embroidery, with child age, adult supervision, tool allocation, and piece expectations written in advance.', language), href: '/safaris/family-heritage/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('与村寨慢住或梯田步行组成完整人文日,但工坊本身保留足够时间,不再叠加第二项重体验。', 'Build a full culture day with a village stay or terrace walk while preserving real studio time and avoiding a second major activity.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('贵州蜡染工坊内的蓝染作品与工作台', +
+
+
+ + Human Culture / 01 +

{pickText('苗族蜡染・苗绣', 'Miao Batik & Embroidery', language)}

+

{pickText('先认识纹样、材料和时间,再在具名老师带领下完成一段真实、不过度承诺的手作。', 'Begin with motif, material, and time—then make one honest piece with a named teacher and no inflated promises.', language)}

+ {pickText('查看三种体验方式', 'See Three Experience Formats', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('不是把蓝色带回家,而是先理解一块布为什么需要这么多时间。', 'The point is not simply to take blue home, but to understand why one cloth asks for so much time.', language)}

+

{pickText('客户文档提出由“第三代传承人”讲授、使用天然蓝靛、全程英文并把成品带走。第一版保留这些体验期待,但将它们全部转为具名工坊的确认项:传承人身份要有公开名录依据,天然蓝靛要核对实际染缸,英文要说明具体人员,作品则要写清完成、干燥或后寄方式。蜡染与苗绣是两门不同的技艺,不默认一次短课全部完成。', 'The brief asks for third-generation artisans, natural indigo, full English interpretation, and a take-home piece. This first version treats every one as a named-studio confirmation: heritage status needs an official-list basis, natural indigo needs the actual dye vat, English needs a named person, and the piece needs a clear finish, drying, or shipping plan. Batik and embroidery are distinct crafts and are not both completed by default in one short session.', language)}

+
+
+
{pickText('地区候选', 'Area Candidates', language)}
{pickText('丹寨 / 安顺 / 黔东南;最终以具名工坊为准', 'Danzhai / Anshun / Qiandongnan; final location follows the named studio', language)}
+
{pickText('建议时长', 'Suggested Time', language)}
{pickText('单项 1.5–3 小时;双主题半日 4–5 小时', 'One focus 1.5–3 hours; combined half-day 4–5 hours', language)}
+
{pickText('体验重点', 'Core Focus', language)}
{pickText('纹样语境 + 一项可真实完成的动手环节', 'Motif context plus one honestly achievable hands-on stage', language)}
+
{pickText('语言', 'Language', language)}
{pickText('老师直讲、专业译员或英文司导三选一并具名', 'Name one: teacher-led English, interpreter, or English-speaking guide', language)}
+
{pickText('材料', 'Materials', language)}
{pickText('蜡材、蓝靛/染料、布料与线材逐项确认', 'Confirm wax, indigo or other dye, cloth, and thread item by item', language)}
+
{pickText('作品', 'Take-Home Piece', language)}
{pickText('当天带走、完成后寄送或仅做练习,按课程确认', 'Same-day collection, later shipping, or practice only, confirmed by lesson', language)}
+
+
+ +
+
{pickText('客户提供的蜡染制作人物实拍',
{pickText('客户提供实拍 · 人物身份与公开使用授权需归档', 'Client-supplied photography · identity and publication permission to be recorded', language)}
+
{pickText('手作桌面的布料、线材与工具',
+
{pickText('客人在手作桌旁进行精细操作',
+
{pickText('布面蜡绘与蓝染纹样细节',
+
+ +
+
+
01 / Experience Formats

{pickText('一堂好课不靠项目多,而靠一个目标有足够时间完成。', 'A good lesson is not measured by how many crafts it stacks, but by giving one goal enough time.', language)}

{pickText('三种方式都是策划框架;只有具名工坊、老师、材料、语言和日期同时成立,才会写入正式报价。', 'These are planning frameworks; a format enters the final quote only when studio, teacher, materials, language, and date all align.', language)}

+
{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
+
+ +
+
{pickText('蜡染制作过程中的手部动作与布面',
+
+
02 / Experience Flow

{pickText('从“谁来教”开始,到“作品怎么离开工坊”结束。', 'Start with who teaches and finish with how the piece leaves the studio.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Confirmation Gates

{pickText('不能确认老师、材料和作品完成方式,就不能承诺“传承人课、天然蓝靛、全程英文、成品带走”。', 'If teacher, material, and finish are not confirmed, we cannot promise a master artisan, natural indigo, full English, or a take-home piece.', language)}

{pickText('公开资料可以证明苗族蜡染与苗绣的非遗价值,但不能替代某一家工坊当天真实提供的课程和接待条件。', 'Public records establish the heritage value of Miao batik and embroidery, but cannot replace the real lesson and hosting conditions of one studio on one date.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('正式方案可以承诺', 'What a final plan can promise', language)}
  • {pickText('具名工坊、具名老师、实际技艺、课程目标、时长、容量、费用与取消规则', 'Named studio and teacher, actual craft, learning goal, duration, capacity, fee, and cancellation', language)}
  • {pickText('具体材料、工具、安全措施、英文人员与允许拍摄/公开的范围', 'Exact materials, tools, safety measures, English support, and photography or publication scope', language)}
  • {pickText('具体作品规格、完成度、当天带走或后寄、包装与寄送费用', 'Exact piece, finish level, same-day collection or shipping, packaging, and postage', language)}
+
{pickText('必须按工坊与日期复核', 'Claims requiring studio-and-date proof', language)}
  • {pickText('授课人是“第三代传承人”、国家/省/市县级代表性传承人或可代表所有苗族支系', 'That the teacher is a “third-generation artisan,” a listed national/provincial/local bearer, or represents every Miao community', language)}
  • {pickText('实际使用天然蓝靛、客人参与完整染制流程、全程由老师本人英文讲解', 'That natural indigo is used, guests complete the entire dye process, or the teacher personally interprets in English throughout', language)}
  • {pickText('所有年龄都可操作热蜡和针具、所有作品可在课程结束时完整带走', 'That every age may handle hot wax and needles or every piece leaves fully finished at lesson end', language)}
+
+

{pickText('公开依据参考:丹寨苗族蜡染于 2006 年列入第一批国家级非物质文化遗产名录,苗绣同年进入第一批国家级名录;实际工坊、授课人身份、染料配方、课程和开放状态仍按日期书面确认。', 'Public reference: Danzhai Miao batik entered the first national intangible-cultural-heritage list in 2006, as did Miao embroidery. The actual studio, teacher status, dye recipe, lesson, and opening remain date-specific and written into confirmation.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('把工坊放在顺路的位置,也给手上的动作留够时间。', 'Place the studio where the route makes sense—and leave enough time for the hands.', language)}

+ +
+ + +
+ ); +} + +function ShuiHorsetailEmbroideryDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('工艺演示', 'Process Demonstration', language), + title: pickText('先看懂马尾芯与丝线结构', 'Read the Horsehair-and-Silk Structure First', language), + copy: pickText( + '由具名工坊或老师展示已准备好的马尾芯、丝线缠裹、盘线固定与填绣关系,并结合真实绣片解释工序。演示以看懂材料和时间为目标,不默认动针,也不承诺带走作品。', + 'A named studio or teacher uses prepared horsehair cores, silk wrapping, couching, and filling to explain the structure through real samples. The goal is process literacy—not automatic needlework or a take-home piece.', + language, + ), + status: pickText('建议 45–75 分钟;适合时间有限、儿童或先了解再决定是否动手的客人', 'Allow 45–75 minutes; suited to limited time, families, or guests deciding before hands-on work', language), + }, + { + number: '02', + label: pickText('入门短课', 'Intro Workshop', language), + title: pickText('马尾芯缠线 + 一段基础小样', 'Wrap the Core + Make One Basic Sample', language), + copy: pickText( + '老师预先准备适合初学者的材料和部分工序,客人练习丝线缠裹马尾芯,并在老师选择的简单纹样中完成一个可控环节。课程不从采集或处理马尾开始,也不把复杂背带、服饰片包装成当日成品。', + 'The teacher prepares beginner-suitable materials and selected stages. Guests practise wrapping silk around a horsehair core and complete one controlled part of a simple motif. The lesson does not begin with collecting or processing horsehair, nor promise a complex carrier or garment panel in one day.', + language, + ), + status: pickText('建议 2–3 小时;针具、精细动作、儿童年龄与小样完成度按老师确认', 'Allow 2–3 hours; needles, fine-motor needs, child age, and sample finish are teacher-confirmed', language), + }, + { + number: '03', + label: pickText('深度工坊', 'Deep Studio Session', language), + title: pickText('半日或分段学习,不追赶复杂成品', 'Half-Day or Multi-Session Learning Without Rushing', language), + copy: pickText( + '只在具名老师、材料、容量与回访条件都成立时安排半日或多次学习,围绕制线、盘绣、填心等一至两个阶段深入练习。可完成的只写明为基础样片;复杂作品由老师收尾或后寄时,提前约定范围、周期与费用。', + 'Offer a half-day or repeat sessions only when a named teacher, materials, capacity, and return plan are real. Practise one or two stages such as thread preparation, couching, or filling. Promise only a basic sample; if the teacher finishes or ships a larger piece later, scope, timing, and fee are agreed first.', + language, + ), + status: pickText('建议 4–5 小时或多次课程;不把长期技艺压缩成“一课学会”', 'Allow 4–5 hours or repeat sessions; a long-practised craft is never sold as “mastered in one class”', language), + }, + ]; + const flow = [ + { title: pickText('锁定三都具名工坊与老师', 'Name the Sandu Studio & Teacher', language), copy: pickText('确认地址、授课人、实际技艺与课程;使用“代表性传承人”等称号时,核对官方名录、项目层级与姓名对应关系。', 'Confirm address, teacher, actual craft, and lesson. Any “representative heritage bearer” title must match the official list, project level, and person.', language) }, + { title: pickText('选择演示、小样或深度学习', 'Choose Demonstration, Sample or Deep Learning', language), copy: pickText('根据时间、年龄、精细动作与作品期待,只确定一个真实目标;短时到访优先演示,动手课优先基础小样。', 'Set one honest goal around time, age, fine-motor comfort, and piece expectations; short visits favour demonstration, while hands-on sessions favour a basic sample.', language) }, + { title: pickText('核对马尾芯、丝线与预制材料', 'Check Horsehair Core, Silk & Prepared Materials', language), copy: pickText('写明马尾来源与清洁/预处理、丝线、布料、针具、绣架和老师预制部分;现场不安排从活体马匹取材。', 'Record horsehair source and cleaning or preparation, silk, cloth, needles, frame, and teacher-prepared stages; no collection from live horses takes place during the class.', language) }, + { title: pickText('现场确认针具、拍摄与讲解', 'Reconfirm Needles, Photography & Interpretation', language), copy: pickText('开课前说明针具回收、儿童陪同、手部敏感和中途退出;人物、作品、家庭空间与制作过程分别征得拍摄及公开使用同意。', 'Before class, explain needle control, child supervision, hand sensitivities, and exit. Seek separate consent for photographing and publishing people, works, homes, and process.', language) }, + { title: pickText('记录完成度、包装与后寄', 'Record Finish, Packing & Shipping', language), copy: pickText('离开前写清小样已完成、由老师收尾或后寄,以及预计周期、包装、邮费、国际寄送限制、破损与未完成责任。', 'Before departure, record whether the sample is finished, teacher-finished, or shipped later, plus timing, packing, postage, international limits, damage, and unfinished-work responsibility.', language) }, + ]; + const gates = [ + { title: pickText('具名主体与真实课程', 'Named Host & Real Lesson', language), copy: pickText('三都具体工坊、老师身份与技艺范围、官方称号依据、实际课程、日期容量、费用、取消和接待窗口全部确认。', 'Confirm the specific Sandu studio, teacher and craft scope, basis for official titles, real lesson, date capacity, fee, cancellation, and hosting window.', language) }, + { title: pickText('材料与工序真实性', 'Material & Process Authenticity', language), copy: pickText('马尾作芯、丝线缠裹后盘绣固定的材料关系要由老师和实物支持;不把普通刺绣、蜡染或通用手作图片冒充马尾绣。', 'The horsehair core, silk wrapping, and couched structure must be supported by the teacher and real samples; generic embroidery, batik, or craft images are never presented as horsehair embroidery proof.', language) }, + { title: pickText('时长与作品复杂度', 'Duration & Piece Complexity', language), copy: pickText('演示、入门小样和深度学习分别定时;复杂背带或服饰片不作短课成品承诺,“刺绣活化石”只作有来源的文化称谓。', 'Time demonstration, basic sample, and deep learning separately. Do not promise a complex carrier or garment panel in a short class; “living fossil of embroidery” remains a sourced cultural descriptor, not a sales claim.', language) }, + { title: pickText('针具、儿童、英文与结算', 'Needles, Children, English & Payment', language), copy: pickText('儿童年龄与成人陪同、针具管理、手部敏感、英文由谁完成、拍摄范围,以及老师、材料、翻译、包装和寄送费用逐项写清。', 'Write down child age and supervision, needle control, hand sensitivities, who provides English, photography scope, and separate teacher, material, interpretation, packing, and shipping fees.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('只在黔南/三都动线成立时加入 2–3 小时入门课;若需从黔东南或荔波绕行,先核算真实车程和住宿。', 'Add a 2–3 hour intro only when the Qiannan–Sandu route fits; from Qiandongnan or Libo, calculate real transfer and overnight impact first.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('亲子人文慢游', 'Family Heritage Slow Travel', language), copy: pickText('优先 45–75 分钟演示或低针具参与的小样课;儿童能否动针由具名老师、年龄和成人陪同共同决定。', 'Prefer a 45–75 minute demonstration or low-needle sample; child needlework follows the named teacher, age, and adult supervision.', language), href: '/safaris/family-heritage/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('可与荔波—三都—黔东南转场组成半日人文节点,但当天不再叠加另一项耗时手作。', 'Use the Libo–Sandu–Qiandongnan transfer as a half-day culture node, without adding another time-heavy craft the same day.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('客户提供的通用手作桌面与线材氛围参考,非水族马尾绣工序证明', +
+
+
+ + Human Culture / 03 +

{pickText('水族马尾绣', 'Shui Horsetail Embroidery', language)}

+

{pickText('先确认三都的一位老师、一束真实材料和一段可完成的工序,再谈“活化石”与带走作品。', 'Name one Sandu teacher, real materials, and one achievable stage before speaking of a “living fossil” or take-home piece.', language)}

+ {pickText('查看三种体验方式', 'See Three Experience Formats', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('真正珍贵的不是“马尾”两个字,而是材料如何被时间变成一条可以绣的线。', 'The value is not the word “horsehair,” but how material and time become a thread that can be embroidered.', language)}

+

{pickText('水族马尾绣于 2006 年列入第一批国家级非物质文化遗产名录,公开资料记录其以马尾为芯、丝线缠裹,再盘在纹样轮廓并固定、填绣。客户文档提供了中英文方向与通用手作照片,但没有给出可公开使用的三都马尾绣工坊、老师和工序实拍;因此第一版先把体验拆成可核验的演示、入门与深度三档,图片也明确不作为工艺证明。', 'Shui horsetail embroidery entered China’s first national intangible-cultural-heritage list in 2006. Public records describe horsehair as a core wrapped in silk, then couched along motif outlines and filled. The client brief supplies bilingual direction and general craft photography, but no publishable Sandu studio, teacher, or process record. This first version therefore separates demonstration, introduction, and deep learning into verifiable formats, and labels imagery as reference rather than craft proof.', language)}

+
+
+
{pickText('地区', 'Area', language)}
{pickText('三都水族自治县 / 具名工坊按日期确认', 'Sandu Shui Autonomous County / named studio by date', language)}
+
{pickText('建议时长', 'Suggested Time', language)}
{pickText('演示 45–75 分钟;入门 2–3 小时;深度半日或多次', 'Demo 45–75 min; intro 2–3 hr; deep half-day or repeat sessions', language)}
+
{pickText('工艺核心', 'Process Core', language)}
{pickText('马尾作芯、丝线缠裹、盘线固定与填绣', 'Horsehair core, silk wrapping, couching, and filling', language)}
+
{pickText('材料', 'Materials', language)}
{pickText('预处理马尾芯、丝线、布料、针具与预制部分逐项确认', 'Confirm prepared horsehair, silk, cloth, needles, and pre-made stages', language)}
+
{pickText('语言', 'Language', language)}
{pickText('老师 + 具名译员/英文司导,不默认老师可全程英文', 'Teacher plus named interpreter or English-speaking guide; no assumed teacher English', language)}
+
{pickText('作品', 'Piece', language)}
{pickText('基础小样 / 老师收尾 / 后寄三种状态写入确认', 'Record one: basic sample, teacher-finished, or shipped later', language)}
+
+
+ +
+
{pickText('客户提供的通用手作动作参考',
{pickText('通用手作动作参考 · 非马尾绣材料、针法或老师证明', 'General handwork reference · not proof of horsehair material, stitch, or teacher', language)}
+
{pickText('客户提供的线材、绣绷和工具桌面参考',
{pickText('客户提供的工坊氛围 · 具体技艺与地点待确认', 'Client-provided studio atmosphere · exact craft and place to confirm', language)}
+
+ +
+
+
01 / Experience Formats

{pickText('先选要看懂多少,再决定手上做到哪一步。', 'Choose how much to understand before deciding what the hands should finish.', language)}

{pickText('三种方式都需要具名老师与真实材料;短课只承诺一个可完成阶段,不用复杂成品制造不切实际的期待。', 'Every format needs a named teacher and real materials. A short lesson promises one achievable stage, never an unrealistic complex piece.', language)}

+
{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
+
+ +
+
{pickText('通用手作动作与精细操作氛围参考',
+
+
02 / Experience Flow

{pickText('从一位真实老师开始,到一件诚实标注完成度的小样结束。', 'Begin with a real teacher and end with an honestly labelled sample.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Confirmation Gates

{pickText('没有具名老师、真实材料和合理时长,就还不是一项可以售卖的马尾绣体验。', 'Without a named teacher, real materials, and honest timing, this is not yet a sellable horsehair-embroidery experience.', language)}

{pickText('文化身份可以由公开资料说明,商业接待仍要逐日落到具体工坊、老师、材料、课程与责任边界。', 'Public records can establish the craft’s cultural identity; commercial hosting must still resolve to a specific studio, teacher, material, lesson, and responsibility boundary for each date.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('正式方案可以承诺', 'What a final plan can promise', language)}
  • {pickText('三都具名工坊、老师、课程类型、真实时长、容量、价格与取消条件', 'Named Sandu studio and teacher, lesson type, real duration, capacity, price, and cancellation', language)}
  • {pickText('实际马尾芯、丝线、工具、开放工序、基础小样及老师预制/收尾范围', 'Actual horsehair core, silk, tools, open stages, basic sample, and teacher-prepared or finishing scope', language)}
  • {pickText('针具与儿童边界、语言人员、拍摄同意、作品包装/后寄与各项费用', 'Needle and child boundaries, language support, photography consent, packing or shipping, and itemised fees', language)}
+
{pickText('必须按主体与日期复核', 'Claims requiring host-and-date proof', language)}
  • {pickText('页面图片就是马尾绣工坊/工序实拍、授课老师是代表性传承人或课程日常可约', 'That page images show the actual studio or process, the teacher is a listed bearer, or the class is normally bookable', language)}
  • {pickText('客人能现场采集马尾、完整经历所有传统工序或短课完成复杂背带/服饰作品', 'That guests collect horsehair, complete every traditional stage, or finish a complex carrier or garment in a short class', language)}
  • {pickText('“活化石”“独家”“大师私授”、全程英文、作品当天带走或国际寄送必然成功', '“Living fossil,” “exclusive,” or “private masterclass” sales claims, full English, same-day collection, or guaranteed international shipping', language)}
+
+

{pickText('公开依据参考:文化和旅游部、国家民委及贵州公开文化资料中对三都水族马尾绣的记录。该技艺于 2006 年列入第一批国家级非物质文化遗产名录,公开工艺资料记录以马尾为芯、丝线缠裹并盘绣固定;实际工坊、老师、材料、课程与开放状态仍按日期书面确认。', 'Public references include records from the Ministry of Culture and Tourism, the national ethnic-affairs authority, and Guizhou cultural sources. The craft entered the first national intangible-cultural-heritage list in 2006; published process records describe horsehair cores wrapped in silk and couched into place. Actual studio, teacher, material, lesson, and opening remain date-specific and confirmed in writing.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('让三都成为真实的一站,不把一堂课藏进不可能的转场。', 'Make Sandu a real stop—not a class hidden inside an impossible transfer.', language)}

+ +
+ + +
+ ); +} + +function BuyiTraditionalCraftsDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('安顺石头寨', 'Anshun Stone Village', language), + title: pickText('具名工坊蜡染入门', 'Named-Studio Batik Introduction', language), + copy: pickText( + '在安顺石头寨或周边具名布依工坊,由老师讲清村寨、纹样、蜡材、布料与染液,再开放点蜡、染色、去蜡或成品讲解中适合当天的一段。600 多年石头寨与蜡染传统有公开资料支持,但具体老师、工坊和课程仍按日期确认。', + 'At a named Buyi studio in or near Anshun Stone Village, the teacher explains village context, motifs, wax, cloth, and dye before opening one date-suitable stage—wax drawing, dyeing, wax removal, or finished-piece reading. Public records support the 600-plus-year village and its batik tradition, while the teacher, studio, and lesson remain date-specific.', + language, + ), + status: pickText('建议 2–3 小时;热蜡、染液、通风、皮肤敏感、作品干燥与带走方式逐项确认', 'Allow 2–3 hours; confirm hot wax, dye, ventilation, skin sensitivity, drying, and collection', language), + }, + { + number: '02', + label: pickText('黔西南织造', 'Qianxinan Weaving', language), + title: pickText('从绞纱到织布,只学一个真实阶段', 'From Yarn to Cloth—Learn One Real Stage', language), + copy: pickText( + '在晴隆、望谟或黔西南具名工坊中,根据老师的日常工序选择绞纱、上浆、排线、上机织造、纹样理解或服饰制作中的一项。短课不承诺从棉线到完整布匹,更不把服饰成衣压缩成半日完成。', + 'At a named studio in Qinglong, Wangmo, or elsewhere in Qianxinan, select one real stage from winding, sizing, warping, loom weaving, motif reading, or garment making. A short lesson never promises a whole cloth from raw yarn, much less a finished garment in half a day.', + language, + ), + status: pickText('建议 2.5–4 小时或半日;织机类型、材料、老师预制部分、坐姿与精细动作按工坊确认', 'Allow 2.5–4 hours or a half-day; confirm loom, material, teacher-prepared stages, posture, and fine-motor needs', language), + }, + { + number: '03', + label: pickText('葫芦彩绘候选', 'Gourd-Painting Candidate', language), + title: pickText('先确认地方归属,再决定如何命名', 'Verify Local Attribution Before Naming the Class', language), + copy: pickText( + '客户文档提出在石头寨体验葫芦彩绘,但现有资料没有证明它属于该村的布依传统技艺。只有找到具名布依社区老师、真实作品与日常课程,才能写为布依体验;若只是现代颜料装饰,应诚实命名为当代文创课。若采用烙画,还需另行核对热针与通风。', + 'The brief proposes gourd painting in a stone village, but the available evidence does not establish it as that village’s Buyi tradition. It becomes a Buyi experience only with a named community teacher, real work, and normal lesson; modern paint decoration should be labelled a contemporary creative workshop. Pyrography also requires separate hot-tool and ventilation checks.', + language, + ), + status: pickText('建议 1.5–2.5 小时;地点、老师、传统依据、颜料或烙画工具确认前不上线销售', 'Allow 1.5–2.5 hours; do not sell before place, teacher, tradition, and paint or pyrography tools are confirmed', language), + }, + ]; + const flow = [ + { title: pickText('先选安顺或黔西南', 'Choose Anshun or Qianxinan First', language), copy: pickText('根据整条线路确定石头寨蜡染或黔西南织造,不把相距很远的工艺包装成同一村、同一位老师或同一堂课。', 'Choose Stone Village batik or Qianxinan weaving from the real route; never present distant crafts as one village, one teacher, or one class.', language) }, + { title: pickText('锁定具名工坊、老师与课程', 'Name the Studio, Teacher & Lesson', language), copy: pickText('确认地址、老师真实技艺与身份、课程是否日常开放、容量、时长、费用、取消;使用传承人称号时核对官方名录。', 'Confirm address, teacher identity and actual craft, normal availability, capacity, duration, fee, and cancellation; verify any heritage-bearer title against an official list.', language) }, + { title: pickText('只选一个可以完成的工序', 'Choose One Achievable Stage', language), copy: pickText('在点蜡/染色、绞纱/排线/织造或经核验的葫芦彩绘中只选一个重点,并写明老师预制、客人操作和老师收尾的分界。', 'Choose one focus—wax or dye, winding or weaving, or a verified gourd-painting class—and state what the teacher prepares, what guests do, and what the teacher finishes.', language) }, + { title: pickText('现场复核工具、安全与拍摄', 'Recheck Tools, Safety & Photography', language), copy: pickText('开课前说明热蜡、染料、织机夹点、剪刀针具或烙画热针;儿童参与、人物/家庭空间/作品拍摄和公开使用分别征得同意。', 'Before class, explain hot wax, dye, loom pinch points, scissors and needles, or a pyrography tool; separately confirm child participation and consent for photographing or publishing people, homes, and works.', language) }, + { title: pickText('确认完成、干燥、后寄与结算', 'Confirm Finish, Drying, Shipping & Payment', language), copy: pickText('离开前记录作品完成度、去蜡/清洗/晾干、当天带走或后寄、包装与邮费,并分别结算老师、工坊、材料、翻译和社区接待。', 'Before departure, record finish, wax removal, washing or drying, same-day collection or shipping, packing and postage, and itemise teacher, studio, materials, interpretation, and community hosting.', language) }, + ]; + const gates = [ + { title: pickText('地点与技艺不能混写', 'Keep Place & Craft Specific', language), copy: pickText('石头寨、晴隆、望谟或其他地点分别写明;蜡染、织锦/织布、刺绣、服饰与葫芦彩绘不能因为都属“布依”就合并成一种通用古法。', 'Name Stone Village, Qinglong, Wangmo, or another place separately; batik, weaving, embroidery, dress, and gourd painting do not become one generic ancient craft simply because they are Buyi-related.', language) }, + { title: pickText('材料与工序要有实物', 'Materials & Process Need Evidence', language), copy: pickText('蜡材、蓝靛或其他染液、棉麻/线材、织机、颜料或烙画工具、老师预制部分和真实作品必须由具名工坊说明并可现场对应。', 'Wax, indigo or other dye, cotton or other fibre, loom, paint or pyrography tool, teacher-prepared stages, and real work must be explained and shown by the named studio.', language) }, + { title: pickText('热蜡、染料、织机与儿童', 'Wax, Dye, Looms & Children', language), copy: pickText('核对热源、防烫桌面、通风、皮肤接触、织机活动部位、剪刀针具、烙画热针、洗手与急救;儿童年龄和成人陪同由老师确认。', 'Check heat, burn-safe surfaces, ventilation, skin contact, moving loom parts, scissors and needles, pyrography heat, handwashing, and first aid; the teacher confirms child age and supervision.', language) }, + { title: pickText('英文、影像、作品与费用', 'English, Images, Piece & Fees', language), copy: pickText('英文由谁完成、哪些人物和空间可拍、现有客户图片能证明什么、作品是否带走或后寄,以及工坊/老师/材料/翻译/寄送费用逐项写清。', 'State who provides English, who and what may be photographed, what the client imagery actually proves, whether the piece is collected or shipped, and each studio, teacher, material, interpretation, and shipping fee.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('在黄果树段顺路安排安顺石头寨具名蜡染工坊半日;不为黔西南织造额外做跨区域往返。', 'Place a named Stone Village batik half-day alongside Huangguoshu; do not add a cross-region return trip for Qianxinan weaving.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('亲子人文慢游', 'Family Heritage Slow Travel', language), copy: pickText('优先低风险点蜡/成品讲解或经核验的常温彩绘;儿童是否接触热蜡、染液、织机与热针由老师决定。', 'Prefer lower-risk wax drawing, finished-piece interpretation, or verified room-temperature painting; the teacher decides child access to hot wax, dye, looms, or hot tools.', language), href: '/safaris/family-heritage/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('在兴义、晴隆或万峰林段选择具名织造/服饰工坊作为半日人文重点,保留真实车程,不再叠加另一项耗时手作。', 'Use a named weaving or dress studio near Xingyi, Qinglong, or Wanfenglin as the half-day culture focus, keeping real transfer time and avoiding another major craft.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('客户提供的贵州蓝染工坊氛围参考,非布依工坊或石头寨实拍证明', +
+
+
+ + Human Culture / 04 +

{pickText('布依族古法技艺', 'Buyi Traditional Crafts', language)}

+

{pickText('先选一座真实村寨和一门真实手艺,再让双手进入材料、工具与时间。', 'Choose one real village and one real craft before placing your hands into its material, tools, and time.', language)}

+ {pickText('查看三种体验方向', 'See Three Experience Directions', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('“布依古法”不是一个万能标签,而是不同地方、不同材料和不同老师的具体手艺。', '“Buyi tradition” is not a catch-all label, but a specific craft, material, teacher, and place.', language)}

+

{pickText('客户文档提出在“600 年石头寨”体验古法织造与葫芦彩绘。官方资料能够支持安顺石头寨有 600 多年历史及当地蜡染传统,也能支持黔西南布依纺织包含绞纱、上浆、排线、织布和服饰制作等复杂工序;但现有资料不能证明这两类技艺与葫芦彩绘由石头寨同一工坊提供。因此第一版把它们拆开,分别按具名地点、工坊、老师和课程核验。', 'The brief proposes ancient weaving and gourd painting in a “600-year-old stone village.” Official records support the 600-plus-year history and batik tradition of Anshun Stone Village, and the complex winding, sizing, warping, weaving, and dress-making stages of Buyi textiles in Qianxinan. The available evidence does not show that one Stone Village studio offers all of these plus gourd painting, so this version separates them for place-, studio-, teacher-, and lesson-specific verification.', language)}

+
+
+
{pickText('地区候选', 'Area Candidates', language)}
{pickText('安顺石头寨 / 晴隆、望谟或黔西南具名工坊', 'Anshun Stone Village / a named Qinglong, Wangmo, or Qianxinan studio', language)}
+
{pickText('建议时长', 'Suggested Time', language)}
{pickText('单项 1.5–4 小时;织造深度体验建议半日或多次', 'One focus 1.5–4 hours; deep weaving needs a half-day or repeat sessions', language)}
+
{pickText('体验选择', 'Choose One', language)}
{pickText('蜡染 / 织造与服饰 / 经地方核验的葫芦彩绘', 'Batik / weaving and dress / locally verified gourd painting', language)}
+
{pickText('材料', 'Materials', language)}
{pickText('蜡、染液、布线、织机、颜料或热针按真实课程确认', 'Confirm wax, dye, fibre, loom, paint, or hot tool from the real lesson', language)}
+
{pickText('语言', 'Language', language)}
{pickText('具名老师 + 专业译员或英文司导', 'Named teacher plus interpreter or English-speaking guide', language)}
+
{pickText('关键边界', 'Key Boundary', language)}
{pickText('不同地区和技艺不拼成“一站式古法课”', 'No one-stop class combining unrelated places and crafts', language)}
+
+
+ +
+
{pickText('客户提供的蓝染工坊陈列与工作空间参考',
{pickText('蓝染氛围参考 · 非布依工坊、石头寨或具体课程证明', 'Indigo atmosphere · not proof of a Buyi studio, Stone Village, or lesson', language)}
+
{pickText('客户提供的点蜡与蓝染拼图参考',
{pickText('点蜡工序参考 · 具体民族、老师、材料与地点待确认', 'Wax-process reference · community, teacher, material, and place to confirm', language)}
+
{pickText('客户提供的线材、绣绷与工具桌面参考',
{pickText('通用手作桌面 · 非布依织机或纺织工序证明', 'General craft table · not proof of a Buyi loom or weaving process', language)}
+
{pickText('客户提供的通用精细手作动作参考',
{pickText('通用操作氛围 · 非葫芦彩绘或具名老师证明', 'General making reference · not proof of gourd painting or a named teacher', language)}
+
+ +
+
+
01 / Craft Directions

{pickText('一座村寨,一门手艺,一个可以诚实完成的目标。', 'One village, one craft, and one honestly achievable goal.', language)}

{pickText('三个方向不是一张通票;只有地点、老师、材料与日常课程都成立,才进入正式行程。', 'These directions are not a single pass. Each enters a final route only when its place, teacher, material, and normal lesson are real.', language)}

+
{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
+
+ +
+
{pickText('点蜡、染色与手作步骤的通用概念参考',
+
+
02 / Experience Flow

{pickText('先把地点和工艺分开核验,再把一堂课写得具体。', 'Verify place and craft separately, then make one lesson specific.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Confirmation Gates

{pickText('不能说清在哪里、跟谁学、用什么做,就不能只用“古法”两个字销售。', 'If place, teacher, and material cannot be named, “traditional” is not enough to sell the experience.', language)}

{pickText('公开资料证明的是地区文化传统,不等于任何村寨、任何老师、任何日期都能提供同一套课程。', 'Public records establish regional traditions, not that every village, teacher, and date offers the same class.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('正式方案可以承诺', 'What a final plan can promise', language)}
  • {pickText('具名村寨、工坊、老师、真实课程、日期容量、时长、价格与取消条件', 'Named village, studio, teacher, real lesson, date capacity, duration, price, and cancellation', language)}
  • {pickText('实际蜡染/织造/彩绘材料、工具、开放工序、老师预制与收尾范围', 'Actual batik, weaving, or painting materials, tools, open stage, and teacher preparation or finish', language)}
  • {pickText('热源/染料/织机/儿童边界、英文人员、拍摄同意、作品带走或后寄', 'Heat, dye, loom, and child boundaries, language support, photography consent, and collection or shipping', language)}
+
{pickText('必须按主体与日期复核', 'Claims requiring host-and-date proof', language)}
  • {pickText('现有图片就是布依工坊、石头寨、织机或葫芦彩绘实拍,人物就是授课老师', 'That current images show the Buyi studio, Stone Village, loom, or gourd painting, or that pictured people are teachers', language)}
  • {pickText('石头寨同一工坊同时提供古法织布、完整蜡染、服饰制作与葫芦彩绘', 'That one Stone Village studio offers traditional weaving, full batik, dress making, and gourd painting', language)}
  • {pickText('天然蓝靛、纯手工、传承人私授、全程英文、所有作品当天完成或全部收益回到社区', 'Natural indigo, all-handmade, private bearer teaching, full English, every piece finished same day, or all proceeds returning to the community', language)}
+
+

{pickText('公开依据参考:安顺市人民政府关于石头寨 600 多年布依村寨及蜡染体验的公开资料,以及贵州省人大、国家民委关于布依族纺织、蜡染和织锦的资料。客户提出的“石头寨葫芦彩绘”尚未找到对应地方与传承依据,实际工坊、老师、材料、课程和开放状态仍按日期书面确认。', 'Public references include Anshun government records on the 600-plus-year Buyi Stone Village and batik experiences, plus Guizhou and national ethnic-affairs records on Buyi textiles, batik, and brocade. The brief’s “Stone Village gourd painting” still lacks matching local and tradition evidence; actual studio, teacher, material, lesson, and opening remain date-specific and confirmed in writing.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('让安顺做安顺的手艺,让黔西南做黔西南的手艺。', 'Let Anshun carry Anshun craft, and Qianxinan carry Qianxinan craft.', language)}

+ +
+ + +
+ ); +} + +function DongIndigoArchitectureDetailPage() { + const { language } = useTravelSite(); + const formats = [ + { + number: '01', + label: pickText('工坊单项', 'Studio Focus', language), + title: pickText('具名侗布染制工坊', 'Named Dong Textile-Dyeing Studio', language), + copy: pickText( + '由具名工坊说明当地侗布的纺、织、染或亮布整理中实际开放的一段,再完成小样、染色观察或材料触摸。蓝靛、植物汁、蛋清或其他整理材料都按该工坊真实配方说明,不用“纯天然草木染”统一概括。', + 'A named studio opens one real stage of local Dong cloth—spinning, weaving, dyeing, or bright-cloth finishing—followed by a small sample, dye observation, or material handling. Indigo, plant juice, egg white, or other finishing materials are described from that studio’s actual recipe, not bundled under a generic “all-natural botanical dye” claim.', + language, + ), + status: pickText('建议 2–3 小时;材料、染缸、可动手工序、通风与皮肤敏感按工坊确认', 'Allow 2–3 hours; confirm materials, vats, hands-on stage, ventilation, and skin sensitivity with the studio', language), + }, + { + number: '02', + label: pickText('建筑单项', 'Architecture Focus', language), + title: pickText('鼓楼与风雨桥公共建筑观察', 'Drum Tower & Wind-and-Rain Bridge Walk', language), + copy: pickText( + '以一个具名侗寨和一至两座具名公共建筑为主线,观察柱网、穿枋、榫卯、重檐、桥廊和社区使用方式。默认只在公共开放区域讲解,不攀爬、不进入封闭结构,也不把所有建筑都统一写成“不用一钉一铁”。', + 'Follow one named Dong village and one or two named public buildings, reading columns, crossbeams, mortise-and-tenon joints, layered eaves, bridge galleries, and community use. Interpretation stays in public areas by default—no climbing, no closed structures, and no claim that every building uses no metal fasteners.', + language, + ), + status: pickText('建议 1.5–2.5 小时;具体建筑、开放区域、讲解人、集会/仪式冲突与拍摄边界逐项确认', 'Allow 1.5–2.5 hours; confirm each building, open area, interpreter, community-event conflict, and photography boundary', language), + }, + { + number: '03', + label: pickText('完整文化日', 'Full Culture Day', language), + title: pickText('侗布工坊 + 木构村寨慢行', 'Dong Cloth Studio + Timber-Village Walk', language), + copy: pickText( + '只有工坊与建筑位于同一村寨或真实顺路、两端接待人和开放时间都成立时,才组合为完整一日。上午和下午各保留一个重点,中间安排真实车程与用餐,不把远距离素材包装成“村内联动”。', + 'Create a full day only when the studio and buildings are in the same village or genuinely en route, with both hosts and opening times confirmed. Give morning and afternoon one focus each, including real transfer and meal time rather than presenting distant references as one village experience.', + language, + ), + status: pickText('建议完整 1 日;优先黔东南具名侗寨,最终路线随工坊和建筑开放确认', 'Allow one full day; prioritise a named Qiandongnan Dong village, with the route set by studio and building access', language), + }, + ]; + const flow = [ + { title: pickText('先锁定一个具名侗寨', 'Name One Dong Village First', language), copy: pickText('确认村寨、进出道路、停车/步行、公共空间、当日集会或仪式,以及是否适合境外客人正常进入。', 'Confirm the village, access road, parking and walking, public spaces, community meetings or rituals that day, and normal access for international guests.', language) }, + { title: pickText('分别锁定工坊与建筑讲解人', 'Name the Studio and Architecture Interpreter', language), copy: pickText('工坊端确认老师与课程;建筑端确认熟悉具体鼓楼/风雨桥的讲解人。两者可以是不同主体,不强行使用“传承人”统一称谓。', 'Confirm the teacher and lesson for textiles, and a person who understands the named drum tower or bridge for architecture. They may be different hosts and are not automatically labelled “heritage bearers.”', language) }, + { title: pickText('核对材料与建筑事实', 'Check Materials & Building Facts', language), copy: pickText('写清染料、整理材料、实际工序和作品;建筑则写清名称、年代/修缮信息来源、结构类型及哪些部位能够支持榫卯或无铁钉表述。', 'Write down dye and finishing materials, actual stages, and the piece; for buildings, record name, source for age or restoration, structural type, and exactly which parts support a mortise-and-tenon or no-metal-fastener statement.', language) }, + { title: pickText('现场按社区使用让路', 'Yield to Community Use On Site', language), copy: pickText('鼓楼、桥廊和寨中道路首先是社区空间。遇到议事、仪式、施工、宴席或居民不便拍摄时,缩短、绕行或取消对应内容。', 'Drum towers, bridge galleries, and village lanes are community spaces first. When meetings, rituals, works, meals, or resident privacy intervene, shorten, reroute, or remove that content.', language) }, + { title: pickText('完成作品并记录真实结算', 'Finish the Piece & Record Settlement', language), copy: pickText('说明小样是否完成、干燥或后寄,并分别记录工坊、老师、建筑讲解、翻译、餐食和社区接待费用,不用模糊的“收益全部回到村寨”。', 'State whether the sample is finished, drying, or shipped later, and record studio, teacher, architecture interpretation, language, meal, and community hosting fees separately instead of a vague “all benefit returns to the village” claim.', language) }, + ]; + const gates = [ + { title: pickText('具名工坊与真实材料', 'Named Studio & Real Materials', language), copy: pickText('老师身份、实际侗布类型、纺/织/染/亮布哪一段开放、蓝靛或其他材料、染缸与热源、通风、皮肤接触、作品和费用。', 'Teacher identity, actual Dong cloth, which spinning, weaving, dyeing, or finishing stage opens, indigo or other materials, vats and heat, ventilation, skin contact, piece, and fee.', language) }, + { title: pickText('具体建筑与结构口径', 'Named Building & Structural Claim', language), copy: pickText('鼓楼/风雨桥名称、位置、开放范围、修缮状态、结构资料来源;“不用铁钉”只用于能被具体建筑或营造资料支持的部分。', 'Building name and location, open areas, restoration status, and structural source; use “no metal fasteners” only where the named building or construction record supports it.', language) }, + { title: pickText('公共空间、拍摄与安全', 'Public Space, Photography & Safety', language), copy: pickText('社区活动优先、居民与儿童不默认可拍;雨天木桥湿滑、临水临边、楼梯、夜间照明、施工区域、老人儿童步行和中途退出。', 'Community activity takes priority and residents or children are not automatically photographable; check wet timber, water and edges, stairs, night lighting, works, walking needs, and exit.', language) }, + { title: pickText('距离、语言与责任', 'Distance, Language & Responsibility', language), copy: pickText('工坊与建筑真实距离、车辆/步行接驳、用餐与卫生间、英文由谁完成、迟到或社区活动导致调整时的费用和责任边界。', 'Real studio-to-building distance, vehicle or walking transfer, meals and toilets, who provides English, and fee or responsibility boundaries when delays or community use change the day.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('选择建筑观察或工坊单项之一,作为黔东南村寨段的完整半日,不再叠加远距离手作。', 'Choose either architecture or one studio as a full Qiandongnan village half-day, without adding a distant second craft.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('亲子人文慢游', 'Family Heritage Slow Travel', language), copy: pickText('优先公共空间慢行和低风险材料观察;儿童是否进入染制环节由具名老师、材料和工具决定。', 'Prioritise a public-space walk and low-risk material observation; child participation in dyeing follows the named teacher, materials, and tools.', language), href: '/safaris/family-heritage/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('最适合使用完整一日组合,与村寨慢住衔接;住宿、工坊和建筑必须在真实动线上成立。', 'Best for the full-day combination alongside a village stay, only when accommodation, studio, and buildings form a real route.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('客户提供的贵州木构村寨与稻田实拍', +
+
+
+ + Human Culture / 02 +

{pickText('侗族草木染・木构建筑', 'Dong Indigo & Timber Architecture', language)}

+

{pickText('一块布讲材料,一座鼓楼讲结构;只有具名工坊与具名建筑都成立时,才把它们放进同一天。', 'One cloth explains material; one drum tower explains structure. They share a day only when both the studio and building can be named.', language)}

+ {pickText('查看三种体验方式', 'See Three Experience Formats', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('染布不是建筑课的装饰,鼓楼也不是工坊的拍照背景。', 'Textile dyeing is not decoration for an architecture lesson, and a drum tower is not a studio photo backdrop.', language)}

+

{pickText('客户文档把“侗族草木染・木构建筑”列为一个核心体验方向。第一版保留这种组合,但拆成两个独立、可核验的主体:具名侗布工坊负责材料与工序,具名村寨及建筑讲解人负责鼓楼、风雨桥和社区空间。客户图片能够提供村寨与蓝染氛围,但没有证明具体侗布工坊、老师或所有建筑结构,因此页面只把这些内容作为候选,不作现成可售承诺。', 'The brief proposes “Dong Indigo Weaving & Drum Tower Architecture” as one core direction. This version keeps the pairing but separates two verifiable hosts: a named Dong textile studio for material and process, and a named village plus architecture interpreter for drum towers, bridges, and community space. Client photography supports village and indigo atmosphere, but does not prove a specific Dong studio, teacher, or every structural claim, so the page presents candidates rather than a ready-to-sell promise.', language)}

+
+
+
{pickText('地区候选', 'Area Candidate', language)}
{pickText('黔东南具名侗寨;最终随工坊与建筑开放确认', 'A named Qiandongnan Dong village, finalised by studio and building access', language)}
+
{pickText('建议时长', 'Suggested Time', language)}
{pickText('单项 1.5–3 小时;组合建议完整 1 日', 'One focus 1.5–3 hours; combined format one full day', language)}
+
{pickText('工坊重点', 'Studio Focus', language)}
{pickText('纺、织、染或亮布整理中实际开放的一段', 'One genuinely open stage of spinning, weaving, dyeing, or bright-cloth finishing', language)}
+
{pickText('建筑重点', 'Architecture Focus', language)}
{pickText('具名鼓楼/风雨桥的结构与社区使用,不默认进入封闭区域', 'Structure and community use of named towers or bridges; no default closed-area access', language)}
+
{pickText('语言', 'Language', language)}
{pickText('工坊与建筑两端分别确认老师、讲解人和英文支持', 'Confirm teacher, architecture interpreter, and English support on both sides', language)}
+
{pickText('关键边界', 'Key Boundary', language)}
{pickText('“草木染”“无一钉一铁”均按材料和具体建筑复核', 'Verify “botanical dye” by material and “no nails” by named building', language)}
+
+
+ +
+
{pickText('山谷稻田与木构村寨',
{pickText('客户提供实拍 · 具体村寨归属与公开使用授权需归档', 'Client-supplied photography · exact village attribution and publication permission to be recorded', language)}
+
{pickText('客户提供的鼓楼与村寨屋顶参考',
+
{pickText('溪谷中的贵州木构村落参考',
{pickText('村寨建筑参考 · 具体地点与建筑身份待确认', 'Village architecture reference · exact place and building identity to confirm', language)}
+
{pickText('客户提供的蓝染工坊氛围参考',
{pickText('蓝染氛围参考 · 非侗布工坊或具体课程证明', 'Indigo atmosphere reference · not proof of a Dong textile studio or lesson', language)}
+
+ +
+
+
01 / Experience Formats

{pickText('先让一块布和一座建筑各自成立,再谈它们为什么适合放在一起。', 'Let one cloth and one building stand on their own before explaining why they belong together.', language)}

{pickText('三个方式都要落到具名主体;公开建筑观察不冒充私享课程,工坊手作也不替代社区空间的真实使用。', 'Every format needs named hosts. Public architecture is not presented as a private masterclass, and a studio does not replace the real community life of public space.', language)}

+
{formats.map((format) =>
{format.number}{format.label}

{format.title}

{format.copy}

{format.status}
)}
+
+
+ +
+
{pickText('鼓楼屋檐与木构村寨屋顶',
+
+
02 / Experience Flow

{pickText('从具名村寨开始,以社区空间优先结束。', 'Begin with a named village and end by putting community use first.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Confirmation Gates

{pickText('不能说清是哪块布、哪座楼、谁来讲,就不能把公开村寨包装成私享非遗。', 'If we cannot name the cloth, building, and interpreter, a public village cannot be sold as private heritage access.', language)}

{pickText('侗族木构榫卯传统和侗布工艺都有公开文化依据,但实际材料、建筑结构、开放范围与接待关系必须回到具体工坊和具体建筑。', 'Dong timber joinery and textile traditions have public cultural records, but material, structure, access, and hosting must return to a specific studio and building.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('正式方案可以承诺', 'What a final plan can promise', language)}
  • {pickText('具名侗寨、工坊、老师、建筑讲解人、具体课程与公共步行路线', 'Named Dong village, studio, teacher, architecture interpreter, real lesson, and public walking route', language)}
  • {pickText('实际染制/整理材料、开放工序、作品,以及具名建筑、开放范围和可支持的结构表述', 'Actual dye or finishing materials, open stage, piece, plus named buildings, open areas, and supported structural description', language)}
  • {pickText('真实距离、车辆/步行接驳、语言人员、拍摄范围、社区活动备选和各项费用', 'Real distance, vehicle or walking transfer, language support, photography scope, community-event fallback, and itemised fees', language)}
+
{pickText('必须按主体与日期复核', 'Claims requiring host-and-date proof', language)}
  • {pickText('图片中的蓝染空间就是侗布工坊、授课人为代表性传承人或课程日常可预约', 'That the pictured indigo space is a Dong studio, the teacher is a listed bearer, or the lesson is normally bookable', language)}
  • {pickText('所有侗布都是纯天然草木染、所有鼓楼/风雨桥都完全不用铁钉或可进入内部结构', 'That all Dong cloth uses all-natural botanical dye or every tower and bridge uses no metal and opens its structure', language)}
  • {pickText('鼓楼夜间歌唱、百家宴、仪式、家庭接待或居民拍摄每天都会发生并可为游客安排', 'That tower singing, community banquets, rituals, home hosting, or resident photography happen daily and can be arranged for visitors', language)}
+
+

{pickText('公开依据参考:国家民委关于侗族木构建筑营造技艺的资料,以及贵州公开文化资料中对鼓楼、风雨桥榫卯体系和侗族蛋浆亮布工艺的记录。实际村寨、建筑、工坊、材料和开放状态仍按日期书面确认。', 'Public references include the national ethnic-affairs authority on Dong timber-building craft and Guizhou cultural records on drum-tower and bridge joinery and Dong egg-finished bright cloth. The actual village, building, studio, material, and opening remain date-specific and written into confirmation.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('让工坊和村寨在真实动线上相遇,而不是在文案里相邻。', 'Let studio and village meet on a real route, not merely sit next to each other in copy.', language)}

+ +
+ + +
+ ); +} + +function HeritageOutdoorPairingsDetailPage() { + const { language } = useTravelSite(); + const pairings = [ + { + number: '01', + label: pickText('优先落地', 'First to Build', language), + title: pickText('梯田慢行 + 具名蜡染工坊', 'Terrace Walk + Named Batik Studio', language), + copy: pickText( + '上午由本地带路人完成一段低至中等强度的梯田、村路或田野步行,下午进入具名工坊,在老师带领下完成一件小型蜡染作品。正式方案分别写明步行路线与工坊,不把两处相距很远的素材强行拼成一天。', + 'Walk a low- to moderate-intensity terrace, village, or field route with a local guide, then enter a named studio to make one small batik piece. The final plan names both the walk and studio rather than forcing distant reference locations into one day.', + language, + ), + status: pickText('最容易控制强度、天气和作品完成时间;丹寨或安顺工坊按老师档期确认', 'The easiest pairing for pacing, weather, and a finished piece; Danzhai or Anshun studio by host availability', language), + }, + { + number: '02', + label: pickText('材料受控', 'Materials Controlled', language), + title: pickText('植物观察 + 石桥古法造纸', 'Plant Observation + Shiqiao Papermaking', language), + copy: pickText( + '户外部分以识别、观察和讲解为主,不默认让客人在峡谷或保护地采集植物;造纸使用由石桥具名工坊依法准备的构皮、纤维、纸浆或花草材料。根据开放工序体验抄纸、压制、嵌花或晾晒中的一段。', + 'The outdoor part focuses on identification and observation, with no default guest collection in canyons or protected land. Papermaking uses bark fibre, pulp, or botanical materials lawfully prepared by a named Shiqiao studio, then offers one available stage such as sheet forming, pressing, flower inlay, or drying.', + language, + ), + status: pickText('“寻草”不等于自由采集;植物、原料、老师和当天开放工序必须确认', '“Plant finding” is not free collection; species, materials, teacher, and available process require confirmation', language), + }, + { + number: '03', + label: pickText('季节与路线', 'Season & Route', language), + title: pickText('茶园步行/骑行 + 制茶体验', 'Tea Walk or Ride + Tea Craft', language), + copy: pickText( + '先在湄潭或普安候选区选择一条具名茶园步行或骑行路线,再进入具名茶园、合作社或工坊体验当季可做的采摘、摊晾、杀青、揉捻或品鉴。骑行与制茶各自留足时间,不承诺每个季节都能参与完整流程。', + 'Choose a named tea-country walk or ride in a Meitan or Pu’an candidate area, then enter a named estate, cooperative, or studio for whichever seasonal stage is available—picking, withering, fixing, rolling, or tasting. Leave real time for both sides and do not promise the full process in every season.', + language, + ), + status: pickText('可在同一天选择步行或骑行之一;骑行需单独核验道路、车辆、头盔与保障车', 'Choose walking or riding—not both; cycling separately requires route, bikes, helmets, and support vehicle', language), + }, + ]; + const flow = [ + { title: pickText('先选一个组合,不堆项目', 'Choose One Pairing', language), copy: pickText('确认客人更看重风景、手作、植物、茶或骑行,并根据年龄、体力、精细动作、过敏和当天转场选择一组。', 'Clarify whether guests value landscape, craft, plants, tea, or cycling, then choose one pairing around age, fitness, fine-motor ability, allergies, and transfers.', language) }, + { title: pickText('锁定两端的具名主体', 'Name Both Hosts', language), copy: pickText('户外端确认路线、带路人和进入许可;文化端确认工坊、老师、真实课程、材料、作品和费用。只有两端都成立才组合。', 'Confirm route, guide, and access for the outdoor half; confirm studio, teacher, real lesson, materials, take-home piece, and fee for the heritage half. Pair only when both stand.', language) }, + { title: pickText('核对距离、季节与时间', 'Check Distance, Season & Time', language), copy: pickText('计算接驳和用餐,确认农时、茶季、工坊生产安排与作品干燥/带走时间;不使用“附近”掩盖长距离转场。', 'Calculate transfer and meal time, season, tea harvest, studio production, and drying or collection time; do not use “nearby” to hide a long drive.', language) }, + { title: pickText('出发前做双重 Go / No-Go', 'Dual Go / No-Go', language), copy: pickText('户外按雨、雷电、路面、温度和客人状态决定;工坊按老师、材料、通风、工具与安全条件决定。任一端不成立就缩短或改为单项。', 'Decide the outdoor half by rain, lightning, surface, temperature, and guest condition; decide the studio half by teacher, materials, ventilation, tools, and safety. If either fails, shorten or run one activity only.', language) }, + { title: pickText('完成作品并记录真实贡献', 'Finish & Record Real Benefit', language), copy: pickText('作品标明是否可当天带走或后寄;按约向带路人、老师、工坊或合作社结算,不把单次体验泛化成“全部收益直达社区”。', 'State whether the piece leaves that day or ships later; settle as agreed with guide, teacher, studio, or cooperative without turning one transaction into an “all benefit goes direct” claim.', language) }, + ]; + const gates = [ + { title: pickText('具名路线与户外条件', 'Named Route & Outdoor Conditions', language), copy: pickText('入口与许可、里程/爬升/路面、带路人、天气阈值、补水/卫生间、儿童边界、车辆接回和中途退出。', 'Access and permission, distance, ascent and surface, guide, weather limits, water and toilets, child boundaries, vehicle recovery, and exit.', language) }, + { title: pickText('工坊、老师与真实课程', 'Studio, Teacher & Real Lesson', language), copy: pickText('具名工坊和授课人、实际技艺与工序、课程时长、语言支持、容量、费用、作品规格、干燥/寄送和取消。', 'Named studio and teacher, actual craft and stage, lesson duration, language support, capacity, fee, piece size, drying or shipping, and cancellation.', language) }, + { title: pickText('材料、工具与植物边界', 'Materials, Tools & Plant Boundary', language), copy: pickText('野生植物只观察或使用工坊合法准备材料;复核蜡/染料/热源/刀具/纸浆、通风、皮肤过敏、儿童操作和洗手处置。', 'Observe wild plants or use lawfully prepared studio material; check wax, dye, heat, blades, pulp, ventilation, skin allergy, child handling, and handwashing.', language) }, + { title: pickText('骑行、接驳与责任', 'Cycling, Transfer & Responsibility', language), copy: pickText('具名骑行道路、机动车混行、车辆尺寸、头盔、领骑/收队、保障车、保险、医疗点,以及两端迟到或取消时的责任边界。', 'Named cycle road, traffic mix, bike fit, helmet, lead and sweep, support vehicle, insurance, medical point, and responsibility when either half runs late or cancels.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('使用梯田慢行 + 一项具名手作的轻量版本,控制为完整半日或一日。', 'Use a gentle terrace walk plus one named craft as a full half-day or day.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('在高强度活动之间加入工坊恢复半日;植物观察或茶园步行只做低强度,不再叠加骑行。', 'Use a studio half-day for recovery between active days; keep plant or tea walking gentle and do not add cycling.', language), href: '/safaris/outdoor-coffee/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('最适合安排完整一日组合,在村寨慢住段或黔东南/茶区转场之间使用。', 'Best for one complete pairing day within a village stay or between Qiandongnan and tea-country transfers.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('山谷村落与田野组成的轻户外环境', +
+
+
+ + Unique Local Experiences / 05 +

{pickText('非遗与轻户外融合体验', 'Heritage & Gentle Outdoor Pairings', language)}

+

{pickText('一天只做一个组合,让路上的风景和手里的作品都拥有完整时间。', 'Choose one pairing per day, giving both the landscape and the piece in your hands enough time.', language)}

+ {pickText('查看三种组合', 'See the three pairings', language)} +
+
+
+ + + +
+
+ {pickText('体验定义', 'Experience Definition', language)} +

{pickText('融合不是把两个打卡点连起来,而是让两段体验在同一片土地上互相解释。', 'A pairing is not two check-ins—it lets two experiences explain the same landscape.', language)}

+

{pickText('客户文档提出“梯田徒步 + 蜡染”“峡谷寻草 + 古法造纸”“茶山骑行 + 制茶”。第一版保留三种方向,但要求户外路线与工坊在地理、时间和主题上真实相连。客户本组3张图片均为扎染打卡空间概念拼图,没有展示户外路线、造纸、茶园或制茶,因此只采用1张并明确标注为概念参考,其余画面使用客户此前提供的田野和手作素材。', 'The brief proposes terrace hiking with batik, canyon plants with papermaking, and tea-country cycling with tea craft. This first version keeps the three directions only when route and studio connect in place, time, and theme. All three supplied images are indigo check-in concept collages, showing no outdoor route, papermaking, tea field, or tea craft; one is retained and labelled as concept reference, while earlier client field and craft material carries the real visual story.', language)}

+
+
+
{pickText('核心原则', 'Core Rule', language)}
{pickText('一天只做一组;户外与非遗两端分别具名确认', 'One pairing per day; name and confirm both outdoor and heritage hosts', language)}
+
{pickText('地理候选', 'Geographic Candidates', language)}
{pickText('丹寨/安顺蜡染、丹寨石桥造纸、湄潭/普安茶区', 'Danzhai or Anshun batik, Shiqiao paper, Meitan or Pu’an tea country', language)}
+
{pickText('建议时长', 'Suggested Time', language)}
{pickText('完整半日至 1 日;骑行组合优先整日', 'Full half-day to one day; cycling pairing preferably a full day', language)}
+
{pickText('适合', 'Best For', language)}
{pickText('文化爱好者、亲子、慢旅行与轻至中等体力客人', 'Culture lovers, families, slow travellers, and low- to moderate-fitness guests', language)}
+
{pickText('作品', 'Take-Home Piece', language)}
{pickText('小型蜡染、手工纸或当季茶样;规格和寄送按工坊确认', 'Small batik, handmade paper, or seasonal tea sample; size and shipping by studio', language)}
+
{pickText('天气备选', 'Weather Fallback', language)}
{pickText('缩短户外、延长工坊;必要时只保留室内体验', 'Shorten outdoors and extend studio time; run indoors only when needed', language)}
+
+
+ +
+
{pickText('山谷田野与村落',
+
{pickText('客人在工坊完成小型手作',
+
{pickText('在地手艺人进行蜡染制作',
+
{pickText('客户提供的扎染空间概念参考',
{pickText('客户概念参考 · 非真实场地证明', 'Client concept reference · not proof of a real venue', language)}
+
+ +
+
+
01 / Paired Experiences

{pickText('每一组都必须回答:走哪里、跟谁学、为什么放在一起。', 'Every pairing must answer: where to go, who teaches, and why these belong together.', language)}

{pickText('三种组合不是套餐清单;只有路线、主理人、季节、材料和接驳都成立时,才进入正式方案。', 'These are not package checklists; route, host, season, material, and transfer must all work before entering a final plan.', language)}

+
{pairings.map((pairing) =>
{pairing.number}{pairing.label}

{pairing.title}

{pairing.copy}

{pairing.status}
)}
+
+
+ +
+
{pickText('工坊中的手作细节',
+
+
02 / Planning Flow

{pickText('先让两个真实主体说得通,再让页面看起来好看。', 'Make the two real hosts work together before making the page look seamless.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Operating Gates

{pickText('路线、老师或材料少一项,这一天就退回单项体验。', 'Lose the route, teacher, or materials and the day returns to one activity.', language)}

{pickText('“跟随传承人、识别本地植物、参与采茶制茶全过程、作品可带走”全部要落实到具名主体、当季工序和安全条件。', '“Learn from a heritage bearer, identify local plants, complete tea making, take the piece home” all require a named host, seasonal stage, and operating conditions.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('可以写入正式方案', 'What the final plan can promise', language)}
  • {pickText('具名户外路线、里程/爬升/路面、带路人、接驳和天气备选', 'Named route, distance, ascent, surface, guide, transfer, and weather fallback', language)}
  • {pickText('具名工坊/老师、真实课程、当季工序、材料、时长、费用与语言支持', 'Named studio or teacher, real lesson, seasonal stage, materials, duration, fee, and language support', language)}
  • {pickText('具体作品、是否当天带走/后寄,以及骑行车辆、头盔和保障方式', 'Exact piece and same-day collection or shipping, plus bikes, helmets, and support when cycling', language)}
+
{pickText('必须按主体和日期复核', 'Claims requiring host-and-date proof', language)}
  • {pickText('图片中的扎染空间为真实工坊、真实老师或实际可预约接待现场', 'That the pictured indigo space is a real studio, teacher, or bookable venue', language)}
  • {pickText('客人可在峡谷/山林自由采草、植物可安全接触/食用或用于造纸', 'That guests may freely collect plants or that plants are safe to touch, eat, or use in paper', language)}
  • {pickText('非遗传承人一对一、采茶与完整制茶全流程、所有作品当天完成带走', 'One-to-one heritage bearer, picking plus full tea production, or every piece finished that day', language)}
+
+

{pickText('公开依据参考:文化和旅游部门关于丹寨石桥古法造纸保护基地的资料、贵州林业部门野生植物采集要求,以及文化和旅游部普安茶海密林骑行线路案例。实际老师、路线、课程和开放状态仍按日期书面确认。', 'Public references include cultural authorities on the Shiqiao papermaking protection base, Guizhou forestry rules for wild-plant collection, and the Ministry of Culture and Tourism’s Pu’an tea-country cycling route. Actual teachers, routes, lessons, and opening remain date-specific.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('把文化放进地貌里,也把体力留给真正重要的那一段。', 'Place culture inside the landscape—and save energy for the part that matters.', language)}

+ +
+ + +
+ ); +} + +function HighBridgeAdventureReviewPage() { + const { language } = useTravelSite(); + const modes = [ + { + number: '01', + label: pickText('可优先核验', 'First to Verify', language), + title: pickText('桥梁观景与工程参观', 'Bridge Viewing & Engineering Visit', language), + copy: pickText( + '把坝陵河大桥作为黄果树附近的工程与峡谷景观节点,优先确认观景廊道、桥体参观或高空步行当天是否开放。它与蹦极不是同一种产品,也不要求客人接受自由坠落。', + 'Use Balinghe Bridge as an engineering and canyon-view stop near Huangguoshu, subject to same-day access for the viewing corridor, internal bridge visit, or elevated walk. This is separate from bungee and does not require free-fall exposure.', + language, + ), + status: pickText('开放时间、参观范围、年龄/行动能力与交通接驳仍需按日期确认', 'Hours, accessible areas, age/mobility rules, and transfers remain date-specific', language), + }, + { + number: '02', + label: pickText('高风险候选', 'High-Risk Candidate', language), + title: pickText('坝陵河大桥蹦极', 'Balinghe Bridge Bungee', language), + copy: pickText( + '可核验的历史事实是:吉尼斯世界纪录在 2019 年 1 月 12 日认证这里的商业蹦极设施高度为 370.25 米。页面不把这个设施高度写成客人的自由落差,也不在未确认当前运营状态前使用“世界最高”作为现行承诺。', + 'The verifiable historical fact is that Guinness World Records certified a 370.25 m commercial bungee facility here on 12 January 2019. This page does not equate facility height with guest free-fall distance or use “world’s highest” as a current promise without renewed confirmation.', + language, + ), + status: pickText('只有具名运营方、当天开放、限制条件、装备记录、教练、保险和救援全部通过后才进入正式行程', 'Enters a final itinerary only after the named operator, live availability, restrictions, equipment records, instructor, insurance, and rescue all pass', language), + }, + { + number: '03', + label: pickText('项目性质待确认', 'Activity Type Unconfirmed', language), + title: pickText('“破茧之翼”受控体验候选', '“Cocoon Wing” Controlled-Experience Candidate', language), + copy: pickText( + '客户图片证明现场存在特制服装与体验平台,但不足以证明它是普通人自由翼装飞行,也不能证明 260 米、飞越森林河道等宣传描述。正式对外前必须确认它究竟是悬吊、滑索、摆荡、定轨飞行或其他受控系统。', + 'The supplied image proves a special suit and platform exist, but not that this is free wingsuit flight for the public or that the proposed 260 m and landscape-flight claims are accurate. Before publication, the exact system—tether, zipline, swing, guided rail, or another controlled mechanism—must be identified.', + language, + ), + status: pickText('在产品名称、机械系统、运营规则与救援资料确认前不提供预约', 'No booking before the product name, mechanical system, operating rules, and rescue records are confirmed', language), + }, + ]; + const flow = [ + { title: pickText('先确认项目到底是什么', 'Define the Activity', language), copy: pickText('向运营方索取当前正式产品名、玩法说明、起终点、控制系统、实际高度/落差与可公开素材,避免把蹦极、跳伞、滑翔伞和“破茧之翼”混写。', 'Obtain the current official product name, operating description, start/finish, control system, actual height/drop, and approved media—without mixing bungee, parachuting, paragliding, and the “Cocoon Wing.”', language) }, + { title: pickText('核验运营主体与当天开放', 'Verify Operator & Availability', language), copy: pickText('确认具名运营主体、场地授权、预约渠道、开放日期、天气停运标准、维护停运与现场联系人。', 'Confirm the named operator, site authorization, booking channel, operating date, weather closure criteria, maintenance closure, and on-site contact.', language) }, + { title: pickText('取得限制条件与客人资料', 'Match Guest Restrictions', language), copy: pickText('书面确认年龄、身高、体重、健康、用药、既往病史、孕期、饮酒与运动经验限制,再筛查客人。', 'Obtain written age, height, weight, health, medication, medical-history, pregnancy, alcohol, and experience restrictions before screening guests.', language) }, + { title: pickText('核对装备、人员、保险与救援', 'Check Gear, Team, Insurance & Rescue', language), copy: pickText('核验装备标准与检查记录、操作人员身份与比例、通信、急救、峡谷救援、医疗转运和保险对具体项目的覆盖。', 'Verify gear standards and logs, operator identities and ratios, communication, first aid, canyon rescue, medical transfer, and insurance coverage for the exact activity.', language) }, + { title: pickText('出发前再次 Go / No-Go', 'Final Go / No-Go', language), copy: pickText('按当天风、雨、雷电、能见度、设备状态和客人状态决定执行、降级为观景,或完全取消;极限项目不作为行程完成度指标。', 'Use live wind, rain, lightning, visibility, equipment, and guest condition to operate, downgrade to viewing, or cancel. An extreme activity is never a trip-completion metric.', language) }, + ]; + const gates = [ + { title: pickText('合法运营与场地授权', 'Legal Operation & Site Authority', language), copy: pickText('具名主体、营业/经营范围、场地授权、当前可售产品、官方预约渠道与投诉/应急联系人。', 'Named entity, operating scope, site authorization, current sellable product, official booking channel, and complaint/emergency contact.', language) }, + { title: pickText('装备系统与维护记录', 'System & Maintenance Records', language), copy: pickText('绳索、连接、回收或飞行控制系统的标准、序列号/检查记录、维护周期、报废规则与当天双人复核。', 'Standards, serial/inspection records, maintenance cycle, retirement rules, and same-day dual check for ropes, connections, recovery, or flight-control systems.', language) }, + { title: pickText('人员、天气与救援', 'Team, Weather & Rescue', language), copy: pickText('现场负责人、操作人员、客人比例、风雨雷电停运阈值、通信、急救、垂直/峡谷救援与医疗转运演练。', 'Site lead, operators, guest ratio, wind/rain/lightning thresholds, communication, first aid, vertical/canyon rescue, and medical-transfer drills.', language) }, + { title: pickText('客人限制与专项保险', 'Guest Limits & Specialist Insurance', language), copy: pickText('年龄、身高、体重、健康和心理限制的书面依据,知情同意,以及保险是否明确覆盖具名场地和具体活动。', 'Written basis for age, size, health, and psychological limits, informed consent, and insurance explicitly covering the named site and exact activity.', language) }, + ]; + const routes = [ + { number: '01', title: pickText('经典全景线', 'Classic Panorama Route', language), copy: pickText('在黄果树段加入桥梁观景或工程参观;高风险项目只有专项资料通过后才替换进去。', 'Add bridge viewing or an engineering visit near Huangguoshu; a high-risk activity replaces it only after specialist evidence passes.', language), href: '/safaris/classic-guizhou/' }, + { number: '02', title: pickText('喀斯特户外探险线', 'Karst Outdoor Adventure', language), copy: pickText('可作为独立的高风险候选日,前后不叠加洞穴、瀑降或水上活动,并保留完整取消方案。', 'Use only as a standalone high-risk candidate day, without caves, abseiling, or water activity around it, and retain a full cancellation plan.', language), href: '/safaris/outdoor-coffee/' }, + { number: '03', title: pickText('均衡探索线', 'Balanced Discovery Route', language), copy: pickText('只建议桥梁观景和轻量参观,不让极限项目破坏村寨、人文与恢复节奏。', 'Prefer viewing and a gentle visit so an extreme activity does not disrupt village, cultural, and recovery pacing.', language), href: '/safaris/culture-outdoor-mix/' }, + ]; + + return ( +
+
+ {pickText('坝陵河大桥观景廊道与吉尼斯认证展示', +
+
+
+ + Specialist Verification / High Bridge +

{pickText('高桥观景与极限体验核验', 'High Bridge Viewing & Adventure Verification', language)}

+

{pickText('这不是预约商品页。先分清观景、蹦极与受控飞行体验,再用具名运营资料决定哪一项可以进入行程。', 'This is not a booking page. Separate viewing, bungee, and controlled-flight experiences before named operator evidence decides what can enter an itinerary.', language)}

+ {pickText('查看当前核验结论', 'See the current review', language)} +
+
+
+ + + +
+
+ {pickText('公开边界', 'Publication Boundary', language)} +

{pickText('可以展示真实证据,但不能把历史纪录写成今天的可售承诺。', 'Show real evidence without turning a historical record into today’s sellable promise.', language)}

+

{pickText('客户文档把坝陵河大桥观光、370 米蹦极、低空跳伞、滑翔伞和“破茧之翼”放在同一段。它们的运营主体、活动系统、客人限制和安全要求不同。第一版只公开地理位置、历史认证事实、客户提供的现场图和核验门槛;不提供直接预约。', 'The brief places Balinghe Bridge viewing, a 370 m bungee, parachuting, paragliding, and the “Cocoon Wing” in one section. These have different operators, systems, guest limits, and safety requirements. This first version publishes only location, the historical record fact, supplied on-site images, and verification gates—with no direct booking.', language)}

+
+
+
{pickText('地理依托', 'Geographic Base', language)}
{pickText('安顺关岭 / 坝陵河大桥 / 黄果树附近', 'Guanling, Anshun / Balinghe Bridge / near Huangguoshu', language)}
+
{pickText('当前定位', 'Current Position', language)}
{pickText('桥梁观景候选 + 两项专项核验候选', 'Bridge-viewing candidate + two specialist-review candidates', language)}
+
{pickText('历史事实', 'Historical Fact', language)}
{pickText('2019-01-12 获认证的商业蹦极设施高度:370.25 米', 'Commercial bungee facility certified on 12 Jan 2019: 370.25 m', language)}
+
{pickText('不直接承诺', 'Not Promised', language)}
{pickText('当前“世界最高”、实际自由落差、当天开放与普通人翼装飞行', 'Current “world’s highest,” actual free-fall, live operation, or public wingsuit flight', language)}
+
{pickText('适合讨论', 'Who Can Enquire', language)}
{pickText('先接受健康、体重、天气、保险和运营方规则筛查的成人客人', 'Adults willing to pass health, weight, weather, insurance, and operator screening', language)}
+
{pickText('默认备选', 'Default Fallback', language)}
{pickText('桥梁观景 / 工程参观 / 黄果树与峡谷摄影', 'Bridge viewing / engineering visit / Huangguoshu and canyon photography', language)}
+
+
+ +
+
{pickText('坝陵河大桥蹦极现场',
+
{pickText('坝陵河大桥廊道与吉尼斯证书展示',
+
{pickText('破茧之翼体验服装与平台',
+
+ +
+
+
01 / Current Review

{pickText('三种体验,三套完全不同的证据。', 'Three experiences. Three completely different evidence sets.', language)}

{pickText('观景可以先核验开放状态;蹦极需要专项运营与保险;“破茧之翼”必须先确认产品性质。', 'Viewing starts with access checks; bungee requires specialist operation and insurance; the “Cocoon Wing” first requires a clear product definition.', language)}

+
{modes.map((mode) =>
{mode.number}{mode.label}

{mode.title}

{mode.copy}

{mode.status}
)}
+
+
+ +
+
{pickText('高桥体验平台与特制服装',
+
+
02 / Verification Flow

{pickText('先把资料核清,再把客人带到桥上。', 'Clear the evidence before taking a guest to the bridge.', language)}

+
    {flow.map((item, index) =>
  1. {String(index + 1).padStart(2, '0')}

    {item.title}

    {item.copy}

  2. )}
+
+
+ +
+
+
03 / Launch Gates

{pickText('四类资料缺一项,高风险项目就不进入正式行程。', 'Miss one evidence group and the high-risk activity stays out.', language)}

{pickText('“正规商业运营、进口装备、专业教练一对一、完整安全预案”不能作为笼统宣传句,必须拆成具名文件与可核验记录。', '“Regular commercial operation, imported equipment, one-to-one professional instructor, and complete safety plan” cannot remain generic marketing lines; each must become named documents and verifiable records.', language)}

+
{gates.map((gate, index) =>
{String(index + 1).padStart(2, '0')}

{gate.title}

{gate.copy}

)}
+
+
{pickText('当前可以公开', 'Safe to publish now', language)}
  • {pickText('坝陵河大桥的位置与桥梁/峡谷观景方向', 'Balinghe Bridge location and its bridge/canyon viewing direction', language)}
  • {pickText('2019 年 1 月 12 日、370.25 米商业蹦极设施的历史认证事实', 'The historical 12 Jan 2019 certification of a 370.25 m commercial bungee facility', language)}
  • {pickText('三张客户提供的现场图,以及“按日期专项核验”的状态', 'Three client-supplied on-site images and the date-specific specialist-review status', language)}
+
{pickText('暂不作为可售承诺', 'Not a sellable promise yet', language)}
  • {pickText('当前“世界最高”、370 米实际自由落差、低空跳伞或滑翔伞日常开放', 'Current “world’s highest,” a 370 m actual free-fall, or daily parachuting/paragliding access', language)}
  • {pickText('“普通人翼装飞行”、260 米高度、飞越森林河道或玻璃桥', '“Public wingsuit flight,” a 260 m height, or flight over forest, river, or glass bridge', language)}
  • {pickText('进口装备、一对一教练、完整预案、年龄/体重范围与保险已覆盖', 'Imported equipment, one-to-one instructor, complete response plan, age/weight range, or confirmed insurance', language)}
+
+

{pickText('纪录口径:吉尼斯世界纪录“Highest commercial bungee jump facility”页面,认证日期 2019-01-12;当前开放与运营资料仍须由业务取得运营方书面确认。', 'Record wording: Guinness World Records, “Highest commercial bungee jump facility,” achieved 12 January 2019. Current access and operating evidence still require written confirmation from the operator.', language)}

+
+
+ +
+
04 / Route Pairing

{pickText('桥梁观景可以顺路,高风险项目必须独立决策。', 'Viewing can sit on the route; high risk requires its own decision.', language)}

+ +
+ + +
+ ); +} + +function GenericExperienceDetailPage({ slug }: { slug: string }) { const { cmsContent, language } = useTravelSite(); const experience = experienceFromSlug(slug); const baseDetail = makeExperiencePoiDetail(experience); @@ -3036,6 +6963,32 @@ function ExperienceDetailPage({ slug }: { slug: string }) { return ; } +function ExperienceDetailPage({ slug }: { slug: string }) { + if (slug === 'cave-expedition') return ; + if (slug === 'stream-tracing') return ; + if (slug === 'rafting') return ; + if (slug === 'karst-hiking-cycling') return ; + if (slug === 'cave-luxury-dining') return ; + if (slug === 'wild-coffee') return ; + if (slug === 'village-immersion') return ; + if (slug === 'ethnic-festivals') return ; + if (slug === 'culinary-cultural-journeys') return ; + if (slug === 'iconic-waterfall-clusters') return ; + if (slug === 'indigenous-ethnic-villages') return ; + if (slug === 'historic-ancient-towns') return ; + if (slug === 'qingyan-battle-immersive-roleplay') return ; + if (slug === 'loushanguan-battle-immersive-march') return ; + if (slug === 'loushanguan') return ; + if (slug === 'karst-glamping') return ; + if (slug === 'miao-craft') return ; + if (slug === 'dong-indigo-architecture') return ; + if (slug === 'shui-horsetail-embroidery') return ; + if (slug === 'buyi-traditional-crafts') return ; + if (slug === 'heritage-outdoor-pairings') return ; + if (slug === 'high-bridge-adventure') return ; + return ; +} + function PoiDetailPage({ detail, breadcrumbs }: { detail: PoiDetailContent; breadcrumbs: string[] }) { const { language } = useTravelSite(); const [activeGalleryIndex, setActiveGalleryIndex] = useState(null); @@ -3140,7 +7093,12 @@ function PoiDetailPage({ detail, breadcrumbs }: { detail: PoiDetailContent; brea function SafariDetailPage({ slug }: { slug: string }) { const { language, cmsContent } = useTravelSite(); - const safari = safariFromSlug(slug, cmsContent, language); + const isClassicPanorama = slug === 'classic-guizhou'; + const isOutdoorAdventure = slug === 'outdoor-coffee'; + const isBalancedDiscovery = slug === 'culture-outdoor-mix'; + const isEditorialRoute = isClassicPanorama || isOutdoorAdventure || isBalancedDiscovery; + const canonicalRoute = safariCards.find((route) => route.slug === slug) || safariCards[0]; + const safari = isEditorialRoute ? localizeSafariCard(canonicalRoute, language) : safariFromSlug(slug, cmsContent, language); const [activeGalleryIndex, setActiveGalleryIndex] = useState(null); const routePillars = routeDetailPillars(safari, language); const [routeFlow, stayPlan, foodPlan, transportPlan] = routePillars; @@ -3150,9 +7108,189 @@ function SafariDetailPage({ slug }: { slug: string }) { alt: language === 'zh' ? `${safari.title}图册 ${index + 1}` : `${safari.title} gallery ${index + 1}`, caption: safari.title, })); + const classicFacts = [ + { label: pickText('建议天数', 'Suggested length', language), value: safari.duration }, + { label: pickText('起止点', 'Start / finish', language), value: pickText('贵阳 / 贵阳', 'Guiyang / Guiyang', language) }, + { label: pickText('体力等级', 'Physical level', language), value: safari.intensity }, + { label: pickText('适合人群', 'Best for', language), value: safari.bestFor[0] }, + { label: pickText('最佳季节', 'Best season', language), value: safari.season }, + { label: pickText('旅行方式', 'Travel model', language), value: safari.service }, + ]; + const outdoorFacts = [ + { label: pickText('建议天数', 'Suggested length', language), value: safari.duration }, + { label: pickText('起止点', 'Start / finish', language), value: pickText('贵阳 / 贵阳', 'Guiyang / Guiyang', language) }, + { label: pickText('活动等级', 'Activity grade', language), value: pickText('轻量 / 活力 / 进阶三档', 'Light / Active / Advanced', language) }, + { label: pickText('适合人群', 'Best for', language), value: safari.bestFor[0] }, + { label: pickText('执行门槛', 'Operating gate', language), value: pickText('天气、水位、运营方与客人状态', 'Weather, water, operator and guest condition', language) }, + { label: pickText('旅行方式', 'Travel model', language), value: safari.service }, + ]; + const balancedFacts = [ + { label: pickText('建议天数', 'Suggested length', language), value: safari.duration }, + { label: pickText('起止点', 'Start / finish', language), value: pickText('贵阳 / 贵阳', 'Guiyang / Guiyang', language) }, + { label: pickText('节奏原则', 'Rhythm rule', language), value: pickText('每天一个核心预约', 'One core booking per day', language) }, + { label: pickText('适合人群', 'Best for', language), value: safari.bestFor[0] }, + { label: pickText('户外强度', 'Outdoor level', language), value: pickText('轻量,可替换', 'Gentle and replaceable', language) }, + { label: pickText('旅行方式', 'Travel model', language), value: safari.service }, + ]; + const routeFacts = isOutdoorAdventure ? outdoorFacts : isBalancedDiscovery ? balancedFacts : classicFacts; + const classicRouteStops = [ + { place: pickText('贵阳', 'Guiyang', language), transfer: pickText('抵达与适应', 'Arrival and reset', language) }, + { place: pickText('黄果树', 'Huangguoshu', language), transfer: pickText('约 2–2.5 小时', 'about 2–2.5 hr', language) }, + { place: pickText('荔波', 'Libo', language), transfer: pickText('约 4–5 小时', 'about 4–5 hr', language) }, + { place: pickText('西江 / 黔东南', 'Xijiang / Southeast Guizhou', language), transfer: pickText('约 3.5–4 小时', 'about 3.5–4 hr', language) }, + { place: pickText('镇远', 'Zhenyuan', language), transfer: pickText('约 2–3 小时', 'about 2–3 hr', language) }, + { place: pickText('贵阳', 'Guiyang', language), transfer: pickText('约 3.5–4 小时', 'about 3.5–4 hr', language) }, + ]; + const outdoorRouteStops = [ + { place: pickText('贵阳', 'Guiyang', language), transfer: pickText('抵达、问询与装备清单', 'Arrival, assessment and gear list', language) }, + { place: pickText('北部洞穴区域', 'Northern cave country', language), transfer: pickText('转场 + 基础适应', 'Transfer + field adaptation', language) }, + { place: pickText('洞穴主活动', 'Cave activity base', language), transfer: pickText('单日单项,不叠加技术活动', 'One core activity, no technical stacking', language) }, + { place: pickText('安顺 / 黔南', 'Anshun / Qiannan', language), transfer: pickText('恢复后进入峡谷河谷', 'Recover, then enter canyon country', language) }, + { place: pickText('山野咖啡', 'Mountain coffee', language), transfer: pickText('轻量活动与恢复', 'Light movement and recovery', language) }, + { place: pickText('贵阳', 'Guiyang', language), transfer: pickText('返程安全缓冲', 'Departure safety buffer', language) }, + ]; + const balancedRouteStops = [ + { place: pickText('贵阳', 'Guiyang', language), transfer: pickText('抵达与偏好对齐', 'Arrival and preference alignment', language) }, + { place: pickText('黔东南村寨', 'Southeast Guizhou village', language), transfer: pickText('主人见面与慢住', 'Meet the host and settle in', language) }, + { place: pickText('梯田与工坊', 'Terraces and workshop', language), transfer: pickText('本地人带路 + 一项非遗', 'Local-led walk + one heritage craft', language) }, + { place: pickText('荔波 / 黔南', 'Libo / Qiannan', language), transfer: pickText('一项轻户外', 'One gentle outdoor day', language) }, + { place: pickText('山野停靠', 'Mountain pause', language), transfer: pickText('咖啡、短步行或观景', 'Coffee, short walk or a view', language) }, + { place: pickText('贵阳', 'Guiyang', language), transfer: pickText('城市收尾与返程', 'City finish and departure', language) }, + ]; + const activeRouteStops = isOutdoorAdventure ? outdoorRouteStops : isBalancedDiscovery ? balancedRouteStops : classicRouteStops; + const outdoorGrades = [ + { + level: pickText('01 / 轻量', '01 / Light', language), + title: pickText('先建立舒适感', 'Build comfort first', language), + activities: pickText('入门洞穴步行、峰林轻徒步、平缓水域或森林步道。', 'Intro cave walking, gentle peak-forest hikes, calm water or forest trails.', language), + fit: pickText('第一次参加户外、怕水/怕高或希望保留更多休息的人。', 'First-time outdoor guests, anyone uneasy with water/heights, or travelers who want more rest.', language), + }, + { + level: pickText('02 / 活力', '02 / Active', language), + title: pickText('有挑战,但可随时降级', 'A challenge with a clear downgrade', language), + activities: pickText('洞穴桨板、溯溪、峡谷步行或较长距离峰林徒步。', 'Cave paddleboarding, stream tracing, canyon walks or a longer peak-forest hike.', language), + fit: pickText('具备基础体能、能听从现场指令,并接受因水位替换项目的人。', 'Guests with basic fitness who follow field instructions and accept water-led substitutions.', language), + }, + { + level: pickText('03 / 进阶', '03 / Advanced', language), + title: pickText('只在完整核验后开放', 'Available only after full validation', language), + activities: pickText('未开发洞段、瀑降、技术绳索或激流水域,仅作为候选方向。', 'Undeveloped cave sections, waterfall rappel, technical rope work or white water remain candidate options only.', language), + fit: pickText('需验证既往经验、专项保险、运营资质、装备系统和撤离能力。', 'Requires verified prior experience, specialist insurance, operator records, equipment systems and evacuation ability.', language), + }, + ]; + const outdoorSafetyGates = [ + { title: pickText('天气与水位', 'Weather & water', language), copy: pickText('降雨、上游水情、能见度或现场道路任何一项不合适,项目降级或取消。', 'Rainfall, upstream conditions, visibility or access can downgrade or cancel the activity.', language) }, + { title: pickText('运营方与带队人员', 'Operator & field team', language), copy: pickText('具体公司、领队/向导资料、活动边界与应急联系人写入正式方案。', 'The operator, field-team records, operating boundary and emergency contact are stated in the final plan.', language) }, + { title: pickText('装备、通信与保险', 'Gear, comms & insurance', language), copy: pickText('头盔、照明、绳索/浮具、通信、保暖和保险逐项确认,不用笼统“全套保障”代替。', 'Helmet, lighting, rope/flotation gear, communication, warmth and insurance are checked item by item.', language) }, + { title: pickText('客人状态与退出机制', 'Guest condition & exit plan', language), copy: pickText('出发当天仍要确认睡眠、伤病、恐惧与执行能力,并预先说明撤回点和替代安排。', 'Sleep, injury, fear and ability are rechecked on the day, with exit points and alternatives explained first.', language) }, + ]; + const balancedLayers = [ + { + label: pickText('01 / 慢住', '01 / Stay', language), + title: pickText('先让村寨成为生活场景', 'Let the village become a lived setting', language), + copy: pickText('连续住下,保留夜晚、清晨、主人家常饭与社区节奏,而不是日间参观后马上离开。', 'Stay long enough to keep the evening, morning, home meal and community rhythm rather than leaving after a daytime visit.', language), + }, + { + label: pickText('02 / 跟随', '02 / Follow', language), + title: pickText('让熟悉土地和手艺的人带路', 'Follow people who know the land and craft', language), + copy: pickText('梯田交给本地农民,手作交给真实工坊;体验随农时、季节和主人档期确认。', 'Terraces belong with a local farmer and craft with a real workshop, all confirmed around season and host availability.', language), + }, + { + label: pickText('03 / 轻探', '03 / Explore', language), + title: pickText('自然段只保留一项核心活动', 'Keep one core nature experience', language), + copy: pickText('水森林、河谷步道、平缓水域或入门洞穴四选一,让风景拥有时间,而不是成为任务清单。', 'Choose one water forest, river trail, calm-water or intro-cave day so nature has time instead of becoming a checklist.', language), + }, + ]; + const balancedFlexRules = [ + { label: pickText('需要提前锁定', 'Book first', language), value: pickText('村寨住宿、主人、农民带路、非遗老师与交通', 'Village stay, host, local farmer, heritage maker and transport', language) }, + { label: pickText('当天可以调整', 'Flex on the day', language), value: pickText('停留时长、轻量散步、休息、咖啡与用餐节奏', 'Stay time, gentle walks, rest, coffee and meal rhythm', language) }, + { label: pickText('可能需要替换', 'May be replaced', language), value: pickText('受天气、农时、节庆和社区日常影响的活动', 'Activities affected by weather, farming, festivals or community life', language) }, + ]; + const classicPracticalNotes = [ + { + title: pickText('山地天气', 'Mountain weather', language), + copy: pickText('贵州天气与水量变化快,轻薄分层、防雨外套和防滑鞋比单一厚衣更实用。', 'Weather and water levels change quickly. Light layers, rain protection and grippy shoes are more useful than one heavy layer.', language), + }, + { + title: pickText('车程不是固定承诺', 'Drive times are estimates', language), + copy: pickText('页面车程用于路线判断;最终方案会按酒店位置、道路、天气和节假日重新核对。', 'Times help with route decisions; the final plan is rechecked against hotels, roads, weather and holiday traffic.', language), + }, + { + title: pickText('酒店逐家确认', 'Hotels checked one by one', language), + copy: pickText('预订前逐家确认当前涉外接待条件、房态与图片,不使用“全部具备涉外资质”的笼统承诺。', 'Current foreign-guest reception, rooms and imagery are confirmed property by property before booking.', language), + }, + { + title: pickText('护照与预约', 'Passport and reservations', language), + copy: pickText('景区、酒店和交通可能需要护照信息;具体提交时间与隐私说明在确认方案后告知。', 'Attractions, hotels and transport may require passport details; timing and privacy handling are explained after the plan is aligned.', language), + }, + { + title: pickText('保险单列确认', 'Insurance confirmed separately', language), + copy: pickText('旅行保险是否包含、保障范围与承保主体必须在正式报价中单独列明。', 'Whether insurance is included, its coverage and provider must be stated separately in the formal quote.', language), + }, + { + title: pickText('每天都可讨论', 'Every day stays discussable', language), + copy: pickText('开放时间和安全条件允许时,可按体力、天气与兴趣调整停留时长和先后顺序。', 'When opening hours and safety allow, stay times and sequencing can flex around energy, weather and interests.', language), + }, + ]; + const outdoorPracticalNotes = [ + { + title: pickText('先分级,再选项目', 'Grade first, choose second', language), + copy: pickText('页面中的洞穴、峡谷与亲水活动是候选库,不代表每位客人或每个日期都能执行。', 'The cave, canyon and water activities form a candidate library; they are not guaranteed for every guest or date.', language), + }, + { + title: pickText('每天重新做 Go / No-go', 'A fresh Go / No-go each day', language), + copy: pickText('即使出发前确认过,现场天气、水位、道路与身体状态仍拥有最终否决权。', 'Even after pre-trip approval, field weather, water, access and guest condition retain the final veto.', language), + }, + { + title: pickText('不连续堆叠技术活动', 'No back-to-back technical stacking', language), + copy: pickText('技术型主活动之间安排恢复或转场日,避免为了“项目更多”牺牲判断力与体力。', 'Recovery or transfer days sit between technical activities so volume never overrides judgment and energy.', language), + }, + { + title: pickText('个人信息必须如实', 'Health disclosure matters', language), + copy: pickText('既往伤病、用药、心肺情况、怕水怕高与户外经验都会影响活动等级。', 'Past injury, medication, cardio-respiratory condition, water/height fear and experience all affect the grade.', language), + }, + { + title: pickText('保险与责任单列', 'Insurance stated separately', language), + copy: pickText('旅行保险、专项户外保险、承保主体、免责范围与理赔方式必须在确认文件中单列。', 'Travel and specialist cover, provider, exclusions and claims process must be stated separately in confirmation documents.', language), + }, + { + title: pickText('极限项目暂不发布', 'Extreme options are withheld', language), + copy: pickText('370 米蹦极、翼装及未经核验的高度、证书、年龄承诺不进入当前可售清单。', 'The 370 m bungee, wingsuit and unverified height, certificate or age claims do not enter the current sellable list.', language), + }, + ]; + const balancedPracticalNotes = [ + { + title: pickText('灵活不等于不预约', 'Flexible does not mean unbooked', language), + copy: pickText('主人、农民带路人、非遗工坊、住宿和车辆先锁定,再在可行范围内调整当天节奏。', 'Hosts, local farmers, workshops, stays and vehicles are booked first; the day then flexes inside those realities.', language), + }, + { + title: pickText('村寨住宿逐家确认', 'Village stays checked one by one', language), + copy: pickText('卫浴、卫生、隔音、行李搬运、涉外接待与真实图片都需在预订前确认。', 'Bathroom setup, hygiene, sound, luggage access, foreign-guest reception and real imagery are checked before booking.', language), + }, + { + title: pickText('不把生活保证成表演', 'Daily life is not staged on demand', language), + copy: pickText('鼓楼对歌、农事、集市与节庆取决于社区真实安排,不能承诺每天固定出现。', 'Drum-tower singing, farming, markets and festivals follow real community schedules and cannot be promised daily.', language), + }, + { + title: pickText('手作只选一项做深', 'One craft, enough time', language), + copy: pickText('蜡染、刺绣、草木染或马尾绣根据地区、老师与客人耐心选择,不做流水线拼盘。', 'Batik, embroidery, indigo or horsetail work is chosen by place, maker and guest patience—not compressed into a sampler.', language), + }, + { + title: pickText('节庆日期需复核', 'Festival dates need reconfirmation', language), + copy: pickText('农历节庆、村寨批次与具体活动每年可能变化,正式方案以当年官方和社区信息为准。', 'Lunar festivals, village rotations and events can change; the final plan follows current official and community information.', language), + }, + { + title: pickText('轻户外仍看天气', 'Gentle outdoors still follows weather', language), + copy: pickText('水森林、河谷与入门洞穴也可能因降雨、水位或道路调整,地面文化备选会提前准备。', 'Water forests, valleys and intro caves can still change with rain, water or access, so a cultural ground alternative is prepared.', language), + }, + ]; + const practicalNotes = isOutdoorAdventure ? outdoorPracticalNotes : isBalancedDiscovery ? balancedPracticalNotes : classicPracticalNotes; const detailSections = [ safari.gallery.length > 0 && { label: translateUi('图册', language), id: 'gallery', iconKey: 'detail-tab-gallery' }, { label: translateUi('预览', language), id: 'overview', iconKey: 'detail-tab-overview' }, + isEditorialRoute && { label: 'Why This Trip', id: 'why-this-trip', iconKey: 'detail-tab-description' }, + isOutdoorAdventure && { label: pickText('活动分级', 'Activity Levels', language), id: 'activity-levels', iconKey: 'detail-tab-overview' }, + isBalancedDiscovery && { label: pickText('平衡方式', 'How It Balances', language), id: 'balance-framework', iconKey: 'detail-tab-overview' }, routeFlow && { label: routeFlow.label, id: routeFlow.id, iconKey: 'detail-tab-itineraries' }, ...servicePillars.map((pillar) => ({ label: pillar.label, @@ -3165,10 +7303,11 @@ function SafariDetailPage({ slug }: { slug: string }) { : 'detail-tab-location', })), { label: translateUi('费用说明', language), id: 'includes', iconKey: 'detail-tab-rates' }, + isEditorialRoute && { label: pickText('安全说明', 'Safety', language), id: 'practical', iconKey: 'detail-tab-includes' }, ].filter(Boolean) as AnchorTabItem[]; return ( -
+
{pickText('先判断这是不是你的贵州线路', 'First, decide if this is your Guizhou route', language)}

{safari.summary}

{safari.routeLine}

+ {isEditorialRoute && ( +
+ {routeFacts.map((fact) => ( +
+
{fact.label}
+
{fact.value}
+
+ ))} +
+ )}
{translateUi('适合:', language)}{safari.bestFor[0]} {translateUi('核心:', language)}{safari.focus} @@ -3207,7 +7356,7 @@ function SafariDetailPage({ slug }: { slug: string }) {
+ {isEditorialRoute && ( +
+
+
+ Why This Trip +

+ {isOutdoorAdventure + ? pickText('不是比谁更冒险,而是把野性与判断力放在一起。', 'Wild by design, never careless by default.', language) + : isBalancedDiscovery + ? pickText('不在文化与自然之间取舍,而是给两者同样的时间。', 'Culture and nature, each given enough time.', language) + : pickText('不是多看三个景点,而是多靠近三次真实生活。', 'Three moments that move beyond sightseeing.', language)} +

+
+

+ {isOutdoorAdventure + ? pickText('三项亮点来自客户的户外线资料;洞段、咖啡点与带队团队仍需按日期、天气、资质和现场条件复核。', 'These three ideas come from the client outdoor brief; the cave section, coffee stop and field team are reconfirmed against dates, weather, records and site conditions.', language) + : isBalancedDiscovery + ? pickText('三项亮点来自客户确认的均衡探索线内容。村寨主人、农民带路人和工坊需按季节、社区安排与接待条件复核。', 'These three moments come from the confirmed Balanced Discovery brief. Hosts, local farmers and workshops are reconfirmed around season, community life and reception conditions.', language) + : pickText('这三个亮点来自客户确认的经典全景线内容。具体家庭、村寨和工坊仍需按出行日期与主理人档期复核。', 'These defining moments come from the confirmed Classic Panorama brief. The family, village and artisan availability is still reconfirmed for your dates.', language)} +

+
+
+ {safari.highlights.slice(0, 3).map((highlight, index) => ( +
+ +
+ {String(index + 1).padStart(2, '0')} +

{highlight.title}

+

{highlight.copy}

+
+
+ ))} +
+
+ )} + {isOutdoorAdventure && ( +
+
+
+
+ {pickText('活动分级', 'Activity Levels', language)} +

{pickText('同一片山野,也应该有三种进入方式。', 'One landscape, three ways to enter it.', language)}

+
+

{pickText('等级不是营销标签,而是决定活动边界、人员配置、装备、保险和替代方案的第一步。最终等级可低于预选,不会在现场临时升级。', 'The grade is an operating decision covering boundaries, people, equipment, insurance and alternatives. It can be lowered after review, never casually upgraded in the field.', language)}

+
+
+ {outdoorGrades.map((grade) => ( +
+ {grade.level} +

{grade.title}

+

{grade.activities}

+ {grade.fit} +
+ ))} +
+
+
+ GO / NO-GO +

{pickText('四项全部通过,活动才出发。', 'All four gates must pass.', language)}

+

{pickText('任何一项不满足,就执行已准备好的降级或地面替代方案。', 'If one gate fails, the prepared downgrade or ground-based alternative takes over.', language)}

+
+
    + {outdoorSafetyGates.map((gate, index) => ( +
  1. + {String(index + 1).padStart(2, '0')} +
    {gate.title}

    {gate.copy}

    +
  2. + ))} +
+
+
+
+ )} + {isBalancedDiscovery && ( +
+
+
+
+ {pickText('均衡方式', 'How It Balances', language)} +

{pickText('慢住、跟随、轻探:三层,而不是三张清单。', 'Stay, follow, explore—three layers, not three checklists.', language)}

+
+

{pickText('村寨生活是主线,手艺人与本地农民把文化讲具体,轻户外负责打开地貌;三者互相留时间,不争抢同一天。', 'Village life forms the spine, makers and local farmers make culture tangible, and gentle outdoors opens the landscape. Each gets its own time.', language)}

+
+
+ {balancedLayers.map((layer) => ( +
+ {layer.label} +

{layer.title}

+

{layer.copy}

+
+ ))} +
+
+
+ {pickText('弹性边界', 'The Flex Boundary', language)} +

{pickText('先把重要的人约好,再给一天留白。', 'Book the people who matter, then leave space in the day.', language)}

+
+
+ {balancedFlexRules.map((rule) => ( +
{rule.label}
{rule.value}
+ ))} +
+
+
+
+ )}

{pickText('线路动线先决定体验上限', 'The Route Flow Sets the Experience Ceiling', language)}

{routeFlow.summary}

-
- -
{safari.routeLine}
-
+ {isEditorialRoute ? ( +
+
+ {pickText('路线示意', 'Route schematic', language)} + + {isOutdoorAdventure + ? pickText('活动点通过安全核验后再锁定', 'Activity sites lock only after safety review', language) + : isBalancedDiscovery + ? pickText('村寨与主理人档期确认后锁定', 'Hosts and village stays lock before routing', language) + : pickText('车程为规划参考', 'Drive times are planning references', language)} + +
+
    + {activeRouteStops.map((stop, index) => ( +
  1. + {String(index + 1).padStart(2, '0')} +
    + {stop.place} + {stop.transfer} +
    +
  2. + ))} +
+
{safari.routeLine}
+
+ ) : ( +
+ +
{safari.routeLine}
+
+ )}
{safari.itinerary.map((day) => (
@@ -3237,6 +7519,16 @@ function SafariDetailPage({ slug }: { slug: string }) {

{day.place}

{day.title}

{day.copy}

+ {isEditorialRoute && day.schedule && ( +
+
{pickText('上午', 'Morning', language)}
{day.schedule.morning}
+
{pickText('下午', 'Afternoon', language)}
{day.schedule.afternoon}
+
{pickText('晚上', 'Evening', language)}
{day.schedule.evening}
+
{pickText('车程', 'Drive', language)}
{day.schedule.drive}
+
{pickText('用餐', 'Meals', language)}
{day.schedule.meals}
+
{pickText('住宿', 'Stay', language)}
{day.schedule.stay}
+
+ )}
{day.meta.map((item) => ( {item} @@ -3291,6 +7583,40 @@ function SafariDetailPage({ slug }: { slug: string }) {
+ {isEditorialRoute && ( +
+
+
+
+ {pickText('安全与实用信息', 'Safety & practical notes', language)} +

+ {isOutdoorAdventure + ? pickText('好的户外线路,首先知道什么时候不出发。', 'A good outdoor route knows when not to go.', language) + : isBalancedDiscovery + ? pickText('真正的弹性,来自提前知道什么不能随便改。', 'Real flexibility starts by knowing what cannot move casually.', language) + : pickText('把不确定性提前讲清楚。', 'Make uncertainty visible before departure.', language)} +

+
+

+ {isOutdoorAdventure + ? pickText('这些不是免责小字,而是路线设计的一部分:安全边界越清楚,客人越能放心享受真正适合自己的挑战。', 'These are not waiver fine print. Clear operating boundaries let guests enjoy the challenge that genuinely fits them.', language) + : isBalancedDiscovery + ? pickText('这条线依赖真实的人与社区日常。先尊重预约、季节和接待边界,才能把剩余时间真正还给客人。', 'This route depends on real people and community life. Respecting bookings, season and reception boundaries is what gives the remaining time back to the guest.', language) + : pickText('经典线强度不高,但山地天气、跨区车程、酒店接待条件和预约信息仍会直接影响体验。', 'The route is not highly strenuous, but mountain weather, regional drives, hotel reception rules and reservations still shape the journey.', language)} +

+
+
+ {practicalNotes.map((note, index) => ( +
+ {String(index + 1).padStart(2, '0')} +

{note.title}

+

{note.copy}

+
+ ))} +
+
+
+ )}
{translateUi('线路咨询', language)} @@ -3562,6 +7888,196 @@ function BlogPage() { return ; } +function CultureLandingPage() { + const { language } = useTravelSite(); + const crafts = [ + { + number: '01', + title: pickText('苗族蜡染・苗绣', 'Miao Batik & Embroidery', language), + place: pickText('丹寨 / 安顺 / 黔东南候选', 'Danzhai / Anshun / Qiandongnan candidates', language), + copy: pickText('从纹样、点蜡或针法中选择一个适合当日时长的主题;老师身份、材料、翻译与成品带走条件在确认单中写清。', 'Choose one theme—motif, wax drawing, or stitch technique—suited to the time available. Host identity, materials, interpretation, and take-home terms are written into the confirmation.', language), + href: '/experiences/miao-craft/', + }, + { + number: '02', + title: pickText('侗族草木染・木构建筑', 'Dong Indigo & Timber Architecture', language), + place: pickText('黔东南侗寨 / 具名工坊待确认', 'Qiandongnan Dong villages / named workshop to confirm', language), + copy: pickText('把侗布染制和鼓楼、风雨桥的木构观察组合起来;不把公开参观包装成“传承人私享课”。', 'Pair Dong textile dyeing with close observation of drum towers and wind-and-rain bridges, without presenting a public visit as a private masterclass.', language), + href: '/experiences/dong-indigo-architecture/', + }, + { + number: '03', + title: pickText('水族马尾绣', 'Shui Horsetail Embroidery', language), + place: pickText('三都水族自治县 / 具名老师待确认', 'Sandu Shui Autonomous County / named teacher to confirm', language), + copy: pickText('先理解马尾作芯、丝线缠裹的结构,再决定是演示、短时体验还是深度学习;不承诺短课完成复杂绣品。', 'First understand the horsehair core and silk-wrapping structure, then choose a demonstration, short session, or deeper study—without promising a complex finished piece in a brief class.', language), + href: '/experiences/shui-horsetail-embroidery/', + }, + { + number: '04', + title: pickText('布依族传统织染技艺', 'Buyi Traditional Textile Crafts', language), + place: pickText('黔西南或安顺候选 / 以工坊为准', 'Qianxinan or Anshun candidate / workshop-led', language), + copy: pickText('按实际村寨选择织布、服饰或染色其中一项,不把不同地区、不同支系的技艺拼成一个笼统产品。', 'Select one locally grounded theme—weaving, dress, or dyeing—rather than combining techniques from different communities into one generic product.', language), + href: '/experiences/buyi-traditional-crafts/', + }, + ]; + const principles = [ + [pickText('具名接待', 'Named host', language), pickText('确认谁讲、谁教、在哪里发生,以及英文沟通如何完成。', 'Confirm who hosts, who teaches, where it happens, and how English communication works.', language)], + [pickText('时间真实', 'Honest duration', language), pickText('区分看懂工艺、动手体验和完成作品,不把十分钟演示写成大师课。', 'Separate understanding, hands-on practice, and making a finished piece; a ten-minute demo is not a masterclass.', language)], + [pickText('同意与拍摄', 'Consent & photography', language), pickText('人物肖像、家庭空间、仪式与作品拍摄都在现场再次征得同意。', 'Reconfirm consent for portraits, homes, ceremonies, and works before photographing.', language)], + [pickText('收益可说明', 'Visible benefit path', language), pickText('把工坊费、材料费、翻译和社区接待拆开说明,保留真实结算记录。', 'Separate workshop, materials, interpretation, and community hosting fees, with a clear settlement record.', language)], + ]; + + return ( +
+
+
+ + Living Heritage · Guizhou +

+ {pickText('不是看一场表演,', 'Not a performance,', language)} + {pickText('而是认识一门仍在生活里的手艺。', 'but a craft still held in daily life.', language)} +

+

{pickText('贵州有 17 个世居少数民族。我们从具名工坊、真实村寨与可说明的接待关系出发,把手艺、建筑、餐食和生活节奏放进一段尊重彼此的旅程。', 'Guizhou is home to 17 long-established ethnic minority groups. We begin with named workshops, real villages, and accountable hosting—placing craft, architecture, food, and daily rhythm inside a journey built on mutual respect.', language)}

+ +
+
17
{pickText('个世居少数民族', 'long-established ethnic minority groups', language)}
+
4
{pickText('类核心手作方向', 'core craft directions', language)}
+
1:1
{pickText('按日期核对接待关系', 'date-specific host verification', language)}
+
+
+
+ {pickText('贵州蜡染工坊里的蓝染作品与工作台', +
+ {pickText('手艺人在布面进行传统蜡染制作', +
{pickText('客户提供实拍 · 具体工坊与接待人按日期确认', 'Client-supplied photography · exact workshop and host confirmed by date', language)}
+
+
+
+ + + +
+
+ 01 / Heritage Craft Workshops +

{pickText('一抹贵州蓝,只是理解这片文化的起点。', 'Guizhou blue is only the beginning of the story.', language)}

+

{pickText('传统染织的魅力不在于快速复制一个“民族风”纪念品,而在于看见材料、纹样、时间与人的关系。页面先列出四个可规划方向;最终只选择有具名老师、真实档期和合适时长的一项进入行程。', 'The value of traditional textile craft is not a quick “ethnic-style” souvenir. It lies in the relationship between material, motif, time, and maker. These four directions are planning options; only an experience with a named teacher, real availability, and honest duration enters the final journey.', language)}

+
+
+ {pickText('贵州非遗手作体验桌面的针线、布料与工具', +
{pickText('实拍素材用于呈现手作氛围,不代表四项体验均在同一工坊提供。', 'Photography shows the workshop atmosphere; it does not imply all four crafts are offered by one studio.', language)}
+
+
+ +
+ {crafts.map((craft) => ( + +
{craft.number}{pickText('候选体验 · 需确认', 'Curated candidate · verify first', language)}
+

{craft.title}

+ {craft.place} +

{craft.copy}

+ {pickText('查看体验边界', 'View Experience Notes', language)} +
+ ))} +
+ +
+
+
+ {pickText('山谷与稻田之间的贵州木构村寨', +
{pickText('客户提供村寨实拍 · 住宿和家庭接待须另行核验', 'Client-supplied village photography · stays and home hosting require separate verification', language)}
+
+
+ 02 / Authentic Village Immersion +

{pickText('从半日相识,到两晚慢住;深度不是停留时间的数字。', 'From a half-day introduction to a two-night stay, depth is more than a number.', language)}

+

{pickText('村寨体验会把公开步行、具名主人接待与住宿候选分开。农事、家宴、鼓楼夜晚和故事分享都取决于季节、日常生活与主人意愿,不做每日固定节目表。', 'Village planning separates a public walk, a named-host visit, and an overnight candidate. Farm work, home meals, drum-tower evenings, and storytelling depend on season, daily life, and host consent—not a fixed daily show schedule.', language)}

+
    +
  • {pickText('苗族吊脚楼生活观察', 'Miao stilt-house life and architecture', language)}
  • +
  • {pickText('侗寨鼓楼与社区公共空间', 'Dong drum towers and shared community space', language)}
  • +
  • {pickText('布依石寨的慢行与日常', 'Slow walking and daily life in Buyi stone villages', language)}
  • +
+ +
+
+
+ +
+
+
+ 03 / Ethnic Festivals & Customs +

{pickText('节庆只在它真正发生的时候,进入我们的线路。', 'A festival enters the route only when it is truly happening.', language)}

+

{pickText('苗年、姊妹节、黄岗喊天节、侗族大歌节与水族端节各有不同的历法、举办地和公开边界。我们先核对当年通知,再把交通、住宿、人流和拍摄礼仪写进方案。', 'Miao New Year, Sisters’ Meal, Huanggang Heaven-Calling, Dong Grand Song festivals, and Shui Duan follow different calendars, places, and public boundaries. We check the current notice before adding transport, stays, crowds, and photography etiquette to the plan.', language)}

+
    +
  • {pickText('传统日期、当年活动和内部仪式分开核对', 'Traditional date, current programme, and internal rites checked separately', language)}
  • +
  • {pickText('不把历史公历日期写成每年固定承诺', 'Historic Gregorian dates are never treated as permanent', language)}
  • +
  • {pickText('提前说明拥挤、道路、拍摄和住宿取消条件', 'Crowd, road, photography, and stay-cancellation terms stated first', language)}
  • +
+ {pickText('查看节庆民俗盛典', 'Explore Ethnic Festivals & Customs', language)} +
+
+ {pickText('大型苗族盛装节庆聚会', +
{pickText('客户提供苗族节庆氛围图 · 不能证明侗族或水族节庆,具体活动与授权待归档', 'Client-supplied Miao festival atmosphere · not evidence of Dong or Shui events; activity and rights require records', language)}
+
+
+
+ +
+
+
+ {pickText('木构空间里的贵州长桌宴席', +
{pickText('客户提供长桌氛围图 · 组织者、地点、年份及是否商业场次待确认', 'Client-supplied banquet atmosphere · organiser, place, year, and commercial status require confirmation', language)}
+
+
+ 04 / Culinary Cultural Journeys +

{pickText('不是多吃几道菜,而是理解谁种、谁做、谁坐在这张桌边。', 'Not more dishes, but a clearer sense of who grows, cooks, and sits at the table.', language)}

+

{pickText('家宴烹饪课、原生长桌宴和在地市集探访需要三套不同的接待方式。我们先核验主人或经营主体、市场时段、菜单、饮水、卫生、过敏原、辣度与酒精礼仪,再让一餐成为当天的文化锚点。', 'A home-cooking class, long-table banquet, and local market tour each require a different hosting model. We verify the host or provider, market hours, menu, water, hygiene, allergens, spice, and alcohol etiquette before making one meal the day’s cultural anchor.', language)}

+
    +
  • {pickText('家庭接待、餐厅与商业场次准确标注', 'Home hosting, restaurant, and commercial sessions labelled accurately', language)}
  • +
  • {pickText('过敏原、饮食禁忌、辣度与酒精逐项确认', 'Allergens, dietary needs, spice, and alcohol confirmed item by item', language)}
  • +
  • {pickText('不承诺每个市场和村寨都有同一套菜或长桌宴', 'No promise that every market or village offers the same dishes or banquet', language)}
  • +
+ {pickText('查看民族美食文化', 'Explore Culinary Cultural Journeys', language)} +
+
+
+ +
+
+ 05 / Hosting Principles +

{pickText('先把谁、在哪里、做多久说清楚,再谈“原生态”。', 'Name the who, where, and how long before using the word “authentic.”', language)}

+
+
+ {principles.map(([title, copy], index) => ( +
+ 0{index + 1} +

{title}

+

{copy}

+
+ ))} +
+
+ + +
+ ); +} + function ExperienceTypePage({ variant }: { variant: 'expedition' | 'signature' | 'icon' }) { const { language } = useTravelSite(); const labels = language === 'zh' ? { @@ -3828,11 +8344,12 @@ const menuPageHeroTags: Record = { Transport: 'Transport', }; -function MenuPageHero({ title, children }: { title: string; children: ReactNode }) { +function MenuPageHero({ title, children, headingLevel = 1 }: { title: string; children: ReactNode; headingLevel?: 1 | 2 }) { const tag = menuPageHeroTags[title]; + const headingId = `menu-page-${title}`; return ( -
+
万趣旅行 @@ -3843,7 +8360,9 @@ function MenuPageHero({ title, children }: { title: string; children: ReactNode )}
-

{title}

+ {headingLevel === 1 + ?

{title}

+ :

{title}

}

{children}

@@ -3999,9 +8518,7 @@ function SiteFooter() {

- {translateUi('万趣旅行', language)} - - © 2026 WanderQ. All Rights Reserved. + © {oc(content.footer.copyright, language)} {contactEmail && ( <> @@ -4015,42 +8532,25 @@ function SiteFooter() { {translateUi('服务协议', language)} {translateUi('隐私政策', language)} -

); } -function InfoBand({ title, cta, image, href, children }: { title: string; cta: string; image: string; href?: string; children: ReactNode }) { - const resolvedHref = href || ctaHref(cta); - return ( - - -
-

{title}

-

{children}

- {cta} -
-
- ); -} - -function ctaHref(label: string) { - const normalized = label.toLowerCase(); - if (normalized.includes('guide')) return '/contact/'; - return '/contact/'; -} - function ExperienceBand({ + number, kicker, title, + copy, tone, image, href, }: { + number: number; kicker: string; title: string; + copy?: string; tone: 'tan' | 'clay' | 'charcoal'; image: string; href: string; @@ -4058,174 +8558,66 @@ function ExperienceBand({ const { language } = useTravelSite(); return ( - {title} +
+ {title} +
- {kicker} +
+ {String(number).padStart(2, '0')} + {kicker} +

{title}

+ {copy ?

{copy}

: null} {pickText('了解更多', 'Discover More', language)}
); } -type FilterGroupProps = { - title: string; - items: string[]; - selected: string[]; - onChange: (title: string, item: string) => void; - onSelect: (title: string, item: string) => void; - mobileOpen?: boolean; - onMobileToggle?: () => void; -}; - -function filterAllValue(items: string[]) { - return items.find((item) => item === '全部' || item === 'All') || items[0] || ''; -} - -function filterSelectedValue(selected: string[], items: string[]) { - return selected[0] || filterAllValue(items); -} - -function filterSelectedLabel(selected: string[], items: string[]) { - const value = filterSelectedValue(selected, items); - return selected.length > 1 ? `${value} +${selected.length - 1}` : value; -} - -function FilterBar({ title, children }: { title: string; children: ReactNode }) { - const { language } = useTravelSite(); - const [activeMobileFilter, setActiveMobileFilter] = useState(null); - const childArray = Children.toArray(children); - const filterGroups = childArray.filter((child): child is ReactElement => isValidElement(child)); - const activeGroup = filterGroups.find((child) => child.props.title === activeMobileFilter); - const enhancedChildren = childArray.map((child) => { - if (!isValidElement(child)) return child; - return cloneElement(child, { - mobileOpen: activeMobileFilter === child.props.title, - onMobileToggle: () => setActiveMobileFilter((current) => (current === child.props.title ? null : child.props.title)), - }); - }); - const activeValue = activeGroup ? filterSelectedValue(activeGroup.props.selected, activeGroup.props.items) : ''; - - return ( - - ); -} - -function FilterGroup({ - title, - items, - selected, - onChange, - mobileOpen = false, - onMobileToggle, -}: FilterGroupProps) { - const { language } = useTravelSite(); - const selectedLabel = filterSelectedLabel(selected, items); - - return ( -
-
{translateUi(title, language)}
-
- -
-
- {items.map((item) => { - const active = item === 'All' || item === '全部' ? selected.length === 0 : selected.includes(item); - return ( - - ); - })} -
-
- ); -} - function FilterEmptyState() { const { language } = useTravelSite(); return
{translateUi('没有符合当前筛选的内容。', language)}
; } -function ArchiveCard({ - card, - collective = false, -}: { - card: ArchiveCardItem; - collective?: boolean; -}) { - const { language } = useTravelSite(); - const href = collective ? hotelHref(card) : card.href || '/experiences/cave-expedition/'; - return ( - -
- {card.type && {card.type}} - -
-
-

{card.title}

- {card.meta} -

{card.copy}

- - {translateUi('查看详情', language)} - -
-
- ); -} - -function SafariArchiveCard({ title, duration, price, image, href, category, focus, pace, summary }: SafariCardItem) { +function SafariArchiveCard({ title, duration, image, href, category, focus, pace, summary, routeLine, bestFor, highlights, index = 1 }: SafariCardItem & { index?: number }) { const { language } = useTravelSite(); return (
- {category} + {String(index).padStart(2, '0')}
+

{category} · {duration}

{title}

-
- {duration} - {pace} - {focus} -

{summary}

-
- {category} - {focus} - {pace} - {duration} +

{routeLine}

+ +
+
+ {pickText('节奏', 'Pace', language)} + {pace} +
+
+ {pickText('重点', 'Focus', language)} + {focus} +
+
+ {pickText('适合', 'Best for', language)} + {bestFor[0] || focus} +
-
- {price} - {translateUi('了解线路', language)} + +
+ {pickText('这条线路的 3 个核心亮点', 'Three defining moments', language)} +
    + {highlights.slice(0, 3).map((highlight) => ( +
  1. {highlight.title}
  2. + ))} +
+ + {translateUi('了解线路', language)}
); diff --git a/src/guizhouContent.ts b/src/guizhouContent.ts index 614ef49..cbed50f 100644 --- a/src/guizhouContent.ts +++ b/src/guizhouContent.ts @@ -69,11 +69,13 @@ const slugFromHref = (href?: string) => { return parts[parts.length - 1] || ''; }; -const OSS_ASSET_BASE_URL = 'https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com/wanderq'; -const ossAsset = (key: string) => `${OSS_ASSET_BASE_URL}/${key}`; +// Keep the existing content declarations stable while serving the downloaded +// assets locally. The OSS bucket forces attachment downloads, which prevents +// browsers from rendering those objects inside elements. +const ossAsset = (key: string) => `/${key}`; export const guizhouImages = { - logo: ossAsset('wanderq-logo.webp'), + logo: '/wanderq-logo.png', heroHome: ossAsset('guizhou-assets/hero-wanfenglin.webp'), heroHuangguoshu: ossAsset('guizhou-assets/hero-huangguoshu.webp'), heroFanjing: ossAsset('guizhou-assets/hero-fanjing.webp'), @@ -102,7 +104,84 @@ export const guizhouImages = { footerCta: ossAsset('guizhou-assets/footer-cta.webp'), quoteGuests: ossAsset('guizhou-assets/quote-guests.webp'), faqOutdoor: ossAsset('guizhou-assets/faq-outdoor.webp'), - collectiveHeroLogo: ossAsset('wanderq-logo.webp'), + clientRouteClassic: '/client-assets/route-entry-classic.webp', + clientMiaoMeal: '/client-assets/hero-miao-home-meal.webp', + clientCultureCraft: '/client-assets/experience-entry-culture.webp', + clientWaterfall: '/client-assets/gallery-waterfall-traveler.webp', + clientWaterfallAbseilCanyon: '/client-assets/waterfall-abseil-canyon.jpg', + clientWaterfallAbseilDescent: '/client-assets/waterfall-abseil-descent.jpg', + clientWaterfallAbseilRope: '/client-assets/waterfall-abseil-rope.jpg', + clientWaterfallAbseilSpray: '/client-assets/waterfall-abseil-spray.jpg', + clientRiverPackraftShore: '/client-assets/river-packraft-shore.jpg', + clientRiverKarstAerial: '/client-assets/river-karst-aerial.jpg', + clientRiverForestRest: '/client-assets/river-forest-rest.jpg', + clientRiverCalmRafting: '/client-assets/river-calm-rafting.jpg', + clientRiverCanyonPool: '/client-assets/river-canyon-pool.jpg', + clientKarstHikeSunset: '/client-assets/karst-hike-sunset.jpg', + clientKarstHikeRiverSunset: '/client-assets/karst-hike-river-sunset.jpg', + clientKarstHikeRidge: '/client-assets/karst-hike-ridge.jpg', + clientKarstValleyCamp: '/client-assets/karst-valley-camp.jpg', + clientTransportSedanSuv: '/client-assets/transport-sedan-suv.jpg', + clientTransportPremiumMpv: '/client-assets/transport-premium-mpv.jpg', + clientTransportLightVan: '/client-assets/transport-light-van.jpg', + clientTransportVanInterior: '/client-assets/transport-van-interior.jpg', + clientHighBridgeBungee: '/client-assets/high-bridge-bungee.jpg', + clientHighBridgeRecord: '/client-assets/high-bridge-record.jpg', + clientHighBridgeCocoon: '/client-assets/high-bridge-cocoon.jpg', + clientRouteOutdoor: '/client-assets/route-entry-outdoor.webp', + clientOutdoorCave: '/client-assets/experience-entry-outdoor.webp', + clientKarstAdventure: '/client-assets/gallery-karst-adventure.webp', + clientCaveDinner: '/client-assets/gallery-cave-dinner.webp', + clientCaveLuxuryWaterwalk: '/client-assets/cave-luxury-waterwalk.jpg', + clientCaveLuxuryLounge: '/client-assets/cave-luxury-lounge.jpg', + clientCaveLuxuryDining: '/client-assets/cave-luxury-dining.jpg', + clientWildCoffeeTerraceView: '/client-assets/wild-coffee-terrace-view.jpg', + clientWildCoffeeCliffCup: '/client-assets/wild-coffee-cliff-cup.jpg', + clientWildCoffeeRainDeck: '/client-assets/wild-coffee-rain-deck.jpg', + clientWildCoffeeForestCafe: '/client-assets/wild-coffee-forest-cafe.jpg', + clientVillageDrumtowerRoofs: '/client-assets/village-drumtower-roofs.jpg', + clientVillageBalconyLife: '/client-assets/village-balcony-life.jpg', + clientVillageForestHomes: '/client-assets/village-forest-homes.jpg', + clientVillageValleyFields: '/client-assets/village-valley-fields.jpg', + clientCampCloudDeck: '/client-assets/camp-cloud-deck.jpg', + clientCampMountainPortrait: '/client-assets/camp-mountain-portrait.jpg', + clientCampViewLounge: '/client-assets/camp-view-lounge.jpg', + clientFusionIndigoConcept: '/client-assets/fusion-indigo-concept.jpg', + clientCultureCraftTable: '/client-assets/culture-craft-table.jpg', + clientCultureCraftHands: '/client-assets/culture-craft-hands.jpg', + clientCultureBatikGallery: '/client-assets/culture-batik-gallery.jpg', + clientCultureBatikMaking: '/client-assets/culture-batik-making.jpg', + clientCultureBatikWaxDetail: '/client-assets/culture-batik-wax-detail.jpg', + clientCultureDongVillage: '/client-assets/culture-village-dong-view.jpg', + clientCultureTimberHomes: '/client-assets/culture-village-timber-homes.jpg', + clientFestivalMiaoSilver: '/client-assets/festival-miao-silver-parade.jpg', + clientFestivalLeishanProcession: '/client-assets/festival-leishan-procession.jpg', + clientFestivalMiaoBlue: '/client-assets/festival-miao-blue-procession.jpg', + clientFestivalMiaoGathering: '/client-assets/festival-miao-gathering.jpg', + clientCulinarySourSoupPot: '/client-assets/culinary-sour-soup-pot.jpg', + clientCulinarySourSoupTable: '/client-assets/culinary-sour-soup-table.jpg', + clientCulinarySharedGriddle: '/client-assets/culinary-shared-griddle.jpg', + clientCulinaryLongTable: '/client-assets/culinary-long-table-banquet.jpg', + clientWaterfallHuangguoshuMain: '/client-assets/waterfall-huangguoshu-main.jpg', + clientWaterfallHuangguoshuCurtain: '/client-assets/waterfall-huangguoshu-curtain.jpg', + clientWaterfallDishuitanTiered: '/client-assets/waterfall-dishuitan-tiered.jpg', + clientWaterfallChishuiDanxia: '/client-assets/waterfall-chishui-danxia.jpg', + clientVillagesXijiangDusk: '/client-assets/villages-xijiang-dusk.jpg', + clientVillagesXijiangPanorama: '/client-assets/villages-xijiang-panorama.jpg', + clientVillagesDongValley: '/client-assets/villages-dong-valley.jpg', + clientVillagesDongDrumtowers: '/client-assets/villages-dong-drumtowers.jpg', + clientAncientTownsQingyanOverview: '/client-assets/ancient-towns-qingyan-overview.jpg', + clientAncientTownsStoneWall: '/client-assets/ancient-towns-stone-wall.jpg', + clientAncientTownsZhenyuanNight: '/client-assets/ancient-towns-zhenyuan-night.jpg', + clientAncientTownsZhenyuanRiver: '/client-assets/ancient-towns-zhenyuan-river.jpg', + clientQingyanBattleScene: '/client-assets/qingyan-battle-client-scene.png', + clientLoushanguanImmersiveMarch: '/client-assets/loushanguan-immersive-march-01.png', + clientRiverAdventure: '/client-assets/hero-river-adventure.webp', + clientMountainPause: '/client-assets/gallery-mountain-pause.webp', + clientRouteBalanced: '/client-assets/route-entry-balanced.webp', + clientHeritageHands: '/client-assets/gallery-heritage-hands.webp', + clientLocalTable: '/client-assets/gallery-local-table.webp', + collectiveHeroLogo: '/wanderq-logo.png', menuSafari: ossAsset('guizhou-assets/route-mixed.webp'), menuDestinations: ossAsset('guizhou-assets/menu-landscape.webp'), menuCollective: ossAsset('guizhou-assets/menu-hotels.webp'), @@ -204,13 +283,13 @@ export const guizhouSiteContent: SiteContent = { ctaHref: '/safaris/', groups: [ group('route-classic-must', 'Classic must-play routes', '经典必玩', [ - link('route-classic', 'Classic Guizhou culture route', '经典人文打卡线路', '/safaris/classic-guizhou/'), + link('route-classic', 'Classic Panorama Route', '经典全景线', '/safaris/classic-guizhou/'), link('route-karst', 'Karst waterfall and forest route', '喀斯特瀑布水森林线路', '/safaris/karst-waterfall-forest/'), link('route-fanjing', 'Fanjing Mountain and ancient towns', '梵净山与古镇线路', '/safaris/fanjing-ancient-towns/'), ]), group('route-niche', 'Niche experience routes', '小众体验', [ - link('route-outdoor', 'Mountain outdoor and wild coffee', '极限山野户外野咖线路', '/safaris/outdoor-coffee/'), - link('route-mixed', 'Culture plus outdoor mix', '人文+户外综合混搭线路', '/safaris/culture-outdoor-mix/'), + link('route-outdoor', 'Karst Outdoor Adventure', '喀斯特户外探险线', '/safaris/outdoor-coffee/'), + link('route-mixed', 'Balanced Discovery Route', '均衡探索线', '/safaris/culture-outdoor-mix/'), link('route-family', 'Family heritage slow travel', '亲子人文慢游线路', '/safaris/family-heritage/'), ]), group('route-specials', 'Activity benefits', '活动优惠', [ @@ -236,9 +315,10 @@ export const guizhouSiteContent: SiteContent = { link('stream', 'Stream tracing and rappelling', '溯溪与瀑降', '/experiences/stream-tracing/'), ]), group('culture', 'Human Culture', '民族人文', [ + link('culture-overview', 'Explore all human culture', '查看民族人文总览', '/culture/'), link('miao-craft', 'Miao embroidery and batik', '非遗苗绣与蜡染', '/experiences/miao-craft/'), link('dong-song', 'Dong grand song', '侗族大歌', '/experiences/dong-song/'), - link('history', 'Loushanguan campaign story', '娄山关战役人文线', '/experiences/loushanguan/'), + link('history', 'Loushanguan campaign story', '娄山关战役人文线', '/experiences/loushanguan-battle-immersive-march/'), ]), group('scenery', 'Unique Scenery', '独特风光地貌', [ link('fanjing', 'Fanjing Mountain', '梵净山', '/destinations/fanjing-mountain/'), @@ -290,17 +370,21 @@ export const guizhouSiteContent: SiteContent = { ctaLabel: t('View All Hotels', '查看全部酒店'), ctaHref: '/hotels/', groups: [ - group('classic-hotels', 'Classic Hotels', '经典酒店', [ - link('sheraton', 'Sheraton Guiyang Hotel', '贵阳喜来登贵航酒店', '/hotels/sheraton-guiyang-hotel/'), - link('pearl-gallery', 'Pearl Gallery Hotel Guiyang', '贵阳珍珀酒店', '/hotels/pearl-gallery-hotel-guiyang/'), - link('hampton-guanshanhu', 'Hampton by Hilton Guiyang Guanshan Lake', '贵阳观山湖希尔顿欢朋酒店', '/hotels/hampton-by-hilton-guiyang-guanshan-lake/'), - link('bolian', 'Huangguoshu Bolian Resort', '黄果树柏联酒店', '/hotels/huangguoshu-bolian-resort/'), + group('landscape-hotels', 'Landscape Resorts', '地貌度假', [ + link('fengxi-cave', 'Fengxi Cave Resort', '兴义峰兮・洞穴民宿', '/hotels/#fengxi-cave'), + link('fangxia-yunju', 'Zhusha Ancient Town · Fangxia Yunju', '朱砂古镇・放下云居', '/hotels/#fangxia-yunju'), ]), - group('wild-luxury-hotels', 'Wild Luxury Hotels', '野奢酒店', [ - link('puyu', 'Puyu Wild Luxury Dushan', '朴语野奢(独山)', '/hotels/puyu-wild-luxury/'), - link('sanchunli', 'San Chun Li Meng', '西江三春里孟', '/hotels/san-chun-li-meng/'), - link('yuntun', 'Yuntun Starry Sky', '兴义云屯・星空野奢酒店', '/hotels/yuntun-starry-sky-wild-luxury/'), - link('miaowang', 'Wujiangzhai Miaowang Hotel', '乌江寨苗王酒店', '/hotels/wujiangzhai-miaowang-hotel/'), + group('village-hotels', 'Village Boutique Stays', '村寨美宿', [ + link('tree-frog-leigongshan', 'Tree Frog Tribe Leigongshan', '雷公山树蛙部落', '/hotels/#tree-frog-leigongshan'), + link('jiayi-terrace', 'Jiayi Terrace Mountain Retreat', '榕江加宜梯田民宿', '/hotels/#jiayi-terrace'), + link('shicang-laiyin', 'Shicang Laiyin Boutique Guesthouse', '台江拾仓来隐', '/hotels/#shicang-laiyin'), + ]), + group('pastoral-hotels', 'Pastoral Mountain Stays', '田园山野', [ + link('yuntun-treehouse', 'Yuntun Star Treehouse Resort', '云屯・星空野奢树屋', '/hotels/#yuntun-treehouse'), + link('zhenshanhe', 'Zhensu Zhenshanhe Canyon Retreat', '枕宿・枕山河', '/hotels/#zhenshanhe'), + ]), + group('themed-hotels', 'Themed Family Stay', '主题亲子', [ + link('xingyi-mushroom', 'Xingyi Mushroom Luxury Hotel', '兴义蘑菇野奢酒店', '/hotels/#xingyi-mushroom'), ]), ], specialCards: [], @@ -377,13 +461,16 @@ export const guizhouSiteContent: SiteContent = { }, home: { hero: { - kicker: t('China · Guizhou DMC', '中国·贵州地接定制'), - title: t('Plan your China · Guizhou secret journey', '计划你的中国·贵州秘境之旅'), - imageUrl: guizhouImages.heroHome, + kicker: t('China · Guizhou Private Journeys', '中国 · 贵州私家定制'), + title: t('Private journeys into the real Guizhou', '走进真实贵州的私家旅行'), + imageUrl: '/client-assets/hero-river-adventure.webp', ctaLabel: t('Start Planning', '开始规划'), ctaHref: '/contact/', tripAdvisorUrl: '', - awardUrls: [guizhouImages.heroHuangguoshu, guizhouImages.heroFanjing, guizhouImages.heroXijiang], + awardUrls: [ + '/client-assets/hero-real-travelers.webp', + '/client-assets/hero-miao-home-meal.webp', + ], }, planning: { title: t('Your custom Guizhou journey starts here', '您定制的中国·贵州秘境之旅将从这里开始'), @@ -411,96 +498,129 @@ export const guizhouSiteContent: SiteContent = { ], }, journey: { - title: t('Why travel Guizhou with Wanqu', '为什么选择万趣做贵州定制'), + title: t('Local people behind every journey', '每一段旅程背后的贵州团队'), cards: [ { - id: 'compliance', - title: t('Compliant inbound travel', '合规入境接待'), - ctaLabel: t('See strengths', '查看优势'), - href: '#about-advantages', + id: 'field-planning', + title: t('Field planning, not desk-only routing', '在地规划,不只纸上排线'), + ctaLabel: t('Plan with us', '开始沟通'), + href: '/contact/', imageUrl: guizhouImages.routeClassic, - copy: t('Clear operating process for inbound guests, travel agencies, private groups, and mixed Chinese-English families.', '面向境外游客、旅行同业、私家小团和中英混合家庭,提供清晰合规的地接执行流程。'), + copy: t('Routes are shaped around real mountain roads, weather, water levels and the rhythm of each place.', '根据真实山路、天气、水位和每个地方的生活节奏设计线路。'), }, { - id: 'english-guide', - title: t('English-speaking driver-guide', '英文司导与小包团'), - ctaLabel: t('Meet the team', '团队情况'), + id: 'english-context', + title: t('English support with cultural context', '英文沟通,也讲清文化'), + ctaLabel: t('Plan with us', '开始沟通'), href: '/contact/', imageUrl: guizhouImages.quoteGuests, - copy: t('We explain local customs, food, landscape, and safety details in English while keeping each group private.', '用英文讲清楚贵州的风土、餐食、地貌和安全细节,同时保持纯玩私家小包团节奏。'), + copy: t('We help international guests understand local customs, food, landscapes and practical travel details.', '帮助境外客人理解贵州的风土、餐食、地貌与旅行细节。'), }, { - id: 'service', - title: t('24-hour local support', '24 小时在地服务'), - ctaLabel: t('Social media', '社交媒体'), - href: '#social-media', + id: 'local-coordination', + title: t('24-hour coordination on the road', '24 小时在地协调'), + ctaLabel: t('Plan with us', '开始沟通'), + href: '/contact/', imageUrl: guizhouImages.footerCta, - copy: t('Weather changes, mountain roads, water levels, and route changes are handled by a Guizhou-based operations team.', '山地天气、道路、水位和行程调整,由贵州本地执行团队持续跟进。'), + copy: t('A Guizhou-based operations team stays available during the journey to coordinate changes and local support.', '行程期间由贵州本地执行团队持续响应,协调现场变化与在地支持。'), }, ], }, experiences: { - title: t('What kind of Guizhou do you want?', '你想在贵州获得什么样的体验?'), - copy: t('Choose one focus or mix them into a private route.', '选择一种主题,或把它们混搭进一条私家线路。'), + title: t('Signature Guizhou Experiences', '贵州限定体验'), + copy: t( + 'From underground rivers and waterfall valleys to living heritage workshops, choose one focus or weave all three into a private journey.', + '从地下暗河、峡谷瀑布到仍在生活中的非遗工坊,选择一种主题,或将三种体验组合进一条私家线路。', + ), bands: [ { id: 'outdoor', - kicker: t('Outdoor', '极限户外探险'), - title: t('Caves, canyons, waterfalls, and rivers', '溶洞、峡谷、瀑布与河谷'), + kicker: t('Karst Outdoor Adventures', '喀斯特户外探险'), + title: t('Go beneath the surface', '进入喀斯特腹地'), + copy: t( + 'Paddle underground rivers, trace clear-water canyons and meet Guizhou’s karst terrain at your own level.', + '从洞穴桨板、峡谷溯溪到瀑降,用适合自己的强度进入贵州喀斯特腹地。', + ), tone: 'charcoal', - imageUrl: guizhouImages.experienceCave, + imageUrl: '/client-assets/experience-entry-outdoor.webp', href: '/experiences/cave-expedition/', }, { id: 'culture', - kicker: t('Culture', '独特民族人文风情'), - title: t('Miao, Dong, batik, embroidery, and village life', '苗侗村寨、蜡染苗绣与在地生活'), + kicker: t('Living Ethnic Culture', '鲜活民族人文'), + title: t('Culture, still being lived', '走进仍在生长的文化'), + copy: t( + 'Learn from master artisans, share home-cooked meals and experience village culture as it is truly lived.', + '跟随传承人学习技艺,共享村寨家常饭菜,体验真实生长着的文化。', + ), tone: 'clay', - imageUrl: guizhouImages.experienceCraft, + imageUrl: '/client-assets/experience-entry-culture.webp', href: '/experiences/miao-craft/', }, { id: 'landscape', - kicker: t('Landscape', '经典贵州风景地貌'), - title: t('Karst mountains, waterfalls, terraces, and ancient towns', '喀斯特峰林、瀑布、梯田与古镇'), + kicker: t('Extraordinary Landscapes', '独特风光地貌'), + title: t('Find your scale in nature', '在山水间找到自己的尺度'), + copy: t( + 'Walk through peak forests, waterfall clusters, rice terraces and ancient towns—at the hour and pace that suits you.', + '走过峰林、瀑布群、梯田与古镇,把最合适的时间和节奏留给风景。', + ), tone: 'tan', - imageUrl: guizhouImages.destinationHuangguoshu, + imageUrl: '/client-assets/experience-entry-landscape.webp', href: '/experiences/#landscape', }, ], }, safaris: { - title: t('Guizhou routes that can start your custom plan', '可作为定制起点的贵州线路'), - copy: t('These route families are ready-to-discuss starting points. Dates, vehicle tier, hotels, activity level, and supplier availability are confirmed before quoting.', '以下主题可作为定制沟通起点;日期、车型、酒店、活动强度和供应商档期会在报价前逐项确认。'), - ctaLabel: t('View all route ideas', '查看全部线路'), + title: t('Three ways into Guizhou', '三种方式,走进贵州'), + copy: t( + 'Start with the route that feels most like you. Every journey stays private and can be reshaped around your dates, pace and interests.', + '从最像你的线路开始。每一段旅程都是私家定制,并可根据日期、节奏与兴趣重新组合。', + ), + ctaLabel: t('Explore all route ideas', '探索全部线路'), ctaHref: '/safaris/', cards: [ { id: 'classic-guizhou', - title: t('Classic Guizhou culture route', '经典人文打卡线路'), - nights: t('6-8 days | Guiyang, Anshun, Libo, Southeast Guizhou', '6-8 天 | 贵阳、安顺、荔波、黔东南'), - price: t('USD 1,000-5,000 reference range', '1000-5000 美元预算区间'), - imageUrl: guizhouImages.routeClassic, + title: t('Classic Panorama Route', '经典全景线'), + nights: t('6–8 days · Waterfalls, villages and heritage', '6–8 天 · 瀑布、村寨与非遗'), + price: t('Private journey · Custom quote', '私家定制 · 按方案报价'), + imageUrl: '/client-assets/route-entry-classic.webp', href: '/safaris/classic-guizhou/', - copy: t('Waterfalls, karst rivers, Miao and Dong villages, food, and comfortable hotels.', '瀑布、喀斯特水系、苗侗村寨、美食和舒适酒店组合。'), + copy: t('A first journey through Guizhou’s essential landscapes and living culture, without turning either into a checklist.', '一次看见贵州代表山水与鲜活文化,又不被打卡清单催赶的初见之旅。'), + highlights: [ + t('Private home-cooked meal with a Miao family', '苗家私人家宴,非商业化团餐'), + t('Sunrise access to a Dong village before crowds arrive', '错峰日出进侗寨,避开旅行团人流'), + t('Private workshop with a third-generation batik artisan', '第三代蜡染传承人私人工坊体验'), + ], }, { id: 'outdoor-coffee', - title: t('Mountain outdoor and wild coffee', '极限山野户外野咖线路'), - nights: t('4-7 days | Caves, canyons, wild coffee stops', '4-7 天 | 探洞、峡谷、山野咖啡'), - price: t('Quote by safety setup and group size', '按安全配置与人数报价'), - imageUrl: guizhouImages.experienceCoffee, + title: t('Karst Outdoor Adventure', '喀斯特户外探险线'), + nights: t('4–7 days · Caves, canyons and wild coffee', '4–7 天 · 洞穴、峡谷与山野咖啡'), + price: t('Activity level matched to your group', '活动强度按团队情况匹配'), + imageUrl: '/client-assets/route-entry-outdoor.webp', href: '/safaris/outdoor-coffee/', - copy: t('Caving, stream tracing, rafting, rappelling, and mountain coffee in one active route.', '把探洞、溯溪、漂流、瀑降和山野咖啡放进同一条活力路线。'), + copy: t('A more active route into cave systems, river valleys and hidden mountain stops, paced to your experience level.', '沿洞穴、河谷与隐秘山野停靠点深入贵州,并根据你的经验调整活动难度。'), + highlights: [ + t('An off-the-map cave section accessed with our local team', '跟随万趣本地团队进入小众未开发洞段'), + t('Wild coffee tasting at a hidden mountain cabin', '隐秘山野小屋咖啡体验'), + t('Canyoning guides with 8+ years of local experience', '8 年以上本地经验的专业溯溪带队团队'), + ], }, { id: 'culture-outdoor-mix', - title: t('Culture plus outdoor mix', '人文+户外综合混搭线路'), - nights: t('5-9 days | Villages, craft, rivers, and scenery', '5-9 天 | 村寨、非遗、河谷与风景'), - price: t('Private quote', '私家团询价'), - imageUrl: guizhouImages.routeMixed, + title: t('Balanced Discovery Route', '均衡探索线'), + nights: t('5–9 days · Villages, terraces and light outdoors', '5–9 天 · 村寨、梯田与轻户外'), + price: t('Private journey · Flexible pace', '私家定制 · 节奏灵活'), + imageUrl: '/client-assets/route-entry-balanced.webp', href: '/safaris/culture-outdoor-mix/', - copy: t('A balanced pace for families, friends, and inbound guests who want Guizhou in layers.', '适合家庭、朋友和境外客人,用更均衡的节奏层层进入贵州。'), + copy: t('Village life, local craft and gentle outdoor time in a balanced route that leaves room to slow down.', '把村寨生活、在地手作与轻户外放进一条均衡线路,也为慢下来留出空间。'), + highlights: [ + t('Slow village homestay experience with a local host', '本地村民家民宿过夜,深度融入在地生活'), + t('Guided rice-terrace walk with a local farmer', '本地农民带队梯田徒步,讲解农耕文化'), + t('Flexible daily pace, adjusted around your preferences', '每日节奏灵活,可随时调整停留时长'), + ], }, ], }, @@ -513,18 +633,16 @@ export const guizhouSiteContent: SiteContent = { attribution: t('— Wanqu Team', '——万趣团队'), }, liveGallery: { - title: t('Live from Guizhou', '贵州现场影集'), + title: t('Journeys, as they happened.', '旅行真实发生的样子'), ctaLabel: t('', ''), ctaHref: '', imageUrls: [ - guizhouImages.destinationLibo, - guizhouImages.destinationFanjing, - guizhouImages.destinationXijiang, - guizhouImages.experienceRafting, - guizhouImages.menuCulture, - guizhouImages.destinationZhenyuan, - guizhouImages.experienceCoffee, - guizhouImages.hotelBolian, + '/client-assets/gallery-local-table.webp', + '/client-assets/gallery-karst-adventure.webp', + '/client-assets/gallery-cave-dinner.webp', + '/client-assets/gallery-mountain-pause.webp', + '/client-assets/gallery-heritage-hands.webp', + '/client-assets/gallery-waterfall-traveler.webp', ], }, }, @@ -654,7 +772,7 @@ export const guizhouSiteContent: SiteContent = { phone: '', email: 'travel@wanderq.com', socialLabel: t('Instagram · Facebook · X', 'Instagram · Facebook · X'), - copyright: t('2026 Wanqu Travel. Guizhou custom travel and DMC service.', '2026 万趣旅行。贵州定制旅行与地接服务。'), + copyright: t('2026 WanderQ. Private Guizhou journeys, planned on the ground.', '2026 万趣旅行。贵州私家定制旅行与在地服务。'), tradeLabel: t('Trade Cooperation', '同业合作'), tradeHref: '/contact/', groups: [], @@ -665,13 +783,13 @@ export const guizhouSafariCards = [ { slug: 'classic-guizhou', href: '/safaris/classic-guizhou/', - title: '经典人文打卡线路', + title: '经典全景线', subtitle: '黄果树、荔波、西江、镇远与贵阳城市美食', nights: '6-8 天 | 贵阳、安顺、荔波、黔东南', duration: '6-8 天', - price: 'USD 1,000-5,000', - image: guizhouImages.routeClassic, - gallery: [guizhouImages.routeClassic, guizhouImages.destinationHuangguoshu, guizhouImages.destinationLibo, guizhouImages.destinationXijiang, guizhouImages.destinationZhenyuan], + price: '私家团询价', + image: guizhouImages.clientRouteClassic, + gallery: [guizhouImages.clientRouteClassic, guizhouImages.clientMiaoMeal, guizhouImages.clientCultureCraft, guizhouImages.clientWaterfall, guizhouImages.destinationZhenyuan], theme: '经典地貌', pace: '中等节奏', fit: '初次来黔', @@ -686,99 +804,107 @@ export const guizhouSafariCards = [ decisionTags: ['首次贵州', '经典地貌', '民族人文', '舒适酒店'], bestFor: ['第一次来贵州的境外客人', '想看代表性景观的家庭或朋友小团', '希望少踩坑、重视交通衔接的客人'], highlights: [ - { title: '黄果树错峰动线', copy: '按季节水量与人流安排进入时间,减少排队和逆光拍摄。', image: guizhouImages.destinationHuangguoshu }, - { title: '荔波水森林', copy: '把小七孔步道、亲水节点和轻量摄影时间放进同一天。', image: guizhouImages.destinationLibo }, - { title: '苗侗村寨慢停留', copy: '避开只拍照离开的节奏,保留餐食、夜景或非遗体验。', image: guizhouImages.destinationXijiang }, - { title: '城市餐食收尾', copy: '贵阳夜市、小吃和轻松步行适合抵达或返程前安排。', image: guizhouImages.menuFood }, + { title: '苗家私人家宴', copy: '与苗族家庭共享一顿非商业化团餐的家常饭。', image: guizhouImages.clientMiaoMeal }, + { title: '错峰日出进侗寨', copy: '在人群到来前进入侗寨,把清晨留给木楼、村路与日常生活。', image: guizhouImages.destinationXijiang }, + { title: '第三代蜡染传承人私人工坊', copy: '进入传承人的工作空间,不做流水线体验店式打卡。', image: guizhouImages.experienceCraft }, ], itinerary: [ - { day: 'Day 1', title: '贵阳抵达与轻量适应', place: '贵阳', copy: '接机或接站后入住城市酒店,安排甲秀楼、青云市集或轻量城市餐食,帮助客人适应时差和山地气候。', meta: ['接送机', '城市酒店', '低强度'] }, - { day: 'Day 2-3', title: '黄果树与安顺地貌', place: '安顺', copy: '按天气和人流错峰进入黄果树瀑布群,可加入屯堡餐食或轻量人文停靠,避免一天塞入过多景点。', meta: ['瀑布', '错峰', '短途转场'] }, - { day: 'Day 4-5', title: '荔波小七孔与水森林', place: '荔波', copy: '围绕小七孔水森林、卧龙潭、翠谷瀑布规划步行与摄影时间。夏季可评估漂流或亲水活动。', meta: ['喀斯特水系', '轻徒步', '亲水'] }, - { day: 'Day 6-8', title: '黔东南村寨与镇远古城', place: '西江 / 肇兴 / 镇远', copy: '根据客人兴趣选择苗寨、侗寨、古城或非遗体验,节奏放慢,减少频繁换酒店带来的疲惫。', meta: ['民族文化', '夜景', '古镇'] }, + { day: 'Day 1', title: '贵阳抵达与轻量适应', place: '贵阳', copy: '接机或接站后先入住,让第一天服务时差、饮食适应和山地气候,而不是赶景点。', meta: ['接送机', '城市酒店', '低强度'], schedule: { morning: '按航班或车次抵达;早到客人可先寄存行李。', afternoon: '专车接机/接站,入住贵阳城市酒店,按状态安排轻量城市步行。', evening: '选择容易进入贵州味道的晚餐,尽量早休息。', drive: '机场/高铁站至市区通常约 30–60 分钟,以当天交通为准。', meals: '早餐不含;午晚餐按抵达时间确认。', stay: '贵阳城市酒店。' } }, + { day: 'Day 2', title: '贵阳前往黄果树', place: '贵阳 → 黄果树 / 安顺', copy: '用错峰进入和适度步行建立这条经典线的自然开场。', meta: ['瀑布', '错峰', '中等步行'], schedule: { morning: '酒店早餐后出发前往黄果树,途中根据需要安排休息。', afternoon: '从主瀑布、陡坡塘、天星桥中选择适合当天水量与体力的组合。', evening: '入住黄果树或安顺,安排较轻松的恢复餐。', drive: '贵阳至黄果树规划参考约 2–2.5 小时,景区内另有换乘。', meals: '早餐随房;午晚餐在最终方案中说明。', stay: '黄果树或安顺区域住宿。' } }, + { day: 'Day 3', title: '瀑布收尾与前往荔波', place: '黄果树 / 安顺 → 荔波', copy: '根据前一日完成度保留短体验,再把较长转场拆成舒适节奏。', meta: ['跨区转场', '天气缓冲', '荔波入住'], schedule: { morning: '补充前一日未完成的轻量瀑布节点,或直接休息后出发。', afternoon: '专车前往荔波,途中安排午餐与休息,不叠加高强度景点。', evening: '抵达荔波入住,确认次日小七孔进入时间。', drive: '黄果树/安顺至荔波规划参考约 4–5 小时,以酒店位置和路况为准。', meals: '早餐随房;午餐途中;晚餐在荔波。', stay: '荔波县城或景区周边住宿。' } }, + { day: 'Day 4', title: '荔波小七孔与水森林', place: '荔波', copy: '把碧水、古桥、跌水与森林步道留给完整一天,不急着当天继续跨城。', meta: ['水森林', '轻徒步', '摄影'], schedule: { morning: '按人流选择入口,游览古桥、拉雅瀑布及水森林一线。', afternoon: '根据体力继续卧龙潭、翠谷瀑布或亲水停留,保留摄影时间。', evening: '返回酒店休息,可安排酸汤或更清淡的替代餐。', drive: '以景区换乘与短距离酒店接送为主。', meals: '早餐随房;午餐以景区效率为先;晚餐在荔波。', stay: '荔波连住。' } }, + { day: 'Day 5', title: '荔波前往西江苗寨', place: '荔波 → 西江 / 黔东南', copy: '从自然地貌转入苗族村寨,把抵达后的时间留给家宴和夜晚。', meta: ['苗寨', '家宴', '夜景'], schedule: { morning: '早餐后离开荔波,途中根据节奏安排短停。', afternoon: '抵达黔东南,入住后再进入村寨,减少拖着行李游览。', evening: '安排苗家私人家宴或口味更稳的替代餐,慢看村寨灯火。', drive: '荔波至西江规划参考约 3.5–4 小时,以当天道路和停靠为准。', meals: '早餐随房;午餐途中;晚餐可安排苗家家宴。', stay: '西江或周边村寨住宿。' } }, + { day: 'Day 6', title: '清晨村寨、蜡染工坊与镇远', place: '西江 / 周边村寨 → 镇远', copy: '在旅行团高峰前看见村寨清晨,再进入手艺人的真实工作空间。', meta: ['清晨错峰', '蜡染工坊', '古镇慢住'], schedule: { morning: '错峰进入侗寨或在苗寨慢走,观察木楼、集市与日常生活。', afternoon: '预约第三代蜡染传承人私人工坊,之后前往镇远。', evening: '入住镇远,沿舞阳河轻松步行与晚餐。', drive: '黔东南村寨至镇远规划参考约 2–3 小时,按工坊位置调整。', meals: '早餐随房;午餐在村寨/凯里周边;晚餐在镇远。', stay: '镇远古城或周边住宿。' } }, + { day: 'Day 7', title: '镇远慢行与返回贵阳', place: '镇远 → 贵阳', copy: '给古镇半天而不是匆忙拍照,再回贵阳保留返程缓冲。', meta: ['古镇', '返程缓冲', '贵阳美食'], schedule: { morning: '沿舞阳河、古巷与历史街区慢行,按兴趣选择轻量参观。', afternoon: '专车返回贵阳,途中按需要休息。', evening: '贵阳收尾晚餐,可选择夜市、小吃或安静餐厅。', drive: '镇远至贵阳规划参考约 3.5–4 小时,以市区酒店位置为准。', meals: '早餐随房;午餐在镇远或途中;晚餐在贵阳。', stay: '贵阳城市酒店。' } }, + { day: 'Day 8', title: '贵阳返程', place: '贵阳', copy: '根据航班或车次安排送机送站,不在返程日硬塞远距离景点。', meta: ['送机送站', '行程缓冲', '返程'], schedule: { morning: '酒店早餐与整理行李;晚班客人可安排轻量城市活动。', afternoon: '按交通时间退房并专车送机/送站。', evening: '如为晚班离开,具体餐食与休息点在最终方案确认。', drive: '市区至机场/高铁站通常约 30–60 分钟,以当天交通为准。', meals: '早餐随房;其余餐食按离开时间确认。', stay: '无;如需延住单独报价。' } }, ], - inclusions: ['私家车辆与基础调度', '中文或英文司导方案', '行程设计与景区预约建议', '酒店等级匹配与备选方案', '24 小时在地服务支持'], - exclusions: ['国际机票与签证', '个人消费与旅行保险', '未列明餐食', '单房差', '因天气或个人原因产生的额外费用'], - notes: ['12 岁及以上旅客默认按成人标准规划住宿、活动和餐食。', '瀑布、水森林和村寨体验会随天气、人流和节庆活动调整顺序。'], + inclusions: ['定制行程设计与出发前沟通', '最终方案确认的英文向导或英文旅行支持', '全程私家车辆与基础调度', '最终报价列明的住宿与每日早餐', '最终行程列明的景区首道门票', '行程期间 24 小时在地协调'], + exclusions: ['国际/国内大交通与签证', '未在最终报价中列明的午餐和晚餐', '个人消费、小费与单房差', '住宿或活动临时升级费用', '旅行保险及因天气、政策或个人原因产生的额外费用,除非报价另有列明'], + notes: ['车程为路线规划参考,不是实时导航承诺;最终方案会按酒店位置、道路、天气和节假日复核。', '酒店需在预订前逐家确认当前涉外接待条件,不使用“全部酒店均具备涉外资质”的未核验承诺。', '瀑布、水森林和村寨体验会随天气、人流、节庆与主理人档期调整顺序。'], }, { slug: 'outdoor-coffee', href: '/safaris/outdoor-coffee/', - title: '极限山野户外野咖线路', - subtitle: '双河洞/十二背后、峡谷、溯溪、漂流与山野咖啡', - nights: '4-7 天 | 双河洞穴、峡谷、野咖停靠', - duration: '4-7 天', + title: '喀斯特户外探险线', + subtitle: '洞穴、峡谷、亲水活动与山野咖啡的分级组合', + nights: '7 天 | 贵阳、洞穴区域、峡谷河谷与山野咖啡', + duration: '7 天', price: '私家团询价', - image: guizhouImages.experienceCoffee, - gallery: [guizhouImages.experienceCoffee, guizhouImages.experienceCave, guizhouImages.routeOutdoor, guizhouImages.experienceRafting, guizhouImages.menuOutdoor], + image: guizhouImages.clientRouteOutdoor, + gallery: [guizhouImages.clientRouteOutdoor, guizhouImages.clientOutdoorCave, guizhouImages.clientKarstAdventure, guizhouImages.clientRiverAdventure, guizhouImages.clientCaveDinner], theme: '山野户外', pace: '高活动量', fit: '户外玩家', intensity: '中等到进阶', - service: '安全配置优先 / 活动按天气确认', + service: '私家小团 / 安全条件通过后确认活动', season: '春末至秋季更适合亲水与户外', category: '小众体验', focus: '户外探索', destinations: ['Suiyang', 'Anshun', 'Libo', 'Xingyi'], - summary: '给想看贵州野性一面的客人:双河洞/十二背后洞穴、峡谷、溯溪、漂流、瀑降和山野咖啡按体能与天气组合。', - routeLine: '贵阳 - 绥阳双河洞/十二背后 - 安顺峡谷 - 荔波亲水 - 山野咖啡停靠', - decisionTags: ['户外强度高', '天气备选', '安全装备', '野咖场景'], - bestFor: ['喜欢户外活动的朋友小团', '能接受因天气调整路线的客人', '想把贵州山野和生活方式结合的人'], + summary: '给想看贵州野性一面的客人:先按经验和体能分级,再从洞穴、峡谷、溯溪、平缓水域与山野咖啡中组合;每天保留天气、水位与退出机制。', + routeLine: '贵阳 - 北部洞穴区域 - 安顺 / 黔南峡谷河谷 - 山野咖啡 - 贵阳', + decisionTags: ['活动分级', '天气与水位门槛', '单日单项主活动', '恢复节点'], + bestFor: ['有基础体能、愿意接受活动分级的朋友小团', '能接受因天气与水位临时降级或替换项目的客人', '想把贵州山野与慢节奏恢复体验结合的人'], highlights: [ - { title: '活动分级', copy: '探洞、溯溪、瀑降和漂流按水位、体能、装备与向导比例判断。', image: guizhouImages.experienceCave }, - { title: '山野咖啡', copy: '把顺路的山地咖啡馆、村寨茶歇或观景咖啡停靠放进户外恢复日,现场营业需出发前复核。', image: guizhouImages.experienceCoffee }, - { title: '天气备选', copy: '雨季水位变化快,出发前准备替代路线和活动降级方案。', image: guizhouImages.routeOutdoor }, - { title: '小队执行', copy: '更适合 2-8 人私家小团,方便控制安全、节奏和转场。', image: guizhouImages.menuTransport }, + { title: '本地团队协同进入的小众洞段', copy: '是否进入、进入哪一段,都需在出发前复核运营方、天气、水位、装备与退出条件。', image: guizhouImages.clientOutdoorCave }, + { title: '隐秘山野小屋咖啡', copy: '把山野咖啡放在高活动量之后,作为真正的恢复节点;具体点位与营业状态按日期确认。', image: guizhouImages.clientCaveDinner }, + { title: '具备长期本地经验的溯溪团队', copy: '客户资料提出 8 年以上本地经验;官网正式承诺前仍需完成团队、资质与保险资料核验。', image: guizhouImages.clientKarstAdventure }, ], itinerary: [ - { day: 'Day 1', title: '贵阳集合与装备确认', place: '贵阳', copy: '确认鞋服、头灯、防水袋和保险建议,按天气选择第二天活动强度。', meta: ['装备检查', '安全说明', '城市酒店'] }, - { day: 'Day 2-3', title: '洞穴或峡谷活动', place: '安顺 / 黔南', copy: '选择入门洞穴、进阶洞穴、峡谷徒步或轻瀑降,配本地向导和撤退路线。', meta: ['洞穴', '峡谷', '向导'] }, - { day: 'Day 4-5', title: '荔波亲水与漂流评估', place: '荔波', copy: '把小七孔自然景观与亲水活动结合;水位不适合时改为摄影、森林步道或村寨停靠。', meta: ['水位判断', '亲水', '备选路线'] }, - { day: 'Day 6-7', title: '山野咖啡与恢复日', place: '山地咖啡点', copy: '安排山野咖啡、轻徒步或观景恢复,让线路从高强度回到舒适收尾。', meta: ['野咖', '恢复日', '轻徒步'] }, + { day: 'Day 1', title: '贵阳抵达与户外问诊', place: '贵阳', copy: '先了解既往经验、健康情况、怕水/怕高程度与装备基础,再讨论活动等级。', meta: ['健康问询', '装备清单', '低强度'], schedule: { morning: '按航班或车次抵达;早到客人先入住或寄存行李。', afternoon: '专车接机/接站,完成体能、经验与健康信息问询。', evening: '讲解三级活动框架、天气替代方案和第二天转场安排。', drive: '以机场/高铁站至贵阳市区接送为主。', meals: '餐食按抵达时间确认,建议第一晚清淡补水。', stay: '贵阳城市酒店。' } }, + { day: 'Day 2', title: '进入洞穴区域与基础评估', place: '贵阳 → 北部洞穴区域', copy: '转场后只安排低风险适应,不把长车程和高强度活动叠在同一天。', meta: ['区域转场', '现场评估', '轻量适应'], schedule: { morning: '早餐后前往最终确认的洞穴活动区域,途中安排休息。', afternoon: '运营方现场说明,检查头盔、照明、防滑鞋、防水与通信配置;完成轻量适应。', evening: '复盘状态并确认次日执行轻量、进阶或改走地面线路。', drive: '属于跨区域转场日;准确时间按最终洞穴点位、酒店与路况确认。', meals: '早餐随房;午餐途中;晚餐在住宿地。', stay: '洞穴区域或周边县城住宿。' } }, + { day: 'Day 3', title: '洞穴主活动日', place: '洞穴区域', copy: '只执行经运营方和客人共同确认的等级,不在同一天叠加瀑降或激流项目。', meta: ['洞穴体验', '单项主活动', '退出路线'], schedule: { morning: '再次核对天气、水位、装备、通信与退出点,通过后进入。', afternoon: '按确认等级进行洞穴步行、平缓水域体验或进阶探索;不满足条件则改为地面峰林/村寨线路。', evening: '返回、保暖补给并记录身体状态,为次日恢复或转场留余量。', drive: '当天以活动点短距离接驳为主。', meals: '早餐随房;活动补给按运营方要求准备;晚餐以恢复为主。', stay: '洞穴区域连住或按次日动线调整。' } }, + { day: 'Day 4', title: '恢复与转入峡谷河谷', place: '洞穴区域 → 安顺 / 黔南', copy: '用轻徒步、峰林观景或休息完成区域切换,避免连续两天做技术型活动。', meta: ['恢复日', '峰林轻徒步', '弹性转场'], schedule: { morning: '根据前一日状态安排慢早餐、拉伸与轻量散步。', afternoon: '转往最终确认的峡谷/河谷区域,可加入低强度峰林观景。', evening: '与当地活动方确认次日水位、降雨、装备和替代路线。', drive: '跨区域转场时间在点位确定后写入正式方案。', meals: '早餐随房;午餐途中;晚餐在住宿地。', stay: '安顺、黔南或活动点周边住宿。' } }, + { day: 'Day 5', title: '峡谷与亲水选择日', place: '安顺 / 黔南峡谷河谷', copy: '平缓河段、溯溪、峡谷徒步或进阶绳索项目四选一;实际执行由水位、团队能力与运营条件决定。', meta: ['水位门槛', '活动分级', '天气备选'], schedule: { morning: '完成 Go / No-go 确认:天气水位、运营方、装备保险、客人状态全部通过才执行。', afternoon: '进行最终获批的单项活动;若降级,则转为森林步道、摄影或村寨体验。', evening: '热食、保暖与恢复,不安排夜间高风险项目。', drive: '以活动点接驳为主,具体道路条件由运营方当天确认。', meals: '早餐随房;活动补给和晚餐在最终方案列明。', stay: '峡谷/河谷区域住宿。' } }, + { day: 'Day 6', title: '山野咖啡与慢恢复', place: '山地咖啡点 / 贵阳方向', copy: '把客户提出的山野咖啡变成完整恢复段,而不是活动间匆忙打卡。', meta: ['山野咖啡', '轻徒步', '恢复节奏'], schedule: { morning: '睡到自然醒或安排短距离观景步行,根据营业状态前往山野咖啡点。', afternoon: '咖啡品鉴、山景停留或轻量村寨步行,随后向贵阳方向转场。', evening: '回到城市或近郊休息,做返程前整理。', drive: '按咖啡点与住宿位置规划,不承诺未经确认的固定点位。', meals: '早餐随房;咖啡体验是否含简餐在报价中说明;晚餐自由选择。', stay: '贵阳城市酒店或近郊住宿。' } }, + { day: 'Day 7', title: '贵阳返程', place: '贵阳', copy: '把返程日留给安全缓冲,不临时加入远距离或高风险项目。', meta: ['返程缓冲', '送机送站', '行程复盘'], schedule: { morning: '酒店早餐与整理装备;晚班客人可安排轻量城市活动。', afternoon: '按航班或车次专车送机/送站。', evening: '如为晚班离开,餐食与休息点在最终方案确认。', drive: '以贵阳市区至机场/高铁站接送为主。', meals: '早餐随房;其余餐食按离开时间确认。', stay: '无;延住单独报价。' } }, ], - inclusions: ['活动前风险评估', '本地户外向导建议', '基础安全装备协调', '私家车辆与转场', '天气备选方案'], - exclusions: ['专业保险', '未确认的高风险项目', '个人装备采购', '因天气取消产生的第三方费用', '非行程约定餐食'], - notes: ['户外线路不承诺每天按原计划执行,水位、雨量和客人体能会决定活动是否降级。', '儿童、长辈或无户外经验客人建议选择轻户外版本。'], + inclusions: ['定制行程设计、活动分级与出发前问询', '最终方案确认的本地运营方、领队/向导与基础安全配置', '全程私家车辆与活动点接驳', '最终报价列明的住宿与每日早餐', '天气、水位与地面替代方案', '行程期间 24 小时在地协调'], + exclusions: ['国际/国内大交通与签证', '旅行与专项户外保险,除非正式报价单列写明', '未经书面确认的瀑降、未开发洞穴、激流或其他高风险项目', '个人鞋服与个人装备采购', '因天气、水位、运营方或个人原因产生的第三方改退费用,除非报价另有列明', '最终方案未列明的餐食与小费'], + notes: ['户外线路不承诺按原顺序执行;天气、水位、运营方判断与客人状态会决定活动是否执行、降级或替换。', '页面不发布未经核验的 370 米蹦极、翼装体验、瀑降高度、CE 装备、向导证书或最低年龄承诺。', '最终活动清单需在运营方、向导/领队资料、装备、通信、保险、紧急撤离和客人能力全部确认后写入正式方案。'], }, { slug: 'culture-outdoor-mix', aliases: ['mixed-guizhou'], href: '/safaris/culture-outdoor-mix/', - title: '人文+户外综合混搭线路', - subtitle: '村寨、非遗、河谷、风景与在地主理人', - nights: '5-9 天 | 村寨、河谷、风景与非遗', - duration: '5-9 天', + title: '均衡探索线', + subtitle: '村寨慢住、梯田徒步、非遗手作与轻户外', + nights: '8 天 | 贵阳、黔东南村寨、荔波 / 黔南与山野停靠', + duration: '8 天', price: '私家团询价', - image: guizhouImages.routeMixed, - gallery: [guizhouImages.routeMixed, guizhouImages.experienceCraft, guizhouImages.menuCulture, guizhouImages.destinationLibo, guizhouImages.routeOutdoor], + image: guizhouImages.clientRouteBalanced, + gallery: [guizhouImages.clientRouteBalanced, guizhouImages.clientHeritageHands, guizhouImages.clientLocalTable, guizhouImages.clientCultureCraft, guizhouImages.clientMountainPause], theme: '民族人文', pace: '中等节奏', fit: '境外小团', intensity: '按客人调整', - service: '文化讲解 + 轻户外组合', + service: '私家小团 / 在地预约 + 轻户外组合', season: '全年可做,节庆档期需提前确认', category: '小众体验', focus: '人文深度', destinations: ['Southeast Guizhou', 'Libo', 'South Guizhou', 'Guiyang'], - summary: '把苗侗村寨、非遗体验、河谷景观和轻户外放进同一条路线,适合既想理解文化又不想只坐车打卡的人。', - routeLine: '贵阳 - 黔东南村寨 - 荔波 / 黔南河谷 - 山地体验 - 贵阳', - decisionTags: ['文化深度', '轻户外', '村寨停留', '弹性节奏'], - bestFor: ['家庭、朋友与境外小团', '想加入手作、音乐、餐食和在地交流的客人', '希望户外不太硬核的人'], + summary: '把苗侗村寨慢住、非遗手作、梯田行走、河谷风景与轻户外排进同一条有呼吸感的路线;每天只保留一个核心预约,其余时间按客人状态调整。', + routeLine: '贵阳 - 黔东南村寨慢住 - 梯田与非遗 - 荔波 / 黔南轻户外 - 山野停靠 - 贵阳', + decisionTags: ['村寨连住', '本地人带路', '文化 + 轻户外', '每日留白'], + bestFor: ['希望深入村寨、但不想放弃自然体验的家庭和朋友小团', '喜欢手作、餐食、音乐与在地交流的境外客人', '希望每天只有一个主任务、可按状态调整的人'], highlights: [ - { title: '非遗体验', copy: '苗绣、蜡染、侗族大歌或银饰体验按老师档期预约。', image: guizhouImages.experienceCraft }, - { title: '村寨停留', copy: '保留餐食、夜景和慢步行,不把村寨当作单一拍照点。', image: guizhouImages.destinationXijiang }, - { title: '轻户外切入', copy: '可加入河谷步道、轻漂流、洞穴入门或山野咖啡。', image: guizhouImages.routeOutdoor }, - { title: '翻译与沟通', copy: '境外客人更需要文化背景、礼仪说明和现场翻译。', image: guizhouImages.menuCulture }, + { title: '跟随本地主人慢住村寨', copy: '入住村寨民宿或村民家,把夜晚、清晨、家常饭与社区日常都留在行程里;具体住宿按接待条件确认。', image: guizhouImages.clientRouteBalanced }, + { title: '本地农民带队梯田徒步', copy: '由熟悉土地与季节的人带路,在行走中理解梯田、农事与村寨生活;路线按天气和农时确认。', image: guizhouImages.clientMountainPause }, + { title: '每天保留可调整的节奏', copy: '一个核心预约之外,停留时长和轻量活动可按开放时间、安全条件与客人偏好调整。', image: guizhouImages.clientHeritageHands }, ], itinerary: [ - { day: 'Day 1-2', title: '贵阳与黔东南进入', place: '贵阳 / 黔东南', copy: '从城市抵达过渡到村寨,途中安排餐食、短停和文化背景说明。', meta: ['城市到村寨', '文化导入', '慢节奏'] }, - { day: 'Day 3-4', title: '苗侗村寨与非遗', place: '西江 / 肇兴 / 周边村寨', copy: '按客人兴趣预约苗绣、蜡染、侗族大歌或村寨家宴,留足互动和翻译时间。', meta: ['非遗', '在地餐食', '村寨夜景'] }, - { day: 'Day 5-7', title: '荔波或黔南轻户外', place: '荔波 / 黔南', copy: '选择水森林、河谷步道、轻漂流或洞穴入门,让行程有自然体验但不过度消耗。', meta: ['水森林', '轻户外', '摄影'] }, - { day: 'Day 8-9', title: '山野咖啡或贵阳收尾', place: '山地咖啡点 / 贵阳', copy: '安排山野咖啡、城市餐食或返程前轻松购物,完成从文化到自然的混合路线。', meta: ['野咖', '城市餐食', '返程'] }, + { day: 'Day 1', title: '贵阳抵达与偏好对齐', place: '贵阳', copy: '先确认文化兴趣、步行习惯、饮食边界和希望保留的自由时间,再决定村寨与轻户外比例。', meta: ['接机接站', '偏好沟通', '低强度'], schedule: { morning: '按航班或车次抵达;早到客人先寄存行李或入住。', afternoon: '专车接机/接站,在城市酒店安顿并对齐路线偏好。', evening: '用一顿容易进入贵州味道的晚餐介绍酸辣、村寨礼仪与后续节奏。', drive: '以机场/高铁站至贵阳市区接送为主。', meals: '餐食按抵达时间确认;提前沟通辣度、过敏和忌口。', stay: '贵阳城市酒店。' } }, + { day: 'Day 2', title: '进入黔东南与村寨慢住', place: '贵阳 → 黔东南村寨', copy: '从城市进入苗侗地区,抵达后先安顿,不拖着行李赶观景台。', meta: ['跨区转场', '村寨入住', '主人见面'], schedule: { morning: '早餐后出发,途中安排午餐与必要休息。', afternoon: '抵达最终确认的苗寨或侗寨,与主人见面并入住村寨住宿。', evening: '散步熟悉鼓楼、风雨桥或村路;是否安排家宴按主人档期确认。', drive: '转场时间按最终村寨、酒店和道路条件写入正式方案。', meals: '早餐随房;午餐途中;晚餐可安排主人家常饭。', stay: '黔东南村寨民宿或经确认的村民家。' } }, + { day: 'Day 3', title: '农民带路的梯田与村寨日常', place: '黔东南村寨', copy: '让熟悉土地的人带着走,而不是只从观景台拍一张梯田照片。', meta: ['梯田徒步', '本地人带路', '村寨连住'], schedule: { morning: '按天气、农时和体力,与本地农民进行梯田或村路徒步。', afternoon: '保留休息、农事体验、市场停靠或与主人聊天的弹性时间。', evening: '村寨连住,可在鼓楼周边听音乐、看社区活动或安静休息。', drive: '当天以步行和短距离村寨接驳为主。', meals: '早餐随住;午餐按徒步动线安排;晚餐优先当地家常菜。', stay: '同一村寨连住。' } }, + { day: 'Day 4', title: '非遗工坊与自由半日', place: '村寨 / 周边工坊', copy: '在一个真实工作空间里完成蜡染、刺绣或草木染,不把多项手作压缩成流水线体验。', meta: ['非遗预约', '英文文化沟通', '自由半日'], schedule: { morning: '预约一项适合兴趣与耐心的手作,由在地主理人或传承人带领。', afternoon: '自由选择继续手作、村寨散步、咖啡/茶歇或完整休息。', evening: '与主人或团队复盘,按天气与体力确认下一段自然线路。', drive: '按工坊位置安排短接驳,不跨区域赶路。', meals: '早餐随住;午晚餐按工坊与村寨节奏确认。', stay: '村寨连住第三晚或按次日路线转至周边住宿。' } }, + { day: 'Day 5', title: '从村寨转入荔波 / 黔南自然线', place: '黔东南 → 荔波 / 黔南', copy: '用一次克制转场把人文段和自然段连接起来,当天不叠加高强度项目。', meta: ['区域转换', '自然导入', '轻量停靠'], schedule: { morning: '和村寨主人告别,早餐后前往荔波或最终确认的黔南自然节点。', afternoon: '抵达后只安排轻量森林、河谷或观景停靠。', evening: '入住并说明次日轻户外选择与天气替代方案。', drive: '跨区域时间按最终住宿与活动点复核。', meals: '早餐随住;午餐途中;晚餐在住宿地。', stay: '荔波或黔南自然节点住宿。' } }, + { day: 'Day 6', title: '一项轻户外,留出完整呼吸', place: '荔波 / 黔南', copy: '从水森林、河谷步道、平缓水域或入门洞穴中选择一项,不追求在一天内全部完成。', meta: ['轻户外', '天气备选', '摄影留白'], schedule: { morning: '按天气、人流和体力选择当天唯一核心自然活动。', afternoon: '继续慢走、摄影或提前返回;条件不适合时切换村寨、市集或室内手作。', evening: '热食与恢复,不安排第二项需要赶时间的活动。', drive: '以景区换乘与短距离活动接驳为主。', meals: '早餐随房;午餐以活动效率为先;晚餐在住宿地。', stay: '荔波 / 黔南连住。' } }, + { day: 'Day 7', title: '山野停靠与返回贵阳', place: '荔波 / 黔南 → 山野停靠 → 贵阳', copy: '用山景咖啡、短步行或安静观景把自然段收住,再回城市保留返程缓冲。', meta: ['山野咖啡', '轻量恢复', '返程缓冲'], schedule: { morning: '慢早餐后出发,按营业状态选择顺路的山野咖啡或观景停靠。', afternoon: '返回贵阳,途中安排休息,不追加远距离景点。', evening: '贵阳收尾晚餐,可选择市集小吃或更安静的餐厅。', drive: '按最终住宿与山野停靠点规划,不承诺未经确认的固定咖啡点。', meals: '早餐随房;午餐途中;晚餐在贵阳。', stay: '贵阳城市酒店。' } }, + { day: 'Day 8', title: '贵阳返程', place: '贵阳', copy: '按航班或车次送机送站,把最后一天继续留给从容。', meta: ['送机送站', '自由时间', '返程'], schedule: { morning: '酒店早餐与整理行李;晚班客人可安排市场或城市短步行。', afternoon: '按交通时间退房并专车送机/送站。', evening: '如为晚班离开,餐食与休息点在最终方案确认。', drive: '以贵阳市区至机场/高铁站接送为主。', meals: '早餐随房;其余餐食按离开时间确认。', stay: '无;延住单独报价。' } }, ], - inclusions: ['私家车辆与司导', '文化体验预约建议', '村寨礼仪说明', '酒店与餐食节点规划', '轻户外备选方案'], - exclusions: ['个人购买手作或纪念品', '临时增加的深度课程费用', '旅行保险', '未列明餐食', '单房差'], - notes: ['节庆和非遗老师档期会影响体验顺序。', '不建议每天都换酒店,至少保留一个慢停留夜晚。'], + inclusions: ['定制行程设计与出发前偏好沟通', '最终方案确认的英文文化沟通或英文旅行支持', '全程私家车辆与基础调度', '最终报价列明的住宿与每日早餐', '最终行程列明的村寨主人、梯田带路人或非遗工坊预约', '轻户外天气替代方案与行程期间 24 小时在地协调'], + exclusions: ['国际/国内大交通与签证', '未在最终报价中列明的午餐、晚餐与家宴', '个人购买的手作、材料、纪念品与邮寄费用', '节庆、深度课程或临时增加体验的额外费用', '旅行保险、单房差、小费及因天气/个人原因产生的额外费用,除非报价另有列明'], + notes: ['“灵活”不等于无需预约:村寨主人、农民带路人、非遗老师、节庆和住宿需提前确认,开放时间和安全条件允许时再调整停留时长。', '村寨住宿需逐家确认卫生、卫浴、行李搬运、当前涉外接待条件与真实图片,不使用笼统承诺。', '村寨、工坊与农事活动会随季节、社区安排和主人档期调整;不把日常生活包装成保证出现的表演。'], }, { slug: 'family-heritage', @@ -919,16 +1045,31 @@ export const guizhouActivities = [ export const guizhouExperienceArchiveCards = [ { title: '天然溶洞探险', meta: '遵义绥阳 / 双河洞 / 十二背后', type: '户外', image: guizhouImages.experienceCave, copy: '以绥阳洞穴资源为锚点,按头盔、头灯、向导、水位和客人体力匹配进入路线。', href: '/experiences/cave-expedition/' }, - { title: '峡谷漂流与亲水', meta: '荔波 / 黔东南 / 黔南', type: '户外', image: guizhouImages.experienceRafting, copy: '更适合夏季的轻量亲水活动,可与村寨、野餐或家庭线路组合。', href: '/experiences/rafting/' }, - { title: '溯溪与瀑降', meta: '安顺 / 峡谷河谷', type: '户外', image: guizhouImages.routeOutdoor, copy: '以领队、绳索系统、备用方案和天气检查为前提的可控户外体验。', href: '/experiences/stream-tracing/' }, + { title: '洞穴咖啡与私家晚宴', meta: '具名可接待洞穴 / 场地按日期确认', type: '生活方式', image: guizhouImages.clientCaveLuxuryDining, copy: '将咖啡茶点、私家晚宴与轻量探洞拆开核验,按场地、消防、餐饮卫生、照明、电力、接驳与撤离条件执行。', href: '/experiences/cave-luxury-dining/' }, + { title: '峡谷溯溪与白水漂流', meta: '荔波喀斯特峡谷 / 黔南森林溪流', type: '户外', image: guizhouImages.clientRiverKarstAerial, copy: '在平缓亲水、活力溯溪与待核验白水中只选一种模式,按水文、客人能力、运营方、装备、保险与退出条件执行。', href: '/experiences/rafting/' }, + { title: '瀑降与峡谷绳索', meta: '贵州瀑布群 / 具体点位待确认', type: '户外', image: guizhouImages.clientWaterfallAbseilCanyon, copy: '以具名运营方、绳索系统、水量、保险、撤离与客人能力全部通过为执行前提。', href: '/experiences/stream-tracing/' }, + { title: '喀斯特峰林徒步与骑行', meta: '万峰林 / 茂兰 / 赤水河谷候选', type: '户外', image: guizhouImages.clientKarstHikeSunset, copy: '在田园慢骑、森林徒步与进阶骑行中只选一种模式,按路线、里程、地形、装备、保障与退出条件匹配。', href: '/experiences/karst-hiking-cycling/' }, { title: '苗绣与蜡染非遗体验', meta: '黔东南 / 丹寨 / 安顺', type: '人文', image: guizhouImages.experienceCraft, copy: '由在地主理人带领的手作体验,可结合翻译、礼仪说明和村寨停留。', href: '/experiences/miao-craft/' }, + { title: '侗族草木染与木构建筑', meta: '黔东南侗寨 / 具名工坊与木构讲解人待日期确认', type: '人文', image: guizhouImages.clientCultureDongVillage, copy: '把侗布染制与鼓楼、风雨桥的木构观察放在同一天;工坊、讲解人、材料和可进入空间逐项确认。', href: '/experiences/dong-indigo-architecture/' }, + { title: '水族马尾绣工坊', meta: '三都水族自治县 / 具名工坊与老师待日期确认', type: '人文', image: guizhouImages.clientCultureCraftTable, copy: '先理解以马尾为芯、丝线缠裹的工艺逻辑,再按老师档期、体验时长、材料和拍摄同意确认实际手作。', href: '/experiences/shui-horsetail-embroidery/' }, + { title: '布依族传统织染技艺', meta: '黔西南或安顺候选 / 具名村寨与工坊待核验', type: '人文', image: guizhouImages.clientCultureBatikGallery, copy: '围绕布依族织布、服饰或染色选择一个可执行主题;不把不同地区技艺混成同一场体验。', href: '/experiences/buyi-traditional-crafts/' }, { title: '侗族大歌与鼓楼村寨', meta: '肇兴 / 侗寨', type: '人文', image: guizhouImages.menuCulture, copy: '围绕侗族大歌、鼓楼建筑、村寨礼仪和在地餐食展开的慢体验日。', href: '/experiences/dong-song/' }, { title: '黄果树瀑布错峰玩法', meta: '安顺 / 黄果树 5A 景区', type: '风光', image: guizhouImages.destinationHuangguoshu, copy: '按水量、人流、光线、摄影点和附近恢复住宿来设计瀑布日。', href: '/destinations/huangguoshu/' }, { title: '荔波樟江与小七孔水森林', meta: '黔南 / 荔波', type: '风光', image: guizhouImages.destinationLibo, copy: '以喀斯特水系、森林步道、桥、水瀑和亲子友好节奏组成的自然锚点。', href: '/destinations/libo-xiaoqikong/' }, { title: '梵净山天气窗口', meta: '铜仁 / 世界自然遗产', type: '风光', image: guizhouImages.destinationFanjing, copy: '围绕云雾、森林、奇石、票务窗口和早出发物流安排的山地体验。', href: '/destinations/fanjing-mountain/' }, { title: '西江与侗寨夜晚', meta: '黔东南', type: '人文', image: guizhouImages.destinationXijiang, copy: '木楼、梯田视野、音乐、手作、餐食和摄影适合做成慢住村寨体验。', href: '/destinations/xijiang-miao-village/' }, { title: '万峰林田野与咖啡', meta: '兴义 / 黔西南 / 4A 景区', type: '风光', image: guizhouImages.destinationWanfenglin, copy: '峰林、骑行、村寨餐食、布依文化、摄影和山地咖啡停靠可放在同一天。', href: '/destinations/wanfenglin/' }, - { title: '山野咖啡停靠', meta: '按路线匹配山地咖啡与村寨茶歇', type: '生活方式', image: guizhouImages.experienceCoffee, copy: '在户外、摄影或自驾线路中加入顺路的咖啡/茶歇点,出发前复核营业状态。', href: '/experiences/wild-coffee/' }, + { title: '悬崖与山野观景咖啡', meta: '具名观景咖啡馆 / 私家停靠候选', type: '生活方式', image: guizhouImages.clientWildCoffeeCliffCup, copy: '优先选择已有护栏、步道与接待设施的具名咖啡馆;私家户外冲煮仅在场地授权、天气、临崖保护、火源与无痕撤收全部确认后安排。', href: '/experiences/wild-coffee/' }, + { title: '原生村寨沉浸', meta: '苗族 / 侗族 / 布依族村寨候选 / 具名住宿按日期核验', type: '人文', image: guizhouImages.clientVillageDrumtowerRoofs, copy: '从苗族吊脚楼、侗族鼓楼社区与布依石头寨中选择一处,按半日、一晚或两晚逐级确认主人、房间、卫生消防、住宿登记、活动和社区边界。', href: '/experiences/village-immersion/' }, + { title: '节庆民俗盛典', meta: '苗年 / 姊妹节 / 侗族节庆 / 水族端节 / 当年核验', type: '人文', image: guizhouImages.clientFestivalLeishanProcession, copy: '把传统农历节日、当年公共活动和社区内部仪式分开说明;日期、地点、节目、交通、拍摄与住宿均以当年官方和社区通知为准。', href: '/experiences/ethnic-festivals/' }, + { title: '民族美食文化', meta: '家宴烹饪课 / 原生长桌宴 / 在地市集', type: '人文', image: guizhouImages.clientCulinarySourSoupTable, copy: '从具名主人、合规餐饮场所或真实营业的市场中选择一种体验;菜单、食材、卫生、过敏原、辣度与酒精礼仪逐项确认。', href: '/experiences/culinary-cultural-journeys/' }, + { title: '世界级瀑布群', meta: '黄果树 / 滴水滩 / 赤水丹霞 / 当日开放核验', type: '风光', image: guizhouImages.clientWaterfallHuangguoshuMain, copy: '把成熟景区、山谷多级瀑布与丹霞瀑布分成三套游览逻辑;开放、票务、水量、步行、接驳与安全边界按日期确认。', href: '/experiences/iconic-waterfall-clusters/' }, + { title: '原生民族村寨', meta: '西江 / 肇兴与黄岗 / 岜沙 / 安顺布依石寨', type: '人文', image: guizhouImages.clientVillagesXijiangPanorama, copy: '不按“原生态”给村寨排名,而是比较成熟景区、社区节庆、公开演艺与具名主人接待的不同边界,再按日期确认村寨、住宿、交通和拍摄。', href: '/experiences/indigenous-ethnic-villages/' }, + { title: '历史古镇古村', meta: '青岩 / 镇远 / 天龙屯堡', type: '人文', image: guizhouImages.clientAncientTownsZhenyuanRiver, copy: '从明代石城、㵲阳河商埠到安顺屯堡,分别理解公共古镇、活态县城与景区化村寨;门票、演出、夜景、住宿和居民边界按日期确认。', href: '/experiences/historic-ancient-towns/' }, + { title: '青岩战斗・实景沉浸演绎', meta: '《战青岩》已运营 / 境外私家小团待专项确认', type: '人文', image: guizhouImages.clientAncientTownsStoneWall, copy: '官方报道已确认《战青岩》在定广湖畔开展实景战斗体验;官网把现有产品与客户提出的古镇街巷剧本方案分开,档期、票价、年龄、英文与私家接待逐项核验。', href: '/experiences/qingyan-battle-immersive-roleplay/' }, + { title: '娄山关战役・山道沉浸行军', meta: '现有互动演出 / 战斗遗址步行 / 山道剧本待共创', type: '人文', image: guizhouImages.clientLoushanguanImmersiveMarch, copy: '把“跟着团长攻打娄山关”互动演出、娄山关战斗遗址参观与客户提出的4–5小时山道行军剧本分开;场次、路线、英文、体能和特效安全按日期确认。', href: '/experiences/loushanguan-battle-immersive-march/' }, + { title: '喀斯特野奢星空露营', meta: '具名经营营地 / 山地景观住宿 / 私家搭建候选', type: '生活方式', image: guizhouImages.clientKarstValleyCamp, copy: '优先选择具名经营、固定营位、卫浴和夜间值守明确的营地;星空、篝火与天坑/河畔私家搭建只按天气、防火、场地授权和撤离条件确认。', href: '/experiences/karst-glamping/' }, + { title: '非遗与轻户外融合体验', meta: '丹寨 / 安顺 / 湄潭或普安候选 / 一天一组', type: '人文', image: guizhouImages.clientHeritageHands, copy: '从梯田步行与蜡染、植物观察与古法造纸、茶园步行/骑行与制茶中选择一组;具名路线、老师、季节、材料与安全保障逐项确认。', href: '/experiences/heritage-outdoor-pairings/' }, { title: '温泉恢复日', meta: '剑河 / 黄果树柏联', type: '康养', image: guizhouImages.hotelBolian, copy: '适合放在山地、洞穴和亲水活动之后,作为软着陆或体力恢复节点。', href: '/experiences/hot-spring-recovery/' }, ]; @@ -945,13 +1086,13 @@ export const guizhouFoodCards = [ ]; export const guizhouTransportCards = [ - { id: 'vehicle-5', seats: '5 座', english: '轿车 / SUV', image: guizhouImages.menuTransport, bestFor: '1-3 位客人、机场接送、贵阳市区和短途轻装出行', luggage: '2-3 件行李', copy: '适合情侣、小家庭或商务客人的舒适基础车型,灵活进入城市酒店、古镇和景区停车点。' }, - { id: 'vehicle-7', seats: '7 座', english: '商务 MPV', image: guizhouImages.routeMixed, bestFor: '3-5 位客人、亲子小团、英文司导随车', luggage: '4-5 件行李', copy: '贵州私家定制最常用车型之一,兼顾舒适、行李和山路转场,适合 4-8 天路线。' }, - { id: 'vehicle-9', seats: '9 座', english: '商务车', image: guizhouImages.footerCta, bestFor: '5-7 位客人、摄影团、小型同业考察', luggage: '6-7 件行李', copy: '比 MPV 有更好的行李和座位余量,适合多地市转场、设备较多或需要更宽松坐姿的小团。' }, - { id: 'vehicle-14', seats: '14 座', english: '小型客车', image: guizhouImages.routeOutdoor, bestFor: '8-11 位客人、小型研学或户外队伍', luggage: '按线路评估', copy: '适合轻团队,能覆盖贵阳、安顺、黔南、黔东南等常见路线,需按景区道路限制确认。' }, - { id: 'vehicle-19', seats: '17-19 座', english: '中型客车', image: guizhouImages.menuHotels, bestFor: '12-15 位客人、企业小团、同业踩线', luggage: '按后备箱和补给量确认', copy: '适合中型团队,空间更宽裕,可搭配随车领队、英文讲解和多日后勤补给。' }, + { id: 'vehicle-5', seats: '5 座', english: '轿车 / SUV', image: guizhouImages.clientTransportSedanSuv, bestFor: '1-3 位客人、机场接送、贵阳市区和短途轻装出行', luggage: '按箱体尺寸确认,通常 2-3 件', copy: '适合情侣、小家庭或轻装客人的舒适基础车型;具体乘员与行李组合在派车前确认。' }, + { id: 'vehicle-7', seats: '7 座', english: '商务 MPV', image: guizhouImages.clientTransportPremiumMpv, bestFor: '3-5 位客人、亲子小团、多日山路转场', luggage: '按座位使用方式确认', copy: '贵州私家定制常用车型之一,兼顾舒适与上下车便利;英文沟通由行程服务团队另行确认。' }, + { id: 'vehicle-9', seats: '9 座', english: '轻型商务车', image: guizhouImages.clientTransportLightVan, bestFor: '5-7 位客人、摄影小团、装备较多的路线', luggage: '按实车行李区确认', copy: '比 MPV 有更多座位余量,适合跨地市转场;额定座位数不等于建议客人数。' }, + { id: 'vehicle-14', seats: '14 座', english: '小型客车', image: guizhouImages.clientTransportVanInterior, bestFor: '8-11 位客人、家庭或户外小团', luggage: '按线路与实车评估', copy: '适合人数稍多的私家小团,需按营运资质、行李区和景区道路限制确认。' }, + { id: 'vehicle-19', seats: '17-19 座', english: '中型客车', image: guizhouImages.menuTransport, bestFor: '12-15 位客人、亲友或同业小团', luggage: '按后备箱和补给量确认', copy: '提供更宽裕的团队空间,可搭配随车领队、英文旅行支持和多日后勤补给。' }, { id: 'vehicle-30', seats: '22-30 座', english: '团队巴士', image: guizhouImages.heroHuangguoshu, bestFor: '16-24 位客人、常规团队和跨地市线路', luggage: '团队行李方案', copy: '适合标准团队,行程设计会提前避开道路受限点,并安排上下车和用餐动线。' }, - { id: 'vehicle-39', seats: '33-39 座', english: '大型旅游车', image: guizhouImages.destinationLibo, bestFor: '25-34 位客人、会议奖励旅游、校园团队', luggage: '团队行李方案', copy: '适合人数较大的团队,需要更明确的集合、用餐、洗手间、景区停车和备用车调度计划。' }, + { id: 'vehicle-39', seats: '33-39 座', english: '大型旅游车', image: guizhouImages.destinationLibo, bestFor: '25-34 位客人、大型亲友或同业团队', luggage: '团队行李方案', copy: '适合人数较大的团队,需要更明确的集合、用餐、洗手间、景区停车和备用车调度计划。' }, { id: 'vehicle-50', seats: '45-50 座', english: '大型团队巴士', image: guizhouImages.heroHome, bestFor: '35-45 位客人、大型团队和同业批量接待', luggage: '团队行李方案', copy: '用于大团队接待,需提前确认山路、景区停车、酒店车位和分车预案,避免把体验卡在交通上。' }, ]; @@ -1087,7 +1228,7 @@ const products: CmsContent['products'] = [ status: 'published', featured: true, imageId: 'route-classic', - title: t('Classic Guizhou Culture Route', '经典人文打卡线路'), + title: t('Classic Panorama Route', '经典全景线'), subtitle: t('Guiyang, Huangguoshu, Libo, Xijiang, Zhenyuan', '贵阳、黄果树、荔波、西江、镇远'), summary: t('A first-time private route through Guizhou waterfalls, karst water forests, Miao villages, ancient towns, and city food.', '第一次来贵州的私家路线,把瀑布、喀斯特水森林、苗寨、古镇和城市餐食串起来。'), duration: t('6-8 days', '6-8 天'), @@ -1103,7 +1244,7 @@ const products: CmsContent['products'] = [ status: 'published', featured: true, imageId: 'experience-coffee', - title: t('Mountain Outdoor and Wild Coffee', '极限山野户外野咖线路'), + title: t('Karst Outdoor Adventure', '喀斯特户外探险线'), subtitle: t('Caves, canyons, stream tracing, rafting, and mountain coffee', '探洞、峡谷、溯溪、漂流与山野咖啡'), summary: t('An active route for travelers who want the wilder side of Guizhou, with professional safety setup and weather backup plans.', '为想体验贵州野性一面的客人设计,包含专业安全配置和天气备选方案。'), duration: t('4-7 days', '4-7 天'), @@ -1119,7 +1260,7 @@ const products: CmsContent['products'] = [ status: 'published', featured: false, imageId: 'route-mixed', - title: t('Culture plus Outdoor Mix', '人文+户外综合混搭线路'), + title: t('Balanced Discovery Route', '均衡探索线'), subtitle: t('Villages, craft, rivers, scenery, and local hosts', '村寨、非遗、河谷、风景与在地主理人'), summary: t('A balanced route for families, friends, and inbound guests who want culture and outdoor days in one Guizhou plan.', '适合家庭、朋友和境外客人,在一条贵州路线里平衡人文和户外体验。'), duration: t('5-9 days', '5-9 天'), @@ -1566,7 +1707,7 @@ export function createGuizhouCmsContent(): CmsContent { email: 'travel@wanderq.com', wechat: '', office: t('', ''), - defaultLanguage: 'zh', + defaultLanguage: 'en', bookingMode: 'inquiry', externalBookingUrl: '', inquiryOwner: '贵州定制顾问组', diff --git a/src/guizhouEnContent.ts b/src/guizhouEnContent.ts index 7751e97..f92a654 100644 --- a/src/guizhouEnContent.ts +++ b/src/guizhouEnContent.ts @@ -1,10 +1,10 @@ export const safariEnglish = { 'classic-guizhou': { - title: 'Classic Guizhou Culture Route', + title: 'Classic Panorama Route', subtitle: 'Huangguoshu, Libo, Xijiang, Zhenyuan, and Guiyang food', nights: '6-8 days | Guiyang, Anshun, Libo, Southeast Guizhou', duration: '6-8 days', - price: 'USD 1,000-5,000', + price: 'Private quote', theme: 'Classic landscapes', pace: 'Moderate pace', fit: 'First-time Guizhou', @@ -18,88 +18,96 @@ export const safariEnglish = { decisionTags: ['First-time Guizhou', 'Classic landscapes', 'Ethnic culture', 'Comfortable hotels'], bestFor: ['Inbound guests visiting Guizhou for the first time', 'Families or friends who want signature landscapes', 'Travelers who value smooth transfers and fewer surprises'], highlights: [ - { title: 'Huangguoshu timing', copy: 'We time waterfall entry around season, water volume, crowds, and light.', imageKey: 'destinationHuangguoshu' }, - { title: 'Libo water forest', copy: 'Xiaoqikong trails, water stops, and light photography time are planned into one nature day.', imageKey: 'destinationLibo' }, - { title: 'Village slow stay', copy: 'Miao and Dong villages work best with meals, night views, craft, or a slower overnight rhythm.', imageKey: 'destinationXijiang' }, - { title: 'Guiyang food finish', copy: 'Guiyang night markets and snacks are ideal on arrival or before departure.', imageKey: 'menuFood' }, + { title: 'Private home-cooked meal with a Miao family', copy: 'Share a home-cooked meal in a family setting rather than a commercial group restaurant.', imageKey: 'clientMiaoMeal' }, + { title: 'Sunrise access to a Dong village before crowds arrive', copy: 'Enter early and leave the morning to wooden homes, village paths and daily life.', imageKey: 'destinationXijiang' }, + { title: 'Private workshop with a third-generation batik artisan', copy: 'Meet the artisan in a working studio rather than a production-line experience shop.', imageKey: 'clientCultureCraft' }, ], itinerary: [ - { day: 'Day 1', title: 'Arrive in Guiyang', place: 'Guiyang', copy: 'Airport or rail pickup, city hotel check-in, and a light city-food evening to ease into Guizhou.', meta: ['Pickup', 'City hotel', 'Low intensity'] }, - { day: 'Day 2-3', title: 'Huangguoshu and Anshun landscapes', place: 'Anshun', copy: 'Visit the waterfall area with crowd-aware timing, with optional Tunpu food or a light culture stop.', meta: ['Waterfall', 'Off-peak timing', 'Short transfer'] }, - { day: 'Day 4-5', title: 'Libo Xiaoqikong and water forest', place: 'Libo', copy: 'Plan walking, photography, Wolongtan, Cuigu waterfall, and optional light water activities when conditions allow.', meta: ['Karst water', 'Light hiking', 'Water-friendly'] }, - { day: 'Day 6-8', title: 'Southeast Guizhou villages and Zhenyuan', place: 'Xijiang / Zhaoxing / Zhenyuan', copy: 'Choose Miao villages, Dong villages, ancient-town nights, or craft experiences according to the group.', meta: ['Ethnic culture', 'Night views', 'Ancient town'] }, + { day: 'Day 1', title: 'Arrive and ease into Guiyang', place: 'Guiyang', copy: 'Pickup and hotel check-in come first, with a light evening shaped around arrival time and jet lag.', meta: ['Pickup', 'City hotel', 'Low intensity'], schedule: { morning: 'Arrive according to your flight or train; early arrivals can leave luggage first.', afternoon: 'Private pickup, city-hotel check-in and an optional light walk.', evening: 'An approachable first Guizhou meal and an early night.', drive: 'Airport or rail station to the city is usually about 30–60 minutes, subject to traffic.', meals: 'Breakfast not included; lunch and dinner depend on arrival time.', stay: 'Guiyang city hotel.' } }, + { day: 'Day 2', title: 'Guiyang to Huangguoshu', place: 'Guiyang → Huangguoshu / Anshun', copy: 'Begin with Guizhou’s most recognizable waterfall landscape, timed around crowds and walking ability.', meta: ['Waterfall', 'Off-peak', 'Moderate walking'], schedule: { morning: 'Breakfast, then drive to Huangguoshu with a comfort stop if needed.', afternoon: 'Choose the main waterfall, Doupotang and Tianxingqiao according to water flow and energy.', evening: 'Check in around Huangguoshu or Anshun and have a recovery dinner.', drive: 'Planning reference: about 2–2.5 hours from Guiyang, plus scenic-area transfers.', meals: 'Hotel breakfast; lunch and dinner stated in the final plan.', stay: 'Huangguoshu or Anshun area.' } }, + { day: 'Day 3', title: 'Waterfall finish and transfer to Libo', place: 'Huangguoshu / Anshun → Libo', copy: 'Keep a short morning option, then protect comfort during the longer transfer to southern Guizhou.', meta: ['Regional transfer', 'Weather buffer', 'Libo check-in'], schedule: { morning: 'Complete one light waterfall section if needed, or rest before departure.', afternoon: 'Private transfer to Libo with lunch and rest stops; no heavy attraction is added.', evening: 'Check in and confirm the next day’s Xiaoqikong entry plan.', drive: 'Planning reference: about 4–5 hours, depending on hotel location and roads.', meals: 'Hotel breakfast; lunch en route; dinner in Libo.', stay: 'Libo town or near the scenic area.' } }, + { day: 'Day 4', title: 'Libo Xiaoqikong and water forest', place: 'Libo', copy: 'Give the emerald water, old bridge, cascades and forest paths a full day rather than rushing onward.', meta: ['Water forest', 'Light hiking', 'Photography'], schedule: { morning: 'Choose the entrance around crowd flow and walk the ancient bridge, Laya waterfall and water-forest line.', afternoon: 'Continue to Wolongtan, Cuigu waterfall or a slower waterside pause according to energy.', evening: 'Return to the hotel and choose local sour flavors or a gentler alternative meal.', drive: 'Mostly scenic shuttles and short hotel transfers.', meals: 'Hotel breakfast; efficient scenic-area lunch; dinner in Libo.', stay: 'Second night in Libo.' } }, + { day: 'Day 5', title: 'Libo to Xijiang Miao country', place: 'Libo → Xijiang / Southeast Guizhou', copy: 'Shift from karst nature to village life, leaving the evening for a hosted meal and wooden-house lights.', meta: ['Miao village', 'Hosted meal', 'Night view'], schedule: { morning: 'Leave Libo after breakfast with a short stop if the group needs it.', afternoon: 'Arrive in Southeast Guizhou and check in before entering the village without luggage.', evening: 'Share a private Miao family meal or choose a more familiar alternative.', drive: 'Planning reference: about 3.5–4 hours, subject to roads and stops.', meals: 'Hotel breakfast; lunch en route; optional hosted family dinner.', stay: 'Xijiang or a nearby village stay.' } }, + { day: 'Day 6', title: 'Village morning, batik studio and Zhenyuan', place: 'Village → Zhenyuan', copy: 'See the village before peak crowds, then enter a working artisan studio before slowing down in Zhenyuan.', meta: ['Early village', 'Batik studio', 'Ancient-town stay'], schedule: { morning: 'Walk a Dong or Miao village early, with time for wooden homes, lanes and daily life.', afternoon: 'Private session with a third-generation batik artisan, followed by the drive to Zhenyuan.', evening: 'Check in and take an easy riverside walk with dinner.', drive: 'Planning reference: about 2–3 hours, adjusted around the studio location.', meals: 'Hotel breakfast; lunch in the village or Kaili area; dinner in Zhenyuan.', stay: 'Zhenyuan Ancient Town or nearby.' } }, + { day: 'Day 7', title: 'Zhenyuan slowly, then return to Guiyang', place: 'Zhenyuan → Guiyang', copy: 'Keep half a day for the ancient town, then return to Guiyang with a useful departure buffer.', meta: ['Ancient town', 'Departure buffer', 'Guiyang food'], schedule: { morning: 'Walk the Wuyang River, old lanes and historic quarter at a relaxed pace.', afternoon: 'Private transfer back to Guiyang with comfort stops.', evening: 'A final Guiyang dinner, from market snacks to a quieter restaurant.', drive: 'Planning reference: about 3.5–4 hours, depending on the city hotel.', meals: 'Hotel breakfast; lunch in Zhenyuan or en route; dinner in Guiyang.', stay: 'Guiyang city hotel.' } }, + { day: 'Day 8', title: 'Depart from Guiyang', place: 'Guiyang', copy: 'Plan the day around the flight or train instead of forcing in a distant attraction.', meta: ['Departure transfer', 'Time buffer', 'Departure'], schedule: { morning: 'Breakfast and packing; a light city option is possible for a late departure.', afternoon: 'Check out and private transfer to the airport or station.', evening: 'For very late departures, meals and a rest point are confirmed in the final plan.', drive: 'City to airport or rail station is usually about 30–60 minutes, subject to traffic.', meals: 'Hotel breakfast; later meals depend on departure time.', stay: 'None; late checkout or an extra night is quoted separately.' } }, ], - inclusions: ['Private vehicle and basic dispatch', 'Chinese or English guide plan', 'Route design and attraction timing advice', 'Hotel tier matching and backup options', '24-hour local support'], - exclusions: ['International flights and visas', 'Personal spending and travel insurance', 'Meals not listed', 'Single supplement', 'Extra costs caused by weather or personal changes'], - notes: ['Travelers aged 12+ are planned as adults by default.', 'Waterfalls, water forests, and villages may be reordered around weather, crowds, and festivals.'], + inclusions: ['Custom itinerary design and pre-trip planning', 'English-speaking guide or English trip support confirmed in the final plan', 'Private vehicle and core dispatch', 'Hotels and daily breakfast listed in the final quote', 'Main attraction entrance tickets listed in the final itinerary', '24-hour on-ground coordination during the journey'], + exclusions: ['International or domestic transport to Guizhou and visas', 'Lunches and dinners not listed in the final quote', 'Personal spending, tips and single supplement', 'Unconfirmed hotel or activity upgrades', 'Travel insurance and extra costs caused by weather, policy or personal changes unless stated otherwise'], + notes: ['Drive times are planning references, not live-navigation promises; the final plan is checked against hotels, roads, weather and holiday traffic.', 'Each hotel must be reconfirmed for current foreign-guest reception before booking; we do not publish an unverified blanket accreditation claim.', 'Waterfalls, water forests, villages and host visits may be reordered around weather, crowds, festivals and availability.'], }, 'outdoor-coffee': { - title: 'Mountain Outdoor and Wild Coffee Route', - subtitle: 'Caves, canyons, stream tracing, rafting, and mountain coffee', - nights: '4-7 days | Caves, canyons, and wild coffee stops', - duration: '4-7 days', + title: 'Karst Outdoor Adventure', + subtitle: 'A graded mix of caves, canyons, water days, and mountain coffee', + nights: '7 days | Guiyang, cave country, canyon valleys, and mountain coffee', + duration: '7 days', price: 'Private quote', theme: 'Mountain outdoor', pace: 'Active pace', fit: 'Outdoor travelers', intensity: 'Moderate to advanced', - service: 'Safety setup first / activities confirmed by weather', + service: 'Private small group / activities confirmed after safety review', season: 'Late spring to autumn works best for water and outdoor days', category: 'Niche', focus: 'Outdoor', - summary: 'For travelers who want the wilder side of Guizhou: cave systems, canyons, stream tracing, rafting, rappelling, and mountain coffee stops matched to ability and weather.', - routeLine: 'Guiyang - Suiyang Shuanghe Cave / Twelve Back - Anshun canyons - Libo water activities - Mountain coffee stop', - decisionTags: ['High outdoor intensity', 'Weather backup', 'Safety gear', 'Wild coffee moments'], - bestFor: ['Small groups who enjoy outdoor activity', 'Travelers comfortable with weather-led changes', 'Guests who want nature and lifestyle in one route'], + summary: 'For travelers who want the wilder side of Guizhou: first grade the trip by experience and ability, then combine caves, canyons, stream days, calm water and mountain coffee with a daily weather gate and an exit plan.', + routeLine: 'Guiyang - Northern cave country - Anshun / Qiannan canyon valleys - Mountain coffee - Guiyang', + decisionTags: ['Graded activities', 'Weather and water gate', 'One core activity per day', 'Recovery built in'], + bestFor: ['Friends with basic fitness who are comfortable choosing an activity grade', 'Travelers who accept weather- or water-led downgrades and substitutions', 'Guests who want wild Guizhou balanced with thoughtful recovery'], highlights: [ - { title: 'Activity grading', copy: 'Caving, stream tracing, rappelling, and rafting are selected by water level, ability, equipment, and guide ratio.', imageKey: 'experienceCave' }, - { title: 'Wild coffee stops', copy: 'Mountain cafes, village tea breaks, or scenic coffee stops are verified before departure.', imageKey: 'experienceCoffee' }, - { title: 'Weather backup', copy: 'Rainy-season water levels change quickly, so backup routes and downgraded activities are prepared.', imageKey: 'routeOutdoor' }, - { title: 'Small-team operation', copy: 'Best for private groups of 2-8 people so safety, pace, and transfers stay controlled.', imageKey: 'menuTransport' }, + { title: 'An off-the-map cave section reached with our local team', copy: 'Access and the exact section are reconfirmed against operator, weather, water, equipment and exit conditions.', imageKey: 'clientOutdoorCave' }, + { title: 'Wild coffee tasting at a hidden mountain cabin', copy: 'A genuine recovery pause after active days, with the location and opening status reconfirmed for your dates.', imageKey: 'clientCaveDinner' }, + { title: 'A canyoning team with 8+ years of local experience', copy: 'The client brief states 8+ years locally; team, qualification and insurance records still need verification before a formal promise.', imageKey: 'clientKarstAdventure' }, ], itinerary: [ - { day: 'Day 1', title: 'Meet in Guiyang and check gear', place: 'Guiyang', copy: 'Review shoes, lamps, dry bags, insurance suggestions, and the next day activity level.', meta: ['Gear check', 'Safety briefing', 'City hotel'] }, - { day: 'Day 2-3', title: 'Cave or canyon activity', place: 'Anshun / Qiannan', copy: 'Choose entry-level cave, advanced cave, canyon walk, or light rappelling with local guides and exit options.', meta: ['Caves', 'Canyons', 'Local guide'] }, - { day: 'Day 4-5', title: 'Libo water and rafting check', place: 'Libo', copy: 'Pair Xiaoqikong nature with water activities; if levels are unsuitable, switch to photography, forest trails, or village stops.', meta: ['Water-level check', 'Water activity', 'Backup route'] }, - { day: 'Day 6-7', title: 'Wild coffee and recovery', place: 'Mountain coffee stop', copy: 'Finish with wild coffee, light walking, or a scenic rest day after higher-intensity activity.', meta: ['Wild coffee', 'Recovery day', 'Light hike'] }, + { day: 'Day 1', title: 'Arrive in Guiyang and assess the trip', place: 'Guiyang', copy: 'Start with experience, health, water/height comfort and personal gear before discussing the activity grade.', meta: ['Health questions', 'Gear list', 'Low intensity'], schedule: { morning: 'Arrive by flight or train; early arrivals can check in or leave luggage.', afternoon: 'Private pickup followed by a fitness, experience and health conversation.', evening: 'Review the three activity grades, weather substitutions and the next day transfer.', drive: 'Primarily the airport or rail transfer into Guiyang.', meals: 'Meals depend on arrival time; a lighter first dinner and hydration are recommended.', stay: 'Guiyang city hotel.' } }, + { day: 'Day 2', title: 'Enter cave country and complete the field check', place: 'Guiyang → Northern cave country', copy: 'Use the transfer day for low-risk adaptation rather than stacking a long drive and a technical activity.', meta: ['Regional transfer', 'Field check', 'Light adaptation'], schedule: { morning: 'Breakfast, then travel to the finally approved cave area with comfort stops.', afternoon: 'Operator briefing and checks for helmet, lighting, grip, water protection and communication; complete a light adaptation.', evening: 'Review the day and decide whether Day 3 stays light, advances or moves to a ground-based alternative.', drive: 'A regional transfer; exact time follows the final cave site, hotel and road conditions.', meals: 'Hotel breakfast; lunch en route; dinner near the stay.', stay: 'Near the cave area or in the closest practical town.' } }, + { day: 'Day 3', title: 'Single-focus cave day', place: 'Cave country', copy: 'Run only the grade jointly approved by the operator and guests, without adding rappelling or white water on the same day.', meta: ['Cave experience', 'One core activity', 'Exit route'], schedule: { morning: 'Recheck weather, water, equipment, communication and exit points before entry.', afternoon: 'Walk, use calm water or take an advanced cave section at the approved grade; switch to a peak-forest or village day if conditions fail.', evening: 'Return, warm up and review physical condition before the recovery/transfer day.', drive: 'Mostly short transfers around the approved activity site.', meals: 'Hotel breakfast; activity fuel prepared to operator requirements; recovery dinner.', stay: 'A second night near the cave area or adjusted for the following route.' } }, + { day: 'Day 4', title: 'Recover and move into canyon country', place: 'Cave country → Anshun / Qiannan', copy: 'Use a gentle walk, peak-forest view or full rest to change regions without technical activities on consecutive days.', meta: ['Recovery day', 'Gentle walk', 'Flexible transfer'], schedule: { morning: 'Slow breakfast, stretching and a light walk according to Day 3 recovery.', afternoon: 'Transfer to the approved canyon/river area with an optional low-intensity viewpoint.', evening: 'Confirm the next day water, rainfall, operator, equipment and alternative route.', drive: 'Regional transfer time is written into the final plan after sites are fixed.', meals: 'Hotel breakfast; lunch en route; dinner near the stay.', stay: 'Anshun, Qiannan or accommodation near the activity base.' } }, + { day: 'Day 5', title: 'Choose one canyon or water activity', place: 'Anshun / Qiannan canyon valley', copy: 'Select calm water, stream tracing, canyon walking or a technical rope option; actual operation follows water, ability and operator approval.', meta: ['Water gate', 'Graded activity', 'Weather backup'], schedule: { morning: 'Complete the Go / No-go gate: weather and water, operator, equipment and insurance, and guest condition must all pass.', afternoon: 'Run the single approved activity; downgrade to forest walking, photography or a village visit if any gate fails.', evening: 'Warm food and recovery, with no high-risk night activity.', drive: 'Mostly activity-site transfers; road access is reconfirmed by the operator.', meals: 'Hotel breakfast; activity fuel and dinner stated in the final plan.', stay: 'Near the canyon or river area.' } }, + { day: 'Day 6', title: 'Mountain coffee and a slower finish', place: 'Mountain coffee / toward Guiyang', copy: 'Turn the mountain-coffee idea into a real recovery chapter rather than a hurried photo stop.', meta: ['Mountain coffee', 'Gentle walk', 'Recovery rhythm'], schedule: { morning: 'Sleep in or take a short viewpoint walk, then visit the coffee stop if it is operating.', afternoon: 'Coffee tasting, mountain time or an easy village walk before moving toward Guiyang.', evening: 'Return to the city or its outskirts and prepare for departure.', drive: 'Planned around the confirmed coffee stop and hotel; no unverified fixed location is promised.', meals: 'Hotel breakfast; whether coffee includes a light meal is stated in the quote; dinner is flexible.', stay: 'Guiyang city or nearby accommodation.' } }, + { day: 'Day 7', title: 'Depart from Guiyang', place: 'Guiyang', copy: 'Keep the final day as a safety buffer instead of adding a distant or high-risk activity.', meta: ['Departure buffer', 'Airport transfer', 'Trip review'], schedule: { morning: 'Breakfast and gear packing; a light city option is possible for a late departure.', afternoon: 'Private transfer to the airport or rail station.', evening: 'For a late departure, meals and a rest point are confirmed in the final plan.', drive: 'Primarily the Guiyang city transfer to airport or rail station.', meals: 'Hotel breakfast; later meals depend on departure time.', stay: 'None; an extra night is quoted separately.' } }, ], - inclusions: ['Pre-activity risk review', 'Local outdoor guide advice', 'Basic safety gear coordination', 'Private vehicle and transfers', 'Weather backup plan'], - exclusions: ['Specialized insurance', 'Unconfirmed high-risk activities', 'Personal gear purchases', 'Third-party costs from weather cancellations', 'Meals not agreed in the route'], - notes: ['Outdoor routes may change daily based on water, rain, and guest ability.', 'Children, seniors, or beginners should choose a lighter outdoor version.'], + inclusions: ['Custom route design, activity grading and pre-trip questions', 'Local operator, leader/guide and basic safety setup stated in the final plan', 'Private vehicle and activity-site transfers', 'Hotels and daily breakfast listed in the final quote', 'Weather, water and ground-based alternatives', '24-hour on-ground coordination during the journey'], + exclusions: ['International or domestic transport to Guizhou and visas', 'Travel or specialist outdoor insurance unless separately listed in the formal quote', 'Any rappelling, undeveloped cave, white-water or high-risk activity not confirmed in writing', 'Personal clothing, footwear and gear purchases', 'Third-party change/cancellation costs caused by weather, water, operator or guest factors unless otherwise stated', 'Meals and tips not listed in the final plan'], + notes: ['The published sequence is not guaranteed: weather, water, operator judgment and guest condition determine whether an activity runs, downgrades or changes.', 'We do not publish unverified promises about a 370 m bungee, wingsuit experience, rappel height, CE equipment, guide certificates or minimum age.', 'The final activity list is written only after operator, leader/guide records, equipment, communication, insurance, evacuation and guest ability are confirmed.'], }, 'culture-outdoor-mix': { - title: 'Culture Plus Outdoor Mix', - subtitle: 'Villages, craft, rivers, landscapes, and local hosts', - nights: '5-9 days | Villages, rivers, landscapes, and craft', - duration: '5-9 days', + title: 'Balanced Discovery Route', + subtitle: 'Village stays, terrace walks, heritage craft, and gentle outdoors', + nights: '8 days | Guiyang, Southeast Guizhou villages, Libo / Qiannan, and a mountain pause', + duration: '8 days', price: 'Private quote', theme: 'Ethnic culture', pace: 'Moderate pace', fit: 'Inbound small groups', intensity: 'Adjusted to guests', - service: 'Cultural context plus gentle outdoor days', + service: 'Private small group / local bookings plus gentle outdoors', season: 'Year-round; festival dates need early confirmation', category: 'Niche', focus: 'Culture', - summary: 'A balanced route combining Miao and Dong villages, craft, river valleys, scenery, and gentle outdoor moments.', - routeLine: 'Guiyang - Southeast Guizhou villages - Libo / Qiannan valleys - Mountain experience - Guiyang', - decisionTags: ['Cultural depth', 'Gentle outdoor', 'Village stay', 'Flexible rhythm'], - bestFor: ['Families, friends, and inbound small groups', 'Travelers who want craft, music, meals, and local conversation', 'Guests who prefer outdoor days without going too hard'], + summary: 'A breathable route combining slow Miao or Dong village stays, heritage craft, terrace walking, river landscapes and one gentle outdoor day. Each day holds one core booking, with the rest adjusted around the guest.', + routeLine: 'Guiyang - Southeast Guizhou village stay - Terraces and craft - Libo / Qiannan gentle outdoors - Mountain pause - Guiyang', + decisionTags: ['Multi-night village stay', 'Walk with local people', 'Culture + gentle outdoors', 'Space every day'], + bestFor: ['Families and friends who want village depth without giving up nature', 'Inbound guests drawn to craft, food, music and local conversation', 'Travelers who want one main commitment a day and room to adjust'], highlights: [ - { title: 'Intangible-heritage craft', copy: 'Miao embroidery, batik, Dong grand song, or silver craft can be booked around host availability.', imageKey: 'experienceCraft' }, - { title: 'Village stay', copy: 'Keep time for meals, night views, and slow walks instead of treating villages as photo stops.', imageKey: 'destinationXijiang' }, - { title: 'Gentle outdoor entry', copy: 'Add river walks, light rafting, entry-level caves, or mountain coffee where the route fits.', imageKey: 'routeOutdoor' }, - { title: 'Translation and context', copy: 'Inbound guests benefit from cultural background, etiquette notes, and live interpretation.', imageKey: 'menuCulture' }, + { title: 'Slow village homestay experience with a local host', copy: 'Keep the evening, morning, home meal and community rhythm in the journey; the exact stay is checked for current reception conditions.', imageKey: 'clientRouteBalanced' }, + { title: 'Guided rice-terrace walk with a local farmer', copy: 'Walk with someone who knows the land and seasons; the route is reconfirmed around weather and the farming calendar.', imageKey: 'clientMountainPause' }, + { title: 'A flexible daily pace', copy: 'Beyond one core booking, stay times and light activities can flex around opening hours, safety and guest preference.', imageKey: 'clientHeritageHands' }, ], itinerary: [ - { day: 'Day 1-2', title: 'Guiyang to Southeast Guizhou', place: 'Guiyang / Southeast Guizhou', copy: 'Shift from the city into villages with food stops, short breaks, and cultural context along the way.', meta: ['City to village', 'Cultural intro', 'Slower pace'] }, - { day: 'Day 3-4', title: 'Miao and Dong villages plus craft', place: 'Xijiang / Zhaoxing / nearby villages', copy: 'Book embroidery, batik, Dong grand song, or hosted village meals with enough interaction and translation time.', meta: ['Craft', 'Hosted meal', 'Village night'] }, - { day: 'Day 5-7', title: 'Libo or Qiannan gentle outdoor', place: 'Libo / Qiannan', copy: 'Choose water forest, river trails, light rafting, or entry cave days without overloading the route.', meta: ['Water forest', 'Gentle outdoor', 'Photography'] }, - { day: 'Day 8-9', title: 'Mountain coffee or Guiyang finish', place: 'Mountain coffee / Guiyang', copy: 'Close with wild coffee, city food, or easy shopping before departure.', meta: ['Wild coffee', 'City food', 'Departure'] }, + { day: 'Day 1', title: 'Arrive in Guiyang and align preferences', place: 'Guiyang', copy: 'Start with cultural interests, walking habits, food boundaries and desired free time before fixing the village-to-outdoor balance.', meta: ['Pickup', 'Preference check', 'Low intensity'], schedule: { morning: 'Arrive by flight or train; early arrivals can leave luggage or check in.', afternoon: 'Private pickup, city-hotel check-in and a conversation about route priorities.', evening: 'An approachable Guizhou dinner introduces sour-spicy flavors, village etiquette and the rhythm ahead.', drive: 'Primarily the airport or rail transfer into Guiyang.', meals: 'Meals depend on arrival time; spice, allergy and dietary needs are discussed first.', stay: 'Guiyang city hotel.' } }, + { day: 'Day 2', title: 'Enter Southeast Guizhou and settle into village life', place: 'Guiyang → Southeast Guizhou village', copy: 'Move from the city into Miao or Dong country, settling in before any viewpoint or performance.', meta: ['Regional transfer', 'Village check-in', 'Meet the host'], schedule: { morning: 'Leave after breakfast with lunch and comfort stops on the way.', afternoon: 'Arrive at the confirmed Miao or Dong village, meet the host and settle into the stay.', evening: 'Walk around the drum tower, wind-and-rain bridge or village paths; a hosted meal depends on availability.', drive: 'Transfer time is written into the final plan after the village, hotel and roads are fixed.', meals: 'Hotel breakfast; lunch en route; optional home-style dinner with the host.', stay: 'Confirmed village guesthouse or local homestay in Southeast Guizhou.' } }, + { day: 'Day 3', title: 'Terraces and village life with a local farmer', place: 'Southeast Guizhou village', copy: 'Walk with someone who knows the land rather than taking a terrace photo from one viewpoint.', meta: ['Terrace walk', 'Local farmer', 'Second village night'], schedule: { morning: 'Walk terraces or village paths with a local farmer according to weather, season and ability.', afternoon: 'Keep time open for rest, a farming moment, market stop or conversation with the host.', evening: 'Stay a second night; listen around the drum tower, watch community life or rest quietly.', drive: 'Mostly walking and short village transfers.', meals: 'Breakfast at the stay; lunch follows the walk; local home-style dinner preferred.', stay: 'Second night in the same village.' } }, + { day: 'Day 4', title: 'One heritage workshop and a free half-day', place: 'Village / nearby workshop', copy: 'Spend time in a real working studio for batik, embroidery or indigo rather than compressing several crafts into a production line.', meta: ['Heritage booking', 'English cultural context', 'Free half-day'], schedule: { morning: 'Book one craft suited to the guest’s interest and patience with a local maker or heritage practitioner.', afternoon: 'Choose more studio time, a village walk, coffee/tea or complete rest.', evening: 'Review the day and confirm the natural section against weather and energy.', drive: 'Short transfer around the village or workshop; no regional rush.', meals: 'Breakfast at the stay; lunch and dinner follow the workshop and village rhythm.', stay: 'A third village night or a nearby stay aligned to Day 5.' } }, + { day: 'Day 5', title: 'Shift from village culture into Libo / Qiannan nature', place: 'Southeast Guizhou → Libo / Qiannan', copy: 'Use one measured transfer to connect the cultural and natural chapters without adding a demanding activity.', meta: ['Regional shift', 'Nature introduction', 'Light stop'], schedule: { morning: 'Say goodbye to the host and leave after breakfast for the confirmed southern nature base.', afternoon: 'Arrive and keep only a gentle forest, river or viewpoint stop.', evening: 'Check in and review the next day gentle-outdoor choice and weather alternative.', drive: 'Regional time is rechecked after the stay and activity base are selected.', meals: 'Breakfast at the stay; lunch en route; dinner near the hotel.', stay: 'Libo or a Qiannan nature base.' } }, + { day: 'Day 6', title: 'One gentle outdoor experience, with room to breathe', place: 'Libo / Qiannan', copy: 'Choose one water forest, river trail, calm-water or intro-cave experience instead of trying to complete them all.', meta: ['Gentle outdoors', 'Weather backup', 'Photography space'], schedule: { morning: 'Choose the single core nature activity around weather, crowds and energy.', afternoon: 'Continue slowly, photograph or return early; switch to a village, market or indoor craft if conditions fail.', evening: 'Warm food and recovery, with no second activity added for volume.', drive: 'Mostly scenic shuttles and short activity transfers.', meals: 'Hotel breakfast; practical lunch around the activity; dinner near the stay.', stay: 'Second night in Libo / Qiannan.' } }, + { day: 'Day 7', title: 'Mountain pause and return to Guiyang', place: 'Libo / Qiannan → Mountain pause → Guiyang', copy: 'Close the nature chapter with coffee, a short walk or a quiet view, then return to the city with a departure buffer.', meta: ['Mountain coffee', 'Light recovery', 'Departure buffer'], schedule: { morning: 'Leave after a slow breakfast and choose a convenient mountain coffee or viewpoint if it is operating.', afternoon: 'Return to Guiyang with comfort stops and no distant attraction added.', evening: 'A final Guiyang meal, from market snacks to a quieter restaurant.', drive: 'Planned around the confirmed stay and mountain stop; no unverified fixed coffee point is promised.', meals: 'Hotel breakfast; lunch en route; dinner in Guiyang.', stay: 'Guiyang city hotel.' } }, + { day: 'Day 8', title: 'Depart from Guiyang', place: 'Guiyang', copy: 'Shape the day around the flight or train and keep the last hours unhurried.', meta: ['Departure transfer', 'Free time', 'Departure'], schedule: { morning: 'Breakfast and packing; a market or short city walk is possible for a late departure.', afternoon: 'Check out and take a private airport or rail transfer.', evening: 'For a late departure, meals and a rest point are confirmed in the final plan.', drive: 'Primarily the Guiyang city transfer to airport or rail station.', meals: 'Hotel breakfast; later meals depend on departure time.', stay: 'None; an extra night is quoted separately.' } }, ], - inclusions: ['Private vehicle and guide', 'Cultural experience booking advice', 'Village etiquette briefing', 'Hotel and meal planning', 'Gentle outdoor backup plan'], - exclusions: ['Personal craft purchases', 'Extra deep-course fees', 'Travel insurance', 'Meals not listed', 'Single supplement'], - notes: ['Festivals and host availability may affect sequence.', 'At least one slower overnight stay is recommended.'], + inclusions: ['Custom route design and pre-trip preference planning', 'English cultural interpretation or English trip support stated in the final plan', 'Private vehicle and core dispatch', 'Hotels and daily breakfast listed in the final quote', 'Village host, terrace walk or heritage workshop bookings listed in the final itinerary', 'Gentle-outdoor weather alternatives and 24-hour on-ground coordination during the journey'], + exclusions: ['International or domestic transport to Guizhou and visas', 'Lunches, dinners and hosted meals not listed in the final quote', 'Personal craft, material, souvenir and shipping purchases', 'Festival, extended course or newly added experience fees', 'Travel insurance, single supplement, tips and extra costs caused by weather or personal changes unless otherwise stated'], + notes: ['Flexible does not mean unbooked: hosts, farmers, heritage makers, festivals and stays are confirmed first; stay times can flex when opening hours and safety allow.', 'Village accommodation is checked property by property for hygiene, bathroom setup, luggage access, current foreign-guest reception and real imagery.', 'Village, workshop and farming moments change with season, community life and host availability; everyday life is never promised as a staged performance.'], }, 'family-heritage': { title: 'Family Heritage Slow Travel', @@ -207,16 +215,31 @@ export const safariEnglish = { export const experienceEnglish = { 'cave-expedition': { title: 'Cave Expedition', meta: 'Suiyang / Shuanghe Cave / Twelve Back', type: 'Outdoor', copy: 'Cave routes are matched to helmets, headlamps, local guides, water level, and guest ability.' }, - rafting: { title: 'Rafting and Canyon Water', meta: 'Libo / Qiandongnan / Qiannan', type: 'Outdoor', copy: 'A light summer water activity that pairs well with villages, picnics, or family routes.' }, - 'stream-tracing': { title: 'Stream Tracing and Rappelling', meta: 'Anshun / Canyon valleys', type: 'Outdoor', copy: 'A controlled outdoor experience built around leaders, rope systems, backups, and weather checks.' }, + 'cave-luxury-dining': { title: 'Cave Coffee & Private Dining', meta: 'Named hospitality-ready cave / date-specific venue', type: 'Lifestyle', copy: 'Coffee, private dining, and a light cave walk are validated separately around venue, fire safety, food hygiene, lighting, power, transfers, and exit.' }, + rafting: { title: 'Canyon River Tracing & White Water Rafting', meta: 'Libo karst canyons / Qiannan forest streams', type: 'Outdoor', copy: 'Choose one mode—calm water, active tracing, or validated white water—matched to hydrology, guest ability, operator, equipment, insurance, and exit.' }, + 'stream-tracing': { title: 'Waterfall Abseiling & Canyoning', meta: 'Guizhou waterfall regions / exact site to be confirmed', type: 'Outdoor', copy: 'Operated only after the named operator, rope system, water, insurance, exit plan, and guest ability all pass.' }, + 'karst-hiking-cycling': { title: 'Karst Peak Forest Hiking & Cycling', meta: 'Wanfenglin / Maolan / Chishui River Valley candidates', type: 'Outdoor', copy: 'Choose one mode—gentle country riding, forest hiking, or an advanced distance ride—matched to route, distance, terrain, equipment, support, and exits.' }, 'miao-craft': { title: 'Miao Embroidery and Batik', meta: 'Qiandongnan / Danzhai / Anshun', type: 'Culture', copy: 'A hosted craft experience with interpretation, etiquette notes, and village context.' }, + 'dong-indigo-architecture': { title: 'Dong Indigo & Timber Architecture', meta: 'Qiandongnan Dong villages / named workshop and interpreter confirmed by date', type: 'Culture', copy: 'Pair Dong textile dyeing with a close look at drum-tower and wind-and-rain-bridge timber craft; confirm the workshop, interpreter, materials, and accessible spaces first.' }, + 'shui-horsetail-embroidery': { title: 'Shui Horsetail Embroidery', meta: 'Sandu Shui Autonomous County / named workshop and teacher confirmed by date', type: 'Culture', copy: 'Understand how silk is wrapped around horsehair to build the embroidery structure, then confirm the teacher, duration, materials, and photography consent for a hands-on session.' }, + 'buyi-traditional-crafts': { title: 'Buyi Traditional Textile Crafts', meta: 'Qianxinan or Anshun candidate / named village and workshop to be verified', type: 'Culture', copy: 'Choose one locally grounded theme—Buyi weaving, dress, or dyeing—without combining techniques from different places into a generic workshop.' }, 'dong-song': { title: 'Dong Grand Song and Drum-Tower Villages', meta: 'Zhaoxing / Dong villages', type: 'Culture', copy: 'A slower day around Dong grand song, drum-tower architecture, village etiquette, and hosted meals.' }, huangguoshu: { title: 'Huangguoshu Waterfall Timing', meta: 'Anshun / Huangguoshu 5A Scenic Area', type: 'Landscape', copy: 'A waterfall day designed around water volume, crowds, light, photo points, and nearby recovery stays.' }, 'libo-xiaoqikong': { title: 'Libo Zhangjiang and Xiaoqikong Water Forest', meta: 'Qiannan / Libo', type: 'Landscape', copy: 'A nature anchor of karst water, forest paths, bridges, waterfalls, and family-friendly pacing.' }, 'fanjing-mountain': { title: 'Fanjing Mountain Weather Window', meta: 'Tongren / World Natural Heritage', type: 'Landscape', copy: 'A mountain day shaped by cloud, forest, rocks, ticket windows, and early-start logistics.' }, 'xijiang-miao-village': { title: 'Xijiang and Dong Village Nights', meta: 'Qiandongnan', type: 'Culture', copy: 'Wooden houses, terrace views, music, craft, meals, and photography work best as a slow village stay.' }, wanfenglin: { title: 'Wanfenglin Fields and Coffee', meta: 'Xingyi / Qianxinan / 4A Scenic Area', type: 'Landscape', copy: 'Peak forests, cycling, village meals, Buyi culture, photography, and verified mountain coffee can fit into one day.' }, - 'wild-coffee': { title: 'Wild Coffee Stops', meta: 'Mountain coffee and village tea breaks matched to the route', type: 'Lifestyle', copy: 'Add verified coffee or tea stops to outdoor, photography, or self-drive style routes.' }, + 'wild-coffee': { title: 'Cliffside & Mountain-View Coffee', meta: 'Named scenic café / private stop candidate', type: 'Lifestyle', copy: 'Prefer a named café with established paths, edge protection, and guest facilities; private outdoor brewing requires site authority, weather, fire, waste, and exit checks.' }, + 'village-immersion': { title: 'Authentic Village Homestays', meta: 'Miao / Dong / Buyi village candidates / named stay verified by date', type: 'Culture', copy: 'Choose one Miao stilt-house, Dong drum-tower, or Buyi stone-village direction, then verify the host, room, hygiene, fire, registration, activities, and community boundaries for a half-day, one night, or two nights.' }, + 'ethnic-festivals': { title: 'Ethnic Festivals & Customs', meta: 'Miao New Year / Sisters’ Meal / Dong festivals / Shui Duan / annual verification', type: 'Culture', copy: 'Separate the traditional lunar festival, the current public programme, and private community rites; dates, venues, events, transport, photography, and stays follow current official and community notices.' }, + 'culinary-cultural-journeys': { title: 'Culinary Cultural Journeys', meta: 'Home cooking / long-table banquet / local market', type: 'Culture', copy: 'Choose one experience with a named host, accountable food venue, or genuinely operating market; confirm menu, sourcing, hygiene, allergens, spice, and alcohol etiquette item by item.' }, + 'iconic-waterfall-clusters': { title: 'Iconic Waterfall Clusters', meta: 'Huangguoshu / Dishuitan / Chishui Danxia / live access check', type: 'Landscape', copy: 'Treat a developed scenic area, a multi-tier valley waterfall, and a Danxia waterfall as three different visits; confirm access, ticketing, water, walking, transfers, and safety by date.' }, + 'indigenous-ethnic-villages': { title: 'Indigenous Ethnic Villages', meta: 'Xijiang / Zhaoxing & Huanggang / Basha / Anshun Buyi stone villages', type: 'Culture', copy: 'Do not rank villages by “authenticity.” Compare a developed scenic area, community festival, public performance, and named-host visit, then verify the exact village, stay, transport, and photography by date.' }, + 'historic-ancient-towns': { title: 'Historic Towns & Villages', meta: 'Qingyan / Zhenyuan / Tianlong Tunbao', type: 'Culture', copy: 'Read a Ming stone town, a Wuyang River trading city, and an Anshun Tunbao village as three distinct places. Confirm tickets, performance, night lighting, stays, and resident boundaries by date.' }, + 'qingyan-battle-immersive-roleplay': { title: 'Qingyan 1935: Immersive History', meta: 'Zhan Qingyan operating / private international format under review', type: 'Culture', copy: 'Public reporting confirms the Zhan Qingyan field experience beside Dingguang Lake. We separate that live product from the brief’s old-town roleplay concept, verifying dates, price, age, English, and private-group delivery.' }, + 'loushanguan-battle-immersive-march': { title: 'Loushan Pass: Battle & Immersive March', meta: 'Live participatory show / battlefield walk / scripted march under development', type: 'Culture', copy: 'Separate the live “Follow the Commander” battle experience, a heritage-site walk, and the brief’s proposed 4–5-hour mountain narrative. Sessions, route, English, fitness, and effects safety are verified by date.' }, + 'karst-glamping': { title: 'Karst Glamping: Starry Sky Camping', meta: 'Named operating camp / mountain-view stay / private setup candidate', type: 'Lifestyle', copy: 'Prefer a named operator with fixed pitches, bathrooms, and night support; stars, campfire, and sinkhole or riverside setups remain subject to weather, fire, site authority, and evacuation checks.' }, + 'heritage-outdoor-pairings': { title: 'Heritage & Gentle Outdoor Pairings', meta: 'Danzhai / Anshun / Meitan or Pu’an candidates / one pairing per day', type: 'Culture', copy: 'Choose one: terrace walking and batik, plant observation and traditional papermaking, or tea-country walking or cycling and tea craft. Route, host, season, materials, and safety are confirmed item by item.' }, 'hot-spring-recovery': { title: 'Hot Spring Recovery Day', meta: 'Jianhe / Huangguoshu Bolian', type: 'Wellness', copy: 'A soft landing after mountain, cave, or water activities, useful for recovery and slower pacing.' }, } as const; @@ -233,13 +256,13 @@ export const foodEnglish = { } as const; export const transportEnglish = { - 'vehicle-5': { title: '5-seat Sedan / SUV', seats: '5-seat', vehicle: 'Sedan / SUV', bestFor: '1-3 guests, airport transfers, Guiyang city, and light short trips', luggage: '2-3 pieces of luggage', copy: 'A flexible base vehicle for couples, small families, or business guests, useful for city hotels, ancient towns, and scenic parking points.' }, - 'vehicle-7': { title: '7-seat MPV', seats: '7-seat', vehicle: 'Business MPV', bestFor: '3-5 guests, family small groups, English-speaking guide in vehicle', luggage: '4-5 pieces of luggage', copy: 'One of the most common private Guizhou vehicles, balancing comfort, luggage space, and mountain-road transfers.' }, - 'vehicle-9': { title: '9-seat Van', seats: '9-seat', vehicle: 'Business van', bestFor: '5-7 guests, photography groups, small trade inspections', luggage: '6-7 pieces of luggage', copy: 'More seat and luggage room than an MPV, useful for multi-prefecture routes or guests with equipment.' }, - 'vehicle-14': { title: '14-seat Minibus', seats: '14-seat', vehicle: 'Minibus', bestFor: '8-11 guests, study groups, or outdoor teams', luggage: 'Assessed by route', copy: 'A light team vehicle covering common Guiyang, Anshun, Qiannan, and Qiandongnan routes with road-limit checks.' }, - 'vehicle-19': { title: '17-19-seat Coach', seats: '17-19-seat', vehicle: 'Mid-size coach', bestFor: '12-15 guests, company groups, trade scouting', luggage: 'Checked by trunk and supplies', copy: 'A mid-size team option with more space, suitable for leaders, English interpretation, and multi-day logistics.' }, + 'vehicle-5': { title: '5-seat Sedan / SUV', seats: '5-seat', vehicle: 'Sedan / SUV', bestFor: '1-3 guests, airport transfers, Guiyang city, and light short trips', luggage: 'Usually 2-3 pieces; dimensions checked', copy: 'A comfortable base vehicle for couples, small families, or light travelers; the exact passenger-and-luggage combination is confirmed before dispatch.' }, + 'vehicle-7': { title: '7-seat MPV', seats: '7-seat', vehicle: 'Business MPV', bestFor: '3-5 guests, families, and multi-day mountain transfers', luggage: 'Confirmed against the seating layout', copy: 'A common private Guizhou option balancing comfort and easy access; English support is confirmed separately with the trip team.' }, + 'vehicle-9': { title: '9-seat Van', seats: '9-seat', vehicle: 'Light commercial van', bestFor: '5-7 guests, photography groups, and equipment-heavy routes', luggage: 'Confirmed against the actual luggage bay', copy: 'More seat flexibility than an MPV for cross-prefecture routes; rated seats are not the same as recommended guest capacity.' }, + 'vehicle-14': { title: '14-seat Minibus', seats: '14-seat', vehicle: 'Minibus', bestFor: '8-11 guests, family or outdoor small groups', luggage: 'Assessed by route and actual vehicle', copy: 'For larger private groups, subject to operating credentials, luggage space, and scenic-road restrictions.' }, + 'vehicle-19': { title: '17-19-seat Coach', seats: '17-19-seat', vehicle: 'Mid-size coach', bestFor: '12-15 guests, friends, family, or travel-trade groups', luggage: 'Checked by trunk and supplies', copy: 'A roomier group option that can include a tour leader, English travel support, and multi-day logistics.' }, 'vehicle-30': { title: '22-30-seat Bus', seats: '22-30-seat', vehicle: 'Team bus', bestFor: '16-24 guests, standard groups, cross-prefecture routes', luggage: 'Team luggage plan', copy: 'A standard group vehicle; routes should avoid restricted roads and pre-plan drop-off and meal movement.' }, - 'vehicle-39': { title: '33-39-seat Coach', seats: '33-39-seat', vehicle: 'Large coach', bestFor: '25-34 guests, MICE, school groups', luggage: 'Team luggage plan', copy: 'For larger groups that need clear meeting points, meals, restrooms, scenic parking, and backup dispatch.' }, + 'vehicle-39': { title: '33-39-seat Coach', seats: '33-39-seat', vehicle: 'Large coach', bestFor: '25-34 guests, larger family, friends, or travel-trade groups', luggage: 'Team luggage plan', copy: 'For larger groups that need clear meeting points, meals, restrooms, scenic parking, and backup dispatch.' }, 'vehicle-50': { title: '45-50-seat Coach', seats: '45-50-seat', vehicle: 'Large team coach', bestFor: '35-45 guests, large groups, trade batch reception', luggage: 'Team luggage plan', copy: 'For large-group reception, with advance checks on mountain roads, scenic parking, hotel spaces, and split-vehicle plans.' }, } as const; diff --git a/src/siteContent.ts b/src/siteContent.ts index 27d12a1..107291f 100644 --- a/src/siteContent.ts +++ b/src/siteContent.ts @@ -61,6 +61,7 @@ export type SiteExperienceBand = { id: string; kicker: SiteLocalizedText; title: SiteLocalizedText; + copy?: SiteLocalizedText; tone: 'tan' | 'clay' | 'charcoal'; imageUrl: string; href: string; @@ -74,6 +75,7 @@ export type SiteRouteCard = { imageUrl: string; href: string; copy: SiteLocalizedText; + highlights?: SiteLocalizedText[]; }; export type SiteAboutSection = {