feat: refine homepage structure and visual presentation
This commit is contained in:
170
scripts/build_figma_home_structure_svg.mjs
Normal file
170
scripts/build_figma_home_structure_svg.mjs
Normal file
@@ -0,0 +1,170 @@
|
||||
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') =>
|
||||
`<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)
|
||||
Reference in New Issue
Block a user