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.

The Control Panel window transforms your skill set into a retro software-installation experience. Each skill is represented as a progress bar that fills up automatically when the window opens — mimicking the look of a Windows 98 setup wizard copying files. The bars animate staggered one after another, making the whole panel feel alive.

Visual Design

The window header mimics a Control Panel applet: a large Settings gear icon (48 px, text-gray-600) sits beside a bold Control Panel - Skills heading and a subtitle — Installing developer dependencies... — in small gray text. Below the header, each skill occupies its own card-like row separated by gap-6 vertical spacing. Each skill row contains:
  1. Icon + name — the skill’s Lucide icon followed by a bold skill name
  2. Status label — right-aligned; shows Installing... while the bar is animating, switches to Installed once the counter reaches the target level
  3. Progress bar — a win-border-inset bg-gray-200 outer track with a bg-gradient-to-r from-blue-800 to-blue-500 inner fill; blue segmented squares (bg-blue-900 opacity-50, w-2) are rendered as children of the fill div to create the classic chunky Windows progress-bar look

Default Skills

The u array in components/Desktop.js defines four skills out of the box:
#SkillIconIcon ColorTarget Level
1React.jsGlobetext-blue-50090%
2TypeScriptCputext-blue-70085%
3Node.jsDatabasetext-green-60080%
4CSS/TailwindMonitortext-cyan-50095%

Animation Logic

The staggered fill animation is driven by a useEffect that runs once on mount:
useEffect(() => {
  const timers = skills.map((skill, index) =>
    setTimeout(() => {
      let current = 0;
      const interval = setInterval(() => {
        current += 5;
        if (current >= skill.level) {
          current = skill.level;
          clearInterval(interval);
        }
        setProgress(prev => ({ ...prev, [skill.name]: current }));
      }, 50);
    }, index * 500) // stagger: each bar starts 500ms after the previous
  );

  return () => timers.forEach(clearTimeout);
}, []);
  • Each bar’s setTimeout delay is index * 500ms — so bar 1 starts immediately, bar 2 after 0.5 s, bar 3 after 1 s, bar 4 after 1.5 s.
  • Inside each timeout, a setInterval fires every 50 ms and increments the counter by 5 until the target level is reached, at which point clearInterval stops it.
  • The bar width is bound to style={{ width: \$%` }}with atransition-all duration-75` class for a smooth per-tick glide.
  • The number of segmented squares inside the fill is Math.floor(progress / 5) — one square per 5% of progress.

Progress Bar Structure

{/* Outer inset track */}
<div className="w-full h-6 win-border-inset bg-gray-200 p-0.5">
  {/* Animated fill */}
  <div
    className="h-full bg-gradient-to-r from-blue-800 to-blue-500 transition-all duration-75"
    style={{ width: `${progress[skill.name] || 0}%` }}
  >
    {/* Segmented squares */}
    <div className="w-full h-full flex gap-0.5 overflow-hidden">
      {Array.from({ length: Math.floor((progress[skill.name] || 0) / 5) }).map((_, i) => (
        <div key={i} className="h-full w-2 bg-blue-900 opacity-50" />
      ))}
    </div>
  </div>
</div>

Customizing Skills

1

Find the skills array

Open components/Desktop.js and locate the u array defined just above the Z component (the Control Panel component). It is a plain JavaScript array of objects.
2

Edit entries

Each skill entry has three fields:
{
  name: "React.js",            // displayed label and progress-state key
  icon: <Globe className="text-blue-500" />, // any Lucide icon JSX
  level: 90                    // integer 0–100, target fill percentage
}
Change name, swap the icon for any Lucide component that’s already imported, and adjust level to reflect your actual proficiency.
3

Add a new skill

Append a new object to the u array. Import the desired Lucide icon at the top of Desktop.js if it isn’t already there, then reference it in the icon field.
{ name: "GraphQL", icon: <Zap className="text-pink-500" />, level: 70 }
Skill level values should be multiples of 5 (e.g. 85, 90, 95) to ensure the segmented-squares count stays whole. Non-multiples of 5 will cause the final tick to land on a non-square-aligned value, which is harmless but looks slightly off.
The animation only runs once — on initial mount. If you want the bars to replay each time the window is opened, you can reset the progress state object back to {} in the cleanup function returned by the useEffect.

Build docs developers (and LLMs) love