xemu-website/templates/template_index.html
2024-09-02 15:07:32 -07:00

722 lines
22 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">
/* Account for the fixed header (which otherwise would occlude anchor targets) */
/* Via https://stackoverflow.com/a/28824157 */
:target::before {
content: "";
display: block;
height: 5rem; /* fixed header height*/
margin: -5rem 0 0; /* negative fixed header height */
}
#xbox-scene {
position: relative;
padding-bottom: 100%;
border: none;
}
#xbox-scene canvas {
position: absolute;
max-width: 100%;
height: auto;
}
#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;
display:flex;
flex-direction:column;
}
.disclaimer-container {
align-self: bottom;
z-index: 10;
}
.disclaimer {
color: #bbb;
background-color: rgba(0.2,0.2,0.2,0.6);
padding: 0.5em;
font-size: 0.9em;
}
.project-info {
margin: auto;
}
@media only screen and (min-width: 768px)
{
.navbar-transparent
{
background: transparent !important;
transition: background-color 200ms linear;
}
}
.button-icon {
display: inline-block;
margin-right: 0.75em;
}
.screenshot-window {
box-shadow: 0px 5px 25px rgba(0, 0, 0, 0.6);
border-radius: 10px;
max-width: 642px;
margin: 2em auto;
}
.screenshot-titlebar {
width: 100%;
display: block;
}
#screenshot-carousel {
border: 1px rgba(0, 0, 0, 0.6) solid;
border-top: 0;
width: 100%;
}
.feature-card {
min-height: 100%;
padding: 1rem 1rem 1rem 4.5rem;
}
.feature-card p {
margin: 0;
}
.feature-icon {
width: 4.5rem;
margin: 0 0 0 -4.5rem;
text-align: center;
color: #888;
}
{% 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: 10rem;
background-color: #373737;
border-radius: 0.25rem;
overflow: hidden;
}
.title-card-image-container {
background-image: url('cover_front_default.png');
background-size: cover;
width: 100%;
height: 14.18rem;
}
.title-card-image-container img {
height: 100%;
width: 100%;
}
/* 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;
}
</style>
{% endblock %}
{% block jumbotron %}
<div class="jumbotron d-flex align-items-center min-vh-100">
<div id="bg-scene"></div>
<div class="container project-info py-5">
<div class="row mt-5">
<div class="col-md-6 mt-4 pb-5 pb-md-0 text-center">
<div id="xbox-scene"></div>
</div>
<div class="col-md-6 my-auto">
<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 class="lead">
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="d-flex flex-wrap m" style="grid-gap: 1em;">
<div>
<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="text-nowrap">
Version {{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 class="disclaimer-container">
<div class="container text-center mx-auto disclaimer">
<b>Disclaimer:</b> This project is an independent open source emulation effort and is not endorsed by, directly affiliated with, maintained, authorized, or sponsored by Microsoft Corporation. All product and company names are the registered trademarks of their original owners. The use of any trade name or trademark is for identification and reference purposes only and does not imply any association with the trademark holder of their product brand.
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="body-alt-section">
<div class="container">
<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">
{% for i in range(7) %}
<div class="carousel-item {% if i<1 %}active{% endif %}">
<img src="/screenshots/{{ i }}.jpg" class="img-fluid lazy" />
</div>
{% endfor %}
</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>
</div>
</div>
<div class="container">
<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 available on <a href="https://github.com/xemu-project/xemu">GitHub</a>. You are invited to help improve the project! Learn more <a href="/docs/dev/">here</a>.</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. Binaries are available for all platforms, or you can build from source if desired. Learn more <a href="/docs/download/">here</a>.</p>
</div>
</div>
<div class="col-md-6 p-1">
<div class="feature-card">
<h4><i class="fas fa-microchip feature-icon"></i>Low Level Emulation</h4>
<p>xemu emulates the hardware of the original Xbox, providing superior compatibility with kernels, titles, and homebrew applications.</p>
</div>
</div>
<div class="col-md-6 p-1">
<div class="feature-card">
<h4><i class="fas fa-gamepad feature-icon"></i>Controller Support</h4>
<p>Built on <a href="https://www.libsdl.org/">SDL2</a>, xemu supports virtually all controllers. Connect up to 4 controllers at any time, just like a real Xbox. Learn more <a href="/docs/input/">here</a>.</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. Learn more <a href="/docs/snapshots/">here</a>.</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 increasing the resolution that games render at, on the fly. Scale up from 480p to 1080p at the click of a button.</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>Connect to other instances of xemu and real Xboxes, locally or over the Internet. Supports tunneling services and Xbox Live recreation projects. Learn more <a href="/docs/networking/">here</a>.</p>
</div>
</div>
<div class="col-md-6 p-1">
<div class="feature-card">
<h4><i class="fab fa-discord feature-icon"></i>Community</h4>
<p>xemu has a thriving online community of original Xbox fans. Set up multiplayer matches, get help running xemu, and more by joining our community on <a href="https://discord.gg/ayyjsuM">Discord</a>!</p>
</div>
</div>
</div>
</div>
<div class="body-alt-section">
<div class="container">
<h2 id="compatibility" class="mb-4 text-center">Compatibility</h2>
<div class="col-lg-10 mx-auto mt-2">
<p>
<b>Note:</b> 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="row" id="results">
{% for title in titles %}
<div class="col px-1 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>
</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="importmap">
{
"imports": {
"three": "./three/three.module.js",
"three/addons/": "./three/"
}
}
</script>
<script type="text/javascript" src="gl_logo.js"></script>
<script type="module">
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { ArcballControls } from 'three/addons/controls/ArcballControls.js';
const gltf_loader = new GLTFLoader();
const draco_loader = new DRACOLoader();
draco_loader.setDecoderPath( 'three/loaders/' );
draco_loader.setDecoderConfig( { type: 'js' } );
gltf_loader.setDRACOLoader( draco_loader );
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({ antialias: true });
renderer.setSize( canvas.offsetWidth, canvas.offsetHeight );
renderer.setPixelRatio( window.devicePixelRatio );
canvas.appendChild( renderer.domElement );
const texture = new THREE.TextureLoader().load("mesh_pattern.svg");
texture.colorSpace = THREE.SRGBColorSpace;
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 sphere_render = function () {
var t = clock.getElapsedTime();
var s = Math.sin(t * Math.PI / 50);
sphere.rotation.y = (Math.PI/10) * s;
renderer.render(scene, camera);
};
window.addEventListener('resize', function(){
camera.aspect = canvas.offsetWidth/canvas.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(canvas.offsetWidth, canvas.offsetHeight);
}, false);
var xbox_canvas = document.querySelector('#xbox-scene');
var xbox_scene = new THREE.Scene();
// xbox_scene.background = new THREE.Color('black');
var xbox_camera = new THREE.PerspectiveCamera( 42.5, xbox_canvas.offsetWidth/xbox_canvas.offsetHeight, 0.1, 1000 );
var xbox_renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
xbox_renderer.setSize( xbox_canvas.offsetWidth, xbox_canvas.offsetHeight );
xbox_renderer.setPixelRatio( window.devicePixelRatio );
xbox_canvas.appendChild( xbox_renderer.domElement );
var xbox = null;
var xbox_scale = 1;
var xbox_sphere_radius = 0.23 * xbox_scale;
var xbox_base_color = new THREE.Color(0xA0B020);
var xbox_bright_color = new THREE.Color(0xD4E830);
var xbox_material = new THREE.MeshStandardMaterial( {
color: xbox_base_color,
transparent: true,
opacity: 0.25,
depthTest: false,
roughness: 0,
});
gltf_loader.load('xbox.glb', function ( gltf ) {
xbox = gltf.scene;
xbox.renderOrder = 10;
xbox.scale.set(xbox_scale, xbox_scale, xbox_scale);
xbox.position.set(0,0.05,0);
xbox.rotation.x = 0.14 * Math.PI;
xbox.rotation.z = -0.02 * Math.PI;
var order = 0;
xbox.traverse((o) => {
if (o.isMesh) {
o.material = xbox_material;
o.renderOrder = order;
order += 1;
}
});
xbox_scene.add(xbox);
});
const xbox_orb_geometry = new THREE.SphereGeometry( xbox_sphere_radius, 50, 25 );
var xbox_orb_material = new THREE.ShaderMaterial( {
// https://jsfiddle.net/8n36c47p/4/
vertexShader: `
varying vec3 vPositionW;
varying vec3 vNormalW;
void main() {
vPositionW = vec3( vec4( position, 1.0 ) * modelMatrix);
vNormalW = normalize( vec3( vec4( normal, 0.0 ) * modelMatrix ) );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: `
varying vec3 vPositionW;
varying vec3 vNormalW;
void main() {
vec3 color = vec3(0.5, 1.0, 0.3);
vec3 base = vec3(0.1, 0.6, 0.05);
vec3 viewDirectionW = normalize(cameraPosition - vPositionW);
float f = clamp(pow(1.0 - dot(viewDirectionW, vNormalW), 1.3), 0.0, 1.);
gl_FragColor = vec4(mix(base, color, f), f * 0.5);
}`,
transparent: true,
});
var xbox_orb = new THREE.Mesh( xbox_orb_geometry, xbox_orb_material );
xbox_orb.renderOrder = 0;
xbox_scene.add( xbox_orb );
var light_color = new THREE.Color(1,1,1);
const ambient_light = new THREE.AmbientLight(light_color, 2);
xbox_scene.add(ambient_light);
var point_light = new THREE.PointLight(light_color, 5);
point_light.position.set(-1, -0.5, 0.25);
xbox_scene.add(point_light);
xbox_camera.position.z = 0.65;
var xbox_render = function () {
var t = clock.getElapsedTime();
var s = Math.sin(t * Math.PI / 50);
if (xbox != null) {
xbox.rotation.y = Math.PI * (0.1 + s / 10);
var new_color = new THREE.Color(xbox_base_color);
new_color.lerp(xbox_bright_color, Math.abs(Math.sin(t * Math.PI / 4 )));
xbox_material.color.set(new_color);
}
xbox_renderer.render(xbox_scene, xbox_camera);
};
var render = function () {
requestAnimationFrame( render );
sphere_render();
xbox_render();
logo_render(clock.getElapsedTime());
};
render();
const controls = new ArcballControls( xbox_camera, xbox_renderer.domElement, xbox_scene );
controls.addEventListener( 'change', function () {
xbox_renderer.render( xbox_scene, xbox_camera );
} );
controls.update();
window.addEventListener('resize', function(){
xbox_camera.aspect = xbox_canvas.offsetWidth/xbox_canvas.offsetHeight;
xbox_camera.updateProjectionMatrix();
xbox_renderer.setSize(xbox_canvas.offsetWidth, xbox_canvas.offsetHeight);
}, false);
</script>
<script type="text/javascript">
function updateNavbarTransparency() {
var nav = $(".navbar");
var t = $(document).scrollTop() < $("#xbox-scene").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 %}