My Website

Hero banner image

Description

My website—this very page you're on right now. It's got an about page with some personal info and work experience, some pages dedicated to various projects, and some art showcases.

Background

I self-identify as a bit of a maniac when it comes to projecting. I really love learning and building things, and usually have something in the works during my non-work time. Professionally—and also personally—I thought it'd be fun to showcase some of these interests to the world, doubling as a portfolio and place to document a variety of project journeys.

Engineering

The Content

Similarly to my other endeavors, this website is built with NextJS. Almost all of the content for the site is edited and published in Contentful. Contentful is a content management system1 that allows you to maintain and publish content (e.g. this post) with their platform, but consume and serve up the content in any way you see fit via their API. As an example, I've defined a ProjectPost content type in Contentful that I use for each of these projects. The types of content that a project post can have are explicitly defined via a content model.

The content model that contains all the content for this post.

Once you publish a ProjectPost on the Contentful side, you receive a payload via the API on your side that looks something like this:

javascript
fields: {
    title: 'My Website',
    description: 'Website that includes a short CV, coding projects, illustrations, and other things.',
    demoLink: 'tannersmarshall.com',
    slug: 'website',
    mainImage: { metadata: [Object], sys: [Object], fields: [Object] },
    body: { nodeType: 'document', data: {}, content: [Array] }
}

Contentful also has a nice React utility called documentToReactComponents, which helps render rich text as HTML. This utility takes an options argument that allows custom rendering of various HTML elements. For example, to render "code" nodes like this one:

typescript
const options = {
  renderMark: {
    [MARKS.CODE]: (text) => (
      <code className="text-accent-400 px-2 py-1 mx-1 text-[85%] rounded-md font-mono">
        {text}
      </code>
    ),
  },
};

NextJS is set up such that when a user navigates to /project/[slug], it checks all the ProjectPost entries for a matching slug property and renders it if it exists. In this way new project posts get their own pages automagically!

The Face

Another fun part of the website was the root page animation2. The animation was produced with phaser, a JavaScript-based game engine using matter-js to get the saucy explosion effects. The head and facial features were illustrated and exported as separate png images, and their relatives positions mapped to coordinates in the Phaser canvas.

typescript
const textures: Texture[] = [
  { key: 'brain', x: -25, y: -50 },
  { key: 'skull', x: -25, y: -50 },
  { key: 'ear-left', x: -460, y: 35 },
  { key: 'ear-right', x: 425, y: 35 },
  { key: 'earring-left', x: -450, y: 115 },
  { key: 'earring-right', x: 415, y: 115 },
  { key: 'head', x: 0, y: 0 },
  { key: 'eye-left', x: -245, y: 40 },
  { key: 'eye-right', x: 205, y: 40 },
  { key: 'eyebrow-right', x: 200, y: -150 },
  { key: 'mouth', x: 70, y: 300 },
  { key: 'nose', x: 0, y: 125 },
  { key: 'hair', x: -25, y: -300 },
];

Once in place, I set up both pointermove and pointerdown event listeners to trigger an explode method that calculates random x, y, and angular velocities to send the unexpecting facial features off into the ether.

typescript
 explode() {
    if (!this.isExploded && !this.explodingCoolingDown) {
      this.isExploded = true;
      this.explodeCooldown();
      this.parts.forEach((part) => {
        const rangeV = 15;
        const rangeA = 1;
        const vx = randomIntFromInterval(-rangeV, rangeV);
        const vy = randomIntFromInterval(-rangeV, rangeV);
        part.sprite.setVelocity(vx, vy);
        part.sprite.setAngularVelocity(randomIntFromInterval(-rangeA, rangeA) * 0.1);
      });
    }
  }

Stack

Backend

As mentioned, the backend framework is NextJS with Contentful as the content manager, so there's currently no database layer.

Frontend

NextJS uses good ol' fashioned React on the frontend (as well as the backend with server components). For styling, I previously used styled-components and Rebass but have recently migrated to TailwindCSS. Most UI animations are done with framer-motion or basic CSS animations.

Made with 🥒 by T. 2024