<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hasan Alp Güngör</title>
    <description>The latest articles on DEV Community by Hasan Alp Güngör (@hasanalpgungor).</description>
    <link>https://dev.to/hasanalpgungor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4209048%2F8b41db24-efd6-495b-857f-f6afeff2bdea.jpg</url>
      <title>DEV Community: Hasan Alp Güngör</title>
      <link>https://dev.to/hasanalpgungor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hasanalpgungor"/>
    <language>en</language>
    <item>
      <title>A Major-style live broadcast overlay for CS2 with Game State Integration</title>
      <dc:creator>Hasan Alp Güngör</dc:creator>
      <pubDate>Mon, 26 Oct 2026 16:13:22 +0000</pubDate>
      <link>https://dev.to/hasanalpgungor/a-major-style-live-broadcast-overlay-for-cs2-with-game-state-integration-2iaa</link>
      <guid>https://dev.to/hasanalpgungor/a-major-style-live-broadcast-overlay-for-cs2-with-game-state-integration-2iaa</guid>
      <description>&lt;p&gt;Watching Major tournaments, the broadcast overlay always caught my eye: every player's health, money and weapons, the killfeed, the bomb timer. I wanted to build something like it for my own matches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting data out of the game
&lt;/h2&gt;

&lt;p&gt;CS2 has a feature called Game State Integration (GSI). You put a file in the game's cfg folder, and during a match the game sends the data you asked for as JSON to the address you gave it. Mine asks for updates every 0.1 seconds, sent to a local server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Manyaksisko35 GSI v1"
{
 "uri" "http://127.0.0.1:3000"
 "throttle" "0.1"
 "data"
 {
   "map"                    "1"
   "round"                  "1"
   "player_state"           "1"
   "allplayers_state"       "1"
   "allplayers_weapons"     "1"
   "allplayers_match_stats" "1"
   "phase_countdowns"       "1"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Python server in between
&lt;/h2&gt;

&lt;p&gt;The JSON the game sends is big and messy. A small Flask server I wrote in Python takes it and boils it down: scores, the round, the bomb state, the time left, and for every player their name, health, money, main weapon, grenades and K/D. To find the main weapon I skip the knife, the C4 and the pistol, and fall back to the pistol if there's nothing else. The overlay pulls this clean data from a separate address.&lt;/p&gt;

&lt;h2&gt;
  
  
  An overlay that doesn't flicker
&lt;/h2&gt;

&lt;p&gt;The overlay is HTML, CSS and JavaScript, and it pulls from the server 10 times a second. My first version redrew every panel each time and it flickered on stream. Now each field only updates when its value actually changes, and the health bars slide smoothly with a CSS transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  The killfeed the game doesn't send
&lt;/h2&gt;

&lt;p&gt;GSI doesn't say who killed whom. So on every update I compare each player's kills and deaths with the previous state. Players whose kills went up are the killers, players whose deaths went up are the victims; I pair them up and add them to the killfeed with the weapon icon. Each line fades out after five seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kills&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kills&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;killers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deaths&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deaths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;victims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;killers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;victims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;addKillItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;killers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;victims&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The bomb timer
&lt;/h2&gt;

&lt;p&gt;When the bomb is planted, the round timer gives way to a 40-second red bomb bar. When a defuse starts, the bar turns white. The game doesn't say how long the defuse takes, so I look at the time left: under 5.5 seconds means the player has a kit and the bar runs over 5 seconds, otherwise over 10.&lt;/p&gt;

&lt;p&gt;The code is open on GitHub, and I also walked through building it on my YouTube channel.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://hasanalpgungor.com/en/blog/cs2-major-hud" rel="noopener noreferrer"&gt;my website&lt;/a&gt;. I'm Hasan Alp Güngör, a software and game developer from İzmir; more of my projects are at &lt;a href="https://hasanalpgungor.com/en" rel="noopener noreferrer"&gt;hasanalpgungor.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>python</category>
      <category>javascript</category>
      <category>esports</category>
    </item>
    <item>
      <title>Translating game text without breaking {placeholders}: how LocaGen works</title>
      <dc:creator>Hasan Alp Güngör</dc:creator>
      <pubDate>Mon, 26 Oct 2026 15:23:46 +0000</pubDate>
      <link>https://dev.to/hasanalpgungor/translating-game-text-without-breaking-placeholders-how-locagen-works-2h5g</link>
      <guid>https://dev.to/hasanalpgungor/translating-game-text-without-breaking-placeholders-how-locagen-works-2h5g</guid>
      <description>&lt;p&gt;Game text usually lives in a JSON file as keys and values, and translating a game looks as simple as translating those values. That's what I thought at first, until I ran into placeholders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome back, {player}!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"coins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You have %d coins"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;b&amp;gt;Danger:&amp;lt;/b&amp;gt; low health"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you send that straight to a translation service, {player} sometimes comes back translated, %d turns into "% d" and the &lt;b&gt; tag goes missing. The game then can't find its placeholders and either shows the wrong text or throws an error.&lt;/b&gt;&lt;/p&gt;
&lt;b&gt;
&lt;h2&gt;
  
  
  Hiding the placeholders
&lt;/h2&gt;

&lt;/b&gt;&lt;p&gt;My fix is simple: before translating, I find every placeholder and swap it for a marker like [VAR_0] or [VAR_1] that the translator leaves alone. When the translation comes back, I swap the markers back. Translators sometimes lowercase the marker, so I also check for [var_0].&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PATTERN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(\{.*?\}|&amp;lt;.*?&amp;gt;|%[sd])&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;placeholders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PATTERN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[VAR_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;placeholders&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleTranslator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;placeholders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Keys are never touched; only values get translated. The file can be a flat key-value map or a list of objects, and both work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The free version
&lt;/h2&gt;

&lt;p&gt;The free version is a desktop app written in Python. I designed its terminal-style interface with CustomTkinter and use Google Translate through deep-translator. Translation runs on a separate thread, so the interface doesn't freeze and the progress bar fills live. On launch the app checks GitHub for the latest release and downloads it if there's a new one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PRO version: Gemini that talks like a gamer
&lt;/h2&gt;

&lt;p&gt;Google Translate gets the words right, but it doesn't know how games talk; terms like "health potion" come out robotic. In the PRO version Gemini 2.5 Flash does the translation. I tell it to act as a game localization expert, to translate terms the way a player would say them, to never touch the keys and to return nothing but valid JSON.&lt;/p&gt;

&lt;p&gt;The biggest lesson from working with an AI model was not to trust its answer blindly. I always try to parse what Gemini sends back as JSON, and if it's invalid I show an error instead of handing the user a broken file. The PRO version runs on PHP and MySQL, with user accounts, translation history and an admin panel.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://hasanalpgungor.com/en/blog/locagen-placeholders" rel="noopener noreferrer"&gt;my website&lt;/a&gt;. I'm Hasan Alp Güngör, a software and game developer from İzmir; more of my projects are at &lt;a href="https://hasanalpgungor.com/en" rel="noopener noreferrer"&gt;hasanalpgungor.com&lt;/a&gt;.&lt;/em&gt;&lt;br&gt;
``&lt;/p&gt;



</description>
      <category>gamedev</category>
      <category>localization</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>How I turned my logo into 3D glass that breaks apart as you scroll</title>
      <dc:creator>Hasan Alp Güngör</dc:creator>
      <pubDate>Mon, 26 Oct 2026 15:22:36 +0000</pubDate>
      <link>https://dev.to/hasanalpgungor/how-i-turned-my-logo-into-3d-glass-that-breaks-apart-as-you-scroll-569l</link>
      <guid>https://dev.to/hasanalpgungor/how-i-turned-my-logo-into-3d-glass-that-breaks-apart-as-you-scroll-569l</guid>
      <description>&lt;p&gt;My portfolio started with a 3D game controller. Then I decided to use my own logo instead, and I wanted this: when the page opens the logo is intact, as you scroll it first cracks, then breaks apart, and every piece shows one of my skills. This post is about how I built that.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. From an image to a shape
&lt;/h2&gt;

&lt;p&gt;All I had was an image of the logo, and 3D needs corner points. I turned the image black and white with OpenCV, traced its contours, simplified away the extra points and scaled everything to 2.6 units tall. What was left was one outer outline and the eight holes inside the logo.&lt;/p&gt;

&lt;p&gt;Cutting the logo with straight lines looked fake, so I drew jagged "crack" lines and split the logo along them with shapely. That gave seven pieces that look actually broken: web, C# and Java, 3D, Python, game development, hardware and backend.&lt;/p&gt;

&lt;p&gt;I gave each piece depth with three.js's ExtrudeGeometry and added a bevel to the edges. Those rounded edges are what catch the light in the glass effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Glass with nothing behind it
&lt;/h2&gt;

&lt;p&gt;For the glass I used drei's MeshTransmissionMaterial, which fakes glass by bending the scene behind it. But there was a problem: the site's silver background isn't in the 3D scene, it's drawn with CSS. The 3D scene behind the logo was empty, so the glass had nothing to bend and looked like flat gray plastic.&lt;/p&gt;

&lt;p&gt;My fix was to redraw a lookalike of the background with a shader. Every frame I hide the logo, draw that background into an off-screen buffer (an FBO), and hand the buffer to the material as what's behind it. Now the glass bends the silver light behind the page. On top of that I added a separate fresnel shader that makes the edges glow white.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The scroll story
&lt;/h2&gt;

&lt;p&gt;I tied the logo's pose to the scroll position with GSAP ScrollTrigger. There are a few key poses (intact, cracked, exploded, turned) and scrolling moves between them. The labels on the pieces are plain HTML: every frame I project the piece's 3D position onto the screen and move the label there.&lt;/p&gt;

&lt;p&gt;The most annoying bug showed up here: scrolling back to the very top made the logo disappear. The cause was setting the ScrollTrigger start to "top top". At scroll 0 the trigger wasn't considered active, and I stopped rendering while it was inactive. Changing the start to "top bottom" fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Weak phones
&lt;/h2&gt;

&lt;p&gt;This glass effect is heavy, so when the site opens it looks at the device and picks one of three quality levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High: the full glass effect at a sharper resolution&lt;/li&gt;
&lt;li&gt;Medium: for phones and devices with 4 GB of memory or less; the glass uses fewer samples&lt;/li&gt;
&lt;li&gt;Lite: when data saver is on or the device is very weak; a simple translucent material instead of the glass effect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In case that guess is wrong, after a few seconds the site starts watching the frame rate, and if it stays under 30 frames per second it steps down a level on its own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Quality&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nav&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;Navigator&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;deviceMemory&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;saveData&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceMemory&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hardwareConcurrency&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;saveData&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;cores&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(pointer: coarse)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;cores&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Glass that ripples under the cursor
&lt;/h2&gt;

&lt;p&gt;Finally, I wanted the glass to ripple when the cursor is over the logo. I cast a ray from the pointer and check whether it hits the logo. If it does, I push the coordinates around the cursor with a sine wave in the background shader. Since the glass refracts that warped background, the ripple shows up inside the glass.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;toMouse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;uMouse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;vec2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uAspect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toMouse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;uTime&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;uHover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toMouse&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mo"&gt;03&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see the result, just open the home page and scroll down. All the code is open on GitHub.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://hasanalpgungor.com/en/blog/glass-logo" rel="noopener noreferrer"&gt;my website&lt;/a&gt;. I'm Hasan Alp Güngör, a software and game developer from İzmir; more of my projects are at &lt;a href="https://hasanalpgungor.com/en" rel="noopener noreferrer"&gt;hasanalpgungor.com&lt;/a&gt;.&lt;/em&gt;&lt;br&gt;
``&lt;/p&gt;

</description>
      <category>threejs</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
