SlantTitle.astro (3180B)
1 --- 2 /** 3 * The slanted page banner — ported from daemon-sec.xyz's SlantTitle. A slab 4 * skewed skewX(-11deg) in the section's accent, the title counter-skewed so 5 * the letterforms stay upright, and a misregistered ink edge nudged up-left so 6 * the pair reads as two printing plates rather than one bordered box. 7 * 8 * The accent is a theme-aware Rosé Pine semantic token, so the slab keeps its 9 * role in both Dawn and Night. The title decodes on view via app.ts's 10 * initUnscramble() (data-unscramble), and the edge registers in with a CSS 11 * settle that collapses to nothing under prefers-reduced-motion. 12 */ 13 type Tone = 'love' | 'iris' | 'pine' | 'foam' | 'gold' | 'rose'; 14 interface Props { 15 eyebrow: string; 16 title: string; 17 intro?: string; 18 tone?: Tone; 19 } 20 const { eyebrow, title, intro, tone = 'pine' } = Astro.props; 21 const SKEW = -11; 22 --- 23 <header class="slant" style={`--acc: var(--${tone});`}> 24 <p class="slant__eyebrow">{eyebrow}</p> 25 <div class="slant__box"> 26 <span class="slant__edge" aria-hidden="true" style={`transform: skewX(${SKEW}deg);`}></span> 27 <h1 class="slant__plate" style={`transform: skewX(${SKEW}deg);`}> 28 <span class="slant__title" data-unscramble={title} style={`transform: skewX(${-SKEW}deg);`}>{title}</span> 29 </h1> 30 </div> 31 {intro && <p class="slant__intro">{intro}</p>} 32 {(Astro.slots.has('meta')) && <div class="slant__meta"><slot name="meta" /></div>} 33 </header> 34 35 <style> 36 .slant { display: block; } 37 .slant__eyebrow { 38 font-family: var(--font-plex); 39 font-size: 10.5px; 40 letter-spacing: 0.22em; 41 text-transform: uppercase; 42 color: var(--acc); 43 margin: 0; 44 } 45 .slant__box { position: relative; display: inline-block; margin-top: 1rem; } 46 .slant__edge { 47 position: absolute; 48 top: -7px; 49 left: -7px; 50 height: 100%; 51 width: 100%; 52 border: 2px solid var(--fg); 53 transform-origin: center; 54 animation: slant-edge 900ms var(--ease) both; 55 } 56 .slant__plate { 57 position: relative; 58 margin: 0; 59 padding: 14px 26px; 60 background: var(--acc); 61 color: var(--base); 62 transform-origin: center; 63 animation: slant-plate 900ms var(--ease) both; 64 } 65 .slant__title { 66 display: block; 67 font-family: var(--font-archivo); 68 font-weight: 900; 69 font-size: clamp(30px, 5.4vw, 62px); 70 line-height: 0.94; 71 letter-spacing: -0.03em; 72 font-stretch: 76%; 73 text-transform: uppercase; 74 } 75 .slant__intro { 76 margin-top: 1.75rem; 77 max-width: 62ch; 78 font-size: 16px; 79 line-height: 1.6; 80 color: var(--fg); 81 text-wrap: pretty; 82 } 83 .slant__meta { 84 margin-top: 1.75rem; 85 display: flex; 86 flex-wrap: wrap; 87 align-items: center; 88 gap: 0.5rem 2rem; 89 border-top: 1px solid var(--rule); 90 padding-top: 1rem; 91 font-family: var(--font-plex); 92 font-size: 10px; 93 letter-spacing: 0.18em; 94 text-transform: uppercase; 95 color: var(--acc); 96 } 97 @keyframes slant-edge { 98 from { opacity: 0.25; top: -22px; left: -33px; } 99 to { opacity: 1; top: -7px; left: -7px; } 100 } 101 @keyframes slant-plate { 102 from { transform: skewX(-11deg) translate(14px, -6px); } 103 to { transform: skewX(-11deg) translate(0, 0); } 104 } 105 </style>