Build a Portfolio with S3 and CloudFront

Build and securely deliver a portfolio with private S3 and CloudFront.

Introduction

30 Second Summary

A portfolio often gets only a brief visit before someone decides whether to keep reading. An awkward mobile page can make strong work easy to overlook.

In this project, you will build a one-page student portfolio using responsive web design. Amazon CloudFront will deliver its private Amazon S3 files through a public HTTPS address.

What You'll Build

Picture sharing one link to a polished portfolio that adapts to each visitor's screen.

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

  • A responsive one-page portfolio that presents your introduction, skills, projects, achievements, resume, and contact details clearly.
  • A smooth visitor experience with section navigation, a mobile menu, and a downloadable resume.
  • A private delivery path where CloudFront uses Origin Access Control to reach the S3 origin while anonymous direct requests stay blocked.
  • Secret Mission: Produce an evidence pack that proves the S3 origin is private while CloudFront remains the public delivery layer.

Are there any prerequisites?

Bring prepared portfolio content plus a resume PDF.

You also need internet access plus an AWS account with permission to create and delete Amazon S3 and Amazon CloudFront resources.

Before We Start

Before you create any files or AWS resources, this checkpoint helps you define who your portfolio serves. You will also commit to using Amazon CloudFront as the public HTTPS entry point while keeping the Amazon S3 origin private.

Set Up the Local Project

Your portfolio moves from Windows into Amazon S3 later. A predictable local structure prevents broken file paths during that move.

Visual Studio Code gives you one place to manage the project files. Microsoft Edge gives you a visual check before the site reaches AWS.

Before any AWS resource exists, the AWS Management Console confirms access to S3. It also confirms access to Amazon CloudFront.

In this step, get ready to:
  • Verify the local tools and AWS access.
  • Create the local portfolio structure.
  • Display a temporary setup page in Microsoft Edge.
Verify the tools and AWS access

A quick prerequisite check catches a missing browser before it interrupts your local test. It also protects you from creating future resources in the wrong AWS account.

  • Select Start in the Windows taskbar.

You should see Windows search ready for an app name.

  • Enter Visual Studio Code in the search field.
  • Select Visual Studio Code from the search results.

You should see Visual Studio Code open without a portfolio folder loaded.

  • Select Start in the Windows taskbar.

Windows search opens again.

  • Enter Microsoft Edge in the search field.
  • Select Microsoft Edge from the search results.

Choose the outcome that matches what you see.

✔️ Microsoft Edge opens

Good. Microsoft Edge is ready for the local page check later in this step.

ⓧ Microsoft Edge is absent

The installer may ask for permission to make changes to Windows. Approve that request to continue.

  • Open the official Microsoft Edge support page in your available browser.
  • Select Download Edge for Windows.
  • Complete the Windows installation.
  • Repeat the Start search for Microsoft Edge.

Microsoft Edge Still Missing?

Restart Windows if Microsoft Edge remains absent after installation. Help me troubleshoot the Microsoft Edge installation.

Signing in uses your existing AWS credentials. This access check creates no AWS resources.

  • Open the Amazon S3 console in Microsoft Edge.
  • Sign in with your existing AWS credentials.

You should see the AWS Management Console with Amazon S3 available.

  • Check the account details in the console header against the AWS account you intend to use.
  • Select the console search bar.
  • Enter S3.

You should see Amazon S3 in the search results.

  • Replace S3 with CloudFront in the console search bar.

You should see Amazon CloudFront in the search results. No bucket or distribution exists yet.

Cannot Find an AWS Service?

Confirm that the console search contains only the service name. Check that the current account is the one you intend to use.

Need another check? Help me verify my AWS console access. You can also share the non-sensitive error in the NextWork community.

Create the local project structure

Folder nesting is easy to misread in File Explorer. Each file needs the exact path that the website uses later.

The browser resolves paths such as css/styles.css from the folder containing index.html. Matching this structure locally keeps those paths consistent after upload.

  • Select Start in the Windows taskbar.

Windows search opens.

  • Enter File Explorer in the search field.
  • Select File Explorer from the search results.

You should see File Explorer open.

  • Select Desktop in the left navigation pane.
  • Use the folder creation control in the top toolbar to create student-portfolio.

You should see the new student-portfolio folder on your Desktop.

  • Double-click student-portfolio in File Explorer.
  • Use the folder creation control to create css.
  • Use the folder creation control to create js.

You should now see css beside js.

  • Use the folder creation control to create assets.

The student-portfolio folder should now contain three lowercase folders.

  • Switch back to Visual Studio Code.
  • Use the folder-opening option in the top menu.

You should see the Windows folder picker.

  • Select the student-portfolio folder on your Desktop.
  • Confirm the folder selection.

You should see student-portfolio at the top of the Explorer sidebar.

  • Select the student-portfolio heading in the Explorer sidebar.
  • Use the file creation control in the Explorer sidebar to create index.html.

You should see index.html directly beneath the project heading.

  • Expand the css folder in the Explorer sidebar.
  • Use the file creation control to create styles.css inside css.

You should see styles.css nested beneath css.

  • Expand the js folder in the Explorer sidebar.
  • Use the file creation control to create main.js inside js.

You should see main.js nested beneath js.

  • Switch back to File Explorer.
  • Locate the prepared resume PDF from before this project.

You should see the prepared PDF in File Explorer.

  • Copy the prepared resume PDF.
  • Return to the student-portfolio folder from earlier.

The assets folder should still be empty.

  • Open the assets folder.
  • Paste the copied PDF into assets.

You should see a copy of your resume inside assets.

  • Rename the copied PDF to resume.pdf using File Explorer's rename control.

You should now see resume.pdf inside assets.

Check the Exact Structure

  • `index.html` sits directly inside `student-portfolio`.
  • `styles.css` sits inside `student-portfolio/css`.
  • `main.js` sits inside `student-portfolio/js`.
  • `resume.pdf` sits inside `student-portfolio/assets`.

File Paths Look Different?

Move each misplaced file into the matching lowercase folder. Keep index.html directly inside student-portfolio.

Still unsure? Help me check my student-portfolio file structure.

Display the temporary setup page

A small HTML page proves that the project folder opens correctly in a browser. This gives you a visible result before the full portfolio replaces it.

  • Switch back to Visual Studio Code.
  • Select index.html in the Explorer sidebar.
  • Replace the empty file with the temporary page by pasting the code below:
<!doctype html><title>Portfolio setup</title><h1>Portfolio setup complete</h1>

What Does This Setup Page Do?

  • `<!doctype html>` tells Microsoft Edge to render the file as modern HTML.
  • `<title>` sets the browser tab text to `Portfolio setup`.
  • `<h1>` displays `Portfolio setup complete` on the page.
  • Save index.html using Visual Studio Code's save control.

Before you open the file, what text do you expect to see on the page?

  • Return to the student-portfolio folder in File Explorer.
  • Double-click index.html.
  • Choose Microsoft Edge if Windows asks which app to use.

You should see Portfolio setup complete as a large heading. That first browser result proves the local project path works.

Browser Showing the Code as Text?

Confirm that the filename ends in .html. A file named index.html.txt opens as a text document.

If the heading remains missing, save the file before opening it again. Help me check why Microsoft Edge is displaying my HTML incorrectly.

✔️ Awesome, I've got everything!

Your project structure is ready. Double check that index.html is saved.

ⓧ I'd like to double check the full code

Compare your complete index.html with this checkpoint.

<!doctype html><title>Portfolio setup</title><h1>Portfolio setup complete</h1>

`css/styles.css` is empty at this checkpoint. `js/main.js` is also empty.

`assets/resume.pdf` is your prepared resume PDF copied into the `assets` folder. Its filename is exactly `resume.pdf`.

Your local structure is ready. Next, you will turn this setup page into the responsive portfolio.

