From 9140e2f776a5bdb29ec0d1bb021584538a84fe3f Mon Sep 17 00:00:00 2001 From: George Cushen Date: Wed, 17 Sep 2025 00:08:22 +0100 Subject: [PATCH] feat: modernize blocks with 2025 glassmorphism & UX improvements CTA Card: - Add 2025 glassmorphism styling with backdrop blur - Implement icon support for buttons with hover animations - Add contrast control (text_color, overlay_opacity) - Support custom backgrounds (image, gradient, texture layers) Hero Block: - Fix hash fragment URL parsing (#portfolio -> #portfolio, not /#portfolio) Infrastructure: - Add reusable glassmorphism CSS components - Create SVG noise texture for glassmorphism effects --- .../assets/css/components/all.css | 19 +- .../assets/css/components/glassmorphism.css | 223 ++++++++++++++++++ .../assets/media/textures/noise-pattern.svg | 20 ++ modules/blox-tailwind/blox/cta-card/README.md | 173 +++++++++++++- .../blox-tailwind/blox/cta-card/block.html | 103 ++++++-- .../blox-tailwind/blox/cta-card/schema.json | 114 ++++++++- modules/blox-tailwind/blox/hero/block.html | 37 ++- 7 files changed, 655 insertions(+), 34 deletions(-) create mode 100644 modules/blox-tailwind/assets/css/components/glassmorphism.css create mode 100644 modules/blox-tailwind/assets/media/textures/noise-pattern.svg diff --git a/modules/blox-tailwind/assets/css/components/all.css b/modules/blox-tailwind/assets/css/components/all.css index 760b35d8..fb15c736 100644 --- a/modules/blox-tailwind/assets/css/components/all.css +++ b/modules/blox-tailwind/assets/css/components/all.css @@ -1,9 +1,10 @@ -@import "page.css"; -@import "task-list.css"; -@import "copy.css"; -@import "sidebar-left.css"; -@import "charts.css"; -@import "steps.css"; -@import "cards.css"; -@import "math.css"; -@import "author-notes.css"; +@import 'page.css'; +@import 'task-list.css'; +@import 'copy.css'; +@import 'sidebar-left.css'; +@import 'charts.css'; +@import 'steps.css'; +@import 'cards.css'; +@import 'math.css'; +@import 'author-notes.css'; +@import 'glassmorphism.css'; diff --git a/modules/blox-tailwind/assets/css/components/glassmorphism.css b/modules/blox-tailwind/assets/css/components/glassmorphism.css new file mode 100644 index 00000000..b5c9946c --- /dev/null +++ b/modules/blox-tailwind/assets/css/components/glassmorphism.css @@ -0,0 +1,223 @@ +/** + * 2025 Hugo Blox Glassmorphism Utility Classes + * + * Modern glassmorphism effects for Hugo Blox CTA cards and other components + */ + +/* ========================================================================== + Glassmorphism Base Classes + ========================================================================== */ + +.glassmorphism-primary { + @apply relative overflow-hidden; + background: linear-gradient( + 135deg, + rgb(var(--color-primary-500) / 0.9), + rgb(var(--color-primary-600) / 0.95), + rgb(var(--color-primary-700) / 0.9) + ); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); +} + +.glassmorphism-primary::before { + content: ''; + @apply absolute inset-0 pointer-events-none; + background: linear-gradient(45deg, rgb(0 0 0 / 0.1), transparent, rgb(255 255 255 / 0.1)); + mix-blend-mode: overlay; +} + +.glassmorphism-secondary { + @apply relative overflow-hidden; + background: linear-gradient( + 135deg, + rgb(var(--color-secondary-500) / 0.9), + rgb(var(--color-secondary-600) / 0.95), + rgb(var(--color-secondary-700) / 0.9) + ); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); +} + +.glassmorphism-secondary::before { + content: ''; + @apply absolute inset-0 pointer-events-none; + background: linear-gradient(45deg, rgb(0 0 0 / 0.1), transparent, rgb(255 255 255 / 0.1)); + mix-blend-mode: overlay; +} + +.glassmorphism-dark { + @apply relative overflow-hidden; + background: linear-gradient(135deg, rgb(0 0 0 / 0.7), rgb(31 41 55 / 0.8), rgb(0 0 0 / 0.9)); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); +} + +.glassmorphism-dark::before { + content: ''; + @apply absolute inset-0 pointer-events-none; + background: linear-gradient(45deg, rgb(255 255 255 / 0.05), transparent, rgb(255 255 255 / 0.1)); + mix-blend-mode: overlay; +} + +.glassmorphism-light { + @apply relative overflow-hidden; + background: linear-gradient(135deg, rgb(255 255 255 / 0.9), rgb(248 250 252 / 0.95), rgb(241 245 249 / 0.9)); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); +} + +.glassmorphism-light::before { + content: ''; + @apply absolute inset-0 pointer-events-none; + background: linear-gradient(45deg, rgb(0 0 0 / 0.02), transparent, rgb(255 255 255 / 0.05)); + mix-blend-mode: overlay; +} + +/* ========================================================================== + Noise Texture Classes (when used as background image) + ========================================================================== */ + +.noise-texture { + position: relative; +} + +.noise-texture::after { + content: ''; + @apply absolute inset-0 pointer-events-none; + background-image: url('/media/textures/noise-pattern.svg'); + background-size: 100px 100px; + background-repeat: repeat; + opacity: 0.03; + mix-blend-mode: multiply; +} + +.dark .noise-texture::after { + mix-blend-mode: screen; + opacity: 0.02; +} + +/* ========================================================================== + Enhanced Ring Classes for 2025 Design + ========================================================================== */ + +.glass-ring { + @apply ring-1 ring-white/20 hover:ring-white/40 dark:ring-gray-700/50 dark:hover:ring-gray-600/70; +} + +.glass-shadow { + box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25), 0 0 0 1px rgb(255 255 255 / 0.1), + inset 0 1px 0 rgb(255 255 255 / 0.1); +} + +.dark .glass-shadow { + box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5), 0 0 0 1px rgb(255 255 255 / 0.05), + inset 0 1px 0 rgb(255 255 255 / 0.05); +} + +/* ========================================================================== + CTA Card Glassmorphism Overlay (works with section backgrounds) + ========================================================================== */ + +.cta-glassmorphism { + @apply relative; + + /* Dynamic overlay opacity control */ + --glassmorphism-opacity: 0.15; + + /* Subtle overlay tint that works with any background */ + background: linear-gradient( + 135deg, + rgb(255 255 255 / var(--glassmorphism-opacity)) 0%, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) * 0.5)) 50%, + rgb(255 255 255 / var(--glassmorphism-opacity)) 100% + ); + + /* Enhanced glassmorphism effects */ + backdrop-filter: blur(20px) saturate(1.2); + -webkit-backdrop-filter: blur(20px) saturate(1.2); + + /* Modern border and shadow for light mode */ + border: 1px solid rgb(255 255 255 / calc(var(--glassmorphism-opacity) + 0.1)); + box-shadow: 0 32px 64px -12px rgb(0 0 0 / 0.25), + inset 0 1px 0 rgb(255 255 255 / calc(var(--glassmorphism-opacity) + 0.05)); +} + +.cta-glassmorphism:hover { + /* Enhanced hover state */ + background: linear-gradient( + 135deg, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) + 0.05)) 0%, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) * 0.7)) 50%, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) + 0.05)) 100% + ); + + border: 1px solid rgb(255 255 255 / calc(var(--glassmorphism-opacity) + 0.2)); + box-shadow: 0 40px 80px -16px rgb(0 0 0 / 0.3), + inset 0 1px 0 rgb(255 255 255 / calc(var(--glassmorphism-opacity) + 0.1)); +} + +/* Dark mode adjustments for CTA glassmorphism */ +.dark .cta-glassmorphism { + background: linear-gradient( + 135deg, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) * 0.5)) 0%, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) * 0.25)) 50%, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) * 0.5)) 100% + ); + + border: 1px solid rgb(255 255 255 / var(--glassmorphism-opacity)); + box-shadow: 0 32px 64px -12px rgb(0 0 0 / 0.4), + inset 0 1px 0 rgb(255 255 255 / calc(var(--glassmorphism-opacity) * 0.5)); +} + +.dark .cta-glassmorphism:hover { + background: linear-gradient( + 135deg, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) * 0.75)) 0%, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) * 0.4)) 50%, + rgb(255 255 255 / calc(var(--glassmorphism-opacity) * 0.75)) 100% + ); + + border: 1px solid rgb(255 255 255 / calc(var(--glassmorphism-opacity) + 0.05)); +} + +/* ========================================================================== + CTA Button Glassmorphism (nested within CTA cards) + ========================================================================== */ + +.cta-glassmorphism .group { + /* Ensure proper text contrast in light mode */ + color: rgb(var(--color-gray-900)); +} + +.dark .cta-glassmorphism .group { + /* Maintain white text in dark mode */ + color: rgb(var(--color-gray-100)); +} + +/* Enhanced button glassmorphism for light mode visibility */ +.cta-glassmorphism .group > div:first-child { + /* Light mode button background */ + background: rgb(255 255 255 / 0.9); + border: 1px solid rgb(0 0 0 / 0.1); + box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.1), inset 0 1px 0 rgb(255 255 255 / 0.3); +} + +.dark .cta-glassmorphism .group > div:first-child { + /* Dark mode button background */ + background: rgb(0 0 0 / 0.4); + border: 1px solid rgb(255 255 255 / 0.2); + box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.3), 0 4px 6px -2px rgb(0 0 0 / 0.2), inset 0 1px 0 rgb(255 255 255 / 0.1); +} + +.cta-glassmorphism .group:hover > div:first-child { + background: rgb(255 255 255); + border: 1px solid rgb(0 0 0 / 0.15); + box-shadow: 0 20px 40px -8px rgb(0 0 0 / 0.15), 0 8px 12px -4px rgb(0 0 0 / 0.1), inset 0 1px 0 rgb(255 255 255 / 0.4); +} + +.dark .cta-glassmorphism .group:hover > div:first-child { + background: rgb(0 0 0 / 0.6); + border: 1px solid rgb(255 255 255 / 0.3); +} diff --git a/modules/blox-tailwind/assets/media/textures/noise-pattern.svg b/modules/blox-tailwind/assets/media/textures/noise-pattern.svg new file mode 100644 index 00000000..00cf1b6b --- /dev/null +++ b/modules/blox-tailwind/assets/media/textures/noise-pattern.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/blox-tailwind/blox/cta-card/README.md b/modules/blox-tailwind/blox/cta-card/README.md index 0eb75137..eade1ad1 100644 --- a/modules/blox-tailwind/blox/cta-card/README.md +++ b/modules/blox-tailwind/blox/cta-card/README.md @@ -6,12 +6,13 @@ Make a bold statement and drive immediate action with the CTA Card block - a hig ## ✨ Key Features -- **Bold Typography**: Eye-catching large text that commands attention -- **Customizable Styling**: Full control over background colors, CSS classes, and custom styles -- **High-Contrast Design**: Dark backgrounds with light text for maximum readability -- **Prominent Button**: Conversion-optimized button styling with hover effects -- **Markdown Support**: Rich text formatting for titles and descriptions -- **Mobile Responsive**: Scales beautifully from mobile to desktop +- **🪟 2025 Glassmorphism**: Modern translucent backgrounds with backdrop blur effects +- **🎨 Gradient Typography**: Beautiful gradient text effects for maximum visual appeal +- **🔗 Smart Icon Support**: Add icons to buttons with automatic sizing and hover animations +- **📱 Enhanced Responsiveness**: Fluid typography scaling from mobile to desktop +- **✨ Micro-Interactions**: Smooth hover animations with glow effects and transforms +- **🎯 Customizable Styling**: Multiple glassmorphism presets or custom CSS classes +- **♿ Accessibility Enhanced**: Improved focus states and semantic markup ## 🎯 Perfect For @@ -48,3 +49,163 @@ Make a bold statement and drive immediate action with the CTA Card block - a hig Transform casual visitors into engaged users with a CTA Card that's impossible to ignore. +## 🎨 2025 Glassmorphism Styling Options + +### **Glassmorphism Primary** (Recommended) + +```yaml +- block: cta-card + content: + title: 'Ready to Get Started?' + text: 'Join thousands of users creating amazing websites' + button: + text: 'Get Started Free' + url: '/signup/' + icon: 'hero/rocket-launch' + design: + card: + css_class: 'cta-glassmorphism' + text_color: 'auto' # auto|light|dark + overlay_opacity: 0.15 # 0.0-1.0 for contrast control + background: + gradient: + start: 'primary-500' + end: 'primary-700' + direction: 135 + image: + filename: 'textures/noise-pattern.svg' + size: '32px' + position: 'repeat' +``` + +### **Glassmorphism Dark** + +```yaml +design: + card: + css_class: 'glassmorphism-dark glass-ring glass-shadow text-white' +``` + +### **Glassmorphism Light** + +```yaml +design: + card: + css_class: 'glassmorphism-light glass-ring glass-shadow text-gray-900' +``` + +### **Custom Background + Glassmorphism** + +```yaml +design: + card: + css_class: 'glassmorphism-secondary noise-texture text-white' + background: + color: 'primary-600' + image: + filename: 'your-background.jpg' + size: 'cover' + position: 'center' + opacity: 0.8 + gradient: + start: 'primary-500' + end: 'secondary-600' + direction: 135 +``` + +## 🔗 Icon Support + +Add icons to your CTA buttons: + +```yaml +button: + text: 'Download Now' + url: '/download/' + icon: 'hero/download' # Download icon + +button: + text: 'Contact Us' + url: 'mailto:hello@example.com' + icon: 'hero/paper-airplane' # Email icon + +button: + text: 'Learn More' + url: '#features' + icon: 'hero/arrow-right' # Arrow icon +``` + +## 🎨 Contrast Control Options + +### **Text Color Modes** + +```yaml +card: + text_color: 'auto' # Auto-detects based on background brightness + text_color: 'light' # Force white text (for dark backgrounds) + text_color: 'dark' # Force dark text (for light backgrounds) +``` + +### **Overlay Opacity Control** + +```yaml +card: + overlay_opacity: 0.1 # Subtle glassmorphism (better for text contrast) + overlay_opacity: 0.15 # Default glassmorphism + overlay_opacity: 0.25 # Strong glassmorphism (may reduce contrast) + overlay_opacity: 0.05 # Minimal glassmorphism (maximum contrast) +``` + +### **Perfect Contrast Examples** + +**Dark Background (Light Text):** + +```yaml +design: + card: + css_class: 'cta-glassmorphism' + text_color: 'light' + overlay_opacity: 0.1 + background: + gradient: + start: 'gray-800' + end: 'gray-900' +``` + +**Light Background (Dark Text):** + +```yaml +design: + card: + css_class: 'cta-glassmorphism' + text_color: 'dark' + overlay_opacity: 0.2 + background: + gradient: + start: 'gray-100' + end: 'gray-300' +``` + +**Vibrant Background (Auto Detection):** + +```yaml +design: + card: + css_class: 'cta-glassmorphism' + text_color: 'auto' # Will detect primary-500 as medium and use adaptive colors + overlay_opacity: 0.15 + background: + gradient: + start: 'primary-500' + end: 'primary-700' +``` + +## ✨ Available Glassmorphism Classes + +- **`cta-glassmorphism`**: Smart adaptive glassmorphism with contrast controls +- **`glassmorphism-primary`**: Primary color glassmorphism (legacy) +- **`glassmorphism-secondary`**: Secondary color glassmorphism (legacy) +- **`glassmorphism-dark`**: Dark glassmorphism (legacy) +- **`glassmorphism-light`**: Light glassmorphism (legacy) +- **`glass-ring`**: Enhanced ring borders +- **`glass-shadow`**: Multi-layer glass shadows +- **`noise-texture`**: Subtle noise texture overlay diff --git a/modules/blox-tailwind/blox/cta-card/block.html b/modules/blox-tailwind/blox/cta-card/block.html index 615c9c32..99bcdd02 100644 --- a/modules/blox-tailwind/blox/cta-card/block.html +++ b/modules/blox-tailwind/blox/cta-card/block.html @@ -11,20 +11,95 @@ {{ $card_class := $block.design.card.css_class }} {{ $card_style := $block.design.card.css_style }} +{{/* Contrast and accessibility controls */}} +{{ $text_color_mode := $block.design.card.text_color | default "auto" }} +{{ $overlay_opacity := $block.design.card.overlay_opacity | default 0.15 }} -
-

- {{ $title }} -

-
- {{ $text }} -
- {{ with $block.content.button }} - +{{/* Auto-detect text color based on background */}} +{{ $text_classes := "" }} +{{ $button_classes := "" }} +{{ if eq $text_color_mode "light" }} + {{ $text_classes = "text-white" }} + {{ $button_classes = "text-gray-900" }} +{{ else if eq $text_color_mode "dark" }} + {{ $text_classes = "text-gray-900" }} + {{ $button_classes = "text-gray-900" }} +{{ else }} + {{/* Auto mode - detect based on gradient colors */}} + {{ $is_dark_bg := false }} + {{ with $block.design.background.gradient.start }} + {{ if or (hasPrefix . "primary-7") (hasPrefix . "primary-8") (hasPrefix . "primary-9") (hasPrefix . "secondary-7") (hasPrefix . "secondary-8") (hasPrefix . "secondary-9") }} + {{ $is_dark_bg = true }} + {{ end }} {{ end }} + {{ if $is_dark_bg }} + {{ $text_classes = "text-white" }} + {{ $button_classes = "text-gray-900" }} + {{ else }} + {{ $text_classes = "text-gray-900 dark:text-white" }} + {{ $button_classes = "text-gray-900 dark:text-gray-100" }} + {{ end }} +{{ end }} + + +{{/* Modern 2025 CTA Card - Background controlled via design.background in front matter */}} +{{ $custom_style := printf "--glassmorphism-opacity: %s;" (string $overlay_opacity) }} +{{ if $card_style }} + {{ $custom_style = printf "%s%s" $custom_style $card_style }} +{{ end }} + +
+ + {{/* Modern Typography Hierarchy - Responsive text colors */}} +

+ {{ $title }} +

+ + {{/* Enhanced Text with Better Spacing - Responsive colors */}} +
+ {{ $text }} +
+ + {{ with $block.content.button }} + {{/* Modern 2025 Button Container */}} +
+ {{ $button_link := .url }} + {{ $button_scheme := (urls.Parse $button_link).Scheme }} + {{ $button_target := "" }} + {{ if not $button_scheme }} + {{/* Check if it's a hash fragment (page anchor) */}} + {{ if hasPrefix $button_link "#" }} + {{/* Keep hash fragments as-is */}} + {{ $button_link = $button_link }} + {{ else }} + {{/* Apply relLangURL for relative paths */}} + {{ $button_link = $button_link | relLangURL }} + {{ end }} + {{ else if in (slice "http" "https") $button_scheme }} + {{ $button_target = "target=\"_blank\" rel=\"noopener\"" }} + {{ end }} + + {{/* 2025 Glassmorphism Button */}} + + + {{/* Button Background with Glassmorphism */}} +
+ + {{/* Button Content */}} + {{ .text }} + {{ with .icon }} + + {{ partial "functions/get_icon" (dict "name" . "attributes" "style=\"height: 1.25em\" class='inline-block'") }} + + {{ end }} + + {{/* Subtle glow effect */}} +
+
+
+ {{ end }} +
diff --git a/modules/blox-tailwind/blox/cta-card/schema.json b/modules/blox-tailwind/blox/cta-card/schema.json index 6564bf24..ff603728 100644 --- a/modules/blox-tailwind/blox/cta-card/schema.json +++ b/modules/blox-tailwind/blox/cta-card/schema.json @@ -31,19 +31,129 @@ "properties": { "url": { "type": "string", - "description": "Button URL" + "description": "Button URL (supports hash fragments, relative paths, and absolute URLs)" }, "text": { "type": "string", "description": "Button text" + }, + "icon": { + "type": "string", + "description": "Optional button icon (icon pack format: pack/icon-name)" } }, "required": ["url", "text"] } } + }, + "design": { + "type": "object", + "description": "Design and styling options", + "properties": { + "card": { + "type": "object", + "description": "Card-specific styling", + "properties": { + "css_class": { + "type": "string", + "description": "CSS classes to apply to the card (supports glassmorphism classes: glassmorphism-primary, glassmorphism-dark, glass-ring, glass-shadow, noise-texture)" + }, + "text_color": { + "type": "string", + "enum": ["auto", "light", "dark"], + "description": "Text color mode for optimal contrast (auto detects based on background)", + "default": "auto" + }, + "overlay_opacity": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Glassmorphism overlay opacity for fine-tuning contrast", + "default": 0.15 + }, + "css_style": { + "type": "string", + "description": "Custom CSS styles to apply to the card" + } + } + }, + "background": { + "type": "object", + "description": "Modern background options supporting layered effects", + "properties": { + "color": { + "type": "string", + "description": "Background color" + }, + "gradient": { + "type": "object", + "description": "Modern gradient syntax for 2025 glassmorphism", + "properties": { + "start": { + "type": "string", + "description": "Gradient start color (e.g., 'primary-500', 'blue-600')" + }, + "end": { + "type": "string", + "description": "Gradient end color (e.g., 'primary-700', 'blue-800')" + }, + "direction": { + "type": "number", + "description": "Gradient angle in degrees (default: 90)", + "default": 90 + } + } + }, + "image": { + "type": "object", + "description": "Background image (layers with gradient for glassmorphism)", + "properties": { + "filename": { + "type": "string", + "description": "Image filename in assets/media/" + }, + "size": { + "type": "string", + "enum": ["cover", "contain", "auto", "100px", "50%"], + "description": "Background size", + "default": "cover" + }, + "position": { + "type": "string", + "enum": [ + "center", + "top", + "bottom", + "left", + "right", + "repeat", + "top left", + "top right", + "bottom left", + "bottom right" + ], + "description": "Background position (use 'repeat' for tiling textures)", + "default": "center" + }, + "repeat": { + "type": "string", + "enum": ["repeat", "repeat-x", "repeat-y", "no-repeat"], + "description": "Explicit background-repeat control", + "default": "no-repeat" + }, + "opacity": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Image opacity for subtle textures" + } + } + } + } + } + } } } } ] } - diff --git a/modules/blox-tailwind/blox/hero/block.html b/modules/blox-tailwind/blox/hero/block.html index 831b0d00..b964a439 100644 --- a/modules/blox-tailwind/blox/hero/block.html +++ b/modules/blox-tailwind/blox/hero/block.html @@ -13,7 +13,24 @@ {{ $announcement := $block.content.announcement }} {{end}} @@ -27,7 +44,14 @@ {{ $scheme := (urls.Parse $link).Scheme }} {{ $target := "" }} {{ if not $scheme }} - {{ $link = $link | relLangURL }} + {{/* Check if it's a hash fragment (page anchor) */}} + {{ if hasPrefix $link "#" }} + {{/* Keep hash fragments as-is */}} + {{ $link = $link }} + {{ else }} + {{/* Apply relLangURL for relative paths */}} + {{ $link = $link | relLangURL }} + {{ end }} {{ else if in (slice "http" "https") $scheme }} {{ $target = "target=\"_blank\" rel=\"noopener\"" }} {{ end }} @@ -45,7 +69,14 @@ {{ $scheme_alt := (urls.Parse $link_alt).Scheme }} {{ $target_alt := "" }} {{ if not $scheme_alt }} - {{ $link_alt = $link_alt | relLangURL }} + {{/* Check if it's a hash fragment (page anchor) */}} + {{ if hasPrefix $link_alt "#" }} + {{/* Keep hash fragments as-is */}} + {{ $link_alt = $link_alt }} + {{ else }} + {{/* Apply relLangURL for relative paths */}} + {{ $link_alt = $link_alt | relLangURL }} + {{ end }} {{ else if in (slice "http" "https") $scheme_alt }} {{ $target_alt = "target=\"_blank\" rel=\"noopener\"" }} {{ end }}