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

add most of the zola theme files

+2 -1
plain-html/game.html
···
<html>
<head>
<title>!!YOUR GAME JAM TITLE!! | !! GAME NAME !!</title>
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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">
···
</div>
<!-- uncomment if you want to drop in some kind of commenting system and put in whatever script you're using -->
<!--<div class="comments">
-
+
put comment code here
</div>-->
</div>
</main>
+1
plain-html/index.html
···
<html>
<head>
<title>!!YOUR GAME JAM TITLE!!</title>
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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">
+2 -1
plain-html/submissions.html
···
<!doctype html>
<html>
<head>
-
<title>!!YOUR GAME JAM TITLE!!</title>
+
<title>!!YOUR GAME JAM TITLE!! | Submissions</title>
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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">
+570
zola/sass/style.scss
···
+
@import url('https://fonts.googleapis.com/css2?family=Knewave&family=Work+Sans:ital,wght@0,100..900;1,100..900&display=swap');
+
/* if you want to use different fonts, you can use other stuff from google fonts
+
if you don't want to use google, you may want to look into hosting your own fonts locally */
+
+
+
/* display variables! skip the images if you don't want them */
+
:root {
+
--background: #ECEBDE;
+
--foreground: #000000;
+
--accent: #A59D84;
+
--mainfont: 'Work Sans', Helvetica, Arial, sans-serif;
+
--headingfont: 'Knewave', 'Arial Black', sans-serif;
+
--roundedCorners: 0px;
+
+
--pageBgImage: url();
+
--contentBgImage: url();
+
}
+
+
* {
+
box-sizing: border-box;
+
}
+
+
body {
+
background-color: var(--background);
+
background-image: url(--pageBgImage);
+
color: var(--foreground);
+
font-family: var(--mainfont);
+
font-size: 1em;
+
}
+
+
a {
+
color: var(--accent);
+
text-decoration: underline;
+
+
&:hover {
+
color: color-mix(in srgb-linear, var(--accent), #000000 50%);
+
}
+
}
+
+
.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;
+
+
&:hover {
+
background-color: color-mix(in srgb-linear, var(--accent), #000000 50%);
+
color: var(--background);
+
}
+
}
+
+
#page {
+
max-width: 960px;
+
margin: 50px auto;
+
background-color: color-mix(in srgb-linear, var(--background), #FFFFFF 50%);
+
background-image: var(--contentBgImage);
+
border-radius: var(--roundedCorners);
+
+
body.wide & {
+
max-width: none;
+
margin: 0;
+
}
+
}
+
+
header {
+
padding: 20px 20px 0;
+
position: relative;
+
background-color: color-mix(in srgb-linear, var(--background), #FFFFFF 25%);
+
+
h1 {
+
font-size: 2.5em;
+
font-weight: bold;
+
margin: 0;
+
}
+
+
.hosted {
+
margin: 0;
+
}
+
+
.joined,
+
.entries {
+
text-align: center;
+
position: absolute;
+
top: 20px;
+
right: 20px;
+
+
.count {
+
font-size: 2.25em;
+
display: block;
+
}
+
+
.caption {
+
font-size: .8em;
+
display: block;
+
color: color-mix(in srgb-linear, var(--foreground) #FFFFFF, 25%);
+
}
+
}
+
+
nav {
+
margin-top: 20px;
+
padding-bottom: 10px;
+
+
ul {
+
margin-bottom: 0;
+
display: flex;
+
gap: 10px;
+
list-style: none;
+
padding: 0;
+
+
li {
+
a {
+
text-decoration: none;
+
padding: 5px 0;
+
border-bottom: 5px transparent solid;
+
color: var(--foreground);
+
+
&.current,
+
&:hover {
+
border-bottom: 5px var(--accent) solid;
+
color: var(--foreground);
+
}
+
}
+
}
+
}
+
}
+
}
+
+
main {
+
padding: 20px;
+
text-align: center;
+
+
.clock {
+
border: 1px var(--accent) solid;
+
border-radius: 5px;
+
display: inline-grid;
+
grid-template-areas: "date date" "countdown join";
+
margin: 50px auto;
+
+
.dates {
+
border-bottom: 1px var(--accent) solid;
+
padding: 10px;
+
grid-area: date;
+
text-align: center;
+
}
+
+
.countdown {
+
grid-area: countdown;
+
border-right: 1px var(--accent) solid;
+
padding: 20px;
+
text-align: right;
+
display: flex;
+
align-items: center;
+
justify-content: flex-end;
+
+
.counters {
+
display: flex;
+
align-items: stretch;
+
+
& > div {
+
padding: 10px;
+
border-right: 2px var(--accent) solid;
+
display: flex;
+
flex-direction: column;
+
justify-content: center;
+
align-items: center;
+
white-space: pre;
+
+
&:last-child {
+
border-right: none;
+
}
+
}
+
+
span {
+
display: block;
+
text-align: center;
+
+
&.num {
+
font-weight: bold;
+
}
+
+
&.caption {
+
font-size: .8em;
+
font-style: italic;
+
}
+
}
+
}
+
}
+
+
.join {
+
display: flex;
+
align-items: center;
+
justify-content: flex-start;
+
padding: 20px;
+
}
+
+
.joinbtn {
+
padding: 10px 20px;
+
font-size: 1.25em;
+
font-weight: bold;
+
border-radius: 5px;
+
background-color: var(--accent);
+
color: var(--background);
+
text-decoration: none;
+
white-space: pre;
+
+
&:hover {
+
background-color: color-mix(in srgb-linear, var(--accent), #000000 50%);
+
}
+
}
+
}
+
+
.content {
+
text-align: left;
+
}
+
}
+
+
footer {
+
text-align: center;
+
font-size: .8em;
+
padding: 5px 10px;
+
margin-top: 20px;
+
+
a {
+
text-decoration: none;
+
}
+
}
+
+
.submissions {
+
main {
+
display: grid;
+
grid-template-areas: "filters list";
+
grid-template-columns: 250px 1fr;
+
gap: 20px;
+
}
+
+
#filters {
+
grid-area: filters;
+
text-align: left;
+
+
#tags {
+
display: flex;
+
flex-wrap: wrap;
+
gap: 5px;
+
justify-content: flex-start;
+
+
button {
+
white-space: pre;
+
}
+
}
+
+
details {
+
margin-bottom: 10px;
+
+
ul {
+
list-style: none;
+
margin: 0;
+
padding: 0;
+
}
+
}
+
+
p.label,
+
details summary {
+
font-size: .8em;
+
color: color-mix(in srgb-linear, var(--foreground), var(--background) 10%);
+
padding: 5px 0;
+
margin-bottom: 0;
+
}
+
}
+
}
+
+
.submissions #filters ul#sorts {
+
list-style: none;
+
margin: 0 0 10px;
+
padding: 0;
+
}
+
+
.submissions #filters ul#sorts li button {
+
appearance: none;
+
border: none;
+
background-color: transparent;
+
color: var(--foreground);
+
border-radius: 0;
+
font-family: inherit;
+
font-size: inherit;
+
font-size: .9em;
+
}
+
+
.submissions #filters ul#sorts li button.sort-active {
+
color: color-mix(in srgb-linear, var(--accent), #000000 10%);
+
font-weight: bold;
+
}
+
+
.submissions #filters ul#sorts li button img {
+
width: 16px;
+
height: 16px;
+
margin-right: 5px;
+
display: inline-block;
+
vertical-align: middle;
+
}
+
+
.submissions #filters .afs-btn-filter {
+
appearance: none;
+
padding: 3px 0;
+
border: none;
+
border-radius: 0;
+
font-family: inherit;
+
background-color: transparent;
+
}
+
+
.submissions #filters .afs-btn-filter.active {
+
color: color-mix(in srgb-linear, var(--accent), #000000 10%);
+
background-color: transparent;
+
font-weight: bold;
+
}
+
+
.submissions #filters .afs-btn-filter.active img {
+
fill: color-mix(in srgb-linear, var(--accent), #000000 10%);
+
}
+
+
.submissions #filters .afs-filter-search {
+
width: 100%;
+
padding: 0.5rem;
+
border: 1px solid var(--accent);
+
border-radius: 0.25rem;
+
font-size: 0.875rem;
+
color: var(--foreground);
+
transition: border-color 0.2s ease;
+
margin-bottom: 10px;
+
font-family: inherit;
+
}
+
+
.submissions #filters .afs-filter-counter {
+
text-align: center;
+
font-style: italic;
+
font-size: .9em;
+
}
+
+
.submissions #list {
+
display: grid;
+
grid-area: list;
+
grid-template-columns: repeat(4, 1fr);
+
grid-template-rows: auto;
+
gap: 10px;
+
}
+
+
.submissions #list .item {
+
text-align: left;
+
}
+
+
.submissions #list .item h3 {
+
margin: 0;
+
font-size: 1.25em;
+
}
+
+
.submissions #list .item h3 a {
+
color: var(--foreground);
+
text-decoration: none;
+
}
+
+
.submissions #list .item h3 a:hover {
+
text-decoration: underline;
+
}
+
+
.submissions #list .item .thumb {
+
position: relative;
+
padding-top: 75%;
+
}
+
+
.submissions #list .item .thumb img {
+
position: absolute;
+
top: 0;
+
left: 0;
+
width: 100%;
+
height: 100%;
+
object-fit: cover;
+
}
+
+
.submissions #list .item .authors {
+
font-size: .9em;
+
margin: 5px 0;
+
}
+
+
.submissions #list .item .authors a {
+
color: color-mix(in srgb-linear, var(--foreground), #FFFFFF 10%);
+
text-decoration: none;
+
}
+
+
.submissions #list .item .authors a:hover {
+
text-decoration: underline;
+
}
+
+
.submissions #list .item .blurb {
+
margin-top: 5px;
+
font-size: .8em;
+
color: color-mix(in srgb-linear, var(--foreground), #FFFFFF 10%);
+
}
+
+
.game main {
+
display: grid;
+
grid-template-areas: "header header" "screenshots stuff";
+
text-align: left;
+
border-bottom: 1px var(--accent) solid;
+
padding-bottom: 0;
+
}
+
+
.game main h3 {
+
margin: 0;
+
}
+
+
.game .game-header {
+
margin: -20px -20px 0;
+
width: calc(100% + 40px);
+
border-bottom: 1px var(--accent) solid;
+
padding: 20px;
+
text-align: left;
+
grid-area: header;
+
}
+
+
.game .game-header .jamsub {
+
font-size: .8em;
+
font-style: italic;
+
color: color-mix(in srgb-linear, var(--foreground), var(--background) 15%);
+
margin: 0;
+
}
+
+
.game .game-header h2 {
+
margin: 0;
+
}
+
+
.game .game-header h2 a {
+
font-size: .9rem;
+
margin-left: 10px;
+
display: inline-block;
+
vertical-align: middle;
+
}
+
+
.game .game-header .blurb {
+
margin: 1em 0;
+
}
+
+
.game .screenshots {
+
border-right: 1px var(--accent) solid;
+
grid-area: screenshots;
+
padding: 20px;
+
}
+
+
.game .screenshots .gallery {
+
display: grid;
+
gap: 10px;
+
grid-template-columns: repeat(2, 1fr);
+
grid-template-rows: auto;
+
grid-template-areas: "featured featured";
+
}
+
+
.game .screenshots .gallery a,
+
.game .screenshots .gallery button {
+
position: relative;
+
padding-top: 75%;
+
width: 100%;
+
}
+
+
.game .screenshots .gallery a img,
+
.game .screenshots .gallery button img {
+
position: absolute;
+
top: 0;
+
left: 0;
+
width: 100%;
+
height: 100%;
+
object-fit: cover;
+
}
+
+
.game .screenshots .gallery > :first-child {
+
grid-area: featured;
+
}
+
+
.game .interaction {
+
grid-area: stuff;
+
}
+
+
.game .downloads {
+
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);
+
}
zola/static/images/android-chrome-192x192.png

This is a binary file and will not be displayed.

zola/static/images/android-chrome-512x512.png

This is a binary file and will not be displayed.

+1
zola/static/images/android.svg
···
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M452.5 317.9C465.8 317.9 476.5 328.6 476.5 341.9C476.5 355.2 465.8 365.9 452.5 365.9C439.2 365.9 428.5 355.2 428.5 341.9C428.5 328.6 439.2 317.9 452.5 317.9zM187.4 317.9C200.7 317.9 211.4 328.6 211.4 341.9C211.4 355.2 200.7 365.9 187.4 365.9C174.1 365.9 163.4 355.2 163.4 341.9C163.4 328.6 174.1 317.9 187.4 317.9zM461.1 221.4L509 138.4C509.8 137.3 510.3 136 510.5 134.6C510.7 133.2 510.7 131.9 510.4 130.5C510.1 129.1 509.5 127.9 508.7 126.8C507.9 125.7 506.9 124.8 505.7 124.1C504.5 123.4 503.2 123 501.8 122.8C500.4 122.6 499.1 122.8 497.8 123.2C496.5 123.6 495.3 124.3 494.2 125.1C493.1 125.9 492.3 127.1 491.7 128.3L443.2 212.4C404.4 195 362.4 186 319.9 186C277.4 186 235.4 195 196.6 212.4L148.2 128.4C147.6 127.2 146.7 126.1 145.7 125.2C144.7 124.3 143.4 123.7 142.1 123.3C140.8 122.9 139.4 122.8 138.1 122.9C136.8 123 135.4 123.5 134.2 124.2C133 124.9 132 125.8 131.2 126.9C130.4 128 129.8 129.3 129.5 130.6C129.2 131.9 129.2 133.3 129.4 134.7C129.6 136.1 130.2 137.3 130.9 138.5L178.8 221.5C96.5 266.2 40.2 349.5 32 448L608 448C599.8 349.5 543.5 266.2 461.1 221.4z"/></svg>
zola/static/images/apple-touch-icon.png

This is a binary file and will not be displayed.

zola/static/images/favicon-16x16.png

This is a binary file and will not be displayed.

zola/static/images/favicon-32x32.png

This is a binary file and will not be displayed.

zola/static/images/favicon.ico

This is a binary file and will not be displayed.