Build and Test the Responsive Portfolio

Your local setup page proves that Microsoft Edge can load index.html from your student-portfolio folder. You now have a reliable starting point for the portfolio.

Building the visible portfolio locally gives you a working product before AWS configuration introduces storage or caching problems. You will create the page with HTML, style it with CSS, then add interactions with JavaScript.

In this step, get ready to:
  • Replace the temporary page with your complete portfolio structure.
  • Style the portfolio for wide screens and narrow browser windows.
  • Add the mobile menu behavior and complete the local acceptance test.
Build the page structure

The page structure connects every section to its navigation link. Its relative paths also keep the stylesheet, script, and resume working when the same files move to Amazon S3.

Keep Your Public Details Safe

Use public professional information in this portfolio. Leave out private phone numbers, home addresses, student IDs, account details, and other sensitive information.

  • Switch back to index.html in the open Visual Studio Code window.
  • Replace the temporary setup page with this document shell:
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Portfolio and resume website for a Computer Science student.">
  <title>Your Name | Computer Science Portfolio</title>
  <link rel="stylesheet" href="css/styles.css">
  <script src="js/main.js" defer></script>
</head>
<body>
  <a class="skip-link" href="#main-content">Skip to main content</a>

  <main id="main-content">
  </main>

  <footer class="site-footer">
    <div class="container">
      <p>&copy; <span id="year"></span> Your Name. Built with HTML, CSS, JavaScript, Amazon S3, and Amazon CloudFront.</p>
    </div>
  </footer>
</body>
</html>

What Does This Structure Do?

  • The metadata helps browsers display the page correctly across different screen widths.
  • The stylesheet and script paths point to the existing css and js folders.
  • The empty main-content area gives you a stable insertion point for the portfolio sections.
  • Save index.html.
  • Refresh the existing portfolio tab in Microsoft Edge.

You will see the unstyled footer text near the bottom of the page. This confirms that the new document shell has replaced the temporary setup message.

Still Seeing the Setup Message?

Confirm that you saved the index.html inside student-portfolio. Make sure Microsoft Edge is displaying that same file.

Still stuck? Help me check why Microsoft Edge still shows my old local HTML page.

  • Insert this header immediately before the opening <main id="main-content"> line:
  <header class="site-header">
    <nav class="nav container" aria-label="Primary navigation">
      <a class="brand" href="#home">Your Name</a>
      <button class="menu-toggle" type="button" aria-expanded="false" aria-controls="nav-links">
        Menu
      </button>
      <ul class="nav-links" id="nav-links">
        <li><a href="#home">Home</a></li>
        <li><a href="#about">About</a></li>
        <li><a href="#skills">Skills</a></li>
        <li><a href="#projects">Projects</a></li>
        <li><a href="#resume">Resume</a></li>
        <li><a href="#contact">Contact</a></li>
      </ul>
    </nav>
  </header>

How the Navigation Works

  • Each navigation link targets a matching section ID lower on the page.
  • The Menu button begins with aria-expanded="false" because the mobile navigation starts closed.
  • The nav-links ID connects the button to the list that it controls.
  • Save index.html.
  • Refresh the portfolio in Microsoft Edge.

You will see your name, the Menu control, and six navigation links in an unstyled vertical layout.

Navigation Missing?

Check that the header sits between the skip link and the opening main element. A missing angle bracket can prevent the browser from reading the remaining markup correctly.

Need another pair of eyes? Help me find the HTML mistake hiding my navigation.

  • Insert this hero section directly after the opening <main id="main-content"> line:
    <section class="hero" id="home">
      <div class="container hero-grid">
        <div>
          <p class="eyebrow">Computer Science Student</p>
          <h1>Hello, I am Your Name.</h1>
          <p class="hero-copy">
            I build practical software projects while learning web development,
            programming, networking, databases, and cloud computing.
          </p>
          <p class="status">Currently looking for opportunities to learn, contribute, and grow.</p>
          <div class="button-row">
            <a class="button primary" href="#projects">View my projects</a>
            <a class="button secondary" href="assets/resume.pdf" download>Download resume</a>
          </div>
        </div>
        <div class="profile-mark" aria-label="Your initials">YN</div>
      </div>
    </section>

What Does the Hero Add?

  • The home ID gives the first navigation link a destination.
  • The project button points to the future projects section.
  • The resume button uses the exact relative path assets/resume.pdf.
  • Save index.html.
  • Refresh the portfolio in Microsoft Edge.

You will see the introduction, status text, initials, and two links beneath the navigation.

  • Insert this About section immediately after the closing hero </section> tag:
    <section class="section" id="about">
      <div class="container two-column">
        <div>
          <p class="section-label">About Me</p>
          <h2>A curious student building strong technical foundations.</h2>
        </div>
        <div>
          <p>
            I am a Computer Science student at Your College. I enjoy turning ideas
            into useful projects and learning how software works from the browser
            to the cloud.
          </p>
          <p>
            My current interests include web development, cloud computing,
            computer networking, databases, and problem solving with Java and Python.
          </p>
        </div>
      </div>
    </section>

Why Use Section IDs?

The about ID matches the header link. This creates an in-page destination without another HTML file.

  • Save index.html.
  • Refresh the portfolio in Microsoft Edge.

You will see the About Me heading and two introduction paragraphs below the hero.

  • Insert this Skills section immediately after the closing About </section> tag:
    <section class="section section-muted" id="skills">
      <div class="container">
        <p class="section-label">Skills</p>
        <h2>Technical toolkit</h2>
        <ul class="skill-list" aria-label="Technical skills">
          <li>HTML</li>
          <li>CSS</li>
          <li>JavaScript</li>
          <li>Java</li>
          <li>Python</li>
          <li>Database basics</li>
          <li>Computer networking</li>
          <li>AWS basics</li>
        </ul>
      </div>
    </section>

How the Skills List Helps

A semantic list keeps the skills understandable before any styling loads. The stylesheet later turns each item into a compact skill tag.

  • Save index.html.
  • Refresh the portfolio in Microsoft Edge.

You will see the Technical toolkit heading followed by eight skills.

  • Insert this Projects section immediately after the closing Skills </section> tag:
    <section class="section" id="projects">
      <div class="container">
        <p class="section-label">Projects</p>
        <h2>Selected school and personal work</h2>
        <div class="project-grid">
          <article class="card">
            <p class="card-number">01</p>
            <h3>Student Task Tracker</h3>
            <p>A responsive web page for organizing coursework, priorities, and deadlines.</p>
            <p class="tech">HTML, CSS, JavaScript</p>
          </article>
          <article class="card">
            <p class="card-number">02</p>
            <h3>Library Management Program</h3>
            <p>A console application that manages books, borrowers, and basic search operations.</p>
            <p class="tech">Java, object-oriented programming</p>
          </article>
          <article class="card">
            <p class="card-number">03</p>
            <h3>Network Study Lab</h3>
            <p>A documented lab exploring addressing, connectivity tests, and common network services.</p>
            <p class="tech">Computer networking, troubleshooting</p>
          </article>
        </div>
      </div>
    </section>

How Project Cards Are Grouped

Each project is an independent article inside project-grid. This structure lets the stylesheet arrange all three projects as cards.

  • Save index.html.
  • Refresh the portfolio in Microsoft Edge.

