Skip to main content
gamedev.net
🔒 Locked

Javascript question

Started by Stibmit Sep 27, 2002 at 7:11 PM 5 replies 600+ views
Original Post
Stibmit
Stibmit
Can anyone tell me how to slap on a piece of &#106avascript that, upon entering a webpage, waits a few seconds before re-directing them to a different page? Thanks for any help you can give me.

If you keep on dividing (man) you end up as a collection of monkeys throwing nuts at each other out of separate trees." - Merlyn, The Once and Future King (T.H. White)
If you keep on dividing (man) you end up as a collection of monkeys throwing nuts at each other out of separate trees." - Merlyn, The Once and Future King (T.H. White)
Alimonster
Alimonster
You can use the meta tag with an http-equiv:

<meta http-equiv="refresh" content="14;URL=someotherpage.htm">

Not &#106avascript but it works. (The number [here, 14] is the seconds to wait until the new page is loaded.)<br><br><SPAN CLASS=editedby>[edited by - Alimonster &#111;n September 27, 2002 8:22:08 PM]</SPAN> </div> <footer class="mt-auto pt-3 border-t border-gray-100 dark:border-gray-700"> <div class="flex items-center justify-between flex-wrap gap-2 text-xs"> <div class="hidden sm:flex items-center gap-2 text-gray-400 dark:text-gray-500"> <time title="September 27, 2002 07:21 PM">Sep 27, 2002 at 7:21 PM</time> </div> <div class="flex items-center gap-1 sm:gap-2 flex-wrap text-xs"> <div class="reactions-group flex items-center gap-1" id="reactions-1630108"> <button type="button" hx-post="/forums/post/1630108/react/" hx-vals='{"type": "like"}' hx-target="#reactions-1630108" hx-swap="outerHTML" title="Like this post" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-rose-500 dark:hover:text-rose-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/> </svg> <span class="hidden sm:inline">Like</span> </button> <button type="button" hx-post="/forums/post/1630108/react/" hx-vals='{"type": "helpful"}' hx-target="#reactions-1630108" hx-swap="outerHTML" title="This is helpful" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-emerald-500 dark:hover:text-emerald-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/> </svg> <span class="hidden sm:inline">Helpful</span> </button> <button type="button" hx-post="/forums/post/1630108/react/" hx-vals='{"type": "thanks"}' hx-target="#reactions-1630108" hx-swap="outerHTML" title="Thanks for this" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-amber-500 dark:hover:text-amber-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/> </svg> <span class="hidden sm:inline">Thanks</span> </button> </div> <span class="hidden sm:inline text-gray-200 dark:text-gray-700 mx-1">|</span> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="quotePost(1630108, 'Alimonster')" title="Quote this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg> <span class="hidden sm:inline">Quote</span> </button> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="copyPostUrl(1630108)" title="Copy link to this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg> <span class="hidden sm:inline">Link</span> </button> </div> </div> </footer> </div> </div> </div> </article> <article id="post-1630144" data-author-name="Stibmit" class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col min-h-[200px]"> <div class="p-6 flex-1 flex flex-col"> <div class="flex gap-4 flex-1"> <div class="hidden sm:block w-28 flex-shrink-0 text-center"> <a href="/profile/19799-stibmit" class="inline-block" aria-label="Stibmit's profile"> <span class="gdx-avatar w-12 h-12 rounded-full "> <span class="gdx-avatar__img gdx-avatar__fallback rounded-full cursor-pointer text-lg" role="img" aria-label="Stibmit" data-user-tooltip data-member-id="19799" aria-haspopup="dialog"> <span aria-hidden="true">S</span> </span> </span> </a> <a href="/profile/19799-stibmit" class="mt-2 block text-sm font-medium text-gray-900 dark:text-white hover:text-sky-600 truncate" data-tooltip-target="tooltip-author-19799"> Stibmit </a> <div class="flex flex-wrap justify-center gap-1 mt-1"> <span class="inline-flex items-center px-1.5 py-0.5 text-xs font-medium bg-sky-100 dark:bg-sky-900/30 text-sky-700 dark:text-sky-300 rounded" title="Topic Author">Author</span> </div> <p class="mt-1 text-xs text-gray-500 dark:text-gray-400 flex items-center justify-center gap-1"> <svg class="w-3 h-3 text-yellow-500" fill="currentColor" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg> 28 </p> </div> <div class="flex-1 min-w-0 flex flex-col"> <div class="sm:hidden mb-4"> <div class="flex items-center gap-1.5"> <span class="gdx-avatar w-6 h-6 rounded-full ring-2 ring-white dark:ring-gray-800"> <img src="/static/media/user/generic-user.png" alt="Stibmit" class="gdx-avatar__img rounded-full cursor-pointer object-cover" data-user-tooltip data-member-id="19799"> </span> <div> <a href="/profile/19799-stibmit" class="font-medium text-gray-900 dark:text-white hover:text-purple-600 text-xs" data-user-tooltip data-member-id="19799"> Stibmit </a> <div class="text-[10px] text-gray-500 dark:text-gray-400"> <time>Sep 27, 2002 at 8:05 PM</time> </div> </div> </div> </div> <div id="post-content-1630144" class="prose dark:prose-invert max-w-none flex-1 pb-3"> I did a short test where I created a folder and two webpages &#111;n my desktop, and tried doing what you suggested... Page A refreshes every 5 seconds, like it is supposed to, except that it doesn''t switch over to Page B.<br><br>Here''s what I have<br><br> <!--STARTSCRIPT--><BR><DIV CLASS=source><pre> <br>&lt;html&gt;<br><br>&lt;body&gt;<br><br>Page 1.<br><br>&lt;meta http-equiv=<font color="darkred">"refresh"</font> content=<font color="darkred">"5"</font>; URL=<font color="darkred">"C:\Documents and Settings\Freddie\Desktop\test2.html"</font>&gt;<br><br>&lt;/body&gt;<br>&lt;/html&gt;<br> </pre></DIV><!--ENDSCRIPT--> <br><br><br><br><br><i>If you keep &#111;n dividing (man) you end up as a collection of monkeys throwing nuts at each other out of separate trees." - Merlyn, The &#79;nce and Future King (T.H. White) </div> <footer class="mt-auto pt-3 border-t border-gray-100 dark:border-gray-700"> <div class="mb-3 forum-signature text-gray-400 dark:text-gray-500"><i>If you keep on dividing (man) you end up as a collection of monkeys throwing nuts at each other out of separate trees." - Merlyn, The Once and Future King (T.H. White)</i></div> <div class="flex items-center justify-between flex-wrap gap-2 text-xs"> <div class="hidden sm:flex items-center gap-2 text-gray-400 dark:text-gray-500"> <time title="September 27, 2002 08:05 PM">Sep 27, 2002 at 8:05 PM</time> </div> <div class="flex items-center gap-1 sm:gap-2 flex-wrap text-xs"> <div class="reactions-group flex items-center gap-1" id="reactions-1630144"> <button type="button" hx-post="/forums/post/1630144/react/" hx-vals='{"type": "like"}' hx-target="#reactions-1630144" hx-swap="outerHTML" title="Like this post" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-rose-500 dark:hover:text-rose-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/> </svg> <span class="hidden sm:inline">Like</span> </button> <button type="button" hx-post="/forums/post/1630144/react/" hx-vals='{"type": "helpful"}' hx-target="#reactions-1630144" hx-swap="outerHTML" title="This is helpful" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-emerald-500 dark:hover:text-emerald-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/> </svg> <span class="hidden sm:inline">Helpful</span> </button> <button type="button" hx-post="/forums/post/1630144/react/" hx-vals='{"type": "thanks"}' hx-target="#reactions-1630144" hx-swap="outerHTML" title="Thanks for this" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-amber-500 dark:hover:text-amber-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/> </svg> <span class="hidden sm:inline">Thanks</span> </button> </div> <span class="hidden sm:inline text-gray-200 dark:text-gray-700 mx-1">|</span> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="quotePost(1630144, 'Stibmit')" title="Quote this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg> <span class="hidden sm:inline">Quote</span> </button> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="copyPostUrl(1630144)" title="Copy link to this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg> <span class="hidden sm:inline">Link</span> </button> </div> </div> </footer> </div> </div> </div> </article> <article id="post-1630201" data-author-name="Stibmit" class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col min-h-[200px]"> <div class="p-6 flex-1 flex flex-col"> <div class="flex gap-4 flex-1"> <div class="hidden sm:block w-28 flex-shrink-0 text-center"> <a href="/profile/19799-stibmit" class="inline-block" aria-label="Stibmit's profile"> <span class="gdx-avatar w-12 h-12 rounded-full "> <span class="gdx-avatar__img gdx-avatar__fallback rounded-full cursor-pointer text-lg" role="img" aria-label="Stibmit" data-user-tooltip data-member-id="19799" aria-haspopup="dialog"> <span aria-hidden="true">S</span> </span> </span> </a> <a href="/profile/19799-stibmit" class="mt-2 block text-sm font-medium text-gray-900 dark:text-white hover:text-sky-600 truncate" data-tooltip-target="tooltip-author-19799"> Stibmit </a> <div class="flex flex-wrap justify-center gap-1 mt-1"> <span class="inline-flex items-center px-1.5 py-0.5 text-xs font-medium bg-sky-100 dark:bg-sky-900/30 text-sky-700 dark:text-sky-300 rounded" title="Topic Author">Author</span> </div> <p class="mt-1 text-xs text-gray-500 dark:text-gray-400 flex items-center justify-center gap-1"> <svg class="w-3 h-3 text-yellow-500" fill="currentColor" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg> 28 </p> </div> <div class="flex-1 min-w-0 flex flex-col"> <div class="sm:hidden mb-4"> <div class="flex items-center gap-1.5"> <span class="gdx-avatar w-6 h-6 rounded-full ring-2 ring-white dark:ring-gray-800"> <img src="/static/media/user/generic-user.png" alt="Stibmit" class="gdx-avatar__img rounded-full cursor-pointer object-cover" data-user-tooltip data-member-id="19799"> </span> <div> <a href="/profile/19799-stibmit" class="font-medium text-gray-900 dark:text-white hover:text-purple-600 text-xs" data-user-tooltip data-member-id="19799"> Stibmit </a> <div class="text-[10px] text-gray-500 dark:text-gray-400"> <time>Sep 27, 2002 at 9:15 PM</time> </div> </div> </div> </div> <div id="post-content-1630201" class="prose dark:prose-invert max-w-none flex-1 pb-3"> Wow, that's weird. I thought you had made a typo <img src="tongue.gif" width=15 height=15 align=middle><br><br> Anyways... there's still a problem. After opening Page 1, it refreshes, but when it refreshes the Url (at least in the address bar) doesn't change, and the page becomes blank (I had printed Page 1 &#111;n page1 and Page 2 &#111;n page2 so I would know.)<br><br> It looks like this now:<br><br> <!--STARTSCRIPT--><BR><DIV CLASS=source><pre> <br>&lt;html&gt;<br><br>&lt;body&gt;<br><br>Page 1.<br><br>&lt;meta http-equiv=<font color="darkred">"refresh"</font> content=<font color="darkred">"5;URL=C:\Documents and Settings\Freddie\Desktop\test2.html"</font>&gt; <br><br>&lt;/body&gt;<br>&lt;/html&gt;<br> </pre></DIV><!--ENDSCRIPT--><br><br> Thanks again for taking the time to help me. <br><br><br><br><br><i>If you keep &#111;n dividing (man) you end up as a collection of monkeys throwing nuts at each other out of separate trees." - Merlyn, The &#79;nce and Future King (T.H. White)<br><br><SPAN CLASS=editedby>[edited by - Stibmit &#111;n September 27, 2002 10:15:35 PM]</SPAN> </i> </div> <footer class="mt-auto pt-3 border-t border-gray-100 dark:border-gray-700"> <div class="mb-3 forum-signature text-gray-400 dark:text-gray-500"><i>If you keep on dividing (man) you end up as a collection of monkeys throwing nuts at each other out of separate trees." - Merlyn, The Once and Future King (T.H. White)</i></div> <div class="flex items-center justify-between flex-wrap gap-2 text-xs"> <div class="hidden sm:flex items-center gap-2 text-gray-400 dark:text-gray-500"> <time title="September 27, 2002 09:15 PM">Sep 27, 2002 at 9:15 PM</time> </div> <div class="flex items-center gap-1 sm:gap-2 flex-wrap text-xs"> <div class="reactions-group flex items-center gap-1" id="reactions-1630201"> <button type="button" hx-post="/forums/post/1630201/react/" hx-vals='{"type": "like"}' hx-target="#reactions-1630201" hx-swap="outerHTML" title="Like this post" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-rose-500 dark:hover:text-rose-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/> </svg> <span class="hidden sm:inline">Like</span> </button> <button type="button" hx-post="/forums/post/1630201/react/" hx-vals='{"type": "helpful"}' hx-target="#reactions-1630201" hx-swap="outerHTML" title="This is helpful" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-emerald-500 dark:hover:text-emerald-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/> </svg> <span class="hidden sm:inline">Helpful</span> </button> <button type="button" hx-post="/forums/post/1630201/react/" hx-vals='{"type": "thanks"}' hx-target="#reactions-1630201" hx-swap="outerHTML" title="Thanks for this" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-amber-500 dark:hover:text-amber-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/> </svg> <span class="hidden sm:inline">Thanks</span> </button> </div> <span class="hidden sm:inline text-gray-200 dark:text-gray-700 mx-1">|</span> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="quotePost(1630201, 'Stibmit')" title="Quote this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg> <span class="hidden sm:inline">Quote</span> </button> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="copyPostUrl(1630201)" title="Copy link to this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg> <span class="hidden sm:inline">Link</span> </button> </div> </div> </footer> </div> </div> </div> </article> <article id="post-1630503" data-author-name="Kylotan" class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col min-h-[200px]"> <div class="p-6 flex-1 flex flex-col"> <div class="flex gap-4 flex-1"> <div class="hidden sm:block w-28 flex-shrink-0 text-center"> <a href="/profile/2996-kylotan" class="inline-block" aria-label="Kylotan's profile"> <span class="gdx-avatar w-12 h-12 rounded-full "> <img src="https://uploads.gamedev.net/monthly_2018_04/loc_wizard_rgb.thumb.png.a1df5717c00039dbef23f4abeb5d4c84.png" alt="Kylotan" class="gdx-avatar__img rounded-full cursor-pointer" data-user-tooltip data-member-id="2996" aria-haspopup="dialog"> </span> </a> <a href="/profile/2996-kylotan" class="mt-2 block text-sm font-medium text-gray-900 dark:text-white hover:text-sky-600 truncate" data-tooltip-target="tooltip-author-2996"> Kylotan </a> <div class="flex flex-wrap justify-center gap-1 mt-1"> <span class="inline-flex items-center px-1.5 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded" title="Moderator">Mod</span> </div> <p class="mt-1 text-xs text-gray-500 dark:text-gray-400 flex items-center justify-center gap-1"> <svg class="w-3 h-3 text-yellow-500" fill="currentColor" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg> 3,487 </p> </div> <div class="flex-1 min-w-0 flex flex-col"> <div class="sm:hidden mb-4"> <div class="flex items-center gap-1.5"> <span class="gdx-avatar w-6 h-6 rounded-full ring-2 ring-white dark:ring-gray-800"> <img src="https://uploads.gamedev.net/monthly_2018_04/loc_wizard_rgb.thumb.png.a1df5717c00039dbef23f4abeb5d4c84.png" alt="Kylotan" class="gdx-avatar__img rounded-full cursor-pointer object-cover" data-user-tooltip data-member-id="2996"> </span> <div> <a href="/profile/2996-kylotan" class="font-medium text-gray-900 dark:text-white hover:text-purple-600 text-xs" data-user-tooltip data-member-id="2996"> Kylotan </a> <div class="text-[10px] text-gray-500 dark:text-gray-400"> <time>Sep 28, 2002 at 9:19 AM</time> </div> </div> </div> </div> <div id="post-content-1630503" class="prose dark:prose-invert max-w-none flex-1 pb-3"> Funny how the original poster asked for &#106avascript, and no-one posted any <img src="smile.gif" width=15 height=15 align=middle><br><br>Something like this should do it:<br> <!--STARTSCRIPT--><BR><DIV CLASS=source><pre> <br>&lt;head&gt;<br>&lt;script type=<font color="darkred">"text/javascript"</font>&gt;<br>function redirect() {<br> window.location = <font color="darkred">"newURL.html"</font><br>}<br>&lt;/script&gt;<br>&lt;/head&gt;<br><br>&lt;body onload=<font color="darkred">"setTimeout(redirect(), 5000);"</font>&gt;<br>&lt;--- Rest of your page goes here... <font color="gray">//--&gt;<br></font><br> </pre></DIV><!--ENDSCRIPT--><br><br>That basically says, "once the page has loaded, wait 5000 milliseconds before calling the function ''redirect()'', which changes the window to the specified URL."<br><br>Btw, if you use meta tags instead, they should really go in the head, not the body.<br><br>Also bear in mind that you can''t necessarily control what the browser displays in the URL bar accurately. That''s beyond the scope of tags or &#106avascript and it''s largely up to the browser designers how it reacts to redirection. <br><br><small>[ <a href="http://www.dinkumware.com/vc_fixes.html">MSVC Fixes</a> | <a href="http://www.sgi.com/tech/stl/">STL</a> | <a href="http://www.libsdl.org/index.html">SDL</a> | <a href="http://www.gameai.com/ai.html">Game AI</a> | <a href="http://www.ecst.csuchico.edu/%7Ebeej/guide/net/">Sockets</a> | <a href="http://www.parashift.com/c++-faq-lite/"><span class="whitespace-nowrap">C++</span> Faq Lite</a> | <a href="http://www.boost.org">Boost</a> | <a href="http://www.tuxedo.org/~esr/faqs/smart-questions.html">Asking Questions</a> | <a href="http://www.gamedev.net/reference/programming/features/orgfiles/">Organising code files</a> | <a href="http://pages.eidosnet.co.uk/kylotan/downloads.html">My stuff</a> ]</small> </div> <footer class="mt-auto pt-3 border-t border-gray-100 dark:border-gray-700"> <div class="flex items-center justify-between flex-wrap gap-2 text-xs"> <div class="hidden sm:flex items-center gap-2 text-gray-400 dark:text-gray-500"> <time title="September 28, 2002 09:19 AM">Sep 28, 2002 at 9:19 AM</time> </div> <div class="flex items-center gap-1 sm:gap-2 flex-wrap text-xs"> <div class="reactions-group flex items-center gap-1" id="reactions-1630503"> <button type="button" hx-post="/forums/post/1630503/react/" hx-vals='{"type": "like"}' hx-target="#reactions-1630503" hx-swap="outerHTML" title="Like this post" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-rose-500 dark:hover:text-rose-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/> </svg> <span class="hidden sm:inline">Like</span> </button> <button type="button" hx-post="/forums/post/1630503/react/" hx-vals='{"type": "helpful"}' hx-target="#reactions-1630503" hx-swap="outerHTML" title="This is helpful" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-emerald-500 dark:hover:text-emerald-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/> </svg> <span class="hidden sm:inline">Helpful</span> </button> <button type="button" hx-post="/forums/post/1630503/react/" hx-vals='{"type": "thanks"}' hx-target="#reactions-1630503" hx-swap="outerHTML" title="Thanks for this" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-amber-500 dark:hover:text-amber-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/> </svg> <span class="hidden sm:inline">Thanks</span> </button> </div> <span class="hidden sm:inline text-gray-200 dark:text-gray-700 mx-1">|</span> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="quotePost(1630503, 'Kylotan')" title="Quote this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg> <span class="hidden sm:inline">Quote</span> </button> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="copyPostUrl(1630503)" title="Copy link to this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg> <span class="hidden sm:inline">Link</span> </button> </div> </div> </footer> </div> </div> </div> </article> <article id="post-1630628" data-author-name="Stibmit" class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col min-h-[200px]"> <div class="p-6 flex-1 flex flex-col"> <div class="flex gap-4 flex-1"> <div class="hidden sm:block w-28 flex-shrink-0 text-center"> <a href="/profile/19799-stibmit" class="inline-block" aria-label="Stibmit's profile"> <span class="gdx-avatar w-12 h-12 rounded-full "> <span class="gdx-avatar__img gdx-avatar__fallback rounded-full cursor-pointer text-lg" role="img" aria-label="Stibmit" data-user-tooltip data-member-id="19799" aria-haspopup="dialog"> <span aria-hidden="true">S</span> </span> </span> </a> <a href="/profile/19799-stibmit" class="mt-2 block text-sm font-medium text-gray-900 dark:text-white hover:text-sky-600 truncate" data-tooltip-target="tooltip-author-19799"> Stibmit </a> <div class="flex flex-wrap justify-center gap-1 mt-1"> <span class="inline-flex items-center px-1.5 py-0.5 text-xs font-medium bg-sky-100 dark:bg-sky-900/30 text-sky-700 dark:text-sky-300 rounded" title="Topic Author">Author</span> </div> <p class="mt-1 text-xs text-gray-500 dark:text-gray-400 flex items-center justify-center gap-1"> <svg class="w-3 h-3 text-yellow-500" fill="currentColor" viewBox="0 0 20 20"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg> 28 </p> </div> <div class="flex-1 min-w-0 flex flex-col"> <div class="sm:hidden mb-4"> <div class="flex items-center gap-1.5"> <span class="gdx-avatar w-6 h-6 rounded-full ring-2 ring-white dark:ring-gray-800"> <img src="/static/media/user/generic-user.png" alt="Stibmit" class="gdx-avatar__img rounded-full cursor-pointer object-cover" data-user-tooltip data-member-id="19799"> </span> <div> <a href="/profile/19799-stibmit" class="font-medium text-gray-900 dark:text-white hover:text-purple-600 text-xs" data-user-tooltip data-member-id="19799"> Stibmit </a> <div class="text-[10px] text-gray-500 dark:text-gray-400"> <time>Sep 28, 2002 at 11:49 AM</time> </div> </div> </div> </div> <div id="post-content-1630628" class="prose dark:prose-invert max-w-none flex-1 pb-3"> Thanks a lot, it works beautifully. </div> <footer class="mt-auto pt-3 border-t border-gray-100 dark:border-gray-700"> <div class="mb-3 forum-signature text-gray-400 dark:text-gray-500"><i>If you keep on dividing (man) you end up as a collection of monkeys throwing nuts at each other out of separate trees." - Merlyn, The Once and Future King (T.H. White)</i></div> <div class="flex items-center justify-between flex-wrap gap-2 text-xs"> <div class="hidden sm:flex items-center gap-2 text-gray-400 dark:text-gray-500"> <time title="September 28, 2002 11:49 AM">Sep 28, 2002 at 11:49 AM</time> </div> <div class="flex items-center gap-1 sm:gap-2 flex-wrap text-xs"> <div class="reactions-group flex items-center gap-1" id="reactions-1630628"> <button type="button" hx-post="/forums/post/1630628/react/" hx-vals='{"type": "like"}' hx-target="#reactions-1630628" hx-swap="outerHTML" title="Like this post" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-rose-500 dark:hover:text-rose-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/> </svg> <span class="hidden sm:inline">Like</span> </button> <button type="button" hx-post="/forums/post/1630628/react/" hx-vals='{"type": "helpful"}' hx-target="#reactions-1630628" hx-swap="outerHTML" title="This is helpful" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-emerald-500 dark:hover:text-emerald-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/> </svg> <span class="hidden sm:inline">Helpful</span> </button> <button type="button" hx-post="/forums/post/1630628/react/" hx-vals='{"type": "thanks"}' hx-target="#reactions-1630628" hx-swap="outerHTML" title="Thanks for this" class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs transition-colors text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-amber-500 dark:hover:text-amber-400"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/> </svg> <span class="hidden sm:inline">Thanks</span> </button> </div> <span class="hidden sm:inline text-gray-200 dark:text-gray-700 mx-1">|</span> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="quotePost(1630628, 'Stibmit')" title="Quote this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg> <span class="hidden sm:inline">Quote</span> </button> <button class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-sky-600 dark:hover:text-sky-400 transition-colors" onclick="copyPostUrl(1630628)" title="Copy link to this post"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg> <span class="hidden sm:inline">Link</span> </button> </div> </div> </footer> </div> </div> </div> </article> <div id="last-post"></div> </div> <aside class="my-5 overflow-hidden rounded-2xl border border-emerald-200 bg-white shadow-sm transition-all hover:border-emerald-300 hover:shadow-md dark:border-emerald-500/25 dark:bg-gray-900/60 dark:hover:border-emerald-400/50 dark:hover:bg-gray-900/80" data-commerce-surface="forum_topic_endcap" data-commerce-product="foundations-of-game-engine-development-volume-2-rendering" data-commerce-reason="product_fallback:global" data-commerce-variant="compact_product" aria-label="Affiliate recommendation" > <a href="/recommends/go/14/?surface=forum_topic_endcap&amp;src=forum_topic%3A116421&amp;v=compact" rel="sponsored nofollow noopener" target="_blank" class="group block p-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-950"> <div class="flex gap-4"> <img src="https://m.media-amazon.com/images/I/61rh1fzB61L._SL1233_.jpg" alt="" loading="lazy" class="h-20 w-20 flex-none rounded-xl bg-gray-100 object-cover shadow-sm ring-1 ring-gray-200 dark:bg-gray-950 dark:ring-white/10"> <div class="min-w-0 flex-1"> <div class="mb-2 flex flex-wrap items-center gap-2"> <span class="rounded-full bg-emerald-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-emerald-700 dark:bg-emerald-400/10 dark:text-emerald-300">Recommended resource</span> <span class="rounded-full bg-gray-100 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-gray-500 dark:bg-white/5 dark:text-gray-400">Affiliate</span> </div> <h3 class="text-base font-bold leading-snug text-gray-950 line-clamp-2 group-hover:text-emerald-700 dark:text-white dark:group-hover:text-emerald-300">Foundations of Game Engine Development, Volume 2: Rendering</h3> <p class="mt-1 text-sm text-gray-500 line-clamp-1 dark:text-gray-400">Amazon &middot; Book</p> <p class="mt-2 text-sm leading-relaxed text-gray-600 line-clamp-2 dark:text-gray-300">This second volume in the Foundations of Game Engine Development series explores the vast subject of real-time rendering in modern game engines. The book provides a detailed introduction to color science, world structure, projections, shaders, lighting, shadows, fog, and visibility methods. This is followed by extensive discussions of a variety of advanced rendering techniques that include volumetric effects, atmospheric shadowing, ambient occlusion, motion blur, and isosurface extraction. Emphasis is placed on practical implementation, and code is included.</p> </div> </div> <div class="mt-4 flex items-center justify-between gap-3"> <p class="min-w-0 text-[11px] leading-snug text-gray-400 dark:text-gray-500">As an Amazon Associate I earn from qualifying purchases.</p> <span class="inline-flex flex-none items-center rounded-lg bg-emerald-600 px-3 py-1.5 text-xs font-bold text-white transition-colors group-hover:bg-emerald-700 dark:bg-emerald-400 dark:text-gray-950 dark:group-hover:bg-emerald-300"> View resource <svg class="ml-1.5 h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"/></svg> </span> </div> </a> </aside> <section class="mt-8 mb-8" id="reply"> <div class="bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 text-center"> <div class="flex justify-center mb-3"> <div class="w-12 h-12 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center"> <svg class="w-6 h-6 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/> </svg> </div> </div> <h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-2">Topic Locked</h3> <p class="text-gray-600 dark:text-gray-400"> This topic has been locked by a moderator. New replies are not allowed. </p> </div> </section> <div class="mt-8 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg text-center text-gray-600 dark:text-gray-400"> <a href="/login/?next=/forums/topic/116421-javascript-question/" class="text-primary-600 hover:underline">Sign in</a> to reply to this topic. </div> </div> <aside> <div class="sticky-sidebar space-y-6"> <div class="d-flex flex-column justify-content-center align-items-center mb-3 ads-lazy mpu flex flex-col justify-center items-center mb-3" style="min-height:250px;" data-slot-id="LEGACY_BOX_SIDEBAR" data-tier="0" data-delivery="gam"> <div id="gpt-box-sidebar" class="gpt-ad-slot" data-gpt-path="/1004699/Ad_Box_Sidebar" data-gpt-position="sidebar_top"></div> <div class="text-uppercase advert-label uppercase tracking-wider text-[10px] text-gray-500 dark:text-gray-500 d-none" style="letter-spacing:1px">Advertisement</div> </div> <section class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4"> <h3 class="font-bold text-gray-900 dark:text-white mb-4">Topic Info</h3> <ul class="space-y-3 text-sm"> <li class="flex items-center justify-between text-gray-500 dark:text-gray-400"> <span>Views</span> <span class="font-medium text-gray-900 dark:text-white">600+</span> </li> <li class="flex items-center justify-between text-gray-500 dark:text-gray-400"> <span>Replies</span> <span class="font-medium text-gray-900 dark:text-white">5</span> </li> <li class="flex items-center justify-between text-gray-500 dark:text-gray-400"> <span>Created</span> <span class="font-medium text-gray-900 dark:text-white">Sep 27, 2002</span> </li> <li class="flex items-center justify-between text-gray-500 dark:text-gray-400"> <span>Last Reply</span> <span class="font-medium text-gray-900 dark:text-white">Sep 28, 2002</span> </li> </ul> <div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700"> <h4 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3">Participants (1)</h4> <div class="flex flex-wrap gap-1"> <a href="/profile/19799-stibmit" title="Stibmit" class="hover:opacity-80 transition-opacity"> <span class="gdx-avatar w-6 h-6 rounded-full "> <span class="gdx-avatar__img gdx-avatar__fallback rounded-full cursor-pointer text-xs" role="img" aria-label="Stibmit" data-user-tooltip data-member-id="19799" aria-haspopup="dialog"> <span aria-hidden="true">S</span> </span> </span> </a> </div> </div> </section> <div hx-get="/forums/topic/116421/related/" hx-trigger="revealed" hx-swap="outerHTML"> <div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 animate-pulse"> <div class="h-4 w-28 bg-gray-200 dark:bg-gray-700 rounded mb-4"></div> <div class="space-y-3"> <div class="h-3 bg-gray-100 dark:bg-gray-700 rounded"></div> <div class="h-3 w-5/6 bg-gray-100 dark:bg-gray-700 rounded"></div> <div class="h-3 w-4/6 bg-gray-100 dark:bg-gray-700 rounded"></div> </div> </div> </div> <section class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4"> <h3 class="font-bold text-gray-900 dark:text-white mb-4 flex items-center gap-2"> <svg class="w-5 h-5 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.879 16.121A3 3 0 1012.015 11L11 14H9c0 .768.293 1.536.879 2.121z"/> </svg> Popular Topics </h3> <ul class="space-y-2"> <li class="group"> <a href="/forums/topic/720534-kaivern-a-persistent-rpg-built-around-one-living-world-looking-for-professional-feedback-on-technical-feasibility" class="block hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 py-1.5 rounded transition-colors"> <div class="flex items-start gap-1.5"> <span class="text-sm text-gray-900 dark:text-white group-hover:text-sky-600 line-clamp-2"> Kaivern - a persistent RPG built around one living world. Looking for professional feedback on technical feasibility </span> </div> <div class="mt-1 flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400"> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/> </svg> 8 </span> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/> </svg> 850+ </span> <span class="truncate">Game Design and Theory</span> </div> </a> </li> <li class="group"> <a href="/forums/topic/720559-multithreading-scheduling-in-game-engines-with-dags" class="block hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 py-1.5 rounded transition-colors"> <div class="flex items-start gap-1.5"> <span class="text-sm text-gray-900 dark:text-white group-hover:text-sky-600 line-clamp-2"> Multithreading, scheduling in game engines with DAGs </span> </div> <div class="mt-1 flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400"> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/> </svg> 7 </span> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/> </svg> 550+ </span> <span class="truncate">Engines and Middleware</span> </div> </a> </li> <li class="group"> <a href="/forums/topic/720523-whata-do-you-suggest" class="block hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 py-1.5 rounded transition-colors"> <div class="flex items-start gap-1.5"> <span class="text-sm text-gray-900 dark:text-white group-hover:text-sky-600 line-clamp-2"> Whata do you suggest ? </span> </div> <div class="mt-1 flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400"> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/> </svg> 5 </span> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/> </svg> 350+ </span> <span class="truncate">For Beginners</span> </div> </a> </li> <li class="group"> <a href="/forums/topic/720712-replacement-universe" class="block hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 py-1.5 rounded transition-colors"> <div class="flex items-start gap-1.5"> <span class="flex-shrink-0 mt-0.5 text-orange-500 text-xs">🔥</span> <span class="text-sm text-gray-900 dark:text-white group-hover:text-sky-600 line-clamp-2"> Replacement Universe </span> </div> <div class="mt-1 flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400"> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/> </svg> 8 </span> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/> </svg> 300+ </span> <span class="truncate">Writing for Games</span> </div> </a> </li> <li class="group"> <a href="/forums/topic/720702-order-of-actions-from-executing-a-command-to-changing-the-character-state-who-is-the-owner" class="block hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 py-1.5 rounded transition-colors"> <div class="flex items-start gap-1.5"> <span class="flex-shrink-0 mt-0.5 text-orange-500 text-xs">🔥</span> <span class="text-sm text-gray-900 dark:text-white group-hover:text-sky-600 line-clamp-2"> Order of actions, from executing a command to changing the character state, who is the owner? </span> </div> <div class="mt-1 flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400"> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/> </svg> 6 </span> <span class="flex items-center gap-1"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/> </svg> 200+ </span> <span class="truncate">General and Gameplay Programming</span> </div> </a> </li> </ul> </section> <section class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4"> <h3 class="font-bold text-gray-900 dark:text-white mb-4 flex items-center gap-2"> <svg class="w-5 h-5 text-sky-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"/> </svg> Browse Forums </h3> <nav class="space-y-1"> <details class="group" > <summary class="flex items-center justify-between cursor-pointer py-1.5 px-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700/50 text-sm font-medium text-gray-700 dark:text-gray-300"> <span class="truncate">New to GameDev?</span> <svg class="w-4 h-4 text-gray-400 group-open:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/> </svg> </summary> <ul class="ml-3 mt-1 space-y-0.5 border-l-2 border-gray-100 dark:border-gray-700"> <li> <a href="/forums/forum/71-for-beginners" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> For Beginners </a> </li> </ul> </details> <details class="group" > <summary class="flex items-center justify-between cursor-pointer py-1.5 px-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700/50 text-sm font-medium text-gray-700 dark:text-gray-300"> <span class="truncate">Audio</span> <svg class="w-4 h-4 text-gray-400 group-open:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/> </svg> </summary> <ul class="ml-3 mt-1 space-y-0.5 border-l-2 border-gray-100 dark:border-gray-700"> <li> <a href="/forums/forum/18-music-and-sound-fx" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Music and Sound FX </a> </li> </ul> </details> <details class="group" > <summary class="flex items-center justify-between cursor-pointer py-1.5 px-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700/50 text-sm font-medium text-gray-700 dark:text-gray-300"> <span class="truncate">Business</span> <svg class="w-4 h-4 text-gray-400 group-open:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/> </svg> </summary> <ul class="ml-3 mt-1 space-y-0.5 border-l-2 border-gray-100 dark:border-gray-700"> <li> <a href="/forums/forum/23-games-business-and-law" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Games Business and Law </a> </li> <li> <a href="/forums/forum/21-games-career-development" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Games Career Development </a> </li> <li> <a href="/forums/forum/22-production-and-management" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Production and Management </a> </li> </ul> </details> <details class="group" > <summary class="flex items-center justify-between cursor-pointer py-1.5 px-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700/50 text-sm font-medium text-gray-700 dark:text-gray-300"> <span class="truncate">Game Design</span> <svg class="w-4 h-4 text-gray-400 group-open:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/> </svg> </summary> <ul class="ml-3 mt-1 space-y-0.5 border-l-2 border-gray-100 dark:border-gray-700"> <li> <a href="/forums/forum/19-game-design-and-theory" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Game Design and Theory </a> </li> <li> <a href="/forums/forum/20-writing-for-games" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Writing for Games </a> </li> </ul> </details> <details class="group" > <summary class="flex items-center justify-between cursor-pointer py-1.5 px-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700/50 text-sm font-medium text-gray-700 dark:text-gray-300"> <span class="truncate">Visual Arts</span> <svg class="w-4 h-4 text-gray-400 group-open:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/> </svg> </summary> <ul class="ml-3 mt-1 space-y-0.5 border-l-2 border-gray-100 dark:border-gray-700"> <li> <a href="/forums/forum/12-2d-and-3d-art" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> 2D and 3D Art </a> </li> <li> <a href="/forums/forum/13-art-critique-and-feedback" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Art Critique and Feedback </a> </li> </ul> </details> <details class="group" > <summary class="flex items-center justify-between cursor-pointer py-1.5 px-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700/50 text-sm font-medium text-gray-700 dark:text-gray-300"> <span class="truncate">Affiliates</span> <svg class="w-4 h-4 text-gray-400 group-open:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/> </svg> </summary> <ul class="ml-3 mt-1 space-y-0.5 border-l-2 border-gray-100 dark:border-gray-700"> <li> <a href="/forums/forum/34-angelcode" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> AngelCode </a> </li> <li> <a href="/forums/forum/33-nehe-productions" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> NeHe Productions </a> </li> </ul> </details> <details class="group" open> <summary class="flex items-center justify-between cursor-pointer py-1.5 px-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700/50 text-sm font-medium text-gray-700 dark:text-gray-300"> <span class="truncate">Programming</span> <svg class="w-4 h-4 text-gray-400 group-open:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/> </svg> </summary> <ul class="ml-3 mt-1 space-y-0.5 border-l-2 border-gray-100 dark:border-gray-700"> <li> <a href="/forums/forum/6-artificial-intelligence" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Artificial Intelligence </a> </li> <li> <a href="/forums/forum/10-engines-and-middleware" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Engines and Middleware </a> </li> <li> <a href="/forums/forum/9-general-and-gameplay-programming" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-sky-600 font-medium bg-sky-50 dark:bg-sky-900/20"> General and Gameplay Programming </a> </li> <li> <a href="/forums/forum/5-graphics-and-gpu-programming" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Graphics and GPU Programming </a> </li> <li> <a href="/forums/forum/7-math-and-physics" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Math and Physics </a> </li> <li> <a href="/forums/forum/8-networking-and-multiplayer" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Networking and Multiplayer </a> </li> </ul> </details> <details class="group" > <summary class="flex items-center justify-between cursor-pointer py-1.5 px-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700/50 text-sm font-medium text-gray-700 dark:text-gray-300"> <span class="truncate">Community</span> <svg class="w-4 h-4 text-gray-400 group-open:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/> </svg> </summary> <ul class="ml-3 mt-1 space-y-0.5 border-l-2 border-gray-100 dark:border-gray-700"> <li> <a href="/forums/forum/82-gamedev-challenges" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> GameDev Challenges </a> </li> <li> <a href="/forums/forum/26-gdnet-comments-suggestions-and-ideas" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> GDNet Comments, Suggestions, and Ideas </a> </li> <li> <a href="/forums/forum/25-gdnet-lounge" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> GDNet Lounge </a> </li> <li> <a href="/forums/forum/29-hobby-project-classifieds" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Hobby Project Classifieds </a> </li> <li> <a href="/forums/forum/30-indie-showcase" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Indie Showcase </a> </li> <li> <a href="/forums/forum/28-your-announcements" class="block py-1 px-3 text-sm rounded-r hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors text-gray-600 dark:text-gray-400"> Your Announcements </a> </li> </ul> </details> </nav> </section> <section class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4"> <h3 class="font-bold text-gray-900 dark:text-white mb-4">Quick Actions</h3> <ul class="space-y-2"> <li> <a href="/forums/forum/9-general-and-gameplay-programming" class="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-sky-600 text-sm"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> </svg> Back to General and Gameplay Programming </a> </li> <li> <a href="/forums/" class="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-sky-600 text-sm"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/> </svg> All Forums </a> </li> </ul> </section> </div> </aside> </div> <div class="mt-8"> <div class="d-flex flex-column justify-content-center align-items-center mb-3 ads-lazy leaderboard flex flex-col justify-center items-center rounded mb-3" style="min-height:90px;" data-slot-id="LEGACY_LEADERBOARD_FOOTER" data-tier="0" data-delivery="gam"> <div id="gpt-leaderboard-footer" class="gpt-ad-slot" data-gpt-path="/1004699/Ad_Leaderboard_Footer" data-gpt-position="footer"></div> <div class="text-uppercase advert-label uppercase tracking-wider text-[10px] text-gray-500 dark:text-gray-500 d-none" style="letter-spacing:1px">Advertisement</div> </div> </div> </div> <script> // Pre-submit quality checks const LOW_EFFORT_PATTERNS = [ /^(thanks|thank you|thx|ty|ok|okay|yes|no|bump|same|this|agreed|\+1|me too|nice|cool|great|good|lol|haha)\.?$/i, /^.{1,15}$/ // Very short replies (less than 15 chars) ]; const SPAM_PATTERNS = [ /(https?:\/\/[^\s]+){3,}/i, // 3+ links /(.)\1{10,}/, // Same character repeated 10+ times /\b(buy|sale|discount|cheap|free|click here|visit|casino|crypto|nft)\b.*\b(buy|sale|discount|cheap|free|click here|visit|casino|crypto|nft)\b/i ]; function checkReplyQuality(content) { const text = content.replace(/<[^>]*>/g, '').trim(); // Strip HTML const issues = []; // Check for low-effort for (const pattern of LOW_EFFORT_PATTERNS) { if (pattern.test(text)) { issues.push({ type: 'low_effort', message: 'Your reply seems brief. Consider adding more detail to help the community.' }); break; } } // Check for spam patterns for (const pattern of SPAM_PATTERNS) { if (pattern.test(text)) { issues.push({ type: 'spam', message: 'Your reply contains patterns that may be flagged as spam.' }); break; } } // Check for empty content if (text.length === 0) { issues.push({ type: 'empty', message: 'Please write something before posting.' }); } return issues; } function showQualityNudge(issues, onContinue) { const modal = document.getElementById('quality-nudge-modal'); const issuesList = document.getElementById('quality-issues-list'); issuesList.innerHTML = issues.map(i => `<li class="flex items-start gap-2 text-amber-700 dark:text-amber-300"> <svg class="w-5 h-5 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/> </svg> <span>${i.message}</span> </li>` ).join(''); // Store callback for continue button window._qualityNudgeContinue = onContinue; modal.classList.remove('hidden'); } function closeQualityNudge() { document.getElementById('quality-nudge-modal').classList.add('hidden'); } function continuePostingAnyway() { closeQualityNudge(); if (window._qualityNudgeContinue) { window._qualityNudgeContinue(); } } // Sync TipTap editor content before form submit document.addEventListener('DOMContentLoaded', function() { const replyForm = document.querySelector('#reply-form'); if (replyForm) { // Sync editor content before form submit replyForm.addEventListener('submit', function(e) { if (window.gamedevEditors && window.gamedevEditors.length > 0) { window.gamedevEditors[0].syncToHiddenFields(); } }); // Also handle HTMX requests with quality check replyForm.addEventListener('htmx:beforeRequest', function(e) { if (window.gamedevEditors && window.gamedevEditors.length > 0) { window.gamedevEditors[0].syncToHiddenFields(); } // Quality check (skip if already bypassed) if (!replyForm.dataset.qualityBypassed) { const content = document.getElementById('content_html')?.value || ''; const issues = checkReplyQuality(content); // Only block for non-empty issues (empty is always blocked) const blockingIssues = issues.filter(i => i.type !== 'empty' || issues.length === 1); if (blockingIssues.length > 0 && blockingIssues[0].type !== 'empty') { e.preventDefault(); showQualityNudge(blockingIssues, function() { replyForm.dataset.qualityBypassed = 'true'; htmx.trigger(replyForm, 'submit'); delete replyForm.dataset.qualityBypassed; }); return false; } } }); // Handle successful reply submission replyForm.addEventListener('htmx:afterRequest', function(e) { if (e.detail.successful) { // Small delay to ensure DOM is ready setTimeout(function() { clearReplyEditor(); showToast('Reply posted!', 'success'); }, 100); } }); } }); // Clear editor and reset form function clearReplyEditor() { if (window.gamedevEditors && window.gamedevEditors.length > 0) { const gdEditor = window.gamedevEditors[0]; // Clear editor content if (gdEditor.editor) { gdEditor.editor.commands.setContent('', false); } // Clear autosave draft using the editor's method if (typeof gdEditor.clearDraft === 'function') { gdEditor.clearDraft(); } else if (gdEditor.autosaveKey) { localStorage.removeItem(gdEditor.autosaveKey); } // Sync to hidden field if (typeof gdEditor.syncToHiddenFields === 'function') { gdEditor.syncToHiddenFields(); } } // Also directly clear the hidden field const contentField = document.getElementById('content_html'); if (contentField) { contentField.value = ''; } } function quotePost(postId, authorName) { // Get selected text if any let textToQuote = window.getSelection().toString().trim(); // If no selection, get the post content if (!textToQuote && postId) { const postContentEl = document.getElementById(`post-content-${postId}`); if (postContentEl) { // Get text content, clean up whitespace textToQuote = postContentEl.innerText.trim().substring(0, 500); if (postContentEl.innerText.trim().length > 500) { textToQuote += '...'; } } } // Try to insert into TipTap editor if (window.gamedevEditors && window.gamedevEditors.length > 0) { const editor = window.gamedevEditors[0].editor; if (editor) { const content = `<blockquote><p><strong>${authorName}</strong> wrote:</p><p>${textToQuote || ''}</p></blockquote><p></p>`; editor.commands.insertContent(content); editor.commands.focus('end'); document.getElementById('reply')?.scrollIntoView({ behavior: 'smooth' }); return; } } // Fallback to textarea const textarea = document.getElementById('reply-content'); if (textarea && textarea.tagName === 'TEXTAREA') { const formattedQuote = textToQuote ? `> **${authorName}** wrote:\n> ${textToQuote.replace(/\n/g, '\n> ')}\n\n` : `@${authorName} `; textarea.value += formattedQuote; textarea.focus(); textarea.scrollIntoView({ behavior: 'smooth' }); } } function copyPostUrl(postId) { const url = window.location.origin + window.location.pathname + '#post-' + postId; navigator.clipboard.writeText(url).then(() => { // Show brief feedback const btn = event.target instanceof Element ? event.target.closest('button') : event.target.parentElement; const originalTitle = btn.title; btn.title = 'Copied!'; setTimeout(() => { btn.title = originalTitle; }, 2000); }); } // Floating quote selection popup (function() { let quotePopup = null; let currentPostId = null; let currentAuthorName = null; let currentSelectedText = null; // Store the selected text function createQuotePopup() { const popup = document.createElement('div'); popup.id = 'quote-selection-popup'; popup.style.cssText = 'position: fixed; z-index: 9999; display: none;'; popup.innerHTML = ` <button class="flex items-center gap-1.5 px-3 py-1.5 bg-gray-800 dark:bg-gray-700 text-white text-sm rounded-lg shadow-lg hover:bg-gray-700 dark:hover:bg-gray-600 transition-colors border border-gray-600"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/> </svg> Quote </button> `; popup.querySelector('button').addEventListener('mousedown', (e) => { e.preventDefault(); e.stopPropagation(); if (currentPostId !== null && currentAuthorName && currentSelectedText) { insertQuote(currentAuthorName, currentSelectedText); } hideQuotePopup(); }); document.body.appendChild(popup); return popup; } function insertQuote(authorName, text) { // Try to insert into TipTap editor if (window.gamedevEditors && window.gamedevEditors.length > 0) { const editor = window.gamedevEditors[0].editor; if (editor) { const content = `<blockquote><p><strong>${authorName}</strong> wrote:</p><p>${text}</p></blockquote><p></p>`; editor.commands.insertContent(content); editor.commands.focus('end'); document.getElementById('reply')?.scrollIntoView({ behavior: 'smooth' }); return; } } // Fallback to textarea const textarea = document.getElementById('reply-content'); if (textarea && textarea.tagName === 'TEXTAREA') { textarea.value += `> **${authorName}** wrote:\n> ${text.replace(/\n/g, '\n> ')}\n\n`; textarea.focus(); textarea.scrollIntoView({ behavior: 'smooth' }); } } function showQuotePopup(x, y, postId, authorName, selectedText) { if (!quotePopup) quotePopup = createQuotePopup(); currentPostId = postId; currentAuthorName = authorName; currentSelectedText = selectedText; // Position popup above selection quotePopup.style.left = `${Math.max(10, x)}px`; quotePopup.style.top = `${Math.max(10, y - 45)}px`; quotePopup.style.display = 'block'; } function hideQuotePopup() { if (quotePopup) { quotePopup.style.display = 'none'; } currentPostId = null; currentAuthorName = null; currentSelectedText = null; } // Listen for text selection in posts document.addEventListener('mouseup', (e) => { // Small delay to let selection finalize setTimeout(() => { const selection = window.getSelection(); if (!selection.rangeCount) { hideQuotePopup(); return; } const text = selection.toString().trim(); if (text.length < 10) { hideQuotePopup(); return; } // Check if selection is within a post content area const range = selection.getRangeAt(0); let node = range.commonAncestorContainer; if (node.nodeType === Node.TEXT_NODE) { node = node.parentElement; } // Find the post article element const postEl = node.closest('article[id^="post-"]'); if (!postEl) { hideQuotePopup(); return; } // Get post ID and author name from data attribute const postId = postEl.id.replace('post-', ''); const authorName = postEl.dataset.authorName || 'Unknown'; // Get selection coordinates const rect = range.getBoundingClientRect(); const x = rect.left + (rect.width / 2) - 40; const y = rect.top; // Store the selected text NOW before it gets cleared showQuotePopup(x, y, postId, authorName, text); }, 50); }); // Hide popup when clicking elsewhere document.addEventListener('mousedown', (e) => { if (quotePopup && !quotePopup.contains(e.target)) { hideQuotePopup(); } }); })(); // Mod drawer functions (staff and moderators) function openModDrawer() { const drawer = document.getElementById('mod-drawer'); drawer.classList.remove('translate-x-full'); // Load content via HTMX htmx.ajax('GET', '/forums/topic/116421/mod-notes/', {target: '#mod-drawer-content', swap: 'innerHTML'}); } function closeModDrawer() { document.getElementById('mod-drawer').classList.add('translate-x-full'); } // Post mod menu functions function openPostModMenu(postId) { const modal = document.getElementById('post-mod-modal'); const postIdInput = document.getElementById('post-mod-post-id'); postIdInput.value = postId; document.getElementById('post-mod-id-display').textContent = postId; modal.classList.remove('hidden'); } function closePostModMenu() { document.getElementById('post-mod-modal').classList.add('hidden'); } function executePostModAction(action) { const postId = document.getElementById('post-mod-post-id').value; fetch('/moderation/action/post/' + postId + '/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value }, body: 'action=' + action }).then(() => { closePostModMenu(); window.location.reload(); }); } </script> <div class="hidden md:block fixed bottom-4 right-4 z-10"> <button onclick="document.getElementById('shortcuts-modal').classList.remove('hidden')" class="text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded shadow-sm border border-gray-200 dark:border-gray-700"> Press <kbd class="px-1 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-gray-600 dark:text-gray-300">?</kbd> for shortcuts </button> </div> <script src="/static/gamedevnext/js/forum-shortcuts.a18f66b1e18e.js"></script> <script> /** * Toggle like on content * @param {string} contentType - Content type identifier * @param {number} contentId - Content ID * @param {HTMLElement} btn - Button element */ function toggleLike(contentType, contentId, btn) { if (btn.dataset.pending === 'true') return; btn.dataset.pending = 'true'; btn.disabled = true; const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value || document.querySelector('meta[name="csrf-token"]')?.content || document.cookie.split('; ').find(row => row.startsWith('csrftoken='))?.split('=')[1] || ''; fetch(`/api/content/${contentType}/${contentId}/like/`, { method: 'POST', headers: { 'X-CSRFToken': csrfToken, 'Content-Type': 'application/json', }, }) .then(response => response.json()) .then(data => { if (data.message === 'OK') { const svg = btn.querySelector('svg'); const countSpan = btn.querySelector('.like-count'); if (data.vote > 0) { // Liked btn.classList.remove('text-gray-500', 'hover:text-pink-600', 'hover:bg-pink-50', 'dark:text-gray-400', 'dark:hover:text-pink-400', 'dark:hover:bg-pink-900/20'); btn.classList.add('bg-pink-100', 'text-pink-600', 'dark:bg-pink-900/30', 'dark:text-pink-400'); svg.classList.add('fill-current'); svg.setAttribute('fill', 'currentColor'); btn.title = 'Unlike'; } else { // Unliked btn.classList.add('text-gray-500', 'hover:text-pink-600', 'hover:bg-pink-50', 'dark:text-gray-400', 'dark:hover:text-pink-400', 'dark:hover:bg-pink-900/20'); btn.classList.remove('bg-pink-100', 'text-pink-600', 'dark:bg-pink-900/30', 'dark:text-pink-400'); svg.classList.remove('fill-current'); svg.setAttribute('fill', 'none'); btn.title = 'Like'; } if (countSpan && data.total !== undefined) { countSpan.textContent = data.total; } } else if (data.message) { // Show message (rate limit, self-like, etc.) showToast(data.message, 'info'); } else if (data.error) { showToast(data.error, 'error'); } }) .catch(err => { console.error('Like error:', err); showToast('Failed to update like', 'error'); }) .finally(() => { btn.dataset.pending = 'false'; btn.disabled = false; }); } /** * Toggle bookmark on content * @param {string} contentType - Content type identifier * @param {number} contentId - Content ID * @param {HTMLElement} btn - Button element */ function toggleBookmark(contentType, contentId, btn) { const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value || document.querySelector('meta[name="csrf-token"]')?.content || document.cookie.split('; ').find(row => row.startsWith('csrftoken='))?.split('=')[1] || ''; fetch(`/api/content/${contentType}/${contentId}/bookmark/`, { method: 'POST', headers: { 'X-CSRFToken': csrfToken, 'Content-Type': 'application/json', }, }) .then(response => response.json()) .then(data => { if (data.bookmarked !== undefined) { const svg = btn.querySelector('svg'); const labelSpan = btn.querySelector('.bookmark-label'); if (data.bookmarked) { // Bookmarked btn.classList.remove('text-gray-500', 'hover:text-amber-600', 'hover:bg-amber-50', 'dark:text-gray-400', 'dark:hover:text-amber-400', 'dark:hover:bg-amber-900/20'); btn.classList.add('bg-amber-100', 'text-amber-600', 'dark:bg-amber-900/30', 'dark:text-amber-400'); svg.classList.add('fill-current'); svg.setAttribute('fill', 'currentColor'); btn.title = 'Remove bookmark'; if (labelSpan) labelSpan.textContent = 'Saved'; } else { // Unbookmarked btn.classList.add('text-gray-500', 'hover:text-amber-600', 'hover:bg-amber-50', 'dark:text-gray-400', 'dark:hover:text-amber-400', 'dark:hover:bg-amber-900/20'); btn.classList.remove('bg-amber-100', 'text-amber-600', 'dark:bg-amber-900/30', 'dark:text-amber-400'); svg.classList.remove('fill-current'); svg.setAttribute('fill', 'none'); btn.title = 'Save bookmark'; if (labelSpan) labelSpan.textContent = 'Save'; } } else if (data.error) { showToast(data.error, 'error'); } }) .catch(err => { console.error('Bookmark error:', err); showToast('Failed to update bookmark', 'error'); }); } /** * Simple toast notification (uses existing toast if available, else alert) */ function showToast(message, type) { // Check for existing toast system if (typeof window.Toast !== 'undefined') { window.Toast.show(message, type); } else if (typeof window.showNotification !== 'undefined') { window.showNotification(message, type); } else { // Fallback to a simple toast const toast = document.createElement('div'); toast.className = `fixed bottom-4 right-4 px-4 py-2 rounded-lg shadow-lg z-50 transition-opacity duration-300 ${type === 'error' ? 'bg-red-600 text-white' : 'bg-gray-800 text-white dark:bg-gray-700'}`; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.classList.add('opacity-0'); setTimeout(() => toast.remove(), 300); }, 3000); } } </script> <!-- Report Modal --> <div id="report-modal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 99999; align-items: center; justify-content: center;" aria-labelledby="modal-title" role="dialog" aria-modal="true"> <!-- Background overlay --> <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5);" onclick="closeReportModal()"></div> <!-- Modal panel --> <div style="position: relative; z-index: 100000; background: white; border-radius: 8px; max-width: 500px; width: 90%; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25);" class="dark:bg-gray-800"> <form id="report-form" onsubmit="submitReport(event)"> <div class="px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <div class="sm:flex sm:items-start"> <div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-900/30 sm:mx-0 sm:h-10 sm:w-10"> <svg class="h-6 w-6 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/> </svg> </div> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left flex-1"> <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white" id="modal-title"> Report Content </h3> <div class="mt-4"> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> Reason for reporting </label> <select name="reason" id="report-reason" required class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white shadow-sm focus:border-purple-500 focus:ring-purple-500"> <option value="">Select a reason...</option> <option value="spam">Spam or advertising</option> <option value="harassment">Harassment or abuse</option> <option value="inappropriate">Inappropriate content</option> <option value="off_topic">Off-topic or irrelevant</option> <option value="misinformation">Misinformation</option> <option value="copyright">Copyright violation</option> <option value="other">Other</option> </select> </div> <div class="mt-4"> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" id="report-comment-label"> Additional details (optional) </label> <textarea name="comment" id="report-comment" rows="3" maxlength="500" placeholder="Please provide any additional context..." class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white shadow-sm focus:border-purple-500 focus:ring-purple-500"></textarea> <p class="mt-1 text-xs text-gray-500 dark:text-gray-400" id="report-comment-help">Max 500 characters</p> </div> </div> </div> </div> <div class="bg-gray-50 dark:bg-gray-700/50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse gap-2"> <button type="submit" id="report-submit-btn" class="w-full inline-flex justify-center rounded-lg border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:w-auto sm:text-sm disabled:opacity-50 disabled:cursor-not-allowed"> Submit Report </button> <button type="button" onclick="closeReportModal()" class="mt-3 w-full inline-flex justify-center rounded-lg border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-800 text-base font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:mt-0 sm:w-auto sm:text-sm"> Cancel </button> </div> <!-- Hidden fields for content identification --> <input type="hidden" id="report-content-type" name="content_type" value=""> <input type="hidden" id="report-content-id" name="content_id" value=""> </form> </div> </div> </div> <script> let reportModalOpen = false; // Must mirror ContentReport.DETAIL_REQUIRED_REASONS / MIN_DETAIL_LENGTH. // These reasons are meaningless to a moderator on their own, so the reporter // has to explain what happened. The server enforces this too. const REPORT_DETAIL_REQUIRED_REASONS = ['other', 'harassment']; const REPORT_MIN_DETAIL_LENGTH = 20; function reportDetailRequired(reason) { return REPORT_DETAIL_REQUIRED_REASONS.includes(reason); } function updateReportDetailRequirement() { const reason = document.getElementById('report-reason').value; const label = document.getElementById('report-comment-label'); const help = document.getElementById('report-comment-help'); const comment = document.getElementById('report-comment'); if (reportDetailRequired(reason)) { label.textContent = 'What happened? (required)'; help.textContent = `Please give at least ${REPORT_MIN_DETAIL_LENGTH} characters so a moderator can act on this.`; comment.setAttribute('required', 'required'); comment.placeholder = 'Describe what this user did, and where it happened...'; } else { label.textContent = 'Additional details (optional)'; help.textContent = 'Max 500 characters'; comment.removeAttribute('required'); comment.placeholder = 'Please provide any additional context...'; } } function openReportModal(contentType, contentId) { const modal = document.getElementById('report-modal'); if (!modal) { console.error('Report modal element not found!'); return; } document.getElementById('report-content-type').value = contentType; document.getElementById('report-content-id').value = contentId; document.getElementById('report-reason').value = ''; document.getElementById('report-comment').value = ''; document.getElementById('modal-title').textContent = contentType === 'user' ? 'Report User' : 'Report Content'; updateReportDetailRequirement(); modal.style.display = 'flex'; reportModalOpen = true; } function closeReportModal() { const modal = document.getElementById('report-modal'); modal.style.display = 'none'; reportModalOpen = false; } function submitReport(event) { event.preventDefault(); const contentType = document.getElementById('report-content-type').value; const contentId = document.getElementById('report-content-id').value; const reason = document.getElementById('report-reason').value; const comment = document.getElementById('report-comment').value; const submitBtn = document.getElementById('report-submit-btn'); if (!reason) { showToast('Please select a reason for reporting', 'error'); return; } if (reportDetailRequired(reason) && comment.trim().length < REPORT_MIN_DETAIL_LENGTH) { showToast( `Please describe what happened (at least ${REPORT_MIN_DETAIL_LENGTH} characters) for this reason.`, 'error' ); return; } submitBtn.disabled = true; submitBtn.textContent = 'Submitting...'; const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value || document.querySelector('meta[name="csrf-token"]')?.content; fetch(`/api/content/${contentType}/${contentId}/report/`, { method: 'POST', headers: { 'X-CSRFToken': csrfToken, 'Content-Type': 'application/json', }, body: JSON.stringify({ reason, comment }) }) .then(response => response.json()) .then(data => { if (data.reported) { closeReportModal(); showToast(data.message, 'success'); } else { // Keep the modal open on a validation error so the reporter can // fix their submission instead of losing what they typed. showToast(data.error || data.message || 'Failed to submit report', 'error'); if (!data.error) { closeReportModal(); } } }) .catch(err => { console.error('Report error:', err); showToast('Failed to submit report', 'error'); }) .finally(() => { submitBtn.disabled = false; submitBtn.textContent = 'Submit Report'; }); } document.addEventListener('DOMContentLoaded', function() { const reasonSelect = document.getElementById('report-reason'); if (reasonSelect) { reasonSelect.addEventListener('change', updateReportDetailRequirement); } }); // Close on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && reportModalOpen) { closeReportModal(); } }); </script> <script> (function() { const MAX_QUOTE_HEIGHT = 150; // pixels before collapse function isEmptyNode(node) { if (node.nodeType === Node.TEXT_NODE) { return !node.textContent.replace(/\u00a0/g, ' ').trim(); } if (node.nodeType !== Node.ELEMENT_NODE) return true; if (node.tagName === 'BR') return true; const text = (node.textContent || '').replace(/\u00a0/g, ' ').trim(); if (text) return false; if (node.querySelector && node.querySelector('img, video, iframe, svg, audio')) return false; return true; } function trimTrailingEmpty(el) { while (el.lastChild && isEmptyNode(el.lastChild)) { el.removeChild(el.lastChild); } } function initCollapsibleQuotes() { document.querySelectorAll('.prose blockquote, .post-content blockquote').forEach(quote => { // Skip if already processed if (quote.dataset.collapsible) return; // Skip blockquotes inside editors (TipTap/ProseMirror) to avoid DOM mutation loops if (quote.closest('[contenteditable], .ProseMirror, .gamedev-editor-content, [data-tiptap], [data-tiptap-inline]')) return; quote.dataset.collapsible = 'true'; // Strip trailing empty paragraphs / <br> / whitespace so height reflects real content trimTrailingEmpty(quote); // Measure actual height const originalHeight = quote.scrollHeight; if (originalHeight > MAX_QUOTE_HEIGHT) { // Add collapsed state quote.style.maxHeight = MAX_QUOTE_HEIGHT + 'px'; quote.style.overflow = 'hidden'; quote.classList.add('quote-collapsed'); // Create expand button const expandBtn = document.createElement('button'); expandBtn.type = 'button'; expandBtn.className = 'quote-expand-btn mt-2 flex w-full items-center justify-center py-2 text-xs font-medium text-sky-600 dark:text-sky-400 hover:text-sky-700 dark:hover:text-sky-300 transition-colors'; expandBtn.innerHTML = ` <span class="flex items-center justify-center gap-1"> <svg class="w-4 h-4 expand-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/> </svg> <span class="expand-text">Show more</span> </span> `; expandBtn.addEventListener('click', function(e) { e.preventDefault(); const isCollapsed = quote.classList.contains('quote-collapsed'); if (isCollapsed) { quote.style.maxHeight = originalHeight + 'px'; quote.classList.remove('quote-collapsed'); quote.classList.add('quote-expanded'); expandBtn.querySelector('.expand-text').textContent = 'Show less'; expandBtn.querySelector('.expand-icon').style.transform = 'rotate(180deg)'; } else { quote.style.maxHeight = MAX_QUOTE_HEIGHT + 'px'; quote.classList.add('quote-collapsed'); quote.classList.remove('quote-expanded'); expandBtn.querySelector('.expand-text').textContent = 'Show more'; expandBtn.querySelector('.expand-icon').style.transform = 'rotate(0deg)'; } }); // Add wrapper for positioning const wrapper = document.createElement('div'); wrapper.className = 'quote-wrapper flex flex-col'; quote.parentNode.insertBefore(wrapper, quote); wrapper.appendChild(quote); wrapper.appendChild(expandBtn); } }); } // Run on page load document.addEventListener('DOMContentLoaded', initCollapsibleQuotes); // Re-run after HTMX swaps only for post content areas (not sidebar/nav updates) document.body.addEventListener('htmx:afterSwap', function(e) { var target = e.detail.target || e.target; if (target instanceof Element && (target.id === 'posts-list' || target.closest('#posts-list') || target.classList.contains('post-content'))) { initCollapsibleQuotes(); } }); })(); </script> <style> .quote-wrapper { position: relative; } .quote-wrapper blockquote { transition: max-height 0.3s ease-out; } .quote-wrapper blockquote.quote-collapsed { margin-bottom: 0; } .quote-expand-btn { cursor: pointer; border: none; background: transparent; } .quote-expand-btn .expand-icon { transition: transform 0.2s ease; } </style> </main> </div> <!-- Tailwind Footer - Full width --> <footer class="bg-gray-900 text-gray-300 w-full"> <div class="site-shell py-12"> <div class="grid grid-cols-1 md:grid-cols-4 gap-8"> <!-- Brand --> <div class="md:col-span-1"> <a href="/" class="text-2xl font-bold text-white">GameDev<span class="text-sky-500">.net</span></a> <p class="mt-3 text-sm text-gray-400">The game development community since 1999. Learn, share, and connect with developers worldwide.</p> </div> <!-- Quick Links --> <div> <h4 class="font-semibold text-white mb-4">Community</h4> <ul class="space-y-2 text-sm"> <li><a href="/forums/" class="hover:text-white transition">Forums</a></li> <li><a href="/blogs/" class="hover:text-white transition">Blogs</a></li> <li><a href="/projects/" class="hover:text-white transition">Projects</a></li> <li><a href="/portfolios/" class="hover:text-white transition">Portfolios</a></li> <li><a href="/badges/" class="hover:text-white transition">Badges</a></li> </ul> </div> <!-- Resources --> <div> <h4 class="font-semibold text-white mb-4">Resources</h4> <ul class="space-y-2 text-sm"> <li><a href="/recommends/" class="hover:text-white transition">Recommends</a></li> <li><a href="/tutorials/" class="hover:text-white transition">Tutorials</a></li> <li><a href="/news/" class="hover:text-white transition">News</a></li> <li><a href="/start-game-development/" class="hover:text-white transition">How to Make a Game</a></li> <li><a href="/guide/" class="hover:text-white transition">Getting Started Guide</a></li> <li><a href="/guilds/" class="hover:text-white transition">Topics & Guilds</a></li> <li><a href="/pro/" class="hover:text-white transition">GameDev Pro</a></li> <li><a href="/developers/mcp/" class="hover:text-white transition">Developers</a></li> </ul> </div> <!-- Legal --> <div> <h4 class="font-semibold text-white mb-4">About & Legal</h4> <ul class="space-y-2 text-sm"> <li><a href="/guidelines/" class="hover:text-white transition">Community Guidelines</a></li> <li><a href="/terms/" class="hover:text-white transition">Terms of Service</a></li> <li><a href="/privacy/" class="hover:text-white transition">Privacy Policy</a></li> <li><a href="/licensing/" class="hover:text-white transition">Content Licensing</a></li> <li><a href="/contact/" class="hover:text-white transition">Contact Us</a></li> <li><a href="/about/" class="hover:text-white transition">About</a></li> <li><a href="/changelog/" class="hover:text-white transition">Changelog</a></li> </ul> </div> </div> <!-- Social Links --> <div class="border-t border-gray-800 mt-10 pt-6"> <div class="flex flex-col md:flex-row justify-between items-center gap-4"> <p class="text-sm text-gray-500">&copy; 1999-2026 GameDev.net, LLC. All rights reserved.</p> <div class="flex items-center gap-3"> <a href="https://discord.gg/tgEGVNy" target="_blank" rel="noopener noreferrer" class="text-gray-400 hover:text-white transition" title="Discord"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/></svg> </a> <a href="https://twitter.com/gdevnet" target="_blank" rel="noopener noreferrer" class="text-gray-400 hover:text-white transition" title="X / Twitter"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg> </a> <a href="https://www.facebook.com/GameDev.net/" target="_blank" rel="noopener noreferrer" class="text-gray-400 hover:text-white transition" title="Facebook"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg> </a> <a href="https://instagram.com/gdevnet" target="_blank" rel="noopener noreferrer" class="text-gray-400 hover:text-white transition" title="Instagram"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg> </a> <a href="https://threads.net/gdevnet" target="_blank" rel="noopener noreferrer" class="text-gray-400 hover:text-white transition" title="Threads"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.59 12c.025 3.086.718 5.496 2.057 7.164 1.432 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.042 1.259-1.233 1.885-2.742 1.86-4.486-.016-1.146-.348-2.143-.988-2.962-.587-.752-1.357-1.18-2.254-1.205a2.559 2.559 0 0 0-.793.062c.135.478.2.976.188 1.48-.046 2.02-1.2 3.51-3.01 3.885a4.066 4.066 0 0 1-.88.098c-1.2 0-2.255-.504-2.975-1.418-.6-.762-.907-1.726-.907-2.852 0-1.893.827-3.544 2.268-4.522a5.126 5.126 0 0 1 2.91-.901c.63.006 1.237.1 1.816.282.26-.586.652-1.076 1.164-1.44a3.487 3.487 0 0 1 2-.614h.076c1.478.025 2.789.698 3.692 1.893 1.007 1.333 1.52 3.092 1.527 5.238.026 2.387-.87 4.505-2.594 6.125C17.904 23.14 15.413 23.98 12.186 24zM11.29 11.083c-.885.386-1.465 1.348-1.465 2.56 0 1.097.528 1.93 1.406 2.222.216.072.445.108.681.108.985 0 1.804-.66 1.91-1.553a2.84 2.84 0 0 0-.037-.793 3.86 3.86 0 0 0-.18-.602 3.592 3.592 0 0 0-2.315-1.942z"/></svg> </a> <a href="https://reddit.com/r/gamedevnet" target="_blank" rel="noopener noreferrer" class="text-gray-400 hover:text-white transition" title="Reddit"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/></svg> </a> <span class="w-px h-4 bg-gray-700"></span> <a href="/rss/" target="_blank" class="text-gray-400 hover:text-white transition" title="RSS Feed"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M6.503 20.752c0 1.794-1.456 3.248-3.251 3.248-1.796 0-3.252-1.454-3.252-3.248 0-1.794 1.456-3.248 3.252-3.248 1.795 0 3.251 1.454 3.251 3.248zm-6.503-12.572v4.811c6.05.062 10.96 4.966 11.022 11.009h4.817c-.062-8.742-7.115-15.793-15.839-15.82zm0-8.18v4.819c12.951.115 23.412 10.617 23.5 23.581h4.5c-.115-15.452-12.652-27.988-28-28.4z"/></svg> </a> <span class="w-px h-4 bg-gray-700"></span> <a href="#" class="text-sm text-gray-400 hover:text-white transition flex items-center gap-1"> <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd"/></svg> Back to Top </a> </div> </div> </div> </div> </footer> <script>(function(){var darkIcon=document.getElementById('theme-toggle-dark-icon');var lightIcon=document.getElementById('theme-toggle-light-icon');var systemIcon=document.getElementById('theme-toggle-system-icon');var btn=document.getElementById('theme-toggle');var stored=localStorage.getItem('color-theme');function hideAllIcons(){darkIcon.classList.add('hidden');lightIcon.classList.add('hidden');systemIcon.classList.add('hidden');} function showIcon(){hideAllIcons();if(!stored){systemIcon.classList.remove('hidden');btn.title='Theme: Auto (system)';}else if(stored==='dark'){lightIcon.classList.remove('hidden');btn.title='Theme: Dark';}else{darkIcon.classList.remove('hidden');btn.title='Theme: Light';}} function applyTheme(){if(stored==='dark'||(!stored&&window.matchMedia('(prefers-color-scheme: dark)').matches)){document.documentElement.classList.add('dark');}else{document.documentElement.classList.remove('dark');}} showIcon();btn.addEventListener('click',function(){if(!stored){stored='light';localStorage.setItem('color-theme','light');}else if(stored==='light'){stored='dark';localStorage.setItem('color-theme','dark');}else{stored=null;localStorage.removeItem('color-theme');} applyTheme();showIcon();if(!stored){window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change',function handler(e){if(localStorage.getItem('color-theme')){e.target.removeEventListener('change',handler);return;} applyTheme();});}});})();;</script> <script src="https://unpkg.com/htmx.org@2.0.4"></script> <script> // Global HTMX CSRF — ensures all HTMX POST/PUT/DELETE requests include Django's CSRF token document.body.addEventListener('htmx:configRequest', function(evt) { var token = document.querySelector('[name=csrfmiddlewaretoken]')?.value || document.cookie.split('; ').find(function(r){return r.startsWith('csrftoken=')})?.split('=')[1] || ''; if (token) evt.detail.headers['X-CSRFToken'] = token; }); </script> <script src="https://cdn.jsdelivr.net/npm/fslightbox@3.4.2/index.min.js"></script> <script src="/static/gamedevnext/js/content-lightbox.4e75fcdf81fc.js"></script> <script> document.addEventListener('DOMContentLoaded', function() { // Find all oembed elements and convert them to actual embeds document.querySelectorAll('figure.media > oembed, oembed').forEach(function(item) { var url = item.getAttribute('url'); if (!url) return; fetch('/embed/get/?url=' + encodeURIComponent(url), { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => response.json()) .then(data => { if (data.preview) { // Replace the oembed element or its parent figure var parent = item.closest('figure.media') || item.parentElement; if (parent) { parent.replaceWith(data.preview); } else { item.replaceWith(data.preview); } } }) .catch(err => console.warn('Failed to load embed:', url, err)); }); }); </script> <script src="/static/gamedevnext/js/user-tooltip.08c872581492.js"></script> <script src="/static/gamedevnext/js/shader-tiles.3e9477d364a5.js" defer></script> <script src="/static/bundles/runtime-5781e36a.js" ></script> <script src="/static/bundles/tiptap-gdx-vendors-02e12e81-2ba9b67b.js" ></script> <script src="/static/bundles/tiptap-gdx-vendors-839f78ca-54605f01.js" ></script> <script src="/static/bundles/tiptap-gdx-vendors-631f0d8c-7e852c16.js" ></script> <script src="/static/bundles/tiptap-gdx-vendors-da055998-064d2c61.js" ></script> <script src="/static/bundles/tiptap-gdx-vendors-f0157c14-f6130f88.js" ></script> <script src="/static/bundles/tiptap-gdx-vendors-d2d18b07-0d4f8bbf.js" ></script> <script src="/static/bundles/7515-625c095f.js" ></script> <script src="/static/bundles/8339-b65c7b02.js" ></script> <script src="/static/bundles/803-6483b301.js" ></script> <script src="/static/bundles/2845-52745896.js" ></script> <script src="/static/bundles/8262-7d245816.js" ></script> <script src="/static/bundles/914-e576fc61.js" ></script> <script src="/static/bundles/3801-9b9f5dd6.js" ></script> <link href="/static/bundles/message_editor-d1be09fe.css" rel="stylesheet" /> <script src="/static/bundles/message_editor-c58f8947.js" ></script> <!-- Beta Feedback Modal --> <div id="beta-feedback-modal" class="fixed inset-0 z-[100] hidden" role="dialog" aria-modal="true" aria-labelledby="beta-feedback-title" onclick="if(event.target === this) closeBetaFeedback()"> <div class="fixed inset-0 bg-black/50 backdrop-blur-sm" aria-hidden="true"></div> <div class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md"> <div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl p-6 m-4" id="beta-feedback-dialog"> <div class="flex items-center justify-between mb-4"> <h3 id="beta-feedback-title" class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2"> <svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"/> </svg> Beta Feedback </h3> <button onclick="closeBetaFeedback()" aria-label="Close feedback dialog" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500 rounded"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> </svg> </button> </div> <div id="beta-feedback-content"> <form id="beta-feedback-form" hx-post="/api/beta/feedback/" hx-target="#beta-feedback-content" hx-swap="innerHTML" hx-indicator="#feedback-spinner"> <input type="hidden" name="csrfmiddlewaretoken" value="wwRkVMnqOlvIyKjW7agJhil8seKBcv7nNRji2ecbh9nUYAQG2zMAItGTy8JuWHcq"> <input type="hidden" name="page_url" id="feedback-page-url"> <input type="hidden" name="browser_info" id="feedback-browser-info"> <div class="mb-4"> <label for="feedback-type" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Type</label> <select name="feedback_type" id="feedback-type" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-3 py-2 text-sm focus:ring-2 focus:ring-purple-500 focus:outline-none"> <option value="bug">🐛 Bug Report</option> <option value="suggestion">💡 Suggestion</option> <option value="question">❓ Question</option> <option value="other">📝 Other</option> </select> </div> <div class="mb-4"> <label for="feedback-description" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> Description <span class="text-gray-400 font-normal">(max 5000 chars)</span> </label> <textarea name="description" id="feedback-description" rows="4" required maxlength="5000" placeholder="Describe the issue or feedback..." class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-3 py-2 text-sm focus:ring-2 focus:ring-purple-500 placeholder-gray-400 resize-none focus:outline-none"></textarea> </div> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> Screenshot <span class="text-gray-400 font-normal">(optional)</span> </label> <input type="hidden" name="screenshot_url" id="feedback-screenshot-url"> <div id="screenshot-preview" class="hidden mb-2"> <div class="relative inline-block"> <img id="screenshot-img" class="max-h-32 rounded border border-gray-300 dark:border-gray-600"> <button type="button" onclick="clearScreenshot()" class="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1 hover:bg-red-600"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> </svg> </button> </div> </div> <div id="screenshot-buttons" class="flex gap-2"> <button type="button" onclick="captureScreenshot()" class="px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-1.5"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/> </svg> Capture Screen </button> <label class="px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-1.5 cursor-pointer"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/> </svg> Upload Image <input type="file" accept="image/png,image/jpeg,image/webp" class="hidden" onchange="uploadScreenshot(this)"> </label> </div> <p id="screenshot-status" class="text-xs text-gray-500 mt-1 hidden"></p> </div> <div class="flex justify-end gap-2"> <button type="button" onclick="closeBetaFeedback()" class="px-4 py-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200"> Cancel </button> <button type="submit" class="px-4 py-2 text-sm bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors flex items-center gap-2 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"> <span id="feedback-spinner" class="htmx-indicator"> <svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> </span> Submit Feedback </button> </div> </form> </div> </div> </div> </div> <script> (function() { let previousActiveElement = null; const feedbackContentEl = document.getElementById('beta-feedback-content'); const originalFeedbackHTML = feedbackContentEl ? feedbackContentEl.innerHTML : null; window.openBetaFeedback = function() { previousActiveElement = document.activeElement; // Restore form HTML first if it was replaced by HTMX submission if (feedbackContentEl && originalFeedbackHTML && !document.getElementById('feedback-page-url')) { feedbackContentEl.innerHTML = originalFeedbackHTML; if (typeof htmx !== 'undefined') htmx.process(feedbackContentEl); } const pageUrlEl = document.getElementById('feedback-page-url'); const browserInfoEl = document.getElementById('feedback-browser-info'); if (pageUrlEl) pageUrlEl.value = window.location.href; if (browserInfoEl) browserInfoEl.value = navigator.userAgent; const modal = document.getElementById('beta-feedback-modal'); if (!modal) return; modal.classList.remove('hidden'); document.body.classList.add('overflow-hidden'); // Focus first input after modal opens setTimeout(() => { const ft = document.getElementById('feedback-type'); if (ft) ft.focus(); }, 50); }; window.closeBetaFeedback = function() { const modal = document.getElementById('beta-feedback-modal'); modal.classList.add('hidden'); document.body.classList.remove('overflow-hidden'); // Return focus to trigger element if (previousActiveElement) previousActiveElement.focus(); // Restore original form HTML after a delay (HTMX replaces it on submit) setTimeout(() => { if (feedbackContentEl && originalFeedbackHTML) { feedbackContentEl.innerHTML = originalFeedbackHTML; htmx.process(feedbackContentEl); } }, 300); }; // Close on Escape key (only when modal is open) document.addEventListener('keydown', function(e) { const modal = document.getElementById('beta-feedback-modal'); if (e.key === 'Escape' && modal && !modal.classList.contains('hidden')) { closeBetaFeedback(); } }); // Focus trap for modal document.addEventListener('keydown', function(e) { const modal = document.getElementById('beta-feedback-modal'); if (!modal || modal.classList.contains('hidden') || e.key !== 'Tab') return; const dialog = document.getElementById('beta-feedback-dialog'); const focusable = dialog.querySelectorAll('button, select, textarea, [tabindex]:not([tabindex="-1"])'); const first = focusable[0], last = focusable[focusable.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } }); // Screenshot functions window.captureScreenshot = async function() { const modal = document.getElementById('beta-feedback-modal'); const status = document.getElementById('screenshot-status'); // Hide modal so the screenshot shows the actual page if (modal) modal.style.visibility = 'hidden'; try { // Use Screen Capture API (browser shows picker while modal is hidden) const stream = await navigator.mediaDevices.getDisplayMedia({ video: { mediaSource: 'screen' } }); // Restore modal immediately after user picks a source if (modal) modal.style.visibility = ''; status.classList.remove('hidden'); status.textContent = 'Capturing...'; // Capture frame from stream const video = document.createElement('video'); video.srcObject = stream; await video.play(); const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.getContext('2d').drawImage(video, 0, 0); // Stop stream stream.getTracks().forEach(track => track.stop()); // Convert to blob and upload status.textContent = 'Uploading screenshot...'; canvas.toBlob(async (blob) => { await uploadScreenshotBlob(blob); }, 'image/png'); } catch (err) { // Restore modal on error too if (modal) modal.style.visibility = ''; console.error('Screenshot capture failed:', err); status.classList.remove('hidden'); status.textContent = err.name === 'NotAllowedError' ? 'Screen capture was cancelled' : 'Failed to capture screenshot'; setTimeout(() => status.classList.add('hidden'), 3000); } }; window.uploadScreenshot = function(input) { if (!input.files || !input.files[0]) return; const file = input.files[0]; const status = document.getElementById('screenshot-status'); status.classList.remove('hidden'); status.textContent = 'Uploading...'; uploadScreenshotBlob(file); }; async function uploadScreenshotBlob(blob) { const status = document.getElementById('screenshot-status'); const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''; const formData = new FormData(); formData.append('screenshot', blob, 'screenshot.png'); try { const response = await fetch('/api/beta/feedback/screenshot/', { method: 'POST', headers: { 'X-CSRFToken': csrfToken }, body: formData }); const data = await response.json(); if (data.status === 'ok') { document.getElementById('feedback-screenshot-url').value = data.url; document.getElementById('screenshot-img').src = data.url; document.getElementById('screenshot-preview').classList.remove('hidden'); document.getElementById('screenshot-buttons').classList.add('hidden'); status.classList.add('hidden'); } else { status.textContent = data.message || 'Upload failed'; setTimeout(() => status.classList.add('hidden'), 3000); } } catch (err) { console.error('Screenshot upload failed:', err); status.textContent = 'Upload failed'; setTimeout(() => status.classList.add('hidden'), 3000); } } window.clearScreenshot = function() { document.getElementById('feedback-screenshot-url').value = ''; document.getElementById('screenshot-img').src = ''; document.getElementById('screenshot-preview').classList.add('hidden'); document.getElementById('screenshot-buttons').classList.remove('hidden'); }; })(); </script> <!-- Error Tracking for All --> <script> (function() { const loggedErrors = new Set(); // Dedupe within session const MAX_ERRORS = 5; // Max errors to log per page load let errorCount = 0; // Third-party scripts to ignore (ads, analytics, etc.) const IGNORED_PATTERNS = [ /venatus/i, /vntsm\.com/i, /googletag/i, /googletagmanager/i, /google-analytics/i, /doubleclick/i, /securepubads/i, /googlesyndication/i, /fundingchoicesmessages/i, /googlefcPresent/i, /adsystem/i, /adsbygoogle/i, /pagead/i, /prebid/i, /amazon-adsystem/i, /facebook\.net/i, /connect\.facebook/i, /twitter\.com\/widgets/i, /platform\.twitter/i, /disqus/i, /cdn\.segment/i, /hotjar/i, /clarity\.ms/i, /newrelic/i, /sentry/i, /Script error\.?$/i, // Generic cross-origin script error ]; function isIgnored(/* ...strings */) { for (var i = 0; i < arguments.length; i++) { var s = arguments[i]; if (s && IGNORED_PATTERNS.some(function(p) { return p.test(s); })) return true; } return false; } function logError(source, type, message, stack, extra) { // Limit errors per page to prevent flooding if (errorCount >= MAX_ERRORS) return; // Skip third-party/ad network errors — check message, stack, AND source filename var filename = extra && extra.filename || ''; if (isIgnored(message, stack, filename)) return; // Dedupe by fingerprint (type + first 100 chars of message) const fingerprint = type + ':' + (message || '').slice(0, 100); if (loggedErrors.has(fingerprint)) return; loggedErrors.add(fingerprint); errorCount++; fetch('/api/errors/log/', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ source: source, type: type, message: message, stack: stack || '', url: window.location.href, userAgent: navigator.userAgent, ...extra }) }).catch(() => {}); // Silently fail } // Capture unhandled JS errors window.addEventListener('error', function(event) { var stack = event.error?.stack || ''; // Build a synthetic location string when the Error object has no stack // (common with cross-origin scripts or older browsers) if (!stack && event.filename) { stack = 'at ' + event.filename + ':' + (event.lineno || '?') + ':' + (event.colno || '?'); } logError( 'client', event.error?.name || 'Error', event.message, stack, { filename: event.filename || '', lineno: event.lineno || 0, colno: event.colno || 0 } ); }); // Capture unhandled promise rejections window.addEventListener('unhandledrejection', function(event) { var reason = event.reason; // Skip undefined/null rejections with no stack — almost always // third-party scripts (ad SDKs, consent managers) that reject // promises without providing a reason. Not actionable. if (reason == null && !(reason instanceof Error)) return; var message = reason?.message || String(reason); var stack = reason?.stack || ''; var type = 'UnhandledPromise'; // If reason is an Error subclass, use its name for better triage if (reason instanceof Error && reason.constructor?.name) { type = 'UnhandledPromise:' + reason.constructor.name; } // If reason isn't an Error, add type info for diagnostics if (reason && !(reason instanceof Error) && typeof reason !== 'string') { message = '[' + typeof reason + '] ' + message; } logError('client', type, message, stack); }); // Decorative fragments loaded lazily after render. Their failure is // invisible to the user, and 403/429 on them comes from the edge // (WAF/bot protection) or rate limiting rather than an app fault. var OPTIONAL_FRAGMENT_PATTERNS = [ /^\/forums\/topic\/\d+\/related\/$/, /^\/forums\/api\/popular-topics\/$/, /^\/forums\/api\/tab\//, ]; var EDGE_BLOCK_STATUSES = [403, 429]; function isOptionalFragmentBlock(path, status) { if (EDGE_BLOCK_STATUSES.indexOf(status) === -1) return false; return OPTIONAL_FRAGMENT_PATTERNS.some(function(p) { return p.test(path); }); } // Capture HTMX errors document.body.addEventListener('htmx:responseError', function(event) { var reqPath = event.detail.pathInfo?.requestPath || 'unknown'; if (isOptionalFragmentBlock(reqPath, event.detail.xhr.status)) return; logError('htmx', 'ResponseError', 'HTTP ' + event.detail.xhr.status + ' on ' + reqPath, '', { requestUrl: reqPath, triggerId: event.detail.elt?.id }); }); document.body.addEventListener('htmx:sendError', function(event) { var reqUrl = event.detail.pathInfo?.requestPath || event.detail.requestConfig?.path || event.detail.elt?.getAttribute('hx-get') || event.detail.elt?.getAttribute('hx-post') || 'unknown'; logError('htmx', 'NetworkError', 'Request failed', '', { requestUrl: reqUrl, triggerId: event.detail.elt?.id }); }); })(); </script> <!-- Syntax Highlighting for code blocks (site-wide) --> <link rel="stylesheet" id="hljs-theme-light" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css"> <link rel="stylesheet" id="hljs-theme-dark" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"> <script> (function() { function syncHljsTheme() { var dark = document.documentElement.classList.contains('dark'); var light = document.getElementById('hljs-theme-light'); var darkEl = document.getElementById('hljs-theme-dark'); if (light) light.disabled = dark; if (darkEl) darkEl.disabled = !dark; } syncHljsTheme(); new MutationObserver(syncHljsTheme).observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); })(); </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> <!-- Game dev languages (C++, JS, Python, Java, JSON, XML, Bash, CSS are in core bundle) --> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/glsl.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/rust.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/csharp.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/lua.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/swift.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/kotlin.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/objectivec.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/cmake.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/wasm.min.js"></script> <script> document.addEventListener("DOMContentLoaded", function() { document.querySelectorAll('pre code').forEach((block) => { block.classList.remove('language-plaintext'); }); if (typeof hljs !== 'undefined') { hljs.highlightAll(); } }); </script> <script src="/static/gamedevnext/js/code-decorate.237c7d751ec8.js" defer></script> <!-- Global Toast Notification System --> <div id="toast-container" class="fixed bottom-16 right-4 z-50 flex flex-col gap-2 pointer-events-none"></div> <style> .toast { pointer-events: auto; transform: translateX(100%); opacity: 0; transition: transform 300ms ease-out, opacity 300ms ease-out; } .toast.show { transform: translateX(0); opacity: 1; } .toast.hiding { transform: translateX(100%); opacity: 0; } </style> <script> (function() { const toastColors = { 'success': 'bg-green-600 text-white', 'error': 'bg-red-600 text-white', 'warning': 'bg-amber-500 text-white', 'info': 'bg-gray-800 dark:bg-gray-200 text-white dark:text-gray-900', 'debug': 'bg-gray-600 text-white' }; const toastIcons = { 'success': '<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>', 'error': '<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>', 'warning': '<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>', 'info': '<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>' }; /** * Show a toast notification * @param {string} message - The message to display * @param {string} type - Type: 'success', 'error', 'warning', 'info' * @param {object} options - Optional settings: duration, action, actionLabel, isMessageNotification */ window.showToast = function(message, type = 'info', options = {}) { const container = document.getElementById('toast-container'); if (!container) return; // Suppress message notifications when on the messages page if (options.isMessageNotification && window.isMessagesPage) return; const duration = options.duration || (type === 'error' ? 0 : (type === 'warning' ? 6000 : 4000)); const action = options.action; const actionLabel = options.actionLabel || 'Undo'; const toast = document.createElement('div'); toast.className = `toast ${toastColors[type] || toastColors.info} px-4 py-3 rounded-lg shadow-lg text-sm flex items-center gap-3 max-w-sm`; let html = `${toastIcons[type] || toastIcons.info}<span class="flex-1">${message}</span>`; if (action) { html += `<button class="toast-action font-medium underline underline-offset-2 hover:no-underline">${actionLabel}</button>`; } html += `<button class="toast-close opacity-70 hover:opacity-100 transition-opacity" aria-label="Dismiss"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg> </button>`; toast.innerHTML = html; container.appendChild(toast); // Trigger reflow for animation toast.offsetHeight; toast.classList.add('show'); const dismiss = () => { toast.classList.add('hiding'); toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }; // Close button toast.querySelector('.toast-close').addEventListener('click', dismiss); // Action button if (action) { toast.querySelector('.toast-action').addEventListener('click', () => { action(); dismiss(); }); } if (duration > 0) { setTimeout(dismiss, duration); } return { dismiss }; }; /** * Show a message notification toast with quick reply capability * @param {object} data - Message notification data * - senderName: Name of the sender * - senderAvatar: Avatar URL (optional) * - preview: Message preview text * - conversationId: Conversation ID for reply */ window.showMessageToast = function(data) { const container = document.getElementById('toast-container'); if (!container || window.isMessagesPage) return; const toast = document.createElement('div'); toast.className = 'toast bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 px-4 py-3 rounded-lg shadow-lg text-sm max-w-sm'; const avatarHtml = data.senderAvatar ? `<img src="${data.senderAvatar}" alt="${data.senderName}" class="w-10 h-10 rounded-full object-cover">` : `<div class="w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center text-gray-500 dark:text-gray-400 font-medium">${(data.senderName || '?').charAt(0).toUpperCase()}</div>`; toast.innerHTML = ` <div class="flex items-start gap-3 mb-2"> ${avatarHtml} <div class="flex-1 min-w-0"> <p class="font-medium text-gray-900 dark:text-white truncate">${data.senderName || 'Unknown'}</p> <p class="text-gray-600 dark:text-gray-400 text-xs truncate">${data.preview || 'New message'}</p> </div> <button class="toast-close text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" aria-label="Dismiss"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg> </button> </div> <form class="quick-reply-form flex gap-2"> <input type="text" class="flex-1 px-3 py-1.5 text-xs bg-gray-100 dark:bg-gray-700 border-0 rounded-lg focus:ring-2 focus:ring-sky-500 text-gray-900 dark:text-white placeholder-gray-400" placeholder="Quick reply..." autocomplete="off"> <button type="submit" class="px-3 py-1.5 bg-sky-600 text-white text-xs rounded-lg hover:bg-sky-700 disabled:opacity-50"> Send </button> </form> <div class="flex gap-2 mt-2"> <a href="/messages/${data.conversationId}/" class="text-xs text-sky-600 dark:text-sky-400 hover:underline">Open conversation</a> </div> `; container.appendChild(toast); toast.offsetHeight; toast.classList.add('show'); const dismiss = () => { toast.classList.add('hiding'); toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }; toast.querySelector('.toast-close').addEventListener('click', dismiss); // Handle quick reply form const form = toast.querySelector('.quick-reply-form'); const input = form.querySelector('input'); const submitBtn = form.querySelector('button[type="submit"]'); form.addEventListener('submit', async (e) => { e.preventDefault(); const content = input.value.trim(); if (!content) return; submitBtn.disabled = true; submitBtn.textContent = '...'; try { const formData = new FormData(); formData.append('content', content); const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value || document.cookie.split('; ').find(row => row.startsWith('csrftoken='))?.split('=')[1] || ''; const response = await fetch(`/messages/api/${data.conversationId}/send/`, { method: 'POST', headers: { 'X-CSRFToken': csrfToken, 'X-Requested-With': 'XMLHttpRequest' }, body: formData }); if (response.ok) { window.showToast('Reply sent!', 'success'); dismiss(); } else { const errData = await response.json(); window.showToast(errData.error || 'Failed to send', 'error'); submitBtn.disabled = false; submitBtn.textContent = 'Send'; } } catch (err) { window.showToast('Failed to send reply', 'error'); submitBtn.disabled = false; submitBtn.textContent = 'Send'; } }); // Auto-dismiss after 15 seconds (longer for message notifications) setTimeout(dismiss, 15000); return { dismiss }; }; // ===================================== // Global Emoji Conversion Utility // ===================================== window.emojiUtils = (function() { // Text emoticons → emoji const TEXT_EMOTICONS = { ':-)': '😊', ':)': '😊', '=)': '😊', ':-D': '😄', ':D': '😄', '=D': '😄', ':-P': '😛', ':P': '😛', ':p': '😛', ':-p': '😛', ';-)': '😉', ';)': '😉', ':-(': '😢', ':(': '😢', '=(': '😢', ":'(": '😭', ':-O': '😮', ':O': '😮', ':o': '😮', ':-o': '😮', 'O_O': '😳', 'o_o': '😳', ':-/': '😕', ':/': '😕', ':-\\': '😕', ':\\': '😕', ':-|': '😐', ':|': '😐', 'B-)': '😎', 'B)': '😎', '8-)': '😎', '8)': '😎', '<3': '❤️', '</3': '💔', ':-*': '😘', ':*': '😘', '^_^': '😊', '^-^': '😊', '>_<': '😣', '>.<': '😣', '-_-': '😑', 'XD': '😆', 'xD': '😆', ':3': '😺', ':-3': '😺', }; // Common shortcodes (subset for client-side) const SHORTCODES = { ':smile:': '😄', ':grin:': '😁', ':joy:': '😂', ':wink:': '😉', ':blush:': '😊', ':heart_eyes:': '😍', ':sunglasses:': '😎', ':thinking:': '🤔', ':cry:': '😢', ':sob:': '😭', ':thumbsup:': '👍', ':thumbs_up:': '👍', ':+1:': '👍', ':thumbsdown:': '👎', ':-1:': '👎', ':clap:': '👏', ':wave:': '👋', ':pray:': '🙏', ':muscle:': '💪', ':heart:': '❤️', ':fire:': '🔥', ':100:': '💯', ':star:': '⭐', ':sparkles:': '✨', ':zap:': '⚡', ':boom:': '💥', ':game:': '🎮', ':gamepad:': '🎮', ':trophy:': '🏆', ':crown:': '👑', ':gem:': '💎', ':rocket:': '🚀', ':sword:': '⚔️', ':computer:': '💻', ':bug:': '🐛', ':bulb:': '💡', ':gear:': '⚙️', ':check:': '✅', ':x:': '❌', ':warning:': '⚠️', ':eyes:': '👀', ':tada:': '🎉', ':party:': '🎉', ':coffee:': '☕', }; // Build regex patterns (sorted by length, longest first) const emoticonKeys = Object.keys(TEXT_EMOTICONS).sort((a, b) => b.length - a.length); const emoticonPattern = new RegExp( '(?<![a-zA-Z0-9/])(' + emoticonKeys.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')(?![a-zA-Z0-9])', 'g' ); const shortcodeKeys = Object.keys(SHORTCODES).sort((a, b) => b.length - a.length); const shortcodePattern = new RegExp( '(' + shortcodeKeys.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')', 'g' ); return { // Convert text emoticons like :) to emoji convertEmoticons(text) { if (!text) return text; return text.replace(emoticonPattern, (match, p1) => TEXT_EMOTICONS[p1] || match); }, // Convert shortcodes like :smile: to emoji convertShortcodes(text) { if (!text || !text.includes(':')) return text; return text.replace(shortcodePattern, (match) => SHORTCODES[match] || match); }, // Convert all emoji representations convertAll(text) { if (!text) return text; text = this.convertEmoticons(text); text = this.convertShortcodes(text); return text; }, // Get all available emoticons for reference getEmoticons() { return { ...TEXT_EMOTICONS }; }, getShortcodes() { return { ...SHORTCODES }; }, }; })(); // HTMX Integration: Listen for server-triggered toasts via HX-Trigger header // Server can send: HX-Trigger: {"showToast": {"message": "Saved!", "type": "success"}} document.body.addEventListener('showToast', function(evt) { const detail = evt.detail || {}; window.showToast(detail.message || 'Action completed', detail.type || 'info', { duration: detail.duration, action: detail.action, actionLabel: detail.actionLabel }); }); // Also listen for htmx:afterRequest for HX-Trigger headers document.body.addEventListener('htmx:afterRequest', function(evt) { const trigger = evt.detail.xhr?.getResponseHeader('HX-Trigger'); if (trigger) { try { const parsed = JSON.parse(trigger); if (parsed.showToast) { const t = parsed.showToast; window.showToast(t.message, t.type, t); } } catch (e) { // Not JSON or doesn't have showToast } } }); // Render Django messages as toasts on page load document.addEventListener('DOMContentLoaded', function() { // Moderator queue notification }); })(); </script> <style> img.img-broken { position: relative; min-height: 40px; background: #f3f4f6; border-radius: 0.375rem; } .dark img.img-broken { background: #1f2937; } img.img-broken::after { content: attr(alt); display: flex; align-items: center; justify-content: center; position: absolute; inset: 0; background: inherit; color: #9ca3af; font-size: 0.75rem; border-radius: inherit; padding: 0.5rem; text-align: center; overflow: hidden; } </style> <script> // Global broken-image fallback — runs in two stages to avoid false positives: // 1) First error: strip the Referer (no-referrer) and/or upgrade to https, // then retry. Many external hosts (Google user-content, Imgur, etc.) // 403 with a Referer but load fine without one. // 2) Still failing: mark .img-broken so the CSS ::after overlay shows. // Also re-checks once the element actually loaded successfully and clears // the broken state if it recovers (e.g. slow network). document.addEventListener('error', function(e) { var img = e.target; if (!img || img.tagName !== 'IMG') return; // Skip images inside TipTap/ProseMirror editors — they have their own retry logic if (img.closest && img.closest('.ProseMirror, [contenteditable], .resizable-image-container')) return; if (!img.getAttribute('src')) return; if (!img.dataset.retryAttempted) { img.dataset.retryAttempted = 'true'; var src = img.getAttribute('src'); var changed = false; // Drop Referer on retry if (img.getAttribute('referrerpolicy') !== 'no-referrer') { img.setAttribute('referrerpolicy', 'no-referrer'); changed = true; } // Upgrade mixed-content http:// to https:// if (src.indexOf('http://') === 0) { src = 'https://' + src.slice(7); changed = true; } // Force a reload by resetting src (cache-busted) img.src = src + (src.indexOf('?') === -1 ? '?' : '&') + '_r=' + Date.now(); return; } if (img.dataset.fallbackApplied) return; img.dataset.fallbackApplied = 'true'; img.classList.add('img-broken'); if (!img.alt) img.alt = 'Image unavailable'; }, true); // If an image eventually loads after being marked broken, clear the overlay. document.addEventListener('load', function(e) { var img = e.target; if (!img || img.tagName !== 'IMG') return; if (img.naturalWidth > 0 && img.classList.contains('img-broken')) { img.classList.remove('img-broken'); } }, true); </script> <div data-nosnippet x-data="{ show: false, readConsent() { try { return localStorage.getItem('gdx_cookie_consent'); } catch (err) { return null; } }, writeConsent(value) { try { localStorage.setItem('gdx_cookie_consent', value); return true; } catch (err) { return false; } }, init() { var stored = this.readConsent(); if (!stored) { this.show = true; } else if (stored === 'accepted') { if (window.gtag) { gtag('consent', 'update', {'analytics_storage': 'granted'}); } } else if (stored === 'rejected') { this.show = false; } else { this.show = true; } if (this.show) { this.watchBottomBars(); } }, isBottomBar(el) { if (!el || el === this.$root || this.$root.contains(el)) { return false; } if (getComputedStyle(el).position !== 'fixed') { return false; } var r = el.getBoundingClientRect(); return r.height > 0 && r.height <= Math.min(300, window.innerHeight * 0.5) && r.bottom >= window.innerHeight - 1 && r.right > 0 && r.left < window.innerWidth; }, bottomBarHeight() { var max = 0; var els = document.querySelectorAll( '.ads-anchor, body > ins, body > iframe, body > span, body > div' ); for (var i = 0; i < els.length; i++) { if (this.isBottomBar(els[i])) { max = Math.max(max, els[i].getBoundingClientRect().height); } } return max; }, updateAnchorOffset() { document.documentElement.style.setProperty( '--gdx-anchor-h', this.bottomBarHeight() + 'px' ); }, watchBottomBars() { var self = this; this._observed = []; this._debounce = null; this._ro = null; if ('ResizeObserver' in window) { this._ro = new ResizeObserver(function() { self.updateAnchorOffset(); }); } var scan = function() { var els = document.querySelectorAll( '.ads-anchor, body > ins, body > iframe, body > span, body > div' ); for (var i = 0; i < els.length; i++) { var el = els[i]; if (!self.isBottomBar(el)) { continue; } if (self._ro && self._observed.indexOf(el) === -1) { self._observed.push(el); self._ro.observe(el); } } self.updateAnchorOffset(); }; if ('MutationObserver' in window) { this._mo = new MutationObserver(function() { clearTimeout(self._debounce); self._debounce = setTimeout(scan, 200); }); this._mo.observe(document.body, {childList: true, subtree: true}); } else { this._mo = null; } this._onResize = function() { self.updateAnchorOffset(); }; window.addEventListener('resize', this._onResize); scan(); }, stopWatching() { if (this._mo) { this._mo.disconnect(); this._mo = null; } if (this._ro) { this._ro.disconnect(); this._ro = null; } if (this._onResize) { window.removeEventListener('resize', this._onResize); this._onResize = null; } if (this._debounce) { clearTimeout(this._debounce); this._debounce = null; } this._observed = []; document.documentElement.style.removeProperty('--gdx-anchor-h'); }, accept() { var persisted = this.writeConsent('accepted'); this.show = false; this.stopWatching(); if (!persisted) return; if (window.gtag) { gtag('consent', 'update', {'analytics_storage': 'granted'}); } window.dispatchEvent(new Event('gdx:analytics-consent-granted')); }, reject() { this.writeConsent('rejected'); this.show = false; this.stopWatching(); if (window.gtag) { gtag('consent', 'update', {'analytics_storage': 'denied'}); } window.dispatchEvent(new Event('gdx:analytics-consent-denied')); } }" x-show="show" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="translate-y-full opacity-0" x-transition:enter-end="translate-y-0 opacity-100" x-transition:leave="transition ease-in duration-200" x-transition:leave-start="translate-y-0 opacity-100" x-transition:leave-end="translate-y-full opacity-0" x-cloak class="fixed inset-x-0" style="bottom: var(--gdx-anchor-h, 0px); z-index: 2147483647;" role="dialog" aria-label="Cookie consent"> <div class="mx-auto max-w-5xl px-4 pb-4"> <div class="flex flex-col sm:flex-row items-center justify-between gap-3 rounded-xl bg-gray-900 dark:bg-gray-800 border border-gray-700 px-5 py-4 shadow-2xl text-sm text-gray-300"> <p class="text-center sm:text-left"> With your permission, GameDev.net uses analytics cookies to understand how people use the platform. You can accept analytics or continue with necessary cookies only. <a href="/privacy/" class="underline text-blue-400 hover:text-blue-300 ml-1">Learn more</a> </p> <div class="flex shrink-0 items-center gap-2"> <button type="button" @click="reject()" class="rounded-lg border border-gray-600 bg-transparent hover:bg-gray-800 active:bg-gray-700 px-5 py-2 text-sm font-semibold text-gray-200 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900"> Necessary only </button> <button type="button" @click="accept()" class="rounded-lg bg-blue-600 hover:bg-blue-500 active:bg-blue-700 px-5 py-2 text-sm font-semibold text-white transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900"> Accept analytics </button> </div> </div> </div> </div> </body> </html>