feat: Hugo engine for iGaming affiliate template (layouts, assets, i18n, verified data, CI build script)

This commit is contained in:
pipeline
2026-07-16 06:07:22 +03:00
parent b9385a1f72
commit 1816ff2093
58 changed files with 5631 additions and 0 deletions
+228
View File
@@ -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%);
}
}