lovstudio-maintain-partners

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

maintain-partners — LovStudio 合作伙伴板块维护

maintain-partners — LovStudio合作伙伴板块维护

Operates on
/Users/mark/lovstudio/coding/web
(the LovStudio website repo). The partners strip lives in
app/(main)/(home)/WorkshopDispatch.tsx
as a
PARTNERS: Partner[]
array; logos in
public/partners/<slug>/logo.png
; taglines in
src/i18n/messages/{zh-CN,en,ja,th}.json
under
dispatch.partner*Tagline
.
操作路径为
/Users/mark/lovstudio/coding/web
(LovStudio网站代码仓库)。 合作伙伴条带位于
app/(main)/(home)/WorkshopDispatch.tsx
文件中,以
PARTNERS: Partner[]
数组形式存在;Logo存储于
public/partners/<slug>/logo.png
; 标语位于
src/i18n/messages/{zh-CN,en,ja,th}.json
文件的
dispatch.partner*Tagline
字段下。

When 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
    height: 32px
    ≈ 2.5× density, sharp enough), 240px for event posters or any retina export at
    scale: 2
    or higher.
  • 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
    --show-name
    when adding so the brand name renders next to the icon.
  • Tagline format:
    <品牌名> · <一句话定位>
    in Chinese; mirror style in en/ja/th.
  • Logo画布:网站合作伙伴条带的内容高度为80px (浅灰度,CSS
    height: 32px
    ≈ 2.5倍密度,清晰度足够), 活动海报或任何视网膜屏导出(
    scale: 2
    及以上)的内容高度为240px
  • 白底透明Logo:进行反转(完全或选择性),使其在浅灰度条带上清晰显示。
  • 标准化后宽度小于约40px的纯图标Logo:添加时传入
    --show-name
    参数,使品牌名称显示在图标旁。
  • 标语格式:中文为
    <品牌名> · <一句话定位>
    ;英文、日文、泰语遵循相同格式。

Workflow

工作流程

Op 1: Add a new partner

操作1:添加新合作伙伴

  1. Ask the user for the brand name + homepage URL via
    AskUserQuestion
    .
  2. Try to scrape the logo (static first, JS fallback):
    bash
    python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/scrape_logo.py \
      --url <URL> --download /tmp/<slug>.png
    If empty, retry with
    --js
    . If still empty, ask the user for a direct logo URL or local file.
  3. Visually verify with the Read tool before normalizing.
  4. 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
  5. Read the normalized PNG to confirm it's visible (not white-on-white).
  6. 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]
  1. 通过
    AskUserQuestion
    向用户询问品牌名称+主页URL。
  2. 尝试抓取Logo(优先静态抓取,失败则用JS渲染 fallback):
    bash
    python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/scrape_logo.py \
      --url <URL> --download /tmp/<slug>.png
    如果抓取结果为空,添加
    --js
    参数重试。若仍为空,向用户索要直接的Logo URL或本地文件。
  3. 在标准化前用Read工具进行视觉验证。
  4. 标准化处理:
    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
  5. 读取标准化后的PNG文件,确认其可见性(避免白底白字问题)。
  6. 将新合作伙伴添加至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 auto
Re-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 auto
JPEG 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 auto
JPEG格式输入文件会自动将接近白色的背景转为透明后再裁剪。

Op 4: Audit

操作4:审计

bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/audit_partners.py
bash
python3 ~/.claude/skills/lovstudio-maintain-partners/scripts/audit_partners.py

add --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
height
. Common in event posters, hero sections, "联办 / co-host" rows.
Root cause: each source file has different internal padding (designer canvas margin), so two PNGs both set to
height: 24px
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.
Reliable fix — trim at file level, uniform CSS box:
  1. Normalize every logo to identical content height. Default raster file target is 240px (3× density for retina poster export at
    scale: 2
    ; 80px gives only 1.7× and looks soft after PNG export). Use
    --invert off
    if the source is already light-on-transparent (don't double-invert):
    bash
    for 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
    Always normalize from the original source, never from a previously normalized 80px file (upscaling = blurry — burned by this on juanyi).
  2. For SVG sources, rasterize first.
    normalize_logo.py
    operates on raster pixels and cannot crop SVG viewBox padding. Without this step an SVG always renders smaller than rasterized PNG siblings:
    bash
    rsvg-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 off
    rsvg-convert
    ships with
    librsvg
    (
    brew install librsvg
    ).
  3. For SVG with embedded background rect (icon wrapped in a black/colored rounded square — common in app-icon-style SVGs from
    find-logo
    ), strip the background before rasterizing, otherwise filter
    brightness(0) invert(1)
    flattens it into a solid white block that hides the icon:
    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
  4. 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.
  5. 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); }
    brightness(0)
    flattens all colors to black, then
    invert(1)
    produces uniform white at the configured opacity. The
    .ps-logo-original
    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.
  6. 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 canvas
    The PNG goes through the same
    brightness(0) invert(1)
    filter as raster logos — match colors with all other entries automatically. Use the brand's own font (often shipped under
    <brand>/fonts/
    by the find-logo skill); fall back to system SF / Helvetica only if no brand font is available.
  7. Anti-pattern — do not try to fix alignment by setting per-logo heights like
    .ps-logo-juanyi { height: 26px }
    . 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.
