{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Stefan Wagner",
  "home_page_url": "https://stefanwagner.dev/",
  "feed_url": "https://stefanwagner.dev/feed.json",
  "description": "Senior Software Engineer with 20 years of experience building innovative solutions in robotics, AI/ML, and full-stack development. From building software for industrial robots to creating mobile games with millions of downloads, I bring technical expertise and proven leadership to every project.",
  "icon": "https://stefanwagner.dev/apple-touch-icon.png",
  "favicon": "https://stefanwagner.dev/favicon.ico",
  "authors": [
    {
      "name": "Stefan Wagner",
      "url": "https://stefanwagner.dev/"
    }
  ],
  "language": "en-US",
  "items": [
    
    {
      "id": "https://stefanwagner.dev/2021/02/15/wandelbots/",
      "url": "https://stefanwagner.dev/2021/02/15/wandelbots/",
      "title": "Joining Wandelbots",
      "content_html": "\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/robot_clip_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/robot_clip.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/robot_clip.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/robot_clip.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>I’m excited to join <a href=\"https://wandelbots.com\">Wandelbots</a> as a Senior Software Engineer! We’re building no-code solutions that make industrial robotics accessible to everyone.</p>\n\n<!--more-->\n\n<p>Traditional robot programming is complex, especially when environments change frequently, making it extremely hard to account for every variation in code. Wandelbots is changing this by letting robots learn from human demonstrations and adapt to new situations through imitation learning.</p>\n\n<p>I’m working across several areas. On the platform side, I’m building infrastructure that lets customers embed interactive 3D robot visualizations in their own applications using Three.js and React. This includes real-time robot simulation and path planning visualization. I’m also contributing to AI systems where robots learn from human demonstrations to adapt to changing environments that would otherwise be very difficult to program explicitly. Additionally, I’m developing specialized industrial solutions like robot welding applications with path planning and weld parameter optimization.</p>\n\n<p>The tech stack spans TypeScript, React, Three.js, Python, WebGL, and ROS. It’s a great mix of full-stack development, 3D graphics, robotics control systems, and cutting-edge AI/ML work. Looking forward to helping manufacturers adopt robotics automation more easily!</p>\n\n<p><a href=\"https://wandelbots.com\">Learn more about Wandelbots</a></p>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nI’m excited to join Wandelbots as a Senior Software Engineer! We’re building no-code solutions that make industrial robotics accessible to everyone.\n\n",
      "date_published": "2021-02-15T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/2020/12/01/sqin-google/",
      "url": "https://stefanwagner.dev/2020/12/01/sqin-google/",
      "title": "SQIN - Google Play Best App of 2020",
      "content_html": "\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/sqin_google_best_app_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/sqin_google_best_app.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/sqin_google_best_app.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/sqin_google_best_app.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>Google Play recognized SQIN as one of the <strong>Best Apps of 2020</strong> in the #PersonalGrowth category! 🥇</p>\n\n<!--more-->\n\n<p>Out of millions of apps on Google Play, SQIN was selected for Google’s prestigious annual awards. It’s a beauty and skincare app that helps users make informed decisions about cosmetic products through ingredient analysis, personalized recommendations, and community reviews.</p>\n\n<p>As a lead developer, I worked on performance optimization, technical architecture improvements, and implementing beauty and skincare analysis features. This included mobile app development with modern Android best practices and integration with computer vision and ML systems for product recognition and analysis.</p>\n\n<p>The award was great validation of our focus on user experience and technical excellence in the personal care space.</p>\n\n<p><a href=\"https://play.google.com/store/apps/editorial_collection/promotion_topic_bestof2020_xfn_hub\">View Google Play Best of 2020 Collection</a></p>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nGoogle Play recognized SQIN as one of the Best Apps of 2020 in the #PersonalGrowth category! 🥇\n\n",
      "date_published": "2020-12-01T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/2020/08/20/bequ-face-scanner/",
      "url": "https://stefanwagner.dev/2020/08/20/bequ-face-scanner/",
      "title": "SQIN Face Scanner",
      "content_html": "\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/sqin_face_shape_scanner_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/sqin_face_shape_scanner.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/sqin_face_shape_scanner.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/sqin_face_shape_scanner.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>Built a custom face analysis system for SQIN using TensorFlow Lite and Unity. The system does accurate facial feature recognition across devices without requiring ARKit or ARCore.</p>\n\n<!--more-->\n\n<p>I developed a Unity plugin that integrates TensorFlow Lite for real-time 3D face mesh extraction. It’s based on Google’s MediaPipe Face Mesh technology but optimized for mobile performance. The plugin works consistently across iOS, Android, and even the Unity Editor, with no dependency on platform-specific AR frameworks. We’re getting 30+ FPS on mid-range devices with 468 3D facial landmark points.</p>\n\n<p>The implementation involved building a custom C++ to C# bridge for TensorFlow Lite integration and careful memory management to stay within mobile constraints. The face scanner analyzes facial features to provide personalized skincare and beauty product recommendations tailored to face shape, skin tone, and other features.</p>\n\n<p>The main challenge was achieving reliable face mesh extraction without platform-specific AR frameworks. This made the feature accessible to a much broader range of devices while maintaining high performance and accuracy.</p>\n\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/sqin_ar_in_unity_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/sqin_ar_in_unity.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/sqin_ar_in_unity.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/sqin_ar_in_unity.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>Built with Unity, C#, C++, and TensorFlow Lite.</p>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nBuilt a custom face analysis system for SQIN using TensorFlow Lite and Unity. The system does accurate facial feature recognition across devices without req...",
      "date_published": "2020-08-20T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/2020/04/26/covid/",
      "url": "https://stefanwagner.dev/2020/04/26/covid/",
      "title": "Visualization of the regional COVID-19 Development in Germany",
      "content_html": "\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/last_7_days_maps_result_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/last_7_days_maps_result.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/last_7_days_maps_result.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/last_7_days_maps_result.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>The situation report from the RKI contains a figure which shows the development of the pandemic in Germany over the last 7 days. I wanted to know how this progresses over time and whether the outbreak clusters remain regional.</p>\n\n<p>I used Pandas and Folium to recreate the figure and created this animation. See the <a href=\"https://github.com/bompo/covid19_rki_visualization\">code on Github</a> to reproduce the result with current data.</p>\n\n<!--more-->\n\n<p>I used Pandas to preprocess the raw data from the RKI.</p>\n\n<figure class=\"highlight\"><pre><code class=\"language-js\" data-lang=\"js\"><span class=\"nx\">def</span> <span class=\"nf\">new_cases_by_date</span><span class=\"p\">(</span><span class=\"nx\">rki_raw</span><span class=\"p\">,</span> <span class=\"nx\">rki_flag_column</span><span class=\"o\">=</span><span class=\"dl\">'</span><span class=\"s1\">Neuer Fall</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">rki_count_columns</span><span class=\"o\">=</span><span class=\"dl\">'</span><span class=\"s1\">AnzahlFall</span><span class=\"dl\">'</span><span class=\"p\">):</span>\n    <span class=\"nx\">condition</span> <span class=\"o\">=</span> <span class=\"nx\">rki_raw</span><span class=\"p\">[</span><span class=\"nx\">rki_flag_column</span><span class=\"p\">].</span><span class=\"nf\">isin</span><span class=\"p\">((</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">))</span>\n    <span class=\"nx\">rki_series</span> <span class=\"o\">=</span> <span class=\"nx\">rki_raw</span><span class=\"p\">[</span><span class=\"nx\">condition</span><span class=\"p\">].</span><span class=\"nf\">groupby</span><span class=\"p\">([</span><span class=\"dl\">'</span><span class=\"s1\">IdLandkreis</span><span class=\"dl\">'</span><span class=\"p\">])[</span><span class=\"nx\">rki_count_columns</span><span class=\"p\">].</span><span class=\"nf\">sum</span><span class=\"p\">().</span><span class=\"nf\">to_frame</span><span class=\"p\">(</span><span class=\"nx\">name</span> <span class=\"o\">=</span> <span class=\"nx\">rki_count_columns</span><span class=\"p\">).</span><span class=\"nf\">reset_index</span><span class=\"p\">()</span>\n    \n    <span class=\"nx\">rki_series</span> <span class=\"o\">=</span> <span class=\"nx\">rki_series</span><span class=\"p\">[[</span><span class=\"dl\">'</span><span class=\"s1\">IdLandkreis</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"dl\">'</span><span class=\"s1\">AnzahlFall</span><span class=\"dl\">'</span><span class=\"p\">]]</span>\n\n    <span class=\"err\">#</span> <span class=\"nx\">join</span> <span class=\"nx\">geodata</span>\n    <span class=\"nx\">rki_series</span><span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">IdLandkreis</span><span class=\"dl\">'</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"nx\">rki_series</span><span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">IdLandkreis</span><span class=\"dl\">'</span><span class=\"p\">].</span><span class=\"nf\">astype</span><span class=\"p\">(</span><span class=\"nx\">str</span><span class=\"p\">).</span><span class=\"nx\">str</span><span class=\"p\">.</span><span class=\"nf\">zfill</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">)</span>\n    <span class=\"nx\">rki_series</span> <span class=\"o\">=</span> <span class=\"nx\">rki_series</span><span class=\"p\">.</span><span class=\"nf\">set_index</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">IdLandkreis</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n    <span class=\"nx\">rki_series</span> <span class=\"o\">=</span> <span class=\"nx\">rki_series</span><span class=\"p\">.</span><span class=\"nf\">join</span><span class=\"p\">(</span><span class=\"nx\">rki_geo</span><span class=\"p\">)</span>\n\n    <span class=\"k\">return</span> <span class=\"nx\">rki_series</span></code></pre></figure>\n\n<figure class=\"highlight\"><pre><code class=\"language-js\" data-lang=\"js\"><span class=\"nx\">start_date</span> <span class=\"o\">=</span> <span class=\"nx\">pd</span><span class=\"p\">.</span><span class=\"nf\">to_datetime</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">2020-03-01</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n<span class=\"nx\">end_date</span> <span class=\"o\">=</span> <span class=\"nx\">rki_raw</span><span class=\"p\">.</span><span class=\"nx\">index</span><span class=\"p\">.</span><span class=\"nf\">max</span><span class=\"p\">().</span><span class=\"nf\">tz_convert</span><span class=\"p\">(</span><span class=\"nx\">None</span><span class=\"p\">)</span>\n\n<span class=\"nx\">frames</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n\n<span class=\"k\">for</span> <span class=\"nx\">j</span> <span class=\"k\">in</span> <span class=\"nx\">pd</span><span class=\"p\">.</span><span class=\"nf\">date_range</span><span class=\"p\">(</span><span class=\"nx\">start_date</span><span class=\"p\">,</span> <span class=\"nx\">end_date</span> <span class=\"o\">-</span> <span class=\"nx\">pd</span><span class=\"p\">.</span><span class=\"nf\">to_timedelta</span><span class=\"p\">(</span><span class=\"mi\">6</span><span class=\"p\">,</span><span class=\"dl\">'</span><span class=\"s1\">d</span><span class=\"dl\">'</span><span class=\"p\">)):</span>\n    <span class=\"nx\">startrange</span> <span class=\"o\">=</span> <span class=\"nx\">j</span><span class=\"p\">.</span><span class=\"nf\">strftime</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">%Y-%m-%d</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n    <span class=\"nx\">endrange</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">j</span> <span class=\"o\">+</span> <span class=\"nx\">pd</span><span class=\"p\">.</span><span class=\"nf\">to_timedelta</span><span class=\"p\">(</span><span class=\"mi\">6</span><span class=\"p\">,</span><span class=\"dl\">'</span><span class=\"s1\">d</span><span class=\"dl\">'</span><span class=\"p\">)).</span><span class=\"nf\">strftime</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">%Y-%m-%d</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n    <span class=\"nx\">rki_cases</span> <span class=\"o\">=</span> <span class=\"nf\">new_cases_by_date</span><span class=\"p\">(</span><span class=\"nx\">rki_raw</span><span class=\"p\">[</span><span class=\"nx\">startrange</span><span class=\"p\">:</span><span class=\"nx\">endrange</span><span class=\"p\">],</span> <span class=\"nx\">rki_flag_column</span><span class=\"o\">=</span><span class=\"dl\">'</span><span class=\"s1\">NeuerFall</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">rki_count_columns</span><span class=\"o\">=</span><span class=\"dl\">'</span><span class=\"s1\">AnzahlFall</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n    <span class=\"nx\">rki_cases</span><span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">date</span><span class=\"dl\">'</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">j</span> <span class=\"o\">+</span> <span class=\"nx\">pd</span><span class=\"p\">.</span><span class=\"nf\">to_timedelta</span><span class=\"p\">(</span><span class=\"mi\">6</span><span class=\"p\">,</span><span class=\"dl\">'</span><span class=\"s1\">d</span><span class=\"dl\">'</span><span class=\"p\">))</span>\n    <span class=\"nx\">rki_cases</span><span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">RS</span><span class=\"dl\">'</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"nx\">rki_cases</span><span class=\"p\">.</span><span class=\"nx\">index</span>\n    <span class=\"nx\">rki_cases</span><span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">cases_per_100k</span><span class=\"dl\">'</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"nx\">rki_cases</span><span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">AnzahlFall</span><span class=\"dl\">'</span><span class=\"p\">]</span> <span class=\"o\">/</span> <span class=\"nx\">rki_cases</span><span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">EWZ</span><span class=\"dl\">'</span><span class=\"p\">]</span> <span class=\"o\">*</span> <span class=\"mi\">100000</span>\n\n    <span class=\"nx\">rki_cases</span> <span class=\"o\">=</span> <span class=\"nx\">rki_cases</span><span class=\"p\">.</span><span class=\"nf\">set_index</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">date</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n    <span class=\"nx\">frames</span><span class=\"p\">.</span><span class=\"nf\">append</span><span class=\"p\">(</span><span class=\"nx\">rki_cases</span><span class=\"p\">)</span>\n\n<span class=\"nx\">result</span> <span class=\"o\">=</span> <span class=\"nx\">pd</span><span class=\"p\">.</span><span class=\"nf\">concat</span><span class=\"p\">(</span><span class=\"nx\">frames</span><span class=\"p\">)</span></code></pre></figure>\n\n<p>The map is generated with the choropleth function from Folium. Selenium is used to store the maps as png sequence.</p>\n\n<figure class=\"highlight\"><pre><code class=\"language-js\" data-lang=\"js\"><span class=\"nx\">bins</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">25</span><span class=\"p\">,</span> <span class=\"mi\">50</span><span class=\"p\">,</span> <span class=\"mi\">100</span><span class=\"p\">,</span> <span class=\"mi\">500</span><span class=\"p\">]</span>\n<span class=\"nx\">map_osm</span> <span class=\"o\">=</span> <span class=\"nx\">folium</span><span class=\"p\">.</span><span class=\"nc\">Map</span><span class=\"p\">(</span><span class=\"nx\">attr</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">Robert Koch-Institut (RKI), dl-de/by-2-0</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"nx\">location</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"mf\">51.3</span><span class=\"p\">,</span> <span class=\"mf\">10.5</span><span class=\"p\">],</span> <span class=\"nx\">tiles</span><span class=\"o\">=</span><span class=\"dl\">'</span><span class=\"s1\">cartodbpositron</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">zoom_start</span><span class=\"o\">=</span><span class=\"mi\">7</span><span class=\"p\">)</span>\n\n<span class=\"nx\">#result</span><span class=\"p\">.</span><span class=\"nf\">apply</span><span class=\"p\">(</span><span class=\"nx\">lambda</span> <span class=\"nx\">row</span><span class=\"p\">:</span><span class=\"nx\">folium</span><span class=\"p\">.</span><span class=\"nc\">GeoJson</span><span class=\"p\">(</span><span class=\"nx\">row</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">],</span> <span class=\"nx\">fill_color</span><span class=\"o\">=</span><span class=\"nf\">colorscale</span><span class=\"p\">(</span><span class=\"nx\">row</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">])).</span><span class=\"nf\">add_to</span><span class=\"p\">(</span><span class=\"nx\">map_osm</span><span class=\"p\">),</span> <span class=\"nx\">axis</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n<span class=\"nx\">folium</span><span class=\"p\">.</span><span class=\"nc\">Choropleth</span><span class=\"p\">(</span>\n    <span class=\"nx\">geo_data</span><span class=\"o\">=</span><span class=\"nx\">rki_geo_raw</span><span class=\"p\">,</span>\n    <span class=\"nx\">data</span><span class=\"o\">=</span><span class=\"nx\">result</span><span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">03-10-2020</span><span class=\"dl\">'</span><span class=\"p\">:</span><span class=\"dl\">'</span><span class=\"s1\">03-10-2020</span><span class=\"dl\">'</span><span class=\"p\">],</span>\n    <span class=\"nx\">columns</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"dl\">'</span><span class=\"s1\">RS</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"dl\">'</span><span class=\"s1\">cases_per_100k</span><span class=\"dl\">'</span><span class=\"p\">],</span>\n    <span class=\"nx\">key_on</span><span class=\"o\">=</span><span class=\"dl\">'</span><span class=\"s1\">feature.properties.RS</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n    <span class=\"nx\">fill_color</span><span class=\"o\">=</span><span class=\"dl\">'</span><span class=\"s1\">YlOrRd</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n    <span class=\"nx\">fill_opacity</span><span class=\"o\">=</span><span class=\"mf\">0.6</span><span class=\"p\">,</span>\n    <span class=\"nx\">line_opacity</span><span class=\"o\">=</span><span class=\"mf\">0.0</span><span class=\"p\">,</span>\n    <span class=\"nx\">nan_fill_color</span><span class=\"o\">=</span><span class=\"dl\">'</span><span class=\"s1\">#f5f5f3</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n    <span class=\"nx\">legend_name</span><span class=\"o\">=</span><span class=\"dl\">'</span><span class=\"s1\">cases per 100k</span><span class=\"dl\">'</span><span class=\"p\">,</span>\n    <span class=\"nx\">bins</span><span class=\"o\">=</span><span class=\"p\">[</span><span class=\"nf\">float</span><span class=\"p\">(</span><span class=\"nx\">x</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"nx\">x</span> <span class=\"k\">in</span> <span class=\"nx\">bins</span><span class=\"p\">],</span>\n    <span class=\"nx\">smooth_factor</span> <span class=\"o\">=</span> <span class=\"mf\">0.1</span>\n<span class=\"p\">).</span><span class=\"nf\">add_to</span><span class=\"p\">(</span><span class=\"nx\">map_osm</span><span class=\"p\">)</span></code></pre></figure>\n\n<p>The final animation is created with FFMPEG and this <a href=\"https://gist.github.com/anguyen8/d0630b6aef6c1cd79b9a1341e88a573e\">Gist</a></p>\n\n<p><a href=\"https://github.com/bompo/covid19_rki_visualization\">Github</a></p>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nThe situation report from the RKI contains a figure which shows the development of the pandemic in Germany over the last 7 days. I wanted to know how this p...",
      "date_published": "2020-04-26T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/2020/04/10/bequ-scanner/",
      "url": "https://stefanwagner.dev/2020/04/10/bequ-scanner/",
      "title": "BeQu Product Scanner",
      "content_html": "\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/bequ_scanner_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/bequ_scanner.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/bequ_scanner.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/bequ_scanner.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>We developed a product recognition system using machine learning that identifies cosmetic products without requiring barcodes. Many beauty products either don’t have visible barcodes or they’re hidden inside packaging, so this was a real game-changer for the user experience.</p>\n\n<!--more-->\n\n<p>The system uses a custom-trained convolutional neural network that can recognize products in real-time on mobile devices. It works even with partial product views and various lighting conditions, handling thousands of SKUs with high accuracy. The architecture is optimized for on-device inference, so it’s fast and works offline once the model is downloaded.</p>\n\n<p>We integrated it with a product database and inventory system, and added a gamification layer where users earn stars for scanning products. They can exchange these stars for vouchers and products, which significantly improved engagement.</p>\n\n<p>The technical implementation involved mobile-first architecture, integration with backend systems for the product catalog, and careful optimization to make the inference run smoothly on mid-range devices. We built it with TensorFlow/PyTorch for the ML components, standard mobile development frameworks for Android/iOS, and REST APIs for the backend communication.</p>\n\n<div class=\"youtube-container\" data-video-id=\"wWG9vu5i4ts\">\n  \n  <picture>\n    <source srcset=\"/assets/images/bequ_scanner.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/bequ_scanner.jpg\" alt=\"Video thumbnail for wWG9vu5i4ts\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nWe developed a product recognition system using machine learning that identifies cosmetic products without requiring barcodes. Many beauty products either d...",
      "date_published": "2020-04-10T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/2019/10/10/bequ-at-glow/",
      "url": "https://stefanwagner.dev/2019/10/10/bequ-at-glow/",
      "title": "BeQu at Glow 2019",
      "content_html": "\n<div class=\"youtube-container\" data-video-id=\"JMhP_zKbFMo\">\n  \n  <picture>\n    <source srcset=\"/assets/images/bequ_cover.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/bequ_cover.jpg\" alt=\"Video thumbnail for JMhP_zKbFMo\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n\n<p>We showcased our new app BeQu at Glow 2019. For two days users could try out exclusive content and win products from our partner brands!</p>\n\n<p>In our app, we link the right users with the products that interest them in the form of mobile games, user surveys and state-of-the-art augmented reality.\nBeQu turns simple shopping into an emotional, shareable experience that takes the pulse of the hard-to-reach millennials up to the Alpha generation.</p>\n",
      "summary": "\n\n  \n  \n    \n    \n  \n  \n  \n    \n      \n      \n    \n  \n\n\nWe showcased our new app BeQu at Glow 2019. For two days users could try out exclusive content and win products from our partner brands!\n\nIn our app, we link the right users with the products that interest them in the for...",
      "date_published": "2019-10-10T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/2019/08/03/beautygan/",
      "url": "https://stefanwagner.dev/2019/08/03/beautygan/",
      "title": "BeautyGAN",
      "content_html": "<p><img src=\"/assets/images/beautygan.jpg\" alt=\"placeholder\" /></p>\n\n<p>Explored Make-Up Style Transfer with <a href=\"https://github.com/Honlan/BeautyGAN\">BeautyGAN</a>.</p>\n\n<p>The network is trained with make-up and non-make-up pictures. It’s possible to apply different make-up styles (eg. from Influencers from Instagram) to your image. See the picture for examples.</p>\n",
      "summary": "\n\nExplored Make-Up Style Transfer with BeautyGAN.\n\nThe network is trained with make-up and non-make-up pictures. It’s possible to apply different make-up styles (eg. from Influencers from Instagram) to your image. See the picture for examples.\n",
      "date_published": "2019-08-03T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/2019/07/01/coffee/",
      "url": "https://stefanwagner.dev/2019/07/01/coffee/",
      "title": "Coffee Barista",
      "content_html": "\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/coffee_barista_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/coffee_barista.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/coffee_barista.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/coffee_barista.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>An ASMR game prototype this time together with @pavethem. The goal of the game is to draw latte art and satisfy your customers. We used fluid dynamics (base from <a href=\"https://github.com/keijiro/StableFluids\">stable fluids</a>) on the GPU to simulate the foam.</p>\n\n<!--more-->\n\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/coffee_proto_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/coffee_proto.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/coffee_proto.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/coffee_proto.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nAn ASMR game prototype this time together with @pavethem. The goal of the game is to draw latte art and satisfy your customers. We used fluid dynamics (base...",
      "date_published": "2019-07-01T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/2019/04/14/pix2pix/",
      "url": "https://stefanwagner.dev/2019/04/14/pix2pix/",
      "title": "Pix2Pix ML",
      "content_html": "\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/pix2pix_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/pix2pix.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/pix2pix.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/pix2pix.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>Different kind of training for CR7 to use him as a virtual avatar with Pix2Pix (using the code of <a href=\"https://github.com/datitran/face2face-demo\">datitran</a>).</p>\n\n<p>I generated the dataset from <a href=\"https://www.youtube.com/watch?v=V_vjWX8tawQ\">this video</a>. The Network is trained with OpenCV landmark images from a source video. The same landmark detection is applied to the webcam output.</p>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nDifferent kind of training for CR7 to use him as a virtual avatar with Pix2Pix (using the code of datitran).\n\nI generated the dataset from this video. The N...",
      "date_published": "2019-04-14T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/2019/02/26/visual-importance/",
      "url": "https://stefanwagner.dev/2019/02/26/visual-importance/",
      "title": "Visual Importance ML",
      "content_html": "\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/instagram_visual_importance_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/instagram_visual_importance.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/instagram_visual_importance.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/instagram_visual_importance.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>Experimenting with ML-driven design analysis using <a href=\"https://github.com/cvzoya/visimportance\">visual importance networks</a> trained on eye-tracking data to predict where users focus attention in UI designs.</p>\n\n<!--more-->\n\n<p>Visual importance is a machine learning approach trained on real eye-tracking heatmaps from UI studies. The network predicts which areas of a design naturally draw user attention - red areas show high interest, blue shows low interest.</p>\n\n<p>I applied the model to Instagram’s Oscar feed and analyzed designs from our Quiz Friends app to validate UI flow. The model effectively highlights high-contrast areas and visual hierarchy, but it primarily learned to detect contrast rather than semantic importance or reading flow patterns. It’s useful for quick design iteration and identifying potential attention conflicts, but needs to be combined with user flow analysis, A/B testing, and traditional usability testing for comprehensive UX analysis.</p>\n\n<p>I integrated it with our design pipeline using Python and TensorFlow/PyTorch to generate automated heatmaps for rapid design feedback. It’s a nice example of how ML can augment traditional UX research methods during the design iteration phase, even if it can’t replace them.</p>\n\n<p><img src=\"/assets/images/heatmap_quizfriends.jpg\" alt=\"placeholder\" /></p>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nExperimenting with ML-driven design analysis using visual importance networks trained on eye-tracking data to predict where users focus attention in UI desi...",
      "date_published": "2019-02-26T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/portfolio/2018/06/01/quizfriends/",
      "url": "https://stefanwagner.dev/portfolio/2018/06/01/quizfriends/",
      "title": "Quiz Friends",
      "content_html": "\n<div class=\"youtube-container\" data-video-id=\"Bb-R-4kr0tI\">\n  \n  <picture>\n    <source srcset=\"/assets/images/quizfriends_cover.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/quizfriends_cover.jpg\" alt=\"Video thumbnail for Bb-R-4kr0tI\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/quizfriends_game_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/quizfriends_game.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/quizfriends_game.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/quizfriends_game.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<blockquote>\n  <p>Gather your Quiz Friends and discover the new kind of quiz games now with Quizfriends! Play fun and exciting quizzes and MAKE MONEY online for free! Every correct answer will be rewarded in Quizfriends! Earn coins, convert your coins into diamonds with the help of your robots and exchange your diamonds for real money and gift cards. So easy and fun!</p>\n</blockquote>\n\n<!--more-->\n\n<p>Quiz Friends was a real-money quiz game developed over a year with a 7-person team. It featured 3000+ trivia questions, each with short GIF animations from the GIPHY API. The game let users earn real cash by collecting coins in quizzes or completing offers, then converting those coins into diamonds and eventually real money or gift cards.</p>\n\n<p>We built it in Unity with a custom Heroku backend handling user authentication and game logic. Payouts were handled through PayPal as the payment provider. On our side we built wallet management, automated PayPal verification to reduce payout time from days to hours, basic fraud detection heuristics, and error handling for payment failures. We also built a chatbot for customer support.</p>\n\n<p>The system had to handle concurrent quiz sessions, real-time score calculations, and content delivery for thousands of questions with media.</p>\n\n<p>It was a challenging project because implementing a fast, reliable payout system was crucial for user trust. We had to detect fraud without creating friction for legitimate users, which required a lot of fine-tuning.</p>\n\n<p><strong>Play</strong><br />\n<a href=\"https://play.google.com/store/apps/details?id=de.mobileheroes.quizfriends\">https://play.google.com/store/apps/details?id=de.mobileheroes.quizfriends</a></p>\n",
      "summary": "\n\n  \n  \n    \n    \n  \n  \n  \n    \n      \n      \n    \n  \n\n\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\n\n  Gather your Quiz Friends and discover the new kind of quiz games now with Quizfriends! Play fun an...",
      "date_published": "2018-06-01T00:00:00+00:00",
      
      "tags": ["Portfolio"]
    },
    
    {
      "id": "https://stefanwagner.dev/portfolio/2018/04/01/mowylawn/",
      "url": "https://stefanwagner.dev/portfolio/2018/04/01/mowylawn/",
      "title": "Mowy Lawn",
      "content_html": "\n<div class=\"youtube-container\" data-video-id=\"FF5_Pw0Limc\">\n  \n  <picture>\n    <source srcset=\"/assets/images/mowylawn_cover.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/mowylawn_cover.jpg\" alt=\"Video thumbnail for FF5_Pw0Limc\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/mowylawn_game_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/mowylawn_game.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/mowylawn_game.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/mowylawn_game.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<blockquote>\n  <p>Welcome to lawnmower heaven - a Garden of Eden where lucky blade-spinners are set free on fields of glorious bushy overgrowth.\nPick from a selection of souped-up mowers and dive into a sprawling mass of grass until everything is shaved down into neat, tidy, satisfying strips.\nBut beware of dog! Grassland is filled with four-legged hazards, from canines to frogs to, uh, sheep!</p>\n</blockquote>\n\n<p>Mowy Lawn is an easy to control casual arcade game. It published by our publisher Playstack. Apple featured the game several times as part of AR game collections.</p>\n\n<p>Fokus of this game is easy controls (just one button) and quick sessions.</p>\n\n<p>The game is developed in Unity. We added a special AR mode to this game using ARKit on iOS and ARCore on Android.</p>\n\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/mowylawn_ar_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/mowylawn_ar.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/mowylawn_ar.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/mowylawn_ar.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p><strong>Play</strong></p>\n\n<p><a href=\"https://play.google.com/store/apps/details?id=com.playstack.mowylawn\">https://play.google.com/store/apps/details?id=com.playstack.mowylawn</a>\n<a href=\"https://apps.apple.com/us/app/mowy-lawn/id1324982051\">https://apps.apple.com/us/app/mowy-lawn/id1324982051</a></p>\n",
      "summary": "\n\n  \n  \n    \n    \n  \n  \n  \n    \n      \n      \n    \n  \n\n\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\n\n  Welcome to lawnmower heaven - a Garden of Eden where lucky blade-spinners are set free on fields o...",
      "date_published": "2018-04-01T00:00:00+00:00",
      
      "tags": ["Portfolio"]
    },
    
    {
      "id": "https://stefanwagner.dev/portfolio/2017/11/01/guess_success/",
      "url": "https://stefanwagner.dev/portfolio/2017/11/01/guess_success/",
      "title": "Guess Success",
      "content_html": "\n<div class=\"youtube-container\" data-video-id=\"kA2JL1Yu8fs\">\n  \n  <picture>\n    <source srcset=\"/assets/images/guesssuccess_cover.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/guesssuccess_cover.jpg\" alt=\"Video thumbnail for kA2JL1Yu8fs\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/guesssuccess_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/guesssuccess.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/guesssuccess.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/guesssuccess.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<blockquote>\n  <p>How good can you guess? Do you know how much lipstick a woman inadvertently eats in her life? Or how many Smarties fit into a Smart?! 🤔\nImpossible to know? You don’t need to know the exact answer here! Give your best guess and beat your friends in this entertaining social quiz. 👫🏆\nLearn exciting and curious facts to impress your friends!</p>\n</blockquote>\n\n<p>Guess Success is our first Facebook Instant Game. This is Facebook’s game platform for his messenger. It’s different from most quiz games as you only have to guess the answer instead of choosing from a set of answers. The game supports 1v1 vs Facebook friends or random people.</p>\n\n<p>We had to shift from Unity to an HTML5 game engine as Unity wasn’t supported at the time of development. I chose the <a href=\"https://haxe.org/\">Haxe</a> language with <a href=\"http://haxeflixel.com/\">haxe flixel</a> as a Framework for this game. The backend and multiplayer game logic are written in Firebase.</p>\n\n<h3 id=\"links\">Links</h3>\n<p><a href=\"https://play.google.com/store/apps/details?id=de.mobileheroes.guesssuccess\">https://play.google.com/store/apps/details?id=de.mobileheroes.guesssuccess</a>\n<a href=\"https://www.facebook.com/GuessSuccess/\">https://www.facebook.com/GuessSuccess/</a></p>\n",
      "summary": "\n\n  \n  \n    \n    \n  \n  \n  \n    \n      \n      \n    \n  \n\n\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\n\n  How good can you guess? Do you know how much lipstick a woman inadvertently eats in her life? Or h...",
      "date_published": "2017-11-01T00:00:00+00:00",
      
      "tags": ["Portfolio"]
    },
    
    {
      "id": "https://stefanwagner.dev/portfolio/2017/09/01/cs-ar/",
      "url": "https://stefanwagner.dev/portfolio/2017/09/01/cs-ar/",
      "title": "CS AR",
      "content_html": "\n<div class=\"youtube-container\" data-video-id=\"ASsDXFp5rnQ\">\n  \n  <picture>\n    <source srcset=\"/assets/images/csar_cover.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/csar_cover.jpg\" alt=\"Video thumbnail for ASsDXFp5rnQ\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n\n<blockquote>\n  <p>CS:AR is an Augmented Reality Game for your mobile device. Solve Escape Room-like murder cases in your living room with the help of FIA, your Forensic Investigation Assistant AI.</p>\n</blockquote>\n\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/cs_ar_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/cs_ar.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/cs_ar.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/cs_ar.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>CS:AR is an AR game developed in Unity. We used ARKit and ARCore as base frameworks. The game consists of various minigames like identify fingerprints or search for clues on virtual and real objects. The biggest challenge in the game was the different real environments the player can be in. There had to be enough space to place all hidden objects for instance. AR tracking also needs an environment with enough contrast to be stable.</p>\n",
      "summary": "\n\n  \n  \n    \n    \n  \n  \n  \n    \n      \n      \n    \n  \n\n\n\n  CS:AR is an Augmented Reality Game for your mobile device. Solve Escape Room-like murder cases in your living room with the help of FIA, your Forensic Investigation Assistant AI.\n\n\n\n  \n    \n    \n    \n      Your browser...",
      "date_published": "2017-09-01T00:00:00+00:00",
      
      "tags": ["Portfolio"]
    },
    
    {
      "id": "https://stefanwagner.dev/portfolio/2017/06/01/goldfever/",
      "url": "https://stefanwagner.dev/portfolio/2017/06/01/goldfever/",
      "title": "Gold Fever",
      "content_html": "\n<div class=\"youtube-container\" data-video-id=\"ialkaOi87zg\">\n  \n  <picture>\n    <source srcset=\"/assets/images/goldfever_cover.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/goldfever_cover.jpg\" alt=\"Video thumbnail for ialkaOi87zg\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n\n<blockquote>\n  <p>Welcome to your Gold Mine! In Gold Fever you can easily MAKE MONEY online for free! Hire hardworking miners for coins. Boost their speed by watching exciting video ads. Check in daily and get extra coins. Harvest sparkling gold and exchange it for real money.</p>\n</blockquote>\n\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/goldfever_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/goldfever.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/goldfever.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/goldfever.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>In Gold Fever you have to complete various offers to collect coins. Those coins can then be used to mine gold. Which then can be exchanged to real money.</p>\n\n<p>The game is developed in Unity. We used a custom Heroku Backend for the user and game logic.</p>\n\n<p><img src=\"/assets/images/goldfever_wallpaper.jpg\" alt=\"placeholder\" /></p>\n\n<p><strong>Play</strong></p>\n\n<p><a href=\"https://play.google.com/store/apps/details?id=de.mobileheroes.realmoneyminer\">https://play.google.com/store/apps/details?id=de.mobileheroes.realmoneyminer</a></p>\n",
      "summary": "\n\n  \n  \n    \n    \n  \n  \n  \n    \n      \n      \n    \n  \n\n\n\n  Welcome to your Gold Mine! In Gold Fever you can easily MAKE MONEY online for free! Hire hardworking miners for coins. Boost their speed by watching exciting video ads. Check in daily and get extra coins. Harvest spark...",
      "date_published": "2017-06-01T00:00:00+00:00",
      
      "tags": ["Portfolio"]
    },
    
    {
      "id": "https://stefanwagner.dev/2017/04/01/balloon/",
      "url": "https://stefanwagner.dev/2017/04/01/balloon/",
      "title": "Balloon",
      "content_html": "\n<figure class=\"video-container video-portrait\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/balloon_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/balloon.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/balloon.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/balloon.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>This is a Prototyp for a hyper-casual game. The Balloon is driven by cloth physics. Since there is no pressure property since Unity 4, I used an inner sphere that expands as the balloon gets larger.</p>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nThis is a Prototyp for a hyper-casual game. The Balloon is driven by cloth physics. Since there is no pressure property since Unity 4, I used an inner spher...",
      "date_published": "2017-04-01T00:00:00+00:00",
      
      "tags": []
    },
    
    {
      "id": "https://stefanwagner.dev/portfolio/2017/03/01/tiny_armies/",
      "url": "https://stefanwagner.dev/portfolio/2017/03/01/tiny_armies/",
      "title": "Tiny Armies",
      "content_html": "\n<div class=\"youtube-container\" data-video-id=\"rhLI2zwYeEQ\">\n  \n  <picture>\n    <source srcset=\"/assets/images/tinyarmies_cover.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/tinyarmies_cover.jpg\" alt=\"Video thumbnail for rhLI2zwYeEQ\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/tinyarmies_game_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/tinyarmies_game.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/tinyarmies_game.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/tinyarmies_game.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<blockquote>\n  <p>A new era has begun. Fight for ultimate supremacy in Tiny Armies: Clash Arena, where players from around the world take part in massive, epic multiplayer battles.\nRecruit soldiers, capture bases, and grow your army into a formidable force with skull-crushing giants and fire-breathing dragons - all live and in-play.\nBecome the most feared warrior on the battlefield and destroy your enemies! Choose your hero, battle in bustling arenas across the kingdom, and earn awesome bonuses by taking on exciting live missions!</p>\n</blockquote>\n\n<!--more-->\n\n<p>Tiny Armies was our second big game after Hi Frog, taking nearly 3 years of development with external publisher <a href=\"https://playstack.com/\">PlayStack</a>. It got featured as an early access title on Google Play Store. I led the technical architecture as Tech Lead of a 6-person engineering team.</p>\n\n<p>The biggest technical challenge was implementing smooth 8-player simultaneous gameplay over mobile networks. We built it on <a href=\"https://www.photonengine.com/\">Photon Engine</a> but needed extensive custom optimization. We used deterministic lockstep networking for synchronized gameplay across all clients and optimized the network protocol to reduce packet size by 60% compared to our initial implementation. The transmission had to work smoothly even on 3G connections, so we added client-side prediction and lag compensation for responsive controls.</p>\n\n<p><img src=\"/assets/images/tinyarmies_assets.jpg\" alt=\"placeholder\" title=\"Assets\" /></p>\n\n<p>The game features a league system with ELO-based skill rating for fair matchmaking, ensuring veteran players compete at appropriate levels. We iteratively designed the monetization system through multiple redesigns, driven by comprehensive analytics and A/B testing infrastructure. The goal was a balanced economy that felt fair for non-paying users.</p>\n\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/tinyarmies_menu_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/tinyarmies_menu.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/tinyarmies_menu.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/tinyarmies_menu.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>As Tech Lead, I established code review processes, CI/CD pipeline for automated builds and testing, analytics infrastructure for data-driven decisions, and the overall technical architecture for the multiplayer systems. We built it with Unity and C#.</p>\n\n<p><strong>Play</strong> <a href=\"https://play.google.com/store/apps/details?id=com.playstack.tinyarmies\">https://play.google.com/store/apps/details?id=com.playstack.tinyarmies</a></p>\n",
      "summary": "\n\n  \n  \n    \n    \n  \n  \n  \n    \n      \n      \n    \n  \n\n\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\n\n  A new era has begun. Fight for ultimate supremacy in Tiny Armies: Clash Arena, where players from ...",
      "date_published": "2017-03-01T00:00:00+00:00",
      
      "tags": ["Portfolio"]
    },
    
    {
      "id": "https://stefanwagner.dev/game/2016/03/14/badminton/",
      "url": "https://stefanwagner.dev/game/2016/03/14/badminton/",
      "title": "Badminton",
      "content_html": "\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/badminton_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/badminton.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/badminton.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/badminton.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>I rigged the model in blender, imported it to unity and then placed a rigid body on each bone. The tentacles who hold the racked and the head has a constant up force applied. The rotation is done with torque force applied to the root bone.</p>\n\n<hr />\n\n<p><img src=\"/assets/images/badminton_unit_1.jpg\" alt=\"placeholder\" title=\"ocotopus\" width=\"400px\" /></p>\n\n<p><img src=\"/assets/images/badminton_unit_1.gif\" alt=\"placeholder\" title=\"ocotopus\" /></p>\n\n<p><img src=\"/assets/images/badminton_unit_2.jpg\" alt=\"placeholder\" title=\"elephant\" width=\"400px\" /></p>\n\n<p><img src=\"/assets/images/badminton_unit_2.gif\" alt=\"placeholder\" title=\"elephant\" /></p>\n\n<p>Badminton minigame character concept.</p>\n\n<p><a href=\"https://github.com/bompo/SuperTurboBadmintonDeluxeUnity\">Open Source</a></p>\n",
      "summary": "\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\nI rigged the model in blender, imported it to unity and then placed a rigid body on each bone. The tentacles who hold the racked and the head has a constant...",
      "date_published": "2016-03-14T00:00:00+00:00",
      
      "tags": ["Game"]
    },
    
    {
      "id": "https://stefanwagner.dev/portfolio/2015/11/01/rebounce/",
      "url": "https://stefanwagner.dev/portfolio/2015/11/01/rebounce/",
      "title": "Rebounce!",
      "content_html": "\n<div class=\"youtube-container\" data-video-id=\"VvXOjgvff6g\">\n  \n  <picture>\n    <source srcset=\"/assets/images/rebounce_cover.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/rebounce_cover.jpg\" alt=\"Video thumbnail for VvXOjgvff6g\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/rebounce_menu_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/rebounce_menu.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/rebounce_menu.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/rebounce_menu.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<blockquote>\n  <p>ReBounce! is simple: Shoot the ball into the goal! Sounds too easy? Then challenge your friends or impress players around the world with your trick shots!\nIt’s an exciting physics puzzle game with a strong multiplayer functionality. Connect with your friends over Facebook and play against them in quick and easy matches. Send them challenges and watch them fail in replays!</p>\n</blockquote>\n\n<p>This is a casual game with a simple mechanic. We added an asynchronous multiplayer mode to this game. You don’t play in real-time against your opponents.  Rather, your matches are recorded and used later. This is great for slow 3G devices as there is no constant internet connection needed.</p>\n\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/rebounce_game_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/rebounce_game.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/rebounce_game.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/rebounce_game.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p><strong>Play</strong>\n<a href=\"https://play.google.com/store/apps/details?id=de.mobileheroes.rebounce\">https://play.google.com/store/apps/details?id=de.mobileheroes.rebounce</a>\n<a href=\"https://apps.apple.com/om/app/rebounce/id1043137178\">https://apps.apple.com/om/app/rebounce/id1043137178</a></p>\n",
      "summary": "\n\n  \n  \n    \n    \n  \n  \n  \n    \n      \n      \n    \n  \n\n\n\n  \n    \n    \n    \n      Your browser doesn't support HTML5 video.\n      Download the video\n      instead.\n    \n  \n  \n\n\n\n  ReBounce! is simple: Shoot the ball into the goal! Sounds too easy? Then challenge your friends or...",
      "date_published": "2015-11-01T00:00:00+00:00",
      
      "tags": ["Portfolio"]
    },
    
    {
      "id": "https://stefanwagner.dev/portfolio/2015/06/01/release_totem_tower/",
      "url": "https://stefanwagner.dev/portfolio/2015/06/01/release_totem_tower/",
      "title": "Totem Tower",
      "content_html": "\n<div class=\"youtube-container\" data-video-id=\"EMLplfjU9aU\">\n  \n  <picture>\n    <source srcset=\"/assets/images/totemtower_cover.webp\" type=\"image/webp\" />\n    <img class=\"youtube-thumb\" src=\"/assets/images/totemtower_cover.jpg\" alt=\"Video thumbnail for EMLplfjU9aU\" loading=\"lazy\" width=\"1280\" height=\"720\" decoding=\"async\" />\n  </picture>\n  \n  <button class=\"youtube-play-btn\" aria-label=\"Play video\">\n    <svg viewBox=\"0 0 68 48\" width=\"68\" height=\"48\">\n      <path d=\"M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\" fill=\"#f00\" />\n      <path d=\"M 45,24 27,14 27,34\" fill=\"#fff\" />\n    </svg>\n  </button>\n</div>\n\n<blockquote>\n  <p><strong>Enter the world of Totem Tower and defeat your opponents!</strong></p>\n\n  <ul>\n    <li>Play against FRIENDS via Google Play Games…</li>\n    <li>… or compete ONLINE against players from around the world!</li>\n    <li>Experience action packed moments against an opponent in TOWER MATCH, whether AGAINST EACH OTHER ON ONE DEVICE or ONLINE</li>\n    <li>Set a time in the turn-based TIME TOWER mode and challenge your friends!</li>\n    <li>Compete with your daily highscore in CHALLENGE mode against players from around the world!</li>\n    <li>Train your skills against A.I. in the TUTORIAL mode before you compete ONLINE against real players!</li>\n  </ul>\n</blockquote>\n\n<!--more-->\n\n<p>We just released Totem Tower for Android, and it got featured on Google Play as an Indie Game Highlight! This was our first multiplayer game, built on Google Play Games Services.</p>\n\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/totem_tower_battle_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/totem_tower_battle.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/totem_tower_battle.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/totem_tower_battle.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>The biggest technical challenge was synchronizing gameplay between players without a dedicated game server. We solved this with a dual-timer system running independently on each device. When players match, there’s a conflict resolution algorithm that determines priority between the peers. Each device maintains local authority for game state with peer verification and latency compensation to keep gameplay smooth.</p>\n\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/totem_tower_matchmaking_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/totem_tower_matchmaking.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/totem_tower_matchmaking.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/totem_tower_matchmaking.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>The game has several modes: Tower Match for real-time 1v1 battles (online or on the same device), Time Tower for turn-based play, Challenge Mode with daily leaderboards, and Tutorial Mode with AI opponents. There’s also a league system where you rank up by winning games, which influences the matchmaking to pair players of similar skill levels.</p>\n\n<figure class=\"video-container video-landscape\">\n  <video class=\"lazy-video\" controls=\"\" loop=\"\" data-autoplay=\"\" muted=\"\" playsinline=\"\" preload=\"none\" poster=\"/assets/videos/totem_tower_result_poster.jpg\" aria-label=\"Video content\">\n    <source data-src=\"/assets/videos/totem_tower_result.webm\" type=\"video/webm\" />\n    <source data-src=\"/assets/videos/totem_tower_result.mp4\" type=\"video/mp4\" />\n    <p>\n      Your browser doesn't support HTML5 video.\n      <a href=\"/assets/videos/totem_tower_result.mp4\">Download the video</a>\n      instead.\n    </p>\n  </video>\n  \n</figure>\n\n<p>Built with Unity, C#, and Google Play Games Services for the peer-to-peer networking.</p>\n\n<p><a href=\"https://play.google.com/store/apps/details?id=de.kapitaene.totem\">Play Now</a></p>\n",
      "summary": "\n\n  \n  \n    \n    \n  \n  \n  \n    \n      \n      \n    \n  \n\n\n\n  Enter the world of Totem Tower and defeat your opponents!\n\n  \n    Play against FRIENDS via Google Play Games…\n    … or compete ONLINE against players from around the world!\n    Experience action packed moments against ...",
      "date_published": "2015-06-01T00:00:00+00:00",
      
      "tags": ["Portfolio"]
    }
    
  ]
}
