Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/fortune-seeker/llms.txt

Use this file to discover all available pages before exploring further.

The Work History page visualises professional experience as a pendulum swinging through time. A gold rod with a weighted sphere is anchored at the top-center of the page and rotates from −30° to +30° as the visitor scrolls from the top to the bottom of the section. Timeline entries alternate left and right of the central axis, each connected to the pendulum path by a short gold horizontal line. The effect transforms a conventional work history list into a kinetic, meditative experience.

Overview

The Work History page lives at route /work and is titled “The Pendulum Swing”. Its opening quote — “Time is merely a construct, but experience leaves a mark.” — frames career experience as something felt rather than merely listed. The page uses Framer Motion’s useScroll hook (via the project’s De utility) to track scroll progress against the page container ref, then maps that progress to a rotation value using useTransform.
The scroll-linked rotation uses useScroll with offset: ["start end", "end start"] targeting the page’s root div ref. This means the pendulum starts swinging when the top of the section enters the viewport bottom, and finishes when the section bottom exits the viewport top — giving the full scroll range to the pendulum.

Layout & Components

Pendulum pivot

A small 64×64 gold ring sits absolutely at top: 0, left: 50% as the pivot point. Inside it is a 8×8 gold dot. The pivot is z-index: 20 to always appear in front of the timeline entries.

Pendulum rod & sphere

A motion.div anchored at top: 4px, left: 50% with origin-top transform origin. It contains a 2px-wide gold gradient rod 400px tall and a 64×64 sphere at its base. The sphere has a pulsing inner dot with animate-pulse. The rotate style is bound to the Framer Motion useTransform value.

Timeline entries (×3)

Each entry is a motion.div with whileInView: { opacity: 1, x: 0 } from opacity: 0, x: ±50 (left entries start from the left, right entries from the right). Each entry occupies w-5/12 and uses text-right pr-12 (left) or text-left pl-12 (right). A short h-[1px] gold horizontal connector bridges the entry to the center axis.

useScroll + useTransform

useScroll monitors the scrollYProgress of the page container. useTransform maps the [0, 1] progress range to [-30, 30] degrees of rotation. The rotation value is passed directly as style={{ rotate: rotationValue }} on the pendulum motion.div.

Data Structure

Work entries are defined in a flat array. Add, reorder, or remove entries freely — the timeline layout and alternating alignment are computed from the array index:
const workHistory = [
  {
    year: "2023 - Present",
    title: "Senior Alchemist (Frontend)",
    company: "Tech Coven Inc.",
    desc: "Leading a circle of developers in transmuting design files into living, breathing web applications. Reduced load times by 40% through dark optimization arts.",
  },
  {
    year: "2020 - 2023",
    title: "Spellweaver (Full Stack)",
    company: "Mystic Systems",
    desc: "Architected the core API gateway. Maintained the sacred databases and ensured the continuous integration rituals never failed.",
  },
  {
    year: "2018 - 2020",
    title: "Apprentice Coder",
    company: "Digital Guild",
    desc: "Learned the ancient ways of vanilla JavaScript. Fixed bugs that lurked in the shadows of legacy systems.",
  },
];
The alternating alignment is derived automatically:
const isLeft = index % 2 === 0;
// isLeft === true  → justify-start, text-right
// isLeft === false → justify-end,   text-left

Animations

useScroll is called with { target: containerRef, offset: ["start end", "end start"] }. The returned scrollYProgress motion value runs from 0 to 1 as the page scrolls. useTransform(scrollYProgress, [0, 1], [-30, 30]) maps that range to a rotation in degrees. The value is bound to the pendulum arm’s style prop, so it updates on every scroll frame without React re-renders.
The gold dot at the center of the sphere uses animate-pulse (a Tailwind utility). It has no Framer Motion involvement — the 1 s CSS keyframe opacity pulse is sufficient and avoids over-engineering a decorative element.
Each entry’s motion.div starts off-screen horizontally (x: -50 for left entries, x: 50 for right entries) and at opacity: 0. When the entry enters the viewport (with margin: "-100px" guard), it transitions to x: 0, opacity: 1 over 0.8 s with once: true. This prevents re-animation on scroll-back.
The page title and quote share a single motion.div entrance: initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 }. Unlike the timeline entries, this fires immediately on mount, not on scroll.

Customization

1

Add more work entries

Push new objects into the workHistory array. Each entry automatically receives a left or right alignment based on its even/odd index. With more than five entries, consider reducing the per-entry mb-32 bottom margin to mb-20 so the page doesn’t become excessively tall.
2

Adjust pendulum swing range

The useTransform output range is [-30, 30]. To make the pendulum swing more dramatically, widen to [-45, 45]. For a more subtle sway on a short timeline, narrow to [-15, 15]. The input range remains [0, 1] matching scrollYProgress.
3

Extend the rod length

The pendulum rod div is h-[400px]. Increase this if you add more timeline entries so the sphere reaches further down toward the final entry. Keep the sphere’s absolute positioning (bottom: 0) so it remains anchored to the rod tip.
4

Rename job titles

The mystical job titles (“Senior Alchemist”, “Spellweaver”) are in the title field. Replace them with real titles or keep the theme — the company and desc fields hold the grounding factual information. The year field accepts any string, so “Q2 2020 – Q4 2022” is valid if you prefer precision.
The useScroll target is the page’s root div referenced via useRef. If you wrap the page content in an additional scrollable container, pass that container’s ref instead. Targeting the wrong scrolling ancestor causes the pendulum to never animate — scrollYProgress will always read 0.

Build docs developers (and LLMs) love