1
0
mirror of https://github.com/locomotivemtl/locomotive-boilerplate.git synced 2026-01-15 00:55:08 +08:00

Add swup & swup fragment plugin to test its implementation w/ the boilerplate

This commit is contained in:
Jérémy Minié
2024-01-18 11:42:17 -05:00
parent a37c5b047a
commit d9c3bd9f92
13 changed files with 39797 additions and 121 deletions

View File

@@ -1,3 +1,4 @@
export {default as Example} from './modules/Example';
// export {default as Example} from './modules/Example';
export {default as Dialog} from './modules/Dialog';
export {default as Load} from './modules/Load';
export {default as Scroll} from './modules/Scroll';

View File

@@ -0,0 +1,36 @@
import { module } from 'modujs';
export default class extends module {
constructor(m) {
super(m);
this.$closeBtn = this.$('close')[0]
}
init() {
// Prevent ESC to close dialog
this.onKeyDown = this.onKeyDown.bind(this)
}
onKeyDown(e) {
if(e.key === 'Escape') {
console.log('ESCAPE');
e.preventDefault()
this.$closeBtn.click();
}
}
populate(container) {
this.el.appendChild(container)
}
show() {
this.el.showModal();
window.addEventListener('keydown', this.onKeyDown);
}
close() {
window.removeEventListener('keydown', this.onKeyDown);
this.el.close();
}
}

View File