You will see three numbered projects below the Skills section.

  • Insert this Resume section immediately after the closing Projects </section> tag:
    <section class="section section-muted" id="resume">
      <div class="container">
        <p class="section-label">Resume</p>
        <h2>Education and experience</h2>
        <div class="resume-grid">
          <article class="resume-block">
            <h3>Education</h3>
            <p><strong>Bachelor of Science in Computer Science</strong></p>
            <p>Your College, Expected Graduation Year</p>
          </article>
          <article class="resume-block">
            <h3>Achievements</h3>
            <ul>
              <li>Add an academic achievement or scholarship.</li>
              <li>Add a certification, workshop, or competition.</li>
            </ul>
          </article>
          <article class="resume-block">
            <h3>Experience</h3>
            <p>Add an internship, volunteer role, campus activity, or relevant responsibility.</p>
          </article>
        </div>
        <a class="button primary" href="assets/resume.pdf" download>Download full resume</a>
      </div>
    </section>

How the Resume Stays Connected

The Resume section summarizes your background on the page. Its download link points to the same assets/resume.pdf file as the hero button.

  • Save index.html.
  • Refresh the portfolio in Microsoft Edge.

You will see Education, Achievements, and Experience blocks followed by a resume download link.

  • Insert this Contact section immediately after the closing Resume </section> tag:
    <section class="section contact" id="contact">
      <div class="container contact-panel">
        <div>
          <p class="section-label">Contact</p>
          <h2>Let us connect.</h2>
          <p>I am open to student projects, internships, and learning opportunities.</p>
        </div>
        <div class="contact-links">
          <a href="mailto:your-email@example.com">your-email@example.com</a>
          <a href="https://github.com/your-username" target="_blank" rel="noreferrer">GitHub profile</a>
        </div>
      </div>
    </section>

How Visitors Reach You

The email link opens a visitor's mail application. The GitHub link opens your public profile in a separate browser tab.

  • Replace every Your Name placeholder with your prepared public name.
  • Replace the education placeholders with your prepared public education details.
  • Replace the sample project content with your prepared project details.
  • Replace the achievement placeholders with your prepared public achievements.
  • Replace your-email@example.com with your public professional email address.
  • Replace your-username with your GitHub username.
  • Replace YN with your initials.
  • Save index.html.

Refresh the portfolio. You will see all six sections containing your prepared non-sensitive content.

A Navigation Link Goes Nowhere?

Compare each link fragment with its section ID. For example, href="#projects" must match id="projects" exactly.

Need help checking the connections? Help me match my navigation links to my portfolio section IDs.

✔️ Awesome, I've got everything!

Great. Your index.html now contains the complete portfolio structure. Confirm that you saved your personal substitutions.

ⓧ I'd like to double check the full code

Compare your index.html with this complete reference. Keep the markup, IDs, classes, links, and relative paths unchanged while substituting your prepared public content.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Portfolio and resume website for a Computer Science student.">
  <title>Your Name | Computer Science Portfolio</title>
  <link rel="stylesheet" href="css/styles.css">
  <script src="js/main.js" defer></script>
</head>
<body>
  <a class="skip-link" href="#main-content">Skip to main content</a>

  <header class="site-header">
    <nav class="nav container" aria-label="Primary navigation">
      <a class="brand" href="#home">Your Name</a>
      <button class="menu-toggle" type="button" aria-expanded="false" aria-controls="nav-links">
        Menu
      </button>
      <ul class="nav-links" id="nav-links">
        <li><a href="#home">Home</a></li>
        <li><a href="#about">About</a></li>
        <li><a href="#skills">Skills</a></li>
        <li><a href="#projects">Projects</a></li>
        <li><a href="#resume">Resume</a></li>
        <li><a href="#contact">Contact</a></li>
      </ul>
    </nav>
  </header>

  <main id="main-content">
    <section class="hero" id="home">
      <div class="container hero-grid">
        <div>
          <p class="eyebrow">Computer Science Student</p>
          <h1>Hello, I am Your Name.</h1>
          <p class="hero-copy">
            I build practical software projects while learning web development,
            programming, networking, databases, and cloud computing.
          </p>
          <p class="status">Currently looking for opportunities to learn, contribute, and grow.</p>
          <div class="button-row">
            <a class="button primary" href="#projects">View my projects</a>
            <a class="button secondary" href="assets/resume.pdf" download>Download resume</a>
          </div>
        </div>
        <div class="profile-mark" aria-label="Your initials">YN</div>
      </div>
    </section>

    <section class="section" id="about">
      <div class="container two-column">
        <div>
          <p class="section-label">About Me</p>
          <h2>A curious student building strong technical foundations.</h2>
        </div>
        <div>
          <p>
            I am a Computer Science student at Your College. I enjoy turning ideas
            into useful projects and learning how software works from the browser
            to the cloud.
          </p>
          <p>
            My current interests include web development, cloud computing,
            computer networking, databases, and problem solving with Java and Python.
          </p>
        </div>
      </div>
    </section>

    <section class="section section-muted" id="skills">
      <div class="container">
        <p class="section-label">Skills</p>
        <h2>Technical toolkit</h2>
        <ul class="skill-list" aria-label="Technical skills">
          <li>HTML</li>
          <li>CSS</li>
          <li>JavaScript</li>
          <li>Java</li>
          <li>Python</li>
          <li>Database basics</li>
          <li>Computer networking</li>
          <li>AWS basics</li>
        </ul>
      </div>
    </section>

    <section class="section" id="projects">
      <div class="container">
        <p class="section-label">Projects</p>
        <h2>Selected school and personal work</h2>
        <div class="project-grid">
          <article class="card">
            <p class="card-number">01</p>
            <h3>Student Task Tracker</h3>
            <p>A responsive web page for organizing coursework, priorities, and deadlines.</p>
            <p class="tech">HTML, CSS, JavaScript</p>
          </article>
          <article class="card">
            <p class="card-number">02</p>
            <h3>Library Management Program</h3>
            <p>A console application that manages books, borrowers, and basic search operations.</p>
            <p class="tech">Java, object-oriented programming</p>
          </article>
          <article class="card">
            <p class="card-number">03</p>
            <h3>Network Study Lab</h3>
            <p>A documented lab exploring addressing, connectivity tests, and common network services.</p>
            <p class="tech">Computer networking, troubleshooting</p>
          </article>
        </div>
      </div>
    </section>

    <section class="section section-muted" id="resume">
      <div class="container">
        <p class="section-label">Resume</p>
        <h2>Education and experience</h2>
        <div class="resume-grid">
          <article class="resume-block">
            <h3>Education</h3>
            <p><strong>Bachelor of Science in Computer Science</strong></p>
            <p>Your College, Expected Graduation Year</p>
          </article>
          <article class="resume-block">
            <h3>Achievements</h3>
            <ul>
              <li>Add an academic achievement or scholarship.</li>
              <li>Add a certification, workshop, or competition.</li>
            </ul>
          </article>
          <article class="resume-block">
            <h3>Experience</h3>
            <p>Add an internship, volunteer role, campus activity, or relevant responsibility.</p>
          </article>
        </div>
        <a class="button primary" href="assets/resume.pdf" download>Download full resume</a>
      </div>
    </section>

    <section class="section contact" id="contact">
      <div class="container contact-panel">
        <div>
          <p class="section-label">Contact</p>
          <h2>Let us connect.</h2>
          <p>I am open to student projects, internships, and learning opportunities.</p>
        </div>
        <div class="contact-links">
          <a href="mailto:your-email@example.com">your-email@example.com</a>
          <a href="https://github.com/your-username" target="_blank" rel="noreferrer">GitHub profile</a>
        </div>
      </div>
    </section>
  </main>

  <footer class="site-footer">
    <div class="container">
      <p>&copy; <span id="year"></span> Your Name. Built with HTML, CSS, JavaScript, Amazon S3, and Amazon CloudFront.</p>
    </div>
  </footer>
</body>
</html>
Style the responsive layout

Responsive web design lets one page adapt to the visitor's available screen width. This stylesheet begins with shared colors and layout rules before adding a narrow-screen breakpoint.

  • Switch back to the empty css/styles.css file.
  • Add the color system and page defaults with this code:
