Build an SDE II Impact Portfolio

Build and publish a filterable React portfolio for backend SDE II roles.

Introduction

30 Second Summary

A recruiter can understand your level only when your strongest results are easy to find. Dense résumé bullets can hide the scale of work you have already delivered.

In this project, you will build an impact-first portfolio for backend SDE II applications with React and TypeScript. You will publish it from GitHub to Vercel so recruiters can open it from a live URL.

What You'll Build

A recruiter can open your portfolio on any screen to see a backend SDE II identity backed by measurable case studies.

By the end of this project, you'll have:

  • An impact-first opening that shows your backend SDE II focus beside four quantified results.
  • A filterable evidence view that lets recruiters switch among Backend, Distributed Systems, and Data Engineering case studies.
  • A GitHub-backed Vercel URL that gives recruiters a responsive view of your Wells Fargo experience.
  • Secret Mission: Create a job-specific preview that changes the headline and evidence order for one real SDE II vacancy without replacing the main portfolio.

Are there any prerequisites?

Bring your résumé content plus one target SDE II role. The project covers every required Windows setup step.

Before We Start

An impact-first portfolio starts with a clear target role. This checkpoint captures the quantified result recruiters should remember.

Set Up the React Workspace

Your impact portfolio needs a dependable local workspace before its recruiter-facing content can take shape. A verified toolchain keeps environment problems from interrupting the first visible result.

In this step, you will prepare Visual Studio Code, Node.js, Git for Windows, and npm. You will use Vite to scaffold a React project powered by TypeScript.

In this step, get ready to:
  • Verify the required Windows development tools.
  • Scaffold the React and TypeScript workspace.
  • Pin the project dependencies before starting the development server.
Verify the Windows toolchain

Each tool handles a separate part of the workflow. Visual Studio Code holds your files. Node.js runs the local development tools. Git records the changes you publish later.

  • Press the Windows key to open Windows search.
  • Type Visual Studio Code and press Enter to open it.

✔️ Visual Studio Code opens

Visual Studio Code is ready. You will return to it after creating the portfolio folder.

ⓧ Visual Studio Code is missing

The Windows User Setup installer is recommended for most learners. It adds Visual Studio Code to your user account without requiring a system-wide installation.

  • Open the Visual Studio Code Windows setup page in your browser.
  • Download the User Setup installer.
  • Run the downloaded installer.
  • Complete the installer with its default options.
  • Repeat the Windows search to open Visual Studio Code.

You should see the Visual Studio Code welcome screen.

Visual Studio Code still missing?

Restart Windows search after the installer finishes. If the app still does not appear, help me troubleshoot the Visual Studio Code installation.

Node.js v24.20.0 LTS satisfies the version requirements for this Vite project. Checking Node.js also confirms whether npm is available.

  • Press the Windows key to open Windows search.
  • Type PowerShell and press Enter to open PowerShell.
  • Check Node.js plus npm by running these commands:
node -v
npm -v

What do these checks show?

  • The first command prints the installed Node.js version.
  • The second command confirms that npm is available for managing project dependencies.

✔️ I see the required LTS version

Your first line should report v24.20.0. Your second line should contain an npm version number.

That confirms the JavaScript toolchain is ready for Vite.

ⓧ I see an older version

The installed Node.js version is below the project pin. Updating it now prevents compatibility errors during installation.

  • Open the official Node.js download page in your browser.
  • Download the Windows installer for v24.20.0 LTS.
  • Run the downloaded installer.
  • Restart PowerShell after installation.
  • Repeat the version check above.

You should now see v24.20.0 on the first line.

Still seeing the older version?

A PowerShell window opened before installation can retain the old command path. Close every PowerShell window before checking again.

If the old version remains, help me diagnose the Node.js version on Windows.

ⓧ Command not found

Node.js is not available to PowerShell yet. Installing the LTS release also installs npm.

  • Open the official Node.js download page in your browser.
  • Download the Windows installer for v24.20.0 LTS.
  • Run the downloaded installer.
  • Restart PowerShell after installation.
  • Repeat the version check above.

You should now see the Node.js version followed by an npm version.

Commands still unavailable?

The installer updates the Windows command path. A PowerShell window that stayed open cannot see that update.

If a fresh window still cannot find the commands, help me troubleshoot the Node.js installation.

  • Check whether Git is available by running this command:
git --version

What does this check show?

The command prints the installed Git version. A reported version confirms that PowerShell can reach Git for Windows.

✔️ I see a Git version

Git for Windows is ready. You can now attach your identity to future portfolio commits.

ⓧ Command not found

Windows may request approval for package source terms or administrator changes during installation. Accept those prompts so the installation can finish.

  • Install Git for Windows by running this command:
winget install --id Git.Git -e --source winget

What does this command do?

Windows Package Manager finds the exact Git for Windows package. It installs the package from the configured winget source.

  • Restart PowerShell after installation.
  • Repeat the Git version check above.

You should now see an installed Git version.

Git installation failed?

If winget is unavailable, use the official Git for Windows page to download the current installer.

If Git remains unavailable after restarting PowerShell, help me troubleshoot Git for Windows.

Git stores an author name plus an email on every commit. Use the same email connected to your GitHub account so your future portfolio commits link back to your profile.

  • Replace Your Name with your Git commit name.
  • Replace YOUR_EMAIL with your GitHub-connected email.
  • Configure your global Git identity by running these commands:
git config --global user.name "Your Name"
git config --global user.email "YOUR_EMAIL"

What does this configuration do?

  • The global name becomes the author name for commits made from this Windows account.
  • The global email associates those commits with the matching GitHub account.
  • Confirm the saved Git identity by running these commands:
git config --global user.name
git config --global user.email

What should you see?

PowerShell should print your chosen name on the first line. It should print your GitHub-connected email on the second line.

Seeing the placeholders?

Seeing Your Name or YOUR_EMAIL means the example values were saved. Run the configuration commands again with your real details.

For help without sharing personal details, help me correct my Git identity.

Scaffold the React starter

Vite creates the starter files that React needs to run in the browser. Starting on the Desktop keeps the new backend-engineer-portfolio folder easy to find.

  • Move to your Desktop by running this command:
cd ~/Desktop

What does this command do?

PowerShell changes its current location to your Desktop. The scaffolded project folder is created there.

The scaffold command starts an interactive wizard. It asks for the project name first. It then asks for a framework plus a variant.

  • Start the Vite scaffolder by running this command:
npm create vite@latest

What does this command do?

The command downloads the current Vite project scaffolder. The current create-vite release is 9.2.0.

PowerShell may ask for permission to download the package. Approve that prompt to continue to the project choices.

  • Enter backend-engineer-portfolio as the project name.
  • Select React as the framework.
  • Select TypeScript as the variant.

The scaffold creates the backend-engineer-portfolio folder on your Desktop. PowerShell confirms the folder path after generation finishes.

  • Enter the generated folder plus open it in Visual Studio Code by running these commands:
cd backend-engineer-portfolio
code .

What do these commands do?

  • The first command moves PowerShell into the generated project folder.
  • The second command opens that current folder in Visual Studio Code.
  • Confirm the Explorer sidebar lists index.html, package.json, tsconfig.json, tsconfig.app.json, tsconfig.node.json, and vite.config.ts.
  • Confirm the src folder contains App.tsx, App.css, index.css, and main.tsx.
  • Select src/main.tsx in the Explorer to confirm createRoot(...) renders <App /> inside <StrictMode>.

Your scaffold now contains the React root plus the starter component files. The TypeScript configuration files already match the generated React and Node environments.

Project folder missing?

Check that the scaffold completed before running the folder commands. The project name must be exactly backend-engineer-portfolio.

If code . is unavailable, restart PowerShell so it can load the Visual Studio Code command path. You can also ask for help opening the generated folder.

Run the pinned React starter

The generated starter can use flexible dependency ranges. Replacing package.json gives every learner the same React plus Vite toolchain.

  • Select package.json in the Visual Studio Code Explorer.
  • Press Ctrl+A to select its contents.
  • Replace the selected contents by pasting this code:
{
  "name": "backend-engineer-portfolio",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "19.2.8",
    "react-dom": "19.2.8"
  },
  "devDependencies": {
    "@types/node": "24.13.3",
    "@types/react": "19.2.18",
    "@types/react-dom": "19.2.5",
    "@vitejs/plugin-react": "6.1.1",
    "typescript": "6.0.3",
    "vite": "8.2.2"
  }
}

What does this file control?

  • The scripts provide commands for development, production builds, and local production previews.
  • React plus React DOM are pinned to 19.2.8.
  • TypeScript is pinned to 6.0.3 for compatibility with the Vite scaffold.
  • Vite is pinned to 8.2.2. The React plugin is pinned to 6.1.1.
  • Save package.json by pressing Ctrl+S.

✔️ Awesome, I've got everything!

Great. Your saved package.json now pins the dependencies used throughout the project.

ⓧ I'd like to double check the full code

{
  "name": "backend-engineer-portfolio",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "19.2.8",
    "react-dom": "19.2.8"
  },
  "devDependencies": {
    "@types/node": "24.13.3",
    "@types/react": "19.2.18",
    "@types/react-dom": "19.2.5",
    "@vitejs/plugin-react": "6.1.1",
    "typescript": "6.0.3",
    "vite": "8.2.2"
  }
}
  • Install the pinned project dependencies by running this command in PowerShell:
npm install

What does this command do?

npm reads the exact versions from package.json. It downloads those packages into this project.

