mirror of
https://github.com/tiennm99/HugoBlox-kit.git
synced 2026-10-12 22:14:08 +00:00
feat: port page cover system
Just upload a cover image, `cover.*` to your blog post's folder
This commit is contained in:
1 parent
d1e63984e6
commit
c8f2322fa6
14 files changed
+576
-28
No files matched your search
@@ -9,3 +9,4 @@
|
||||
@import "notebook.css";
|
||||
@import "author-notes.css";
|
||||
@import "glassmorphism.css";
|
||||
@import "cover.css";
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Article Cover Component Styles
|
||||
* Notion-inspired page cover with icon overlay
|
||||
*/
|
||||
|
||||
.article-cover {
|
||||
@apply relative w-full;
|
||||
/* Height is set inline via style attribute, but ensure it displays */
|
||||
min-height: 200px;
|
||||
/* Allow icon to overflow bottom */
|
||||
overflow: visible;
|
||||
/* Add margin for overlapping icon */
|
||||
margin-bottom: 2.5rem;
|
||||
|
||||
/* Responsive height adjustments for mobile */
|
||||
@media (max-width: 768px) {
|
||||
height: 240px !important;
|
||||
min-height: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Cover image container */
|
||||
.article-cover img {
|
||||
@apply w-full h-full object-cover;
|
||||
}
|
||||
|
||||
/* Cover icon container base styles */
|
||||
.article-cover-icon {
|
||||
@apply flex items-center justify-center w-full h-full;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.article-cover-icon:hover {
|
||||
@apply scale-105;
|
||||
}
|
||||
|
||||
/* Glass morphism effect for icons */
|
||||
.article-cover-icon.glass {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(12px) saturate(180%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dark .article-cover-icon.glass {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Fade effect styles */
|
||||
.article-cover-fade {
|
||||
@apply absolute left-0 right-0 pointer-events-none;
|
||||
bottom: -1px;
|
||||
background: linear-gradient(to bottom, transparent, white);
|
||||
}
|
||||
|
||||
.dark .article-cover-fade {
|
||||
background: linear-gradient(to bottom, transparent, rgb(15, 23, 42));
|
||||
}
|
||||
|
||||
/* Style variants */
|
||||
.article-cover[data-cover-style="minimal"] {
|
||||
@apply shadow-sm;
|
||||
}
|
||||
|
||||
.article-cover[data-cover-style="glass"] {
|
||||
@apply overflow-visible;
|
||||
}
|
||||
|
||||
.article-cover[data-cover-style="gradient"] {
|
||||
/* Applied via inline overlay divs */
|
||||
}
|
||||
|
||||
/* Ensure cover works well with dark mode */
|
||||
.dark .article-cover {
|
||||
@apply border-b border-gray-800;
|
||||
}
|
||||
|
||||
/* Caption styles */
|
||||
.article-cover .cover-caption {
|
||||
@apply absolute bottom-4 left-4 right-4 text-sm text-white/90;
|
||||
@apply bg-black/50 backdrop-blur-sm px-3 py-2 rounded-lg;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 640px) {
|
||||
.article-cover-icon {
|
||||
@apply scale-90;
|
||||
}
|
||||
}
|
||||
|
||||
/* Parallax effect support (optional feature) */
|
||||
.article-cover.parallax {
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.article-cover.parallax img {
|
||||
transform: translateZ(-1px) scale(1.5);
|
||||
}
|
||||
|
||||
/* Link hover effect if cover is clickable */
|
||||
.article-cover a {
|
||||
@apply block w-full h-full;
|
||||
}
|
||||
|
||||
.article-cover a:hover img {
|
||||
@apply scale-105 transition-transform duration-500;
|
||||
}
|
||||
|
||||
/* Print styles - show cover but optimize */
|
||||
@media print {
|
||||
.article-cover {
|
||||
max-height: 200px !important;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.article-cover-fade,
|
||||
.article-cover-icon {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* Accessibility: Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.article-cover,
|
||||
.article-cover *,
|
||||
.article-cover-icon {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.article-cover.parallax img {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* High contrast mode support */
|
||||
@media (prefers-contrast: high) {
|
||||
.article-cover-icon {
|
||||
@apply border-2 border-current;
|
||||
}
|
||||
|
||||
.article-cover .cover-caption {
|
||||
@apply bg-black text-white;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
{{/* Article Cover Component - Notion-inspired page cover with icon overlay */}}
|
||||
{{/* Requires: page context */}}
|
||||
|
||||
{{ $cover := partial "functions/get_cover_image.html" . }}
|
||||
{{ if $cover }}
|
||||
|
||||
{{/* Get cover configuration with defaults */}}
|
||||
{{ $cover_config := .Params.cover | default (dict) }}
|
||||
{{ if not (reflect.IsMap $cover_config) }}
|
||||
{{/* Simple string format: cover: "image.jpg" */}}
|
||||
{{ $cover_config = dict "image" $cover_config }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Default alt text to page title if not specified */}}
|
||||
{{ $alt_text := $cover_config.alt_text | default (.Title | plainify) }}
|
||||
|
||||
{{/* Height configuration */}}
|
||||
{{ $height_map := dict
|
||||
"small" "200px"
|
||||
"medium" "320px"
|
||||
"large" "480px"
|
||||
"full" "60vh"
|
||||
}}
|
||||
{{ $height := index $height_map ($cover_config.height | default "medium") | default "320px" }}
|
||||
|
||||
{{/* Style configuration */}}
|
||||
{{ $style := $cover_config.style | default "gradient" }}
|
||||
|
||||
{{/* Position configuration */}}
|
||||
{{ $position := $cover_config.position | default (dict) }}
|
||||
{{ $x := $position.x | default 50 }}
|
||||
{{ $y := $position.y | default 50 }}
|
||||
|
||||
{{/* Overlay configuration */}}
|
||||
{{ $overlay := $cover_config.overlay | default (dict) }}
|
||||
{{ $overlay_enabled := $overlay.enabled | default true }}
|
||||
{{ $overlay_type := $overlay.type | default "gradient" }}
|
||||
{{ $overlay_opacity := $overlay.opacity | default 0.3 }}
|
||||
{{ $overlay_color := $overlay.color | default "#000000" }}
|
||||
{{ $overlay_gradient := $overlay.gradient | default "bottom" }}
|
||||
|
||||
{{/* Fade configuration */}}
|
||||
{{ $fade := $cover_config.fade | default (dict) }}
|
||||
{{ $fade_enabled := $fade.enabled | default true }}
|
||||
{{ $fade_height := $fade.height | default "60px" }}
|
||||
{{ $fade_blur := $fade.blur | default false }}
|
||||
|
||||
{{/* Icon configuration */}}
|
||||
{{ $icon_config := $cover_config.icon | default (dict) }}
|
||||
{{ $has_icon := or $icon_config.name $icon_config.emoji }}
|
||||
{{ $icon_position := $icon_config.position | default "overlap" }}
|
||||
{{ $icon_size := $icon_config.size | default "large" }}
|
||||
{{ $icon_style := $icon_config.style | default "glass" }}
|
||||
{{ $icon_border := $icon_config.border | default true }}
|
||||
|
||||
{{/* Responsive hide on mobile */}}
|
||||
{{ $hide_mobile := false }}
|
||||
{{ if $cover_config.responsive }}
|
||||
{{ $hide_mobile = $cover_config.responsive.hide_on_mobile | default false }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Process cover image */}}
|
||||
{{ $cover_width := 2560 }}
|
||||
{{ $cover_height := 520 }}
|
||||
{{ if eq ($cover_config.height | default "medium") "small" }}
|
||||
{{ $cover_height = 320 }}
|
||||
{{ else if eq ($cover_config.height | default "medium") "large" }}
|
||||
{{ $cover_height = 768 }}
|
||||
{{ else if eq ($cover_config.height | default "medium") "full" }}
|
||||
{{ $cover_height = 1080 }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Generate responsive images */}}
|
||||
{{ $processed_cover := $cover }}
|
||||
{{ $srcset := "" }}
|
||||
{{ if ne $cover.MediaType.SubType "gif" }}
|
||||
{{ $fill_spec := printf "%dx%d Center q85 webp" $cover_width $cover_height }}
|
||||
{{ $processed_cover = $cover.Fill $fill_spec }}
|
||||
|
||||
{{ $variants := slice }}
|
||||
{{ $widths := slice 640 768 1024 1366 1920 2560 }}
|
||||
{{ range $widths }}
|
||||
{{ $w := . }}
|
||||
{{ $h := div (mul $w $cover_height) $cover_width }}
|
||||
{{ $variant_spec := printf "%dx%d Center q85 webp" $w $h }}
|
||||
{{ $variant := $cover.Fill $variant_spec }}
|
||||
{{ $variants = $variants | append (printf "%s %dw" $variant.RelPermalink $w) }}
|
||||
{{ end }}
|
||||
{{ $srcset = delimit $variants ", " }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Build CSS classes based on style */}}
|
||||
{{ $cover_classes := "article-cover relative w-full" }}
|
||||
{{ if $hide_mobile }}
|
||||
{{ $cover_classes = printf "%s hidden md:block" $cover_classes }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Container styles */}}
|
||||
{{ $container_style := printf "height: %s;" $height }}
|
||||
|
||||
{{/* Calculate mask style for fade */}}
|
||||
{{ $mask_style := "" }}
|
||||
{{ if and $fade_enabled (ne $style "minimal") }}
|
||||
{{/* Mask fades from black (visible) to transparent (invisible) at the bottom */}}
|
||||
{{ $mask_style = printf "-webkit-mask-image: linear-gradient(to bottom, black calc(100%% - %s), transparent 100%%); mask-image: linear-gradient(to bottom, black calc(100%% - %s), transparent 100%%);" $fade_height $fade_height }}
|
||||
{{ end }}
|
||||
|
||||
<div class="{{ $cover_classes }}" style="{{ $container_style | safeCSS }}" data-cover-style="{{ $style }}">
|
||||
<!-- Image Container (Clipped) -->
|
||||
<div class="absolute inset-0 overflow-hidden" style="{{ $mask_style | safeCSS }}">
|
||||
{{/* Cover Image */}}
|
||||
<div class="absolute inset-0">
|
||||
{{ if ne $cover.MediaType.SubType "gif" }}
|
||||
<img
|
||||
srcset="{{ $srcset }}"
|
||||
sizes="100vw"
|
||||
src="{{ $processed_cover.RelPermalink }}"
|
||||
alt="{{ $alt_text }}"
|
||||
class="w-full h-full object-cover object-center"
|
||||
style="object-position: {{ $x }}% {{ $y }}%;"
|
||||
fetchpriority="high"
|
||||
loading="eager">
|
||||
{{ else }}
|
||||
<img
|
||||
src="{{ $cover.RelPermalink }}"
|
||||
alt="{{ $alt_text }}"
|
||||
class="w-full h-full object-cover object-center"
|
||||
style="object-position: {{ $x }}% {{ $y }}%;"
|
||||
fetchpriority="high"
|
||||
loading="eager">
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Overlay Effects */}}
|
||||
{{ if and $overlay_enabled (ne $style "minimal") }}
|
||||
{{ if eq $style "gradient" }}
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black/{{ $overlay_opacity | mul 100 }}"></div>
|
||||
{{ else if eq $style "glass" }}
|
||||
<div class="absolute inset-0 backdrop-blur-sm backdrop-saturate-150" style="background: rgba(255, 255, 255, 0.1);"></div>
|
||||
{{ else if eq $style "blur" }}
|
||||
<div class="absolute inset-x-0 bottom-0 h-1/3 backdrop-blur-md"></div>
|
||||
{{ else if eq $overlay_type "gradient" }}
|
||||
{{ if eq $overlay_gradient "bottom" }}
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-transparent to-black/{{ $overlay_opacity | mul 100 }}"></div>
|
||||
{{ else if eq $overlay_gradient "top" }}
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-transparent to-black/{{ $overlay_opacity | mul 100 }}"></div>
|
||||
{{ else if eq $overlay_gradient "both" }}
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-black/{{ $overlay_opacity | mul 50 }} via-transparent to-black/{{ $overlay_opacity | mul 100 }}"></div>
|
||||
{{ else if eq $overlay_gradient "radial" }}
|
||||
<div class="absolute inset-0" style="background: radial-gradient(circle, transparent 0%, rgba(0,0,0,{{ $overlay_opacity }}) 100%);"></div>
|
||||
{{ end }}
|
||||
{{ else if eq $overlay_type "solid" }}
|
||||
<div class="absolute inset-0" style="background-color: {{ $overlay_color }}; opacity: {{ $overlay_opacity }};"></div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Blur Effect at Bottom (Optional) */}}
|
||||
{{ if and $fade_enabled $fade_blur (ne $style "minimal") }}
|
||||
{{/* Taller blur zone with mask to create smooth blur transition before fade out */}}
|
||||
{{/* Semi-transparent background needed for backdrop-filter to render */}}
|
||||
<div class="absolute inset-x-0 bottom-0 pointer-events-none"
|
||||
style="height: calc({{ $fade_height }} * 2.5);
|
||||
background: rgba(255, 255, 255, 0.01);
|
||||
backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
||||
-webkit-mask-image: linear-gradient(to bottom, transparent, black); mask-image: linear-gradient(to bottom, transparent, black);"></div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Caption */}}
|
||||
{{ with $cover_config.caption }}
|
||||
<div class="absolute bottom-4 left-4 right-4 text-sm text-white/90 bg-black/50 backdrop-blur-sm px-3 py-2 rounded-lg z-20">
|
||||
{{ . | markdownify }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Icon Overlay - Only if position is "overlap" - NOW UNCLIPPED */}}
|
||||
{{ if and $has_icon (eq $icon_position "overlap") }}
|
||||
{{ $icon_size_map := dict "small" "w-8 h-8" "medium" "w-12 h-12" "large" "w-16 h-16" }}
|
||||
{{ $icon_size_class := index $icon_size_map $icon_size | default "w-16 h-16" }}
|
||||
{{ $icon_padding := cond (eq $icon_size "small") "p-1.5" (cond (eq $icon_size "medium") "p-2" "p-3") }}
|
||||
|
||||
<div class="absolute left-6 md:left-12 {{ $icon_size_class }}" style="bottom: -32px; z-index: 30;">
|
||||
<div class="article-cover-icon {{ $icon_padding }} rounded-xl shadow-lg backdrop-blur-md
|
||||
{{ if eq $icon_style "glass" }}bg-white/90 dark:bg-gray-800/80 border border-white/30 dark:border-gray-700/30
|
||||
{{ else if eq $icon_style "rounded" }}bg-white dark:bg-gray-800 rounded-full
|
||||
{{ else }}bg-white dark:bg-gray-800{{ end }}
|
||||
{{ if $icon_border }}border border-gray-200 dark:border-gray-700{{ end }}">
|
||||
{{ if $icon_config.emoji }}
|
||||
<span class="text-2xl md:text-3xl lg:text-4xl leading-none flex items-center justify-center">{{ $icon_config.emoji }}</span>
|
||||
{{ else if $icon_config.name }}
|
||||
{{ partial "functions/get_icon.html" (dict "name" $icon_config.name "attributes" (printf "class=\"%s text-gray-700 dark:text-gray-200\"" $icon_size_class)) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
</div>
|
||||
|
||||
{{/* Icon Below Cover - If position is "below" */}}
|
||||
{{ if and $has_icon (eq $icon_position "below") }}
|
||||
{{ $icon_size_map := dict "small" "w-8 h-8" "medium" "w-12 h-12" "large" "w-16 h-16" }}
|
||||
{{ $icon_size_class := index $icon_size_map $icon_size | default "w-16 h-16" }}
|
||||
{{ $icon_padding := cond (eq $icon_size "small") "p-1.5" (cond (eq $icon_size "medium") "p-2" "p-3") }}
|
||||
|
||||
<div class="flex justify-center -mt-8 mb-4 relative z-10">
|
||||
<div class="article-cover-icon {{ $icon_size_class }} {{ $icon_padding }} rounded-xl shadow-lg backdrop-blur-md
|
||||
{{ if eq $icon_style "glass" }}bg-white/90 dark:bg-gray-800/80 border border-white/30 dark:border-gray-700/30
|
||||
{{ else if eq $icon_style "rounded" }}bg-white dark:bg-gray-800 rounded-full
|
||||
{{ else }}bg-white dark:bg-gray-800{{ end }}
|
||||
{{ if $icon_border }}border border-gray-200 dark:border-gray-700{{ end }}">
|
||||
{{ if $icon_config.emoji }}
|
||||
<span class="text-2xl md:text-3xl lg:text-4xl leading-none flex items-center justify-center">{{ $icon_config.emoji }}</span>
|
||||
{{ else if $icon_config.name }}
|
||||
{{ partial "functions/get_icon.html" (dict "name" $icon_config.name "attributes" (printf "class=\"%s text-gray-700 dark:text-gray-200\"" $icon_size_class)) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
||||
@@ -0,0 +1,41 @@
|
||||
{{/* HugoBlox function to retrieve the cover image */}}
|
||||
{{/* Inputs: page context */}}
|
||||
{{/* Output: image resource, or nil if not found */}}
|
||||
|
||||
{{/*
|
||||
Cover image is searched in this order:
|
||||
1. Search for a file `*cover*` in the page directory
|
||||
2. Search for a file `.Params.cover.image` in the page directory
|
||||
3. Search for a file `.Params.cover.image` in the `assets/media/` directory
|
||||
*/}}
|
||||
|
||||
{{/* Search for an image "*cover*" in page folder */}}
|
||||
{{ $resource := (.Resources.ByType "image").GetMatch "*cover*" }}
|
||||
{{ if eq $resource nil }}
|
||||
{{/* Otherwise fall back to the image file specified in front matter */}}
|
||||
{{ $filename := "" }}
|
||||
{{ if .Params.cover }}
|
||||
{{ if reflect.IsMap .Params.cover }}
|
||||
{{ $filename = .Params.cover.image }}
|
||||
{{ else }}
|
||||
{{/* Support simple string format: cover: "image.jpg" */}}
|
||||
{{ $filename = .Params.cover }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if $filename }}
|
||||
{{/* Check if it's a remote URL */}}
|
||||
{{ if or (strings.HasPrefix $filename "http://") (strings.HasPrefix $filename "https://") }}
|
||||
{{ $resource = resources.GetRemote $filename }}
|
||||
{{ if eq $resource nil }}
|
||||
{{ warnf "Remote cover image not found: %s" $filename }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{/* Search in page folder */}}
|
||||
{{ $resource = (.Resources.ByType "image").GetMatch $filename }}
|
||||
{{/* Otherwise in `assets/media/` folder */}}
|
||||
{{ if eq $resource nil }} {{ $resource = resources.GetMatch (path.Join "media" $filename) }} {{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ return $resource }}
|
||||
@@ -14,6 +14,7 @@
|
||||
{{ $attrs := .attributes | default "" }}
|
||||
{{/* Convert SVG sizing to emoji text sizing with important specificity */}}
|
||||
{{ if in $attrs "w-24" }}{{ $attrs = replaceRE "w-24 h-24" "" $attrs }}{{ $attrs = printf "%s text-8xl leading-none" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-16" }}{{ $attrs = replaceRE "w-16 h-16" "" $attrs }}{{ $attrs = printf "%s text-5xl leading-none" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-12" }}{{ $attrs = replaceRE "w-12 h-12" "" $attrs }}{{ $attrs = printf "%s text-5xl leading-none" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-8" }}{{ $attrs = replaceRE "w-8 h-8" "" $attrs }}{{ $attrs = printf "%s text-4xl leading-none" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-6" }}{{ $attrs = replaceRE "w-6 h-6" "" $attrs }}{{ $attrs = printf "%s text-2xl leading-none" $attrs }}{{ end }}
|
||||
@@ -43,37 +44,55 @@
|
||||
{{ $icon = printf `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %s %s">%s</svg>` (cast.ToString $icon_size) (cast.ToString $icon_size) $icon_body }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ with index (index site.Data.icons.hb "icons") $name -}}
|
||||
{{ $icon = . }}
|
||||
{{/* Icon NOT found in pack - check if it's a direct Unicode emoji */}}
|
||||
{{ if not (strings.Contains $.name "/") }}
|
||||
{{/* No explicit pack specified (defaulted to hero) - treat as direct Unicode emoji */}}
|
||||
{{ $attrs := .attributes | default "" }}
|
||||
{{/* Convert SVG sizing to emoji text sizing (same as emoji pack) */}}
|
||||
{{ if in $attrs "w-24" }}{{ $attrs = replaceRE "w-24 h-24" "text-8xl" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-16" }}{{ $attrs = replaceRE "w-16 h-16" "text-5xl" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-12" }}{{ $attrs = replaceRE "w-12 h-12" "text-5xl" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-8" }}{{ $attrs = replaceRE "w-8 h-8" "text-4xl" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-6" }}{{ $attrs = replaceRE "w-6 h-6" "text-2xl" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-5" }}{{ $attrs = replaceRE "w-5 h-5" "text-xl" $attrs }}{{ end }}
|
||||
{{ if in $attrs "w-4" }}{{ $attrs = replaceRE "w-4 h-4" "text-lg" $attrs }}{{ end }}
|
||||
{{/* Add centering and line-height classes if not already present */}}
|
||||
{{ if not (in $attrs "flex") }}{{ $attrs = replaceRE "class=\"" "class=\"flex items-center justify-center leading-none " $attrs }}{{ end }}
|
||||
{{ $icon = printf "<span %s>%s</span>" $attrs $.name }}
|
||||
{{ else }}
|
||||
{{- $prefix := printf "media/icons/%s/" $pack -}}
|
||||
{{- $icon_path := printf "%s%s.svg" $prefix $name -}}
|
||||
{{- if not (fileExists (printf "assets/%s" $icon_path)) -}}
|
||||
{{ $pageRef := "unknown location" }}
|
||||
{{ if .Page }}
|
||||
{{ if .Page.File }}
|
||||
{{ if .Page.File.Path }}
|
||||
{{ $pageRef = .Page.File.Path }}
|
||||
{{/* Explicit pack specified but icon not found - continue with fallback logic */}}
|
||||
{{ with index (index site.Data.icons.hb "icons") $name -}}
|
||||
{{ $icon = . }}
|
||||
{{ else }}
|
||||
{{- $prefix := printf "media/icons/%s/" $pack -}}
|
||||
{{- $icon_path := printf "%s%s.svg" $prefix $name -}}
|
||||
{{- if not (fileExists (printf "assets/%s" $icon_path)) -}}
|
||||
{{ $pageRef := "unknown location" }}
|
||||
{{ if .Page }}
|
||||
{{ if .Page.File }}
|
||||
{{ if .Page.File.Path }}
|
||||
{{ $pageRef = .Page.File.Path }}
|
||||
{{ else if .Page.RelPermalink }}
|
||||
{{ $pageRef = .Page.RelPermalink }}
|
||||
{{ end }}
|
||||
{{ else if .Page.RelPermalink }}
|
||||
{{ $pageRef = .Page.RelPermalink }}
|
||||
{{ end }}
|
||||
{{ else if .Page.RelPermalink }}
|
||||
{{ $pageRef = .Page.RelPermalink }}
|
||||
{{ end }}
|
||||
{{ partial "functions/logger" (dict
|
||||
"page" .Page
|
||||
"level" "warn"
|
||||
"id" (printf "missing-icon-%s-%s" $pack $name)
|
||||
"message" (printf "The icon `%s` was not found in `assets/media/icons/%s/`. Referenced in: %s" $name $pack $pageRef)
|
||||
)
|
||||
}}
|
||||
{{- end -}}
|
||||
{{- $icon_resource := resources.Get $icon_path -}}
|
||||
{{ if $icon_resource }}
|
||||
{{ $icon = $icon_resource.Content }}
|
||||
{{ else }}
|
||||
{{ $icon = index (index site.Data.icons.brands "icons") "hugo" }}
|
||||
{{ end }}
|
||||
{{ partial "functions/logger" (dict
|
||||
"page" .Page
|
||||
"level" "warn"
|
||||
"id" (printf "missing-icon-%s-%s" $pack $name)
|
||||
"message" (printf "The icon `%s` was not found in `assets/media/icons/%s/`. Referenced in: %s" $name $pack $pageRef)
|
||||
)
|
||||
}}
|
||||
{{- end -}}
|
||||
{{- $icon_resource := resources.Get $icon_path -}}
|
||||
{{ if $icon_resource }}
|
||||
{{ $icon = $icon_resource.Content }}
|
||||
{{ else }}
|
||||
{{ $icon = index (index site.Data.icons.brands "icons") "hugo" }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="page-body {{if ne .Type "landing"}} my-10{{end}}">
|
||||
{{ $cover := partial "functions/get_cover_image.html" . }}
|
||||
<div class="page-body {{if and (ne .Type "landing") (not $cover)}} my-10{{end}}">
|
||||
{{ block "main" . }}{{ end }}
|
||||
</div>
|
||||
<div class="page-footer">
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Page Cover */}}
|
||||
{{ partial "components/cover.html" . }}
|
||||
|
||||
<div class="mx-auto flex max-w-screen-xl">
|
||||
{{ partial "components/sidebar.html" (dict "context" . "no_sidebar" true) }}
|
||||
{{ partial "components/toc.html" . }}
|
||||
@@ -21,8 +24,45 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<h1 class="mt-2 text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100">
|
||||
{{- .Title -}}
|
||||
{{/* Add spacing if cover has overlapping icon */}}
|
||||
{{ $cover := partial "functions/get_cover_image.html" . }}
|
||||
{{ $has_overlap_icon := false }}
|
||||
{{ if $cover }}
|
||||
{{ $icon_config := .Params.cover.icon | default (dict) }}
|
||||
{{ if or $icon_config.name $icon_config.emoji }}
|
||||
{{ $icon_position := $icon_config.position | default "overlap" }}
|
||||
{{ if eq $icon_position "overlap" }}
|
||||
{{ $has_overlap_icon = true }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<h1 class="{{ if $has_overlap_icon }}mt-12{{ else }}mt-2{{ end }} text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100">
|
||||
{{/* Inline icon before title if specified */}}
|
||||
{{ if $cover }}
|
||||
{{ $icon_config := .Params.cover.icon | default (dict) }}
|
||||
{{ if or $icon_config.name $icon_config.emoji }}
|
||||
{{ $icon_position := $icon_config.position | default "overlap" }}
|
||||
{{ if eq $icon_position "inline" }}
|
||||
<span class="inline-flex items-center gap-3">
|
||||
{{ if $icon_config.emoji }}
|
||||
<span class="text-4xl">{{ $icon_config.emoji }}</span>
|
||||
{{ else if $icon_config.name }}
|
||||
<span class="w-10 h-10">
|
||||
{{ partial "components/icon.html" (dict "name" $icon_config.name "attributes" "class=\"w-full h-full\"") }}
|
||||
</span>
|
||||
{{ end }}
|
||||
<span>{{- .Title -}}</span>
|
||||
</span>
|
||||
{{ else }}
|
||||
{{- .Title -}}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{- .Title -}}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{- .Title -}}
|
||||
{{ end }}
|
||||
</h1>
|
||||
|
||||
<div class="mt-4 mb-16">
|
||||
|
||||
@@ -8,6 +8,21 @@ tags:
|
||||
- Hugo
|
||||
- Hugo Blox
|
||||
- Markdown
|
||||
cover:
|
||||
image: "https://images.unsplash.com/photo-1557682250-33bd709cbe85?q=80&w=2560"
|
||||
position:
|
||||
x: 50
|
||||
y: 40
|
||||
overlay:
|
||||
enabled: true
|
||||
type: "gradient"
|
||||
opacity: 0.4
|
||||
gradient: "bottom"
|
||||
fade:
|
||||
enabled: true
|
||||
height: "80px"
|
||||
icon:
|
||||
name: "✨"
|
||||
image:
|
||||
caption: 'Image credit: [**Unsplash**](https://unsplash.com)'
|
||||
---
|
||||
|
||||
@@ -8,6 +8,22 @@ date: 2023-10-27
|
||||
image:
|
||||
caption: 'Image credit: [**Unsplash**](https://unsplash.com)'
|
||||
|
||||
cover:
|
||||
image: "https://images.unsplash.com/photo-1557682250-33bd709cbe85?q=80&w=2560"
|
||||
position:
|
||||
x: 50
|
||||
y: 40
|
||||
overlay:
|
||||
enabled: true
|
||||
type: "gradient"
|
||||
opacity: 0.4
|
||||
gradient: "bottom"
|
||||
fade:
|
||||
enabled: true
|
||||
height: "80px"
|
||||
icon:
|
||||
name: "✨"
|
||||
|
||||
authors:
|
||||
- admin
|
||||
- Ted
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 220 KiB |
@@ -9,6 +9,10 @@ tags:
|
||||
- Jupyter
|
||||
- Open Science
|
||||
- Tutorials
|
||||
cover:
|
||||
# image: cover.jpg # Auto-detected from cover image in this folder
|
||||
icon:
|
||||
name: "📔"
|
||||
image:
|
||||
caption: "Image credit: [HugoBlox](https://hugoblox.com)"
|
||||
focal_point: Center
|
||||
|
||||
@@ -9,6 +9,21 @@ tags:
|
||||
- Markdown
|
||||
image:
|
||||
caption: 'Image credit: [**Unsplash**](https://unsplash.com)'
|
||||
cover:
|
||||
image: "https://images.unsplash.com/photo-1557682250-33bd709cbe85?q=80&w=2560"
|
||||
position:
|
||||
x: 50
|
||||
y: 40
|
||||
overlay:
|
||||
enabled: true
|
||||
type: "gradient"
|
||||
opacity: 0.4
|
||||
gradient: "bottom"
|
||||
fade:
|
||||
enabled: true
|
||||
height: "80px"
|
||||
icon:
|
||||
name: "✨"
|
||||
---
|
||||
|
||||
Easily manage your projects - create ideation mind maps, Gantt charts, todo lists, and more!
|
||||
|
||||
@@ -9,6 +9,21 @@ tags:
|
||||
- Markdown
|
||||
image:
|
||||
caption: 'Image credit: [**Unsplash**](https://unsplash.com)'
|
||||
cover:
|
||||
image: "https://images.unsplash.com/photo-1557682250-33bd709cbe85?q=80&w=2560"
|
||||
position:
|
||||
x: 50
|
||||
y: 40
|
||||
overlay:
|
||||
enabled: true
|
||||
type: "gradient"
|
||||
opacity: 0.4
|
||||
gradient: "bottom"
|
||||
fade:
|
||||
enabled: true
|
||||
height: "80px"
|
||||
icon:
|
||||
name: "✨"
|
||||
---
|
||||
|
||||
Create a personal knowledge base and share your knowledge with your peers.
|
||||
|
||||
@@ -11,6 +11,21 @@ tags:
|
||||
- Markdown
|
||||
image:
|
||||
caption: 'Embed rich media such as videos and LaTeX math'
|
||||
cover:
|
||||
image: "https://images.unsplash.com/photo-1557682250-33bd709cbe85?q=80&w=2560"
|
||||
position:
|
||||
x: 50
|
||||
y: 40
|
||||
overlay:
|
||||
enabled: true
|
||||
type: "gradient"
|
||||
opacity: 0.4
|
||||
gradient: "bottom"
|
||||
fade:
|
||||
enabled: true
|
||||
height: "80px"
|
||||
icon:
|
||||
name: "✨"
|
||||
---
|
||||
|
||||
[Hugo Blox Builder](https://hugoblox.com) is designed to give technical content creators a seamless experience. You can focus on the content and the Hugo Blox Builder which this template is built upon handles the rest.
|
||||
|
||||
Reference in new issue
Block a user