:root {
  --background: #f7f8fc;
  --surface: #ffffff;
  --surface-muted: #eef2ff;
  --text: #182033;
  --muted: #566078;
  --primary: #3157d5;
  --primary-dark: #1f3da3;
  --border: #dce2f0;
  --shadow: 0 16px 40px rgba(24, 32, 51, 0.1);
  --radius: 18px;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--background);
  color: var(--text);
  font-family: Arial, Helvetica, sans-serif;
  line-height: 1.6;
}

What Do These Styles Control?

  • The custom properties keep the colors, shadow, and corner radius consistent.
  • The universal sizing rule makes element dimensions easier to predict.
  • Smooth scrolling makes in-page navigation feel connected.
  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see a pale background, dark text, and updated typography across the page.

  • Add the shared sizing and accessibility styles below the current CSS:
img {
  max-width: 100%;
}

a {
  color: var(--primary);
}

button,
a {
  -webkit-tap-highlight-color: transparent;
}

.container {
  width: min(1100px, calc(100% - 2rem));
  margin-inline: auto;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  padding: 0.75rem 1rem;
  background: var(--text);
  color: white;
  z-index: 100;
}

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

Why Include a Skip Link?

The skip link stays off-screen until it receives keyboard focus. It lets keyboard users move directly to the main portfolio content.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see the content constrained to a readable width with space along both sides.

  • Add the desktop header and navigation styles below the current CSS:
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.96);
  border-bottom: 1px solid var(--border);
}