PowerShell should return to the prompt without an installation error. The project is now ready to run with its pinned dependencies.

Dependency installation failed?

Confirm that PowerShell is inside the backend-engineer-portfolio folder. Check that the saved package.json matches the double-check tab.

If npm reports a compatibility or network problem, help me debug npm install.

Before you start the server, what signal do you expect PowerShell to provide when the workspace is ready?

  • Start the Vite development server by running this command:
npm run dev

What should you see?

PowerShell prints a local development URL. The documented default is http://localhost:5173.

The command keeps running because Vite is serving your starter page. Leave this PowerShell window running for the next step.

  • Switch back to the browser you used earlier.
  • Enter http://localhost:5173 in the address bar.

You should see the Vite React starter page with its starter controls. This proves that the React root, TypeScript setup, pinned dependencies, and development server work together.

Starter page not loading?

Keep the PowerShell window running while the browser loads the local URL. Confirm that the terminal is inside backend-engineer-portfolio.

Check that the dependency installation completed before the server started. If the page still does not load, help me diagnose the Vite starter.

That is the foundation in place. Your pinned React workspace now runs locally from the exact folder you will build on.

Next, you will replace the starter screen with an impact-first landing page that puts your backend identity plus quantified results in front of recruiters.

Build the Impact-First Landing Page

Your Vite starter now proves that your local React workspace can serve a page. The next goal is to turn that starter into a focused backend engineering portfolio.

A polished headline earns a recruiter's attention. Scannable evidence gives that headline weight.

In this step, get ready to:
  • Create a typed data file for your profile details and impact metrics.
  • Replace the starter component with a semantic portfolio landing page.
  • Apply a dark visual system and inspect the first evidence gap.
Create the portfolio data model

A separate data file keeps your career evidence away from the page structure. The Specialty type also defines the three engineering strengths that the completed portfolio supports.

  • Select the src folder in the Visual Studio Code Explorer sidebar.
  • Click the New File button at the top of the Explorer sidebar.
  • Name the new file portfolioData.ts.
  • Add the profile model to src/portfolioData.ts by pasting this code:
export type Specialty =
  | 'Backend'
  | 'Distributed Systems'
  | 'Data Engineering'

export const profile = {
  name: 'Your Name',
  headline: 'Backend Software Engineer building reliable, data-intensive systems',
  summary:
    'Software Engineer with 3+ years of experience building production APIs, microservices, and data pipelines for fintech systems with Java, Python, and TypeScript.',
  availability:
    'Open to SDE II-equivalent opportunities across Europe, APAC, and North America.',
  email: 'your-email@example.com',
  githubUrl: '',
  linkedInUrl: '',
  resumeUrl: '',
}

What does this data represent?

  • The Specialty union limits future evidence filters to three supported values.
  • The profile object holds the identity and contact details displayed throughout the page.
  • The empty profile URLs prevent unfinished external links from appearing later.
  • Save src/portfolioData.ts.
  • Confirm that portfolioData.ts now appears beneath the src folder in the Explorer sidebar.

Cannot find the new data file?

Check that portfolioData.ts sits inside src. A file beside the src folder uses a different import path.

Ask for help if the file appears in the wrong location. Help me place portfolioData.ts inside my Vite src folder

  • Add the quantified impact evidence below the profile object by pasting this code:
export const metrics = [
  { value: '50%', label: 'faster credit approvals' },
  { value: '45%', label: 'less manual review time' },
  { value: '130+', label: 'production data pipelines supported' },
  { value: '10,000+', label: 'applications served each month' },
]

Why store metrics as an array?

Each array item pairs a result with its meaning. React can map over this structure to create every metric card from the same markup.

  • Save src/portfolioData.ts.
  • Return to the PowerShell terminal from earlier. Confirm that the development server remains free of TypeScript errors.

Seeing a TypeScript error?

Check every quote and comma in metrics. Confirm that the closing square bracket appears after the fourth metric.

Use this prompt for a targeted comparison. Help me find the syntax error in my portfolioData.ts profile or metrics export

✔️ Awesome, I've got everything!

Great. Save src/portfolioData.ts before moving to the page component.

ⓧ I'd like to double check the full code

export type Specialty =
  | 'Backend'
  | 'Distributed Systems'
  | 'Data Engineering'

export const profile = {
  name: 'Your Name',
  headline: 'Backend Software Engineer building reliable, data-intensive systems',
  summary:
    'Software Engineer with 3+ years of experience building production APIs, microservices, and data pipelines for fintech systems with Java, Python, and TypeScript.',
  availability:
    'Open to SDE II-equivalent opportunities across Europe, APAC, and North America.',
  email: 'your-email@example.com',
  githubUrl: '',
  linkedInUrl: '',
  resumeUrl: '',
}

export const metrics = [
  { value: '50%', label: 'faster credit approvals' },
  { value: '45%', label: 'less manual review time' },
  { value: '130+', label: 'production data pipelines supported' },
  { value: '10,000+', label: 'applications served each month' },
]
Build the semantic landing page

Semantic elements give each part of the portfolio a clear purpose. The first runnable slice starts with the impact panel before the rest of the page grows around it.

  • Select src/App.tsx in the Explorer sidebar.
  • Press Ctrl+A to select the starter component.
  • Replace the selected starter code by pasting this first page shell:
import './App.css'
import { metrics, profile } from './portfolioData'

function App() {
  return (
    <>
      <main id="main-content">
        <section className="hero" id="top">
          <aside className="impact-panel" aria-label="Selected engineering impact">
            <p className="panel-label">Production impact</p>
            <div className="metric-grid">
              {metrics.map((metric) => (
                <div className="metric" key={metric.label}>
                  <strong>{metric.value}</strong>
                  <span>{metric.label}</span>
                </div>
              ))}
            </div>
          </aside>
        </section>
      </main>

      <footer>
        <p>{profile.name} | Backend Software Engineer</p>
      </footer>
    </>
  )
}

export default App

What does this component do?

  • The main element provides the page's primary content landmark.
  • The impact panel maps over metrics to render one card for each result.
  • Each metric label becomes a stable React key.
  • Save src/App.tsx.
  • Switch back to the local browser page from earlier.

You should see all four metric values plus the placeholder name in the footer. The starter counter and logos are gone.

Still seeing the Vite starter?

Confirm that you edited src/App.tsx. Check that the file is saved.

Use this prompt if the browser stays unchanged. Help me find why my saved App.tsx is not updating in the Vite browser page

  • Find the impact-panel aside in src/App.tsx.
  • Insert the hero copy immediately above that aside by pasting this code:
          <div className="hero-copy">
            <p className="eyebrow">Backend Software Engineer | Fintech | 3+ years</p>
            <h1>{profile.headline}</h1>
            <p className="hero-summary">{profile.summary}</p>
            <p className="availability">{profile.availability}</p>
            <div className="hero-actions">
              <a className="button primary" href="#projects">
                Explore my evidence
              </a>
              <a className="button secondary" href={`mailto:${profile.email}`}>
                Email me
              </a>
              {profile.resumeUrl && (
                <a className="button secondary" href={profile.resumeUrl}>
                  View resume
                </a>
              )}
            </div>
          </div>

How does the hero use profile data?

  • The headline and summary come directly from profile.
  • The email action builds an email link from profile.email.
  • The resume action renders only when profile.resumeUrl contains a value.
  • Save src/App.tsx.
  • Return to the local browser page.

You should see the backend engineering headline above the impact metrics. You should also see the evidence and email actions.

Missing the headline?

Confirm that the hero copy sits inside the hero section. It must appear before the impact-panel aside.

Ask for a JSX structure check if the page becomes blank. Help me check the hero and impact panel nesting in my App.tsx file

  • Find the opening main element in src/App.tsx.
  • Insert the skip link and site header immediately above main by pasting this code:
      <a className="skip-link" href="#main-content">
        Skip to main content
      </a>

      <header className="site-header">
        <a className="brand" href="#top" aria-label="Go to the top of the portfolio">
          {profile.name}
        </a>
        <nav aria-label="Primary navigation">
          <a href="#projects">Projects</a>
          <a href="#contact">Contact</a>
        </nav>
      </header>

Why use a header and skip link?

The header gives the page a labelled navigation landmark. The skip link lets keyboard users move directly to the main portfolio content.

  • Save src/App.tsx.
  • Return to the local browser page.

You should see your placeholder name above the hero. The header also shows links for projects and contact.

Header links appearing inside the hero?

Move the entire site-header block above main. Keep both elements inside the outer React fragment.

Use this prompt to check the surrounding tags. Help me fix the header placement in my App.tsx fragment

  • Find the closing tag for the hero section.
  • Insert the initial project evidence section immediately below it by pasting this code:
        <section className="section" id="projects" aria-labelledby="projects-title">
          <div className="section-heading">
            <div>
              <p className="eyebrow">Selected work</p>
              <h2 id="projects-title">Evidence matched to the role</h2>
            </div>
            <p>
              Filter the case studies by the engineering concern you want to inspect.
            </p>
          </div>
        </section>

What does this section establish?

The section creates a labelled destination for the hero action and navigation link. Its empty body exposes the difference between claiming impact and showing detailed evidence.

  • Save src/App.tsx.
  • Select the Explore my evidence link in the local browser page.

You should move to the selected work heading. The section has no case studies yet.

Evidence link does not move?

Confirm that the link uses #projects. Confirm that the section uses id="projects".

