···
+
class BushelAuthor extends HTMLElement {
+
this.attachShadow({ mode: 'open' });
+
static get observedAttributes() {
+
'name', 'given-name', 'family-name', 'nickname',
+
'email', 'website', 'github', 'mastodon', 'twitter',
+
'photo', 'bio', 'org', 'job-title',
+
'display-mode', 'link-profile'
+
attributeChangedCallback() {
+
return this.getAttribute('display-mode') || 'inline';
+
get shouldLinkProfile() {
+
return this.hasAttribute('link-profile');
+
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';
+
const img = document.createElement('img');
+
img.className = 'u-photo author-photo';
+
img.alt = `Photo of ${displayName}`;
+
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
+
const givenSpan = document.createElement('span');
+
givenSpan.className = 'p-given-name visually-hidden';
+
givenSpan.textContent = givenName;
+
nameElement.appendChild(givenSpan);
+
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
+
const titleElement = document.createElement('div');
+
titleElement.className = 'author-title';
+
const jobSpan = document.createElement('span');
+
jobSpan.className = 'p-job-title';
+
jobSpan.textContent = jobTitle;
+
titleElement.appendChild(jobSpan);
+
titleElement.appendChild(document.createTextNode(' at '));
+
const orgSpan = document.createElement('span');
+
orgSpan.className = 'p-org';
+
orgSpan.textContent = org;
+
titleElement.appendChild(orgSpan);
+
infoContainer.appendChild(titleElement);
+
const bioElement = document.createElement('div');
+
bioElement.className = 'p-note author-bio';
+
bioElement.textContent = bio;
+
infoContainer.appendChild(bioElement);
+
const socialContainer = document.createElement('div');
+
socialContainer.className = 'author-social';
+
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);
+
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`);
+
socialContainer.appendChild(githubLink);
+
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);
+
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);
+
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);
+
infoContainer.appendChild(socialContainer);
+
hcard.appendChild(infoContainer);
+
display: var(--bushel-author-display, inline-block);
+
font-family: var(--bushel-author-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
+
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;
+
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);
+
width: var(--bushel-author-photo-size, 2.5rem);
+
height: var(--bushel-author-photo-size, 2.5rem);
+
border-radius: var(--bushel-author-photo-radius, 50%);
+
:host([display-mode="card"]) .author-photo {
+
width: var(--bushel-author-card-photo-size, 4rem);
+
height: var(--bushel-author-card-photo-size, 4rem);
+
flex-direction: column;
+
gap: var(--bushel-author-info-gap, 0.25rem);
+
font-weight: var(--bushel-author-name-weight, 600);
+
color: var(--bushel-author-name-color, inherit);
+
font-size: var(--bushel-author-name-size, 1rem);
+
text-decoration: underline;
+
font-size: var(--bushel-author-title-size, 0.875rem);
+
color: var(--bushel-author-title-color, #666);
+
font-size: var(--bushel-author-bio-size, 0.875rem);
+
color: var(--bushel-author-bio-color, #555);
+
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);
+
font-size: var(--bushel-author-social-size, 0.8125rem);
+
color: var(--bushel-author-social-color, #007bff);
+
padding: var(--bushel-author-social-padding, 0.125rem 0.25rem);
+
border-radius: var(--bushel-author-social-radius, 0.25rem);
+
background: var(--bushel-author-social-hover-bg, rgba(0, 123, 255, 0.1));
+
position: absolute !important;
+
height: 1px !important;
+
margin: -1px !important;
+
overflow: hidden !important;
+
clip: rect(0, 0, 0, 0) !important;
+
white-space: nowrap !important;
+
@media (max-width: 600px) {
+
:host([display-mode="card"]) .h-card {
+
padding: var(--bushel-author-mobile-padding, 0.75rem);
+
this.shadowRoot.innerHTML = styles + this.buildHCard().outerHTML;
+
customElements.define('bushel-author', BushelAuthor);