My agentic slop goes here. Not intended for anyone else!

more slop

+118
webcomp/BUSHEL.md
···
+
# Bushel Schemas
+
+
Bushel is a webring platform for collaborative online content sharing, described as "a reconstruction of LiveJournal for the 2020s." It enables open source communities to track information across multiple communication channels with privacy controls.
+
+
## Overview
+
+
Based on the repository structure and documentation, Bushel provides:
+
+
- **Collaborative Content Sharing**: Individuals can quickly write about their work
+
- **Bidirectional Linking**: Groups can share content with interconnected references
+
- **Version-Controlled Content**: Supports versioned data structures like blog posts, wiki entries, and social media feeds
+
- **Privacy Management**: GitHub OAuth integration for controlling access to private content
+
- **Resolvable Markdown**: Uses enhanced Markdown for sharing structured data
+
+
## Core Schema Modules
+
+
The library is organized into several key modules:
+
+
### Contact Schema
+
+
The `contact` module likely defines structures for:
+
+
```ocaml
+
(* Hypothetical contact schema based on social networking requirements *)
+
type contact = {
+
id: string;
+
name: string;
+
email: string option;
+
github_username: string option;
+
website: string option;
+
bio: string option;
+
avatar_url: string option;
+
public_key: string option;
+
}
+
+
type social_link = {
+
platform: string; (* github, mastodon, twitter, etc *)
+
handle: string;
+
url: string;
+
}
+
+
type contact_with_social = {
+
contact: contact;
+
social_links: social_link list;
+
trusted: bool; (* for privacy/trust relationships *)
+
}
+
```
+
+
### Entry Schema
+
+
The `entry` module represents content items:
+
- Blog posts
+
- Wiki entries
+
- Notes
+
- Ideas
+
- News items
+
+
### Project Schema
+
+
The `project` module handles:
+
- Open source project metadata
+
- Collaboration settings
+
- Privacy controls
+
- Version tracking
+
+
### Link Schema
+
+
The `link` module manages:
+
- Bidirectional references between content
+
- External links with metadata
+
- Cross-references between users and projects
+
+
## Contact-Focused Features
+
+
### Identity Management
+
- GitHub OAuth integration for authentication
+
- Support for multiple identity providers
+
- Public key infrastructure for secure communication
+
+
### Social Networking
+
- Contact discovery within communities
+
- Trust relationships for privacy
+
- Cross-platform identity linking
+
+
### Communication Channels
+
- Multi-channel content aggregation
+
- Private design discussions
+
- Public collaboration spaces
+
+
### Privacy Controls
+
- Individual privacy settings per contact
+
- Group-based access controls
+
- Selective content sharing
+
+
## Markdown Extensions
+
+
Bushel extends standard Markdown with special syntax:
+
- `@avsm/bushel` - Reference to users/projects
+
- Support for structured data embedding
+
- Version-controlled content references
+
+
## Technical Architecture
+
+
- **Language**: Primarily OCaml (77%) with Python utilities (22.5%)
+
- **Content Format**: Enhanced Markdown with structured data
+
- **Versioning**: Git-based version control for all content
+
- **Authentication**: GitHub OAuth with extensible identity providers
+
- **Privacy**: Granular access controls and trust relationships
+
+
## Use Cases
+
+
1. **Open Source Communities**: Track contributions across multiple channels
+
2. **Collaborative Writing**: Shared documentation with version control
+
3. **Social Networking**: Privacy-aware community building
+
4. **Content Aggregation**: Unified view of distributed content
+
5. **Project Management**: Cross-project collaboration and references
+
+
The contact schema is central to enabling these use cases by providing a foundation for identity, trust, and communication within the Bushel ecosystem.
+3
webcomp/CLAUDE.md
···
+
I'd like to experiment with webcomponents and build up a website entirely composed around the principled use of them, with semantically meaningful tags and layout templates that are translated to appropriate html.
+
+
We'll start by building up a library of small components based on the bushel schemas in https://github.com/avsm/bushel
+356
webcomp/bushel-author-demo.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>Bushel Author Component Demo</title>
+
<script src="bushel-author.js"></script>
+
<style>
+
body {
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+
max-width: 1200px;
+
margin: 0 auto;
+
padding: 2rem;
+
line-height: 1.6;
+
}
+
+
.demo-section {
+
margin: 3rem 0;
+
padding: 2rem;
+
border: 1px solid #e9ecef;
+
border-radius: 0.5rem;
+
background: #f8f9fa;
+
}
+
+
.demo-section h2 {
+
margin-top: 0;
+
color: #495057;
+
}
+
+
.demo-grid {
+
display: grid;
+
gap: 2rem;
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+
margin-top: 1.5rem;
+
}
+
+
.demo-item {
+
padding: 1rem;
+
background: white;
+
border-radius: 0.25rem;
+
border: 1px solid #dee2e6;
+
}
+
+
.demo-item h3 {
+
margin-top: 0;
+
font-size: 1rem;
+
color: #6c757d;
+
}
+
+
pre {
+
background: #f1f3f4;
+
padding: 1rem;
+
border-radius: 0.25rem;
+
overflow-x: auto;
+
font-size: 0.875rem;
+
}
+
+
.custom-styles {
+
--bushel-author-name-color: #d63384;
+
--bushel-author-photo-radius: 0.25rem;
+
--bushel-author-background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+
--bushel-author-padding: 1rem;
+
--bushel-author-border-radius: 0.5rem;
+
color: white;
+
}
+
</style>
+
</head>
+
<body>
+
<h1>Bushel Author Web Component Demo</h1>
+
+
<p>This component generates semantic HTML with h-card microformats for author information,
+
automatically expanding simple attributes into rich, accessible markup.</p>
+
+
<div class="demo-section">
+
<h2>Basic Usage</h2>
+
<div class="demo-grid">
+
<div class="demo-item">
+
<h3>Minimal Example</h3>
+
<bushel-author
+
name="Jane Smith"
+
github="janesmith">
+
</bushel-author>
+
+
<pre>&lt;bushel-author
+
name="Jane Smith"
+
github="janesmith"&gt;
+
&lt;/bushel-author&gt;</pre>
+
</div>
+
+
<div class="demo-item">
+
<h3>With Photo and Links</h3>
+
<bushel-author
+
name="Alex Chen"
+
photo="https://picsum.photos/200/200?random=1"
+
website="https://alexchen.dev"
+
github="alexchen"
+
mastodon="https://mastodon.social/@alexchen"
+
link-profile>
+
</bushel-author>
+
+
<pre>&lt;bushel-author
+
name="Alex Chen"
+
photo="https://picsum.photos/200/200?random=1"
+
website="https://alexchen.dev"
+
github="alexchen"
+
mastodon="https://mastodon.social/@alexchen"
+
link-profile&gt;
+
&lt;/bushel-author&gt;</pre>
+
</div>
+
</div>
+
</div>
+
+
<div class="demo-section">
+
<h2>Display Modes</h2>
+
<div class="demo-grid">
+
<div class="demo-item">
+
<h3>Inline (Default)</h3>
+
<bushel-author
+
name="Dr. Sarah Johnson"
+
job-title="Senior Researcher"
+
org="MIT"
+
photo="https://picsum.photos/200/200?random=2"
+
email="sarah@mit.edu">
+
</bushel-author>
+
+
<pre>&lt;bushel-author
+
name="Dr. Sarah Johnson"
+
job-title="Senior Researcher"
+
org="MIT"
+
photo="https://picsum.photos/200/200?random=2"
+
email="sarah@mit.edu"&gt;
+
&lt;/bushel-author&gt;</pre>
+
</div>
+
+
<div class="demo-item">
+
<h3>Card Mode</h3>
+
<bushel-author
+
display-mode="card"
+
name="Marcus Williams"
+
job-title="Frontend Developer"
+
org="TechCorp"
+
photo="https://picsum.photos/200/200?random=3"
+
bio="Building accessible web experiences with modern technologies."
+
github="marcusw"
+
twitter="marcusw_dev">
+
</bushel-author>
+
+
<pre>&lt;bushel-author
+
display-mode="card"
+
name="Marcus Williams"
+
job-title="Frontend Developer"
+
org="TechCorp"
+
photo="https://picsum.photos/200/200?random=3"
+
bio="Building accessible web experiences."
+
github="marcusw"
+
twitter="marcusw_dev"&gt;
+
&lt;/bushel-author&gt;</pre>
+
</div>
+
+
<div class="demo-item">
+
<h3>Compact Mode</h3>
+
<bushel-author
+
display-mode="compact"
+
name="Emma Rodriguez"
+
photo="https://picsum.photos/200/200?random=4"
+
github="emmarodriguez">
+
</bushel-author>
+
+
<pre>&lt;bushel-author
+
display-mode="compact"
+
name="Emma Rodriguez"
+
photo="https://picsum.photos/200/200?random=4"
+
github="emmarodriguez"&gt;
+
&lt;/bushel-author&gt;</pre>
+
</div>
+
</div>
+
</div>
+
+
<div class="demo-section">
+
<h2>Structured Names</h2>
+
<div class="demo-grid">
+
<div class="demo-item">
+
<h3>Separate Name Parts</h3>
+
<bushel-author
+
given-name="Robert"
+
family-name="Taylor"
+
nickname="Bob"
+
photo="https://picsum.photos/200/200?random=5"
+
job-title="UX Designer">
+
</bushel-author>
+
+
<pre>&lt;bushel-author
+
given-name="Robert"
+
family-name="Taylor"
+
nickname="Bob"
+
photo="https://picsum.photos/200/200?random=5"
+
job-title="UX Designer"&gt;
+
&lt;/bushel-author&gt;</pre>
+
+
<p><small>Note: Structured name parts are embedded as hidden microformat data while displaying the formatted name.</small></p>
+
</div>
+
</div>
+
</div>
+
+
<div class="demo-section">
+
<h2>Custom Styling</h2>
+
<div class="demo-grid">
+
<div class="demo-item">
+
<h3>CSS Custom Properties</h3>
+
<bushel-author
+
class="custom-styles"
+
name="Luna Zhang"
+
job-title="Creative Director"
+
photo="https://picsum.photos/200/200?random=6"
+
website="https://lunazhang.com"
+
link-profile>
+
</bushel-author>
+
+
<pre>&lt;bushel-author
+
class="custom-styles"
+
name="Luna Zhang"
+
job-title="Creative Director"
+
photo="https://picsum.photos/200/200?random=6"
+
website="https://lunazhang.com"
+
link-profile&gt;
+
&lt;/bushel-author&gt;
+
+
&lt;style&gt;
+
.custom-styles {
+
--bushel-author-name-color: #d63384;
+
--bushel-author-photo-radius: 0.25rem;
+
--bushel-author-background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+
--bushel-author-padding: 1rem;
+
--bushel-author-border-radius: 0.5rem;
+
color: white;
+
}
+
&lt;/style&gt;</pre>
+
</div>
+
</div>
+
</div>
+
+
<div class="demo-section">
+
<h2>Generated HTML Structure</h2>
+
<p>Each component generates semantic HTML with h-card microformats:</p>
+
<pre>&lt;div class="h-card bushel-author-card"&gt;
+
&lt;img class="u-photo author-photo" src="..." alt="Photo of John Doe"&gt;
+
&lt;div class="author-info"&gt;
+
&lt;a class="p-name author-name u-url" href="https://johndoe.com"&gt;
+
John Doe
+
&lt;span class="p-given-name visually-hidden"&gt;John&lt;/span&gt;
+
&lt;span class="p-family-name visually-hidden"&gt;Doe&lt;/span&gt;
+
&lt;/a&gt;
+
&lt;div class="author-title"&gt;
+
&lt;span class="p-job-title"&gt;Software Engineer&lt;/span&gt; at
+
&lt;span class="p-org"&gt;Acme Corp&lt;/span&gt;
+
&lt;/div&gt;
+
&lt;div class="p-note author-bio"&gt;Building great software.&lt;/div&gt;
+
&lt;div class="author-social"&gt;
+
&lt;a class="u-email social-link" href="mailto:john@example.com"&gt;Email&lt;/a&gt;
+
&lt;a class="u-url social-link" href="https://github.com/johndoe" rel="me"&gt;GitHub&lt;/a&gt;
+
&lt;/div&gt;
+
&lt;/div&gt;
+
&lt;/div&gt;</pre>
+
</div>
+
+
<div class="demo-section">
+
<h2>Available Attributes</h2>
+
<table style="width: 100%; border-collapse: collapse; margin-top: 1rem;">
+
<thead>
+
<tr style="background: #e9ecef;">
+
<th style="padding: 0.75rem; text-align: left; border: 1px solid #dee2e6;">Attribute</th>
+
<th style="padding: 0.75rem; text-align: left; border: 1px solid #dee2e6;">Description</th>
+
<th style="padding: 0.75rem; text-align: left; border: 1px solid #dee2e6;">Microformat Class</th>
+
</tr>
+
</thead>
+
<tbody>
+
<tr>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>name</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Full display name</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>p-name</code></td>
+
</tr>
+
<tr style="background: #f8f9fa;">
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>given-name</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">First name</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>p-given-name</code></td>
+
</tr>
+
<tr>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>family-name</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Last name</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>p-family-name</code></td>
+
</tr>
+
<tr style="background: #f8f9fa;">
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>nickname</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Alias or handle</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>p-nickname</code></td>
+
</tr>
+
<tr>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>email</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Email address</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>u-email</code></td>
+
</tr>
+
<tr style="background: #f8f9fa;">
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>website</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Personal website URL</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>u-url</code></td>
+
</tr>
+
<tr>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>photo</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Profile photo URL</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>u-photo</code></td>
+
</tr>
+
<tr style="background: #f8f9fa;">
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>bio</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Biography or description</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>p-note</code></td>
+
</tr>
+
<tr>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>org</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Organization name</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>p-org</code></td>
+
</tr>
+
<tr style="background: #f8f9fa;">
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>job-title</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Job title or role</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>p-job-title</code></td>
+
</tr>
+
<tr>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>github</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">GitHub username</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>u-url</code></td>
+
</tr>
+
<tr style="background: #f8f9fa;">
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>mastodon</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Full Mastodon profile URL</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>u-url</code></td>
+
</tr>
+
<tr>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>twitter</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Twitter username</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>u-url</code></td>
+
</tr>
+
<tr style="background: #f8f9fa;">
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>display-mode</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Layout: inline, card, compact</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">-</td>
+
</tr>
+
<tr>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;"><code>link-profile</code></td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">Make name clickable to website</td>
+
<td style="padding: 0.75rem; border: 1px solid #dee2e6;">-</td>
+
</tr>
+
</tbody>
+
</table>
+
</div>
+
</body>
+
</html>
+327
webcomp/bushel-author.js
···
+
class BushelAuthor extends HTMLElement {
+
constructor() {
+
super();
+
this.attachShadow({ mode: 'open' });
+
}
+
+
static get observedAttributes() {
+
return [
+
'name', 'given-name', 'family-name', 'nickname',
+
'email', 'website', 'github', 'mastodon', 'twitter',
+
'photo', 'bio', 'org', 'job-title',
+
'display-mode', 'link-profile'
+
];
+
}
+
+
connectedCallback() {
+
this.render();
+
}
+
+
attributeChangedCallback() {
+
this.render();
+
}
+
+
get displayMode() {
+
return this.getAttribute('display-mode') || 'inline';
+
}
+
+
get shouldLinkProfile() {
+
return this.hasAttribute('link-profile');
+
}
+
+
buildHCard() {
+
const name = this.getAttribute('name');
+
const givenName = this.getAttribute('given-name');
+
const familyName = this.getAttribute('family-name');
+
const nickname = this.getAttribute('nickname');
+
const email = this.getAttribute('email');
+
const website = this.getAttribute('website');
+
const github = this.getAttribute('github');
+
const mastodon = this.getAttribute('mastodon');
+
const twitter = this.getAttribute('twitter');
+
const photo = this.getAttribute('photo');
+
const bio = this.getAttribute('bio');
+
const org = this.getAttribute('org');
+
const jobTitle = this.getAttribute('job-title');
+
+
const displayName = name || `${givenName || ''} ${familyName || ''}`.trim() || nickname;
+
+
const hcard = document.createElement('div');
+
hcard.className = 'h-card bushel-author-card';
+
+
// Photo
+
if (photo) {
+
const img = document.createElement('img');
+
img.className = 'u-photo author-photo';
+
img.src = photo;
+
img.alt = `Photo of ${displayName}`;
+
img.loading = 'lazy';
+
hcard.appendChild(img);
+
}
+
+
const infoContainer = document.createElement('div');
+
infoContainer.className = 'author-info';
+
+
// Name (with optional link)
+
const nameElement = document.createElement(this.shouldLinkProfile && website ? 'a' : 'span');
+
nameElement.className = 'p-name author-name';
+
nameElement.textContent = displayName;
+
+
if (this.shouldLinkProfile && website) {
+
nameElement.href = website;
+
nameElement.className += ' u-url';
+
}
+
+
// Hidden structured name parts
+
if (givenName) {
+
const givenSpan = document.createElement('span');
+
givenSpan.className = 'p-given-name visually-hidden';
+
givenSpan.textContent = givenName;
+
nameElement.appendChild(givenSpan);
+
}
+
+
if (familyName) {
+
const familySpan = document.createElement('span');
+
familySpan.className = 'p-family-name visually-hidden';
+
familySpan.textContent = familyName;
+
nameElement.appendChild(familySpan);
+
}
+
+
if (nickname && nickname !== displayName) {
+
const nickSpan = document.createElement('span');
+
nickSpan.className = 'p-nickname visually-hidden';
+
nickSpan.textContent = nickname;
+
nameElement.appendChild(nickSpan);
+
}
+
+
infoContainer.appendChild(nameElement);
+
+
// Job title and organization
+
if (jobTitle || org) {
+
const titleElement = document.createElement('div');
+
titleElement.className = 'author-title';
+
+
if (jobTitle) {
+
const jobSpan = document.createElement('span');
+
jobSpan.className = 'p-job-title';
+
jobSpan.textContent = jobTitle;
+
titleElement.appendChild(jobSpan);
+
+
if (org) {
+
titleElement.appendChild(document.createTextNode(' at '));
+
}
+
}
+
+
if (org) {
+
const orgSpan = document.createElement('span');
+
orgSpan.className = 'p-org';
+
orgSpan.textContent = org;
+
titleElement.appendChild(orgSpan);
+
}
+
+
infoContainer.appendChild(titleElement);
+
}
+
+
// Bio/note
+
if (bio) {
+
const bioElement = document.createElement('div');
+
bioElement.className = 'p-note author-bio';
+
bioElement.textContent = bio;
+
infoContainer.appendChild(bioElement);
+
}
+
+
// Social links
+
const socialContainer = document.createElement('div');
+
socialContainer.className = 'author-social';
+
let hasSocial = false;
+
+
if (email) {
+
const emailLink = document.createElement('a');
+
emailLink.className = 'u-email social-link';
+
emailLink.href = `mailto:${email}`;
+
emailLink.textContent = 'Email';
+
emailLink.setAttribute('aria-label', `Email ${displayName}`);
+
socialContainer.appendChild(emailLink);
+
hasSocial = true;
+
}
+
+
if (github) {
+
const githubLink = document.createElement('a');
+
githubLink.className = 'u-url social-link';
+
githubLink.href = `https://github.com/${github}`;
+
githubLink.textContent = 'GitHub';
+
githubLink.setAttribute('aria-label', `${displayName} on GitHub`);
+
githubLink.rel = 'me';
+
socialContainer.appendChild(githubLink);
+
hasSocial = true;
+
}
+
+
if (mastodon) {
+
const mastodonLink = document.createElement('a');
+
mastodonLink.className = 'u-url social-link';
+
mastodonLink.href = mastodon;
+
mastodonLink.textContent = 'Mastodon';
+
mastodonLink.setAttribute('aria-label', `${displayName} on Mastodon`);
+
mastodonLink.rel = 'me';
+
socialContainer.appendChild(mastodonLink);
+
hasSocial = true;
+
}
+
+
if (twitter) {
+
const twitterLink = document.createElement('a');
+
twitterLink.className = 'u-url social-link';
+
twitterLink.href = `https://twitter.com/${twitter}`;
+
twitterLink.textContent = 'Twitter';
+
twitterLink.setAttribute('aria-label', `${displayName} on Twitter`);
+
twitterLink.rel = 'me';
+
socialContainer.appendChild(twitterLink);
+
hasSocial = true;
+
}
+
+
if (website && !this.shouldLinkProfile) {
+
const websiteLink = document.createElement('a');
+
websiteLink.className = 'u-url social-link';
+
websiteLink.href = website;
+
websiteLink.textContent = 'Website';
+
websiteLink.setAttribute('aria-label', `${displayName}'s website`);
+
socialContainer.appendChild(websiteLink);
+
hasSocial = true;
+
}
+
+
if (hasSocial) {
+
infoContainer.appendChild(socialContainer);
+
}
+
+
hcard.appendChild(infoContainer);
+
return hcard;
+
}
+
+
render() {
+
const styles = `
+
<style>
+
:host {
+
display: var(--bushel-author-display, inline-block);
+
font-family: var(--bushel-author-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
+
}
+
+
.h-card {
+
display: flex;
+
align-items: var(--bushel-author-align, flex-start);
+
gap: var(--bushel-author-gap, 0.75rem);
+
padding: var(--bushel-author-padding, 0);
+
background: var(--bushel-author-background, transparent);
+
border: var(--bushel-author-border, none);
+
border-radius: var(--bushel-author-border-radius, 0);
+
}
+
+
:host([display-mode="card"]) .h-card {
+
flex-direction: column;
+
text-align: center;
+
padding: var(--bushel-author-card-padding, 1rem);
+
background: var(--bushel-author-card-background, #f8f9fa);
+
border: var(--bushel-author-card-border, 1px solid #e9ecef);
+
border-radius: var(--bushel-author-card-border-radius, 0.5rem);
+
}
+
+
:host([display-mode="compact"]) .h-card {
+
gap: var(--bushel-author-compact-gap, 0.5rem);
+
}
+
+
.author-photo {
+
width: var(--bushel-author-photo-size, 2.5rem);
+
height: var(--bushel-author-photo-size, 2.5rem);
+
border-radius: var(--bushel-author-photo-radius, 50%);
+
object-fit: cover;
+
flex-shrink: 0;
+
}
+
+
:host([display-mode="card"]) .author-photo {
+
width: var(--bushel-author-card-photo-size, 4rem);
+
height: var(--bushel-author-card-photo-size, 4rem);
+
}
+
+
.author-info {
+
display: flex;
+
flex-direction: column;
+
gap: var(--bushel-author-info-gap, 0.25rem);
+
min-width: 0;
+
}
+
+
.author-name {
+
font-weight: var(--bushel-author-name-weight, 600);
+
color: var(--bushel-author-name-color, inherit);
+
text-decoration: none;
+
font-size: var(--bushel-author-name-size, 1rem);
+
}
+
+
.author-name:hover {
+
text-decoration: underline;
+
}
+
+
.author-title {
+
font-size: var(--bushel-author-title-size, 0.875rem);
+
color: var(--bushel-author-title-color, #666);
+
}
+
+
.author-bio {
+
font-size: var(--bushel-author-bio-size, 0.875rem);
+
color: var(--bushel-author-bio-color, #555);
+
line-height: 1.4;
+
}
+
+
.author-social {
+
display: flex;
+
gap: var(--bushel-author-social-gap, 0.75rem);
+
margin-top: var(--bushel-author-social-margin, 0.25rem);
+
}
+
+
:host([display-mode="card"]) .author-social {
+
justify-content: center;
+
}
+
+
:host([display-mode="compact"]) .author-social {
+
gap: var(--bushel-author-compact-social-gap, 0.5rem);
+
}
+
+
.social-link {
+
font-size: var(--bushel-author-social-size, 0.8125rem);
+
color: var(--bushel-author-social-color, #007bff);
+
text-decoration: none;
+
padding: var(--bushel-author-social-padding, 0.125rem 0.25rem);
+
border-radius: var(--bushel-author-social-radius, 0.25rem);
+
}
+
+
.social-link:hover {
+
background: var(--bushel-author-social-hover-bg, rgba(0, 123, 255, 0.1));
+
text-decoration: none;
+
}
+
+
.visually-hidden {
+
position: absolute !important;
+
width: 1px !important;
+
height: 1px !important;
+
padding: 0 !important;
+
margin: -1px !important;
+
overflow: hidden !important;
+
clip: rect(0, 0, 0, 0) !important;
+
white-space: nowrap !important;
+
border: 0 !important;
+
}
+
+
@media (max-width: 600px) {
+
:host([display-mode="card"]) .h-card {
+
padding: var(--bushel-author-mobile-padding, 0.75rem);
+
}
+
+
.author-social {
+
flex-wrap: wrap;
+
}
+
}
+
</style>
+
`;
+
+
this.shadowRoot.innerHTML = styles + this.buildHCard().outerHTML;
+
}
+
}
+
+
customElements.define('bushel-author', BushelAuthor);
+478
webcomp/index.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>Bushel Author Components - Real World Examples</title>
+
<script src="bushel-author.js"></script>
+
<style>
+
* {
+
margin: 0;
+
padding: 0;
+
box-sizing: border-box;
+
}
+
+
body {
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+
line-height: 1.6;
+
color: #333;
+
background: #fafafa;
+
}
+
+
header {
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+
color: white;
+
padding: 3rem 1rem;
+
text-align: center;
+
}
+
+
header h1 {
+
font-size: 2.5rem;
+
margin-bottom: 0.5rem;
+
}
+
+
header p {
+
font-size: 1.1rem;
+
opacity: 0.9;
+
max-width: 600px;
+
margin: 0 auto;
+
}
+
+
main {
+
max-width: 1200px;
+
margin: 0 auto;
+
padding: 0 1rem;
+
}
+
+
.section {
+
background: white;
+
margin: 3rem 0;
+
padding: 2.5rem;
+
border-radius: 0.75rem;
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+
}
+
+
.section h2 {
+
color: #495057;
+
margin-bottom: 1.5rem;
+
font-size: 1.75rem;
+
}
+
+
.section p {
+
margin-bottom: 1.5rem;
+
color: #666;
+
}
+
+
/* Blog post layout */
+
.blog-post {
+
border-left: 4px solid #667eea;
+
padding-left: 1.5rem;
+
margin: 2rem 0;
+
}
+
+
.blog-post h3 {
+
color: #495057;
+
margin-bottom: 0.5rem;
+
}
+
+
.blog-post .meta {
+
display: flex;
+
align-items: center;
+
gap: 1rem;
+
margin: 1rem 0;
+
padding: 1rem 0;
+
border-top: 1px solid #e9ecef;
+
border-bottom: 1px solid #e9ecef;
+
}
+
+
.blog-post .content {
+
color: #555;
+
font-size: 1.1rem;
+
}
+
+
/* Team grid */
+
.team-grid {
+
display: grid;
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+
gap: 2rem;
+
margin-top: 2rem;
+
}
+
+
.team-member {
+
background: #f8f9fa;
+
padding: 1.5rem;
+
border-radius: 0.5rem;
+
text-align: center;
+
}
+
+
/* Author bylines */
+
.article-byline {
+
display: flex;
+
align-items: center;
+
gap: 1rem;
+
padding: 1rem;
+
background: #f8f9fa;
+
border-radius: 0.5rem;
+
margin: 1rem 0;
+
}
+
+
.article-content {
+
background: white;
+
padding: 2rem;
+
border-radius: 0.5rem;
+
border: 1px solid #e9ecef;
+
margin-top: 1rem;
+
}
+
+
/* Comment thread */
+
.comment-thread {
+
background: #f8f9fa;
+
border-radius: 0.5rem;
+
padding: 1.5rem;
+
margin: 1rem 0;
+
}
+
+
.comment {
+
background: white;
+
border-radius: 0.5rem;
+
padding: 1rem;
+
margin: 1rem 0;
+
border-left: 3px solid #667eea;
+
}
+
+
.comment-header {
+
display: flex;
+
align-items: center;
+
gap: 1rem;
+
margin-bottom: 0.75rem;
+
}
+
+
.comment-body {
+
color: #555;
+
}
+
+
/* Footer */
+
footer {
+
background: #343a40;
+
color: white;
+
text-align: center;
+
padding: 2rem 1rem;
+
margin-top: 4rem;
+
}
+
+
/* Custom theme examples */
+
.theme-dark {
+
--bushel-author-background: #2d3748;
+
--bushel-author-name-color: #e2e8f0;
+
--bushel-author-title-color: #a0aec0;
+
--bushel-author-social-color: #4299e1;
+
--bushel-author-padding: 1rem;
+
--bushel-author-border-radius: 0.5rem;
+
}
+
+
.theme-minimal {
+
--bushel-author-photo-size: 1.5rem;
+
--bushel-author-gap: 0.5rem;
+
--bushel-author-name-size: 0.875rem;
+
--bushel-author-social-size: 0.75rem;
+
}
+
+
.theme-colorful {
+
--bushel-author-background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
+
--bushel-author-name-color: white;
+
--bushel-author-title-color: rgba(255,255,255,0.8);
+
--bushel-author-social-color: white;
+
--bushel-author-padding: 1rem;
+
--bushel-author-border-radius: 1rem;
+
}
+
+
@media (max-width: 768px) {
+
header h1 {
+
font-size: 2rem;
+
}
+
+
.section {
+
margin: 2rem 0;
+
padding: 1.5rem;
+
}
+
+
.blog-post .meta {
+
flex-direction: column;
+
align-items: flex-start;
+
gap: 0.5rem;
+
}
+
+
.article-byline {
+
flex-direction: column;
+
align-items: flex-start;
+
text-align: left;
+
}
+
}
+
</style>
+
</head>
+
<body>
+
<header>
+
<h1>Bushel Author Components</h1>
+
<p>Semantic, accessible author information with microformat support for the modern web</p>
+
</header>
+
+
<main>
+
<section class="section">
+
<h2>Blog Post Authors</h2>
+
<p>Perfect for article bylines and author attribution in blog posts and news articles.</p>
+
+
<article class="blog-post">
+
<h3>Building Accessible Web Components in 2025</h3>
+
<div class="meta">
+
<bushel-author
+
name="Sarah Chen"
+
job-title="Senior Frontend Developer"
+
org="Mozilla"
+
photo="https://picsum.photos/200/200?random=1"
+
github="sarahc"
+
mastodon="https://mastodon.social/@sarahc"
+
website="https://sarahchen.dev"
+
link-profile>
+
</bushel-author>
+
<span style="color: #666;">Published March 15, 2025</span>
+
</div>
+
<div class="content">
+
<p>Web components have revolutionized how we build reusable UI elements, but accessibility
+
often gets overlooked. This comprehensive guide covers everything you need to know about
+
creating web components that work for everyone...</p>
+
</div>
+
</article>
+
+
<article class="blog-post">
+
<h3>The Future of Distributed Social Networks</h3>
+
<div class="meta">
+
<bushel-author
+
name="Dr. Marcus Thompson"
+
job-title="Research Scientist"
+
org="Decentralized Web Foundation"
+
photo="https://picsum.photos/200/200?random=2"
+
email="marcus@dwf.org"
+
github="marcust"
+
bio="Researcher focused on decentralized protocols and social networks">
+
</bushel-author>
+
<span style="color: #666;">Published March 12, 2025</span>
+
</div>
+
<div class="content">
+
<p>As we move beyond centralized social media platforms, new protocols and technologies
+
are emerging that put users back in control of their data and social connections...</p>
+
</div>
+
</article>
+
</section>
+
+
<section class="section">
+
<h2>Team Pages</h2>
+
<p>Showcase your team members with rich profile cards that include all their professional information.</p>
+
+
<div class="team-grid">
+
<div class="team-member">
+
<bushel-author
+
display-mode="card"
+
name="Emily Rodriguez"
+
job-title="Lead UX Designer"
+
org="DesignCorp"
+
photo="https://picsum.photos/300/300?random=3"
+
bio="Creating intuitive experiences that delight users and drive business results."
+
website="https://emilyrodriguez.design"
+
github="emilyux"
+
twitter="emilyux_design"
+
link-profile>
+
</bushel-author>
+
</div>
+
+
<div class="team-member">
+
<bushel-author
+
display-mode="card"
+
name="Alex Kim"
+
job-title="DevOps Engineer"
+
org="DesignCorp"
+
photo="https://picsum.photos/300/300?random=4"
+
bio="Building reliable infrastructure and deployment pipelines for modern applications."
+
github="alexkimops"
+
mastodon="https://fosstodon.org/@alexkim"
+
website="https://alexkim.dev">
+
</bushel-author>
+
</div>
+
+
<div class="team-member">
+
<bushel-author
+
display-mode="card"
+
given-name="Isabella"
+
family-name="Martinez"
+
nickname="Izzy"
+
job-title="Product Manager"
+
org="DesignCorp"
+
photo="https://picsum.photos/300/300?random=5"
+
bio="Bridging the gap between user needs and technical possibilities."
+
email="izzy@designcorp.com"
+
github="izzymartinez"
+
website="https://izzy.pm">
+
</bushel-author>
+
</div>
+
</div>
+
</section>
+
+
<section class="section">
+
<h2>Article Collaboration</h2>
+
<p>Show multiple authors and contributors for collaborative content.</p>
+
+
<div class="article-byline">
+
<strong>Authors:</strong>
+
<bushel-author
+
display-mode="compact"
+
name="David Park"
+
job-title="Security Researcher"
+
photo="https://picsum.photos/200/200?random=6"
+
github="davidpark">
+
</bushel-author>
+
<bushel-author
+
display-mode="compact"
+
name="Lisa Wang"
+
job-title="Cryptography Expert"
+
photo="https://picsum.photos/200/200?random=7"
+
github="lisawang">
+
</bushel-author>
+
</div>
+
+
<div class="article-content">
+
<h3>Zero-Knowledge Proofs in Web Applications</h3>
+
<p>This collaborative research paper explores the practical implementation of zero-knowledge
+
proofs in modern web applications, covering both theoretical foundations and real-world use cases...</p>
+
</div>
+
</section>
+
+
<section class="section">
+
<h2>Comment Threads</h2>
+
<p>Perfect for showing comment authors in discussion threads and forums.</p>
+
+
<div class="comment-thread">
+
<h3>Discussion: "Best Practices for Web Component Testing"</h3>
+
+
<div class="comment">
+
<div class="comment-header">
+
<bushel-author
+
display-mode="compact"
+
name="Rachel Green"
+
photo="https://picsum.photos/200/200?random=8"
+
github="rachelgreen">
+
</bushel-author>
+
<span style="color: #666; font-size: 0.875rem;">2 hours ago</span>
+
</div>
+
<div class="comment-body">
+
Great article! I've been struggling with testing shadow DOM interactions.
+
The approach you outlined for using testing-library with web components is exactly what I needed.
+
</div>
+
</div>
+
+
<div class="comment">
+
<div class="comment-header">
+
<bushel-author
+
display-mode="compact"
+
name="James Wilson"
+
job-title="Test Automation Engineer"
+
photo="https://picsum.photos/200/200?random=9"
+
github="jameswilson">
+
</bushel-author>
+
<span style="color: #666; font-size: 0.875rem;">1 hour ago</span>
+
</div>
+
<div class="comment-body">
+
@rachelgreen Have you tried the new Web Component Testing utilities in Playwright?
+
They've made shadow DOM testing much more straightforward.
+
</div>
+
</div>
+
+
<div class="comment">
+
<div class="comment-header">
+
<bushel-author
+
display-mode="compact"
+
name="Priya Patel"
+
photo="https://picsum.photos/200/200?random=10"
+
mastodon="https://mastodon.social/@priya"
+
github="priyapatel">
+
</bushel-author>
+
<span style="color: #666; font-size: 0.875rem;">30 minutes ago</span>
+
</div>
+
<div class="comment-body">
+
Thanks for mentioning Playwright! I'll definitely check that out.
+
We've been using Cypress but shadow DOM support has been limited.
+
</div>
+
</div>
+
</div>
+
</section>
+
+
<section class="section">
+
<h2>Themed Variants</h2>
+
<p>Customize the appearance using CSS custom properties to match your brand.</p>
+
+
<div style="display: grid; gap: 2rem; margin-top: 2rem;">
+
<div style="background: #1a202c; padding: 2rem; border-radius: 0.5rem;">
+
<h3 style="color: white; margin-bottom: 1rem;">Dark Theme</h3>
+
<bushel-author
+
class="theme-dark"
+
name="Morgan Foster"
+
job-title="Night Shift Developer"
+
photo="https://picsum.photos/200/200?random=11"
+
github="morganf"
+
website="https://morganfoster.dev"
+
link-profile>
+
</bushel-author>
+
</div>
+
+
<div style="padding: 1rem; border: 2px dashed #ddd; border-radius: 0.5rem;">
+
<h3 style="margin-bottom: 1rem;">Minimal Theme</h3>
+
<bushel-author
+
class="theme-minimal"
+
name="Taylor Swift"
+
job-title="Minimalist Designer"
+
photo="https://picsum.photos/200/200?random=12"
+
github="taylorswift">
+
</bushel-author>
+
</div>
+
+
<div style="padding: 2rem; background: #f0f8ff; border-radius: 0.5rem;">
+
<h3 style="margin-bottom: 1rem;">Colorful Theme</h3>
+
<bushel-author
+
class="theme-colorful"
+
name="Jordan Rivers"
+
job-title="Creative Developer"
+
photo="https://picsum.photos/200/200?random=13"
+
github="jordanrivers"
+
mastodon="https://mastodon.art/@jordan"
+
website="https://jordanrivers.art"
+
link-profile>
+
</bushel-author>
+
</div>
+
</div>
+
</section>
+
+
<section class="section">
+
<h2>Technical Implementation</h2>
+
<p>Each component automatically generates semantic HTML with proper microformat markup:</p>
+
<ul style="list-style: disc; padding-left: 2rem; color: #666;">
+
<li><strong>h-card microformat</strong> - Machine-readable contact information</li>
+
<li><strong>Semantic HTML</strong> - Proper heading hierarchy and landmark elements</li>
+
<li><strong>Accessibility</strong> - ARIA labels, keyboard navigation, screen reader support</li>
+
<li><strong>Progressive Enhancement</strong> - Works without JavaScript, enhanced with it</li>
+
<li><strong>Responsive Design</strong> - Adapts to all screen sizes and devices</li>
+
<li><strong>Social Verification</strong> - Includes rel="me" links for identity verification</li>
+
</ul>
+
</section>
+
</main>
+
+
<footer>
+
<p>Built with ❤️ using Bushel Web Components</p>
+
<div style="margin-top: 1rem;">
+
<bushel-author
+
display-mode="compact"
+
name="Bushel Team"
+
website="https://github.com/avsm/bushel"
+
github="avsm/bushel">
+
</bushel-author>
+
</div>
+
</footer>
+
</body>
+
</html>