Ask for help comparing the matching fragment and ID. Help me fix the projects anchor link in my React portfolio

  • Find the closing main tag in src/App.tsx.
  • Insert the contact section immediately above that closing tag by pasting this code:
        <section className="contact-section" id="contact" aria-labelledby="contact-title">
          <p className="eyebrow">Next conversation</p>
          <h2 id="contact-title">Looking for a backend engineer who ships measurable outcomes?</h2>
          <p>{profile.availability}</p>
          <div className="contact-actions">
            <a className="button primary" href={`mailto:${profile.email}`}>
              {profile.email}
            </a>
          </div>
        </section>

How does the contact section work?

The section repeats your availability near the final action. Its email link comes from the same profile.email value used in the hero.

  • Save src/App.tsx.
  • Select the Contact navigation link in the local browser page.

You should move to the contact heading and see the placeholder email action.

Contact section missing?

Confirm that the contact section sits inside main. Check that it appears after the projects section.

Use this prompt if JSX nesting causes a blank page. Help me fix the contact section nesting in App.tsx

✔️ Awesome, I've got everything!

Your page structure is ready. Save src/App.tsx before adding the visual system.

ⓧ I'd like to double check the full code

import './App.css'
import { metrics, profile } from './portfolioData'

function App() {
  return (
    <>
      <a className="skip-link" href="#main-content">
        Skip to main content
      </a>

      <header className="site-header">
        <a className="brand" href="#top" aria-label="Go to the top of the portfolio">
          {profile.name}
        </a>
        <nav aria-label="Primary navigation">
          <a href="#projects">Projects</a>
          <a href="#contact">Contact</a>
        </nav>
      </header>

      <main id="main-content">
        <section className="hero" id="top">
          <div className="hero-copy">
            <p className="eyebrow">Backend Software Engineer | Fintech | 3+ years</p>
            <h1>{profile.headline}</h1>
            <p className="hero-summary">{profile.summary}</p>
            <p className="availability">{profile.availability}</p>
            <div className="hero-actions">
              <a className="button primary" href="#projects">
                Explore my evidence
              </a>
              <a className="button secondary" href={`mailto:${profile.email}`}>
                Email me
              </a>
              {profile.resumeUrl && (
                <a className="button secondary" href={profile.resumeUrl}>
                  View resume
                </a>
              )}
            </div>
          </div>

          <aside className="impact-panel" aria-label="Selected engineering impact">
            <p className="panel-label">Production impact</p>
            <div className="metric-grid">
              {metrics.map((metric) => (
                <div className="metric" key={metric.label}>
                  <strong>{metric.value}</strong>
                  <span>{metric.label}</span>
                </div>
              ))}
            </div>
          </aside>
        </section>

        <section className="section" id="projects" aria-labelledby="projects-title">
          <div className="section-heading">
            <div>
              <p className="eyebrow">Selected work</p>
              <h2 id="projects-title">Evidence matched to the role</h2>
            </div>
            <p>
              Filter the case studies by the engineering concern you want to inspect.
            </p>
          </div>
        </section>

        <section className="contact-section" id="contact" aria-labelledby="contact-title">
          <p className="eyebrow">Next conversation</p>
          <h2 id="contact-title">Looking for a backend engineer who ships measurable outcomes?</h2>
          <p>{profile.availability}</p>
          <div className="contact-actions">
            <a className="button primary" href={`mailto:${profile.email}`}>
              {profile.email}
            </a>
          </div>
        </section>
      </main>

      <footer>
        <p>{profile.name} | Backend Software Engineer</p>
      </footer>
    </>
  )
}

export default App
Style and test the first impression

The page structure now communicates the right information. A consistent visual system turns that structure into a clear scanning path.

  • Select src/index.css in the Explorer sidebar.
  • Press Ctrl+A to select the starter global styles.
  • Replace the selected styles with the global foundation by pasting this code:
:root {
  font-family:
    ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
  color: #e8edf7;
  background: #08111f;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-width: 320px;
  min-height: 100vh;
  background:
    radial-gradient(circle at 80% 10%, rgba(45, 212, 191, 0.12), transparent 30rem),
    #08111f;
}

What does the global foundation control?

  • The root rules define the page font and dark colour foundation.
  • Universal box sizing keeps element dimensions predictable.
  • The body background adds a subtle accent behind the portfolio.
  • Save src/index.css.
  • Return to the local browser page.

You should see a dark blue background with light text. The teal glow should sit near the upper-right area.

Page still has a white background?

Confirm that src/main.tsx still imports ./index.css. Check that the new rules replaced the starter styles.

Use this prompt to trace the stylesheet import. Help me find why src/index.css is not styling my Vite page

  • Append the interaction styles to src/index.css by pasting this code:
button,
a {
  font: inherit;
}

button {
  cursor: pointer;
}

button:focus-visible,
a:focus-visible {
  outline: 3px solid #5eead4;
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

How does this support interaction?

Buttons and links inherit the page font. Keyboard focus receives a visible teal outline.

The reduced-motion query shortens animation for people who request less movement in their operating system.

  • Save src/index.css.
  • Press Tab in the local browser page.

You should see a teal outline move to the first available link.

Cannot see the focus outline?

Use the keyboard to move focus. A mouse click may use a different focus treatment because the selector targets focus-visible.

Ask for help if keyboard focus remains invisible. Help me debug my CSS focus-visible outline

✔️ Awesome, I've got everything!

The global styles are ready. Save src/index.css before styling the component classes.

ⓧ I'd like to double check the full code

:root {
  font-family:
    ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
  color: #e8edf7;
  background: #08111f;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-width: 320px;
  min-height: 100vh;
  background:
    radial-gradient(circle at 80% 10%, rgba(45, 212, 191, 0.12), transparent 30rem),
    #08111f;
}

button,
a {
  font: inherit;
}

button {
  cursor: pointer;
}

button:focus-visible,
a:focus-visible {
  outline: 3px solid #5eead4;
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
  • Select src/App.css in the Explorer sidebar.
  • Press Ctrl+A to select the starter component styles.
  • Replace the selected styles with the colour variables and skip-link rules by pasting this code:
:root {
  --surface: #0e1a2d;
  --surface-strong: #13233b;
  --border: #29405f;
  --text: #e8edf7;
  --muted: #a9b7cc;
  --accent: #5eead4;
  --accent-dark: #0f766e;
  --max-width: 1120px;
}

.skip-link {
  position: fixed;
  top: -5rem;
  left: 1rem;
  z-index: 100;
  padding: 0.75rem 1rem;
  color: #08111f;
  background: var(--accent);
  border-radius: 0.5rem;
}

.skip-link:focus {
  top: 1rem;
}

What do these styles establish?

The custom properties keep repeated colours and widths consistent. The skip link stays off-screen until keyboard focus moves to it.

  • Save src/App.css.
  • Press Tab on the local browser page.

You should see the skip link move into the top-left corner.

Skip link stays hidden?

Confirm that src/App.tsx imports ./App.css. Check that the link uses the skip-link class.

Use this prompt to compare the class and selector. Help me debug why my skip-link CSS does not appear on keyboard focus

  • Append the header and navigation styles to src/App.css by pasting this code:
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: min(calc(100% - 2rem), var(--max-width));
  margin: 0 auto;
  padding: 1rem 0;
  background: rgba(8, 17, 31, 0.9);
  backdrop-filter: blur(12px);
}

.brand,
.site-header nav a {
  color: var(--text);
  text-decoration: none;
}

.brand {
  font-weight: 800;
}

.site-header nav {
  display: flex;
  gap: 1.25rem;
}

How does the header stay useful?

The header remains at the top while the page scrolls. Its constrained width aligns the brand and navigation with the page content.

  • Save src/App.css.
  • Scroll down the local browser page.

You should see the header remain at the top with the brand on the left and navigation on the right.

Header does not stay visible?

Confirm that the header uses the site-header class. Check that position: sticky and top: 0 are both present.

Ask for help if another rule overrides the position. Help me debug the sticky site-header in App.css

  • Append the navigation colour and hero layout styles to src/App.css by pasting this code:
.site-header nav a {
  color: var(--muted);
}

.site-header nav a:hover {
  color: var(--accent);
}

main,
footer {
  width: min(calc(100% - 2rem), var(--max-width));
  margin: 0 auto;
}

.hero {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: 3rem;
  align-items: center;
  min-height: 78vh;
  padding: 5rem 0;
}

Why use a hero grid?

The wider grid column gives the career message more room. The second column keeps the impact metrics visible during the first scan.

  • Save src/App.css.
  • Return to the top of the local browser page.

You should see the hero copy and impact panel form two columns on a wide browser window.

Hero still appears as one column?

Confirm that both hero-copy and impact-panel are direct children of the hero section.

Use this prompt to inspect the grid children. Help me debug the two-column hero grid in my React portfolio

  • Append the hero typography styles to src/App.css by pasting this code:
.eyebrow {
  margin: 0 0 0.75rem;
  color: var(--accent);
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

h1 {
  max-width: 13ch;
  margin-bottom: 1.5rem;
  font-size: clamp(2.5rem, 7vw, 5.4rem);
  line-height: 0.98;
  letter-spacing: -0.055em;
}

How does typography guide the scan?

The eyebrow identifies the role before the main statement. The responsive headline scale makes that statement the strongest element on the page.

  • Save src/App.css.
  • Return to the local browser page.

You should see a large backend engineering headline with a compact teal role label above it.

Headline size unchanged?

Check that the page headline uses an h1 element. Confirm that the new typography appears after the earlier hero rules.

Ask for help if a starter selector still overrides it. Help me find which CSS rule overrides my h1 styles

  • Append the supporting text and action layout styles to src/App.css by pasting this code:
h2 {
  margin-bottom: 0.75rem;
  font-size: clamp(2rem, 4vw, 3.25rem);
  line-height: 1.05;
  letter-spacing: -0.035em;
}

.hero-summary,
.availability,
.section-heading > p,
.contact-section > p {
  color: var(--muted);
  line-height: 1.75;
}

.hero-summary {
  max-width: 65ch;
  font-size: 1.1rem;
}

.availability {
  max-width: 58ch;
}

Why soften supporting text?

Muted text keeps the headline and metrics visually dominant. Wider line spacing keeps the summary readable without competing for attention.

  • Save src/App.css.
  • Return to the local browser page.

You should see softer summary and availability text beneath the bright headline.

Supporting text remains bright?

Confirm that the paragraphs use the hero-summary and availability classes. Check the spelling against the CSS selectors.

Use this prompt to compare the class names. Help me match my hero text class names between App.tsx and App.css

  • Append the action and button styles to src/App.css by pasting this code:
.hero-actions,
.contact-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.hero-actions {
  margin-top: 2rem;
}

.button {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  padding: 0.75rem 1rem;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  text-decoration: none;
  transition:
    transform 160ms ease,
    border-color 160ms ease,
    background 160ms ease;
}

What makes these actions scannable?

The flex layout keeps related actions together. A consistent minimum height gives each link a clear interaction target.

  • Save src/App.css.
  • Return to the local browser page.

You should see the hero links align as rounded controls with equal heights.

Actions still look like plain links?

Confirm that every action includes the button class. The colour modifiers build on that shared class.

Ask for help if the shared class is present without visible styling. Help me debug why my button class is not styling anchor elements

  • Append the button colour and hover styles to src/App.css by pasting this code:
.button:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
}

.primary {
  color: #052e2b;
  background: var(--accent);
  border-color: var(--accent);
  font-weight: 800;
}

.secondary {
  color: var(--text);
  background: transparent;
}

How do the action priorities differ?

The filled primary action attracts the first click. Secondary actions remain visible with less visual weight.

  • Save src/App.css.
  • Move the pointer over each hero action in the local browser page.

You should see each action lift slightly. The evidence action should use a teal background.

Primary action has no teal fill?

Confirm that the evidence link includes both button and primary in its class value.

Use this prompt to inspect the combined classes. Help me debug the primary button classes in my React portfolio

  • Append the impact panel foundation to src/App.css by pasting this code:
.impact-panel,
.contact-section {
  border: 1px solid var(--border);
  border-radius: 1.25rem;
  background: linear-gradient(145deg, rgba(19, 35, 59, 0.92), rgba(10, 24, 42, 0.92));
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
}

.impact-panel {
  padding: 1.5rem;
}

.panel-label {
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 700;
  text-transform: uppercase;
}

.metric-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
}

