My agentic slop goes here. Not intended for anyone else!
at jsont 10 kB view raw
1class BushelAuthor extends HTMLElement { 2 constructor() { 3 super(); 4 this.attachShadow({ mode: 'open' }); 5 } 6 7 static get observedAttributes() { 8 return [ 9 'name', 'given-name', 'family-name', 'nickname', 10 'email', 'website', 'github', 'mastodon', 'twitter', 11 'photo', 'bio', 'org', 'job-title', 12 'display-mode', 'link-profile' 13 ]; 14 } 15 16 connectedCallback() { 17 this.render(); 18 } 19 20 attributeChangedCallback() { 21 this.render(); 22 } 23 24 get displayMode() { 25 return this.getAttribute('display-mode') || 'inline'; 26 } 27 28 get shouldLinkProfile() { 29 return this.hasAttribute('link-profile'); 30 } 31 32 buildHCard() { 33 const name = this.getAttribute('name'); 34 const givenName = this.getAttribute('given-name'); 35 const familyName = this.getAttribute('family-name'); 36 const nickname = this.getAttribute('nickname'); 37 const email = this.getAttribute('email'); 38 const website = this.getAttribute('website'); 39 const github = this.getAttribute('github'); 40 const mastodon = this.getAttribute('mastodon'); 41 const twitter = this.getAttribute('twitter'); 42 const photo = this.getAttribute('photo'); 43 const bio = this.getAttribute('bio'); 44 const org = this.getAttribute('org'); 45 const jobTitle = this.getAttribute('job-title'); 46 47 const displayName = name || `${givenName || ''} ${familyName || ''}`.trim() || nickname; 48 49 const hcard = document.createElement('div'); 50 hcard.className = 'h-card bushel-author-card'; 51 52 // Photo 53 if (photo) { 54 const img = document.createElement('img'); 55 img.className = 'u-photo author-photo'; 56 img.src = photo; 57 img.alt = `Photo of ${displayName}`; 58 img.loading = 'lazy'; 59 hcard.appendChild(img); 60 } 61 62 const infoContainer = document.createElement('div'); 63 infoContainer.className = 'author-info'; 64 65 // Name (with optional link) 66 const nameElement = document.createElement(this.shouldLinkProfile && website ? 'a' : 'span'); 67 nameElement.className = 'p-name author-name'; 68 nameElement.textContent = displayName; 69 70 if (this.shouldLinkProfile && website) { 71 nameElement.href = website; 72 nameElement.className += ' u-url'; 73 } 74 75 // Hidden structured name parts 76 if (givenName) { 77 const givenSpan = document.createElement('span'); 78 givenSpan.className = 'p-given-name visually-hidden'; 79 givenSpan.textContent = givenName; 80 nameElement.appendChild(givenSpan); 81 } 82 83 if (familyName) { 84 const familySpan = document.createElement('span'); 85 familySpan.className = 'p-family-name visually-hidden'; 86 familySpan.textContent = familyName; 87 nameElement.appendChild(familySpan); 88 } 89 90 if (nickname && nickname !== displayName) { 91 const nickSpan = document.createElement('span'); 92 nickSpan.className = 'p-nickname visually-hidden'; 93 nickSpan.textContent = nickname; 94 nameElement.appendChild(nickSpan); 95 } 96 97 infoContainer.appendChild(nameElement); 98 99 // Job title and organization 100 if (jobTitle || org) { 101 const titleElement = document.createElement('div'); 102 titleElement.className = 'author-title'; 103 104 if (jobTitle) { 105 const jobSpan = document.createElement('span'); 106 jobSpan.className = 'p-job-title'; 107 jobSpan.textContent = jobTitle; 108 titleElement.appendChild(jobSpan); 109 110 if (org) { 111 titleElement.appendChild(document.createTextNode(' at ')); 112 } 113 } 114 115 if (org) { 116 const orgSpan = document.createElement('span'); 117 orgSpan.className = 'p-org'; 118 orgSpan.textContent = org; 119 titleElement.appendChild(orgSpan); 120 } 121 122 infoContainer.appendChild(titleElement); 123 } 124 125 // Bio/note 126 if (bio) { 127 const bioElement = document.createElement('div'); 128 bioElement.className = 'p-note author-bio'; 129 bioElement.textContent = bio; 130 infoContainer.appendChild(bioElement); 131 } 132 133 // Social links 134 const socialContainer = document.createElement('div'); 135 socialContainer.className = 'author-social'; 136 let hasSocial = false; 137 138 if (email) { 139 const emailLink = document.createElement('a'); 140 emailLink.className = 'u-email social-link'; 141 emailLink.href = `mailto:${email}`; 142 emailLink.textContent = 'Email'; 143 emailLink.setAttribute('aria-label', `Email ${displayName}`); 144 socialContainer.appendChild(emailLink); 145 hasSocial = true; 146 } 147 148 if (github) { 149 const githubLink = document.createElement('a'); 150 githubLink.className = 'u-url social-link'; 151 githubLink.href = `https://github.com/${github}`; 152 githubLink.textContent = 'GitHub'; 153 githubLink.setAttribute('aria-label', `${displayName} on GitHub`); 154 githubLink.rel = 'me'; 155 socialContainer.appendChild(githubLink); 156 hasSocial = true; 157 } 158 159 if (mastodon) { 160 const mastodonLink = document.createElement('a'); 161 mastodonLink.className = 'u-url social-link'; 162 mastodonLink.href = mastodon; 163 mastodonLink.textContent = 'Mastodon'; 164 mastodonLink.setAttribute('aria-label', `${displayName} on Mastodon`); 165 mastodonLink.rel = 'me'; 166 socialContainer.appendChild(mastodonLink); 167 hasSocial = true; 168 } 169 170 if (twitter) { 171 const twitterLink = document.createElement('a'); 172 twitterLink.className = 'u-url social-link'; 173 twitterLink.href = `https://twitter.com/${twitter}`; 174 twitterLink.textContent = 'Twitter'; 175 twitterLink.setAttribute('aria-label', `${displayName} on Twitter`); 176 twitterLink.rel = 'me'; 177 socialContainer.appendChild(twitterLink); 178 hasSocial = true; 179 } 180 181 if (website && !this.shouldLinkProfile) { 182 const websiteLink = document.createElement('a'); 183 websiteLink.className = 'u-url social-link'; 184 websiteLink.href = website; 185 websiteLink.textContent = 'Website'; 186 websiteLink.setAttribute('aria-label', `${displayName}'s website`); 187 socialContainer.appendChild(websiteLink); 188 hasSocial = true; 189 } 190 191 if (hasSocial) { 192 infoContainer.appendChild(socialContainer); 193 } 194 195 hcard.appendChild(infoContainer); 196 return hcard; 197 } 198 199 render() { 200 const styles = ` 201 <style> 202 :host { 203 display: var(--bushel-author-display, inline-block); 204 font-family: var(--bushel-author-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); 205 } 206 207 .h-card { 208 display: flex; 209 align-items: var(--bushel-author-align, flex-start); 210 gap: var(--bushel-author-gap, 0.75rem); 211 padding: var(--bushel-author-padding, 0); 212 background: var(--bushel-author-background, transparent); 213 border: var(--bushel-author-border, none); 214 border-radius: var(--bushel-author-border-radius, 0); 215 } 216 217 :host([display-mode="card"]) .h-card { 218 flex-direction: column; 219 text-align: center; 220 padding: var(--bushel-author-card-padding, 1rem); 221 background: var(--bushel-author-card-background, #f8f9fa); 222 border: var(--bushel-author-card-border, 1px solid #e9ecef); 223 border-radius: var(--bushel-author-card-border-radius, 0.5rem); 224 } 225 226 :host([display-mode="compact"]) .h-card { 227 gap: var(--bushel-author-compact-gap, 0.5rem); 228 } 229 230 .author-photo { 231 width: var(--bushel-author-photo-size, 2.5rem); 232 height: var(--bushel-author-photo-size, 2.5rem); 233 border-radius: var(--bushel-author-photo-radius, 50%); 234 object-fit: cover; 235 flex-shrink: 0; 236 } 237 238 :host([display-mode="card"]) .author-photo { 239 width: var(--bushel-author-card-photo-size, 4rem); 240 height: var(--bushel-author-card-photo-size, 4rem); 241 } 242 243 .author-info { 244 display: flex; 245 flex-direction: column; 246 gap: var(--bushel-author-info-gap, 0.25rem); 247 min-width: 0; 248 } 249 250 .author-name { 251 font-weight: var(--bushel-author-name-weight, 600); 252 color: var(--bushel-author-name-color, inherit); 253 text-decoration: none; 254 font-size: var(--bushel-author-name-size, 1rem); 255 } 256 257 .author-name:hover { 258 text-decoration: underline; 259 } 260 261 .author-title { 262 font-size: var(--bushel-author-title-size, 0.875rem); 263 color: var(--bushel-author-title-color, #666); 264 } 265 266 .author-bio { 267 font-size: var(--bushel-author-bio-size, 0.875rem); 268 color: var(--bushel-author-bio-color, #555); 269 line-height: 1.4; 270 } 271 272 .author-social { 273 display: flex; 274 gap: var(--bushel-author-social-gap, 0.75rem); 275 margin-top: var(--bushel-author-social-margin, 0.25rem); 276 } 277 278 :host([display-mode="card"]) .author-social { 279 justify-content: center; 280 } 281 282 :host([display-mode="compact"]) .author-social { 283 gap: var(--bushel-author-compact-social-gap, 0.5rem); 284 } 285 286 .social-link { 287 font-size: var(--bushel-author-social-size, 0.8125rem); 288 color: var(--bushel-author-social-color, #007bff); 289 text-decoration: none; 290 padding: var(--bushel-author-social-padding, 0.125rem 0.25rem); 291 border-radius: var(--bushel-author-social-radius, 0.25rem); 292 } 293 294 .social-link:hover { 295 background: var(--bushel-author-social-hover-bg, rgba(0, 123, 255, 0.1)); 296 text-decoration: none; 297 } 298 299 .visually-hidden { 300 position: absolute !important; 301 width: 1px !important; 302 height: 1px !important; 303 padding: 0 !important; 304 margin: -1px !important; 305 overflow: hidden !important; 306 clip: rect(0, 0, 0, 0) !important; 307 white-space: nowrap !important; 308 border: 0 !important; 309 } 310 311 @media (max-width: 600px) { 312 :host([display-mode="card"]) .h-card { 313 padding: var(--bushel-author-mobile-padding, 0.75rem); 314 } 315 316 .author-social { 317 flex-wrap: wrap; 318 } 319 } 320 </style> 321 `; 322 323 this.shadowRoot.innerHTML = styles + this.buildHCard().outerHTML; 324 } 325} 326 327customElements.define('bushel-author', BushelAuthor);