| ... | @@ -0,0 +1,316 @@ |
| 1 | import { useAtomValue } from "@effect/atom-react"; |
| 2 | |
| 3 | import { APP_STAGE_LABEL } from "../branding"; |
| 4 | import { resolveServerBackedAppStageLabel } from "../branding.logic"; |
| 5 | import { primaryServerConfigAtom } from "../state/server"; |
| 6 | |
| 7 | export type SidebarStageBackdropVariant = "nightly" | "dev"; |
| 8 | |
| 9 | // A wide viewBox keeps the 96-unit art height at a fixed scale while sidebar resizing reveals |
| 10 | // more horizontal canvas instead of zooming the scene. |
| 11 | const STAGE_BACKDROP_VIEW_BOX = "0 0 8192 96"; |
| 12 | |
| 13 | export function resolveSidebarStageBackdropVariant( |
| 14 | stageLabel: string, |
| 15 | ): SidebarStageBackdropVariant | null { |
| 16 | const normalized = stageLabel.trim().toLowerCase(); |
| 17 | if (normalized === "nightly") return "nightly"; |
| 18 | if (normalized === "dev") return "dev"; |
| 19 | return null; |
| 20 | } |
| 21 | |
| 22 | export function useSidebarStageBackdropVariant(): SidebarStageBackdropVariant | null { |
| 23 | const primaryServerVersion = |
| 24 | useAtomValue(primaryServerConfigAtom)?.environment.serverVersion ?? null; |
| 25 | |
| 26 | return resolveSidebarStageBackdropVariant( |
| 27 | resolveServerBackedAppStageLabel({ |
| 28 | primaryServerVersion, |
| 29 | fallbackStageLabel: APP_STAGE_LABEL, |
| 30 | }), |
| 31 | ); |
| 32 | } |
| 33 | |
| 34 | /** Stage-channel header art; palettes mirror the per-channel app icons in `assets/`. */ |
| 35 | export function SidebarStageBackdrop({ variant }: { variant: SidebarStageBackdropVariant }) { |
| 36 | return ( |
| 37 | <div |
| 38 | aria-hidden |
| 39 | className="sidebar-stage-backdrop pointer-events-none absolute inset-x-0 top-0 z-0 h-20 select-none overflow-hidden" |
| 40 | > |
| 41 | {variant === "nightly" ? <NightlySkyArt /> : <DevBlueprintArt />} |
| 42 | </div> |
| 43 | ); |
| 44 | } |
| 45 | |
| 46 | const NIGHTLY_STARS: ReadonlyArray<{ |
| 47 | cx: number; |
| 48 | cy: number; |
| 49 | r: number; |
| 50 | opacity: number; |
| 51 | }> = [ |
| 52 | { cx: 14, cy: 10, r: 0.6, opacity: 0.85 }, |
| 53 | { cx: 38, cy: 22, r: 0.4, opacity: 0.55 }, |
| 54 | { cx: 58, cy: 8, r: 0.5, opacity: 0.7 }, |
| 55 | { cx: 84, cy: 16, r: 0.4, opacity: 0.5 }, |
| 56 | { cx: 104, cy: 7, r: 0.6, opacity: 0.8 }, |
| 57 | { cx: 126, cy: 20, r: 0.4, opacity: 0.55 }, |
| 58 | { cx: 148, cy: 11, r: 0.5, opacity: 0.7 }, |
| 59 | { cx: 170, cy: 24, r: 0.4, opacity: 0.5 }, |
| 60 | { cx: 192, cy: 9, r: 0.6, opacity: 0.8 }, |
| 61 | { cx: 214, cy: 18, r: 0.4, opacity: 0.55 }, |
| 62 | { cx: 236, cy: 8, r: 0.5, opacity: 0.7 }, |
| 63 | { cx: 258, cy: 20, r: 0.45, opacity: 0.6 }, |
| 64 | { cx: 278, cy: 11, r: 0.55, opacity: 0.75 }, |
| 65 | { cx: 26, cy: 34, r: 0.4, opacity: 0.45 }, |
| 66 | { cx: 118, cy: 34, r: 0.4, opacity: 0.45 }, |
| 67 | { cx: 202, cy: 32, r: 0.4, opacity: 0.5 }, |
| 68 | { cx: 268, cy: 34, r: 0.4, opacity: 0.45 }, |
| 69 | ]; |
| 70 | |
| 71 | const NIGHTLY_SPARKLES: ReadonlyArray<{ x: number; y: number }> = [ |
| 72 | { x: 70, y: 28 }, |
| 73 | { x: 160, y: 36 }, |
| 74 | { x: 246, y: 26 }, |
| 75 | ]; |
| 76 | |
| 77 | function NightlySkyArt() { |
| 78 | return ( |
| 79 | <svg |
| 80 | className="h-full w-full" |
| 81 | fill="none" |
| 82 | preserveAspectRatio="xMinYMin slice" |
| 83 | viewBox={STAGE_BACKDROP_VIEW_BOX} |
| 84 | xmlns="http://www.w3.org/2000/svg" |
| 85 | > |
| 86 | <defs> |
| 87 | <linearGradient |
| 88 | id="stage-night-sky" |
| 89 | x1="24" |
| 90 | y1="0" |
| 91 | x2="264" |
| 92 | y2="96" |
| 93 | gradientUnits="userSpaceOnUse" |
| 94 | spreadMethod="reflect" |
| 95 | > |
| 96 | <stop stopColor="#07152F" /> |
| 97 | <stop offset="0.5" stopColor="#151443" /> |
| 98 | <stop offset="1" stopColor="#32155B" /> |
| 99 | </linearGradient> |
| 100 | <radialGradient |
| 101 | id="stage-night-glow" |
| 102 | cx="0" |
| 103 | cy="0" |
| 104 | r="1" |
| 105 | gradientTransform="translate(216 18) rotate(137) scale(120 84)" |
| 106 | gradientUnits="userSpaceOnUse" |
| 107 | > |
| 108 | <stop stopColor="#5165D8" stopOpacity="0.4" /> |
| 109 | <stop offset="0.5" stopColor="#283075" stopOpacity="0.16" /> |
| 110 | <stop offset="1" stopColor="#111635" stopOpacity="0" /> |
| 111 | </radialGradient> |
| 112 | <linearGradient |
| 113 | id="stage-night-cloud" |
| 114 | x1="0" |
| 115 | y1="60" |
| 116 | x2="288" |
| 117 | y2="96" |
| 118 | gradientUnits="userSpaceOnUse" |
| 119 | > |
| 120 | <stop stopColor="#4EA4FF" stopOpacity="0.5" /> |
| 121 | <stop offset="0.52" stopColor="#696FEA" stopOpacity="0.62" /> |
| 122 | <stop offset="1" stopColor="#A85BEA" stopOpacity="0.5" /> |
| 123 | </linearGradient> |
| 124 | <filter |
| 125 | id="stage-night-soft" |
| 126 | x="-24" |
| 127 | y="-24" |
| 128 | width="336" |
| 129 | height="144" |
| 130 | filterUnits="userSpaceOnUse" |
| 131 | > |
| 132 | <feGaussianBlur stdDeviation="4" /> |
| 133 | </filter> |
| 134 | <pattern id="stage-night-stars" width="288" height="96" patternUnits="userSpaceOnUse"> |
| 135 | <g fill="#E4EAFF"> |
| 136 | {NIGHTLY_STARS.map((star) => ( |
| 137 | <circle |
| 138 | key={`${star.cx}-${star.cy}`} |
| 139 | cx={star.cx} |
| 140 | cy={star.cy} |
| 141 | r={star.r} |
| 142 | fillOpacity={star.opacity} |
| 143 | /> |
| 144 | ))} |
| 145 | </g> |
| 146 | <g stroke="#C8D7FF" strokeLinecap="round" strokeOpacity="0.7" strokeWidth="0.6"> |
| 147 | {NIGHTLY_SPARKLES.map((sparkle) => ( |
| 148 | <g key={`${sparkle.x}-${sparkle.y}`}> |
| 149 | <path d={`M${sparkle.x - 1.5} ${sparkle.y}H${sparkle.x + 1.5}`} /> |
| 150 | <path d={`M${sparkle.x} ${sparkle.y - 1.5}V${sparkle.y + 1.5}`} /> |
| 151 | </g> |
| 152 | ))} |
| 153 | </g> |
| 154 | </pattern> |
| 155 | <pattern id="stage-night-glows" width="640" height="96" patternUnits="userSpaceOnUse"> |
| 156 | <rect width="640" height="96" fill="url(#stage-night-glow)" /> |
| 157 | </pattern> |
| 158 | </defs> |
| 159 | |
| 160 | <rect width="100%" height="96" fill="url(#stage-night-sky)" /> |
| 161 | <rect width="100%" height="96" fill="url(#stage-night-glows)" /> |
| 162 | <rect width="100%" height="96" fill="url(#stage-night-stars)" /> |
| 163 | |
| 164 | <g filter="url(#stage-night-soft)"> |
| 165 | <path |
| 166 | d="M-12 88C-12 74 0 63 14 63C18 50 30 41 44 41C58 41 70 49 74 62C79 57 86 54 94 54C110 54 123 66 124 82C132 83 138 88 141 96H-12V88Z" |
| 167 | fill="url(#stage-night-cloud)" |
| 168 | /> |
| 169 | </g> |
| 170 | <g filter="url(#stage-night-soft)"> |
| 171 | <path |
| 172 | d="M150 96C151 84 161 75 173 75C176 64 186 57 198 57C210 57 220 64 223 75C231 75 238 80 241 87C250 87 257 91 260 96H150Z" |
| 173 | fill="url(#stage-night-cloud)" |
| 174 | fillOpacity="0.8" |
| 175 | /> |
| 176 | </g> |
| 177 | </svg> |
| 178 | ); |
| 179 | } |
| 180 | |
| 181 | function DevBlueprintArt() { |
| 182 | return ( |
| 183 | <svg |
| 184 | className="stage-blueprint h-full w-full" |
| 185 | fill="none" |
| 186 | preserveAspectRatio="xMinYMin slice" |
| 187 | viewBox={STAGE_BACKDROP_VIEW_BOX} |
| 188 | xmlns="http://www.w3.org/2000/svg" |
| 189 | > |
| 190 | <defs> |
| 191 | <linearGradient |
| 192 | id="stage-bp-paper" |
| 193 | x1="60" |
| 194 | y1="0" |
| 195 | x2="220" |
| 196 | y2="96" |
| 197 | gradientUnits="userSpaceOnUse" |
| 198 | spreadMethod="reflect" |
| 199 | > |
| 200 | <stop style={{ stopColor: "var(--stage-bp-top)" }} /> |
| 201 | <stop offset="0.5" style={{ stopColor: "var(--stage-bp-mid)" }} /> |
| 202 | <stop offset="1" style={{ stopColor: "var(--stage-bp-bottom)" }} /> |
| 203 | </linearGradient> |
| 204 | <radialGradient |
| 205 | id="stage-bp-glow" |
| 206 | cx="0" |
| 207 | cy="0" |
| 208 | r="1" |
| 209 | gradientTransform="translate(216 14) rotate(137) scale(120 84)" |
| 210 | gradientUnits="userSpaceOnUse" |
| 211 | > |
| 212 | <stop stopColor="#D4F6FF" stopOpacity="0.4" /> |
| 213 | <stop offset="0.52" stopColor="#65C8FF" stopOpacity="0.16" /> |
| 214 | <stop offset="1" stopColor="#276AF1" stopOpacity="0" /> |
| 215 | </radialGradient> |
| 216 | <radialGradient |
| 217 | id="stage-bp-glow-celeste" |
| 218 | cx="0" |
| 219 | cy="0" |
| 220 | r="1" |
| 221 | gradientTransform="translate(474 44) rotate(166) scale(156 92)" |
| 222 | gradientUnits="userSpaceOnUse" |
| 223 | > |
| 224 | <stop stopColor="#D2FFFF" stopOpacity="0.34" /> |
| 225 | <stop offset="0.5" stopColor="#48DCF5" stopOpacity="0.18" /> |
| 226 | <stop offset="1" stopColor="#277EF1" stopOpacity="0" /> |
| 227 | </radialGradient> |
| 228 | <radialGradient |
| 229 | id="stage-bp-glow-violet" |
| 230 | cx="0" |
| 231 | cy="0" |
| 232 | r="1" |
| 233 | gradientTransform="translate(704 18) rotate(145) scale(132 88)" |
| 234 | gradientUnits="userSpaceOnUse" |
| 235 | > |
| 236 | <stop stopColor="#D9D8FF" stopOpacity="0.3" /> |
| 237 | <stop offset="0.52" stopColor="#7C8BFF" stopOpacity="0.14" /> |
| 238 | <stop offset="1" stopColor="#3155DF" stopOpacity="0" /> |
| 239 | </radialGradient> |
| 240 | <pattern id="stage-bp-grid-minor" width="8" height="8" patternUnits="userSpaceOnUse"> |
| 241 | <path d="M8 0H0V8" stroke="#EAF6FF" strokeOpacity="0.14" strokeWidth="0.5" /> |
| 242 | </pattern> |
| 243 | <pattern id="stage-bp-grid-major" width="32" height="32" patternUnits="userSpaceOnUse"> |
| 244 | <path d="M32 0H0V32" stroke="#EAF6FF" strokeOpacity="0.26" strokeWidth="0.6" /> |
| 245 | </pattern> |
| 246 | <pattern id="stage-bp-ruler" width="32" height="6" patternUnits="userSpaceOnUse"> |
| 247 | <path |
| 248 | d="M4 0V2.5M12 0V2.5M20 0V4M28 0V2.5" |
| 249 | stroke="#DDF7FF" |
| 250 | strokeOpacity="0.5" |
| 251 | strokeWidth="0.5" |
| 252 | /> |
| 253 | </pattern> |
| 254 | <pattern id="stage-bp-glows" width="768" height="96" patternUnits="userSpaceOnUse"> |
| 255 | <rect width="768" height="96" fill="url(#stage-bp-glow)" /> |
| 256 | <rect width="768" height="96" fill="url(#stage-bp-glow-celeste)" /> |
| 257 | <rect width="768" height="96" fill="url(#stage-bp-glow-violet)" /> |
| 258 | </pattern> |
| 259 | <pattern id="stage-bp-annotations" width="768" height="96" patternUnits="userSpaceOnUse"> |
| 260 | <g stroke="#DDF7FF" strokeLinecap="round" strokeOpacity="0.6" strokeWidth="0.7"> |
| 261 | <path d="M180 64H264" strokeDasharray="5 4" /> |
| 262 | <path d="M180 61V67M264 61V67" /> |
| 263 | <path d="M276 10V44" strokeDasharray="4 4" strokeOpacity="0.5" /> |
| 264 | <path d="M273 10H279M273 44H279" strokeOpacity="0.5" /> |
| 265 | <path d="M348 30H428" strokeDasharray="3.5 5" strokeOpacity="0.5" /> |
| 266 | <path d="M348 27V33M428 27V33" strokeOpacity="0.5" /> |
| 267 | <path d="M512 48V80" strokeDasharray="5 3" strokeOpacity="0.45" /> |
| 268 | <path d="M509 48H515M509 80H515" strokeOpacity="0.45" /> |
| 269 | <path d="M590 70H724" strokeDasharray="7 4" strokeOpacity="0.55" /> |
| 270 | <path d="M590 67V73M724 67V73" strokeOpacity="0.55" /> |
| 271 | </g> |
| 272 | |
| 273 | <g stroke="#DDF7FF" strokeLinecap="round" strokeOpacity="0.55" strokeWidth="0.6"> |
| 274 | <g> |
| 275 | <path d="M34 60L38 64M38 60L34 64" /> |
| 276 | </g> |
| 277 | <g> |
| 278 | <path d="M228 26H234M231 23V29" /> |
| 279 | </g> |
| 280 | <g> |
| 281 | <path d="M143 51H149M146 48V54" /> |
| 282 | </g> |
| 283 | <g> |
| 284 | <path d="M316 16L322 22M322 16L316 22" /> |
| 285 | </g> |
| 286 | <g> |
| 287 | <path d="M468 70H476M472 66V74" /> |
| 288 | </g> |
| 289 | <g> |
| 290 | <path d="M558 28L564 34M564 28L558 34" /> |
| 291 | </g> |
| 292 | <g> |
| 293 | <path d="M742 44H750M746 40V48" /> |
| 294 | </g> |
| 295 | </g> |
| 296 | |
| 297 | <g stroke="#DDF7FF" strokeOpacity="0.35" strokeWidth="0.6"> |
| 298 | <circle cx="196" cy="38" r="13" strokeDasharray="3.5 4" /> |
| 299 | <path d="M196 33V43M191 38H201" strokeOpacity="0.6" strokeWidth="0.4" /> |
| 300 | <circle cx="414" cy="64" r="10" strokeDasharray="2.5 3.5" /> |
| 301 | <path d="M414 60V68M410 64H418" strokeOpacity="0.6" strokeWidth="0.4" /> |
| 302 | <circle cx="648" cy="32" r="15" strokeDasharray="4 5" /> |
| 303 | <path d="M648 26V38M642 32H654" strokeOpacity="0.6" strokeWidth="0.4" /> |
| 304 | </g> |
| 305 | </pattern> |
| 306 | </defs> |
| 307 | |
| 308 | <rect width="100%" height="96" fill="url(#stage-bp-paper)" /> |
| 309 | <rect width="100%" height="96" fill="url(#stage-bp-glows)" /> |
| 310 | <rect width="100%" height="96" fill="url(#stage-bp-grid-minor)" /> |
| 311 | <rect width="100%" height="96" fill="url(#stage-bp-grid-major)" /> |
| 312 | <rect width="100%" height="6" fill="url(#stage-bp-ruler)" /> |
| 313 | <rect width="100%" height="96" fill="url(#stage-bp-annotations)" /> |
| 314 | </svg> |
| 315 | ); |
| 316 | } |