Replace approximate recipes with 27 source-matched component layouts
wiki CI and deployment / verify-and-deploy (push) Successful in 16m40s
wiki CI and deployment / verify-and-deploy (push) Successful in 16m40s
This commit is contained in:
@@ -1,63 +1,52 @@
|
||||
import recipes from './recipes.json';
|
||||
import layouts from './reference/manifest.json';
|
||||
import type {Block} from '../skills/catalog';
|
||||
import type {GuidanceDocument,GuidanceResource} from '../skills/guidance';
|
||||
import {guidanceUrl} from '../page-links';
|
||||
export {recipes};
|
||||
export const baseCss=`*{box-sizing:border-box}
|
||||
body{margin:0;background:#e4e6e1;color:#243230}
|
||||
.pl-frame{width:min(100%,1080px);margin-inline:auto;container-type:inline-size}
|
||||
.pl-sheet{--paper:#f5f3ec;--ink:#243230;--muted:#9aa6a5;--deep:#5d706e;position:relative;isolation:isolate;width:100%;aspect-ratio:3/4;overflow:hidden;background:var(--paper);color:var(--ink);font-size:1cqw;font-family:Arial,"PingFang SC","Microsoft YaHei",sans-serif}
|
||||
.pl-sheet .panel{position:absolute;background:var(--muted)}
|
||||
.pl-sheet .title{position:absolute;margin:0;font-weight:500;line-height:1.12;max-width:86%;overflow-wrap:anywhere}
|
||||
.pl-sheet .copy{position:absolute;margin:0;font-size:2.22em;line-height:1.65;white-space:pre-line}
|
||||
.pl-sheet .edition{position:absolute;left:7%;right:7%;bottom:2.5%;display:flex;justify-content:space-between;border-top:.1em solid currentColor;padding-top:.7em;font-size:1.6em;letter-spacing:.08em}
|
||||
.pl-sheet.monumental-type .edition,.pl-sheet.split-contrast .edition{color:var(--paper)}
|
||||
@media print{@page{size:1080px 1440px;margin:0}body{background:transparent}.pl-frame{width:1080px;max-width:none}.pl-sheet{print-color-adjust:exact;-webkit-print-color-adjust:exact}}`;
|
||||
export {layouts};
|
||||
const p=(text:string):Block=>({type:'paragraph',text});
|
||||
const h=(text:string):Block=>({type:'heading',text});
|
||||
const code=(text:string):Block=>({type:'code',text});
|
||||
const links=(...refs:string[]):Block=>({type:'links',refs});
|
||||
const path=(slug:string)=>'premium-layout/'+slug;
|
||||
const doc=(slug:string,title:string):GuidanceResource=>({id:slug,kind:'document',title,documentPath:path(slug),href:guidanceUrl(path(slug))+'?agent_mode=True',human:guidanceUrl(path(slug))});
|
||||
const image=(slug:string,title:string):GuidanceResource=>({id:'cover',kind:'image',title,href:'/layout-recipes/'+slug+'.png'});
|
||||
export function recipeCss(recipe:typeof recipes[number]){return baseCss+'\n'+recipe.css.replaceAll('.sheet','.pl-sheet');}
|
||||
export function posterMarkup(recipe:typeof recipes[number]){return `<div class="pl-frame"><article class="pl-sheet ${recipe.slug}" aria-label="${recipe.name}排版示例">\n${recipe.markup}\n<div class="edition"><span>FORM / STUDIES</span><span>2026 · ${String(recipes.indexOf(recipe)+1).padStart(2,'0')}</span></div>\n</article></div>`;}
|
||||
export function recipeHtml(recipe:typeof recipes[number]){return `<!doctype html>\n<html lang="zh-CN">\n<head>\n<meta charset="utf-8">\n<meta name="viewport" content="width=device-width,initial-scale=1">\n<title>${recipe.name} · 排版示例</title>\n<style>\n${recipeCss(recipe)}\n</style>\n</head>\n<body>\n${posterMarkup(recipe)}\n</body>\n</html>`;}
|
||||
export function recipeReact(recipe:typeof recipes[number]){
|
||||
const title=recipe.markup.match(/<h1 class="title">([\s\S]*?)<\/h1>/)![1].replace(/<br\s*\/?\s*>/g,' ');
|
||||
const subtitle=recipe.markup.match(/<p class="copy">([\s\S]*?)<\/p>/)![1];
|
||||
const jsx=posterMarkup(recipe).replace(/<h1 class="title">[\s\S]*?<\/h1>/,'<h1 class="title">{title}</h1>').replace(/<p class="copy">[\s\S]*?<\/p>/,'<p class="copy">{subtitle}</p>').replaceAll('class=','className=').replaceAll('<br>','<br />');
|
||||
return `// components/layouts/${recipe.slug}/LayoutPoster.tsx\ntype PosterProps = { title?: string; subtitle?: string };\nexport default function LayoutPoster({title = ${JSON.stringify(title)}, subtitle = ${JSON.stringify(subtitle)}}: PosterProps) {\n return (\n${jsx}\n );\n}`;
|
||||
}
|
||||
export function recipeNext(recipe:typeof recipes[number]){return `// app/designs/${recipe.slug}/page.tsx\nimport type { Metadata } from 'next';\nimport LayoutPoster from '@/components/layouts/${recipe.slug}/LayoutPoster';\nimport '@/components/layouts/${recipe.slug}/poster.css';\nexport const metadata: Metadata = { title: '${recipe.name} | FORM STUDIES' };\nexport default function Page() {\n return <main><LayoutPoster /></main>;\n}`;}
|
||||
export const layoutDocuments:GuidanceDocument[]=[{
|
||||
path:path('router'),title:'高级感排版 · 设计 Router',collection:'高级感排版',
|
||||
resources:[image('router','12 套构图的结构索引'),...recipes.map((r,i)=>doc(r.slug,`${String(i+1).padStart(2,'0')} · ${r.name}`))],
|
||||
blocks:[p('把“高级感”变成可执行的构图决策:先识别内容,再选择一套主构图,最后按 HTML、React 或 Next.js 环境实现。这里提供 12 套独立排版方法,风格来自用户提供的灰绿与纸白排版拼贴参考;所有示意图均为重新编排的结构图,不是原图拆分、原作者模板或代码效果截图。'),
|
||||
h('输入协议'),p('收集用途(海报、封面、网页首屏或正文)、画板比例、主题标题、正文长度、主图数量、品牌色、素材来源与输出技术栈。缺省使用 1080×1440、4—8 字中文标题、40 字以内导语、纸白与灰绿。若用户给出品牌规范或尺寸,优先遵守。内容超过 90 字,先拆成多页或独立阅读正文。'),
|
||||
code(JSON.stringify({task:'展览预告',canvas:{width:1080,height:1440},title:'秩序之间',body:'一段不超过 40 字的导语',images:2,tone:'克制、编辑感',stack:'next-app',output:['源码','PNG','检查记录']},null,2)),
|
||||
h('选择器:先按信息结构筛选'),
|
||||
p(recipes.map((r,i)=>`${String(i+1).padStart(2,'0')} / ${r.name}:${r.scene}。避免:${r.avoid}。`).join('\n')),
|
||||
links(...recipes.map(r=>r.slug)),
|
||||
h('调度规则与冲突优先级'),p('先满足可读性与内容完整,再满足品牌规范,最后考虑情绪。只有一张主图且短中文标题→窄轨竖排;两张主图→错位双矩形;三至六项并列→模块网格;两种观点→双区对照;文字宣言→巨字;文化导读→衬线刊头;其余按主题在圆形、弧面、色带、卡片、标注和字框之间选择。一页只选一套主构图,可借用另一套的一个小元素;不要同时叠加巨字、斜卡、圆环和多组线条。'),
|
||||
code(`type Input = { items: number; images: number; verticalTitle?: boolean; comparison?: boolean; statement?: boolean; editorial?: boolean };\nexport function selectLayout(x: Input) {\n if (x.items >= 3 && x.items <= 6) return 'modular-grid';\n if (x.comparison) return 'split-contrast';\n if (x.images === 2) return 'offset-panels';\n if (x.images === 1 && x.verticalTitle) return 'vertical-rail';\n if (x.statement) return 'monumental-type';\n if (x.editorial) return 'editorial-serif';\n return 'organic-ellipse';\n}\n// 这是首轮候选选择器;items > 6 或长正文先分页,再由设计师审阅。`),
|
||||
h('三条实现路线'),p('HTML:子文档中的完整 HTML 保存为 poster.html 即可运行。React:将 TSX 放在文档标明的组件目录,将同篇 CSS 代码保存为 poster.css,在应用入口 import CSS。Next.js App Router:使用同篇 TSX、CSS 和 page.tsx;保留项目已有根布局。示例中的 @/ 指向项目根目录;使用 src/ 结构时,所有路径整体移到 src/ 下,并沿用项目别名。此系列是面向 App Router 的使用文档;当前知识站本身沿用已有架构。'),
|
||||
h('Next.js:新项目根布局'),code(`// app/layout.tsx;已有项目合并 lang 等必要设置,不重复嵌套 html/body。\nimport type { ReactNode } from 'react';\nexport default function RootLayout({ children }: { children: ReactNode }) {\n return <html lang="zh-CN"><body>{children}</body></html>;\n}`),
|
||||
h('图片与字体'),p('所有色块都是显式结构占位,不是已加载的照片。有图片时把目标 panel div 改为 figure 并清除默认 margin;使用绝对定位的 img、width:100%、height:100%、object-fit:cover。先核对人物和产品是否被裁切;需要完整展示时改用 contain。正文 alt 描述实际图片;纯装饰使用空 alt。Next.js 使用下列 Image 写法;提供自己的本地照片,不复制参考图的品牌、二维码或未知授权素材。'),
|
||||
code(`// 放在目标 panel 内;父容器保留对应布局的 absolute 与宽高,并设置 margin:0。\nimport Image from 'next/image';\n// public/hero.jpg 由你提供;sizes 表示图片显示宽度,不是原图像素。\n<figure className="panel panel-a">\n <Image src="/hero.jpg" alt="描述实际主体" fill sizes="(max-width: 1080px) 68vw, 734px" style={{objectFit:'cover'}} />\n</figure>\n// sizes 的 68% 仅对应双矩形主块;其他构图按自己的宽度调整。`),
|
||||
p('默认示例使用系统字体,因设备不同可能换行。正式交付使用已获授权的本地中文字体和拉丁字体,限定两类字体;校验字重、缺字与标点。Next.js 可在根布局用 next/font/local 接入,路径相对该文件;只有提供了真实文件才启用。'),
|
||||
code(`// 可选 app/layout.tsx;先放入 app/fonts/editorial.woff2。\nimport localFont from 'next/font/local';\nimport type { ReactNode } from 'react';\nconst editorial = localFont({src:'./fonts/editorial.woff2',display:'swap'});\nexport default function RootLayout({children}:{children:ReactNode}) {\n return <html lang="zh-CN"><body className={editorial.className}>{children}</body></html>;\n}`),
|
||||
h('交互、高级工具与边界'),p('静态海报组件保持纯渲染;在 App Router 中可直接作为 Server Component。调色器、拖拽和动画控制单独放到带 use client 的小组件,通过 props 传入标题和配色。不要在服务端组件中读取 window,不把整页静态排版改为客户端渲染。需要自由曲线或严格字距时使用 SVG;需要复杂材质时再引入 Canvas/WebGL,并保留文字 DOM、静态后备图与 prefers-reduced-motion 支持。Figma 等工具可复刻同一网格与令牌;最终交付必须保留可编辑源文件。'),
|
||||
h('统一令牌与代码组合'),p('每套示例独立运行;同一网页并列展示多套时,base CSS 只引入一次,各篇布局 CSS 按 slug 命名。示例中的 body 样式只适用于独立海报页,接入现有产品时移除;所有 .pl-frame / .pl-sheet 类应保留以维持容器单位。最小留白为画板宽的 7%(约 76px),底部版本行例外;正文默认 24px,说明至少 18px。深灰绿只用作形状与大字背景,小字优先深墨色与纸白。'),
|
||||
h('海报缩放与网页阅读模式'),p('1080×1440 示例按整张海报等比缩放,因此手机上小字会随画板缩小,不能直接用作长文阅读页。网页交付时在海报后提供真实标题与正文,或采用下面的 reading 模式,取消绝对定位与裁切,以 16px 以上正文、自然高度重排。装饰巨字与圆环在阅读模式隐藏;视觉海报与阅读版之间避免重复朗读,可把纯展示海报整体标 aria-hidden。'),
|
||||
code(`/* 可选:把 reading 类加在 pl-frame 上,用于网页内容流。 */\n.reading .pl-sheet{aspect-ratio:auto;overflow:visible;font-size:10px;padding:32px 24px}\n.reading .pl-sheet .title,.reading .pl-sheet .copy,.reading .pl-sheet .answer,.reading .pl-sheet .sub{position:static;width:auto;max-width:none;writing-mode:horizontal-tb;color:var(--ink);margin:0 0 24px}\n.reading .pl-sheet .title,.reading .pl-sheet .answer,.reading .pl-sheet .sub{font-size:32px}\n.reading .pl-sheet .copy{font-size:16px}\n.reading .pl-sheet .panel,.reading .pl-sheet .orbit,.reading .pl-sheet .oval,.reading .pl-sheet .band,.reading .pl-sheet .dark,.reading .pl-sheet .measure,.reading .pl-sheet .tag,.reading .pl-sheet .rule,.reading .pl-sheet .display,.reading .pl-sheet .corners,.reading .pl-sheet .center{display:none}\n.reading .pl-sheet .grid{position:static;display:grid;grid-template-columns:1fr;gap:16px}\n.reading .pl-sheet .cell{font-size:16px;padding:16px}\n.reading .pl-sheet .edition{position:static;font-size:12px;margin-top:32px}`),
|
||||
h('执行、验收与交付协议'),p('1. 先输出 layout_id、选择理由与内容预算。2. 阅读对应子文档,生成源码并替换所有占位内容。3. 对最终页面在 1080×1440 截图,并检查 375px 与 768px:无意外横向溢出、主标题不裁切、图片主体完整。4. 验证阅读顺序、替代文本与实际文字对比;普通正文目标至少 4.5:1,大字至少 3:1。5. 若标题超出预算,先改字或减字号,再缩减装饰,不能悄悄裁掉语义文字。6. 交付源代码、使用字体与素材说明、PNG/PDF 和检查记录。'),
|
||||
p('使用截图工具时等待 document.fonts.ready 与图片 decode;若使用动画,先冻结在指定帧。确认画板真实像素为 1080×1440、deviceScaleFactor=1,避免生成双倍尺寸。示意封面仅展示构图语法;生产导出必须重新渲染自己的实际源码。'),
|
||||
code(JSON.stringify({layout_id:'offset-panels',reason:'两张主图,主次明确',stack:'next-app',files:['LayoutPoster.tsx','poster.css','page.tsx'],checks:{canvas:'1080x1440',title:'待验收',mobile:'待验收',contrast:'待验收'},assets:'填写字体与图片来源'},null,2)),
|
||||
const doc=(id:string,title:string):GuidanceResource=>({id,kind:'document',title,documentPath:'premium-layout/'+id,href:guidanceUrl('premium-layout/'+id)+'?agent_mode=True',human:guidanceUrl('premium-layout/'+id)});
|
||||
const file=(id:string,title:string,href:string):GuidanceResource=>({id,kind:'document',title,href});
|
||||
const image=(id:string,title:string,href:string):GuidanceResource=>({id,kind:'image',title,href});
|
||||
const typeName=(kind:string)=>kind==='text'?'文字':kind==='image'?'图片占位 / 色块':'装饰';
|
||||
const sourceNote='原始拼图仅 736×980,每张约 112×173 像素。小字无法可靠辨认;其内容标为待核对,并保存独立原字形图层。放大不会恢复缺失信息。灰绿色区域为原设计中的图片占位 / 色块,参考图没有可辨认的真实照片。';
|
||||
export const legacyLayoutIds=['5971fc3d2563aa8005df5c13','9ad47529a7eaeee1fef771ca','7f158059ba3a2b80dd750cc3','370d1881f229af63f1946df9','31a978620a0d388f84772c95','c34cf81137654ad38850c9bd','dbe94ccaa708bd120549ae86','768587e537d6f1ee77a51961','0962c2bf1c558e0e32425f59','347b732caa4af4afa43b65a4','3edf144a443c1fdf23827fb3','97227671a9dc95c4948e591b'];
|
||||
export function reactExample(id:string){return `// components/Example.tsx\nimport ReferenceLayout from './reference-layouts/ReferenceLayout';\nexport default function Example() {\n return <ReferenceLayout id="${id}" />;\n}\n// 原样模式使用该 ID 的独立图层,保留原字形与原构图。\n// 组件、manifest.json 和 public/layout-reference 素材必须一起复制。`}
|
||||
export function nextExample(id:string){return `// app/designs/${id}/page.tsx\nimport type { Metadata } from 'next';\nimport ReferenceLayout from '@/components/reference-layouts/ReferenceLayout';\nexport const metadata: Metadata = {title:'${id} · 原图对应排版'};\nexport default function Page() {\n return <main><ReferenceLayout id="${id}" /></main>;\n}\n// 沿用项目根布局。@/ 指向源码根,组件可直接作为 Server Component。`}
|
||||
export function htmlExample(id:string){return `<!doctype html>\n<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${id}</title>\n<style>body{margin:0}.canvas{position:relative;width:min(100%,672px);background:#f4f3ee}.canvas img{position:absolute;display:block}</style></head>\n<body><figure id="canvas" class="canvas" aria-label="${id} 原图构件重组"></figure>\n<script type="module">\nconst id='${id}';\nconst response=await fetch('/layout-reference/'+id+'/manifest.json');\nif(!response.ok)throw Error('构件清单未加载');\nconst m=await response.json();\nif(m.id!==id)throw Error('版面 ID 不一致');\nconst canvas=document.getElementById('canvas');\ncanvas.style.aspectRatio=m.width+'/'+m.height;\nconst bg=document.createElement('img');bg.src=m.background;bg.alt='';bg.style.cssText='inset:0;width:100%;height:100%';canvas.append(bg);\nfor(const e of m.elements){\n const [x,y,w,h]=e.box;const img=document.createElement('img');\n img.src=e.asset;img.dataset.component=id+'/'+e.id;img.dataset.kind=e.kind;\n img.alt=e.kind==='text'?(e.text||e.role+':未辨识原字形'):'';\n Object.assign(img.style,{left:x/m.width*100+'%',top:y/m.height*100+'%',width:w/m.width*100+'%',height:h/m.height*100+'%',zIndex:e.kind==='text'?'3':e.kind==='decoration'?'2':'1'});\n canvas.append(img);\n}\n</script><!-- 同站点服务 /layout-reference 素材;不要以 file:// 打开。 --></body></html>`}
|
||||
const router:GuidanceDocument={
|
||||
path:'premium-layout/router',title:'高级感排版 · 27 张原图 Router',collection:'高级感排版',
|
||||
resources:[image('source-map','27 张原图编号对应图','/layout-reference/numbered-source.png'),file('inspector','27 张构件检查器','/layout-reference/index.html'),file('index-json','27 张机器索引','/layout-reference/index.json'),file('runtime','React / TSX 完整组件源文件','/layout-reference/ReferenceLayout.tsx'),file('manifest','React 组件配套 manifest.json','/layout-reference/manifest.json'),file('bundle','27 张完整源码与素材 ZIP','/layout-reference/layout-reference-toolkit.zip'),...layouts.map(l=>doc(l.id,l.id+' · '+l.name))],
|
||||
blocks:[p('本系列严格对应用户参考图中的 27 张独立设计,每张保留自己的原图裁片、文字、图片占位 / 色块、装饰和坐标。左侧“高级感排版 / 模板合集”说明栏不计入。编号从上到下、每行从左到右:前 3 行各 5 张,后 2 行各 6 张,共 27 张设计 post,另有本篇 Router。'),
|
||||
h('先查原图编号,再调用固定 ID'),links('inspector','index-json','bundle'),p('指定 ref-01 至 ref-27 中的一个 ID。禁止把旧的“同心圆”“巨字”等类型名当成任意近似模板;每个 ID 只对应一个 source.box、一份 manifest 和本设计的构件。无法确定编号时先返回候选原图裁片,由使用者选择,再生成。'),
|
||||
p('| ID | 原图位置(含左侧说明栏所在列) | 对应设计 |\n| --- | --- | --- |\n'+layouts.map(l=>`| ${l.id} | 第 ${l.source.row} 行、第 ${l.source.column} 列 | ${l.name} |`).join('\n')),
|
||||
links(...layouts.map(l=>l.id)),
|
||||
h('识别算法与人工复核'),p('1. 从拼图外沿采样灰底,按 RGB 最大通道差 >19 建立前景二值掩膜。2. 四邻域连通域提取外框,按宽 86–124px、高 146–189px 过滤,检测出 27 张卡片。3. 按 y 坐标聚成 5 行,行内按 x 排序,保存绝对裁片坐标。4. 使用本机 macOS Vision 提取 OCR 候选与置信度,再逐张复核文字区、色块与装饰 ROI。5. 根据各图位的局部颜色种子建立图形掩膜,文字前景独立分离;残余字形按基线、间距、字高与已有文字块合并。6. 对所有独立图层重组并执行像素比较。'),
|
||||
p('OCR 置信度不是事实保证;本清单对可读标题进行了人工核对。ref-01 的品牌字标整体为 T01,不能把末尾字符再标为 T03。编号在一张图内按文字 T、图片占位 I、装饰 D 分组;对外调用使用完整键 ref-01/T01,防止跨版面混用。'),
|
||||
h('三类构件及背景'),p('T / text:标题、正文、品牌字、日期、图注与字距标签。文字不属于图片区;已核对内容记录在 text 字段,无法辨认的内容为 null。I / image:原设计的图片占位或灰绿色色块。它们可由实际照片替换,原始形状和位置由 mask 保持;本参考没有真实照片。D / decoration:线框、圆环、圆节点、引线、分隔线与贴条。纸底与原拼图画框单独保存在 background。'),
|
||||
h('必须区分:保真重组与可编辑替换'),p('默认模式按分类好的原图构件重组,保留原字形和像素;它不是高分辨率矢量重绘,也不意味着 OCR 全部识别正确。替换模式按同一 ID 和原坐标渲染新文字或新图片;字体与内容变化会产生视觉差异。替换过的版本不得称为与原图逐像素一致。曲线文字保留原字形,若要替换须沿该版面的专用 SVG 路径排字,通用组件会拒绝普通文本替换,以免把弧形改成直排。'),p(sourceNote),
|
||||
h('严格调用协议'),code(JSON.stringify({layoutId:'ref-01',mode:'reference',sourceBox:layouts[0].source.box,components:{'ref-01/T01':{kind:'text',role:'品牌字标'},'ref-01/T02':{kind:'text',role:'左下说明'},'ref-01/I01':{kind:'image',role:'上方主图'},'ref-01/I02':{kind:'image',role:'下方副图'}}},null,2)),
|
||||
h('React / TSX 与 Next.js 文件准备'),links('runtime','manifest','bundle'),p('将 ZIP 中 components/reference-layouts 放进项目组件目录,将 public/layout-reference 放到项目 public 下。React 项目直接导入组件使用;Next.js App Router 可将组件保留为 Server Component。已有根布局继续沿用,设置中文 lang,按项目路径调整 @/ 别名。只有图层开关、拖拽或编辑器等交互外壳需要 use client;图片和字形保真层使用原生 img 避免优化改变原像素或透明遮罩。'),
|
||||
code(reactExample('ref-01')),code(nextExample('ref-01')),
|
||||
h('替换必须按构件类型和完整键执行'),code(`import ReferenceLayout from './reference-layouts/ReferenceLayout';\nexport default function Customized() {\n return <ReferenceLayout id="ref-01"\n text={{'ref-01/T01':'MY BRAND'}}\n images={{'ref-01/I01':'/photos/main.jpg','ref-01/I02':'/photos/detail.jpg'}}\n />;\n}\n// ref-02/T01 不能传给 ref-01;I01 不能作为文字键。运行时会拒绝。\n// 保留某个构件原样:不要传对应键。未知细字不能猜写。`),
|
||||
h('字体、图片与高级工具'),p('参考图中的字形已保存在独立文字资产;尚不能从低清拼图可靠确定具体字体。编辑时使用用户提供且已授权的中文 / 拉丁字体,先检查横排、竖排、旋转和倾斜方向,再调整字距与行距。SVG 适用于弧形文字和可编辑轮廓,Figma 可按每张 manifest 的源坐标建立画板与图层;不要重新套用另一张版面的圆形、网格或色块位置。图片替换采用 cover,并保留本设计的 mask;需要完整主体时先检查裁切。'),
|
||||
h('验收与停止条件'),p('逐张核对原裁片、编号和 source.box;检查文字、图片占位和装饰三类掩膜。默认模式必须逐像素重组一致;该检查只证明资产重组保真,不证明语义标注或 OCR 一定正确,仍须核对标注图。特别检查同一单词是否被拆成两个 T、数字是否被旋转标题吞并、底线是否被并入文字。调用时不能跨设计借构件。更换内容后再次检查溢出、换行、透明边缘与图片裁切。网页正文另外提供可读的 DOM 文本,不把 112×173 的海报小字当作移动端正文。'),
|
||||
],
|
||||
},...recipes.map((r,i):GuidanceDocument=>({
|
||||
path:path(r.slug),title:`${String(i+1).padStart(2,'0')} · ${r.name}`,collection:'高级感排版',
|
||||
resources:[image(r.slug,r.name+' · 结构示意,非代码截图'),doc('router','总览 Router · 选择、技术栈与交付协议'),{id:'demo',kind:'document',title:'打开可运行 HTML 示例',href:'/layout-recipes/'+r.slug+'.html'}],
|
||||
blocks:[p(`${r.name}:${r.scene}。本篇是一套可执行的设计配方;封面是独立绘制的构图示意,实际示例以 HTML/CSS 与 React 组件为准。`),h('何时使用与停止条件'),p(`适用:${r.scene}。不适用:${r.avoid}。标题默认 4—8 个汉字、正文不超过 40 字;超出时先拆页或转阅读版。品牌色与素材可替换,保持构图关系。`),h('构图参数'),p(r.geometry),p('默认 1080×1440,3:4;纸白 #f5f3ec,灰绿 #9aa6a5,深灰绿 #5d706e,文字 #243230。字号按画板缩放:1em=10.8px,正文 2.22em≈24px。网页阅读模式、字体与图片替换规则由总览 Router 统一管理。'),h('制作步骤'),p(r.steps),h('HTML / CSS:完整独立文件'),p('复制以下完整代码到 poster.html;默认无需网络资源。色块为占位,替换素材时遵守 Router 的图像与版权协议。'),code(recipeHtml(r)),links('demo'),h('React / TSX:可复用组件'),p(`保存为 components/layouts/${r.slug}/LayoutPoster.tsx;支持 title 与 subtitle 字符串 props。把下段 CSS 保存为同目录 poster.css,并在 React 应用入口导入。静态组件不需要 use client。`),code(recipeReact(r)),h('React 与 Next.js 共用 CSS'),code(recipeCss(r)),h('Next.js App Router:路由接入'),p(`下面的 page.tsx 对应 /designs/${r.slug},使用上面的组件和 CSS。新项目根布局、路径别名、next/image、next/font/local 与交互边界见 Router;已有项目保留自己的根布局。`),code(recipeNext(r)),h('本构图的验收标准'),p(r.check),p('共同检查:1080×1440 下标题完整、正文与底部版本行不重叠;375px 和 768px 下等比海报无意外横向滚动。若用于网页正文,启用 Router 的阅读模式并逐项检查。所有示例只有系统字体与结构占位,替换真实标题、字体和图片后须重新验收。'),h('变体与修改顺序'),p('先替换真实内容,再调整标题字号与说明长度,随后微调图形位置;最后才改变背景色和装饰。只调整该 slug 下的布局规则,避免影响其他构图。交付保留 HTML 或 TSX/CSS 源码、素材说明与导出文件。'),links('router'),
|
||||
],
|
||||
}))];
|
||||
};
|
||||
export const layoutDocuments:GuidanceDocument[]=[router,...layouts.map((l):GuidanceDocument=>{
|
||||
const title=l.id+' · '+l.name;
|
||||
const texts=l.elements.filter(e=>e.kind==='text'),images=l.elements.filter(e=>e.kind==='image'),decor=l.elements.filter(e=>e.kind==='decoration');
|
||||
return {path:'premium-layout/'+l.id,title,collection:'高级感排版',resources:[image('reference',l.id+' · 原图裁片',l.reference),image('annotated',l.id+' · 文字/图片/装饰标注',l.annotated),image('text-map',l.id+' · 仅文字掩膜(蓝色)',`/layout-reference/${l.id}/text-map.png`),image('image-map',l.id+' · 仅图片占位/色块(橙色)',`/layout-reference/${l.id}/image-map.png`),image('decor-map',l.id+' · 仅装饰掩膜(绿色)',`/layout-reference/${l.id}/decoration-map.png`),doc('router','27 张原图 Router'),file('inspect','打开本设计构件检查器',`/layout-reference/${l.id}/index.html`),file('json','本设计完整构件 JSON',`/layout-reference/${l.id}/manifest.json`),file('runtime','React 完整组件','/layout-reference/ReferenceLayout.tsx'),file('bundle','完整源码与素材 ZIP','/layout-reference/layout-reference-toolkit.zip')],blocks:[
|
||||
p(`${l.id} 只对应原图第 ${l.source.row} 行、可见第 ${l.source.column} 列的“${l.name}”。原裁片坐标 [x,y,w,h] = [${l.source.box.join(', ')}];本篇逐项列出文字 ${texts.length} 组、图片占位/色块 ${images.length} 组、装饰 ${decor.length} 组,不替换成近似构图。`),links('inspect','json'),
|
||||
h('原图与分类图怎么看'),p('本 post 的第 1 张是直接裁出的原版面,第 2 张标出构件框,第 3 张只显示蓝色文字掩膜,第 4 张只显示橙色图片区掩膜,第 5 张只显示绿色装饰。T 与 I 可在坐标上叠放,例如标题压住图位;它们仍是不同类型,不能合并成图片。检查器可独立关闭各类构件并点击表格定位。'),
|
||||
h('构件逐项对应表'),p('| 完整构件键 | 类型 | 用途 | 本裁片坐标 x,y,w,h | 内容 / 状态 |\n| --- | --- | --- | --- | --- |\n'+l.elements.map(e=>`| ${l.id}/${e.id} | ${typeName(e.kind)} | ${e.role} | ${e.box.join(', ')} | ${e.kind==='text'?(e.text??'待核对,保留原字形'):'非文字'} |`).join('\n')),
|
||||
h('布局与制作步骤'),p(`1. 以 ${l.width}×${l.height} 的源坐标建立画板;输出可等比放大,比例必须保持 ${l.width}:${l.height}。2. 建立本设计的 ${images.map(e=>e.id+' '+e.role).join('、')},使用对应 mask,保持它们各自位置。3. 单独放置 ${texts.map(e=>e.id+' '+e.role).join('、')},按表内方向与坐标定位。4. 还原 ${decor.map(e=>e.id+' '+e.role).join('、')||'原设计已有的细节'},不得借用别的设计。5. 与原裁片叠加比较,再检查文字是否被图层遮挡。`),
|
||||
h('文字和图层的具体规则'),p(texts.map(e=>`${e.id}:${e.direction==='vertical'?'竖排':e.direction==='rotated'?'整体旋转排字':e.direction==='angled'?'倾斜排字':e.direction==='curved'?'沿曲线排字':'横排'};${e.text?'已核对可读内容“'+e.text+'”':'内容无法可靠辨认,保留原字形,不编造正文'}。`).join('\n')),p(sourceNote),
|
||||
h('HTML / CSS:固定本设计 ID'),code(htmlExample(l.id)),
|
||||
h('React / TSX:固定本设计组件'),code(reactExample(l.id)),links('runtime','bundle'),
|
||||
h('Next.js App Router:固定本设计路由'),code(nextExample(l.id)),
|
||||
h('本设计的验收标准'),p(`layoutId 必须等于 ${l.id},source.box 必须为 [${l.source.box.join(', ')}];所有构件资产必须来自 /layout-reference/${l.id}/。核对本篇 ${texts.length} 组 T、${images.length} 组 I、${decor.length} 组 D 的归属和坐标。默认原字形模式的图层拼回与原裁片一致;文字替换模式则按布局与可读性验收,不再声称像素一致。原字标、日期和细字不能因为识别残余而重复编号。`),links('router'),
|
||||
]};
|
||||
})];
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
[
|
||||
{
|
||||
"slug": "offset-panels",
|
||||
"name": "错位双矩形",
|
||||
"scene": "作品集封面、摄影选集、两张主图的品牌提案",
|
||||
"avoid": "三张以上同等重要的图;长篇正文",
|
||||
"geometry": "主块 x=18%、y=16%、w=68%、h=37%;副块 x=37%、y=57%、w=49%、h=31%;左侧留下文字呼吸带。",
|
||||
"steps": "先让两块右边缘对齐;再用不等宽与 4% 的竖向间隔制造节奏;把标题留在左上,说明压到左下。",
|
||||
"check": "两块不接触;主块面积大于副块;标题与图块相距至少 24px;替换图片后两个容器比例不变。",
|
||||
"css": ".sheet.offset-panels .panel-a{left:18%;top:16%;width:68%;height:37%}.sheet.offset-panels .panel-b{left:37%;top:57%;width:49%;height:31%;background:var(--deep)}.sheet.offset-panels .title{left:7%;top:6%;font-size:6em}.sheet.offset-panels .copy{left:7%;top:66%;width:25%}",
|
||||
"markup": "<div class=\"panel panel-a\" aria-hidden=\"true\"></div>\n<div class=\"panel panel-b\" aria-hidden=\"true\"></div>\n<h1 class=\"title\">秩序之间</h1>\n<p class=\"copy\">以留白建立关系,\n让视线缓慢移动。</p>",
|
||||
"shapes": "<rect x=\"194\" y=\"230\" width=\"734\" height=\"533\" fill=\"#9aa6a5\"/><rect x=\"400\" y=\"821\" width=\"529\" height=\"446\" fill=\"#5d706e\"/>"
|
||||
},
|
||||
{
|
||||
"slug": "vertical-rail",
|
||||
"name": "窄轨竖排",
|
||||
"scene": "东方文化、书籍封面、展览预告;短中文标题",
|
||||
"avoid": "长英文标题;必须快速扫读的大段信息",
|
||||
"geometry": "主图占左侧 64%×76%,位于 x=7%、y=12%;右侧 14% 为竖排轨道;中间留 5% 通道。",
|
||||
"steps": "先画主矩形;右侧建立独立竖排栏;中文使用 vertical-rl,数字日期保持横向;底部信息与主图左边对齐。",
|
||||
"check": "竖排标题不超过 8 个汉字;拉丁单词不要逐字竖排;正文横排;文字不进入主图安全区。",
|
||||
"css": ".sheet.vertical-rail .panel-a{left:7%;top:12%;width:64%;height:76%;background:var(--deep)}.sheet.vertical-rail .title{right:8%;top:13%;writing-mode:vertical-rl;font-size:6.3em;letter-spacing:.15em}.sheet.vertical-rail .copy{left:7%;bottom:5%;width:64%}",
|
||||
"markup": "<div class=\"panel panel-a\" aria-hidden=\"true\"></div>\n<h1 class=\"title\">静物的秩序</h1>\n<p class=\"copy\">空间研究 / 第二辑 / 2026</p>",
|
||||
"shapes": "<rect x=\"76\" y=\"173\" width=\"691\" height=\"1094\" fill=\"#5d706e\"/>"
|
||||
},
|
||||
{
|
||||
"slug": "monumental-type",
|
||||
"name": "巨字与裁切",
|
||||
"scene": "品牌宣言、创意节、单词主题封面",
|
||||
"avoid": "说明密集的教程内页;不可裁切的商标",
|
||||
"geometry": "中央横跨 96% 的巨字,字号 240px;底部色块高 40%;可裁切装饰英文,中文信息保持完整。",
|
||||
"steps": "先写完整可读的中文主题;再将一个英文词放大并裁掉外缘 4%;让巨字压住色块边界;日期与说明保持小尺寸。",
|
||||
"check": "裁切只作用于装饰字;H1 与正文不裁切;英文与主题有语义关联;字号层级至少 3:1。",
|
||||
"css": ".sheet.monumental-type .panel-a{left:0;bottom:0;width:100%;height:40%;background:var(--deep)}.sheet.monumental-type .display{position:absolute;left:-4%;top:26%;font:bold 24em/.8 Georgia,serif;letter-spacing:-.08em}.sheet.monumental-type .title{left:7%;top:10%;font-size:6em}.sheet.monumental-type .copy{left:7%;bottom:10%;width:60%;color:var(--paper)}",
|
||||
"markup": "<div class=\"panel panel-a\" aria-hidden=\"true\"></div>\n<div class=\"display\" aria-hidden=\"true\">FORM</div>\n<h1 class=\"title\">形式与留白</h1>\n<p class=\"copy\">让一个词成为画面的重心。\nDESIGN STUDIES / 2026</p>",
|
||||
"shapes": "<rect x=\"0\" y=\"864\" width=\"1080\" height=\"576\" fill=\"#5d706e\"/><text x=\"-40\" y=\"780\" font-family=\"Georgia,serif\" font-size=\"270\" font-weight=\"bold\" letter-spacing=\"-25\">FORM</text>"
|
||||
},
|
||||
{
|
||||
"slug": "orbital-rings",
|
||||
"name": "同心圆与轨道",
|
||||
"scene": "年度回顾、声音主题、循环或时间概念",
|
||||
"avoid": "方形实物必须完整展示的产品图;严肃数据图表",
|
||||
"geometry": "圆心位于画板 (50%,43%);直径 72% 画板宽;三层外环等距;下方 18% 为说明区。",
|
||||
"steps": "用 aspect-ratio 保持正圆;三层细线围绕同一圆心;一处小圆作节拍;标题和说明放在圆外,避免环形正文。",
|
||||
"check": "窄屏仍为正圆;环线不穿过文字;不要把装饰环误称数据刻度;有动画时尊重减少动态偏好。",
|
||||
"css": ".sheet.orbital-rings .orbit{position:absolute;left:14%;top:16%;width:72%;aspect-ratio:1;border-radius:50%;background:var(--deep);box-shadow:0 0 0 1.2em var(--paper),0 0 0 1.3em var(--muted),0 0 0 2.5em var(--paper),0 0 0 2.6em var(--muted),0 0 0 3.8em var(--paper),0 0 0 3.9em var(--muted)}.sheet.orbital-rings .title{left:7%;bottom:15%;font-size:6.5em}.sheet.orbital-rings .copy{left:7%;bottom:6%;width:80%}",
|
||||
"markup": "<div class=\"orbit\" aria-hidden=\"true\"></div>\n<h1 class=\"title\">周而复始</h1>\n<p class=\"copy\">时间的形状 / ANNUAL REVIEW / 2026</p>",
|
||||
"shapes": "<g fill=\"none\" stroke=\"#9aa6a5\" stroke-width=\"2\"><circle cx=\"540\" cy=\"620\" r=\"438\"/><circle cx=\"540\" cy=\"620\" r=\"424\"/><circle cx=\"540\" cy=\"620\" r=\"410\"/></g><circle cx=\"540\" cy=\"620\" r=\"389\" fill=\"#5d706e\"/>"
|
||||
},
|
||||
{
|
||||
"slug": "organic-ellipse",
|
||||
"name": "有机椭圆与弧面",
|
||||
"scene": "生活方式、艺术展、柔和的品牌视觉",
|
||||
"avoid": "精准工程数据;需要硬边产品轮廓的主视觉",
|
||||
"geometry": "宽 102%、高 62% 的椭圆自右侧出血;中心落在右半区;左上与左下各保留一块完整文字区。",
|
||||
"steps": "先建立椭圆,旋转 -12 度;只裁掉形状外缘;标题放左上,说明放左下;不要再叠加第二个大形状。",
|
||||
"check": "椭圆与标题无交叠;出血边缘不生成横向滚动;曲线的视觉重量在右侧;正文底部安全距至少 64px。",
|
||||
"css": ".sheet.organic-ellipse .oval{position:absolute;left:24%;top:27%;width:102%;height:62%;border-radius:50%;transform:rotate(-12deg);background:var(--deep)}.sheet.organic-ellipse .title{left:7%;top:10%;font-size:6.5em}.sheet.organic-ellipse .copy{left:7%;bottom:7%;width:40%}",
|
||||
"markup": "<div class=\"oval\" aria-hidden=\"true\"></div>\n<h1 class=\"title\">缓慢生长</h1>\n<p class=\"copy\">一条曲线,\n一处停顿。</p>",
|
||||
"shapes": "<ellipse cx=\"810\" cy=\"835\" rx=\"550\" ry=\"446\" transform=\"rotate(-12 810 835)\" fill=\"#5d706e\"/>"
|
||||
},
|
||||
{
|
||||
"slug": "modular-grid",
|
||||
"name": "模块网格与索引",
|
||||
"scene": "作品目录、系列课程、设计档案;3—6 个平级项目",
|
||||
"avoid": "只有一张图的情绪封面;长故事",
|
||||
"geometry": "12 栏思维;主体为两列三行,列间距 3%、行间距 2%;左上预留标题与索引。",
|
||||
"steps": "先确定信息项数量;使用等宽网格;每格保留编号与短标题;只用一个深色格建立主次,其余保持同一色阶。",
|
||||
"check": "顺序与 DOM 阅读顺序一致;每项标题不超过 8 字;格间距一致;内容超出六项时分页,不能挤小文字。",
|
||||
"css": ".sheet.modular-grid .grid{position:absolute;left:7%;right:7%;top:28%;bottom:13%;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2em}.sheet.modular-grid .cell{padding:2em;background:var(--muted);font-size:2.6em}.sheet.modular-grid .cell:first-child{background:var(--deep);color:var(--paper)}.sheet.modular-grid .title{left:7%;top:10%;font-size:6.5em}.sheet.modular-grid .copy{left:7%;bottom:5%;width:86%}",
|
||||
"markup": "<h1 class=\"title\">视觉档案</h1>\n<div class=\"grid\"><div class=\"cell\">01 / 观察</div><div class=\"cell\">02 / 结构</div><div class=\"cell\">03 / 材料</div><div class=\"cell\">04 / 字体</div><div class=\"cell\">05 / 色彩</div><div class=\"cell\">06 / 留白</div></div>\n<p class=\"copy\">INDEX / SIX DESIGN STUDIES</p>",
|
||||
"shapes": "<rect x=\"76\" y=\"403\" width=\"448\" height=\"250\" fill=\"#5d706e\"/><rect x=\"556\" y=\"403\" width=\"448\" height=\"250\" fill=\"#9aa6a5\"/><rect x=\"76\" y=\"681\" width=\"448\" height=\"250\" fill=\"#9aa6a5\"/><rect x=\"556\" y=\"681\" width=\"448\" height=\"250\" fill=\"#9aa6a5\"/><rect x=\"76\" y=\"959\" width=\"448\" height=\"250\" fill=\"#9aa6a5\"/><rect x=\"556\" y=\"959\" width=\"448\" height=\"250\" fill=\"#9aa6a5\"/>"
|
||||
},
|
||||
{
|
||||
"slug": "editorial-serif",
|
||||
"name": "衬线刊头与杂志栏",
|
||||
"scene": "文化杂志、建筑专题、品牌故事导读",
|
||||
"avoid": "紧急公告;复杂交互控制台",
|
||||
"geometry": "顶部刊头高 18%;中央图块占 58%×49%;右侧 25% 为短导语;底部期号区与分隔线对齐。",
|
||||
"steps": "用衬线字建立刊头;用无衬线字写导语;主图与旁栏共用顶部基线;正文超过 90 个汉字就另起阅读页。",
|
||||
"check": "全页最多两类字体;标题不超过两行;导语每行约 10—14 字;细线只分区,不包围所有段落。",
|
||||
"css": ".sheet.editorial-serif .title{left:7%;top:7%;font:7.8em/.95 Georgia,\"Songti SC\",serif}.sheet.editorial-serif .panel-a{left:7%;top:32%;width:58%;height:49%}.sheet.editorial-serif .copy{left:70%;top:33%;width:23%;font-size:2em;line-height:1.8}.sheet.editorial-serif .rule{position:absolute;left:7%;right:7%;top:27%;border-top:.15em solid var(--ink)}",
|
||||
"markup": "<h1 class=\"title\">Quiet<br>Design</h1>\n<div class=\"rule\" aria-hidden=\"true\"></div>\n<div class=\"panel panel-a\" aria-hidden=\"true\"></div>\n<p class=\"copy\">从一块留白开始,观察材料、光线与文字之间的关系。</p>",
|
||||
"shapes": "<rect x=\"76\" y=\"461\" width=\"626\" height=\"706\" fill=\"#9aa6a5\"/><path d=\"M76 389H1004\" stroke=\"#243230\" stroke-width=\"2\"/>"
|
||||
},
|
||||
{
|
||||
"slug": "horizontal-band",
|
||||
"name": "横向色带与断层",
|
||||
"scene": "章节扉页、品牌手册、主题分组页",
|
||||
"avoid": "三段以上相互独立的内容;密集菜单",
|
||||
"geometry": "中央色带贯穿 100% 宽,位于 y=34%—67%;标题跨上边界,说明置于底部左侧。",
|
||||
"steps": "先画全宽色带;把一个短主题放在色带上方;用浅底与深带产生节奏;底部元信息保持同一左边界。",
|
||||
"check": "标题不能被色带边界切成难读的两段;带内文字保持足够对比;色带只负责分区,不伪装成按钮。",
|
||||
"css": ".sheet.horizontal-band .band{position:absolute;left:0;top:34%;width:100%;height:33%;background:var(--deep)}.sheet.horizontal-band .title{left:7%;top:12%;font-size:7em}.sheet.horizontal-band .sub{position:absolute;left:7%;top:43%;font-size:5.5em;color:var(--paper);line-height:1.1}.sheet.horizontal-band .copy{left:7%;bottom:12%;width:65%}",
|
||||
"markup": "<div class=\"band\" aria-hidden=\"true\"></div>\n<h1 class=\"title\">留白练习</h1>\n<p class=\"sub\">CHAPTER<br>ZERO</p>\n<p class=\"copy\">以一道横向色带,\n建立章节之间的停顿。</p>",
|
||||
"shapes": "<rect x=\"0\" y=\"490\" width=\"1080\" height=\"475\" fill=\"#5d706e\"/><text x=\"76\" y=\"705\" font-size=\"66\" fill=\"#f5f3ec\">CHAPTER ZERO</text>"
|
||||
},
|
||||
{
|
||||
"slug": "diagonal-cards",
|
||||
"name": "轻斜卡片与拼贴",
|
||||
"scene": "创意工作室、实验记录、艺术项目封面",
|
||||
"avoid": "正式表格;大量小字;多张同时倾斜的图",
|
||||
"geometry": "后卡片旋转 -5 度,前卡片旋转 3 度;两张卡错开 6%;组合限制在 x=14%—86%、y=24%—78%。",
|
||||
"steps": "先构造两张同尺寸卡片;只给前后卡片相反的小角度;固定重叠顺序;标题和说明始终水平。",
|
||||
"check": "倾斜不超过 6 度;角点留在安全区;不要给正文整体旋转;阴影只用一层低透明度。",
|
||||
"css": ".sheet.diagonal-cards .back{left:14%;top:27%;width:66%;height:49%;transform:rotate(-5deg);background:#dce0da}.sheet.diagonal-cards .front{left:20%;top:29%;width:66%;height:49%;transform:rotate(3deg);background:var(--deep);box-shadow:0 .6em 1.2em #24323015}.sheet.diagonal-cards .title{left:7%;top:8%;font-size:6.5em}.sheet.diagonal-cards .copy{left:7%;bottom:8%;width:80%}",
|
||||
"markup": "<div class=\"panel back\" aria-hidden=\"true\"></div>\n<div class=\"panel front\" aria-hidden=\"true\"></div>\n<h1 class=\"title\">纸上的实验</h1>\n<p class=\"copy\">材料笔记 / 两张卡片的轻微偏移</p>",
|
||||
"shapes": "<rect x=\"151\" y=\"389\" width=\"713\" height=\"706\" transform=\"rotate(-5 507 742)\" fill=\"#dce0da\"/><rect x=\"216\" y=\"418\" width=\"713\" height=\"706\" transform=\"rotate(3 572 771)\" fill=\"#5d706e\"/>"
|
||||
},
|
||||
{
|
||||
"slug": "annotation-lines",
|
||||
"name": "细线标注与技术图录",
|
||||
"scene": "产品细节、工艺流程、设计参数的导读页",
|
||||
"avoid": "真实数值图表;没有可解释对象的纯装饰",
|
||||
"geometry": "中心图块位于 x=20%、y=24%,宽 60%、高 48%;两侧各有一条标注线;上方标题、下方解释。",
|
||||
"steps": "确定真正要说明的两个部位;用 1—2px 线连接短标注;线条不得穿过文字;尺寸和参数必须由真实资料提供。",
|
||||
"check": "线条最多三条;每条标注有对应对象;示意数字注明非测量值;线宽导出后仍清晰。",
|
||||
"css": ".sheet.annotation-lines .panel-a{left:20%;top:24%;width:60%;height:48%}.sheet.annotation-lines .measure{position:absolute;left:8%;top:35%;width:84%;height:24%;border-top:.15em solid var(--ink);border-bottom:.15em solid var(--ink)}.sheet.annotation-lines .tag{position:absolute;left:8%;top:31%;font-size:1.8em;background:var(--paper);padding:.2em}.sheet.annotation-lines .title{left:7%;top:9%;font-size:6.5em}.sheet.annotation-lines .copy{left:7%;bottom:10%;width:75%}",
|
||||
"markup": "<div class=\"panel panel-a\" aria-hidden=\"true\"></div>\n<div class=\"measure\" aria-hidden=\"true\"></div>\n<span class=\"tag\">A / 表面结构示意</span>\n<h1 class=\"title\">细节的尺度</h1>\n<p class=\"copy\">标注用于解释真实细节。\n本例色块为结构占位,并非实物测量。</p>",
|
||||
"shapes": "<rect x=\"216\" y=\"346\" width=\"648\" height=\"691\" fill=\"#9aa6a5\"/><path d=\"M86 504H994M86 850H994\" stroke=\"#243230\" stroke-width=\"2\"/>"
|
||||
},
|
||||
{
|
||||
"slug": "split-contrast",
|
||||
"name": "双区对照与大留白",
|
||||
"scene": "新旧对照、两种材质、设计观点对谈",
|
||||
"avoid": "需要精密等量比较的统计图;超过两种方案",
|
||||
"geometry": "上区 48% 高保留纸白;下区 52% 为深灰绿;两个信息块以相同左边界连接。",
|
||||
"steps": "先确认两个区块是对照还是递进;上下各保留一处标题;深区使用浅字;用相同编号系统说明关系。",
|
||||
"check": "两个区域的关系能一句话说清;深区小字对比足够;边界没有压住文字;阅读顺序先上后下。",
|
||||
"css": ".sheet.split-contrast .dark{position:absolute;left:0;top:48%;width:100%;height:52%;background:var(--deep)}.sheet.split-contrast .title{left:7%;top:12%;font-size:7.5em}.sheet.split-contrast .answer{position:absolute;left:7%;top:59%;font-size:7.5em;line-height:1.1;color:var(--paper);margin:0}.sheet.split-contrast .copy{left:7%;bottom:10%;width:80%;color:var(--paper)}",
|
||||
"markup": "<div class=\"dark\" aria-hidden=\"true\"></div>\n<h1 class=\"title\">看见留白</h1>\n<h2 class=\"answer\">读懂秩序</h2>\n<p class=\"copy\">从空间到关系 / TWO PERSPECTIVES</p>",
|
||||
"shapes": "<rect x=\"0\" y=\"691\" width=\"1080\" height=\"749\" fill=\"#5d706e\"/><text x=\"76\" y=\"940\" font-size=\"88\" fill=\"#f5f3ec\">读懂秩序</text>"
|
||||
},
|
||||
{
|
||||
"slug": "type-frame",
|
||||
"name": "四角字框与中心场",
|
||||
"scene": "设计展、海报合集、极短主题的概念封面",
|
||||
"avoid": "完整句子必须连续阅读的文案;长标题",
|
||||
"geometry": "四个角字构成外框,中心 58%×52% 为主形;阅读用完整 H1,分散角字仅作为装饰副本。",
|
||||
"steps": "选择四字主题;先提供完整语义标题;再把四个装饰字分布四角;中央只留一个图形,说明放底部。",
|
||||
"check": "屏幕阅读器只读一次完整标题;四角不被裁切;中央图形不压字;四角间距视觉平衡。",
|
||||
"css": ".sheet.type-frame .title{left:7%;top:7%;font-size:3em}.sheet.type-frame .center{position:absolute;left:21%;top:25%;width:58%;height:52%;border-radius:50%;background:var(--muted)}.sheet.type-frame .corners{position:absolute;inset:15% 7% 17%;display:grid;grid-template-columns:1fr 1fr;align-content:space-between;font:10em/1 \"Songti SC\",serif}.sheet.type-frame .corners span:nth-child(even){text-align:right}.sheet.type-frame .copy{left:7%;bottom:6%;width:86%}",
|
||||
"markup": "<h1 class=\"title\">高级排版 / FORM STUDIES</h1>\n<div class=\"center\" aria-hidden=\"true\"></div>\n<div class=\"corners\" aria-hidden=\"true\"><span>高</span><span>级</span><span>排</span><span>版</span></div>\n<p class=\"copy\">四角建立边界,中央保留呼吸。</p>",
|
||||
"shapes": "<ellipse cx=\"540\" cy=\"735\" rx=\"313\" ry=\"374\" fill=\"#9aa6a5\"/><g font-size=\"120\" font-family=\"serif\"><text x=\"76\" y=\"360\">高</text><text x=\"875\" y=\"360\">级</text><text x=\"76\" y=\"1190\">排</text><text x=\"875\" y=\"1190\">版</text></g>"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import type {CSSProperties} from 'react';
|
||||
import layouts from './manifest.json';
|
||||
export const layoutIds = ['ref-01','ref-02','ref-03','ref-04','ref-05','ref-06','ref-07','ref-08','ref-09','ref-10','ref-11','ref-12','ref-13','ref-14','ref-15','ref-16','ref-17','ref-18','ref-19','ref-20','ref-21','ref-22','ref-23','ref-24','ref-25','ref-26','ref-27'] as const;
|
||||
export type LayoutId=typeof layoutIds[number];
|
||||
export type Replacements={text?:Record<string,string>;images?:Record<string,string>};
|
||||
export function getLayout(id:string){return layouts.find(layout=>layout.id===id)}
|
||||
export function validateReplacements(id:string,replacements:Replacements){
|
||||
const layout=getLayout(id);if(!layout)throw Error(`Unknown layout ID: ${id}`);
|
||||
for(const[kind,values]of [['text',replacements.text],['image',replacements.images]] as const)for(const key of Object.keys(values||{})){
|
||||
if(!layout.elements.some(e=>key===id+'/'+e.id&&e.kind===kind))throw Error(`Component does not belong to ${id} or has the wrong kind: ${key}`);
|
||||
}return layout;
|
||||
}
|
||||
function textStyle(text:string,box:number[],width:number,direction:string):CSSProperties{
|
||||
const vertical=direction==='vertical'||direction==='rotated',lines=text.split('\n'),units=Math.max(...lines.map(line=>Array.from(line).reduce((n,c)=>n+(/[\u3400-\u9fff]/.test(c)?1:.56),0)),1);
|
||||
const size=vertical?Math.min(box[2],box[3]/Math.max(text.length,1)):Math.min(box[3]/(lines.length*1.1),box[2]/units);
|
||||
return {fontSize:`${size/width*100}cqw`,lineHeight:1.1,whiteSpace:'pre-wrap',writingMode:vertical?'vertical-rl':undefined,textOrientation:direction==='rotated'?'sideways':undefined,transform:direction==='angled'?'rotate(-12deg)':undefined,transformOrigin:'left center',color:'#222',fontFamily:'"Songti SC", "Noto Serif CJK SC", serif'};
|
||||
}
|
||||
/** Untouched mode reconstructs typed source layers; replacement mode keeps the exact preset geometry. */
|
||||
export default function ReferenceLayout({id,text={},images={},assetBase=''}:{id:LayoutId;text?:Record<string,string>;images?:Record<string,string>;assetBase?:string}){
|
||||
const layout=validateReplacements(id,{text,images}),src=(url:string)=>assetBase+url;
|
||||
return <figure data-layout-id={id} aria-label={`${id} ${layout.name}`} style={{margin:0,width:'min(100%, 672px)',containerType:'inline-size'}}>
|
||||
<div style={{position:'relative',width:'100%',aspectRatio:`${layout.width}/${layout.height}`,isolation:'isolate',background:'#f4f3ee'}}>
|
||||
<img src={src(layout.background)} alt="" width={layout.width} height={layout.height} style={{position:'absolute',inset:0,width:'100%',height:'100%'}}/>
|
||||
{layout.elements.map(e=>{
|
||||
const[x,y,w,h]=e.box,key=id+'/'+e.id,position:CSSProperties={position:'absolute',left:x/layout.width*100+'%',top:y/layout.height*100+'%',width:w/layout.width*100+'%',height:h/layout.height*100+'%',zIndex:e.kind==='text'?3:e.kind==='decoration'?2:1};
|
||||
if(e.kind==='text'&&Object.hasOwn(text,key)){
|
||||
if(e.direction==='curved')throw Error(`Curved text requires the source-specific SVG path: ${key}`);
|
||||
return <React.Fragment key={key}>
|
||||
{'underlay' in e&&<img src={src(String(e.underlay))} alt="" style={position}/>}
|
||||
<span data-component={key} data-kind="text" style={{...position,...textStyle(text[key],e.box,layout.width,e.direction)}}>{text[key]}</span>
|
||||
</React.Fragment>;
|
||||
}
|
||||
if(e.kind==='image'&&Object.hasOwn(images,key))return <img key={key} data-component={key} data-kind="image" src={images[key]} alt={e.role} style={{...position,objectFit:'cover',maskImage:'mask' in e?`url("${src(String(e.mask))}")`:undefined,maskSize:'100% 100%'}}/>;
|
||||
return <img key={key} data-component={key} data-kind={e.kind} src={src(e.asset)} alt={e.kind==='text'?e.text||`${e.role}:原字形保真,内容未辨识`:''} width={w} height={h} style={position}/>;
|
||||
})}
|
||||
</div>
|
||||
</figure>;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
export type Box=[number,number,number,number];
|
||||
export type TextRegion={box:Box;direction:string};
|
||||
/** Associate residual glyphs with an existing semantic word/paragraph, never create a second word ID. */
|
||||
export function textMergeTarget(fragment:Box,regions:TextRegion[]):number{
|
||||
const[x,y,w,h]=fragment;let result=-1,best=Infinity;
|
||||
for(const[index,r]of regions.entries()){
|
||||
const[a,b,c,d]=r.box,vertical=r.direction==='vertical';
|
||||
const gapX=Math.max(a-x-w,x-a-c,0),gapY=Math.max(b-y-h,y-b-d,0);
|
||||
const overlapX=Math.max(0,Math.min(x+w,a+c)-Math.max(x,a)),overlapY=Math.max(0,Math.min(y+h,b+d)-Math.max(y,b));
|
||||
const sameBaseline=overlapY/Math.max(1,Math.min(h,d))>=.55&&gapX<=Math.max(3,Math.min(7,d*.4));
|
||||
const sameColumn=overlapX/Math.max(1,Math.min(w,c))>=.55&&gapY<=Math.max(2,Math.min(4,vertical?c*.3:2));
|
||||
if(!(sameBaseline||sameColumn))continue;
|
||||
// Nearby small footer blocks remain independent when the fragment has no baseline/column affinity.
|
||||
const score=gapX+gapY+(vertical?(1-overlapX/Math.max(1,Math.min(w,c))):(1-overlapY/Math.max(1,Math.min(h,d))));
|
||||
if(score<best){result=index;best=score}
|
||||
}return result;
|
||||
}
|
||||
export function unionBoxes(a:Box,b:Box):Box{const x=Math.min(a[0],b[0]),y=Math.min(a[1],b[1]);return[x,y,Math.max(a[0]+a[2],b[0]+b[2])-x,Math.max(a[1]+a[3],b[1]+b[3])-y]}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,493 @@
|
||||
[
|
||||
{
|
||||
"file" : "ref-01.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.073746314917825531,
|
||||
0.12865497059314956,
|
||||
0.31268436578171094,
|
||||
0.062378167641325533
|
||||
],
|
||||
"confidence" : 0.5,
|
||||
"text" : "BULUBLUE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-02.png",
|
||||
"lines" : [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-03.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.022132919255381901,
|
||||
0.028650177161836932,
|
||||
0.33292714615314328,
|
||||
0.089135101994567756
|
||||
],
|
||||
"confidence" : 0.5,
|
||||
"text" : "蓝羽毛"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-04.png",
|
||||
"lines" : [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-05.png",
|
||||
"lines" : [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-06.png",
|
||||
"lines" : [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-07.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.026785715591931197,
|
||||
0.75581395330534029,
|
||||
0.23511904761904764,
|
||||
0.067829457364341095
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "2025"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.52921506067374158,
|
||||
0.90859448832286593,
|
||||
0.43264130183628624,
|
||||
0.051028077916581482
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "能車贤好雞 蓋羽毛"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-08.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.041297932751125623,
|
||||
0.019267822827299441,
|
||||
0.89380530973451322,
|
||||
0.061657032755298657
|
||||
],
|
||||
"confidence" : 0.5,
|
||||
"text" : "BULUBLUE"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.041297933952802367,
|
||||
0.078998073513166345,
|
||||
0.91150442477876115,
|
||||
0.088631984585741841
|
||||
],
|
||||
"confidence" : 0.5,
|
||||
"text" : "蓝羽毛设计"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-09.png",
|
||||
"lines" : [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-10.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
1.2867459860650037e-09,
|
||||
0.32755298650587994,
|
||||
0.7463126843657818,
|
||||
0.1502890173410405
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "DUE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-11.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.0029239778265106767,
|
||||
0.044315992678227389,
|
||||
0.47660818713450293,
|
||||
0.11560693641618491
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "蓝羽毛"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.026110667628392013,
|
||||
0.17872041532632799,
|
||||
0.60859738177026224,
|
||||
0.11654760842148737
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "版式灵感"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-12.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.014880952149470902,
|
||||
-3.6751579557403602e-10,
|
||||
0.2678571428571429,
|
||||
0.19845857418111745
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "版"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-13.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.07309941752558477,
|
||||
0.067437379901252426,
|
||||
0.8421052631578948,
|
||||
0.10404624277456653
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "版式=灵感"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-14.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.07374630927974439,
|
||||
0.45785440622605367,
|
||||
0.81710914454277295,
|
||||
0.049808429118773923
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "置羽毛 版式煲感设计分享"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-15.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.043711436004017792,
|
||||
0.054446479151035843,
|
||||
0.68638665335518967,
|
||||
0.089565615203798576
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "bulublue"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.043607735502465307,
|
||||
0.1459897803830712,
|
||||
0.5169511976696195,
|
||||
0.10108402377148817
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "design"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-16.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.085458582825798823,
|
||||
0.67023348199897037,
|
||||
0.28041027147861358,
|
||||
0.037182361404330733
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "經引毛飯式"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-17.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.050147494284660782,
|
||||
0.065134099832375503,
|
||||
0.5663716814159292,
|
||||
0.068965517241379226
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "Bulublue"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.046612091924946461,
|
||||
0.13624270198706689,
|
||||
0.48789676452456682,
|
||||
0.076173599652403667
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "Design"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.44788804184380798,
|
||||
0.30109252421060173,
|
||||
0.54965164949760315,
|
||||
0.075975871177468735
|
||||
],
|
||||
"confidence" : 0.5,
|
||||
"text" : "高级感版式,"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-18.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.03818345576318917,
|
||||
0.6239403036164336,
|
||||
0.68469503847195334,
|
||||
0.083537017705340055
|
||||
],
|
||||
"confidence" : 0.5,
|
||||
"text" : "Bulublue"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.036029248937117057,
|
||||
0.69494847907178836,
|
||||
0.73614549214861047,
|
||||
0.14112464495545596
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "Design m"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-19.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.041297934983846013,
|
||||
0.015594541432284448,
|
||||
0.22713864306784656,
|
||||
0.2007797270955165
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "高"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.032448377516592908,
|
||||
0.70760233846856724,
|
||||
0.23893805309734517,
|
||||
0.22807017543859653
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "排"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.69616519158554568,
|
||||
-5.3118909271177017e-10,
|
||||
0.23303834808259583,
|
||||
0.21247563352826515
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "级"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.70501474906895278,
|
||||
0.71734892719298249,
|
||||
0.2271386430678467,
|
||||
0.21832358674463936
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "版"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-20.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.19446010141722203,
|
||||
0.58329392990046902,
|
||||
0.31818376507675439,
|
||||
0.099253917245736178
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "高级"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-21.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.032163742013888892,
|
||||
0.65125240855009636,
|
||||
0.10818713450292397,
|
||||
0.046242774566473965
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "25"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.70095725028416789,
|
||||
0.02139049098390966,
|
||||
0.2679496675904034,
|
||||
0.69436710188613915
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "Bulublue:"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.74242488044068555,
|
||||
0.72010859462359933,
|
||||
0.21105667025025121,
|
||||
0.049185508478124196
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "2025"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-22.png",
|
||||
"lines" : [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-23.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.017024611458375291,
|
||||
0.45011763555939854,
|
||||
0.9542548642521016,
|
||||
0.15596627819445708
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "BULUBLUE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-24.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.027486666794077769,
|
||||
0.00811422680517071,
|
||||
0.61448201317351125,
|
||||
0.19655708194703103
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "蓝羽毛版式"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-25.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.064896738135557824,
|
||||
0.23121387201950871,
|
||||
0.082595906778071132,
|
||||
0.26011560693641611
|
||||
],
|
||||
"confidence" : 0.30000001192092896,
|
||||
"text" : "高設•版式"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-26.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
0.011695904385964868,
|
||||
0.017341040546724473,
|
||||
0.65497076023391809,
|
||||
0.07707129094412335
|
||||
],
|
||||
"confidence" : 0.5,
|
||||
"text" : "Format 58"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.014619884705353111,
|
||||
0.14065510587668595,
|
||||
0.43274853801169588,
|
||||
0.08285163776493254
|
||||
],
|
||||
"confidence" : 0.5,
|
||||
"text" : "Bulubl"
|
||||
},
|
||||
{
|
||||
"box" : [
|
||||
0.017385159403405345,
|
||||
0.23650755152999758,
|
||||
0.58218874568827672,
|
||||
0.083824973574952666
|
||||
],
|
||||
"confidence" : 1,
|
||||
"text" : "Bulublue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file" : "ref-27.png",
|
||||
"lines" : [
|
||||
{
|
||||
"box" : [
|
||||
-0.021385720300243924,
|
||||
0.43859550876054387,
|
||||
0.83889416285923546,
|
||||
0.20254762043324548
|
||||
],
|
||||
"confidence" : 0.5,
|
||||
"text" : "蓝羽毛-版式"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -25,5 +25,32 @@
|
||||
"guidance:premium-layout/diagonal-cards": "0962c2bf1c558e0e32425f59",
|
||||
"guidance:premium-layout/annotation-lines": "347b732caa4af4afa43b65a4",
|
||||
"guidance:premium-layout/split-contrast": "3edf144a443c1fdf23827fb3",
|
||||
"guidance:premium-layout/type-frame": "97227671a9dc95c4948e591b"
|
||||
"guidance:premium-layout/type-frame": "97227671a9dc95c4948e591b",
|
||||
"guidance:premium-layout/ref-01": "eb937b42be92a9f6feee684f",
|
||||
"guidance:premium-layout/ref-02": "1179d1b1a5f21a99fa0cf542",
|
||||
"guidance:premium-layout/ref-03": "f3079f1dbb242180e04b602b",
|
||||
"guidance:premium-layout/ref-04": "b7a5981ea582ed94384af2ae",
|
||||
"guidance:premium-layout/ref-05": "a01a0ab78461632b8cb148f9",
|
||||
"guidance:premium-layout/ref-06": "c524f9c3858052ebcc5ab8ef",
|
||||
"guidance:premium-layout/ref-07": "7d4cefdfada28bbe9e570af3",
|
||||
"guidance:premium-layout/ref-08": "e53f7620a90d3f8076c44326",
|
||||
"guidance:premium-layout/ref-09": "18aff77f5f032eda1a2cd04b",
|
||||
"guidance:premium-layout/ref-10": "d5906c34d3efb6fdbcb40da8",
|
||||
"guidance:premium-layout/ref-11": "d1718fd03494af0c8c95e4c4",
|
||||
"guidance:premium-layout/ref-12": "f78131152506cbf7dec7506a",
|
||||
"guidance:premium-layout/ref-13": "d3c5eae33973291530d1118e",
|
||||
"guidance:premium-layout/ref-14": "b042317268286cff3e52d145",
|
||||
"guidance:premium-layout/ref-15": "acbebf9c77fb2432be877bf0",
|
||||
"guidance:premium-layout/ref-16": "d3b2ec816983aadb5b4690be",
|
||||
"guidance:premium-layout/ref-17": "7b1b2fc5b9690213bbbe07ac",
|
||||
"guidance:premium-layout/ref-18": "5b376e57c70ce8a49cf4e491",
|
||||
"guidance:premium-layout/ref-19": "246c33f9fff157ab9a4c1df7",
|
||||
"guidance:premium-layout/ref-20": "b5db869088ed8296d8329f83",
|
||||
"guidance:premium-layout/ref-21": "fd6b7caabcf3077f9bdc32c7",
|
||||
"guidance:premium-layout/ref-22": "8561d1fd6f23335373eab1ba",
|
||||
"guidance:premium-layout/ref-23": "30f3978167ba3da8770ec5de",
|
||||
"guidance:premium-layout/ref-24": "6c72e53a6e2f497852f98f04",
|
||||
"guidance:premium-layout/ref-25": "51c0712b17a8372a5fa151df",
|
||||
"guidance:premium-layout/ref-26": "f356b77194288909ee4352b4",
|
||||
"guidance:premium-layout/ref-27": "b30a9778355b7132633f51b3"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type {GetServerSideProps} from 'next';
|
||||
import {legacyLayoutIds} from '@/content/layouts/documents';
|
||||
import {guidanceUrl} from '@/content/page-links';
|
||||
import {ExploreDetail} from '@/components/ExploreDetail';
|
||||
import {getEntry,entryText,exploreFeed} from '@/lib/explore';
|
||||
export default ExploreDetail;
|
||||
export const getServerSideProps:GetServerSideProps=async({params,query,res,req})=>{try{const entry=await getEntry(String(params?.id||''));if(!entry)return {notFound:true};if(String(query.agent_mode||'').toLowerCase()==='true'){res.setHeader('Content-Type','text/plain; charset=utf-8');res.setHeader('Cache-Control','no-cache');res.end(req.method==='HEAD'?undefined:entryText(entry));return {props:{}};}return {props:{entry,background:(await exploreFeed()).entries}};}catch{res.statusCode=503;res.setHeader('Retry-After','10');res.end('内容暂时无法读取,请稍后重试');return {props:{}};}};
|
||||
export const getServerSideProps:GetServerSideProps=async({params,query,res,req})=>{if(legacyLayoutIds.includes(String(params?.id)))return {redirect:{destination:guidanceUrl('premium-layout/router'),permanent:false}};try{const entry=await getEntry(String(params?.id||''));if(!entry)return {notFound:true};if(String(query.agent_mode||'').toLowerCase()==='true'){res.setHeader('Content-Type','text/plain; charset=utf-8');res.setHeader('Cache-Control','no-cache');res.end(req.method==='HEAD'?undefined:entryText(entry));return {props:{}};}return {props:{entry,background:(await exploreFeed()).entries}};}catch{res.statusCode=503;res.setHeader('Retry-After','10');res.end('内容暂时无法读取,请稍后重试');return {props:{}};}};
|
||||
|
||||
Reference in New Issue
Block a user