Files
HugoBlox-kit/modules/blox/layouts/_partials/functions/parse_block_v3.html
T
George Cushen f584ad1610 fix(parse_block_v3): replace section_break mask-image with gradient overlay
The mask-image approach for section_break was wrong: making pixels
transparent reveals the body background, not the next sibling section
(which sits below in document flow, not behind in z-order). When the
body bg didn't match the adjacent section, this produced a visible
white/light strip between sections — exactly what the fade was
supposed to prevent.

Replace mask-image with absolutely-positioned gradient overlay divs at
the section's top/bottom edges. The overlay fades from transparent to
a user-specified colour matching the adjacent sibling's background.
Explicit colour declaration is required because Hugo can't reliably
auto-detect the next sibling's bg at parse time.

API change (no backward-compat — feature was added this session, no
sites depend on the legacy string form):

  # before
  section_break: fade-bottom

  # after
  section_break:
    fade_bottom: "#101828"   # CSS colour matching next section's bg
    fade_top:    "#0a0e27"   # optional, independent
    size:        "6rem"      # optional, default 6rem

Update the landing page hero (fade-bottom into dark logos band) and
cta-card (fade-top from white FAQ section above) to the new form.
2026-05-02 18:25:18 +01:00

361 lines
18 KiB
HTML

{{/* Hugo Blox Parser v3 - Preact Component Support */}}
{{/* Documentation: https://hugoblox.com/blocks/ */}}
{{/* License: https://github.com/HugoBlox/kit/blob/main/LICENSE.md */}}
{{ $page := .page }}
{{ $block := .block }}
{{- $block_type := partial "hbx/resolve-block-param" (dict "config" $block "context" "content section") | default "markdown" -}}
{{ $block_type = lower $block_type }}
{{/* Handle block renames/aliases */}}
{{ range $r := hugo.Data.blox_aliases.renames }}
{{ $block_type = cond (eq $block_type $r.old) $r.new $block_type }}
{{ end }}
{{/* Check if this is a Preact-enabled block by looking for component file */}}
{{ $component_path := printf "js/hbx/blocks/%s/component.jsx" $block_type }}
{{ $is_preact := resources.Get $component_path }}
{{/* Apply styling wrapper for all blocks (both Preact and traditional) */}}
{{ $bg := $block.design.background }}
{{ $style := "" }}
{{ $style_bg := "" }}
{{/* Process background colors */}}
{{ if $bg.color }}
{{ $color_type := printf "%T" $bg.color }}
{{ if eq $color_type "map[string]interface {}" }}
{{ if and $bg.color.light $bg.color.dark }}
{{ $light_color := $bg.color.light }}
{{ $dark_color := $bg.color.dark }}
{{ $style_bg = printf "background-color: %s;" $light_color }}
{{ $style_bg = printf "%s--dark-bg-color: %s;" $style_bg $dark_color }}
{{ else if $bg.color.light }}
{{ $light_color := $bg.color.light }}
{{ $style_bg = printf "background-color: %s;" $light_color }}
{{ else if $bg.color.dark }}
{{ $dark_color := $bg.color.dark }}
{{ $style_bg = printf "--dark-bg-color: %s; background-color: var(--dark-bg-color);" $dark_color }}
{{ end }}
{{ else }}
{{ $bg_color := $bg.color }}
{{ $style_bg = printf "background-color: %s;" $bg_color }}
{{ end }}
{{ end }}
{{/* Process gradients and images */}}
{{ $background_layers := slice }}
{{ $gradient_layer := "" }}
{{ $has_gradient_mesh := false }}
{{ $gradient_mesh_html := "" }}
{{/* Enhanced Gradient Mesh System */}}
{{ $gradient_mesh := $bg.gradient_mesh | default dict }}
{{ if $gradient_mesh.enable }}
{{ $has_gradient_mesh = true }}
{{/* Stacking-fix: the mesh wrapper sits at z-index:-10, while .home-section-bg sits at z-index:-1.
If we leave bg-color on home-section-bg, the colour paints OVER the mesh and the orbs are hidden.
Migrate the bg-color off home-section-bg and onto the section element itself, so the section's
own background paints BEHIND the mesh layer (orbs visible) and the radial gradient + image layers
on home-section-bg paint ABOVE the mesh (still visible). */}}
{{ if $style_bg }}
{{ $style = print $style $style_bg }}
{{ $style_bg = "" }}
{{ end }}
{{ $mesh_style := $gradient_mesh.style | default "orbs" }}
{{ $mesh_animation_raw := $gradient_mesh.animation | default "pulse" }}
{{ $mesh_intensity := $gradient_mesh.intensity | default "subtle" }}
{{/* Auto-theme: Mix Primary and Secondary brand colors for dynamic personalization */}}
{{ $mesh_colors := $gradient_mesh.colors | default (slice "primary-500/20" "secondary-500/20") }}
{{/* Map animation names to CSS classes */}}
{{ $mesh_animation := "" }}
{{ if eq $mesh_animation_raw "pulse" }}
{{ $mesh_animation = "pulse" }}
{{ else if eq $mesh_animation_raw "float" }}
{{ $mesh_animation = "float" }}
{{ else if eq $mesh_animation_raw "rotate" }}
{{ $mesh_animation = "rotate-slow" }}
{{ else if ne $mesh_animation_raw "none" }}
{{ $mesh_animation = "pulse" }} {{/* Default fallback */}}
{{ end }}
{{/* Build gradient mesh HTML */}}
{{ $mesh_html := "<div class=\"absolute inset-0 -z-10\">" }}
{{/* Base gradient overlay */}}
{{ $base_gradient := printf "<div class=\"absolute inset-0 bg-gradient-to-br from-%s via-transparent to-%s\"></div>" (index $mesh_colors 0) (index $mesh_colors 1) }}
{{ $mesh_html = printf "%s%s" $mesh_html $base_gradient }}
{{/* Animated elements based on style */}}
{{ if eq $mesh_style "orbs" }}
{{ $orb_count := $gradient_mesh.orb_count | default 2 }}
{{ $orb_positions := $gradient_mesh.positions | default (slice "top-0 left-1/4" "bottom-0 right-1/4") }}
{{ $orb_sizes := $gradient_mesh.sizes | default (slice "w-96 h-96" "w-96 h-96") }}
{{ range $index, $position := first $orb_count $orb_positions }}
{{ $size := index $orb_sizes $index | default "w-96 h-96" }}
{{ $color := index $mesh_colors $index | default "primary-500/30" }}
{{ $blur_class := cond (eq $mesh_intensity "bold") "blur-2xl" (cond (eq $mesh_intensity "medium") "blur-3xl" "blur-3xl") }}
{{ $opacity := cond (eq $mesh_intensity "bold") "40" (cond (eq $mesh_intensity "medium") "30" "30") }}
{{/* Extract base color and adjust opacity for orbs */}}
{{ $orb_color := $color }}
{{ if not (in $color "/") }}
{{ $orb_color = printf "%s/%s" $color $opacity }}
{{ end }}
{{ $animation_delay := "" }}
{{ if gt $index 0 }}
{{ $animation_delay = printf " style=\"animation-delay: %ds;\"" (mul $index 2) }}
{{ end }}
{{/* Build orb with or without animation */}}
{{ $animate_class := cond (ne $mesh_animation "") (printf "animate-%s" $mesh_animation) "" }}
{{ $orb := printf "<div class=\"absolute %s %s bg-%s rounded-full filter %s %s\"%s></div>" $position $size $orb_color $blur_class $animate_class $animation_delay }}
{{ $mesh_html = printf "%s%s" $mesh_html $orb }}
{{ end }}
{{ else if eq $mesh_style "waves" }}
{{/* Flowing wave effect - multiple horizontal bands */}}
{{ $wave_count := $gradient_mesh.wave_count | default 3 }}
{{ $blur_class := cond (eq $mesh_intensity "bold") "blur-2xl" (cond (eq $mesh_intensity "medium") "blur-3xl" "blur-3xl") }}
{{ range $index := seq 0 (sub $wave_count 1) }}
{{ $color_index := mod $index (len $mesh_colors) }}
{{ $color := index $mesh_colors $color_index }}
{{ $top_pos := mul $index 33 }}
{{ $height := "h-64" }}
{{ $anim_delay := printf " style=\"animation-delay: %ds; animation-duration: %ds;\"" (mul $index 1) (add 8 (mul $index 2)) }}
{{/* Apply animation if not none */}}
{{ $animate_class := cond (ne $mesh_animation "") (printf "animate-%s" $mesh_animation) "" }}
{{ $wave := printf "<div class=\"absolute left-0 right-0 %s bg-%s filter %s %s\" style=\"top: %d%%; transform: skewY(-2deg);\"%s></div>" $height $color $blur_class $animate_class $top_pos $anim_delay }}
{{ $mesh_html = printf "%s%s" $mesh_html $wave }}
{{ end }}
{{ else if eq $mesh_style "dots" }}
{{/* Dotted grid pattern */}}
{{ $dot_size := $gradient_mesh.dot_size | default "w-4 h-4" }}
{{ $spacing := $gradient_mesh.spacing | default "16" }}
{{ $color := index $mesh_colors 0 | default "primary-500/20" }}
{{/* Create a dotted pattern background */}}
{{ $dots := printf "<div class=\"absolute inset-0\" style=\"background-image: radial-gradient(circle, rgb(59 130 246 / 0.2) 2px, transparent 2px); background-size: %spx %spx; background-position: 0 0, %spx %spx;\"></div>" $spacing $spacing (div (int $spacing) 2) (div (int $spacing) 2) }}
{{ $mesh_html = printf "%s%s" $mesh_html $dots }}
{{ else if eq $mesh_style "grid" }}
{{/* Grid lines pattern */}}
{{ $color := index $mesh_colors 0 | default "primary-500/10" }}
{{ $line_width := $gradient_mesh.line_width | default "1" }}
{{ $spacing := $gradient_mesh.spacing | default "32" }}
{{/* Create grid pattern */}}
{{ $grid := printf "<div class=\"absolute inset-0\" style=\"background-image: linear-gradient(rgb(59 130 246 / 0.1) %spx, transparent %spx), linear-gradient(90deg, rgb(59 130 246 / 0.1) %spx, transparent %spx); background-size: %spx %spx;\"></div>" $line_width $line_width $line_width $line_width $spacing $spacing }}
{{ $mesh_html = printf "%s%s" $mesh_html $grid }}
{{ end }}
{{ $mesh_html = printf "%s</div>" $mesh_html }}
{{ $gradient_mesh_html = $mesh_html | safeHTML }}
{{ end }}
{{/* Linear or radial gradient (radial added for 2026 hero spotlight pattern) */}}
{{ if and $bg.gradient.start $bg.gradient.end }}
{{ $start_color := $bg.gradient.start }}
{{ $end_color := $bg.gradient.end }}
{{ $gradient_type := $bg.gradient.type | default "linear" }}
{{ if eq $gradient_type "radial" }}
{{ $shape := $bg.gradient.shape | default "ellipse" }}
{{ $position := $bg.gradient.position | default "center" }}
{{ $size := $bg.gradient.size | default "farthest-corner" }}
{{ $gradient_layer = printf "radial-gradient(%s %s at %s, %s, %s)" $shape $size $position $start_color $end_color }}
{{ else }}
{{ $angle := string $bg.gradient.direction | default "135" }}
{{ $gradient_layer = printf "linear-gradient(%sdeg, %s, %s)" $angle $start_color $end_color }}
{{ end }}
{{ end }}
{{/* Process video backgrounds */}}
{{ $bg_video := "" }}
{{ $bg_video_class := "" }}
{{ if $bg.video.filename }}
{{ $bg_video = resources.Get (printf "media/%s" $bg.video.filename) }}
{{ if $bg.video.flip }}
{{ $bg_video_class = "flip" }}
{{ end }}
{{ end }}
{{/* Process image backgrounds */}}
{{ if $bg.image.filename }}
{{ $bg_img := resources.Get (printf "media/%s" $bg.image.filename) }}
{{ if $bg_img }}
{{ if reflect.IsImageResourceProcessable $bg_img }}
{{ $bg_img = $bg_img.Fit "1920x1920 webp" }}
{{ end }}
{{ $image_layer := printf "url('%s')" $bg_img.RelPermalink }}
{{ $background_layers = $background_layers | append $image_layer }}
{{ else }}
{{ errorf "Couldn't find `%s` in the `assets/media/` folder - please add it." $bg.image.filename }}
{{ end }}
{{ end }}
{{ if or $background_layers $gradient_layer }}
{{ $all_layers := $background_layers }}
{{ if $gradient_layer }}
{{ $all_layers = $all_layers | append $gradient_layer }}
{{ end }}
{{ if $all_layers }}
{{ $combined_backgrounds := delimit $all_layers ", " }}
{{ $style_bg = printf "%sbackground-image: %s;" $style_bg $combined_backgrounds }}
{{/* Background image size/position/repeat properties */}}
{{ $layer_count := len $all_layers }}
{{ if gt $layer_count 1 }}
{{/* Multiple layers: image + gradient */}}
{{ $img_size := $bg.image.size | default "cover" }}
{{ $img_position := $bg.image.position | default "center" }}
{{ $img_repeat := $bg.image.repeat | default "no-repeat" }}
{{ if eq $img_position "repeat" }}
{{ $img_repeat = "repeat" }}
{{ $img_position = "center" }}
{{ end }}
{{ $style_bg = printf "%sbackground-size: %s, cover;" $style_bg $img_size }}
{{ $style_bg = printf "%sbackground-position: %s, center;" $style_bg $img_position }}
{{ $style_bg = printf "%sbackground-repeat: %s, no-repeat;" $style_bg $img_repeat }}
{{ else }}
{{/* Single layer */}}
{{ if $background_layers }}
{{ with $bg.image.size }}
{{ $style_bg = printf "%sbackground-size: %s;" $style_bg . }}
{{ end }}
{{ with $bg.image.position }}
{{ if eq . "repeat" }}
{{ $style_bg = printf "%sbackground-position: center;" $style_bg }}
{{ $style_bg = printf "%sbackground-repeat: repeat;" $style_bg }}
{{ else }}
{{ $style_bg = printf "%sbackground-position: %s;" $style_bg . }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{/* Background image filters: brightness, blur, contrast, saturate, grayscale */}}
{{ with $bg.image.filters }}
{{ $filter_parts := slice }}
{{ with .brightness }}
{{ $filter_parts = $filter_parts | append (printf "brightness(%s)" (string .)) }}
{{ end }}
{{ with .blur }}
{{ $filter_parts = $filter_parts | append (printf "blur(%s)" (string .)) }}
{{ end }}
{{ with .contrast }}
{{ $filter_parts = $filter_parts | append (printf "contrast(%s)" (string .)) }}
{{ end }}
{{ with .saturate }}
{{ $filter_parts = $filter_parts | append (printf "saturate(%s)" (string .)) }}
{{ end }}
{{ with .grayscale }}
{{ $filter_parts = $filter_parts | append (printf "grayscale(%s)" (string .)) }}
{{ end }}
{{ if $filter_parts }}
{{ $style_bg = printf "%sfilter: %s;" $style_bg (delimit $filter_parts " ") }}
{{ end }}
{{ end }}
{{ with $block.design.spacing.padding }}
{{ $style_pad := printf "padding: %s;" (delimit . " ") }}
{{ $style = print $style $style_pad }}
{{ end }}
{{ with $block.design.css_style }}
{{ $style = print $style . }}
{{ end }}
{{/* Support for clip path (design.clip_path) */}}
{{ with $block.design.clip_path }}
{{ $style = printf "%sclip-path: %s;" $style . }}
{{ end }}
{{/* Section break: smooth transitions between sibling sections via gradient
overlay divs at the section's top/bottom edges. Each edge fades from
transparent (toward the section's content) to a user-specified colour
(matching the adjacent sibling's background).
Example — hero fading into a dark logos band beneath:
section_break:
fade_bottom: "#101828" # any CSS colour (hex, rgb, var, named)
fade_top: "#0a0e27" # optional, independent of fade_bottom
size: "6rem" # optional, default 6rem (applies to both edges)
Why explicit colour: a gradient overlay needs to know what colour to fade
INTO. Hugo can't reliably auto-detect the next sibling's background at
parse time, so the user declares intent. */}}
{{ $break_overlay := "" }}
{{ with $block.design.section_break }}
{{ $sb_size := .size | default "6rem" }}
{{ $overlays := slice }}
{{ with .fade_bottom }}
{{ $overlays = $overlays | append (printf "<div class=\"absolute inset-x-0 bottom-0 pointer-events-none z-0\" style=\"height: %s; background: linear-gradient(to bottom, transparent, %s);\" aria-hidden=\"true\"></div>" $sb_size .) }}
{{ end }}
{{ with .fade_top }}
{{ $overlays = $overlays | append (printf "<div class=\"absolute inset-x-0 top-0 pointer-events-none z-0\" style=\"height: %s; background: linear-gradient(to top, transparent, %s);\" aria-hidden=\"true\"></div>" $sb_size .) }}
{{ end }}
{{ $break_overlay = delimit $overlays "" }}
{{ end }}
{{ $hash_id := $block.id | default (printf "section-%s" (replace $block_type "." "-")) }}
{{ $css_classes := $block.design.css_class | default "" }}
{{ $widget_class := printf "blox-%s" (strings.ReplacePairs $block_type "." "-" "_" "-") }}
{{/* Extract background-specific CSS classes */}}
{{ $bg_css_classes := $bg.css_class | default "" }}
{{/* Route to appropriate handler */}}
{{ if $is_preact }}
{{/* Render Preact block with styling wrapper */}}
<section id="{{$hash_id}}" class="relative {{if $has_gradient_mesh}}overflow-hidden{{end}} hbb-section {{$widget_class}} {{if $bg.text_color_light}}dark{{else if (eq $bg.text_color_light false)}}light{{end}} {{with $css_classes}}{{.}}{{end}}" {{with $style}}style="{{. | safeCSS}}"{{end}}>
{{/* Gradient Mesh Background */}}
{{ if $has_gradient_mesh }}{{ $gradient_mesh_html }}{{ end }}
<div class="home-section-bg {{if $bg.image}} bg-image{{if ($bg.image.parallax | default true) }} parallax{{end}}{{end}}{{with $bg_css_classes}} {{.}}{{end}}" {{with $style_bg}}style="{{. | safeCSS}}"{{end}}>
{{with $bg_video}}<video class="bg-video {{$bg_video_class}}" playsinline="" preload="auto" loop="" muted autoplay="" tabindex="-1" width="100%" height="100%" src="{{.RelPermalink}}" style="width: 100%; height: 100%; object-fit: cover; object-position: center center; opacity: 1;"></video>{{end}}
</div>
{{/* Section-break gradient overlays (above bg, below content) */}}
{{ with $break_overlay }}{{ . | safeHTML }}{{ end }}
{{/* Use generic Preact wrapper for content */}}
{{ partial "blox/preact-wrapper" (dict "wcPage" $page "wcBlock" $block "wcBlockType" $block_type "wcIdentifier" $hash_id) }}
</section>
{{ else }}
{{/* Fall back to traditional Go template blocks */}}
{{ $block_path := printf "hbx/blocks/%s/block.html" $block_type }}
{{ if not (templates.Exists (printf "_partials/%s" $block_path)) }}
{{ errorf "%s uses a `%s` block but the `%s` block was not found at `%s`. Check the name of the block and ensure it exists in the blox/%s/ directory." $page.File.Path $block_type $block_type $block_path $block_type }}
{{ end }}
{{/* Handle legacy block requirements */}}
{{ if $block.Page.Store.Get "has_mermaid" }}
{{ $page.Page.Store.Set "has_mermaid" true }}
{{ end }}
{{ if (gt (len (findRE `\{\{< gallery` $block.content.text 1)) 0) }}
{{ $page.Page.Store.Set "has_gallery" true }}
{{ end }}
{{ $widget_args := dict "wcPage" $page "wcBlock" $block "wcIdentifier" $hash_id }}
{{/* Render traditional block with wrapper */}}
<section id="{{$hash_id}}" class="relative {{if $has_gradient_mesh}}overflow-hidden{{end}} hbb-section {{$widget_class}} {{if $bg.text_color_light}}dark{{else if (eq $bg.text_color_light false)}}light{{end}} {{with $css_classes}}{{.}}{{end}}" {{with $style}}style="{{. | safeCSS}}"{{end}}>
{{/* Gradient Mesh Background */}}
{{ if $has_gradient_mesh }}{{ $gradient_mesh_html }}{{ end }}
<div class="home-section-bg {{if $bg.image}} bg-image{{if ($bg.image.parallax | default true) }} parallax{{end}}{{end}}{{with $bg_css_classes}} {{.}}{{end}}" {{with $style_bg}}style="{{. | safeCSS}}"{{end}}>
{{with $bg_video}}<video class="bg-video {{$bg_video_class}}" playsinline="" preload="auto" loop="" muted autoplay="" tabindex="-1" width="100%" height="100%" src="{{.RelPermalink}}" style="width: 100%; height: 100%; object-fit: cover; object-position: center center; opacity: 1;"></video>{{end}}
</div>
{{/* Section-break gradient overlays (above bg, below content) */}}
{{ with $break_overlay }}{{ . | safeHTML }}{{ end }}
{{ partial $block_path $widget_args }}
</section>
{{ end }}