diff --git a/_includes/header.11ty.js b/_includes/header.11ty.js new file mode 100644 index 00000000000..41c2ca14207 --- /dev/null +++ b/_includes/header.11ty.js @@ -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 ` + + `; +}; diff --git a/_includes/layout.11ty.js b/_includes/layout.11ty.js index 0698b9cfa5a..6a7c956f212 100644 --- a/_includes/layout.11ty.js +++ b/_includes/layout.11ty.js @@ -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 = ""; @@ -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 @@ -55,16 +59,19 @@ module.exports = async function(data) { - -
-
- ${data.content} -
- + ${headerHtml} +
+ +
+
+ ${data.content} +
+ +
`; @@ -90,19 +97,16 @@ module.exports = async function(data) { ` : ""} -
-

${data.metadata.username}'s avatar${data.metadata.username}'s Twitter Archive${titleTweetNumberStr}

- ${!data.hideHeaderTweetsLink ? ``: ""} -
-
- ${data.content} -
- ${navHtml} - + ${headerHtml} +
+
+ ${data.content} +
+ ${navHtml} + +
`; }; diff --git a/assets/style.css b/assets/style.css index 3dbe92e762a..0961571258b 100644 --- a/assets/style.css +++ b/assets/style.css @@ -10,7 +10,7 @@ body { margin: 0; padding: 0; display: flex; - flex-direction: row; + flex-direction: column; } @media (max-width: 48em) { /* 768px */ body { @@ -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; + } } \ No newline at end of file diff --git a/docs/header-component.md b/docs/header-component.md new file mode 100644 index 00000000000..98129ff08e2 --- /dev/null +++ b/docs/header-component.md @@ -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 `
` and `