Why group the metrics?

The bordered panel turns separate figures into one body of production evidence. Its two-column grid keeps all four results within a compact scan.

  • Save src/App.css.
  • Return to the local browser page.

You should see the four metrics inside a dark rounded panel arranged as a two-by-two grid.

Metrics remain in one line?

Confirm that the metric wrapper uses the metric-grid class. Check that each metric sits directly inside that wrapper.

Ask for help if the grid selector appears crossed out. Help me debug the metric-grid layout in App.css

  • Append the individual metric styles to src/App.css by pasting this code:
.metric {
  min-height: 9rem;
  padding: 1rem;
  border-radius: 1rem;
  background: rgba(8, 17, 31, 0.65);
}

.metric strong {
  display: block;
  margin-bottom: 0.5rem;
  color: var(--accent);
  font-size: 2rem;
}

.metric span {
  color: var(--muted);
  line-height: 1.4;
}

How does each metric communicate impact?

The bright value catches attention first. Its muted label immediately explains the outcome behind that number.

  • Save src/App.css.
  • Return to the local browser page.

You should see teal values for 50%, 45%, 130+, and 10,000+ above their outcome labels.

Metric values lack emphasis?

Confirm that each value renders inside a strong element within a metric card.

Use this prompt to inspect the selector structure. Help me match the metric strong selector to my JSX

  • Append the section and contact layout styles to src/App.css by pasting this code:
.section {
  padding: 5rem 0;
  scroll-margin-top: 5rem;
}

.section-heading {
  display: grid;
  grid-template-columns: 1fr 0.7fr;
  gap: 2rem;
  align-items: end;
  margin-bottom: 2rem;
}

.contact-section {
  margin: 5rem 0;
  padding: clamp(1.5rem, 5vw, 3.5rem);
  text-align: center;
}

.contact-section h2,
.contact-section > p {
  max-width: 760px;
  margin-right: auto;
  margin-left: auto;
}

How do sections create a scanning path?

Generous vertical spacing separates the page into distinct decisions. The centred contact section closes the page with one clear next action.

  • Save src/App.css.
  • Select the Contact navigation link in the local browser page.

You should move to a centred contact panel with space separating it from the evidence heading.

Navigation hides the section heading?

Confirm that the projects section uses the section class. Its scroll margin leaves room for the sticky header.

Ask for help if the heading still sits behind the header. Help me debug scroll-margin-top with my sticky header

  • Append the contact action and footer styles to src/App.css by pasting this code:
.contact-actions {
  justify-content: center;
  margin-top: 1.5rem;
}

footer {
  padding: 1rem 0 3rem;
  color: var(--muted);
  text-align: center;
}

What does this finishing layer add?

The contact action sits beneath the closing message. The muted footer completes the page without competing with the main call to action.

  • Save src/App.css.
  • Scroll to the bottom of the local browser page.

You should see the email action centred above a muted footer line.

Footer touches the browser edge?

Confirm that the footer rule includes bottom padding. Check that the footer remains outside main in src/App.tsx.

Use this prompt to inspect the footer placement. Help me fix the spacing and placement of my React footer

  • Append the initial responsive rules to src/App.css by pasting this code:
@media (max-width: 900px) {
  .hero,
  .section-heading {
    grid-template-columns: 1fr;
  }

  .hero {
    min-height: auto;
  }
}

@media (max-width: 760px) {
  .site-header {
    align-items: flex-start;
  }

  .site-header nav {
    display: none;
  }

  .hero {
    gap: 2rem;
    padding: 4rem 0 2rem;
  }

  .metric-grid {
    grid-template-columns: 1fr;
  }

  .button {
    width: 100%;
  }
}

How does the first responsive pass work?

The hero becomes one column when horizontal space tightens. Smaller screens also stack metrics and actions into full-width rows.

  • Save src/App.css.
  • Resize the local browser window to a narrow phone-like width.

You should see the hero form one column. The metric cards and buttons should also stack without horizontal scrolling.

Mobile layout still uses two columns?

Confirm that both media queries close with matching braces. Check that the responsive rules appear after the default grid rules.

Use this prompt to check the cascade. Help me debug why my responsive hero and metric grids are not stacking

✔️ Awesome, I've got everything!

Your component styles are complete for this first landing page. Save src/App.css before the final check.

ⓧ I'd like to double check the full code

:root {
  --surface: #0e1a2d;
  --surface-strong: #13233b;
  --border: #29405f;
  --text: #e8edf7;
  --muted: #a9b7cc;
  --accent: #5eead4;
  --accent-dark: #0f766e;
  --max-width: 1120px;
}

.skip-link {
  position: fixed;
  top: -5rem;
  left: 1rem;
  z-index: 100;
  padding: 0.75rem 1rem;
  color: #08111f;
  background: var(--accent);
  border-radius: 0.5rem;
}

.skip-link:focus {
  top: 1rem;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: min(calc(100% - 2rem), var(--max-width));
  margin: 0 auto;
  padding: 1rem 0;
  background: rgba(8, 17, 31, 0.9);
  backdrop-filter: blur(12px);
}

.brand,
.site-header nav a {
  color: var(--text);
  text-decoration: none;
}

.brand {
  font-weight: 800;
}

.site-header nav {
  display: flex;
  gap: 1.25rem;
}

.site-header nav a {
  color: var(--muted);
}

.site-header nav a:hover {
  color: var(--accent);
}

main,
footer {
  width: min(calc(100% - 2rem), var(--max-width));
  margin: 0 auto;
}

.hero {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: 3rem;
  align-items: center;
  min-height: 78vh;
  padding: 5rem 0;
}

.eyebrow {
  margin: 0 0 0.75rem;
  color: var(--accent);
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

h1 {
  max-width: 13ch;
  margin-bottom: 1.5rem;
  font-size: clamp(2.5rem, 7vw, 5.4rem);
  line-height: 0.98;
  letter-spacing: -0.055em;
}

h2 {
  margin-bottom: 0.75rem;
  font-size: clamp(2rem, 4vw, 3.25rem);
  line-height: 1.05;
  letter-spacing: -0.035em;
}

.hero-summary,
.availability,
.section-heading > p,
.contact-section > p {
  color: var(--muted);
  line-height: 1.75;
}

.hero-summary {
  max-width: 65ch;
  font-size: 1.1rem;
}

.availability {
  max-width: 58ch;
}

.hero-actions,
.contact-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.hero-actions {
  margin-top: 2rem;
}

.button {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  padding: 0.75rem 1rem;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  text-decoration: none;
  transition:
    transform 160ms ease,
    border-color 160ms ease,
    background 160ms ease;
}

.button:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
}

