feat: add profile header skeleton component

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Changed files
+99
src
components
+99
src/components/molecules/grain-profile-header-skeleton.js
··· 1 + import { LitElement, html, css } from 'lit'; 2 + 3 + export class GrainProfileHeaderSkeleton extends LitElement { 4 + static styles = css` 5 + :host { 6 + display: block; 7 + padding: var(--space-md) var(--space-sm); 8 + } 9 + @media (min-width: 600px) { 10 + :host { 11 + padding-left: 0; 12 + padding-right: 0; 13 + } 14 + } 15 + .top-row { 16 + display: flex; 17 + align-items: flex-start; 18 + gap: var(--space-md); 19 + margin-bottom: var(--space-sm); 20 + } 21 + .right-column { 22 + flex: 1; 23 + min-width: 0; 24 + padding-top: var(--space-xs); 25 + } 26 + .placeholder { 27 + background: var(--color-bg-elevated); 28 + border-radius: 4px; 29 + animation: pulse 1.5s ease-in-out infinite; 30 + } 31 + .avatar { 32 + width: var(--avatar-size-lg, 80px); 33 + height: var(--avatar-size-lg, 80px); 34 + border-radius: 50%; 35 + flex-shrink: 0; 36 + } 37 + .handle { 38 + width: 120px; 39 + height: 20px; 40 + margin-bottom: var(--space-xs); 41 + } 42 + .name { 43 + width: 80px; 44 + height: 14px; 45 + margin-bottom: var(--space-xs); 46 + } 47 + .stats { 48 + display: flex; 49 + gap: var(--space-sm); 50 + margin-bottom: var(--space-xs); 51 + } 52 + .stat { 53 + width: 70px; 54 + height: 14px; 55 + } 56 + .bio-line { 57 + height: 14px; 58 + margin-top: var(--space-xs); 59 + } 60 + .bio-line:first-of-type { 61 + width: 100%; 62 + } 63 + .bio-line:last-of-type { 64 + width: 60%; 65 + } 66 + .button { 67 + width: 100%; 68 + height: 40px; 69 + border-radius: 8px; 70 + margin-top: var(--space-sm); 71 + } 72 + @keyframes pulse { 73 + 0%, 100% { opacity: 0.4; } 74 + 50% { opacity: 1; } 75 + } 76 + `; 77 + 78 + render() { 79 + return html` 80 + <div class="top-row"> 81 + <div class="placeholder avatar"></div> 82 + <div class="right-column"> 83 + <div class="placeholder handle"></div> 84 + <div class="placeholder name"></div> 85 + <div class="stats"> 86 + <div class="placeholder stat"></div> 87 + <div class="placeholder stat"></div> 88 + <div class="placeholder stat"></div> 89 + </div> 90 + <div class="placeholder bio-line"></div> 91 + <div class="placeholder bio-line"></div> 92 + </div> 93 + </div> 94 + <div class="placeholder button"></div> 95 + `; 96 + } 97 + } 98 + 99 + customElements.define('grain-profile-header-skeleton', GrainProfileHeaderSkeleton);