Skip to main content

Documentation Index

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

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

Overview

CrtOverlay.js is a thin wrapper component that layers three divs over the entire viewport to produce a convincing retro CRT monitor effect: a phosphor-glowing content layer, a scanline/vignette overlay, and an optional flicker animation. All visual work is done in CSS (assets/main.css).

CSS Custom Properties

The entire colour scheme is driven by four CSS variables defined on :root, swapped by adding the theme-amber class to <body>:
/* Default: green phosphor */
:root {
  --color-primary: #39ff14;
  --color-bg:      #000000;
  --color-dim:     rgba(57, 255, 20, 0.3);
  --color-glow:    rgba(57, 255, 20, 0.5);
}

/* Amber phosphor theme */
.theme-amber {
  --color-primary: #ffb000;
  --color-dim:     rgba(255, 176, 0, 0.3);
  --color-glow:    rgba(255, 176, 0, 0.5);
}
Switching themes is done by calling setTheme("amber") or setTheme("green") from TerminalContext — the useEffect in TerminalContext applies the class: document.body.className = theme === "amber" ? "theme-amber" : "".

Component Structure

const CrtOverlay = ({ children }) => {
  const { reduceMotion, isBooting } = useTerminal();

  return (
    <div className="crt-container">
      <div
        className={`crt-content ${reduceMotion ? "" : "crt-flicker"} ${
          isBooting ? "animate-turn-on" : ""
        } p-4 md:p-8 h-full flex flex-col`}
      >
        {children}
      </div>
      {!reduceMotion && <div className="crt-overlay" />}
    </div>
  );
};
Three layers stack on top of each other:
Classz-indexRole
.crt-container— (relative)Viewport-sized root, overflow: hidden
.crt-contentz-index: 10Children + phosphor text glow
.crt-overlayz-index: 50Scanlines, vignette, sweep animation

Phosphor Glow

The .crt-content class adds a soft text shadow using --color-glow, making all text look like it is emitting light from a phosphor-coated screen:
.crt-content {
  position: relative;
  z-index: 10;
  height: 100%;
  width: 100%;
  text-shadow: 0 0 5px var(--color-glow);
}

Scanlines

The .crt-overlay div renders a horizontal scanline grid using a repeating linear gradient. Every 4 px, half the height is transparent and the other half is a semi-transparent black band — simulating the dark horizontal stripes between phosphor rows:
.crt-overlay {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  z-index: 50;
  pointer-events: none;

  /* Scanlines */
  background: linear-gradient(
    #12101000 50%,     /* transparent */
    #00000040 50%      /* semi-opaque black */
  );
  background-size: 100% 4px;

  /* Vignette */
  box-shadow: inset 0 0 100px #000000e6;
}

Scanline Sweep Animation

The .crt-overlay::before pseudo-element adds a single bright horizontal band that travels down the screen continuously, simulating the electron beam of a real CRT:
.crt-overlay:before {
  content: " ";
  display: block;
  position: absolute;
  top: 0; left: 0; bottom: 0; right: 0;

  background: linear-gradient(
    to bottom,
    transparent,
    rgba(255, 255, 255, 0.1) 50%,
    transparent
  );
  background-size: 100% 8px;

  animation: scanline 8s linear infinite;
  opacity: 0.1;
}
The scanline keyframe moves the band from top to bottom over 8 seconds, looping forever.

Vignette

The deep inset box-shadow on .crt-overlay darkens the edges of the screen, pulling the viewer’s eye toward the centre — just like the curved glass of a real CRT:
box-shadow: inset 0 0 100px #000000e6;

Screen Curvature

Applying the .crt-curvature class to .crt-container (or any wrapper) rounds the corners and adds a secondary inset shadow to increase the illusion of a curved glass bezel:
.crt-curvature {
  border-radius: 16px;
  box-shadow: inset 0 0 60px #000c;
}
To toggle the curvature effect at runtime, simply add or remove the .crt-curvature class from the .crt-container element. You could wire this to a terminal command — for example theme curved — by calling document.querySelector('.crt-container').classList.toggle('crt-curvature') inside a new case in the Terminal’s command switch.

Flicker Animation

The .crt-flicker class is applied to .crt-content whenever reduceMotion is false. It runs a rapid 0.15 s brightness oscillation that mimics the subtle flicker of an aging CRT tube:
.crt-flicker {
  animation: flicker 0.15s infinite;
}
If the user has enabled the reduceMotion flag (accessible via TerminalContext), both the flicker animation and the .crt-overlay div are suppressed entirely.

Turn-On Animation

When isBooting is true, the .crt-content div receives the animate-turn-on class, playing a 4-second power-on effect defined with Tailwind’s @keyframes:
@keyframes turn-on {
  0%   { transform: scaleY(0.001) translateZ(0); filter: brightness(10); opacity: 1; }
  20%  { transform: scaleY(0.001) translateZ(0); filter: brightness(10); opacity: 1; }
  50%  { transform: scale(1) translateZ(0);      filter: brightness(1);  opacity: 1; }
  100% { transform: scale(1) translateZ(0);      filter: brightness(1);  opacity: 1; }
}

.animate-turn-on {
  animation: turn-on 4s linear forwards;
}
The screen “snaps on” as a thin bright horizontal line before expanding to full height, exactly like a CRT warming up.

TypewriterText Component

TypewriterText.js is a utility component used by both BootSequence and AsciiBanner. It types a string character by character, with variable delays for punctuation to make it feel organic:
const TypewriterText = ({ text, delay = 30, onComplete, fast = false }) => {
  const [displayed, setDisplayed] = useState("");
  const [index, setIndex]         = useState(0);

  useEffect(() => {
    if (index < text.length) {
      const char = text[index];
      let wait = fast ? delay / 2 : delay;

      if (!fast) {
        if (char === " ")  wait = delay * 0.5;
        if (char === "." || char === ",") wait = delay * 3;
        if (char === "\n") wait = delay * 5;
      }

      const t = setTimeout(() => {
        setDisplayed((prev) => prev + char);
        setIndex((prev) => prev + 1);
      }, wait);
      return () => clearTimeout(t);
    } else {
      onComplete?.();
    }
  }, [index, text, delay, fast, onComplete]);

  return <span className="whitespace-pre-wrap">{displayed}</span>;
};
Props:
PropTypeDefaultDescription
textstringThe full string to type out
delaynumber30Base delay in ms per character
fastbooleanfalseHalves delay; disables punctuation pauses
onComplete() => voidCallback fired when all characters are typed

Custom Scrollbar

The terminal’s scrollbar is styled to match the active theme colour so it doesn’t break the retro aesthetic:
::-webkit-scrollbar              { width: 12px; }
::-webkit-scrollbar-track        { background: var(--color-bg);
                                   border-left: 1px solid var(--color-dim); }
::-webkit-scrollbar-thumb        { background: var(--color-primary);
                                   border: 1px solid var(--color-bg); }
::-webkit-scrollbar-thumb:hover  { background: var(--color-glow); }
Text selection is also themed — selected text uses --color-primary as the background and --color-bg as the text colour, maintaining the green-on-black or amber-on-black inversion.

Typewriter Cursor

The blinking block cursor used throughout the UI (in TerminalInput and BootSequence) is a CSS-only animated <span>:
.typewriter-cursor {
  display: inline-block;
  width: 0.6em;
  height: 1em;
  background-color: var(--color-primary);
  vertical-align: text-bottom;
  animation: blink 1.06s step-end infinite;
}
The step-end timing function produces a hard on/off blink rather than a fade, matching the look of real terminal cursors.

Build docs developers (and LLMs) love