.primary {
  color: #052e2b;
  background: var(--accent);
  border-color: var(--accent);
  font-weight: 800;
}

.secondary {
  color: var(--text);
  background: transparent;
}

.impact-panel,
.contact-section {
  border: 1px solid var(--border);
  border-radius: 1.25rem;
  background: linear-gradient(145deg, rgba(19, 35, 59, 0.92), rgba(10, 24, 42, 0.92));
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
}

.impact-panel {
  padding: 1.5rem;
}

.panel-label {
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 700;
  text-transform: uppercase;
}

.metric-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
}

.metric {
  min-height: 9rem;
  padding: 1rem;
  border-radius: 1rem;
  background: rgba(8, 17, 31, 0.65);
}

.metric strong {
  display: block;
  margin-bottom: 0.5rem;
  color: var(--accent);
  font-size: 2rem;
}

.metric span {
  color: var(--muted);
  line-height: 1.4;
}

.section {
  padding: 5rem 0;
  scroll-margin-top: 5rem;
}

.section-heading {
  display: grid;
  grid-template-columns: 1fr 0.7fr;
  gap: 2rem;
  align-items: end;
  margin-bottom: 2rem;
}

.contact-section {
  margin: 5rem 0;
  padding: clamp(1.5rem, 5vw, 3.5rem);
  text-align: center;
}

.contact-section h2,
.contact-section > p {
  max-width: 760px;
  margin-right: auto;
  margin-left: auto;
}

.contact-actions {
  justify-content: center;
  margin-top: 1.5rem;
}

footer {
  padding: 1rem 0 3rem;
  color: var(--muted);
  text-align: center;
}

@media (max-width: 900px) {
  .hero,
  .section-heading {
    grid-template-columns: 1fr;
  }

  .hero {
    min-height: auto;
  }
}

@media (max-width: 760px) {
  .site-header {
    align-items: flex-start;
  }

  .site-header nav {
    display: none;
  }

  .hero {
    gap: 2rem;
    padding: 4rem 0 2rem;
  }

  .metric-grid {
    grid-template-columns: 1fr;
  }

  .button {
    width: 100%;
  }
}

Before you inspect the finished page, do you think four strong metrics can defend every claim in the headline?

  • Return to the PowerShell terminal from earlier. Confirm that the development server still displays its local URL.
  • Switch back to the local browser page.
  • Refresh the page if the latest styles are not already visible.

You should see the Backend Software Engineer headline plus the 50%, 45%, 130+, and 10,000+ impact metrics. You should also see an evidence heading with no case studies beneath it.

The evidence gap is intentional

Your headline now earns attention through a focused identity and quantified impact. The empty evidence area shows that a recruiter still cannot inspect the projects or experience behind those claims.

Your first impression now communicates a backend SDE II identity in one scan. Next, you will turn its strongest claims into filterable project and experience evidence.

Turn Claims into Filtered Evidence

Your landing page makes a strong first impression. However, recruiters still need proof that each claim comes from real engineering work.

This step turns your résumé details into structured evidence with TypeScript. You will use React state to help visitors filter that evidence by the engineering specialty they care about.

In this step, get ready to:
  • Add experience, project, skill, and education evidence to the portfolio data.
  • Build typed filters that select relevant project case studies.
  • Render and style the complete evidence sections.
Complete the evidence model

A consistent data model keeps your résumé evidence separate from the page layout. Each project also receives one or more specialties so the interface can decide when to show it.

  • Switch back to src/portfolioData.ts in your editor.
  • Replace the entire file with the complete evidence model in the comparison tab below.

✔️ Awesome, I've got everything!

Your data file should now contain the two Wells Fargo roles, three case studies, four skill groups, and education details.

ⓧ I'd like to double check the full code

export type Specialty =
  | 'Backend'
  | 'Distributed Systems'
  | 'Data Engineering'

export const profile = {
  name: 'Your Name',
  headline: 'Backend Software Engineer building reliable, data-intensive systems',
  summary:
    'Software Engineer with 3+ years of experience building production APIs, microservices, and data pipelines for fintech systems with Java, Python, and TypeScript.',
  availability:
    'Open to SDE II-equivalent opportunities across Europe, APAC, and North America.',
  email: 'your-email@example.com',
  githubUrl: '',
  linkedInUrl: '',
  resumeUrl: '',
}

export const metrics = [
  { value: '50%', label: 'faster credit approvals' },
  { value: '45%', label: 'less manual review time' },
  { value: '130+', label: 'production data pipelines supported' },
  { value: '10,000+', label: 'applications served each month' },
]

export const experience = [
  {
    role: 'Software Engineer',
    company: 'Wells Fargo',
    location: 'Bengaluru, India',
    period: 'Sep 2024 - Present',
    achievements: [
      'Accelerated credit approval speed by 50% by shipping real-time KYC and credit-decisioning microservices in Spring Boot.',
      'Scaled unstructured-data processing beyond 100 MB daily with a distributed Java and Python pipeline, reducing manual review time by 45%.',
      'Cut turnaround time by 25% for credit lines exceeding $100,000 by architecting Java backend services for a small-business banking platform.',
      'Increased multi-owner account onboarding conversion by 15% through REST APIs for business accounts with multiple stakeholders.',
    ],
  },
  {
    role: 'Program Associate - Data & ETL Engineering',
    company: 'Wells Fargo',
    location: 'Bengaluru, India',
    period: 'Aug 2023 - Sep 2024',
    achievements: [
      'Cut manual reconciliation effort by 60% with an audit framework that centralized workflow load counts.',
      'Maintained 24/7 data availability across 130+ integration and migration pipelines while resolving failures within a 2-hour SLA.',
    ],
  },
]

export const projects: Array<{
  title: string
  stack: string
  summary: string
  evidence: string[]
  specialties: Specialty[]
}> = [
  {
    title: 'SyncBoard',
    stack: 'TypeScript, React, Node.js, Express, Socket.IO, PostgreSQL',
    summary:
      'A real-time collaborative board designed around event-driven state synchronization and durable storage.',
    evidence: [
      'Synchronized task changes across connected clients in under 50 ms.',
      'Separated in-memory presence tracking from persistent PostgreSQL storage.',
      'Reduced redundant renders through custom React hooks.',
    ],
    specialties: ['Backend', 'Distributed Systems'],
  },
  {
    title: 'AI-Powered SQL Assistant',
    stack: 'Python, FastAPI, React, PostgreSQL, Docker',
    summary:
      'A natural-language SQL assistant with validation boundaries before database execution.',
    evidence: [
      'Blocked destructive DELETE and DROP operations before execution.',
      'Translated natural-language questions into SQLite-compatible SQL with 90% accuracy.',
      'Reduced malformed request errors by 25% with FastAPI and Pydantic schemas.',
    ],
    specialties: ['Backend', 'Data Engineering'],
  },
  {
    title: 'Full-Stack E-Commerce Platform',
    stack: 'React, Firebase, Stripe, Express, JavaScript',
    summary:
      'A production-deployed commerce flow covering authentication, checkout, and transaction persistence.',
    evidence: [
      'Processed end-to-end checkout through Stripe Elements and Firebase Cloud Functions.',
      'Persisted 500+ transactions with zero reported data loss.',
      'Scoped order history and session state for more than 1,000 authenticated users.',
    ],
    specialties: ['Backend'],
  },
]

export const skillGroups = [
  {
    label: 'Backend & APIs',
    skills: [
      'Spring Boot',
      'REST API design',
      'FastAPI',
      'Flask',
      'Node.js',
      'Express',
      'Microservices',
    ],
  },
  {
    label: 'Languages',
    skills: ['Java', 'Python', 'SQL', 'JavaScript', 'TypeScript', 'C/C++'],
  },
  {
    label: 'Data & Infrastructure',
    skills: [
      'PostgreSQL',
      'Oracle',
      'Informatica Cloud',
      'PowerCenter',
      'Docker',
      'Kubernetes',
      'Jenkins',
      'Autosys',
      'UNIX',
      'Git',
    ],
  },
  {
    label: 'Frontend',
    skills: ['React', 'Material UI', 'Socket.IO', 'Firebase', 'HTML/CSS'],
  },
]

export const education = {
  school: 'Delhi Technological University',
  degree: 'Bachelor of Technology in Computer Engineering',
  period: 'Aug 2019 - May 2023',
}

How is the evidence organized?

  • The Specialty type limits project categories to three supported values.
  • The projects array connects each case study to the specialties it proves.
  • The experience array preserves quantified outcomes from both Wells Fargo roles.
  • The skillGroups array organizes tools by the engineering problems they help solve.
  • Save src/portfolioData.ts.
  • Return to the terminal panel from earlier.

You should see the development server continue without a TypeScript error. Good progress. Your evidence now has a typed structure that the interface can use.

Seeing a TypeScript error?

Check that each project specialty matches Backend, Distributed Systems, or Data Engineering exactly.

Make sure each object ends with the same comma shown in the comparison file.

Help me fix my portfolio data error

Render the filtered evidence

The page needs one active filter at a time. The Focus type adds an All option to the three specialties.

The selected value lives in activeFocus. Every render derives visibleProjects from the original project array without changing the source data.

  • Switch back to src/App.tsx.
  • Replace the entire file with the complete evidence interface in the comparison tab below.

✔️ Awesome, I've got everything!

Your component should now render filters, case studies, experience, skills, education, and conditional profile links.

ⓧ I'd like to double check the full code

import { useState } from 'react'
import './App.css'
import {
  education,
  experience,
  metrics,
  profile,
  projects,
  skillGroups,
  type Specialty,
} from './portfolioData'

