A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

Add ErrorScene horizon component for SP12

+176
+133
src/components/ErrorScene.astro
··· 1 + --- 2 + import Base from '../layouts/Base.astro'; 3 + import Logo from './Logo.astro'; 4 + 5 + interface Props { 6 + eyebrow: string; 7 + heading: string; 8 + subline: string; 9 + } 10 + const { eyebrow, heading, subline } = Astro.props; 11 + --- 12 + 13 + <Base title={`${ heading } · SkyPress`} socialMeta={false}> 14 + <Fragment slot="head"> 15 + <meta name="robots" content="noindex" /> 16 + </Fragment> 17 + 18 + <header class="err-mast"> 19 + <a href="/"><Logo /></a> 20 + </header> 21 + 22 + <main class="err"> 23 + <div class="err__sky" aria-hidden="true"> 24 + <span class="err__birds">︶&nbsp;&nbsp;︶</span> 25 + <span class="err__sun"></span> 26 + <span class="err__horizon"></span> 27 + </div> 28 + <div class="err__body"> 29 + <p class="eyebrow">{eyebrow}</p> 30 + <h1 class="err__heading">{heading}</h1> 31 + <p class="err__sub">{subline}</p> 32 + <a class="btn btn--primary" href="/">Back to homepage</a> 33 + </div> 34 + </main> 35 + </Base> 36 + 37 + <style> 38 + .err-mast { 39 + padding: 1.5rem clamp(1.25rem, 5vw, 4rem); 40 + } 41 + .err-mast a { 42 + text-decoration: none; 43 + } 44 + .err { 45 + position: relative; 46 + min-height: calc(100vh - 6rem); 47 + display: flex; 48 + flex-direction: column; 49 + align-items: center; 50 + justify-content: flex-end; 51 + text-align: center; 52 + padding: 0 1.5rem clamp(3rem, 12vh, 7rem); 53 + overflow: hidden; 54 + /* Light sky: warm dawn at the top, a hard-ish stop forms the horizon band. */ 55 + background: linear-gradient(180deg, #fff4df 0%, #fcdfb8 33%, var(--paper) 33%); 56 + } 57 + .err__sky { 58 + position: absolute; 59 + inset: 0; 60 + z-index: 0; 61 + } 62 + .err__sun { 63 + position: absolute; 64 + top: clamp(4rem, 16vh, 9rem); 65 + left: 50%; 66 + width: clamp(72px, 14vw, 104px); 67 + height: clamp(72px, 14vw, 104px); 68 + transform: translateX(-50%); 69 + border-radius: 50%; 70 + background: radial-gradient(circle at 50% 38%, #f8b850, var(--sun)); 71 + box-shadow: 0 0 46px 10px rgba(232, 146, 12, 0.3); 72 + animation: err-glow 4.5s ease-in-out infinite; 73 + } 74 + @keyframes err-glow { 75 + 0%, 100% { 76 + box-shadow: 0 0 46px 10px rgba(232, 146, 12, 0.3); 77 + } 78 + 50% { 79 + box-shadow: 0 0 66px 18px rgba(232, 146, 12, 0.42); 80 + } 81 + } 82 + @media (prefers-reduced-motion: reduce) { 83 + .err__sun { 84 + animation: none; 85 + } 86 + } 87 + .err__horizon { 88 + position: absolute; 89 + top: 33%; 90 + left: 0; 91 + right: 0; 92 + height: 1px; 93 + background: linear-gradient( 94 + 90deg, 95 + transparent, 96 + var(--line-strong) 18%, 97 + var(--line-strong) 82%, 98 + transparent 99 + ); 100 + } 101 + .err__birds { 102 + position: absolute; 103 + top: clamp(3rem, 11vh, 6rem); 104 + left: 0; 105 + right: 0; 106 + text-align: center; 107 + color: var(--sun-strong); 108 + opacity: 0.5; 109 + letter-spacing: 0.6em; 110 + font-size: 0.9rem; 111 + } 112 + .err__body { 113 + position: relative; 114 + z-index: 1; 115 + max-width: 36ch; 116 + } 117 + .err__heading { 118 + font-size: clamp(2rem, 6vw, 2.9rem); 119 + line-height: 1.04; 120 + margin: 0.6rem 0 0; 121 + } 122 + .err__sub { 123 + color: var(--ink-soft); 124 + font-size: 1.05rem; 125 + line-height: 1.55; 126 + margin: 0.9rem 0 1.6rem; 127 + } 128 + @media (prefers-color-scheme: dark) { 129 + .err { 130 + background: linear-gradient(180deg, #2a1f12 0%, #3a2a14 33%, var(--paper) 33%); 131 + } 132 + } 133 + </style>
+43
src/components/ErrorScene.meta.test.ts
··· 1 + /** 2 + * Source-level guard for the shared error scene. Page/component rendering through 3 + * astro/container isn't viable in this jsdom-pinned suite (see Base.meta.test.ts), 4 + * so we pin the wiring at the source level. 5 + */ 6 + import { readFileSync } from 'node:fs'; 7 + import { dirname, join } from 'node:path'; 8 + import { fileURLToPath } from 'node:url'; 9 + import { describe, expect, it } from 'vitest'; 10 + 11 + const here = dirname( fileURLToPath( import.meta.url ) ); 12 + const component = readFileSync( join( here, './ErrorScene.astro' ), 'utf8' ); 13 + 14 + describe( 'ErrorScene component', () => { 15 + it( 'renders inside Base and opts out of social meta', () => { 16 + expect( component ).toMatch( /import Base from '[^']*layouts\/Base.astro'/ ); 17 + expect( component ).toMatch( /socialMeta=\{false\}/ ); 18 + } ); 19 + 20 + it( 'marks the page noindex so playful copy is never indexed', () => { 21 + expect( component ).toMatch( 22 + /<meta\s+name="robots"\s+content="noindex"\s*\/?>/ 23 + ); 24 + } ); 25 + 26 + it( 'renders the eyebrow, heading and subline props', () => { 27 + expect( component ).toMatch( /\{eyebrow\}/ ); 28 + expect( component ).toMatch( /\{heading\}/ ); 29 + expect( component ).toMatch( /\{subline\}/ ); 30 + } ); 31 + 32 + it( 'provides a single Back to homepage link to the site root', () => { 33 + expect( component ).toMatch( /href="\/"[^>]*>\s*Back to homepage/ ); 34 + } ); 35 + 36 + it( 'disables the sun-glow animation under reduced motion', () => { 37 + expect( component ).toMatch( /prefers-reduced-motion/ ); 38 + } ); 39 + 40 + it( 'is dependency-free (no @wordpress imports on the read path)', () => { 41 + expect( component ).not.toMatch( /@wordpress\// ); 42 + } ); 43 + } );