This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Academic lab website for the Lee Optimization Group (LOG) at POSTECH, served by
GitHub Pages at opt.postech.ac.kr. Content and design are separate: content is
data in data/, design is Jinja templates in layout/, and tools/build.py
renders them into _site/. .github/workflows/deploy.yml builds and deploys on every
push to main (Pages source must be "GitHub Actions"). _site/ is output — never edit
it, never commit it. The root README.md maps every kind of edit to its file.
python3 tools/build.py # build into _site/
python3 tools/build.py --check # validate data/ only (exit 1 on errors)
python3 tools/build.py --serve # build and serve on http://localhost:8000| Template | Purpose | Data |
|---|---|---|
layout/index.html |
Home — lab intro, recent news | data/news/ |
layout/news.html |
News list (linked from the home page) | data/news/ |
content/research.html |
Research areas | — |
layout/publications.html |
Paper list | data/publications/ |
layout/group.html |
Members, gallery preview, alumni | data/members/, data/gallery/ |
layout/gallery.html |
Lab photos | data/gallery/ |
layout/photo.html |
Single-photo viewer (photo.html?image=stories/…) |
— |
content/hiring.html |
Open positions | — |
content/contact.html |
Contact info | — |
Pages built from data have their own layout in layout/. Pages that are just text
(content/*.html) are plain HTML for inside <main> — no template code — wrapped by
layout/page.html; their CSS is layout/styles/<page>.css and any extra script
layout/scripts/<page>.js (after the shared layout/scripts/drawer.js). Their title
is their nav label in data/site.toml. Every page extends layout/base.html, which
holds the head, header/nav, footer and mobile drawer. Nav items, footer logos and the lab name come from data/site.toml.
Each directory in data/ is one database: its entries, a config.toml of
presentation settings, and a README.md on how to add an entry. Read that README
before adding or changing content.
| Database | Entries | Notes |
|---|---|---|
data/news/ |
<year>.yaml: items keyed by idx (39:) with date, text (one paragraph of Markdown), optional paper |
Higher idx = newer, unique across years; a repeated key is an error. Home page shows the newest home.count. |
data/publications/ |
one papers.bib; entry order = display order within a heading |
Headings: is_preprint, then year (newest first), then is_previous. News refers to entries as @key (@lee26fresco). |
data/members/ |
members.yaml (members: and alumni:, each person keyed by tag) + photos/ |
The tag is the person's id everywhere (@tag). |
data/gallery/ |
gallery.yaml (photos keyed by idx, highest first) + photos/ |
Thumbnails are generated by the build; never commit thumbnails. |
The build validates all of it (file names, required fields, unknown fields, missing photos) and refuses to build on errors.
Numbers that used to be hard-coded in page scripts (news shown/added per click, home
news count, gallery preview page size) come from the
config.toml files — change them there, not in the template.
Rendering rules applied by the build:
- Markdown links to
http(s)://gettarget="_blank" rel="noopener"; relative links stay in the same tab. The same holds for paper links. - Text is HTML-escaped.
Published paths are stable public URLs and must not change: gallery photos are served
at stories/, thumbnails at stories/thumbs/, member photos at group_profile/
(see PUBLISH in tools/build.py). static/ is copied to the site root as is, so
static/research_focus/ is served at research_focus/.
Every data file starts with a comment listing every field of an entry.
Every person in data/members/members.yaml (members and alumni) is keyed by their tag. News
text and BibTeX author fields refer to lab people only by tag (@hyunjijung);
plain names are plain text. In news a tag renders as the person's short name
(default: first name), in author lists as the full name. Unknown tags fail the build.
tools/build.py renders small rounded-square faces of current members at build
time (alumni tags give the name without a face):
- News (news page, home page): under each item's date — the in-lab authors of the
papers it refers to — its
paperfield, then links written[paper](@shin23closer), which the build points at the entry'spaperURL — then members@taggedin the text; up tofaces.max(3), then+N. - Publications: before each tagged current member in
.paper-authors.
Nothing is inferred from names or links. Each face links to the member's url (new
tab) if set, otherwise to group.html#<photo file name>; group.html assigns those
ids at load time.
CSS is inline in each page's <style>, assembled by base.html from:
layout/partials/chassis.css— reset, layout tokens (:root), body, header, nav- the page's
{% block style_page %} layout/partials/footer.css— footer, footer logos, menu button- the page's
{% block style_after_footer %}(holds the page's@media (max-width: 900px)) layout/partials/drawer.css— mobile drawer- the page's
{% block style_after_drawer %}
This order is the order the pages always had their rules in, so cascade behaviour is
unchanged. A visual change that applies site-wide goes in a partial or in
base.html, once. A change to one page goes in that page's template.
Asset directories:
static/logo/— logos (PNG/SVG); footer logos are listed indata/site.tomlstatic/lab_photo/— home page hero photodata/gallery/photos/— gallery photos, published atstories/data/members/photos/— member headshots (filename = person's name slug), published atgroup_profile/static/research_focus/— research topic icons, used bycontent/research.html
Do not resize or compress images by hand. Drop the original in, commit, and the
pre-commit hook does it — resizing, re-encoding, stripping EXIF (which also removes
GPS coordinates from lab photos), and updating the data file that names the image if
the extension changes. Gallery thumbnails are not stored: the build crops them from
the photos (data/gallery/config.toml), with the same ImageMagick command the hook
used to run.
tools/setup-hooks.sh # once per clone — enables the hook
tools/optimize-images.sh # optimize everything that needs it
tools/optimize-images.sh --check # report only, change nothingThe hook also runs tools/build.py --check. Both scripts run on the bash 3.2 that
ships with macOS. Requires ImageMagick (apt install imagemagick / brew install imagemagick); the hook refuses the commit if it is missing rather than letting a
10MB camera original into history; git commit --no-verify bypasses it.
Targets are ~3x the CSS display size, so images stay sharp on retina screens:
| Directory | Max long edge | Format | Displayed at |
|---|---|---|---|
data/gallery/photos/ |
2560px | JPEG q82 | click-through full view |
| thumbnails (build) | 600px square | JPEG q80 | 112–180px grid tiles |
static/lab_photo/ |
1600px | JPEG q85 | ≤800px hero |
data/members/photos/ |
512px | JPEG q85 | 140px avatar |
static/research_focus/ |
500px | keep (PNG stays PNG — line art) | ~124px card |
static/logo/ |
untouched | — | logos must stay crisp |
Processed files carry a log-opt-v1 marker in their image comment, so re-running is
safe — already-optimized files are skipped, never re-compressed. To force a full
re-pass after changing the rules, bump MARKER in tools/optimize-images.sh.
Gallery photos are stored as lowercase .jpg; the optimizer normalizes .png/.jpeg/
.JPG and rewrites the reference in the data file for you.
Scratch files: static/index_v1.html is a redirect kept for old links — do not modify unless explicitly asked.
layout/partials/chassis.css holds the :root block of CSS custom properties,
shared by every page. This block is the single source of truth for spacing — all chassis
rules (.inner, header, .lab-name, nav, footer, section headings, body text)
reference it via var(...) instead of literal pixel values.
| Token | Value | Controls |
|---|---|---|
--content-max |
800px |
.inner max width |
--page-pad-top / --page-pad-x / --page-pad-bottom |
clamp() |
.inner padding |
--header-pad-bottom / --header-gap |
28px / 40px |
header padding + gap to <main> |
--lab-name-gap |
18px |
lab name → nav |
--nav-gap |
clamp(10px, 3vw, 24px) |
space between nav links |
--section-gap |
40px |
space above a section heading |
--heading-gap |
14px |
space below a section heading |
--para-gap |
14px |
space between stacked paragraphs |
--footer-gap / --footer-pad-top |
52px / 20px |
footer offset + padding |
--footer-logo-gap / --footer-logo-h |
14px / 28px |
footer logo row |
--text-body / --leading-body |
0.93em / 1.85 |
body text size + line height |
--rule-color |
#e2e6f0 |
header/footer hairlines |
Rules for editing:
- To change spacing site-wide, edit the token value in
layout/partials/chassis.css. - Never reintroduce a literal
pxfor anything a token already covers. - The
@media (max-width: 900px)block redefines tokens on:rootrather than re-declaring.inner/header/.footer-logos img. Add responsive spacing the same way. - Section headings (
h2,h3,.year-divider,.news-year) all usemargin-top: var(--section-gap)+margin-bottom: var(--heading-gap), paired with a:first-of-type { margin-top: 0 }rule so the first heading sits flush.
- Background:
#ffffff - Primary color:
#1B3A8A(deep blue); hover/accent:#3B6FD4 - No top rule — the 3px gradient bar was deliberately removed (
6d008de,a3a85de); do not reintroduce it - Heading font: EB Garamond (serif), weight 500
- Body font: Inter (sans-serif), weights 300/400/500/600
- Max content width: 800px, centered via
.inner(--content-max) - Padding:
clamp()for responsive spacing (--page-pad-*) - Text color:
#111(headings),#374151(body),#6b7280(muted) - Links:
#1B3A8A, underline on hover only
When adding or modifying any page, verify all of the following:
- Navigation comes from
data/site.tomlviabase.html— never hand-write nav in a page template - Drawer nav has both
nav a.activeand.nav-drawer a.activestyling - Lab name / logo links back to
index.htmlon all inner pages - Favicon (
logo/log_logo.png) is referenced in<head> - Google Fonts
<link>for EB Garamond + Inter is present - Page
<title>follows the patternPage Name | Lee Optimization Group(set by{% block title %}) -
python3 tools/build.py --checkpasses, and the built page was checked in a browser (--serve)
- No inline styles that override the established color palette
- No new fonts introduced without approval
- New sections reuse existing CSS classes (
.inner,.news-item, etc.) before adding new ones - Responsive: layout must not break on mobile (≤ 480px) or tablet (≤ 768px)
- Font sizes must not shrink on small screens — never use
body { font-size: 14px }or reducefont-sizefor text elements inside@mediaqueries - Lab name font size is fixed at
2.8em— do not useclamp()with a viewport unit for.lab-name, and do not override it in media queries - Body text uses the tokens —
p,.intro,.news-content, list items and equivalent paragraph elements must usefont-size: var(--text-body)andline-height: var(--leading-body), never literal values - Spacing uses the tokens — no literal
pxfor page padding, header/footer offsets, section-heading margins, or paragraph gaps; see the Layout Tokens section - Shared CSS stays in
layout/partials/— never copy a shared rule into a page template - Logo card containers use
flex-wrap: wrapso logos reflow to multiple rows on narrow screens instead of shrinking — do not setflex-wrap: nowrapor force images to scale down inside logo cards (.sponsor-logos,.collab-logos) - Members grid keeps fixed avatar size — member photos must not shrink on small screens; reduce the number of columns instead (
4 → 3 → 2 → 1) and keep visible spacing between cards - Gallery page uses dynamic grid —
.gallery-gridshould userepeat(auto-fit, minmax(..., 1fr))with consistentgap, so tile count adapts smoothly to viewport width
- External links open in a new tab (
target="_blank") and haverel="noopener" - Images have descriptive
alttext - Large images use
loading="lazy" - Images were never hand-resized — the pre-commit hook handles it; see Images above
- No thumbnails committed — the build generates them from
data/gallery/photos/ - No content in
layout/— news, papers, members and photos are entries indata/; the text of research, hiring and contact is incontent/ - Dates are written consistently:
MMM YYYY(e.g.,Mar 2025) - Paper/thesis links use lowercase labels:
[paper],[thesis],[code],[slides]
- No JavaScript frameworks — plain HTML/CSS/JS; the only build step is
tools/build.py(Python + Jinja) - No unused CSS rules left behind after edits
- HTML is valid and properly nested
- No hardcoded absolute URLs for internal pages (use relative paths)
The professor wants the site to have uniqueness and originality. The references below are all personal academic websites — we are a lab website, so we do not copy their structure directly. Instead, extract specific UI patterns and adapt them to a multi-person lab context.
| Site | Owner | What to borrow |
|---|---|---|
| arkitus.com/research | Ali Eslami | Research sections organized by topic/category with thumbnail images per paper; anchor-based in-page navigation |
| alnurali.com/publications | Alnur Ali | Representative publications panel (curated cards with image + abstract excerpt) before the full list; scroll progress indicator; dashed→solid border hover on nav links |
| theis.io | Lucas Theis | Ultra-minimal aesthetic — no decorative borders or chrome; content is the interface; publication entries as clean typographic blocks without explicit card boxes |
| pluskid.org | Chiyuan Zhang | Restrained, accessibility-first layout; understated horizontal nav; personality through simplicity |
- Representative publications panel (
publications.html): Show 3–5 highlighted papers as visual cards (thumbnail + title + one-line summary) at the top, before the full chronological list. Inspired by alnurali.com. - Research organized by topic (
research.html): Each research area has a clear category label and an associated paper thumbnail. Already partially done — reinforce with stronger visual separation. Inspired by arkitus.com. - Scroll progress indicator: A thin bar at the top of the page (below the gradient rule) that fills as the user scrolls. Useful on long pages (
publications.html,group.html). Inspired by alnurali.com. - Publication entries as typographic blocks: On the full publication list, avoid heavy box borders. Use whitespace and typographic weight to separate entries instead. Inspired by theis.io.
- Nav hover treatment: Consider dashed underline → solid underline on hover for nav links (subtle, distinctive). Inspired by alnurali.com.
- Single-column personal bio layout — we need multi-section lab structure
- Profile photo as hero — we use the lab group photo instead
- No-navigation style (theis.io) — our site has 6 distinct pages that need nav
- Heavy animations or JavaScript frameworks — keep plain HTML/CSS
Follow the existing prefix convention:
ADD:— new content (people, papers, news items, logos)STYLE:— visual/CSS changesPERF:— performance improvementsFIX:— bug fixesREFACTOR:— structural changes with no visual effect
Do not add Co-Authored-By or any Claude attribution in commit messages.