Skip to content

Commit 59da18e

Browse files
authored
Merge pull request #105 from ahnafnafee/chore/spacing-headings
2 parents 59874e4 + 73b5d75 commit 59da18e

17 files changed

Lines changed: 267 additions & 88 deletions

CLAUDE.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ All blog, portfolio, and research entries are MDX files on disk — there's no C
4949
- `Mermaid` — renders ` ```mermaid ` blocks; mermaid + stylis are transpiled and pre-bundled (see `transpilePackages` and `experimental.optimizePackageImports` in `next.config.js`).
5050
- `ContentImage` — explicit component for ImageKit-hosted images. The default `img` MDX mapping was intentionally removed so external images / SVGs in MDX don't break; use `<ContentImage>` (or a raw `<img>`) explicitly.
5151
- `MDXLink`, `Blockquote`, `Table`, `Headings` — styled overrides.
52-
6. **Research-specific components** live in `src/components/content/research/`: `HeadingResearch` (status chip, title, structured authors + affiliations, venue, action-button row, hr), `ResearchTeaser` (full-bleed figure + caption, lives outside `prose` so it can breathe), `ResearchAbstract` (purple-accented `not-prose` card), `ResearchBibTeX` (anchored at `#bibtex`, copy-to-clipboard, mirrors `Pre.tsx` chrome), `ResearchItem` + `ResearchSections` (listing card + section grouping). The detail page composition is in `src/app/research/[slug]/page.tsx`.
52+
6. **Research-specific components** live in `src/components/content/research/`:
53+
- **Detail-page**: `HeadingResearch` (status chip, title, structured authors + affiliations, venue, action-button row, hr — author/affiliation `<sup>` markers are conditional + carry `title` tooltips, see Conventions), `ResearchTeaser` (full-bleed figure + caption, lives outside `prose` so it can breathe), `ResearchAbstract` (purple-accented `not-prose` card), `ResearchBibTeX` (anchored at `#bibtex`, copy-to-clipboard, mirrors `Pre.tsx` chrome).
54+
- **Listing-page**: `ResearchOverview` (intro paragraph), `ResearchNews` (date-column timeline; entries live in the `NEWS` array inside the file), `ResearchAreas` (colored chip row), `ResearchItem` + `ResearchSections` (listing card + section grouping). `ComingSoonImage` is a pastel-gradient placeholder used in place of the listing thumbnail when an entry has `comingSoon: true`.
55+
- **Shared**: `SectionHeading` is the bordered `text-lg md:text-xl font-bold text-black dark:text-white` h2 used by every section on the listing page (Overview / News / Research Areas / Sections groupers). Use it for any new sections to keep the rhythm consistent.
56+
- The detail page composition is in `src/app/research/[slug]/page.tsx`. The listing page is in `src/app/research/page.tsx` — both pass `pt-6 md:pt-10` to `AppLayoutPage` to give the first section breathing room from the sticky header.
5357
7. **SEO** is generated per-page in `src/app/blog/[slug]/page.tsx` and the portfolio / research equivalents: `generateMetadata` builds Next.js Metadata (OG, Twitter, canonical, `modifiedTime`), and the page itself injects a single `@graph` JSON-LD block:
5458
- Blog: `BlogPosting` + `WebPage` + `BreadcrumbList`
5559
- Portfolio: `SoftwareSourceCode` + `WebPage` + `BreadcrumbList`
@@ -86,7 +90,7 @@ Two layers feed Tailwind v4:
8690

8791
Net result: `bg-primary` (shadcn) and `bg-primary-500` (legacy) render the same color; both surfaces remain valid during the transition. When adding new code, prefer the semantic tokens. Don't write `dark:` overrides for shadcn classes — `--background` etc. switch automatically via `.dark`.
8892

89-
Fonts: `--font-sans` resolves to `var(--font-inter)` (Next/font Google) with the local `@font-face` Inter as a fallback. The legacy `font-primary` utility still maps to Inter via `tailwind.config.js`.
93+
Fonts: `--font-sans` resolves to `'Google Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'`. Google Sans is loaded via Google Fonts CSS (`<link>` in `layout.tsx` head, with `preconnect` to `fonts.gstatic.com``font-src` in `next.config.js` CSP must include it). Local Inter `@font-face` stays in `globals.css` as a deeper fallback so the page never FOUCs. `tailwind.config.js` `fontFamily.primary` mirrors the same stack so the legacy `font-primary` utility picks up Google Sans too.
9094

