Skip to main content

Documentation Index

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

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

Clippy is a faithful recreation of the beloved (and often despised) Microsoft Office paperclip assistant — rendered as a 📎 emoji with animated eyes — that pops up 5 seconds after the desktop loads and offers to help visitors navigate the portfolio. It’s a deliberate easter egg and a love letter to the Windows 98 era.

How It Works

Clippy lives in components/Clippy.js and is a single functional React component. It uses a useState flag (l) to toggle its own visibility, and a useEffect that fires a setTimeout to flip that flag after 5 seconds:
const r = setTimeout(() => {
  s(true); // setVisible(true)
}, 5e3); // 5000ms
return () => clearTimeout(r);
When the timeout fires, Clippy animates into view from the bottom-right corner of the viewport using Framer Motion. When dismissed — either via the close button or the “Just browse by myself” action — setVisible(false) is called and Clippy disappears entirely (the component returns null).

Animation & Appearance

Clippy enters with a Framer Motion motion.div slide-in from below:
<motion.div
  initial={{ opacity: 0, y: 50, x: 50 }}
  animate={{ opacity: 1, y: 0, x: 0 }}
  className="fixed bottom-16 right-4 z-[9000] flex items-end gap-2 pointer-events-auto"
>
It is fixed to the bottom-right corner at z-[9000] — high enough to float above all windows and the taskbar. The paperclip emoji container has animate-bounce applied, making it perpetually bounce to attract attention:
<div className="relative w-16 h-20 flex items-center justify-center animate-bounce">
  <div className="text-6xl">📎</div>
  ...
</div>

Animated Eyes

Two small black dots are positioned over the emoji inside white pill shapes, using animate-[ping_3s_infinite] to simulate blinking eyes:
<div className="absolute top-6 left-4 flex gap-1">
  <div className="w-2 h-3 bg-white rounded-full border border-black flex items-center justify-center overflow-hidden">
    <div className="w-1 h-1 bg-black rounded-full animate-[ping_3s_infinite]" />
  </div>
  <div className="w-2 h-3 bg-white rounded-full border border-black flex items-center justify-center overflow-hidden">
    <div className="w-1 h-1 bg-black rounded-full animate-[ping_3s_infinite]" />
  </div>
</div>

The Speech Bubble

The speech bubble is styled as a yellow sticky note — a nod to Office’s tooltip aesthetic — using bg-[#ffffcc] border border-black:
<div className="bg-[#ffffcc] border border-black p-3 rounded-lg shadow-md relative max-w-[200px] mb-8">
A speech-bubble tail is created by an absolutely positioned rotated div in the bottom-right corner of the bubble:
<div className="absolute -bottom-3 right-8 w-4 h-4 bg-[#ffffcc] border-b border-r border-black transform rotate-45" />

Default Message

The initial message text is stored in the i state variable and reads:
“It looks like you’re trying to view a portfolio. Would you like some help with that?”

Action Buttons

The bubble contains two action buttons:
ButtonBehaviour
Get help with navigationDoes nothing (visual only)
Just browse by myselfCalls s(false) — dismisses Clippy
A close button (X icon, size={12}) sits in the top-right corner of the bubble and also calls s(false).
<button onClick={() => s(false)} className="absolute top-1 right-1 hover:bg-yellow-200 rounded">
  <X size={12} />
</button>

Customizing Clippy

1

Edit the greeting message

Open components/Clippy.js and find the initial useState call for i. Change the string to any message you want Clippy to display:
const [i, n] = useState("Need help finding my projects? I've got you covered!");
2

Change the delay

Find the 5e3 literal in the setTimeout call and replace it with the number of milliseconds you want. For example, 10000 for 10 seconds or 1000 for 1 second:
const r = setTimeout(() => {
  s(true);
}, 10000); // Show after 10 seconds
3

Add more action buttons

Extend the button list inside the flex flex-col gap-1 mt-3 div. Each button can trigger any action — opening a window, scrolling, or simply dismissing Clippy:
<button
  className="text-xs text-left hover:bg-yellow-200 px-1 rounded flex items-center gap-1"
  onClick={() => openWindow(aboutEntry)}
>
  <div className="w-1.5 h-1.5 bg-black rounded-full" /> Open About Me
</button>
Clippy’s component returns null after dismissal and never re-mounts during the same page session. If you want Clippy to be re-triggerable, lift the visibility state up to a parent component or use localStorage to track whether the user has already dismissed it.

Build docs developers (and LLMs) love