xemu-website/templates/template_index.html
2023-01-17 23:55:59 -07:00

535 lines
16 KiB
HTML

{% set title_status_colors = {'Unknown' : '#2A2A2A', 'Broken' : '#D7263D', 'Intro' : '#F86624', 'Starts' : '#FF9800', 'Playable' : '#42991b', 'Perfect' : '#1b8799'} %}
{% extends "template_base.html" %}
{% block append_head %}
<style type="text/css">
/* https://www.gungorbudak.com/blog/2018/12/12/bootstrap-4-search-box-with-search-icon/ */
.has-search .form-control {
padding-left: 2.375rem;
}
.has-search .form-control-feedback {
position: absolute;
z-index: 2;
display: block;
width: 2.375rem;
height: 2.375rem;
line-height: 2.375rem;
text-align: center;
pointer-events: none;
color: #aaa;
}
{% for key in title_status_colors %}
.text-color-{{ key }} {
color: {{ title_status_colors[key] }};
}
.fill-color-{{ key }} {
background-color: {{ title_status_colors[key] }};
color: #fff;
font-weight: bold;
}
{% endfor %}
.title-card a {
color: #fff;
font-weight: bolder;
text-decoration: none;
}
.title-card-container {
width: 8rem;
background-color: #373737;
}
.title-card-image-container {
background-image: url('cover_front_default.png');
background-size: cover;
width: 100%;
min-height: 11rem;
}
/* Account for the fixed header (which otherwise would occlude anchor targets) */
/* Via https://stackoverflow.com/a/28824157 */
:target::before {
content: "";
display: block;
height: 60px; /* fixed header height*/
margin: -60px 0 0; /* negative fixed header height */
}
.feature-card {
background-color: #181818;
border: 1px #0e0e0e solid;
border-radius: 5px;
min-height: 100%;
padding: 1rem;
padding-left: 3.5rem;
}
.feature-card p {
margin: 0;
}
.feature-icon {
width: 3.5rem;
margin: 0;
margin-left: -3.5rem;
text-align: center;
}
.button-icon {
display: inline-block;
margin-right: 0.75em;
}
#logo-canvas {
display: block;
visibility: hidden;
width: 256px;
height: 256px;
margin-left: -38px;
margin-bottom: -177px;
margin-top: -80px;
padding: 0;
border: none;
}
#bg-scene {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
}
.jumbotron {
position: relative;
}
@media only screen and (min-width: 768px)
{
.navbar-transparent
{
background: transparent !important;
transition: background-color 200ms linear;
}
}
h2 {
margin-top: 1em;
}
.screenshot-window {
box-shadow: 0px 5px 25px rgba(0, 0, 0, 0.6);
border-radius: 10px;
max-width: 642px;
margin: auto;
}
.screenshot-titlebar {
width: 100%;
}
#screenshot-carousel {
border: 1px rgba(0, 0, 0, 0.6) solid;
border-top: 0;
width: 100%;
}
</style>
{% endblock %}
{% block jumbotron %}
<div class="jumbotron d-flex align-items-center min-vh-100">
<div id="bg-scene"></div>
<div class="container">
<div class="row mt-5">
<div class="col-md-6 mt-4 pb-5 pb-md-0 text-center">
<img src="xbox_logo.png" id="xbox-logo" class="img-fluid" width=450 />
</div>
<div class="col-md-6">
<h1 class="display-3">
<canvas id="logo-canvas" class="gl-logo"></canvas>
<img id="logo-fallback" alt="xemu logo" src="/logo-green-jumbotron.svg" style="height: 1.03em;">
</h1>
<h4 class="card-subtitle mb-2">Original Xbox Emulator</h4>
<p>
A free and open-source application that emulates the original Microsoft
Xbox game console, enabling people to play their original Xbox games on
Windows, macOS, and Linux systems.
</p>
<div class="container pl-2 p-0">
<div class="row">
<div class="column text-center text-md-left p-2">
<a class="btn btn-green btn-lg" role="button" id="download-gen" href="/docs/download">
<i class="fa fa-laptop button-icon" aria-hidden="true"></i>View Download Options
</a>
<a class="btn btn-green btn-lg" role="button" id="download-win" style="display: none" href="https://github.com/xemu-project/xemu/releases/latest/download/xemu-win-release.zip">
<i class="fab fa-windows button-icon" aria-hidden="true"></i>Download for Windows
</a>
<a class="btn btn-green btn-lg" role="button" id="download-mac" style="display: none" href="https://github.com/xemu-project/xemu/releases/latest/download/xemu-macos-universal-release.zip">
<i class="fab fa-apple button-icon" aria-hidden="true"></i>Download for macOS
</a>
<a class="btn btn-green btn-lg" role="button" id="download-linux" style="display: none" href="/docs/download/#download-for-linux">
<i class="fab fa-linux button-icon" aria-hidden="true"></i>Download for Linux
</a>
</div>
<div class="column p-2">
<strong>Latest release:</strong> v{{xemu_build_version}} ({{xemu_build_date.strftime('%b %-d, %Y')}})
<br />
<a href="/docs/download" id="download-options" style="display: none">Alternative download options</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<h2 class="mb-4 text-center">Features</h2>
<div class="row">
<div class="col-md-6 p-1">
<div class="feature-card">
<h4><i class="fab fa-github feature-icon"></i>Open Source</h4>
<p>The source code for xemu is publicly available. You are invited to help improve the project!</p>
</div>
</div>
<div class="col-md-6 p-1">
<div class="feature-card">
<h4><i class="fas fa-users feature-icon"></i>Cross Platform</h4>
<p>xemu runs natively on Windows, macOS, and Linux platforms. Pre-compiled binaries are available for Windows, macOS, and Linux.</p>
</div>
</div>
<div class="col-md-6 p-1">
<div class="feature-card">
<h4><i class="fas fa-people-arrows feature-icon"></i>Networking</h4>
<p>Networking is supported out of the box. Connect to other instances of xemu and even real Xboxes, locally or over the Internet.</p>
</div>
</div>
<div class="col-md-6 p-1">
<div class="feature-card">
<h4><i class="fas fa-gamepad feature-icon"></i>Gamepad Support</h4>
<p>Built on SDL2, xemu supports virtually all gamepads. Connect up to 4 controllers at any time, just like a real Xbox.</p>
</div>
</div>
<div class="col-md-6 p-1">
<div class="feature-card">
<h4><i class="fas fa-history feature-icon"></i>Snapshots (Save States)</h4>
<p>No need to wait for game checkpoints. xemu supports saving the current machine state and loading it back up at any time.</p>
</div>
</div>
<div class="col-md-6 p-1">
<div class="feature-card">
<h4><i class="fas fa-expand-alt feature-icon"></i>Render Scaling</h4>
<p>Breathe new life into your original Xbox games by easily scaling up the resolution that games render at.</p>
</div>
</div>
</div>
<h2 id="screenshots" class="mb-4 text-center">Screenshots</h2>
<div class="screenshot-window">
<img src="linux_title_bar_dark_2x.png" class="screenshot-titlebar" />
<div id="screenshot-carousel" class="carousel slide" data-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="/screenshots/0.png" class="img-fluid lazy" />
</div>
<div class="carousel-item">
<img src="/screenshots/1.png" class="img-fluid lazy" />
</div>
<div class="carousel-item">
<img src="/screenshots/2.png" class="img-fluid lazy" />
</div>
<div class="carousel-item">
<img src="/screenshots/3.png" class="img-fluid lazy" />
</div>
<div class="carousel-item">
<img src="/screenshots/4.png" class="img-fluid lazy" />
</div>
<div class="carousel-item">
<img src="/screenshots/5.png" class="img-fluid lazy" />
</div>
</div>
<a class="carousel-control-prev" href="#screenshot-carousel" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#screenshot-carousel" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
<h2 id="compatibility" class="mb-4 text-center">Compatibility</h2>
<p>Title compatibility status is provided by volunteer reporters in the community, as the reporter experienced the title in the current version of xemu on their computer at time of reporting. As the project evolves, reports may need to be updated. You are invited to help improve the project by submitting an updated compatibility report. Join the Discord server to learn how to contribute!</p>
<div id="title_stats" style="height: 120px"></div>
<div class="form-group has-search">
<span class="fa fa-search form-control-feedback"></span>
<input class="form-control" type="text" placeholder="Search" id="filter">
</div>
<div class="container mt-2">
<div class="row" id="results">
{% for title in titles %}
<div class="col mb-4 title-card" data-title-name="{{ title.title_name }}" data-title-status="{{ title.status }}">
<a href="{{ title.title_url }}">
<div class="mx-auto title-card-container">
<div class="title-card-image-container">
{% if not title.have_cover %}
<div class="card-body text-center">{{ title.title_name }}</div>
{% else %}
<img data-src="{{ title.cover_thumbnail_url }}" class="img-fluid lazy" title="{{ title.title_name }}">
{% endif %}
</div>
<div class="fill-color-{{ title.status }} card-body text-center py-1 my-0">
<small><strong>{{ title.status }}</strong></small>
</div>
</div>
</a>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block append_foot %}
<script type="text/javascript">
// Show platform-specific download button
var platform = undefined;
if (navigator.userAgent.indexOf("Win") != -1) platform = "win";
else if (navigator.userAgent.indexOf("Mac") != -1) {
if (navigator.userAgent.indexOf("iPhone") == -1) {
platform = "mac";
}
}
else if (navigator.userAgent.indexOf("Linux") != -1) platform = "linux";
if (platform != undefined) {
$('#download-gen').hide();
$('#download-' + platform).show();
$('#download-options').show();
}
</script>
<script type="text/javascript" src="three.min.js"></script>
<script type="text/javascript">
var canvas = document.querySelector('#bg-scene');
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 74.5, canvas.offsetWidth/canvas.offsetHeight, 0.1, 1000 );
scene.fog = new THREE.Fog('black', 0.2, 2.45);
scene.background = new THREE.Color('black');
var renderer = new THREE.WebGLRenderer();
renderer.setSize( canvas.offsetWidth, canvas.offsetHeight );
renderer.setPixelRatio( window.devicePixelRatio );
canvas.appendChild( renderer.domElement );
const texture = new THREE.TextureLoader().load("mesh_pattern.svg");
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 50, -25 );
const geometry = new THREE.SphereGeometry( 1, 50, 25 );
var material = new THREE.MeshBasicMaterial( { map: texture, side: THREE.BackSide } );
var sphere = new THREE.Mesh( geometry, material );
scene.add( sphere );
camera.position.z = 0.65;
camera.position.x = 0.12;
var clock = new THREE.Clock();
var render = function () {
requestAnimationFrame( render );
var t = clock.getElapsedTime();
sphere.rotation.y = (Math.PI/10) * Math.sin(t * Math.PI / 50);
renderer.render(scene, camera);
};
render();
window.addEventListener('resize', function(){
camera.aspect = canvas.offsetWidth/canvas.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(canvas.offsetWidth, canvas.offsetHeight);
}, false);
</script>
<script type="text/javascript" src="gl_logo.js"></script>
<script type="text/javascript">
function updateNavbarTransparency() {
var nav = $(".navbar");
var t = $(document).scrollTop() < $("#xbox-logo").offset().top - nav.height();
nav.toggleClass('navbar-transparent', t);
}
$(document).scroll(updateNavbarTransparency);
$(function () {
$(".navbar-toggle").click(updateNavbarTransparency);
});
updateNavbarTransparency();
</script>
<script type="text/javascript" src="/apexcharts.js"></script>
<script type="text/javascript" src="/jquery.lazy-1.7.10.min.js"></script>
<script type="text/javascript" src="/jquery.lazy.plugins-1.7.10.min.js"></script>
<script type="text/javascript">
// Lazy-load images
var lazyInstance = $('img.lazy').Lazy({
scrollDirection: 'vertical',
visibleOnly: true,
chainable: false,
effect: "fadeIn",
effectTime: 333,
onError: function(element) {
console.log('error loading ' + element.data('src'));
}
});
// https://stackoverflow.com/a/1909508
function delay(fn, ms) {
let timer = 0
return function(...args) {
clearTimeout(timer)
timer = setTimeout(fn.bind(this, ...args), ms || 0)
}
}
var filter_title_str = null;
var filter_status_str = null;
var hidden_status_list = [];
// Simple filter via: https://stackoverflow.com/a/44736154
$("#filter").keyup(delay(function() {
filter_title_str = $(this).val();
filter_titles();
}, 250));
function toggle_single_status_filter(status)
{
filter_status_str = filter_status_str === status ? null : status;
filter_titles();
}
function toggle_hidden_status_filter(status)
{
if (hidden_status_list.includes(status)) {
// User has toggled a status that's already hidden, remove status from hidden filter list
hidden_status_list = hidden_status_list.filter(function(hidden_status) {
return hidden_status !== status;
});
} else {
hidden_status_list.push(status);
}
filter_titles();
}
function filter_titles()
{
$('.title-card').each(function() {
// Only show titles that match this critera:
// Status of title matches the filtered status if it's been set
// Status of title hasn't been excluded in the hidden status list
// Title name matches the user input
if ((filter_status_str != null && $(this).attr('data-title-status') !== filter_status_str) ||
(hidden_status_list.includes($(this).attr('data-title-status'))) ||
(filter_title_str != null && $(this).attr('data-title-name').search(new RegExp(filter_title_str, "i")) < 0 )) {
$(this).hide();
} else {
$(this).show();
}
});
lazyInstance.update();
}
var options = {
series: [
{% for status in game_status_counts: %}
{
name: '{{ status }}',
data: [
{{ game_status_counts[status] }},
]
},
{% endfor %}
],
chart: {
foreColor: '#a7a7a7', // text color
background: '#444',
type: 'bar',
height: 100,
stacked: true,
stackType: '100%',
toolbar: {
show: false
},
events : {
legendClick : function statustoggleclick(chartContext, seriesIndex, config) {
let status = config.globals.seriesNames[seriesIndex];
toggle_hidden_status_filter(status);
},
dataPointSelection : function statusclick(event) {
let status = event.currentTarget.parentNode.getAttribute("seriesName");
toggle_single_status_filter(status);
}
}
},
plotOptions: {
bar: {
horizontal: true,
},
},
stroke: {
show: false,
width: 0,
colors: ['#999']
},
grid: {
show: false
},
xaxis: {
categories: [
'Titles'
],
labels: {
show: false
},
axisBorder: {
show: false,
color: '#ffff00'
},
axisTicks: {
show: false,
}
},
yaxis: {
labels: {
show: true,
maxWidth: 0
},
axisBorder: {
show: false,
color: '#ff00ff'
},
axisTicks: {
show: false,
}
},
tooltip: {
y: {
formatter: function (val) {
return val
}
}
},
colors: [
{% for key in title_status_colors %}
'{{ title_status_colors[key] }}',
{% endfor %}
],
legend: {
position: 'top',
horizontalAlign: 'center',
offsetY: 20
}
};
var chart = new ApexCharts(document.querySelector("#title_stats"), options);
chart.render();
</script>
{% endblock %}