/* ========================================================================= 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%); } }