import fs from 'node:fs' import path from 'node:path' import { execFileSync } from 'node:child_process' const root = path.resolve(import.meta.dirname, '..') const linkedAssets = process.env.FIGMA_SVG_LINKED === '1' const asset = (name) => { if (linkedAssets) return `http://127.0.0.1:5174/client-assets/${name}` const file = path.join(root, 'public', 'client-assets', name) const cacheDir = '/tmp/wanderq-figma-svg-assets' fs.mkdirSync(cacheDir, { recursive: true }) const output = path.join(cacheDir, `${path.parse(name).name}.jpg`) execFileSync('/usr/bin/sips', ['-Z', '360', '-s', 'format', 'jpeg', '-s', 'formatOptions', '40', file, '--out', output], { stdio: 'ignore' }) return `data:image/jpeg;base64,${fs.readFileSync(output).toString('base64')}` } const images = { village: asset('home-hero-mountain-village.webp'), scale: asset('home-hero-waterfall-scale.webp'), valley: asset('village-valley-fields.jpg'), adventure: asset('waterfall-abseil-spray.jpg'), culture: asset('culture-batik-making.jpg'), landscape: asset('waterfall-huangguoshu-main.jpg'), } const esc = (s) => s.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>') const text = (x, y, value, cls, anchor = 'start') => `${esc(value)}` const lines = (x, y, values, cls, gap) => values.map((v, i) => text(x, y + i * gap, v, cls)).join('') const W = 1440 const H = 2328 const svg = ` ${text(72, 48, 'WanderQ 首页结构稿', 'cn', 'start').replace('class="cn"', 'class="cn white" style="font-size:17px;font-weight:700"')} ${text(1368, 47, 'DESKTOP · 1440 · STRUCTURE REVIEW', 'en', 'end').replace('class="en"', 'class="en white" style="font-size:11px;letter-spacing:1.5px;opacity:.7"')} ${text(72, 126, 'SECTION 01', 'label')} ${text(250, 129, '首屏只负责吸引,不承担产品分类', 'cn').replace('class="cn"', 'class="cn" style="font-size:23px;font-weight:700"')} ${text(250, 159, '保留四组照片轮播,但主题名称退居辅助信息。用户先感受贵州,而不是先理解栏目。', 'body')} ${text(72, 248, 'Wander', 'en').replace('class="en"', 'class="en" style="font-size:29px;font-weight:800;letter-spacing:-2px"')} ${text(173, 248, 'Q.', 'en').replace('class="en"', 'class="en" style="font-size:29px;font-weight:800;letter-spacing:-2px;fill:#56b960"')} ${text(610, 244, '贵州体验  酒店  食物  关于我们', 'cn', 'middle').replace('class="cn"', 'class="cn" style="font-size:13px;font-weight:600"')} ${text(1368, 244, '中 EN  ☰  开始规划', 'cn', 'end').replace('class="cn"', 'class="cn" style="font-size:12px;font-weight:600"')} ${text(72, 350, 'GUIZHOU · CHINA', 'label')} ${lines(72, 418, ['不是看过贵州,', '是走进贵州。'], 'cn', 58).replaceAll('class="cn"', 'class="cn" style="font-size:46px;font-weight:800;letter-spacing:-2px"')} ${lines(72, 570, ['用三张同一气质的竖图组成一幅目的地印象。', '每次轮播切换整组照片,而不是让四个主题', '争夺注意力。'], 'body', 28)} ${lines(94, 710, ['本区不出现“玩法入口”按钮。', '唯一任务:让人愿意继续向下看。'], 'small', 24)} ${text(446, 340, '01', 'en').replace('class="en"', 'class="en" style="font-size:34px;font-weight:500"')} ${text(500, 340, '/ 04 野贵州', 'small')} ${text(765, 340, '野贵州  活态遗产  贵州边缘  贵州之下', 'small')} ${text(1332, 340, '← →', 'en', 'end').replace('class="en"', 'class="en" style="font-size:25px"')} ${text(468, 900, '山地与村落', 'small').replace('class="small"', 'class="small white"')} ${text(776, 900, '瀑布的尺度', 'small').replace('class="small"', 'class="small white"')} ${text(1054, 900, '山谷与田野', 'small').replace('class="small"', 'class="small white"')} ${text(72, 1060, 'TRANSITION', 'label').replace('fill:#a6553f', 'fill:#d8ee45')} ${lines(250, 1055, ['漂亮只是入口。', '接下来,告诉他如何进入贵州。'], 'cn', 40).replaceAll('class="cn"', 'class="cn white" style="font-size:28px;font-weight:700"')} ${lines(850, 1058, ['把“目的地想象”转换成“旅行选择”。', '上面是感受,下面才是产品表达。'], 'small', 26).replaceAll('class="small"', 'class="small white" style="opacity:.7"')} ${text(72, 1266, 'SECTION 02', 'label')} ${text(250, 1310, '贵州,不只是看风景。', 'cn').replace('class="cn"', 'class="cn" style="font-size:46px;font-weight:800;letter-spacing:-2px"')} ${text(808, 1266, '三种方式,深入贵州。', 'cn').replace('class="cn"', 'class="cn" style="font-size:15px;font-weight:700"')} ${lines(808, 1300, ['进入洞穴与峡谷,走近仍在延续的村寨生活,', '或沿着山峰、森林与瀑布慢慢旅行。', '三种体验可以单独选择,也可以组合。'], 'body', 27)} ${text(98, 1886, '01 · ADVENTURE', 'label').replace('fill:#a6553f', 'fill:#d8ee45')} ${text(98, 1935, '贵州极境', 'cn').replace('class="cn"', 'class="cn white" style="font-size:30px;font-weight:700"')} ${lines(98, 1971, ['探洞、瀑降、峡谷穿越与山地徒步。', '把喀斯特地貌变成真正可以进入的', '体验。'], 'small', 21).replaceAll('class="small"', 'class="small white" style="font-size:11px;opacity:.66"')} ${text(98, 2057, '进入极境体验', 'small').replace('class="small"', 'class="small white"')} ${text(462, 2057, '→', 'en', 'end').replace('class="en"', 'class="en acid" style="font-size:20px"')} ${text(538, 1910, '02 · LIVING CULTURE', 'label').replace('fill:#a6553f', 'fill:#d8ee45')} ${text(538, 1959, '活态贵州', 'cn').replace('class="cn"', 'class="cn white" style="font-size:30px;font-weight:700"')} ${lines(538, 1995, ['村寨、非遗、节庆与地方生活。', '看见仍在被使用、被传承的文化。'], 'small', 21).replaceAll('class="small"', 'class="small white" style="font-size:11px;opacity:.66"')} ${text(538, 2081, '走近在地生活', 'small').replace('class="small"', 'class="small white"')} ${text(902, 2081, '→', 'en', 'end').replace('class="en"', 'class="en acid" style="font-size:20px"')} ${text(978, 1886, '03 · LANDSCAPE', 'label').replace('fill:#a6553f', 'fill:#d8ee45')} ${text(978, 1935, '山水贵州', 'cn').replace('class="cn"', 'class="cn white" style="font-size:30px;font-weight:700"')} ${lines(978, 1971, ['黄果树、梵净山、小七孔、森林与湖泊。', '用更从容的节奏建立完整山水印象。'], 'small', 21).replaceAll('class="small"', 'class="small white" style="font-size:11px;opacity:.66"')} ${text(978, 2057, '探索山水路线', 'small').replace('class="small"', 'class="small white"')} ${text(1342, 2057, '→', 'en', 'end').replace('class="en"', 'class="en acid" style="font-size:20px"')} ${text(98, 2208, 'USER FLOW', 'label')} ${text(250, 2208, '首屏产生兴趣 → 过渡层确认“下面有内容” → 三张卡片理解三种核心产品 → 进入体验页或组合行程', 'body')} ${text(72, 2310, 'WanderQ · Homepage Structure Draft', 'en').replace('class="en"', 'class="en" style="font-size:10px;fill:#5d635d"')} ${text(1368, 2310, '用于确认信息层级,不是最终视觉定稿', 'small', 'end').replace('class="small"', 'class="small" style="font-size:10px"')} ` const output = path.join(root, linkedAssets ? 'figma-home-structure-linked.svg' : 'figma-home-structure.svg') fs.writeFileSync(output, svg) console.log(output)