The official website for the open-source compatibility layer fpPS4
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

compatibility list release

The compatibility list is released!

+833 -69
+1
.gitignore
··· 9 9 public_html/.htaccess 10 10 public_html/ads.txt 11 11 public_html/images/Thumbs.db 12 + public_html/images/CUSA
+388 -5
public_html/compatibility/app.js
··· 1 - function adjustSizes() { 1 + let imageLoading = true; 2 + let avifSupport = false; 3 + let sTimer; 4 + let iTimer; 5 + let pTimer; 6 + let skeletonTimeout; 7 + let tagFilter = []; 8 + let oldestFilter = false; 9 + let datesButton = false; 10 + let lightTheme = false; 11 + let pageNumber = 1; 12 + let inputValue = 1; 13 + let updatePageValue = false; 14 + let noImagesLocked = false; 15 + 16 + function setCookie(name, value) { 17 + var date = new Date(); 18 + date.setMonth(date.getMonth() + 1); 19 + var expires = "; expires=" + date.toUTCString(); 20 + document.cookie = name + "=" + value + expires + "; path=/"; 21 + } 22 + 23 + // Checks cookies 24 + function checkCookies(name, defaultValue) { 25 + return document.cookie.split("; ").find(c => c.startsWith(name + "="))?.split("=")[1] ?? defaultValue; // if cookie doesnt exist use the default value 26 + } 27 + 28 + const imageLoadingCookie = checkCookies("imageLoading", "true"); 29 + if (imageLoadingCookie === "false") { 30 + imageLoading = false; 31 + document.getElementById("imageButton").classList.add('selected'); 32 + } 33 + 34 + const dateCookie = checkCookies("datesButton", "false"); 35 + if (dateCookie === "true") { 36 + datesButton = true; 37 + 38 + document.getElementById("datesButton").classList.add('selected'); 39 + } 40 + 41 + const lightThemeCookie = checkCookies("lightTheme", "false"); 42 + if (lightThemeCookie === "true") { 43 + lightTheme = true; 44 + document.body.classList.toggle("lightMode"); 45 + } 46 + //- Checks cookies 47 + 48 + function LightModeIconChange() { 49 + if (lightTheme === true) { 50 + document.getElementById("lightModeIcon").style.display = "flex"; 51 + document.getElementById("darkModeIcon").style.display = "none"; 52 + } else { 53 + document.getElementById("darkModeIcon").style.opacity = "1"; 54 + } 55 + } 56 + 57 + function adjustFontSize() { 2 58 var screenWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 59 + var referenceWidth = 2000; 3 60 var referenceFontSize = 16; 4 - var phoneReferenceWidth = 560; 61 + var phoneReferenceWidth = 550; 5 62 6 - if (screenWidth >= phoneReferenceWidth) { 63 + if (screenWidth >= referenceWidth) { 64 + // if its larger then 2000px 65 + var fontSize = (screenWidth / referenceWidth) * referenceFontSize; 66 + document.documentElement.style.fontSize = fontSize + 'px'; 67 + } else if (screenWidth >= phoneReferenceWidth) { 68 + // if its higer then 550 but under 2000 then set it as default 7 69 document.documentElement.style.fontSize = referenceFontSize + 'px'; 8 70 } else { 71 + // if the width is lower then 550 9 72 var fontSize = (screenWidth / phoneReferenceWidth) * referenceFontSize; 10 73 document.documentElement.style.fontSize = fontSize + 'px'; 11 74 } 12 75 } 13 76 14 - window.addEventListener('DOMContentLoaded', adjustSizes); 15 - window.addEventListener('resize', adjustSizes); 77 + // resize on load and when rezsizing the window 78 + window.addEventListener('load', adjustFontSize); 79 + window.addEventListener('resize', adjustFontSize); 80 + 81 + // Check Avif Support 82 + console.log(`Hey there! I've implemented an avif support check to spare browsers like Edge from having a stroke :D!`); 83 + const avif = new Image(); 84 + avif.src = "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A="; 85 + avif.onload = function() { 86 + console.log('AVIF IS SUPPORTED :D'); 87 + avifSupport = true; 88 + }; 89 + avif.onerror = function() { 90 + console.log('AVIF IS NOT SUPPORTED D:'); 91 + avifSupport = false; 92 + noImagesLocked = true; 93 + imageButton(); 94 + }; 95 + 96 + // Page Load 97 + document.addEventListener('DOMContentLoaded', function() { 98 + fetch('https://fpps4.net/scripts/search.php?q=&stats') 99 + .then(response => response.text()) 100 + .then(data => { 101 + document.querySelector('#gameWrapper').innerHTML = data; 102 + applyMods(); 103 + gameStats(); 104 + filter(); 105 + }) 106 + .catch(console.error); 107 + }); 108 + 109 + // Game Stats 110 + function gameStats() { 111 + var labelData = []; 112 + var tempPercentage = 0; 113 + var tempTotal = 0; 114 + var labels = ['N/A', 'Nothing', 'Boots', 'Menus', 'Ingame', 'Playable']; 115 + console.log("\nCOMPATIBILITY STATS"); 116 + 117 + labels.forEach(label => { 118 + var div = document.getElementById(label).getAttribute('data').split('+'); 119 + var percentage = div[0]; 120 + var total = div[1]; 121 + labelData[label] = [percentage, total]; 122 + console.log(label + ' = ' + percentage + '% ['+ total +']'); 123 + }); 124 + console.log("\n"); 125 + 126 + for (var label in labelData) { 127 + var data = labelData[label]; 128 + var percentage = data[0]; 129 + var total = data[1]; 130 + if (label === 'N/A' || label === 'Nothing') { //combine N/A & Nothing 131 + tempPercentage = (parseFloat(percentage) + parseFloat(tempPercentage)); 132 + tempTotal = (parseFloat(total) + parseFloat(tempTotal)); 133 + percentage = tempPercentage.toFixed(2); 134 + total = tempTotal.toFixed(0); 135 + element = document.getElementById('NothingBar'); 136 + text = document.getElementById('NothingInfo') 137 + } else { 138 + element = document.getElementById(label + 'Bar'); 139 + text = document.getElementById(label + 'Info') 140 + } 141 + element.style.width = percentage + '%'; 142 + text.textContent = percentage + '% - ' + total; 143 + } 144 + }; 145 + 146 + // Header Load 147 + fetch('https://fpps4.net/parts/navbar.html') 148 + .then(response => response.text()) 149 + .then(data => { 150 + document.querySelector('#header').innerHTML = data; 151 + LightModeIconChange(); 152 + }) 153 + .catch(console.error); 154 + 155 + // Searching 156 + document.querySelector('#search').addEventListener('input', function() { 157 + updatePageValue = true; 158 + pageNumber = 1; 159 + UpdateSearchResults() 160 + }); 161 + 162 + function UpdateSearchResults() { 163 + clearTimeout(sTimer); 164 + 165 + const gameWrapper = document.querySelector('#gameWrapper'); 166 + gameWrapper.querySelectorAll('.gameContainer').forEach(container => { 167 + const skeletonDiv = document.createElement('div'); 168 + skeletonDiv.classList.add('gameContainer', 'skeletonAnimation'); 169 + gameWrapper.replaceChild(skeletonDiv, container); 170 + }); 171 + 172 + sTimer = setTimeout(() => { 173 + const searchQuery = document.querySelector('#search').value; 174 + fetch('https://fpps4.net/scripts/search.php?q=' + searchQuery + '&tag=' + tagFilter + '&page=' + pageNumber + '&oldest=' + oldestFilter) 175 + .then(response => response.text()) 176 + .then(data => { 177 + document.querySelector('#gameWrapper').innerHTML = data; 178 + applyMods(); 179 + }) 180 + .catch(console.error); 181 + }, 300); 182 + } 183 + 184 + // Filters 185 + function filter() { 186 + var ids = ['Nothing', 'Boots', 'Menus', 'Ingame', 'Playable']; 187 + 188 + ids.forEach(id => { 189 + element = document.getElementById(id + 'Bar'); 190 + parent = element.parentElement; 191 + parent.addEventListener('click', function() { 192 + this.classList.toggle('selected'); 193 + 194 + tagFilter.includes(id) ? tagFilter.splice(tagFilter.indexOf(id), 1) : tagFilter.push(id); 195 + console.log('Tag Filter has been updated to: ' + tagFilter); 196 + updatePageValue = true; 197 + pageNumber = 1; 198 + UpdateSearchResults(); 199 + }); 200 + }); 201 + }; 202 + 203 + // Image Handler 204 + function imageHandler() { 205 + document.querySelectorAll('.gameImage').forEach(gameImage => { 206 + gameImage.setAttribute("loading", "lazy"); 207 + const data = gameImage.dataset.cusa; 208 + gameImage.src = data.includes('CUSA') && imageLoading ? (avifSupport ? "../images/CUSA/" + data + ".avif" : "../images/NA.jpg") : (avifSupport ? "../images/NA.avif" : "../images/NA.jpg"); 209 + }); 210 + } 211 + 212 + // Link Handler 213 + function linkHandler() { 214 + document.querySelectorAll('.gameImageLink').forEach(link => { 215 + link.setAttribute("target", "_blank"); 216 + link.href = "https://github.com/red-prig/fpps4-game-compatibility/issues/" + link.dataset.id; 217 + link.removeAttribute('data-id'); 218 + }); 219 + } 220 + 221 + // Game Status Colors 222 + function gameColors() { 223 + document.querySelectorAll('.gameContainer').forEach(game => { 224 + const status = game.querySelector('.gameStatus').textContent; 225 + const statusClass = (['Nothing', 'Boots', 'Ingame', 'Menus', 'Playable'].includes(status) ? status : 'Nothing'); 226 + game.querySelector('.gameStatus').classList.add(statusClass); 227 + game.querySelector('.gameSeparator').classList.add(statusClass); 228 + }); 229 + } 230 + 231 + // Image Effect 232 + function imageEffect() { 233 + document.querySelectorAll('.gameImage').forEach(image => { 234 + image.addEventListener('mousemove', e => { 235 + const r = image.getBoundingClientRect(); 236 + const x = e.clientX - r.left; 237 + const y = e.clientY - r.top; 238 + image.style.transformOrigin = `${x}px ${y}px`; 239 + image.style.transform = 'scale(1.08)'; 240 + }); 241 + image.addEventListener('mouseleave', () => { 242 + image.style.transform = 'scale(1)'; 243 + }); 244 + }); 245 + } 246 + 247 + function applyMods() { 248 + gameColors(); 249 + imageHandler(); 250 + linkHandler(); 251 + imageEffect(); 252 + dateHandler(); 253 + updatePageSelector(); 254 + updateFooter() 255 + } 256 + 257 + // update footer position 258 + function updateFooter() { 259 + const viewportHeight = window.innerHeight; 260 + const bodyHeight = document.body.clientHeight; 261 + if (bodyHeight < viewportHeight) { 262 + document.getElementById('footer').style.position = 'fixed'; 263 + } else { 264 + document.getElementById('footer').style = ''; 265 + } 266 + } 267 + 268 + // NoImage button 269 + function imageButton() { 270 + e = document.getElementById('imageButton'); 271 + if (noImagesLocked === true) { 272 + e.classList.add('selected'); 273 + e.style.cursor = 'default'; 274 + } else { 275 + clearTimeout(iTimer); 276 + e.classList.toggle('selected'); 277 + 278 + imageLoading = !imageLoading; 279 + setCookie("imageLoading", imageLoading); 280 + iTimer = setTimeout(imageHandler, 450); 281 + } 282 + } 283 + 284 + // show last updated dates button 285 + function dateButton() { 286 + datesButton = !datesButton; 287 + setCookie("datesButton", datesButton); 288 + dateHandler(); 289 + } 290 + 291 + function dateHandler() { 292 + var gameCusaText = document.querySelectorAll(".gameCusa"); 293 + var button = document.getElementById('datesButton'); 294 + datesButton ? button.classList.add('selected') : button.classList.remove('selected'); 295 + 296 + gameCusaText.forEach(element => { 297 + var currentText = element.textContent; 298 + var date = element.getAttribute('data'); 299 + 300 + if (datesButton === true) { 301 + element.dataset.status = currentText; 302 + element.textContent = date; 303 + } else if (currentText === date) { 304 + element.textContent = element.dataset.status; 305 + } 306 + }); 307 + } 308 + 309 + // Sorting Button 310 + function sortButton(e) { 311 + clearTimeout(iTimer); 312 + oldestFilter = !oldestFilter; 313 + e.classList.toggle('selected'); 314 + iTimer = setTimeout(UpdateSearchResults, 450); 315 + } 316 + 317 + function toggleLightMode() { 318 + lightTheme = !lightTheme; 319 + setCookie("lightTheme", lightTheme); 320 + document.body.classList.toggle("lightMode"); 321 + var x = document.getElementById("darkModeIcon") 322 + var y = document.getElementById("lightModeIcon") 323 + if (y.style.display === "none" || y.style.display == '') { 324 + y.style.display = "flex"; 325 + x.style.display = "none"; 326 + } else { 327 + x.style.opacity = "1"; 328 + x.style.display = "flex"; 329 + y.style.display = "none"; 330 + }; 331 + }; 332 + 333 + function toggleMenu() { 334 + // var x = document.getElementById("overlay"); 335 + var y = document.getElementById("close-icon") 336 + var z = document.getElementById("menu-icon") 337 + console.log('pretend like you see a menu') 338 + if (y.style.display === "none" || y.style.display == '') { 339 + // x.style.display = "flex"; 340 + y.style.display = "flex"; 341 + z.style.display = "none"; 342 + setTimeout(function() { 343 + // x.style.opacity = 1; 344 + }, 50); 345 + } else { 346 + // x.style.opacity = 0; 347 + y.style.display = "none"; 348 + z.style.display = "flex"; 349 + setTimeout(function() { 350 + // x.style.display = "none"; 351 + }, 300); 352 + }; 353 + }; 354 + 355 + function updatePageSelector() { 356 + const inputElement = document.getElementById('search2'); 357 + let maxValue = document.getElementById("totalPages").getAttribute('data'); 358 + if (updatePageValue === true) { 359 + inputValue = 1; 360 + updatePageValue = false; 361 + } 362 + 363 + inputElement.placeholder = inputValue + "/" + maxValue; 364 + 365 + inputElement.addEventListener('input', function(event) { 366 + clearTimeout(pTimer); 367 + inputValue = parseInt(inputElement.value, 10); 368 + 369 + if (isNaN(parseFloat(inputValue))) { 370 + inputValue = 1; 371 + } 372 + if (inputValue > maxValue) { 373 + inputValue = maxValue; 374 + } else if (inputValue < 1) { 375 + inputValue = 1; 376 + } 377 + 378 + inputElement.placeholder = inputValue + "/" + maxValue; 379 + pTimer = setTimeout(() => { 380 + if (inputValue > 9) { 381 + inputElement.style.padding = "0 0.3rem 0 0.7rem"; 382 + } else if (inputValue < 10) { 383 + inputElement.style.padding = ""; 384 + } 385 + inputElement.value = ""; 386 + pageNumber = inputValue; 387 + UpdateSearchResults(); 388 + }, 500); 389 + }); 390 + } 391 + 392 + // Footer Load 393 + fetch('https://fpps4.net/parts/footer.html') 394 + .then(response => response.text()) 395 + .then(data => { 396 + document.querySelector('#footer').innerHTML = data; 397 + }) 398 + .catch(console.error);
+43 -30
public_html/compatibility/index.html
··· 3 3 <head> 4 4 <meta charset="UTF-8"> 5 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> 7 7 <title>fpPS4 - Compatibility List</title> 8 8 <meta property="title" content="Game Compatibility List"> 9 9 <meta property="og:title" content="fpPS4 - Game Compatibility List"> ··· 13 13 <meta name="description" content="These are the games that have been tested with fpPS4"> 14 14 <meta name="theme-color" content="#4C566A"> 15 15 <meta name="copyright" content="fpPS4"> 16 - <meta name="keywords" content="fpPS4, playstation, ps4, playstation 4, emulator, windows, open source, free pascal, compatibility layer, red-prig, compatibility list, compatibility"> 16 + <meta name="keywords" content="fpPS4, playstation, ps4, playstation 4, emulator, windows, open source, free pascal, compatibility layer, red-prig, compatibility list, compatibility, playable, menus, ingame, boots, nothing"> 17 17 <meta name="robots" content="index,follow"> 18 18 <meta name="author" content="Mr. Snowy"> 19 19 <link rel="stylesheet" href="./styles.css"> 20 20 <link rel="icon" href="https://fpps4.net/images/fpPS4-icon.png" type="image/png"> 21 21 </head> 22 + <style> 23 + /* fixes page load stuff */ 24 + .progressBarInfo {opacity: 0;} 25 + </style> 22 26 <body> 23 - <div class="main"> 24 - <h1 class="mainText">Game Compatibility List</h1> 25 - <h1 class="smolMainText">Soon</h1> 26 - <div class=progressContainer> 27 - <div class="progressWrap"></div> 28 - <div class="progressRow"> 29 - <div class="progressWrap"></div> 30 - <div class="progressWrap"></div> 31 - </div> 32 - <div class="progressRow"> 33 - <div class="progressWrap"></div> 34 - <div class="progressWrap"></div> 35 - </div></div> 36 - <div class="searchContainer"> 37 - <input type="text" name="fpps4" class="searchBar"> 38 - <div class="optionButtons"> 39 - <div class="optionButton"></div> 40 - <div class="optionButton"></div> 41 - <div class="optionButton"></div> 42 - </div> 27 + <header id="header"></header> 28 + <main class="main"> 29 + <h1 class="mainText">Game Compatibility List</h1> 30 + <h3 class="smolMainText">These are the games that have been tested with <span style="color: #f0e74a;">fp</span>PS4.<br>Click on an image to view the game's GitHub issue.<br>The database is updated every 20 minutes.</h3> 31 + <div class=progressContainer> 32 + <div class="progressWrap"><span class="progressBarInfo" id="PlayableInfo"></span><span class="progressBarText">Playable</span><div class="progressBar" id="PlayableBar" style="background:#54A396;"></div></div> 33 + <div class="progressRow"> 34 + <div class="progressWrap"><span class="progressBarInfo" id="MenusInfo"></span><span class="progressBarText">Menus</span><div class="progressBar" id="MenusBar" style="background:#4288B7;"></div></div> 35 + <div class="progressWrap"><span class="progressBarInfo" id="IngameInfo"></span><span class="progressBarText">Ingame</span><div class="progressBar" id="IngameBar" style="background:#fabb44;"></div></div> 36 + </div> 37 + <div class="progressRow"> 38 + <div class="progressWrap"><span class="progressBarInfo" id="NothingInfo"></span><span class="progressBarText">Nothing</span><div class="progressBar" id="NothingBar" style="background:#1F2325;"></div></div> 39 + <div class="progressWrap"><span class="progressBarInfo" id="BootsInfo"></span><span class="progressBarText">Boots</span><div class="progressBar" id="BootsBar" style="background:#F2766E;"></div></div> 40 + </div></div> 41 + <div class="searchContainer"> 42 + <div class="searchBarContainer"> 43 + <input autocomplete="off" type="text" name="fpps4" class="searchBar" id="search" placeholder="Search TITLE / CUSA"> 44 + <input autocomplete="off" type="number" min="1" placeholder="1/1" name="fpps4" class="pageSelector" id="search2"> 45 + <!-- chrome, stop thinking that an text and number field is used to input passwords... please--> 43 46 </div> 44 - <div class="gameWrapper"> 45 - <div class="gameContainer skeletonAnimation"></div> 46 - <div class="gameContainer skeletonAnimation"></div> 47 - <div class="gameContainer skeletonAnimation"></div> 48 - <div class="gameContainer skeletonAnimation"></div> 49 - <div class="gameContainer skeletonAnimation"></div> 50 - <div class="gameContainer skeletonAnimation"></div> 47 + 48 + <div class="optionButtons"> 49 + <div onclick="sortButton(this)" class="optionButton">Oldest</div> 50 + <div id="imageButton" onclick="imageButton()" class="optionButton">No Images</div> 51 + <div id="datesButton" onclick="dateButton()" class="optionButton">Dates</div> 51 52 </div> 52 53 </div> 53 - <script defer src="app.js"></script> 54 + <div id="gameWrapper" class="gameWrapper"> 55 + <div class="gameContainer skeletonAnimation"></div> 56 + <div class="gameContainer skeletonAnimation"></div> 57 + <div class="gameContainer skeletonAnimation"></div> 58 + <div class="gameContainer skeletonAnimation"></div> 59 + <div class="gameContainer skeletonAnimation"></div> 60 + <div class="gameContainer skeletonAnimation"></div> 61 + </div> 62 + <h5 style="text-align: center; font-size: 0.8rem;">Trademarks and logos displayed on this website are the property of their respective owners.<br> The use of any third-party trademarks, brand names, product names, and logos is for<br> informational purposes only and does not imply endorsement or sponsorship.</h5> 63 + <br> 64 + </main> 65 + <footer id="footer"></footer> 66 + <script defer src="app.js"></script> 54 67 </body> 55 68 </html>
+217 -32
public_html/compatibility/styles.css
··· 2 2 3 3 /*+ scrollbar */ 4 4 ::-webkit-scrollbar { 5 - width: 9px;} 5 + width: 0.57rem;} 6 6 7 7 ::-webkit-scrollbar-thumb { 8 - background: var(--main0); 9 - border-radius: 2px;} 8 + background: var(--hover); 9 + border-radius: 0.13rem;} 10 10 11 11 ::-webkit-scrollbar-thumb:hover { 12 - background: var(--main1); } 12 + background: var(--selected); } 13 13 /*- scrollbar */ 14 - .skeletonAnimation { 15 - background-image: linear-gradient(90deg, var(--border), var(--main1), var(--border)) !important; 16 - background-size: 200% 100% !important; 17 - animation: skeletonLoading 2s ease-in-out infinite; 18 - } 19 - @keyframes skeletonLoading { 20 - 0% {background-position: -100% 0;} 21 - 100% {background-position: 100% 0;} 22 - } 23 14 24 15 :root { 25 16 --text: #eceff4; 17 + --whiteText: #eceff4; 26 18 --border: #e1e1e10d; 27 19 --solidBorder: #393b3e; 28 20 --background: #101112; 29 21 --main0: #26272a; 30 22 --main1: #2f3134; 31 23 --hover: #393b3e; 24 + --selected: #424448; 25 + } 26 + 27 + .lightMode { 28 + --text: #2E3440; 29 + --border: #b2b8c770; 30 + --solidBorder: #b2b8c7; 31 + --background: #ECEFF4; 32 + --main0: #E5E9F0; 33 + --main1: #D8DEE9; 34 + --hover: #c6ccd9; 35 + --selected: #b0b7c7; 36 + } 37 + .lightMode .lightmodeText{color: #eceff4} 38 + 39 + /* .lightmode ::selection{background-color: #3864d4} 40 + ::selection { 41 + background-color: yellow; 42 + } */ 43 + 44 + .Nothing {--status-color: #1F2325;} 45 + .Boots {--status-color: #F2766E;} 46 + .Menus {--status-color: #4288B7;} 47 + .Ingame {--status-color: #fabb44;} 48 + .Playable {--status-color: #54A396;} 49 + 50 + .skeletonAnimation { 51 + background-image: linear-gradient(90deg, var(--solidBorder), var(--main1), var(--solidBorder)) !important; 52 + background-size: 200% 100% !important; 53 + animation: skeletonLoading 2s ease-in-out infinite; 54 + } 55 + @keyframes skeletonLoading { 56 + 0% {background-position: -100% 0;} 57 + 100% {background-position: 100% 0;} 32 58 } 33 59 34 60 * { ··· 39 65 color: var(--text); 40 66 text-decoration: none; 41 67 font-weight: 400; 68 + -webkit-tap-highlight-color: transparent; 42 69 } 43 70 44 71 html { ··· 47 74 48 75 body{ 49 76 background: var(--background); 50 - overflow-x: hidden; 51 - display: flex; 52 77 flex-direction: column; 53 78 align-items: center; 54 79 overflow-y: scroll; 80 + overflow-x: hidden; 81 + display: flex; 55 82 } 56 83 57 84 .main { 58 85 display: flex; 59 86 flex-direction: column; 60 - position: absolute; 87 + position: relative; 61 88 align-items: center; 62 89 box-shadow: 0 0.3rem 0.7rem 0 rgba(0, 0, 0, 0.5); 63 90 background: var(--main0); 64 91 border-radius: 6px; 65 - margin-top: 90px; 92 + margin-top: 6rem; 66 93 padding: 2rem 0 0; 67 94 width: 40rem; 68 95 border: 0.2rem solid var(--main0); ··· 71 98 .mainText {font-weight: 500;} 72 99 73 100 .smolMainText { 74 - width: 600px; 101 + width: 37.5rem; 75 102 padding: 4% 2% 3%; 76 103 text-align: center; 77 104 font-size: 1.1rem; ··· 85 112 gap: 1rem; 86 113 } 87 114 88 - .progressWrap { 89 - border: 0.05rem solid var(--border); 115 + .progressWrap {border: 0.05rem solid var(--border);} 116 + .progressWrap, .progressWrapb { 90 117 background: var(--main1); 91 118 position: relative; 92 119 overflow: hidden; ··· 101 128 cursor: pointer; 102 129 user-select: none; 103 130 } 104 - .progressWrap:hover {background: var(--hover);} 131 + .progressWrap:hover, .progressWrapb:hover {background: var(--hover);} 132 + 133 + .progressWrapb::after { /*fix black progressbar*/ 134 + content: ''; 135 + position: absolute; 136 + width: 100%; 137 + height: 100%; 138 + box-sizing: border-box; 139 + border: 0.05rem solid var(--border); 140 + border-radius: 0.5rem; 141 + z-index: 4; 142 + } 143 + 144 + .progressBar { 145 + position: absolute; 146 + height: 100%; 147 + width: 0%; 148 + border-radius: 0.3rem; 149 + transition: width 0.5s ease-in-out; 150 + } 151 + 152 + .progressBarText { 153 + position: absolute; 154 + font-weight: 500; 155 + left: 1rem; 156 + z-index: 3; 157 + } 158 + 159 + .progressBarInfo { 160 + position: absolute; 161 + z-index: 4; 162 + right: 0.5rem; 163 + font-size: 0.9rem; 164 + transition: opacity 0.15s ease-in-out; 165 + } 166 + .selected, .selected:hover {background: var(--selected) !important;} 105 167 /*- PROGRESS BAR*/ 106 168 107 - .searchBar { 169 + 170 + .searchBarContainer {display: flex;} 171 + .searchBar, 172 + .pageSelector { 108 173 padding-left: 1rem; 109 - width: 19.4rem; 174 + /* width: 19.4rem; */ 110 175 height: 2.15rem; 111 176 margin: 1.9rem 0 0.3rem; 112 177 background: var(--main1); 113 178 border: 0.05rem solid var(--border); 114 - border-radius: 0.6rem; 179 + /* border-radius: 0.6rem; */ 180 + line-height: 0; 181 + font-size: 0.9rem; 182 + } 183 + 184 + .searchBar { 185 + width: 14.3rem; 186 + border-radius: 0.6rem 0rem 0rem 0.6rem; 187 + } 188 + 189 + .pageSelector { 190 + width: 3rem; 191 + border-radius: 0rem 0.6rem 0.6rem 0rem; 192 + font-weight: 500; 193 + -moz-appearance: textfield; 194 + appearance: textfield; 115 195 } 116 - .searchBar:focus {outline: none; background: var(--hover); transition: background 0.1s ease-in-out;} 117 - .searchBar::placeholder {color: #8C8C8C; font-weight: 500; font-size: 0.8rem;} 118 - .searchBar:hover {background-color: var(--hover);} 196 + 197 + .pageSelector::-webkit-inner-spin-button, 198 + .pageSelector::-webkit-outer-spin-button { 199 + -webkit-appearance: none; 200 + } 201 + 202 + .searchBar:focus, 203 + .pageSelector:focus {outline: none; background: var(--hover); transition: background 0.1s ease-in-out;} 204 + .searchBar::placeholder {color: #8C8C8C; font-weight: 500;} 205 + .pageSelector::placeholder {color: var(--text);} 206 + .searchBar:hover, .pageSelector:hover {background-color: var(--hover);} 119 207 120 208 .optionButtons { 121 209 display: flex; ··· 138 226 } 139 227 .optionButton:hover {background: var(--hover);} 140 228 229 + .noResultsText { 230 + text-align: center; 231 + padding: 2rem 0 0rem; 232 + font-weight: 500; 233 + } 234 + 235 + .noResultsEmoji { 236 + text-align: center; 237 + font-size: 2.5rem; 238 + padding: 2rem 0 3rem; 239 + } 240 + 141 241 /*+ GAME CARDS*/ 242 + 142 243 .gameWrapper {width: 95%;} 143 244 144 245 .gameContainer { ··· 168 269 z-index: 2; 169 270 } 170 271 .gameContainer:hover {background: var(--hover);} 272 + 273 + .gameImageLink { 274 + border-radius: 0.6rem 0.4rem 0.4rem 0.6rem; 275 + overflow: hidden; 276 + } 277 + 278 + .gameImage { 279 + width: 5.3rem; 280 + height: 5.3rem; 281 + max-width: 5.3rem; 282 + border-radius: 0.5rem 0.4rem 0.4rem 0.5rem; 283 + transition: transform 0.2s ease-in-out; 284 + } 285 + 286 + .gameSeparator { 287 + height: 3.9rem; 288 + margin: 0.7rem 1.25rem; 289 + border-right: 0.375rem solid var(--status-color); 290 + border-radius: 0.25rem; 291 + } 292 + 293 + .gameDetails { 294 + flex: 1; 295 + display: flex; 296 + flex-direction: column; 297 + justify-content: center; 298 + } 299 + 300 + .gameName { 301 + font-weight: 500; 302 + font-size: 1.125rem; 303 + overflow: hidden; 304 + white-space: nowrap; 305 + text-overflow: ellipsis; 306 + transition: width 0.4s ease-out; 307 + width: 22rem; 308 + } 309 + .gameName:hover {width: 29.3rem;} 310 + 311 + .gameCusa { 312 + position: absolute; 313 + right: 0.94rem; 314 + padding: 0.25rem 0.38rem; 315 + font-weight: 500; 316 + font-size: 0.87rem; 317 + } 318 + 319 + .gameStatus { 320 + width: 5.7rem; 321 + height: 1.5rem; 322 + margin-top: 0.94rem; 323 + background-color: var(--status-color); 324 + border-radius: 0.25rem; 325 + text-align: center; 326 + line-height: 1.5rem; 327 + user-select: none; 328 + color: var(--whiteText); 329 + } 171 330 /*- GAME CARDS*/ 172 331 173 - @media screen and (max-width: 750px) { 332 + .totalTimeText { 333 + text-align: center; 334 + font-size: 1rem; 335 + padding: 0 0 0.7rem; 336 + } 337 + 338 + @media screen and (max-width: 700px) { 174 339 .main { 175 - width: 35rem; 176 - border-radius: 1rem; 340 + width: 34rem; 341 + border-radius: 0.8rem; 177 342 } 178 343 .gameName { 179 - width: 18.5rem; 344 + width: 17.5rem; 180 345 } 181 346 .gameName:hover { 182 - width: 24rem; 347 + width: 23rem; 348 + } 349 + 350 + .smolMainText { 351 + width: 30rem; 352 + } 353 + .footerText { 354 + font-size: 0.89rem !important; 183 355 } 356 + } 357 + @media screen and (max-width: 550px) { 358 + .optionButton:hover {background: var(--main1);} 359 + .progressWrap:hover, .progressWrapb:hover {background: var(--main1);} 360 + .selected .progressBarInfo{opacity: 1;} 361 + .menu-icon:hover, .gh-logo:hover, .dc-logo:hover, .lightModeButton:hover, .logo:hover {background-color: #00000000 !important}; 362 + .gameDetails:hover .gameName {width: 23rem !important;} 363 + .footerSeparator {display: flex !important;} 364 + .footerContent:hover {background-color: #00000000 !important;} 365 + } 366 + 367 + @media screen and (min-width: 550px) { 368 + .progressWrap:hover .progressBarInfo, .progressWrapb:hover .progressBarInfo{opacity: 1;} 184 369 }
public_html/images/NA.avif

This is a binary file and will not be displayed.

public_html/images/NA.jpg

This is a binary file and will not be displayed.

+2 -2
public_html/scripts/issue_fetcher.php
··· 173 173 $httpsIconUrl = str_replace('http://', 'https://', $iconUrl); 174 174 $imagick = new Imagick(); 175 175 176 - $avifIconURL = "/home/{$serverUsername}/domains/fpps4.net/public_html/beta/images/CUSA/{$cusaCode}.avif"; 176 + $avifIconURL = "/home/{$serverUsername}/domains/fpps4.net/public_html/images/CUSA/{$cusaCode}.avif"; 177 177 $result = null; 178 178 if ($imagick->readImage($httpsIconUrl)) { 179 179 $imageFormat = $imagick->getImageFormat(); ··· 226 226 foreach ($issues_array as $issue) { 227 227 $title = $issue['title']; 228 228 $cusaCode = extract_cusaCode($title); 229 - $avifIconURL = "/home/{$serverUsername}/domains/fpps4.net/public_html/beta/images/CUSA/{$cusaCode}.avif"; 229 + $avifIconURL = "/home/{$serverUsername}/domains/fpps4.net/public_html/images/CUSA/{$cusaCode}.avif"; 230 230 231 231 // Check if cusa code is empty 232 232 if ($cusaCode) {
+182
public_html/scripts/search.php
··· 1 + <?php 2 + // I try to make my code as easy to understand as possible. 3 + // When there is a comment "//" that useally means that that part of the code starts there until the next comment. 4 + 5 + /// timer 6 + $startTime = microtime(true); 7 + 8 + 9 + /// connecting to database 10 + $serverUsername = getenv('USERNAME'); 11 + require_once "/home/{$serverUsername}/domains/fpps4.net/config/config.php"; // import config file 12 + 13 + $host = DATABASE_HOST; 14 + $database = DATABASE_NAME; 15 + $username = DATABASE_USERNAME; 16 + $password = DATABASE_PASSWORD; 17 + 18 + try { 19 + $conn = new PDO("mysql:host=$host;dbname=$database", $username, $password); 20 + } catch (PDOException $e) { 21 + echo "The server got itself into trouble, please try to refresh.<br>"; 22 + } 23 + 24 + 25 + /// processing user input 26 + $searchQuery = isset($_GET['q']) ? $_GET['q'] : ''; 27 + $tags = isset($_GET['tag']) ? $_GET['tag'] : ''; 28 + $page = isset($_GET['page']) ? $_GET['page'] : 1; 29 + $oldest = isset($_GET['oldest']) ? $_GET['oldest'] : false; 30 + $giveStats = isset($_GET['stats']) ? true : false; 31 + 32 + $searchQuery = filter_var($searchQuery, FILTER_SANITIZE_STRING); 33 + $tags = filter_var($tags, FILTER_SANITIZE_STRING); 34 + $page = filter_var($page, FILTER_VALIDATE_INT); 35 + $oldest = filter_var($oldest, FILTER_VALIDATE_BOOLEAN); 36 + $giveStats = filter_var($giveStats, FILTER_VALIDATE_BOOLEAN); 37 + 38 + 39 + /// defining stuff lmao 40 + $maxResults = (!empty($searchQuery)) ? 10 : 20; 41 + $pageNumber = ($page - 1) * $maxResults; 42 + $sqlQuery = "SELECT * FROM issues"; 43 + $params = array(); 44 + $conditions = array(); 45 + 46 + 47 + /// if there is an search query in the request 48 + if (!empty($searchQuery)) { 49 + $searchQuery = strtolower($searchQuery); 50 + $conditions[] = strpos($searchQuery, 'cusa') ? "cusaCode LIKE :searchQuery" : "title LIKE :searchQuery"; 51 + $params[':searchQuery'] = $searchQuery . '%'; 52 + } 53 + 54 + 55 + /// if there are tags in the request 56 + if (!empty($tags)) { 57 + $tagFilters = explode(',', $tags); 58 + $tagConditions = []; 59 + $tagParams = []; 60 + 61 + foreach ($tagFilters as $index => $tag) { 62 + $tagParam = ":tagFilter{$index}"; 63 + $tagConditions[] = "(CONCAT(',', tags, ',') LIKE CONCAT('%,', {$tagParam}, ',%'))"; 64 + $tagParams[$tagParam] = "{$tag}"; 65 + } 66 + 67 + if (!empty($tagConditions)) { 68 + $conditions[] = "(" . implode(" OR ", $tagConditions) . ")"; 69 + $params = array_merge($params, $tagParams); 70 + } 71 + } 72 + 73 + 74 + /// im too lazy to write a comment 75 + if (!empty($conditions)) { 76 + $sqlQuery .= " WHERE " . implode(" AND ", $conditions); 77 + } 78 + 79 + 80 + /// gets the total amount of issues based on the search query 81 + $sqlQueryForTotal = "SELECT COUNT(*) AS total FROM ($sqlQuery) AS totalIssues"; 82 + $stmtTotal = $conn->prepare($sqlQueryForTotal); 83 + 84 + foreach ($params as $param => &$value) { 85 + $stmtTotal->bindParam($param, $value, PDO::PARAM_STR); 86 + } 87 + 88 + $stmtTotal->execute(); 89 + $totalIssuesAmount = $stmtTotal->fetch(PDO::FETCH_ASSOC)['total']; 90 + $totalPages = ceil($totalIssuesAmount / $maxResults); 91 + echo "<div id='totalPages' data='{$totalPages}'></div>"; 92 + if ($totalIssuesAmount < 1) { 93 + echo "<p class='noResultsText'>No results found based on your query</p>"; 94 + echo "<p class='noResultsEmoji'>¯\_(ツ)_/¯</p>"; 95 + } 96 + 97 + /// forming and executing sql query 98 + $sqlQuery .= " ORDER BY id " . ($oldest ? "ASC" : "DESC"); 99 + $sqlQuery .= " LIMIT :offset, :limit"; 100 + $params[':offset'] = $pageNumber; 101 + $params[':limit'] = $maxResults; 102 + 103 + $stmt = $conn->prepare($sqlQuery); 104 + 105 + foreach ($params as $param => &$value) { 106 + if (is_int($value)) { 107 + $stmt->bindParam($param, $value, PDO::PARAM_INT); 108 + } else { 109 + $stmt->bindParam($param, $value, PDO::PARAM_STR); 110 + } 111 + } 112 + 113 + $stmt->execute(); 114 + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); 115 + 116 + 117 + /// Outputing results 118 + foreach ($result as $row) { 119 + $id = $row['id']; 120 + $cusaCode = $row['cusaCode']; 121 + $clean_title = $row['title']; 122 + $tags = $row['tags']; 123 + $updatedAt = $row['updatedDate']; 124 + 125 + $dataCusa = ''; 126 + $avifIconURL = "../images/CUSA/{$cusaCode}.avif"; 127 + if (!file_exists($avifIconURL)) { 128 + $dataCusa = null; // Skip image 129 + } else { 130 + $dataCusa = $cusaCode; 131 + } 132 + 133 + echo "<div class='gameContainer'>"; 134 + echo "<a data-id='$id' class='gameImageLink'>"; 135 + echo "<img class='gameImage' data-cusa='$dataCusa' alt='$clean_title'>"; 136 + echo "</a>"; 137 + echo "<div class='gameSeparator'></div>"; 138 + echo "<div class='gameDetails'>"; 139 + echo "<p class='gameName'>$clean_title</p>"; 140 + echo "<p data='$updatedAt' class='gameCusa'>$cusaCode</p>"; 141 + echo "<p class='gameStatus'>$tags</p>"; 142 + echo "</div>"; 143 + echo "</div>"; 144 + } 145 + 146 + 147 + /// stats on the compatibility list 148 + if ($giveStats === true) { 149 + $availableTags = ['N/A', 'Nothing', 'Boots', 'Menus', 'Ingame', 'Playable']; 150 + $tagPercentages = []; 151 + 152 + $stmt = $conn->prepare("SELECT COUNT(*) AS total FROM issues"); 153 + $stmt->execute(); 154 + $total = $stmt->fetch(PDO::FETCH_ASSOC)['total']; 155 + 156 + foreach ($availableTags as $tag) { 157 + $stmt = $conn->prepare("SELECT COUNT(*) AS count FROM issues WHERE tags = :tag"); 158 + $stmt->bindParam(':tag', $tag, PDO::PARAM_STR); 159 + $stmt->execute(); 160 + 161 + $count = $stmt->fetch(PDO::FETCH_ASSOC)['count']; 162 + $percentage = ($total > 0) ? ($count / $total) * 100 : 0; 163 + $tagPercentages[$tag] = $percentage; 164 + $tagCount[$tag] = $count; 165 + } 166 + 167 + $amount = (100 - array_sum($tagPercentages)) / count($tagPercentages); 168 + 169 + foreach ($tagPercentages as $tag => $percentage) { 170 + $percentage += $amount; 171 + $percentage = round($percentage, 2); 172 + echo "<div id='$tag' data='$percentage+$tagCount[$tag]'></div>"; 173 + } 174 + } 175 + 176 + 177 + /// end 178 + $conn = null; //exit connection 179 + $endTime = microtime(true); 180 + $executionTime = round(($endTime - $startTime) * 1000, 2); // Convert to milliseconds 181 + echo "<h4 class= 'totalTimeText'>$totalIssuesAmount results in {$executionTime}ms </h4>"; 182 + ?>