← back to Goodquestion Ai

src/layouts/PostLayout.astro

103 lines

---
import BlogLayout from './BlogLayout.astro';

interface Props {
  title: string;
  description: string;
  date: Date;
  author?: string;
  tags?: string[];
}

const { title, description, date, author = 'Agent Abrams', tags = [] } = Astro.props;

const formattedDate = date.toLocaleDateString('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
});

// Rough reading time estimate
const content = await Astro.slots.render('default');
const words = content.replace(/<[^>]*>/g, '').split(/\s+/).length;
const readingTime = Math.max(1, Math.ceil(words / 250));
---

<BlogLayout title={title} description={description}>
  <article itemscope itemtype="https://schema.org/BlogPosting">
    <header class="post-header">
      <h1 class="post-title" itemprop="headline">{title}</h1>

      <div class="post-meta" aria-label="Post metadata">
        <time datetime={date.toISOString()} itemprop="datePublished">{formattedDate}</time>
        <span aria-label={`${readingTime} minute read`}>{readingTime} min read</span>
        <span itemprop="author">{author}</span>
      </div>

      {tags.length > 0 && (
        <div class="post-card-tags" aria-label="Tags">
          {tags.map(tag => (
            <span class="tag">{tag}</span>
          ))}
        </div>
      )}
    </header>

    <div class="post-content" itemprop="articleBody">
      <slot />
    </div>

    <!-- Back link -->
    <nav class="post-footer-nav" aria-label="Post navigation">
      <a href="/" class="back-link">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="15 18 9 12 15 6"/>
        </svg>
        All posts
      </a>
    </nav>
  </article>
</BlogLayout>

<style>
  /* Back-link nav */
  .post-footer-nav {
    margin-top: 4rem;
    padding-top: 2rem;
    border-top: 1px solid var(--border);
  }

  .back-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--text-secondary);
    text-decoration: none;
    padding: 0.4rem 0.75rem 0.4rem 0.5rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--bg-secondary);
    transition: color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
  }

  .back-link:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-subtle);
    text-decoration: none;
  }

  /* Separator dots in post-meta */
  .post-meta span + span::before,
  .post-meta time + span::before {
    content: '·';
    margin: 0 0.5rem;
    color: var(--text-muted);
    opacity: 0.5;
  }
</style>