Skip to main content
GameDev.net gamedev.net
🔒 Locked

Help a newbe practice HTML

Started by smiley4 Jan 30, 2003 at 4:01 PM 19 replies 500+ views
Original Post
smiley4
smiley4
Please help me practice. All you have to do is write things that will require operations out of normal conversation to be performed. Type what you want, and put it in a color format, a font, a size, and/or try to put in something that requires a more advanced understanding so I can read the text and figure out how to make my own messsages that way. Please start simple though and maybe this can become a new way of teaching others.
Now I shall systematicly disimboule you with a .... Click here for Project Anime
smiley4
smiley4
I know how to make a sentence in bold.

I know how to make a sentence in italics.

I think I know how to combine the two.

And I think I know how to change the font size as well.

[edited by - smiley4 on January 30, 2003 5:09:00 PM]
Now I shall systematicly disimboule you with a .... Click here for Project Anime
smiley4
smiley4
Thank you. I'm learning as I go.

Plus I've had prior experience with a WIYSIWYG editor.

Some of these basics I'm guessing at. Is there any script for moving text?

:* ;(

[edited by - smiley4 on January 30, 2003 5:21:51 PM]
Now I shall systematicly disimboule you with a .... Click here for Project Anime
alnite
alnite



return 0;
TheSorcerer
TheSorcerer
So you want to learn the basics of HTML.

(*Note: remove the space after < to make the tags work)

Text styling:
< FONT SIZE=[1-7] FACE="[Font Name]" COLOR="[Color Code]">[TEXT]< /FONT>
Example:
Test

Pictures:
< IMG SRC="[URL]" BORDER=[0 and bigger]>
Example:


Linking:
< A HREF="[URL]"> [TEXT] < /A>
Example:
Gamedev.net

Moving Text:
< P ALIGN="left/center/right">[TEXT]< /P>
Example:

Left

Center

Right





[edited by - TheSorcerer on January 30, 2003 5:32:18 PM]

[edited by - TheSorcerer on January 30, 2003 5:35:17 PM]
nes8bit
nes8bit
when linking images or pages, don''t forget to include the http:// part because it defaults to the current directory otherwise. (http://www.gamedev.net/community/forums/) For example. I wanted to link www.yourethemannowdog.com. You would have to include the http:// to create http://www.yourethemannowdog.com otherwise your browser will pick it up as http://www.gamedev.net/community/forums/www.yourethemannowdog.com


Days Hours Minutes remaining
owl
owl
quote:
Original post by Californium
Do you want to know how to crash peoples'' browsers?


Witch, you''ll burn in flames in hell!!
[size="2"]I like the Walrus best.
smiley4
smiley4
[u]what is the code for centering objects and text? [/u]

I know how to insert pics, I''m just finding the wrong web sites to insert them from.
Now I shall systematicly disimboule you with a .... Click here for Project Anime
smiley4
smiley4
I''m wanting to learn things in HTML, Cali, I don''t want to learn what can get me banned.
Now I shall systematicly disimboule you with a .... Click here for Project Anime
owl
owl
this font remembers me something...
wtf do you wanna hang today?

what if I write


oh I see...

Does
this work?



[edited by - xaxa on January 30, 2003 5:56:42 PM]
[size="2"]I like the Walrus best.
Beer Hunter
Beer Hunter
∀x,y∈R+ √(xy)=√x√y

[edited by - Beer Hunter on January 30, 2003 5:39:38 PM]
smiley4
smiley4
so you must counter everything in a carot or bracket?


so how do you make html send routines that correspond to code used in C++? (like in an online game)

[edited by - smiley4 on January 30, 2003 5:49:10 PM]
Now I shall systematicly disimboule you with a .... Click here for Project Anime
googlyeyes
googlyeyes
You don't need to counter everything
like <img> does not need </img>
and same for <embed> (embed is used for sound and video)
lots of things should be countered but don't need to if your lazy. like <body> and <html>

I suggest you check out this site: htmlgoodies.com

it's were I learned HTML and &#106avascript.<br><br>EDIT: edited so many times the post was twice as large as it should be so I got rid of all the extra edits <img src="smile.gif" width=15 height=15 align=middle><br><br><SPAN CLASS=editedby>[edited by - googlyeyes &#111;n January 30, 2003 6:13:15 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="January 30, 2003 05:03 PM">Jan 30, 2003 at 5:03 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-1759111"> <button type="button" hx-post="/forums/post/1759111/react/" hx-vals='{"type": "like"}' hx-target="#reactions-1759111" 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/1759111/react/" hx-vals='{"type": "helpful"}' hx-target="#reactions-1759111" 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/1759111/react/" hx-vals='{"type": "thanks"}' hx-target="#reactions-1759111" 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(1759111, 'googlyeyes')" 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(1759111)" 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-1759129" data-author-name="Evil Bill" 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"> <aside class="hidden sm:block w-28 flex-shrink-0 text-center"> <a href="/profile/15187-evil-bill" class="inline-block"> <span class="gdx-avatar w-12 h-12 rounded-full "> <span class="gdx-avatar__img gdx-avatar__fallback rounded-full cursor-pointer text-lg" data-user-tooltip data-member-id="15187"> <span aria-hidden="true">E</span> </span> </span> </a> <a href="/profile/15187-evil-bill" class="mt-2 block text-sm font-medium text-gray-900 dark:text-white hover:text-sky-600 truncate" data-tooltip-target="tooltip-author-15187"> Evil Bill </a> <div class="flex flex-wrap justify-center gap-1 mt-1"> </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> 48 </p> </aside> <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="Evil Bill" class="gdx-avatar__img rounded-full cursor-pointer object-cover" data-user-tooltip data-member-id="15187"> </span> <div> <a href="/profile/15187-evil-bill" class="font-medium text-gray-900 dark:text-white hover:text-purple-600 text-xs" data-user-tooltip data-member-id="15187"> Evil Bill </a> <div class="text-[10px] text-gray-500 dark:text-gray-400"> <time>Jan 30, 2003 at 5:17 PM</time> </div> </div> </div> </div> <div id="post-content-1759129" class="prose dark:prose-invert max-w-none flex-1 pb-3"> you could use <a href="http://www.doctor-html.com/RxHTML/cgi-bin/single.cgi">Dr. HTML</a> to check stuff. <br><br><img src="http://www.cee.hw.ac.uk/~ceesm6/Pics/StevesSig.gif"><br><br><br><SPAN CLASS=editedby>[edited by - Evil Bill &#111;n January 30, 2003 6:18:22 PM]</SPAN><br><br><SPAN CLASS=editedby>[edited by - Evil Bill &#111;n January 30, 2003 6:18:44 PM]</SPAN> </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"><span><span>Member of the Unban Mindwipe Society (UMWS)</span></span></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="January 30, 2003 05:17 PM">Jan 30, 2003 at 5:17 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-1759129"> <button type="button" hx-post="/forums/post/1759129/react/" hx-vals='{"type": "like"}' hx-target="#reactions-1759129" 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/1759129/react/" hx-vals='{"type": "helpful"}' hx-target="#reactions-1759129" 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/1759129/react/" hx-vals='{"type": "thanks"}' hx-target="#reactions-1759129" 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(1759129, 'Evil Bill')" 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(1759129)" 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-1759196" data-author-name="evil sausage" 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"> <aside class="hidden sm:block w-28 flex-shrink-0 text-center"> <a href="/profile/34317-evil-sausage" class="inline-block"> <span class="gdx-avatar w-12 h-12 rounded-full "> <span class="gdx-avatar__img gdx-avatar__fallback rounded-full cursor-pointer text-lg" data-user-tooltip data-member-id="34317"> <span aria-hidden="true">E</span> </span> </span> </a> <a href="/profile/34317-evil-sausage" class="mt-2 block text-sm font-medium text-gray-900 dark:text-white hover:text-sky-600 truncate" data-tooltip-target="tooltip-author-34317"> evil sausage </a> <div class="flex flex-wrap justify-center gap-1 mt-1"> </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> 40 </p> </aside> <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="evil sausage" class="gdx-avatar__img rounded-full cursor-pointer object-cover" data-user-tooltip data-member-id="34317"> </span> <div> <a href="/profile/34317-evil-sausage" class="font-medium text-gray-900 dark:text-white hover:text-purple-600 text-xs" data-user-tooltip data-member-id="34317"> evil sausage </a> <div class="text-[10px] text-gray-500 dark:text-gray-400"> <time>Jan 30, 2003 at 6:08 PM</time> </div> </div> </div> </div> <div id="post-content-1759196" class="prose dark:prose-invert max-w-none flex-1 pb-3"> <font face=wingdings size=100 color=orange><br>Look at me!!!!!! I am typing in wingdings!!!!!!!!!!! <br><br><a href=http://www31.brinkster.com/evilsausage/death>Let your eyes bleed!</a><br><br><SPAN CLASS=editedby>[edited by - evil sausage &#111;n January 30, 2003 7:08:58 PM]</SPAN> </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">And so, the <a href="http://gamedev.net/community/forums/topic.asp?topic_id=151944">evil religion thread</a> stole the wonderful "What the Heck?" thread's throne. Truly a sad day for gamedev.</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="January 30, 2003 06:08 PM">Jan 30, 2003 at 6:08 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-1759196"> <button type="button" hx-post="/forums/post/1759196/react/" hx-vals='{"type": "like"}' hx-target="#reactions-1759196" 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/1759196/react/" hx-vals='{"type": "helpful"}' hx-target="#reactions-1759196" 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/1759196/react/" hx-vals='{"type": "thanks"}' hx-target="#reactions-1759196" 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(1759196, 'evil sausage')" 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(1759196)" 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-1759222" data-author-name="TheSorcerer" 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"> <aside class="hidden sm:block w-28 flex-shrink-0 text-center"> <a href="/profile/30653-thesorcerer" class="inline-block"> <span class="gdx-avatar w-12 h-12 rounded-full "> <span class="gdx-avatar__img gdx-avatar__fallback rounded-full cursor-pointer text-lg" data-user-tooltip data-member-id="30653"> <span aria-hidden="true">T</span> </span> </span> </a> <a href="/profile/30653-thesorcerer" class="mt-2 block text-sm font-medium text-gray-900 dark:text-white hover:text-sky-600 truncate" data-tooltip-target="tooltip-author-30653"> TheSorcerer </a> <div class="flex flex-wrap justify-center gap-1 mt-1"> </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> 72 </p> </aside> <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="TheSorcerer" class="gdx-avatar__img rounded-full cursor-pointer object-cover" data-user-tooltip data-member-id="30653"> </span> <div> <a href="/profile/30653-thesorcerer" class="font-medium text-gray-900 dark:text-white hover:text-purple-600 text-xs" data-user-tooltip data-member-id="30653"> TheSorcerer </a> <div class="text-[10px] text-gray-500 dark:text-gray-400"> <time>Jan 30, 2003 at 6:25 PM</time> </div> </div> </div> </div> <div id="post-content-1759222" class="prose dark:prose-invert max-w-none flex-1 pb-3"> <FONT FACE="wingdings" SIZE=6>You suck at typing in wingdings</FONT><br><br> <br><br><A HREF="http://thesorcerer.no-ip.org" TARGET="_blank"><IMG SRC="http://www.home.zonnet.nl/TheSorcerer/GDNetSig.jpg" BORDER="0"></a> </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"><a href="http://www.sorcsoft.com"></a><a title="Popular Mechanics, 1949">"Computers in the future may weigh no more than 1.5 tons."</a><a title="Thomas Watson, Chairman of IBM, 1943">"I think there is a world market for maybe five computers."</a><a title="Ken Olson, President, Chairman and Founder of DEC">"There is no reason anyone would want a computer in their home."</a></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="January 30, 2003 06:25 PM">Jan 30, 2003 at 6:25 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-1759222"> <button type="button" hx-post="/forums/post/1759222/react/" hx-vals='{"type": "like"}' hx-target="#reactions-1759222" 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/1759222/react/" hx-vals='{"type": "helpful"}' hx-target="#reactions-1759222" 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/1759222/react/" hx-vals='{"type": "thanks"}' hx-target="#reactions-1759222" 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(1759222, 'TheSorcerer')" 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(1759222)" 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="3d-environment-design-with-blender-5" data-commerce-reason="product_fallback:global" data-commerce-variant="compact_product" aria-label="Affiliate recommendation" > <a href="/resources/go/3/?surface=forum_topic_endcap" 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/71N3k+HUY2L._SL1360_.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">3D Environment Design with Blender 5</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">Based on Blender 5, this updated second edition simplifies the complexities by guiding you through clear, practical workflows to overcome those challenges. You’ll start by addressing common modeling and proportion mistakes, then move on to realistic texturing and unwrapping to create stunning scenes based on actual references.</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">GameDev.net may earn a commission if you purchase through these links. This helps fund the site at no extra cost to you.</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> <nav class="mt-8 flex items-center justify-between"> <div class="text-sm text-gray-500 dark:text-gray-400"> Page 1 of 2 </div> <div class="flex items-center gap-1"> <span class="px-3 py-2 rounded-lg bg-primary-600 text-white text-sm font-medium">1</span> <a href="/forums/topic/136843-help-a-newbe-practice-html?page=2" class="px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 text-sm"> 2 </a> <a href="/forums/topic/136843-help-a-newbe-practice-html?page=2" class="px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 text-sm"> Next → </a> </div> </nav> <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/136843-help-a-newbe-practice-html/" 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-400 dark:text-gray-600 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">500+</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">19</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">Jan 30, 2003</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">Jan 30, 2003</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/39054-smiley4" title="smiley4" 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" data-user-tooltip data-member-id="39054"> <span aria-hidden="true">S</span> </span> </span> </a> </div> </div> </section> <div hx-get="/forums/topic/136843/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/720320-exploring-some-general-gameplay-ideas-looking-for-feedback" 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"> Exploring some general gameplay ideas, looking for feedback. </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> 43 </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> 1.6k </span> <span class="truncate">Game Design and Theory</span> </div> </a> </li> <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> 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> 400+ </span> <span class="truncate">Game Design and Theory</span> </div> </a> </li> <li class="group"> <a href="/forums/topic/720440-ritual-before-work" 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"> ritual before work </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> 200+ </span> <span class="truncate">GDNet Lounge</span> </div> </a> </li> <li class="group"> <a href="/forums/topic/720542-double-free-during-foreach-loop-of-arraystring-with-custom-array-and-string-addons" 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 w-4 h-4 flex items-center justify-center bg-emerald-100 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-400 rounded text-[10px]">✓</span> <span class="text-sm text-gray-900 dark:text-white group-hover:text-sky-600 line-clamp-2"> Double free during foreach loop of array&lt;string&gt; with custom array and string addons </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> 250+ </span> <span class="truncate">AngelCode</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">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-gray-600 dark:text-gray-400"> 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">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">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-sky-600 font-medium bg-sky-50 dark:bg-sky-900/20"> 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/25-gdnet-lounge" 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 GDNet Lounge </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-400 dark:text-gray-600 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/136843/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="/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.4ca339e55408.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="Xe7dV8RkpAANdSAjaph7YRUjlqqAC3bwgxrhUVrwGwhOuTf3sd6Op6yywdLfBDg8"> <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; } }, accept() { var persisted = this.writeConsent('accepted'); this.show = false; 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; 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 bottom-0 inset-x-0 z-[9999]" 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>