9195
## Two Build Modes
9296

@@ -123,7 +127,14 @@ ISR endpoint at `GET /api/revalidate?secret=<SECRET>&slug=/blog/<slug>`. Secret
123127
- Commits go through `commitizen` with `cz-conventional-changelog` — run `yarn commit` for the prompt. `lint-staged` runs Prettier on staged `js/jsx/ts/tsx/mdx/md/css` files.
124128
- Frontmatter `published` for blog and research is `MM/DD/YYYY` (portfolio uses `date`). `featured: true` surfaces an entry on the home page. Optional `updated: "MM/DD/YYYY"` overrides `published` as the freshness signal — it flows into `dateModified` (BlogPosting / ScholarlyArticle JSON-LD), `<meta property="article:modified_time">`, and the sitemap `lastmod`. Set it whenever you make a substantive content edit; leave it off for typo fixes.
125129
- Authoring convention: open every blog post with a 60–120 word TL;DR paragraph immediately under the H1. LLM crawlers (ChatGPT, Perplexity, AI Overviews) draw ~44% of their citations from the first 30% of an article, so front-loading the answer compounds visibility.
126-
- **Research authoring**: use the YAML folded scalar (`abstract: >-`) — block scalar (`|`) preserves newlines and the abstract renders with broken lines. Authors use a structured array; `affiliations` indices are 1-based and reference the entry's `affiliations` array. `section` drives listing-page grouping (`top-tier` / `conferences` / `journals` / `workshops` / `others`). `new: true` renders a NEW badge inline with the title on the listing card. Authors matching `SITE_AUTHOR.name` are auto-bolded across `HeadingResearch` and `ResearchItem`.
130+
- **Research authoring**: use the YAML folded scalar (`abstract: >-`) — block scalar (`|`) preserves newlines and the abstract renders with broken lines. Authors use a structured array; `affiliations` indices are 1-based and reference the entry's `affiliations` array. `section` drives listing-page grouping (`top-tier` / `conferences` / `journals` / `workshops` / `others`). `new: true` renders a NEW badge inline with the title on the listing card. `comingSoon: true` swaps the listing thumbnail for the `ComingSoonImage` pastel placeholder (no thumbnail/teaser required). Authors matching `SITE_AUTHOR.name` are auto-bolded across `HeadingResearch` and `ResearchItem`.
131+
- **Author / affiliation superscripts** in `HeadingResearch` are conditional. Each marker only renders when it actually disambiguates something:
132+
- `*` (corresponding author) — `corresponding: true` on the author. Shows when **2+ authors** AND at least one is corresponding.
133+
- `` (equal contribution) — `equalContribution: true` on the author. Shows when **2+ authors** AND at least 2 are flagged. Renders on every flagged author.
134+
- `` (principal investigator) — `principalInvestigator: true` on the author. Shows when **2+ authors** AND at least one is PI.
135+
- `¹ ² ³` (affiliation indices) — derived from `affiliations: [...]` on each author. Shows when **2+ affiliations** exist on the entry.
136+
137+
Marker order on each author follows academic convention: `*†‡` then numeric indices. The affiliation legend below the author line drops its leading `<sup>` under the same `showAffSup` rule. Single-author / single-affiliation entries collapse to clean text — no orphan markers. Each `<sup>` has `cursor-help` + a native `title` tooltip (affiliation index sups resolve to the full affiliation name; symbol sups resolve to their caption text). A combined caption line appears below the affiliation row when any symbol marker is shown — e.g. `*Corresponding author · †Equal contribution · ‡Principal investigator` — joined by ` · `.
127138

128139
## Indexing Helper
129140

README.md

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ Use it as your portfolio. Fork it as a template. Read the source as a reference
7070
- **Topic archive pages** auto-generated from frontmatter `topics`.
7171
- **RSS** at `/rss.xml` (summaries) and `/rss-full.xml` (full content) with `<link rel="alternate">` discovery in `<head>`.
7272