type Focus = 'All' | Specialty

const focusOptions: Focus[] = [
  'All',
  'Backend',
  'Distributed Systems',
  'Data Engineering',
]

function App() {
  const [activeFocus, setActiveFocus] = useState<Focus>('All')

  const visibleProjects =
    activeFocus === 'All'
      ? projects
      : projects.filter((project) =>
          project.specialties.includes(activeFocus),
        )

  const externalLinks = [
    { label: 'GitHub', href: profile.githubUrl },
    { label: 'LinkedIn', href: profile.linkedInUrl },
  ].filter((link) => link.href.length > 0)

  return (
    <>
      <a className="skip-link" href="#main-content">
        Skip to main content
      </a>

      <header className="site-header">
        <a className="brand" href="#top" aria-label="Go to the top of the portfolio">
          {profile.name}
        </a>
        <nav aria-label="Primary navigation">
          <a href="#projects">Projects</a>
          <a href="#experience">Experience</a>
          <a href="#skills">Skills</a>
          <a href="#contact">Contact</a>
        </nav>
      </header>

      <main id="main-content">
        <section className="hero" id="top">
          <div className="hero-copy">
            <p className="eyebrow">Backend Software Engineer | Fintech | 3+ years</p>
            <h1>{profile.headline}</h1>
            <p className="hero-summary">{profile.summary}</p>
            <p className="availability">{profile.availability}</p>
            <div className="hero-actions">
              <a className="button primary" href="#projects">
                Explore my evidence
              </a>
              <a className="button secondary" href={`mailto:${profile.email}`}>
                Email me
              </a>
              {profile.resumeUrl && (
                <a className="button secondary" href={profile.resumeUrl}>
                  View resume
                </a>
              )}
            </div>
          </div>

          <aside className="impact-panel" aria-label="Selected engineering impact">
            <p className="panel-label">Production impact</p>
            <div className="metric-grid">
              {metrics.map((metric) => (
                <div className="metric" key={metric.label}>
                  <strong>{metric.value}</strong>
                  <span>{metric.label}</span>
                </div>
              ))}
            </div>
          </aside>
        </section>

        <section className="section" id="projects" aria-labelledby="projects-title">
          <div className="section-heading">
            <div>
              <p className="eyebrow">Selected work</p>
              <h2 id="projects-title">Evidence matched to the role</h2>
            </div>
            <p>
              Filter the case studies by the engineering concern you want to inspect.
            </p>
          </div>

          <div className="filters" aria-label="Filter projects by specialty">
            {focusOptions.map((focus) => (
              <button
                className="filter-button"
                type="button"
                key={focus}
                aria-pressed={activeFocus === focus}
                onClick={() => setActiveFocus(focus)}
              >
                {focus}
              </button>
            ))}
          </div>

          <div className="project-grid" aria-live="polite">
            {visibleProjects.map((project) => (
              <article className="project-card" key={project.title}>
                <div className="project-topline">
                  <h3>{project.title}</h3>
                  <span>{project.specialties.join(' + ')}</span>
                </div>
                <p className="stack">{project.stack}</p>
                <p>{project.summary}</p>
                <ul>
                  {project.evidence.map((item) => (
                    <li key={item}>{item}</li>
                  ))}
                </ul>
              </article>
            ))}
          </div>
        </section>

        <section className="section" id="experience" aria-labelledby="experience-title">
          <div className="section-heading compact">
            <div>
              <p className="eyebrow">Production experience</p>
              <h2 id="experience-title">Engineering outcomes at scale</h2>
            </div>
          </div>

          <div className="timeline">
            {experience.map((item) => (
              <article className="timeline-item" key={`${item.role}-${item.period}`}>
                <div className="timeline-meta">
                  <p>{item.period}</p>
                  <p>{item.location}</p>
                </div>
                <div>
                  <h3>{item.role}</h3>
                  <p className="company">{item.company}</p>
                  <ul>
                    {item.achievements.map((achievement) => (
                      <li key={achievement}>{achievement}</li>
                    ))}
                  </ul>
                </div>
              </article>
            ))}
          </div>
        </section>

        <section className="section" id="skills" aria-labelledby="skills-title">
          <div className="section-heading compact">
            <div>
              <p className="eyebrow">Technical range</p>
              <h2 id="skills-title">Tools grouped by engineering problem</h2>
            </div>
          </div>

          <div className="skills-grid">
            {skillGroups.map((group) => (
              <article className="skill-card" key={group.label}>
                <h3>{group.label}</h3>
                <ul className="skill-list">
                  {group.skills.map((skill) => (
                    <li key={skill}>{skill}</li>
                  ))}
                </ul>
              </article>
            ))}
          </div>

          <article className="education-card">
            <p className="eyebrow">Education</p>
            <h3>{education.degree}</h3>
            <p>{education.school}</p>
            <p>{education.period}</p>
          </article>
        </section>

        <section className="contact-section" id="contact" aria-labelledby="contact-title">
          <p className="eyebrow">Next conversation</p>
          <h2 id="contact-title">Looking for a backend engineer who ships measurable outcomes?</h2>
          <p>{profile.availability}</p>
          <div className="contact-actions">
            <a className="button primary" href={`mailto:${profile.email}`}>
              {profile.email}
            </a>
            {externalLinks.map((link) => (
              <a
                className="button secondary"
                href={link.href}
                target="_blank"
                rel="noreferrer"
                key={link.label}
              >
                {link.label} (opens in new tab)
              </a>
            ))}
          </div>
        </section>
      </main>

      <footer>
        <p>{profile.name} | Backend Software Engineer</p>
      </footer>
    </>
  )
}

export default App

How does the filter work?

  • The useState hook stores the current focus as a typed value.
  • The filter() operation creates a new visible list without changing projects.
  • The aria-pressed attribute communicates which filter is selected.
  • The aria-live region announces project-list updates to assistive technology.
  • Save src/App.tsx.
  • Return to the browser tab served by the development server.
  • Refresh the portfolio page.

You should see four filter buttons above three project cards. You should also see the Wells Fargo timeline, grouped skills, education, and contact sections below them.

Seeing a blank page?

Check that every imported name in src/App.tsx matches an export in src/portfolioData.ts.

Confirm that useState is imported from React at the top of the file.

Help me debug the evidence interface

Style and test each focus

The new sections need a clear scanning hierarchy. Selected filters receive the accent treatment while project cards, timeline entries, skill groups, and education share the same visual language.

  • Switch back to src/App.css.
  • Replace the entire file with the complete evidence styles in the comparison tab below.

✔️ Awesome, I've got everything!

Your stylesheet should now cover every evidence section plus the two-column and one-column responsive layouts.

ⓧ I'd like to double check the full code

:root {
  --surface: #0e1a2d;
  --surface-strong: #13233b;
  --border: #29405f;
  --text: #e8edf7;
  --muted: #a9b7cc;
  --accent: #5eead4;
  --accent-dark: #0f766e;
  --max-width: 1120px;
}

.skip-link {
  position: fixed;
  top: -5rem;
  left: 1rem;
  z-index: 100;
  padding: 0.75rem 1rem;
  color: #08111f;
  background: var(--accent);
  border-radius: 0.5rem;
}

.skip-link:focus {
  top: 1rem;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: min(calc(100% - 2rem), var(--max-width));
  margin: 0 auto;
  padding: 1rem 0;
  background: rgba(8, 17, 31, 0.9);
  backdrop-filter: blur(12px);
}

.brand,
.site-header nav a {
  color: var(--text);
  text-decoration: none;
}

.brand {
  font-weight: 800;
}

.site-header nav {
  display: flex;
  gap: 1.25rem;
}

.site-header nav a {
  color: var(--muted);
}

.site-header nav a:hover {
  color: var(--accent);
}

main,
footer {
  width: min(calc(100% - 2rem), var(--max-width));
  margin: 0 auto;
}

.hero {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: 3rem;
  align-items: center;
  min-height: 78vh;
  padding: 5rem 0;
}

.eyebrow {
  margin: 0 0 0.75rem;
  color: var(--accent);
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

h1 {
  max-width: 13ch;
  margin-bottom: 1.5rem;
  font-size: clamp(2.5rem, 7vw, 5.4rem);
  line-height: 0.98;
  letter-spacing: -0.055em;
}

h2 {
  margin-bottom: 0.75rem;
  font-size: clamp(2rem, 4vw, 3.25rem);
  line-height: 1.05;
  letter-spacing: -0.035em;
}

h3 {
  margin-bottom: 0.5rem;
  font-size: 1.2rem;
}

.hero-summary,
.availability,
.section-heading > p,
.project-card > p,
.contact-section > p {
  color: var(--muted);
  line-height: 1.75;
}

.hero-summary {
  max-width: 65ch;
  font-size: 1.1rem;
}

.availability {
  max-width: 58ch;
}

.hero-actions,
.contact-actions,
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.hero-actions {
  margin-top: 2rem;
}

.button,
.filter-button {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  padding: 0.75rem 1rem;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  text-decoration: none;
  transition:
    transform 160ms ease,
    border-color 160ms ease,
    background 160ms ease;
}

.button:hover,
.filter-button:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
}

.primary {
  color: #052e2b;
  background: var(--accent);
  border-color: var(--accent);
  font-weight: 800;
}

.secondary {
  color: var(--text);
  background: transparent;
}

