lovstudio-maintain-partners
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesemaintain-partners — LovStudio 合作伙伴板块维护
maintain-partners — LovStudio合作伙伴板块维护
Operates on (the LovStudio website repo).
The partners strip lives in as a
array; logos in ;
taglines in under .
/Users/mark/lovstudio/coding/webapp/(main)/(home)/WorkshopDispatch.tsxPARTNERS: Partner[]public/partners/<slug>/logo.pngsrc/i18n/messages/{zh-CN,en,ja,th}.jsondispatch.partner*Tagline操作路径为(LovStudio网站代码仓库)。
合作伙伴条带位于文件中,以数组形式存在;Logo存储于;
标语位于文件的字段下。
/Users/mark/lovstudio/coding/webapp/(main)/(home)/WorkshopDispatch.tsxPARTNERS: Partner[]public/partners/<slug>/logo.pngsrc/i18n/messages/{zh-CN,en,ja,th}.jsondispatch.partner*TaglineWhen to Use
使用场景
- User asks to add one or more new partners (with or without a logo URL).
- User asks to standardize / normalize a logo (sizing wrong, white-on-white, etc.).
- User provides a local file and asks to replace an existing partner's logo.
- User asks to audit the partners section before a release.
- 用户要求添加一个或多个新合作伙伴(提供或不提供Logo URL)。
- 用户要求标准化/规范化Logo(尺寸错误、白底白字等问题)。
- 用户提供本地文件并要求替换现有合作伙伴的Logo。
- 用户要求在发布前审计合作伙伴板块。
Standards
规范标准
- Logo canvas: 80px content height for the website partners strip
(light grayscale, CSS ≈ 2.5× density, sharp enough), 240px for event posters or any retina export at
height: 32pxor higher.scale: 2 - For white-on-transparent logos: invert (full or selective) so they show on the light grayscale strip.
- For icon-only logos < ~40px wide after normalization: pass when adding so the brand name renders next to the icon.
--show-name - Tagline format: in Chinese; mirror style in en/ja/th.
<品牌名> · <一句话定位>
- Logo画布:网站合作伙伴条带的内容高度为80px
(浅灰度,CSS ≈ 2.5倍密度,清晰度足够), 活动海报或任何视网膜屏导出(
height: 32px及以上)的内容高度为240px。scale: 2 - 白底透明Logo:进行反转(完全或选择性),使其在浅灰度条带上清晰显示。
- 标准化后宽度小于约40px的纯图标Logo:添加时传入参数,使品牌名称显示在图标旁。
--show-name - 标语格式:中文为;英文、日文、泰语遵循相同格式。
<品牌名> · <一句话定位>
Workflow
工作流程
Op 1: Add a new partner
操作1:添加新合作伙伴
- Ask the user for the brand name + homepage URL via .
AskUserQuestion - Try to scrape the logo (static first, JS fallback):
If empty, retry withbash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/scrape_logo.py \ --url <URL> --download /tmp/<slug>.png. If still empty, ask the user for a direct logo URL or local file.--js - Visually verify with the Read tool before normalizing.
- Normalize:
bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \ --src /tmp/<slug>.png \ --dst /Users/mark/lovstudio/coding/web/public/partners/<slug>/logo.png \ --invert auto - Read the normalized PNG to confirm it's visible (not white-on-white).
- Append to PARTNERS + all 4 locale JSONs:
bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/add_partner.py \ --name "<显示名>" --href "<URL>" \ --logo "/partners/<slug>/logo.png" \ --key partner<Slug>Tagline \ --zh "..." --en "..." --ja "..." --th "..." \ [--show-name]
- 通过向用户询问品牌名称+主页URL。
AskUserQuestion - 尝试抓取Logo(优先静态抓取,失败则用JS渲染 fallback):
如果抓取结果为空,添加bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/scrape_logo.py \ --url <URL> --download /tmp/<slug>.png参数重试。若仍为空,向用户索要直接的Logo URL或本地文件。--js - 在标准化前用Read工具进行视觉验证。
- 标准化处理:
bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \ --src /tmp/<slug>.png \ --dst /Users/mark/lovstudio/coding/web/public/partners/<slug>/logo.png \ --invert auto - 读取标准化后的PNG文件,确认其可见性(避免白底白字问题)。
- 将新合作伙伴添加至PARTNERS数组及所有4个语言版本的JSON文件:
bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/add_partner.py \ --name "<显示名>" --href "<URL>" \ --logo "/partners/<slug>/logo.png" \ --key partner<Slug>Tagline \ --zh "..." --en "..." --ja "..." --th "..." \ [--show-name]
Op 2: Normalize an existing logo
操作2:标准化现有Logo
bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \
--src public/partners/<slug>/logo.png \
--dst public/partners/<slug>/logo.png \
--invert autoRe-read after to verify.
bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \
--src public/partners/<slug>/logo.png \
--dst public/partners/<slug>/logo.png \
--invert auto处理后重新读取文件进行验证。
Op 3: Replace logo from a user-provided file
操作3:用用户提供的文件替换Logo
User typically provides a path under .
~/lovstudio/partners/<品牌>/<file>bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \
--src "<user-provided path>" \
--dst /Users/mark/lovstudio/coding/web/public/partners/<slug>/logo.png \
--invert autoJPEG inputs auto-strip near-white background to transparent before crop.
用户通常提供的路径为。
~/lovstudio/partners/<品牌>/<file>bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \
--src "<user-provided path>" \
--dst /Users/mark/lovstudio/coding/web/public/partners/<slug>/logo.png \
--invert autoJPEG格式输入文件会自动将接近白色的背景转为透明后再裁剪。
Op 4: Audit
操作4:审计
bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/audit_partners.pybash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/audit_partners.pyadd --probe to also HTTP-check every href (slow, requires proxy)
添加--probe参数可对每个href进行HTTP检查(速度较慢,需代理)
Reports: missing logo files, missing i18n keys per locale, dead URLs.
审计报告内容:缺失的Logo文件、各语言版本缺失的i18n键、无效URL。Op 5: Align a row of partner logos (cross-asset visual height parity)
操作5:对齐一行合作伙伴Logo(跨资产视觉高度一致性)
When: putting 3+ partner logos in a single horizontal strip and they look
different sizes despite having the same CSS . Common in event posters,
hero sections, "联办 / co-host" rows.
heightRoot cause: each source file has different internal padding (designer
canvas margin), so two PNGs both set to render at different
visible heights because their content occupies different fractions of the
canvas. Per-logo CSS height tweaks based on eyeballed content ratios are
unstable—different displays / scaling will diverge again.
height: 24pxReliable fix — trim at file level, uniform CSS box:
-
Normalize every logo to identical content height. Default raster file target is 240px (3× density for retina poster export at; 80px gives only 1.7× and looks soft after PNG export). Use
scale: 2if the source is already light-on-transparent (don't double-invert):--invert offbashfor f in lujiazui juanyi citic-bookstore citic-thinker-lab; do python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \ --src ~/lovstudio/partners/<brand>/<file>.png \ --dst <event-assets>/partners/$f.png \ --height 240 --invert auto doneAlways normalize from the original source, never from a previously normalized 80px file (upscaling = blurry — burned by this on juanyi). -
For SVG sources, rasterize first.operates on raster pixels and cannot crop SVG viewBox padding. Without this step an SVG always renders smaller than rasterized PNG siblings:
normalize_logo.pybashrsvg-convert -h 720 brand.svg -o /tmp/brand-raw.png # 3× of 240 python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \ --src /tmp/brand-raw.png --dst <event-assets>/partners/brand.png \ --height 240 --invert offships withrsvg-convert(librsvg).brew install librsvg -
For SVG with embedded background rect (icon wrapped in a black/colored rounded square — common in app-icon-style SVGs from), strip the background before rasterizing, otherwise filter
find-logoflattens it into a solid white block that hides the icon:brightness(0) invert(1)bash# Drop the outer <rect fill="#000"...> wrapper sed -E 's|<rect[^/]*fill="#0+"[^/]*/>||' brand.svg > /tmp/brand-clean.svg rsvg-convert -h 720 /tmp/brand-clean.svg -o /tmp/brand-raw.png -
Wrap each logo in a fixed-size box (recommended over auto-width flex):html
<span class="ps-logo-box"><img src="..." class="ps-logo"></span>css.ps-logo-box { width: 96px; height: 30px; /* fixed grid cell */ display: inline-flex; align-items: center; justify-content: center; border: 1px solid rgba(255,255,255,0.10); border-radius: 4px; padding: 3px 6px; box-sizing: border-box; } .ps-logo { max-width: 100%; max-height: 100%; width: auto; height: auto; display: block; }Fixed boxes give a stable matrix look — narrow logos (icon-only) and wide logos (icon + wordmark) all occupy the same footprint, with the asset scaled to fit. Auto-width flex (the older recipe) makes per-row total widths unpredictable as logos get added/removed. -
Dark-background unification — when the row sits on a dark canvas (e.g. event poster), most brand logos are designed for white BG and look inconsistent (some have black text, some have brand-colored marks). The stable recipe:css
.ps-logo { filter: brightness(0) invert(1) opacity(0.88); } /* logos already white-on-transparent — opt out of inversion */ .ps-logo.ps-logo-original { filter: opacity(0.88); }flattens all colors to black, thenbrightness(0)produces uniform white at the configured opacity. Theinvert(1)escape hatch is for source files that are already white-on-transparent (white SVG variants from a brand kit) so you don't double-process them into invisible black-on-dark..ps-logo-original -
Icon-only SVG → composite icon + wordmark — if the brand SVG only has an icon (no "BrandName" wordmark beside it), don't ship just the icon in a 96×30 box (it'll look like an unidentified mark). Compose the wordmark with PIL using the brand's own font when possible:python
from PIL import Image, ImageDraw, ImageFont, ImageOps # 1. rasterize cleaned SVG, invert white→black so default filter works icon = Image.open('/tmp/brand-icon.png').convert('RGBA') r, g, b, a = icon.split() inv = Image.merge('RGB', (ImageOps.invert(r), ImageOps.invert(g), ImageOps.invert(b))) icon = Image.merge('RGBA', (*inv.split(), a)) icon = icon.crop(icon.getbbox()) target_h = 240 icon = icon.resize((int(icon.width * target_h / icon.height), target_h), Image.LANCZOS) # 2. render wordmark in brand font (find-logo bundles fonts/ when found) font = ImageFont.truetype('partners/<brand>/fonts/<Family>.ttf', 150) # 3. compose icon + gap + text on transparent canvasThe PNG goes through the samefilter as raster logos — match colors with all other entries automatically. Use the brand's own font (often shipped underbrightness(0) invert(1)by the find-logo skill); fall back to system SF / Helvetica only if no brand font is available.<brand>/fonts/ -
Anti-pattern — do not try to fix alignment by setting per-logo heights like. It's brittle (every new logo needs another magic number), unstable across browsers, and breaks the moment a designer reships the source asset with different padding.
.ps-logo-juanyi { height: 26px }
适用场景:将3个及以上合作伙伴Logo放入同一水平条带时,尽管CSS 相同,但视觉上大小不一。常见于活动海报、英雄区、“联办/co-host”行。
height根本原因:每个源文件的内部内边距(设计画布边距)不同,因此两个均设置为的PNG文件,因内容占画布的比例不同,视觉高度也不同。基于目测内容比例调整单个Logo的CSS高度不稳定——不同显示器/缩放比例下会再次出现差异。
height: 24px可靠修复方案——文件层面裁剪,统一CSS容器:
-
将所有Logo标准化为相同内容高度。默认光栅文件目标高度为240px(视网膜屏海报导出时为3倍密度;80px仅为1.7倍密度,PNG导出后会模糊)。如果源文件已是浅色透明底,使用
scale: 2参数(避免重复反转):--invert offbashfor f in lujiazui juanyi citic-bookstore citic-thinker-lab; do python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \ --src ~/lovstudio/partners/<brand>/<file>.png \ --dst <event-assets>/partners/$f.png \ --height 240 --invert auto done始终从原始源文件进行标准化,切勿从已标准化的80px文件处理( upscale会导致模糊——曾在juanyi品牌上踩过此坑)。 -
SVG源文件先光栅化。基于光栅像素操作,无法裁剪SVG viewBox的内边距。若跳过此步骤,SVG的视觉大小始终会小于光栅化的PNG:
normalize_logo.pybashrsvg-convert -h 720 brand.svg -o /tmp/brand-raw.png # 240px的3倍大小 python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/normalize_logo.py \ --src /tmp/brand-raw.png --dst <event-assets>/partners/brand.png \ --height 240 --invert off是rsvg-convert的组件(可通过librsvg安装)。brew install librsvg -
对于带有嵌入背景矩形的SVG(图标包裹在黑色/彩色圆角正方形中——常见于工具获取的应用图标风格SVG),光栅化前需移除背景,否则
find-logo滤镜会将其转为纯白色块,遮挡图标:brightness(0) invert(1)bash# 移除外层<rect fill="#000"...>容器 sed -E 's|<rect[^/]*fill="#0+"[^/]*/>||' brand.svg > /tmp/brand-clean.svg rsvg-convert -h 720 /tmp/brand-clean.svg -o /tmp/brand-raw.png -
将每个Logo包裹在固定尺寸容器中(推荐替代自适应宽度flex布局):html
<span class="ps-logo-box"><img src="..." class="ps-logo"></span>css.ps-logo-box { width: 96px; height: 30px; /* 固定网格单元 */ display: inline-flex; align-items: center; justify-content: center; border: 1px solid rgba(255,255,255,0.10); border-radius: 4px; padding: 3px 6px; box-sizing: border-box; } .ps-logo { max-width: 100%; max-height: 100%; width: auto; height: auto; display: block; }固定容器可实现稳定的矩阵布局——窄Logo(纯图标)和宽Logo(图标+文字标识)占用相同空间,资源会自适应缩放。自适应宽度flex布局(旧方案)会导致每行总宽度随Logo增删而不可预测。 -
深色背景统一处理——当Logo行位于深色画布上(如活动海报),多数品牌Logo为白底设计,视觉上不一致(部分为黑色文字,部分为品牌色标识)。稳定处理方案:css
.ps-logo { filter: brightness(0) invert(1) opacity(0.88); } /* 已为白底透明的Logo——跳过反转 */ .ps-logo.ps-logo-original { filter: opacity(0.88); }将所有颜色转为黑色,brightness(0)生成统一的白色并设置指定透明度。invert(1)用于已为白底透明的源文件(品牌套件中的白色SVG变体),避免重复处理导致在深色背景上变为不可见的黑色。.ps-logo-original -
纯图标SVG → 合成图标+文字标识——如果品牌SVG仅包含图标(无“BrandName”文字标识),不要仅在96×30容器中使用图标(会看起来像不明标识)。尽可能使用品牌自身字体,通过PIL合成文字标识:python
from PIL import Image, ImageDraw, ImageFont, ImageOps # 1. 光栅化清理后的SVG,将白色反转成黑色以便默认滤镜生效 icon = Image.open('/tmp/brand-icon.png').convert('RGBA') r, g, b, a = icon.split() inv = Image.merge('RGB', (ImageOps.invert(r), ImageOps.invert(g), ImageOps.invert(b))) icon = Image.merge('RGBA', (*inv.split(), a)) icon = icon.crop(icon.getbbox()) target_h = 240 icon = icon.resize((int(icon.width * target_h / icon.height), target_h), Image.LANCZOS) # 2. 使用品牌字体渲染文字标识(find-logo工具找到Logo时会附带fonts/目录) font = ImageFont.truetype('partners/<brand>/fonts/<Family>.ttf', 150) # 3. 在透明画布上合成图标+间隙+文字生成的PNG会与光栅Logo一样经过滤镜处理——自动与其他条目颜色匹配。优先使用品牌自身字体(通常由find-logo工具存放在brightness(0) invert(1)目录下);若无品牌字体,再 fallback到系统SF/Helvetica字体。<brand>/fonts/ -
反模式——不要通过设置单个Logo高度(如)来修复对齐问题。这种方式脆弱(每个新Logo都需要一个新的魔法数值),在不同浏览器中不稳定,且一旦设计师重新提供内边距不同的源文件就会失效。
.ps-logo-juanyi { height: 26px }
CLI Reference
CLI参考
normalize_logo.py
normalize_logo.py
| Flag | Default | Notes |
|---|---|---|
| required | input image (PNG/JPG/rasterized SVG) |
| required | output PNG path; parent dirs auto-created |
| | target content height. Use 240 for retina poster export ( |
| | |
| 参数 | 默认值 | 说明 |
|---|---|---|
| 必填 | 输入图片(PNG/JPG/光栅化SVG) |
| 必填 | 输出PNG路径;父目录会自动创建 |
| | 目标内容高度。视网膜屏海报导出( |
| | |
scrape_logo.py
scrape_logo.py
| Flag | Default | Notes |
|---|---|---|
| required | brand homepage |
| off | use Playwright headless Chromium for SPAs |
| off | save first candidate to this path |
| 参数 | 默认值 | 说明 |
|---|---|---|
| 必填 | 品牌主页 |
| off | 使用Playwright无头Chromium抓取单页应用 |
| off | 将首个候选Logo保存至指定路径 |
add_partner.py
add_partner.py
| Flag | Notes |
|---|---|
| display name (CJK ok) |
| brand URL |
| path under |
| i18n key, e.g. |
| tagline strings (all required) |
| render name next to icon for narrow logos |
| 参数 | 说明 |
|---|---|
| 显示名称(支持中日韩文) |
| 品牌URL |
| |
| i18n键名,例如 |
| 标语字符串(均为必填) |
| 为窄Logo在图标旁显示名称 |
audit_partners.py
audit_partners.py
| Flag | Notes |
|---|---|
| HTTP-probe every href (slow, needs proxy env vars) |
| 参数 | 说明 |
|---|---|
| 对每个href进行HTTP探测(速度慢,需代理环境变量) |
Network proxy
网络代理
Sandbox child processes don't inherit the system ClashX proxy. Before
scraping or probing, export:
bash
export https_proxy=http://127.0.0.1:7890 \
http_proxy=http://127.0.0.1:7890 \
all_proxy=socks5://127.0.0.1:7891scrape_logo.pyaudit_partners.pycurl沙盒子进程不会继承系统ClashX代理。在抓取或探测前,需导出以下环境变量:
bash
export https_proxy=http://127.0.0.1:7890 \
http_proxy=http://127.0.0.1:7890 \
all_proxy=socks5://127.0.0.1:7891scrape_logo.pyaudit_partners.pycurlDependencies
依赖安装
bash
pip install Pillow --break-system-packagesbash
pip install Pillow --break-system-packagesOptional, for JS-rendered SPAs:
可选,用于JS渲染的单页应用:
pip install playwright --break-system-packages && playwright install chromium
undefinedpip install playwright --break-system-packages && playwright install chromium
undefined