73+
### Design system
74+
75+
- **shadcn/ui** (radix-nova style) as the single primitive layer. Components live under `src/components/ui/` and are managed by the shadcn CLI (`npx shadcn@latest add <name>`); rerun with `--diff` to merge upstream updates without losing local edits.
76+
- **Theme bridge** in `src/styles/globals.css` `@theme inline` — shadcn semantic tokens (`--primary`, `--background`, `--foreground`, `--muted`, `--border`, `--ring`) map onto the legacy blue-primary / neutral-theme palette in OKLCH so legacy classes (`bg-primary-500`, `text-theme-700`) and shadcn classes (`bg-primary`, `text-foreground`) render identically across light/dark.
77+
- **Site composition components** under `src/components/site/` (Header, Footer, Nav, Hero, Searchbar, BackToTop, MobileNav, ThemeMenu, link/image wrappers, AppLayoutPage) — bespoke pieces that compose shadcn primitives + project state.
78+
- **Single primitive ecosystem**`radix-ui` (shadcn's base), `lucide-react` (icons inside shadcn), `sonner` (toasts), `tw-animate-css` (animations), `class-variance-authority` (variants), `tailwind-merge` (className merging).
79+
- **Typography** — Google Sans loaded from Google Fonts with a system-font fallback chain (`system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, …`). Local Inter `@font-face` is kept as a deeper fallback so the page never FOUCs.
80+
7381
### Performance
7482

7583
- **Core Web Vitals attribution** for CLS, LCP, **INP**, FCP, TTFB via Vercel Speed Insights.
@@ -80,13 +88,15 @@ Use it as your portfolio. Fork it as a template. Read the source as a reference
8088

8189
### Tooling
8290

83-
- **TypeScript** strict-mode, `@/*` path aliases.
91+
- **TypeScript** strict-mode, `@/*` path alias.
8492
- **ESLint flat config** (`eslint.config.mjs`, replacing the deprecated `next lint`).
85-
- **Vitest** suite (48 tests, ~1.7s) covering schema generators, sorters, content readers.
93+
- **Prettier** with `@ianvs/prettier-plugin-sort-imports` (actively maintained successor to `@trivago`) + `prettier-plugin-tailwindcss`.
94+
- **shadcn CLI** for primitive sync — `npx shadcn@latest add`, `--diff`, `--dry-run`.
95+
- **Vitest** suite (48 tests, ~1.7s) covering schema generators, sorters, content readers. Vite resolves `@/*` paths via the native `resolve.tsconfigPaths: true` (no plugin).
8696
- **JSON-LD validator** (`yarn validate:json-ld`) walks the build output and verifies every `<script type="application/ld+json">` block.
8797
- **Alt-text auditor** (`yarn audit:alt-text`) flags weak `alt` attributes in MDX.
8898
- **GitHub Actions CI**: type-check + lint + test + build + JSON-LD validation on every push/PR.
89-
- **Yarn 4** via Corepack, with a `vercel.json` `installCommand` so deploy hosts honour the lockfile.
99+
- **Yarn 4** via Corepack, with a `vercel.json` `installCommand` (`corepack enable && corepack prepare yarn@4.14.1 --activate && yarn install --immutable`) so deploy hosts actually run Yarn 4 and honour the lockfile.
90100

91101
---
92102

@@ -97,9 +107,12 @@ Use it as your portfolio. Fork it as a template. Read the source as a reference
97107
| Framework | Next.js 16 (App Router) + React 19 |
98108
| Language | TypeScript |
99109
| Styling | Tailwind CSS v4 (with `@tailwindcss/typography`) |
110+
| Design system | shadcn/ui (radix-nova) on `radix-ui` |
111+
| Icons | `lucide-react` (shadcn) + `react-icons` (legacy site components) |
112+
| Toasts | `sonner` |
113+
| Fonts | Google Sans (Google Fonts) + Inter (local @font-face fallback) |
100114
| Content | MDX (`next-mdx-remote/rsc`) + `gray-matter` |
101115
| Markdown plugins | remark-gfm, remark-math, rehype-prism-plus, rehype-slug, rehype-katex |
102-
| Animation | framer-motion |
103116
| Comments | Giscus (deferred) |
104117
| Diagrams | Mermaid (lazy) |
105118
| Math | KaTeX |
@@ -111,6 +124,7 @@ Use it as your portfolio. Fork it as a template. Read the source as a reference
111124
| PWA | @ducanh2912/next-pwa (Workbox 7) |
112125
| Testing | Vitest + happy-dom + @testing-library/react |
113126
| Lint | ESLint 9 (flat config) + eslint-config-next |
127+
| Format | Prettier 3 + `@ianvs/prettier-plugin-sort-imports` |
114128
| Package manager | Yarn 4 (Corepack) |
115129
| Hosting | Vercel (primary) + static export for any CDN |
116130

@@ -177,7 +191,10 @@ src/
177191
│ ├── rss.xml/route.ts # RSS summary feed
178192
│ └── rss-full.xml/route.ts # RSS full-content feed
179193
├── components/ # UI + content components
194+
│ ├── ui/ # shadcn/ui primitives (managed by `npx shadcn@latest add`)
195+
│ ├── site/ # Bespoke composition components (Header, Footer, Nav, AppLayoutPage, Hero, Searchbar, BackToTop, …)
180196
│ ├── content/mdx/ # MDX overrides (Pre, Code, ContentImage, TLDR, FAQ, HowTo, …)
197+
│ ├── content/research/ # Research-page sections (HeadingResearch, ResearchOverview, ResearchNews, ResearchAreas, ResearchSections, ComingSoonImage, SectionHeading, …)
181198
│ └── SEO/Breadcrumbs.tsx
182199
├── data/
183200
│ ├── blog/*.mdx # Blog posts (slug = filename)
@@ -320,18 +337,21 @@ authors:
320337
email: 'aannafee@gmu.edu'
321338
affiliations: [1] # 1-based indices into the entry's affiliations array
322339
corresponding: true
340+
# equalContribution: true # OPTIONAL: → † on each flagged author + a "†Equal contribution" caption
341+
# principalInvestigator: true # OPTIONAL: → ‡ on PI + a "‡Principal investigator" caption
323342
affiliations:
324343
- name: 'George Mason University'
325344
location: 'Fairfax, Virginia, USA'
326345
url: 'https://www.gmu.edu'
327346
venue:
328-
name: 'CS700 — Computer Geometry, Course Project'
347+
name: 'CS700 — Research Methodology in Computer Science, Course Project'
329348
short: 'GMU CS700'
330349
year: 2025
331350
status: 'tech-report' # preprint | under-review | accepted | published | workshop | tech-report
332351
published: '12/08/2025'
333352
featured: true # surfaces on the home page
334353
new: true # renders a "NEW" badge inline with the title on the listing card
354+
comingSoon: true # OPTIONAL: renders a "Coming soon!" pastel placeholder in place of the listing thumbnail (for conditionally accepted / pre-publication entries that don't have a teaser yet)
335355
section: 'others' # top-tier | conferences | journals | workshops | others
336356
topics: ['3D Graphics', 'Mesh Simplification']
337357
keywords: ['mesh decimation', 'QEM', 'vertex clustering']
@@ -357,6 +377,19 @@ bibtex: |
357377

358378
The detail page renders the structured fields in the hero (status chip from `venue.status`, authors with affiliation superscripts, venue line, action-button row driven by `links` and `bibtex`). Authors matching `SITE_AUTHOR.name` are bolded. The `bibtex` field renders as a copy-to-clipboard code block anchored at `#bibtex`.
359379

380+
**Author / affiliation superscripts are conditional** — each marker only renders when it actually disambiguates something:
381+
382+
| Flag on `Author` | Glyph | Caption | Renders when |
383+
| ----------------------------- | ------- | ----------------------- | ------------------------------- |
384+
| `corresponding: true` | `*` | \*Corresponding author | 2+ authors AND ≥1 corresponding |
385+
| `equalContribution: true` | `` | †Equal contribution | 2+ authors AND ≥2 flagged equal |
386+
| `principalInvestigator: true` | `` | ‡Principal investigator | 2+ authors AND ≥1 PI |
387+
| `affiliations: [n]` | `¹ ² ³` | _(legend below)_ | 2+ affiliations on the entry |
388+
389+
Marker order on each author: `*†‡` then numeric affiliation indices (academic convention). Single-author / single-affiliation entries skip everything — no orphan markers. When markers do show, each `<sup>` carries a native `title` tooltip (affiliation index sups resolve to the full affiliation name; symbol sups resolve to their caption). A combined caption line — e.g. `*Corresponding author · †Equal contribution · ‡Principal investigator` — auto-renders below the affiliation row.
390+
391+
The research listing page (`/research`) renders an Overview paragraph, a date-column News timeline, and a colored chip row of Research Areas above the section-grouped listings. Section headings across the page (Overview / News / Research Areas / Top-Tier Venues / Conferences / Journals / Workshops / Others) share a single `<SectionHeading>` component (`text-lg md:text-xl`, `font-bold`, `text-black dark:text-white`, bordered bottom).
392+
360393
### Special MDX components
361394

362395
```mdx
@@ -420,7 +453,7 @@ y = \sigma(Wx + b)
420453

421454
### Vercel (recommended)
422455

423-
Zero-config — Vercel detects Next.js and uses the `installCommand` from `vercel.json` (`corepack enable && yarn install --immutable`) so Yarn 4 is honoured. Set `NEXT_PUBLIC_SITE_URL` to your production domain in the Vercel project settings.
456+
Zero-config — Vercel detects Next.js and uses the `installCommand` from `vercel.json` (`corepack enable && corepack prepare yarn@4.14.1 --activate && yarn install --immutable`) so Yarn 4 actually runs (the explicit `corepack prepare` step is required because `corepack enable` alone doesn't reliably switch the active yarn binary on Vercel's image — without it, Vercel falls back to Yarn 1.22 and silently rewrites the lockfile). Set `NEXT_PUBLIC_SITE_URL` to your production domain in the Vercel project settings.
424457

425458
### Static export
426459

src/app/research/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export default async function ResearchEntryPage({ params }: Props) {
226226
}
227227

228228
return (
229-
<AppLayoutPage>
229+
<AppLayoutPage className='pt-6 md:pt-10'>
230230
<script type='application/ld+json' dangerouslySetInnerHTML={{ __html: JSON.stringify(graphJsonLd) }} />
231231
<BackToTop />
232232

src/app/research/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export default async function ResearchPage() {
114114
}
115115

116116
return (
117-
<AppLayoutPage>
117+
<AppLayoutPage className='pt-6 md:pt-10'>
118118
<script type='application/ld+json' dangerouslySetInnerHTML={{ __html: JSON.stringify(graphJsonLd) }} />
119119
{/* h1 kept for accessibility / SEO; visual title removed per design — sections below carry the page */}
120120
<h1 className='sr-only'>Research</h1>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { cn } from '@/lib/utils'
2+
3+
type ComingSoonImageProps = {
4+
className?: string
5+
}
6+
7+
/**
8+
* Pastel-gradient placeholder used in place of a real thumbnail/OG image when a
9+
* research entry is pre-publication (`comingSoon: true` in the MDX frontmatter).
10+
* Sized to fill its parent — drop into the same `relative aspect-[5/4]` slot the
11+
* NextImage normally occupies.
12+
*/
13+
export const ComingSoonImage: React.FunctionComponent<ComingSoonImageProps> = ({ className }) => {
14+
return (
15+
<div
16+
role='img'
17+
aria-label='Coming soon'
18+
className={cn(
19+
'flex h-full w-full items-center justify-center',
20+
'bg-gradient-to-br from-amber-50 via-rose-50 to-blue-100',
21+
'dark:from-amber-950/40 dark:via-rose-950/30 dark:to-blue-900/40',
22+
className
23+
)}
24+
>
25+
<span className='font-serif text-2xl leading-none font-medium tracking-tight text-gray-900 md:text-3xl dark:text-gray-100'>
26+
Coming <span className='italic'>soon!</span>
27+
</span>
28+
</div>
29+
)
30+
}

0 commit comments

Comments
 (0)