适用场景:将3个及以上合作伙伴Logo放入同一水平条带时,尽管CSS
height
相同,但视觉上大小不一。常见于活动海报、英雄区、“联办/co-host”行。
根本原因:每个源文件的内部内边距(设计画布边距)不同,因此两个均设置为
height: 24px
的PNG文件,因内容占画布的比例不同,视觉高度也不同。基于目测内容比例调整单个Logo的CSS高度不稳定——不同显示器/缩放比例下会再次出现差异。
可靠修复方案——文件层面裁剪,统一CSS容器
  1. 将所有Logo标准化为相同内容高度。默认光栅文件目标高度为240px(视网膜屏海报导出
    scale: 2
    时为3倍密度;80px仅为1.7倍密度,PNG导出后会模糊)。如果源文件已是浅色透明底,使用
    --invert off
    参数(避免重复反转):
    bash
    for 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品牌上踩过此坑)。
  2. SVG源文件先光栅化
    normalize_logo.py
    基于光栅像素操作,无法裁剪SVG viewBox的内边距。若跳过此步骤,SVG的视觉大小始终会小于光栅化的PNG:
    bash
    rsvg-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
    安装)。
  3. 对于带有嵌入背景矩形的SVG(图标包裹在黑色/彩色圆角正方形中——常见于
    find-logo
    工具获取的应用图标风格SVG),光栅化前需移除背景,否则
    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
  4. 将每个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增删而不可预测。
  5. 深色背景统一处理——当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)
    生成统一的白色并设置指定透明度。
    .ps-logo-original
    用于已为白底透明的源文件(品牌套件中的白色SVG变体),避免重复处理导致在深色背景上变为不可见的黑色。
  6. 纯图标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一样经过
    brightness(0) invert(1)
    滤镜处理——自动与其他条目颜色匹配。优先使用品牌自身字体(通常由find-logo工具存放在
    <brand>/fonts/
    目录下);若无品牌字体,再 fallback到系统SF/Helvetica字体。
  7. 反模式——不要通过设置单个Logo高度(如
    .ps-logo-juanyi { height: 26px }
    )来修复对齐问题。这种方式脆弱(每个新Logo都需要一个新的魔法数值),在不同浏览器中不稳定,且一旦设计师重新提供内边距不同的源文件就会失效。

CLI Reference

CLI参考

normalize_logo.py

normalize_logo.py

FlagDefaultNotes
--src
requiredinput image (PNG/JPG/rasterized SVG)
--dst
requiredoutput PNG path; parent dirs auto-created
--height
80
target content height. Use 240 for retina poster export (
scale: 2
) — 80 looks soft after 2× downscale.
--invert
auto
auto
/
off
/
full
/
selective
(selective preserves colored icons)
参数默认值说明
--src
必填输入图片(PNG/JPG/光栅化SVG)
--dst
必填输出PNG路径;父目录会自动创建
--height
80
目标内容高度。视网膜屏海报导出(
scale: 2
)请使用240
——80px经过2倍缩小后会模糊。
--invert
auto
auto
/
off
/
full
/
selective
(selective模式保留彩色图标)

scrape_logo.py

scrape_logo.py

FlagDefaultNotes
--url
requiredbrand homepage
--js
offuse Playwright headless Chromium for SPAs
--download
offsave first candidate to this path
参数默认值说明
--url
必填品牌主页
--js
off使用Playwright无头Chromium抓取单页应用
--download
off将首个候选Logo保存至指定路径

add_partner.py

add_partner.py

FlagNotes
--name
display name (CJK ok)
--href
brand URL
--logo
path under
/public
, e.g.
/partners/foo/logo.png
--key
i18n key, e.g.
partnerFooTagline
--zh / --en / --ja / --th
tagline strings (all required)
--show-name
render name next to icon for narrow logos
参数说明
--name
显示名称(支持中日韩文)
--href
品牌URL
--logo
/public
下的路径,例如
/partners/foo/logo.png
--key
i18n键名,例如
partnerFooTagline
--zh / --en / --ja / --th
标语字符串(均为必填)
--show-name
为窄Logo在图标旁显示名称

audit_partners.py

audit_partners.py

FlagNotes
--probe
HTTP-probe every href (slow, needs proxy env vars)
参数说明
--probe
对每个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:7891
scrape_logo.py
and
audit_partners.py
already inject these for
curl
/ Playwright invocations.
沙盒子进程不会继承系统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:7891
scrape_logo.py
audit_partners.py
已为
curl
/Playwright调用自动注入这些代理设置。

Dependencies

依赖安装

bash
pip install Pillow --break-system-packages
bash
pip install Pillow --break-system-packages

Optional, for JS-rendered SPAs:

可选,用于JS渲染的单页应用:

pip install playwright --break-system-packages && playwright install chromium
undefined
pip install playwright --break-system-packages && playwright install chromium
undefined