Lesson 10 of 10 — Final Project

Build a Real Website

Put everything together. Build a complete developer portfolio from scratch using layouts, components, loops, conditionals, filters, markdown, and deploy it live.

The Goal

We'll build a developer portfolio website with:

  • A shared layout with header & footer
  • Reusable components (nav, hero, project card, skill badge)
  • Dynamic data with <script data>
  • Loops to render project cards & skill badges
  • Conditionals for featured projects
  • Filters for formatting text
  • A markdown blog post
  • Production-ready minified output

Final project structure

Directory tree files
portfolio/
├── orb.config.json
├── package.json
├── layouts/
│   └── main.layout
├── components/
│   ├── nav.orb
│   ├── hero.orb
│   ├── project-card.orb
│   ├── skill-badge.orb
│   └── footer.orb
├── pages/
│   ├── index.orb          # Homepage
│   ├── projects.orb       # Projects page
│   └── blog/
│       └── hello-world.orb # Blog post
├── assets/
│   └── style.css
└── dist/                   # ← Generated output

Step 1 — Scaffold the Project

Terminal bash
mkdir portfolio && cd portfolio
npm init -y
npm install orblayout
npx orb init

Update your package.json scripts:

package.json (scripts section) json
{
  "scripts": {
    "dev":   "orb dev",
    "build": "orb build",
    "prod":  "orb clean && orb build --minify"
  }
}

Step 2 — Create the Layout

This layout wraps every page with a consistent HTML shell, nav, and footer.

layouts/main.layout .layout
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport"
    content="width=device-width, initial-scale=1.0" />
  <title>{{ title }} — Portfolio</title>
  <link rel="stylesheet"
    href="/assets/style.css" />
  {{ head }}
</head>
<body>

  <use component="nav" active="{{ navActive }}" />

  <main>
    {{ content }}
  </main>

  <use component="footer" />

</body>
</html>
💡

Named slot: {{ head }} lets each page inject custom meta tags or styles into <head>.

Step 3 — Build Components

Navigation

components/nav.orb .orb
<nav class="site-nav">
  <a href="/" class="logo">◉ Dan</a>
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/projects">Projects</a></li>
    <li><a href="/blog/hello-world">Blog</a></li>
  </ul>
</nav>

Hero Section