@@ -1,5 +1,6 @@
import { module } from 'modujs';
import modularLoad from 'modularload';
import Swup from 'swup';
import SwupFragmentPlugin from '@swup/fragment-plugin';
export default class extends module {
constructor(m) {
@@ -7,16 +8,70 @@ export default class extends module {
}
init() {
const load = new modularLoad({
enterDelay: 0,
transitions: {
customTransition: {}
const load = new Swup({
containers: ['[data-load-container]'],
// cache: false,
plugins: [
new SwupFragmentPlugin({
rules: [
{
from: ['/','/index.php','/index.php/per_page/:per_page/page/:page'],
to: ['/','/index.php','/index.php/per_page/:per_page/page/:page'],
containers: ['#paginated']
},
{
from: ['/','/index.php', '/index.php/per_page/:per_page/page/:page'],
to: ['/index.php/modal/:modal'],
containers: ['#modal'],
name: 'open-modal'
},
{
from: ['/index.php/modal/:modal'],
to: ['/index.php/modal/:modal'],
containers: ['#modal'],
name: 'modal-update'
},
{
from: ['/index.php/modal/:modal'],
to: ['/','/index.php', '/index.php/per_page/:per_page/page/:page'],
containers: ['#modal', '#paginated'],
name: 'close-modal'
},
]
})
]
});
load.hooks.before('content:replace', async (visit) => {
console.log('before content replace:', visit);
for(let container of visit.containers) {
const oldContainer = this.el.querySelector(container)
console.log('old container: ', oldContainer)
this.call('destroy', oldContainer, 'app');
}
});
load.on('loaded', (transition, oldContainer, newContainer) => {
this.call('destroy', oldContainer, 'app');
this.call('update', newContainer, 'app');
load.hooks.on('content:replace', (visit) => {
console.log('On content replace:', visit);
if(visit.fragmentVisit) {
if(visit.fragmentVisit.name == 'open-modal') {
this.call('populate', document.getElementById('modal'), 'Dialog');
this.call('show', null, 'Dialog')
} else if(visit.fragmentVisit.name == 'close-modal') {
this.call('close', null, 'Dialog')
}
}
for(let container of visit.containers) {
const newContainer = this.el.querySelector(container)
console.log('new container: ', newContainer)
newContainer.classList.add('transition-fade')
this.call('update', newContainer, 'app');
}
});
console.log(this, load);
}
}

View File

@@ -69,3 +69,27 @@ a {
color: $color-link-hover;
}
}
/* Define a transition duration during page visits */
html.is-changing .transition-fade {
transition: opacity 0.1s;
opacity: 1;
}
/* Define the styles for the unloaded pages */
html.is-animating .transition-fade {
opacity: 0;
}
#paginated.is-changing {
transition: opacity 250ms;
}
#paginated.is-animating {
opacity: 0;
}
#modal.is-changing {
transition: opacity 100ms;
}
#modal.is-animating {
opacity: 0;
}

112
package-lock.json generated
View File

@@ -8,11 +8,13 @@
"name": "@locomotivemtl/boilerplate",
"version": "1.0.0",
"dependencies": {
"@swup/fragment-plugin": "^0.3.7",
"locomotive-scroll": "^5.0.0-beta.9",
"modujs": "^1.4.2",
"modularload": "^1.2.6",
"normalize.css": "^8.0.1",
"svg4everybody": "^2.1.9"
"svg4everybody": "^2.1.9",
"swup": "^4.5.1"
},
"devDependencies": {
"autoprefixer": "^10.4.13",
@@ -395,6 +397,25 @@
"resolved": "https://registry.npmjs.org/@studio-freight/lenis/-/lenis-1.0.27.tgz",
"integrity": "sha512-1I6EaWR9rxdFjIJtF52CtETJt9ngfY4AjawrJY5pLxCvHa/lQZ+1v2gTUntwNZkuks6E2It6YEXV6jnpnCZFjA=="
},
"node_modules/@swup/fragment-plugin": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/@swup/fragment-plugin/-/fragment-plugin-0.3.7.tgz",
"integrity": "sha512-tTu8K9gpec4EPy4tiV6xLghwW5YHGb5jQtJvN6gx28eOErk0k3hvD5whoe3zfnSzP8ZrdbK++EzzRoEwn2Tvyw==",
"dependencies": {
"@swup/plugin": "^4.0.0"
},
"peerDependencies": {
"swup": "^4.0.0"
}
},
"node_modules/@swup/plugin": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@swup/plugin/-/plugin-4.0.0.tgz",
"integrity": "sha512-3Kq31BJxnzoPg643YxGoWQggoU6VPKZpdE5CqqmP7wwkpCYTzkRmrfcQ29mGhsSS7xfS7D33iZoBiwY+wPoo2A==",
"dependencies": {
"swup": "^4.0.0"
}
},
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
@@ -1430,6 +1451,17 @@
"node": ">=0.10.0"
}
},
"node_modules/delegate-it": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/delegate-it/-/delegate-it-6.0.1.tgz",
"integrity": "sha512-ZS2hRm/SaoPzaeWcWyYjzVVF4/PgALZqma9FXsunFt4XQGVAtQ79Vx7v57vNQNaI75Rl12C+x6TkLqHS5PNKLg==",
"dependencies": {
"typed-query-selector": "^2.10.0"
},
"funding": {
"url": "https://github.com/sponsors/fregante"
}
},
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -3093,6 +3125,14 @@
"wrappy": "1"
}
},
"node_modules/opencollective-postinstall": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
"bin": {
"opencollective-postinstall": "index.js"
}
},
"node_modules/opn": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
@@ -3177,6 +3217,11 @@
"node": ">=0.10.0"
}
},
"node_modules/path-to-regexp": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -4494,6 +4539,17 @@
"node": ">=0.8.0"
}
},
"node_modules/swup": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/swup/-/swup-4.5.1.tgz",
"integrity": "sha512-FH9+x7sXzU19OgrLP23wdC8KZOkpBXC0cBKcnf0gM5Unurm5cn5LunCLesA6WZJmT/o5o9TZguM1cFmPaD6K4Q==",
"hasInstallScript": true,
"dependencies": {
"delegate-it": "^6.0.0",
"opencollective-postinstall": "^2.0.2",
"path-to-regexp": "^6.2.1"
}
},
"node_modules/tiny-glob": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
@@ -4571,6 +4627,11 @@
"integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=",
"dev": true
},
"node_modules/typed-query-selector": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.11.0.tgz",
"integrity": "sha512-qBs4sfmnLlPOyo2oSdvHbIFHe2CPgU54/1UGfSNceb7LARpIEVxUaeRX0Doje6oKpuySS2stqy90R3YrynR8Kg=="
},
"node_modules/ua-parser-js": {
"version": "1.0.33",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz",
@@ -5067,6 +5128,22 @@
"resolved": "https://registry.npmjs.org/@studio-freight/lenis/-/lenis-1.0.27.tgz",
"integrity": "sha512-1I6EaWR9rxdFjIJtF52CtETJt9ngfY4AjawrJY5pLxCvHa/lQZ+1v2gTUntwNZkuks6E2It6YEXV6jnpnCZFjA=="
},
"@swup/fragment-plugin": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/@swup/fragment-plugin/-/fragment-plugin-0.3.7.tgz",
"integrity": "sha512-tTu8K9gpec4EPy4tiV6xLghwW5YHGb5jQtJvN6gx28eOErk0k3hvD5whoe3zfnSzP8ZrdbK++EzzRoEwn2Tvyw==",
"requires": {
"@swup/plugin": "^4.0.0"
}
},
"@swup/plugin": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@swup/plugin/-/plugin-4.0.0.tgz",
"integrity": "sha512-3Kq31BJxnzoPg643YxGoWQggoU6VPKZpdE5CqqmP7wwkpCYTzkRmrfcQ29mGhsSS7xfS7D33iZoBiwY+wPoo2A==",
"requires": {
"swup": "^4.0.0"
}
},
"@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
@@ -5853,6 +5930,14 @@
}
}
},
"delegate-it": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/delegate-it/-/delegate-it-6.0.1.tgz",
"integrity": "sha512-ZS2hRm/SaoPzaeWcWyYjzVVF4/PgALZqma9FXsunFt4XQGVAtQ79Vx7v57vNQNaI75Rl12C+x6TkLqHS5PNKLg==",
"requires": {
"typed-query-selector": "^2.10.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -7167,6 +7252,11 @@
"wrappy": "1"
}
},
"opencollective-postinstall": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q=="
},
"opn": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
@@ -7224,6 +7314,11 @@
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-to-regexp": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -8298,6 +8393,16 @@
"resolved": "https://registry.npmjs.org/svg4everybody/-/svg4everybody-2.1.9.tgz",
"integrity": "sha1-W9n23vwTOFmgRGRtR0P6vCjbfi0="
},
"swup": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/swup/-/swup-4.5.1.tgz",
"integrity": "sha512-FH9+x7sXzU19OgrLP23wdC8KZOkpBXC0cBKcnf0gM5Unurm5cn5LunCLesA6WZJmT/o5o9TZguM1cFmPaD6K4Q==",
"requires": {
"delegate-it": "^6.0.0",
"opencollective-postinstall": "^2.0.2",
"path-to-regexp": "^6.2.1"
}
},
"tiny-glob": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
@@ -8362,6 +8467,11 @@
"integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=",
"dev": true
},
"typed-query-selector": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.11.0.tgz",
"integrity": "sha512-qBs4sfmnLlPOyo2oSdvHbIFHe2CPgU54/1UGfSNceb7LARpIEVxUaeRX0Doje6oKpuySS2stqy90R3YrynR8Kg=="
},
"ua-parser-js": {
"version": "1.0.33",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz",

View File

@@ -14,11 +14,13 @@
"build": "node --experimental-json-modules --no-warnings build/build.js"
},
"dependencies": {
"@swup/fragment-plugin": "^0.3.7",
"locomotive-scroll": "^5.0.0-beta.9",
"modujs": "^1.4.2",
"modularload": "^1.2.6",
"normalize.css": "^8.0.1",
"svg4everybody": "^2.1.9"
"svg4everybody": "^2.1.9",
"swup": "^4.5.1"
},
"devDependencies": {
"autoprefixer": "^10.4.13",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -34,24 +34,6 @@
<div class="o-container">
<h1 class="c-heading -h1">Images</h1>
<section>
<h2 class="c-heading -h2">Lazy load demo</h2>
<h3 class="c-heading -h3">Basic</h3>
<div style="width: 640px; max-width: 100%;">
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/800/600?v=1" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
<div class="o-ratio u-4:3"><img data-load-src="http://picsum.photos/800/600?v=2" alt="" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /></div>
</div>
<h4 class="c-heading -h3">Using o-ratio & background-image</h3>
<div style="width: 480px; max-width: 100%;">
<div class="o-ratio u-16:9" data-load-style="background-size: cover; background-position: center; background-image: url(http://picsum.photos/640/480?v=1);"></div>
<div class="o-ratio u-16:9" data-load-style="background-size: cover; background-position: center; background-image: url(http://picsum.photos/640/480?v=2);"></div>
</div>
</section>
<section>
<h3 class="c-heading -h3">Relative to scroll</h3>

View File

@@ -1,82 +0,0 @@
<!doctype html>
<html class="is-loading" lang="en" data-page="home">
<head>
<meta charset="utf-8">
<title>Locomotive Boilerplate</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<meta name="msapplication-TileColor" content="#ffffff">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicons/favicon-16x16.png">
<link rel="mask-icon" href="assets/images/favicons/safari-pinned-tab.svg" color="#000000">
<!-- For a dark mode managment and svg favicon add this in your favicon.svg -->
<!--
<style>
path {
fill: #000;
}
@media (prefers-color-scheme: dark) {
path {
fill: #fff;
}
}
</style>
-->
<!-- <link rel="icon" href="assets/images/favicons/favicon.svg"> -->
<!-- Preload Fonts -->
<link rel="preload" href="assets/fonts/SourceSans3-Bold.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/SourceSans3-BoldIt.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/SourceSans3-Regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/SourceSans3-RegularIt.woff2" as="font" type="font/woff2" crossorigin>
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print"
onload="this.media='all'; this.onload=null; this.isLoaded=true">
</head>
<body data-module-load>
<div data-load-container>
<div data-module-scroll="main">
<header>
<a href="/">
<h1>Locomotive Boilerplate</h1>
</a>
<nav>
<ul>
<li><a href="images.html">Images</a></li>
<li><a href="form.html" data-load="customTransition">Form</a></li>
<li><a href="grid.html">Grid</a></li>
</ul>
</nav>
</header>
<main data-module-example>
<div class="o-container">
<h1 class="c-heading -h1">Hello</h1>
</div>
</main>
<footer>
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
</footer>
</div>
</div>
<script nomodule src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.6.0/polyfill.min.js"
crossorigin="anonymous"></script>
<script nomodule
src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.remove%2CElement.prototype.append%2Cfetch%2CCustomEvent%2CElement.prototype.matches%2CNodeList.prototype.forEach%2CAbortController"
crossorigin="anonymous"></script>
<script src="assets/scripts/vendors.js" defer></script>
<script src="assets/scripts/app.js" defer></script>
</body>
</html>

166
www/index.php Normal file
View File

@@ -0,0 +1,166 @@
<?php
$pathSegments = explode('/', getenv('REQUEST_URI'));
// Initialize an empty key-value array
$keyValuePairs = [];
// Iterate through the segments to extract key-value pairs
$count = count($pathSegments);
for ($i = 0; $i < $count; $i += 2) {
// Check if the segment has both key and value
if (isset($pathSegments[$i + 1])) {
$key = $pathSegments[$i];
$value = $pathSegments[$i + 1];
$keyValuePairs[$key] = $value;
}
}
$items = [
'item 1',
'item 2',
'item 3',
'item 4',
'item 5',
'item 6',
'item 7',
'item 8',
'item 9',
'item 10',
'item 11',
'item 12',
];
?>
<!doctype html>
<html class="is-loading" lang="en" data-page="home">
<head>
<meta charset="utf-8">
<title>Locomotive Boilerplate</title>
<base href="/index.php">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<meta name="msapplication-TileColor" content="#ffffff">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicons/favicon-16x16.png">
<link rel="mask-icon" href="assets/images/favicons/safari-pinned-tab.svg" color="#000000">
<!-- For a dark mode managment and svg favicon add this in your favicon.svg -->
<!--
<style>
path {
fill: #000;
}
@media (prefers-color-scheme: dark) {
path {
fill: #fff;
}
}
</style>
-->
<!-- <link rel="icon" href="assets/images/favicons/favicon.svg"> -->
<!-- Preload Fonts -->
<link rel="preload" href="assets/fonts/SourceSans3-Bold.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/SourceSans3-BoldIt.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/SourceSans3-Regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="assets/fonts/SourceSans3-RegularIt.woff2" as="font" type="font/woff2" crossorigin>
<link id="main-css" rel="stylesheet" href="assets/styles/main.css" media="print"
onload="this.media='all'; this.onload=null; this.isLoaded=true">
</head>
<body data-module-load>
<main data-load-container>
<dialog id="dialog" data-module-dialog>
<a href="/" data-swup-link-to-fragment="#paginated" style="float: right;" data-dialog="close">Close</a>
</dialog>
<div data-module-scroll="main">
<header>
<a href="/">
<h1>Locomotive Boilerplate</h1>
</a>
<nav>
<ul>
<li><a href="/images.html">Images</a></li>
<li><a href="/form.html" data-load="customTransition">Form</a></li>
<li><a href="/grid.html">Grid</a></li>
</ul>
</nav>
</header>
<?php
if(isset($keyValuePairs['modal'])) { ?>
<main id="modal">
<h1><?= $items[$keyValuePairs['modal']] ?></h1>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Earum, sed, dolorum quae velit totam sit reprehenderit soluta beatae error iure aliquid laborum voluptatum sunt eum cum harum corporis! Perspiciatis, impedit!</p>
<?php if($keyValuePairs['modal'] > 0) { ?>
<a style="float: left;" href="/index.php/modal/<?= $keyValuePairs['modal'] - 1 ?>">Prev</a>
<?php }
if($keyValuePairs['modal']+1 < sizeof($items)) { ?>
<a style="float: right;" href="/index.php/modal/<?= $keyValuePairs['modal'] + 1 ?>">Next</a>
<?php } ?>
<div style="clear: both;"></div>
</main>
<?php } else { ?>
<main data-module-example>
<div class="o-container">
<h1 class="c-heading -h1">Hello</h1>
<div id="paginated">
<ul>
<?php
$page = $keyValuePairs['page'] ?? 1;
$per_page = $keyValuePairs['per_page'] ?? 3;
foreach (array_slice($items, ($page-1) * $per_page, $per_page) as $item) {
$key = array_search($item, $items);
?>
<li>
<a href="/index.php/modal/<?= $key ?>"><?= $item ?></a>
</li>
<?php
}
?>
</ul>
<?php for ($i=1; $i <= ceil(sizeof($items)/$per_page); $i++) { ?>
<?php if($i == $page) { ?>
<span><?= $i ?></span>
<?php } else { ?>
<a href="/index.php/per_page/<?= $per_page ?>/page/<?= $i ?>"><?= $i ?></a>
<?php } ?>
<?php } ?>
</div>
</div>
</main>
<template id="modal"></template>
<?php } ?>
<footer>
<p>Made with <a href="https://github.com/locomotivemtl/locomotive-boilerplate"
title="Locomotive Boilerplate" target="_blank" rel="noopener">🚂</a></p>
</footer>
</div>
</main>
<script nomodule src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.6.0/polyfill.min.js"
crossorigin="anonymous"></script>
<script nomodule
src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.remove%2CElement.prototype.append%2Cfetch%2CCustomEvent%2CElement.prototype.matches%2CNodeList.prototype.forEach%2CAbortController"
crossorigin="anonymous"></script>
<script src="assets/scripts/vendors.js" defer></script>
<script src="assets/scripts/app.js" defer></script>
</body>
</html>