Files
Emil_Shanayev_Resume/components/Site.tsx
T
EmilandClaude Sonnet 5 b5ffc67a82
Deploy to GitHub Pages / build (push) Canceled after 0s
Deploy to GitHub Pages / deploy (push) Canceled after 0s
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
2026-09-20 21:37:35 +03:00

59 lines
2.3 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
import type { Live, Project, Resume } from '@/lib/types'
import Gizmo from './Gizmo'
import Hero from './Hero'
import Projects from './Projects'
import { Contact, Education, Experience, Skills } from './Sections'
import TopBar from './TopBar'
// Deployed on shanaty.ru the build points at the same-origin file; elsewhere (dev, GitHub Pages) it uses the public URL.
const DATA_URL = process.env.NEXT_PUBLIC_DATA_URL ?? 'https://shanaty.ru/data/resume.json'
const GITEA_PROFILE = 'https://git.shanaty.ru/Emil_Shanaty'
type Status = 'loading' | 'live' | 'offline'
export default function Site({ initial }: Readonly<{ initial: Resume }>) {
const [resume, setResume] = useState<Resume>(initial)
const [projects, setProjects] = useState<Project[]>([])
const [generatedAt, setGeneratedAt] = useState<string>()
const [status, setStatus] = useState<Status>('loading')
useEffect(() => {
const controller = new AbortController()
fetch(DATA_URL, { cache: 'no-store', signal: controller.signal })
.then((response) => (response.ok ? response.json() : Promise.reject(new Error(`HTTP ${response.status}`))))
.then((data: Live) => {
if (!data?.resume || !Array.isArray(data.projects)) throw new Error('unexpected data')
setResume(data.resume)
setProjects(data.projects)
setGeneratedAt(data.generated_at)
setStatus('live')
})
.catch((error: Error) => {
if (error.name !== 'AbortError') setStatus('offline')
})
return () => controller.abort()
}, [])
// Once the list has loaded and is empty (no repository carries the `resume` topic yet) the section is left out.
const showProjects = status !== 'live' || projects.length > 0
const mailHref = resume.contacts[0].href
return (
<>
<TopBar name={resume.name} mailHref={mailHref} showProjects={showProjects} />
<Gizmo />
<main>
<Hero resume={resume} mailHref={mailHref} showProjects={showProjects} />
{showProjects && <Projects projects={projects} status={status} generatedAt={generatedAt} profileUrl={GITEA_PROFILE} />}
<Skills resume={resume} />
<Experience resume={resume} />
<Education resume={resume} />
</main>
<Contact resume={resume} />
</>
)
}