mirror of
https://github.com/tiennm99/HugoBlox-kit.git
synced 2026-10-13 14:15:47 +00:00
feat: add new blocks
For contact-info, logos, research-areas, team-showcase Update: - hero block: to prevent wide sub-title text on large screen - stats block: re-designed stats block with new styles
This commit is contained in:
1 parent
5f2290d590
commit
c34ccd27b0
22 files changed
+2628
-63
No files matched your search
@@ -0,0 +1,139 @@
|
||||
# Contact Info Block
|
||||
|
||||
Display comprehensive contact information for your lab or organization with a modern, responsive card layout.
|
||||
|
||||
## Features
|
||||
|
||||
- **Dual Card Layout**: Separate cards for location and contact methods
|
||||
- **Rich Information Display**: Address, office hours, phone, email, social media
|
||||
- **Interactive Elements**: Click-to-call, email links, map integration
|
||||
- **Optional Contact Form**: Built-in contact form support
|
||||
- **Map Integration**: External map links or embedded maps
|
||||
- **Social Media Links**: Connect your social profiles
|
||||
- **Prospective Members Section**: Dedicated area for recruitment
|
||||
- **Fully Responsive**: Beautiful on all devices
|
||||
- **Dark Mode Support**: Seamless light/dark theme switching
|
||||
|
||||
## Usage
|
||||
|
||||
```yaml
|
||||
sections:
|
||||
- block: contact-info
|
||||
content:
|
||||
title: Contact Us
|
||||
subtitle: Get in touch with our research team
|
||||
visit_title: Visit Our Lab
|
||||
connect_title: Connect With Us
|
||||
address:
|
||||
lines:
|
||||
- Smith Laboratory
|
||||
- Department of Computer Science
|
||||
- University of Excellence
|
||||
- 123 Science Drive
|
||||
- Excellence City, EC 12345
|
||||
- United States
|
||||
office_hours:
|
||||
- "Monday - Friday: 9:00 AM - 5:00 PM"
|
||||
- "Lab Meetings: Fridays 2:00 PM"
|
||||
email: lab@example.edu
|
||||
phone: "+1 (555) 123-4567"
|
||||
social:
|
||||
- icon: brands/x
|
||||
url: https://twitter.com/SmithLabResearch
|
||||
- icon: brands/linkedin
|
||||
url: https://linkedin.com/company/smith-lab
|
||||
- icon: brands/github
|
||||
url: https://github.com/smith-lab
|
||||
prospective:
|
||||
title: Prospective Members
|
||||
text: Interested in joining our lab? We're looking for motivated researchers.
|
||||
button:
|
||||
text: View Open Positions
|
||||
url: /opportunities
|
||||
map_url: https://maps.google.com/?q=University+of+Excellence
|
||||
show_form: false
|
||||
design:
|
||||
css_class: "bg-gray-50 dark:bg-gray-900"
|
||||
spacing:
|
||||
padding: ["3rem", 0, "3rem", 0]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### Content Options
|
||||
|
||||
- **title**: Main heading for the contact section
|
||||
- **subtitle**: Optional subtitle text
|
||||
- **visit_title**: Heading for the location card (default: "Visit Us")
|
||||
- **connect_title**: Heading for the contact card (default: "Connect")
|
||||
- **address**: Physical address with multiple lines
|
||||
- `lines`: Array of address line strings
|
||||
- **office_hours**: Array of office hour strings
|
||||
- **email**: Contact email address
|
||||
- **phone**: Contact phone number
|
||||
- **social**: Array of social media links
|
||||
- `icon`: Icon identifier (e.g., "brands/x", "brands/linkedin")
|
||||
- `url`: Social media profile URL
|
||||
- **prospective**: Information for prospective members
|
||||
- `title`: Section heading
|
||||
- `text`: Description text
|
||||
- `button`: CTA button with `text` and `url`
|
||||
- **map_url**: External map service link
|
||||
- **map_embed**: HTML embed code for inline map
|
||||
- **show_form**: Display contact form (default: false)
|
||||
- **form_action**: Form submission endpoint
|
||||
|
||||
### Design Options
|
||||
|
||||
Standard design options apply including:
|
||||
- `css_class`: Background and styling classes
|
||||
- `spacing`: Padding configuration
|
||||
|
||||
## Map Integration
|
||||
|
||||
### External Map Link
|
||||
Provide a `map_url` to link to Google Maps, Apple Maps, or other services:
|
||||
|
||||
```yaml
|
||||
map_url: "https://maps.google.com/?q=Your+Location"
|
||||
```
|
||||
|
||||
### Embedded Map
|
||||
For inline maps, provide the iframe HTML in `map_embed`:
|
||||
|
||||
```yaml
|
||||
map_embed: |
|
||||
<iframe src="https://www.google.com/maps/embed?..."
|
||||
width="100%" height="450" style="border:0;"
|
||||
allowfullscreen="" loading="lazy"></iframe>
|
||||
```
|
||||
|
||||
## Contact Form
|
||||
|
||||
Enable the built-in contact form:
|
||||
|
||||
```yaml
|
||||
show_form: true
|
||||
form_action: "https://formspree.io/f/YOUR_FORM_ID"
|
||||
```
|
||||
|
||||
Note: You'll need to set up a form handler service like Formspree, Netlify Forms, or custom endpoint.
|
||||
|
||||
## Styling
|
||||
|
||||
The block uses modern card-based design with:
|
||||
- Subtle shadows and hover effects
|
||||
- Icon accents for visual hierarchy
|
||||
- Consistent spacing and typography
|
||||
- Fully responsive grid layout
|
||||
- Dark mode optimized colors
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Complete Information**: Provide all relevant contact methods
|
||||
2. **Office Hours**: Clearly state availability
|
||||
3. **Social Proof**: Link active social media profiles
|
||||
4. **Map Integration**: Help visitors find your location
|
||||
5. **Recruitment CTA**: Include prospective member information
|
||||
6. **Form Handler**: Set up proper form submission handling
|
||||
7. **Mobile Testing**: Verify all links work on mobile devices
|
||||
@@ -0,0 +1,184 @@
|
||||
{{/* Hugo Blox: Contact Info Block */}}
|
||||
{{/* Documentation: https://hugoblox.com/blocks/ */}}
|
||||
{{/* License: https://github.com/HugoBlox/hugo-blox-builder/blob/main/LICENSE.md */}}
|
||||
|
||||
{{/* Initialise */}}
|
||||
{{ $page := .wcPage }}
|
||||
{{ $block := .wcBlock }}
|
||||
|
||||
{{ $title := $block.content.title | default "Contact Us" | emojify | $page.RenderString }}
|
||||
{{ $subtitle := $block.content.subtitle | emojify | $page.RenderString }}
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 sm:py-16 lg:py-20">
|
||||
|
||||
{{/* Section Header */}}
|
||||
<div class="text-center mb-12">
|
||||
{{ with $title }}
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white tracking-tight mb-4">
|
||||
{{ . }}
|
||||
</h2>
|
||||
{{ end }}
|
||||
|
||||
{{ with $subtitle }}
|
||||
<p class="text-lg sm:text-xl text-gray-600 dark:text-gray-400 max-w-3xl mx-auto">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Contact Cards Grid */}}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
|
||||
|
||||
{{/* Visit Us Card */}}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-8 hover:shadow-2xl transition-shadow duration-300">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="flex-shrink-0 w-12 h-12 bg-primary-100 dark:bg-primary-900/50 rounded-full flex items-center justify-center">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/map-pin" "attributes" "class=\"w-6 h-6 text-primary-600 dark:text-primary-400\"") }}
|
||||
</div>
|
||||
<h3 class="ml-4 text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{{ $block.content.visit_title | default "Visit Us" }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
{{ with $block.content.address }}
|
||||
<div class="text-gray-600 dark:text-gray-300">
|
||||
{{ range .lines }}
|
||||
<p>{{ . }}</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with $block.content.office_hours }}
|
||||
<div class="pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<h4 class="font-semibold text-gray-900 dark:text-white mb-2">Office Hours</h4>
|
||||
<div class="text-gray-600 dark:text-gray-300 space-y-1">
|
||||
{{ range . }}
|
||||
<p>{{ . }}</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with $block.content.map_url }}
|
||||
<a href="{{ . }}" target="_blank" rel="noopener"
|
||||
class="inline-flex items-center mt-4 px-4 py-2 bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 rounded-lg hover:bg-primary-100 dark:hover:bg-primary-900/50 transition-colors">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/map" "attributes" "class=\"w-5 h-5 mr-2\"") }}
|
||||
View on Map
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Connect Card */}}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-8 hover:shadow-2xl transition-shadow duration-300">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="flex-shrink-0 w-12 h-12 bg-primary-100 dark:bg-primary-900/50 rounded-full flex items-center justify-center">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/chat-bubble-left-right" "attributes" "class=\"w-6 h-6 text-primary-600 dark:text-primary-400\"") }}
|
||||
</div>
|
||||
<h3 class="ml-4 text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{{ $block.content.connect_title | default "Connect" }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
{{/* Contact Methods */}}
|
||||
<div class="space-y-3">
|
||||
{{ with $block.content.email }}
|
||||
<div class="flex items-center">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/envelope" "attributes" "class=\"w-5 h-5 text-gray-500 dark:text-gray-400 mr-3\"") }}
|
||||
<a href="mailto:{{ . }}" class="text-gray-700 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors">
|
||||
{{ . }}
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with $block.content.phone }}
|
||||
<div class="flex items-center">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/phone" "attributes" "class=\"w-5 h-5 text-gray-500 dark:text-gray-400 mr-3\"") }}
|
||||
<a href="tel:{{ . }}" class="text-gray-700 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors">
|
||||
{{ . }}
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Social Media */}}
|
||||
{{ with $block.content.social }}
|
||||
<div class="pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<h4 class="font-semibold text-gray-900 dark:text-white mb-3">Follow Us</h4>
|
||||
<div class="flex gap-3">
|
||||
{{ range . }}
|
||||
<a href="{{ .url }}" target="_blank" rel="noopener"
|
||||
class="w-10 h-10 flex items-center justify-center rounded-full bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 hover:bg-primary-100 dark:hover:bg-primary-900/50 hover:text-primary-600 dark:hover:text-primary-400 transition-all">
|
||||
{{ partial "functions/get_icon" (dict "name" .icon "attributes" "class=\"w-5 h-5\"") }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Prospective Members */}}
|
||||
{{ with $block.content.prospective }}
|
||||
<div class="pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<h4 class="font-semibold text-gray-900 dark:text-white mb-2">{{ .title | default "Prospective Members" }}</h4>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-4">{{ .text }}</p>
|
||||
{{ with .button }}
|
||||
<a href="{{ .url }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded-lg shadow hover:shadow-lg transition-all">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/user-plus" "attributes" "class=\"w-5 h-5 mr-2\"") }}
|
||||
{{ .text }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Optional Map Embed */}}
|
||||
{{ with $block.content.map_embed }}
|
||||
<div class="mt-12 rounded-2xl overflow-hidden shadow-xl">
|
||||
<div class="aspect-w-16 aspect-h-9 lg:aspect-h-6">
|
||||
{{ . | safeHTML }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Contact Form (optional) */}}
|
||||
{{ if $block.content.show_form }}
|
||||
<div class="mt-12 max-w-2xl mx-auto">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-8">
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Send us a Message</h3>
|
||||
<form action="{{ $block.content.form_action | default "#" }}" method="POST" class="space-y-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name</label>
|
||||
<input type="text" id="name" name="name" required
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent dark:bg-gray-700 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Email</label>
|
||||
<input type="email" id="email" name="email" required
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent dark:bg-gray-700 dark:text-white">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="subject" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Subject</label>
|
||||
<input type="text" id="subject" name="subject" required
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent dark:bg-gray-700 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label for="message" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Message</label>
|
||||
<textarea id="message" name="message" rows="5" required
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent dark:bg-gray-700 dark:text-white"></textarea>
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="w-full px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg shadow hover:shadow-lg transition-all">
|
||||
Send Message
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "contact-info",
|
||||
"name": "Contact Info",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"category": "content",
|
||||
"tags": ["contact", "information", "address", "location", "lab", "research", "organization"],
|
||||
"description": "Display contact information with modern card layout, including address, phone, email, social media, and optional contact form",
|
||||
"author": "Hugo Blox",
|
||||
"homepage": "https://hugoblox.com/blocks/",
|
||||
"repository": "https://github.com/HugoBlox/hugo-blox-builder",
|
||||
"keywords": ["hugo", "static-site", "contact", "information", "address", "research-lab"]
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://hugoblox.com/schemas/blocks/contact-info.json",
|
||||
"title": "Contact Info Block Schema",
|
||||
"description": "Schema for the Contact Info block - displays lab contact information in a modern card layout",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "../shared/schemas/base-block.json"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"block": {
|
||||
"const": "contact-info"
|
||||
},
|
||||
"content": {
|
||||
"type": "object",
|
||||
"description": "Content configuration for the contact info block",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Main section title",
|
||||
"default": "Contact Us"
|
||||
},
|
||||
"subtitle": {
|
||||
"type": "string",
|
||||
"description": "Section subtitle"
|
||||
},
|
||||
"visit_title": {
|
||||
"type": "string",
|
||||
"description": "Title for the visit section",
|
||||
"default": "Visit Us"
|
||||
},
|
||||
"connect_title": {
|
||||
"type": "string",
|
||||
"description": "Title for the connect section",
|
||||
"default": "Connect"
|
||||
},
|
||||
"address": {
|
||||
"type": "object",
|
||||
"description": "Physical address information",
|
||||
"properties": {
|
||||
"lines": {
|
||||
"type": "array",
|
||||
"description": "Address lines",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"office_hours": {
|
||||
"type": "array",
|
||||
"description": "Office hours information",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Contact email address"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"description": "Contact phone number"
|
||||
},
|
||||
"social": {
|
||||
"type": "array",
|
||||
"description": "Social media links",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Icon name (e.g., 'brands/x', 'brands/linkedin')"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Social media URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"prospective": {
|
||||
"type": "object",
|
||||
"description": "Information for prospective members",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"default": "Prospective Members"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"button": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"map_url": {
|
||||
"type": "string",
|
||||
"description": "URL to external map service"
|
||||
},
|
||||
"map_embed": {
|
||||
"type": "string",
|
||||
"description": "HTML embed code for map (e.g., Google Maps iframe)"
|
||||
},
|
||||
"show_form": {
|
||||
"type": "boolean",
|
||||
"description": "Show contact form",
|
||||
"default": false
|
||||
},
|
||||
"form_action": {
|
||||
"type": "string",
|
||||
"description": "Form submission URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<div class="text-center">
|
||||
{{ with $block.content.title }}<h1 class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 sm:text-6xl">{{ . | markdownify }}</h1>{{end}}
|
||||
{{ with $block.content.text }}<p class="mt-6 text-lg leading-8 text-gray-600 dark:text-gray-300">{{ . | $page.RenderString | emojify }}</p>{{end}}
|
||||
{{ with $block.content.text }}<p class="mt-6 text-lg leading-8 text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">{{ . | $page.RenderString | emojify }}</p>{{end}}
|
||||
|
||||
{{ if $block.content.primary_action.url }}
|
||||
{{ $link := $block.content.primary_action.url }}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const HeroBlock = ({content, design, id, icon_svg}) => {
|
||||
|
||||
{/* Subtitle/Text */}
|
||||
{content.text && (
|
||||
<p class="mt-6 text-lg leading-8 text-gray-600 dark:text-gray-300" dangerouslySetInnerHTML={{__html: renderText(content.text)}} />
|
||||
<p class="mt-6 text-lg leading-8 text-gray-600 dark:text-gray-300 max-w-2xl mx-auto" dangerouslySetInnerHTML={{__html: renderText(content.text)}} />
|
||||
)}
|
||||
|
||||
{/* Action Buttons */}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
# Logos Block
|
||||
|
||||
Display partner, sponsor, or collaborator logos with modern interactive effects and multiple display modes. Perfect for showcasing affiliations, building trust, and highlighting collaborations.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Display Modes**: Grid, carousel, or marquee layouts
|
||||
- **Interactive Effects**: Grayscale to color on hover, smooth scaling
|
||||
- **Clickable Logos**: Link to partner websites
|
||||
- **Hover Tooltips**: Show partner descriptions on hover
|
||||
- **Responsive Design**: Adapts perfectly to all screen sizes
|
||||
- **Smooth Animations**: Professional transitions and effects
|
||||
- **Flexible Configuration**: Use structured data or folder-based logos
|
||||
- **Performance Optimized**: Lazy loading and WebP format support
|
||||
- **Dark Mode Support**: Seamless theme switching
|
||||
|
||||
## Display Modes
|
||||
|
||||
### Grid Mode (Default)
|
||||
Classic responsive grid layout with hover effects:
|
||||
- 2 columns on mobile
|
||||
- 3-6 columns on larger screens
|
||||
- Grayscale to color transition on hover
|
||||
- Scale animation on hover
|
||||
|
||||
### Carousel Mode
|
||||
Auto-scrolling horizontal carousel:
|
||||
- Continuous smooth scrolling
|
||||
- Pauses on hover
|
||||
- Duplicate items for seamless loop
|
||||
- Ideal for many logos
|
||||
|
||||
### Marquee Mode
|
||||
Traditional marquee scroll effect:
|
||||
- Continuous horizontal movement
|
||||
- Lightweight and performant
|
||||
- Good for header/footer placement
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Grid Layout
|
||||
```yaml
|
||||
sections:
|
||||
- block: logos
|
||||
content:
|
||||
title: Our Partners & Collaborators
|
||||
subtitle: Working with leading institutions worldwide
|
||||
text: We collaborate with top universities and research centers
|
||||
logos:
|
||||
- name: MIT
|
||||
image: partners/mit.png
|
||||
url: https://mit.edu
|
||||
description: Massachusetts Institute of Technology
|
||||
- name: Stanford University
|
||||
image: partners/stanford.svg
|
||||
url: https://stanford.edu
|
||||
description: Leading research university
|
||||
- name: Google Research
|
||||
image: partners/google.png
|
||||
url: https://research.google
|
||||
description: AI and ML research
|
||||
- name: Microsoft Research
|
||||
image: partners/microsoft.svg
|
||||
url: https://www.microsoft.com/research
|
||||
description: Computing research lab
|
||||
design:
|
||||
display_mode: grid
|
||||
css_class: "bg-gray-50 dark:bg-gray-900"
|
||||
```
|
||||
|
||||
### Carousel Display
|
||||
```yaml
|
||||
sections:
|
||||
- block: logos
|
||||
content:
|
||||
title: Trusted By Industry Leaders
|
||||
logos:
|
||||
# Add 8-12 logos for best carousel effect
|
||||
- name: Company 1
|
||||
image: sponsors/company1.png
|
||||
url: https://example.com
|
||||
# ... more logos
|
||||
design:
|
||||
display_mode: carousel
|
||||
```
|
||||
|
||||
### Legacy Folder Mode
|
||||
For backward compatibility or quick setup:
|
||||
```yaml
|
||||
sections:
|
||||
- block: logos
|
||||
content:
|
||||
title: Our Sponsors
|
||||
logo_folder: sponsors # Loads all images from assets/media/sponsors/
|
||||
design:
|
||||
display_mode: grid
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### Content Options
|
||||
|
||||
- **title**: Main heading for the section
|
||||
- **subtitle**: Optional subtitle (shown in accent color)
|
||||
- **text**: Description text below title
|
||||
- **logos**: Array of logo configurations
|
||||
- `name`: Partner/sponsor name (shown in tooltip)
|
||||
- `image`: Path relative to `assets/media/`
|
||||
- `url`: Website URL
|
||||
- `external`: Open in new tab (default: true)
|
||||
- `description`: Hover tooltip text
|
||||
- **logo_folder**: Legacy option to load all images from a folder
|
||||
- **cta**: Optional call-to-action button
|
||||
- `text`: Button text
|
||||
- `url`: Button link
|
||||
- `icon`: Optional icon
|
||||
|
||||
### Design Options
|
||||
|
||||
- **display_mode**: Choose layout style
|
||||
- `grid`: Responsive grid (default)
|
||||
- `carousel`: Auto-scrolling carousel
|
||||
- `marquee`: Classic marquee effect
|
||||
- **show_pattern**: Add decorative background pattern (default: false)
|
||||
|
||||
## Image Guidelines
|
||||
|
||||
### Recommended Formats
|
||||
- **SVG**: Best for logos (scalable, small file size)
|
||||
- **PNG**: For logos with transparency
|
||||
- **WebP**: Modern format with best compression
|
||||
|
||||
### Size Recommendations
|
||||
- **Grid Mode**: 280x140px (will be auto-fitted)
|
||||
- **Carousel Mode**: 280x140px minimum
|
||||
- **Aspect Ratio**: 2:1 or square works best
|
||||
|
||||
### Optimization Tips
|
||||
1. Use SVG when possible for crisp rendering
|
||||
2. Optimize PNGs with tools like TinyPNG
|
||||
3. Keep file sizes under 50KB per logo
|
||||
4. Use consistent dimensions for visual harmony
|
||||
|
||||
## Styling
|
||||
|
||||
### Hover Effects
|
||||
- Grayscale filter removed on hover
|
||||
- Opacity increases from 60% to 100%
|
||||
- Smooth scale transformation (110%)
|
||||
- Background highlight on hover
|
||||
|
||||
### Custom CSS
|
||||
Add custom styles to enhance the block:
|
||||
|
||||
```css
|
||||
/* Example: Custom logo sizing */
|
||||
.logos-block img {
|
||||
max-height: 80px;
|
||||
}
|
||||
|
||||
/* Example: Remove grayscale on specific logo */
|
||||
.partner-featured img {
|
||||
filter: none !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Logo Quality**: Use high-resolution logos (2x display size)
|
||||
2. **Consistency**: Keep logos similar in visual weight
|
||||
3. **Accessibility**: Always provide alt text via `name` field
|
||||
4. **Performance**: Optimize images before adding
|
||||
5. **Responsive**: Test on mobile devices
|
||||
6. **Links**: Verify all external links work
|
||||
7. **Copyright**: Ensure you have permission to display logos
|
||||
|
||||
## Examples
|
||||
|
||||
### Research Lab Partners
|
||||
```yaml
|
||||
logos:
|
||||
- name: National Science Foundation
|
||||
image: partners/nsf.svg
|
||||
url: https://nsf.gov
|
||||
description: Federal funding agency
|
||||
- name: NIH
|
||||
image: partners/nih.png
|
||||
url: https://nih.gov
|
||||
description: National Institutes of Health
|
||||
```
|
||||
|
||||
### Conference Sponsors
|
||||
```yaml
|
||||
logos:
|
||||
- name: Gold Sponsor - Microsoft
|
||||
image: sponsors/microsoft-gold.png
|
||||
url: https://microsoft.com
|
||||
description: Gold level sponsor
|
||||
```
|
||||
|
||||
### Industry Collaborations
|
||||
```yaml
|
||||
logos:
|
||||
- name: OpenAI
|
||||
image: collaborators/openai.svg
|
||||
url: https://openai.com
|
||||
description: AI research partnership
|
||||
```
|
||||
|
||||
## Animation Speed
|
||||
|
||||
To adjust animation speeds, modify the CSS:
|
||||
|
||||
```yaml
|
||||
design:
|
||||
css_style: |
|
||||
.animate-marquee { animation-duration: 20s; }
|
||||
.animate-scroll { animation-duration: 30s; }
|
||||
```
|
||||
@@ -0,0 +1,235 @@
|
||||
{{/* Hugo Blox: Logos Block */}}
|
||||
{{/* Documentation: https://hugoblox.com/blocks/ */}}
|
||||
{{/* License: https://github.com/HugoBlox/hugo-blox-builder/blob/main/LICENSE.md */}}
|
||||
|
||||
{{/* Initialise */}}
|
||||
{{ $page := .wcPage }}
|
||||
{{ $block := .wcBlock }}
|
||||
{{ $display_mode := $block.design.display_mode | default "grid" }}
|
||||
|
||||
{{ $title := $block.content.title | emojify | $page.RenderString }}
|
||||
{{ $subtitle := $block.content.subtitle | emojify | $page.RenderString }}
|
||||
{{ $text := $block.content.text | emojify | $page.RenderString }}
|
||||
|
||||
<div class="relative py-16 sm:py-20 lg:py-24 overflow-hidden">
|
||||
{{/* Background Pattern */}}
|
||||
{{ if $block.design.show_pattern }}
|
||||
<div class="absolute inset-0 opacity-5">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-primary-100 to-transparent dark:from-primary-900 dark:to-transparent"></div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
{{/* Section Header */}}
|
||||
<div class="text-center max-w-3xl mx-auto mb-12">
|
||||
{{ with $title }}
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white tracking-tight mb-4">
|
||||
{{ . }}
|
||||
</h2>
|
||||
{{ end }}
|
||||
|
||||
{{ with $subtitle }}
|
||||
<p class="text-xl text-primary-600 dark:text-primary-400 font-medium mb-2">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
|
||||
{{ with $text }}
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Grid Display Mode */}}
|
||||
{{ if eq $display_mode "grid" }}
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-8 lg:gap-12 items-center">
|
||||
|
||||
{{/* Logo Items from Data */}}
|
||||
{{ if $block.content.logos }}
|
||||
{{ range $block.content.logos }}
|
||||
<div class="group relative">
|
||||
{{ $link := .url | default "#" }}
|
||||
{{ $target := cond .external "_blank" "_self" }}
|
||||
<a href="{{ $link }}" target="{{ $target }}" rel="{{ if .external }}noopener{{ end }}"
|
||||
class="block p-4 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-all duration-300"
|
||||
{{ with .name }}title="{{ . }}"{{ end }}>
|
||||
<div class="relative">
|
||||
{{/* Logo Image */}}
|
||||
{{ if .image }}
|
||||
{{ $img := resources.Get (printf "media/%s" .image) }}
|
||||
{{ if $img }}
|
||||
{{ $isSVG := eq $img.MediaType.SubType "svg" }}
|
||||
{{ if not $isSVG }}
|
||||
{{ $img = $img.Fit "280x140 webp" }}
|
||||
{{ end }}
|
||||
<img src="{{ $img.RelPermalink }}"
|
||||
alt="{{ .name | default "Partner logo" }}"
|
||||
{{ if not $isSVG }}width="{{ $img.Width }}" height="{{ $img.Height }}"{{ end }}
|
||||
class="h-12 sm:h-14 lg:h-16 w-auto mx-auto object-contain filter grayscale opacity-60 group-hover:grayscale-0 group-hover:opacity-100 transition-all duration-300 group-hover:scale-110"
|
||||
loading="lazy">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Tooltip on Hover */}}
|
||||
{{ with .description }}
|
||||
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-gray-900 dark:bg-gray-700 text-white text-xs rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none whitespace-nowrap z-10">
|
||||
{{ . }}
|
||||
<div class="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-l-transparent border-r-4 border-r-transparent border-t-4 border-t-gray-900 dark:border-t-gray-700"></div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Logo Images from Folder (Legacy Support) */}}
|
||||
{{ else if $block.content.logo_folder }}
|
||||
{{ $images := resources.Match (printf "media/%s/*.{svg,png,jpg,webp}" $block.content.logo_folder) }}
|
||||
{{ range $images }}
|
||||
<div class="group relative">
|
||||
<div class="block p-4 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-all duration-300">
|
||||
{{ $image := . }}
|
||||
{{ $isSVG := eq $image.MediaType.SubType "svg" }}
|
||||
{{ if not $isSVG }}
|
||||
{{ $image = $image.Fit "280x140 webp" }}
|
||||
{{ end }}
|
||||
<img src="{{ $image.RelPermalink }}"
|
||||
alt="Partner logo"
|
||||
{{ if not $isSVG }}width="{{ $image.Width }}" height="{{ $image.Height }}"{{ end }}
|
||||
class="h-12 sm:h-14 lg:h-16 w-auto mx-auto object-contain filter grayscale opacity-60 hover:grayscale-0 hover:opacity-100 transition-all duration-300 hover:scale-110"
|
||||
loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Carousel Display Mode */}}
|
||||
{{ else if eq $display_mode "carousel" }}
|
||||
<div class="relative overflow-hidden">
|
||||
<div class="flex animate-scroll space-x-12 lg:space-x-16">
|
||||
{{/* Duplicate items for continuous scroll */}}
|
||||
{{ $items := slice }}
|
||||
{{ if $block.content.logos }}
|
||||
{{ $items = $block.content.logos }}
|
||||
{{ else if $block.content.logo_folder }}
|
||||
{{ $images := resources.Match (printf "media/%s/*.{svg,png,jpg,webp}" $block.content.logo_folder) }}
|
||||
{{ range $images }}
|
||||
{{ $items = $items | append (dict "image" .Name) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* First set of logos */}}
|
||||
{{ range $items }}
|
||||
<div class="flex-shrink-0">
|
||||
{{ if .image }}
|
||||
{{ $img := resources.Get (printf "media/%s" .image) }}
|
||||
{{ if $img }}
|
||||
{{ $isSVG := eq $img.MediaType.SubType "svg" }}
|
||||
{{ if not $isSVG }}
|
||||
{{ $img = $img.Fit "280x140 webp" }}
|
||||
{{ end }}
|
||||
<img src="{{ $img.RelPermalink }}"
|
||||
alt="{{ .name | default "Partner logo" }}"
|
||||
{{ if not $isSVG }}width="{{ $img.Width }}" height="{{ $img.Height }}"{{ end }}
|
||||
class="h-16 lg:h-20 w-auto object-contain opacity-70 hover:opacity-100 transition-opacity"
|
||||
loading="lazy">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Duplicate for seamless loop */}}
|
||||
{{ range $items }}
|
||||
<div class="flex-shrink-0">
|
||||
{{ if .image }}
|
||||
{{ $img := resources.Get (printf "media/%s" .image) }}
|
||||
{{ if $img }}
|
||||
{{ $isSVG := eq $img.MediaType.SubType "svg" }}
|
||||
{{ if not $isSVG }}
|
||||
{{ $img = $img.Fit "280x140 webp" }}
|
||||
{{ end }}
|
||||
<img src="{{ $img.RelPermalink }}"
|
||||
alt="{{ .name | default "Partner logo" }}"
|
||||
{{ if not $isSVG }}width="{{ $img.Width }}" height="{{ $img.Height }}"{{ end }}
|
||||
class="h-16 lg:h-20 w-auto object-contain opacity-70 hover:opacity-100 transition-opacity"
|
||||
loading="lazy">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Marquee Display Mode */}}
|
||||
{{ else if eq $display_mode "marquee" }}
|
||||
<div class="relative">
|
||||
<div class="overflow-hidden">
|
||||
<div class="flex animate-marquee whitespace-nowrap">
|
||||
{{ if $block.content.logos }}
|
||||
{{ range $block.content.logos }}
|
||||
<div class="mx-8 inline-flex items-center">
|
||||
{{ if .image }}
|
||||
{{ $img := resources.Get (printf "media/%s" .image) }}
|
||||
{{ if $img }}
|
||||
{{ $isSVG := eq $img.MediaType.SubType "svg" }}
|
||||
{{ if not $isSVG }}
|
||||
{{ $img = $img.Fit "280x140 webp" }}
|
||||
{{ end }}
|
||||
<img src="{{ $img.RelPermalink }}"
|
||||
alt="{{ .name | default "Partner logo" }}"
|
||||
{{ if not $isSVG }}width="{{ $img.Width }}" height="{{ $img.Height }}"{{ end }}
|
||||
class="h-14 lg:h-16 w-auto object-contain opacity-70"
|
||||
loading="lazy">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Call to Action */}}
|
||||
{{ with $block.content.cta }}
|
||||
<div class="mt-12 text-center">
|
||||
<a href="{{ .url }}"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg shadow hover:shadow-lg transition-all duration-300">
|
||||
{{ .text }}
|
||||
{{ with .icon }}
|
||||
{{ partial "functions/get_icon" (dict "name" . "attributes" "class=\"ml-2 w-5 h-5\"") }}
|
||||
{{ end }}
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Custom CSS for Animations */}}
|
||||
<style>
|
||||
@keyframes marquee {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
@keyframes scroll {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
.animate-marquee {
|
||||
animation: marquee 30s linear infinite;
|
||||
}
|
||||
|
||||
.animate-scroll {
|
||||
animation: scroll 40s linear infinite;
|
||||
}
|
||||
|
||||
.animate-marquee:hover,
|
||||
.animate-scroll:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "logos",
|
||||
"name": "Logos",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"category": "content",
|
||||
"tags": ["logos", "partners", "sponsors", "collaborators", "brands", "clients", "affiliations"],
|
||||
"description": "Display partner, sponsor, or collaborator logos with interactive effects and multiple display modes",
|
||||
"author": "Hugo Blox",
|
||||
"homepage": "https://hugoblox.com/blocks/",
|
||||
"repository": "https://github.com/HugoBlox/hugo-blox-builder",
|
||||
"keywords": ["hugo", "static-site", "logos", "partners", "sponsors", "carousel", "marquee"]
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://hugoblox.com/schemas/blocks/logos.json",
|
||||
"title": "Logos Block Schema",
|
||||
"description": "Schema for the Logos block - displays partner/sponsor logos with various display modes",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "../shared/schemas/base-block.json"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"block": {
|
||||
"const": "logos"
|
||||
},
|
||||
"content": {
|
||||
"type": "object",
|
||||
"description": "Content configuration for the logos block",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Main section title"
|
||||
},
|
||||
"subtitle": {
|
||||
"type": "string",
|
||||
"description": "Section subtitle"
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Section description text"
|
||||
},
|
||||
"logos": {
|
||||
"type": "array",
|
||||
"description": "Array of logo items with details",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Partner/sponsor name"
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"description": "Path to logo image relative to assets/media/"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Link to partner website"
|
||||
},
|
||||
"external": {
|
||||
"type": "boolean",
|
||||
"description": "Open link in new tab",
|
||||
"default": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Short description shown on hover"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"logo_folder": {
|
||||
"type": "string",
|
||||
"description": "Legacy: Folder containing logo images (deprecated, use logos array instead)"
|
||||
},
|
||||
"cta": {
|
||||
"type": "object",
|
||||
"description": "Call to action button",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Button text"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Button URL"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Optional icon name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"design": {
|
||||
"type": "object",
|
||||
"description": "Design options for the logos block",
|
||||
"properties": {
|
||||
"display_mode": {
|
||||
"type": "string",
|
||||
"enum": ["grid", "carousel", "marquee"],
|
||||
"description": "How to display the logos",
|
||||
"default": "grid"
|
||||
},
|
||||
"show_pattern": {
|
||||
"type": "boolean",
|
||||
"description": "Show decorative background pattern",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
# Research Areas Block
|
||||
|
||||
Display your research focus areas with stunning visuals, metrics, and interactive layouts. Perfect for research labs, academic groups, and R&D departments to showcase their areas of expertise.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Layout Options**: Cards, hexagon, or timeline layouts
|
||||
- **Rich Visual Options**: Icons, emojis, images, or gradient backgrounds
|
||||
- **Metrics Display**: Team size, publications, funding
|
||||
- **Interactive Elements**: Hover effects, status badges, CTAs
|
||||
- **Topic Keywords**: Highlight key research topics
|
||||
- **Responsive Design**: Beautiful on all devices
|
||||
- **Dark Mode Support**: Seamless theme switching
|
||||
- **Performance Optimized**: Lazy loading and WebP support
|
||||
|
||||
## Layout Options
|
||||
|
||||
### Cards Layout (Default)
|
||||
Modern card-based design with:
|
||||
- Large visual header (image/icon/emoji)
|
||||
- Rich content area
|
||||
- Hover animations
|
||||
- Status badges
|
||||
- Metrics display
|
||||
|
||||
### Hexagon Layout
|
||||
Unique hexagonal grid for visual impact:
|
||||
- Geometric design
|
||||
- Gradient backgrounds
|
||||
- Compact information display
|
||||
- Great for 3-6 areas
|
||||
|
||||
### Timeline Layout
|
||||
Chronological or priority-based display:
|
||||
- Vertical timeline
|
||||
- Alternating left/right cards
|
||||
- Visual progression
|
||||
- Good for research evolution
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Research Areas with Cards
|
||||
```yaml
|
||||
sections:
|
||||
- block: research-areas
|
||||
content:
|
||||
title: Research Focus Areas
|
||||
subtitle: Advancing Science Through Innovation
|
||||
text: Our lab conducts cutting-edge research across multiple domains
|
||||
items:
|
||||
- name: Computational Biology
|
||||
description: Developing algorithms for genomic analysis and protein structure prediction
|
||||
emoji: 🧬
|
||||
gradient: from-green-400 to-blue-500
|
||||
status: active
|
||||
topics:
|
||||
- Genomics
|
||||
- Proteomics
|
||||
- Bioinformatics
|
||||
- Systems Biology
|
||||
team_size: 12 researchers
|
||||
publications: 45 papers
|
||||
funding: $2.5M
|
||||
cta:
|
||||
text: Learn More
|
||||
url: /research/computational-biology
|
||||
|
||||
- name: Machine Learning
|
||||
description: Advancing deep learning methods for scientific discovery
|
||||
emoji: 🤖
|
||||
gradient: from-purple-400 to-pink-500
|
||||
status: active
|
||||
topics:
|
||||
- Deep Learning
|
||||
- Neural Networks
|
||||
- Computer Vision
|
||||
- NLP
|
||||
team_size: 8 researchers
|
||||
publications: 32 papers
|
||||
funding: $1.8M
|
||||
|
||||
- name: Materials Science
|
||||
description: Designing novel materials through computational modeling
|
||||
emoji: 🔬
|
||||
gradient: from-orange-400 to-red-500
|
||||
status: emerging
|
||||
topics:
|
||||
- Nanomaterials
|
||||
- Quantum Materials
|
||||
- Polymers
|
||||
team_size: 6 researchers
|
||||
publications: 28 papers
|
||||
funding: $1.2M
|
||||
design:
|
||||
layout: cards
|
||||
```
|
||||
|
||||
### Using Icons Instead of Emojis
|
||||
```yaml
|
||||
items:
|
||||
- name: Artificial Intelligence
|
||||
description: Building next-generation AI systems
|
||||
icon: hero/cpu-chip
|
||||
gradient: from-blue-500 to-indigo-600
|
||||
# ... rest of configuration
|
||||
```
|
||||
|
||||
### Using Images
|
||||
```yaml
|
||||
items:
|
||||
- name: Climate Science
|
||||
description: Understanding and mitigating climate change
|
||||
image: research/climate-hero.jpg
|
||||
status: active
|
||||
# ... rest of configuration
|
||||
```
|
||||
|
||||
### Hexagon Layout for Visual Impact
|
||||
```yaml
|
||||
sections:
|
||||
- block: research-areas
|
||||
content:
|
||||
title: Core Research Pillars
|
||||
items:
|
||||
- name: AI & ML
|
||||
description: Artificial intelligence research
|
||||
icon: hero/cpu-chip
|
||||
gradient: from-blue-400 to-purple-600
|
||||
- name: Quantum
|
||||
description: Quantum computing systems
|
||||
icon: hero/sparkles
|
||||
gradient: from-green-400 to-teal-600
|
||||
# ... more items
|
||||
design:
|
||||
layout: hexagon
|
||||
```
|
||||
|
||||
### Timeline Layout for Evolution
|
||||
```yaml
|
||||
sections:
|
||||
- block: research-areas
|
||||
content:
|
||||
title: Research Evolution
|
||||
subtitle: Our Journey of Discovery
|
||||
items:
|
||||
- name: Foundation (2015-2018)
|
||||
description: Established core ML research
|
||||
icon: hero/academic-cap
|
||||
- name: Expansion (2018-2021)
|
||||
description: Added biomedical applications
|
||||
icon: hero/beaker
|
||||
- name: Current Focus (2021-Present)
|
||||
description: AI for scientific discovery
|
||||
icon: hero/sparkles
|
||||
design:
|
||||
layout: timeline
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Content Options
|
||||
|
||||
- **title**: Main section heading
|
||||
- **subtitle**: Section subtitle (accent color)
|
||||
- **text**: Description paragraph
|
||||
- **items**: Array of research areas
|
||||
- `name`: Area name (required)
|
||||
- `description`: Area description (required)
|
||||
- `icon`: Icon identifier (e.g., 'hero/beaker')
|
||||
- `emoji`: Emoji alternative to icon
|
||||
- `image`: Image path relative to assets/media/
|
||||
- `gradient`: Tailwind gradient classes
|
||||
- `url`: Link to detailed page
|
||||
- `status`: 'active', 'emerging', or 'planning'
|
||||
- `topics`: Array of keywords/topics
|
||||
- `team_size`: Team member count
|
||||
- `publications`: Publication count
|
||||
- `funding`: Funding amount
|
||||
- `cta`: Individual area CTA with text and url
|
||||
- **cta**: Global call-to-action button
|
||||
|
||||
### Design Options
|
||||
|
||||
- **layout**: Visual layout style
|
||||
- `cards`: Modern card grid (default)
|
||||
- `hexagon`: Hexagonal grid
|
||||
- `timeline`: Vertical timeline
|
||||
|
||||
## Visual Guidelines
|
||||
|
||||
### Gradients
|
||||
Use Tailwind gradient classes for visual appeal:
|
||||
```yaml
|
||||
gradient: from-blue-400 to-purple-600
|
||||
gradient: from-green-500 to-teal-400
|
||||
gradient: from-orange-400 to-red-500
|
||||
```
|
||||
|
||||
### Icons vs Emojis vs Images
|
||||
- **Icons**: Professional, consistent style
|
||||
- **Emojis**: Friendly, universal, no loading
|
||||
- **Images**: Rich visuals, specific imagery
|
||||
|
||||
### Status Badges
|
||||
- `active`: Green - Currently active research
|
||||
- `emerging`: Yellow - Growing area
|
||||
- `planning`: Blue - Future direction
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Consistency**: Use the same visual type (icons OR emojis OR images) across all areas
|
||||
2. **Metrics**: Include quantifiable achievements
|
||||
3. **Keywords**: 3-5 relevant topics per area
|
||||
4. **Descriptions**: Keep concise (50-100 words)
|
||||
5. **CTAs**: Link to detailed pages for each area
|
||||
6. **Gradients**: Choose complementary colors
|
||||
7. **Status**: Be transparent about research maturity
|
||||
|
||||
## Responsive Behavior
|
||||
|
||||
### Cards Layout
|
||||
- Mobile: Single column
|
||||
- Tablet: 2 columns
|
||||
- Desktop: 3 columns
|
||||
|
||||
### Hexagon Layout
|
||||
- Mobile: Stacked hexagons
|
||||
- Desktop: Honeycomb grid
|
||||
|
||||
### Timeline Layout
|
||||
- Mobile: Single column timeline
|
||||
- Desktop: Alternating left/right
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Images**: Optimize to <100KB
|
||||
2. **WebP Format**: Use for better compression
|
||||
3. **Lazy Loading**: Automatic for images
|
||||
4. **Icon Usage**: Prefer icons/emojis over images for speed
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Semantic HTML structure
|
||||
- Proper heading hierarchy
|
||||
- Alt text for images
|
||||
- Keyboard navigable
|
||||
- Screen reader friendly
|
||||
- High contrast for text
|
||||
|
||||
## Examples by Field
|
||||
|
||||
### AI/ML Lab
|
||||
```yaml
|
||||
items:
|
||||
- name: Deep Learning
|
||||
emoji: 🧠
|
||||
gradient: from-purple-500 to-pink-500
|
||||
- name: Computer Vision
|
||||
emoji: 👁️
|
||||
gradient: from-blue-500 to-cyan-500
|
||||
- name: NLP
|
||||
emoji: 💬
|
||||
gradient: from-green-500 to-teal-500
|
||||
```
|
||||
|
||||
### Biology Lab
|
||||
```yaml
|
||||
items:
|
||||
- name: Genomics
|
||||
emoji: 🧬
|
||||
gradient: from-green-400 to-emerald-600
|
||||
- name: Cell Biology
|
||||
emoji: 🦠
|
||||
gradient: from-purple-400 to-indigo-600
|
||||
- name: Neuroscience
|
||||
emoji: 🧠
|
||||
gradient: from-pink-400 to-rose-600
|
||||
```
|
||||
|
||||
### Physics Lab
|
||||
```yaml
|
||||
items:
|
||||
- name: Quantum Computing
|
||||
emoji: ⚛️
|
||||
gradient: from-indigo-500 to-purple-600
|
||||
- name: Particle Physics
|
||||
emoji: 🌌
|
||||
gradient: from-blue-500 to-indigo-600
|
||||
- name: Condensed Matter
|
||||
emoji: 💎
|
||||
gradient: from-cyan-500 to-blue-600
|
||||
```
|
||||
@@ -0,0 +1,263 @@
|
||||
{{/* Hugo Blox: Research Areas Block */}}
|
||||
{{/* Documentation: https://hugoblox.com/blocks/ */}}
|
||||
{{/* License: https://github.com/HugoBlox/hugo-blox-builder/blob/main/LICENSE.md */}}
|
||||
|
||||
{{/* Initialise */}}
|
||||
{{ $page := .wcPage }}
|
||||
{{ $block := .wcBlock }}
|
||||
{{ $layout := $block.design.layout | default "cards" }}
|
||||
|
||||
{{ $title := $block.content.title | emojify | $page.RenderString }}
|
||||
{{ $subtitle := $block.content.subtitle | emojify | $page.RenderString }}
|
||||
{{ $text := $block.content.text | emojify | $page.RenderString }}
|
||||
|
||||
<div class="py-16 sm:py-20 lg:py-24 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
|
||||
{{/* Section Header */}}
|
||||
<div class="text-center max-w-3xl mx-auto mb-12 lg:mb-16">
|
||||
{{ with $title }}
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white tracking-tight mb-4">
|
||||
{{ . }}
|
||||
</h2>
|
||||
{{ end }}
|
||||
|
||||
{{ with $subtitle }}
|
||||
<p class="text-xl text-primary-600 dark:text-primary-400 font-medium mb-3">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
|
||||
{{ with $text }}
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Cards Layout */}}
|
||||
{{ if eq $layout "cards" }}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 lg:gap-10">
|
||||
{{ range $idx, $item := $block.content.items }}
|
||||
<div class="group relative">
|
||||
<div class="h-full bg-white dark:bg-gray-800 rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2 overflow-hidden flex flex-col">
|
||||
|
||||
{{/* Card Image/Icon Header */}}
|
||||
{{ $header_content := "" }}
|
||||
{{ if $item.image }}
|
||||
{{ $img := resources.Get (printf "media/%s" $item.image) }}
|
||||
{{ if $img }}
|
||||
{{ $img = $img.Fill "600x400 Center webp" }}
|
||||
{{ $header_content = printf "<img src=\"%s\" alt=\"%s\" class=\"w-full h-full object-cover opacity-80 group-hover:opacity-100 group-hover:scale-110 transition-all duration-500\">" $img.RelPermalink $item.name }}
|
||||
{{ end }}
|
||||
{{ else if $item.icon }}
|
||||
{{ $icon_html := partial "functions/get_icon" (dict "name" $item.icon "attributes" "class=\"w-24 h-24 text-white/80 group-hover:scale-110 transition-transform duration-300\"") }}
|
||||
{{ $header_content = printf "<div class=\"absolute inset-0 flex items-center justify-center\">%s</div>" $icon_html }}
|
||||
{{ end }}
|
||||
|
||||
{{ if and $item.cta.url $header_content }}
|
||||
<a href="{{ $item.cta.url }}" class="block relative h-48 bg-gradient-to-br {{ $item.gradient | default "from-primary-400 to-secondary-400" }} overflow-hidden">
|
||||
{{ $header_content | safeHTML }}
|
||||
</a>
|
||||
{{ else }}
|
||||
<div class="relative h-48 bg-gradient-to-br {{ $item.gradient | default "from-primary-400 to-secondary-400" }} overflow-hidden">
|
||||
{{ $header_content | safeHTML }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{/* Status Badge */}}
|
||||
{{ with $item.status }}
|
||||
<div class="absolute top-4 right-4 z-10">
|
||||
<span class="px-3 py-1 bg-white/90 dark:bg-gray-900/90 backdrop-blur text-xs font-semibold rounded-full
|
||||
{{ if eq . "active" }}text-green-600 dark:text-green-400
|
||||
{{ else if eq . "emerging" }}text-yellow-600 dark:text-yellow-400
|
||||
{{ else if eq . "planning" }}text-blue-600 dark:text-blue-400
|
||||
{{ end }}">
|
||||
{{ . | title }}
|
||||
</span>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Card Content - Flexible layout */}}
|
||||
<div class="flex flex-col flex-1 p-6">
|
||||
{{/* Main Content */}}
|
||||
<div class="flex-1">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
|
||||
{{ if $item.url }}
|
||||
<a href="{{ $item.url }}" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors">
|
||||
{{ $item.name }}
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/arrow-top-right-on-square" "attributes" "class=\"inline-block w-4 h-4 ml-1\"") }}
|
||||
</a>
|
||||
{{ else }}
|
||||
{{ $item.name }}
|
||||
{{ end }}
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{{ $item.description }}
|
||||
</p>
|
||||
|
||||
{{/* Key Topics/Keywords */}}
|
||||
{{ with $item.topics }}
|
||||
<div class="flex flex-wrap gap-2 mb-6">
|
||||
{{ range first 3 . }}
|
||||
<span class="inline-block px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs rounded">
|
||||
{{ . }}
|
||||
</span>
|
||||
{{ end }}
|
||||
{{ if gt (len .) 3 }}
|
||||
<span class="inline-block px-2 py-1 text-gray-500 dark:text-gray-400 text-xs">
|
||||
+{{ sub (len .) 3 }} more
|
||||
</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Footer Section - Always at bottom */}}
|
||||
<div class="mt-auto">
|
||||
{{/* Stats/Metrics */}}
|
||||
{{ if or $item.team_size $item.publications $item.funding }}
|
||||
<div class="grid grid-cols-3 gap-3 pt-4 border-t border-gray-200 dark:border-gray-700 mb-4">
|
||||
{{ with $item.team_size }}
|
||||
<div class="flex flex-col items-center text-center text-gray-500 dark:text-gray-400">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/user-group" "attributes" "class=\"w-4 h-4 mx-auto mb-1\"") }}
|
||||
<span class="text-xs leading-tight">{{ . | replaceRE " researchers" "" }}</span>
|
||||
<span class="text-xs text-gray-400">team</span>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ with $item.publications }}
|
||||
<div class="flex flex-col items-center text-center text-gray-500 dark:text-gray-400">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/document-text" "attributes" "class=\"w-4 h-4 mx-auto mb-1\"") }}
|
||||
<span class="text-xs leading-tight">{{ . | replaceRE " papers" "" }}</span>
|
||||
<span class="text-xs text-gray-400">papers</span>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ with $item.funding }}
|
||||
<div class="flex flex-col items-center text-center text-gray-500 dark:text-gray-400">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/currency-dollar" "attributes" "class=\"w-4 h-4 mx-auto mb-1\"") }}
|
||||
<span class="text-xs leading-tight">{{ . }}</span>
|
||||
<span class="text-xs text-gray-400">funding</span>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Call to Action */}}
|
||||
{{ with $item.cta }}
|
||||
<a href="{{ .url }}"
|
||||
class="inline-flex items-center w-full justify-center px-4 py-2 bg-primary-50 dark:bg-primary-900/20 text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 font-medium text-sm rounded-lg hover:bg-primary-100 dark:hover:bg-primary-900/30 transition-all duration-300">
|
||||
{{ .text }}
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/arrow-right" "attributes" "class=\"ml-1 w-4 h-4\"") }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Hexagon Layout */}}
|
||||
{{ else if eq $layout "hexagon" }}
|
||||
<div class="flex flex-wrap justify-center gap-8 lg:gap-4">
|
||||
{{ range $idx, $item := $block.content.items }}
|
||||
<div class="hexagon-container group">
|
||||
<div class="hexagon bg-gradient-to-br {{ $item.gradient | default "from-primary-400 to-secondary-400" }} shadow-xl hover:shadow-2xl transition-all duration-300">
|
||||
<div class="hexagon-content">
|
||||
{{ if $item.icon }}
|
||||
{{ partial "functions/get_icon" (dict "name" $item.icon "attributes" "class=\"w-12 h-12 text-white mb-3\"") }}
|
||||
{{ end }}
|
||||
<h3 class="text-lg font-bold text-white mb-2">{{ $item.name }}</h3>
|
||||
<p class="text-white/90 text-sm">{{ $item.description | truncate 60 }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Timeline Layout */}}
|
||||
{{ else if eq $layout "timeline" }}
|
||||
<div class="relative">
|
||||
{{/* Vertical Line */}}
|
||||
<div class="absolute left-1/2 transform -translate-x-1/2 w-1 h-full bg-gradient-to-b from-primary-400 to-secondary-400"></div>
|
||||
|
||||
<div class="space-y-12">
|
||||
{{ range $idx, $item := $block.content.items }}
|
||||
<div class="relative flex items-center {{ if eq (mod $idx 2) 0 }}justify-start{{ else }}justify-end{{ end }}">
|
||||
{{/* Timeline dot */}}
|
||||
<div class="absolute left-1/2 transform -translate-x-1/2 w-8 h-8 bg-white dark:bg-gray-800 border-4 border-primary-500 rounded-full z-10"></div>
|
||||
|
||||
<div class="w-5/12 {{ if eq (mod $idx 2) 1 }}mr-auto{{ end }}">
|
||||
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-lg hover:shadow-xl transition-shadow">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
{{ if $item.icon }}
|
||||
<div class="w-10 h-10 bg-primary-100 dark:bg-primary-900/50 rounded-full flex items-center justify-center">
|
||||
{{ partial "functions/get_icon" (dict "name" $item.icon "attributes" "class=\"w-5 h-5 text-primary-600 dark:text-primary-400\"") }}
|
||||
</div>
|
||||
{{ end }}
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-white">{{ $item.name }}</h3>
|
||||
</div>
|
||||
<p class="text-gray-600 dark:text-gray-400">{{ $item.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Global Call to Action */}}
|
||||
{{ with $block.content.cta }}
|
||||
<div class="mt-12 text-center">
|
||||
<a href="{{ .url }}"
|
||||
class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg shadow hover:shadow-lg transition-all duration-300">
|
||||
{{ .text }}
|
||||
{{ with .icon }}
|
||||
{{ partial "functions/get_icon" (dict "name" . "attributes" "class=\"ml-2 w-5 h-5\"") }}
|
||||
{{ end }}
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Custom CSS for Hexagon Layout */}}
|
||||
{{ if eq $layout "hexagon" }}
|
||||
<style>
|
||||
.hexagon-container {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.hexagon {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.hexagon:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.hexagon-content {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hexagon-container {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "research-areas",
|
||||
"name": "Research Areas",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"category": "content",
|
||||
"tags": ["research", "areas", "focus", "topics", "science", "lab", "academic", "showcase"],
|
||||
"description": "Display research focus areas with rich visuals, metrics, and interactive layouts",
|
||||
"author": "Hugo Blox",
|
||||
"homepage": "https://hugoblox.com/blocks/",
|
||||
"repository": "https://github.com/HugoBlox/hugo-blox-builder",
|
||||
"keywords": ["hugo", "static-site", "research", "areas", "academic", "lab", "science"]
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://hugoblox.com/schemas/blocks/research-areas.json",
|
||||
"title": "Research Areas Block Schema",
|
||||
"description": "Schema for the Research Areas block - displays research focus areas with rich visuals and metrics",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "../shared/schemas/base-block.json"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"block": {
|
||||
"const": "research-areas"
|
||||
},
|
||||
"content": {
|
||||
"type": "object",
|
||||
"description": "Content configuration for the research areas block",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Section title"
|
||||
},
|
||||
"subtitle": {
|
||||
"type": "string",
|
||||
"description": "Section subtitle"
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Section description text"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"description": "Array of research area items",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Research area name",
|
||||
"required": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Research area description",
|
||||
"required": true
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Icon identifier (e.g., 'hero/beaker', 'devicon/python')"
|
||||
},
|
||||
"emoji": {
|
||||
"type": "string",
|
||||
"description": "Emoji as alternative to icon"
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"description": "Image path relative to assets/media/"
|
||||
},
|
||||
"gradient": {
|
||||
"type": "string",
|
||||
"description": "Tailwind gradient classes (e.g., 'from-blue-400 to-purple-400')"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Link to detailed page"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["active", "emerging", "planning"],
|
||||
"description": "Research area status"
|
||||
},
|
||||
"topics": {
|
||||
"type": "array",
|
||||
"description": "Key topics/keywords",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"team_size": {
|
||||
"type": "string",
|
||||
"description": "Number of team members (e.g., '12 researchers')"
|
||||
},
|
||||
"publications": {
|
||||
"type": "string",
|
||||
"description": "Number of publications (e.g., '45 papers')"
|
||||
},
|
||||
"funding": {
|
||||
"type": "string",
|
||||
"description": "Funding amount (e.g., '$2.5M')"
|
||||
},
|
||||
"cta": {
|
||||
"type": "object",
|
||||
"description": "Call to action for this area",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"type": "object",
|
||||
"description": "Global call to action",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"design": {
|
||||
"type": "object",
|
||||
"description": "Design options for the research areas block",
|
||||
"properties": {
|
||||
"layout": {
|
||||
"type": "string",
|
||||
"enum": ["cards", "hexagon", "timeline"],
|
||||
"description": "Layout style for research areas",
|
||||
"default": "cards"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,59 +1,227 @@
|
||||
# Stats Block
|
||||
|
||||
**Let your numbers tell your success story**
|
||||
Display impressive statistics with modern animations, icons, and engaging visual effects. Perfect for showcasing lab achievements, research metrics, and organizational highlights.
|
||||
|
||||
Showcase your achievements, milestones, and key metrics with the Stats block - a powerful component that transforms dry numbers into compelling visual statements that build credibility and demonstrate your impact.
|
||||
## Key Features
|
||||
|
||||
## ✨ Key Features
|
||||
- **🎬 Counter Animations**: Numbers count up on scroll with easing
|
||||
- **📊 Visual Icons**: Icons for each statistic enhance comprehension
|
||||
- **🎨 Multiple Layouts**: Cards, compact, or minimal display modes
|
||||
- **📱 Responsive Design**: Adapts beautifully to all screen sizes
|
||||
- **🌙 Dark Mode Ready**: Seamless theme switching
|
||||
- **⚡ Performance**: CSS-first animations with minimal JavaScript
|
||||
- **🎯 Intersection Observer**: Animations trigger when scrolled into view
|
||||
- **✨ Hover Effects**: Subtle interactions for engagement
|
||||
|
||||
- **Bold Typography**: Extra-large numbers (4xl to 6xl) that command attention
|
||||
- **Three-Column Grid**: Perfect layout for highlighting your top 3 metrics
|
||||
- **Visual Separators**: Subtle borders that organize information without clutter
|
||||
- **Responsive Design**: Adapts beautifully from mobile to desktop
|
||||
- **Markdown Support**: Rich formatting for both statistics and descriptions
|
||||
- **Optional Header**: Add context with title and descriptive text
|
||||
- **Professional Styling**: Clean, modern design that builds trust
|
||||
## Layout Options
|
||||
|
||||
## 🎯 Perfect For
|
||||
### Cards Layout (Default)
|
||||
Modern card-based design featuring:
|
||||
- Individual cards with shadows and hover effects
|
||||
- Icons with background circles
|
||||
- Gradient hover states
|
||||
- Staggered animation entry
|
||||
- Optimal for 2-4 statistics
|
||||
|
||||
- **Business Metrics**: Revenue, customers served, years in business
|
||||
- **Product Statistics**: Downloads, users, installations, success rates
|
||||
- **Company Achievements**: Awards won, projects completed, team size
|
||||
- **Performance Data**: Uptime, response times, satisfaction scores
|
||||
- **Social Proof**: Followers, testimonials, reviews, case studies
|
||||
- **Impact Metrics**: Lives changed, money saved, time reduced
|
||||
### Compact Layout
|
||||
Single card with divided sections:
|
||||
- Unified background
|
||||
- Bordered divisions
|
||||
- Space-efficient
|
||||
- Good for 3-6 statistics
|
||||
|
||||
## 🚀 Why Choose Stats Block?
|
||||
### Minimal Layout
|
||||
Clean, icon-centric design:
|
||||
- No backgrounds or borders
|
||||
- Focus on typography
|
||||
- Suitable for headers/footers
|
||||
- Works well with many statistics
|
||||
|
||||
**Instant Credibility**: Large, prominent numbers immediately establish authority and success
|
||||
## Usage Examples
|
||||
|
||||
**Social Proof**: Quantified achievements provide concrete evidence of your value
|
||||
### Research Lab Metrics (Enhanced)
|
||||
```yaml
|
||||
sections:
|
||||
- block: stats
|
||||
content:
|
||||
title: Impact by the Numbers
|
||||
text: Driving scientific discovery through measurable achievements
|
||||
items:
|
||||
- statistic: "150+"
|
||||
description: Publications in top-tier journals
|
||||
sub_metric: Nature, Science, Cell, PNAS
|
||||
icon: hero/document-text
|
||||
|
||||
- statistic: "25"
|
||||
description: Brilliant researchers and scientists
|
||||
sub_metric: From 12 countries worldwide
|
||||
icon: hero/user-group
|
||||
|
||||
- statistic: "$12M"
|
||||
description: Active research funding
|
||||
sub_metric: NSF, NIH, DOE grants
|
||||
icon: hero/currency-dollar
|
||||
|
||||
- statistic: "8"
|
||||
description: Breakthrough discoveries
|
||||
sub_metric: Patent applications filed
|
||||
icon: hero/light-bulb
|
||||
design:
|
||||
layout: cards
|
||||
css_class: "bg-gradient-to-b from-primary-50 to-white dark:from-primary-900/20 dark:to-gray-800"
|
||||
```
|
||||
|
||||
**Visual Impact**: Bold typography and clean layout make statistics impossible to ignore
|
||||
### University Department Stats
|
||||
```yaml
|
||||
content:
|
||||
items:
|
||||
- statistic: "2,500"
|
||||
description: Students enrolled
|
||||
icon: hero/academic-cap
|
||||
- statistic: "95%"
|
||||
description: Graduation rate
|
||||
icon: hero/chart-bar
|
||||
- statistic: "40+"
|
||||
description: Countries represented
|
||||
icon: hero/globe-alt
|
||||
design:
|
||||
layout: compact
|
||||
```
|
||||
|
||||
**Trust Building**: Professional presentation of metrics builds confidence in your offering
|
||||
### Startup Metrics
|
||||
```yaml
|
||||
content:
|
||||
items:
|
||||
- statistic: "$2.5M"
|
||||
description: Funding raised
|
||||
icon: hero/banknotes
|
||||
- statistic: "10K+"
|
||||
description: Active users
|
||||
icon: hero/users
|
||||
- statistic: "98.5%"
|
||||
description: Uptime reliability
|
||||
icon: hero/chart-line
|
||||
design:
|
||||
layout: minimal
|
||||
```
|
||||
|
||||
## 📊 Psychology of Numbers
|
||||
## Animation Features
|
||||
|
||||
- **Cognitive Ease**: Large numbers are processed faster by the human brain
|
||||
- **Authority Signaling**: Impressive statistics position you as an industry leader
|
||||
- **Decision Shortcuts**: Metrics help visitors make quick trust decisions
|
||||
- **Competitive Advantage**: Quantified success differentiates you from competitors
|
||||
### Counter Animation
|
||||
- **Easing**: Smooth ease-out cubic function
|
||||
- **Duration**: 2 seconds for optimal viewing
|
||||
- **Trigger**: Intersection Observer when 50% visible
|
||||
- **Number Extraction**: Automatically extracts numbers from text
|
||||
|
||||
## 💡 Best Practices
|
||||
### Entrance Animation
|
||||
- **Staggered Entry**: Items animate in sequence
|
||||
- **Fade + Slide**: Combines opacity and translateY
|
||||
- **Delay Timing**: 150ms between items
|
||||
- **Threshold**: Triggers 100px before entering viewport
|
||||
|
||||
- Choose your three most impressive and relevant metrics
|
||||
- Use round numbers when possible (1M+ instead of 987,432)
|
||||
- Include context in descriptions ("Happy Customers", "Projects Delivered")
|
||||
- Update regularly to maintain accuracy and freshness
|
||||
- Order statistics by impact or importance
|
||||
### Hover Effects
|
||||
- **Scale Transform**: Icons scale 110% on hover
|
||||
- **Color Transitions**: Text color changes
|
||||
- **Background Glow**: Subtle gradient overlay
|
||||
- **Shadow Enhancement**: Deeper shadows on hover
|
||||
|
||||
## 🎨 Visual Design
|
||||
## Configuration Options
|
||||
|
||||
- **High Contrast**: Bold numbers against clean backgrounds
|
||||
- **Generous Spacing**: Prevents overwhelming busy appearance
|
||||
- **Consistent Sizing**: Uniform treatment creates professional cohesion
|
||||
- **Mobile-First**: Stacks gracefully on smaller screens
|
||||
### Content Fields
|
||||
- **title**: Optional section heading
|
||||
- **text**: Optional description
|
||||
- **items**: Array of statistics
|
||||
- `statistic`: Number or value (required)
|
||||
- `description`: What the number represents (required)
|
||||
- `icon`: Icon identifier (optional)
|
||||
- `sub_metric`: Additional context (optional)
|
||||
|
||||
Perfect for SaaS companies, agencies, consultants, and any business that has measurable achievements worth celebrating. Turn your success metrics into powerful marketing assets that speak louder than words.
|
||||
### Design Options
|
||||
- **layout**: Visual layout
|
||||
- `cards`: Individual cards (default)
|
||||
- `compact`: Single divided card
|
||||
- `minimal`: Clean typography focus
|
||||
|
||||
## Icon Guidelines
|
||||
|
||||
### Recommended Icons for Common Stats
|
||||
|
||||
**Publications/Research:**
|
||||
- `hero/document-text` - Publications
|
||||
- `hero/beaker` - Research projects
|
||||
- `hero/light-bulb` - Discoveries/innovations
|
||||
- `hero/trophy` - Awards/recognition
|
||||
|
||||
**People/Team:**
|
||||
- `hero/user-group` - Team members
|
||||
- `hero/academic-cap` - Students/graduates
|
||||
- `hero/users` - Community/users
|
||||
- `hero/heart` - Satisfaction ratings
|
||||
|
||||
**Financial/Business:**
|
||||
- `hero/currency-dollar` - Funding/revenue
|
||||
- `hero/banknotes` - Investments
|
||||
- `hero/chart-bar` - Growth metrics
|
||||
- `hero/building-office` - Locations
|
||||
|
||||
**Technology/Digital:**
|
||||
- `hero/cpu-chip` - Computing power
|
||||
- `hero/globe-alt` - Global reach
|
||||
- `hero/chart-line` - Performance metrics
|
||||
- `hero/server` - Infrastructure
|
||||
|
||||
## Performance Notes
|
||||
|
||||
### JavaScript Usage
|
||||
Minimal JavaScript for:
|
||||
- Intersection Observer (viewport detection)
|
||||
- Counter animation (number progression)
|
||||
- Modern browser APIs only
|
||||
|
||||
### CSS Animations
|
||||
- Hardware accelerated transforms
|
||||
- Optimized for 60fps
|
||||
- Respects `prefers-reduced-motion`
|
||||
- Fallbacks for older browsers
|
||||
|
||||
### Image Optimization
|
||||
- SVG icons preferred (scalable, fast)
|
||||
- WebP fallbacks for raster images
|
||||
- Lazy loading for non-critical images
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Number Selection**: Choose impactful, verifiable metrics
|
||||
2. **Descriptions**: Keep concise but descriptive
|
||||
3. **Icons**: Match icons to metric meaning
|
||||
4. **Sub-metrics**: Add credibility with specific details
|
||||
5. **Layout**: Choose based on number of items (2-4: cards, 5+: compact)
|
||||
6. **Consistency**: Use similar metric types (all percentages, all counts)
|
||||
7. **Context**: Include timeframes where relevant
|
||||
|
||||
## Advanced Customization
|
||||
|
||||
### Custom Animations
|
||||
Override animation timing:
|
||||
```css
|
||||
.stats-item {
|
||||
transition-duration: 1s;
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Counters
|
||||
For complex number formatting:
|
||||
```yaml
|
||||
statistic: "99.9%" # Will animate 0 → 99
|
||||
statistic: "$2.5M" # Will animate 0 → 2.5
|
||||
statistic: "1st" # Will animate 0 → 1
|
||||
```
|
||||
|
||||
### Accessibility
|
||||
- Numbers are announced by screen readers
|
||||
- Animations respect motion preferences
|
||||
- High contrast maintained
|
||||
- Keyboard navigation supported
|
||||
|
||||
The enhanced Stats block creates visual impact while maintaining professional credibility - essential for research lab websites targeting academic and industry audiences.
|
||||
@@ -7,31 +7,264 @@
|
||||
{{ $block := .wcBlock }}
|
||||
{{ $title := $block.content.title | emojify | $page.RenderString }}
|
||||
{{ $text := $block.content.text | emojify | $page.RenderString }}
|
||||
{{ $layout := $block.design.layout | default "cards" }}
|
||||
|
||||
|
||||
<div class="px-4 py-16 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8 lg:py-20">
|
||||
|
||||
{{ with $title }}
|
||||
<div class="py-8 px-4 mx-auto max-w-screen-xl sm:py-16 lg:px-6">
|
||||
<div class="max-w-screen-md mb-8 lg:mb-16 flex flex-col justify-center mx-auto">
|
||||
<h2 class="mb-4 text-4xl tracking-tight font-extrabold text-gray-900 dark:text-white text-center">{{.}}</h2>
|
||||
<div class="py-12 sm:py-16 lg:py-20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
{{/* Section Header */}}
|
||||
{{ if or $title $text }}
|
||||
<div class="text-center mb-12 lg:mb-16">
|
||||
{{ with $title }}
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white tracking-tight mb-4">
|
||||
{{ . }}
|
||||
</h2>
|
||||
{{ end }}
|
||||
{{ with $text }}
|
||||
<p class="text-gray-500 sm:text-xl dark:text-gray-400 text-center">{{.}}</p>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400 max-w-3xl mx-auto">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<div class="grid grid-cols-3 row-gap-8">
|
||||
{{ range $idx, $item := $block.content.items }}
|
||||
<div class="text-center {{if ne (math.Add $idx 1) (len $block.content.items) }}md:border-r{{end}}">
|
||||
<h6 class="text-4xl font-bold lg:text-5xl xl:text-6xl">
|
||||
{{- .statistic | $page.RenderString -}}
|
||||
</h6>
|
||||
<p class="text-sm font-medium tracking-widest lg:text-base text-gray-800 dark:text-gray-300">
|
||||
{{- .description | $page.RenderString -}}
|
||||
</p>
|
||||
{{/* Cards Layout */}}
|
||||
{{ if eq $layout "cards" }}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-{{ len $block.content.items }} gap-6 lg:gap-8">
|
||||
{{ range $idx, $item := $block.content.items }}
|
||||
<div class="group relative bg-white dark:bg-gray-800 rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-500 overflow-hidden border border-gray-100 dark:border-gray-700 hover:border-primary-200 dark:hover:border-primary-800">
|
||||
|
||||
{{/* Background Gradient */}}
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-primary-50 to-transparent dark:from-primary-900/20 dark:to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||
|
||||
<div class="relative p-8 text-center">
|
||||
{{/* Icon */}}
|
||||
{{ if $item.icon }}
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 mb-6 bg-primary-100 dark:bg-primary-900/50 rounded-xl group-hover:scale-110 transition-transform duration-300">
|
||||
{{ partial "functions/get_icon" (dict "name" $item.icon "attributes" "class=\"w-8 h-8 text-primary-600 dark:text-primary-400\"") }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Statistic */}}
|
||||
<div class="mb-3">
|
||||
<h3 class="text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-black text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors duration-300 counter" data-target="{{ $item.statistic | replaceRE `[^0-9.]` "" }}">
|
||||
{{ $item.statistic | $page.RenderString }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{{/* Description */}}
|
||||
<p class="text-sm sm:text-base font-medium text-gray-600 dark:text-gray-300 leading-relaxed group-hover:text-gray-700 dark:group-hover:text-gray-200 transition-colors duration-300">
|
||||
{{ $item.description | $page.RenderString }}
|
||||
</p>
|
||||
|
||||
{{/* Optional Sub-metric */}}
|
||||
{{ with $item.sub_metric }}
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Compact Layout */}}
|
||||
{{ else if eq $layout "compact" }}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-{{ len $block.content.items }} divide-y sm:divide-y-0 sm:divide-x divide-gray-200 dark:divide-gray-700">
|
||||
{{ range $idx, $item := $block.content.items }}
|
||||
<div class="group p-8 text-center hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors duration-300">
|
||||
{{ if $item.icon }}
|
||||
<div class="inline-flex items-center justify-center w-12 h-12 mb-4 bg-primary-100 dark:bg-primary-900/50 rounded-lg group-hover:scale-110 transition-transform duration-300">
|
||||
{{ partial "functions/get_icon" (dict "name" $item.icon "attributes" "class=\"w-6 h-6 text-primary-600 dark:text-primary-400\"") }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<h3 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white mb-2 counter" data-target="{{ $item.statistic | replaceRE `[^0-9.]` "" }}">
|
||||
{{ $item.statistic | $page.RenderString }}
|
||||
</h3>
|
||||
|
||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||
{{ $item.description | $page.RenderString }}
|
||||
</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Minimal Layout */}}
|
||||
{{ else }}
|
||||
<div class="grid grid-cols-2 lg:grid-cols-{{ len $block.content.items }} gap-8 lg:gap-12">
|
||||
{{ range $item := $block.content.items }}
|
||||
<div class="text-center group">
|
||||
{{ if $item.icon }}
|
||||
<div class="inline-flex items-center justify-center w-14 h-14 mb-4 bg-primary-100 dark:bg-primary-900/50 rounded-full group-hover:scale-110 transition-transform duration-300">
|
||||
{{ partial "functions/get_icon" (dict "name" $item.icon "attributes" "class=\"w-7 h-7 text-primary-600 dark:text-primary-400\"") }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<h3 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white mb-2 counter" data-target="{{ $item.statistic | replaceRE `[^0-9.]` "" }}">
|
||||
{{ $item.statistic | $page.RenderString }}
|
||||
</h3>
|
||||
|
||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-300">
|
||||
{{ $item.description | $page.RenderString }}
|
||||
</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* Enhanced Animation CSS */}}
|
||||
<style>
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 0 0 rgb(59 130 246 / 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 10px rgb(59 130 246 / 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Intersection Observer Animation */
|
||||
.stats-animate {
|
||||
animation: fadeInUp 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.6s ease-out;
|
||||
}
|
||||
|
||||
.stats-item.animate {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Stagger animation delays */
|
||||
.stats-item:nth-child(1) { transition-delay: 0ms; }
|
||||
.stats-item:nth-child(2) { transition-delay: 150ms; }
|
||||
.stats-item:nth-child(3) { transition-delay: 300ms; }
|
||||
.stats-item:nth-child(4) { transition-delay: 450ms; }
|
||||
.stats-item:nth-child(5) { transition-delay: 600ms; }
|
||||
|
||||
/* Number counting animation */
|
||||
@keyframes countUp {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.counter-animate {
|
||||
animation: countUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* Hover glow effect */
|
||||
.stats-card-glow:hover {
|
||||
animation: pulse-glow 2s infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
{{/* Animation JavaScript for Counter and Intersection Observer */}}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
function parseTarget(counter) {
|
||||
const dataTarget = counter.getAttribute('data-target');
|
||||
const fallbackMatch = counter.textContent.match(/[0-9,.]+/);
|
||||
const raw = (dataTarget ?? (fallbackMatch ? fallbackMatch[0] : '0')).replace(/,/g, '');
|
||||
const num = Number(raw);
|
||||
return isNaN(num) ? 0 : num;
|
||||
}
|
||||
|
||||
function extractParts(text) {
|
||||
const parts = text.match(/([^0-9]*)([0-9][0-9,.]*)(.*)/);
|
||||
return {
|
||||
prefix: parts ? parts[1] : '',
|
||||
numberPart: parts ? parts[2] : '',
|
||||
suffix: parts ? parts[3] : ''
|
||||
};
|
||||
}
|
||||
|
||||
function animateCountersFor(container) {
|
||||
container.classList.add('animate');
|
||||
|
||||
const counters = container.querySelectorAll('.counter');
|
||||
counters.forEach(counter => {
|
||||
const target = parseTarget(counter);
|
||||
if (target <= 0) return;
|
||||
|
||||
const { prefix, numberPart, suffix } = extractParts(counter.textContent);
|
||||
const decimals = (numberPart.split('.')[1] || '').length;
|
||||
const formatter = new Intl.NumberFormat(undefined, {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
});
|
||||
|
||||
if (prefersReducedMotion) {
|
||||
counter.textContent = `${prefix}${formatter.format(target)}${suffix}`;
|
||||
return;
|
||||
}
|
||||
|
||||
const duration = 2000; // 2 seconds
|
||||
const start = Date.now();
|
||||
|
||||
function update() {
|
||||
const now = Date.now();
|
||||
const progress = Math.min((now - start) / duration, 1);
|
||||
const easeOut = 1 - Math.pow(1 - progress, 3); // Ease-out cubic
|
||||
const current = target * easeOut;
|
||||
counter.textContent = `${prefix}${formatter.format(current)}${suffix}`;
|
||||
if (progress < 1) requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
requestAnimationFrame(update);
|
||||
});
|
||||
}
|
||||
|
||||
function isInView(el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
const vw = window.innerWidth || document.documentElement.clientWidth;
|
||||
return rect.top < vh * 0.9 && rect.bottom > 0 && rect.left < vw && rect.right > 0;
|
||||
}
|
||||
|
||||
// Intersection Observer for stats animation (with near-fold sensitivity)
|
||||
const statsObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
animateCountersFor(entry.target);
|
||||
statsObserver.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.2,
|
||||
rootMargin: '0px 0px -10% 0px'
|
||||
});
|
||||
|
||||
// Observe all stats containers (and trigger immediately if already visible)
|
||||
const statsContainers = document.querySelectorAll('.stats-item, [class*="grid"] > div');
|
||||
statsContainers.forEach(container => {
|
||||
container.classList.add('stats-item');
|
||||
if (isInView(container)) {
|
||||
animateCountersFor(container);
|
||||
} else {
|
||||
statsObserver.observe(container);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"id": "stats",
|
||||
"name": "Stats",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"category": "content",
|
||||
"tags": ["statistics", "numbers", "metrics", "achievements", "data", "showcase", "grid"],
|
||||
"description": "Showcase impressive statistics and achievements in a clean, impactful grid layout",
|
||||
"tags": ["statistics", "numbers", "metrics", "achievements", "data", "showcase", "grid", "animated", "counters", "icons"],
|
||||
"description": "Showcase impressive statistics with modern animations, icons, and multiple layout options - enhanced for 2025/2026",
|
||||
"author": "Hugo Blox",
|
||||
"homepage": "https://hugoblox.com/blocks/",
|
||||
"repository": "https://github.com/HugoBlox/hugo-blox-builder",
|
||||
|
||||
@@ -38,6 +38,14 @@
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Description of the statistic (supports Markdown)"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Icon identifier (e.g., 'hero/chart-bar', 'hero/user-group')"
|
||||
},
|
||||
"sub_metric": {
|
||||
"type": "string",
|
||||
"description": "Additional context or sub-metric (supports Markdown)"
|
||||
}
|
||||
},
|
||||
"required": ["statistic", "description"]
|
||||
@@ -45,6 +53,18 @@
|
||||
}
|
||||
},
|
||||
"required": ["items"]
|
||||
},
|
||||
"design": {
|
||||
"type": "object",
|
||||
"description": "Design options for the stats block",
|
||||
"properties": {
|
||||
"layout": {
|
||||
"type": "string",
|
||||
"enum": ["cards", "compact", "minimal"],
|
||||
"description": "Display layout style",
|
||||
"default": "cards"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# Team Showcase Block
|
||||
|
||||
Display your team members with a beautiful, responsive grid layout featuring rich profiles, social links, and customizable grouping.
|
||||
|
||||
## Features
|
||||
|
||||
- **Responsive Grid Layout**: Automatically adjusts from 1-4 columns based on screen size
|
||||
- **User Groups**: Organize team members by role (e.g., Principal Investigators, PhD Students)
|
||||
- **Rich Profiles**: Display avatar, name, role, bio, interests, and social links
|
||||
- **Customizable Display**: Toggle visibility of role, organization, interests, and social links
|
||||
- **Sorting**: Sort team members by last name or custom field
|
||||
- **Hover Effects**: Smooth image zoom and shadow effects on hover
|
||||
- **Call to Action**: Optional button to recruitment or team page
|
||||
|
||||
## Usage
|
||||
|
||||
```yaml
|
||||
sections:
|
||||
- block: team-showcase
|
||||
content:
|
||||
title: Meet Our Team
|
||||
subtitle: World-class researchers advancing science
|
||||
text: Our diverse team brings together expertise from multiple disciplines.
|
||||
user_groups:
|
||||
- Principal Investigators
|
||||
- Postdoctoral Researchers
|
||||
- PhD Students
|
||||
sort_by: 'Params.last_name'
|
||||
sort_ascending: true
|
||||
cta:
|
||||
text: Join Our Team
|
||||
url: /opportunities
|
||||
icon: user-plus
|
||||
design:
|
||||
show_role: true
|
||||
show_organizations: false
|
||||
show_interests: true
|
||||
show_social: true
|
||||
# Section background color (CSS class)
|
||||
css_class: "bg-gray-50 dark:bg-gray-900"
|
||||
```
|
||||
|
||||
## Content Structure
|
||||
|
||||
Team members should be created in the `content/authors/` directory. Each team member needs:
|
||||
|
||||
```yaml
|
||||
# content/authors/jane-doe/_index.md
|
||||
---
|
||||
title: Dr. Jane Doe
|
||||
role: Postdoctoral Researcher
|
||||
bio: Research interests include machine learning and computational biology.
|
||||
interests:
|
||||
- Machine Learning
|
||||
- Computational Biology
|
||||
- Data Science
|
||||
organizations:
|
||||
- name: University of Excellence
|
||||
url: https://example.edu
|
||||
social:
|
||||
- icon: envelope
|
||||
icon_pack: fas
|
||||
link: 'mailto:jane@example.edu'
|
||||
- icon: twitter
|
||||
icon_pack: fab
|
||||
link: https://twitter.com/janedoe
|
||||
- icon: github
|
||||
icon_pack: fab
|
||||
link: https://github.com/janedoe
|
||||
user_groups:
|
||||
- Postdoctoral Researchers
|
||||
---
|
||||
|
||||
Full biography content here...
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### Content Options
|
||||
|
||||
- **title**: Main heading for the team section
|
||||
- **subtitle**: Optional subtitle
|
||||
- **text**: Optional description text (supports Markdown)
|
||||
- **user_groups**: Array of group names to display
|
||||
- **sort_by**: Field to sort by (default: "Params.last_name")
|
||||
- **sort_ascending**: Sort direction (default: true)
|
||||
- **cta**: Optional call-to-action button with text, url, and icon
|
||||
|
||||
### Design Options
|
||||
|
||||
- **show_role**: Display team member roles (default: true)
|
||||
- **show_organizations**: Display affiliated organizations (default: false)
|
||||
- **show_interests**: Display research interests (default: false)
|
||||
- **show_social**: Display social media links (default: true)
|
||||
|
||||
## Styling
|
||||
|
||||
The block uses Tailwind CSS classes and supports:
|
||||
- Dark mode
|
||||
- Custom background colors via `css_class`
|
||||
- Responsive breakpoints
|
||||
- Smooth hover transitions
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Optimize Images**: Use square avatar images (minimum 400x400px) for best results
|
||||
2. **User Groups**: Create logical groupings that reflect your organization structure
|
||||
3. **Bio Length**: Keep bios concise (2-3 lines) for the card view
|
||||
4. **Social Links**: Include relevant professional networks (ORCID, Google Scholar, GitHub)
|
||||
@@ -0,0 +1,177 @@
|
||||
{{/* Hugo Blox: Team Showcase */}}
|
||||
{{/* Documentation: https://hugoblox.com/blocks/ */}}
|
||||
{{/* License: https://github.com/HugoBlox/hugo-blox-builder/blob/main/LICENSE.md */}}
|
||||
|
||||
{{/* Initialise */}}
|
||||
{{ $page := .wcPage }}
|
||||
{{ $block := .wcBlock }}
|
||||
{{ $show_social := $block.design.show_social | default true }}
|
||||
{{ $show_interests := $block.design.show_interests | default false }}
|
||||
{{ $show_role := $block.design.show_role | default true }}
|
||||
{{ $show_organizations := $block.design.show_organizations | default false }}
|
||||
|
||||
{{ $title := $block.content.title | emojify | $page.RenderString }}
|
||||
{{ $subtitle := $block.content.subtitle | emojify | $page.RenderString }}
|
||||
{{ $text := $block.content.text | emojify | $page.RenderString }}
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
{{/* Section Header */}}
|
||||
<div class="flex flex-col items-center text-center mb-12">
|
||||
{{ with $title }}
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white tracking-tight">
|
||||
{{ . }}
|
||||
</h2>
|
||||
{{ end }}
|
||||
|
||||
{{ with $subtitle }}
|
||||
<p class="mt-4 text-xl text-gray-600 dark:text-gray-400">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
|
||||
{{ with $text }}
|
||||
<div class="mt-6 prose prose-lg lg:prose-xl dark:prose-invert max-w-3xl">
|
||||
{{ . }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Team Grid */}}
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
|
||||
{{ range $block.content.user_groups }}
|
||||
{{ $group_name := . }}
|
||||
{{ $query := where (where site.Pages "Section" "authors") ".Params.user_groups" "intersect" (slice .) }}
|
||||
|
||||
{{/* Sort */}}
|
||||
{{ $sort_by := $block.content.sort_by | default "Params.last_name" }}
|
||||
{{ $sort_by = partial "functions/get_sort_by_parameter" $sort_by }}
|
||||
{{ $sort_ascending := $block.content.sort_ascending | default true }}
|
||||
{{ $sort_order := cond $sort_ascending "asc" "desc" }}
|
||||
{{ $query = sort $query $sort_by $sort_order }}
|
||||
|
||||
{{/* Group Header (if multiple groups) */}}
|
||||
{{ if and $query (gt (len $block.content.user_groups) 1) }}
|
||||
<div class="col-span-full">
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-6 pb-2 border-b border-gray-200 dark:border-gray-700">
|
||||
{{ $group_name | markdownify }}
|
||||
</h3>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Team Members */}}
|
||||
{{ range $person := $query }}
|
||||
{{ $avatar := (.Resources.ByType "image").GetMatch "*avatar*" }}
|
||||
{{ $link := "" }}
|
||||
{{ with site.GetPage (printf "/authors/%s" (path.Base .File.Dir)) }}
|
||||
{{ $link = .RelPermalink }}
|
||||
{{ end }}
|
||||
|
||||
<div class="group relative">
|
||||
<div class="relative overflow-hidden rounded-2xl bg-white dark:bg-gray-800 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||
|
||||
{{/* Avatar */}}
|
||||
{{ if $avatar }}
|
||||
<a href="{{ $link }}" class="block relative h-64 overflow-hidden">
|
||||
{{ $image := $avatar.Fill "400x400 Center webp" }}
|
||||
<img src="{{ $image.RelPermalink }}"
|
||||
alt="{{ .Title }}"
|
||||
width="{{ $image.Width }}"
|
||||
height="{{ $image.Height }}"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110">
|
||||
|
||||
{{/* Gradient Overlay */}}
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
{{/* Content */}}
|
||||
<div class="p-6">
|
||||
{{/* Name */}}
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-1">
|
||||
<a href="{{ $link }}" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors">
|
||||
{{ .Title }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
{{/* Role & Organization */}}
|
||||
{{ if $show_role }}
|
||||
<div class="text-gray-600 dark:text-gray-400 mb-3">
|
||||
{{ with .Params.role }}
|
||||
<p class="font-medium">{{ . }}</p>
|
||||
{{ end }}
|
||||
|
||||
{{ if and $show_organizations .Params.organizations }}
|
||||
{{ range first 1 .Params.organizations }}
|
||||
<p class="text-sm mt-1">{{ .name }}</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Bio */}}
|
||||
{{ with .Params.bio }}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
|
||||
{{/* Interests */}}
|
||||
{{ if and $show_interests .Params.interests }}
|
||||
<div class="mb-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{{ range first 3 .Params.interests }}
|
||||
<span class="inline-block bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">
|
||||
{{ . }}
|
||||
</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Social Links */}}
|
||||
{{ if $show_social }}
|
||||
<div class="flex gap-3 pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||
{{ range .Params.social }}
|
||||
{{ $link := .url | default .link }}
|
||||
{{ $scheme := (urls.Parse $link).Scheme }}
|
||||
{{ $target := "" }}
|
||||
{{ if not $scheme }}
|
||||
{{ $link = (.url | default .link) | relLangURL }}
|
||||
{{ if eq (path.Ext $link) ".pdf" }}{{ $target = "target=\"_blank\" rel=\"noopener\"" }}{{ end }}
|
||||
{{ else if in (slice "http" "https") $scheme }}
|
||||
{{ $target = "target=\"_blank\" rel=\"noopener\"" }}
|
||||
{{ end }}
|
||||
|
||||
<a href="{{ $link | safeURL }}"
|
||||
{{ $target | safeHTMLAttr }}
|
||||
aria-label="{{ .icon }}"
|
||||
{{ with .label }} title="{{ . }}"{{ end }}
|
||||
class="text-gray-500 hover:text-primary-600 dark:text-gray-400 dark:hover:text-primary-400 transition-colors">
|
||||
{{ partial "functions/get_icon" (dict "name" .icon "attributes" "class=\"w-5 h-5\"") }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Call to Action */}}
|
||||
{{ with $block.content.cta }}
|
||||
<div class="mt-12 text-center">
|
||||
<a href="{{ .url }}" class="inline-flex items-center px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transition-all duration-300">
|
||||
{{ .text }}
|
||||
{{ with .icon }}
|
||||
{{ partial "functions/get_icon" (dict "name" . "attributes" "class=\"ml-2 w-5 h-5\"") }}
|
||||
{{ end }}
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "team-showcase",
|
||||
"name": "Team Showcase",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"category": "content",
|
||||
"tags": ["team", "people", "members", "staff", "lab", "research", "organization"],
|
||||
"description": "Display team members with customizable grouping, filtering, and rich profiles",
|
||||
"author": "Hugo Blox",
|
||||
"homepage": "https://hugoblox.com/blocks/",
|
||||
"repository": "https://github.com/HugoBlox/hugo-blox-builder",
|
||||
"keywords": ["hugo", "static-site", "team", "people", "organization", "research-lab"]
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://hugoblox.com/schemas/blocks/team-showcase.json",
|
||||
"title": "Team Showcase Block Schema",
|
||||
"description": "Schema for the Team Showcase block - displays team members with filtering and grouping",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "../shared/schemas/base-block.json"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"block": {
|
||||
"const": "team-showcase"
|
||||
},
|
||||
"content": {
|
||||
"type": "object",
|
||||
"description": "Content configuration for the team showcase block",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Section title (supports Markdown)"
|
||||
},
|
||||
"subtitle": {
|
||||
"type": "string",
|
||||
"description": "Section subtitle (supports Markdown)"
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Section description (supports Markdown)"
|
||||
},
|
||||
"user_groups": {
|
||||
"type": "array",
|
||||
"description": "User groups to display (e.g., 'Principal Investigators', 'PhD Students')",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"sort_by": {
|
||||
"type": "string",
|
||||
"description": "Field to sort team members by",
|
||||
"default": "Params.last_name"
|
||||
},
|
||||
"sort_ascending": {
|
||||
"type": "boolean",
|
||||
"description": "Sort in ascending order",
|
||||
"default": true
|
||||
},
|
||||
"cta": {
|
||||
"type": "object",
|
||||
"description": "Call to action button",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Button text"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Button URL"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Optional icon name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"design": {
|
||||
"type": "object",
|
||||
"description": "Design options for the team showcase",
|
||||
"properties": {
|
||||
"show_role": {
|
||||
"type": "boolean",
|
||||
"description": "Show team member roles",
|
||||
"default": true
|
||||
},
|
||||
"show_organizations": {
|
||||
"type": "boolean",
|
||||
"description": "Show team member organizations",
|
||||
"default": false
|
||||
},
|
||||
"show_interests": {
|
||||
"type": "boolean",
|
||||
"description": "Show team member interests",
|
||||
"default": false
|
||||
},
|
||||
"show_social": {
|
||||
"type": "boolean",
|
||||
"description": "Show social media links",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user