feat: Hugo engine for iGaming affiliate template (layouts, assets, i18n, verified data, CI build script)
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
/* =========================================================================
|
||||
main.css — Hugo Pipes entry (processed with resources.ExecuteAsTemplate |
|
||||
css.PostCSS | fingerprint). The :root block below is injected from
|
||||
data/brand.yaml so every brand colour flows through Tailwind utilities
|
||||
(bg-primary, text-primary, ...) with zero template edits.
|
||||
========================================================================= */
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* --- Brand tokens injected from data/brand.yaml --------------------------
|
||||
Both a hex value (for gradients/SVG) and an "R G B" channel triplet (for
|
||||
Tailwind alpha modifiers) are emitted for each brand colour. Colours in
|
||||
data/brand.yaml MUST be full 6-digit hex (e.g. #0057FF). */
|
||||
{{- $brand := .Site.Data.brand -}}
|
||||
{{- $hexmap := dict "0" 0 "1" 1 "2" 2 "3" 3 "4" 4 "5" 5 "6" 6 "7" 7 "8" 8 "9" 9 "a" 10 "b" 11 "c" 12 "d" 13 "e" 14 "f" 15 -}}
|
||||
{{- $colors := dict "primary" $brand.primaryColor "secondary" $brand.secondaryColor "primary-contrast" ($brand.primaryContrast | default "#FFFFFF") -}}
|
||||
:root {
|
||||
{{- range $name, $hex := $colors }}
|
||||
{{- /* Normalise: strip '#', lowercase, expand 3-digit shorthand (#05f -> #0055ff). */ -}}
|
||||
{{- $h := lower (strings.TrimPrefix "#" $hex) }}
|
||||
{{- if eq (len $h) 3 }}
|
||||
{{- $h = printf "%s%s%s%s%s%s" (substr $h 0 1) (substr $h 0 1) (substr $h 1 1) (substr $h 1 1) (substr $h 2 1) (substr $h 2 1) }}
|
||||
{{- end }}
|
||||
{{- /* Validate: exactly 6 hex characters, else fail the build naming the colour. */ -}}
|
||||
{{- $ok := eq (len $h) 6 }}
|
||||
{{- range $ch := (split $h "") }}{{- if not (isset $hexmap $ch) }}{{- $ok = false }}{{- end }}{{- end }}
|
||||
{{- if not $ok }}{{- errorf "brand.yaml: %s colour %q is not a valid 3- or 6-digit hex value (e.g. #0057FF)" $name $hex }}{{- end }}
|
||||
--brand-{{ $name }}: #{{ $h }};
|
||||
{{- if $ok }}
|
||||
--brand-{{ $name }}-rgb: {{ add (mul (index $hexmap (substr $h 0 1)) 16) (index $hexmap (substr $h 1 1)) }} {{ add (mul (index $hexmap (substr $h 2 1)) 16) (index $hexmap (substr $h 3 1)) }} {{ add (mul (index $hexmap (substr $h 4 1)) 16) (index $hexmap (substr $h 5 1)) }};
|
||||
{{- else }}
|
||||
--brand-{{ $name }}-rgb: 0 0 0;
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
/* --- Base layer --------------------------------------------------------- */
|
||||
@layer base {
|
||||
/* Alpine.js: hide elements until initialised to avoid flashes. */
|
||||
[x-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
body {
|
||||
@apply bg-white text-secondary antialiased;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
@apply font-bold tracking-tight text-secondary;
|
||||
}
|
||||
a {
|
||||
@apply text-primary underline-offset-2;
|
||||
}
|
||||
a:hover {
|
||||
@apply underline;
|
||||
}
|
||||
/* Visible keyboard focus for accessibility */
|
||||
:focus-visible {
|
||||
@apply outline outline-2 outline-offset-2 outline-primary;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Components --------------------------------------------------------- */
|
||||
@layer components {
|
||||
.site-container {
|
||||
@apply mx-auto w-full max-w-6xl px-4 sm:px-6;
|
||||
}
|
||||
|
||||
/* Buttons ------------------------------------------------------------- */
|
||||
.btn {
|
||||
@apply inline-flex items-center justify-center gap-2 rounded-lg px-5 py-3 text-base font-semibold no-underline transition-transform duration-150 focus-visible:outline-2;
|
||||
}
|
||||
.btn:hover {
|
||||
@apply -translate-y-0.5 no-underline;
|
||||
}
|
||||
.btn-primary {
|
||||
@apply btn bg-primary text-primary-contrast shadow-sm hover:shadow-md;
|
||||
}
|
||||
.btn-secondary {
|
||||
@apply btn border-2 border-primary bg-transparent text-primary hover:bg-primary hover:text-primary-contrast;
|
||||
}
|
||||
.btn-ghost {
|
||||
@apply btn bg-secondary/5 text-secondary hover:bg-secondary/10;
|
||||
}
|
||||
.btn-lg {
|
||||
@apply px-7 py-4 text-lg;
|
||||
}
|
||||
|
||||
/* Cards --------------------------------------------------------------- */
|
||||
.card {
|
||||
@apply rounded-xl border border-secondary/10 bg-white p-6 shadow-sm;
|
||||
}
|
||||
.card-hover {
|
||||
@apply transition-shadow duration-150 hover:shadow-lg;
|
||||
}
|
||||
|
||||
/* Badges -------------------------------------------------------------- */
|
||||
.badge {
|
||||
@apply inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold;
|
||||
}
|
||||
.badge-primary {
|
||||
@apply badge bg-primary/10 text-primary;
|
||||
}
|
||||
.badge-muted {
|
||||
@apply badge bg-secondary/10 text-secondary;
|
||||
}
|
||||
|
||||
/* Section helpers ----------------------------------------------------- */
|
||||
.section {
|
||||
@apply py-12 sm:py-16;
|
||||
}
|
||||
.eyebrow {
|
||||
@apply text-sm font-semibold uppercase tracking-wider text-primary;
|
||||
}
|
||||
|
||||
/* Rendered markdown body --------------------------------------------- */
|
||||
.prose-content {
|
||||
@apply max-w-none text-[1.05rem] leading-relaxed text-secondary/90;
|
||||
}
|
||||
.prose-content h2 {
|
||||
@apply mb-4 mt-10 text-2xl font-bold text-secondary;
|
||||
}
|
||||
.prose-content h3 {
|
||||
@apply mb-3 mt-8 text-xl font-semibold text-secondary;
|
||||
}
|
||||
.prose-content p {
|
||||
@apply mb-4;
|
||||
}
|
||||
.prose-content ul {
|
||||
@apply mb-4 list-disc space-y-1 pl-6;
|
||||
}
|
||||
.prose-content ol {
|
||||
@apply mb-4 list-decimal space-y-1 pl-6;
|
||||
}
|
||||
/* Exclude affiliate CTA buttons so their button styling is preserved. */
|
||||
.prose-content a:not([data-affiliate]) {
|
||||
@apply font-medium text-primary underline;
|
||||
}
|
||||
.prose-content a[data-affiliate] {
|
||||
@apply my-2 no-underline;
|
||||
}
|
||||
.prose-content blockquote {
|
||||
@apply my-6 border-l-4 border-primary/40 bg-secondary/5 py-2 pl-4 italic;
|
||||
}
|
||||
.prose-content table {
|
||||
@apply my-6 w-full border-collapse text-left text-sm;
|
||||
}
|
||||
.prose-content th {
|
||||
@apply border-b-2 border-secondary/20 bg-secondary/5 px-3 py-2 font-semibold;
|
||||
}
|
||||
.prose-content td {
|
||||
@apply border-b border-secondary/10 px-3 py-2 align-top;
|
||||
}
|
||||
.prose-content code {
|
||||
@apply rounded bg-secondary/10 px-1.5 py-0.5 text-sm;
|
||||
}
|
||||
|
||||
/* Data tables (payment table, etc.) ---------------------------------- */
|
||||
.data-table {
|
||||
@apply w-full border-collapse overflow-hidden text-left text-sm;
|
||||
}
|
||||
.data-table th {
|
||||
@apply border-b-2 border-secondary/20 bg-secondary/5 px-3 py-2 font-semibold;
|
||||
}
|
||||
.data-table td {
|
||||
@apply border-b border-secondary/10 px-3 py-2 align-middle;
|
||||
}
|
||||
.data-table tr:hover td {
|
||||
@apply bg-primary/5;
|
||||
}
|
||||
.data-table tr.is-active-locale td {
|
||||
@apply bg-primary/10;
|
||||
}
|
||||
|
||||
/* Nav ---------------------------------------------------------------- */
|
||||
.nav-link {
|
||||
@apply rounded-md px-3 py-2 text-sm font-medium text-secondary/80 no-underline hover:bg-secondary/5 hover:text-secondary;
|
||||
}
|
||||
.nav-link-active {
|
||||
@apply bg-primary/10 text-primary;
|
||||
}
|
||||
/* Desktop nav can scroll horizontally when there are many sections; hide the
|
||||
scrollbar chrome so it stays clean (still scrollable via trackpad/shift-wheel). */
|
||||
.nav-scroll {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
.nav-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Overlays: age gate + cookie consent -------------------------------- */
|
||||
@layer components {
|
||||
.age-gate {
|
||||
@apply fixed inset-0 z-[100] hidden items-center justify-center bg-secondary/95 p-4 text-center;
|
||||
}
|
||||
.age-gate-active .age-gate {
|
||||
@apply flex;
|
||||
}
|
||||
.age-gate-card {
|
||||
@apply w-full max-w-md rounded-2xl bg-white p-8 shadow-2xl;
|
||||
}
|
||||
.cookie-banner {
|
||||
@apply fixed inset-x-0 bottom-0 z-[90] border-t-2 border-primary/30 bg-white p-4 shadow-2xl sm:p-6;
|
||||
}
|
||||
.cookie-toggle {
|
||||
@apply flex items-start gap-3 rounded-lg border border-secondary/10 p-3;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Utilities layer ---------------------------------------------------- */
|
||||
@layer utilities {
|
||||
/* Lock page scroll while the age gate is open. */
|
||||
.age-gate-active {
|
||||
overflow: hidden;
|
||||
}
|
||||
.hero-gradient {
|
||||
background-image: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-secondary) 100%);
|
||||
}
|
||||
/* Placeholder used when an image resource is missing (guarded pipeline). */
|
||||
.img-placeholder {
|
||||
background-image: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-secondary) 100%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* =========================================================================
|
||||
tailwind.src.css — template-free entry used ONLY by `npm run build:css`
|
||||
to emit a standalone, verifiable Tailwind stylesheet (dist/).
|
||||
The site itself is compiled from main.css via Hugo Pipes (css.PostCSS),
|
||||
which additionally injects the data/brand.yaml :root tokens.
|
||||
========================================================================= */
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,175 @@
|
||||
/* =========================================================================
|
||||
age-gate.js — non-dismissible age verification interstitial.
|
||||
The <html> class "age-gate-active" is set as early as possible by an inline
|
||||
script in baseof.html to avoid a flash for returning (confirmed) visitors.
|
||||
This module wires the confirm/exit buttons, syncs the displayed age with the
|
||||
active locale (set by the locale switcher) and — because the dialog is
|
||||
role="dialog" aria-modal="true" — manages focus: it moves focus into the
|
||||
dialog, traps Tab/Shift+Tab, marks the rest of the page inert while active,
|
||||
and restores focus on confirm.
|
||||
|
||||
Deliberately dependency-free (no Alpine, no eval/new Function): the gate is
|
||||
driven purely by the CSS "age-gate-active" class, so it still fails safe
|
||||
(gate shows, confirm works) even if Alpine never loads.
|
||||
========================================================================= */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var STORAGE_KEY = "ageConfirmed";
|
||||
var INERT_SELECTORS = ["header", "main", "footer"];
|
||||
var FOCUSABLE =
|
||||
'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])';
|
||||
|
||||
var gate = null;
|
||||
var lastFocused = null;
|
||||
var trapActive = false;
|
||||
|
||||
function hasConfirmed() {
|
||||
try {
|
||||
return localStorage.getItem(STORAGE_KEY) === "1";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function focusableIn(el) {
|
||||
return Array.prototype.filter.call(
|
||||
el.querySelectorAll(FOCUSABLE),
|
||||
function (node) {
|
||||
return (
|
||||
node.offsetWidth > 0 ||
|
||||
node.offsetHeight > 0 ||
|
||||
node === document.activeElement
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function onKeydown(e) {
|
||||
if (!trapActive || e.key !== "Tab" || !gate) return;
|
||||
var f = focusableIn(gate);
|
||||
if (!f.length) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
var first = f[0];
|
||||
var last = f[f.length - 1];
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault();
|
||||
last.focus();
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function onFocusIn(e) {
|
||||
if (trapActive && gate && !gate.contains(e.target)) {
|
||||
var f = focusableIn(gate);
|
||||
if (f.length) f[0].focus();
|
||||
}
|
||||
}
|
||||
|
||||
function setBackgroundInert(on) {
|
||||
INERT_SELECTORS.forEach(function (sel) {
|
||||
var el = document.querySelector(sel);
|
||||
if (!el) return;
|
||||
if (on) {
|
||||
el.setAttribute("inert", "");
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
} else {
|
||||
el.removeAttribute("inert");
|
||||
el.removeAttribute("aria-hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function activateTrap() {
|
||||
if (trapActive || !gate) return;
|
||||
trapActive = true;
|
||||
lastFocused = document.activeElement;
|
||||
setBackgroundInert(true);
|
||||
document.addEventListener("keydown", onKeydown, true);
|
||||
document.addEventListener("focusin", onFocusIn);
|
||||
var confirmBtn = gate.querySelector("[data-age-confirm]");
|
||||
if (confirmBtn) {
|
||||
// Defer to ensure the element is focusable after the first paint.
|
||||
window.setTimeout(function () {
|
||||
confirmBtn.focus();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function releaseTrap() {
|
||||
trapActive = false;
|
||||
setBackgroundInert(false);
|
||||
document.removeEventListener("keydown", onKeydown, true);
|
||||
document.removeEventListener("focusin", onFocusIn);
|
||||
}
|
||||
|
||||
function restoreFocus() {
|
||||
var target =
|
||||
(lastFocused &&
|
||||
document.contains(lastFocused) &&
|
||||
lastFocused !== document.body &&
|
||||
lastFocused) ||
|
||||
document.querySelector("header a, header button") ||
|
||||
document.getElementById("main");
|
||||
if (!target) return;
|
||||
if (target.id === "main" && !target.hasAttribute("tabindex")) {
|
||||
target.setAttribute("tabindex", "-1");
|
||||
}
|
||||
try {
|
||||
target.focus();
|
||||
} catch (e) {
|
||||
/* focus can fail on detached nodes — ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function confirmAge() {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, "1");
|
||||
} catch (e) {
|
||||
/* storage unavailable — still allow through for this session */
|
||||
}
|
||||
document.documentElement.classList.remove("age-gate-active");
|
||||
releaseTrap();
|
||||
restoreFocus();
|
||||
}
|
||||
|
||||
function exitSite() {
|
||||
window.location.href = "https://www.google.com/";
|
||||
}
|
||||
|
||||
function init() {
|
||||
gate = document.getElementById("age-gate");
|
||||
if (!gate) {
|
||||
document.documentElement.classList.remove("age-gate-active");
|
||||
return;
|
||||
}
|
||||
|
||||
var confirmBtn = gate.querySelector("[data-age-confirm]");
|
||||
if (confirmBtn) {
|
||||
confirmBtn.addEventListener("click", confirmAge);
|
||||
}
|
||||
|
||||
var exitBtn = gate.querySelector("[data-age-exit]");
|
||||
if (exitBtn) {
|
||||
exitBtn.addEventListener("click", exitSite);
|
||||
}
|
||||
|
||||
if (hasConfirmed()) {
|
||||
document.documentElement.classList.remove("age-gate-active");
|
||||
setBackgroundInert(false);
|
||||
} else {
|
||||
document.documentElement.classList.add("age-gate-active");
|
||||
activateTrap();
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,154 @@
|
||||
/* =========================================================================
|
||||
cookie-consent.js — GDPR-style consent manager.
|
||||
Non-essential scripts must be added to the page as:
|
||||
<script type="text/plain" data-cookiecategory="analytics"> ... </script>
|
||||
They stay inert until the visitor grants that category, at which point this
|
||||
module reactivates them. Consent is stored in localStorage.
|
||||
========================================================================= */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var STORAGE_KEY = "cookieConsent";
|
||||
var CATEGORIES = ["necessary", "analytics", "marketing"];
|
||||
|
||||
function readConsent() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(STORAGE_KEY) || "null");
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeConsent(consent) {
|
||||
consent.necessary = true; // always on
|
||||
consent.ts = Date.now();
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(consent));
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
activateScripts(consent);
|
||||
}
|
||||
|
||||
function activateScripts(consent) {
|
||||
var nodes = document.querySelectorAll(
|
||||
'script[type="text/plain"][data-cookiecategory]'
|
||||
);
|
||||
Array.prototype.forEach.call(nodes, function (node) {
|
||||
if (node.getAttribute("data-cookie-activated") === "1") return;
|
||||
var cat = node.getAttribute("data-cookiecategory");
|
||||
if (!consent[cat]) return;
|
||||
|
||||
var s = document.createElement("script");
|
||||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
var attr = node.attributes[i];
|
||||
if (attr.name === "type" || attr.name === "data-cookiecategory") continue;
|
||||
s.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
s.type = "text/javascript";
|
||||
if (!node.src) {
|
||||
s.text = node.textContent;
|
||||
}
|
||||
node.parentNode.insertBefore(s, node.nextSibling);
|
||||
node.setAttribute("data-cookie-activated", "1");
|
||||
});
|
||||
}
|
||||
|
||||
function banner() {
|
||||
return document.getElementById("cookie-consent");
|
||||
}
|
||||
|
||||
function showBanner() {
|
||||
var b = banner();
|
||||
if (b) b.hidden = false;
|
||||
}
|
||||
|
||||
function hideBanner() {
|
||||
var b = banner();
|
||||
if (b) b.hidden = true;
|
||||
}
|
||||
|
||||
function acceptAll() {
|
||||
var consent = { necessary: true };
|
||||
CATEGORIES.forEach(function (c) {
|
||||
consent[c] = true;
|
||||
});
|
||||
writeConsent(consent);
|
||||
hideBanner();
|
||||
}
|
||||
|
||||
function rejectNonEssential() {
|
||||
var consent = { necessary: true, analytics: false, marketing: false };
|
||||
writeConsent(consent);
|
||||
hideBanner();
|
||||
}
|
||||
|
||||
function savePreferences() {
|
||||
var b = banner();
|
||||
var consent = { necessary: true };
|
||||
CATEGORIES.forEach(function (c) {
|
||||
if (c === "necessary") return;
|
||||
var box = b
|
||||
? b.querySelector('[data-cc-category="' + c + '"]')
|
||||
: null;
|
||||
consent[c] = !!(box && box.checked);
|
||||
});
|
||||
writeConsent(consent);
|
||||
hideBanner();
|
||||
}
|
||||
|
||||
function toggleManage() {
|
||||
var b = banner();
|
||||
if (!b) return;
|
||||
var panel = b.querySelector("[data-cc-panel]");
|
||||
if (panel) panel.hidden = !panel.hidden;
|
||||
}
|
||||
|
||||
function init() {
|
||||
var b = banner();
|
||||
var existing = readConsent();
|
||||
|
||||
if (existing) {
|
||||
activateScripts(existing);
|
||||
if (b) b.hidden = true;
|
||||
} else if (b) {
|
||||
b.hidden = false;
|
||||
}
|
||||
|
||||
if (!b) return;
|
||||
|
||||
var accept = b.querySelector("[data-cc-accept-all]");
|
||||
if (accept) accept.addEventListener("click", acceptAll);
|
||||
|
||||
var reject = b.querySelector("[data-cc-reject]");
|
||||
if (reject) reject.addEventListener("click", rejectNonEssential);
|
||||
|
||||
var save = b.querySelector("[data-cc-save]");
|
||||
if (save) save.addEventListener("click", savePreferences);
|
||||
|
||||
var manage = b.querySelector("[data-cc-manage]");
|
||||
if (manage) manage.addEventListener("click", toggleManage);
|
||||
}
|
||||
|
||||
// Public: reopen the consent banner (used by footer / cookie-policy links).
|
||||
window.openCookieSettings = function () {
|
||||
var existing = readConsent();
|
||||
var b = banner();
|
||||
if (b && existing) {
|
||||
CATEGORIES.forEach(function (c) {
|
||||
if (c === "necessary") return;
|
||||
var box = b.querySelector('[data-cc-category="' + c + '"]');
|
||||
if (box) box.checked = !!existing[c];
|
||||
});
|
||||
var panel = b.querySelector("[data-cc-panel]");
|
||||
if (panel) panel.hidden = false;
|
||||
}
|
||||
showBanner();
|
||||
};
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,47 @@
|
||||
/* =========================================================================
|
||||
games-filter.js — Alpine.js component powering the slots library filter.
|
||||
Cards are rendered server-side by Hugo (so thumbnails go through the image
|
||||
pipeline); this component toggles their visibility via data-attributes.
|
||||
Registered on `alpine:init`, so this file must load before the Alpine CDN.
|
||||
========================================================================= */
|
||||
document.addEventListener("alpine:init", function () {
|
||||
window.Alpine.data("gamesFilter", function () {
|
||||
return {
|
||||
provider: "all",
|
||||
category: "all",
|
||||
search: "",
|
||||
visibleCount: 0,
|
||||
|
||||
get normalizedSearch() {
|
||||
return this.search.trim().toLowerCase();
|
||||
},
|
||||
|
||||
cardVisible: function (el) {
|
||||
var okProvider =
|
||||
this.provider === "all" || el.dataset.provider === this.provider;
|
||||
var okCategory =
|
||||
this.category === "all" || el.dataset.category === this.category;
|
||||
var okSearch =
|
||||
this.normalizedSearch === "" ||
|
||||
(el.dataset.title || "").indexOf(this.normalizedSearch) !== -1;
|
||||
return okProvider && okCategory && okSearch;
|
||||
},
|
||||
|
||||
recount: function () {
|
||||
var self = this;
|
||||
var cards = this.$root.querySelectorAll("[data-game]");
|
||||
var n = 0;
|
||||
Array.prototype.forEach.call(cards, function (el) {
|
||||
if (self.cardVisible(el)) n++;
|
||||
});
|
||||
this.visibleCount = n;
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
this.provider = "all";
|
||||
this.category = "all";
|
||||
this.search = "";
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
Vendored
+5
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user