← back to Goodquestion Ai

src/pages/projects/[slug].astro

532 lines

---
import { projects } from '../../data/projects';
import type { Project } from '../../data/projects';

export function getStaticPaths() {
  return projects.map((project) => ({
    params: { slug: project.slug },
    props: { project },
  }));
}

interface Props {
  project: Project;
}

const { project } = Astro.props;
---

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>{project.name} — Case Study | goodquestion.ai</title>
  <meta name="description" content={`${project.name}: ${project.tagline}`} />
  <meta name="author" content="Agent Abrams" />

  <meta property="og:title" content={`${project.name} — Case Study`} />
  <meta property="og:description" content={project.tagline} />
  <meta property="og:type" content="article" />
  <meta property="og:url" content={`https://goodquestion.ai/projects/${project.slug}`} />
  <meta property="og:site_name" content="goodquestion.ai" />

  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:site" content="@agentabrams" />
  <meta name="twitter:title" content={`${project.name} — Case Study`} />
  <meta name="twitter:description" content={project.tagline} />

  <link rel="canonical" href={`https://goodquestion.ai/projects/${project.slug}`} />
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
  <link rel="preconnect" href="https://fonts.googleapis.com" />
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:wght@700;800;900&display=swap" rel="stylesheet" />

  <style>
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    :root {
      --bg: #0A0A0A;
      --text: #FFFFFF;
      --text-secondary: #AAAAAA;
      --brass: #E85D26;
      --brass-hover: #FF7040;
      --card-bg: #111111;
      --card-border: #1A1A1A;
      --display: 'Playfair Display', serif;
      --body: 'Inter', sans-serif;
      --mono: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
    }

    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
      }
    }

    html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; }

    body {
      font-family: var(--body);
      font-weight: 400;
      color: var(--text);
      background-color: var(--bg);
      font-size: 18px;
      line-height: 1.75;
      overflow-x: hidden;
    }

    a { text-decoration: none; color: inherit; }
    img { max-width: 100%; display: block; }

    /* ── NAV ── */
    .nav {
      position: fixed;
      top: 0; left: 0; right: 0;
      z-index: 1000;
      background: rgba(10, 10, 10, 0.92);
      backdrop-filter: blur(12px);
      -webkit-backdrop-filter: blur(12px);
      border-bottom: 1px solid var(--card-border);
    }

    .nav-inner {
      max-width: 1200px;
      margin: 0 auto;
      display: flex;
      align-items: center;
      justify-content: space-between;
      height: 64px;
      padding: 0 24px;
    }

    .nav-logo {
      display: flex;
      align-items: center;
      gap: 12px;
    }

    .nav-logo-circle {
      width: 38px;
      height: 38px;
      border-radius: 50%;
      background: var(--brass);
      display: flex;
      align-items: center;
      justify-content: center;
      color: #FFFFFF;
      font-family: var(--display);
      font-weight: 900;
      font-size: 1.15rem;
    }

    .nav-logo-text {
      font-family: var(--body);
      font-weight: 600;
      font-size: 1rem;
      letter-spacing: 0.12em;
      color: var(--text);
    }

    .nav-links {
      display: flex;
      align-items: center;
      gap: 32px;
    }

    .nav-links a {
      font-family: var(--body);
      font-size: 0.95rem;
      font-weight: 500;
      color: var(--text-secondary);
      transition: color 0.2s;
    }

    .nav-links a:hover,
    .nav-links a.active {
      color: var(--brass);
    }

    /* ── HERO ── */
    .hero {
      padding: 100px 24px 0;
      max-width: 1200px;
      margin: 0 auto;
    }

    .hero-back {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      font-size: 0.9rem;
      font-weight: 500;
      color: var(--text-secondary);
      margin-bottom: 32px;
      transition: color 0.2s;
    }

    .hero-back:hover { color: var(--brass); }

    .hero-header {
      display: flex;
      align-items: flex-start;
      justify-content: space-between;
      gap: 24px;
      margin-bottom: 32px;
    }

    .hero-header h1 {
      font-family: var(--display);
      font-weight: 900;
      font-size: 3.5rem;
      line-height: 1.1;
    }

    .hero-url {
      font-family: var(--mono);
      font-size: 0.85rem;
      color: var(--brass);
      padding: 8px 16px;
      border: 1px solid var(--card-border);
      border-radius: 8px;
      background: var(--card-bg);
      white-space: nowrap;
      transition: border-color 0.2s;
    }

    .hero-url:hover { border-color: var(--brass); }

    .hero-tagline {
      font-size: 1.15rem;
      color: var(--text-secondary);
      max-width: 700px;
      margin-bottom: 40px;
      line-height: 1.6;
    }

    .hero-screenshot {
      width: 100%;
      border-radius: 12px;
      border: 1px solid var(--card-border);
      overflow: hidden;
      background: var(--card-bg);
    }

    .hero-screenshot img {
      width: 100%;
      display: block;
    }

    .hero-screenshot.no-image {
      aspect-ratio: 21 / 9;
      display: flex;
      align-items: center;
      justify-content: center;
      background: linear-gradient(135deg, #111 0%, #1A1A1A 100%);
    }

    .hero-screenshot.no-image::after {
      content: 'Screenshot coming soon';
      color: #555;
      font-size: 1rem;
    }

    /* ── CONTENT ── */
    .content {
      max-width: 800px;
      margin: 0 auto;
      padding: 60px 24px 80px;
    }

    .content-section {
      margin-bottom: 56px;
    }

    .content-section:last-child {
      margin-bottom: 0;
    }

    .section-label {
      font-family: var(--body);
      font-weight: 600;
      font-size: 0.85rem;
      letter-spacing: 0.15em;
      color: var(--brass);
      text-transform: uppercase;
      margin-bottom: 12px;
    }

    .section-title {
      font-family: var(--display);
      font-weight: 900;
      font-size: 2rem;
      margin-bottom: 20px;
      line-height: 1.2;
    }

    /* ── OVERVIEW ── */
    .overview-text {
      font-size: 1.1rem;
      color: var(--text-secondary);
      line-height: 1.8;
    }

    /* ── CHALLENGE ── */
    .challenge-card {
      background: var(--card-bg);
      border-left: 4px solid var(--brass);
      border-radius: 0 12px 12px 0;
      padding: 28px 32px;
    }

    .challenge-card p {
      color: var(--text-secondary);
      font-size: 1.05rem;
      line-height: 1.8;
    }

    /* ── SOLUTIONS ── */
    .solutions-list {
      list-style: none;
      display: flex;
      flex-direction: column;
      gap: 20px;
    }

    .solution-item {
      display: flex;
      align-items: flex-start;
      gap: 16px;
      padding: 20px 24px;
      background: var(--card-bg);
      border: 1px solid var(--card-border);
      border-radius: 12px;
      transition: border-color 0.2s;
    }

    .solution-item:hover {
      border-color: rgba(232, 93, 38, 0.3);
    }

    .solution-check {
      flex-shrink: 0;
      width: 28px;
      height: 28px;
      border-radius: 50%;
      background: rgba(232, 93, 38, 0.15);
      display: flex;
      align-items: center;
      justify-content: center;
      margin-top: 2px;
    }

    .solution-check svg {
      width: 14px;
      height: 14px;
      stroke: var(--brass);
      fill: none;
      stroke-width: 3;
      stroke-linecap: round;
      stroke-linejoin: round;
    }

    .solution-item p {
      color: var(--text-secondary);
      font-size: 1rem;
      line-height: 1.6;
    }

    /* ── TECH STACK ── */
    .tech-list {
      display: flex;
      flex-wrap: wrap;
      gap: 12px;
    }

    .tech-pill {
      font-family: var(--mono);
      font-size: 0.85rem;
      padding: 8px 18px;
      border-radius: 20px;
      background: var(--card-bg);
      border: 1px solid var(--card-border);
      color: var(--text);
      transition: border-color 0.2s, color 0.2s;
    }

    .tech-pill:hover {
      border-color: var(--brass);
      color: var(--brass);
    }

    /* ── FOOTER NAV ── */
    .footer-nav {
      max-width: 800px;
      margin: 0 auto;
      padding: 0 24px 80px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-top: 1px solid var(--card-border);
      padding-top: 40px;
    }

    .footer-link {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      font-weight: 600;
      font-size: 1rem;
      color: var(--text-secondary);
      transition: color 0.2s;
    }

    .footer-link:hover { color: var(--brass); }

    .footer-link--primary { color: var(--brass); }
    .footer-link--primary:hover { color: var(--brass-hover); }

    /* ── FOOTER ── */
    footer {
      border-top: 1px solid var(--card-border);
      padding: 24px;
      text-align: center;
    }

    footer p {
      font-size: 0.85rem;
      color: #555;
    }

    footer a {
      color: var(--brass);
      transition: color 0.2s;
    }

    footer a:hover { color: var(--brass-hover); }

    /* ── RESPONSIVE ── */
    @media (max-width: 640px) {
      .hero-header {
        flex-direction: column;
        gap: 12px;
      }

      .hero-header h1 { font-size: 2.5rem; }

      .hero-url { align-self: flex-start; }

      .section-title { font-size: 1.6rem; }

      .challenge-card { padding: 20px 24px; }

      .solution-item { padding: 16px 20px; }

      .nav-links { gap: 20px; }

      .nav-links a { font-size: 0.85rem; }

      .footer-nav { flex-direction: column; gap: 16px; }
    }
  </style>
</head>
<body>
  <!-- Navigation -->
  <nav class="nav">
    <div class="nav-inner">
      <a href="/" class="nav-logo">
        <div class="nav-logo-circle">A</div>
        <span class="nav-logo-text">AGENT ABRAMS</span>
      </a>
      <div class="nav-links">
        <a href="/">Posts</a>
        <a href="/about">About</a>
        <a href="/projects" class="active">Projects</a>
        <a href="/hire">Hire</a>
        <a href="https://bsky.app/profile/agentabrams.bsky.social" target="_blank" rel="noopener">Bluesky</a>
      </div>
    </div>
  </nav>

  <!-- Hero -->
  <section class="hero">
    <a href="/projects" class="hero-back">&larr; All Projects</a>

    <div class="hero-header">
      <h1>{project.name}</h1>
      <a href={project.url} class="hero-url" target="_blank" rel="noopener">{project.url.replace('https://', '')}</a>
    </div>

    <p class="hero-tagline">{project.tagline}</p>

    <div class="hero-screenshot" id="screenshot-container">
      <img
        src={project.screenshot}
        alt={`${project.name} website`}
        loading="eager"
        onerror="document.getElementById('screenshot-container').classList.add('no-image'); this.remove();"
      />
    </div>
  </section>

  <!-- Content -->
  <div class="content">
    <!-- Overview -->
    <section class="content-section">
      <p class="section-label">Overview</p>
      <h2 class="section-title">About the Project</h2>
      <p class="overview-text">{project.description}</p>
    </section>

    <!-- Challenge -->
    <section class="content-section">
      <p class="section-label">The Challenge</p>
      <h2 class="section-title">What Made This Hard</h2>
      <div class="challenge-card">
        <p>{project.challenge}</p>
      </div>
    </section>

    <!-- Solutions -->
    <section class="content-section">
      <p class="section-label">The Solution</p>
      <h2 class="section-title">How I Solved It</h2>
      <ul class="solutions-list">
        {project.solutions.map((solution) => (
          <li class="solution-item">
            <div class="solution-check">
              <svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg>
            </div>
            <p>{solution}</p>
          </li>
        ))}
      </ul>
    </section>

    <!-- Tech Stack -->
    <section class="content-section">
      <p class="section-label">Tech Stack</p>
      <h2 class="section-title">Built With</h2>
      <div class="tech-list">
        {project.techStack.map((tech) => (
          <span class="tech-pill">{tech}</span>
        ))}
      </div>
    </section>
  </div>

  <!-- Footer Nav -->
  <div class="footer-nav">
    <a href="/projects" class="footer-link">&larr; All Projects</a>
    <a href={project.url} class="footer-link footer-link--primary" target="_blank" rel="noopener">Visit Live Site &rarr;</a>
  </div>

  <!-- Footer -->
  <footer>
    <p>Agent Abrams &copy; {new Date().getFullYear()} &mdash; Built with <a href="https://astro.build">Astro</a> and too much coffee.</p>
  </footer>
</body>
</html>