name: update-website description: Make changes to kfzyqin.github.io — add an accepted paper, talk, or service item; add or re-crop card art; reorder the hero or the news/research grids; or adjust the about-page visitor widgets. Use for any edit to index.html, research.html, their page-2 files, or about.html / about-cn.html.
Updating kfzyqin.github.io
A static personal research site — no build step, no framework. Edit the HTML directly and open it in a browser.
Site map
| File | Holds |
| --- | --- |
| index.html | Hero slider (5 slides) + news grid (12 bricks) |
| index-page-2.html | News overflow |
| research.html | Publication catalogue (12 bricks), newest first |
| research-page-2.html | Research overflow |
| about.html / about-cn.html | Bio, Experience, Services, Visitor Analytics. The -cn file mirrors it with translated strings |
Shared assets: css/styles.css (one large file, sections marked with # Name banner
comments), js/main.js (theme JS, incl. the Swiper hero), js/visitor-map.js,
resources/<slug>/ for images.
Invariants
Every addition prepends at the top and pushes the tail off the bottom:
| Surface | Capacity | Overflow goes to |
| --- | --- | --- |
| Hero slider | 5 slides | dropped from the hero |
| News grid | 12 bricks | index-page-2.html (top) |
| Research grid | 12 bricks | research-page-2.html (top) |
An item in the hero must NOT also have a news brick. The hero and the masonry below it
are one continuous feed, so a paper in the hero is deleted from the news grid, and a paper
demoted out of the hero gets a news brick back. research.html is exempt — it is a
catalogue, not the feed, so hero papers keep their research card there.
Adding a hero slide therefore usually means: prepend the slide, drop the oldest slide, delete that paper's news brick, and pull a brick back from page 2 to restore the 12.
Not every item hits every surface. A paper acceptance → hero (or news brick) + research card. A talk, an area-chair role, or a conference photo → news grid only.
Ordering
- News grid runs strictly newest-first by the
YYYY-MMin each byline. A new entry's date must be >= the current top entry's. - Hero order is an editorial choice, not chronological — ask rather than assume.
- Research leads with the same papers the hero leads with, then follows the news chronology. Research-only papers (no news entry) keep their relative position.
1. Card art
Images live at resources/<slug>/coverimage.jpg, where <slug> is a short topic name
(fedllm, sovtr, fakenews, geodano, handcraft). One image serves the hero slide,
the news brick, and the research brick.
Target is an exact 4:5 ratio, matching aspect-ratio: 4 / 5 in css/styles.css
(search for "Masonry Thumbnail Uniformity"). Anything else still renders, but
object-fit: cover center-crops it in the browser.
Crop, never squash, and never upscale. Run the helper next to this file, which collects every image the grids actually reference and normalizes each one:
python3 .claude/skills/update-website/normalize-card-art.py [--dry-run]
Its rule, if you need to apply it by hand:
cw = int(min(W, H * 0.8)) // 4 * 4 # snap to a multiple of 4 so H is a whole number
ch = cw * 5 // 4
sips -s format jpeg -s formatOptions 50 --cropToHeightWidth <ch> <cw> file.jpg
sips -s format jpeg -s formatOptions 50 -z 1500 1200 file.jpg # ONLY if cw > 1200
For a brand-new poster, crop straight out of the source into place:
mkdir -p resources/<slug>
# NOTE: sips takes HEIGHT before WIDTH, and crops from the center.
sips -s format jpeg -s formatOptions 50 --cropToHeightWidth 1400 1120 <source>.png \
--out resources/<slug>/coverimage.jpg
Why those numbers:
- Never upscale. Cards render a few hundred CSS pixels wide, so an 800x1066 source
becomes 800x1000 and stops there. Forcing everything up to 1200x1500 was tried once: it
softened every image and tripled
index.html's art to 5 MB for no visible gain. formatOptions 50is whatsipsneeds to reproduce the bytes-per-pixel the existing art was already encoded at (~0.07-0.24 B/px measured across a sample). Anything higher re-encodes files larger than their source while looking identical.- The script is idempotent — a file already at an exact 4:5 within the cap is left untouched, so re-running never stacks generation loss.
Tooling constraints on this machine:
sipsis the only image tool — no ImageMagick, no Pillow.sipscan read webp but cannot write it. Five research cards have a.webpsibling served via<picture><source>. Leave them alone: a 3:4 webp in a 4:5 box is center-cropped byobject-fit: coverto pixel-for-pixel what our sips crop produces, so there is nothing to fix and the bandwidth saving is free.- Never ship a multi-megabyte PNG. Convert to JPEG and update the
src— two 1024x1536 PNGs were 2.2 and 2.8 MB and became 87 KB and 140 KB. pdftotext(for reading a paper's title and abstract) is at/opt/homebrew/bin/pdftotext; Homebrew itself is at/opt/homebrew/bin/brew, not onPATH. Do not guess a title from a filename, and do not guess which poster belongs to which paper from the artwork — read the abstracts and match. That guess has been wrong before.
2. Hero slide (index.html)
Insert before the current first slide (the comment right after <div class="swiper-wrapper">),
then delete slides from the end until 5 remain.
Keep the title short — a noun phrase, matching neighbours like "Set-of-Vision-Text Rewards" or "LMOD+ Ophthalmology Benchmark" — and the description to two lines. Name the contribution, not just the topic.
<!-- Short Name -->
<article class="hero__slide swiper-slide">
<div class="hero__entry-image"
style="background-image: url('resources/<slug>/coverimage.jpg');"></div>
<div class="hero__entry-text">
<div class="hero__entry-text-inner">
<div class="hero__entry-meta">
<span class="cat-links">
<a href="URL">VENUE YEAR</a>
</span>
</div>
<h2 class="hero__entry-title">
<a href="URL">
Short Title
</a>
</h2>
<p class="hero__entry-desc">
Two lines on what the work does.
</p>
<a class="hero__more-link" href="URL">Read More</a>
</div>
</div>
</article>
Journal names go in full in cat-links ("ACM Transactions on Computing for Healthcare",
"npj Digital Medicine 2026"), not abbreviated. The hero image renders as a
background-size: cover panel filling the left 50% of the slide, so portrait posters work.
3. News brick (index.html → index-page-2.html)
Prepend after <div class="grid-sizer"></div>, then move the trailing bricks (everything
from the 13th onward, up to </div> <!-- end bricks-wrapper -->) to the top of
index-page-2.html, right after its grid-sizer, preserving order.
News bricks use Time: plus a YYYY-MM date. The title is a headline
("X Accepted by Y"), not the paper title. The excerpt is 2-4 sentences, first person,
usually thanking collaborators by first name.
<!-- Short Name VENUE YEAR -->
<article class="brick entry" data-animate-el>
<div class="entry__thumb">
<a href="URL" class="thumb-link">
<img src="resources/<slug>/coverimage.jpg" loading="lazy" alt="">
</a>
</div> <!-- end entry__thumb -->
<div class="entry__text">
<div class="entry__header">
<div class="entry__meta">
<span class="cat-links">
<a href="URL">Paper</a>
</span>
<span class="byline">
Time:
<a href="VENUE_URL">2026-08</a>
</span>
</div>
<h1 class="entry__title"><a href="URL">X Accepted by Y</a></h1>
</div>
<div class="entry__excerpt">
<p>
Our paper on ... has been accepted by ... I'm grateful to have collaborated
with an amazing team: ...
</p>
</div>
<a class="entry__more-link" href="URL">Read More</a>
</div> <!-- end entry__text -->
</article> <!-- end article -->
cat-links is the item type: Paper, Presentation, Talk, Service, or Career.
4. Research brick (research.html → research-page-2.html)
Same prepend-and-cascade, with three differences from a news brick:
- byline label is
On:and holds a venue name (EMNLP Findings 2026), not a date entry__titleis the full paper title- the closing comment is named:
</article> <!-- end SoVTR -->, not<!-- end article -->
The excerpt is a 3-5 sentence technical summary drawn from the abstract — what problem, what method, what result — not a first-person announcement.
Note research.html has no grid-sizer div (the other three pages do); prepend before
the first entry comment instead. A card with a webp sibling wraps the img:
<picture>
<source srcset="resources/<slug>/coverimage.webp" type="image/webp">
<img src="resources/<slug>/coverimage.jpg" loading="lazy" alt="">
</picture>
5. Visitor Analytics (about pages)
about.html and about-cn.html end with a .analytics-section: one regular column
(.analytics-stack) of two full-width .analytics-cards — the Flag Counter map, then the
country flag grid. Styling lives in css/styles.css under # Visitor Analytics; there is
no inline <style> and there must not be one again.
Both widgets are remote images on counter a24d, path-segment parameters:
map: /map/a24d/size_l/txt_000000/border_CCCCCC/pageviews_1/viewers_0/flags_0/
flags: /count2/a24d/bg_FFFFFF/txt_000000/border_CCCCCC/columns_8/maxflags_250/
viewers_Visitors/labels_0/pageviews_1/flags_1/percent_0/
size_s|m|l map size; columns_N grid width; maxflags_N cap; labels_0 flag+count only;
percent_0 no percentages; flags_1 render the grid; pageviews_1 show the total.
Sizing, measured from live fetches:
width = 8 + 77 × columns (624px at columns_8 — independent of flag count)
height = 80 + 18 × rows (rows = ceil(flags / columns))
Because width is fixed and the card is 760px wide, the grid never scales down; it only
grows taller as countries accumulate (16 flags = 116px, 100 = 314px, the 250 cap = 656px).
That is intended — do not reintroduce a fixed-size box around it. A square 250x250 wrapper
is exactly what once shrank the whole image until the flags were unreadable. columns_8
(624px) is near the widest that fits; columns_10 (778px) would start scaling down.
.visitor-map-frame carries no styling — it is purely the hook js/visitor-map.js walks
up to with closest(). That script watches every img[data-visitor-map], swaps in a
.visitor-map-fallback placeholder on error, and retries at 5s/20s/60s. Both widgets carry
the attribute; a new one must too, plus data-fallback-text.
The four grid pages carry a hidden Flag Counter pixel so page views outside the about pages register on the same counter.
6. After editing css/styles.css
python3 .claude/skills/update-website/bump-css-version.py
Every page links the stylesheet as css/styles.css?v=<hash of the file>. Browsers
cache CSS aggressively -- file:// most of all -- and stale CSS repeatedly made
correct edits look like they had not applied, costing several debugging rounds
chasing a bug that was not in the code. The hash is content-derived, so it changes
exactly when the CSS does. Run this after any stylesheet edit, before asking anyone
to look at the result.
7. Conventions and gotchas
- No link yet? Use
href="#0"— the repo's convention for a dead link (see the disabled pagination arrows). Never leavehrefempty or omitted. - The
-page-2.htmlfiles use an older, shallower-indented style with single-line<h1 class="entry__title">. Paste moved blocks verbatim; do not reformat page 2. <div class="grid-sizer">is vestigial (display: none) but is still the reliable insertion anchor.sipstakes height before width, everywhere.- HTML parsers report one unclosed
<div id="page">on every page (two on the about pages). This is pre-existing in the template — verify againstgit show HEAD:<file>rather than trying to fix it. - Prefer a Python script over hand-editing when moving blocks — split on the entry comment,
bound the region with
</div> <!-- end bricks-wrapper -->, and assert the expectedclass="brick entry"count before writing.
8. Verify
for f in index.html index-page-2.html research.html research-page-2.html; do
echo "$f bricks=$(grep -c 'class=\"brick entry\"' $f)"
done # index & research must be 12
grep -c 'class="hero__slide swiper-slide"' index.html # must be 5
# hero images and news-grid images must be DISJOINT sets
# every card image an exact 4:5; every resources/ path resolves; tag balance == HEAD
python3 .claude/skills/update-website/normalize-card-art.py --dry-run # all "ok"
python3 -m http.server 8000 # then eyeball every page and every breakpoint
Check the responsive column counts too: 4 cols default, 3 at <=1500px, 2 at <=1080px, 1 at <=700px.
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.