Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions _includes/header.11ty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const metadata = require("../_data/metadata.js");

module.exports = function(data) {
// Determine if this is the current page for navigation highlighting
const isHome = data.page.url === "/" || data.page.fileSlug === "index";
const isRecent = data.page.url.startsWith("/recent");
const isPopular = data.page.url.startsWith("/popular");

return `
<header class="site-header" role="banner">
<div class="site-header-content">
<h1 class="site-title">
<a href="/" aria-label="Home - ${metadata.username}'s Twitter Archive">
<img src="${metadata.avatar}" width="40" height="40" alt="" class="site-avatar" aria-hidden="true">
<span class="site-title-text">${metadata.username}'s Twitter Archive</span>
</a>
</h1>
<nav class="site-nav" aria-label="Main navigation">
<ul class="site-nav-list">
<li><a href="/" ${isHome ? 'aria-current="page"' : ''}>Home</a></li>
<li><a href="/recent/" ${isRecent ? 'aria-current="page"' : ''}>Recent</a></li>
<li><a href="/popular/" ${isPopular ? 'aria-current="page"' : ''}>Popular</a></li>
<li><a href="${metadata.homeUrl}" rel="external">${metadata.homeLabel}</a></li>
</ul>
</nav>
</div>
</header>
`;
};
50 changes: 27 additions & 23 deletions _includes/layout.11ty.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const dataSource = require("../src/DataSource");
const metadata = require("../_data/metadata.js");
const header = require("./header.11ty.js");