components/hero.orb .orb
<section class="hero">
  <h1>{{ heading }}</h1>
  <p>{{ subheading }}</p>
  {{#if cta}}
    <a href="{{ ctaLink }}" class="btn">{{ cta }}</a>
  {{/if}}
</section>

Project Card

components/project-card.orb .orb
<style>
.project-card {
  background: #111;
  border-radius: 12px;
  padding: 1.5rem;
  border: 1px solid #222;
  transition: transform 0.2s;
}
.project-card:hover { transform: translateY(-4px); }
.project-card h3 { margin: 0 0 0.5rem; }
.project-card .tag {
  display: inline-block;
  background: #6c5ce7;
  color: #fff;
  padding: 2px 10px;
  border-radius: 99px;
  font-size: 0.75rem;
}
</style>

<div class="project-card">
  <h3>{{ name }}</h3>
  <p>{{ description }}</p>
  <span class="tag">{{ tech }}</span>
  {{#if url}}
    <a href="{{ url }}">View →</a>
  {{/if}}
</div>

Skill Badge

components/skill-badge.orb .orb
<span class="skill-badge">{{ name }}</span>

Footer

components/footer.orb .orb
<footer class="site-footer">
  <p>&copy; {{ @page.year }} Dan — Built with
    <a href="https://github.com/rukkit-official/orblayout">OrbLayout</a>
  </p>
</footer>

Step 4 — Build the Homepage

The homepage brings it all together: data, components, loops, conditionals, and filters.

pages/index.orb .orb
<script data>
({
  title: "Home",
  navActive: "home",
  name: "Dan",
  role: "full-stack developer",
  skills: [
    "JavaScript", "Node.js", "React",
    "Python", "CSS", "OrbLayout"
  ],
  featuredProjects: [
    {
      name: "OrbLayout",
      description: "A lightweight static site builder",
      tech: "Node.js",
      url: "https://github.com/rukkit-official/orblayout"
    },
    {
      name: "TaskFlow",
      description: "Minimal task management app",
      tech: "React",
      url: ""
    }
  ]
})
</script>

<layout src="main.layout">
  <content>

    <!-- Hero using a component -->
    <use component="hero"
      heading="Hi, I'm {{ name }}"
      subheading="I'm a {{ role | capitalize }}"
      cta="View Projects"
      ctaLink="/projects" />

    <!-- Skills — loop over primitives -->
    <section class="skills">
      <h2>Skills</h2>
      <div class="skill-list">
        {{#each skills}}
          <use component="skill-badge"
            name="{{ this }}" />
        {{/each}}
      </div>
    </section>

    <!-- Featured Projects — loop over objects -->
    <section class="featured">
      <h2>Featured Projects</h2>
      <div class="projects-grid">
        {{#each featuredProjects}}
          <use component="project-card"
            name="{{ name }}"
            description="{{ description }}"
            tech="{{ tech }}"
            url="{{ url }}" />
        {{/each}}
      </div>
    </section>

  </content>
</layout>
🔍

Spot the features: Data block, layout, 3 components, 2 loops, filters (capitalize), conditional ({{#if url}} inside project-card), and @page.year in the footer.

Step 5 — Projects Page

pages/projects.orb .orb
<script data>
({
  title: "Projects",
  navActive: "projects",
  projects: [
    {
      name: "OrbLayout",
      description: "Lightweight static site builder with components, layouts, and zero config",
      tech: "Node.js",
      url: "https://github.com/rukkit-official/orblayout",
      featured: true
    },
    {
      name: "TaskFlow",
      description: "Minimal task management app with drag-and-drop",
      tech: "React",
      url: "",
      featured: false
    },
    {
      name: "PixelSnap",
      description: "Screenshot annotation tool",
      tech: "Electron",
      url: "https://pixelsnap.dev",
      featured: true
    }
  ]
})
</script>

<layout src="main.layout">
  <content>
    <h1>{{ title }}</h1>

    <div class="projects-grid">
      {{#each projects}}
        <use component="project-card"
          name="{{ name }}"
          description="{{ description | truncate }}"
          tech="{{ tech }}"
          url="{{ url }}" />
      {{/each}}
    </div>

    <p>Showing {{ projects | length }} projects</p>
  </content>
</layout>

Step 6 — Blog Post with Markdown

pages/blog/hello-world.orb .orb
<script data>
({
  title: "Hello World",
  navActive: "blog",
  author: {
    name: "Dan",
    role: "Creator"
  }
})
</script>

<layout src="main.layout">
  <slot:head>
    <meta property="og:title"
      content="{{ title }}" />
  </slot:head>

  <content>
    <article class="blog-post">
      <h1>{{ title }}</h1>

      {{#with author}}
        <p class="byline">
          By {{ name }}, {{ role }}
        </p>
      {{/with}}

      <time>Published {{ @page.date }}</time>

      <markdown>
## Why I built OrbLayout

I wanted a static site builder that felt like
writing **real HTML**, not a framework.

### The problems with existing tools

- Too much JavaScript
- Complex build configs
- Templates that don't look like HTML
- Slow builds

### How OrbLayout is different

OrbLayout uses `.orb` files that are just
HTML with superpowers:

- **Layouts** for shared page shells
- **Components** for reusable UI blocks
- **Loops** and **conditionals** for dynamic content
- **Filters** for text transformation
- **Markdown** for content-heavy pages
- **Zero config** to get started

---

Thanks for reading! Star us on
[GitHub](https://github.com/rukkit-official/orblayout).
      </markdown>
    </article>
  </content>
</layout>
🎯

Features used: Data block, layout, named slot (slot:head), {{#with}}, @page.date, <markdown> block. Six features in one page!

Step 7 — Global Styles

assets/style.css css
/* Reset & base */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
  font-family: system-ui, sans-serif;
  background: #0a0a1a;
  color: #e0e0e0;
  line-height: 1.6;
}

/* Navigation */
.site-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 2rem;
  border-bottom: 1px solid #1a1a2e;
}
.site-nav ul { list-style: none; display: flex; gap: 1.5rem; }
.site-nav a { color: #a0a0b0; text-decoration: none; }
.site-nav a:hover { color: #6c5ce7; }

/* Hero */
.hero {
  text-align: center;
  padding: 6rem 2rem;
}
.hero h1 { font-size: 3rem; }
.hero .btn {
  display: inline-block;
  margin-top: 1.5rem;
  padding: 0.75rem 2rem;
  background: #6c5ce7;
  color: #fff;
  border-radius: 8px;
  text-decoration: none;
}

/* Skills */
.skill-list { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.skill-badge {
  background: #1a1a2e;
  padding: 6px 16px;
  border-radius: 99px;
  font-size: 0.85rem;
  border: 1px solid #2a2a4e;
}

/* Projects grid */
.projects-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.5rem;
  padding: 2rem 0;
}

/* Blog */
.blog-post { max-width: 680px; margin: 0 auto; padding: 3rem 2rem; }
.byline { color: #888; margin: 0.5rem 0; }

/* Footer */
.site-footer {
  text-align: center;
  padding: 2rem;
  border-top: 1px solid #1a1a2e;
  color: #666;
}
.site-footer a { color: #6c5ce7; }

Step 8 — Build & Deploy

Build for production

Terminal bash
npm run prod

# Output:
#   ✓ index.orb → index.html
#   ✓ projects.orb → projects.html
#   ✓ blog/hello-world.orb → blog/hello-world.html
#   ✓ Assets copied
#   Done! 3 page(s) compiled in 18ms

Your dist/ folder now contains pure HTML, CSS, and assets — ready to deploy anywhere.

Deploy to GitHub Pages

.github/workflows/deploy.yml yaml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm install
      - run: npx orb build --minify
      - uses: peaceiris/actions-gh-pages@v3
        with:
          publish_dir: ./dist

Deploy to Netlify

netlify.toml toml
[build]
  command = "npx orb build --minify"
  publish = "dist"

Deploy to Vercel

vercel.json json
{
  "buildCommand": "npx orb build --minify",
  "outputDirectory": "dist"
}

Deploy to Cloudflare Pages

Cloudflare Pages Settings config
Build command:     npx orb build --minify
Build output:      dist
Node version:      20
🚀

It's just static files! OrbLayout outputs pure HTML/CSS/JS — no server-side runtime needed. Deploy to any static hosting provider.

Course Recap — Everything You've Learned

Congratulations! You've completed the entire OrbLayout course. Here's what you now know:

LessonFeatureStatus
01Installation, scaffolding, project structure, build pipeline
02Layouts, {{ content }}, named slots, multiple layouts
03Components, props, nesting, styles, imports & aliases
04Data blocks, variables, types, nested objects, @page.*
05Loops, {{ this }}, loop helpers, components in loops
06Conditionals, {{#if}}, {{else}}, {{#unless}}, truthy/falsy
07All 12 built-in filters, chaining, custom filters API
08Markdown blocks, {{#with}}, partials, :class directives
09CLI commands, dev server, config, npm scripts, programmatic API
10Built a complete portfolio with deployment configs
🎉

You've completed the OrbLayout Learning Course!

You're now equipped to build any static website with OrbLayout — from personal blogs and portfolios to documentation sites and landing pages.

⭐ Star on GitHub   ·   📦 View on npm   ·   📚 Back to Course Index

✅ Final Checkpoint

After this lesson you can:

  1. Build a complete multi-page website from scratch
  2. Structure a project with layouts, components, and pages
  3. Use data, loops, conditionals, filters, and markdown together
  4. Build production-ready minified output
  5. Deploy to GitHub Pages, Netlify, Vercel, or Cloudflare Pages
  6. Confidently build anything with OrbLayout 💪