Add fluid typography scale and responsive headings

This commit is contained in:
2025-11-19 00:22:09 +08:00
parent b4ee8b122f
commit dc5ca97fee
9 changed files with 256 additions and 79 deletions

43
components/footer-cue.tsx Normal file
View File

@@ -0,0 +1,43 @@
'use client';
import { useEffect, useRef, useState } from 'react';
export function FooterCue() {
const ref = useRef<HTMLDivElement | null>(null);
const [active, setActive] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
if (!('IntersectionObserver' in window)) {
setActive(true);
return;
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActive(true);
}
});
},
{ threshold: 0.2 }
);
observer.observe(el);
return () => observer.disconnect();
}, []);
return (
<div ref={ref} className="flex flex-col items-center gap-2 py-4 text-[11px] uppercase tracking-[0.3em] text-slate-400 dark:text-slate-500">
<span className="text-xs"></span>
<span
className={`h-10 w-px overflow-hidden rounded-full bg-gradient-to-b from-transparent via-accent to-transparent transition-[height,opacity] duration-500 ease-snappy ${
active ? 'opacity-80' : 'h-4 opacity-30'
}`}
/>
</div>
);
}

View File

@@ -64,11 +64,12 @@ export function Hero() {
<div className="pointer-events-none absolute -bottom-20 right-[-3rem] h-44 w-44 rounded-full bg-indigo-300/35 blur-3xl mix-blend-soft-light motion-safe:animate-float-soft dark:bg-indigo-500/25" />
<div className="relative flex items-center gap-4 motion-safe:animate-fade-in-up">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-slate-900 text-2xl font-semibold text-slate-50 shadow-md transition-transform duration-300 ease-out group-hover:scale-105 dark:bg-slate-100 dark:text-slate-900">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-slate-900 text-xl font-semibold text-slate-50 shadow-md transition-transform duration-300 ease-out group-hover:scale-105 dark:bg-slate-100 dark:text-slate-900">
{initial}
</div>
<div>
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
<h1 className="hero-title type-display font-bold tracking-tight">
<span className="hero-title__sweep" aria-hidden="true" />
{name}
</h1>
<div className="mt-1">

View File

@@ -103,7 +103,7 @@ export function RightSidebar() {
<Link
key={tag}
href={`/tags/${slug}`}
className={`${sizeClass} rounded-full bg-accent-soft px-2 py-0.5 text-accent-textLight transition hover:bg-accent hover:text-white dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-slate-700`}
className={`${sizeClass} tag-chip rounded-full bg-accent-soft px-2 py-0.5 text-accent-textLight transition hover:bg-accent hover:text-white dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-slate-700`}
>
{tag}
</Link>

View File

@@ -0,0 +1,53 @@
'use client';
import { useEffect, useRef, useState, ReactNode } from 'react';
import clsx from 'clsx';
interface SectionDividerProps {
children: ReactNode;
className?: string;
}
export function SectionDivider({ children, className }: SectionDividerProps) {
const ref = useRef<HTMLDivElement | null>(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
if (!('IntersectionObserver' in window)) {
setVisible(true);
return;
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setVisible(true);
}
});
},
{ threshold: 0.15, rootMargin: '0px 0px -20% 0px' }
);
observer.observe(el);
return () => observer.disconnect();
}, []);
return (
<div
ref={ref}
className={clsx('space-y-4', className)}
>
<span
className={clsx(
'block h-[2px] w-full origin-left rounded-full bg-gradient-to-r from-slate-200 via-accent-soft to-slate-200 transition-transform duration-500 ease-snappy dark:from-slate-800 dark:to-slate-800',
visible ? 'scale-x-100 opacity-100' : 'scale-x-50 opacity-30'
)}
/>
{children}
</div>
);
}