Files
WanderQ-Web2.0/scripts/build_figma_home_structure_svg.mjs

171 lines
12 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')
const text = (x, y, value, cls, anchor = 'start') =>
`<text x="${x}" y="${y}" class="${cls}" text-anchor="${anchor}">${esc(value)}</text>`
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 = `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
<defs>
<style>
.cn{font-family:'Noto Sans SC','PingFang SC',sans-serif;fill:#161b17}
.en{font-family:Inter,Arial,sans-serif;fill:#161b17}
.label{font-family:Inter,Arial,sans-serif;font-size:12px;font-weight:600;letter-spacing:2px;fill:#a6553f}
.body{font-family:'Noto Sans SC','PingFang SC',sans-serif;font-size:15px;font-weight:400;fill:#5d635d}
.small{font-family:'Noto Sans SC','PingFang SC',sans-serif;font-size:12px;font-weight:400;fill:#5d635d}
.white{fill:#f3efe5}
.acid{fill:#d8ee45}
</style>
<clipPath id="hero1"><path d="M476 376H746V924H476Q446 924 446 894V406Q446 376 476 376Z"/></clipPath>
<clipPath id="hero2"><rect x="754" y="376" width="270" height="548"/></clipPath>
<clipPath id="hero3"><path d="M1032 376H1302Q1332 376 1332 406V894Q1332 924 1302 924H1032Z"/></clipPath>
<clipPath id="card1"><path d="M72 1532H488V2075H72Z"/></clipPath>
<clipPath id="card2"><rect x="512" y="1556" width="416" height="543"/></clipPath>
<clipPath id="card3"><path d="M952 1532H1368V2045Q1368 2075 1338 2075H952Z"/></clipPath>
</defs>
<g id="WanderQ Homepage Structure Draft">
<rect width="1440" height="2328" fill="#f3efe5"/>
<g id="Document Header">
<rect width="1440" height="78" fill="#173126"/>
<rect y="74" width="1440" height="4" fill="#d8ee45"/>
${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"')}
</g>
<g id="01 Visual Attraction Annotation">
${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')}
<line x1="72" y1="188" x2="1368" y2="188" stroke="#161b17" stroke-opacity=".19"/>
</g>
<g id="Website Header">
${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"')}
<line x1="0" y1="278" x2="1440" y2="278" stroke="#161b17" stroke-opacity=".19"/>
</g>
<g id="Hero Copy">
${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)}
<line x1="72" y1="680" x2="380" y2="680" stroke="#161b17" stroke-opacity=".19"/>
<circle cx="78" cy="714" r="5" fill="#d8ee45"/>
${lines(94, 710, ['本区不出现“玩法入口”按钮。', '唯一任务:让人愿意继续向下看。'], 'small', 24)}
</g>
<g id="Hero Carousel Meta">
${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')}
<line x1="765" y1="356" x2="814" y2="356" stroke="#173126" stroke-width="2"/>
${text(1332, 340, '← →', 'en', 'end').replace('class="en"', 'class="en" style="font-size:25px"')}
<line x1="446" y1="366" x2="1332" y2="366" stroke="#161b17" stroke-opacity=".19"/>
</g>
<g id="Hero Image Triptych">
<image x="446" y="376" width="300" height="548" preserveAspectRatio="xMidYMid slice" xlink:href="${images.village}" clip-path="url(#hero1)"/>
<image x="754" y="376" width="270" height="548" preserveAspectRatio="xMidYMid slice" xlink:href="${images.scale}" clip-path="url(#hero2)"/>
<image x="1032" y="376" width="300" height="548" preserveAspectRatio="xMidYMid slice" xlink:href="${images.valley}" clip-path="url(#hero3)"/>
${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"')}
</g>
<g id="Transition">
<rect y="968" width="1440" height="226" fill="#173126"/>
${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"')}
</g>
<g id="02 Product Content Annotation">
${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)}
</g>
<g id="Experience Cards">
<g id="Card 01 Guizhou Extreme">
<rect x="72" y="1532" width="416" height="543" fill="#173126" rx="0"/>
<image x="72" y="1532" width="416" height="320" preserveAspectRatio="xMidYMid slice" xlink:href="${images.adventure}" clip-path="url(#card1)"/>
${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"')}
<line x1="98" y1="2030" x2="462" y2="2030" stroke="#f3efe5" stroke-opacity=".16"/>
${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"')}
</g>
<g id="Card 02 Living Guizhou">
<rect x="512" y="1556" width="416" height="543" fill="#173126"/>
<image x="512" y="1556" width="416" height="320" preserveAspectRatio="xMidYMid slice" xlink:href="${images.culture}" clip-path="url(#card2)"/>
${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"')}
<line x1="538" y1="2054" x2="902" y2="2054" stroke="#f3efe5" stroke-opacity=".16"/>
${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"')}
</g>
<g id="Card 03 Landscape Guizhou">
<rect x="952" y="1532" width="416" height="543" fill="#173126"/>
<image x="952" y="1532" width="416" height="320" preserveAspectRatio="xMidYMid slice" xlink:href="${images.landscape}" clip-path="url(#card3)"/>
${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"')}
<line x1="978" y1="2030" x2="1342" y2="2030" stroke="#f3efe5" stroke-opacity=".16"/>
${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"')}
</g>
</g>
<g id="User Flow Note">
<rect x="72" y="2160" width="1296" height="82" fill="none" stroke="#161b17" stroke-opacity=".19"/>
${text(98, 2208, 'USER FLOW', 'label')}
${text(250, 2208, '首屏产生兴趣 → 过渡层确认“下面有内容” → 三张卡片理解三种核心产品 → 进入体验页或组合行程', 'body')}
</g>
<g id="Footer">
<rect y="2282" width="1440" height="46" fill="#e8e2d5"/>
${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"')}
</g>
</g>
</svg>`
const output = path.join(root, linkedAssets ? 'figma-home-structure-linked.svg' : 'figma-home-structure.svg')
fs.writeFileSync(output, svg)
console.log(output)