+1
zola/static/images/linux.svg
···
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M316.9 187.3C317.9 187.8 318.7 189 319.9 189C321 189 322.7 188.6 322.8 187.5C323 186.1 320.9 185.2 319.6 184.6C317.9 183.9 315.7 183.6 314.1 184.5C313.7 184.7 313.3 185.2 313.5 185.6C313.8 186.9 315.8 186.7 316.9 187.3zM295 189C296.2 189 297 187.8 298 187.3C299.1 186.7 301.1 186.9 301.5 185.7C301.7 185.3 301.3 184.8 300.9 184.6C299.3 183.7 297.1 184 295.4 184.7C294.1 185.3 292 186.2 292.2 187.6C292.3 188.6 294 189.1 295 189zM516 467.8C512.4 463.8 510.7 456.2 508.8 448.1C507 440 504.9 431.3 498.3 425.7C497 424.6 495.7 423.6 494.3 422.8C493 422 491.6 421.3 490.2 420.8C499.4 393.5 495.8 366.3 486.5 341.7C475.1 311.6 455.2 285.3 440 267.3C422.9 245.8 406.3 225.4 406.6 195.3C407.1 149.4 411.7 64.1 330.8 64C228.4 63.8 254 167.4 252.9 199.2C251.2 222.6 246.5 241 230.4 263.9C211.5 286.4 184.9 322.7 172.3 360.6C166.3 378.5 163.5 396.7 166.1 413.9C159.6 419.7 154.7 428.6 149.5 434.1C145.3 438.4 139.2 440 132.5 442.4C125.8 444.8 118.5 448.4 114 456.9C111.9 460.8 111.2 465 111.2 469.3C111.2 473.2 111.8 477.2 112.4 481.1C113.6 489.2 114.9 496.8 113.2 501.9C108 516.3 107.3 526.3 111 533.6C114.8 540.9 122.4 544.1 131.1 545.9C148.4 549.5 171.9 548.6 190.4 558.4C210.2 568.8 230.3 572.5 246.3 568.8C257.9 566.2 267.4 559.2 272.2 548.6C284.7 548.5 298.5 543.2 320.5 542C335.4 540.8 354.1 547.3 375.6 546.1C376.2 548.4 377 550.7 378.1 552.8L378.1 552.9C386.4 569.6 401.9 577.2 418.4 575.9C435 574.6 452.5 564.9 466.7 548C480.3 531.6 502.7 524.8 517.6 515.8C525 511.3 531 505.7 531.5 497.5C531.9 489.3 527.1 480.2 516 467.8zM319.8 151.3C329.6 129.1 354 129.5 363.8 150.9C370.3 165.1 367.4 181.8 359.5 191.3C357.9 190.5 353.6 188.7 346.9 186.4C348 185.2 350 183.7 350.8 181.8C355.6 170 350.6 154.8 341.7 154.5C334.4 154 327.8 165.3 329.9 177.5C325.8 175.5 320.5 174 316.9 173.1C315.9 166.2 316.6 158.5 319.8 151.3zM279.1 139.8C289.2 139.8 299.9 154 298.2 173.3C294.7 174.3 291.1 175.8 288 177.9C289.2 169 284.7 157.8 278.4 158.3C270 159 268.6 179.5 276.6 186.4C277.6 187.2 278.5 186.2 270.7 191.9C255.1 177.3 260.2 139.8 279.1 139.8zM265.5 200.5C271.7 195.9 279.1 190.5 279.6 190C284.3 185.6 293.1 175.8 307.5 175.8C314.6 175.8 323.1 178.1 333.4 184.7C339.7 188.8 344.7 189.1 356 194C364.4 197.5 369.7 203.7 366.5 212.2C363.9 219.3 355.5 226.6 343.8 230.3C332.7 233.9 324 246.3 305.6 245.2C301.7 245 298.6 244.2 296 243.1C288 239.6 283.8 232.7 276 228.1C267.4 223.3 262.8 217.7 261.3 212.8C259.9 207.9 261.3 203.8 265.5 200.5zM268.8 534.5C266.1 569.6 224.9 568.9 193.5 552.5C163.6 536.7 124.9 546 117 530.6C114.6 525.9 114.6 517.9 119.6 504.2L119.6 504C122 496.4 120.2 488 119 480.1C117.8 472.3 117.2 465.1 119.9 460.1C123.4 453.4 128.4 451 134.7 448.8C145 445.1 146.5 445.4 154.3 438.9C159.8 433.2 163.8 426 168.6 420.9C173.7 415.4 178.6 412.8 186.3 414C194.4 415.2 201.4 420.8 208.2 430L227.8 465.6C237.3 485.5 270.9 514 268.8 534.5zM267.4 508.6C263.3 502 257.8 495 253 489C260.1 489 267.2 486.8 269.7 480.1C272 473.9 269.7 465.2 262.3 455.2C248.8 437 224 422.7 224 422.7C210.5 414.3 202.9 404 199.4 392.8C195.9 381.6 196.4 369.5 199.1 357.6C204.3 334.7 217.7 312.4 226.3 298.4C228.6 296.7 227.1 301.6 217.6 319.2C209.1 335.3 193.2 372.5 215 401.6C215.6 380.9 220.5 359.8 228.8 340.1C240.8 312.7 266.1 265.2 268.1 227.4C269.2 228.2 272.7 230.6 274.3 231.5C278.9 234.2 282.4 238.2 286.9 241.8C299.3 251.8 315.4 251 329.3 243C335.5 239.5 340.5 235.5 345.2 234C355.1 230.9 363 225.4 367.5 219C375.2 249.4 393.2 293.3 404.7 314.7C410.8 326.1 423 350.2 428.3 379.3C431.6 379.2 435.3 379.7 439.2 380.7C453 345 427.5 306.5 415.9 295.8C411.2 291.2 411 289.2 413.3 289.3C425.9 300.5 442.5 323 448.5 348.3C451.3 359.9 451.8 372 448.9 384C465.3 390.8 484.8 401.9 479.6 418.8C477.4 418.7 476.4 418.8 475.4 418.8C478.6 408.7 471.5 401.2 452.6 392.7C433 384.1 416.6 384.1 414.3 405.2C402.2 409.4 396 419.9 392.9 432.5C390.1 443.7 389.3 457.2 388.5 472.4C388 480.1 384.9 490.4 381.7 501.4C349.6 524.3 305 534.3 267.4 508.6zM524.8 497.1C523.9 513.9 483.6 517 461.6 543.6C448.4 559.3 432.2 568 418 569.1C403.8 570.2 391.5 564.3 384.3 549.8C379.6 538.7 381.9 526.7 385.4 513.5C389.1 499.3 394.6 484.7 395.3 472.9C396.1 457.7 397 444.4 399.5 434.2C402.1 423.9 406.1 417 413.2 413.1C413.5 412.9 413.9 412.8 414.2 412.6C415 425.8 421.5 439.2 433 442.1C445.6 445.4 463.7 434.6 471.4 425.8C480.4 425.5 487.1 424.9 494 430.9C503.9 439.4 501.1 461.2 511.1 472.5C521.7 484.1 525.1 492 524.8 497.1zM269.4 212.7C271.4 214.6 274.1 217.2 277.4 219.8C284 225 293.2 230.4 304.7 230.4C316.3 230.4 327.2 224.5 336.5 219.6C341.4 217 347.4 212.6 351.3 209.2C355.2 205.8 357.2 202.9 354.4 202.6C351.6 202.3 351.8 205.2 348.4 207.7C344 210.9 338.7 215.1 334.5 217.5C327.1 221.7 315 227.7 304.6 227.7C294.2 227.7 285.9 222.9 279.7 218C276.6 215.5 274 213 272 211.1C270.5 209.7 270.1 206.5 267.7 206.2C266.3 206.1 265.9 209.9 269.4 212.7z"/></svg>
+1
zola/static/images/macos.svg
···
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M320 176C311.2 176 304 168.8 304 160L304 144C304 99.8 339.8 64 384 64L400 64C408.8 64 416 71.2 416 80L416 96C416 140.2 380.2 176 336 176L320 176zM96 352C96 275.7 131.7 192 208 192C235.3 192 267.7 202.3 290.7 211.3C309.5 218.6 330.6 218.6 349.4 211.3C372.3 202.4 404.8 192 432.1 192C508.4 192 544.1 275.7 544.1 352C544.1 480 464.1 576 384.1 576C367.6 576 346 569.4 332.6 564.7C324.5 561.9 315.7 561.9 307.6 564.7C294.2 569.4 272.6 576 256.1 576C176.1 576 96.1 480 96.1 352z"/></svg>
+1
zola/static/images/random.svg
···
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M467.8 98.4C479.8 93.4 493.5 96.2 502.7 105.3L566.7 169.3C572.7 175.3 576.1 183.4 576.1 191.9C576.1 200.4 572.7 208.5 566.7 214.5L502.7 278.5C493.5 287.7 479.8 290.4 467.8 285.4C455.8 280.4 448 268.9 448 256L448 224L416 224C405.9 224 396.4 228.7 390.4 236.8L358 280L318 226.7L339.2 198.4C357.3 174.2 385.8 160 416 160L448 160L448 128C448 115.1 455.8 103.4 467.8 98.4zM218 360L258 413.3L236.8 441.6C218.7 465.8 190.2 480 160 480L96 480C78.3 480 64 465.7 64 448C64 430.3 78.3 416 96 416L160 416C170.1 416 179.6 411.3 185.6 403.2L218 360zM502.6 534.6C493.4 543.8 479.7 546.5 467.7 541.5C455.7 536.5 448 524.9 448 512L448 480L416 480C385.8 480 357.3 465.8 339.2 441.6L185.6 236.8C179.6 228.7 170.1 224 160 224L96 224C78.3 224 64 209.7 64 192C64 174.3 78.3 160 96 160L160 160C190.2 160 218.7 174.2 236.8 198.4L390.4 403.2C396.4 411.3 405.9 416 416 416L448 416L448 384C448 371.1 455.8 359.4 467.8 354.4C479.8 349.4 493.5 352.2 502.7 361.3L566.7 425.3C572.7 431.3 576.1 439.4 576.1 447.9C576.1 456.4 572.7 464.5 566.7 470.5L502.7 534.5z"/></svg>
+1
zola/static/images/sort-asc.svg
···
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M278.6 438.6L182.6 534.6C170.1 547.1 149.8 547.1 137.3 534.6L41.3 438.6C28.8 426.1 28.8 405.8 41.3 393.3C53.8 380.8 74.1 380.8 86.6 393.3L128 434.7L128 128C128 110.3 142.3 96 160 96C177.7 96 192 110.3 192 128L192 434.7L233.4 393.3C245.9 380.8 266.2 380.8 278.7 393.3C291.2 405.8 291.2 426.1 278.7 438.6zM352 96L384 96C401.7 96 416 110.3 416 128C416 145.7 401.7 160 384 160L352 160C334.3 160 320 145.7 320 128C320 110.3 334.3 96 352 96zM352 224L448 224C465.7 224 480 238.3 480 256C480 273.7 465.7 288 448 288L352 288C334.3 288 320 273.7 320 256C320 238.3 334.3 224 352 224zM352 352L512 352C529.7 352 544 366.3 544 384C544 401.7 529.7 416 512 416L352 416C334.3 416 320 401.7 320 384C320 366.3 334.3 352 352 352zM352 480L576 480C593.7 480 608 494.3 608 512C608 529.7 593.7 544 576 544L352 544C334.3 544 320 529.7 320 512C320 494.3 334.3 480 352 480z"/></svg>
+1
zola/static/images/sort-desc.svg
···
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M278.6 438.6L182.6 534.6C170.1 547.1 149.8 547.1 137.3 534.6L41.3 438.6C28.8 426.1 28.8 405.8 41.3 393.3C53.8 380.8 74.1 380.8 86.6 393.3L128 434.7L128 128C128 110.3 142.3 96 160 96C177.7 96 192 110.3 192 128L192 434.7L233.4 393.3C245.9 380.8 266.2 380.8 278.7 393.3C291.2 405.8 291.2 426.1 278.7 438.6zM352 544C334.3 544 320 529.7 320 512C320 494.3 334.3 480 352 480L384 480C401.7 480 416 494.3 416 512C416 529.7 401.7 544 384 544L352 544zM352 416C334.3 416 320 401.7 320 384C320 366.3 334.3 352 352 352L448 352C465.7 352 480 366.3 480 384C480 401.7 465.7 416 448 416L352 416zM352 288C334.3 288 320 273.7 320 256C320 238.3 334.3 224 352 224L512 224C529.7 224 544 238.3 544 256C544 273.7 529.7 288 512 288L352 288zM352 160C334.3 160 320 145.7 320 128C320 110.3 334.3 96 352 96L576 96C593.7 96 608 110.3 608 128C608 145.7 593.7 160 576 160L352 160z"/></svg>
+1
zola/static/images/web.svg
···
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M415.9 344L225 344C227.9 408.5 242.2 467.9 262.5 511.4C273.9 535.9 286.2 553.2 297.6 563.8C308.8 574.3 316.5 576 320.5 576C324.5 576 332.2 574.3 343.4 563.8C354.8 553.2 367.1 535.8 378.5 511.4C398.8 467.9 413.1 408.5 416 344zM224.9 296L415.8 296C413 231.5 398.7 172.1 378.4 128.6C367 104.2 354.7 86.8 343.3 76.2C332.1 65.7 324.4 64 320.4 64C316.4 64 308.7 65.7 297.5 76.2C286.1 86.8 273.8 104.2 262.4 128.6C242.1 172.1 227.8 231.5 224.9 296zM176.9 296C180.4 210.4 202.5 130.9 234.8 78.7C142.7 111.3 74.9 195.2 65.5 296L176.9 296zM65.5 344C74.9 444.8 142.7 528.7 234.8 561.3C202.5 509.1 180.4 429.6 176.9 344L65.5 344zM463.9 344C460.4 429.6 438.3 509.1 406 561.3C498.1 528.6 565.9 444.8 575.3 344L463.9 344zM575.3 296C565.9 195.2 498.1 111.3 406 78.7C438.3 130.9 460.4 210.4 463.9 296L575.3 296z"/></svg>
+1
zola/static/images/windows.svg
···
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M96 157.7L279.6 132.4L279.6 309.8L96 309.8L96 157.7zM96 482.3L279.6 507.6L279.6 332.4L96 332.4L96 482.3zM299.8 510.3L544 544L544 332.4L299.8 332.4L299.8 510.3zM299.8 129.7L299.8 309.8L544 309.8L544 96L299.8 129.7z"/></svg>
+2
zola/static/js/afs.modern.js
···
+
class t{constructor(t=!1,i="info"){this.enabled=t,this.level=i,this.levels={error:0,warn:1,info:2,debug:3}}t(t,...i){if(("error"===t||this.enabled)&&this.levels[t]<=this.levels[this.level]){const e=(new Date).toLocaleTimeString(),s=`[AFS ${t.toUpperCase()}]`;(console[t]||console.log).call(console,s,e,...i)}}error(...t){this.t("error",...t)}warn(...t){this.t("warn",...t)}info(...t){this.t("info",...t)}debug(...t){this.t("debug",...t)}setDebugMode(t,i="info"){const e=this.enabled;this.enabled=!!t,this.levels.hasOwnProperty(i)&&(this.level=i),(this.enabled||e)&&this.t("info",`Debug mode ${this.enabled?"enabled":"disabled"} with level: ${this.level}`)}getState(){return{enabled:this.enabled,level:this.level}}}class i{static defaults={containerSelector:".afs-filter-container",itemSelector:".afs-filter-item",filterButtonSelector:".afs-btn-filter",filterDropdownSelector:".afs-filter-dropdown",searchInputSelector:".afs-filter-search",counterSelector:".afs-filter-counter",sortButtonSelector:".afs-btn-sort",activeClass:"active",hiddenClass:"hidden",activeSortClass:"sort-active",transitionClass:"afs-transition",filterMode:"OR",groupMode:"AND",searchKeys:["title"],debounceTime:300,debug:!1,logLevel:"info",responsive:!0,preserveState:!1,stateExpiry:864e5,observeDOM:!1,dateFormat:"YYYY-MM-DD",counter:{template:"Showing {visible} of {total}",showFiltered:!0,filteredTemplate:"({filtered} filtered)",noResultsTemplate:"No items found",formatter(t){return t.toLocaleString()}},styles:{slider:{ui:{showHistogram:!1,bins:10,track:{radius:"0",background:"#e5e7eb"},selected:{background:"#000"},thumb:{radius:"50%",size:"16px",background:"#000"},histogram:{background:"#e5e7eb",bar:{background:"#000"}}}},pagination:{ui:{button:{background:"transparent",border:"1px solid #000",borderRadius:"4px",padding:"8px 12px",color:"#000",active:{background:"#000",color:"#fff"},hover:{background:"#000",color:"#fff"}}}},colors:{primary:"#000",background:"#e5e7eb",text:"#000",textHover:"#fff"}},slider:{containerClass:"afs-range-slider",trackClass:"afs-range-track",thumbClass:"afs-range-thumb",valueClass:"afs-range-value",selectedClass:"afs-range-selected"},pagination:{enabled:!1,itemsPerPage:10,container:".afs-pagination-container",pageButtonClass:"afs-page-button",activePageClass:"afs-page-active",containerClass:"afs-pagination",scrollToTop:!1,scrollOffset:50,scrollBehavior:"smooth"},animation:{type:"fade",duration:300,easing:"ease-out",inClass:"afs-animation-enter",outClass:"afs-animation-leave"}};constructor(t={}){this.options=this.mergeOptions(i.defaults,t),this.initializeStyles(),this.validate()}initializeStyles(){const t=i.defaults.styles,e=this.options.styles||{};this.options.styles=this.mergeOptions(t,e),this.options.styles.colors={...t.colors,...e.colors||{}}}mergeOptions(t,i){const e={...t};for(const s in i)null===i[s]||"object"!=typeof i[s]||Array.isArray(i[s])?void 0!==i[s]&&(e[s]=i[s]):(t[s]&&"object"==typeof t[s]||(e[s]={}),e[s]=this.mergeOptions(e[s],i[s]));return e}validate(){const t=["containerSelector","itemSelector"];for(const i of t)if("string"!=typeof this.options[i])throw Error(i+" must be a string");if("number"!=typeof this.options.animation?.duration||this.options.animation?.duration<0)throw Error("animation.duration must be a positive number");if(!["OR","AND"].includes(this.options.filterMode.toUpperCase()))throw Error('filterMode must be either "OR" or "AND"');if(!["OR","AND"].includes(this.options.groupMode.toUpperCase()))throw Error('groupMode must be either "OR" or "AND"');if(!Array.isArray(this.options.searchKeys)||0===this.options.searchKeys.length)throw Error("searchKeys must be a non-empty array");if(this.options.counter){if("string"!=typeof this.options.counter.template)throw Error("counter.template must be a string");"boolean"!=typeof this.options.counter.showFiltered&&(this.options.counter.showFiltered=!0),"function"!=typeof this.options.counter.formatter&&(this.options.counter.formatter=t=>t.toLocaleString())}else this.options.counter={...i.defaults.counter}}get(t){return t.split(".").reduce(((t,i)=>t?.[i]),this.options)}set(t,i){const e=t.split("."),s=e.pop();e.reduce(((t,i)=>(i in t||(t[i]={}),t[i])),this.options)[s]=i,this.validate()}update(t){this.options=this.mergeOptions(this.options,t),this.validate()}reset(){this.options={...i.defaults}}export(){return{...this.options}}}class e{constructor(){this.state={filters:{current:new Set(["*"]),groups:new Map,ranges:new Map,dateRanges:new Map,mode:"OR",groupMode:"OR"},search:{query:"",keys:["title"]},sort:{orders:{},current:null},items:{visible:new Set,total:0},pagination:{currentPage:1,itemsPerPage:10,totalPages:0}}}getState(){return this.state}setState(t,i){const e=t.split(".");let s=this.state;for(let t=0;t<e.length-1;t++)e[t]in s||(s[e[t]]={}),s=s[e[t]];s[e[e.length-1]]=i}export(){return{filters:{current:Array.from(this.state.filters.current),groups:Array.from(this.state.filters.groups.entries()),ranges:Array.from(this.state.filters.ranges.entries()),dateRanges:Array.from(this.state.filters.dateRanges.entries()),mode:this.state.filters.mode,groupMode:this.state.filters.groupMode},search:{...this.state.search},sort:{...this.state.sort},pagination:{...this.state.pagination}}}import(t){t.filters&&(this.state.filters.current=new Set(t.filters.current),this.state.filters.groups=new Map(t.filters.groups),this.state.filters.ranges=new Map(t.filters.ranges),this.state.filters.dateRanges=new Map(t.filters.dateRanges),this.state.filters.mode=t.filters.mode,this.state.filters.groupMode=t.filters.groupMode),t.search&&(this.state.search={...t.search}),t.sort&&(this.state.sort={...t.sort}),t.pagination&&(this.state.pagination={...t.pagination})}reset(){this.state={filters:{current:new Set(["*"]),groups:new Map,ranges:new Map,dateRanges:new Map,mode:"OR",groupMode:"OR"},search:{query:"",keys:["title"]},sort:{orders:{},current:null},items:{visible:new Set,total:0},pagination:{currentPage:1,itemsPerPage:10,totalPages:0}}}}class s{constructor(t){this.options=t,this.styleElement=null}createBaseStyles(){const t=this.options.get("hiddenClass")||"hidden",i=this.options.get("itemSelector")||".afs-filter-item",e=this.options.get("filterButtonSelector")||".afs-btn-filter",s=this.options.get("activeClass")||"active",n=this.options.get("animation.duration")||"300ms",r=this.options.get("animation.easing")||"ease-out",a=this.options.get("filterDropdownSelector")||".afs-filter-dropdown",o=this.options.get("styles.colors.primary")||"#000",h=this.options.get("styles.colors.background")||"#e5e7eb",c=this.options.get("styles.colors.text")||"#000",l=this.options.get("styles.colors.textHover")||"#fff",u=this.options.get("styles.button")||{},d=this.options.get("styles.dropdown")||{},p=this.options.get("styles.checkbox")||{},m=this.options.get("styles.radio")||{},f=u.padding||"4px 8px",g=d.padding||"4px 32px 4px 8px",b=u.border||"1px solid "+h,y=d.border||"1px solid "+h,w=u.borderRadius||"4px",$=d.borderRadius||"4px",v=u.fontSize||"14px",S=d.fontSize||"14px",x=u.fontFamily||"inherit",M=d.fontFamily||"inherit",F=u.fontWeight||"normal",C=d.fontWeight||"normal",R=u.lineHeight||"1.5",k=d.lineHeight||"1.5",A=u.letterSpacing||"normal",D=d.letterSpacing||"normal",P=u.textTransform||"none",T=d.textTransform||"none",I=u.boxShadow||"none",E=d.boxShadow||"none",N=u.background||"transparent",O=d.background||"transparent",L=u.color||c,z=d.color||c,U=p.border||"1px solid "+h,q=p.borderRadius||"4px",B=p.background||"transparent",H=p.color||c,Y=p.padding||"8px",_=p.height||"20px",V=p.width||"20px",j=p.activeBorder||"none",X=m.border||"1px solid "+h,G=m.borderRadius||"50%",K=m.background||"transparent",J=m.color||c,W=m.padding||"8px",Q=m.height||"20px",Z=m.width||"20px",tt=m.activeBorder||"none",it=o.match(/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i),et=it?`rgba(${parseInt(it[1],16)}, ${parseInt(it[2],16)}, ${parseInt(it[3],16)}, 0.2)`:"rgba(0, 0, 0, 0.2)",st=`data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='${encodeURIComponent(c)}' d='M6 8L1 3h10z'/%3E%3C/svg%3E`;return`\n /* Hidden state */\n .${t} {\n display: none !important;\n }\n\n /* Filterable items */\n ${i} {\n opacity: 1;\n transform: scale(1);\n filter: blur(0);\n transition: opacity ${n} ${r},\n transform ${n} ${r},\n filter ${n} ${r};\n }\n\n ${i}.${t} {\n opacity: 0;\n transform: scale(0.95);\n filter: blur(5px);\n }\n\n /* Common styles for both buttons and dropdowns */\n ${e} {\n appearance: none;\n -webkit-appearance: none;\n padding: ${f};\n border: ${b};\n border-radius: ${w};\n font-size: ${v};\n font-family: ${x};\n font-weight: ${F};\n letter-spacing: ${A};\n text-transform: ${P};\n background-color: ${N};\n color: ${L};\n cursor: pointer;\n transition: all ${n} ${r};\n line-height: ${R};\n display: inline-flex;\n align-items: center;\n justify-content: center;\n white-space: nowrap;\n margin: 0;\n box-shadow: ${I};\n }\n\n ${a} {\n appearance: none;\n -webkit-appearance: none;\n padding: ${g};\n border: ${y};\n border-radius: ${$};\n font-size: ${S};\n font-family: ${M};\n font-weight: ${C};\n letter-spacing: ${D};\n text-transform: ${T};\n background-color: ${O};\n color: ${z};\n cursor: pointer;\n transition: all ${n} ${r};\n line-height: ${k};\n display: inline-flex;\n align-items: center;\n justify-content: center;\n white-space: nowrap;\n margin: 0;\n box-shadow: ${E};\n }\n\n /* Checkbox styles */\n ${e}[type="checkbox"] {\n position: relative;\n appearance: none;\n -webkit-appearance: none;\n padding: ${Y};\n background-color: ${B};\n color: ${H};\n border: ${U};\n border-radius: ${q};\n height: ${_};\n width: ${V};\n cursor: pointer;\n transition: all ${n} ${r};\n }\n\n ${e}[type="checkbox"]:hover:before {\n position: absolute;\n top: 0;\n left: 0;\n content: "";\n display: block;\n width: 100%;\n height: 100%;\n border-radius: ${q};\n border: ${j};\n }\n \n ${e}.${s}[type="checkbox"]:before {\n position: absolute;\n top: 0;\n left: 0;\n content: "";\n display: block;\n width: 100%;\n height: 100%;\n border-radius: ${q};\n border: ${j};\n }\n\n /* Radio button styles */\n ${e}[type="radio"] {\n position: relative;\n appearance: none;\n -webkit-appearance: none;\n padding: ${W};\n background-color: ${K};\n color: ${J};\n border: ${X};\n border-radius: ${G};\n height: ${Q};\n width: ${Z};\n cursor: pointer;\n transition: all ${n} ${r};\n }\n\n ${e}[type="radio"]:hover:before {\n position: absolute;\n top: 0;\n left: 0;\n content: "";\n display: block;\n width: 100%;\n height: 100%;\n border-radius: ${G};\n border: ${tt};\n }\n \n ${e}.${s}[type="radio"]:before {\n position: absolute;\n top: 0;\n left: 0;\n content: "";\n display: block;\n width: 100%;\n height: 100%;\n border-radius: ${G};\n border: ${tt};\n }\n\n /* Hover state */\n ${e}:hover,{\n border-color: ${o};\n background-color: ${o};\n color: ${l};\n box-shadow: ${u.hover?.boxShadow||d.hover?.boxShadow||"none"};\n }\n\n /* Focus state */\n ${e}:focus,\n ${a}:focus {\n outline: none;\n border-color: ${o};\n box-shadow: 0 0 0 2px ${et};\n }\n\n /* Active state */\n ${e}.${s} {\n background-color: ${o};\n border-color: ${o};\n color: ${l};\n box-shadow: ${u.active?.boxShadow||"none"};\n }\n\n /* Disabled state */\n ${e}:disabled,\n ${a}:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n background-color: ${h};\n }\n\n /* Dropdown specific styles */\n ${a} {\n padding: ${g};\n position: relative;\n background-image: url("${st}");\n background-repeat: no-repeat;\n background-position: right 12px center;\n text-align: left;\n }\n\n /* Mobile optimization */\n @media (max-width: 768px) {\n ${e},\n ${a} {\n width: 100%;\n justify-content: flex-start;\n }\n }\n `}addTransitionStyles(){const t=document.createElement("style");t.textContent="\n .afs-transition {\n transition: opacity 300ms ease-in-out,\n transform 300ms ease-in-out,\n filter 300ms ease-in-out !important;\n }\n .afs-hidden {\n opacity: 0;\n pointer-events: none;\n }\n ",document.head.appendChild(t)}createRangeStyles(){const t=this.options.get("styles"),i=this.options.get("slider")||{},e=t.slider,s=t.colors;return`\n /* Range Slider Styles */\n .${i.containerClass||"afs-range-slider"} {\n position: relative;\n width: auto;\n height: 40px;\n margin: 10px 0;\n padding: 0 8px;\n }\n\n .${i.trackClass||"afs-range-track"} {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n width: 100%;\n height: 4px;\n background: ${e.ui.track.background||s.background};\n border-radius: ${e.ui.track.radius||"0"};\n }\n\n .${i.thumbClass||"afs-range-thumb"} {\n position: absolute;\n top: 50%;\n width: ${e.ui.thumb.size||"16px"};\n height: ${e.ui.thumb.size||"16px"};\n background: ${e.ui.thumb.background||s.primary};\n border-radius: ${e.ui.thumb.radius||"50%"};\n transform: translate(-50%, -50%);\n cursor: pointer;\n z-index: 2;\n }\n\n .${i.valueClass||"afs-range-value"} {\n position: absolute;\n top: -20px;\n transform: translateX(-50%);\n font-size: 10px;\n color: ${s.text};\n }\n\n .${i.selectedClass||"afs-range-selected"} {\n position: absolute;\n height: 4px;\n background: ${e.ui.selected.background||s.primary};\n top: 50%;\n transform: translateY(-50%);\n }\n\n /* Histogram Styles */\n .afs-histogram {\n position: absolute;\n bottom: 22px;\n left: 8px;\n right: 8px;\n height: 20px;\n display: flex;\n align-items: flex-end;\n gap: 1px;\n opacity: 0.5;\n }\n\n .afs-histogram-bar {\n flex: 1;\n background-color: ${e.ui.histogram.background||s.background};\n min-height: 4px;\n transition: background-color 0.2s ease;\n }\n\n .afs-histogram-bar.active {\n background-color: ${e.ui.histogram.bar.background||s.primary};\n }\n `}createDateStyles(){const t=this.options.get("styles").colors;return`\n .afs-date-range-container {\n display: flex;\n flex-wrap: wrap;\n gap: 1rem;\n margin: 10px 0;\n }\n\n .afs-date-input-wrapper {\n flex: 1;\n }\n\n .afs-date-input-wrapper label {\n display: block;\n font-size: 0.875rem;\n color: ${t.text};\n margin-bottom: 0.5rem;\n }\n\n .afs-date-input {\n width: 100%;\n padding: 0.5rem;\n border: 1px solid ${t.background};\n border-radius: 0.25rem;\n font-size: 0.875rem;\n color: ${t.text};\n transition: border-color 0.2s ease;\n }\n\n .afs-date-input:focus {\n outline: none;\n border-color: ${t.primary};\n }\n `}createInputRangeStyles(){const t=this.options.get("styles").colors;return`\n .afs-input-range-container {\n display: flex;\n flex-wrap: wrap;\n gap: 1rem;\n margin: 10px 0;\n }\n\n .afs-input-wrapper {\n flex: 1;\n }\n\n .afs-input-label {\n display: block;\n font-size: 0.875rem;\n color: ${t.text};\n margin-bottom: 0.5rem;\n }\n\n .afs-input {\n width: 100%;\n padding: 0.5rem;\n border: 1px solid ${t.background};\n border-radius: 0.25rem;\n font-size: 0.875rem;\n color: ${t.text};\n transition: border-color 0.2s ease;\n }\n\n .afs-input:focus {\n outline: none;\n border-color: ${t.primary};\n }\n `}applyStyles(){try{const t=`\n \n /* Global transition styles */\n ${this.addTransitionStyles()}\n\n /* Base styles */\n ${this.createBaseStyles()}\n\n /* Range slider styles */\n ${this.createRangeStyles()}\n\n /* Date filter styles */\n ${this.createDateStyles()}\n\n /* Pagination styles */\n ${this.createPaginationStyles()}\n\n /* Search styles */\n ${this.createSearchStyles()}\n\n /* Input range styles */\n ${this.createInputRangeStyles()}\n `;this.styleElement?this.styleElement.textContent=t:(this.styleElement=document.createElement("style"),this.styleElement.textContent=t,document.head.appendChild(this.styleElement))}catch(t){const i=this.createBaseStyles();this.styleElement?this.styleElement.textContent=i:(this.styleElement=document.createElement("style"),this.styleElement.textContent=i,document.head.appendChild(this.styleElement))}}createPaginationStyles(){const t=this.options.get("styles"),i=this.options.get("pagination")||{},e=this.options.get("styles").colors,s=i.pageButtonClass||"afs-page-button",n=t.pagination;return`\n .${i.containerClass||"afs-pagination"} {\n display: flex;\n justify-content: center;\n gap: 8px;\n margin-top: 20px;\n }\n\n .${s} {\n padding: ${n.ui.button.padding||"8px 12px"};\n border: ${n.ui.button.border||"1px solid "+e.primary};\n border-radius: ${n.ui.button.borderRadius||"4px"};\n cursor: pointer;\n transition: all 200ms ease-out;\n background: ${n.ui.button.background||"transparent"};\n color: ${n.ui.button.color||e.primary};\n }\n\n .${s}:hover {\n background: ${n.ui.button.hover.background||e.primary};\n color: ${n.ui.button.hover.color||"white"};\n }\n\n .${s}.${i.activePageClass||"afs-page-active"} {\n background: ${n.ui.button.active.background||e.primary};\n color: ${n.ui.button.active.color||"white"};\n }\n\n .${s}:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n `}createSearchStyles(){const t=this.options.get("searchInputClass")||"afs-search",i=this.options.get("styles").colors;return`\n .${t} {\n padding: 8px;\n border: 1px solid ${i.background};\n border-radius: 4px;\n width: 100%;\n max-width: 300px;\n transition: border-color 200ms ease-out;\n }\n\n .${t}:focus {\n outline: none;\n border-color: ${i.primary};\n }\n `}updateStyles(t){this.options=t,this.applyStyles()}removeStyles(){this.styleElement&&(this.styleElement.remove(),this.styleElement=null)}}class n{constructor(){this.events=new Map,this.onceEvents=new Map}on(t,i){return this.events.has(t)||this.events.set(t,new Set),this.events.get(t).add(i),()=>this.off(t,i)}once(t,i){const e=(...s)=>{this.off(t,e),i.apply(this,s)};return this.onceEvents.has(t)||this.onceEvents.set(t,new Map),this.onceEvents.get(t).set(i,e),this.on(t,e)}off(t,i){if(this.events.has(t)&&(this.events.get(t).delete(i),0===this.events.get(t).size&&this.events.delete(t)),this.onceEvents.has(t)){const e=this.onceEvents.get(t).get(i);e&&(this.events.get(t)?.delete(e),this.onceEvents.get(t).delete(i)),0===this.onceEvents.get(t).size&&this.onceEvents.delete(t)}}emit(t,...i){this.events.has(t)&&this.events.get(t).forEach((t=>{try{t.apply(this,i)}catch(t){}}))}removeAllListeners(t){t?(this.events.delete(t),this.onceEvents.delete(t)):(this.events.clear(),this.onceEvents.clear())}listenerCount(t){return(this.events.get(t)?.size||0)+(this.onceEvents.get(t)?.size||0)}}class r{constructor(t){this.afs=t,this.options=this.afs.options,this.animations={fade:{in:{opacity:1,transform:"scale(1)",transitionTimingFunction:"ease-in"},out:{opacity:0,transform:"scale(0.95)",transitionTimingFunction:"ease-out"}},slide:{in:{opacity:1,transform:"translateY(0)",transitionTimingFunction:"ease-in-out"},out:{opacity:0,transform:"translateY(20px)",transitionTimingFunction:"ease-in-out"}},scale:{in:{opacity:1,transform:"scale(1)",transitionTimingFunction:"ease-in"},out:{opacity:0,transform:"scale(0.8)",transitionTimingFunction:"ease-out"}},rotate:{in:{opacity:1,transform:"rotate(0deg) scale(1)",transitionTimingFunction:"ease-in"},out:{opacity:0,transform:"rotate(90deg) scale(0.9)",transitionTimingFunction:"ease-out"}},flip:{in:{opacity:1,transform:"rotateY(0)",transitionTimingFunction:"ease-in"},out:{opacity:0,transform:"rotateY(180deg)",transitionTimingFunction:"ease-out"}},zoom:{in:{opacity:1,transform:"scale(1.2)",transitionTimingFunction:"ease-in"},out:{opacity:0,transform:"scale(0.8)",transitionTimingFunction:"ease-out"}},bounce:{in:{opacity:1,transform:"translateY(0)",animation:"bounce 1s cubic-bezier(0.68, -0.55, 0.27, 1.55)"},out:{opacity:0,transform:"translateY(-20px)",animation:"bounceOut 1s ease-out"}},blur:{in:{opacity:1,filter:"blur(0)",transitionTimingFunction:"ease-in"},out:{opacity:0,filter:"blur(5px)",transitionTimingFunction:"ease-out"}},skew:{in:{opacity:1,transform:"skew(0deg)",transitionTimingFunction:"ease-in-out"},out:{opacity:0,transform:"skew(10deg)",transitionTimingFunction:"ease-in-out"}},slideInLeft:{in:{opacity:1,transform:"translateX(0)",transitionTimingFunction:"ease-in"},out:{opacity:0,transform:"translateX(-100%)",transitionTimingFunction:"ease-out"}},slideInRight:{in:{opacity:1,transform:"translateX(0)",transitionTimingFunction:"ease-in"},out:{opacity:0,transform:"translateX(100%)",transitionTimingFunction:"ease-out"}},fadeInUp:{in:{opacity:1,transform:"translateY(0)",transitionTimingFunction:"ease-in"},out:{opacity:0,transform:"translateY(10px)",transitionTimingFunction:"ease-out"}},fadeInDown:{in:{opacity:1,transform:"translateY(0)",transitionTimingFunction:"ease-in"},out:{opacity:0,transform:"translateY(-10px)",transitionTimingFunction:"ease-out"}},bounceIn:{in:{opacity:1,transform:"scale(1.05)",transitionTimingFunction:"cubic-bezier(0.68, -0.55, 0.27, 1.55)"},out:{opacity:0,transform:"scale(0.9)",transitionTimingFunction:"ease-out"}}}}applyShowAnimation(t,i="fade"){const e=this.animations[i]?.in||this.animations.fade.in;t.classList.add("afs-transition"),t.style.display=this.afs.filter.getItemDisplayType(t),t.style.visibility="visible",window.innerWidth<=768&&(t.style.filter="none"),requestAnimationFrame((()=>{Object.assign(t.style,{opacity:"0",transform:"scale(0.95)",display:this.afs.filter.getItemDisplayType(t)}),requestAnimationFrame((()=>{Object.assign(t.style,e)}))}));const s=this.afs.options.get("animation.duration")||300;setTimeout((()=>{window.innerWidth<=768?(t.style.transform="",t.style.opacity="1",t.style.filter="none",t.style.transition=""):this.afs.state.getState().items.visible.has(t)&&Object.assign(t.style,{transform:"",opacity:"1",filter:"none",display:this.afs.filter.getItemDisplayType(t)})}),s+50)}applyHideAnimation(t,i="fade"){const e=this.animations[i]?.out||this.animations.fade.out;t.classList.add("afs-transition"),t.style.display=this.afs.filter.getItemDisplayType(t),t.style.visibility="visible",requestAnimationFrame((()=>{Object.assign(t.style,e)}));const s=this.afs.options.get("animation.duration")||300;setTimeout((()=>{this.afs.state.getState().items.visible.has(t)||(t.style.display="none",t.style.visibility="hidden",t.style.opacity="0",t.style.transform="",t.style.filter="none",t.style.transition="")}),s+50)}updateOptions(t){const i=t.duration||300,e=t.timing||"ease-in-out",s=document.querySelector(".afs-transition");s&&(s.textContent=`\n .afs-transition {\n transition: opacity ${i}ms ${e},\n transform ${i}ms ${e},\n filter ${i}ms ${e} !important;\n }\n `)}setAnimation(t){this.animations[t]&&this.afs.options.set("animation.type",t)}}class a{constructor(t){this.afs=t,this.animation=new r(t),this.filterButtons=new Map,this.activeFilters=new Set(["*"]),this.currentFilters=new Set(["*"]),this.filterGroups=new Map,this.sortOrders=new Map,this.itemDisplayTypes=new Map,this.isScrolling=!1,this.scrollTimeout=null,this.setupFilters()}setupFilters(){this.afs.logger.debug("Setting up filters");const t=this.afs.options.get("filterButtonSelector");if(!t)return;document.querySelectorAll(t).forEach((t=>{const i=t.dataset.filter;i?(this.filterButtons.set(t,i),this.bindFilterEvent(t)):this.afs.logger.warn("Filter button missing data-filter attribute:",t)}));const i=this.afs.options.get("filterDropdownSelector");i&&document.querySelectorAll(i).forEach((t=>{this.bindDropdownEvent(t)})),this.afs.items.forEach((t=>{const i=window.getComputedStyle(t);this.itemDisplayTypes.set(t,"none"===i.display?"block":i.display)})),this.afs.logger.debug("Filters initialized")}setLogic(t){if("boolean"==typeof t)this.afs.options.set("filterMode",t?"AND":"OR");else{const i=t.toUpperCase();if(!["OR","AND"].includes(i))return void this.afs.logger.warn("Invalid filter mode: "+t);this.afs.options.set("filterMode",i)}this.afs.logger.debug("Filter logic set to: "+this.afs.options.get("filterMode")),this.applyFilters()}clearAllFilters(){this.afs.logger.debug("Clearing all filters and resetting selects"),this.activeFilters.clear(),this.activeFilters.add("*"),this.filterButtons.forEach(((t,i)=>{i.classList.remove(this.afs.options.get("activeClass"))})),this.filterGroups.clear();const t=this.findAllButton();t&&t.classList.add(this.afs.options.get("activeClass"));const i=this.afs.options.get("filterDropdownSelector")||".afs-filter-dropdown";document.querySelectorAll(i).forEach((t=>{const i=t.getAttribute("data-filter-type")||t.id.replace("Filter","").toLowerCase(),e=Array.from(t.options).find((t=>{const e=t.value;return"*"===e||e===i+":all"||e.endsWith(":all")}));if(e){t.value=e.value;const i=new Event("change",{bubbles:!0,cancelable:!0});t.dispatchEvent(i)}else{t.selectedIndex=0;const i=new Event("change",{bubbles:!0,cancelable:!0});t.dispatchEvent(i)}})),this.sortOrders.clear(),this.applyFilters(),this.afs.urlManager.updateURL(),this.afs.emit("filtersCleared"),this.afs.logger.debug("All filters cleared and selects reset")}bindDropdownEvent(t){this.afs.logger.debug("Binding filter event to dropdown:",t),t.addEventListener("change",(()=>{const i=t.value,[e]=i.split(":");"*"===i||i.endsWith(":all")||this.activeFilters.delete("*"),this.activeFilters.forEach((t=>{t.startsWith(e+":")&&this.activeFilters.delete(t)})),"*"===i||i.endsWith(":all")?0===this.activeFilters.size&&this.activeFilters.add("*"):this.activeFilters.add(i),!i&&t.options.length>0&&(t.selectedIndex=0,this.activeFilters.add(t.options[0].value)),this.applyFilters(),this.afs.urlManager.updateURL(),this.afs.emit("filterChanged",{type:e,value:i||t.options[0]?.value,activeFilters:Array.from(this.activeFilters)})}))}bindFilterEvent(t){this.afs.logger.debug("Binding filter event to button:",t),t.addEventListener("click",(()=>{const i=this.filterButtons.get(t);i&&("*"===i?this.resetFilters():this.toggleFilter(i,t),this.afs.urlManager.updateURL())}))}resetFilters(){this.afs.logger.debug("Resetting filters"),this.activeFilters.clear(),this.filterButtons.forEach(((t,i)=>{i.classList.remove(this.afs.options.get("activeClass"))})),this.activeFilters.add("*");const t=this.findAllButton();t&&t.classList.add(this.afs.options.get("activeClass")),this.filterGroups.clear();const i=[];this.afs.items.forEach((t=>{const e=new Promise((i=>{t.classList.remove(this.afs.options.get("hiddenClass")),requestAnimationFrame((()=>{this.animation.applyShowAnimation(t,this.afs.options.get("animation.type")),setTimeout(i,this.afs.options.get("animation.duration")||300)}))}));i.push(e)}));const e=new Set(this.afs.items);this.afs.state.setState("items.visible",e),Promise.all(i).then((()=>{this.afs.updateCounter(),this.afs.urlManager.updateURL(),this.afs.emit("filtersReset")}))}findAllButton(){for(const[t,i]of this.filterButtons.entries())if("*"===i)return t;return null}handleFilterClick(t){const i=t.dataset.filter;this.afs.logger.debug("Filter clicked:",i),"*"===i?this.resetFilters():this.toggleFilter(i,t),this.filter(),this.updateURL()}toggleFilter(t,i){this.activeFilters.delete("*");const e=this.findAllButton();if(e&&e.classList.remove(this.afs.options.get("activeClass")),"radio"===i.type||"radio"===i.getAttribute("type")){const e=i.name||i.getAttribute("name");e&&document.querySelectorAll(`input[name="${e}"]`).forEach((t=>{t.classList.remove(this.afs.options.get("activeClass"));const i=this.filterButtons.get(t);i&&this.activeFilters.delete(i)})),i.classList.add(this.afs.options.get("activeClass")),this.activeFilters.add(t)}else if(i.classList.contains(this.afs.options.get("activeClass"))){if(i.classList.remove(this.afs.options.get("activeClass")),this.activeFilters.delete(t),0===this.activeFilters.size)return void this.resetFilters()}else i.classList.add(this.afs.options.get("activeClass")),this.activeFilters.add(t);this.applyFilters(),this.afs.emit("filterToggled",{filter:t,activeFilters:Array.from(this.activeFilters)}),this.afs.logger.debug("Filter toggled:",t)}applyFilters(){const t=Array.from(this.activeFilters);this.afs.logger.debug("Active filters:",t);const i=new Set(this.afs.state.getState().items.visible),e=new Set;this.afs.items.forEach((t=>{(this.activeFilters.has("*")||this.itemMatchesFilters(t))&&e.add(t)})),this.afs.state.setState("items.visible",e),this.activeFilters.has("*");const s=[];this.afs.items.forEach((t=>{const i=new Promise((i=>{e.has(t)?(t.classList.remove(this.afs.options.get("hiddenClass")),t.style.display=this.getItemDisplayType(t),requestAnimationFrame((()=>{this.animation.applyShowAnimation(t,this.afs.options.get("animation.type")),setTimeout(i,parseFloat(this.afs.options.get("animation.duration"))||300)}))):(t.classList.add(this.afs.options.get("hiddenClass")),t.style.display="none",requestAnimationFrame((()=>{this.animation.applyHideAnimation(t,this.afs.options.get("animation.type")),setTimeout(i,parseFloat(this.afs.options.get("animation.duration"))||300)})))}));s.push(i)})),Promise.all(s).then((()=>{this.afs.items.forEach((t=>{e.has(t)?(this.showItem(t),t.style.display=this.getItemDisplayType(t),t.style.opacity="1"):(t.style.display="none",t.classList.add(this.afs.options.get("hiddenClass")))})),this.afs.updateCounter(),this.afs.urlManager.updateURL(),this.afs.emit("filtersApplied",{activeFilters:t,visibleItems:e.size})})),this.emitFilterEvents(i,e)}itemMatchesFilters(t){if(this.activeFilters.has("*"))return!0;const i=new Set(t.dataset.categories?.split(" ")||[]);return"AND"===(this.afs.options.get("filterMode")||"OR")?this.itemMatchesAllFilters(i):this.itemMatchesAnyFilter(i)}itemMatchesAnyFilter(t){return Array.from(this.activeFilters).some((i=>"*"===i||t.has(i)))}itemMatchesAllFilters(t){return Array.from(this.activeFilters).every((i=>"*"===i||t.has(i)))}itemMatchesFilterGroups(t){const i=Array.from(this.filterGroups.values()).map((i=>0===i.filters.size||("OR"===i.operator?Array.from(i.filters).some((i=>t.has(i))):Array.from(i.filters).every((i=>t.has(i))))));return"OR"===this.afs.options.get("groupMode")?i.some((t=>t)):i.every((t=>t))}emitFilterEvents(t,i){const e=new Set([...i].filter((i=>!t.has(i)))),s=new Set([...t].filter((t=>!i.has(t))));this.afs.emit("filter",{activeFilters:Array.from(this.activeFilters),visibleItems:i.size,added:e.size,removed:s.size}),e.size>0&&this.afs.emit("itemsShown",{items:e}),s.size>0&&this.afs.emit("itemsHidden",{items:s})}addFilterGroup(t,i,e="OR"){if(this.afs.logger.debug("Adding filter group: "+t),!Array.isArray(i))return void this.afs.logger.error("Filters must be an array");const s=e.toUpperCase();["AND","OR"].includes(s)||(this.afs.logger.warn(`Invalid operator: ${e}, defaulting to OR`),e="OR"),this.filterGroups.set(t,{filters:new Set(i),operator:s}),this.applyFilters()}removeFilterGroup(t){this.filterGroups.delete(t)&&(this.afs.logger.debug("Removed filter group: "+t),0===this.filterGroups.size?this.resetFilters():this.applyFilters())}setGroupMode(t){const i=t.toUpperCase();["AND","OR"].includes(i)?(this.afs.options.set("groupMode",i),this.afs.logger.debug("Set group mode to: "+i),this.applyFilters()):this.afs.logger.warn("Invalid group mode: "+t)}addFilter(t){if(this.afs.logger.debug("Adding filter: "+t),"*"===t)return void this.resetFilters();const[i]=t.split(":");this.activeFilters.forEach((t=>{t.startsWith(i+":")&&this.activeFilters.delete(t)})),this.activeFilters.delete("*"),this.activeFilters.add(t),this.filterButtons.forEach(((i,e)=>{i===t?e.classList.add(this.afs.options.get("activeClass")):"*"===i&&e.classList.remove(this.afs.options.get("activeClass"))})),this.applyFilters()}removeFilter(t){this.afs.logger.debug("Removing filter: "+t),this.activeFilters.delete(t),this.filterButtons.forEach(((i,e)=>{i===t&&e.classList.remove(this.afs.options.get("activeClass"))})),0===this.activeFilters.size?this.resetFilters():this.applyFilters()}setFilterMode(t){this.afs.logger.debug("Setting filter mode to: "+t);const i=t.toUpperCase();["AND","OR"].includes(i)?(this.afs.options.set("filterMode",i),this.afs.logger.debug("Set filter mode to: "+i),this.applyFilters()):this.afs.logger.warn("Invalid filter mode: "+t)}getActiveFilters(){return new Set(this.activeFilters)}getFilterGroups(){return new Map(this.filterGroups)}addFilterButton(t,i){i?(this.filterButtons.set(t,i),this.bindFilterEvent(t),this.afs.logger.debug("Added filter button for: "+i)):this.afs.logger.warn("Filter value required for new filter button")}sortWithOrder(t){this.afs.logger.debug("Sorting by "+t);try{const i=Array.from(this.afs.items),e="asc"===(this.sortOrders.get(t)||"asc")?"desc":"asc";this.sortOrders.set(t,e);const s=this.determineSortType(i[0],t);return i.sort(((i,n)=>{const r=this.getSortValue(i,t,s),a=this.getSortValue(n,t,s);return this.compareValues(r,a,e)})),this.reorderItems(i),this.afs.emit("sort",{key:t,order:e}),this.afs.logger.info(`Sorted items by ${t} in ${e} order`),e}catch(t){return this.afs.logger.error("Sort error:",t),null}}shuffle(){this.afs.logger.debug("Shuffling items");try{const t=Array.from(this.afs.items);for(let i=t.length-1;i>0;i--){const e=Math.floor(Math.random()*(i+1));[t[i],t[e]]=[t[e],t[i]]}this.reorderItems(t),this.sortOrders.clear(),this.afs.emit("shuffled",{itemCount:t.length}),this.afs.logger.debug("Items shuffled successfully")}catch(t){this.afs.logger.error("Shuffle error:",t)}}determineSortType(t,i){this.afs.logger.debug("Determining sort type for "+i);const e=t.dataset[i];return e?isNaN(e)?/^\d{4}-\d{2}-\d{2}/.test(e)?"date":"string":"number":"string"}getSortValue(t,i,e){const s=t.dataset[i];switch(e){case"number":return parseFloat(s)||0;case"date":return new Date(s).getTime()||0;default:return(s||"").toLowerCase()}}compareValues(t,i,e){if(null==t)return"asc"===e?1:-1;if(null==i)return"asc"===e?-1:1;const s=t<i?-1:t>i?1:0;return"asc"===e?s:-s}reorderItems(t){const i=document.createDocumentFragment();t.forEach((t=>i.appendChild(t))),this.afs.container.appendChild(i)}getCurrentSortOrder(t){return this.sortOrders.get(t)||"asc"}clearSortOrders(){this.afs.logger.debug("Clearing all sort orders"),this.sortOrders.clear(),this.afs.emit("sortCleared")}clearAllFilters(){this.afs.logger.debug("Clearing all filters and search"),this.activeFilters.clear(),this.activeFilters.add("*"),this.filterButtons.forEach(((t,i)=>{i.classList.remove(this.afs.options.get("activeClass"))}));const t=this.findAllButton();t&&t.classList.add(this.afs.options.get("activeClass")),document.querySelectorAll(this.afs.options.get("filterButtonSelector")+'[type="checkbox"]').forEach((t=>{t.classList.contains(this.afs.options.get("activeClass"))&&(t.checked=!1,t.classList.remove(this.afs.options.get("activeClass")))})),this.afs.search&&this.afs.search.setValue(""),this.filterGroups.clear(),this.sortOrders.clear(),this.applyFilters(),this.afs.urlManager&&this.afs.urlManager.updateURL(),this.afs.emit("filtersCleared"),this.afs.logger.info("All filters cleared")}refresh(){this.afs.logger.debug("Refreshing view"),this.applyFilters(),this.afs.updateCounter()}removeFilterButton(t){this.filterButtons.delete(t),t.removeEventListener("click",this.handleFilterClick)}destroy(){this.filterButtons.forEach(((t,i)=>{this.removeFilterButton(i)})),this.filterButtons.clear(),this.activeFilters.clear(),this.filterGroups.clear(),this.afs.logger.debug("Filter functionality destroyed")}getItemDisplayType(t){return this.itemDisplayTypes.get(t)||"block"}showItem(t){t.classList.remove(this.afs.options.get("hiddenClass"));const i=this.getItemDisplayType(t);"none"===t.style.display&&(t.style.display=i&&"none"!==i?i:""),t.style.opacity="1",t.style.visibility="visible",t.style.filter="none",t.style.transform=""}}const o=(t,i,e=!1)=>{let s;return function(...n){const r=this,a=e&&!s;clearTimeout(s),s=setTimeout((()=>{s=null,e||t.apply(r,n)}),i),a&&t.apply(r,n)}};class h{constructor(t){this.afs=t,this.activeRanges=new Map}addInputRange({key:t,container:i,min:e,max:s,step:n=1,label:r=""}){if(this.afs.logger.debug("Adding input range for "+t),!i)return void this.afs.logger.error("Container element required for input range");const a=this.calculateMinMax(t);e=e??a.min,s=s??a.max;const o=this.createInputElements(r),h=this.initializeState(e,s,n);this.appendElements(i,o),this.setupEventHandlers(o,h,t),this.activeRanges.set(t,{state:h,elements:o}),this.updateInputUI(t),this.afs.logger.info("Input range added for "+t)}calculateMinMax(t){try{const i=Array.from(this.afs.items).map((i=>{if(!i||!i.dataset||!i.dataset[t])return null;const e=parseFloat(i.dataset[t]);return isNaN(e)?null:e})).filter((t=>null!==t));return 0===i.length?{min:0,max:100}:{min:Math.min(...i),max:Math.max(...i)}}catch(t){return this.afs.logger.error("Error calculating range:",t),{min:0,max:100}}}createInputElements(t){const i=document.createElement("div");if(i.className="afs-input-range-container",t){const e=document.createElement("div");e.className="afs-input-range-label",e.textContent=t,i.appendChild(e)}const e=document.createElement("div");e.className="afs-input-wrapper";const s=document.createElement("label");s.textContent="Min",s.className="afs-input-label";const n=document.createElement("input");n.type="number",n.className="afs-input min",e.appendChild(s),e.appendChild(n);const r=document.createElement("div");r.className="afs-input-wrapper";const a=document.createElement("label");a.textContent="Max",a.className="afs-input-label";const o=document.createElement("input");return o.type="number",o.className="afs-input max",r.appendChild(a),r.appendChild(o),i.appendChild(e),i.appendChild(r),{container:i,minInput:n,maxInput:o}}initializeState(t,i,e){return{min:t,max:i,step:e,currentMin:t,currentMax:i}}appendElements(t,i){t.appendChild(i.container)}setupEventHandlers(t,i,e){const{minInput:s,maxInput:n}=t,r=o((()=>{const t=parseFloat(s.value),r=parseFloat(n.value);isNaN(t)||isNaN(r)||(i.currentMin=Math.max(i.min,Math.min(r,t)),i.currentMax=Math.min(i.max,Math.max(t,r)),this.updateInputUI(e),this.applyFilter(e))}),300);s.addEventListener("input",r),n.addEventListener("input",r)}updateInputUI(t){try{const{state:i,elements:e}=this.activeRanges.get(t),{minInput:s,maxInput:n}=e;s.min=i.min,s.max=i.max,s.step=i.step,n.min=i.min,n.max=i.max,n.step=i.step,s.value=i.currentMin,n.value=i.currentMax}catch(t){this.afs.logger.error("Error updating input UI:",t)}}applyFilter(t){this.afs.logger.info("Applying input filter for "+t);const{state:i}=this.activeRanges.get(t);this.afs.items.forEach((e=>{try{if(!e||!e.dataset||!e.dataset[t])return void this.afs.hideItem(e);const s=parseFloat(e.dataset[t]);if(isNaN(s))return void this.afs.hideItem(e);s>=i.currentMin&&s<=i.currentMax?this.afs.showItem(e):this.afs.hideItem(e)}catch(t){this.afs.logger.error("Error filtering item:",t),this.afs.hideItem(e)}})),this.afs.updateCounter(),this.afs.urlManager.updateURL(),this.afs.emit("inputRangeFilter",{key:t,min:i.currentMin,max:i.currentMax})}getRange(t){const i=this.activeRanges.get(t);return i?{min:i.state.currentMin,max:i.state.currentMax}:null}setRange(t,i,e){const s=this.activeRanges.get(t);s&&(s.state.currentMin=i,s.state.currentMax=e,this.updateInputUI(t),this.applyFilter(t))}removeInputRange(t){const i=this.activeRanges.get(t);i&&(i.elements.container.remove(),this.activeRanges.delete(t),this.afs.logger.info("Input range removed for "+t))}}class c{constructor(t){this.afs=t,this.searchInput=null,this.searchKeys=["title"],this.minSearchLength=2,this.highlightClass="afs-highlight",this.setupSearch()}setupSearch(){const t=this.afs.options.get("searchInputSelector");t&&(this.searchInput=document.querySelector(t),this.searchInput?(this.searchKeys=this.afs.options.get("searchKeys")||this.searchKeys,this.minSearchLength=this.afs.options.get("minSearchLength")||this.minSearchLength,this.bindSearchEvents(),this.afs.logger.debug("Search functionality initialized")):this.afs.logger.warn("Search input not found: "+t))}bindSearchEvents(){if(!this.searchInput)return;const t=o((t=>{this.search(t.target.value)}),this.afs.options.get("debounceTime")||300);this.searchInput.addEventListener("input",t),this.searchInput.addEventListener("search",(t=>{t.target.value||this.clearSearch()})),this.searchInput.addEventListener("keypress",(t=>{"Enter"===t.key&&(t.preventDefault(),this.search(t.target.value))}))}search(t){this.afs.logger.debug("Performing search:",t);const i=this.normalizeQuery(t);this.afs.state.setState("search.query",i);let e=0;if(i)if(i.length<this.minSearchLength)this.afs.logger.debug("Search query too short");else try{const t=this.createSearchRegex(i),s=[];this.afs.items.forEach((i=>{const n=this.getItemSearchText(i),r=t.test(n),a=new Promise((s=>{r?(this.afs.showItem(i),this.highlightMatches(i,t),e++):(this.afs.hideItem(i),this.removeHighlights(i)),setTimeout(s,this.afs.options.get("animation.duration")||300)}));s.push(a)})),Promise.all(s).then((()=>{this.afs.items.forEach((t=>{this.afs.state.getState().items.visible.has(t)?(t.style.display="",t.style.opacity="1"):t.style.display="none"})),this.afs.urlManager.updateURL(),this.afs.emit("search",{query:i,matches:e,total:this.afs.items.length}),this.afs.updateCounter(),this.afs.logger.info(`Search complete. Found ${e} matches`)}))}catch(t){this.afs.logger.error("Search error:",t)}else this.clearSearch()}normalizeQuery(t){return t.toLowerCase().trim().replace(/\s+/g," ")}createSearchRegex(t){const i=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").split(" ").filter(Boolean).map((t=>`(?=.*${t})`)).join("");return RegExp(i,"i")}getItemSearchText(t){return this.searchKeys.map((i=>t.dataset[i]||"")).join(" ").toLowerCase()}highlightMatches(t,i){this.afs.options.get("highlightMatches")&&this.searchKeys.forEach((i=>{const e=t.querySelector(`[data-search-key="${i}"]`);if(!e)return;const s=e.textContent,n=this.afs.state.getState().search.query.split(" ");let r=s;n.forEach((t=>{if(!t)return;const i=RegExp(`(${t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")})`,"gi");r=r.replace(i,`<span class="${this.highlightClass}">$1</span>`)})),e.innerHTML=r}))}removeHighlights(t){this.afs.options.get("highlightMatches")&&this.searchKeys.forEach((i=>{const e=t.querySelector(`[data-search-key="${i}"]`);e&&e.querySelectorAll("."+this.highlightClass).forEach((t=>{t.replaceWith(t.textContent)}))}))}clearSearch(){this.afs.logger.debug("Clearing search"),this.searchInput&&(this.searchInput.value=""),this.afs.state.setState("search.query","");const t=[];this.afs.items.forEach((i=>{const e=new Promise((t=>{this.afs.showItem(i),this.removeHighlights(i),setTimeout(t,this.afs.options.get("animation.duration")||300)}));t.push(e)})),Promise.all(t).then((()=>{this.afs.items.forEach((t=>{t.style.display="",t.style.opacity="1"})),this.afs.urlManager.updateURL(),this.afs.emit("searchCleared"),this.afs.updateCounter()}))}setValue(t){this.searchInput&&(this.searchInput.value=t),this.search(t)}getValue(){return this.afs.state.getState().search.query}updateConfig({searchKeys:t,minSearchLength:i,highlightClass:e,debounceTime:s}={}){t&&(this.searchKeys=t),i&&(this.minSearchLength=i),e&&(this.highlightClass=e),s&&this.bindSearchEvents()}destroy(){this.searchInput&&(this.searchInput.removeEventListener("input",this.debouncedSearch),this.searchInput.removeEventListener("search",this.handleClear),this.searchInput.removeEventListener("keypress",this.handleEnter)),this.clearSearch()}}class l{constructor(t){this.afs=t,this.sortButtons=new Map,this.setupSort()}setupSort(){const t=this.afs.options.get("sortButtonSelector");if(!t)return;const i=document.querySelectorAll(t);0!==i.length?(i.forEach((t=>{const i=t.dataset.sortKey;i?(this.sortButtons.set(t,{key:i,direction:t.dataset.sortDirection||"asc"}),this.bindSortEvent(t)):this.afs.logger.warn("Sort button missing data-sort-key attribute:",t)})),this.afs.logger.debug("Sort functionality initialized")):this.afs.logger.warn("No sort buttons found with selector:",t)}bindSortEvent(t){this.afs.logger.debug("Binding sort event to button:",t),t.addEventListener("click",(()=>{const i=this.sortButtons.get(t);i&&(i.direction="asc"===i.direction?"desc":"asc",this.sortButtons.set(t,i),this.updateSortButtonState(t,i),this.sort(i.key,i.direction))}))}updateSortButtonState(t,i){this.sortButtons.forEach(((t,i)=>{i.classList.remove(this.afs.options.get("activeSortClass"))})),t.classList.add(this.afs.options.get("activeSortClass"));const e=t.querySelector(".sort-direction");e&&(e.textContent="asc"===i.direction?"↑":"↓")}sort(t,i="asc"){this.afs.logger.debug(`Sorting by ${t} in ${i} order`);try{if(!t)throw Error("Sort key is required");["asc","desc"].includes(i.toLowerCase())||(this.afs.logger.warn(`Invalid sort direction: ${i}, defaulting to "asc"`),i="asc"),this.afs.state.setState("sort.current",{key:t,direction:i});const e=Array.from(this.afs.items);if(0===e.length)return this.afs.logger.info("No items to sort"),!0;const s=this.determineSortType(e[0],t);return this.afs.logger.debug(`Determined sort type for key "${t}": ${s}`),e.sort(((e,n)=>{try{const r=this.getSortValue(e,t,s),a=this.getSortValue(n,t,s);return this.compareValues(r,a,i)}catch(t){return this.afs.logger.error("Error during sort comparison:",t),0}})),this.reorderItems(e),this.afs.urlManager.updateURL(),this.afs.emit("sort",{key:t,direction:i,sortType:s,itemCount:e.length}),this.afs.logger.info(`Sorted ${e.length} items by ${t} ${i} (${s})`),!0}catch(t){return this.afs.logger.error("Sort error:",t),!1}}determineSortType(t,i){if(!t)return this.afs.logger.warn("Cannot determine sort type: No items available for key "+i),"string";if(!t.dataset||!(i in t.dataset))return this.afs.logger.warn("Item missing dataset key: "+i,t),"string";const e=t.dataset[i];return null==e||""===e?"string":!isNaN(parseFloat(e))&&isFinite(e)?"number":/^\d{4}-\d{2}-\d{2}/.test(e)?isNaN(new Date(e).getTime())?"string":"date":"string"}getSortValue(t,i,e){if(!t)return this.afs.logger.warn("Undefined item in getSortValue"),null;if(!t.dataset||!{}.hasOwnProperty.call(t.dataset,i))return this.afs.logger.warn(`Missing data attribute: ${i} on item`,t),"number"===e||"date"===e?0:"";const s=t.dataset[i];if(null==s||""===s)return"number"===e||"date"===e?0:"";switch(e){case"number":const t=parseFloat(s);return isNaN(t)?0:t;case"date":const i=new Date(s).getTime();return isNaN(i)?0:i;default:return(s+"").toLowerCase()}}compareValues(t,i,e){const s="asc"===e?1:-1;return t===i?0:null==t?1:null==i?-1:t>i?s:-s}reorderItems(t){const i=this.afs.options.get("containerSelector"),e=document.querySelector(i);if(!e)return void this.afs.logger.error("Container not found:",i);let s=!1;for(let i=0;i<t.length-1;i++)if(!(t[i].compareDocumentPosition(t[i+1])&Node.DOCUMENT_POSITION_FOLLOWING)){s=!0;break}if(!s)return void this.afs.logger.debug("Items already in correct order, skipping DOM operations");const n=document.createDocumentFragment();t.forEach((t=>n.appendChild(t))),e.appendChild(n),this.afs.logger.debug(`Reordered ${t.length} items`)}sortMultiple(t){this.afs.logger.debug("Sorting by multiple criteria:",t);try{if(!Array.isArray(t)||0===t.length)throw Error("Sort criteria must be a non-empty array");t.forEach(((t,i)=>{if(!t.key)throw Error(`Sort criterion at index ${i} missing key property`);t.direction&&!["asc","desc"].includes(t.direction.toLowerCase())&&(this.afs.logger.warn(`Invalid sort direction in criterion ${i}: ${t.direction}, defaulting to "asc"`),t.direction="asc")}));const i=Array.from(this.afs.items);if(0===i.length)return this.afs.logger.info("No items to sort"),!0;const e={};return t.forEach((t=>{e[t.key]||(e[t.key]=this.determineSortType(i[0],t.key))})),i.sort(((i,s)=>{for(const{key:n,direction:r="asc"}of t)try{const t=e[n]||"string",a=this.getSortValue(i,n,t),o=this.getSortValue(s,n,t),h=this.compareValues(a,o,r);if(0!==h)return h}catch(t){this.afs.logger.error(`Error comparing values for key ${n}:`,t)}return 0})),this.reorderItems(i),t.length>0&&this.afs.state.setState("sort.current",t[0]),this.afs.urlManager.updateURL(),this.afs.emit("multiSort",{criteria:t,itemCount:i.length,sortTypes:e}),this.afs.logger.info(`Multi-sorted ${i.length} items with ${t.length} criteria`),!0}catch(t){return this.afs.logger.error("Multiple sort error:",t),!1}}sortWithComparator(t,i){this.afs.logger.debug(`Sorting by ${t} with custom comparator`);try{if(!t)throw Error("Sort key is required");if("function"!=typeof i)throw Error("Comparator must be a function");const e=Array.from(this.afs.items);return 0===e.length?(this.afs.logger.info("No items to sort"),!0):(e.sort(((e,s)=>{try{return e.dataset&&s.dataset&&t in e.dataset&&t in s.dataset?i(e.dataset[t],s.dataset[t]):(this.afs.logger.warn(`Missing data attribute ${t} in one or both items being compared`),0)}catch(t){return this.afs.logger.error("Error in custom comparator:",t),0}})),this.reorderItems(e),this.afs.emit("customSort",{key:t,comparatorName:i.name||"anonymous",itemCount:e.length}),this.afs.logger.info(`Custom sorted ${e.length} items by ${t}`),!0)}catch(t){return this.afs.logger.error("Custom sort error:",t),!1}}shuffle(){this.afs.logger.debug("Shuffling items");try{const t=Array.from(this.afs.items);if(0===t.length)return this.afs.logger.info("No items to shuffle"),!0;if(1===t.length)return this.afs.logger.info("Only one item to shuffle, no change needed"),!0;this.afs.logger.debug(`Shuffling ${t.length} items`);for(let i=t.length-1;i>0;i--){const e=Math.floor(Math.random()*(i+1));[t[i],t[e]]=[t[e],t[i]]}return this.reorderItems(t),this.afs.state.setState("sort.current",null),this.sortButtons.forEach(((t,i)=>{i.classList.remove(this.afs.options.get("activeSortClass"));const e=i.querySelector(".sort-direction");e&&(e.textContent="")})),this.afs.urlManager.updateURL(),this.afs.emit("shuffle",{itemCount:t.length}),this.afs.logger.info(`Shuffled ${t.length} items`),!0}catch(t){return this.afs.logger.error("Shuffle error:",t),!1}}reset(){this.afs.logger.debug("Resetting sort");try{this.afs.state.setState("sort.current",null),this.sortButtons.forEach(((t,i)=>{i.classList.remove(this.afs.options.get("activeSortClass"));const e=i.querySelector(".sort-direction");e&&(e.textContent="")}));let t=0;return this.sortButtons.forEach(((i,e)=>{i.direction="asc",this.sortButtons.set(e,i),t++})),this.afs.urlManager.updateURL(),this.afs.emit("sortReset",{buttonCount:t}),this.afs.logger.info(`Sort reset: ${t} sort buttons reset to default state`),!0}catch(t){return this.afs.logger.error("Sort reset error:",t),!1}}getCurrentSort(){return this.afs.state.getState().sort.current}addSortButton(t,i,e="asc"){i?(this.sortButtons.set(t,{key:i,direction:e}),this.bindSortEvent(t),this.afs.logger.debug("Added sort button for "+i)):this.afs.logger.warn("Sort key required for new sort button")}removeSortButton(t){this.sortButtons.has(t)&&(t.removeEventListener("click",this.bindSortEvent),this.sortButtons.delete(t),this.afs.logger.debug("Removed sort button"))}destroy(){this.sortButtons.forEach(((t,i)=>{this.removeSortButton(i)})),this.sortButtons.clear(),this.afs.logger.debug("Sort functionality destroyed")}}class u{constructor(t){this.afs=t,this.container=null,this.animation=new r(t),this.options=this.afs.options.get("pagination"),this.setupPagination()}setupPagination(){if(this.afs.logger.debug("Setting up pagination"),!this.afs.options.get("pagination.enabled"))return void this.afs.state.setState("pagination",{currentPage:1,itemsPerPage:this.options.itemsPerPage||10,totalPages:1});this.container=document.createElement("div"),this.container.className=this.options.containerClass;const t=document.querySelector(this.afs.options.get("pagination.container"));t?(t.appendChild(this.container),this.afs.state.setState("pagination",{currentPage:1,itemsPerPage:this.options.itemsPerPage,totalPages:0}),this.bindEvents(),this.update(),this.afs.logger.debug("Pagination initialized")):this.afs.logger.error("Items container not found.")}bindEvents(){this.afs.options.get("pagination.enabled")&&this.container&&(this.afs.on("filter",(()=>this.update())),this.afs.on("search",(()=>this.update())),this.afs.on("sort",(()=>this.update())),this.container.addEventListener("click",(t=>{const i=t.target.closest("button");if(!i)return;const e=i.dataset.page;e&&this.goToPage(parseInt(e,10))})))}update(){if(!this.afs.options.get("pagination.enabled"))return void this.showAllItems();const t=Array.from(this.afs.state.getState().items.visible),i=this.afs.state.getState().pagination.itemsPerPage,e=Math.max(1,Math.ceil(t.length/i)),s=this.afs.state.getState().pagination;let n=s.currentPage;n>e&&(n=e),this.afs.state.setState("pagination",{...s,currentPage:n,totalPages:e}),this.updateVisibility(t),this.container&&this.renderPagination(),this.afs.urlManager.updateURL(),this.afs.emit("pagination",{currentPage:n,totalPages:e,itemsPerPage:i,visibleItems:t.length})}updateVisibility(t){if(!this.afs.options.get("pagination.enabled"))return void this.showAllItems();const{currentPage:i,itemsPerPage:e}=this.afs.state.getState().pagination,s=(i-1)*e,n=s+e;this.afs.items.forEach((t=>{t.style.display="none",t.classList.add(this.afs.options.get("hiddenClass"))}));const r=t.slice(s,n);0===r.length&&t.length>0?this.goToPage(1):requestAnimationFrame((()=>{r.forEach((t=>{t.style.display="",t.classList.remove(this.afs.options.get("hiddenClass")),requestAnimationFrame((()=>{this.animation.applyShowAnimation(t,this.options.animationType||"fade")}))}))}))}renderPagination(){if(!this.container||!this.afs.options.get("pagination.enabled"))return;const{currentPage:t,totalPages:i}=this.afs.state.getState().pagination;if(this.container.innerHTML="",i<=1)return void(this.container.style.display="none");this.container.style.display="flex";const e=this.createPaginationControls(t,i);this.container.appendChild(e)}createPaginationControls(t,i){const e=document.createDocumentFragment();if(this.options.showPrevNext){const i=this.createPageButton("‹",t-1,{disabled:1===t,class:"afs-pagination-prev"});e.appendChild(i)}e.appendChild(this.createPageButton("1",1,{active:1===t}));const s=this.calculatePageRange(t,i);s.start>2&&e.appendChild(this.createEllipsis());for(let n=s.start;n<=s.end;n++)1!==n&&n!==i&&e.appendChild(this.createPageButton(""+n,n,{active:t===n}));if(s.end<i-1&&e.appendChild(this.createEllipsis()),i>1&&e.appendChild(this.createPageButton(""+i,i,{active:t===i})),this.options.showPrevNext){const s=this.createPageButton("›",t+1,{disabled:t===i,class:"afs-pagination-next"});e.appendChild(s)}return e}createPageButton(t,i,{active:e=!1,disabled:s=!1,class:n=""}={}){const r=document.createElement("button");return r.textContent=t,r.dataset.page=i,r.classList.add(this.options.pageButtonClass||"afs-page-button"),n&&r.classList.add(n),e&&r.classList.add(this.options.activePageClass||"afs-page-active"),s&&(r.disabled=!0),r}createEllipsis(){const t=document.createElement("span");return t.textContent="...",t.classList.add("afs-pagination-ellipsis"),t}calculatePageRange(t,i){const e=this.options.maxButtons||7;let s=Math.max(2,t-Math.floor((e-3)/2)),n=Math.min(i-1,s+e-3);return n-s<e-3&&(s=Math.max(2,n-(e-3))),{start:s,end:n}}goToPage(t){const i=this.afs.state.getState().pagination,e=Math.max(1,Math.min(t,i.totalPages));e!==i.currentPage&&(this.afs.state.setState("pagination.currentPage",e),this.update(),this.options.scrollToTop&&window.innerWidth>768&&setTimeout((()=>this.scrollToTop()),100),this.afs.emit("pageChanged",{previousPage:i.currentPage,currentPage:e,totalPages:i.totalPages}))}scrollToTop(){const t=document.querySelector(this.afs.options.get("pagination.container"));t?window.scrollTo({top:t.offsetTop-this.options.scrollOffset,behavior:"smooth"}):this.afs.logger.warn("Scroll container not found.")}setPaginationMode(t){this.afs.logger.debug("Setting pagination mode to: "+t),this.afs.options.set("pagination.enabled",t),t?this.setupPagination():(this.container.remove(),this.showAllItems()),this.afs.emit("paginationModeChanged",{enabled:t})}showAllItems(){try{const t=Array.from(this.afs.state.getState().items.visible),i=window.innerWidth<=768;requestAnimationFrame((()=>{t.forEach((t=>{t.style.display="",t.classList.remove(this.afs.options.get("hiddenClass")),i?(t.style.opacity="1",t.style.transform="",t.style.filter="none"):requestAnimationFrame((()=>{this.animation.applyShowAnimation(t,this.options?.animationType||"fade")}))})),i&&setTimeout((()=>{t.forEach((t=>{t.style.opacity="1",t.style.transform="",t.style.filter="none"}))}),50)}))}catch(t){this.afs.logger.error("Error in showAllItems:",t),this.afs.items.forEach((t=>{this.afs.state.getState().items.visible.has(t)&&(t.style.display="",t.classList.remove(this.afs.options.get("hiddenClass")),t.style.opacity="1",t.style.filter="none")}))}}}class d{constructor(t){this.afs=t,this.defaultParams=new URLSearchParams,this.setupPopStateHandler()}initialize(){this.loadFromURL()}setupPopStateHandler(){window.addEventListener("popstate",(()=>{this.loadFromURL()}))}updateURL(){this.afs.logger.debug("Updating URL state");const t=new URLSearchParams,i=this.afs.state.getState(),e=this.afs.filter.getActiveFilters();i.filters.current=e,this.addFiltersToURL(t,i),this.addRangesToURL(t,i),this.addSearchToURL(t,i),this.addSortToURL(t,i),this.addPaginationToURL(t,i),this.pushState(t)}addFiltersToURL(t,i){const e=i.filters;if(0===e.current.size||1===e.current.size&&e.current.has("*"))return;const s={};for(const t of e.current)if("*"!==t){const[i,e]=t.split(":");s[i]||(s[i]=new Set),s[i].add(e)}Object.entries(s).forEach((([i,e])=>{t.set(i,Array.from(e).join(","))})),"OR"!==e.mode&&t.set("filterMode",e.mode.toLowerCase()),e.groups.size>0&&"OR"!==e.groupMode&&t.set("groupMode",e.groupMode.toLowerCase()),e.groups.forEach(((i,e)=>{t.set("group_"+e,Array.from(i.filters).join(",")),"OR"!==i.operator&&t.set("groupOp_"+e,i.operator.toLowerCase())}))}addRangesToURL(t,i){i.filters.ranges.forEach(((i,e)=>{const{currentMin:s,currentMax:n}=i;s===i.min&&n===i.max||t.set("range_"+e,`${s},${n}`)})),i.filters.dateRanges.forEach(((i,e)=>{const{start:s,end:n}=i;t.set("dateRange_"+e,`${s.toISOString()},${n.toISOString()}`)}))}addSearchToURL(t,i){i.search.query&&t.set("search",i.search.query)}addSortToURL(t,i){if(i.sort.current){const{key:e,direction:s}=i.sort.current;t.set("sort",`${e},${s}`)}}addPaginationToURL(t,i){const{currentPage:e,itemsPerPage:s}=i.pagination;this.afs.options.get("pagination.enabled")&&(e>1&&t.set("page",""+e),s!==this.afs.options.get("pagination.itemsPerPage")&&t.set("perPage",""+s))}pushState(t){const i=""+t,e=`${window.location.pathname}${i?"?"+i:""}`;e!==window.location.href&&(window.history.pushState({},"",e),this.afs.logger.debug("URL updated:",e))}loadFromURL(){this.afs.logger.debug("Loading state from URL");const t=new URLSearchParams(window.location.search);try{this.afs.filter&&this.afs.filter.clearAllFilters();const i=t.get("filterMode");i&&this.afs.filter&&this.afs.filter.setFilterMode(i.toUpperCase());const e=Array.from(t.entries()).filter((([t])=>this.isRegularFilter(t)));e.length>0&&this.afs.filter&&(this.afs.filter.activeFilters.clear(),e.forEach((([t,i])=>{i&&i.split(",").forEach((i=>{this.afs.filter.addFilter(`${t}:${i}`)}))}))),this.afs.filter&&this.afs.filter.applyFilters(),this.processSearchFromURL(t),this.processSortFromURL(t),this.processPaginationFromURL(t),this.afs.emit("urlStateLoaded",{params:Object.fromEntries(t)}),this.afs.logger.info("State loaded from URL")}catch(t){this.afs.logger.error("Error loading state from URL:",t),this.afs.filter&&this.afs.filter.clearAllFilters()}}processFiltersFromURL(t){const i=this.afs.state.getState();let e=!1;const s=t.get("filterMode");s&&(i.filters.mode=s.toUpperCase());const n=t.get("groupMode");n&&(i.filters.groupMode=n.toUpperCase());for(const[s,n]of t.entries())this.isRegularFilter(s)&&n.split(",").filter(Boolean).forEach((t=>{e=!0,i.filters.current.add(`${s}:${t}`)}));for(const[e,s]of t.entries())if(e.startsWith("group_")){const n=e.replace("group_",""),r=t.get("groupOp_"+n)?.toUpperCase()||"OR";i.filters.groups.set(n,{filters:new Set(s.split(",")),operator:r})}e||0!==i.filters.groups.size||i.filters.current.add("*")}processRangesFromURL(t){const i=this.afs.state.getState();for(const[e,s]of t.entries())if(e.startsWith("range_")){const t=e.replace("range_",""),[n,r]=s.split(",").map(Number);i.filters.ranges.set(t,{currentMin:n,currentMax:r})}for(const[e,s]of t.entries())if(e.startsWith("dateRange_")){const t=e.replace("dateRange_",""),[n,r]=s.split(",").map((t=>new Date(t)));i.filters.dateRanges.set(t,{start:n,end:r})}}processSearchFromURL(t){const i=t.get("search")||"";this.afs.state.setState("search.query",i),this.afs.options.get("searchInput")&&(this.afs.options.get("searchInput").value=i)}processSortFromURL(t){const i=t.get("sort");if(i){const[t,e]=i.split(",");this.afs.state.setState("sort.current",{key:t,direction:e})}}processPaginationFromURL(t){const i=parseInt(t.get("page"))||1,e=parseInt(t.get("perPage"))||this.afs.options.get("pagination.itemsPerPage");this.afs.state.setState("pagination",{currentPage:i,itemsPerPage:e})}isRegularFilter(t){return!(["search","sort","page","perPage","filterMode","groupMode"].includes(t)||t.startsWith("group_")||t.startsWith("groupOp_")||t.startsWith("range_")||t.startsWith("dateRange_"))}clearURL(){window.history.pushState({},"",window.location.pathname),this.afs.state.reset(),this.afs.filter&&this.afs.filter.clearAllFilters()}getURLParams(){return new URLSearchParams(window.location.search)}hasParams(){return window.location.search.length>1}getParam(t){return new URLSearchParams(window.location.search).get(t)}}class p{constructor(t){this.afs=t,this.activeRanges=new Map,this.options=this.afs.options.get("slider"),this.afs.styleManager||(this.afs.styleManager=new s(this.afs.options)),this.afs.styleManager.applyStyles()}addRangeSlider({key:t,type:i,container:e,min:s,max:n,step:r=1,ui:a}){if(this.afs.logger.debug("Adding range slider for "+t),!e)return void this.afs.logger.error("Container element required for range slider");const o=this.calculateMinMax(t,i);s=s??o.min,n=n??o.max;const h={...this.afs.options.get("styles.slider.ui")||{showHistogram:!1,bins:10},...a},c=h.showHistogram?this.calculateHistogramData(t,h.bins):{counts:[],binEdges:[],max:0},l=this.createSliderElements(c,h),u=this.initializeState(s,n,r,i);u.ui=h,h.showHistogram&&(u.histogram=c),this.appendElements(e,l),this.setupEventHandlers(l,u,t),this.activeRanges.set(t,{state:u,elements:l}),this.updateSliderUI(t),h.showHistogram&&this.setupHistogramHighlight(l,u,c.binEdges),this.afs.logger.info("Range slider added for "+t)}calculateMinMax(t,i){const e=Array.from(this.afs.items).map((e=>{const s=e.dataset[t];return"date"===i?new Date(s).getTime():parseFloat(s)})).filter((t=>!isNaN(t)));return{min:Math.min(...e),max:Math.max(...e)}}createSliderElements(t,i){const e=(this.afs.options.get("styles")||this.afs.styleManager.defaultStyles).colors||this.afs.styleManager.defaultStyles.colors,s=this.afs.options.get("slider")||{},n=document.createElement("div");n.className="afs-range-container";const r=document.createElement("div");r.className=s.containerClass;const a=document.createElement("div");if(a.className=s.trackClass,i?.showHistogram&&t?.counts?.length>0){const i=this.createHistogramBars(t,e);r.appendChild(i)}const o=document.createElement("div");o.className=s.selectedClass;const h=document.createElement("div");h.className=s.thumbClass;const c=document.createElement("div");c.className=s.thumbClass;const l=document.createElement("div");l.className=s.valueClass;const u=document.createElement("div");return u.className=s.valueClass,r.appendChild(a),r.appendChild(o),r.appendChild(h),r.appendChild(c),r.appendChild(l),r.appendChild(u),n.appendChild(r),{container:n,slider:r,track:a,selectedRange:o,minThumb:h,maxThumb:c,minValue:l,maxValue:u}}createHistogram(t,i){const e=document.createElement("div");return e.className="afs-histogram",t.forEach(((t,s)=>{const n=document.createElement("div");n.className="afs-histogram-bar",n.style.height=t+"%",n.style.backgroundColor=i.histogram,e.appendChild(n)})),e}calculateHistogramData(t,i=10){try{const e=Array.from(this.afs.items).map((i=>parseFloat(i.dataset[t]))).filter((t=>!isNaN(t)));if(0===e.length)return{counts:[],binEdges:[],max:0};const s=Math.min(...e),n=Math.max(...e),r=(n-s)/i,a=Array(i).fill(0),o=Array(i+1);for(let t=0;t<=i;t++)o[t]=s+t*r;e.forEach((t=>{t!==n?a[Math.floor((t-s)/r)]++:a[a.length-1]++}));const h=Math.max(...a);return{counts:a.map((t=>Math.max(20,Math.round(t/h*100)))),binEdges:o,max:h,min:s,max:n}}catch(t){return this.afs.logger.error("Error calculating histogram:",t),{counts:[],binEdges:[],max:0}}}setupHistogramHighlight(t,i,e){const s=t.slider.querySelectorAll(".afs-histogram-bar"),n=()=>{const t=i.currentMin,n=i.currentMax;s.forEach(((i,s)=>{e[s]>=t&&e[s+1]<=n?i.classList.add("active"):i.classList.remove("active")}))};this.afs.on("rangeFilter",(()=>n())),n()}createHistogramBars(t,i){const{counts:e}=t,s=document.createElement("div");return s.className="afs-histogram",e.forEach((t=>{const i=document.createElement("div");i.className="afs-histogram-bar",i.style.height=t+"%",s.appendChild(i)})),s}initializeState(t,i,e,s){return{min:t,max:i,currentMin:t,currentMax:i,step:e,type:s,isDragging:!1}}appendElements(t,i){const{slider:e,track:s,selectedRange:n,minThumb:r,maxThumb:a,minValue:o,maxValue:h}=i;e.appendChild(s),e.appendChild(n),e.appendChild(r),e.appendChild(a),e.appendChild(o),e.appendChild(h),t.appendChild(e)}setupEventHandlers(t,i,e){const{minThumb:s,maxThumb:n}=t,r=s=>n=>{n.preventDefault(),i.isDragging=!0;const r=n=>{const r=(a=n).touches?a.touches[0]:a;var a;this.createMoveHandler(t,i,e,s)(r)},a=()=>{i.isDragging=!1,window.removeEventListener("mousemove",r),window.removeEventListener("mouseup",a),window.removeEventListener("touchmove",r),window.removeEventListener("touchend",a),window.removeEventListener("touchcancel",a),this.applyFilter(e)};window.addEventListener("mousemove",r),window.addEventListener("mouseup",a),window.addEventListener("touchmove",r,{passive:!1}),window.addEventListener("touchend",a),window.addEventListener("touchcancel",a)};s.addEventListener("mousedown",r(!0)),s.addEventListener("touchstart",r(!0),{passive:!1}),n.addEventListener("mousedown",r(!1)),n.addEventListener("touchstart",r(!1),{passive:!1})}updateSliderUI(t){const{state:i,elements:e}=this.activeRanges.get(t),{minThumb:s,maxThumb:n,selectedRange:r,minValue:a,maxValue:o}=e,h=i.max-i.min,c=(i.currentMax-i.min)/h*100,l=Math.max(0,Math.min((i.currentMin-i.min)/h*100,100)),u=Math.max(0,Math.min(c,100));s.style.left=l+"%",n.style.left=u+"%",r.style.left=l+"%",r.style.width=u-l+"%";const d="date"===i.type?t=>new Date(t).toLocaleDateString():t=>t.toFixed(2);a.textContent=d(i.currentMin),o.textContent=d(i.currentMax),a.style.left=l+"%",o.style.left=u+"%",a.style.transform=l<5?"translateX(0)":l>95?"translateX(-100%)":"translateX(-50%)",o.style.transform=u<5?"translateX(0)":u>95?"translateX(-100%)":"translateX(-50%)"}createMoveHandler(t,i,e,s){this.afs.logger.debug("Creating move handler for "+e);const{track:n}=t;return o((t=>{const r=t.touches?t.touches[0].clientX:t.clientX,a=n.getBoundingClientRect(),o=a.width,h=.05*o,c=Math.round((i.min+(i.max-i.min)*Math.min(Math.max(0,(r-a.left-h)/(o-2*h)),1))/i.step)*i.step;s?i.currentMin=Math.min(c,i.currentMax):i.currentMax=Math.max(c,i.currentMin),this.updateSliderUI(e)}),16)}applyFilter(t){this.afs.logger.debug("Applying range filter for "+t);const{state:i}=this.activeRanges.get(t);this.afs.items.forEach((e=>{const s="date"===i.type?new Date(e.dataset[t]).getTime():parseFloat(e.dataset[t]);s>=i.currentMin&&s<=i.currentMax?this.afs.showItem(e):this.afs.hideItem(e)})),this.afs.updateCounter(),this.afs.urlManager.updateURL(),this.afs.emit("rangeFilter",{key:t,min:i.currentMin,max:i.currentMax})}getRangeValues(t){const i=this.activeRanges.get(t);return i?{min:i.state.currentMin,max:i.state.currentMax,type:i.state.type}:null}setRangeValues(t,i,e){const s=this.activeRanges.get(t);s&&(s.state.currentMin=i,s.state.currentMax=e,this.updateSliderUI(t),this.applyFilter(t))}removeRangeSlider(t){const i=this.activeRanges.get(t);i&&(i.elements.slider.remove(),this.activeRanges.delete(t),this.afs.logger.info("Range slider removed for "+t))}}class m{constructor(t){this.afs=t,this.activeDateRanges=new Map,this.defaultFormat=this.afs.options.get("dateFormat")||"YYYY-MM-DD"}addDateRange({key:t,container:i,minDate:e,maxDate:s,format:n=this.defaultFormat}){if(this.afs.logger.debug("Adding date range for "+t),!i)return void this.afs.logger.error("Container element required for date range");const r=this.calculateMinMaxDates(t);e=e??r.min,s=s??r.max;const a=this.createDateElements(),o=this.initializeState(e,s,n);this.appendElements(i,a),this.setupEventHandlers(a,o,t),this.activeDateRanges.set(t,{state:o,elements:a}),this.updateDateUI(t),this.afs.logger.info("Date range added for "+t)}calculateMinMaxDates(t){try{const i=Array.from(this.afs.items).map((i=>{if(!i||!i.dataset||!i.dataset[t])return null;const e=new Date(i.dataset[t]);return isNaN(e.getTime())?null:e})).filter((t=>null!==t));if(0===i.length){const t=new Date;return{min:new Date(t.getFullYear(),0,1),max:new Date(t.getFullYear(),11,31)}}return{min:new Date(Math.min(...i)),max:new Date(Math.max(...i))}}catch(t){this.afs.logger.error("Error calculating date range:",t);const i=new Date;return{min:new Date(i.getFullYear(),0,1),max:new Date(i.getFullYear(),11,31)}}}createDateElements(){this.afs.logger.debug("Creating date picker elements");const t=document.createElement("div");t.className="afs-date-range-container";const i=document.createElement("div");i.className="afs-date-input-wrapper";const e=document.createElement("label");e.textContent="Start Date";const s=document.createElement("input");s.type="date",s.className="afs-date-input start-date";const n=document.createElement("div");n.className="afs-date-input-wrapper";const r=document.createElement("label");r.textContent="End Date";const a=document.createElement("input");return a.type="date",a.className="afs-date-input end-date",i.appendChild(e),i.appendChild(s),n.appendChild(r),n.appendChild(a),t.appendChild(i),t.appendChild(n),{container:t,startInput:s,endInput:a}}initializeState(t,i,e){return{minDate:t,maxDate:i,currentStartDate:t,currentEndDate:i,format:e}}appendElements(t,i){t.appendChild(i.container)}setupEventHandlers(t,i,e){this.afs.logger.debug("Setting up event handlers for date range "+e);const{startInput:s,endInput:n}=t,r=o((()=>{const t=new Date(s.value),r=new Date(n.value);isNaN(t.getTime())||isNaN(r.getTime())||(i.currentStartDate=t,i.currentEndDate=r,this.applyDateFilter(e))}),300);s.addEventListener("change",r),n.addEventListener("change",r)}updateDateUI(t){try{const{state:i,elements:e}=this.activeDateRanges.get(t),{startInput:s,endInput:n}=e,r=t=>{try{const i=new Date(t);if(isNaN(i.getTime()))throw Error("Invalid date");return i.toISOString().split("T")[0]}catch(t){return this.afs.logger.error("Error formatting date:",t),""}};s.min=r(i.minDate),s.max=r(i.maxDate),n.min=r(i.minDate),n.max=r(i.maxDate),s.value=r(i.currentStartDate),n.value=r(i.currentEndDate)}catch(t){this.afs.logger.error("Error updating date UI:",t)}}applyDateFilter(t){this.afs.logger.info("Applying date filter for "+t);const{state:i}=this.activeDateRanges.get(t);this.afs.items.forEach((e=>{try{if(!e||!e.dataset||!e.dataset[t])return void this.afs.hideItem(e);const s=new Date(e.dataset[t]);if(isNaN(s.getTime()))return void this.afs.hideItem(e);const n=new Date(i.currentStartDate);n.setHours(0,0,0,0);const r=new Date(i.currentEndDate);r.setHours(23,59,59,999);const a=new Date(s);a.setHours(0,0,0,0),a>=n&&a<=r?this.afs.showItem(e):this.afs.hideItem(e)}catch(t){this.afs.logger.error("Error filtering item by date:",t),this.afs.hideItem(e)}})),this.afs.updateCounter(),this.afs.urlManager.updateURL(),this.afs.emit("dateFilter",{key:t,startDate:i.currentStartDate,endDate:i.currentEndDate})}getDateRange(t){const i=this.activeDateRanges.get(t);return i?{startDate:i.state.currentStartDate,endDate:i.state.currentEndDate}:null}setDateRange(t,i,e){const s=this.activeDateRanges.get(t);s&&(s.state.currentStartDate=i,s.state.currentEndDate=e,this.updateDateUI(t),this.applyDateFilter(t))}removeDateRange(t){const i=this.activeDateRanges.get(t);i&&(i.elements.container.remove(),this.activeDateRanges.delete(t),this.afs.logger.info("Date range removed for "+t))}}const f="1.3.11";class g extends n{constructor(t={}){super(),this.initializeCore(t)}initializeCore(n){try{this.options=new i(n);const r=this.options.get("debug"),a=this.options.get("logLevel");this.logger=new t(r,a),this.logger.debug("Logger initialized with debug:",r,"level:",a),this.state=new e,this.styleManager=new s(this.options),this.initializeDOM(),this.initializeFeatures(),this.setupLifecycle()}catch(t){throw t}}initializeDOM(){if(this.logger.debug("Initializing DOM elements"),this.container=document.querySelector(this.options.get("containerSelector")),!this.container)throw Error("Container not found: "+this.options.get("containerSelector"));this.items=this.container.querySelectorAll(this.options.get("itemSelector")),0===this.items.length&&this.logger.warn("No items found in container"),this.state.setState("items.total",this.items.length),this.state.setState("items.visible",new Set(this.items))}initializeFeatures(){this.logger.debug("Initializing features"),this.filter=new a(this),this.search=new c(this),this.sort=new l(this),this.rangeFilter=new p(this),this.urlManager=new d(this),this.dateFilter=new m(this),this.pagination=new u(this),this.inputRangeFilter=new h(this),this.styleManager.applyStyles(),this.urlManager.initialize()}setupLifecycle(){this.options.get("responsive")&&window.addEventListener("resize",this.handleResize.bind(this)),this.options.get("preserveState")&&document.addEventListener("visibilitychange",this.handleVisibilityChange.bind(this)),this.options.get("observeDOM")&&this.setupMutationObserver(),this.emit("initialized",{itemCount:this.items.length,options:this.options.export()})}showItem(t){const i=this.state.getState().items.visible;i.add(t),this.state.setState("items.visible",i),t.classList.remove(this.options.get("hiddenClass")),t.style.opacity="0",t.style.transform="scale(0.95)",t.style.display="";const e=this.options.get("transitionClass");t.classList.contains(e)||t.classList.add(e),requestAnimationFrame((()=>{requestAnimationFrame((()=>{t.style.opacity="1",t.style.transform="scale(1)"}))}));const s=this.options.get("animation.duration")||300;setTimeout((()=>{i.has(t)&&(t.style.transform="",t.style.opacity="")}),s)}hideItem(t){const i=this.state.getState().items.visible;i.delete(t),this.state.setState("items.visible",i);const e=this.options.get("transitionClass");t.classList.contains(e)||t.classList.add(e),requestAnimationFrame((()=>{t.style.opacity="0",t.style.transform="scale(0.95)"}));const s=this.options.get("animation.duration")||300;setTimeout((()=>{i.has(t)||(t.classList.add(this.options.get("hiddenClass")),t.style.transform="",t.style.opacity="")}),s)}addItems(t){const i=Array.isArray(t)?t:[t],e=document.createDocumentFragment();i.forEach((t=>{e.appendChild(t),this.state.getState().items.visible.add(t)})),this.container.appendChild(e),this.items=this.container.querySelectorAll(this.options.get("itemSelector")),this.filter.applyFilters()}removeItems(t){const i=Array.isArray(t)?t:[t],e=this.state.getState().items.visible;i.forEach((t=>{e.delete(t),t.remove()})),this.items=this.container.querySelectorAll(this.options.get("itemSelector")),this.updateCounter()}saveState(){if(!this.options.get("preserveState"))return;const t={filters:Array.from(this.filter.getActiveFilters()),search:this.search.getValue(),sort:this.sort.getCurrentSort(),pagination:this.pagination.getPageInfo(),timestamp:Date.now()};sessionStorage.setItem("afs_state",JSON.stringify(t)),this.logger.debug("State saved")}restoreState(){if(this.options.get("preserveState"))try{const t=sessionStorage.getItem("afs_state");if(!t)return;const i=JSON.parse(t);if(Date.now()-i.timestamp>this.options.get("stateExpiry"))return void localStorage.removeItem("afs_state");this.setState(i),this.logger.debug("State restored")}catch(t){this.logger.error("Error restoring state:",t)}}getState(){return this.state.export()}setState(t){this.state.import(t),this.refresh()}updateCounter(){const t=document.querySelector(this.options.get("counterSelector"));if(t)try{const e=this.items.length,s=this.state.getState().items.visible.size,n=e-s,r=this.options.get("counter")||i.defaults.counter,a=r.template||"Showing {visible} of {total}",o=r.formatter||(t=>t.toLocaleString()),h=o(s),c=o(e),l=o(n);let u=a.replace("{visible}",h).replace("{total}",c).replace("{filtered}",l);n>0&&r.showFiltered&&(u+=" "+(r.filteredTemplate||"({filtered} filtered)").replace("{filtered}",l)),0===s&&r.noResultsTemplate&&(u=r.noResultsTemplate),t.textContent=u,this.emit("counterUpdated",{total:e,visible:s,filtered:n,formattedTotal:c,formattedVisible:h,formattedFiltered:l})}catch(i){this.logger.error("Error updating counter:",i),t.textContent=`${this.state.getState().items.visible.size}/${this.items.length}`}}updateOptions(t){this.options.update(t),this.styleManager.updateStyles(t),this.refresh()}refresh(){this.logger.debug("Refreshing AFS"),this.items=this.container.querySelectorAll(this.options.get("itemSelector")),this.state.setState("items.total",this.items.length),this.filter.applyFilters(),this.search.search(this.search.getValue()),this.options.get("pagination.enabled")&&this.pagination.update(),this.emit("refreshed",{itemCount:this.items.length})}handleResize=(()=>o((()=>{this.emit("resize")}),250))();handleVisibilityChange(){document.hidden?(this.emit("hidden"),this.saveState()):(this.emit("visible"),this.restoreState())}setupMutationObserver(){new MutationObserver((t=>{t.some((t=>"childList"===t.type))&&this.refresh()})).observe(this.container,{childList:!0,subtree:!0})}getVersion(){return f}isFeatureSupported(t){return!!{search:!!this.search,pagination:!0,animation:void 0!==document.createElement("div").style.transition,urlState:"function"==typeof window.history.pushState,localStorage:(()=>{try{return localStorage.setItem("test","test"),localStorage.removeItem("test"),!0}catch(t){return!1}})()}[t]}destroy(){this.logger.debug("Destroying AFS instance"),window.removeEventListener("resize",this.handleResize),document.removeEventListener("visibilitychange",this.handleVisibilityChange),this.filter.destroy(),this.search.destroy(),this.sort.destroy(),this.pagination.destroy(),this.rangeFilter.destroy(),this.styleManager.removeStyles(),this.state.reset(),sessionStorage.removeItem("afs_state"),this.items.forEach((t=>{t.style="",t.classList.remove(this.options.get("hiddenClass"),this.options.get("activeClass"))})),this.emit("destroyed")}}export{g as AFS,f as VERSION};
+
//# sourceMappingURL=afs.modern.js.map
+170
zola/static/js/script.mjs
···
+
import { AFS } from './afs.modern.js';
+
+
const startContent = document.getElementById('startDate').textContent;
+
const startDate = document.getElementById('startDate').getAttribute('data-raw');
+
const endContent = document.getElementById('endDate').textContent;
+
const endDate = document.getElementById('endDate').getAttribute('data-raw');
+
+
const numEntries = 0; // fix this later to be dynamic
+
+
// Date formatting stuff. You probably only need to touch locale and time zone.
+
const locale = 'en-US';
+
const timeZone = 'America/Chicago';
+
+
// DON'T EDIT BELOW THIS LINE
+
// unless you know what you're doing.
+
+
const start = new Date(startDate);
+
const end = new Date(endDate);
+
const now = Date.now();
+
+
const dateElt = document.getElementById('dates');
+
+
const daysElt = document.getElementById('days');
+
const hoursElt = document.getElementById('hours');
+
const minutesElt = document.getElementById('minutes');
+
const secondsElt = document.getElementById('seconds');
+
+
const list = document.getElementById('list');
+
+
const dayMult = 24*60*60;
+
const hourMult = 60*60;
+
const minuteMult = 60;
+
+
const countdownTick = () => {
+
const now = Date.now();
+
let diff;
+
if (now < start.getTime()) {
+
// Jam hasn't started yet
+
diff = (start.getTime() - now) / 1000; // get total # of seconds
+
} else if (now < end.getTime()) {
+
// Jam has started but not ended
+
diff = (end.getTime() - now) / 1000;
+
} else {
+
// Jam has ended
+
dates.innerHTML = `The jam is now over. It ran from <b>${startContent}</b> to <b>${endContent}</b>. <a href="submissions.html">View ${numEntries} ${numEntries !== 1 ? 'entries' : 'entry'}`
+
}
+
+
if (diff) {
+
const days = Math.floor(diff / dayMult);
+
diff = diff - (days * dayMult);
+
const hours = Math.floor(diff / hourMult);
+
diff = diff - (hours * hourMult);
+
const minutes = Math.floor(diff / minuteMult);
+
diff = diff - (minutes * minuteMult);
+
const seconds = Math.floor(diff);
+
daysElt.textContent = days;
+
hoursElt.textContent = hours;
+
minutesElt.textContent = minutes;
+
secondsElt.textContent = seconds;
+
}
+
}
+
+
if (document.querySelector('.clock')) {
+
countdownTick();
+
if (now < end.getTime()) {
+
setInterval(() => {
+
countdownTick();
+
}, 1000);
+
}
+
}
+
+
if (list) {
+
const afs = new AFS({
+
// Required Selectors
+
containerSelector: '#list',
+
itemSelector: '.item',
+
+
// CSS Classes
+
activeClass: 'active',
+
hiddenClass: 'hidden',
+
transitionClass: 'afs-transition',
+
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'
+
},
+
+
// 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'
+
}
+
}
+
});
+
+
// add date range filter
+
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])
+
});
+
+
// fixes some behavior of the sort buttons
+
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'));
+
}
+
});
+
});
+
+
// random sort button
+
document.querySelector('[data-sort-key="shuffle"]').addEventListener('click', (e) => {
+
afs.sort.shuffle();
+
});
+
}
+80
zola/templates/games/section.html
···
+
{% extends "index.html" %}
+
+
{% block body_classes %}wide submissions{% endblock %}
+
+
{% block main_content %}
+
{{ section.content | safe }}
+
<aside id="filters">
+
<div class="afs-filter-container">
+
<!-- Filter Controls -->
+
<div class="afs-filter-controls">
+
<!-- Basic Filters -->
+
+
<!-- Search Input -->
+
<input type="text" class="afs-filter-search" placeholder="Search by title, author, or tags" />
+
+
<p class="label">Sort entries by:</p>
+
<ul id="sorts">
+
<li><button class="custom-sort" data-sort-key="date" data-sort-direction="desc"> <span class="afs-sort-direction"><img src="{{ config.base_url }}/images/sort-desc.svg" alt="Sort descending" /></span> Date</button></li>
+
<li><button class="custom-sort" data-sort-key="title" data-sort-direction="asc"> <span class="afs-sort-direction"><img src="{{ config.base_url }}/images/sort-asc.svg" alt="Sort descending" /></span> Title</button></li>
+
<li><button class="custom-sort" data-sort-key="shuffle" data-sort-direction="desc"> <span class="afs-sort-direction"><img src="{{ config.base_url }}/images/sort-desc.svg" alt="Sort descending" /></span> Random</button></li>
+
</ul>
+
+
<details open><summary>Platform</summary>
+
<ul>
+
<li><button class="afs-btn-filter" data-filter="platforms:browser"><img src="{{ config.base_url }}/images/web.svg" alt="" class="icon" /> Play in browser</button></li>
+
<li><button class="afs-btn-filter" data-filter="platforms:windows"><img src="{{ config.base_url }}/images/windows.svg" alt="" class="icon" /> Windows</button></li>
+
<li><button class="afs-btn-filter" data-filter="platforms:macos"><img src="{{ config.base_url }}/images/macos.svg" alt="" class="icon" /> Mac OS</button></li>
+
<li><button class="afs-btn-filter" data-filter="platforms:linux"><img src="{{ config.base_url }}/images/linux.svg" alt="" class="icon" /> Linux</button></li>
+
<li><button class="afs-btn-filter" data-filter="platforms:android"><img src="{{ config.base_url }}/images/android.svg" alt="" class="icon" /> Android</button></li>
+
</ul>
+
</details>
+
+
<details><summary>Tags</summary>
+
<div id="tags">
+
{% set tax = get_taxonomy(kind="games/tags") %}
+
<button class="afs-btn-filter" data-filter="*">all</button>
+
{% for tag in tax.items %}
+
<button class="afs-btn-filter" data-filter="tags:{{ tag.name }}">#{{ tag.name }}</button>
+
{% endfor %}
+
</div>
+
</details>
+
+
<details><summary>Submission Date</summary>
+
<div id="date-filter"></div>
+
</details>
+
+
<!-- Results Counter -->
+
<div class="afs-filter-counter"></div>
+
</div>
+
+
<!-- Pagination Container -->
+
<div class="afs-pagination-container"></div>
+
</div>
+
</aside>
+
<section id="list">
+
{%- for page in section.pages %}
+
{%- if page.draft %}
+
{% continue %}
+
{% endif -%}
+
<article class="item" data-date="{{ page.date }}" data-categories="" data-title="{{ page.title }}" data-authors="{% if page.authors %}{{ page.authors | join(sep=", ") }}{% endif %}" data-platforms="">
+
<div class="thumb">
+
<a href="{{ page.permalink }}"><img src="{{ page.components | join(sep="/") }}/{{ page.extra.thumbnail }}" alt="{{ page.title }} thumbnail image" /></a>
+
</div>
+
<h3><a href="{{ page.permalink }}">{{ page.title }}</a></h3>
+
<div class="authors">
+
{% if page.extra.authors %}
+
{% for author in page.extra.authors %}
+
{% if author.link != "" %}
+
<a href="{{ author.link }}" target="_blank">{{ author.name }}</a>
+
{% else %}
+
{{ author }}
+
{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}
+
{% else %}
+
Anonymous
+
{% endif %}
+
</div>
+
</article>
+
{% endfor %}
+
</section>
+
{% endblock main_content %}
+12
zola/templates/games/tags/list.html
···
+
{% extends "index.html" %}
+
+
{% block main_content %}
+
<h2><a href="/games">games</a>: tags</h2>
+
<section id="games">
+
<ul>
+
{% for term in terms %}
+
<li><a href="{{ get_taxonomy_url(kind='games/tags', name=term.name) }}">{{ term.name }}</a></li>
+
{% endfor %}
+
</ul>
+
</section>
+
{% endblock main_content %}
+8
zola/templates/games/tags/single.html
···
+
{% extends "index.html" %}
+
+
{% block main_content %}
+
<h2><a href="/games">games</a>: {{ term.name }}</h2>
+
<section id="games">
+
{{ post_macros::list_posts(pages=term.pages, extra=config.extra) }}
+
</section>
+
{% endblock main_content %}
+36
zola/templates/index.html
···
+
{% import "macros/macros.html" as post_macros %}
+
+
<!DOCTYPE html>
+
<html lang="{{lang}}">
+
{% include "partials/head.html" %}
+
<body class="{% block body_classes %}{% endblock body_classes %}">
+
{% include "partials/banner.html" %}
+
<div id="page">
+
{% include "partials/header.html" %}
+
<main>
+
{% block main_content %}
+
<div class="clock">
+
<div class="dates" id="dates">Submissions open from <b id="startDate" data-raw="{{ config.extra.start_date }}">{{ config.extra.start_date | date(format="%B %d, %Y at %l:%m%P", timezone=config.extra.timezone) }}</b> to <b id="endDate" data-raw="{{ config.extra.end_date }}">{{ config.extra.end_date | date(format="%B %d, %Y at %l:%m%P", timezone=config.extra.timezone) }}</b></div>
+
<div class="countdown">
+
<div class="counters">
+
<div class="verb">Starts in</div>
+
<div class="days"><span class="num" id="days">#</span><span class="caption">days</span></div>
+
<div class="hours"><span class="num" id="hours">#</span><span class="caption">hours</span></div>
+
<div class="minutes"><span class="num" id="minutes">#</span><span class="caption">minutes</span></div>
+
<div class="seconds"><span class="num" id="seconds">#</span><span class="caption">seconds</span></div>
+
</div>
+
</div>
+
<div class="join">
+
<a href="{{ config.extra.join_link }}" class="joinbtn">Join Jam</a>
+
</div>
+
</div>
+
<div class="content">
+
{% set page = get_section(path="_index.md") %}
+
{{ page.content | safe }}
+
</div>
+
{% endblock main_content %}
+
</main>
+
{% include "partials/footer.html" %}
+
</div>
+
</body>
+
</html>
+97
zola/templates/macros/macros.html
···
+
{% macro list_posts(pages, extra) %}
+
{%- for page in pages %}
+
{%- if page.draft %}
+
{% continue %}
+
{% endif -%}
+
+
<article>
+
<h3 class="title">
+
<a href="{{ page.permalink }}">{{page.title}}</a>
+
</h3>
+
+
{% if page.date %}
+
<p class="datetime"><time>{{ page.date | date(format="%Y-%m-%d") }}</time></p>
+
{% endif %}
+
{% if page.extra.author %}
+
<span>| Posted by <a href="{{ page.extra.author.social }}" target="_blank">{{ page.extra.author.name }}</a></span>
+
{% endif %}
+
+
{% if page.extra.thumbnail %}
+
<div class="img-wrapper">
+
<a href="{{ page.permalink }}"><img src="{{ page.extra.thumbnail }}" alt="{{ page.title }}" /></a>
+
</div>
+
{% endif %}
+
+
{% if page.taxonomies["games/tags"] %}
+
<div class="tags">
+
{% for tag in page.taxonomies["games/tags"] %}
+
<a href="{{ get_taxonomy_url(kind='games/tags', name=tag)}}">{{ tag }}</a>
+
{% endfor %}
+
</div>
+
{% endif %}
+
</article>
+
+
{% endfor -%}
+
{% endmacro list_posts %}
+
+
{% macro content(page, extra, taxonomy=false) %}
+
<article>
+
<h2 class="title">{% if taxonomy %}{{ taxonomy.name }}{% else %}{{ page.title }}{% endif %}</h2>
+
<div class="meta">
+
{% if page.extra.author %}
+
Posted by <a href="{{ page.extra.author.social }}" target="_blank">{{ page.extra.author.name }}</a>
+
{% endif %}
+
{% if page.date %}
+
Posted on {{ page.date | date(format="%Y-%m-%d") }}
+
{% endif %}
+
+
{% if page.draft %}
+
<span class="draft-label">DRAFT</span>
+
{% endif %}
+
</div>
+
+
{% if page.extra.subnav %}
+
<ul id="subnav">
+
{% for subpage in page.extra.subnav %}
+
<li>
+
<a href="{{subpage.link}}"{% if subpage.target and subpage.target == '_blank' %} target="_blank"{% endif %}>
+
{% if subpage.img %}
+
<img src="{{subpage.img}}" alt="{{subpage.label}}" />
+
{% else %}
+
{{subpage.label}}
+
{% endif %}
+
</a>
+
</li>
+
{% endfor %}
+
</ul>
+
{% endif %}
+
+
{% if page.extra.tldr %}
+
<div class="tldr">
+
<strong>tl;dr:</strong>
+
{{ page.extra.tldr }}
+
</div>
+
{% endif %}
+
+
<div class="content">
+
{{ page.content | safe }}
+
</div>
+
+
{% if page.taxonomies and page.taxonomies.tags %}
+
<div class="post-tags">
+
<nav class="nav tags">
+
<ul class="tags">
+
{% for tag in page.taxonomies.tags %}
+
<li><a href={{ get_taxonomy_url(kind='tags', name=tag) | safe }}>{{ tag }}</a></li>
+
{% endfor %}
+
</ul>
+
</nav>
+
</div>
+
{% endif %}
+
{% if page.extra.scripts %}
+
{% for script in page.extra.scripts %}
+
<script src="{{script}}" type="text/javascript"></script>
+
{% endfor %}
+
{% endif %}
+
</article>
+
{% endmacro content %}
+62
zola/templates/page.html
···
+
{% extends "index.html" %}
+
{% block body_classes %}wide game{% endblock %}
+
{% block main_content %}
+
{% if 'games/_index.md' in page.ancestors %}
+
<div class="game-header">
+
<p class="jamsub">A jam submission</p>
+
<h2>{{ page.title }} {% if page.extra.website_link %}<a href="{{ page.extra.website_link }}" target="_blank">View game website &rarr;</a>{% endif %}</h2>
+
<div class="blurb">
+
{{ page.description }}
+
</div>
+
<div class="authors">
+
Submitted by {% if page.extra.authors %}{% for author in page.extra.authors %}{% if author.link != "" %}<a href="{{ author.link }}" target="_blank">{{ author.name }}</a>{% else %}{{ author.name }}{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}{% else %}Anonymous{% endif %} at <span class="submissiontime">{{ page.date | date(format="%B %d, %Y at %l:%m%P", timezone=config.extra.timezone) }}</span>
+
</div>
+
</div>
+
<div class="screenshots">
+
<div class="gallery">
+
{% if page.extra.screenshots %}
+
{% for screenshot in page.extra.screenshots %}
+
<button popovertarget="img{{ loop.index }}"><img src="{{ screenshot.url }}" alt="{{ screenshot.alt }}" /></button>
+
<img src="{{ screenshot.url }}" alt="{{ screenshot.alt }}" popover id="img{{ loop.index }}" />
+
{% endfor %}
+
{% endif %}
+
</div>
+
</div>
+
<div class="interaction">
+
<div class="downloads">
+
{{ page.content | safe }}
+
{% if page.extra.downloads %}
+
<h3>Downloads</h3>
+
<ul>
+
{% for dl in page.extra.downloads %}
+
<li><a href="{{ dl.link }}" class="btn" download>Download</a> <b>{{ dl.filename }}</b> <span class="size">{{ dl.filesize }}</span> <span class="platforms"><span class="platforms-label">Platforms: </span>{% for p in dl.platforms %}<span class="icon {{ p }}" title="{{ p }}">{{ p }}</span>{% endfor %}</li>
+
{% endfor %}
+
</ul>
+
{% endif %}
+
</div>
+
<!-- uncomment if you want to drop in some kind of commenting system and put in whatever script you're using -->
+
<!--<div class="comments">
+
put comment code here
+
</div>-->
+
</div>
+
{% else %}
+
<div class="content">
+
<h2>{{ page.title }}</h2>
+
{% if page.date %}
+
<p class="datetime">{{ page.date }}</p>
+
{% endif %}
+
{{ page.content | safe }}
+
{% if page.taxonomies %}
+
<p class="tags"><b>Tags:</b>
+
{% if page.taxonomies["tags"] %}
+
{% for tag in page.taxonomies["tags"] %}
+
<a href="{{ get_taxonomy_url(kind='tags', name=tag) }}">{{ tag }}</a>
+
{% endfor %}
+
{% endif %}
+
</p>
+
{% endif %}
+
<br />
+
<button class="tinylytics_kudos"></button>
+
</div>
+
{% endif %}
+
{% endblock main_content %}
+4
zola/templates/partials/banner.html
···
+
<div id="banner">
+
<!-- uncomment the below line if banner is desired -->
+
<!-- <img src="!!YOURBANNER!!" alt="!!BANNERALT!!" /> -->
+
</div>
+4
zola/templates/partials/footer.html
···
+
<footer>
+
<a href="https://veryroundbird.house" target="_blank">♥︎</a>
+
</footer>
+
<script type="module" src="{{ config.base_url }}/js/script.mjs"></script>
+86
zola/templates/partials/head.html
···
+
{% import "macros/macros.html" as post_macros %}
+
+
<head>
+
<meta charset="UTF-8">
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
{% if page.extra.meta %}
+
<!-- the meta data config goes here -->
+
{% for data in page.extra.meta %}
+
<meta
+
{% for key, value in data%}
+
{% if key == "property" and value == "og:title"%}
+
{% set_global page_has_og_title = true -%}
+
{% endif %}
+
{% if key == "property" and value == "og:description"%}
+
{% set_global page_has_og_description = true -%}
+
{% endif %}
+
{% if key == "name" and value == "description"%}
+
{% set_global page_has_description = true -%}
+
{% endif %}
+
{{ key }}="{{ value }}"
+
{% endfor %}
+
/>
+
{% endfor %}
+
{% endif %}
+
+
{% if current_path == "/" %}
+
<title>
+
{{ config.title }}
+
</title>
+
{% if not page_has_og_title %}
+
<meta property="og:title" content="{{ config.title }}" />
+
{% endif %}
+
{% else %}
+
<title>
+
{{ config.title }} > {% if page.title %} {{ page.title | lower }}
+
{% elif section.title %} {{ section.title | lower }}
+
{% else %} post {% endif %}
+
</title>
+
+
{% if not page_has_og_title %}
+
<meta property="og:title" content="{% if page.title -%}{{ page.title | lower }}{% elif section.title %}{{ section.title | lower }}{% elif config.title -%}{{ config.title | lower }}{% else -%}post{% endif -%}" />
+
{% endif %}
+
{% endif %}
+
+
{% if not page_has_og_description %}
+
{% if page.description %}
+
<meta property="og:description" content="{{ page.description }}" />
+
{% elif section.description %}
+
<meta property="og:description" content="{{ section.description }}" />
+
{% elif config.description %}
+
<meta property="og:description" content="{{ config.description }}" />
+
{% endif %}
+
{% endif %}
+
+
{% if not page_has_description %}
+
{% if page.description %}
+
<meta name="description" content="{{ page.description }}" />
+
{% elif section.description %}
+
<meta name="description" content="{{ section.description }}" />
+
{% elif config.description %}
+
<meta name="description" content="{{ config.description }}" />
+
{% endif %}
+
{% endif %}
+
+
{% if config.extra.favicon %}
+
<link rel="icon" type="image/png" sizes="16x16" href={{ config.extra.favicon }}>
+
{% endif %}
+
+
{% if config.extra.favicon32 %}
+
<link rel="icon" type="image/png" sizes="32x32" href={{ config.extra.favicon32 }}>
+
{% endif %}
+
+
{% if config.extra.appletouch %}
+
<link rel="apple-touch-icon" sizes="180x180" href={{ config.extra.appletouch }}>
+
{% endif %}
+
+
{% if config.extra.webmanifest %}
+
<link rel="manifest" href="{{ config.base_url }}/site.webmanifest">
+
{% endif %}
+
+
{# opengraph, twitter_cards #}
+
+
<link rel="stylesheet" type="text/css" media="screen" href="{{ config.base_url }}/style.css" />
+
</head>
+25
zola/templates/partials/header.html
···
+
<header>
+
<h1>{{ config.title | safe }}</h1>
+
<p class="hosted">Hosted by <a href="{{ config.extra.host_link }}" target="_blank">{{ config.extra.host_name }}</a> &bull; {% if config.extra.jam_hashtag != "" %}<a href="{{ config.extra.jam_hashtag_link }}" target="_blank">#{{ config.extra.jam_hashtag }}</a>{% endif %}</p>
+
{% if section and section.components | length == 0 %}
+
<div class="joined">
+
<span class="count" id="joinedCount">!!#!!</span>
+
<span class="caption">Joined</span>
+
</div>
+
{% else %}
+
{% set games = get_section(path="games/_index.md") %}
+
<div class="entries">
+
<span class="count" id="entryCount">{{ games.pages | length }}</span>
+
<span class="caption">Entries</span>
+
</div>
+
{% endif %}
+
<nav>
+
<ul>
+
<li><a href="{{ config.base_url }}"{% if section and section.components | length == 0 %} class="current"{% endif %}>Overview</a></li>
+
{% if config.extra.community_link != "" %}
+
<li><a href="{{ config.extra.community_link }}">Community</a></li>
+
{% endif %}
+
<li><a href="{{ config.base_url }}/games"{% if page or (section and section.components | length > 0) %} class="current"{% endif %}>Submissions</a></li>
+
</ul>
+
</nav>
+
</header>
zola/templates/posts.html

This is a binary file and will not be displayed.

+27
zola/templates/resources/section.html
···
+
{% extends "index.html" %}
+
+
{% block main_content %}
+
<h2>{{ section.title }}</h2>
+
{{ section.content | safe }}
+
<section id={{ section.title | lower | replace(from=" ", to="-") }}>
+
{% set tax = get_taxonomy(kind="resources/tags") %}
+
{% for term in tax.items %}
+
<h3>{{ term.name }}</h3>
+
<ol class="post-list">
+
{% for post in term.pages %}
+
<li>
+
<a href={{ post.permalink }}><span class="date">{{ post.date }} /</span> <span class="title">{{ post.title }}</span></a>
+
{% if section.extra.tag_tax %}
+
{% set tax = section.extra.tag_tax %}
+
<span class="tags">
+
{% for tag in post.taxonomies[tax] %}
+
<a href="{{ get_taxonomy_url(kind=tax, name=tag) }}">{{ tag }}</a>
+
{% endfor %}
+
</span>
+
{% endif %}
+
</li>
+
{% endfor %}
+
</ol>
+
{% endfor %}
+
</section>
+
{% endblock main_content %}
+23
zola/templates/section.html
···
+
{% extends "index.html" %}
+
+
{% block main_content %}
+
<h2>{{ section.title }}</h2>
+
{{ section.content | safe }}
+
<section id={{ section.title | lower | replace(from=" ", to="-") }}>
+
<ol class="post-list">
+
{% for post in section.pages | reverse %}
+
<li>
+
<a href={{ post.permalink }}><span class="date">{{ post.date }} /</span> <span class="title">{{ post.title }}</span></a>
+
{% if section.extra.tag_tax %}
+
{% set tax = section.extra.tag_tax %}
+
<span class="tags">
+
{% for tag in post.taxonomies[tax] %}
+
<a href="{{ get_taxonomy_url(kind=tax, name=tag) }}">{{ tag }}</a>
+
{% endfor %}
+
</span>
+
{% endif %}
+
</li>
+
{% endfor %}
+
</ol>
+
</section>
+
{% endblock main_content %}
+16
zola/templates/shortcodes/gallery.html
···
+
<div class="gallery">
+
{% set gallength = page.extra.gallery | length %}
+
{% for asset in page.extra.gallery -%}
+
<div id="gal-{{ loop.index }}" class="gallery-item">
+
{% if gallength > 1 %}
+
<a href="#gal-{% if loop.index-1 <= 0 %}{{ gallength }}{% else %}{{ loop.index-1}}{% endif %}" aria-label="Previous"><i class="fa-solid fa-arrow-left"></i></a>
+
{% endif %}
+
<a href="{{ asset }}" target="_blank">
+
<img src="{{ config.extra.cdn_prefix }}{{ asset }}" />
+
</a>
+
{% if gallength > 1 %}
+
<a href="#gal-{% if loop.index+1 > gallength %}1{% else %}{{ loop.index+1 }}{% endif %}" aria-label="Next"><i class="fa-solid fa-arrow-right"></i></a>
+
{% endif %}
+
</div>
+
{%- endfor %}
+
</div>
+9
zola/templates/shortcodes/links.html
···
+
{% if page.extra.links %}
+
<div class="links">
+
<ul>
+
{% for link in page.extra.links %}
+
<li><a href="{{ link.url }}" target="_blank">{{ link.name }}</a></li>
+
{% endfor %}
+
</ul>
+
</div>
+
{% endif %}
+12
zola/templates/taxonomy_list.html
···
+
{% extends "index.html" %}
+
+
{% block main_content %}
+
<h2>{{ taxonomy.name }}</h2>
+
<section>
+
<ul>
+
{% for term in terms %}
+
<li><a href="{{ get_taxonomy_url(kind=taxonomy.name, name=term.name) }}">{{ term.name }}</a></li>
+
{% endfor %}
+
</ul>
+
</section>
+
{% endblock main_content %}
+22
zola/templates/taxonomy_single.html
···
+
{% extends "index.html" %}
+
+
{% block main_content %}
+
<h2>#{{ term.name }}</h2>
+
<section>
+
<ol class="post-list">
+
{% for post in term.pages %}
+
<li>
+
<a href={{ post.permalink }}><span class="date">{{ post.date }} /</span> <span class="title">{{ post.title }}</span></a>
+
{% if section.extra.tag_tax %}
+
{% set tax = section.extra.tag_tax %}
+
<span class="tags">
+
{% for tag in post.taxonomies[tax] %}
+
<a href="{{ get_taxonomy_url(kind=tax, name=tag) }}">{{ tag }}</a>
+
{% endfor %}
+
</span>
+
{% endif %}
+
</li>
+
{% endfor %}
+
</ol>
+
</section>
+
{% endblock main_content %}
+66
zola/theme.toml
···
+
name = "homemade jam"
+
description = "scaffolding for game or other creative jams"
+
license = "idk"
+
homepage = "https://tangled.sh/@veryroundbird/homemadejam"
+
# The minimum version of Zola required
+
min_version = "0.20.0"
+
demo = ""
+
compile_sass = true
+
generate_feeds = true
+
+
taxonomies = [
+
{ name = "tags" },
+
{ name = "games/tags" },
+
{ name = "games/platforms" }
+
]
+
+
# override this in your `config.toml`
+
title = "!! YOUR GAME JAM NAME !!"
+
+
joined_count_source = "manual" # options are "manual" / "airtable" / "google sheets" / "json" / "csv"
+
+
joined_count = 0
+
+
joined_airtable_base_id = ""
+
joined_airtable_table_name = ""
+
joined_airtable_token = ""
+
+
joined_google_document_id = ""
+
joined_google_sheet_id = ""
+
+
joined_json_data_source = ""
+
+
joined_csv_data_source = ""
+
+
[slugify]
+
taxonomies = "off"
+
+
# override these in your `config.toml`
+
[extra]
+
# actually important stuff
+
host_name = "!! YOUR NAME !!"
+
host_link = "!! YOUR LINK !!"
+
jam_hashtag = "!! YOUR HASHTAG !!"
+
jam_hashtag_link = "#"
+
join_link = "!! JOIN LINK !!"
+
start_date = "2026-02-01T00:00:00"
+
end_date = "2026-02-28T23:59:59"
+
locale = "en-US"
+
timezone = "America/Chicago"
+
favicon = "images/android-chrome-512x512.png"
+
favicon32 = "images/favicon-32x32.png"
+
appletouch = "images/favicon-192x192.png"
+
+
# optional features
+
banner = ""
+
community_link = ""
+
+
# analytics stuff, optional
+
tinylytics_script_url = ""
+
goatcounter_script_url = ""
+
use_tinylytics_kudos = true
+
tinylytics_kudos_emoji = "❤️"
+
+
[author]
+
name = "Carly Smallbird"
+
homepage = "https://veryroundbird.house"