this repo has no description
0
fork

Configure Feed

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

Sky effects: slider under Protocol, static vs animated sky

Replace the nav text button with a compact Effects switch below Protocol.
When off, keep clouds visible but freeze gradient, sun/rays, and parallax
at first-load (p=0). Rename visible label to Effects and update aria text.

Made-with: Cursor

+235 -76
+112 -2
assets/styles.css
··· 138 138 Sun Glow / Rays (behind clouds) 139 139 ================================ */ 140 140 141 - #sun-glow, #sun-rays { 141 + #sun-glow, 142 + #sun-rays { 142 143 position: fixed; 143 144 top: 0; 144 145 left: 0; ··· 146 147 height: 100%; 147 148 pointer-events: none; 148 149 z-index: 0; 150 + } 151 + 152 + /* Sky “static” mode is handled in JS (p=0, no cloud parallax) — clouds stay visible */ 153 + 154 + .nav-protocol-stack { 155 + display: flex; 156 + flex-direction: column; 157 + align-items: flex-end; 158 + gap: 0.35rem; 159 + } 160 + 161 + .nav-sky-switch-label { 162 + display: flex; 163 + align-items: center; 164 + gap: 0.4rem; 165 + cursor: pointer; 166 + user-select: none; 167 + margin: 0; 168 + padding: 0; 169 + font-family: "IBM Plex Mono", ui-monospace, monospace; 170 + font-size: 0.65rem; 171 + font-weight: 500; 172 + letter-spacing: 0.04em; 173 + text-transform: uppercase; 174 + color: rgba(18, 26, 47, 0.55); 175 + } 176 + 177 + .nav-sky-switch-text { 178 + line-height: 1; 179 + } 180 + 181 + .nav-sky-switch { 182 + position: relative; 183 + display: inline-block; 184 + width: 34px; 185 + height: 18px; 186 + flex-shrink: 0; 187 + } 188 + 189 + .nav-sky-switch-input { 190 + position: absolute; 191 + opacity: 0; 192 + width: 0; 193 + height: 0; 194 + margin: 0; 195 + } 196 + 197 + .nav-sky-switch-track { 198 + position: absolute; 199 + inset: 0; 200 + border-radius: 100px; 201 + background: rgba(18, 26, 47, 0.14); 202 + transition: background 0.2s ease; 203 + cursor: pointer; 204 + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06); 205 + } 206 + 207 + .nav-sky-switch-input:focus-visible + .nav-sky-switch-track { 208 + outline: 2px solid rgba(42, 90, 168, 0.45); 209 + outline-offset: 2px; 210 + } 211 + 212 + .nav-sky-switch-input:checked + .nav-sky-switch-track { 213 + background: rgba(42, 90, 168, 0.42); 214 + } 215 + 216 + .nav-sky-switch-track::after { 217 + content: ""; 218 + position: absolute; 219 + width: 14px; 220 + height: 14px; 221 + left: 2px; 222 + top: 2px; 223 + border-radius: 50%; 224 + background: #fff; 225 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); 226 + transition: transform 0.2s ease; 227 + } 228 + 229 + .nav-sky-switch-input:checked + .nav-sky-switch-track::after { 230 + transform: translateX(16px); 231 + } 232 + 233 + @media (max-width: 480px) { 234 + .nav-sky-switch-label { 235 + font-size: 0.6rem; 236 + gap: 0.3rem; 237 + } 238 + 239 + .nav-protocol-stack { 240 + gap: 0.28rem; 241 + } 149 242 } 150 243 151 244 /* ================================ ··· 254 347 255 348 .nav-links { 256 349 display: flex; 257 - align-items: center; 350 + align-items: flex-start; 258 351 gap: 0.75rem; 259 352 } 260 353 ··· 1327 1420 .dark-phase .nav-btn-ghost:hover { 1328 1421 color: #fff; 1329 1422 background: rgba(255, 255, 255, 0.14); 1423 + } 1424 + 1425 + .dark-phase .nav-sky-switch-label { 1426 + color: rgba(255, 255, 255, 0.55); 1427 + } 1428 + 1429 + .dark-phase .nav-sky-switch-track { 1430 + background: rgba(255, 255, 255, 0.18); 1431 + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); 1432 + } 1433 + 1434 + .dark-phase .nav-sky-switch-input:checked + .nav-sky-switch-track { 1435 + background: rgba(158, 200, 240, 0.35); 1436 + } 1437 + 1438 + .dark-phase .nav-sky-switch-track::after { 1439 + background: rgba(255, 255, 255, 0.95); 1330 1440 } 1331 1441 1332 1442 .dark-phase .nav-coming-soon:hover::after,
+23 -8
components/Nav.tsx
··· 9 9 <span class="nav-btn nav-btn-ghost nav-coming-soon" title="Coming soon"> 10 10 Explore 11 11 </span> 12 - <a 13 - href="https://atproto.com" 14 - target="_blank" 15 - rel="noopener noreferrer" 16 - class="nav-btn nav-btn-glass" 17 - > 18 - Protocol 19 - </a> 12 + <div class="nav-protocol-stack"> 13 + <a 14 + href="https://atproto.com" 15 + target="_blank" 16 + rel="noopener noreferrer" 17 + class="nav-btn nav-btn-glass" 18 + > 19 + Protocol 20 + </a> 21 + <label class="nav-sky-switch-label"> 22 + <span class="nav-sky-switch-text">Effects</span> 23 + <span class="nav-sky-switch"> 24 + <input 25 + type="checkbox" 26 + id="sky-effects-toggle" 27 + class="nav-sky-switch-input" 28 + defaultChecked 29 + aria-label="Effects on. Turn off to keep colors and clouds fixed like the first screen." 30 + /> 31 + <span class="nav-sky-switch-track" aria-hidden="true" /> 32 + </span> 33 + </label> 34 + </div> 20 35 </div> 21 36 </nav> 22 37 );
+100 -66
routes/_app.tsx
··· 30 30 document.querySelectorAll('lottie-player').forEach(function(el){ lottieIo.observe(el); }); 31 31 32 32 var nav = document.getElementById('main-nav'); 33 + var SKY_KEY = 'atmosphere-sky-effects'; 34 + function skyAnimated() { 35 + return !document.documentElement.classList.contains('sky-static'); 36 + } 33 37 34 38 /* ---- Sky gradient: gentle day -> golden hour -> warm sunset -> back to day (never dark) ---- */ 35 39 var K = [ ··· 66 70 /* ---- Sun glow + rays elements ---- */ 67 71 var layer=document.querySelector('.cloud-layer'); 68 72 var sunEl=document.createElement('div'); 73 + sunEl.id='sun-glow'; 69 74 sunEl.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;backface-visibility:hidden;transform:translateZ(0);'; 70 75 var raysEl=document.createElement('div'); 76 + raysEl.id='sun-rays'; 71 77 /* soft-light is less prone to full-viewport seam artifacts than screen while scrolling */ 72 78 raysEl.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;mix-blend-mode:soft-light;backface-visibility:hidden;transform:translateZ(0);'; 73 79 if(layer){ ··· 88 94 var scrollY=window.scrollY; 89 95 var maxScroll=Math.max(1,document.documentElement.scrollHeight-window.innerHeight); 90 96 var p=Math.min(1,Math.max(0,scrollY/maxScroll)); 97 + var pUse=skyAnimated()?p:0; 91 98 92 - document.body.style.background=gradient(p); 99 + document.body.style.background=gradient(pUse); 93 100 94 - var lum=luminance(p); 101 + var lum=luminance(pUse); 95 102 document.body.classList.toggle('dark-phase',lum<0.45); 96 103 97 - /* ---- Sun arc: noon at p=0, sunset right, midnight below, sunrise left ---- */ 98 - var ang=2*Math.PI*p; 99 - var sunX=50+65*Math.sin(ang); 100 - var sunY=40-58*Math.cos(ang); 104 + /* ---- Sun arc: noon at p=0; when static, pUse stays 0 (first-load look) ---- */ 105 + var ang=2*Math.PI*pUse; 106 + var sunX=50+65*Math.sin(ang); 107 + var sunY=40-58*Math.cos(ang); 101 108 102 - /* Visibility: 1 at highest point, fades toward horizon */ 103 - var vis=Math.max(0,Math.min(1,(52-sunY)/65)); 109 + /* Visibility: 1 at highest point, fades toward horizon */ 110 + var vis=Math.max(0,Math.min(1,(52-sunY)/65)); 104 111 105 - /* Color temperature: warm gold when low, bright yellow-white when high */ 106 - var ht=Math.max(0,Math.min(1,(25-sunY)/55)); 107 - var sr=255; 108 - var sg=Math.round(185+ht*55); 109 - var sb=Math.round(60+ht*140); 112 + /* Color temperature: warm gold when low, bright yellow-white when high */ 113 + var ht=Math.max(0,Math.min(1,(25-sunY)/55)); 114 + var sr=255; 115 + var sg=Math.round(185+ht*55); 116 + var sb=Math.round(60+ht*140); 110 117 111 - var op=vis*0.6; 112 - var sz=50+vis*30; 118 + var op=vis*0.6; 119 + var sz=50+vis*30; 113 120 114 - /* Primary glow: large warm radial from sun position */ 115 - if(vis>0.005){ 116 - sunEl.style.background= 117 - 'radial-gradient(ellipse '+sz+'% '+sz+'% at '+sunX+'% '+sunY+'%,'+ 118 - 'rgba('+sr+','+sg+','+sb+','+op.toFixed(3)+') 0%,'+ 119 - 'rgba('+sr+','+sg+','+sb+','+(op*0.45).toFixed(3)+') 20%,'+ 120 - 'rgba('+sr+','+sg+','+sb+','+(op*0.15).toFixed(3)+') 45%,'+ 121 - 'transparent 70%),'+ 122 - 'radial-gradient(ellipse '+(sz*1.8)+'% '+(sz*1.8)+'% at '+sunX+'% '+sunY+'%,'+ 123 - 'rgba('+sr+','+sg+','+sb+','+(op*0.1).toFixed(3)+') 0%,'+ 124 - 'transparent 60%)'; 121 + /* Primary glow: large warm radial from sun position */ 122 + if(vis>0.005){ 123 + sunEl.style.background= 124 + 'radial-gradient(ellipse '+sz+'% '+sz+'% at '+sunX+'% '+sunY+'%,'+ 125 + 'rgba('+sr+','+sg+','+sb+','+op.toFixed(3)+') 0%,'+ 126 + 'rgba('+sr+','+sg+','+sb+','+(op*0.45).toFixed(3)+') 20%,'+ 127 + 'rgba('+sr+','+sg+','+sb+','+(op*0.15).toFixed(3)+') 45%,'+ 128 + 'transparent 70%),'+ 129 + 'radial-gradient(ellipse '+(sz*1.8)+'% '+(sz*1.8)+'% at '+sunX+'% '+sunY+'%,'+ 130 + 'rgba('+sr+','+sg+','+sb+','+(op*0.1).toFixed(3)+') 0%,'+ 131 + 'transparent 60%)'; 125 132 126 - /* God rays: conic gradient beams radiating from sun */ 127 - var rayOp=vis*0.18; 128 - var rayC='rgba('+sr+','+sg+','+sb+','; 129 - raysEl.style.background= 130 - 'conic-gradient(from 0deg at '+sunX+'% '+sunY+'%,'+ 131 - rayC+rayOp.toFixed(3)+') 0deg,'+ 132 - 'transparent 8deg,'+ 133 - 'transparent 25deg,'+ 134 - rayC+(rayOp*0.7).toFixed(3)+') 30deg,'+ 135 - 'transparent 38deg,'+ 136 - 'transparent 60deg,'+ 137 - rayC+(rayOp*0.9).toFixed(3)+') 65deg,'+ 138 - 'transparent 75deg,'+ 139 - 'transparent 100deg,'+ 140 - rayC+(rayOp*0.6).toFixed(3)+') 105deg,'+ 141 - 'transparent 115deg,'+ 142 - 'transparent 140deg,'+ 143 - rayC+(rayOp*0.8).toFixed(3)+') 148deg,'+ 144 - 'transparent 158deg,'+ 145 - 'transparent 185deg,'+ 146 - rayC+(rayOp*0.5).toFixed(3)+') 190deg,'+ 147 - 'transparent 200deg,'+ 148 - 'transparent 225deg,'+ 149 - rayC+(rayOp*0.7).toFixed(3)+') 232deg,'+ 150 - 'transparent 242deg,'+ 151 - 'transparent 270deg,'+ 152 - rayC+(rayOp*0.6).toFixed(3)+') 278deg,'+ 153 - 'transparent 288deg,'+ 154 - 'transparent 315deg,'+ 155 - rayC+(rayOp*0.8).toFixed(3)+') 322deg,'+ 156 - 'transparent 332deg,'+ 157 - 'transparent 355deg,'+ 158 - rayC+(rayOp*0.4).toFixed(3)+') 360deg)'; 159 - raysEl.style.opacity=vis; 160 - } else { 161 - sunEl.style.background='none'; 162 - raysEl.style.opacity='0'; 163 - } 133 + /* God rays: conic gradient beams radiating from sun */ 134 + var rayOp=vis*0.18; 135 + var rayC='rgba('+sr+','+sg+','+sb+','; 136 + raysEl.style.background= 137 + 'conic-gradient(from 0deg at '+sunX+'% '+sunY+'%,'+ 138 + rayC+rayOp.toFixed(3)+') 0deg,'+ 139 + 'transparent 8deg,'+ 140 + 'transparent 25deg,'+ 141 + rayC+(rayOp*0.7).toFixed(3)+') 30deg,'+ 142 + 'transparent 38deg,'+ 143 + 'transparent 60deg,'+ 144 + rayC+(rayOp*0.9).toFixed(3)+') 65deg,'+ 145 + 'transparent 75deg,'+ 146 + 'transparent 100deg,'+ 147 + rayC+(rayOp*0.6).toFixed(3)+') 105deg,'+ 148 + 'transparent 115deg,'+ 149 + 'transparent 140deg,'+ 150 + rayC+(rayOp*0.8).toFixed(3)+') 148deg,'+ 151 + 'transparent 158deg,'+ 152 + 'transparent 185deg,'+ 153 + rayC+(rayOp*0.5).toFixed(3)+') 190deg,'+ 154 + 'transparent 200deg,'+ 155 + 'transparent 225deg,'+ 156 + rayC+(rayOp*0.7).toFixed(3)+') 232deg,'+ 157 + 'transparent 242deg,'+ 158 + 'transparent 270deg,'+ 159 + rayC+(rayOp*0.6).toFixed(3)+') 278deg,'+ 160 + 'transparent 288deg,'+ 161 + 'transparent 315deg,'+ 162 + rayC+(rayOp*0.8).toFixed(3)+') 322deg,'+ 163 + 'transparent 332deg,'+ 164 + 'transparent 355deg,'+ 165 + rayC+(rayOp*0.4).toFixed(3)+') 360deg)'; 166 + raysEl.style.opacity=vis; 167 + } else { 168 + sunEl.style.background='none'; 169 + raysEl.style.opacity='0'; 170 + } 164 171 165 - /* Cloud parallax + sun-lit highlight on each cloud */ 172 + /* Cloud parallax — frozen at first-load position when static */ 166 173 for(var i=0;i<cData.length;i++){ 167 174 var d=cData[i]; 168 - var ty=scrollY*d.speed; 175 + var ty=skyAnimated()?scrollY*d.speed:0; 169 176 d.el.style.transform='translate3d(0,'+ty+'px,0)'+(d.flip?' scaleX(-1)':''); 170 177 } 171 178 ··· 176 183 ticking=false; 177 184 } 178 185 186 + var toggleInput=document.getElementById('sky-effects-toggle'); 187 + if(toggleInput){ 188 + function syncSkyToggle(){ 189 + var on=skyAnimated(); 190 + toggleInput.checked=on; 191 + toggleInput.setAttribute('aria-label',on?'Effects on. Turn off to keep colors and clouds fixed like the first screen.':'Effects off. Sky matches the first-load colors and cloud positions.'); 192 + } 193 + toggleInput.addEventListener('change',function(){ 194 + if(toggleInput.checked){ 195 + document.documentElement.classList.remove('sky-static'); 196 + try{localStorage.setItem(SKY_KEY,'1');}catch(_){} 197 + }else{ 198 + document.documentElement.classList.add('sky-static'); 199 + try{localStorage.setItem(SKY_KEY,'0');}catch(_){} 200 + } 201 + syncSkyToggle(); 202 + update(); 203 + }); 204 + syncSkyToggle(); 205 + } 206 + 179 207 window.addEventListener('scroll',function(){ 180 208 if(!ticking){ticking=true;requestAnimationFrame(update);} 181 209 },{passive:true}); ··· 212 240 <meta name="twitter:image" content={socialImageUrl("/union.svg")} /> 213 241 <link rel="icon" type="image/svg+xml" href="/union.svg" /> 214 242 <link rel="apple-touch-icon" href="/union.svg" /> 243 + <script 244 + dangerouslySetInnerHTML={{ 245 + __html: 246 + "(function(){try{if(localStorage.getItem('atmosphere-sky-effects')==='0')document.documentElement.classList.add('sky-static');}catch(e){}})();", 247 + }} 248 + /> 215 249 <script 216 250 src="https://unpkg.com/@lottiefiles/lottie-player@2.0.8/dist/lottie-player.js" 217 251 defer