.nav {
  min-height: 68px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.brand {
  color: var(--text);
  font-size: 1.1rem;
  font-weight: 800;
  text-decoration: none;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

How the Header Stays Available

The sticky position keeps the navigation at the top while the visitor scrolls. Flexbox places the brand and links on the same row.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see the brand on the left and the navigation links arranged across the right side of a sticky white header.

  • Add the navigation link and menu button styles below the current CSS:
.nav-links a {
  color: var(--text);
  font-weight: 700;
  text-decoration: none;
}

.nav-links a:hover,
.nav-links a:focus-visible {
  color: var(--primary);
}

.menu-toggle {
  display: none;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  padding: 0.55rem 0.8rem;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

Why the Menu Starts Hidden

Wide screens have enough room for the full navigation list. The media query later reveals the Menu button when the browser becomes narrow.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see darker navigation links that change color when you point to them.

  • Add the hero layout and section-label styles below the current CSS:
.hero {
  padding: 6rem 0 5rem;
  background: linear-gradient(135deg, #ffffff 0%, #eef2ff 100%);
}

.hero-grid {
  display: grid;
  grid-template-columns: 1.5fr 0.5fr;
  align-items: center;
  gap: 3rem;
}

.eyebrow,
.section-label {
  margin: 0 0 0.6rem;
  color: var(--primary);
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

How the Hero Uses Space

The hero grid gives more room to your introduction than your initials. The gradient separates this first section from the rest of the page.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see the hero split into an introduction column and an initials column.

  • Add the heading and hero-text styles below the current CSS:
h1,
h2,
h3,
p {
  margin-top: 0;
}

h1 {
  max-width: 760px;
  margin-bottom: 1rem;
  font-size: clamp(2.4rem, 7vw, 4.8rem);
  line-height: 1.05;
}

h2 {
  margin-bottom: 1.5rem;
  font-size: clamp(1.8rem, 4vw, 2.8rem);
  line-height: 1.15;
}

h3 {
  line-height: 1.25;
}

.hero-copy {
  max-width: 680px;
  color: var(--muted);
  font-size: 1.15rem;
}

.status {
  font-weight: 700;
}

How the Type Scales

The clamp() values let headings grow with the browser width while preserving readable minimum and maximum sizes.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see a larger hero heading, clearer section headings, and muted introduction text.

  • Add the profile mark and shared button styles below the current CSS:
.profile-mark {
  width: min(230px, 60vw);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  justify-self: center;
  border: 10px solid white;
  border-radius: 50%;
  background: var(--primary);
  color: white;
  box-shadow: var(--shadow);
  font-size: 4rem;
  font-weight: 800;
}

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem;
  margin-top: 1.5rem;
}

.button {
  display: inline-block;
  border: 2px solid var(--primary);
  border-radius: 999px;
  padding: 0.75rem 1.1rem;
  font-weight: 800;
  text-decoration: none;
}

What Creates the Hero Focus?

The profile mark turns your initials into a circular visual anchor. The shared button class gives every main action a consistent shape.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see your initials inside a blue circle and both hero links displayed as rounded buttons.

  • Add the button variants and section layouts below the current CSS:
.button.primary {
  background: var(--primary);
  color: white;
}

.button.primary:hover,
.button.primary:focus-visible {
  background: var(--primary-dark);
  border-color: var(--primary-dark);
}

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

.section {
  padding: 5rem 0;
}

.section-muted {
  background: var(--surface-muted);
}

.two-column {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
}

How Sections Gain Contrast

Alternating section backgrounds divide the long page into readable regions. The About section uses two equal columns on wider screens.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see generous spacing between sections, alternating backgrounds, and a two-column About layout.

  • Add the skill and card-grid styles below the current CSS:
.skill-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.skill-list li {
  border: 1px solid #cbd4f6;
  border-radius: 999px;
  background: var(--surface);
  padding: 0.6rem 0.9rem;
  font-weight: 700;
}

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

How Repeated Content Aligns

The skills wrap onto new rows when space runs out. The project and resume grids use three equal columns on wider screens.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see pill-shaped skill tags plus three-column project and resume layouts.

  • Add the card-detail styles below the current CSS:
.card,
.resume-block {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 1.5rem;
  box-shadow: var(--shadow);
}

.card-number {
  color: var(--primary);
  font-weight: 800;
}

.tech {
  margin-bottom: 0;
  color: var(--primary-dark);
  font-weight: 700;
}

.resume-block ul {
  padding-left: 1.25rem;
}

What Makes a Card Readable?

Borders, spacing, and shadows separate each project or resume block from its background. The blue labels make project numbers and technologies easier to scan.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see the projects and resume details inside white cards with rounded corners and soft shadows.

  • Add the contact and footer styles below the current CSS:
.contact-panel {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  border-radius: var(--radius);
  background: var(--text);
  color: white;
  padding: 2rem;
}

.contact-links {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-width: 220px;
}

.contact-links a {
  color: white;
  font-weight: 700;
}

.site-footer {
  padding: 1.5rem 0;
  background: #101626;
  color: #dce2f0;
  text-align: center;
}

.site-footer p {
  margin: 0;
}

How the Page Finishes

The dark contact panel draws attention to your public links. The darker footer creates a clear visual endpoint for the page.

  • Save css/styles.css.
  • Refresh the portfolio in Microsoft Edge.

You will see a dark contact panel followed by a centered dark footer.

  • Add the mobile navigation rules below the current CSS:
@media (max-width: 760px) {
  .menu-toggle {
    display: inline-block;
  }

  .nav-links {
    position: absolute;
    top: 68px;
    left: 1rem;
    right: 1rem;
    display: none;
    flex-direction: column;
    align-items: stretch;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: var(--surface);
    padding: 1rem;
    box-shadow: var(--shadow);
  }

  .nav-links.open {
    display: flex;
  }
}

How the Mobile Menu Appears

At widths of 760px or less, the full navigation list starts hidden. Adding the open class reveals it as a vertical panel.

  • Save css/styles.css.
  • Narrow the Microsoft Edge window until the navigation links disappear.

You will see the Menu control replace the wide navigation list.

  • Insert these remaining rules before the final closing brace of the existing media query:
  .hero {
    padding-top: 4rem;
  }

  .hero-grid,
  .two-column,
  .project-grid,
  .resume-grid {
    grid-template-columns: 1fr;
  }

  .profile-mark {
    width: 150px;
    grid-row: 1;
  }

  .contact-panel {
    align-items: flex-start;
    flex-direction: column;
  }

How the Layout Responds

The multi-column layouts become single columns inside a narrow window. The profile mark becomes smaller while the contact links move below the contact message.

  • Save css/styles.css.
  • Refresh the narrow Microsoft Edge window.

You will see the hero, About content, project cards, resume cards, and contact panel stack vertically without horizontal scrolling.

Page Still Looks Plain?

Confirm that the file is saved at css/styles.css. Check that index.html still contains href="css/styles.css".

Still seeing unstyled content? Help me debug why my local stylesheet is not loading.

✔️ Awesome, I've got everything!

Your wide and narrow layouts now match the portfolio structure. Double check that css/styles.css is saved.

ⓧ I'd like to double check the full code

Compare your stylesheet with this complete reference.

:root {
  --background: #f7f8fc;
  --surface: #ffffff;
  --surface-muted: #eef2ff;
  --text: #182033;
  --muted: #566078;
  --primary: #3157d5;
  --primary-dark: #1f3da3;
  --border: #dce2f0;
  --shadow: 0 16px 40px rgba(24, 32, 51, 0.1);
  --radius: 18px;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--background);
  color: var(--text);
  font-family: Arial, Helvetica, sans-serif;
  line-height: 1.6;
}

img {
  max-width: 100%;
}

a {
  color: var(--primary);
}

button,
a {
  -webkit-tap-highlight-color: transparent;
}

.container {
  width: min(1100px, calc(100% - 2rem));
  margin-inline: auto;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  padding: 0.75rem 1rem;
  background: var(--text);
  color: white;
  z-index: 100;
}

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

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.96);
  border-bottom: 1px solid var(--border);
}

.nav {
  min-height: 68px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.brand {
  color: var(--text);
  font-size: 1.1rem;
  font-weight: 800;
  text-decoration: none;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav-links a {
  color: var(--text);
  font-weight: 700;
  text-decoration: none;
}

.nav-links a:hover,
.nav-links a:focus-visible {
  color: var(--primary);
}

.menu-toggle {
  display: none;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  padding: 0.55rem 0.8rem;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.hero {
  padding: 6rem 0 5rem;
  background: linear-gradient(135deg, #ffffff 0%, #eef2ff 100%);
}

.hero-grid {
  display: grid;
  grid-template-columns: 1.5fr 0.5fr;
  align-items: center;
  gap: 3rem;
}

.eyebrow,
.section-label {
  margin: 0 0 0.6rem;
  color: var(--primary);
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

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

h1 {
  max-width: 760px;
  margin-bottom: 1rem;
  font-size: clamp(2.4rem, 7vw, 4.8rem);
  line-height: 1.05;
}

h2 {
  margin-bottom: 1.5rem;
  font-size: clamp(1.8rem, 4vw, 2.8rem);
  line-height: 1.15;
}

h3 {
  line-height: 1.25;
}

.hero-copy {
  max-width: 680px;
  color: var(--muted);
  font-size: 1.15rem;
}

.status {
  font-weight: 700;
}

.profile-mark {
  width: min(230px, 60vw);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  justify-self: center;
  border: 10px solid white;
  border-radius: 50%;
  background: var(--primary);
  color: white;
  box-shadow: var(--shadow);
  font-size: 4rem;
  font-weight: 800;
}

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem;
  margin-top: 1.5rem;
}

.button {
  display: inline-block;
  border: 2px solid var(--primary);
  border-radius: 999px;
  padding: 0.75rem 1.1rem;
  font-weight: 800;
  text-decoration: none;
}

.button.primary {
  background: var(--primary);
  color: white;
}

.button.primary:hover,
.button.primary:focus-visible {
  background: var(--primary-dark);
  border-color: var(--primary-dark);
}

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

.section {
  padding: 5rem 0;
}

.section-muted {
  background: var(--surface-muted);
}

.two-column {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
}

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

.skill-list li {
  border: 1px solid #cbd4f6;
  border-radius: 999px;
  background: var(--surface);
  padding: 0.6rem 0.9rem;
  font-weight: 700;
}

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

.card,
.resume-block {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 1.5rem;
  box-shadow: var(--shadow);
}

.card-number {
  color: var(--primary);
  font-weight: 800;
}

.tech {
  margin-bottom: 0;
  color: var(--primary-dark);
  font-weight: 700;
}

.resume-block ul {
  padding-left: 1.25rem;
}

.contact-panel {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  border-radius: var(--radius);
  background: var(--text);
  color: white;
  padding: 2rem;
}

.contact-links {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-width: 220px;
}

.contact-links a {
  color: white;
  font-weight: 700;
}

.site-footer {
  padding: 1.5rem 0;
  background: #101626;
  color: #dce2f0;
  text-align: center;
}

.site-footer p {
  margin: 0;
}

@media (max-width: 760px) {
  .menu-toggle {
    display: inline-block;
  }

  .nav-links {
    position: absolute;
    top: 68px;
    left: 1rem;
    right: 1rem;
    display: none;
    flex-direction: column;
    align-items: stretch;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: var(--surface);
    padding: 1rem;
    box-shadow: var(--shadow);
  }

  .nav-links.open {
    display: flex;
  }

  .hero {
    padding-top: 4rem;
  }

  .hero-grid,
  .two-column,
  .project-grid,
  .resume-grid {
    grid-template-columns: 1fr;
  }

  .profile-mark {
    width: 150px;
    grid-row: 1;
  }

  .contact-panel {
    align-items: flex-start;
    flex-direction: column;
  }
}
Add interactions and run the acceptance test

The narrow layout now shows a menu control, but JavaScript must connect that control to the navigation list. The script also inserts the current year into the footer.

  • Switch back to the empty js/main.js file.
  • Add the element references and footer update with this code:
const menuButton = document.querySelector('.menu-toggle');
const navigation = document.querySelector('#nav-links');
const year = document.querySelector('#year');

year.textContent = new Date().getFullYear();

What Does This Code Find?

  • The three constants connect the script to the menu button, navigation list, and footer year.
  • The final line reads the current year from the visitor's browser.
  • Save js/main.js.
  • Refresh the portfolio in Microsoft Edge.

You will see the current year between the copyright symbol and your name in the footer.

  • Insert this menu listener immediately before the year.textContent line:
menuButton.addEventListener('click', () => {
  const isOpen = menuButton.getAttribute('aria-expanded') === 'true';
  menuButton.setAttribute('aria-expanded', String(!isOpen));
  navigation.classList.toggle('open');
});

How the Menu Toggles

  • The listener runs whenever a visitor selects the Menu control.
  • The aria-expanded value records whether the controlled navigation is open.
  • The open class activates the visible mobile-navigation style.
  • Save js/main.js.
  • Refresh the narrow Microsoft Edge window.
  • Select Menu.

You will see the navigation panel open beneath the header. Selecting Menu again closes it.

  • Insert this navigation listener immediately before the year.textContent line:
navigation.querySelectorAll('a').forEach((link) => {
  link.addEventListener('click', () => {
    menuButton.setAttribute('aria-expanded', 'false');
    navigation.classList.remove('open');
  });
});

Why Close the Menu After a Selection?

A selected navigation link moves the visitor to a page section. Closing the panel reveals that destination and resets aria-expanded to match the visible state.

  • Save js/main.js.
  • Refresh the narrow Microsoft Edge window.
  • Select Menu.
  • Select the Projects navigation link.

You will move to the Projects section while the navigation panel closes.

Menu Does Not Respond?

Confirm that index.html loads js/main.js with the defer attribute. Check that .menu-toggle, #nav-links, and #year match the HTML exactly.

Need help tracing the interaction? Help me debug my mobile menu and footer year.

✔️ Awesome, I've got everything!

Your script now updates the footer and controls the complete mobile-navigation interaction.

ⓧ I'd like to double check the full code

Compare your js/main.js with this complete reference.

const menuButton = document.querySelector('.menu-toggle');
const navigation = document.querySelector('#nav-links');
const year = document.querySelector('#year');

menuButton.addEventListener('click', () => {
  const isOpen = menuButton.getAttribute('aria-expanded') === 'true';
  menuButton.setAttribute('aria-expanded', String(!isOpen));
  navigation.classList.toggle('open');
});

navigation.querySelectorAll('a').forEach((link) => {
  link.addEventListener('click', () => {
    menuButton.setAttribute('aria-expanded', 'false');
    navigation.classList.remove('open');
  });
});

year.textContent = new Date().getFullYear();

Before the final check, which parts of the portfolio do you expect to change when the Microsoft Edge window becomes narrow?

  • Refresh index.html in Microsoft Edge.
  • Select every navigation link from Home through Contact.
  • Narrow the Microsoft Edge window below 760px.
  • Select Menu.
  • Select a navigation link from the open mobile menu.
  • Select Download resume.

You will see each navigation link scroll to its matching section. In the narrow window, the cards stack without horizontal scrolling while the mobile menu opens and closes correctly.

The footer shows the current year. The resume button opens or downloads the prepared file from assets/resume.pdf.

Resume Does Not Open?

Confirm that the prepared PDF remains at assets/resume.pdf. Check that both resume links use the same lowercase path.

Still stuck? Help me debug my local resume download link.

Your local portfolio now has a responsive layout, working navigation, a live footer year, and a downloadable resume. Next up, you will place these exact files in a private Amazon S3 bucket.

Store the Website in a Private S3 Bucket

The responsive portfolio already works locally in Microsoft Edge. It is ready to move from your computer to the cloud.

Publishing through a public Amazon S3 bucket would expose the origin to anonymous visitors. This step stores the same files in a private bucket so you can test that permission boundary yourself.

In this step, get ready to:
  • Create a private general purpose S3 bucket.
  • Upload the complete portfolio at the required bucket paths.
  • Test an anonymous request for index.html.
Create a private S3 bucket

A bucket name identifies where your files live. Its access settings determine whether anonymous visitors can reach those files.

Creating this bucket starts a metered AWS resource. The cleanup section shows how to remove it completely.

  • Switch back to the AWS Management Console from earlier.
  • Enter S3 in the console search bar.
  • Select Amazon S3 from the search results.
  • Select General purpose buckets.

You should now see the page for general purpose buckets. This confirms that you are creating the correct S3 resource type.

  • Select Create bucket.
  • Enter student-portfolio-yourname-unique-suffix in the Bucket name field.
  • Select a nearby Region.

The bucket name must be unique. Keep it lowercase so it follows the required S3 naming format.

  • Confirm Object Ownership shows Bucket owner enforced.
  • Confirm all four Block Public Access settings remain enabled.

The ownership setting should show its default value. Every public access control should remain selected.

  • Leave versioning disabled.
  • Keep the default Amazon S3 managed encryption.
  • Choose Create bucket.

That is the privacy foundation in place. Your general purpose bucket now exists without public-read access.

Static website hosting remains disabled because you did not enable it. The bucket is ready to store the portfolio as private files.

What do these defaults protect?

Bucket owner enforced removes ACL-based ownership decisions from this bucket. Block Public Access acts as a guardrail against accidental public policies or ACLs.

The default encryption protects new objects at rest. Disabled versioning keeps this short project from retaining extra copies after replacements.

Upload the complete local site

Object storage keeps each file under an object key. Uploading a folder preserves its name as a key prefix.

  • Return to the bucket you created earlier.
  • Choose Upload.
  • Choose Add file.
  • Select index.html from the existing student-portfolio folder.
  • Choose Add folder.
  • Select the css folder inside student-portfolio.
  • Choose Add folder.
  • Select the js folder inside student-portfolio.

The upload list should now show index.html plus the css folder. It should also show the js folder.

  • Choose Add folder.
  • Select the assets folder inside student-portfolio.
  • Leave the existing private permissions unchanged.
  • Choose Upload.

You should see a success message after the upload finishes. The transfer is complete.

  • Confirm index.html appears at the bucket root.
  • Confirm css/styles.css appears under the css prefix.
  • Confirm js/main.js appears under the js prefix.
  • Confirm assets/resume.pdf appears under the assets prefix.

The complete portfolio now sits in S3 with the same paths that worked locally. Its stylesheet, JavaScript, and resume references remain aligned.

Why do the root paths matter?

The browser reads exact relative paths from index.html. Those paths expect css/styles.css, js/main.js, and assets/resume.pdf at their current locations.

Uploading the outer student-portfolio folder would add an unwanted key prefix. The existing browser paths would no longer find the files.

Test anonymous access

The upload proves that the files exist. The next check tests how the bucket responds to a browser request with no AWS authorization.

  • Select the index.html object in the bucket.
  • Record the direct S3 address shown in the object details here: your direct S3 object URL.

Before you open the address, do you expect the private bucket to return the page or reject the request?

  • Open your direct S3 object URL in a new Microsoft Edge tab.

You'll see Access Denied or HTTP 403 instead of the portfolio.

That failed request is the intended result. It proves that an anonymous visitor cannot use S3 as the public entry point.

Why did S3 reject the request?

S3 evaluates whether the request has permission to read index.html. The anonymous request has no valid permission.

The rejection confirms that your origin remains private. You have now observed the security boundary directly.

Direct request not denied?

  • Confirm the address came from the index.html object details.
  • Confirm all four Block Public Access settings remain enabled.
  • Confirm no public-read permissions were added during upload.

Still seeing the portfolio? Help me audit why my direct S3 object request is publicly accessible.

✔️ My private bucket is ready

Your bucket contains the complete portfolio. The anonymous direct request is denied as intended.

ⓧ I'd like to double check the setup

Use this checklist to compare the bucket configuration with the required end state.

  • Confirm the bucket name is student-portfolio-yourname-unique-suffix.
  • Confirm the bucket is a general purpose bucket in your selected Region.
  • Confirm Object Ownership remains Bucket owner enforced.
  • Confirm default S3 managed encryption remains enabled.
  • Confirm versioning remains disabled.
  • Confirm all four Block Public Access settings remain enabled.
  • Confirm Static website hosting remains disabled.

The remaining checks confirm that the uploaded artifact matches the local portfolio structure.

  • Confirm index.html exists at the bucket root.
  • Confirm css/styles.css exists.
  • Confirm js/main.js exists.
  • Confirm assets/resume.pdf exists.
  • Confirm the anonymous direct request returns Access Denied or HTTP 403.

Your portfolio files now sit behind a private origin. Next, you'll use Amazon CloudFront with Origin Access Control to deliver them through a public HTTPS address.

Deliver the Private Website Through CloudFront

Your private Amazon S3 origin now rejects anonymous requests. Visitors still need a public path to your portfolio.

Amazon CloudFront supplies that path over HTTPS. Origin Access Control authorizes CloudFront to retrieve the private files.

In this step, get ready to:
  • Create a standard CloudFront distribution for the private S3 origin.
  • Configure CloudFront to serve index.html at the root URL.
  • Compare public CloudFront delivery with blocked direct S3 access.
Create the CloudFront distribution

A CloudFront distribution connects a public web address to your private origin. The recommended origin settings create the authorized connection automatically.

  • Use the search bar in the AWS Management Console to search for CloudFront.
  • Select CloudFront to open the distribution console.
  • Choose Create distribution.
  • Enter your distribution name in Distribution name.
  • Choose Single website or app.
  • Continue to the origin settings.

The origin identifies where CloudFront retrieves your portfolio files. Select the regular bucket so OAC can authorize each request.

  • Select Amazon S3 for Origin type.
  • Choose Browse S3 under S3 origin.
  • Select student-portfolio-yourname-unique-suffix.
  • Choose Use recommended origin settings under Settings.

Why use recommended origin settings?

A public S3 website endpoint cannot use OAC. A regular S3 bucket origin supports signed CloudFront requests over HTTPS.

The generated bucket policy authorizes this distribution to retrieve objects. Anonymous visitors still receive no direct S3 permission.

The pricing screen can feel consequential. Pay-as-you-go keeps the project outside a flat-rate subscription.

  • Retain pay-as-you-go if pricing options appear.
  • Leave AWS WAF disabled on Enable security protections.
  • Choose Create distribution.

You'll see the new distribution with Last modified set to Deploying.

That connection is now in place. Your private origin has an authorized CloudFront delivery layer.

Can't create the distribution?

  • Confirm that Browse S3 shows the regular student-portfolio-yourname-unique-suffix bucket.
  • Confirm that you remain signed in to the AWS account containing the bucket.
  • Ask your AWS account administrator for permission to create CloudFront resources if the console blocks the action.

Still stuck? Help me diagnose why I cannot create a CloudFront distribution for my private S3 bucket.

Set the default root object

The distribution currently needs a filename in each request. A default root object tells CloudFront to return index.html when a visitor opens the root address.

  • Choose the General tab in the distribution from earlier.
  • Choose Edit.
  • Enter index.html in Default root object.
  • Save the updated distribution settings.

You'll return to the distribution details with Last modified showing Deploying.

Deployment usually takes a few minutes. The Deploying status is normal while CloudFront sends the configuration to its edge locations.

  • Wait until Last modified changes from Deploying to a date and time.
  • Record the assigned domain name here: your CloudFront distribution domain.

Your recorded domain should resemble d111111abcdef8.cloudfront.net.

Why omit the leading slash?

CloudFront expects the default root object value to be index.html. Entering /index.html can cause 403 Access Denied at the root URL.

Deployment still in progress?

  • Keep the distribution page open while Last modified shows Deploying.
  • Refresh the distribution details after a few minutes.
  • Confirm that Default root object contains exactly index.html if the root URL later fails.

Need help? Help me check why my CloudFront distribution is still deploying or why its root object is failing.

Test the secure delivery path

The final test compares two routes to the same files. The CloudFront route should use the authorized delivery layer while the anonymous S3 route remains blocked.

Before you test both addresses, which request do you expect to load the portfolio?

  • Switch back to Microsoft Edge from earlier.
  • Open a new browser tab.
  • Type https:// into the address bar.
  • Append your CloudFront distribution domain.
  • Add a final /.
  • Press Enter.

You'll see the fully styled hero, cards, skill tags, buttons, spacing, and colors at the CloudFront root URL.

The footer shows the current year. This proves that js/main.js loaded through CloudFront.

  • Test every navigation link on the portfolio.
  • Narrow the Microsoft Edge window until Menu appears.
  • Choose Menu.

You'll see the mobile navigation open.

  • Choose one navigation link from the open menu.

The menu closes. The page scrolls to the selected section.

  • Choose Download resume.

Microsoft Edge opens or downloads assets/resume.pdf.

  • Return to the anonymous direct S3 object request from earlier.
  • Restore the earlier object URL from Microsoft Edge history if its tab is closed.
  • Refresh the direct object request.

You'll still see Access Denied or HTTP 403. CloudFront is now the public delivery layer while Amazon S3 remains private.

Portfolio missing at CloudFront?

  • Confirm that the origin is the regular S3 bucket instead of an S3 website endpoint.
  • Confirm that Use recommended origin settings configured OAC.
  • Confirm that the generated bucket policy remains attached to student-portfolio-yourname-unique-suffix.
  • Confirm that Last modified shows a date and time.
  • Compare missing asset paths with css/styles.css, js/main.js, and assets/resume.pdf.

Still blocked? Help me troubleshoot why my CloudFront portfolio does not load correctly from its private S3 origin.

Your portfolio now has a public HTTPS entry point backed by a private S3 origin. Next, you'll update the deployed page, refresh the CloudFront cache, and prepare the project for presentation.

Update, Troubleshoot, and Document the Deployment

Your portfolio now reaches visitors through Amazon CloudFront while Amazon S3 stays private behind Origin Access Control. The next goal is to prove that you can update this deployment with confidence.

CloudFront can keep an older cached copy after the S3 object changes. You also need a repeatable way to diagnose failures and explain the architecture.

In this step, get ready to:
  • Update the hero status text and refresh the cached portfolio.
  • Test a troubleshooting checklist for common deployment symptoms.
  • Create a presentation record with architecture and deployment evidence.
Refresh the deployed content

Uploading a replacement object updates the S3 origin. A CloudFront invalidation removes the older cached response so the next request retrieves your new version.

The hero uses a paragraph with the class status. Changing this one visible sentence creates a clear test without altering your page structure or asset paths.

  • Switch back to Visual Studio Code.
  • In index.html, find the paragraph whose class is status.
  • Replace only its visible sentence with your revised public hero status.
  • Save index.html.
  • Refresh the local index.html page in Microsoft Edge.

You should see your revised public hero status in the hero section. This confirms the local file contains the intended update.

  • Return to the student-portfolio-yourname-unique-suffix bucket in the AWS Management Console.
  • Choose Upload.
  • Choose Add file.
  • Select student-portfolio/index.html from your local files.
  • Choose Upload.

You should see a success message. The updated file now occupies the same index.html object key.

  • Switch back to the CloudFront distribution from earlier.
  • Select the Invalidations tab.
  • Choose Create invalidation.
  • Enter /* as the invalidation path.
  • Choose Create invalidation again.

Expect a short wait while CloudFront clears the matching cached files. The invalidation is ready when its status shows that it has completed.

  • Wait for the /* invalidation to complete.

Before you refresh the public page, do you expect CloudFront to return the old status or the updated status?

  • Refresh https://<distribution-domain>/ in Microsoft Edge.

You should see your revised public hero status at the HTTPS root URL. That is the complete update loop working from local file to S3 origin to CloudFront cache.

Test the troubleshooting path

A symptom narrows the part of the delivery path you need to inspect. These checks connect common failures to the origin configuration, object keys, cache state, or root object.

  • In the CloudFront distribution, confirm the origin is the regular student-portfolio-yourname-unique-suffix S3 bucket.
  • Confirm Origin Access Control remains configured for the origin.
  • In the S3 bucket, confirm the generated OAC bucket policy remains present.
  • Confirm Last modified shows a date and time instead of Deploying.

Those checks cover the main causes of Access Denied or HTTP 403 responses at CloudFront. A regular S3 origin with OAC needs its generated bucket authorization and a finished deployment.

  • Confirm the stylesheet link matches css/styles.css.
  • Confirm the script link matches js/main.js.
  • Confirm the resume link matches assets/resume.pdf.
  • Compare each path with its exact S3 object key.

How Symptoms Narrow the Search

  • A plain page points to the stylesheet path or object key.
  • An unresponsive menu points to the JavaScript path or an element ID.
  • A failed resume download points to the PDF path or object key.
  • An older hero sentence points to the replacement upload or invalidation.
  • Confirm the replacement upload used the index.html object key.
  • Confirm the /* invalidation completed.
  • Refresh the CloudFront HTTPS root URL.
  • Select the distribution's General tab.
  • Choose Edit.
  • Confirm Default root object contains index.html without a leading slash.

You should now have a specific check for stale content and each common failure path. The healthy deployment retains its regular S3 origin, OAC authorization, exact object paths, completed invalidation, and root object.

Still Seeing a Deployment Problem?

Start with the visible symptom. Trace only the matching part of the delivery path before changing any permissions.

Use the exact URL and current configuration when you ask for help. Help me diagnose my CloudFront portfolio deployment.

Prepare the presentation record

A presentation record turns the working deployment into an explanation another person can follow. It connects the problem, architecture, tests, and evidence in one place.

  • Create a presentation record in a document app you already use.
  • Add the project title.
  • Add the project objective.
  • Describe the problem solved by the private delivery architecture.
  • Describe the proposed solution using Amazon S3 and Amazon CloudFront.

Frame the Architecture Story

The problem is public delivery without exposing the storage origin. The solution uses CloudFront as the HTTPS entry point while OAC authorizes access to the private S3 objects.

  • Record Amazon S3 as the private object storage service.
  • Record Amazon CloudFront as the public HTTPS delivery service.
  • List HTML, CSS, JavaScript, and the resume PDF as the website technologies and content.
  • Summarize the workflow from local editing through upload and invalidation.
  • Describe the expected output at the CloudFront root URL.
  • Document the deployment challenges you observed.
  • Document the fixes associated with those challenges.
  • Describe the AWS knowledge demonstrated by the project.

Keep the Workflow Concrete

Your workflow starts with editing index.html locally. It continues through the replacement upload and the /* invalidation before ending with an HTTPS browser check.

  • Create the architecture diagram in draw.io or Lucidchart.
  • Add a node labelled Microsoft Edge.
  • Add an Amazon CloudFront node below it.
  • Add an Amazon S3 node below CloudFront.
  • Add a final node labelled HTML / CSS / JavaScript / resume.pdf.
  • Connect Microsoft Edge to Amazon CloudFront.
  • Connect Amazon CloudFront to Amazon S3.
  • Connect Amazon S3 to the static content node.

The diagram shows each request moving from the visitor to the public delivery layer. CloudFront retrieves the private source objects from S3 before returning the website content.

Protect Sensitive Account Data

Screenshots can capture account identifiers that have nothing to do with your demonstration. Crop or redact sensitive console data before adding any image to the record.

  • Add the CloudFront URL you recorded earlier.
  • Capture a desktop screenshot of the portfolio at the CloudFront HTTPS URL.
  • Capture a narrow responsive screenshot of the same portfolio.
  • Capture the S3 Block Public Access status.
  • Add your troubleshooting observations from the previous substep.
  • Review every screenshot for sensitive account data.
  • Save the completed presentation record.

Before you review the record, do you think every architecture claim has a matching piece of evidence?

  • Compare the saved presentation record with the project presentation requirements.

You should see a complete record containing the architecture, workflow, tests, troubleshooting observations, challenges, fixes, and AWS skills demonstrated. It should also contain the CloudFront URL, both portfolio screenshots, S3 Block Public Access evidence, and the architecture diagram.

What the Evidence Proves

  • The CloudFront URL and desktop screenshot prove that the public HTTPS delivery path works.
  • The narrow screenshot proves that the portfolio uses responsive web design.
  • The S3 Block Public Access evidence supports the private-origin design.
  • The invalidation result proves that you can publish an update through the cached delivery layer.

You now have a deployed portfolio that you can update, diagnose, and explain with evidence. The project is ready to present.

Secret mission

Audit the Private Delivery Path

Prove that the public CloudFront path works while the private S3 origin remains blocked. You will package the browser evidence with your existing architecture diagram. A three-sentence explanation will connect the results.

Clean Up Your Resources

Clean Up Your Resources

Your private Amazon S3 bucket remains deployed. Stored data and requests can generate charges.

Your Amazon CloudFront distribution also remains deployed. Decide whether to keep the resources running, pause public access, or delete them entirely.

Cost warning

Keeping the portfolio online means Amazon S3 can continue accruing storage and request charges.

CloudFront pay-as-you-go includes 1 TB of data transfer out each month. It also includes 10,000,000 HTTP/HTTPS requests each month.

Usage beyond those CloudFront allowances is billed. Choose Pause or Delete if you no longer need the public portfolio.

Resources you used:

  • AWS resource: One standard CloudFront distribution serving the portfolio over HTTPS.
  • AWS resource: One private Amazon S3 bucket named student-portfolio-yourname-unique-suffix.
  • Local website: The student-portfolio folder containing index.html, css/styles.css, js/main.js, and assets/resume.pdf.
  • Local records: The completed presentation record and Private Delivery Evidence Note.

Keep everything running

No action is needed. Choose this if you want the portfolio to remain available through its CloudFront HTTPS address.

  • Keep the CloudFront distribution enabled.
  • Retain the private student-portfolio-yourname-unique-suffix bucket.
  • Monitor Amazon S3 storage usage.
  • Monitor Amazon S3 request activity.
  • Monitor CloudFront traffic.
  • Preserve the student-portfolio folder for future website updates.
  • Keep the presentation record and Private Delivery Evidence Note for future demonstrations.

Pause - I'll come back to this later

Disable the CloudFront distribution to pause public delivery. Your private Amazon S3 files remain stored for your return.

  • Switch back to the CloudFront console from earlier.
  • Select the portfolio distribution.
  • Choose Disable.
  • Confirm with Yes, Disable.
  • Wait until Last modified displays a new date and time.
  • Refresh the recorded CloudFront HTTPS root URL.

You should see that the public portfolio no longer loads. The private S3 bucket may continue to incur storage charges.

Delete - I don't want to use this again

Remove the deployed AWS resources and local project files to start fresh. This cleanup is permanent.

Take it one resource at a time so each deletion stays deliberate. A deleted CloudFront distribution cannot be recovered.

Delete the CloudFront distribution
  • Switch back to the CloudFront console from earlier.
  • Select the portfolio distribution.
  • Choose Disable if the distribution is still enabled.
  • Confirm with Yes, Disable.
  • Wait until Last modified displays a new date and time.
  • Choose Delete.
  • Confirm that the portfolio distribution no longer appears in the distribution list.
Empty the S3 bucket
  • Switch back to the Amazon S3 console from earlier.
  • Open student-portfolio-yourname-unique-suffix.
  • Empty the bucket by deleting every stored object.
  • Confirm that index.html, css/styles.css, js/main.js, and assets/resume.pdf are no longer listed.
Delete the empty S3 bucket
  • Return to General purpose buckets.
  • Select student-portfolio-yourname-unique-suffix.
  • Choose Delete.
  • Enter student-portfolio-yourname-unique-suffix as the bucket name.
  • Choose Delete bucket.
  • Confirm that the bucket no longer appears under General purpose buckets.
Remove the local project files
  • Locate the student-portfolio folder using File Explorer.
  • Remove the student-portfolio folder using File Explorer.
  • Confirm that the folder no longer appears in File Explorer.
  • Remove the completed presentation record from its saved location.
  • Remove the Private Delivery Evidence Note from its saved location.
  • Confirm that both local records no longer appear in their saved locations.

Your CloudFront distribution and Amazon S3 bucket are now gone. Your local project files and evidence records are also removed.

Nice Work!

Nice Work!

You did it! Your responsive student portfolio now reaches visitors through an HTTPS Amazon CloudFront URL while its Amazon S3 origin stays private.

You've learned how to:

  • Build a responsive student portfolio with clear content sections. Its mobile navigation works in narrow windows. Visitors can also download your resume.
  • Deliver a private S3 origin through CloudFront using Origin Access Control. The public URL uses HTTPS while anonymous direct S3 access remains denied.
  • Manage a repeatable deployment workflow by replacing uploaded content. You refreshed cached files with a /* invalidation. You also documented the architecture and troubleshooting process.
  • Secret Mission: Created a one-page evidence pack proving that CloudFront serves the portfolio while direct anonymous S3 access returns Access Denied or HTTP 403.

Ready to quiz yourself?