🧠 Description
The Neon Glass Context Menu project demonstrates how to replace the default browser right-click menu with a stylish, animated custom menu that uses glassmorphism and neon glow effects. It’s built using HTML, CSS, and JavaScript, featuring smooth transitions, responsive design, and an interactive hover glow — making it perfect for modern websites or web apps that want a futuristic, aesthetic interface.
💻 HTML Code
1<!DOCTYPE html>
2<html lang="en">
3
4 <head>
5 <meta charset="UTF-8">
6 <title>Neon Glass Context Menu</title>
7 <meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="https://public.codepenassets.com/css/normalize-5.0.0.min.css">
8<link rel='stylesheet' href='https://codepen.io/simeydotme/pen/jEOaeNd/63809857734821de257097bf620e7121.css'>
9<link rel='stylesheet' href='https://fonts.bunny.net/css?family=amaranth:400,400i,700,700i|asap-condensed:200,300i,400,400i,600,600i,700,700i,900,900i|asap:200,300i,400,400i,500,500i,600,600i,700,700i,900,900i|kode-mono:400,500,600,700'><link rel="stylesheet" href="./style.css">
10
11 </head>
12
13 <body>
14 <main id="app">
15
16 <header>
17 <h1>Neon Glass Context menu</h1>
18 <p>Right-click to open</p>
19 </header>
20
21 <footer class="dark">
22 <h2>Pick your own colors!</h2>
23 <input type=range id="h1" min=0 max=360 value=255 />
24 <input type=range id="h2" min=0 max=360 value=222 />
25 </footer>
26
27</main>
28
29<aside id="menu" class="open dark">
30 <span class="shine shine-top"></span>
31 <span class="shine shine-bottom"></span>
32 <span class="glow glow-top"></span>
33 <span class="glow glow-bottom"></span>
34 <span class="glow glow-bright glow-top "></span>
35 <span class="glow glow-bright glow-bottom "></span>
36
37 <div class="inner">
38
39 <label class="search">
40 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
41 <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
42 </svg>
43 <input type="text" placeholder="type a command or search" />
44 </label>
45
46 <section>
47 <header>Suggestions</header>
48 <ul>
49 <li class="selected" tabindex=0>
50 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
51 <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" />
52 </svg>
53 Calendar
54 </li>
55 <li tabindex=0>
56 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
57 <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 15.75V18m-7.5-6.75h.008v.008H8.25v-.008Zm0 2.25h.008v.008H8.25V13.5Zm0 2.25h.008v.008H8.25v-.008Zm0 2.25h.008v.008H8.25V18Zm2.498-6.75h.007v.008h-.007v-.008Zm0 2.25h.007v.008h-.007V13.5Zm0 2.25h.007v.008h-.007v-.008Zm0 2.25h.007v.008h-.007V18Zm2.504-6.75h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V13.5Zm0 2.25h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V18Zm2.498-6.75h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V13.5ZM8.25 6h7.5v2.25h-7.5V6ZM12 2.25c-1.892 0-3.758.11-5.593.322C5.307 2.7 4.5 3.65 4.5 4.757V19.5a2.25 2.25 0 0 0 2.25 2.25h10.5a2.25 2.25 0 0 0 2.25-2.25V4.757c0-1.108-.806-2.057-1.907-2.185A48.507 48.507 0 0 0 12 2.25Z" />
58 </svg>
59 Calculator
60 </li>
61 <li tabindex=0>
62 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
63 <path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 0 1 .865-.501 48.172 48.172 0 0 0 3.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z" />
64 </svg>
65 Messages
66 </li>
67 </ul>
68 </section>
69
70 <hr>
71
72 <section>
73 <header>Settings</header>
74 <ul>
75 <li tabindex=0>
76 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
77 <path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
78 </svg>
79 Profile
80 </li>
81 <li tabindex=0>
82 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
83 <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z" />
84 </svg>
85 Billing
86 </li>
87 </ul>
88 </section>
89
90 </div>
91
92</aside>
93
94<!-- social icons -->
95<a class="social-icon codepen" href="https://codepen.io/simeydotme" title="view my codepens">
96 Made by CodewithLord
97</a>
98
99<a class="social-icon twitter" href="https://twitter.com/simeydotme">
100 <svg viewBox="0 0 24 24">
101 <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
102 <path d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z"></path>
103 </svg>
104</a>
105<a class="social-icon github" href="https://github.com/simeydotme">
106 <svg viewBox="0 0 24 24">
107 <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
108 <path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5"></path>
109 </svg>
110</a>
111 <script src="./script.js"></script>
112
113 </body>
114
115</html>
116
The HTML defines the structure of the page and the custom context menu. It includes:
A div or section element to hold the context menu container.
Inside the menu, multiple button or li elements represent menu options like Copy, Paste, Delete, etc.
Each menu item can have an icon and text label for better visual clarity.
In short, HTML sets up the skeleton — defining what elements exist and how they’re grouped for styling and interactivity.
CSS Code
1:root { 2 /* vars */ 3 4 --hue1: 255; 5 --hue2: 222; 6 --border: 1px; 7 --border-color: hsl(var(--hue2), 12%, 20%); 8 --radius: 22px; 9 10 --ease: cubic-bezier(0.5, 1, 0.89, 1); 11 12} 13 14#menu { 15 visibility: hidden; 16 opacity: 0; 17 pointer-events: none; 18 transition-property: visibility, opacity, filter; 19 transition-duration: 0s, 0.25s, 0.25s; 20 transition-delay: 0.5s, 0s, 0s; 21 transition-timing-function: var(--ease); 22 filter: blur(4px); 23 24 font-family: 'Asap', sans-serif; 25 color: #737985; 26 27 position: fixed; 28 top: 140px; 29 left: 2svw; 30 min-width: 275px; 31 min-height: 275px; 32 border-radius: var(--radius); 33 border: var(--border) solid var(--border-color); 34 padding: 1em; 35 background: linear-gradient(235deg, hsl(var(--hue1) 50% 10% / 0.8), hsl(var(--hue1) 50% 10% / 0) 33%), linear-gradient(45deg , hsl(var(--hue2) 50% 10% / 0.8), hsl(var(--hue2) 50% 10% / 0) 33%), linear-gradient(hsl(220deg 25% 4.8% / 0.66)); 36 -webkit-backdrop-filter: blur(12px); 37 backdrop-filter: blur(12px); 38 box-shadow: hsl(var(--hue2) 50% 2%) 0px 10px 16px -8px, hsl(var(--hue2) 50% 4%) 0px 20px 36px -14px; 39} 40 41#menu:not(.open)::before, 42#menu:not(.open)::after, 43#menu:not(.open) .glow { 44 opacity: 0; 45 pointer-events: none; 46 -webkit-animation: glowoff 0.25s var(--ease) both; 47 animation: glowoff 0.25s var(--ease) both; 48} 49 50 51#menu.open { 52 53 visibility: visible; 54 opacity: 1; 55 pointer-events: auto; 56 transition-delay: 0s; 57 filter: blur(0px); 58 59 &::before, 60 &::after, 61 & .glow, 62 & .shine { 63 -webkit-animation: glow 1s var(--ease) both; 64 animation: glow 1s var(--ease) both; 65 } 66 & .shine { 67 -webkit-animation-delay: 0s; 68 animation-delay: 0s; 69 -webkit-animation-duration: 2s; 70 animation-duration: 2s; 71 } 72 & .glow { 73 -webkit-animation-delay: 0.2s; 74 animation-delay: 0.2s; 75 } 76 & .glow-bright { 77 -webkit-animation-delay: 0.1s; 78 animation-delay: 0.1s; 79 -webkit-animation-duration: 1.5s; 80 animation-duration: 1.5s; 81 } 82 & .shine-bottom { 83 -webkit-animation-delay: 0.1s; 84 animation-delay: 0.1s; 85 -webkit-animation-duration: 1.8s; 86 animation-duration: 1.8s; 87 } 88 & .glow-bottom { 89 -webkit-animation-delay: 0.3s; 90 animation-delay: 0.3s; 91 } 92 & .glow-bright.glow-bottom { 93 -webkit-animation-delay: 0.3s; 94 animation-delay: 0.3s; 95 -webkit-animation-duration: 1.1s; 96 animation-duration: 1.1s; 97 } 98} 99 100#menu .shine, 101#menu .glow { 102 --hue: var(--hue1); 103} 104#menu .shine-bottom, 105#menu .glow-bottom { 106 --hue: var(--hue2); 107 --conic: 135deg; 108} 109 110#menu .shine { 111} 112 113#menu .shine, 114#menu .shine::before, 115#menu .shine::after { 116 117 /* shine is the bright white-ish inner pixel-wide lights */ 118 119 pointer-events: none; 120 121 border-radius: 0; 122 border-top-right-radius: inherit; 123 border-bottom-left-radius: inherit; 124 border: 1px solid transparent; 125 126 width: 75%; 127 height: auto; 128 min-height: 0px; 129 aspect-ratio: 1; 130 display: block; 131 position: absolute; 132 right: calc(var(--border) * -1); 133 top: calc(var(--border) * -1); 134 left: auto; 135 136 z-index: 1; 137 138 --start: 12%; 139 background: conic-gradient( 140 from var(--conic, -45deg) at center in oklch, 141 transparent var(--start,0%), hsl( var(--hue), var(--sat,80%), var(--lit,60%)), transparent var(--end,50%) 142 ) border-box; 143 144 -webkit-mask: 145 linear-gradient(transparent), 146 linear-gradient(black); 147 148 mask: 149 linear-gradient(transparent), 150 linear-gradient(black); 151 -webkit-mask-repeat: no-repeat; 152 mask-repeat: no-repeat; 153 -webkit-mask-clip: padding-box, border-box; 154 mask-clip: padding-box, border-box; 155 -webkit-mask-composite: source-out; 156 mask-composite: subtract; 157 158} 159 160#menu .shine::before, 161#menu .shine::after { 162 content: ""; 163 width: auto; 164 inset: -2px; 165 -webkit-mask: none; 166 mask: none; 167} 168 169#menu .shine::after { 170 z-index: 2; 171 --start: 17%; 172 --end: 33%; 173 background: conic-gradient( 174 from var(--conic, -45deg) at center in oklch, 175 transparent var(--start,0%), hsl( var(--hue), var(--sat,80%), var(--lit,85%)), transparent var(--end,50%) 176 ); 177 178} 179 180#menu .shine-bottom { 181 top: auto; 182 bottom: calc(var(--border) * -1); 183 left: calc(var(--border) * -1); 184 right: auto; 185} 186 187#menu .glow { 188 189 /* glow is the sparse colour bleed with noise */ 190 191 pointer-events: none; 192 193 border-top-right-radius: calc(var(--radius) * 2.5); 194 border-bottom-left-radius: calc(var(--radius) * 2.5); 195 border: calc(var(--radius) * 1.25) solid transparent; 196 inset: calc(var(--radius) * -2); 197 198 width: 75%; 199 height: auto; 200 min-height: 0px; 201 aspect-ratio: 1; 202 display: block; 203 position: absolute; 204 left: auto; 205 bottom: auto; 206 207 -webkit-mask: url('https://assets.codepen.io/13471/noise-base.png'); 208 209 mask: url('https://assets.codepen.io/13471/noise-base.png'); 210 mask-mode: luminance; 211 -webkit-mask-size: 29%; 212 mask-size: 29%; 213 214 opacity: 1; 215 filter: blur(12px) saturate(1.25) brightness(0.5); 216 mix-blend-mode: plus-lighter; 217 z-index: 3; 218 219 &.glow-bottom { 220 inset: calc(var(--radius) * -2); 221 top: auto; 222 right: auto; 223 } 224 225 &::before, 226 &::after { 227 content: ""; 228 position: absolute; 229 inset: 0; 230 border: inherit; 231 border-radius: inherit; 232 background: conic-gradient( 233 from var(--conic, -45deg) at center in oklch, 234 transparent var(--start,0%), hsl( var(--hue), var(--sat,95%), var(--lit,60%)), transparent var(--end,50%) 235 ) border-box; 236 -webkit-mask: 237 linear-gradient(transparent), 238 linear-gradient(black); 239 mask: 240 linear-gradient(transparent), 241 linear-gradient(black); 242 -webkit-mask-repeat: no-repeat; 243 mask-repeat: no-repeat; 244 -webkit-mask-clip: padding-box, border-box; 245 mask-clip: padding-box, border-box; 246 -webkit-mask-composite: source-out; 247 mask-composite: subtract; 248 filter: saturate(2) brightness(1); 249 250 } 251 252 &::after { 253 --lit: 70%; 254 --sat: 100%; 255 --start: 15%; 256 --end: 35%; 257 border-width: calc(var(--radius) * 1.75); 258 border-radius: calc(var(--radius) * 2.75); 259 inset: calc(var(--radius) * -0.25); 260 z-index: 4; 261 opacity: 0.75; 262 } 263 &.glow-bottom::after { 264 } 265 266} 267 268#menu .glow-bright { 269 270 /* glow-bright is a sharper colour outline to accentuate the shine */ 271 272 --lit: 80%; 273 --sat: 100%; 274 --start: 13%; 275 --end: 37%; 276 277 border-width: 5px; 278 border-radius: calc(var(--radius) + 2px); 279 inset: -7px; 280 left: auto; 281 filter: blur(2px) brightness(0.66); 282 283 &::after { 284 content: none; 285 } 286 287 &.glow-bottom { 288 inset: -7px; 289 right: auto; 290 top: auto; 291 } 292} 293 294#menu .inner, 295#menu section { 296 display: flex; 297 flex-direction: column; 298 gap: 0.5em; 299} 300 301#menu .inner { 302 font-size: 0.875rem; 303} 304 305#menu hr { 306 width: 100%; 307 height: 0.5px; 308 background: var(--border-color); 309 border: none; 310 margin: 0.25em 0 0.5em; 311 opacity: 0.66; 312} 313 314#menu input { 315 --tint2: hsl(var(--hue2) 50% 90% / 0.1); 316 color: hsl(0 0 100% / 0.5); 317 font-family: 'Asap', sans-serif; 318 font-weight: 300; 319 box-shadow: 0 0 0 1px transparent; 320 border: 1px solid hsl(var(--hue2) 13% 18.5% / 0.5); 321 background: linear-gradient(to bottom, hsl(var(--hue1) 20% 20% / 0.1) 50%, hsl(var(--hue1) 50% 50% / 0.8) 180%); 322 background-size: 100% 300%; 323 background-position: 0% 0%; 324 background-repeat: no-repeat; 325 border-radius: calc(var(--radius) * 0.33333); 326 padding-left: 2.33em; 327 transition: all 0.3s var(--ease); 328 329 &:focus { 330 border-color: hsl(var(--hue1) 20% 70% / 0.5); 331 background-position: 0% 50%; 332 color: hsl(var(--hue1) 20% 80% / 1); 333 } 334 335 &::-moz-placeholder { 336 color: hsl(0 0 100% / 1); 337 } 338 339 &:-ms-input-placeholder { 340 color: hsl(0 0 100% / 1); 341 } 342 343 &::placeholder { 344 color: hsl(0 0 100% / 1); 345 } 346} 347 348#menu label { 349 display: grid; 350 grid-template: 1fr/1fr; 351 margin-bottom: 1em; 352 width: 100%; 353 & > * { 354 grid-area: 1/1; 355 align-self: center; 356 } 357 358 & svg { 359 margin-left: 0.5em; 360 opacity: 0.5; 361 } 362} 363 364#menu .search svg { 365 &:has(+ input:focus) { 366 stroke: hsl(var(--hue1) 60% 70% / 1); 367 } 368} 369 370#menu header { 371 font-size: 0.75rem; 372 font-weight: 300; 373 padding: 0 0.66em; 374} 375 376#menu ul { 377 list-style: none; 378 padding: 0; 379 margin: 0; 380} 381 382#menu li { 383 --item-hue: var(--hue2); 384 position: relative; 385 padding: 0.66em; 386 height: 32px; 387 display: flex; 388 align-items: center; 389 gap: 0.5em; 390 border-radius: calc(var(--radius) * 0.33333); 391 border: 1px solid transparent; 392 transition: all 0.3s ease-in, --item-opacity 0.3s ease-in; 393 background: 394 linear-gradient( 395 90deg in oklch, 396 hsl(var(--item-hue) 29% 13% / var(--item-opacity)), 397 hsl(var(--item-hue) 30% 15% / var(--item-opacity)) 24% 32%, 398 hsl(var(--item-hue) 5% 7% / 0) 95% 399 ) border-box; 400 401 &::after { 402 content: ""; 403 position: absolute; 404 inset: 0; 405 border-radius: inherit; 406 border: inherit; 407 background: 408 linear-gradient( 409 90deg in oklch, 410 hsl(var(--item-hue) 15% 16% / var(--item-opacity)), 411 hsl(var(--item-hue) 40% 24% / var(--item-opacity)) 20% 32%, 412 hsl(var(--item-hue) 2% 12% / 0) 95% 413 ) border-box; 414 -webkit-mask: 415 linear-gradient(transparent), 416 linear-gradient(to right, black, transparent); 417 mask: 418 linear-gradient(transparent), 419 linear-gradient(to right, black, transparent); 420 -webkit-mask-repeat: no-repeat; 421 mask-repeat: no-repeat; 422 -webkit-mask-clip: padding-box, border-box; 423 mask-clip: padding-box, border-box; 424 -webkit-mask-composite: source-out; 425 mask-composite: subtract; 426 } 427 428 &:hover, 429 &.selected, 430 &:hover::after, 431 &.selected::after, 432 &:focus, 433 &:focus::after { 434 --item-opacity: 0.5; 435 transition: all 0.1s ease-out, --item-opacity 0.1s ease-out; 436 color: white; 437 outline: none; 438 } 439 440 &.selected, 441 &.selected::after { 442 -webkit-animation: flash 0.75s ease-out 1 forwards; 443 animation: flash 0.75s ease-out 1 forwards; 444 } 445} 446 447#menu section:nth-of-type(1) li { 448 --item-hue: var(--hue1); 449} 450 451@property --item-opacity { 452 syntax: "<number>"; 453 inherits: false; 454 initial-value: 0; 455} 456 457 458 459 460/* other ui stuff */ 461 462body { 463 background: #08090d; 464 background-image: url(https://assets.codepen.io/13471/abstract-light.jpg), linear-gradient(to right in oklab, hsl(var(--hue2) 50% 75%), hsl(var(--hue1) 50% 75%)); 465 background-size: cover; 466 background-position: center; 467 background-blend-mode: hard-light; 468 padding: 0; 469} 470 471body, #app { 472 min-height: 100svh; 473} 474 475#app { 476 padding: 2svw; 477} 478 479#app > header { 480 display: flex; 481 gap: 0.5em; 482 flex-direction: column; 483} 484 485#app > header h1, 486#app > header p { 487 margin: 0; 488 color: color-mix(in oklab, var(--fg) 70%, hsl(var(--hue1) 50% 50%)); 489} 490 491#app > header p { 492 color: color-mix(in oklab, var(--fg) 40%, hsl(var(--hue2) 50% 50%)); 493} 494 495#app > footer { 496 align-self: end; 497 max-width: calc(96svw - 200px); 498 display: flex; 499 flex-wrap: wrap; 500 gap: 0.5em; 501} 502 503#app > footer h2 { 504 width: 100%; 505 font-size: 1em; 506} 507 508#app > footer [type=range] { 509 --tint: hsl( var(--hue2) 66% 50% ); 510 --tint2: hsl( var(--hue1) 66% 50% ); 511 width: 320px; 512 margin: 0; 513} 514#app > footer #h1 { 515 --tint: hsl( var(--hue1) 66% 50% ); 516 --tint2: hsl( var(--hue1) 66% 50% ); 517 --hue: var(--hue1); 518 width: 320px; 519} 520#app > footer #h2 { 521 --tint: hsl( var(--hue2) 66% 50% ); 522 --tint2: hsl( var(--hue2) 66% 50% ); 523 --hue: var(--hue2); 524 width: 320px; 525} 526 527@media screen and (max-width: 480px) { 528 #app > footer { 529 gap: 1em; 530 } 531} 532 533#app input[type="range"]::-webkit-slider-thumb { 534 box-shadow: 0 1px 2px 1px hsl(var(--hue) 66% 20% / 0.5); 535} 536 537#app input[type="range"]::-moz-range-thumb { 538 box-shadow: 0 1px 2px 1px hsl(var(--hue) 66% 20% / 0.5); 539} 540 541#app input[type="range"]::-ms-thumb { 542 box-shadow: 0 1px 2px 1px hsl(var(--hue) 66% 20% / 0.5); 543} 544 545#menu svg { 546 fill: none; 547 stroke-width: 1; 548 height: 20px; 549} 550 551 552 553@-webkit-keyframes glow { 554 0% { 555 opacity: 0; 556 } 557 3% { 558 opacity: 1; 559 } 560 10% { 561 opacity: 0; 562 } 563 12% { 564 opacity: 0.7; 565 } 566 16% { 567 opacity: 0.3; 568 -webkit-animation-timing-function: var(--ease); 569 animation-timing-function: var(--ease); 570 } 571 100% { 572 opacity: 1; 573 -webkit-animation-timing-function: var(--ease); 574 animation-timing-function: var(--ease); 575 } 576} 577 578 579 580@keyframes glow { 581 0% { 582 opacity: 0; 583 } 584 3% { 585 opacity: 1; 586 } 587 10% { 588 opacity: 0; 589 } 590 12% { 591 opacity: 0.7; 592 } 593 16% { 594 opacity: 0.3; 595 -webkit-animation-timing-function: var(--ease); 596 animation-timing-function: var(--ease); 597 } 598 100% { 599 opacity: 1; 600 -webkit-animation-timing-function: var(--ease); 601 animation-timing-function: var(--ease); 602 } 603} 604 605@-webkit-keyframes glowoff { 606 to { 607 opacity: 0; 608 } 609} 610 611@keyframes glowoff { 612 to { 613 opacity: 0; 614 } 615} 616 617@-webkit-keyframes flash { 618 0% { 619 opacity: 0; 620 --item-opacity: 0; 621 } 622 7% { 623 opacity: 0.5; 624 --item-opacity: 1; 625 } 626 14% { 627 opacity: 0; 628 --item-opacity: 0.5; 629 } 630 21%, 100% { 631 opacity: 1; 632 --item-opacity: 1; 633 } 634} 635 636@keyframes flash { 637 0% { 638 opacity: 0; 639 --item-opacity: 0; 640 } 641 7% { 642 opacity: 0.5; 643 --item-opacity: 1; 644 } 645 14% { 646 opacity: 0; 647 --item-opacity: 0.5; 648 } 649 21%, 100% { 650 opacity: 1; 651 --item-opacity: 1; 652 } 653}:root { 654 /* vars */ 655 656 --hue1: 255; 657 --hue2: 222; 658 --border: 1px; 659 --border-color: hsl(var(--hue2), 12%, 20%); 660 --radius: 22px; 661 662 --ease: cubic-bezier(0.5, 1, 0.89, 1); 663 664} 665 666#menu { 667 visibility: hidden; 668 opacity: 0; 669 pointer-events: none; 670 transition-property: visibility, opacity, filter; 671 transition-duration: 0s, 0.25s, 0.25s; 672 transition-delay: 0.5s, 0s, 0s; 673 transition-timing-function: var(--ease); 674 filter: blur(4px); 675 676 font-family: 'Asap', sans-serif; 677 color: #737985; 678 679 position: fixed; 680 top: 140px; 681 left: 2svw; 682 min-width: 275px; 683 min-height: 275px; 684 border-radius: var(--radius); 685 border: var(--border) solid var(--border-color); 686 padding: 1em; 687 background: linear-gradient(235deg, hsl(var(--hue1) 50% 10% / 0.8), hsl(var(--hue1) 50% 10% / 0) 33%), linear-gradient(45deg , hsl(var(--hue2) 50% 10% / 0.8), hsl(var(--hue2) 50% 10% / 0) 33%), linear-gradient(hsl(220deg 25% 4.8% / 0.66)); 688 -webkit-backdrop-filter: blur(12px); 689 backdrop-filter: blur(12px); 690 box-shadow: hsl(var(--hue2) 50% 2%) 0px 10px 16px -8px, hsl(var(--hue2) 50% 4%) 0px 20px 36px -14px; 691} 692 693#menu:not(.open)::before, 694#menu:not(.open)::after, 695#menu:not(.open) .glow { 696 opacity: 0; 697 pointer-events: none; 698 -webkit-animation: glowoff 0.25s var(--ease) both; 699 animation: glowoff 0.25s var(--ease) both; 700} 701 702 703#menu.open { 704 705 visibility: visible; 706 opacity: 1; 707 pointer-events: auto; 708 transition-delay: 0s; 709 filter: blur(0px); 710 711 &::before, 712 &::after, 713 & .glow, 714 & .shine { 715 -webkit-animation: glow 1s var(--ease) both; 716 animation: glow 1s var(--ease) both; 717 } 718 & .shine { 719 -webkit-animation-delay: 0s; 720 animation-delay: 0s; 721 -webkit-animation-duration: 2s; 722 animation-duration: 2s; 723 } 724 & .glow { 725 -webkit-animation-delay: 0.2s; 726 animation-delay: 0.2s; 727 } 728 & .glow-bright { 729 -webkit-animation-delay: 0.1s; 730 animation-delay: 0.1s; 731 -webkit-animation-duration: 1.5s; 732 animation-duration: 1.5s; 733 } 734 & .shine-bottom { 735 -webkit-animation-delay: 0.1s; 736 animation-delay: 0.1s; 737 -webkit-animation-duration: 1.8s; 738 animation-duration: 1.8s; 739 } 740 & .glow-bottom { 741 -webkit-animation-delay: 0.3s; 742 animation-delay: 0.3s; 743 } 744 & .glow-bright.glow-bottom { 745 -webkit-animation-delay: 0.3s; 746 animation-delay: 0.3s; 747 -webkit-animation-duration: 1.1s; 748 animation-duration: 1.1s; 749 } 750} 751 752#menu .shine, 753#menu .glow { 754 --hue: var(--hue1); 755} 756#menu .shine-bottom, 757#menu .glow-bottom { 758 --hue: var(--hue2); 759 --conic: 135deg; 760} 761 762#menu .shine { 763} 764 765#menu .shine, 766#menu .shine::before, 767#menu .shine::after { 768 769 /* shine is the bright white-ish inner pixel-wide lights */ 770 771 pointer-events: none; 772 773 border-radius: 0; 774 border-top-right-radius: inherit; 775 border-bottom-left-radius: inherit; 776 border: 1px solid transparent; 777 778 width: 75%; 779 height: auto; 780 min-height: 0px; 781 aspect-ratio: 1; 782 display: block; 783 position: absolute; 784 right: calc(var(--border) * -1); 785 top: calc(var(--border) * -1); 786 left: auto; 787 788 z-index: 1; 789 790 --start: 12%; 791 background: conic-gradient( 792 from var(--conic, -45deg) at center in oklch, 793 transparent var(--start,0%), hsl( var(--hue), var(--sat,80%), var(--lit,60%)), transparent var(--end,50%) 794 ) border-box; 795 796 -webkit-mask: 797 linear-gradient(transparent), 798 linear-gradient(black); 799 800 mask: 801 linear-gradient(transparent), 802 linear-gradient(black); 803 -webkit-mask-repeat: no-repeat; 804 mask-repeat: no-repeat; 805 -webkit-mask-clip: padding-box, border-box; 806 mask-clip: padding-box, border-box; 807 -webkit-mask-composite: source-out; 808 mask-composite: subtract; 809 810} 811 812#menu .shine::before, 813#menu .shine::after { 814 content: ""; 815 width: auto; 816 inset: -2px; 817 -webkit-mask: none; 818 mask: none; 819} 820 821#menu .shine::after { 822 z-index: 2; 823 --start: 17%; 824 --end: 33%; 825 background: conic-gradient( 826 from var(--conic, -45deg) at center in oklch, 827 transparent var(--start,0%), hsl( var(--hue), var(--sat,80%), var(--lit,85%)), transparent var(--end,50%) 828 ); 829 830} 831 832#menu .shine-bottom { 833 top: auto; 834 bottom: calc(var(--border) * -1); 835 left: calc(var(--border) * -1); 836 right: auto; 837} 838 839#menu .glow { 840 841 /* glow is the sparse colour bleed with noise */ 842 843 pointer-events: none; 844 845 border-top-right-radius: calc(var(--radius) * 2.5); 846 border-bottom-left-radius: calc(var(--radius) * 2.5); 847 border: calc(var(--radius) * 1.25) solid transparent; 848 inset: calc(var(--radius) * -2); 849 850 width: 75%; 851 height: auto; 852 min-height: 0px; 853 aspect-ratio: 1; 854 display: block; 855 position: absolute; 856 left: auto; 857 bottom: auto; 858 859 -webkit-mask: url('https://assets.codepen.io/13471/noise-base.png'); 860 861 mask: url('https://assets.codepen.io/13471/noise-base.png'); 862 mask-mode: luminance; 863 -webkit-mask-size: 29%; 864 mask-size: 29%; 865 866 opacity: 1; 867 filter: blur(12px) saturate(1.25) brightness(0.5); 868 mix-blend-mode: plus-lighter; 869 z-index: 3; 870 871 &.glow-bottom { 872 inset: calc(var(--radius) * -2); 873 top: auto; 874 right: auto; 875 } 876 877 &::before, 878 &::after { 879 content: ""; 880 position: absolute; 881 inset: 0; 882 border: inherit; 883 border-radius: inherit; 884 background: conic-gradient( 885 from var(--conic, -45deg) at center in oklch, 886 transparent var(--start,0%), hsl( var(--hue), var(--sat,95%), var(--lit,60%)), transparent var(--end,50%) 887 ) border-box; 888 -webkit-mask: 889 linear-gradient(transparent), 890 linear-gradient(black); 891 mask: 892 linear-gradient(transparent), 893 linear-gradient(black); 894 -webkit-mask-repeat: no-repeat; 895 mask-repeat: no-repeat; 896 -webkit-mask-clip: padding-box, border-box; 897 mask-clip: padding-box, border-box; 898 -webkit-mask-composite: source-out; 899 mask-composite: subtract; 900 filter: saturate(2) brightness(1); 901 902 } 903 904 &::after { 905 --lit: 70%; 906 --sat: 100%; 907 --start: 15%; 908 --end: 35%; 909 border-width: calc(var(--radius) * 1.75); 910 border-radius: calc(var(--radius) * 2.75); 911 inset: calc(var(--radius) * -0.25); 912 z-index: 4; 913 opacity: 0.75; 914 } 915 &.glow-bottom::after { 916 } 917 918} 919 920#menu .glow-bright { 921 922 /* glow-bright is a sharper colour outline to accentuate the shine */ 923 924 --lit: 80%; 925 --sat: 100%; 926 --start: 13%; 927 --end: 37%; 928 929 border-width: 5px; 930 border-radius: calc(var(--radius) + 2px); 931 inset: -7px; 932 left: auto; 933 filter: blur(2px) brightness(0.66); 934 935 &::after { 936 content: none; 937 } 938 939 &.glow-bottom { 940 inset: -7px; 941 right: auto; 942 top: auto; 943 } 944} 945 946#menu .inner, 947#menu section { 948 display: flex; 949 flex-direction: column; 950 gap: 0.5em; 951} 952 953#menu .inner { 954 font-size: 0.875rem; 955} 956 957#menu hr { 958 width: 100%; 959 height: 0.5px; 960 background: var(--border-color); 961 border: none; 962 margin: 0.25em 0 0.5em; 963 opacity: 0.66; 964} 965 966#menu input { 967 --tint2: hsl(var(--hue2) 50% 90% / 0.1); 968 color: hsl(0 0 100% / 0.5); 969 font-family: 'Asap', sans-serif; 970 font-weight: 300; 971 box-shadow: 0 0 0 1px transparent; 972 border: 1px solid hsl(var(--hue2) 13% 18.5% / 0.5); 973 background: linear-gradient(to bottom, hsl(var(--hue1) 20% 20% / 0.1) 50%, hsl(var(--hue1) 50% 50% / 0.8) 180%); 974 background-size: 100% 300%; 975 background-position: 0% 0%; 976 background-repeat: no-repeat; 977 border-radius: calc(var(--radius) * 0.33333); 978 padding-left: 2.33em; 979 transition: all 0.3s var(--ease); 980 981 &:focus { 982 border-color: hsl(var(--hue1) 20% 70% / 0.5); 983 background-position: 0% 50%; 984 color: hsl(var(--hue1) 20% 80% / 1); 985 } 986 987 &::-moz-placeholder { 988 color: hsl(0 0 100% / 1); 989 } 990 991 &:-ms-input-placeholder { 992 color: hsl(0 0 100% / 1); 993 } 994 995 &::placeholder { 996 color: hsl(0 0 100% / 1); 997 } 998} 999 1000#menu label { 1001 display: grid; 1002 grid-template: 1fr/1fr; 1003 margin-bottom: 1em; 1004 width: 100%; 1005 & > * { 1006 grid-area: 1/1; 1007 align-self: center; 1008 } 1009 1010 & svg { 1011 margin-left: 0.5em; 1012 opacity: 0.5; 1013 } 1014} 1015 1016#menu .search svg { 1017 &:has(+ input:focus) { 1018 stroke: hsl(var(--hue1) 60% 70% / 1); 1019 } 1020} 1021 1022#menu header { 1023 font-size: 0.75rem; 1024 font-weight: 300; 1025 padding: 0 0.66em; 1026} 1027 1028#menu ul { 1029 list-style: none; 1030 padding: 0; 1031 margin: 0; 1032} 1033 1034#menu li { 1035 --item-hue: var(--hue2); 1036 position: relative; 1037 padding: 0.66em; 1038 height: 32px; 1039 display: flex; 1040 align-items: center; 1041 gap: 0.5em; 1042 border-radius: calc(var(--radius) * 0.33333); 1043 border: 1px solid transparent; 1044 transition: all 0.3s ease-in, --item-opacity 0.3s ease-in; 1045 background: 1046 linear-gradient( 1047 90deg in oklch, 1048 hsl(var(--item-hue) 29% 13% / var(--item-opacity)), 1049 hsl(var(--item-hue) 30% 15% / var(--item-opacity)) 24% 32%, 1050 hsl(var(--item-hue) 5% 7% / 0) 95% 1051 ) border-box; 1052 1053 &::after { 1054 content: ""; 1055 position: absolute; 1056 inset: 0; 1057 border-radius: inherit; 1058 border: inherit; 1059 background: 1060 linear-gradient( 1061 90deg in oklch, 1062 hsl(var(--item-hue) 15% 16% / var(--item-opacity)), 1063 hsl(var(--item-hue) 40% 24% / var(--item-opacity)) 20% 32%, 1064 hsl(var(--item-hue) 2% 12% / 0) 95% 1065 ) border-box; 1066 -webkit-mask: 1067 linear-gradient(transparent), 1068 linear-gradient(to right, black, transparent); 1069 mask: 1070 linear-gradient(transparent), 1071 linear-gradient(to right, black, transparent); 1072 -webkit-mask-repeat: no-repeat; 1073 mask-repeat: no-repeat; 1074 -webkit-mask-clip: padding-box, border-box; 1075 mask-clip: padding-box, border-box; 1076 -webkit-mask-composite: source-out; 1077 mask-composite: subtract; 1078 } 1079 1080 &:hover, 1081 &.selected, 1082 &:hover::after, 1083 &.selected::after, 1084 &:focus, 1085 &:focus::after { 1086 --item-opacity: 0.5; 1087 transition: all 0.1s ease-out, --item-opacity 0.1s ease-out; 1088 color: white; 1089 outline: none; 1090 } 1091 1092 &.selected, 1093 &.selected::after { 1094 -webkit-animation: flash 0.75s ease-out 1 forwards; 1095 animation: flash 0.75s ease-out 1 forwards; 1096 } 1097} 1098 1099#menu section:nth-of-type(1) li { 1100 --item-hue: var(--hue1); 1101} 1102 1103@property --item-opacity { 1104 syntax: "<number>"; 1105 inherits: false; 1106 initial-value: 0; 1107} 1108 1109 1110 1111 1112/* other ui stuff */ 1113 1114body { 1115 background: #08090d; 1116 background-image: url(https://assets.codepen.io/13471/abstract-light.jpg), linear-gradient(to right in oklab, hsl(var(--hue2) 50% 75%), hsl(var(--hue1) 50% 75%)); 1117 background-size: cover; 1118 background-position: center; 1119 background-blend-mode: hard-light; 1120 padding: 0; 1121} 1122 1123body, #app { 1124 min-height: 100svh; 1125} 1126 1127#app { 1128 padding: 2svw; 1129} 1130 1131#app > header { 1132 display: flex; 1133 gap: 0.5em; 1134 flex-direction: column; 1135} 1136 1137#app > header h1, 1138#app > header p { 1139 margin: 0; 1140 color: color-mix(in oklab, var(--fg) 70%, hsl(var(--hue1) 50% 50%)); 1141} 1142 1143#app > header p { 1144 color: color-mix(in oklab, var(--fg) 40%, hsl(var(--hue2) 50% 50%)); 1145} 1146 1147#app > footer { 1148 align-self: end; 1149 max-width: calc(96svw - 200px); 1150 display: flex; 1151 flex-wrap: wrap; 1152 gap: 0.5em; 1153} 1154 1155#app > footer h2 { 1156 width: 100%; 1157 font-size: 1em; 1158} 1159 1160#app > footer [type=range] { 1161 --tint: hsl( var(--hue2) 66% 50% ); 1162 --tint2: hsl( var(--hue1) 66% 50% ); 1163 width: 320px; 1164 margin: 0; 1165} 1166#app > footer #h1 { 1167 --tint: hsl( var(--hue1) 66% 50% ); 1168 --tint2: hsl( var(--hue1) 66% 50% ); 1169 --hue: var(--hue1); 1170 width: 320px; 1171} 1172#app > footer #h2 { 1173 --tint: hsl( var(--hue2) 66% 50% ); 1174 --tint2: hsl( var(--hue2) 66% 50% ); 1175 --hue: var(--hue2); 1176 width: 320px; 1177} 1178 1179@media screen and (max-width: 480px) { 1180 #app > footer { 1181 gap: 1em; 1182 } 1183} 1184 1185#app input[type="range"]::-webkit-slider-thumb { 1186 box-shadow: 0 1px 2px 1px hsl(var(--hue) 66% 20% / 0.5); 1187} 1188 1189#app input[type="range"]::-moz-range-thumb { 1190 box-shadow: 0 1px 2px 1px hsl(var(--hue) 66% 20% / 0.5); 1191} 1192 1193#app input[type="range"]::-ms-thumb { 1194 box-shadow: 0 1px 2px 1px hsl(var(--hue) 66% 20% / 0.5); 1195} 1196 1197#menu svg { 1198 fill: none; 1199 stroke-width: 1; 1200 height: 20px; 1201} 1202 1203 1204 1205@-webkit-keyframes glow { 1206 0% { 1207 opacity: 0; 1208 } 1209 3% { 1210 opacity: 1; 1211 } 1212 10% { 1213 opacity: 0; 1214 } 1215 12% { 1216 opacity: 0.7; 1217 } 1218 16% { 1219 opacity: 0.3; 1220 -webkit-animation-timing-function: var(--ease); 1221 animation-timing-function: var(--ease); 1222 } 1223 100% { 1224 opacity: 1; 1225 -webkit-animation-timing-function: var(--ease); 1226 animation-timing-function: var(--ease); 1227 } 1228} 1229 1230 1231 1232@keyframes glow { 1233 0% { 1234 opacity: 0; 1235 } 1236 3% { 1237 opacity: 1; 1238 } 1239 10% { 1240 opacity: 0; 1241 } 1242 12% { 1243 opacity: 0.7; 1244 } 1245 16% { 1246 opacity: 0.3; 1247 -webkit-animation-timing-function: var(--ease); 1248 animation-timing-function: var(--ease); 1249 } 1250 100% { 1251 opacity: 1; 1252 -webkit-animation-timing-function: var(--ease); 1253 animation-timing-function: var(--ease); 1254 } 1255} 1256 1257@-webkit-keyframes glowoff { 1258 to { 1259 opacity: 0; 1260 } 1261} 1262 1263@keyframes glowoff { 1264 to { 1265 opacity: 0; 1266 } 1267} 1268 1269@-webkit-keyframes flash { 1270 0% { 1271 opacity: 0; 1272 --item-opacity: 0; 1273 } 1274 7% { 1275 opacity: 0.5; 1276 --item-opacity: 1; 1277 } 1278 14% { 1279 opacity: 0; 1280 --item-opacity: 0.5; 1281 } 1282 21%, 100% { 1283 opacity: 1; 1284 --item-opacity: 1; 1285 } 1286} 1287 1288@keyframes flash { 1289 0% { 1290 opacity: 0; 1291 --item-opacity: 0; 1292 } 1293 7% { 1294 opacity: 0.5; 1295 --item-opacity: 1; 1296 } 1297 14% { 1298 opacity: 0; 1299 --item-opacity: 0.5; 1300 } 1301 21%, 100% { 1302 opacity: 1; 1303 --item-opacity: 1; 1304 } 1305} 1306
The CSS brings the futuristic look to life using:
Glassmorphism: Achieved with backdrop-filter: blur() and semi-transparent backgrounds (rgba), giving a frosted glass effect.
Neon Glow: Done with box-shadow and text shadows in bright hues like cyan or magenta.
Animations and Transitions: Smooth hover transitions using transform, scale, and transition properties to make the menu feel responsive and alive.
Positioning: The context menu is positioned absolutely, allowing JavaScript to dynamically move it to the cursor’s location on right-click.
Overall, CSS gives the design its aesthetic and animation appeal, turning a simple menu into a glowing, interactive element.
Javascipt Code
1const $menu = document.getElementById('menu');
2const $li = $menu.querySelectorAll('li');
3const $hue1 = document.querySelector('#h1');
4const $hue2 = document.querySelector('#h2');
5
6let cleanTimer;
7
8document.addEventListener("contextmenu", (event) => {
9
10 const menuBox = $menu.getBoundingClientRect();
11 const bodyBox = { width: window.innerWidth, height: window.innerHeight }
12 const target = { x: event.clientX, y: event.clientY }
13 const padding = { x: 30, y: 20 }
14
15 const hitX = target.x + menuBox.width >= bodyBox.width - padding.x;
16 const hitY = target.y + menuBox.height >= bodyBox.height - padding.y;
17
18 if ( hitX ) {
19 target.x = bodyBox.width - menuBox.width - padding.x;
20 }
21
22 if ( hitY ) {
23 target.y = bodyBox.height - menuBox.height - padding.y;
24 }
25
26 const $target = event.target;
27 const isMenu = $menu.contains( $target );
28 event.preventDefault();
29
30 if( !isMenu ) {
31 $menu.style.left = target.x + 'px';
32 $menu.style.top = target.y + 'px';
33 $menu.classList.add('open');
34 clearTimeout(cleanTimer);
35 }
36
37});
38
39document.addEventListener('pointerdown', (event) => {
40 const $target = event.target;
41 const isMenu = $menu.contains( $target );
42 const isSlider = $target.matches( 'input' );
43
44 if( !isMenu && !isSlider ) {
45 $menu.classList.remove('open');
46 cleanTimer = setTimeout(() => {
47 $menu.querySelector('input').value = '';
48 $li.forEach($el => {
49 $el.classList.remove('selected');
50 })
51 }, 200);
52 } else if (isMenu) {
53 $li.forEach($el => {
54 $el.classList.remove('selected');
55 })
56 if ( $target.matches('li') ) {
57 $target.classList.add('selected');
58 }
59 }
60});
61
62$hue1.addEventListener( 'input', (event) => {
63 requestAnimationFrame(() => {
64 document.body.style.setProperty('--hue1', event.target.value );
65 $menu.classList.add('open');
66 })
67});
68$hue2.addEventListener( 'input', (event) => {
69 requestAnimationFrame(() => {
70 document.body.style.setProperty('--hue2', event.target.value );
71 $menu.classList.add('open');
72 })
73});
74
75const rand1 = 120 + Math.floor(Math.random() * 240);
76const rand2 = rand1 - 80 + (Math.floor(Math.random() * 60) - 30);
77$hue1.value = rand1;
78$hue2.value = rand2;
79document.body.style.setProperty('--hue1', rand1 );
80document.body.style.setProperty('--hue2', rand2 );
The JavaScript handles all interactivity and logic:
It disables the default browser context menu using event.preventDefault().
Tracks the cursor position on right-click (contextmenu event) and positions the custom menu (menu.style.top and menu.style.left) accordingly.
Adds click listeners to close the menu when clicking anywhere else on the page.
Optionally, you can link actions (e.g., Copy, Paste) to menu items by attaching custom event handlers.
In essence, JavaScript is responsible for making the custom menu behave like a real context menu — dynamic, responsive, and intuitive.