.impact-panel,
.project-card,
.timeline-item,
.skill-card,
.education-card,
.contact-section {
  border: 1px solid var(--border);
  border-radius: 1.25rem;
  background: linear-gradient(145deg, rgba(19, 35, 59, 0.92), rgba(10, 24, 42, 0.92));
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
}

.impact-panel {
  padding: 1.5rem;
}

.panel-label {
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 700;
  text-transform: uppercase;
}

.metric-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
}

.metric {
  min-height: 9rem;
  padding: 1rem;
  border-radius: 1rem;
  background: rgba(8, 17, 31, 0.65);
}

.metric strong {
  display: block;
  margin-bottom: 0.5rem;
  color: var(--accent);
  font-size: 2rem;
}

.metric span {
  color: var(--muted);
  line-height: 1.4;
}

.section {
  padding: 5rem 0;
  scroll-margin-top: 5rem;
}

.section-heading {
  display: grid;
  grid-template-columns: 1fr 0.7fr;
  gap: 2rem;
  align-items: end;
  margin-bottom: 2rem;
}

.section-heading.compact {
  grid-template-columns: 1fr;
}

.filters {
  margin-bottom: 1.5rem;
}

.filter-button {
  color: var(--text);
  background: var(--surface);
}

.filter-button[aria-pressed='true'] {
  color: #052e2b;
  background: var(--accent);
  border-color: var(--accent);
  font-weight: 800;
}

.project-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

.project-card,
.skill-card,
.education-card {
  padding: 1.5rem;
}

.project-topline {
  display: flex;
  gap: 1rem;
  align-items: start;
  justify-content: space-between;
}

.project-topline span {
  max-width: 14rem;
  color: var(--accent);
  font-size: 0.75rem;
  font-weight: 800;
  text-align: right;
}

.stack {
  color: var(--accent) !important;
  font-size: 0.84rem;
  font-weight: 700;
}

.project-card ul,
.timeline-item ul {
  margin: 1rem 0 0;
  padding-left: 1.2rem;
  color: var(--muted);
  line-height: 1.65;
}

.project-card li + li,
.timeline-item li + li {
  margin-top: 0.55rem;
}

.timeline {
  display: grid;
  gap: 1rem;
}

.timeline-item {
  display: grid;
  grid-template-columns: 0.35fr 1fr;
  gap: 2rem;
  padding: 1.5rem;
}

.timeline-meta,
.company,
.education-card p {
  color: var(--muted);
}

.timeline-meta p {
  margin-bottom: 0.4rem;
}

.company {
  font-weight: 700;
}

.skills-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
}

.skill-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
}

.skill-list li {
  padding: 0.45rem 0.7rem;
  color: var(--muted);
  background: rgba(8, 17, 31, 0.7);
  border: 1px solid var(--border);
  border-radius: 999px;
}

.education-card {
  margin-top: 1rem;
}

.contact-section {
  margin: 5rem 0;
  padding: clamp(1.5rem, 5vw, 3.5rem);
  text-align: center;
}

.contact-section h2,
.contact-section > p {
  max-width: 760px;
  margin-right: auto;
  margin-left: auto;
}

.contact-actions {
  justify-content: center;
  margin-top: 1.5rem;
}

footer {
  padding: 1rem 0 3rem;
  color: var(--muted);
  text-align: center;
}

@media (max-width: 900px) {
  .hero,
  .section-heading,
  .timeline-item {
    grid-template-columns: 1fr;
  }

  .hero {
    min-height: auto;
  }

  .project-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 760px) {
  .site-header {
    align-items: flex-start;
  }

  .site-header nav {
    display: none;
  }

  .hero {
    gap: 2rem;
    padding: 4rem 0 2rem;
  }

  .metric-grid,
  .project-grid,
  .skills-grid {
    grid-template-columns: 1fr;
  }

  .project-topline {
    display: block;
  }

  .project-topline span {
    display: block;
    margin-bottom: 1rem;
    text-align: left;
  }

  .button,
  .filter-button {
    width: 100%;
  }
}

What do these styles add?

  • The pressed filter receives the same accent treatment as the portfolio's primary actions.
  • The project grid starts with three columns for quick comparison.
  • The timeline separates role context from quantified achievements.
  • The media queries reduce the evidence layout to two columns before reaching one column.
  • Save src/App.css.
  • Return to the portfolio in your browser.
  • Refresh the page.

You should see styled filter controls above a three-column project grid. The experience, skills, education, and contact sections should share the dark card treatment.

Do the new sections look unstyled?

Confirm that src/App.tsx still imports ./App.css near the top of the file.

Check that selector names such as .filter-button, .project-grid, and .timeline-item match the component exactly.

Help me debug the evidence styles

Before you test the filters, which project cards do you expect each specialty to keep visible?

  • Select Backend in the filter controls.
  • Select Distributed Systems in the filter controls.
  • Select Data Engineering in the filter controls.
  • Select All to restore every case study.

The selected highlight should move with aria-pressed. Backend shows all three projects, Distributed Systems shows SyncBoard, and Data Engineering shows AI-Powered SQL Assistant.

Your portfolio now backs its headline with filterable evidence from production work and case studies. Next up, you will make the experience work cleanly on smaller screens and confirm it passes a production build.

Make the Portfolio Recruiter-Ready

Your React portfolio in Visual Studio Code now turns your experience into filtered evidence that recruiters can scan.

A job-search site must remain readable on a phone. It also needs keyboard access, accurate search metadata, and a clean production build.

In this step, get ready to:
  • Verify the portfolio's keyboard behavior.
  • Personalize the recruiter-facing details.
  • Produce a responsive and search-ready build.
Test keyboard and mobile behavior

Your existing semantic HTML gives each part of the page a clear purpose. The responsive CSS adapts that structure to smaller screens.

These checks confirm that the page works without a mouse. They also expose layout problems before recruiters find them.

  • Switch back to the local portfolio in your browser.
  • Press Tab once.

You should see the Skip to main content link move into view with a teal focus outline.

  • Press Enter while the skip link has focus.

Your keyboard focus should move directly to the main portfolio content below the header.

  • Continue pressing Tab through the navigation links.
  • Continue pressing Tab through the filter controls.
  • Continue pressing Tab through the available contact actions.

Every focused link or button should have a visible teal outline. Each control should remain usable without a mouse.

  • Activate each specialty filter with Enter or the Space key.

The selected filter should receive aria-pressed. The visible project cards should update to match that specialty.

  • Resize the browser window until its width is below 760px.
  • Try to scroll the page horizontally.

You should see one column with full-width buttons. The page should stay inside the viewport without horizontal scrolling.

Keyboard focus or mobile layout missing?

Confirm that the browser is showing the local development URL from the running dev server. Refresh the page after saving any recent changes.

Check that your current src/App.tsx, src/App.css, and src/index.css still match the code completed in the earlier steps.

Help me diagnose my portfolio's keyboard focus or mobile layout

Good progress. Your portfolio now stays readable on a narrow screen and remains navigable from the keyboard.

Add public details and search metadata

Search metadata gives browsers a clear title and description for your portfolio. Public profile details give recruiters a direct path to your work.

Use details that you are comfortable publishing. Empty optional URLs stay hidden because externalLinks filters them before rendering.

  • Record the public name for profile.name: your public name.
  • Record the public email for profile.email: your public email address.
  • Record the supplied URL for profile.githubUrl: your public GitHub profile URL.
  • Record the supplied URL for profile.linkedInUrl: your public LinkedIn profile URL.
  • Record the supplied URL for profile.resumeUrl: your hosted resume URL.
  • Replace the contents of index.html with the final metadata structure by pasting this code:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta
      name="description"
      content="Backend software engineer portfolio featuring fintech APIs, microservices, distributed systems, and data engineering impact."
    />
    <title>Backend Software Engineer Portfolio | Your Name</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

What does this metadata do?

  • The viewport metadata lets the layout use the actual width of a phone screen.
  • The description summarizes your fintech APIs, microservices, distributed systems, and data engineering impact for search surfaces.
  • The title identifies the page as a backend software engineering portfolio in the browser tab.
  • The root element and module script preserve the existing application entry point.
  • Replace Your Name in the title with your public name.
  • Save index.html.
  • Return to the local portfolio in your browser.
  • Refresh the page.

The browser tab should now show Backend Software Engineer Portfolio followed by your public name.

Browser tab still shows the starter title?

Confirm that you edited the index.html inside backend-engineer-portfolio. Save the file before refreshing the local page.

Help me fix the metadata in my portfolio

  • In src/portfolioData.ts, locate the profile object.
  • Replace its public name, email, GitHub URL, LinkedIn URL, and resume URL with the five values you recorded.
  • Save src/portfolioData.ts.
  • Return to the local portfolio in your browser.
  • Refresh the page.

You should see your public name in the header and footer. Your real email should appear in the contact section.

You should also see the GitHub and LinkedIn buttons. The resume button should appear with the hero actions.

Public profile buttons missing?

Confirm that githubUrl, linkedInUrl, and resumeUrl contain complete public URLs inside matching quotes.

Check the comma after each property in the profile object. A missing comma can stop the page from compiling.

Help me fix my conditional portfolio links

The reference below shows the final index.html structure for comparison.

✔️ Awesome, I've got everything!

Keep your public name in the title. Double-check that index.html is saved.

ⓧ I'd like to double check the full code

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta
      name="description"
      content="Backend software engineer portfolio featuring fintech APIs, microservices, distributed systems, and data engineering impact."
    />
    <title>Backend Software Engineer Portfolio | Your Name</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

What should you compare?

