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.
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.