diff --git a/public/client-assets/food/bijie/kung-pao-chicken/01.webp b/public/client-assets/food/bijie/kung-pao-chicken/01.webp new file mode 100644 index 0000000..7e39340 Binary files /dev/null and b/public/client-assets/food/bijie/kung-pao-chicken/01.webp differ diff --git a/public/client-assets/food/bijie/kung-pao-chicken/02.webp b/public/client-assets/food/bijie/kung-pao-chicken/02.webp new file mode 100644 index 0000000..b40421e Binary files /dev/null and b/public/client-assets/food/bijie/kung-pao-chicken/02.webp differ diff --git a/public/client-assets/food/bijie/kung-pao-chicken/03.webp b/public/client-assets/food/bijie/kung-pao-chicken/03.webp new file mode 100644 index 0000000..c467122 Binary files /dev/null and b/public/client-assets/food/bijie/kung-pao-chicken/03.webp differ diff --git a/public/client-assets/food/bijie/tofu-hotpot/01.webp b/public/client-assets/food/bijie/tofu-hotpot/01.webp new file mode 100644 index 0000000..0803862 Binary files /dev/null and b/public/client-assets/food/bijie/tofu-hotpot/01.webp differ diff --git a/public/client-assets/food/bijie/tofu-hotpot/02.webp b/public/client-assets/food/bijie/tofu-hotpot/02.webp new file mode 100644 index 0000000..41c73d4 Binary files /dev/null and b/public/client-assets/food/bijie/tofu-hotpot/02.webp differ diff --git a/public/client-assets/food/bijie/tofu-hotpot/03.webp b/public/client-assets/food/bijie/tofu-hotpot/03.webp new file mode 100644 index 0000000..5d5f576 Binary files /dev/null and b/public/client-assets/food/bijie/tofu-hotpot/03.webp differ diff --git a/public/client-assets/food/guiyang/changwang-noodles/01.webp b/public/client-assets/food/guiyang/changwang-noodles/01.webp new file mode 100644 index 0000000..6430ac1 Binary files /dev/null and b/public/client-assets/food/guiyang/changwang-noodles/01.webp differ diff --git a/public/client-assets/food/guiyang/glutinous-rice/01.webp b/public/client-assets/food/guiyang/glutinous-rice/01.webp new file mode 100644 index 0000000..39798f5 Binary files /dev/null and b/public/client-assets/food/guiyang/glutinous-rice/01.webp differ diff --git a/public/client-assets/food/guiyang/huaxi-beef-noodles/01.webp b/public/client-assets/food/guiyang/huaxi-beef-noodles/01.webp new file mode 100644 index 0000000..39fbfe9 Binary files /dev/null and b/public/client-assets/food/guiyang/huaxi-beef-noodles/01.webp differ diff --git a/public/client-assets/food/guiyang/plain-rice-noodles/01.webp b/public/client-assets/food/guiyang/plain-rice-noodles/01.webp new file mode 100644 index 0000000..878070e Binary files /dev/null and b/public/client-assets/food/guiyang/plain-rice-noodles/01.webp differ diff --git a/public/client-assets/food/guiyang/siwawa/01.webp b/public/client-assets/food/guiyang/siwawa/01.webp new file mode 100644 index 0000000..795ccd9 Binary files /dev/null and b/public/client-assets/food/guiyang/siwawa/01.webp differ diff --git a/public/client-assets/food/qiandongnan/five-color-rice/01.webp b/public/client-assets/food/qiandongnan/five-color-rice/01.webp new file mode 100644 index 0000000..78cf2c1 Binary files /dev/null and b/public/client-assets/food/qiandongnan/five-color-rice/01.webp differ diff --git a/public/client-assets/food/qiandongnan/five-color-rice/02.webp b/public/client-assets/food/qiandongnan/five-color-rice/02.webp new file mode 100644 index 0000000..1189526 Binary files /dev/null and b/public/client-assets/food/qiandongnan/five-color-rice/02.webp differ diff --git a/public/client-assets/food/qiandongnan/five-color-rice/03.webp b/public/client-assets/food/qiandongnan/five-color-rice/03.webp new file mode 100644 index 0000000..9e66b89 Binary files /dev/null and b/public/client-assets/food/qiandongnan/five-color-rice/03.webp differ diff --git a/public/client-assets/food/qiandongnan/five-color-rice/04.webp b/public/client-assets/food/qiandongnan/five-color-rice/04.webp new file mode 100644 index 0000000..d07b8a2 Binary files /dev/null and b/public/client-assets/food/qiandongnan/five-color-rice/04.webp differ diff --git a/public/client-assets/food/qiandongnan/niubie-hotpot/01.webp b/public/client-assets/food/qiandongnan/niubie-hotpot/01.webp new file mode 100644 index 0000000..b6965cc Binary files /dev/null and b/public/client-assets/food/qiandongnan/niubie-hotpot/01.webp differ diff --git a/public/client-assets/food/qiandongnan/niubie-hotpot/02.webp b/public/client-assets/food/qiandongnan/niubie-hotpot/02.webp new file mode 100644 index 0000000..878078d Binary files /dev/null and b/public/client-assets/food/qiandongnan/niubie-hotpot/02.webp differ diff --git a/public/client-assets/food/qiandongnan/niubie-hotpot/03.webp b/public/client-assets/food/qiandongnan/niubie-hotpot/03.webp new file mode 100644 index 0000000..c434d5a Binary files /dev/null and b/public/client-assets/food/qiandongnan/niubie-hotpot/03.webp differ diff --git a/public/client-assets/food/qiandongnan/niubie-hotpot/04.webp b/public/client-assets/food/qiandongnan/niubie-hotpot/04.webp new file mode 100644 index 0000000..de6a209 Binary files /dev/null and b/public/client-assets/food/qiandongnan/niubie-hotpot/04.webp differ diff --git a/public/client-assets/food/qiandongnan/preserved-fish-meat/01.webp b/public/client-assets/food/qiandongnan/preserved-fish-meat/01.webp new file mode 100644 index 0000000..f6edfa1 Binary files /dev/null and b/public/client-assets/food/qiandongnan/preserved-fish-meat/01.webp differ diff --git a/public/client-assets/food/qiandongnan/preserved-fish-meat/02.webp b/public/client-assets/food/qiandongnan/preserved-fish-meat/02.webp new file mode 100644 index 0000000..d68ed59 Binary files /dev/null and b/public/client-assets/food/qiandongnan/preserved-fish-meat/02.webp differ diff --git a/public/client-assets/food/qiandongnan/preserved-fish-meat/03.webp b/public/client-assets/food/qiandongnan/preserved-fish-meat/03.webp new file mode 100644 index 0000000..dba73ac Binary files /dev/null and b/public/client-assets/food/qiandongnan/preserved-fish-meat/03.webp differ diff --git a/public/client-assets/food/qiandongnan/roast-fragrant-pig/01.webp b/public/client-assets/food/qiandongnan/roast-fragrant-pig/01.webp new file mode 100644 index 0000000..77526b2 Binary files /dev/null and b/public/client-assets/food/qiandongnan/roast-fragrant-pig/01.webp differ diff --git a/public/client-assets/food/qiandongnan/roast-fragrant-pig/02.webp b/public/client-assets/food/qiandongnan/roast-fragrant-pig/02.webp new file mode 100644 index 0000000..f0cc759 Binary files /dev/null and b/public/client-assets/food/qiandongnan/roast-fragrant-pig/02.webp differ diff --git a/public/client-assets/food/qiandongnan/roast-fragrant-pig/03.webp b/public/client-assets/food/qiandongnan/roast-fragrant-pig/03.webp new file mode 100644 index 0000000..657bf54 Binary files /dev/null and b/public/client-assets/food/qiandongnan/roast-fragrant-pig/03.webp differ diff --git a/public/client-assets/food/qiandongnan/roast-fragrant-pig/04.webp b/public/client-assets/food/qiandongnan/roast-fragrant-pig/04.webp new file mode 100644 index 0000000..b1b2843 Binary files /dev/null and b/public/client-assets/food/qiandongnan/roast-fragrant-pig/04.webp differ diff --git a/public/client-assets/food/qiandongnan/sour-soup-fish/01.webp b/public/client-assets/food/qiandongnan/sour-soup-fish/01.webp new file mode 100644 index 0000000..b5658b8 Binary files /dev/null and b/public/client-assets/food/qiandongnan/sour-soup-fish/01.webp differ diff --git a/public/client-assets/food/qiandongnan/sour-soup-fish/02.webp b/public/client-assets/food/qiandongnan/sour-soup-fish/02.webp new file mode 100644 index 0000000..cb3d525 Binary files /dev/null and b/public/client-assets/food/qiandongnan/sour-soup-fish/02.webp differ diff --git a/public/client-assets/food/qiannan/pingtang-jar-fish/01.webp b/public/client-assets/food/qiannan/pingtang-jar-fish/01.webp new file mode 100644 index 0000000..e7d6548 Binary files /dev/null and b/public/client-assets/food/qiannan/pingtang-jar-fish/01.webp differ diff --git a/public/client-assets/food/qiannan/pingtang-jar-fish/02.webp b/public/client-assets/food/qiannan/pingtang-jar-fish/02.webp new file mode 100644 index 0000000..5c52ab0 Binary files /dev/null and b/public/client-assets/food/qiannan/pingtang-jar-fish/02.webp differ diff --git a/public/client-assets/food/qiannan/pingtang-jar-fish/03.webp b/public/client-assets/food/qiannan/pingtang-jar-fish/03.webp new file mode 100644 index 0000000..167a9b3 Binary files /dev/null and b/public/client-assets/food/qiannan/pingtang-jar-fish/03.webp differ diff --git a/public/client-assets/food/qiannan/salted-vegetable-pork/01.webp b/public/client-assets/food/qiannan/salted-vegetable-pork/01.webp new file mode 100644 index 0000000..ed899f6 Binary files /dev/null and b/public/client-assets/food/qiannan/salted-vegetable-pork/01.webp differ diff --git a/public/client-assets/food/qiannan/salted-vegetable-pork/02.webp b/public/client-assets/food/qiannan/salted-vegetable-pork/02.webp new file mode 100644 index 0000000..df8ef1c Binary files /dev/null and b/public/client-assets/food/qiannan/salted-vegetable-pork/02.webp differ diff --git a/public/client-assets/food/qiannan/salted-vegetable-pork/03.webp b/public/client-assets/food/qiannan/salted-vegetable-pork/03.webp new file mode 100644 index 0000000..3ce560d Binary files /dev/null and b/public/client-assets/food/qiannan/salted-vegetable-pork/03.webp differ diff --git a/public/client-assets/food/qiannan/shrimp-sour-beef/01.webp b/public/client-assets/food/qiannan/shrimp-sour-beef/01.webp new file mode 100644 index 0000000..c47d2cf Binary files /dev/null and b/public/client-assets/food/qiannan/shrimp-sour-beef/01.webp differ diff --git a/public/client-assets/food/qiannan/shrimp-sour-beef/02.webp b/public/client-assets/food/qiannan/shrimp-sour-beef/02.webp new file mode 100644 index 0000000..d198774 Binary files /dev/null and b/public/client-assets/food/qiannan/shrimp-sour-beef/02.webp differ diff --git a/public/client-assets/food/qiannan/shrimp-sour-beef/03.webp b/public/client-assets/food/qiannan/shrimp-sour-beef/03.webp new file mode 100644 index 0000000..5cec329 Binary files /dev/null and b/public/client-assets/food/qiannan/shrimp-sour-beef/03.webp differ diff --git a/public/client-assets/food/qianxinan/buyi-eight-bowls/01.webp b/public/client-assets/food/qianxinan/buyi-eight-bowls/01.webp new file mode 100644 index 0000000..692a220 Binary files /dev/null and b/public/client-assets/food/qianxinan/buyi-eight-bowls/01.webp differ diff --git a/public/client-assets/food/qianxinan/buyi-eight-bowls/02.webp b/public/client-assets/food/qianxinan/buyi-eight-bowls/02.webp new file mode 100644 index 0000000..d953590 Binary files /dev/null and b/public/client-assets/food/qianxinan/buyi-eight-bowls/02.webp differ diff --git a/public/client-assets/food/qianxinan/shuabato/01.webp b/public/client-assets/food/qianxinan/shuabato/01.webp new file mode 100644 index 0000000..87e4fb2 Binary files /dev/null and b/public/client-assets/food/qianxinan/shuabato/01.webp differ diff --git a/public/client-assets/food/qianxinan/street-hotpot/01.webp b/public/client-assets/food/qianxinan/street-hotpot/01.webp new file mode 100644 index 0000000..a2cf912 Binary files /dev/null and b/public/client-assets/food/qianxinan/street-hotpot/01.webp differ diff --git a/public/client-assets/food/qianxinan/street-hotpot/02.webp b/public/client-assets/food/qianxinan/street-hotpot/02.webp new file mode 100644 index 0000000..13ec124 Binary files /dev/null and b/public/client-assets/food/qianxinan/street-hotpot/02.webp differ diff --git a/public/client-assets/food/qianxinan/street-hotpot/03.webp b/public/client-assets/food/qianxinan/street-hotpot/03.webp new file mode 100644 index 0000000..7e8ddd7 Binary files /dev/null and b/public/client-assets/food/qianxinan/street-hotpot/03.webp differ diff --git a/public/client-assets/food/qianxinan/wanfenglin-fried-rice/01.webp b/public/client-assets/food/qianxinan/wanfenglin-fried-rice/01.webp new file mode 100644 index 0000000..7445279 Binary files /dev/null and b/public/client-assets/food/qianxinan/wanfenglin-fried-rice/01.webp differ diff --git a/public/client-assets/food/qianxinan/wanfenglin-fried-rice/02.webp b/public/client-assets/food/qianxinan/wanfenglin-fried-rice/02.webp new file mode 100644 index 0000000..abf59c9 Binary files /dev/null and b/public/client-assets/food/qianxinan/wanfenglin-fried-rice/02.webp differ diff --git a/public/client-assets/food/qianxinan/wanfenglin-fried-rice/03.webp b/public/client-assets/food/qianxinan/wanfenglin-fried-rice/03.webp new file mode 100644 index 0000000..7e9142b Binary files /dev/null and b/public/client-assets/food/qianxinan/wanfenglin-fried-rice/03.webp differ diff --git a/public/client-assets/hotels/fangxia-yunju/01.webp b/public/client-assets/hotels/fangxia-yunju/01.webp new file mode 100644 index 0000000..57741b1 Binary files /dev/null and b/public/client-assets/hotels/fangxia-yunju/01.webp differ diff --git a/public/client-assets/hotels/fangxia-yunju/02.webp b/public/client-assets/hotels/fangxia-yunju/02.webp new file mode 100644 index 0000000..0d80ed1 Binary files /dev/null and b/public/client-assets/hotels/fangxia-yunju/02.webp differ diff --git a/public/client-assets/hotels/fangxia-yunju/03.webp b/public/client-assets/hotels/fangxia-yunju/03.webp new file mode 100644 index 0000000..a9c07f1 Binary files /dev/null and b/public/client-assets/hotels/fangxia-yunju/03.webp differ diff --git a/public/client-assets/hotels/fangxia-yunju/04.webp b/public/client-assets/hotels/fangxia-yunju/04.webp new file mode 100644 index 0000000..963c5dd Binary files /dev/null and b/public/client-assets/hotels/fangxia-yunju/04.webp differ diff --git a/public/client-assets/hotels/fangxia-yunju/05.webp b/public/client-assets/hotels/fangxia-yunju/05.webp new file mode 100644 index 0000000..23faa93 Binary files /dev/null and b/public/client-assets/hotels/fangxia-yunju/05.webp differ diff --git a/public/client-assets/hotels/fengxi-banshan/01.webp b/public/client-assets/hotels/fengxi-banshan/01.webp new file mode 100644 index 0000000..de81cc9 Binary files /dev/null and b/public/client-assets/hotels/fengxi-banshan/01.webp differ diff --git a/public/client-assets/hotels/fengxi-banshan/02.webp b/public/client-assets/hotels/fengxi-banshan/02.webp new file mode 100644 index 0000000..b2105a8 Binary files /dev/null and b/public/client-assets/hotels/fengxi-banshan/02.webp differ diff --git a/public/client-assets/hotels/fengxi-banshan/03.webp b/public/client-assets/hotels/fengxi-banshan/03.webp new file mode 100644 index 0000000..52710c7 Binary files /dev/null and b/public/client-assets/hotels/fengxi-banshan/03.webp differ diff --git a/public/client-assets/hotels/fengxi-banshan/04.webp b/public/client-assets/hotels/fengxi-banshan/04.webp new file mode 100644 index 0000000..2fedb62 Binary files /dev/null and b/public/client-assets/hotels/fengxi-banshan/04.webp differ diff --git a/public/client-assets/hotels/fengxi-banshan/05.webp b/public/client-assets/hotels/fengxi-banshan/05.webp new file mode 100644 index 0000000..5ba90e7 Binary files /dev/null and b/public/client-assets/hotels/fengxi-banshan/05.webp differ diff --git a/public/client-assets/hotels/fengxi-banshan/06.webp b/public/client-assets/hotels/fengxi-banshan/06.webp new file mode 100644 index 0000000..648896a Binary files /dev/null and b/public/client-assets/hotels/fengxi-banshan/06.webp differ diff --git a/public/client-assets/hotels/fengxi-banshan/07.webp b/public/client-assets/hotels/fengxi-banshan/07.webp new file mode 100644 index 0000000..fb82cfe Binary files /dev/null and b/public/client-assets/hotels/fengxi-banshan/07.webp differ diff --git a/public/client-assets/hotels/fengxi-cave-luxury/01.webp b/public/client-assets/hotels/fengxi-cave-luxury/01.webp new file mode 100644 index 0000000..1c5b321 Binary files /dev/null and b/public/client-assets/hotels/fengxi-cave-luxury/01.webp differ diff --git a/public/client-assets/hotels/fengxi-cave-luxury/02.webp b/public/client-assets/hotels/fengxi-cave-luxury/02.webp new file mode 100644 index 0000000..7c013a9 Binary files /dev/null and b/public/client-assets/hotels/fengxi-cave-luxury/02.webp differ diff --git a/public/client-assets/hotels/fengxi-cave-luxury/03.webp b/public/client-assets/hotels/fengxi-cave-luxury/03.webp new file mode 100644 index 0000000..d124ff5 Binary files /dev/null and b/public/client-assets/hotels/fengxi-cave-luxury/03.webp differ diff --git a/public/client-assets/hotels/fengxi-cave-luxury/04.webp b/public/client-assets/hotels/fengxi-cave-luxury/04.webp new file mode 100644 index 0000000..d71f20a Binary files /dev/null and b/public/client-assets/hotels/fengxi-cave-luxury/04.webp differ diff --git a/public/client-assets/hotels/fengxi-cave-luxury/05.webp b/public/client-assets/hotels/fengxi-cave-luxury/05.webp new file mode 100644 index 0000000..9c7908e Binary files /dev/null and b/public/client-assets/hotels/fengxi-cave-luxury/05.webp differ diff --git a/public/client-assets/hotels/fengxi-cave-luxury/06.webp b/public/client-assets/hotels/fengxi-cave-luxury/06.webp new file mode 100644 index 0000000..7408b69 Binary files /dev/null and b/public/client-assets/hotels/fengxi-cave-luxury/06.webp differ diff --git a/public/client-assets/hotels/moon-mountain-dali/01.webp b/public/client-assets/hotels/moon-mountain-dali/01.webp new file mode 100644 index 0000000..a29cdb6 Binary files /dev/null and b/public/client-assets/hotels/moon-mountain-dali/01.webp differ diff --git a/public/client-assets/hotels/moon-mountain-dali/02.webp b/public/client-assets/hotels/moon-mountain-dali/02.webp new file mode 100644 index 0000000..72f56cd Binary files /dev/null and b/public/client-assets/hotels/moon-mountain-dali/02.webp differ diff --git a/public/client-assets/hotels/moon-mountain-dali/03.webp b/public/client-assets/hotels/moon-mountain-dali/03.webp new file mode 100644 index 0000000..95ddf1a Binary files /dev/null and b/public/client-assets/hotels/moon-mountain-dali/03.webp differ diff --git a/public/client-assets/hotels/moon-mountain-dali/04.webp b/public/client-assets/hotels/moon-mountain-dali/04.webp new file mode 100644 index 0000000..3422945 Binary files /dev/null and b/public/client-assets/hotels/moon-mountain-dali/04.webp differ diff --git a/public/client-assets/hotels/moon-mountain-dali/05.webp b/public/client-assets/hotels/moon-mountain-dali/05.webp new file mode 100644 index 0000000..eb03d22 Binary files /dev/null and b/public/client-assets/hotels/moon-mountain-dali/05.webp differ diff --git a/public/client-assets/hotels/moon-mountain-dali/06.webp b/public/client-assets/hotels/moon-mountain-dali/06.webp new file mode 100644 index 0000000..085dbd7 Binary files /dev/null and b/public/client-assets/hotels/moon-mountain-dali/06.webp differ diff --git a/public/client-assets/hotels/moon-mountain-dali/07.webp b/public/client-assets/hotels/moon-mountain-dali/07.webp new file mode 100644 index 0000000..7cc52df Binary files /dev/null and b/public/client-assets/hotels/moon-mountain-dali/07.webp differ diff --git a/public/client-assets/hotels/moon-mountain-dali/08.webp b/public/client-assets/hotels/moon-mountain-dali/08.webp new file mode 100644 index 0000000..53e7feb Binary files /dev/null and b/public/client-assets/hotels/moon-mountain-dali/08.webp differ diff --git a/public/client-assets/hotels/puyu-wild-luxury/01.webp b/public/client-assets/hotels/puyu-wild-luxury/01.webp new file mode 100644 index 0000000..6dd0b1a Binary files /dev/null and b/public/client-assets/hotels/puyu-wild-luxury/01.webp differ diff --git a/public/client-assets/hotels/puyu-wild-luxury/02.webp b/public/client-assets/hotels/puyu-wild-luxury/02.webp new file mode 100644 index 0000000..3aa27cf Binary files /dev/null and b/public/client-assets/hotels/puyu-wild-luxury/02.webp differ diff --git a/public/client-assets/hotels/puyu-wild-luxury/03.webp b/public/client-assets/hotels/puyu-wild-luxury/03.webp new file mode 100644 index 0000000..7bfab24 Binary files /dev/null and b/public/client-assets/hotels/puyu-wild-luxury/03.webp differ diff --git a/public/client-assets/hotels/puyu-wild-luxury/04.webp b/public/client-assets/hotels/puyu-wild-luxury/04.webp new file mode 100644 index 0000000..db7f725 Binary files /dev/null and b/public/client-assets/hotels/puyu-wild-luxury/04.webp differ diff --git a/public/client-assets/hotels/puyu-wild-luxury/05.webp b/public/client-assets/hotels/puyu-wild-luxury/05.webp new file mode 100644 index 0000000..f39e4c0 Binary files /dev/null and b/public/client-assets/hotels/puyu-wild-luxury/05.webp differ diff --git a/public/client-assets/hotels/puyu-wild-luxury/06.webp b/public/client-assets/hotels/puyu-wild-luxury/06.webp new file mode 100644 index 0000000..35f8d67 Binary files /dev/null and b/public/client-assets/hotels/puyu-wild-luxury/06.webp differ diff --git a/public/client-assets/hotels/taijiang-shicang-laiyin/01.webp b/public/client-assets/hotels/taijiang-shicang-laiyin/01.webp new file mode 100644 index 0000000..e992964 Binary files /dev/null and b/public/client-assets/hotels/taijiang-shicang-laiyin/01.webp differ diff --git a/public/client-assets/hotels/taijiang-shicang-laiyin/02.webp b/public/client-assets/hotels/taijiang-shicang-laiyin/02.webp new file mode 100644 index 0000000..cefa08d Binary files /dev/null and b/public/client-assets/hotels/taijiang-shicang-laiyin/02.webp differ diff --git a/public/client-assets/hotels/taijiang-shicang-laiyin/03.webp b/public/client-assets/hotels/taijiang-shicang-laiyin/03.webp new file mode 100644 index 0000000..495b64b Binary files /dev/null and b/public/client-assets/hotels/taijiang-shicang-laiyin/03.webp differ diff --git a/public/client-assets/hotels/taijiang-shicang-laiyin/04.webp b/public/client-assets/hotels/taijiang-shicang-laiyin/04.webp new file mode 100644 index 0000000..3c906a6 Binary files /dev/null and b/public/client-assets/hotels/taijiang-shicang-laiyin/04.webp differ diff --git a/public/client-assets/hotels/xijiang-haoli-villa/01.webp b/public/client-assets/hotels/xijiang-haoli-villa/01.webp new file mode 100644 index 0000000..446fc2c Binary files /dev/null and b/public/client-assets/hotels/xijiang-haoli-villa/01.webp differ diff --git a/public/client-assets/hotels/xijiang-haoli-villa/02.webp b/public/client-assets/hotels/xijiang-haoli-villa/02.webp new file mode 100644 index 0000000..899e2ac Binary files /dev/null and b/public/client-assets/hotels/xijiang-haoli-villa/02.webp differ diff --git a/public/client-assets/hotels/xijiang-haoli-villa/03.webp b/public/client-assets/hotels/xijiang-haoli-villa/03.webp new file mode 100644 index 0000000..21e4557 Binary files /dev/null and b/public/client-assets/hotels/xijiang-haoli-villa/03.webp differ diff --git a/public/client-assets/hotels/xijiang-haoli-villa/04.webp b/public/client-assets/hotels/xijiang-haoli-villa/04.webp new file mode 100644 index 0000000..bf2e08e Binary files /dev/null and b/public/client-assets/hotels/xijiang-haoli-villa/04.webp differ diff --git a/public/client-assets/hotels/xijiang-haoli-villa/05.webp b/public/client-assets/hotels/xijiang-haoli-villa/05.webp new file mode 100644 index 0000000..e44de2a Binary files /dev/null and b/public/client-assets/hotels/xijiang-haoli-villa/05.webp differ diff --git a/public/client-assets/hotels/xijiang-haoli-villa/06.webp b/public/client-assets/hotels/xijiang-haoli-villa/06.webp new file mode 100644 index 0000000..9cccac6 Binary files /dev/null and b/public/client-assets/hotels/xijiang-haoli-villa/06.webp differ diff --git a/public/client-assets/hotels/xijiang-jieshan-yunting/01.webp b/public/client-assets/hotels/xijiang-jieshan-yunting/01.webp new file mode 100644 index 0000000..c61499d Binary files /dev/null and b/public/client-assets/hotels/xijiang-jieshan-yunting/01.webp differ diff --git a/public/client-assets/hotels/xijiang-jieshan-yunting/02.webp b/public/client-assets/hotels/xijiang-jieshan-yunting/02.webp new file mode 100644 index 0000000..434fbdf Binary files /dev/null and b/public/client-assets/hotels/xijiang-jieshan-yunting/02.webp differ diff --git a/public/client-assets/hotels/xijiang-jieshan-yunting/03.webp b/public/client-assets/hotels/xijiang-jieshan-yunting/03.webp new file mode 100644 index 0000000..029fa79 Binary files /dev/null and b/public/client-assets/hotels/xijiang-jieshan-yunting/03.webp differ diff --git a/public/client-assets/hotels/xijiang-jieshan-yunting/04.webp b/public/client-assets/hotels/xijiang-jieshan-yunting/04.webp new file mode 100644 index 0000000..f1a662d Binary files /dev/null and b/public/client-assets/hotels/xijiang-jieshan-yunting/04.webp differ diff --git a/public/client-assets/hotels/xijiang-jieshan-yunting/05.webp b/public/client-assets/hotels/xijiang-jieshan-yunting/05.webp new file mode 100644 index 0000000..83f9471 Binary files /dev/null and b/public/client-assets/hotels/xijiang-jieshan-yunting/05.webp differ diff --git a/public/client-assets/hotels/xijiang-jieshan-yunting/06.webp b/public/client-assets/hotels/xijiang-jieshan-yunting/06.webp new file mode 100644 index 0000000..858a23c Binary files /dev/null and b/public/client-assets/hotels/xijiang-jieshan-yunting/06.webp differ diff --git a/public/client-assets/hotels/xijiang-jieshan-yunting/07.webp b/public/client-assets/hotels/xijiang-jieshan-yunting/07.webp new file mode 100644 index 0000000..4ac01da Binary files /dev/null and b/public/client-assets/hotels/xijiang-jieshan-yunting/07.webp differ diff --git a/public/client-assets/hotels/xijiang-jieshan-yunting/08.webp b/public/client-assets/hotels/xijiang-jieshan-yunting/08.webp new file mode 100644 index 0000000..9ec24b5 Binary files /dev/null and b/public/client-assets/hotels/xijiang-jieshan-yunting/08.webp differ diff --git a/public/client-assets/hotels/xijiang-jieshan-yunting/09.webp b/public/client-assets/hotels/xijiang-jieshan-yunting/09.webp new file mode 100644 index 0000000..6dd38ed Binary files /dev/null and b/public/client-assets/hotels/xijiang-jieshan-yunting/09.webp differ diff --git a/public/client-assets/hotels/xijiang-sanchunli-meng/01.webp b/public/client-assets/hotels/xijiang-sanchunli-meng/01.webp new file mode 100644 index 0000000..11aed4d Binary files /dev/null and b/public/client-assets/hotels/xijiang-sanchunli-meng/01.webp differ diff --git a/public/client-assets/hotels/xijiang-sanchunli-meng/02.webp b/public/client-assets/hotels/xijiang-sanchunli-meng/02.webp new file mode 100644 index 0000000..6d1c88e Binary files /dev/null and b/public/client-assets/hotels/xijiang-sanchunli-meng/02.webp differ diff --git a/public/client-assets/hotels/xijiang-sanchunli-meng/03.webp b/public/client-assets/hotels/xijiang-sanchunli-meng/03.webp new file mode 100644 index 0000000..b60c2f3 Binary files /dev/null and b/public/client-assets/hotels/xijiang-sanchunli-meng/03.webp differ diff --git a/public/client-assets/hotels/xijiang-sanchunli-meng/04.webp b/public/client-assets/hotels/xijiang-sanchunli-meng/04.webp new file mode 100644 index 0000000..5b87085 Binary files /dev/null and b/public/client-assets/hotels/xijiang-sanchunli-meng/04.webp differ diff --git a/public/client-assets/hotels/xijiang-seven-letters/01.webp b/public/client-assets/hotels/xijiang-seven-letters/01.webp new file mode 100644 index 0000000..fa16fdd Binary files /dev/null and b/public/client-assets/hotels/xijiang-seven-letters/01.webp differ diff --git a/public/client-assets/hotels/xijiang-seven-letters/02.webp b/public/client-assets/hotels/xijiang-seven-letters/02.webp new file mode 100644 index 0000000..696a473 Binary files /dev/null and b/public/client-assets/hotels/xijiang-seven-letters/02.webp differ diff --git a/public/client-assets/hotels/xijiang-seven-letters/03.webp b/public/client-assets/hotels/xijiang-seven-letters/03.webp new file mode 100644 index 0000000..6f3131a Binary files /dev/null and b/public/client-assets/hotels/xijiang-seven-letters/03.webp differ diff --git a/public/client-assets/hotels/xijiang-xingzhe-shangju/01.webp b/public/client-assets/hotels/xijiang-xingzhe-shangju/01.webp new file mode 100644 index 0000000..2ecd336 Binary files /dev/null and b/public/client-assets/hotels/xijiang-xingzhe-shangju/01.webp differ diff --git a/public/client-assets/hotels/xijiang-xingzhe-shangju/02.webp b/public/client-assets/hotels/xijiang-xingzhe-shangju/02.webp new file mode 100644 index 0000000..4599c39 Binary files /dev/null and b/public/client-assets/hotels/xijiang-xingzhe-shangju/02.webp differ diff --git a/public/client-assets/hotels/xijiang-xingzhe-shangju/03.webp b/public/client-assets/hotels/xijiang-xingzhe-shangju/03.webp new file mode 100644 index 0000000..cc84a41 Binary files /dev/null and b/public/client-assets/hotels/xijiang-xingzhe-shangju/03.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/01.webp b/public/client-assets/hotels/xingyi-mushroom/01.webp new file mode 100644 index 0000000..ad6dfba Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/01.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/02.webp b/public/client-assets/hotels/xingyi-mushroom/02.webp new file mode 100644 index 0000000..c32c2b2 Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/02.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/03.webp b/public/client-assets/hotels/xingyi-mushroom/03.webp new file mode 100644 index 0000000..cefc002 Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/03.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/04.webp b/public/client-assets/hotels/xingyi-mushroom/04.webp new file mode 100644 index 0000000..c0aa585 Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/04.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/05.webp b/public/client-assets/hotels/xingyi-mushroom/05.webp new file mode 100644 index 0000000..12a4c41 Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/05.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/06.webp b/public/client-assets/hotels/xingyi-mushroom/06.webp new file mode 100644 index 0000000..529cd39 Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/06.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/07.webp b/public/client-assets/hotels/xingyi-mushroom/07.webp new file mode 100644 index 0000000..a4c8bf0 Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/07.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/08.webp b/public/client-assets/hotels/xingyi-mushroom/08.webp new file mode 100644 index 0000000..cb2afb7 Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/08.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/09.webp b/public/client-assets/hotels/xingyi-mushroom/09.webp new file mode 100644 index 0000000..442338c Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/09.webp differ diff --git a/public/client-assets/hotels/xingyi-mushroom/10.webp b/public/client-assets/hotels/xingyi-mushroom/10.webp new file mode 100644 index 0000000..6b5621e Binary files /dev/null and b/public/client-assets/hotels/xingyi-mushroom/10.webp differ diff --git a/public/client-assets/hotels/xingyi-yuntun-starry/01.webp b/public/client-assets/hotels/xingyi-yuntun-starry/01.webp new file mode 100644 index 0000000..78ddcf2 Binary files /dev/null and b/public/client-assets/hotels/xingyi-yuntun-starry/01.webp differ diff --git a/public/client-assets/hotels/xingyi-yuntun-starry/02.webp b/public/client-assets/hotels/xingyi-yuntun-starry/02.webp new file mode 100644 index 0000000..8345cae Binary files /dev/null and b/public/client-assets/hotels/xingyi-yuntun-starry/02.webp differ diff --git a/public/client-assets/hotels/xingyi-yuntun-starry/03.webp b/public/client-assets/hotels/xingyi-yuntun-starry/03.webp new file mode 100644 index 0000000..8a84979 Binary files /dev/null and b/public/client-assets/hotels/xingyi-yuntun-starry/03.webp differ diff --git a/public/client-assets/hotels/xingyi-yuntun-starry/04.webp b/public/client-assets/hotels/xingyi-yuntun-starry/04.webp new file mode 100644 index 0000000..ca46301 Binary files /dev/null and b/public/client-assets/hotels/xingyi-yuntun-starry/04.webp differ diff --git a/public/client-assets/hotels/xingyi-yuntun-starry/05.webp b/public/client-assets/hotels/xingyi-yuntun-starry/05.webp new file mode 100644 index 0000000..e3b5d27 Binary files /dev/null and b/public/client-assets/hotels/xingyi-yuntun-starry/05.webp differ diff --git a/src/TravelSite.css b/src/TravelSite.css index 1d77c1c..9c157e1 100644 --- a/src/TravelSite.css +++ b/src/TravelSite.css @@ -21,6 +21,380 @@ font-family: 'Assistant', Arial, sans-serif; } +/* 2026-08 client accommodation and regional-food supplement */ +.oc-hotel-candidate { + grid-template-rows: auto auto auto auto minmax(0, 1fr) auto auto; +} + +.oc-hotel-candidate > a.oc-hotel-candidate-image { + display: block; + width: 100%; + margin: 24px 0 0; + overflow: hidden; + aspect-ratio: 4 / 3; + background: #e6e9df; +} + +.oc-hotel-candidate-image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 500ms ease; +} + +.oc-hotel-candidate-image:hover img { + transform: scale(1.025); +} + +.oc-hotel-group:nth-child(3) .oc-hotel-candidate-grid { + grid-template-columns: minmax(0, 0.62fr); +} + +.oc-hotel-group:nth-child(4) .oc-hotel-candidate-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.oc-food-region-detail { + min-height: 100vh; + overflow: hidden; + background: #f4f5ed; + color: var(--oc-ink); +} + +.oc-food-region-detail > .oc-breadcrumb { + position: relative; + z-index: 2; + padding-top: 116px; +} + +.oc-food-region-hero { + display: grid; + grid-template-columns: minmax(320px, 0.42fr) minmax(0, 0.58fr); + gap: clamp(48px, 7vw, 112px); + align-items: center; + padding-top: 70px; + padding-bottom: 126px; +} + +.oc-food-region-hero-copy > span, +.oc-food-dishes > header > span, +.oc-food-planning-notes span { + color: var(--oc-clay-dark); + font-size: 10px; + font-weight: 800; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.oc-food-region-hero h1, +.oc-food-dishes h2, +.oc-food-dish h3, +.oc-food-planning-notes h2 { + font-family: Georgia, 'Times New Roman', serif; + font-weight: 400; + letter-spacing: -0.035em; +} + +.oc-food-region-hero h1 { + margin-top: 18px; + font-size: clamp(54px, 7vw, 104px); + line-height: 0.95; +} + +.oc-food-region-hero-copy > p { + max-width: 660px; + margin-top: 28px; + color: var(--oc-muted); + font-size: 16px; + line-height: 1.82; +} + +.oc-food-region-hero-copy > div { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 28px; +} + +.oc-food-region-hero-copy b { + padding: 8px 11px; + border: 1px solid rgba(56, 86, 62, 0.2); + color: #52634f; + font-size: 11px; +} + +.oc-food-region-cover { + position: relative; + display: block; + overflow: hidden; + width: 100%; + padding: 0; + border: 0; + border-radius: 2px; + aspect-ratio: 4 / 3; + background: #dfe4d8; + cursor: zoom-in; +} + +.oc-food-region-cover img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.oc-food-region-cover span { + position: absolute; + right: 18px; + bottom: 18px; + padding: 10px 13px; + background: rgba(32, 51, 40, 0.9); + color: #fff; + font-size: 11px; + font-weight: 800; +} + +.oc-food-dishes { + padding-bottom: 128px; +} + +.oc-food-dishes > header { + display: grid; + grid-template-columns: minmax(0, 0.52fr) minmax(320px, 0.48fr); + column-gap: clamp(48px, 7vw, 112px); + align-items: end; + padding-bottom: 48px; + border-bottom: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-food-dishes > header > span { + grid-column: 1 / -1; + margin-bottom: 18px; +} + +.oc-food-dishes h2 { + max-width: 760px; + font-size: clamp(42px, 5vw, 72px); + line-height: 1.02; +} + +.oc-food-dishes > header p { + color: var(--oc-muted); + font-size: 14px; + line-height: 1.75; +} + +.oc-food-dish-stack { + border-bottom: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-food-dish { + display: grid; + grid-template-columns: minmax(280px, 0.34fr) minmax(0, 0.66fr); + gap: clamp(40px, 6vw, 92px); + align-items: center; + min-height: 520px; + padding: 58px 0; + border-bottom: 1px solid rgba(36, 51, 40, 0.16); +} + +.oc-food-dish:last-child { + border-bottom: 0; +} + +.oc-food-dish.is-text-only { + grid-template-columns: minmax(280px, 0.58fr); + min-height: 0; + padding: 48px 0; +} + +.oc-food-dish-copy > span { + color: rgba(63, 86, 66, 0.45); + font-size: 11px; + font-weight: 800; +} + +.oc-food-dish h3 { + margin-top: 24px; + font-size: clamp(32px, 3.5vw, 52px); + line-height: 1.05; +} + +.oc-food-dish-copy p { + margin-top: 22px; + color: var(--oc-muted); + font-size: 15px; + line-height: 1.78; +} + +.oc-food-dish-copy small { + display: block; + margin-top: 20px; + color: #839083; + font-size: 11px; +} + +.oc-food-dish-gallery { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + min-height: 420px; +} + +.oc-food-dish-gallery.has-1 { + grid-template-columns: 1fr; +} + +.oc-food-dish-gallery.has-3 button:first-child { + grid-row: span 2; +} + +.oc-food-dish-gallery button { + overflow: hidden; + min-height: 200px; + padding: 0; + border: 0; + background: #dfe4d8; + cursor: zoom-in; +} + +.oc-food-dish-gallery img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 450ms ease; +} + +.oc-food-dish-gallery button:hover img { + transform: scale(1.025); +} + +.oc-food-planning-notes { + padding: 112px 0; + background: #203328; + color: #f4f5ed; +} + +.oc-food-planning-notes .oc-container-wide { + display: grid; + grid-template-columns: minmax(300px, 0.42fr) minmax(0, 0.58fr); + gap: clamp(48px, 7vw, 112px); +} + +.oc-food-planning-notes span { + color: #9abf92; +} + +.oc-food-planning-notes h2 { + max-width: 700px; + margin-top: 18px; + color: #f4f5ed; + font-size: clamp(38px, 4.3vw, 62px); + line-height: 1.05; +} + +.oc-food-planning-notes ul { + margin: 0; + padding: 0; + border-top: 1px solid rgba(220, 233, 216, 0.2); + list-style: none; +} + +.oc-food-planning-notes li { + padding: 24px 0; + border-bottom: 1px solid rgba(220, 233, 216, 0.2); + color: rgba(239, 245, 235, 0.75); + font-size: 14px; + line-height: 1.65; +} + +@media (max-width: 980px) { + .oc-hotel-group:nth-child(2) .oc-hotel-candidate-grid, + .oc-hotel-group:nth-child(4) .oc-hotel-candidate-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .oc-food-region-hero, + .oc-food-dishes > header, + .oc-food-planning-notes .oc-container-wide { + grid-template-columns: 1fr; + } + + .oc-food-region-cover { + max-height: 620px; + } + + .oc-food-dishes > header { + gap: 24px; + } + + .oc-food-dish { + grid-template-columns: minmax(240px, 0.38fr) minmax(0, 0.62fr); + gap: 36px; + } +} + +@media (max-width: 700px) { + .oc-hotel-group:nth-child(3) .oc-hotel-candidate-grid, + .oc-hotel-group:nth-child(4) .oc-hotel-candidate-grid { + grid-template-columns: 1fr; + } + + .oc-food-region-detail > .oc-breadcrumb { + padding-top: 92px; + } + + .oc-food-region-hero { + gap: 38px; + padding-top: 42px; + padding-bottom: 84px; + } + + .oc-food-region-hero h1 { + font-size: clamp(48px, 16vw, 72px); + } + + .oc-food-region-hero-copy > p { + font-size: 14px; + } + + .oc-food-dishes { + padding-bottom: 84px; + } + + .oc-food-dishes h2 { + font-size: 42px; + } + + .oc-food-dish, + .oc-food-dish.is-text-only { + grid-template-columns: 1fr; + gap: 30px; + min-height: 0; + padding: 42px 0; + } + + .oc-food-dish-gallery { + min-height: 0; + } + + .oc-food-dish-gallery button { + min-height: 150px; + aspect-ratio: 1 / 1; + } + + .oc-food-dish-gallery.has-1 button { + aspect-ratio: 4 / 3; + } + + .oc-food-planning-notes { + padding: 82px 0; + } + + .oc-food-planning-notes h2 { + font-size: 40px; + } +} + /* Underground karst experience detail */ .oc-cave-detail { --oc-cave-ink: #172019; @@ -7265,7 +7639,7 @@ html[data-oc-language='zh'] .oc-home-safari::after { .oc-hotel-candidate { display: grid; - grid-template-rows: auto auto auto minmax(0, 1fr) auto auto; + grid-template-rows: auto 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); diff --git a/src/TravelSite.tsx b/src/TravelSite.tsx index 4aaf697..0c6fde3 100644 --- a/src/TravelSite.tsx +++ b/src/TravelSite.tsx @@ -34,6 +34,14 @@ import { hotelEnglish, safariEnglish, } from './guizhouEnContent'; +import { + supplementFoodById, + supplementFoodRegions, + supplementHotelById, + supplementHotelGroups, + supplementHotels, + type SupplementFoodRegion, +} from './guizhouSupplementContent'; import { pickText, translateUi, type SiteLanguage } from './i18n'; import { createInquiry } from './api'; import type { CmsContent, Destination, DetailContentBlock, Product, ProductRoutePlan } from './cms'; @@ -85,6 +93,41 @@ function enabledSiteLinks(links: SiteLink[]) { return links.filter((linkItem) => linkItem.enabled !== false); } +function normalizeSearchTerm(value: string) { + return value.toLocaleLowerCase().replace(/[\s\-_–—||·]+/g, ' ').trim(); +} + +function customerPageMetadata(path: string, language: SiteLanguage) { + const brand = language === 'zh' ? '万趣旅行' : 'WanderQ Travel'; + if (path === '/hotels') { + return { + title: language === 'zh' ? `精选酒店|${brand}` : `Guizhou Hotels | ${brand}`, + description: language === 'zh' + ? '从稀缺景观、村寨文化、田园山野与主题亲子四个方向,精选贵州特色住宿候选。' + : 'A shortlist of distinctive Guizhou stays across landscape, village culture, pastoral settings, and family themes.', + }; + } + if (path.startsWith('/hotels/')) { + const slug = path.replace('/hotels/', '').split('/')[0]; + const hotel = supplementHotels.find((hotelItem) => hotelItem.id === slug || hotelItem.legacyAliases?.includes(slug)); + if (hotel) return { title: `${hotel.title[language]} | ${brand}`, description: hotel.copy[language] }; + } + if (path === '/food') { + return { + title: language === 'zh' ? `贵州美食|${brand}` : `Guizhou Food | ${brand}`, + description: language === 'zh' + ? '按贵州地市州整理酸汤、米粉、烙锅、村寨家宴等代表味道。' + : 'Explore Guizhou flavors by city and prefecture, from sour soup and rice noodles to village meals.', + }; + } + if (path.startsWith('/food/')) { + const slug = path.replace('/food/', '').split('/')[0].replace(/^food-/, ''); + const food = supplementFoodById[slug]; + if (food) return { title: `${food.city[language]} | ${brand}`, description: food.intro[language] }; + } + return null; +} + function resolveMegaMenu(content: SiteContent, navId: string): SiteMegaMenu | undefined { return content.megaMenus.find((menu) => menu.navId === navId && menu.enabled !== false); } @@ -188,6 +231,14 @@ type PoiFact = { label: string; value: string }; type PoiHighlight = { title?: string; copy?: string; image?: string }; type PoiRelatedItem = { title: string; kicker: string; copy: string; image: string; href: string }; type GalleryLightboxItem = { source: string; alt: string; caption?: string }; + +const legacyHotelArchiveCards: ArchiveCardItem[] = [ + { title: '贵阳喜来登贵航酒店', meta: '贵阳 / 大南门', image: img.routeClassic, copy: '适合作为贵阳市中心抵达、会议、甲秀楼步行和首晚或末晚住宿的城市基地。', href: '/hotels/sheraton-guiyang-hotel/' }, + { title: '贵阳珍珀酒店', meta: '贵阳 / 观山湖', image: img.menuHotels, copy: '适合偏好安静城市基地、房间质感和新区交通便利性的客人。', href: '/hotels/pearl-gallery-hotel-guiyang/' }, + { title: '贵阳观山湖希尔顿欢朋酒店', meta: '贵阳 / 观山湖', image: img.hotelReference, copy: '适合路线过渡夜、商务抵达和稳定中高端舒适度的实用型酒店。', href: '/hotels/hampton-by-hilton-guiyang-guanshan-lake/' }, + { title: '黄果树柏联酒店', meta: '黄果树 / 关岭', image: img.hotelBolian, copy: '适合瀑布区域恢复夜、温泉节奏和更慢的高品质路线。', href: '/hotels/huangguoshu-bolian-resort/' }, + { title: '遵义乌江寨苗王酒店', meta: '遵义 / 乌江寨', image: img.heroXijiang, copy: '适合演艺、村寨建筑体验和度假区动线更清晰的文化度假住宿。', href: '/hotels/zunyi-wujiangzhai-miaowang/' }, +]; // Rendered view model generated from small CMS records; do not expose every field as a separate admin-controlled page-builder setting. type PoiDetailContent = { kind: PoiKind; @@ -282,6 +333,17 @@ function localizeSafariCard(safari: SafariCardItem, language: SiteLanguage): Saf function localizeFoodCard(card: FoodCardItem, language: SiteLanguage): FoodCardItem { if (language === 'zh') return card; + const supplement = supplementFoodById[card.id]; + if (supplement) { + return { + ...card, + city: supplement.region.en, + english: supplement.city.en, + region: supplement.region.en, + dishes: supplement.dishes.map((dishItem) => dishItem.title.en), + copy: supplement.intro.en, + }; + } const override = foodEnglish[card.id as keyof typeof foodEnglish]; return override ? { ...card, city: override.city, english: override.title, region: override.region, dishes: [...override.dishes], copy: override.copy } @@ -296,10 +358,14 @@ function localizeRelatedItem(item: PoiRelatedItem, language: SiteLanguage): PoiR if (language === 'zh') return item; const slug = slugFromHref(item.href); const foodSlug = slug.replace(/^food-/, ''); + const supplementHotel = supplementHotelById[slug]; + const supplementFood = supplementFoodById[foodSlug]; const experience = experienceEnglish[slug as keyof typeof experienceEnglish]; const hotel = hotelEnglish[slug as keyof typeof hotelEnglish]; const food = foodEnglish[foodSlug as keyof typeof foodEnglish]; + if (supplementHotel) return { ...item, title: supplementHotel.title.en, kicker: supplementHotel.location.en, copy: supplementHotel.copy.en }; + if (supplementFood) return { ...item, title: supplementFood.city.en, kicker: supplementFood.region.en, copy: supplementFood.intro.en }; if (experience) return { ...item, title: experience.title, kicker: experience.meta, copy: experience.copy }; if (hotel) return { ...item, title: hotel.title, kicker: hotel.meta, copy: hotel.copy }; if (food) return { ...item, title: food.title, kicker: food.region, copy: food.copy }; @@ -356,28 +422,29 @@ function localizePoiDetail(detail: PoiDetailContent, language: SiteLanguage, slu } if (detail.kind === 'hotel') { + const supplementHotel = supplementHotelById[slug]; const hotel = hotelEnglish[slug as keyof typeof hotelEnglish]; - const title = hotel?.title || translateUi(detail.title, language); + const title = supplementHotel?.title.en || hotel?.title || translateUi(detail.title, language); return { ...detail, title, - subtitle: hotel?.copy || translateUi(detail.subtitle, language), - heroMeta: hotel ? `${hotel.meta} · Stay` : translateUi(detail.heroMeta, language), + subtitle: supplementHotel?.copy.en || hotel?.copy || translateUi(detail.subtitle, language), + heroMeta: supplementHotel ? `${supplementHotel.location.en} · Stay` : hotel ? `${hotel.meta} · Stay` : translateUi(detail.heroMeta, language), actionLabel: 'Ask About This Hotel', overviewEyebrow: 'Guizhou Hotels', overviewTitle: 'Stay Quality, Location, and Route Fit', overview: [ - hotel?.copy || translateUi(detail.overview[0] || detail.subtitle, language), + supplementHotel?.copy.en || hotel?.copy || translateUi(detail.overview[0] || detail.subtitle, language), 'We compare stays by location, rest quality, breakfast, room type, communication needs, budget, and the next day transfer.', ], facts: [ - { label: 'Area', value: hotel?.meta || 'Guizhou' }, + { label: 'Area', value: supplementHotel?.location.en || hotel?.meta || 'Guizhou' }, { label: 'Best Use', value: 'Matched to route pace and comfort needs' }, { label: 'Quote', value: 'Confirmed by room type, date, and group size' }, ], locationTitle: 'Location and Connections', locationCopy: 'Hotel value depends on whether it reduces backtracking, improves rest, and connects smoothly with attractions, meals, and departure time.', - locationTags: [hotel?.meta || 'Guizhou', 'Route-matched stay', 'Backup options compared'], + locationTags: [supplementHotel?.location.en || hotel?.meta || 'Guizhou', 'Route-matched stay', 'Backup options compared'], bestForTitle: 'Best For', bestFor: ['Guests who care about rest quality and smooth transfers', 'Families, business guests, or inbound small groups', 'Routes where hotel location affects the next day'], highlights: baseHighlights('Location Value', 'We check whether the hotel area genuinely supports that night and the next transfer.'), @@ -391,30 +458,31 @@ function localizePoiDetail(detail: PoiDetailContent, language: SiteLanguage, slu } if (detail.kind === 'food') { + const supplementFood = supplementFoodById[slug]; const food = foodEnglish[slug as keyof typeof foodEnglish]; - const title = food?.title || translateUi(detail.title, language); - const dishes = food ? [...food.dishes] : []; + const title = supplementFood?.city.en || food?.title || translateUi(detail.title, language); + const dishes = supplementFood ? supplementFood.dishes.map((dishItem) => dishItem.title.en) : food ? [...food.dishes] : []; return { ...detail, title, - subtitle: food?.copy || translateUi(detail.subtitle, language), - heroMeta: food ? `${food.region} · Food stop` : translateUi(detail.heroMeta, language), + subtitle: supplementFood?.intro.en || food?.copy || translateUi(detail.subtitle, language), + heroMeta: supplementFood ? `${supplementFood.region.en} · Food stop` : food ? `${food.region} · Food stop` : translateUi(detail.heroMeta, language), actionLabel: 'Add This Food Stop', overviewEyebrow: 'Guizhou Food', overviewTitle: 'Local Flavor, Signature Dishes, and Taste Boundaries', overview: [ - food?.copy || translateUi(detail.overview[0] || detail.subtitle, language), + supplementFood?.intro.en || food?.copy || translateUi(detail.overview[0] || detail.subtitle, language), 'Guizhou meals should be matched to spice tolerance, sour-soup comfort, dietary needs, allergies, driving time, and the day rhythm.', ], facts: [ - { label: 'Area', value: food?.region || 'Guizhou' }, + { label: 'Area', value: supplementFood?.region.en || food?.region || 'Guizhou' }, { label: 'Signature', value: dishes.slice(0, 3).join(' / ') || 'Local food' }, { label: 'Best Use', value: 'Meal node matched to route timing' }, { label: 'Taste Check', value: 'Spice, sour flavors, allergies, and restrictions confirmed first' }, ], locationTitle: 'Meal Location and Day Rhythm', locationCopy: 'Food stops are planned with drive time, attraction timing, hotel location, and guest taste preferences instead of chasing a detour.', - locationTags: [food?.region || 'Guizhou', 'Food node', 'Taste check first'], + locationTags: [supplementFood?.region.en || food?.region || 'Guizhou', 'Food node', 'Taste check first'], bestForTitle: 'Best For', bestFor: ['Guests who want local flavor without avoidable surprises', 'Families or inbound groups with varied taste needs', 'Routes that treat food as part of the travel experience'], highlights: dishes.slice(0, 4).map((dish, index) => ({ @@ -607,7 +675,7 @@ function applyProductCmsDetailContent(detail: PoiDetailContent, product: Product title, subtitle: cmsText(product.subtitle, language) || summary || detail.subtitle, image, - gallery: uniqueImages(image, ...contentBlocks.map((block) => block.image), ...detail.gallery).slice(0, 5), + gallery: uniqueImages(image, ...contentBlocks.map((block) => block.image), ...detail.gallery).slice(0, 12), overviewTitle: title || detail.overviewTitle, overview: summary ? [summary, ...detail.overview.slice(1)] : detail.overview, highlights: contentBlocks.length > 0 ? contentBlocks : detail.highlights, @@ -1372,10 +1440,11 @@ function makeExperiencePoiDetail(card: ArchiveCardItem): PoiDetailContent { } function makeHotelPoiDetail(hotel: ArchiveCardItem): PoiDetailContent { - const related = poiRelatedFromArchive(collectiveArchiveCards, hotel.title, hotelHref); - const gallery = uniqueImages(hotel.image, img.menuHotels, img.hotelBolian, img.destinationXijiang, img.destinationWanfenglin).slice(0, 5); - const isWild = /wild|野奢|星空|梯田|独山|兴义|乌江寨|terraces|Dushan|Xingyi|Wujiangzhai/i.test(`${hotel.title} ${hotel.meta}`); const slug = archiveCardSlug(hotel); + const supplementHotel = supplementHotelById[slug]; + const related = poiRelatedFromArchive(collectiveArchiveCards, hotel.title, hotelHref); + const gallery = supplementHotel?.images || uniqueImages(hotel.image, img.menuHotels, img.hotelBolian, img.destinationXijiang, img.destinationWanfenglin).slice(0, 5); + const isWild = /wild|野奢|星空|梯田|独山|兴义|乌江寨|terraces|Dushan|Xingyi|Wujiangzhai/i.test(`${hotel.title} ${hotel.meta}`); const detail: PoiDetailContent = { kind: 'hotel', @@ -1406,12 +1475,18 @@ function makeHotelPoiDetail(hotel: ArchiveCardItem): PoiDetailContent { bestFor: isWild ? ['希望住宿本身成为体验的一部分', '想在山地、村寨或景观段放慢节奏', '重视照片、夜晚氛围和私密感的小团'] : ['重视舒适、卫生和交通效率的客人', '抵达首晚或返程前需要稳定酒店的人', '家庭、商务或境外小团的基础住宿需求'], - highlights: [ - { title: '位置价值', copy: '先看区域是否适合当晚需求,再判断房型、价格和服务细节。', image: hotel.image }, - { title: '休息质量', copy: '连续山路、户外和村寨行程后,酒店节奏比单纯星级更重要。', image: gallery[1] || hotel.image }, - { title: '服务沟通', copy: '境外客人可提前确认英文沟通、早餐、房型和行李安排。', image: gallery[2] || hotel.image }, - { title: '替代选择', copy: '同区域会保留可替代酒店,避免房态变化影响入住体验。', image: gallery[3] || hotel.image }, - ], + highlights: supplementHotel + ? gallery.slice(0, 6).map((image, index) => ({ + title: index === 0 ? '住宿空间与景观' : undefined, + copy: index === 0 ? supplementHotel.copy.zh : undefined, + image, + })) + : [ + { title: '位置价值', copy: '先看区域是否适合当晚需求,再判断房型、价格和服务细节。', image: hotel.image }, + { title: '休息质量', copy: '连续山路、户外和村寨行程后,酒店节奏比单纯星级更重要。', image: gallery[1] || hotel.image }, + { title: '服务沟通', copy: '境外客人可提前确认英文沟通、早餐、房型和行李安排。', image: gallery[2] || hotel.image }, + { title: '替代选择', copy: '同区域会保留可替代酒店,避免房态变化影响入住体验。', image: gallery[3] || hotel.image }, + ], planningTitle: '预订与入住提醒', planningNotes: [ '最终报价受房型、淡旺季、取消政策、是否含早和团队人数影响。', @@ -1424,7 +1499,7 @@ function makeHotelPoiDetail(hotel: ArchiveCardItem): PoiDetailContent { relatedItems: related, }; - return withPoiOverride(detail, slug, hotelDetailOverrides); + return supplementHotel ? detail : withPoiOverride(detail, slug, hotelDetailOverrides); } function makeFoodPoiDetail(food: FoodCardItem): PoiDetailContent { @@ -1533,6 +1608,20 @@ function TravelSite({ content, cmsContent }: { content?: SiteContent; cmsContent return () => observer.disconnect(); }, [language, path, menuOpen, searchOpen]); + useEffect(() => { + const metadata = customerPageMetadata(path, language); + const descriptionMeta = document.querySelector('meta[name="description"]'); + if (metadata) { + document.title = metadata.title; + descriptionMeta?.setAttribute('content', metadata.description); + } else { + document.title = language === 'zh' ? '万趣旅行|贵州秘境定制旅行' : 'WanderQ Travel | Private Journeys in Guizhou'; + descriptionMeta?.setAttribute('content', language === 'zh' + ? '万趣旅行提供中国·贵州秘境定制旅行、入境地接、英文司导、纯玩小包团、户外探险与民族人文体验。' + : 'WanderQ Travel creates private journeys in Guizhou with local guides, outdoor adventures, cultural experiences, and thoughtful stays.'); + } + }, [language, path]); + useEffect(() => { if (!window.location.hash) return undefined; let targetId = window.location.hash.slice(1); @@ -2352,10 +2441,20 @@ function SearchOverlay({ onClose }: { onClose: () => void }) { const searchRef = useRef(null); const returnFocusRef = useRef(null); const allSearchLinks = useMemo(() => { + const dishLinks: SiteLink[] = supplementFoodRegions.flatMap((foodRegion) => foodRegion.dishes.map((dishItem) => ({ + id: `search-food-${foodRegion.id}-${dishItem.id}`, + label: { + zh: `${dishItem.title.zh}|${foodRegion.city.zh}`, + en: `${dishItem.title.en} | ${foodRegion.city.en}`, + }, + href: `/food/food-${foodRegion.id}/`, + enabled: true, + }))); const links = [ ...enabledSiteLinks(content.search.suggestions), ...enabledSiteLinks(content.header.nav), ...content.megaMenus.flatMap((menu) => menu.groups.flatMap((group) => enabledSiteLinks(group.links))), + ...dishLinks, ]; const uniqueLinks = new Map(); links.forEach((link) => { @@ -2365,10 +2464,10 @@ function SearchOverlay({ onClose }: { onClose: () => void }) { return Array.from(uniqueLinks.values()); }, [content, language]); const visibleSearchLinks = useMemo(() => { - const normalizedQuery = query.trim().toLocaleLowerCase(); + const normalizedQuery = normalizeSearchTerm(query); const source = normalizedQuery ? allSearchLinks : enabledSiteLinks(content.search.suggestions); return source - .filter((link) => !normalizedQuery || oc(link.label, language).toLocaleLowerCase().includes(normalizedQuery)) + .filter((link) => !normalizedQuery || normalizeSearchTerm(oc(link.label, language)).includes(normalizedQuery)) .slice(0, 12); }, [allSearchLinks, content.search.suggestions, language, query]); @@ -2950,105 +3049,25 @@ function FoodPage() { function CollectivePage() { const { language } = useTravelSite(); - 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 hotelGroups = supplementHotelGroups.map((hotelGroup) => ({ + number: hotelGroup.number, + title: hotelGroup.title[language], + intro: hotelGroup.intro[language], + hotels: hotelGroup.hotels.map((hotel) => ({ + id: hotel.id, + title: hotel.title[language], + location: hotel.location[language], + copy: hotel.copy[language], + verify: hotel.verify[language], + route: hotel.route[language], + images: hotel.images, + })), + })); 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('万峰林・万峰湖', 'Wanfenglin · Wanfeng Lake', language), stays: pickText('峰兮洞穴 / 云屯星空 / 峰兮半山', 'Fengxi Cave / Yuntun Starry / Fengxi Banshan', language), note: pickText('在湖景、亲子森林与半山峰林之间选择一处慢住,避免连续换宿。', 'Choose one slower stay across lake, family-forest, or hillside peak views instead of changing hotels repeatedly.', language) }, + { route: pickText('西江・榕江・台江', 'Xijiang · Rongjiang · Taijiang', language), stays: pickText('西江观景美宿 / 月亮山居 / 拾仓来隐', 'Xijiang viewpoint stays / Moon Mountain Residence / Shicang Laiyin', language), note: pickText('按具体村寨、交通时间和可接待条件选择,不建议为了“打卡”频繁换宿。', 'Choose by exact village, transfer time, and confirmed reception; 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) }, + { route: pickText('独山・黔南山地', 'Dushan · Qiannan mountains', language), stays: pickText('朴语野奢', 'Puyu Wild-Luxury Retreat', language), note: pickText('适合在黔南自然线中留出一晚恢复,先核实真实车程、道路和天气备选。', 'Use it as a recovery night within a Qiannan nature route after checking real drive time, roads, and weather backup.', language) }, ]; const bookingChecks = [ pickText('正式店名、经营与预订主体、准确地址和当前位置', 'Legal name, operator, booking entity, exact address and map pin', language), @@ -3075,9 +3094,9 @@ function CollectivePage() {

{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('这 13 家来自客户最新修改意见,并已使用客户提供的对应酒店照片。经营主体、实时房态、具体房型和境外客人登记仍需在正式预订前逐家确认,宣传卖点不会直接写成所有日期均保证提供。', 'These 13 stays come from the latest client brief and now use the matching client-supplied photography. Operator, live availability, exact room, and foreign-guest registration still require confirmation before booking, and promotional features are not presented as guaranteed for every date.', language)}

- {pickText('8 家候选', '8 candidates', language)} + {pickText('13 家候选', '13 candidates', language)} {pickText('4 类住宿', '4 stay types', language)} {pickText('预订前逐家确认', 'Verified before booking', language)}
@@ -3101,6 +3120,9 @@ function CollectivePage() { {group.number}.{String(index + 1).padStart(2, '0')} {pickText('客户推荐・待确认', 'Client shortlist · To verify', language)}
+ + {hotel.title} +

{hotel.title}

{hotel.location}

{hotel.copy}

@@ -3108,7 +3130,7 @@ function CollectivePage() {
{pickText('路线', 'Route fit', language)}
{hotel.route}
{pickText('确认', 'Verify', language)}
{hotel.verify}
- {pickText('加入住宿比选', 'Add to Stay Shortlist', language)} + {pickText('查看住宿详情', 'View Stay Details', language)} ))} @@ -3159,20 +3181,111 @@ function CollectivePage() { function HotelDetailPage({ slug }: { slug: string }) { const { cmsContent, language } = useTravelSite(); - const hotel = collectiveArchiveCards.find((card) => archiveCardSlug(card) === slug || slugifyTitle(card.title) === slug) || collectiveArchiveCards[0]; - const cmsProduct = findCmsProduct(cmsContent, slug, hotel.title, ['hotel']); - const detail = localizePoiDetail(applyProductCmsDetailContent(makeHotelPoiDetail(hotel), cmsProduct, cmsContent, language), language, archiveCardSlug(hotel)); + const supplementMatch = supplementHotels.find((hotelItem) => hotelItem.id === slug || hotelItem.legacyAliases?.includes(slug)); + const canonicalSlug = supplementMatch?.id || slug; + const hotel = [...collectiveArchiveCards, ...legacyHotelArchiveCards].find((card) => archiveCardSlug(card) === canonicalSlug || slugifyTitle(card.title) === canonicalSlug) || collectiveArchiveCards[0]; + const cmsProduct = findCmsProduct(cmsContent, canonicalSlug, hotel.title, ['hotel']); + const baseDetail = makeHotelPoiDetail(hotel); + const hydratedDetail = supplementMatch ? baseDetail : applyProductCmsDetailContent(baseDetail, cmsProduct, cmsContent, language); + const detail = localizePoiDetail(hydratedDetail, language, archiveCardSlug(hotel)); return ; } function FoodDetailPage({ slug }: { slug: string }) { const { cmsContent, language } = useTravelSite(); const food = foodFromSlug(slug); + const supplementFood = supplementFoodById[food.id]; + if (supplementFood) return ; const cmsProduct = findCmsProduct(cmsContent, slug, `${food.city}美食`, ['food']); const detail = localizePoiDetail(applyProductCmsDetailContent(makeFoodPoiDetail(food), cmsProduct, cmsContent, language), language, food.id); return ; } +function SupplementFoodDetailPage({ food }: { food: SupplementFoodRegion }) { + const { language } = useTravelSite(); + const [activeGalleryIndex, setActiveGalleryIndex] = useState(null); + const allImages = food.dishes.flatMap((dishItem) => dishItem.images); + const cover = allImages[0] || img.menuFood; + const galleryItems: GalleryLightboxItem[] = allImages.map((source, index) => ({ + source, + alt: language === 'zh' ? `${food.city.zh}图片 ${index + 1}` : `${food.city.en} image ${index + 1}`, + caption: food.city[language], + })); + + return ( +
+ +
+
+ {pickText('贵州在地味道', 'Local Guizhou Flavors', language)} +

{food.city[language]}

+

{food.intro[language]}

+
+ {food.tags.map((tag) => {tag[language]})} +
+
+ +
+ +
+
+ {pickText('按一道菜理解一个地方', 'One Dish, One Place', language)} +

{pickText('代表味道与现场细节', 'Signature Flavors and Their Context', language)}

+

{pickText('图片均来自客户本轮提供的对应菜品素材;没有配图的条目保留为文字介绍,不使用无关图片替代。', 'Images come from the matching dish material supplied in this update. Text-only dishes remain text-only instead of borrowing unrelated photography.', language)}

+
+
+ {food.dishes.map((dishItem, dishIndex) => ( +
+
+ {String(dishIndex + 1).padStart(2, '0')} +

{dishItem.title[language]}

+

{dishItem.copy[language]}

+ {dishItem.images.length === 0 && {pickText('客户本轮未提供对应配图', 'No matching image supplied in this update', language)}} +
+ {dishItem.images.length > 0 && ( +
+ {dishItem.images.map((source, imageIndex) => { + const galleryIndex = allImages.indexOf(source); + return ( + + ); + })} +
+ )} +
+ ))} +
+
+ +
+
+
+ {pickText('加入行程前', 'Before Adding It to a Route', language)} +

{pickText('先确认口味,再决定餐厅和时段', 'Match Taste First, Then Choose the Place and Time', language)}

+
+
    +
  • {pickText('提前说明辣度、酸汤、鱼类、牛羊肉、内脏与折耳根接受度。', 'Confirm comfort with chili, sour soup, fish, beef or lamb, offal, and fish mint.', language)}
  • +
  • {pickText('过敏、素食、清真、儿童餐和长辈饮食需求逐项记录。', 'Record allergies, vegetarian or halal needs, children’s meals, and senior dietary needs.', language)}
  • +
  • {pickText('长车程或户外日优先卫生、出餐效率和顺路程度,不为网红店反向绕行。', 'On long-drive or outdoor days, prioritize hygiene, serving speed, and route fit rather than detouring for a viral venue.', language)}
  • +
+
+
+ setActiveGalleryIndex(null)} /> + +
+ ); +} + function CaveExpeditionDetailPage() { const { language } = useTravelSite(); const levels = [ @@ -7609,26 +7722,50 @@ function GalleryLightbox({ onClose: () => void; }) { const { language } = useTravelSite(); + const dialogRef = useRef(null); + const closeRef = useRef(null); + const returnFocusRef = useRef(null); + const activeIndexRef = useRef(activeIndex ?? 0); + const onSelectRef = useRef(onSelect); + const onCloseRef = useRef(onClose); const isOpen = activeIndex !== null && items.length > 0; const activeItem = isOpen ? items[activeIndex] : null; + useEffect(() => { + activeIndexRef.current = activeIndex ?? 0; + }, [activeIndex]); + + useEffect(() => { + onSelectRef.current = onSelect; + }, [onSelect]); + + useEffect(() => { + onCloseRef.current = onClose; + }, [onClose]); + useEffect(() => { if (!isOpen) return undefined; + returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null; + const focusFrame = window.requestAnimationFrame(() => closeRef.current?.focus()); + const handleKey = (event: globalThis.KeyboardEvent) => { - if (event.key === 'Escape') onClose(); - if (event.key === 'ArrowLeft') onSelect((activeIndex - 1 + items.length) % items.length); - if (event.key === 'ArrowRight') onSelect((activeIndex + 1) % items.length); + if (event.key === 'Escape') onCloseRef.current(); + if (event.key === 'ArrowLeft') onSelectRef.current((activeIndexRef.current - 1 + items.length) % items.length); + if (event.key === 'ArrowRight') onSelectRef.current((activeIndexRef.current + 1) % items.length); + keepFocusInsideDialog(event, dialogRef.current); }; document.body.classList.add('oc-lightbox-open'); window.addEventListener('keydown', handleKey); return () => { + window.cancelAnimationFrame(focusFrame); document.body.classList.remove('oc-lightbox-open'); window.removeEventListener('keydown', handleKey); + returnFocusRef.current?.focus(); }; - }, [activeIndex, isOpen, items.length, onClose, onSelect]); + }, [isOpen, items.length]); if (!isOpen || !activeItem) return null; @@ -7636,7 +7773,7 @@ function GalleryLightbox({ const isVideo = isVideoSource(activeItem.source) || isVideoSource(resolvedSource); return ( -
+
)} -
diff --git a/src/guizhouContent.ts b/src/guizhouContent.ts index e7dc98f..02eefe8 100644 --- a/src/guizhouContent.ts +++ b/src/guizhouContent.ts @@ -1,6 +1,12 @@ import type { CmsContent } from './cms'; import type { SiteContent } from './siteContent'; import { foodEnglish, hotelEnglish } from './guizhouEnContent.ts'; +import { + supplementFoodById, + supplementFoodRegions, + supplementHotelGroups, + supplementHotels, +} from './guizhouSupplementContent.ts'; const t = (en: string, zh = en) => ({ zh, en }); @@ -332,22 +338,17 @@ export const guizhouSiteContent: SiteContent = { ctaLabel: t('View All Hotels', '查看全部酒店'), ctaHref: '/hotels/', groups: [ - 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('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'), - ]), + ...supplementHotelGroups.map((hotelGroup) => group( + `hotel-group-${hotelGroup.number}`, + hotelGroup.title.en, + hotelGroup.title.zh, + hotelGroup.hotels.map((hotel) => link( + hotel.id, + hotel.title.en, + hotel.title.zh, + `/hotels/${hotel.id}/`, + )), + )), ], specialCards: [], }, @@ -1007,30 +1008,40 @@ export const guizhouExperienceArchiveCards = [ { title: '温泉恢复日', meta: '剑河 / 黄果树柏联', type: '康养', image: guizhouImages.hotelBolian, copy: '适合放在山地、洞穴和亲水活动之后,作为软着陆或体力恢复节点。', href: '/experiences/hot-spring-recovery/' }, ]; +const foodCardFromSupplement = (id: keyof typeof supplementFoodById) => { + const food = supplementFoodById[id]; + return { + id: food.id, + city: food.region.zh.replace(/州$/, ''), + english: food.region.en, + region: food.region.zh, + image: food.dishes.find((item) => item.images.length > 0)?.images[0] || guizhouImages.menuFood, + dishes: food.dishes.map((item) => item.title.zh), + copy: food.intro.zh, + }; +}; + export const guizhouFoodCards = [ - { id: 'guiyang', city: '贵阳', english: 'Guiyang', region: '贵阳', image: guizhouImages.routeClassic, dishes: ['肠旺面', '丝娃娃', '恋爱豆腐果', '怪噜饭'], copy: '适合做抵达首晚或返程前的城市餐食线,把贵阳小吃、酸辣口味和轻松步行放在一起。' }, + foodCardFromSupplement('guiyang'), { id: 'zunyi', city: '遵义', english: 'Zunyi', region: '遵义', image: guizhouImages.menuCulture, dishes: ['遵义羊肉粉', '豆花面', '辣椒宴', '鸡蛋糕'], copy: '适合搭配红色文化、人文线路和北线交通节点,口味更浓,适合喜欢粉面和辣椒风味的客人。' }, { id: 'liupanshui', city: '六盘水', english: 'Liupanshui', region: '六盘水', image: guizhouImages.routeOutdoor, dishes: ['水城烙锅', '水城羊肉粉', '盘州火腿', '凉都烧烤'], copy: '山地凉都适合夏季避暑与夜间烟火气,烙锅和羊肉粉可以作为西线或户外后的热食补给。' }, { id: 'anshun', city: '安顺', english: 'Anshun', region: '安顺', image: guizhouImages.destinationHuangguoshu, dishes: ['安顺裹卷', '夺夺粉', '破酥包', '屯堡家常菜'], copy: '黄果树和屯堡之间的高效餐食节点,适合安排小吃试吃、轻正餐或瀑布日后的恢复餐。' }, - { id: 'bijie', city: '毕节', english: 'Bijie', region: '毕节', image: guizhouImages.heroFanjing, dishes: ['织金荞凉粉', '威宁火腿', '豆干火锅', '乌蒙土豆'], copy: '高原风味更质朴,适合搭配织金洞、百里杜鹃、韭菜坪等自然和季节线路。' }, + foodCardFromSupplement('bijie'), { id: 'tongren', city: '铜仁', english: 'Tongren', region: '铜仁', image: guizhouImages.destinationFanjing, dishes: ['社饭', '米豆腐', '锅巴粉', '江口豆干'], copy: '梵净山前后可以安排轻量地方餐,兼顾早出发、爬山体力和古城慢住节奏。' }, - { id: 'qiannan', city: '黔南', english: 'Qiannan', region: '黔南州', image: guizhouImages.destinationLibo, dishes: ['虾酸牛肉', '荔波酸肉', '都匀冲冲糕', '水族酸汤'], copy: '荔波和都匀一线适合把酸汤、河鲜、民族风味与水森林、漂流或亲水日组合。' }, - { id: 'qiandongnan', city: '黔东南', english: 'Qiandongnan', region: '黔东南州', image: guizhouImages.destinationXijiang, dishes: ['凯里酸汤鱼', '牛瘪火锅', '侗家腌鱼', '苗家长桌宴'], copy: '村寨餐食是人文体验的一部分,需要提前说明酸汤、辣度、礼仪和可接受度,适合做主理人讲解餐。' }, - { id: 'qianxinan', city: '黔西南', english: 'Qianxinan', region: '黔西南州', image: guizhouImages.destinationWanfenglin, dishes: ['兴义羊肉粉', '刷把头', '鸡肉汤圆', '峰林布依风味'], copy: '万峰林、马岭河和兴义城市停留可加入更生活化的粉面、点心和布依风味餐。' }, + foodCardFromSupplement('qiannan'), + foodCardFromSupplement('qiandongnan'), + foodCardFromSupplement('qianxinan'), ]; -export const guizhouCollectiveArchiveCards = [ - { title: '贵阳喜来登贵航酒店', meta: '贵阳 / 大南门', image: guizhouImages.routeClassic, copy: '适合作为贵阳市中心抵达、会议、甲秀楼步行和首晚/末晚住宿的城市基地。', href: '/hotels/sheraton-guiyang-hotel/' }, - { title: '贵阳珍珀酒店', meta: '贵阳 / 观山湖', image: guizhouImages.menuHotels, copy: '适合偏好安静城市基地、房间质感和新区交通便利性的客人。', href: '/hotels/pearl-gallery-hotel-guiyang/' }, - { title: '贵阳观山湖希尔顿欢朋酒店', meta: '贵阳 / 观山湖', image: guizhouImages.hotelReference, copy: '适合路线过渡夜、商务抵达和稳定中高端舒适度的实用型酒店;图片为贵州住宿参考图,最终酒店与房态以报价确认为准。', href: '/hotels/hampton-by-hilton-guiyang-guanshan-lake/' }, - { title: '黄果树柏联酒店', meta: '黄果树 / 关岭', image: guizhouImages.hotelBolian, copy: '适合瀑布区域恢复夜、温泉节奏和更慢的高品质路线。', href: '/hotels/huangguoshu-bolian-resort/' }, - { title: '璞语野奢', meta: '独山 / 黔南', image: guizhouImages.footerCta, copy: '适合想在黔南获得安静山地氛围和野奢住宿体验的客人。', href: '/hotels/puyu-wild-luxury/' }, - { title: '西江三春里孟度假民宿', meta: '西江千户苗寨 / 梯田', image: guizhouImages.destinationXijiang, copy: '适合苗寨体验、摄影和人文线路的梯田视野住宿。', href: '/hotels/xijiang-sanchunli-meng/' }, - { title: '兴义云屯星空野奢酒店', meta: '兴义 / 万峰林', image: guizhouImages.destinationWanfenglin, copy: '适合万峰林、轻徒步、峰林摄影和慢节奏星空住宿。', href: '/hotels/xingyi-yuntun-starry-sky/' }, - { title: '遵义乌江寨苗王酒店', meta: '遵义 / 乌江寨', image: guizhouImages.heroXijiang, copy: '适合演艺、村寨建筑体验和度假区动线更清晰的文化度假住宿。', href: '/hotels/zunyi-wujiangzhai-miaowang/' }, -]; +export const guizhouCollectiveArchiveCards = supplementHotels.map((hotel) => ({ + title: hotel.title.zh, + meta: hotel.location.zh, + image: hotel.images[0], + copy: hotel.copy.zh, + href: `/hotels/${hotel.id}/`, +})); -const media = [ +const baseMedia = [ ['hero-wanfenglin', 'Homepage Wanfenglin hero', '首页万峰林主视觉', guizhouImages.heroHome, 'homepage hero'], ['hero-huangguoshu', 'Huangguoshu hero slide', '黄果树首屏轮播', guizhouImages.heroHuangguoshu, 'homepage hero slide'], ['hero-fanjing', 'Fanjing hero slide', '梵净山首屏轮播', guizhouImages.heroFanjing, 'homepage hero slide'], @@ -1067,6 +1078,33 @@ const media = [ alt: { zh, en }, })); +const supplementMedia = [ + ...supplementHotels.flatMap((hotel) => hotel.images.map((url, index) => ({ + id: `hotel-${hotel.id}-${index + 1}`, + type: 'image' as const, + placement: 'hotel', + url, + title: hotel.title, + alt: { + zh: `${hotel.title.zh}图片 ${index + 1}`, + en: `${hotel.title.en} image ${index + 1}`, + }, + }))), + ...supplementFoodRegions.flatMap((region) => region.dishes.flatMap((dishItem) => dishItem.images.map((url, index) => ({ + id: `food-${region.id}-${dishItem.id}-${index + 1}`, + type: 'image' as const, + placement: 'food', + url, + title: dishItem.title, + alt: { + zh: `${dishItem.title.zh}图片 ${index + 1}`, + en: `${dishItem.title.en} image ${index + 1}`, + }, + })))), +]; + +const media = [...baseMedia, ...supplementMedia]; + const mediaIdForUrl = (url: string, fallbackId: string) => media.find((assetItem) => assetItem.url === url)?.id || fallbackId; const productDetailImagePools: Record, string[]> = { @@ -1290,37 +1328,41 @@ const products: CmsContent['products'] = [ }, ...guizhouFoodCards.map((food) => { const englishFood = foodEnglish[food.id as keyof typeof foodEnglish]; + const supplementFood = supplementFoodById[food.id]; return { id: `food-${food.id}`, kind: 'food' as const, status: 'published' as const, featured: false, imageId: mediaIdForUrl(food.image, 'menu-food'), - title: t(englishFood?.title || `${food.english} Food`, `${food.city}美食`), - subtitle: t(englishFood?.region || food.region, food.region), - summary: t(englishFood?.copy || food.copy, food.copy), + title: t(supplementFood?.city.en || englishFood?.title || `${food.english} Food`, supplementFood?.city.zh || `${food.city}美食`), + subtitle: t(supplementFood?.region.en || englishFood?.region || food.region, supplementFood?.region.zh || food.region), + summary: t(supplementFood?.intro.en || englishFood?.copy || food.copy, supplementFood?.intro.zh || food.copy), duration: t('Flexible meal stop', '灵活餐食节点'), - region: t(englishFood?.region || food.region, food.region), + region: t(supplementFood?.region.en || englishFood?.region || food.region, supplementFood?.region.zh || food.region), priceLabel: t('Planned by menu and group size', '按菜单与人数确认'), difficulty: t('Taste preference checked in advance', '提前确认口味接受度'), - highlights: food.dishes.map((dish, index) => t(englishFood?.dishes[index] || dish, dish)), + highlights: supplementFood + ? supplementFood.dishes.map((dishItem) => dishItem.title) + : food.dishes.map((dish, index) => t(englishFood?.dishes[index] || dish, dish)), cta: t('Plan this food stop', '规划这个美食节点'), }; }), ...guizhouCollectiveArchiveCards.map((hotel, index) => { const hotelId = slugFromHref(hotel.href) || `hotel-${index + 1}`; const englishHotel = hotelEnglish[hotelId as keyof typeof hotelEnglish]; + const supplementHotel = supplementHotels.find((item) => item.id === hotelId); return { id: hotelId, kind: 'hotel' as const, status: 'published' as const, featured: false, imageId: mediaIdForUrl(hotel.image, 'menu-hotels'), - title: t(englishHotel?.title || hotel.title, hotel.title), - subtitle: t(englishHotel?.meta || hotel.meta, hotel.meta), - summary: t(englishHotel?.copy || hotel.copy, hotel.copy), + title: t(supplementHotel?.title.en || englishHotel?.title || hotel.title, supplementHotel?.title.zh || hotel.title), + subtitle: t(supplementHotel?.location.en || englishHotel?.meta || hotel.meta, supplementHotel?.location.zh || hotel.meta), + summary: t(supplementHotel?.copy.en || englishHotel?.copy || hotel.copy, supplementHotel?.copy.zh || hotel.copy), duration: t('1-2 nights by route design', '按线路安排 1-2 晚'), - region: t(englishHotel?.meta || hotel.meta, hotel.meta), + region: t(supplementHotel?.location.en || englishHotel?.meta || hotel.meta, supplementHotel?.location.zh || hotel.meta), priceLabel: t('Quoted by room type and date', '按房型与日期询价'), difficulty: t('Comfort and location matched to route pace', '按舒适度与动线匹配'), highlights: [ diff --git a/src/guizhouSupplementContent.ts b/src/guizhouSupplementContent.ts new file mode 100644 index 0000000..0365b33 --- /dev/null +++ b/src/guizhouSupplementContent.ts @@ -0,0 +1,349 @@ +export type BilingualText = { + zh: string; + en: string; +}; + +export type SupplementHotel = { + id: string; + title: BilingualText; + location: BilingualText; + copy: BilingualText; + route: BilingualText; + verify: BilingualText; + images: string[]; + legacyAliases?: string[]; +}; + +export type SupplementHotelGroup = { + number: string; + title: BilingualText; + intro: BilingualText; + hotels: SupplementHotel[]; +}; + +export type SupplementFoodDish = { + id: string; + title: BilingualText; + copy: BilingualText; + images: string[]; +}; + +export type SupplementFoodRegion = { + id: string; + city: BilingualText; + region: BilingualText; + intro: BilingualText; + tags: BilingualText[]; + dishes: SupplementFoodDish[]; +}; + +const series = (path: string, count: number) => + Array.from({ length: count }, (_, index) => `/client-assets/${path}/${String(index + 1).padStart(2, '0')}.webp`); + +export const supplementHotelGroups: SupplementHotelGroup[] = [ + { + number: '01', + title: { zh: '天然地貌与隐奢住宿', en: 'Landscape & Hidden-Luxury Stays' }, + intro: { + zh: '以洞穴、悬崖、湖景和工业遗产为核心,让住宿本身成为旅程的一部分;具体房型、设施和道路条件仍按日期确认。', + en: 'Caves, cliffs, lake views, and industrial heritage make the stay part of the journey. Rooms, facilities, and road access are still checked for the travel date.', + }, + hotels: [ + { + id: 'fengxi-cave-luxury', + title: { zh: '万峰湖峰兮·洞穴丨隐奢金宿', en: 'Fengxi Cave · Hidden Luxury Stay at Wanfeng Lake' }, + location: { zh: '兴义 / 万峰湖方向', en: 'Xingyi / Wanfeng Lake area' }, + copy: { + zh: '天然地貌与舒适酒店环境融为一体,既适合安静停留,也适合在晚霞与清晨光线中拍摄。泳池、洞穴空间和具体景观以所订房型为准。', + en: 'Natural formations meet a comfortable contemporary stay, suited to quiet time and photography in evening or early-morning light. Pool, cave spaces, and views depend on the booked room.', + }, + route: { zh: '万峰湖 / 万峰林慢住线', en: 'Wanfeng Lake / Wanfenglin slow route' }, + verify: { zh: '正式店名、房型景观、临崖防护、道路与行李接驳', en: 'Legal name, room view, cliff protection, road and luggage transfer' }, + images: series('hotels/fengxi-cave-luxury', 6), + legacyAliases: ['fengxi-cave'], + }, + { + id: 'fangxia-yunju', + title: { zh: '铜仁放下云居', en: 'Tongren Fangxia Yunju' }, + location: { zh: '铜仁万山 / 朱砂古镇', en: 'Wanshan, Tongren / Zhusha Ancient Town' }, + copy: { + zh: '酒店位于贵州铜仁朱砂古镇,依托当地山水与工业遗产环境,结合悬崖泳池和高空视野,适合作为铜仁或梵净山线路的延伸住宿。', + en: 'Set in Tongren’s Zhusha Ancient Town, this stay combines the surrounding landscape and mining heritage with cliffside pool and elevated-view experiences, making it a possible extension around Tongren or Fanjing Mountain.', + }, + route: { zh: '铜仁 / 梵净山延伸线', en: 'Tongren / Fanjing Mountain extension' }, + verify: { zh: '运营主体、房型数量、矿区开放、泳池和高空区域安全', en: 'Operator, room inventory, mine access, pool and elevated-area safety' }, + images: series('hotels/fangxia-yunju', 5), + }, + ], + }, + { + number: '02', + title: { zh: '民族风情村寨美宿', en: 'Ethnic Village Boutique Stays' }, + intro: { + zh: '以西江、榕江大利侗寨和台江红阳苗寨为主要停留区域,让夜晚、清晨与村寨视野进入行程;住宿不等于自动进入居民私人生活。', + en: 'Stays around Xijiang, Dali Dong Village in Rongjiang, and Hongyang Miao Village in Taijiang bring village evenings, mornings, and views into the route without implying access to private community life.', + }, + hotels: [ + { + id: 'xijiang-haoli-villa', + title: { zh: '西江千户苗寨豪利维拉薇墅酒店', en: 'Xijiang Haoli Villa Boutique Hotel' }, + location: { zh: '西江千户苗寨 / 观景区域', en: 'Xijiang Miao Village / viewpoint area' }, + copy: { + zh: '观景露台适合观看梯田、山间云雾与苗寨灯火,日出、日落和夜景都能成为住宿体验的一部分。具体视野和露台配置按房型确认。', + en: 'Viewing terraces frame rice fields, mountain mist, and the village lights, making sunrise, sunset, and evening scenery part of the stay. Exact views and terrace access depend on room type.', + }, + route: { zh: '西江夜景与清晨摄影线', en: 'Xijiang evening and sunrise photography route' }, + verify: { zh: '准确位置、房型视野、台阶、行李搬运与接驳', en: 'Exact location, room view, steps, luggage handling and transfer' }, + images: series('hotels/xijiang-haoli-villa', 6), + }, + { + id: 'xijiang-sanchunli-meng', + title: { zh: '西江三春里·孟度假民宿', en: 'Xijiang Sanchunli · Meng Retreat' }, + location: { zh: '西江千户苗寨 / 观景台区域', en: 'Xijiang Miao Village / viewpoint area' }, + copy: { + zh: '以无边际泳池和苗寨传统木构空间为主要特色,原木建筑、绿植与较大的公共区域适合摄影和慢停留。泳池开放与房间视野按日期和房型确认。', + en: 'Known for an infinity-pool setting and timber architecture inspired by Miao building traditions, with warm wood, planting, and generous shared areas for a slower stay. Pool opening and room views are date- and room-specific.', + }, + route: { zh: '西江人文与观景慢住', en: 'Xijiang culture and viewpoint stay' }, + verify: { zh: '泳池开放、房型、观景位置、行李接驳和夜间安静度', en: 'Pool opening, room type, viewpoint, luggage transfer and night noise' }, + images: series('hotels/xijiang-sanchunli-meng', 4), + }, + { + id: 'xijiang-seven-letters', + title: { zh: '七封信·展阅安邸', en: 'Seven Letters · Zhanyue Residence' }, + location: { zh: '西江千户苗寨 / 观景台区域', en: 'Xijiang Miao Village / viewpoint area' }, + copy: { + zh: '客房和露台面向苗寨山谷,适合把傍晚灯火与清晨村寨视野留在住宿中。客户未提供正式介绍,具体房型、景观和服务以预订前核实为准。', + en: 'Rooms and terraces face the village valley, suited to keeping evening lights and morning views within the stay. The brief provides no formal description, so room type, view, and service are verified before booking.', + }, + route: { zh: '西江观景住宿候选', en: 'Xijiang viewpoint stay candidate' }, + verify: { zh: '正式名称、准确地址、房型景观、台阶和行李搬运', en: 'Legal name, exact address, room view, steps and luggage handling' }, + images: series('hotels/xijiang-seven-letters', 3), + }, + { + id: 'xijiang-jieshan-yunting', + title: { zh: '介山·云庭', en: 'Jieshan · Yunting Retreat' }, + location: { zh: '西江千户苗寨 / 观景台区域', en: 'Xijiang Miao Village / viewpoint area' }, + copy: { + zh: '以大幅观景窗、山谷露台和室内泡池等空间呈现苗寨视野,适合重视住宿质感与摄影的客人。泡池、餐饮空间和景观按实际房型确认。', + en: 'Large viewing windows, valley terraces, and selected indoor soaking spaces frame the Miao village landscape for guests who value design and photography. Baths, dining spaces, and views depend on the actual room.', + }, + route: { zh: '西江景观与住宿体验线', en: 'Xijiang scenery and stay-led route' }, + verify: { zh: '房型、泡池、餐食、观景方向、台阶与接驳', en: 'Room, soaking bath, dining, view direction, steps and transfer' }, + images: series('hotels/xijiang-jieshan-yunting', 9), + }, + { + id: 'xijiang-xingzhe-shangju', + title: { zh: '行者上居全景美宿', en: 'Xingzhe Shangju Panoramic Stay' }, + location: { zh: '西江千户苗寨 / 山景区域', en: 'Xijiang Miao Village / hillside view area' }, + copy: { + zh: '木构公共空间、露台和山谷视野适合村寨慢住。客户未提供独立介绍,页面只呈现图片能够支持的空间与景观信息。', + en: 'Timber shared spaces, terraces, and valley views suit a slower village stay. As no separate description was supplied, the page presents only the spaces and scenery supported by the photographs.', + }, + route: { zh: '西江慢住与村寨摄影', en: 'Xijiang slow stay and village photography' }, + verify: { zh: '准确店址、房型、观景方向、公共区域和行李动线', en: 'Exact address, room, view direction, shared areas and luggage route' }, + images: series('hotels/xijiang-xingzhe-shangju', 3), + }, + { + id: 'moon-mountain-dali', + title: { zh: '月亮山居(大利侗寨店)', en: 'Moon Mountain Residence · Dali Dong Village' }, + location: { zh: '榕江 / 大利侗寨', en: 'Rongjiang / Dali Dong Village' }, + copy: { + zh: '以侗寨木构建筑、山林环境与水边村落为主要氛围,适合把大利侗寨的夜晚和清晨留在行程中。具体房型、道路、消防和境外客人登记需逐项确认。', + en: 'Dong timber architecture, forest surroundings, and the waterside village shape a stay that keeps Dali’s evening and morning in the route. Room type, roads, fire safety, and foreign-guest registration require individual confirmation.', + }, + route: { zh: '榕江 / 大利侗寨慢住线', en: 'Rongjiang / Dali Dong Village slow route' }, + verify: { zh: '经营主体、房型卫浴、木结构消防、道路台阶和住宿登记', en: 'Operator, room and bathroom, timber fire safety, road and steps, guest registration' }, + images: series('hotels/moon-mountain-dali', 8), + legacyAliases: ['jiayi-terrace'], + }, + { + id: 'taijiang-shicang-laiyin', + title: { zh: '台江拾仓来隐', en: 'Taijiang Shicang Laiyin' }, + location: { zh: '台江 / 红阳苗寨', en: 'Taijiang / Hongyang Miao Village' }, + copy: { + zh: '藏在红阳苗寨山野之间,保留老寨外部肌理,同时提供现代客房的舒适度。适合远离人潮、听山林风声并感受村寨日常。', + en: 'Tucked into the hills of Hongyang Miao Village, the stay keeps the older village texture outside while offering modern comfort within—suited to travelers seeking forest quiet and village rhythm away from crowds.', + }, + route: { zh: '台江 / 苗族人文线', en: 'Taijiang / Miao culture route' }, + verify: { zh: '村址、住宿登记、消防、房型设施和文化活动主体', en: 'Village address, guest registration, fire safety, room facilities and culture hosts' }, + images: series('hotels/taijiang-shicang-laiyin', 4), + legacyAliases: ['shicang-laiyin'], + }, + ], + }, + { + number: '03', + title: { zh: '田园山野精品民宿', en: 'Pastoral Mountain Boutique Stays' }, + intro: { + zh: '适合在森林、草坪和轻户外之间安排较轻松的一天;儿童设施、泡池与户外活动按房型和日期确认。', + en: 'A gentler day among forest, lawn, and light outdoor activities. Children’s facilities, soaking pools, and activities are confirmed by room and date.', + }, + hotels: [ + { + id: 'xingyi-yuntun-starry', + title: { zh: '兴义云屯·星空野奢酒店', en: 'Xingyi Yuntun · Starry-Sky Retreat' }, + location: { zh: '兴义 / 云屯生态公园方向', en: 'Xingyi / Yuntun Ecological Park area' }, + copy: { + zh: '坐落在松林与草坪之间,以帐篷和山野住宿氛围、亲子主题房及私享泡池为主要卖点。星空、旋转滑梯、儿童阁楼和泡池按实际房型及天气确认。', + en: 'Set among pine forest and lawns, this retreat combines tent-inspired mountain lodging, family-themed rooms, and selected private soaking pools. Stargazing, slides, children’s lofts, and baths depend on room type and weather.', + }, + route: { zh: '兴义 / 万峰林亲子轻松线', en: 'Xingyi / Wanfenglin easy family route' }, + verify: { zh: '正式店名、真实房型、园区关系、儿童设施和活动运营方', en: 'Legal name, actual room, park relationship, children’s facilities and activity operator' }, + images: series('hotels/xingyi-yuntun-starry', 5), + legacyAliases: ['yuntun-treehouse', 'xingyi-yuntun-starry-sky'], + }, + ], + }, + { + number: '04', + title: { zh: '城市与主题特色住宿', en: 'Urban & Themed Stays' }, + intro: { + zh: '从亲子童话主题到万峰林半山景观和独山悬崖野奢,用不同住宿气质收束一段路线;休息质量、交通和安全优先于拍照效果。', + en: 'From a playful family theme to Wanfenglin hillside views and Dushan cliffside luxury, each stay can close a route in a different mood. Rest, access, and safety come before photo appeal.', + }, + hotels: [ + { + id: 'xingyi-mushroom', + title: { zh: '兴义蘑菇野奢酒店', en: 'Xingyi Mushroom-Themed Retreat' }, + location: { zh: '兴义近郊 / 喀斯特山谷', en: 'Xingyi outskirts / karst valley' }, + copy: { + zh: '彩色蘑菇小屋、花海山林和夜间灯火组成童话村落氛围,适合亲子轻松停留。滑梯主题房、萌宠、手作、泳池和儿童设施以出行日期实际开放为准。', + en: 'Colorful mushroom cottages, flowers, forest, and evening lights create a playful village atmosphere for families. Slide rooms, animals, craft activities, pool, and children’s facilities depend on what is operating on the date.', + }, + route: { zh: '兴义 / 亲子主题住宿', en: 'Xingyi / themed family stay' }, + verify: { zh: '正式名称与地址、年龄适配、泳池和乐园开放、房型与夜间安静度', en: 'Legal name and address, age fit, pool and playground opening, room and night noise' }, + images: series('hotels/xingyi-mushroom', 10), + }, + { + id: 'fengxi-banshan', + title: { zh: '峰兮・半山(万峰林)', en: 'Fengxi · Banshan at Wanfenglin' }, + location: { zh: '兴义 / 万峰林上纳灰村', en: 'Xingyi / Shangnahui Village, Wanfenglin' }, + copy: { + zh: '坐落在万峰林上纳灰村半山,可远望喀斯特群峰与田园景观,无边泳池面向峰林,适合摄影和较慢的兴义停留。', + en: 'Set above Shangnahui Village in Wanfenglin, the property looks across karst peaks and farmland, with an infinity-pool setting facing the landscape for photography and a slower Xingyi stay.', + }, + route: { zh: '万峰林田园与摄影线', en: 'Wanfenglin countryside and photography route' }, + verify: { zh: '准确店址、房型视野、泳池开放、山路和接驳', en: 'Exact address, room view, pool opening, mountain road and transfer' }, + images: series('hotels/fengxi-banshan', 7), + }, + { + id: 'puyu-wild-luxury', + title: { zh: '朴语野奢', en: 'Puyu Wild-Luxury Retreat' }, + location: { zh: '独山 / 黔南山地', en: 'Dushan / Qiannan mountains' }, + copy: { + zh: '位于高海拔云山与峡谷之间,独栋别墅沿山脊错落分布,以大幅落地窗、露台泡池、云海、落日和星空视野为主要体验。面积、海拔、栋数与房型配置以酒店确认资料为准。', + en: 'Set among high mountain ridges and valleys, standalone villas use broad windows and selected terrace baths to frame cloud, sunset, and night-sky views. Site size, elevation, villa count, and room configuration require hotel confirmation.', + }, + route: { zh: '独山 / 黔南山地慢住', en: 'Dushan / Qiannan mountain slow stay' }, + verify: { zh: '经营主体、海拔与房量、道路、泡池、餐食和天气备选', en: 'Operator, elevation and inventory, road, bath, dining and weather backup' }, + images: series('hotels/puyu-wild-luxury', 6), + }, + ], + }, +]; + +export const supplementHotels = supplementHotelGroups.flatMap((group) => group.hotels); + +export const supplementHotelById = Object.fromEntries( + supplementHotels.map((hotel) => [hotel.id, hotel]), +) as Record; + +const dish = (id: string, zh: string, en: string, copyZh: string, copyEn: string, imagePath?: string, count = 0): SupplementFoodDish => ({ + id, + title: { zh, en }, + copy: { zh: copyZh, en: copyEn }, + images: imagePath ? series(imagePath, count) : [], +}); + +export const supplementFoodRegions: SupplementFoodRegion[] = [ + { + id: 'guiyang', + city: { zh: '贵阳美食', en: 'Guiyang Food' }, + region: { zh: '贵阳', en: 'Guiyang' }, + intro: { + zh: '贵阳美食从清晨粉面延续到夜市烙锅,酸、辣、脆哨与糯食构成城市烟火。可按抵达时间选择早餐线、小吃线或夜间热食。', + en: 'Guiyang food runs from breakfast noodles to late-night hotplates, shaped by sour notes, chili, crisp pork, and glutinous rice. Choose a breakfast, snack, or evening route around arrival time.', + }, + tags: [{ zh: '早餐粉面', en: 'Breakfast noodles' }, { zh: '城市小吃', en: 'City snacks' }, { zh: '夜间热食', en: 'Late-night hot food' }], + dishes: [ + dish('changwang-noodles', '肠旺面', 'Changwang Noodles', '劲道碱水面搭配软糯肥肠、滑嫩血旺和焦香脆哨,红油鲜亮、层次丰富,是贵阳代表性早餐。', 'Springy alkaline noodles with braised intestine, blood curd, and crisp pork in bright chili oil—a signature Guiyang breakfast.', 'food/guiyang/changwang-noodles', 1), + dish('huaxi-beef-noodles', '花溪牛肉粉', 'Huaxi Beef Rice Noodles', '鲜熬牛骨汤配牛肉薄片和爽滑米粉,可用糊辣椒与薄荷调整清香和辣度。', 'Beef-bone broth, thin beef slices, and smooth rice noodles, adjusted with roasted chili and mint.', 'food/guiyang/huaxi-beef-noodles', 1), + dish('plain-rice-noodles', '老贵阳素粉', 'Old Guiyang Plain Rice Noodles', '发酵酸粉米香独特,以油辣椒、酸萝卜和葱花调味,清爽开胃。', 'Fermented rice noodles with a distinctive grain aroma, dressed with chili oil, pickled radish, and scallion.', 'food/guiyang/plain-rice-noodles', 1), + dish('siwawa', '丝娃娃', 'Siwawa Vegetable Rolls', '薄米皮包裹多种鲜蔬,再淋红酸汤蘸水,清爽、脆嫩而开胃。', 'Thin rice wrappers filled with fresh vegetables and dressed with red sour-soup dipping sauce.', 'food/guiyang/siwawa', 1), + dish('glutinous-rice', '老贵阳糯米饭', 'Guiyang Glutinous Rice', '油润软糯的糯米裹脆哨、酸萝卜、花生和油辣椒,是方便而扎实的街头早餐。', 'Soft glutinous rice with crisp pork, pickled radish, peanuts, and chili oil—a compact street breakfast.', 'food/guiyang/glutinous-rice', 1), + dish('grilled-tofu', '恋爱豆腐果', 'Grilled Tofu with Chili Relish', '豆腐烤至外皮焦脆,剖开填入折耳根、酸萝卜和辣椒,适合夜市小吃线。', 'Grilled tofu with a crisp shell, filled with fish mint, pickled radish, and chili relish—best on an evening snack route.'), + dish('posu-bun', '破酥包', 'Flaky Posu Bun', '面皮层层起酥,常见鲜肉或甜香馅料,是贵阳经典早点。', 'A layered, flaky breakfast bun with savory pork or sweet fillings.'), + dish('guizhou-hotplate', '贵州烙锅', 'Guizhou Shared Hotplate', '五花肉、洋芋、豆腐和时蔬在圆鼓铁锅上现烙,配五香辣椒面,适合围桌分享。', 'Pork, potato, tofu, and vegetables cooked at the table on a domed hotplate and shared with spiced chili powder.'), + dish('bean-hotpot', '豆米火锅', 'Bean Hotpot', '软烂芸豆熬出浓汤,豆香温和,适合不想吃重辣的客人。', 'A mellow hotpot built on soft beans and a rich, gentle broth—an approachable option for guests avoiding heavy chili.'), + dish('guiyang-grilled-fish', '贵阳烤鱼', 'Guiyang Grilled Fish', '炭火烤鱼铺满配菜后继续在汤汁中加热,适合多人夜间分享。', 'Charcoal-grilled fish finished in a bubbling broth with abundant vegetables, suited to a shared evening meal.'), + dish('ice-slush', '贵阳冰浆', 'Guiyang Fruit Ice Slush', '水果与糯米冰打成绵密冰浆,常配花生碎,是火锅和烧烤后的解腻甜品。', 'Fruit and glutinous-rice ice blended into a smooth slush, often topped with peanuts after hotpot or barbecue.'), + ], + }, + { + id: 'qiandongnan', + city: { zh: '黔东南美食', en: 'Qiandongnan Food' }, + region: { zh: '黔东南州', en: 'Qiandongnan Prefecture' }, + intro: { + zh: '“三天不吃酸,走路打蹿蹿。”黔东南以发酵酸香、山野食材和糯食文化为核心,既有长桌宴大菜,也有村寨家常味。', + en: 'Qiandongnan is defined by fermented sour flavors, mountain ingredients, and glutinous-rice traditions, ranging from banquet dishes to everyday village food.', + }, + tags: [{ zh: '酸汤', en: 'Sour soup' }, { zh: '村寨宴席', en: 'Village banquets' }, { zh: '糯食文化', en: 'Glutinous-rice culture' }], + dishes: [ + dish('sour-soup-fish', '凯里酸汤鱼', 'Kaili Sour-Soup Fish', '红酸汤以毛辣果发酵,白酸汤以米汤发酵,配稻田鱼与木姜子、折耳根蘸水,是黔东南代表味道。', 'Red sour soup is fermented from local tomatoes while white sour soup comes from rice liquid, paired with tender fish and a litsea-and-fish-mint dip.', 'food/qiandongnan/sour-soup-fish', 2), + dish('preserved-fish-meat', '腌鱼腌肉', 'Dong Preserved Fish and Pork', '禾花鱼或土猪肉与糯米、辣椒入坛发酵,煎烤后酸香醇厚,是侗家传统待客风味。', 'Fish or pork is fermented in jars with glutinous rice and chili, then fried or grilled for a deep, tangy Dong flavor.', 'food/qiandongnan/preserved-fish-meat', 3), + dish('roast-fragrant-pig', '从江烤香猪', 'Congjiang Roast Fragrant Pig', '山林散养香猪经炭火烤制,外皮焦脆、肉质细嫩,是节庆和长桌宴常见硬菜。', 'Free-range fragrant pig is charcoal-roasted for crisp skin and tender meat, often served at festivals and long-table meals.', 'food/qiandongnan/roast-fragrant-pig', 4), + dish('niubie-hotpot', '牛瘪火锅', 'Niubie Hotpot', '以经过处理的牛胃草料汁形成独特草本风味,干锅版本通常更易初次尝试。', 'A distinctive herbal dish made from carefully processed bovine stomach contents; the dry-pot style is often the more approachable introduction.', 'food/qiandongnan/niubie-hotpot', 4), + dish('five-color-rice', '五彩糯米饭', 'Five-Color Glutinous Rice', '使用植物材料天然染色后蒸制,色彩鲜明并带草木清香,是节庆中的代表性糯食。', 'Naturally dyed with plant materials and steamed for bright color and a gentle botanical aroma, especially associated with festival tables.', 'food/qiandongnan/five-color-rice', 4), + ], + }, + { + id: 'qiannan', + city: { zh: '黔南美食', en: 'Qiannan Food' }, + region: { zh: '黔南州', en: 'Qiannan Prefecture' }, + intro: { + zh: '黔南汇聚布依、水族与瑶族风味,以独山发酵酸香、山野河鲜和糯食小吃为主要特色,适合与荔波和天眼线路组合。', + en: 'Qiannan brings together Buyi, Shui, and Yao flavors, led by Dushan-style fermentation, mountain and river ingredients, and glutinous-rice snacks.', + }, + tags: [{ zh: '独山酸香', en: 'Dushan fermentation' }, { zh: '布依风味', en: 'Buyi flavors' }, { zh: '河鲜', en: 'River fish' }], + dishes: [ + dish('shrimp-sour-beef', '虾酸牛肉', 'Shrimp-Sour Beef', '小河虾入坛发酵形成鲜香底味,与牛肉、芹菜和香料干锅爆炒,酸辣浓郁。', 'Fermented river shrimp creates a savory sour base for beef stir-fried with celery and spices.', 'food/qiannan/shrimp-sour-beef', 3), + dish('salted-vegetable-pork', '盐酸菜扣肉', 'Pork Belly with Dushan Salted Pickles', '独山盐酸菜与五花肉同蒸,酸菜中和油脂,呈现咸酸脆甜与软糯肉香。', 'Dushan salted pickles are steamed with pork belly, balancing rich fat with salty, sour, crisp, and lightly sweet notes.', 'food/qiannan/salted-vegetable-pork', 3), + dish('pingtang-jar-fish', '平塘坛子鱼', 'Pingtang Clay-Jar Fish', '整条鲤鱼在牙舟陶土坛中慢煨,鱼肉酥烂并带陶坛香气,是布依古法菜肴。', 'A whole carp slow-braised in a Yazhou clay jar until tender, carrying the aroma of the vessel in a traditional Buyi preparation.', 'food/qiannan/pingtang-jar-fish', 3), + ], + }, + { + id: 'qianxinan', + city: { zh: '黔西南美食', en: 'Qianxinan Food' }, + region: { zh: '黔西南州', en: 'Qianxinan Prefecture' }, + intro: { + zh: '黔西南毗邻滇桂,以布依风味和糯食文化为主,酸笋、豆豉与地摊火锅构成峰林之下的烟火滋味。', + en: 'Bordering Yunnan and Guangxi, Qianxinan is shaped by Buyi flavors, glutinous rice, sour bamboo, fermented beans, and lively street hotpots beneath the peak forests.', + }, + tags: [{ zh: '布依风味', en: 'Buyi flavors' }, { zh: '兴义小吃', en: 'Xingyi snacks' }, { zh: '地摊火锅', en: 'Street hotpot' }], + dishes: [ + dish('buyi-eight-bowls', '布依八大碗', 'Buyi Eight-Bowl Banquet', '八道扎实土菜组成布依待客宴席,常见猪脚炖豆、扣肉和酥肉,适合多人分享。', 'A generous eight-dish Buyi hospitality meal, often including pork knuckle with beans, steamed pork, and crisp-fried pork.', 'food/qianxinan/buyi-eight-bowls', 2), + dish('street-hotpot', '地摊火锅', 'Qianxinan Street Hotpot', '猪油炒香豆豉与糍粑辣椒后加入高汤,再涮鲜肉和野菜,锅气浓、围炉氛围强。', 'Fermented beans and pounded chili are fried in lard before broth, fresh meat, and greens are added for a robust communal hotpot.', 'food/qianxinan/street-hotpot', 3), + dish('shuabato', '刷把头', 'Shuabato Dumplings', '薄皮裹鲜肉和笋丁,外形像小刷把,旺火蒸熟后配胡椒蘸水,是兴义代表小吃。', 'Thin wrappers hold pork and diced bamboo shoot in a brush-shaped dumpling, steamed and served with a peppery dip.', 'food/qianxinan/shuabato', 1), + dish('wanfenglin-fried-rice', '万峰林蛋炒饭', 'Wanfenglin Egg Fried Rice', '米粒干爽,常搭配泡萝卜与凉拌小菜,是万峰林游玩中的轻松田园简餐。', 'Dry, separate grains of egg fried rice served with pickled radish and cold sides—a simple countryside meal around Wanfenglin.', 'food/qianxinan/wanfenglin-fried-rice', 3), + ], + }, + { + id: 'bijie', + city: { zh: '毕节美食', en: 'Bijie Food' }, + region: { zh: '毕节', en: 'Bijie' }, + intro: { + zh: '毕节地处乌蒙山区,多民族风味与高山杂粮、豆干、山珍相互交融,豆豉与洋芋构成厚重质朴的黔西北味道。', + en: 'In the Wumeng Mountains, Bijie combines multi-ethnic traditions with highland grains, tofu, mountain produce, fermented beans, and potatoes.', + }, + tags: [{ zh: '乌蒙山野', en: 'Wumeng mountain food' }, { zh: '豆豉香', en: 'Fermented-bean aroma' }, { zh: '高原热食', en: 'Highland comfort food' }], + dishes: [ + dish('kung-pao-chicken', '织金宫保鸡丁', 'Zhijin Kung Pao Chicken', '鸡肉滑嫩、花生酥脆,酸甜微辣,是与织金相关的经典黔菜代表。', 'Tender chicken and crisp peanuts in a lightly sweet, sour, and spicy sauce—a classic Guizhou dish associated with Zhijin.', 'food/bijie/kung-pao-chicken', 3), + dish('tofu-hotpot', '大方六龙豆干火锅', 'Dafang Liulong Tofu Hotpot', '豆豉炒制红汤锅底,手工豆干在砂锅中吸收汤汁,香气浓郁,适合搭配高原蔬菜。', 'A red broth built on fermented beans, with handmade tofu simmered until it absorbs the deeply savory soup and served with highland vegetables.', 'food/bijie/tofu-hotpot', 3), + ], + }, +]; + +export const supplementFoodById = Object.fromEntries( + supplementFoodRegions.map((region) => [region.id, region]), +) as Record;