Compare the document structure and description character for character. Keep your public name in the title if you already replaced the placeholder.

Build the production portfolio

The development server gives fast feedback while you edit. A production build applies the stricter checks that matter before publishing.

Your current dev server is still using its terminal. A second PowerShell terminal lets the build run while the local preview remains available.

  • Create a second PowerShell terminal from the terminal panel in Visual Studio Code.

Before you run the build, do you think every personalized value and accessibility feature can pass the production checks?

  • Build the production portfolio by running:
npm run build

What does this build check?

The build script checks your TypeScript code first. Vite then creates the production output.

A successful run completes without TypeScript errors. This proves that the complete portfolio can be prepared for deployment.

  • In the Explorer sidebar, confirm that the generated production output now exists.

Production build failing?

Check the recent edits in src/portfolioData.ts for missing quotes or commas. Confirm that every URL remains inside a string.

Read the first TypeScript error in the terminal before checking later messages. The first error often causes the rest.

Help me debug my production build

That's the quality gate cleared. Your portfolio is responsive, keyboard-friendly, personalized, and ready for a public deployment.

Next up, you will publish the source and give recruiters a stable URL they can open.

Publish the Portfolio

Your recruiter-ready portfolio in Visual Studio Code now passes a production build. The next goal is to give recruiters a stable place to open it.

A local portfolio cannot support applications because recruiters cannot reach it. A public GitHub repository gives them a place to inspect the source.

Vercel turns that source into a live HTTPS deployment. The public URL gives each application a stable destination.

In this step, get ready to:
  • Publish the complete source in a public GitHub repository.
  • Connect that repository to a Vercel Hobby project.
  • Verify the HTTPS deployment before sharing it with recruiters.
Publish the source to GitHub

A Git repository records the portfolio as a versioned project. GitHub hosts that history where recruiters can review it.

Visual Studio Code may ask whether it should stage all project files during the first commit. Approving that prompt includes the complete portfolio in the checkpoint.

  • Create a GitHub account through GitHub's account creation flow if you do not already have one.
  • Return to Visual Studio Code when your account is ready.
  • Select Source Control in the left activity bar.
  • Use the repository initialization action in the Source Control view to initialize backend-engineer-portfolio.
  • Enter Publish portfolio as the commit message.
  • Use the commit action to create the first local commit.

That first checkpoint is done. Your complete portfolio now has local version history.

The publishing flow may open GitHub in your browser for authorization. Repository access allows Visual Studio Code to create the remote repository.

  • Select Publish to GitHub from the Source Control view.
  • Complete the GitHub authorization in your browser if requested.
  • Select the public visibility option for backend-engineer-portfolio.
  • Open the published repository from the confirmation message.

You should see package.json on the public repository page. You should also see the src folder.

Good work. Recruiters now have a public source repository that proves how the portfolio was built.

Publishing stuck?

  • Return to any GitHub authorization tab that is waiting for approval.
  • Retry Publish to GitHub after Visual Studio Code confirms that you are signed in.
  • Confirm that the published repository uses public visibility.

Help me fix GitHub publishing.

Deploy the site with Vercel

Vercel reads package.json from the connected repository. Its Vite detection applies the build defaults for this portfolio.

Choosing a hosting plan can feel like a billing decision. Vercel Hobby costs $0/mo. for personal, non-commercial projects such as this portfolio.

  • Create a Vercel account if you do not already have one.
  • Sign in to Vercel when your account is ready.
  • Select the Hobby plan.

Vercel may ask for permission to access your GitHub repositories. Access to this portfolio repository allows each default-branch push to create a production deployment.

  • Open the New Project page in Vercel.
  • Connect your GitHub account.
  • Authorize repository access if requested.
  • Select the public backend-engineer-portfolio repository.
  • Confirm that Vercel detects the project as Vite.
  • Select Deploy.

Vercel now builds the portfolio from the connected source. The deployment page reports progress for the production release.

  • Keep the deployment page open until Vercel reports success.
  • Open the completed production deployment.
  • Record the production address here: your Vercel production URL.
  • Confirm that the browser address uses HTTPS.

That is the public launch complete. Your backend engineering portfolio now loads from a stable production URL over HTTPS.

Deployment not completing?

  • Confirm that Vercel is connected to the public backend-engineer-portfolio repository.
  • Review the deployment log to find the first build step that failed.
  • Confirm that the repository contains the completed package.json from your successful local build.

Help me diagnose the Vercel deployment.

Verify and share the production URL

A production deployment is useful only when an anonymous visitor can use the complete portfolio. A private browser window tests the same route a recruiter follows.

Before you load the site, do you expect the private browser session to match your local portfolio?

  • Open a private browsing window from your browser menu.
  • Navigate to your Vercel production URL.

You should see the backend engineering headline with the four impact metrics. The browser address should use HTTPS.

  • Select the Backend filter.
  • Select the Distributed Systems filter.
  • Select the Data Engineering filter.

You should see the visible project cards change after each selection. The selected filter should keep its active styling.

The email button can trigger your browser's external-app prompt because it uses a mail link. That prompt confirms that the address is connected to an email application.

  • Test the Email me link.
  • Test the View resume link.
  • Test the GitHub link.
  • Test the LinkedIn link.

The email action should target your public email address. Each profile button should open its configured public destination.

A live link supports your job search when recruiters can find it beside the rest of your application evidence. Use the same production address everywhere to keep that path consistent.

  • Return to the public GitHub repository page.
  • Use the repository description editor to add your Vercel production URL.
  • Save the updated repository description.
  • Add your Vercel production URL to your résumé.
  • Add your Vercel production URL to your LinkedIn profile.
  • Add your Vercel production URL to each relevant job application.

You have closed the gap between a local project and public evidence. Recruiters can now inspect your source or open the live portfolio from any application.

Secret mission

Create a Job-Specific Preview

A broad portfolio cannot lead with every specialty at once. Create a target-company branch for one real SDE II vacancy. Ship its tailored profile and evidence order as a separate Vercel preview while production stays unchanged.

Clean Up Your Resources

Clean Up Your Resources

Your personal portfolio uses Vercel Hobby with no ongoing platform charges. Decide whether to keep your resources active, pause public access, or remove them entirely.

Resources you used:

  • Local backend-engineer-portfolio project containing the React and TypeScript source plus its Git branches.
  • Public GitHub repository containing the production default branch plus the target-role branch.
  • Vercel project containing the production deployment plus the job-specific Preview Deployment.

Keep everything running

No action is needed. Choose this option while the portfolio supports active applications.

  • Keep the public GitHub repository available so recruiters can inspect your source.
  • Leave the Vercel project active so the production portfolio remains available over HTTPS.
  • Retain the target-role branch so its job-specific Preview Deployment remains available.
  • Keep the local backend-engineer-portfolio folder for future updates.

New pushes to the connected branches continue creating Vercel deployments. Your production URL continues updating from the production branch.

Pause - I'll come back to this later

Pausing stops production traffic while preserving the connected Vercel project for later.

  • Open the Vercel project connected to your public portfolio repository.
  • Select Settings.
  • Select General.
  • Find Pause Project above Delete Project.
  • Select Pause Project.

Your production URL shows 503 DEPLOYMENT_PAUSED while the project is paused. Your GitHub repository plus local folder remain available.

  • Select Resume Project when you want to restore production traffic.

Delete - I don't want to use this again

Deletion is final, so it is worth slowing down for one last check. Removing the Vercel project stops both public deployment URLs.

  • Open the Vercel project connected to your public portfolio repository.
  • Select Settings.
  • Select General.
  • Select Delete Project.
  • Select Delete.
  • Confirm the project name.
  • Complete the final confirmation.

Vercel removes the project plus its production and preview deployments. The shared deployment URLs stop working.

The public source repository remains after the Vercel project is removed.

  • Return to the public GitHub repository.
  • Select Settings.
  • Select General.
  • Find Danger Zone.
  • Select Delete this repository.
  • Complete GitHub's warnings.
  • Confirm the repository name.

The local source remains on your Windows computer after the hosted resources are deleted.

  • Locate the local backend-engineer-portfolio folder in Windows File Explorer.
  • Open the backend-engineer-portfolio folder.
  • Select the address bar.
  • Record the full folder path here: full path to your backend-engineer-portfolio folder.
  • Press the Windows key.
  • Search for Command Prompt.
  • Select Command Prompt from the search results.
  • Remove the local project folder by running this command:
rmdir /s /q "[[PROJECT_FOLDER_PATH="full path to your backend-engineer-portfolio folder"]]"

What does this command do?

This command permanently removes the selected project folder plus every file inside it. The full path makes sure the command targets the folder you recorded.

  • Return to Windows File Explorer.
  • Confirm that the backend-engineer-portfolio folder is no longer listed.

Folder still present?

  • Check that the recorded path points to the backend-engineer-portfolio folder itself.
  • Close any application that still has a file from the project open.

Ask for help with the removal command: Why did the Windows folder removal command fail?.

Nice Work!

Nice Work!

You did it! Your backend SDE II portfolio now turns production outcomes into a live recruiter-ready story.

You've learned how to:

  • Build an impact-first portfolio with React. Use TypeScript to keep the interactive evidence model typed.
  • Make technical evidence scannable through specialty filters. Keep the experience accessible across screen sizes and keyboard navigation.
  • Publish the source through GitHub. Deliver a live HTTPS portfolio with Vercel.
  • Secret Mission: Produce a role-specific preview from a separate Git branch. Keep the production portfolio unchanged.

Ready to quiz yourself?