Redesign the resume site as a 3D-editor viewport
New design: studio backdrop with a perspective floor and X/Z axis lines, an orientation gizmo that orbits on scroll, Bricolage Grotesque and Instrument Sans (bundled, no build-time network). Projects are laid out as plates by a row planner that keeps every row full for any count; a demo image from the repo root is shown when present, otherwise a generated voxel sculpture. Text and projects still come live from Gitea. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XtcZtnG4GQA1d6mW5PNrS9
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
'use client'
|
||||
|
||||
import { useScrollProgress } from '@/lib/useScroll'
|
||||
import styles from './gizmo.module.css'
|
||||
|
||||
const AXES = [
|
||||
{ label: 'X', vector: [1, 0, 0], color: 'var(--x)' },
|
||||
{ label: 'Y', vector: [0, 1, 0], color: 'var(--y)' },
|
||||
{ label: 'Z', vector: [0, 0, 1], color: 'var(--z)' },
|
||||
] as const
|
||||
|
||||
const PITCH = 0.46
|
||||
const LENGTH = 29
|
||||
const CENTER = 48
|
||||
|
||||
function project([x, y, z]: readonly [number, number, number], yaw: number) {
|
||||
const x1 = x * Math.cos(yaw) + z * Math.sin(yaw)
|
||||
const z1 = -x * Math.sin(yaw) + z * Math.cos(yaw)
|
||||
const y2 = y * Math.cos(PITCH) - z1 * Math.sin(PITCH)
|
||||
const depth = y * Math.sin(PITCH) + z1 * Math.cos(PITCH)
|
||||
return { x: CENTER + x1 * LENGTH, y: CENTER - y2 * LENGTH, depth }
|
||||
}
|
||||
|
||||
/** Orientation gizmo of a 3D viewport. It orbits as the page scrolls and returns to the top when pressed. */
|
||||
export default function Gizmo() {
|
||||
const progress = useScrollProgress()
|
||||
const yaw = 0.62 + progress * Math.PI * 1.35
|
||||
|
||||
const ends = AXES.flatMap((axis) =>
|
||||
([1, -1] as const).map((sign) => ({
|
||||
axis,
|
||||
positive: sign === 1,
|
||||
...project([axis.vector[0] * sign, axis.vector[1] * sign, axis.vector[2] * sign], yaw),
|
||||
})),
|
||||
).sort((a, b) => a.depth - b.depth)
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.gizmo}
|
||||
aria-label="Back to top"
|
||||
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||||
>
|
||||
<svg viewBox="0 0 96 96" aria-hidden="true">
|
||||
{ends.map(({ axis, positive, x, y }) =>
|
||||
positive ? (
|
||||
<g key={axis.label + '+'}>
|
||||
<line x1={CENTER} y1={CENTER} x2={x} y2={y} stroke={axis.color} strokeWidth="3.2" strokeLinecap="round" />
|
||||
<circle cx={x} cy={y} r="9.5" fill={axis.color} />
|
||||
<text x={x} y={y + 3.6} textAnchor="middle" className={styles.letter}>{axis.label}</text>
|
||||
</g>
|
||||
) : (
|
||||
<circle key={axis.label + '-'} cx={x} cy={y} r="6" fill={axis.color} opacity="0.32" />
|
||||
),
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user