1"use strict";(globalThis.webpackChunkmotore_temp=globalThis.webpackChunkmotore_temp||[]).push([[4411],{1361:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>c,default:()=>h,frontMatter:()=>l,metadata:()=>t,toc:()=>u});const t=JSON.parse('{"id":"featured/rpg-creature-search-app/README","title":"RPG Creature Search App","description":"Scritto il 23 novembre del 2025","source":"@site/i18n/it/docusaurus-plugin-content-docs/current/01-featured/rpg-creature-search-app/README.mdx","sourceDirName":"01-featured/rpg-creature-search-app","slug":"/featured/rpg-creature-search-app/","permalink":"/it/docs/featured/rpg-creature-search-app/","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"sidebar_position":4,"sidebar_label":"RPG Creature Search App","title":"RPG Creature Search App"},"sidebar":"tutorialSidebar","previous":{"title":"Mosaic","permalink":"/it/docs/featured/mosaic-ui-concept"},"next":{"title":"UX Engineer Log","permalink":"/it/docs/featured/ux-engineer-log/"}}');var a=i(4848),o=i(8453),r=i(1470),s=i(9365);const l={sidebar_position:4,sidebar_label:"RPG Creature Search App",title:"RPG Creature Search App"},c="RPG Creature Search App",d={},u=[{value:"Il Progetto",id:"il-progetto",level:2},{value:"Codice Sorgente",id:"codice-sorgente",level:2},{value:"Il Progetto Preferito (davvero)",id:"il-progetto-preferito-davvero",level:2},{value:"Design: Aqua, Jelly e Icona Figma",id:"design-aqua-jelly-e-icona-figma",level:2},{value:"Performance: Pipeline di Compressione Immagini",id:"performance-pipeline-di-compressione-immagini",level:2},{value:"Dallo "Static Archive" al Version Control",id:"dallo-static-archive-al-version-control",level:2},{value:"Toccare il Limite",id:"toccare-il-limite",level:3},{value:"Cosa Ho Imparato",id:"cosa-ho-imparato",level:2},{value:"Riflessione Finale",id:"riflessione-finale",level:2}];function p(e){const n={blockquote:"blockquote",br:"br",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("p",{className:"article-meta",children:(0,a.jsx)("time",{dateTime:"2025-11-23",children:"Scritto il 23 novembre del 2025"})}),"\n",(0,a.jsx)(n.header,{children:(0,a.jsx)(n.h1,{id:"rpg-creature-search-app",children:"RPG Creature Search App"})}),"\n",(0,a.jsxs)("figure",{className:"image-caption-figure",style:{textAlign:"center"},children:[(0,a.jsx)("a",{href:"https://github.com/user-attachments/assets/f3634ea7-0fcf-4423-a4d5-48411de88114",target:"_blank",children:(0,a.jsx)("img",{src:i(5962).A,alt:"Interfaccia della RPG Creature Search App durante la ricerca di una creatura",loading:"lazy",decoding:"async",width:"3840",height:"1920",style:{width:"100%",height:"auto",borderRadius:"10px"}})}),(0,a.jsx)("figcaption",{className:"image-caption image-caption--source",children:(0,a.jsx)("em",{children:"Clicca per vedere la demo (1080p, 28 s, 1.1 MB)"})})]}),"\n",(0,a.jsx)(n.h2,{id:"il-progetto",children:"Il Progetto"}),"\n",(0,a.jsxs)(n.p,{children:["RPG Creature Search App sviluppata come Certification Project finale del corso ",(0,a.jsx)(n.strong,{children:"JavaScript Algorithms and Data Structures"})," di freeCodeCamp. Un\u2019app che unisce JavaScript, gestione dello stato e UI curata in stile Aqua/macOS early 2000, con particolare attenzione a SVG vettoriali, effetti \u201cjelly\u201d e ottimizzazione estrema delle immagini."]}),"\n",(0,a.jsx)(n.h2,{id:"codice-sorgente",children:"Codice Sorgente"}),"\n",(0,a.jsxs)(r.A,{children:[(0,a.jsx)(s.A,{value:"html",label:"index.html",default:!0,children:(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-html",children:'\x3c!-- DESIGN \n ------\n * This file contains the HTML structure of the "RPG Creature Search App" application.\n * The design emulates the "Aqua" user interface from Mac OS X 10 (2000s) but reinterpreted\n * in certain aspects\n\n * Font Choice:\n * I didn\'t import external fonts, instead I chose native system font stacks\n * (like Lucida Grande, Verdana) to ensure historical fidelity to the original OS and instant\n * loading performance with no external dependencies\n\n * Flow:\n * - Head with meta tags and link to the local style sheet\n * - Body containing a <main i
1d="window-container"> element that acts as the frame for the\n * main window\n * - Inside the <main>, the structure is divided into two inner containers to allow\n * independent management, specifically:\n * - A <div> ".header-overlap-wrapper" for the title bar and controls (always visible)\n * - A <div> "#main-content-container" that wraps all the functional content\n\n * The main container (#main-content-container) is structured in two main blocks:\n * - #search-container: Contains the input interface. Uses a "stack" technique\n * that overlays elements (background image, icon, and transparent text box) one\n * on top of another, instead of placing them side by side, to perfectly recreate the complex\n * graphic appearance of the original bar.\n * - #creature-output-box: Contains the results area. Currently has "mock-up" data (Creature #1,\n * Pyrolynx) that serves as visual placeholder meant to facilitate my subsequent CSS design\n * and provides the structure that will be dynamically updated via JavaScript.\n--\x3e\n\n\n<!DOCTYPE html>\n<html lang="en">\n <head>\n <title>RPG Creature Search App</title>\n <meta charset="UTF-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <link rel="stylesheet" href="styles.css">\n </head>\n <body>\n \x3c!-- \n * freeCodeCamp instructions:\n * You should have an input element with an id of "search-input", and is required. \u2714\ufe0f\n * You should have a button element with an id of "search-button". \u2714\ufe0f\n * You should have an element with an id of "creature-name". \u2714\ufe0f\n * You should have an element with an id of "creature-id". \u2714\ufe0f\n * You should have an element with an id of "weight". \u2714\ufe0f\n * You should have an element with an id of "height". \u2714\ufe0f\n * You should have an element with an id of "types". \u2714\ufe0f\n * You should have an element with an id of "hp". \u2714\ufe0f\n * You should have an element with an id of "attack". \u2714\ufe0f\n * You should have an element with an id of "defense". \u2714\ufe0f\n * You should have an element with an id of "special-attack". \u2714\ufe0f\n * You should have an element with an id of "special-defense". \u2714\ufe0f\n * You should have an element with an id of "speed". \u2714\ufe0f\n --\x3e\n\n \x3c!-- \n * Later I use a div instead of a button because if I do that I would have\n * to write a lot of "reset" CSS to cancel all the native browser styles,\n * still risking unexpected behavior on different browsers.\n * I\'ll handle this accessibility "debt" with semantics and JavaScript\n --\x3e\n \n <div id="desktop-app-icon" class="hidden" role="button" aria-label="Reopen RPG Search App" tabindex="0"></div>\n <main id="window-container">\n <section id="main-content-container">\n <div class="header-overlap-wrapper">\n <div class="window-top-separator"></div>\n <div class="traffic-lights-container">\n <button id="btn-close" class="traffic-light red" aria-label="Close window" title="Close"></button>\n <button id="btn-minimize" class="traffic-light yellow disabled" aria-label="Minimize window" title="Minimize"></button>\n <button id="btn-maximize" class="traffic-light green" aria-label="Maximize window" title="Maximize"></button>\n </div>\n <h2 class="window-title">RPG Creature Search App</h2>\n </div>\n <div id="search-container">\n <h1 id="main-heading">Search for Creature Name or ID:</h1>\n <div class="input-stack">\n <div class="search-bg-layer"></div>\n <div class="search-content-layer">\n \x3c!-- \n * The magnifying glass icon is a separate element, not drawn directly on the background\n * This allows positioning it with millimetric precision via CSS over the bar, \n * maintaining independent control of the three layers (background, icon, text)\n --\x3e\n <svg class="search-icon-physical" width="24" height="24" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">\n <path d="M17.564 18.1162L24.8043 25.1027" stroke="#686868" stroke-width="3.28349" stroke-linecap="round"/>\n <circle cx="10.8521" cy="10.8521" r="9.21039" stroke="#686868" stroke-width="3.28349"/>\n </svg>\n <input type="text" id="search-input" required aria-label="Search for creature name or ID" autocomplete="off"/>\n <div id="aqua-tooltip" class="aqua-tooltip-box hidden-tooltip">Enter an ID (1-20) or name (e.g. Pyrolynx).</div>\n </div>\n </div>\n <div class="search-btn-container">\n <button id="search-button">Search</button>\n </div>\n </div>\n <section id="creature-output-box" class="output-container">\n <div class="output-left-col">\n <div class="creature-title-container">\n <h2>
1\n <span id="creature-name">PYROLYNX</span>\n <span id="creature-id">#1</span>\n </h2>\n </div>\n <div class="attributes-container">\n <p class="description">\n <span id="special-name" class="special-name">Blazing Reflex</span><br>\n <span id="special-description" class="special-description">Increases speed when hit by a Fire-type move.</span>\n </p>\n\n \x3c!-- \n * I decided to put the fixed text ("Weight: " and "Height: ") and then the number in\n * two separate "span" elements, because this way it will be easier in the JavaScript\n * phase: when it needs to update the data, it only needs to update the field with\n * the number.\n --\x3e\n <p class="attribute-item">\n <span>Weight: </span><span id="weight">42</span>\n </p>\n <p class="attribute-item">\n <span>Height: </span><span id="height">32</span>\n </p>\n </div>\n <div id="types" class="types-container">\n <span class="type-badge type-fire">Fire</span>\n </div>\n </div>\n <div class="output-right-col">\n <table class="stats-table">\n <thead>\n <tr>\n <th>Base</th>\n <th class="text-right">Stats</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>HP</td>\n <td id="hp" class="stat-value">85</td>\n </tr>\n <tr>\n <td>Attack</td>\n <td id="attack" class="stat-value">50</td>\n </tr>\n <tr>\n <td>Defense</td>\n <td id="defense" class="stat-value">120</td>\n </tr>\n <tr>\n <td>Sp. Attack</td>\n <td id="special-attack" class="stat-value">100</td>\n </tr>\n <tr>\n <td>Sp. Defense</td>\n <td id="special-defense" class="stat-value">115</td>\n </tr>\n <tr>\n <td>Speed</td>\n <td id="speed" class="stat-value">55</td>\n </tr>\n </tbody>\n </table>\n </div>\n </section>\n </section>\n </main>\n <script src="script.js"><\/script>\n </body>\n</html>\n'})})}),(0,a.jsx)(s.A,{value:"css",label:"styles.css",children:(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-css",children:'/* DESIGN\n ------\n * The document emulates the "Aqua" user interface, an Apple graphical user interface,\n * reinterpreting it for the modern web through a Mobile-First approach\n * Base styles are optimized for rapid loading on narrow screens (the majority\n * traffic), while desktop versions are handled with media queries\n * I chose Verdana as the main alternative because it\'s the most similar overall\n * to Lucida Grande (the official MacOS X font for 13 years, from Cheetah to Mavericks).\n * Verdana is omnipresent on Mac and PC and shares the distinctive "squared" dots.\n * Unfortunately Verdana isn\'t always present on Linux, so the second choice is\n * DejaVu Sans, the most similar native font on the latter\n * I\'ve included other fallbacks (Geneva, Tahoma) for safety, though it\'s highly likely\n * they\'ll never be used\n * Lucida Grande remains the very first choice in the stack, to ensure\n * maximum native fidelity on modern Macs, where it\'s still hidden in the system.\n\n * CSS Architecture:\n * - "Utility-First" variables: almost total centralization of design tokens (colors,\n * fonts, spacing, animation curves) in :root for easy maintenance, divided into\n * UNIVERSAL, MOBILE DEFAULT, TABLET / SMALL DESKTOP, MEDIUM DESKTOP / TABLET LANDSCAPE\n * and finally FULL DESKTOP\n * - Component structure: CSS rules mirror the HTML hierarchy\n * (Window -> Title Bar -> Search Stack -> Output Area)\n * - Hybrid hierarchy: strategic use of IDs for unique macro-structural areas\n * (e.g. #window-container) and BEM-like classes (Block, Element, Modifier) for\n * repeatable elements.\n * - Desktop overrides: specific media queries handle layout changes for larger screens\n * - Accessibility: beyond respecting WCAG AA color contrasts, the design includes explicit\n * handling for user\'s reduced motion preferences (prefers-reduced-motion) and ensures\n * visible focus states for keyboard navigation.\n\n * Note on Graphic Assets:\n * To achieve maximum fidelity to the complex "jelly" effects and textures typical of Aqua,\n * I opted for vector SVG assets for the frame and main buttons, delegating to CSS only\n * the shadows for depth.\n * I want to mention that I created them from scratch on Figma and regarding the Search button\n * I took inspiration from Arthur Objartel\'s creation (https://www.figma.com/@arthur_objartel),\n * who generously shared the button with the community.\n * The closed application, meaning the app icon, I also designed with Figma, I created the\n * fire (freeCo
1deCamp style) with pixel effect, using the pen tool, it was meticulous and long\n * work but it served to practice with this tool, the lens that wraps it was born from the idea\n * of taking inspiration from Safari of that era which had that skeuomorphic outline with shiny \n * parts, I also added two shiny parts and to complete the tribute to freeCodeCamp I made the\n * darker parts of the outline fall on the sides (right and left) just like the freeCodeCamp logo.\n*/\n\n\n/* DESIGN TOKENS (VARIABLES) - UNIVERSAL */\n:root {\n /* Background & Assets */\n\n /* \n * I achieved just 163KB for the desktop background by refining my image compression process,\n * here\'s how:\n * Instead of applying AI upscaling directly on the original PNG image, I decided to\n * preventively convert it to JPEG applying a slight compression already.\n * Only after this intermediate step did I perform the AI upscaling on the JPEG file.\n * Finally, as a concluding step, I processed the resulting image with ImageOptim to further\n * reduce the weight. The direct comparison results are remarkable: compared to the standard\n * procedure (upscaling on PNG followed by JPEG conversion and ImageOptim), this alternative\n * method allowed me to achieve a file weight reduction of over 50%, thus obtaining 382 KB\n * with the first procedure and only 163 KB with the optimized one! Maintaining an almost\n * identical visual quality.\n\n WPO - Base64 Embedding (Data URIs)\n * Very small critical user interface (UI) assets (< 5KB) have been converted to Base64 \n * strings and embedded directly in the CSS.\n * By doing this I eliminated 5 separate HTTP requests to the server and the interface\n * appears instantly without the loading "flash".\n * The only disadvantage is a slight increase in CSS file size.\n *\n * Procedure followed (macOS):\n * 1. Opened terminal and navigated to the asset folder (using "cd " + dragging\n * the folder containing the svg file), pressed Enter\n * 2. Executed the command "base64 -i file-name.svg", replacing file-name.svg with the actual\n * file name, pressed Enter\n * 3. Copied the long output string (the payload)\n * 4. Pasted in CSS using the prefix: url(\'data:image/svg+xml;base64,COPIEDCODE\').\n */\n\n\n --bg-image-desktop-asset: url(\'background.jpg\'); /* 163 KB */\n --window-top-separator-asset: url(\'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgODkwIDIyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8cGF0aCBkPSJNMCA2LjAwOTI4QzAgMi42OTU1NyAyLjY4NjI5IDAuMDA5Mjc3MzQgNiAwLjAwOTI3NzM0SDg4My42NTVDODg2Ljk2OCAwLjAwOTI3NzM0IDg4OS42NTUgMi42OTU1NyA4ODkuNjU1IDYuMDA5MjhWMjEuNDI2OEgwVjYuMDA5MjhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMCAyMS4xMTQzTDg4OS42NTQgMjEuMTE0MyIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIwLjgiLz4KPHBhdGggZD0iTTUuNTU2MTUgMC4zOTk5MDJMODgzLjAxMSAwLjM5OTk3OSIgc3Ryb2tlPSIjRTNFM0UzIiBzdHJva2Utd2lkdGg9IjAuOCIvPgo8cGF0aCBkPSJNMCAxNi40ODkzTDg4OS42NTQgMTYuNDg5MyIgc3Ryb2tlPSIjRTNFM0UzIiBzdHJva2Utd2lkdGg9IjAuOCIvPgo8cGF0aCBkPSJNMCAxMy4yNjEyTDg4OS42NTQgMTMuMjYxMyIgc3Ryb2tlPSIjRTNFM0UzIiBzdHJva2Utd2lkdGg9IjAuOCIvPgo8cGF0aCBkPSJNMCA2LjgzMDU3TDg4OS42NTQgNi44MzA2NCIgc3Ryb2tlPSIjRTNFM0UzIiBzdHJva2Utd2lkdGg9IjAuOCIvPgo8cGF0aCBkPSJNMCAzLjYxNTIzTDg4OS42NTQgMy42MTUzMSIgc3Ryb2tlPSIjRTNFM0UzIiBzdHJva2Utd2lkdGg9IjAuOCIvPgo8cGF0aCBkPSJNMCAxMC4wNDU5TDg4OS42NTQgMTAuMDQ2IiBzdHJva2U9IiNFM0UzRTMiIHN0cm9rZS13aWR0aD0iMC44Ii8+CjxyZWN0IHk9IjE2Ljk0ODIiIHdpZHRoPSI4ODkuNjU1IiBoZWlnaHQ9IjMuNzI0OSIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzM2XzExNikiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8zNl8xMTYiIHgxPSI0NDQuODI3IiB5MT0iMTYuOTQ4MiIgeDI9IjQ0NC44MjciIHkyPSIyMC42NzMxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNFMkUyRTQiIHN0b3Atb3BhY2l0eT0iMCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM4Nzg3ODciLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4=\'); /* 1KB */\n --red-button-asset: url(\'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyNSAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTIuMzU0NiIgY3k9IjEwLjEzMDQiIHI9IjkuNTAy
1MzgiIHRyYW5zZm9ybT0icm90YXRlKC0xODAgMTIuMzU0NiAxMC4xMzA0KSIgZmlsbD0iI0QwNTQzOCIvPgo8ZyBvcGFjaXR5PSIwLjUiIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2ZfMTJfMTE1KSI+CjxwYXRoIGQ9Ik02LjU4NDE4IDE0LjQ5MjVMNi45Njg3MiAxNy43MzM0TDQuMzU4NjIgMTYuMzQ0MUwzLjUwNzc1IDEwLjc0MjlMNC45MzEzNyA2LjUwMDY0TDguMjIxNTYgMy40NzAwN0w3LjQ2NTE3IDcuMDQ4MjNMNi4yMjIwNyAxMC40NjY5TDYuNTg0MTggMTQuNDkyNVoiIGZpbGw9IiM5MTA4MDgiIGZpbGwtb3BhY2l0eT0iMC43MSIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuNSIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfZl8xMl8xMTUpIj4KPHBhdGggZD0iTTE4LjEyNDMgMTQuNDkyNUwxNy43Mzk4IDE3LjczMzRMMjAuMzQ5OSAxNi4zNDQxTDIxLjIwMDcgMTAuNzQyOUwxOS43NzcxIDYuNTAwNjRMMTYuNDg2OSAzLjQ3MDA3TDE3LjI0MzMgNy4wNDgyM0wxOC40ODY0IDEwLjQ2NjlMMTguMTI0MyAxNC40OTI1WiIgZmlsbD0iIzkxMDgwOCIgZmlsbC1vcGFjaXR5PSIwLjcxIi8+CjwvZz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjJfZl8xMl8xMTUpIj4KPHBhdGggZD0iTTkuNTc0MTggMTQuNjc0MkM5LjcwNDk4IDE2LjAzNTYgMTAuODQ4NyAxNy4wNzQ3IDEyLjIxNjQgMTcuMDc0N0gxMi41MTkyQzEzLjg5NiAxNy4wNzQ3IDE1LjA0NDIgMTYuMDIxOCAxNS4xNjMyIDE0LjY1MDJDMTUuMjkzOCAxMy4xNDUzIDE0LjEwNzUgMTEuODUxNiAxMi41OTY5IDExLjg1MTZIMTIuMTM4NkMxMC42MTg1IDExLjg1MTYgOS40Mjg3OSAxMy4xNjEgOS41NzQxOCAxNC42NzQyWiIgZmlsbD0iI0ZBREZCMiIgZmlsbC1vcGFjaXR5PSIwLjgiLz4KPC9nPgo8cGF0aCBkPSJNNi4xMzg2MyA0LjAyODQxQzYuMjQzODcgMi40NDQ2NyA3LjU1OTE3IDEuMjEzODcgOS4xNDYzOSAxLjIxMzg3SDE1LjU2MTlDMTcuMTQ5MSAxLjIxMzg3IDE4LjQ2NDQgMi40NDQ2NyAxOC41Njk2IDQuMDI4NDFDMTguNjA0NyA0LjU1NzAzIDE4LjE4NTQgNS4wMDUxMyAxNy42NTU2IDUuMDA1MTNINy4wNTI2MUM2LjUyMjgyIDUuMDA1MTMgNi4xMDM1MSA0LjU1NzAzIDYuMTM4NjMgNC4wMjg0MVoiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl8xMl8xMTUpIiBmaWxsLW9wYWNpdHk9IjAuOTUiLz4KPGNpcmNsZSBjeD0iMTIuMzU0NCIgY3k9IjEwLjEzMDMiIHI9IjkuNTMwMzEiIHN0cm9rZT0idXJsKCNwYWludDFfbGluZWFyXzEyXzExNSkiIHN0cm9rZS13aWR0aD0iMS4yIi8+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2ZfMTJfMTE1IiB4PSIwLjU5OTE2NSIgeT0iMC41NjE1NjgiIHdpZHRoPSIxMC41MzEyIiBoZWlnaHQ9IjIwLjA4MDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMS40NTQzMiIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzEyXzExNSIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iZmlsdGVyMV9mXzEyXzExNSIgeD0iMTMuNTc4MiIgeT0iMC41NjE1NjgiIHdpZHRoPSIxMC41MzEyIiBoZWlnaHQ9IjIwLjA4MDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMS40NTQzMiIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzEyXzExNSIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iZmlsdGVyMl9mXzEyXzExNSIgeD0iNS4wODg3IiB5PSI3LjM3ODAxIiB3aWR0aD0iMTQuNTU3OSIgaGVpZ2h0PSIxNC4xNzAyIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9InNoYXBlIi8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuMjM2NzgiIHJlc3VsdD0iZWZmZWN0MV9mb3JlZ3JvdW5kQmx1cl8xMl8xMTUiLz4KPC9maWx0ZXI+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8xMl8xMTUiIHgxPSIxMi4zNTQxIiB5MT0iMC41ODczNDQiIHgyPSIxMi4zNTQxIiB5Mj0iNS4wMDUxMyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSJ3aGl0ZSIvPgo8c3RvcCBvZmZzZXQ9IjAuMDA3NDE0MDciIHN0b3AtY29sb3I9IndoaXRlIi8+CjxzdG9wIG9mZnNldD0iMC4wMDc1MTQwNyIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4wMjkxNzkzIiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjkiLz4KPHN0b3Agb2Zmc2V0PSIwLjA2MzY4IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjk5MiIvPgo8c3RvcCBvZmZzZXQ9IjAuMTA5NzAxIiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjkiLz4KPHN0b3Agb2Zmc2V0PSIwLjE2NTkyNiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4yMzEwNCIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4zMDM3MjciIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuODk4MzciLz4KPHN0b3Agb2Zmc2V0PSIwLjM4MjY3MyIgc3RvcC1jb2xvcj0id2hp
1dGUiIHN0b3Atb3BhY2l0eT0iMC44NDgyOTYiLz4KPHN0b3Agb2Zmc2V0PSIwLjQ2NjU2IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjc4NCIvPgo8c3RvcCBvZmZzZXQ9IjAuNTU0MDc0IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjcwMzcwNCIvPgo8c3RvcCBvZmZzZXQ9IjAuNjQzODk5IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjYwNTYzIi8+CjxzdG9wIG9mZnNldD0iMC43MzQ3MiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC40ODgiLz4KPHN0b3Agb2Zmc2V0PSIwLjgyNTIyMSIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC4zNDkwMzciLz4KPHN0b3Agb2Zmc2V0PSIwLjkxNDA4NiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC4xODY5NjMiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzEyXzExNSIgeDE9IjEyLjM1NDQiIHkxPSIwIiB4Mj0iMTIuMzU0NCIgeTI9IjIwLjI2MDYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3AvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMwNDA0MDQiIHN0b3Atb3BhY2l0eT0iMC40Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==\'); /* 4KB */\n --yellow-button-asset: url(\'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyNSAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTIuMzU0NiIgY3k9IjEwLjEzMDQiIHI9IjkuNTAyMzgiIHRyYW5zZm9ybT0icm90YXRlKC0xODAgMTIuMzU0NiAxMC4xMzA0KSIgZmlsbD0iI0VFQjQyQSIvPgo8ZyBvcGFjaXR5PSIwLjUiIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2ZfMTJfMTE4KSI+CjxwYXRoIGQ9Ik02LjU4NDE4IDE0LjQ5MjVMNi45Njg3MiAxNy43MzM0TDQuMzU4NjIgMTYuMzQ0MUwzLjUwNzc1IDEwLjc0MjlMNC45MzEzNyA2LjUwMDY0TDguMjIxNTYgMy40NzAwN0w3LjQ2NTE3IDcuMDQ4MjNMNi4yMjIwNyAxMC40NjY5TDYuNTg0MTggMTQuNDkyNVoiIGZpbGw9IiM5MTA4MDgiIGZpbGwtb3BhY2l0eT0iMC43MSIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuNSIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfZl8xMl8xMTgpIj4KPHBhdGggZD0iTTE4LjEyNDMgMTQuNDkyNUwxNy43Mzk4IDE3LjczMzRMMjAuMzQ5OSAxNi4zNDQxTDIxLjIwMDcgMTAuNzQyOUwxOS43NzcxIDYuNTAwNjRMMTYuNDg2OSAzLjQ3MDA3TDE3LjI0MzMgNy4wNDgyM0wxOC40ODY0IDEwLjQ2NjlMMTguMTI0MyAxNC40OTI1WiIgZmlsbD0iIzkxMDgwOCIgZmlsbC1vcGFjaXR5PSIwLjcxIi8+CjwvZz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjJfZl8xMl8xMTgpIj4KPHBhdGggZD0iTTkuNDYzNTkgMTQuNjc0MkM5LjU5NDM5IDE2LjAzNTYgMTAuNzM4MSAxNy4wNzQ3IDEyLjEwNTggMTcuMDc0N0gxMi40MDg2QzEzLjc4NTQgMTcuMDc0NyAxNC45MzM2IDE2LjAyMTggMTUuMDUyNiAxNC42NTAyQzE1LjE4MzIgMTMuMTQ1MyAxMy45OTY5IDExLjg1MTYgMTIuNDg2MyAxMS44NTE2SDEyLjAyODFDMTAuNTA3OSAxMS44NTE2IDkuMzE4MiAxMy4xNjEgOS40NjM1OSAxNC42NzQyWiIgZmlsbD0iI0ZFRkZFQyIgZmlsbC1vcGFjaXR5PSIwLjgiLz4KPC9nPgo8cGF0aCBkPSJNNi4xMzkzNiA0LjAyODQxQzYuMjQ0NTkgMi40NDQ2OCA3LjU1OTg5IDEuMjEzODcgOS4xNDcxMiAxLjIxMzg3SDE1LjU2MThDMTcuMTQ5IDEuMjEzODcgMTguNDY0MyAyLjQ0NDY4IDE4LjU2OTYgNC4wMjg0MUMxOC42MDQ3IDQuNTU3MDMgMTguMTg1NCA1LjAwNTEzIDE3LjY1NTYgNS4wMDUxM0g3LjA1MzMzQzYuNTIzNTUgNS4wMDUxMyA2LjEwNDI0IDQuNTU3MDMgNi4xMzkzNiA0LjAyODQxWiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzEyXzExOCkiIGZpbGwtb3BhY2l0eT0iMC45NSIvPgo8Y2lyY2xlIGN4PSIxMi4zNTQ0IiBjeT0iMTAuMTMwMyIgcj0iOS41MzAzMSIgc3Ryb2tlPSJ1cmwoI3BhaW50MV9saW5lYXJfMTJfMTE4KSIgc3Ryb2tlLXdpZHRoPSIxLjIiLz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl8xMl8xMTgiIHg9IjAuNTk5MTY1IiB5PSIwLjU2MTU2OCIgd2lkdGg9IjEwLjUzMTIiIGhlaWdodD0iMjAuMDgwNSIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjQ1NDMyIiByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMTJfMTE4Ii8+CjwvZmlsdGVyPgo8ZmlsdGVyIGlkPSJmaWx0ZXIxX2ZfMTJfMTE4IiB4PSIxMy41NzgyIiB5PSIwLjU2MTU2OCIgd2lkdGg9IjEwLjUzMTIiIGhlaWdodD0iMjAuMDgwNSIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjQ1NDMyIiByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMTJfMTE4Ii8+CjwvZmlsdGVyPgo8ZmlsdGVyIGlkPSJmaWx0ZXIyX2ZfMTJfMTE4IiB4PSI0Ljk3ODExIiB5PSI3LjM3ODAxIiB3aWR0aD0iMTQuNTU3OSIgaGVpZ2h0PSIxNC4xNzAyIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9InNoYXBlIi8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuMjM2NzgiIHJlc3VsdD0iZWZmZWN0MV9mb3JlZ3JvdW5k
1Qmx1cl8xMl8xMTgiLz4KPC9maWx0ZXI+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8xMl8xMTgiIHgxPSIxMi4zNTQ1IiB5MT0iMC41ODczNDQiIHgyPSIxMi4zNTQ1IiB5Mj0iNS4wMDUxMyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSJ3aGl0ZSIvPgo8c3RvcCBvZmZzZXQ9IjAuMDA3NDE0MDciIHN0b3AtY29sb3I9IndoaXRlIi8+CjxzdG9wIG9mZnNldD0iMC4wMDc1MTQwNyIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4wMjkxNzkzIiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjkiLz4KPHN0b3Agb2Zmc2V0PSIwLjA2MzY4IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjk5MiIvPgo8c3RvcCBvZmZzZXQ9IjAuMTA5NzAxIiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjkiLz4KPHN0b3Agb2Zmc2V0PSIwLjE2NTkyNiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4yMzEwNCIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4zMDM3MjciIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuODk4MzciLz4KPHN0b3Agb2Zmc2V0PSIwLjM4MjY3MyIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC44NDgyOTYiLz4KPHN0b3Agb2Zmc2V0PSIwLjQ2NjU2IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjc4NCIvPgo8c3RvcCBvZmZzZXQ9IjAuNTU0MDc0IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjcwMzcwNCIvPgo8c3RvcCBvZmZzZXQ9IjAuNjQzODk5IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjYwNTYzIi8+CjxzdG9wIG9mZnNldD0iMC43MzQ3MiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC40ODgiLz4KPHN0b3Agb2Zmc2V0PSIwLjgyNTIyMSIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC4zNDkwMzciLz4KPHN0b3Agb2Zmc2V0PSIwLjkxNDA4NiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC4xODY5NjMiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzEyXzExOCIgeDE9IjEyLjM1NDQiIHkxPSIwIiB4Mj0iMTIuMzU0NCIgeTI9IjIwLjI2MDYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3AvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMwNDA0MDQiIHN0b3Atb3BhY2l0eT0iMC40Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==\'); /* 4KB */\n --green-button: url(\'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyNSAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGVsbGlwc2UgY3g9IjEyLjM1NTYiIGN5PSIxMC4xMjk3IiByeD0iOS41MDI4MiIgcnk9IjkuNTAyMDkiIHRyYW5zZm9ybT0icm90YXRlKC0xODAgMTIuMzU1NiAxMC4xMjk3KSIgZmlsbD0iIzc3QzgxOSIvPgo8ZyBvcGFjaXR5PSIwLjUiIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2ZfMTI3XzEwOCkiPgo8cGF0aCBkPSJNNi41ODQ0OCAxNC40OTIyTDYuOTY5MDQgMTcuNzMzMUw0LjM1ODgyIDE2LjM0MzhMMy41MDc5MSAxMC43NDI4TDQuOTMxNiA2LjUwMDY3TDguMjIxOTQgMy40NzAxOUw3LjQ2NTUxIDcuMDQ4MjVMNi4yMjIzNiAxMC40NjY4TDYuNTg0NDggMTQuNDkyMloiIGZpbGw9IiMwODRGOTEiIGZpbGwtb3BhY2l0eT0iMC43MSIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuNSIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfZl8xMjdfMTA4KSI+CjxwYXRoIGQ9Ik0xOC4xMjU1IDE0LjQ5MjJMMTcuNzQwOSAxNy43MzMxTDIwLjM1MTEgMTYuMzQzOEwyMS4yMDIxIDEwLjc0MjhMMTkuNzc4NCA2LjUwMDY3TDE2LjQ4OCAzLjQ3MDE5TDE3LjI0NDQgNy4wNDgyNUwxOC40ODc2IDEwLjQ2NjhMMTguMTI1NSAxNC40OTIyWiIgZmlsbD0iIzA4NEY5MSIgZmlsbC1vcGFjaXR5PSIwLjcxIi8+CjwvZz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjJfZl8xMjdfMTA4KSI+CjxwYXRoIGQ9Ik05LjU3NDQzIDE0LjY3NDJDOS43MDUyMyAxNi4wMzU2IDEwLjg0OSAxNy4wNzQ3IDEyLjIxNjYgMTcuMDc0N0gxMi41MTk0QzEzLjg5NjIgMTcuMDc0NyAxNS4wNDQ0IDE2LjAyMTggMTUuMTYzNSAxNC42NTAyQzE1LjI5NDEgMTMuMTQ1MyAxNC4xMDc3IDExLjg1MTYgMTIuNTk3MiAxMS44NTE2SDEyLjEzODlDMTAuNjE4NyAxMS44NTE2IDkuNDI5MDQgMTMuMTYxIDkuNTc0NDMgMTQuNjc0MloiIGZpbGw9IiNGM0ZFRUQiIGZpbGwtb3BhY2l0eT0iMC44Ii8+CjwvZz4KPHBhdGggZD0iTTYuMTc1ODIgNC4wMjg4N0M2LjI4MDQ1IDIuNDQ1MDQgNy41OTU2NSAxLjIxMzg3IDkuMTgyOTMgMS4yMTM4N0gxNS41Mjc4QzE3LjExNTEgMS4yMTM4NyAxOC40MzAzIDIuNDQ1MDQgMTguNTM0OSA0LjAyODg3QzE4LjU2OTggNC41NTcyNCAxOC4xNTA3IDUuMDA1MDEgMTcuNjIxMSA1LjAwNTAxSDcuMDg5NTlDNi41NjAwNyA1LjAwNTAxIDYuMTQwOTEgNC41NTcyNCA2LjE3NTgyIDQuMDI4ODdaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfMTI3XzEwOCkiIGZpbGwtb3BhY2l0eT0iMC45NSIvPgo8cGF0aCBkPSJNMTIuMzU1IDAuNDgwNDY5QzE3LjY4NDkgMC40ODA1MTQgMjIuMDA1MyA0LjgwMDQ1IDIyLjAwNTQgMTAuMTI5OUMyMi4wMDU0IDE1LjQ1OTQgMTcuNjg1IDE5Ljc4MDIgMTIuMzU1IDE5Ljc4MDNDNy4wMjQ5NyAxOS43ODAzIDIuNzA0NTkgMTUuNDU5NCAyLjcwNDU5IDEwLjEyOTlDMi43MDQ2NSA0LjgwMDQyIDcuMDI1MDEgMC40ODA0NjkgMTIuMzU1IDAuNDgwNDY5WiIgc3Ryb2tlPSJ1cmwoI3BhaW50MV9saW5lYXJfMTI3XzEwOCkiIHN0cm9rZS13aWR0aD0iMC45NiIvPgo8ZGVmcz4KPGZpbHRlciBpZD0iZmlsdGVyMF9mXzEyN18xMDgiIHg9IjEuMTgwODkiIHk9IjEuMTQzMyIgd2lkdGg9IjkuMzY3OTUiIGhlaWdodD0iMTguOTE2NSIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjE2MzQ2IiByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMTI3XzEwOCIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iZmlsdGVyMV9mXzEyN18xMDgiIHg9IjE0LjE2MTEiIHk9IjEuMTQzMyIgd2lkdGg9IjkuMzY3OTUiIGhlaWdodD0iMTguOTE2NSIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjE2MzQ2IiByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMTI3XzEwOCIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iZmlsdGVyMl9mXzEyN18xMDgiIHg9IjUuMDg4OTUiIHk9IjcuMzc4MDEiIHdpZHRoPSIxNC41NTc5IiBoZWlnaHQ9IjE0LjE3MDIiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMi4y
1MzY3OCIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzEyN18xMDgiLz4KPC9maWx0ZXI+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8xMjdfMTA4IiB4MT0iMTIuMzU1NCIgeTE9IjAuNTg3MzY0IiB4Mj0iMTIuMzU1NCIgeTI9IjUuMDA1MDEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0id2hpdGUiLz4KPHN0b3Agb2Zmc2V0PSIwLjAwNzQxNDA3IiBzdG9wLWNvbG9yPSJ3aGl0ZSIvPgo8c3RvcCBvZmZzZXQ9IjAuMDA3NTE0MDciIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuOSIvPgo8c3RvcCBvZmZzZXQ9IjAuMDI5MTc5MyIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4wNjM2OCIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45OTIiLz4KPHN0b3Agb2Zmc2V0PSIwLjEwOTcwMSIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4xNjU5MjYiIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuOSIvPgo8c3RvcCBvZmZzZXQ9IjAuMjMxMDQiIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuOSIvPgo8c3RvcCBvZmZzZXQ9IjAuMzAzNzI3IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjg5ODM3Ii8+CjxzdG9wIG9mZnNldD0iMC4zODI2NzMiIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuODQ4Mjk2Ii8+CjxzdG9wIG9mZnNldD0iMC40NjY1NiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC43ODQiLz4KPHN0b3Agb2Zmc2V0PSIwLjU1NDA3NCIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC43MDM3MDQiLz4KPHN0b3Agb2Zmc2V0PSIwLjY0Mzg5OSIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC42MDU2MyIvPgo8c3RvcCBvZmZzZXQ9IjAuNzM0NzIiIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuNDg4Ii8+CjxzdG9wIG9mZnNldD0iMC44MjUyMjEiIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuMzQ5MDM3Ii8+CjxzdG9wIG9mZnNldD0iMC45MTQwODYiIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuMTg2OTYzIi8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8xMjdfMTA4IiB4MT0iMTIuMzU0OSIgeTE9IjAiIHgyPSIxMi4zNTQ5IiB5Mj0iMjAuMjYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3AvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMwNDA0MDQiIHN0b3Atb3BhY2l0eT0iMC40Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==\'); /* 4KB */\n --disabled-button-asset: url(\'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyNSAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMTIuMzU0NiIgY3k9IjEwLjEzMDQiIHI9IjkuNTAyMzgiIHRyYW5zZm9ybT0icm90YXRlKC0xODAgMTIuMzU0NiAxMC4xMzA0KSIgZmlsbD0iI0M2Q0FENiIvPgo8ZyBvcGFjaXR5PSIwLjUiIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2ZfMTJfMTI2KSI+CjxwYXRoIGQ9Ik02LjU4NDE4IDE0LjQ5MjVMNi45Njg3MiAxNy43MzM0TDQuMzU4NjIgMTYuMzQ0MUwzLjUwNzc1IDEwLjc0MjlMNC45MzEzNyA2LjUwMDY0TDguMjIxNTYgMy40NzAwN0w3LjQ2NTE3IDcuMDQ4MjNMNi4yMjIwNyAxMC40NjY5TDYuNTg0MTggMTQuNDkyNVoiIGZpbGw9IiMwODRGOTEiIGZpbGwtb3BhY2l0eT0iMC43MSIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuNSIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfZl8xMl8xMjYpIj4KPHBhdGggZD0iTTE4LjEyNDMgMTQuNDkyNUwxNy43Mzk4IDE3LjczMzRMMjAuMzQ5OSAxNi4zNDQxTDIxLjIwMDcgMTAuNzQyOUwxOS43NzcxIDYuNTAwNjRMMTYuNDg2OSAzLjQ3MDA3TDE3LjI0MzMgNy4wNDgyM0wxOC40ODY0IDEwLjQ2NjlMMTguMTI0MyAxNC40OTI1WiIgZmlsbD0iIzA4NEY5MSIgZmlsbC1vcGFjaXR5PSIwLjcxIi8+CjwvZz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjJfZl8xMl8xMjYpIj4KPHBhdGggZD0iTTkuNDYzNTkgMTQuNjc0MkM5LjU5NDM5IDE2LjAzNTYgMTAuNzM4MSAxNy4wNzQ3IDEyLjEwNTggMTcuMDc0N0gxMi40MDg2QzEzLjc4NTQgMTcuMDc0NyAxNC45MzM2IDE2LjAyMTggMTUuMDUyNiAxNC42NTAyQzE1LjE4MzIgMTMuMTQ1MyAxMy45OTY5IDExLjg1MTYgMTIuNDg2MyAxMS44NTE2SDEyLjAyODFDMTAuNTA3OSAxMS44NTE2IDkuMzE4MiAxMy4xNjEgOS40NjM1OSAxNC42NzQyWiIgZmlsbD0iI0YzRkVFRCIgZmlsbC1vcGFjaXR5PSIwLjgiLz4KPC9nPgo8cGF0aCBkPSJNNi4xMzkxMSA0LjAyODQxQzYuMjQ0MzQgMi40NDQ2OCA3LjU1OTY1IDEuMjEzODcgOS4xNDY4NyAxLjIxMzg3SDE1LjU2MTZDMTcuMTQ4OCAxLjIxMzg3IDE4LjQ2NDEgMi40NDQ2OCAxOC41NjkzIDQuMDI4NDFDMTguNjA0NCA0LjU1NzAzIDE4LjE4NTEgNS4wMDUxMyAxNy42NTUzIDUuMDA1MTNINy4wNTMwOUM2LjUyMzMgNS4wMDUxMyA2LjEwMzk5IDQuNTU3MDMgNi4xMzkxMSA0LjAyODQxWiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzEyXzEyNikiIGZpbGwtb3BhY2l0eT0iMC45NSIvPgo8Y2lyY2xlIGN4PSIxMi4zNTQ0IiBjeT0iMTAuMTMwMyIgcj0iOS41MzAzMSIgc3Ryb2tlPSJ1cmwoI3BhaW50MV9saW5lYXJfMTJfMTI2KSIgc3Ryb2tlLXdpZHRoPSIxLjIiLz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl8xMl8xMjYiIHg9IjAuNTk5MTY1IiB5PSIwLjU2MTU2OCIgd2lkdGg9IjEwLjUzMTIiIGhlaWdodD0iMjAuMDgwNSIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjQ1NDMyIiByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMTJfMTI2Ii8+CjwvZmlsdGVyPgo8ZmlsdGVyIGlkPSJmaWx0ZXIxX2ZfMTJfMTI2IiB4PSIxMy41NzgyIiB5PSIwLjU2MTU2OCIgd2lkdGg9IjEwLjUzMTIiIGhlaWdodD0iMjAuMDgwNSIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjQ1NDMyIiByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMTJfMTI2Ii8+CjwvZmlsdGVyPgo8ZmlsdGVyIGlkPSJmaWx0ZXIyX2ZfMTJfMTI2IiB4PSI0Ljk3ODExIiB5PSI3LjM3ODAxIiB3aWR0aD0iMTQuNTU3OSIgaGVpZ2h0PSIxNC4xNzAyIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9InNoYXBlIi8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuMjM2NzgiIHJlc3VsdD0iZWZmZWN0MV9mb3JlZ3JvdW5k
1Qmx1cl8xMl8xMjYiLz4KPC9maWx0ZXI+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8xMl8xMjYiIHgxPSIxMi4zNTQyIiB5MT0iMC41ODczNDQiIHgyPSIxMi4zNTQyIiB5Mj0iNS4wMDUxMyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSJ3aGl0ZSIvPgo8c3RvcCBvZmZzZXQ9IjAuMDA3NDE0MDciIHN0b3AtY29sb3I9IndoaXRlIi8+CjxzdG9wIG9mZnNldD0iMC4wMDc1MTQwNyIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4wMjkxNzkzIiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjkiLz4KPHN0b3Agb2Zmc2V0PSIwLjA2MzY4IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjk5MiIvPgo8c3RvcCBvZmZzZXQ9IjAuMTA5NzAxIiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjkiLz4KPHN0b3Agb2Zmc2V0PSIwLjE2NTkyNiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4yMzEwNCIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CjxzdG9wIG9mZnNldD0iMC4zMDM3MjciIHN0b3AtY29sb3I9IndoaXRlIiBzdG9wLW9wYWNpdHk9IjAuODk4MzciLz4KPHN0b3Agb2Zmc2V0PSIwLjM4MjY3MyIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC44NDgyOTYiLz4KPHN0b3Agb2Zmc2V0PSIwLjQ2NjU2IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjc4NCIvPgo8c3RvcCBvZmZzZXQ9IjAuNTU0MDc0IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjcwMzcwNCIvPgo8c3RvcCBvZmZzZXQ9IjAuNjQzODk5IiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwLjYwNTYzIi8+CjxzdG9wIG9mZnNldD0iMC43MzQ3MiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC40ODgiLz4KPHN0b3Agb2Zmc2V0PSIwLjgyNTIyMSIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC4zNDkwMzciLz4KPHN0b3Agb2Zmc2V0PSIwLjkxNDA4NiIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMC4xODY5NjMiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzEyXzEyNiIgeDE9IjEyLjM1NDQiIHkxPSIwIiB4Mj0iMTIuMzU0NCIgeTI9IjIwLjI2MDYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3AvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMwNDA0MDQiIHN0b3Atb3BhY2l0eT0iMC40Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==\'); /* 4KB */\n --app-icon: url(\'app-icon.svg\'); /* 17KB, too large for base64 optimization */\n\n /* Typography - Font Families & Weights */\n --font-family-aqua: "Lucida Grande", Verdana, "DejaVu Sans", Geneva, Tahoma, sans-serif;\n --font-weight-normal: 400;\n --font-weight-bold: 700;\n\n /* Colors */\n --font-color-primary: #000000;\n --font-color-secondary: #FFF;\n --background-color-window: #FFFFFF;\n --border-color-input: #939393;\n --border-color-separator: #E3E3E3;\n --background-color-output: #E6E6E6;\n\n /* Shadows - Universal Elements */\n --shadow-output-box-inset: inset 0 4px 4px 0 rgba(0, 0, 0, 0.40);\n --shadow-desktop-icon-drop: drop-shadow(0 7.2px 9px #000);\n\n /* Traffic Lights - Fixed sizes */\n --traffic-gap: 5.63px;\n --traffic-margin-left: 11.66px;\n --traffic-light-size: 24px;\n\n /* Animation Physics (Mac-like easing for fluid motion) */\n --mac-ease-out: cubic-bezier(0.25, 0.1, 0.25, 1.0);\n}\n\n\n/* DESIGN TOKENS (VARIABLES) - MOBILE DEFAULT (< 768px) */\n:root {\n /* Assets - Mobile Specific */\n --search-btn-asset: url(\'search-button-mobile.svg\'); /* 12KB, too large for base64 optimization */\n --search-bar-asset: url(\'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzU0IiBoZWlnaHQ9IjU1IiB2aWV3Qm94PSIwIDAgMzU0IDU1IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9kXzE3XzE3MTMpIj4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfaV8xN18xNzEzKSI+CjxyZWN0IHg9IjMuMTA5ODYiIHdpZHRoPSIzNDcuNTE5IiBoZWlnaHQ9IjQ4LjEzOTEiIHJ4PSIyNC4wNjk1IiBmaWxsPSJ3aGl0ZSIvPgo8L2c+CjxyZWN0IHg9IjMuNzIyMDciIHk9IjAuNjEyMjA3IiB3aWR0aD0iMzQ2LjI5NSIgaGVpZ2h0PSI0Ni45MTQ3IiByeD0iMjMuNDU3MyIgc3Ryb2tlPSJ1cmwoI3BhaW50MF9saW5lYXJfMTdfMTcxMykiIHN0cm9rZS13aWR0aD0iMS4yMjQ0MSIvPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfMTdfMTcxMyIgeD0iLTAuMDAwMTM2NjE0IiB5PSIwIiB3aWR0aD0iMzUzLjc0IiBoZWlnaHQ9IjU0LjM1OTIiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeT0iMy4xMSIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjU1NSIvPgo8ZmVDb21wb3NpdGUgaW4yPSJoYXJkQWxwaGEiIG9wZXJhdG9yPSJvdXQiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMjUgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd18xN18xNzEzIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93XzE3XzE3MTMiIHJlc3VsdD0ic2hhcGUiLz4KPC9maWx0ZXI+CjxmaWx0ZXIgaWQ9ImZpbHRlcjFfaV8xN18xNzEzIiB4PSIzLjEwOTg2IiB5PSIwIiB3aWR0aD0iMzQ3LjUyIiBoZWlnaHQ9IjUzLjAzNjgiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eX
1BlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeT0iNC44OTc2NiIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyLjQ0ODgzIi8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9ImFyaXRobWV0aWMiIGsyPSItMSIgazM9IjEiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMjUgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0ic2hhcGUiIHJlc3VsdD0iZWZmZWN0MV9pbm5lclNoYWRvd18xN18xNzEzIi8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMTdfMTcxMyIgeDE9IjE3Ni44NjkiIHkxPSIwIiB4Mj0iMTc2Ljg2OSIgeTI9IjQ4LjEzOTEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzkzOTM5MyIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMC4zIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==\'); /* 2KB */\n\n /* Typography - Mobile Sizes */\n --font-size-title: 28px;\n --font-size-subtitle: 20px;\n --font-size-description: 18px;\n --font-size-specified: 18px;\n --font-size-label: 24px;\n --font-size-table-top: 20px;\n --font-size-table-content: 15px;\n\n /* Positioning & Spacing - Mobile Margins */\n --margin-top-title: 32px;\n --margin-top-search-bar: 32px;\n --margin-top-search-button: 32px;\n --margin-top-output-container: 48px;\n --margin-top-title-output-container: 42px;\n --margin-top-description-output-container: 32px;\n --margin-top-specified-1-output-container: 32px;\n --margin-top-specified-2-output-container: 12px;\n --margin-top-label-output-container: 32px;\n --margin-top-table-output-container: 48px;\n --margin-bottom-output-container: 40px;\n --padding-bottom-input: 5px;\n\n /* Dimensions - Flexible for Mobile */\n --window-width: 100%;\n --window-height: auto;\n --search-bar-width: 90%;\n --btn-width: 168px;\n --btn-height: 45px;\n --output-container-width: 92%;\n --output-container-height: auto;\n --output-col-padding-extra: 0;\n\n /* Window Styles - Reset for full-screen mobile view */\n --shadow-window-main: none;\n --radius-window-top: 0;\n --radius-window-bottom: 0;\n\n /* Separator & Titlebar heights */\n --separator-height: 35px;\n --titlebar-height: 28px;\n --window-padding-inner: 20px;\n}\n\n\n/* BASE RESET & BODY */\n* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\nbutton {\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n}\n\nbody {\n font-family: var(--font-family-aqua);\n color: var(--font-color-primary);\n min-height: 100vh;\n background-image: var(--bg-image-desktop-asset);\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n background-color: #f0f0f0;\n display: flex;\n justify-content: center;
1\n align-items: center;\n padding: 0;\n}\n\n/* \n * Initially, I was using a single `.hidden` class (with `display: none`).\n * However, I soon encountered a conflict between two different interface requirements \n * that forced me to split this utility.\n * \n * Here\'s what happened in detail:\n * - The mobile "jump" problem: when I hid the results box while waiting for API data\n * using `display: none`, on mobile the entire window would suddenly halve in height.\n * This happened because for the browser the element no longer existed in the layout\n * flow and therefore didn\'t occupy any space.\n *\n * - The animation dilemma: to solve the jump, the temptation was to apply a band-aid,\n * namely `visibility: hidden` (which maintains the occupied space). But by doing\n * so, I solved one problem but caused another: I lost the opening animation of the\n * main window (`macScaleUp`). That animation, in fact, only triggers when an element\n * is physically removed and then re-inserted into the layout flow, an action that\n * only `display: none` performs.\n *\n * Solution: I separated the responsibilities into two distinct classes (`.hidden` and\n * `.invisible`) to satisfy both requirements without compromises.\n*/\n\n.hidden {\n display: none !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n\n/* ANIMATIONS DEFINITIONS */\n\n/* Window Opening (Mac-like scale up from center) */\n@keyframes macScaleUp {\n 0% {\n opacity: 0;\n transform: scale(0.6);\n }\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n/* Desktop Icon appearing (Smooth fade-in) */\n@keyframes smoothFadeIn {\n from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }\n to { opacity: 1; transform: translate(-50%, -50%) scale(1); }\n}\n\n@keyframes AquaContentReveal {\n from {\n opacity: 0;\n transform: scale(0.96); /* starts slightly smaller */\n }\n to {\n opacity: 1;\n transform: scale(1); /* reaches its natural size */\n }\n}\n\n.animate-results {\n animation: AquaContentReveal 0.5s var(--mac-ease-out) forwards;\n}\n\n/* AQUA CUSTOM TOOLTIP */\n/* I decided to implement a custom tooltip to have total control over the appearance timing and for aesthetic fidelity to the yellow "post-it" style of Mac OS X Aqua */\n\n\n#search-input-wrapper {\n position: relative; /* the wrapper must have position: relative to act as an anchor for the absolute tooltip */\n}\n\n/* the Aqua-style yellow "post-it" box style */\n.aqua-tooltip-box {\n position: absolute;\n top: 100%; /* I position it exactly under the search bar */\n left: 35px;\n margin-top: 8px;\n background-color: #FFFFE1;\n border: 1px solid #BDBDBD;\n color: #000;\n font-size: 12px;\n padding: 4px 8px;\n border-radius: 3px;\n box-shadow: 0px 2px 4px rgba(0,0,0,0.2);\n z-index: 100; /* ensures it floats above every other element */\n white-space: nowrap; /* prevents the text from wrapping */\n pointer-events: none; /* crucial: the mouse must "pass through", it must not block underlying clicks */\n transition: opacity 0.2s ease-out, visibility 0.2s ease-out; /* smooth transition for appearance/disappearance */\n}\n\n.hidden-tooltip { /* Properties for the hidden state (default) */\n opacity: 0;\n visibility: hidden; /* for safety I use both opacity and visibility to hide it from the browser when not needed */\n}\n\n\n/* DESKTOP APP ICON (Visible when window is closed) */\n#desktop-app-icon {\n width: 109px;\n height: 132.2px;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background-image: var(--app-icon);\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n cursor: pointer;\n z-index: 0; /* sits behind the main window */\n filter: var(--shadow-desktop-icon-drop);\n transition: opacity 0.3s ease;\n animation: smoothFadeIn 0.4s var(--mac-ease-out);\n}\n\n\n/* MAIN WINDOW STRUCTURE */\n#window-container {\n width: var(--window-width);\n height: var(--window-height);\n background-color: var(--background-color-window);\n border-top-left-radius: var(--radius-window-top);\n border-top-right-radius: var(--radius-window-top);\n border-bottom-left-radius: var(--radius-window-bottom);\n border-bottom-right-radius: var(--radius-window-bottom);\n box-shadow: var(--shadow-window-main);\n display: flex;\n flex-direction: column;\n overflow: hidden; /* hides content during minimize animation */\n transition: width 0.4s var(--mac-ease-out), /* smooth transitions for maximize/minimize state changes */\n height 0.4s var(--mac-ease-out),\n border-radius 0.4s var(--mac-ease-out),\n top 0.4s var(--mac-ease-out),\n left 0.4s var(--mac-ease-out),\n margin 0.4s var(--mac-ease-out);\n animation: macScaleUp 0.4s var(--mac-ease-out) backwards; /* opening animation on page load or reopen */\n}\n\n/* --- TITLE BAR COMPONENT --- */\n.header-overlap-wrapper {\n position: relative;\n width: 100%;\n top: 0;\n position: sticky;\n z-index: 100;\n}\n\n.window-top-separator {\n width: 100%;\n height: var(--separator-height);\n background-image: var(--window-top-separator-asset);\n background-repeat: no-repeat;\n background-position: center top;\n background-size: 100% 100%;\n position: relative;\n z-index: 1;\n}\n\n.title-container {\n height: var(--titlebar-height);\n width: 100%;\n background-color: var(--background-color-window);\n border-bottom: 1px solid var(--border-color-separator);\n display: flex;
1\n align-items: center;\n position: relative;\n padding: 0;\n flex-shrink: 0;\n}\n\n.window-title {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: none; /* hidden on mobile default */\n justify-content: center;\n align-items: center;\n z-index: 2;\n font-weight: var(--font-weight-normal);\n font-size: 16px;\n color: var(--font-color-primary);\n margin: -1.5px 0 0 0;\n white-space: nowrap;\n}\n\n/* --- TRAFFIC LIGHTS (Buttons) --- */\n.traffic-lights-container {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n left: var(--traffic-margin-left);\n display: flex;\n gap: var(--traffic-gap);\n z-index: 3;\n margin-top: -1.5px;\n}\n\n.traffic-light {\n width: var(--traffic-light-size);\n height: var(--traffic-light-size);\n background-color: transparent;\n border: none;\n cursor: pointer;\n background-position: center;\n background-repeat: no-repeat;\n background-size: contain;\n}\n\n/* Button states */\n.traffic-light.red { background-image: var(--red-button-asset); }\n.traffic-light.yellow { background-image: var(--yellow-button-asset); }\n.traffic-light.green { background-image: var(--green-button); }\n\n/* Disabled state (greyed out) */\n.traffic-light.disabled {\n background-image: var(--disabled-button-asset) !important;\n opacity: 1;\n cursor: default !important;\n pointer-events: none !important;\n}\n\n\n/* MAIN CONTENT AREA */\n#main-content-container {\n display: flex;\n flex-direction: column;\n flex: 1;\n}\n\n.content-padding-wrapper {\n padding: var(--window-padding-inner);\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n\n/* SEARCH SECTION (The "Stack" Layout) */\n#search-container {\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n#main-heading {\n font-family: var(--font-family-aqua);\n font-size: var(--font-size-title);\n font-weight: var(--font-weight-bold);\n margin-top: var(--margin-top-title);\n text-align: center;\n padding-left: var(--window-padding-inner);\n padding-right: var(--window-padding-inner);\n}\n\n\n/* \n * It\'s time to explain the stack concept.\n * To achieve maximum graphic fidelity with the complex "Aqua" design of the search\n * bar (which has inner shadows, specific borders and transparencies in the SVG file), \n * I can\'t simply place elements side by side or use a normal background-image.\n * I must physically overlap them, creating a "stack" of elements.\n *\n * - The parent container - The "Frame"\n * In the container (.input-stack) I activate Grid. But the trick here isn\'t \n * using the grid to divide space into columns or rows, but rather to define \n * a single positioning "zone". With `grid-template-areas: "stack";` it\'s as if I\'m \n * telling the browser: "In this grid there\'s only one place to put things, and I\n * call it \'stack\'".\n *\n * - The children - The "Layers"\n * Subsequently, I tell both the background image (.search-bg-layer) and the\n * frontal content (.search-content-layer) to both occupy exactly that specific\n * area (grid-area: stack).\n *\n * - The Result\n * To make an analogy, it\'s as if I told two different people to sit on the same,\n * only chair available in the room: they\'ll inevitably end up one on the other\'s\n * lap (overlapped).\n *\n * I discovered therefore that in CSS Grid, the stacking order follows the order in the HTML code: \n * the first element written (the SVG image) stays below and acts as background, the second (the\n * icon and the input) sits on top of it. This method is excellent because the container\'s total\n * height is automatically decided by the SVG image, ensuring that proportions are always perfect\n * without having to set fixed heights.\n*/\n\n.input-stack { /* the stack container: uses CSS Grid to overlay content onto the SVG background */\n width: var(--search-bar-width);\n margin-top: var(--margin-top-search-bar);\n display: grid;
1\n grid-template-areas: "stack";\n justify-content: center;\n position: relative;\n /* height is determined automatically by the SVG background image layer */\n}\n\n.search-bg-layer { /* Layer 1: the SVG background image */\n grid-area: stack;\n width: 100%;\n aspect-ratio: 354 / 55;\n background-image: var(--search-bar-asset);\n background-size: contain; /* Assicura che l\'SVG si veda tutto */\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.search-content-layer { /* Layer 2: the content overlay (icon + input) */\n grid-area: stack;\n position: relative;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n}\n\n.search-icon-physical {\n position: absolute;\n left: 20px; /* fixed distance from the SVG edge */\n top: 45%;\n transform: translateY(-50%);\n width: 24px;\n height: 24px;\n pointer-events: none;\n z-index: 2;\n}\n\n#search-input { /* search input field */\n width: 100%;\n height: 100%;\n border: none;\n background: transparent;\n outline: none;\n font-family: var(--font-family-aqua);\n font-size: 16px;\n padding-bottom: var(--padding-bottom-input);\n padding-left: 60px; /* fixed padding to clear the icon area */\n padding-right: 40px;\n position: relative;\n z-index: 1;\n color: #525252;\n}\n\n.search-btn-container {\n margin-top: var(--margin-top-search-button);\n}\n\nbutton#search-button {\n width: var(--btn-width);\n height: var(--btn-height);\n cursor: pointer;\n border: none;\n background-color: transparent;\n background-image: var(--search-btn-asset);\n background-position: center;\n background-repeat: no-repeat;\n background-size: contain;\n text-indent: -9999px; /* hides text for accessible image button */\n}\n\n/* --- AQUA INTERACTIVE BEHAVIOR (HOVER & ACTIVE) --- */\nbutton#search-button, .traffic-light {\n transition: filter 0.2s ease-out, transform 0.1s ease-out; /* I add the same fluid transitions used for the search button to uniform the rubbery Aqua user experience. */\n}\n\nbutton#search-button:hover, .traffic-light:not(.disabled):hover, #desktop-app-icon:active {\n filter: brightness(1.08);\n}\n\nbutton#search-button:active, .traffic-light:not(.disabled):active {\n filter: brightness(0.92); /* I reduce the brightness to give the idea that the button is being pressed deep, moving away from the light source. */\n transform: translateY(1px); /* I add a small physical shift downward for tactile feedback. */\n}\n\n\n/* OUTPUT SECTION */\n.output-container {\n width: var(--output-container-width);\n height: var(--output-container-height);\n box-shadow: var(--shadow-output-box-inset);\n background-color: var(--background-color-output);\n border-radius: 8px;\n margin: var(--margin-top-output-container) auto var(--margin-bottom-output-container) auto;\n overflow-y: auto;\n padding: 20px;\n display: flex;\n flex-direction: column; /* mobile default: on narrow screens, we tell elements to stack vertically one under the other, forming a single column. */\n}\n\n.output-left-col {\n text-align: center;\n}\n\n.output-left-col p {\n margin-bottom: 0;\n margin-top: 0;\n}\n\n.creature-title-container {\n font-size: var(--font-size-subtitle);\n letter-spacing: -1.12px;\n}\n\n.attributes-container {\n margin-top: var(--margin-top-description-output-container);\n}\n\n.description {\n font-size: var(--font-size-description);\n letter-spacing: -0.72px;\n line-height: 1.6;\n}\n\n.special-name {\n font-weight: var(--font-weight-bold);\n}\n\n/* \n * I later applied `text-wrap: balance`, although the interface emulates an OS from the past,\n * I considered it useful to use this modern CSS property to improve the text block aesthetics,\n * distributing words uniformly to avoid annoying "widows" (a single word on a new line).\n * In typography, a Widow means: a very short line (often a single word) at the end of a\n * paragraph, which however ends up "alone" at the beginning of the next column or page.\n * Another similar and important concept I discovered is the Orphan: in this case it means the\n * first line of a paragraph that remains "alone" at the end of a column or page.\n * Returning to the `text-wrap: balance\' issue, browsers that don\'t support it yet aren\'t a\n * problem, because the fallback is implicit: the browser\'s standard behavior (wrapping when\n * space runs out) is already the natural fallback.\n * There\'s therefore no need to write extra code to handle it.\n*/\n\n.special-description {\n text-wrap: balance;\n max-width: 30ch;\n display: inline-block;\n}\n\n.attribute-item {\n font-size: var(--font-size-specified);\n letter-spacing: -0.88px;\n}\n\n.description + .attribute-item {\n margin-top: var(--margin-top-specified-1-output-container);\n}\n\n.attribute-item + .attribute-item {\n margin-top: var(--margin-top-specified-2-output-container);\n}\n\n.types-container {\n margin-top: var(--margin-top-label-output-container);\n}\n\n\n/* --- TABLE COMPONENT --- */\n.stats-table {\n width: 100%;\n border-collapse: collapse;\n border-spacing: 0;\n font-family: var(--font-family-aqua);\n margin-bottom: 20px;\n margin-top: var(--margin-top-table-output-container);\n font-size: var(--font-size-table-content);\n}\n\n/* Table Header */\n.stats-table thead th {\n background: linear-gradient(180deg, rgb(255, 255, 255) 0%, rgb(160, 160, 160) 130%); /* 130% is an unusual value but I set it because attempt after attempt I found it to be the most faithful to the Figma design */\n font-size: var(--font-size-table-top);\n font-weight: var(--font-weight-normal);\n text-align: left;\n padding: 8px 12px;\n}\n\n.stats-table thead th:first-child {\n border-top-left-radius: 4px;\n width: 52%;\n}\n\n.stats-table thead th:last-child {\n border-top-right-radius: 4px;\n}\n\n/* Table Body */\n.stats-table tbody td {\n padding: 8px 12px;\n border-bottom: 1px solid #E0E0E0;\n}\n\n.stats-table tbody tr:nth-child(odd) {\n background-color: #EDEDED;\n}\n\n.stat-value {\n font-weight: var(--font-weight-normal);\n}\n\n\n/* --- TYPE BADGE COMPONENT --- */\n.type-badge {\n padding: 0 20px;\n height: 42.344px;\n border-radius: 4px 4px 1px 1px;\n font-size: var(--font-size-label);\n font-weight: var(--font-weight-normal);\n display: inline-flex;\n justify-content: center;
1\n align-items: center;\n background-color: #ccc; /* Fallback, Contrast Ratio: 13.07:1 */\n color: var(--font-color-primary);\n}\n\n/* \n * For each following label I used a tool I have in my Tool folder on the desktop called "WebAIM: Contrast Checker",\n * I took as reference for the background the average gradient value (exact middle of the label).\n * Modern accessibility standards to achieve WCAG AA, require at least 4.5:1 for normal text\n*/\n\n/* Type Variations (Skins) */\n.type-fire { background: linear-gradient(180deg, #FF6D38 0%, #461909 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 6.99:1 */\n.type-water { background: linear-gradient(180deg, #3A80D2 0%, #1F3A93 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 5.62:1 */\n.type-rock { background: linear-gradient(180deg, #9C8A2E 0%, #554919 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 5.62:1 */\n.type-electric { background: linear-gradient(180deg, #F7D02C 0%, #C89400 100%); color: var(--font-color-primary); } /* Contrast Ratio: 11.4:1 */\n.type-dragon { background: linear-gradient(180deg, #6F35FC 0%, #432099 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 6.6:1 */\n.type-grass { background: linear-gradient(180deg, #5E9C3A 0%, #246327 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 5.53:1 */\n.type-poison { background: linear-gradient(180deg, #A33EA1 0%, #642663 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 7.31:1 */\n.type-ice { background: linear-gradient(180deg, #A6E7FF 0%, #5DA9C9 100%); color: var(--font-color-primary); } /* Contrast Ratio: 10.73:1 */\n.type-fairy { background: linear-gradient(180deg, #B86C92 0%, #6e3f56 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 6.57:1 */\n.type-ground { background: linear-gradient(180deg, #E2BF65 0%, #8D6E2A 100%); color: var(--font-color-primary); } /* Contrast Ratio: 6.87:1 */\n.type-flying { background: linear-gradient(180deg, #4A95BD 0%, #204b69 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 5.62:1 */\n.type-bug { background: linear-gradient(180deg, #A6B91A 0%, #343b09 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 5.27:1 */\n.type-dark { background: linear-gradient(180deg, #705746 0%, #47362D 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 10.02:1 */\n.type-psychic { background: linear-gradient(180deg, #F95587 0%, #9C3353 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 5.09:1 */\n.type-steel { background: linear-gradient(180deg, #B7B7CE 0%, #7A7A8F 100%); color: var(--font-color-primary); } /* Contrast Ratio: 6.41:1 */\n.type-ghost { background: linear-gradient(180deg, #735797 0%, #4A3863 100%); color: var(--font-color-secondary); } /* Contrast Ratio: 7.9:1 */\n\n\n/* MEDIA QUERIES (DESKTOP OVERRIDES) */\n\n/* TABLET / SMALL DESKTOP (768px and up) */\n@media (min-width: 768px) {\n :root {\n /* Assets */\n --search-btn-asset: url(\'search-button-desktop.svg\'); /* 14KB, too large for base64 optimization */\n\n /* Dimensions (Fixed for desktop) */\n --window-width: 1112px;\n --window-height: 824px;\n --search-bar-width: 339.297px;\n --btn-width: 168px;\n --btn-height: 45px;\n --output-container-width: 899.7px;\n --output-container-height: 408px;\n --output-container-padding: 24px 32px;\n\n /* Positioning & Margins */\n --margin-top-title: 48px;\n --margin-top-search-button: 28px;\n --margin-top-table-output-container: 88px;\n --output-col-padding-extra: 64px;\n\n /* Window Styles (Shadows & Rounded Corners) */\n --shadow-window-main: 0 7.2px 30.4px 0 #000;\n --radius-window-top: 6px;\n --radius-window-bottom: 2px;\n\n /* Typography */\n --font-size-specified: 22px;\n --font-size-table-top: 24px;\n --font-size-table-content: 18px;\n }\n\n body {\n padding: 20px;\n }\n\n .window-title {\n display: flex;\n }\n\n .output-left-col {\n text-align: left;\n display: flex;\n flex-direction: column;\n justify-content: center;\n }\n\n .output-right-col {\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n }\n .output-container {\n padding: var(--output-container-padding);\n }\n}\n\n\n/* MEDIUM DESKTOP / TABLET LANDSCAPE (768px to 1199px) */\n@media (min-width: 768px) and (max-width: 1199px) {\n :root {\n /* Adjusted Dimensions */\n --window-width: 889.6px;\n --window-height: 720px;\n --output-container-width: 700px;\n --output-container-height: 380px;\n\n /* Adjusted Margins */\n --margin-top-title: 32px;\n --margin-top-search-button: 22px;\n --margin-top-output-container: 42px;\n --output-col-padding-extra: 24px;\n --traffic-margin-left: 16px;\n --traffic-gap: 4px;\n\n /* Adjusted Typography */\n --font-size-title: 32px;\n --font-size-description: 16px;\n --font-size-specified: 18px;\n --font-size-label: 20px;\n --font-size-table-top: 20px;\n --font-size-table-content: 16px;\n --traffic-light-size: 20px;\n }\n\n /* \n * Now that the screen is wider, I abandon the vertical "stack" of mobile\n * and switch to a Grid to be able to place elements side by side.\n\n * Additionally, I subsequently define the columns: the second is locked to \n * a fixed width of 250px. The first uses `1fr` (a "fraction"), which is as if\n * telling the browser: "Take all the horizontal space that\'s left after having\n * arranged the fixed column". This therefore makes it flexible.\n */\n .output-container { \n display: grid;\n grid-template-columns: 1fr 250px; \n gap: 30px;\n }\n\n .output-right-col {\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n padding-left: calc(var(--output-col-padding-extra) + 12px);\n }\n\n .stats-table {\n margin-bottom: 20px;\n margin-top: 0;\n }\n}\n\n\n/* --- FULL DESKTOP (1200px and up) --- */\n@media (min-width: 1200px) {\n :root {\n --margin-top-search-bar: 48px;\n --font-size-subtitle: 24px;\n --font-size-title: 32px;\n --margin-top-output-container: 64px;\n }\n\n /* Output Grid Layout (Full Size) */\n .output-container {\n display: grid;\n grid-template-columns: 1fr 380px;\n gap: 40px;\n }\n\n .stats-table {\n margin-bottom: 20px;\n margin-top: 0;\n }\n}\n\n/* --- MAXIMIZED STATE (Green Button) --- */\n#window-container.maximized { /* anchors the window to the viewport, filling the screen */\n position: fixed !important;\n top: 0 !important;\n left: 0 !important;\n width: 100vw !important;\n height: 100vh !important;\n border-radius: 0 !important;\n margin: 0 !important;\n box-shadow: none !important;\n z-index: 9999;\n overflow-y: auto !important;\n padding-bottom: 0 !important;\n}\n\n/* --- MINIMIZED STATE "WindowShade" (Yellow Button) --- */\n#window-container.minimized { /* collapses the window to show only the title bar */\n height: 29px !important; /* forces the height to exactly match the title bar + border */\n min-height: 0 !important;\n\n /* Removing extra spacing and styling */\n padding-bottom: 0 !important;\n box-shadow: none !important;\n border-bottom-left-radius: 0 !important;\n border-bottom-right-radius: 0 !important;\n border-bottom: 1px solid var(--border-color-separator) !important;\n\n /* Positioning fix: aligns to top to avoid jumping to center */\n align-self: flex-start !important;\n margin-top: 15vh !important;\n}\n\n/* \n * With the following media query we respect the system settings of users\n * who require reduced motion (e.g. for vestibular disorders).\n * It disables large animations like the window opening and the icon\n * appearance, making them instantaneous.\n*/\n@media (prefers-reduced-motion: reduce) {\n #window-container,\n #desktop-app-icon,\n .animate-results,\n .aqua-tooltip-box {\n animation: none !important;\n transition: none !important; /* also disables the fluid button transitions */\n }\n\n button#search-button:active, \n .traffic-light:not(.disabled):active { /* also maintains static visual feedback for hover/active */\n transform: none !important; /* no physical shift */\n filter: brightness(0.92); /* only color change */\n }\n}\n'})})}),(0,a.jsx)(s.A,{value:"js",label:"script.js",children:(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-js",children:"/* DESIGN\n ------\n * This script constitutes the functional heart of the \"RPG Creature Search App\" application.\n * The architecture is modular, avoids over-engineering and is divided into three\n * macro-areas of well-defined responsibilities:\n\n * 1. Data Management:\n * - Handles asynchronous communication with the external API (fetch, network error\n * handling and \"Creature not found\").\n * - Manipulates raw data received (via destructuring) and prepares it for rendering.\n * - Maintains constant references (`const`) to static DOM elements for efficient\n * updates, avoiding querying the DOM repeatedly\n * - The main functions (`handleSearch`, `fetchAndDisplayCreature`, `clearResults`,\n * `displayCreatureData`) are declared in the global scope to be accessible\n * and easily testable.\n\n * 2. UI Management and Accessibility (inside DOMContentLoaded)\n * - This block activates exclusively when DOM loading is complete.\n * - Manages specific UI behaviors like the custom \"Aqua\" style tooltip (implementing\n * delays for a more natural UX) and its intelligent disappearance on focus or typing.\n * - Implements the logic to make the desktop icon completely accessible via keyboard,\n * handling the Enter key via `keydown`. This is necessary because the icon is technically\n * a `<div>` (lacking native button semantics), so its behavior must be emulated.\n\n * 3. \"Aqua\" Window Manager Emulation (also inside DOMContentLoaded)\n * - Centralizes the logic of the \"traffic light\" buttons (close, minimize, maximize) to\n * emulate the behavior of Mac OS X windows.\n * - Manages the visibility states of the main window and desktop icon.\n * - Implements a function (`updateTrafficLightStates`) that dynamically recalculates the\n * enabled/disabled state of buttons based on context (mobile device vs desktop, maximized\n * window vs normal), reacting in real time even to window resizing.\n*/\n\n\n/* freeCodeCamp instructions:\n * - When the #search-input element contains the value Red and the #search-button element is clicked,\n * an alert should appear with the text \"Creature not found\". \u2714\ufe0f\n * - When the #search-input element contains the value Pyrolynx and the #search-button element is clicked,\n * the values in the #creature-name, #creature-id, #weight, #height, #hp, #attack, #defense, #special-attack,\n * #special-defense, and #speed elements should be PYROLYNX, #1 or 1, Weight: 42 or 42, Height: 32 or 32, 65,\n * 80, 50, 90, 55, and 100, respectively. \u2714\ufe0f\n * - When the #search-input element contains the value Pyrolynx and the #search-button element is clicked, a\n * single element should be added within the #types element that contains the text FIRE. The #types element\n * content should be cleared between searches. \u2714\ufe0f\n * - When the #search-input element contains the value 2 and the #search-button element is clicked, the values\n * in the #creature-name, #creature-id, #weight, #height, #hp, #attack, #defense, #special-attack,\n * #special-defense, and #speed elements should be AQUOROC, #2 or 2, Weight: 220 or 220, Height: 53 or 53, 85,\n * 90, 120, 60, 70, and 40, respectively. \u2714\ufe0f\n * - When the #search-input element contains the value 2 and the #search-button element is clicked, two elements\n * should be added within the #types element that contain text values WATER and ROCK, respectively. The #types\n * element content should be cleared between searches. \u2714\ufe0f\n * - When the #search-input element contains an invali
1d creature name, and the #search-button element is clicked,\n * an alert should appear with the text \"Creature not found\". \u2714\ufe0f\n * - When the #search-input element contains a valid creature ID and the #search-button element is clicked, the UI\n * should be filled with the correct data. \u2714\ufe0f\n*/\n\n\n/* I wrote the next two constants all in uppercase because it's a convention that serves to immediately \n * communicate two things to other developers (or to future me):\n * - Immutability: that value is fixed and must not be modified\n * - Global scope: makes them \"jump out\" in the code, signaling their importance, a concept\n * that's extremely useful during debugging when there are errors\n*/\nconst CREATURE_API_URL = \"https://rpg-creature-api.freecodecamp.rocks/api/creature/\"; \nconst ALL_CREATURES_URL = \"https://rpg-creature-api.freecodecamp.rocks/api/creatures\";\n\nconst searchInput = document.getElementById(\"search-input\");\nconst searchButton = document.getElementById(\"search-button\");\nconst creatureName = document.getElementById(\"creature-name\");\nconst creatureId = document.getElementById(\"creature-id\");\nconst specialName = document.getElementById(\"special-name\");\nconst specialDescription = document.getElementById(\"special-description\");\nconst weight = document.getElementById(\"weight\");\nconst height = document.getElementById(\"height\");\nconst types = document.getElementById(\"types\");\nconst hp = document.getElementById(\"hp\");\nconst attack = document.getElementById(\"attack\");\nconst defense = document.getElementById(\"defense\");\nconst specialAttack = document.getElementById(\"special-attack\");\nconst specialDefense = document.getElementById(\"special-defense\");\nconst speed = document.getElementById(\"speed\");\n\nconst outputContainer = document.getElementById(\"creature-output-box\");\n\nconst outputElement = [ creatureName, creatureId, specialName, specialDescription, weight, height, hp, attack, defense, specialAttack, specialDefense, speed ]; // I omitted 'types' for special handling (see below)\n\nconst STAT_MAPPING = {\n hp: hp,\n attack: attack,\n defense: defense,\n 'special-attack': specialAttack, \n 'special-defense': specialDefense,\n speed: speed\n};\n\n// IMPORTANT: I declare functions BEFORE using them in event listeners\nconst handleSearch = async () => {\n const searchTerm = searchInput.value.trim().toLowerCase(); // I clean the input for security (remove all spaces at the beginning and end)\n \n if (!searchTerm) {\n setTimeout(() => alert(\"Please provide a creature name or id valid\"), 10); // The setTimeout is unusual but it solved a critical UX problem: Mobile browsers tend to block alerts, because they know they offer terrible UX due to some sites using them to deliver spam. I don't support the use of alerts because I consider them impersonal and invasive but for this freeCodeCamp project it's an essential requirement\n return;\n }\n \n clearResults(); // clear the previous search results\n \n try {\n await fetchAndDisplayCreature(searchTerm); // call the function that will handle the fetch and display\n } catch (error) {\n console.error(\"Generic error:\", error); // handling unexpected errors, like network error\n setTimeout(() => alert(\"A connection error has occurred.\"), 10);\n }\n};\n\nconst fetchAndDisplayCreature = async (searchTerm) => {\n const url = `${CREATURE_API_URL}${searchTerm}`; // build the final URL for the creature, as we saw in the README of the 44-fcc-forum-leaderboard project I support this more readable syntax over using `.concat()`\n \n try {\n const response = await fetch(url);\n \n if (!response.ok) { // here we intercept before doing .json(). The classic 404/not found error, if the creature doesn't exist, the API returns 404\n throw new Error(\"Creature not found\");\n }\n \n const data = await response.json();\n displayCreatureData(data); // if everything goes well, pass the data to the renderer\n \n } catch (error) {\n if (error.message === \"Creature not found\") { // \"Creature not found\" because that's the name we gave to this error in the if (!response.ok)\n setTimeout(() => alert(\"Creature not found\"), 10);\n } else {\n setTimeout(() => alert(\"Technical error during data retrieval.\"), 10); // we also handle all other possible errors (e.g. failed parsing, the process that analyzes an input and determines its correctness)\n console.error(error);\n }\n }\n};\n\nconst clearResults = () => {\n outputElement.forEach(element => { // cleaning all text fields with a loop, thanks to having created an array at the beginning with all output elements\n element.textContent = \"\";
1 // we apply cleaning only to dynamic values (like #weight, #hp, etc...)\n });\n \n types.textContent = \"\"; // cleaning the dynamic container #types (handled separately because it contains dynamic <span> tags for labels like FIRE/ROCK)\n searchInput.value = ''; // also cleaning the input\n\n outputContainer.classList.add('invisible'); // we hide the entire results container. This way the fixed labels and empty table also disappear. This because I noticed that after searching for another creature Weight, Height and the table remained fixed waiting for the API, this happened because they are fixed (static) HTML elements\n};\n\nconst displayCreatureData = (data) => {\n // \"pop-up\" animation\n outputContainer.classList.remove('invisible'); // 1. I make the container visible in the layout\n outputContainer.classList.remove('animate-results'); // 2. I remove the animation class. For efficiency, the browser doesn't apply this change immediately but puts it on hold\n void outputContainer.offsetWidth; // 3. EXTREMELY IMPORTANT line: by reading a layout property, I force the browser to an immediate \"Reflow\", making the class removal from point 2 effective\n outputContainer.classList.add('animate-results'); // 4. I put the class back. Now the browser sees a real state change (from \"without\" to \"with\" animated class) and restarts the animation from the beginning.\n\n const { id, name, weight: weightValue, height: heightValue, types: creatureTypes, stats, special } = data; // we extract the necessary fields from the API data using destructuring\n \n creatureName.textContent = name.toUpperCase(); // we update the fields (span) using global DOM references\n creatureId.textContent = `#${id}`;\n weight.textContent = weightValue;\n height.textContent = heightValue;\n specialName.textContent = special.name;\n specialDescription.textContent = special.description;
1\n \n stats.forEach(stat => {\n const statName = stat.name.toLowerCase(); \n const statValue = stat.base_stat;\n \n if (STAT_MAPPING[statName]) { // we use the map to find the corresponding DOM element (`hp`, `attack`, etc.)\n STAT_MAPPING[statName].textContent = statValue;\n }\n });\n \n types.textContent = ''; \n creatureTypes.forEach(typeInfo => {\n const typeElement = document.createElement('span'); // create a new <span> element\n const typeName = typeInfo.name.toLowerCase();\n typeElement.textContent = typeName.toUpperCase(); // assign the text (e.g. \"FIRE\")\n typeElement.setAttribute('class', `type-badge type-${typeName}`);\n types.appendChild(typeElement); // \"append\" the node (real DOM element) to the main container\n });\n};\n\nsearchButton.addEventListener(\"click\", handleSearch); // I add the event listener only after declaring the function, otherwise it wouldn't work\n\ndocument.addEventListener('DOMContentLoaded', () => {\nconst searchInputElement = document.getElementById('search-input');\n const aquaTooltip = document.getElementById('aqua-tooltip');\n let tooltipTimeout; // fundamental variable to store and clear the tooltip timer if the user moves the mouse quickly\n\n searchInputElement.addEventListener('mouseenter', () => { // when the mouse enters the input area\n tooltipTimeout = setTimeout(() => { // instead of showing it immediately, I set a 300ms delay, it's a good compromise, faster than native but not instantaneous.\n if (aquaTooltip) aquaTooltip.classList.remove('hidden-tooltip'); // show the tooltip if the timer hasn't been cancelled\n }, 300);\n });\n\n searchInputElement.addEventListener('mouseleave', () => { // so when the mouse exits the input area\n clearTimeout(tooltipTimeout); // EXTREMELY IMPORTANT step: I cancel the current timer if exiting before the 300ms\n if (aquaTooltip) aquaTooltip.classList.add('hidden-tooltip'); // immediately hide the tooltip\n });\n\n const hideTooltipImmediately = () => { // if the user clicks (focus) or types (keydown), the tooltip is a distraction and must be hidden\n clearTimeout(tooltipTimeout);\n if (aquaTooltip) aquaTooltip.classList.add('hidden-tooltip');\n };\n\n searchInputElement.addEventListener('focus', hideTooltipImmediately);\n searchInputElement.addEventListener('keydown', hideTooltipImmediately);\n const windowContainer = document.getElementById('window-container');\n const desktopIcon = document.getElementById('desktop-app-icon'); // The icon that appears when the app is closed\n const btnClose = document.getElementById('btn-close'); // Red\n const btnMinimize = document.getElementById('btn-minimize'); // Yellow\n const btnMaximize = document.getElementById('btn-maximize'); // Green\n \n const handleCloseClick = () => { // click on red --\x3e close\n if (windowContainer) windowContainer.classList.add('hidden'); // hide the main window\n if (desktopIcon) desktopIcon.classList.remove('hidden'); // show the icon on desktop\n updateTrafficLightStates(); // update the traffic lights state (both disabled)\n };\n \n const handleIconClick = () => { // click on desktop icon --\x3e reopen window\n if (desktopIcon) desktopIcon.classList.add('hidden'); // hide the desktop icon\n if (windowContainer) windowContainer.classList.remove('hidden'); // show the main window again\n updateTrafficLightStates(); // restore the correct traffic lights state\n };\n \n const handleMaximizeClick = () => { // click on green --\x3e maximize\n if (btnMaximize && btnMaximize.classList.contains('disabled')) return;\n if (windowContainer) windowContainer.classList.add('maximized');\n updateTrafficLightStates(); // update the traffic lights state after maximization\n };\n \n const handleMinimizeClick = () => { // click on yellow --\x3e minimize\n if (btnMinimize && btnMinimize.classList.contains('disabled')) return;\n if (windowContainer && windowContainer.classList.contains('maximized')) {\n windowContainer.classList.remove('maximized');\n updateTrafficLightStates(); // update the traffic lights state after normalization\n }\n };\n \n const updateTrafficLightStates = () => {\n if (!btnMaximize || !btnMinimize || !windowContainer || !desktopIcon) return;\n \n if (windowContainer.classList.contains('hidden')) { // if the window is closed, disable both traffic lights\n btnMaximize.classList.add('disabled');\n btnMinimize.classList.add('disabled');\n return;\n }\n \n const isDesktop = window.innerWidth >= 768; // uses the CSS breakpoint (768px)\n \n if (isDesktop) {\n if (windowContainer.classList.contains('maximized')) { // on desktop, enable/disable based on maximized state.\n btnMaximize.classList.add('disabled');\n btnMinimize.classList.remove('disabled');\n } else {\n btnMaximize.classList.remove('disabled');\n btnMinimize.classList.add('disabled');\n }\n } else {\n btnMaximize.classList.add('disabled'); // on mobile, disable both traffic lights and remove the 'maximized' class\n btnMinimize.classList.add('disabled');\n if (windowContainer) windowContainer.classList.remove('maximized');\n }\n };\n \n if (btnClose) btnClose.addEventListener('click', handleCloseClick);\n if (btnMaximize) btnMaximize.addEventListener('click', handleMaximizeClick);\n if (btnMinimize) btnMinimize.addEventListener('click', handleMinimizeClick);\n if (desktopIcon) { // a function is necessary here because the desktop icon is <div>, not a real <button>\n desktopIcon.addEventListener('click', handleIconClick);\n desktopIcon.addEventListener('keydown', (event) => { // handles the \"Enter\" key when the icon has focus\n if (event.key === 'Enter') {\n event.preventDefault(); // prevents unwanted default behaviors\n handleIconClick(); // executes the same open function\n }\n });\n }\n \n updateTrafficLightStates(); // set initial state\n window.addEventListener('resize', updateTrafficLightStates); // update on resize\n});\n\nsearchInput.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n searchButton.click();\n }\n});\n"})})})]}),"\n",(0,a.jsx)(n.h2,{id:"il-progetto-preferito-davvero",children:"Il Progetto Preferito (davvero)"}),"\n",(0,a.jsxs)(n.p,{children:["Difficile concludere questo percorso in un modo migliore di questo.",(0,a.jsx)(n.br,{}),"\n","Lo dico spesso, ma questa volta lo sento in modo ancora pi\xf9 forte: \xe8 stato il mio progetto preferito.",(0,a.jsx)(n.br,{}),"\n","Mi sono reso conto del perch\xe9 fatico a trovare cose da dire nei README: i commenti all\u2019interno dei file ",(0,a.jsx)(n.strong,{children:"sono gi\xe0 il README"}),". \xc8 l\xec che ho documentato le decisioni, i compromessi e le scelte progettuali."]}),"\n",(0,a.jsx)(n.p,{children:"Per non lasciare questo README vuoto, ci tengo a riportare due elementi che rappresentano due aspetti importanti del lavoro fatto: il design vettoriale in stile Aqua e la pipeline di compressione immagini che mi ha portato a risultati sorprendenti."}),"\n",(0,a.jsx)(n.h2,{id:"design-aqua-jelly-e-icona-figma",children:"Design: Aqua, Jelly e Icona Figma"}),"\n",(0,a.jsx)(n.p,{children:"Dal foglio di stile:"}),"\n",(0,a.jsxs)(n.blockquote,{children:["\n",(0,a.jsx)(n.p,{children:"\u201cPer ottenere la massima fedelt\xe0 ai complessi effetti \u2018gelatina\u2019 (jelly) e alle texture tipiche di Aqua, ho optato per asset SVG vettoriali per la cornice e i pulsanti principali, delegando al CSS solo le ombre per la profondit\xe0.\u201d"}),"\n"]}),"\n",(0,a.jsxs)(n.p,{children:["Tutti gli elementi chiave dell\u2019interfaccia, ovvero cornice, pulsanti principali, dettagli jelly, li ho creati da zero in Figma.",(0,a.jsx)(n.br,{}),"\n","Per il bottone ",(0,a.jsx)(n.strong,{children:"Search"})," invece ho tratto grande aiuto da una creazione di ",(0,a.jsx)(n.em,{children:"Arthur Objartel"})," che ha generosamente condiviso il bottone su Figma con la community, che ho adattato allo stile dell\u2019app."]}),"\n",(0,a.jsx)(n.p,{children:"L\u2019icona dell\u2019app (app chiusa) l\u2019ho disegnata anch\u2019essa in Figma:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Ho creato il fuoco in stile freeCodeCamp con effetto pixel, usando lo strumento penna: un lavoro minuzioso, ma \xe8 stato eccezionale per esercitarmi con questo strumento."}),"\n",(0,a.jsx)(n.li,{children:"La lente che avvolge il fuoco nasce dall\u2019idea di citare il Safari dell\u2019epoca, con quel bordo skeuomorfico e le parti lucide."}),"\n",(0,a.jsx)(n.li,{children:"Ho aggiunto due zone lucide e, per completare il tributo a freeCodeCamp, ho fatto ricadere le parti pi\xf9 scure del c
1ontorno sui lati destro e sinistro, proprio come nel logo di freeCodeCamp."}),"\n"]}),"\n",(0,a.jsx)(n.h2,{id:"performance-pipeline-di-compressione-immagini",children:"Performance: Pipeline di Compressione Immagini"}),"\n",(0,a.jsxs)(n.p,{children:["Sempre da ",(0,a.jsx)(n.code,{children:"styles.css"}),":"]}),"\n",(0,a.jsxs)(n.blockquote,{children:["\n",(0,a.jsx)(n.p,{children:"\u201cHo raggiunto i soli 163KB del background del desktop affinando il mio processo di compressione delle immagini\u2026\u201d"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"Invece di fare upscaling AI direttamente sul PNG originale, ho seguito una pipeline in tre step:"}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Conversione PNG \u2192 JPEG"})," con gi\xe0 una leggera compressione."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Upscaling AI sul JPEG"}),", non sul PNG, per lavorare su un file gi\xe0 pi\xf9 leggero."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.strong,{children:"Passaggio finale in ImageOptim"})," per ridurre ancora il peso, mantenendo la qualit\xe0 visiva."]}),"\n"]}),"\n",(0,a.jsxs)(n.p,{children:["Il confronto \xe8 stato netto, con la procedura standard (upscaling su PNG \u2192 JPEG \u2192 ImageOptim) ho ottenuto ",(0,a.jsx)(n.strong,{children:"382 KB"}),", mentre con la procedura ottimizzata (PNG \u2192 JPEG compresso \u2192 upscaling AI \u2192 ImageOptim) soltanto ~",(0,a.jsx)(n.strong,{children:"163 KB"}),". ",(0,a.jsx)("br",{}),"\nPi\xf9 del ",(0,a.jsx)(n.strong,{children:"50% di riduzione"}),", con qualit\xe0 visiva pressoch\xe9 identica!"]}),"\n",(0,a.jsx)(n.h2,{id:"dallo-static-archive-al-version-control",children:'Dallo "Static Archive" al Version Control'}),"\n",(0,a.jsxs)(n.p,{children:["Questo progetto non rappresenta solo il culmine di questo percorso, ma anche il punto di svolta del mio metodo di lavoro. ",(0,a.jsx)("br",{}),"\nHo trattato intenzionalmente questa repository come un archivio di snapshot perfette: ogni progetto caricato rappresentava un capitolo chiuso e immutabile del mio percorso di apprendimento. Il mio obiettivo era infatti di tracciare la progressione lineare, non il processo di sviluppo ramificato."]}),"\n",(0,a.jsx)(n.h3,{id:"toccare-il-limite",children:"Toccare il Limite"}),"\n",(0,a.jsxs)(n.p,{children:['Ho toccato con mano con questo progetto i limiti di questo approccio, che potremmo definire "upload manuale". ',(0,a.jsx)("br",{}),"\nHo capito che in un contesto di ",(0,a.jsx)(n.em,{children:"Software Engineering"})," reale, Git non \xe8 un archivio di backup (il Google Drive del codice), ma una macchina del tempo e uno strumento di gestione della complessit\xe0.\nSono altres\xec consapevole che ogni metodo di lavoro ha il suo contesto. ",(0,a.jsx)("br",{}),"\nPer un percorso di apprendimento come questo sono felice col senno di poi di aver messo come priorit\xe0 assoluta l'apprendimento dei concetti che mi ha trasmesso freeCodeCamp, ma ora sono pronto per il next level."]}),"\n",(0,a.jsx)(n.h2,{id:"cosa-ho-imparato",children:"Cosa Ho Imparato"}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Architettura JavaScript e gestione asincrona:"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Organizzazione del codice in tre macro-aree chiare: gestione dati (fetch, parsing, mapping), gestione UI/accessibilit\xe0 e emulazione del window manager in stile macOS, con funzioni globali e facilmente testabili."}),"\n",(0,a.jsxs)(n.li,{children:["Uso di ",(0,a.jsx)(n.code,{children:"async/await"})," con ",(0,a.jsx)(n.code,{children:"try...catch"}),' per gestire le chiamate all\u2019API delle creature, distinguendo tra errori attesi (404 "Creature not found") ed errori tecnici generici, e coordinando il tutto tramite una funzione principale ',(0,a.jsx)(n.code,{children:"handleSearch"}),"."]}),"\n",(0,a.jsxs)(n.li,{children:["Destructuring dei dati API (es. ",(0,a.jsx)(n.code,{children:"const { id, name, weight: weightValue, height: heightValue, types: creatureTypes, stats, special } = data;"}),") e uso di mappe (",(0,a.jsx)(n.code,{children:"STAT_MAPPING"}),") per collegare nomi statistica API a elementi DOM (",(0,a.jsx)(n.code,{children:"hp"}),", ",(0,a.jsx)(n.code,{children:"attack"}),", ecc.), riducendo codice duplicato e aumentando la chiarezza."]}),"\n",(0,a.jsxs)(n.li,{children:["Pattern di \u201cpulizia\u201d e \u201crendering\u201d separati: ",(0,a.jsx)(n.code,{children:"clearResults()"})," per azzerare in blocco output e UI (incluso nascondere il container), ",(0,a.jsx)(n.code,{children:"displayCreatureData()"})," per riempire i campi, creare dinamicamente i badge dei tipi con ",(0,a.jsx)(n.code,{children:"createElement"}),"/",(0,a.jsx)(n.code,{children:"appendChild"})," e riavviare l\u2019animazione CSS con il trucco remove \u2192 reflow \u2192 add."]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Gestione eventi, UX e accessibilit\xe0 via JS:"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["Gestione coerente dell\u2019input: normalizzazione con ",(0,a.jsx)(n.code,{children:".trim().toLowerCase()"}),", guard clause su input vuoto, invio via Enter intercettando ",(0,a.jsx)(n.code,{children:"key
1down"})," e trasformandolo in click sul bottone."]}),"\n",(0,a.jsxs)(n.li,{children:["Tooltip \u201cAqua\u201d personalizzato con timer: delay di 300ms su ",(0,a.jsx)(n.code,{children:"mouseenter"}),", ",(0,a.jsx)(n.code,{children:"clearTimeout"})," su ",(0,a.jsx)(n.code,{children:"mouseleave"})," e chiusura immediata su ",(0,a.jsx)(n.code,{children:"focus"}),"/",(0,a.jsx)(n.code,{children:"keydown"})," per evitare distrazioni quando l\u2019utente inizia a digitare."]}),"\n",(0,a.jsxs)(n.li,{children:["Emulazione dei pulsanti \u201csemaforo\u201d (chiudi/minimizza/massimizza) con logica centralizzata in ",(0,a.jsx)(n.code,{children:"updateTrafficLightStates()"}),", che tiene conto di: finestra chiusa/aperta, stato maximized, e dimensione viewport (desktop vs mobile), aggiornando pulsanti e classi in maniera reattiva anche su ",(0,a.jsx)(n.code,{children:"resize"}),"."]}),"\n",(0,a.jsxs)(n.li,{children:["Aggiunta di accessibilit\xe0 a elementi non semantici: icona desktop implementata come ",(0,a.jsx)(n.code,{children:"<div>"})," ma resa accessibile via tastiera con listener su ",(0,a.jsx)(n.code,{children:"keydown"})," per ",(0,a.jsx)(n.code,{children:"Enter"}),", replicando il comportamento di un vero ",(0,a.jsx)(n.code,{children:"button"}),"."]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Web Performance Optimization (HTML/CSS):"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Pipeline ragionata per ridurre drasticamente il peso del background desktop: PNG originale \u2192 JPEG leggermente compresso \u2192 upscaling AI sul JPEG \u2192 passaggio finale in ImageOptim, passando da ~382 KB a ~163 KB senza perdita visibile di qualit\xe0."}),"\n",(0,a.jsx)(n.li,{children:"Uso mirato dell\u2019embedding Base64 per asset molto piccoli (sotto i 5KB), iniettandoli direttamente nel CSS per eliminare richieste HTTP extra e prevenire flash di caricamento di icone/texture di interfaccia."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Layout avanzato con CSS Grid e Flexbox:"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["Sperimentazione con la \u201cStack Technique\u201d tramite CSS Grid: definizione di una singola area (es. ",(0,a.jsx)(n.code,{children:"grid-area: stack"}),") assegnata a pi\xf9 elementi (sfondo, icona, input) per sovrapporli con precisione, evitando assoluti caotici; comprensione che l\u2019ordine di sovrapposizione segue anche l\u2019ordine dell\u2019HTML quando gli z-index sono impliciti."]}),"\n",(0,a.jsx)(n.li,{children:"Uso combinato di Flexbox e Grid: layout a colonna singola/flessibile sui device piccoli e passaggio a Grid su viewport pi\xf9 ampie per gestire meglio allineamenti, spaziature e proporzioni della finestra Aqua e dei suoi contenuti."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Problem solving UI: visibilit\xe0, layout e animazioni:"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["Comprensione profonda della differenza tra ",(0,a.jsx)(n.code,{children:"display: none"})," e ",(0,a.jsx)(n.code,{children:"visibility: hidden"})," e dei loro effetti su layout e animazioni:","\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"display: none"})," rimuove l\u2019elemento dal flusso causando \u201csalti\u201d (collapse di altezza) su mobile."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"visibility: hidden"})," conserva lo spazio ma pu\xf2 interferire con animazioni che richiedono reinserimento nel flusso."]}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["Soluzione tramite due utility distinte: una classe per controllare il layout (es. ",(0,a.jsx)(n.code,{children:".hidden"}),") e una per il solo aspetto/visibilit\xe0 (es. ",(0,a.jsx)(n.code,{children:".invisible"}),"), cos\xec da separare responsabilit\xe0 tra \u201ccosa occupa spazio\u201d e \u201ccosa si vede\u201d."]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Accessibilit\xe0 (A11y) a livello di CSS e struttura:"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Verifica dei contrasti colore per i badge dei tipi di creatura (FIRE, WATER, ROCK, ecc...) usando strumenti come WebAIM Contrast Checker, e adattamento dei colori per raggiungere almeno WCAG AA (4.5:1 su normal text)."}),"\n",(0,a.jsxs)(n.li,{children:["Introduzione di ",(0,a.jsx)(n.code,{children:"@media (prefers-reduced-motion: reduce)"})," per rispettare utenti sensibili al movimento, spegnendo o semplificando animazioni come il pop-up della finestra o transizioni troppo vistose."]}),"\n",(0,a.jsx)(n.li,{children:"Consapevolezza del \u201cdebito di accessibilit\xe0\u201d quando si scelgono elementi non semantici (div al posto di button) per esigenze di design/CSS, e bilanciamento con role, aria-label e tabindex (anche se parte di questo \xe8 gestito soprattutto via JS)."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Tipografia, design e \u201cAqua thinking\u201d:"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Studio dei font nativi sui vari sistemi (Lucida Grande, Verdana, DejaVu Sans, ecc.) per replicare il feeling Aqua senza dover caricare font esterni, costruend
1o una font stack robusta e storicamente accurata."}),"\n",(0,a.jsxs)(n.li,{children:["Scoperta e uso del moderno ",(0,a.jsx)(n.code,{children:"text-wrap: balance"})," per ridurre \u201cwidows\u201d e \u201corphans\u201d tipografiche, distribuendo meglio il testo nei titoli e nei blocchi descrittivi."]}),"\n",(0,a.jsx)(n.li,{children:"Ricostruzione di uno stile skeuomorfico credibile usando combo di SVG vettoriali (per forme complesse) e CSS moderno (box-shadow, gradienti, filtri) invece di PNG pesanti, mantenendo al tempo stesso performance e nitidezza su schermi ad alta densit\xe0."}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Architettura HTML/CSS orientata al JS futuro:"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["Organizzazione del CSS con un approccio \u201cutility-first per le variabili\u201d: colori, spaziature, dimensioni centralizzate in ",(0,a.jsx)(n.code,{children:":root"}),", e una gerarchia che combina ID per macro-sezioni e classi riusabili per componenti."]}),"\n",(0,a.jsxs)(n.li,{children:["Strutturazione dell\u2019HTML pensando alla manipolazione JavaScript: separazione tra etichette statiche (\u201cWeight:\u201d) e valori dinamici in ",(0,a.jsx)(n.code,{children:"<span>"})," dedicati, facilitando aggiornamenti mirati senza dover ricostruire str
1inghe testuali intere."]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.strong,{children:"Architettura di progetto e commenti come README:"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Documentare direttamente nel codice le scelte architetturali e di design, in modo che chi legge i file abbia gi\xe0 tutto il contesto."}),"\n",(0,a.jsx)(n.li,{children:"Usare i commenti non solo per spiegare \u201ccosa fa\u201d il codice, ma \u201cperch\xe9 \xe8 stato progettato cos\xec\u201d."}),"\n"]}),"\n",(0,a.jsx)(n.h2,{id:"riflessione-finale",children:"Riflessione Finale"}),"\n",(0,a.jsxs)(n.p,{children:["Questo \xe8 stato il culmine, dopo il quale ",(0,a.jsx)(n.strong,{children:"sono obbligato a fermarmi"}),": ho 5 esami universitari che mi aspettano, oltre a svariati elaborati e laboratori.",(0,a.jsx)(n.br,{}),"\n","Vorrei fermare la mia vita ed iniziare subito il corso ",(0,a.jsx)(n.strong,{children:"\u201cFront End Development Libraries Certification\u201d"})," di freeCodeCamp, perch\xe9 la \u201cfiamma\u201d \xe8 al massimo, ma se lo facessi ora rischierei di ritardare la laurea. E, paradossalmente, \xe8 lo stesso freeCodeCamp a suggerire pazienza: il corso non \xe8 ancora completo, e i moduli finali (Data Visualization and D3, TypeScript Fundamentals e l\u2019esame finale) riportano ancora ",(0,a.jsx)(n.em,{children:"Coming 2026"}),"."]}),"\n",(0,a.jsx)(n.p,{children:"Per quanto siano stati mesi passati per lo pi\xf9 da solo, in compagnia di questo percorso, non esagero se dico che \xe8 stato uno dei periodi pi\xf9 belli della mia vita, se non il pi\xf9 bello."}),"\n",(0,a.jsx)(n.p,{children:"\xc8 arrivato il momento di dedicare nuovamente tutte le energie all'universit\xe0, ma non sar\xe0 difficile grazie alla resilienza e pensiero logico che mi ha donato JavaScript."}),"\n",(0,a.jsx)(n.hr,{}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.strong,{children:"Next"}),":",(0,a.jsx)(n.br,{}),"\n","Universit\xe0 ed\u2026 evolvere questa repository in una knowledge base navigabile, cos\xec che non resti solo un archivio personale ma possa diventare uno strumento di supporto per chi sta affrontando lo stesso percorso."]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(p,{...e})}):p(e)}},1470:(e,n,i)=>{i.d(n,{A:()=>v});var t=i(6540),a=i(4164),o=i(7559),r=i(3104),s=i(6347),l=i(205),c=i(7485),d=i(1682),u=i(679);function p(e){return t.Children.toArray(e).filter(e=>"\n"!==e).map(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad <Tabs> child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`)})?.filter(Boolean)??[]}function h(e){const{values:n,children:i}=e;return(0,t.useMemo)(()=>{const e=n??function(e){return p(e).map(({props:{value:e,label:n,attributes:i,default:t}})=>({value:e,label:n,attributes:i,default:t}))}(i);return function(e){const n=(0,d.XI)(e,(e,n)=>e.value===n.value);if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map(e=>e.value).join(", ")}" found in <Tabs>. Every value needs to be unique.`)}(e),e},[n,i])}function m({value:e,tabValues:n}){return n.some(n=>n.value===e)}function g({queryString:e=!1,groupId:n}){const i=(0,s.W6)(),a=function({queryString:e=!1,groupId:n}){if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw new Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,c.aZ)(a),(0,t.useCallback)(e=>{if(!a)return;const n=new URLSearchParams(i.location.search);n.set(a,e),i.replace({...i.location,search:n.toString()})},[a,i])]}function I(e){const{defaultValue:n,queryString:i=!1,groupId:a}=e,o=h(e),[r,s]=(0,t.useState)(()=>function({defaultValue:e,tabValues:n}){if(0===n.length)throw new Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(e){if(!m({value:e,tabValues:n}))throw new Error(`Docusaurus error: The <Tabs> has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map(e=>e.value).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}const i=n.find(e=>e.default)??n[0];if(!i)throw new Error("Unexpected error: 0 tabValues");return i.value}({defaultValue:n,tabValues:o})),[c,d]=g({queryString:i,groupId:a}),[p,I]=function({groupId:e}){const n=function(e){return e?`docusaurus.tab.${e}`:null}(e),[i,a]=(0,u.Dv)(n);return[i,(0,t.useCallback)(e=>{n&&a.set(e)},[n,a])]}({groupId:a}),b=(()=>{const e=c??p;return m({value:e,tabValues:o})?e:null})();(0,l.A)(()=>{b&&s(b)},[b]);return{selectedValue:r,selectValue:(0,t.useCallback)(e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);s(e),d(e),I(e)},[d,I,o]),tabValues:o}}var b=i(2303);const M={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};var x=i(4848);function j({className:e,block:n,selectedValue:i,selectValue:t,tabValues:o}){const s=[],{blockElementScrollPositionUntilNextRender:l}=(0,r.a_)(),c=e=>{const n=e.currentTarget,a=s.indexOf(n),r=o[a].value;r!==i&&(l(n),t(r))},d=e=>{let n=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const i=s.indexOf(e.currentTarget)+1;n=s[i]??s[0];break}case"ArrowLeft":{const i=s.indexOf(e.currentTarget)-1;n=s[i]??s[s.length-1];break}}n?.focus()};return(0,x.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,a.A)("tabs",{"tabs--block":n},e),children:o.map(({value:e,label:n,attributes:t})=>(0,x.jsx)("li",{role:"tab",tabIndex:i===e?0:-1,"aria-selected":i===e,ref:e=>{s.push(e)},onKeyDown:d,onClick:c,...t,className:(0,a.A)("tabs__item",M.tabItem,t?.className,{"tabs__item--active":i===e}),children:n??e},e))})}function w({lazy:e,children:n,selectedValue:i}
1){const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(e){const e=o.find(e=>e.props.value===i);return e?(0,t.cloneElement)(e,{className:(0,a.A)("margin-top--md",e.props.className)}):null}return(0,x.jsx)("div",{className:"margin-top--md",children:o.map((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==i}))})}function y(e){const n=I(e);return(0,x.jsxs)("div",{className:(0,a.A)(o.G.tabs.container,"tabs-container",M.tabList),children:[(0,x.jsx)(j,{...n,...e}),(0,x.jsx)(w,{...n,...e})]})}function v(e){const n=(0,b.A)();return(0,x.jsx)(y,{...e,children:p(e.children)},String(n))}},5962:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/rpg-creature-search-app-8b18569a596bdd0d2d7f739ab0ce580f.webp"},8453:(e,n,i)=>{i.d(n,{R:()=>r,x:()=>s});var t=i(6540);const a={},o=t.createContext(a);function r(e){const n=t.useContext(o);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function s(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:r(e.components),t.createElement(o.Provider,{value:n},e.children)}},9365:(e,n,i)=>{i.d(n,{A:()=>r});i(6540);var t=i(4164);const a={tabItem:"tabItem_Ymn6"};var o=i(4848);function r({children:e,hidden:n,className:i}){return(0,o.jsx)("div",{role:"tabpanel",className:(0,t.A)(a.tabItem,i),hidden:n,children:e})}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.