templates for self-hosting game jams (or any other kind of jam tbh)

Add game page stuff

Changed files
+182 -106
plain-html
+1 -1
README.md
···
replace things in the template files that are surrounded by double exclamation points (!!). there are some variables to set at the top of the `js/script.mjs` file, and some style variables to set at the top of `css/style.css`. if you want to add more css, there's a `css/custom.css` file you can tack new stuff onto.
-
also, you will have to fiddle with JSON for game data. unless you want to just code all them manually for some reason, in which case i salute you. i recommend using [json console](https://jsonconsole.com/json-editor) if you're not down with the brackets since the table and graph views are really useful for like. checking your work
## plain php
···
replace things in the template files that are surrounded by double exclamation points (!!). there are some variables to set at the top of the `js/script.mjs` file, and some style variables to set at the top of `css/style.css`. if you want to add more css, there's a `css/custom.css` file you can tack new stuff onto.
+
also, you will have to fiddle with JSON for game data. unless you want to just code all them manually for some reason, in which case i salute you. i recommend using [jsonswiss](https://jsonswiss.com/json-table-editor) if you're not down with the brackets since it lets you edit in table view
## plain php
+79 -4
plain-html/css/style.css
···
.icon {
width: 16px;
height: 16px;
display: inline-block;
margin-right: 5px;
}
#page {
···
.game .screenshots .gallery a,
.game .screenshots .gallery button {
position: relative;
-
padding-top: 50%;
width: 100%;
}
···
padding: 20px;
}
.game .comments {
border-top: 1px var(--accent) solid;
}
···
[popover] {
position: fixed;
z-index: 99;
-
top: 50%;
-
left: 50%;
max-width: 90%;
max-height: 90%;
-
transform: translate(-50%, -50%);
}
[popover]:-internal-popover-in-top-layer::backdrop {
background-color: rgba(0,0,0,.5);
}
···
.icon {
width: 16px;
height: 16px;
+
text-indent: -999px;
+
overflow: hidden;
display: inline-block;
+
vertical-align: middle;
margin-right: 5px;
+
}
+
+
.btn {
+
padding: 5px 10px;
+
font-size: 1em;
+
font-weight: bold;
+
border-radius: 5px;
+
background-color: var(--accent);
+
color: var(--background);
+
text-decoration: none;
+
white-space: pre;
+
}
+
+
.btn:hover {
+
background-color: color-mix(in srgb-linear, var(--accent), #000000 50%);
+
color: var(--background);
}
#page {
···
.game .screenshots .gallery a,
.game .screenshots .gallery button {
position: relative;
+
padding-top: 75%;
width: 100%;
}
···
padding: 20px;
}
+
.game .downloads ul {
+
list-style: none;
+
margin: 1em 0;
+
padding: 0;
+
}
+
+
.game .downloads ul li {
+
margin-bottom: 10px;
+
}
+
+
.game .downloads .size {
+
color: color-mix(in srgb-linear, var(--foreground), var(--background) 20%);
+
}
+
+
.game .downloads .platforms-label {
+
display: inline-block;
+
width: 1px;
+
overflow: hidden;
+
text-indent: -999px;
+
}
+
+
.game .downloads .icon {
+
width: 24px;
+
height: 24px;
+
background-color: color-mix(in srgb-linear, var(--foreground), var(--background) 20%);
+
}
+
.game .comments {
border-top: 1px var(--accent) solid;
}
···
[popover] {
position: fixed;
z-index: 99;
max-width: 90%;
max-height: 90%;
}
[popover]:-internal-popover-in-top-layer::backdrop {
background-color: rgba(0,0,0,.5);
+
}
+
+
.icon.web {
+
mask: url(../images/web.svg);
+
}
+
+
.icon.windows {
+
mask: url(../images/windows.svg);
+
}
+
+
.icon.macos {
+
mask: url(../images/macos.svg);
+
}
+
+
.icon.linux {
+
mask: url(../images/linux.svg);
+
}
+
+
.icon.android {
+
mask: url(../images/android.svg);
+
}
+
+
.icon.random {
+
mask: url(../images/random.svg);
+
}
+
+
.icon.sort-asc {
+
mask: url(../images/sort-asc.svg);
+
}
+
+
.icon.sort-desc {
+
mask: url(../images/sort-desc.svg);
}
+4 -3
plain-html/game.html
···
<!doctype html>
<html>
<head>
-
<title>!!YOUR GAME JAM TITLE!!</title>
<link rel="stylesheet" type="text/css" href="./css/style.css" />
<link rel="stylesheet" type="text/css" href="./css/custom.css" />
<link rel="apple-touch-icon" sizes="180x180" href="./images/apple-touch-icon.png">
···
!! BLURB GOES HERE !!
</div>
<div class="authors">
-
Submitted by <a href="#">Author 1</a>, <a href="#">Author 2</a> &mdash; <span id="timebefore">!! TIME !!</span> before deadline
</div>
</div>
<div class="screenshots">
···
<div class="downloads">
<h3>Downloads</h3>
<ul>
-
<li></li>
</ul>
</div>
<!-- uncomment if you want to drop in some kind of commenting system and put in whatever script you're using -->
···
<!doctype html>
<html>
<head>
+
<title>!!YOUR GAME JAM TITLE!! | !! GAME NAME !!</title>
<link rel="stylesheet" type="text/css" href="./css/style.css" />
<link rel="stylesheet" type="text/css" href="./css/custom.css" />
<link rel="apple-touch-icon" sizes="180x180" href="./images/apple-touch-icon.png">
···
!! BLURB GOES HERE !!
</div>
<div class="authors">
+
Submitted by <a href="#">Author 1</a>, <a href="#">Author 2</a> at <span class="submissiontime">!! SUBMISSION TIME !!</span>
</div>
</div>
<div class="screenshots">
···
<div class="downloads">
<h3>Downloads</h3>
<ul>
+
<li><a href="!! DL LINK !!" class="btn" download>Download</a> <b>!! FILE NAME !!</b> <span class="size">!! # MB !!</span> <span class="platforms"><span class="platforms-label">Platforms: </span><span class="icon windows" title="Windows">Windows</span><span class="icon linux" title="Linux">Linux</span></li>
+
<li><a href="!! DL LINK !!" class="btn" download>Download</a> <b>!! FILE NAME !!</b> <span class="size">!! # MB !!</span> <span class="platforms"><span class="platforms-label">Platforms: </span><span class="icon macos" title="MacOS">MacOS</span></li>
</ul>
</div>
<!-- uncomment if you want to drop in some kind of commenting system and put in whatever script you're using -->
+98 -98
plain-html/js/script.mjs
···
list.innerHTML = games.map((item, i) => {
return `<div class="item" id="item-${i}" data-authors="${item.authors.map(a => a.name)}" data-categories="${item.tags.map(t => 'tags:'+t).join(" ")} ${item.platforms.map(p => 'platforms:'+p).join(" ")}" data-title="${item.title}" data-date="${item.submitTime}">
<div class="thumb">
-
<img src="${item.thumbnail}" alt="${item.title} thumbnail image" />
</div>
<h3><a href="${item.page}">${item.title}</a></h3>
<div class="authors">${item.authors.map((auth) => { return `<a href="${auth.link}" target="_blank">${auth.name}</a>`; }).join(", ")}</div>
···
tagsElt.innerHTML = `<button class="afs-btn-filter" data-filter="*">all</button>`+allTags.map((t) => {
return `<button class="afs-btn-filter" data-filter="tags:${t}">#${t}</button>`
}).join("");
-
}
-
-
const afs = new AFS({
-
// Required Selectors
-
containerSelector: '#list',
-
itemSelector: '.item',
-
filterButtonSelector: '.afs-btn-filter',
-
searchInputSelector: '.afs-filter-search',
-
counterSelector: '.afs-filter-counter',
-
// CSS Classes
-
activeClass: 'active',
-
hiddenClass: 'hidden',
-
transitionClass: 'afs-transition',
-
// Filter & Search Configuration
-
filterMode: 'OR', // or 'AND'
-
groupMode: 'AND', // or 'OR'
-
searchKeys: ['title', 'authors', 'tags'],
-
debounceTime: 200, // search input delay
-
// Date Handling
-
dateFormat: 'YYYY-MM-DD',
-
dateFilter: {
-
enabled: true,
-
format: 'YYYY-MM-DDThh:mm:ss'
-
},
-
// Counter Configuration
-
counter: {
-
template: 'Showing {visible} of {total}',
-
showFiltered: true,
-
filteredTemplate: '({filtered} filtered)',
-
noResultsTemplate: 'No items found',
-
formatter: (num) => num.toLocaleString()
-
},
-
sort: {
-
enabled: true,
-
buttonSelector: '.afs-btn-sort'
-
},
-
filter: {
-
enabled: true,
-
buttonSelector: '.afs-btn-filter',
-
mode: 'AND',
-
activeClass: 'afs-active',
-
hiddenClass: 'afs-hidden'
-
},
-
// Animation Configuration
-
animation: {
-
type: 'fade',
-
duration: 200,
-
easing: 'ease-out',
-
inClass: 'afs-animation-enter',
-
outClass: 'afs-animation-leave'
-
},
-
// Lifecycle Options
-
responsive: true,
-
preserveState: false,
-
stateExpiry: 86400000, // 24 hours
-
observeDOM: false,
-
// Style Configuration
-
styles: {
-
colors: {
-
primary: '#000',
-
background: '#e5e7eb',
-
text: '#000',
-
textHover: '#fff'
-
}
-
}
-
});
-
afs.dateFilter.addDateRange({
-
key: 'date',
-
container: document.querySelector('#date-filter'),
-
format: 'YYYY-MM-DD',
-
minDate: new Date(startDate.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)[0]),
-
maxDate: new Date(endDate.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)[0])
-
});
-
afs.filter.setGroupMode('AND');
-
document.querySelectorAll('button.custom-sort:not([data-sort-key="shuffle"])').forEach((elt) => {
-
elt.addEventListener('click', (e) => {
-
e.preventDefault();
-
e.stopPropagation();
-
const btn = e.target.closest('button');
-
document.querySelectorAll('button.custom-sort:not([data-sort-key="'+btn.getAttribute('data-sort-key')+'"])').forEach((s) => {
-
s.classList.remove('sort-active');
-
});
-
if (btn.classList.contains('sort-active')) {
-
if (btn.getAttribute('data-sort-direction') === 'asc') {
-
btn.setAttribute('data-sort-direction', 'desc');
-
btn.querySelector('img').src = './images/sort-desc.svg';
} else {
-
btn.setAttribute('data-sort-direction', 'asc');
-
btn.querySelector('img').src = './images/sort-asc.svg';
}
-
afs.sort.sort(btn.getAttribute('data-sort-key'), btn.getAttribute('data-sort-direction'));
-
} else {
-
btn.classList.add('sort-active');
-
afs.sort.sort(btn.getAttribute('data-sort-key'), btn.getAttribute('data-sort-direction'));
-
}
});
-
});
-
document.querySelector('[data-sort-key="shuffle"]').addEventListener('click', (e) => {
-
afs.sort.shuffle();
-
});
···
list.innerHTML = games.map((item, i) => {
return `<div class="item" id="item-${i}" data-authors="${item.authors.map(a => a.name)}" data-categories="${item.tags.map(t => 'tags:'+t).join(" ")} ${item.platforms.map(p => 'platforms:'+p).join(" ")}" data-title="${item.title}" data-date="${item.submitTime}">
<div class="thumb">
+
<a href="${item.page}"><img src="${item.thumbnail}" alt="${item.title} thumbnail image" /></a>
</div>
<h3><a href="${item.page}">${item.title}</a></h3>
<div class="authors">${item.authors.map((auth) => { return `<a href="${auth.link}" target="_blank">${auth.name}</a>`; }).join(", ")}</div>
···
tagsElt.innerHTML = `<button class="afs-btn-filter" data-filter="*">all</button>`+allTags.map((t) => {
return `<button class="afs-btn-filter" data-filter="tags:${t}">#${t}</button>`
}).join("");
+
+
const afs = new AFS({
+
// Required Selectors
+
containerSelector: '#list',
+
itemSelector: '.item',
+
filterButtonSelector: '.afs-btn-filter',
+
searchInputSelector: '.afs-filter-search',
+
counterSelector: '.afs-filter-counter',
+
// CSS Classes
+
activeClass: 'active',
+
hiddenClass: 'hidden',
+
transitionClass: 'afs-transition',
+
// Filter & Search Configuration
+
filterMode: 'OR', // or 'AND'
+
groupMode: 'AND', // or 'OR'
+
searchKeys: ['title', 'authors', 'tags'],
+
debounceTime: 200, // search input delay
+
// Date Handling
+
dateFormat: 'YYYY-MM-DD',
+
dateFilter: {
+
enabled: true,
+
format: 'YYYY-MM-DDThh:mm:ss'
+
},
+
// Counter Configuration
+
counter: {
+
template: 'Showing {visible} of {total}',
+
showFiltered: true,
+
filteredTemplate: '({filtered} filtered)',
+
noResultsTemplate: 'No items found',
+
formatter: (num) => num.toLocaleString()
+
},
+
sort: {
+
enabled: true,
+
buttonSelector: '.afs-btn-sort'
+
},
+
filter: {
+
enabled: true,
+
buttonSelector: '.afs-btn-filter',
+
mode: 'AND',
+
activeClass: 'afs-active',
+
hiddenClass: 'afs-hidden'
+
},
+
// Animation Configuration
+
animation: {
+
type: 'fade',
+
duration: 200,
+
easing: 'ease-out',
+
inClass: 'afs-animation-enter',
+
outClass: 'afs-animation-leave'
+
},
+
// Lifecycle Options
+
responsive: true,
+
preserveState: false,
+
stateExpiry: 86400000, // 24 hours
+
observeDOM: false,
+
// Style Configuration
+
styles: {
+
colors: {
+
primary: '#000',
+
background: '#e5e7eb',
+
text: '#000',
+
textHover: '#fff'
+
}
+
}
+
});
+
afs.dateFilter.addDateRange({
+
key: 'date',
+
container: document.querySelector('#date-filter'),
+
format: 'YYYY-MM-DD',
+
minDate: new Date(startDate.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)[0]),
+
maxDate: new Date(endDate.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)[0])
+
});
+
afs.filter.setGroupMode('AND');
+
document.querySelectorAll('button.custom-sort:not([data-sort-key="shuffle"])').forEach((elt) => {
+
elt.addEventListener('click', (e) => {
+
e.preventDefault();
+
e.stopPropagation();
+
const btn = e.target.closest('button');
+
document.querySelectorAll('button.custom-sort:not([data-sort-key="'+btn.getAttribute('data-sort-key')+'"])').forEach((s) => {
+
s.classList.remove('sort-active');
+
});
+
if (btn.classList.contains('sort-active')) {
+
if (btn.getAttribute('data-sort-direction') === 'asc') {
+
btn.setAttribute('data-sort-direction', 'desc');
+
btn.querySelector('img').src = './images/sort-desc.svg';
+
} else {
+
btn.setAttribute('data-sort-direction', 'asc');
+
btn.querySelector('img').src = './images/sort-asc.svg';
+
}
+
afs.sort.sort(btn.getAttribute('data-sort-key'), btn.getAttribute('data-sort-direction'));
} else {
+
btn.classList.add('sort-active');
+
afs.sort.sort(btn.getAttribute('data-sort-key'), btn.getAttribute('data-sort-direction'));
}
+
});
});
+
document.querySelector('[data-sort-key="shuffle"]').addEventListener('click', (e) => {
+
afs.sort.shuffle();
+
});
+
}