Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/home-social-links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default function HomeSocialLinks() {
`home-social-link home-social-link--${brand}`;

return (
<div className="flex items-center justify-center gap-5 md:h-full">
<div className="flex items-center justify-center gap-5">
{SOCIAL_LINKS.map(({ href, label, tip, ariaLabel, brand, external, icon }) => (
<HoverTip key={label} tip={tip ?? label} placement="top">
{external ? (
Expand Down
76 changes: 76 additions & 0 deletions app/components/identity-photo-column.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use client";

import { useLayoutEffect, useRef, useState } from "react";
import AvatarCard from "./avatar-card";
import HomeSocialLinks from "./home-social-links";
import { TocSection } from "./toc-section";

const MIN_AVATAR_PX = 120;
/** Space between avatar bottom and social row (matches `gap-4`). */
const AVATAR_SOCIAL_GAP_PX = 16;
const MD_MEDIA = "(min-width: 768px)";

function measureAvatarPx(column: HTMLElement, social: HTMLElement): number {
const available = column.clientHeight - social.offsetHeight - AVATAR_SOCIAL_GAP_PX;
const width = column.clientWidth;
return Math.max(MIN_AVATAR_PX, Math.min(width, available));
}

export default function IdentityPhotoColumn() {
const columnRef = useRef<HTMLDivElement>(null);
const socialRef = useRef<HTMLDivElement>(null);
const [avatarPx, setAvatarPx] = useState<number | null>(null);

useLayoutEffect(() => {
const column = columnRef.current;
const social = socialRef.current;
if (!column || !social) return;

const update = () => {
if (!window.matchMedia(MD_MEDIA).matches) {
setAvatarPx(null);
return;
}
setAvatarPx(measureAvatarPx(column, social));
};

update();

const ro = new ResizeObserver(update);
ro.observe(column);
ro.observe(social);

const mq = window.matchMedia(MD_MEDIA);
mq.addEventListener("change", update);

return () => {
ro.disconnect();
mq.removeEventListener("change", update);
};
}, []);

return (
<div
ref={columnRef}
className="order-1 md:order-none w-full max-w-[320px] mx-auto md:mx-0 md:w-[36%] md:max-w-none shrink-0 flex flex-col gap-4 md:h-full"
>
<div
className="w-full aspect-square mag-card overflow-hidden shrink-0"
style={{
padding: 0,
...(avatarPx != null ? { width: avatarPx, height: avatarPx } : undefined),
}}
>
<AvatarCard src="/avatar.jpg" alt="Kai Thomas Chen" className="w-full h-full" />
</div>

<TocSection label="Contact">
<div ref={socialRef}>
<HomeSocialLinks />
</div>
</TocSection>

<div style={{ height: 1, margin: "0" }} className="w-full bg-zinc-200 dark:bg-zinc-700" />
</div>
);
}
16 changes: 2 additions & 14 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import AvatarCard from "./components/avatar-card";
import JumpText from "./components/jump-text";
import ListeningCard from "./components/listening-card";
import WeatherCard from "./components/weather-card";
import ProjectsSplit from "./components/oxford-dul/projects-split";
import HomeSocialLinks from "./components/home-social-links";
import IdentityPhotoColumn from "./components/identity-photo-column";
import { TocSection } from "./components/toc-section";
import { getPinnedProjects } from "./lib/github-pinned";
import { getBerkeleyWeather } from "./lib/weather";
Expand Down Expand Up @@ -131,18 +130,7 @@ export default async function Home() {
<div style={{ height: 1, margin: "0" }} className="w-full max-w-[630px] bg-zinc-200 dark:bg-zinc-700" />
</div>

{/* Photo + social */}
<div className="order-1 md:order-none w-full max-w-[320px] mx-auto md:mx-0 md:w-[36%] md:max-w-none shrink-0 flex flex-col gap-4 md:grid md:grid-rows-[auto_1fr] md:gap-0">
<div className="w-full aspect-square mag-card overflow-hidden" style={{ padding: 0 }}>
<AvatarCard src="/avatar.jpg" alt="Kai Thomas Chen" className="w-full h-full" />
</div>

<TocSection label="Contact">
<HomeSocialLinks />
</TocSection>

<div style={{ height: 1, margin: "0" }} className="w-full bg-zinc-200 dark:bg-zinc-700" />
</div>
<IdentityPhotoColumn />
</div>

{/* ── Layer 2: Status Cards ───────────────────────────────── */}
Expand Down