module.exports = async function(data) {
let titleTweetNumberStr = "";
Expand Down Expand Up @@ -38,6 +39,9 @@ module.exports = async function(data) {

// Check if this is the index page and should use sidebar layout
const isIndexPage = data.page.url === "/" || data.page.fileSlug === "index.11ty" || data.page.fileSlug === "index";

// Generate the consistent header
const headerHtml = header.call(this, data);

if (isIndexPage) {
// Sidebar layout for index page
Expand All @@ -55,16 +59,19 @@ module.exports = async function(data) {
<script src="/assets/is-land.js" type="module"></script>
</head>
<body>
<aside class="sidebar">
${data.sidebarContent || ''}
</aside>
<div class="main-content">
<main>
${data.content}
</main>
<footer>
<p>An open source project from <a href="https://github.com/tweetback">tweetback</a>.</p>
</footer>
${headerHtml}
<div class="page-container">
<aside class="sidebar">
${data.sidebarContent || ''}
</aside>
<div class="main-content">
<main>
${data.content}
</main>
<footer>
<p>An open source project from <a href="https://github.com/tweetback">tweetback</a>.</p>
</footer>
</div>
</div>
</body>
</html>`;
Expand All @@ -90,19 +97,16 @@ module.exports = async function(data) {
` : ""}
</head>
<body>
<header>
<h1 class="tweets-title"><a href="/"><img src="${metadata.avatar}" width="104" height="104" alt="${data.metadata.username}'s avatar" class="tweet-avatar">${data.metadata.username}'s Twitter Archive</a>${titleTweetNumberStr}</h1>
${!data.hideHeaderTweetsLink ? `<ul class="tweets-nav">
<li><a rel="home" href="${data.metadata.homeUrl}">← ${data.metadata.homeLabel}</a></li>
</ul>`: ""}
</header>
<main>
${data.content}
</main>
${navHtml}
<footer>
<p>An open source project from <a href="https://github.com/tweetback">tweetback</a>.</p>
</footer>
${headerHtml}
<div class="page-container page-container-single">
<main>
${data.content}
</main>
${navHtml}
<footer>
<p>An open source project from <a href="https://github.com/tweetback">tweetback</a>.</p>
</footer>
</div>
</body>
</html>`;
};
146 changes: 145 additions & 1 deletion assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ body {
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
flex-direction: column;
}
@media (max-width: 48em) { /* 768px */
body {
Expand Down Expand Up @@ -459,4 +459,148 @@ img.tweet-media-og {
margin: 0 auto;
max-width: 40rem; /* 640px /16 */
font-size: 1.25em; /* 20px /16 */
}

/* Site Header */
.site-header {
background-color: #fff;
border-bottom: 2px solid #ddd;
padding: 1rem 2rem;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
@media (max-width: 48em) { /* 768px */
.site-header {
padding: 0.75rem 1rem;
position: static;
}
}

.site-header-content {
max-width: 70em;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 1rem;
}

.site-title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
line-height: 1.2;
}

.site-title a {
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
color: #1C4A1D;
}

.site-title a:hover {
text-decoration: underline;
}

.site-avatar {
border-radius: 50%;
flex-shrink: 0;
}

.site-title-text {
display: inline-block;
}

@media (max-width: 32em) { /* 512px */
.site-title {
font-size: 1rem;
}
.site-avatar {
width: 32px;
height: 32px;
}
}

.site-nav {
flex-shrink: 0;
}

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

@media (max-width: 48em) { /* 768px */
.site-nav-list {
gap: 1rem;
flex-wrap: wrap;
}
}

@media (max-width: 32em) { /* 512px */
.site-nav-list {
gap: 0.75rem;
font-size: 0.9rem;
}
}

.site-nav-list a {
text-decoration: none;
color: #1C4A1D;
font-weight: 500;
padding: 0.25rem 0.5rem;
border-radius: 4px;
transition: background-color 0.2s;
}

.site-nav-list a:hover {
background-color: #f5f5f5;
text-decoration: underline;
}

.site-nav-list a[aria-current="page"] {
background-color: #17bf63;
color: #fff;
}

.site-nav-list a[aria-current="page"]:hover {
background-color: #15a957;
text-decoration: none;
}

/* Page Container */
.page-container {
display: flex;
flex: 1;
flex-direction: row;
}

@media (max-width: 48em) { /* 768px */
.page-container {
flex-direction: column;
}
}

/* Single column layout (no sidebar) */
.page-container-single {
flex-direction: column;
max-width: 70em;
margin: 0 auto;
padding: 2rem;
width: 100%;
}

@media (max-width: 48em) { /* 768px */
.page-container-single {
padding: 1rem;
}
}
71 changes: 71 additions & 0 deletions docs/header-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Site Header Component

## Overview

The site header provides consistent navigation and branding across all pages of the tweetback Twitter Archive.

## Features

- **Consistent Branding**: Logo with avatar and site title appears on every page
- **Primary Navigation**: Quick access to Home, Recent, Popular pages, and external website link
- **Accessibility**: Proper ARIA labels, semantic HTML, and keyboard navigation support
- **Responsive Design**: Adapts to mobile and desktop viewports
- **Active Page Indication**: Current page is highlighted in the navigation

## Implementation

The header is implemented as a reusable component in `_includes/header.11ty.js` and is automatically included in the main layout (`_includes/layout.11ty.js`).

### File Structure

```
_includes/
├── header.11ty.js # Header component
└── layout.11ty.js # Main layout that includes the header
```

### Navigation Items

The header includes the following navigation links:

1. **Home** - Links to the homepage (/)
2. **Recent** - Links to recent tweets page (/recent/)
3. **Popular** - Links to popular tweets page (/popular/)
4. **External Link** - Configurable link to user's main website (from metadata.js)

### Styling

Header styles are defined in `assets/style.css` with the following CSS classes:

- `.site-header` - Main header container
- `.site-header-content` - Content wrapper with max-width
- `.site-title` - Logo and title
- `.site-avatar` - User avatar image
- `.site-nav` - Navigation container
- `.site-nav-list` - Navigation list
- `[aria-current="page"]` - Active page indicator

### Customization

To customize the header:

1. **Change Logo/Avatar**: Update the `avatar` property in `_data/metadata.js`
2. **Modify Navigation Links**: Edit the navigation structure in `_includes/header.11ty.js`
3. **Adjust Styling**: Modify the `.site-header` styles in `assets/style.css`
4. **Add New Navigation Items**: Add new list items to the `.site-nav-list` in `header.11ty.js`

### Accessibility Features

- Semantic HTML5 `<header>` and `<nav>` elements
- ARIA labels for screen readers (`role="banner"`, `aria-label="Main navigation"`)
- `aria-current="page"` attribute for current page indication
- Keyboard navigation support
- Sufficient color contrast for links and text

## Browser Support

The header works across all modern browsers and gracefully degrades for older browsers. The responsive design adapts to:

- Desktop (>768px)
- Tablet (768px)
- Mobile (<512px)
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"entities": "^4.5.0",
"lodash": "^4.17.21",
"numeral": "^2.0.6",
"pagefind": "^0.10.6",
"parse-domain": "^5.0.0",
"sentiment": "^5.0.2",
"sqlite3": "^5.1.6",
Expand Down