add portfolio section; implement carousel for project showcase and improve layout

This commit is contained in:
Michael Czechowski
2025-06-14 13:46:07 +02:00
parent c6c1ad26de
commit 917bf40e6d
6 changed files with 88 additions and 33 deletions

30
.pugrc
View File

@@ -82,6 +82,36 @@
"href": "https://www.stollvongati.com/" "href": "https://www.stollvongati.com/"
} }
], ],
"portfolio": [
{
"type": "image",
"src": "./src/assets/screenshot-codecrispies.png",
"alt": "",
"href": "https://codecrispi.es",
"caption": "Code Crispi.es"
},
{
"type": "image",
"src": "./src/assets/screenshot-codecrispies.png",
"alt": "",
"href": "https://codecrispi.es",
"caption": "Code Crispi.es"
},
{
"type": "image",
"src": "./src/assets/screenshot-codecrispies.png",
"alt": "",
"href": "https://codecrispi.es",
"caption": "Code Crispi.es"
},
{
"type": "image",
"src": "./src/assets/screenshot-codecrispies.png",
"alt": "",
"href": "https://codecrispi.es",
"caption": "Code Crispi.es"
}
],
"intro": { "intro": {
"heading": "After $1" "heading": "After $1"
}, },

View File

@@ -8,6 +8,7 @@ html.scroll-smooth(lang=head.lang)
include src/components/Landingpage include src/components/Landingpage
include src/components/Brands include src/components/Brands
include src/components/Academia include src/components/Academia
include src/components/Portfolio
include src/components/Professional include src/components/Professional
include src/components/Footer include src/components/Footer

View File

@@ -1,7 +1,11 @@
mixin Carousel(items, options) mixin Carousel(items, options)
// Set options with fallbacks for defaults
- const id = options.id || "carousel"; - const id = options.id || "carousel";
- const color = options.color || "white"; - const color = options.color || "white";
- const category = options.category || "portfolio"; - const category = options.category || "portfolio";
- const slideClasses = options.slideClasses || "sm:w-1/2 lg:w-1/3 xl:w-1/4";
// Pass through options even if not used
- const autoScroll = options.autoScroll ?? 4_800;
.carousel-container.relative.w-full.overflow-hidden(id=id) .carousel-container.relative.w-full.overflow-hidden(id=id)
//- Navigation buttons //- Navigation buttons
@@ -22,21 +26,21 @@ mixin Carousel(items, options)
path(stroke-linecap="round", stroke-linejoin="round", stroke-width="2", d="M9 5l7 7-7 7") path(stroke-linecap="round", stroke-linejoin="round", stroke-width="2", d="M9 5l7 7-7 7")
//- Carousel track //- Carousel track
.carousel-track.flex.overflow-x-auto.scroll-smooth.scrollbar-hide(style="scroll-snap-type: x mandatory", role="region", aria-label="Image carousel") .carousel-track.flex.overflow-x-auto.rounded-lg.scroll-smooth.scrollbar-hide(style="scroll-snap-type: x mandatory", role="region", aria-label="Image carousel")
each item, index in items each item, index in items
- const href = item?.href || "#"; - const href = item?.href || "#";
.carousel-slide.flex-none.w-full.relative(class="sm:w-1/2 lg:w-1/3 xl:w-1/4", style="scroll-snap-align: start") .carousel-slide.flex-none.w-full.relative(class=slideClasses, style="scroll-snap-align: start")
if item.type === "image" if item.type === "image"
.aspect-video.relative.overflow-hidden.rounded-lg.mx-2.mb-4 .flex.flex-col.aspect-video.relative.overflow-hidden.rounded-lg.mx-2.mb-4(class="sm:flex-row sm:gap-6")
img.w-full.h-full.object-cover.transition-transform.duration-300(class="hover:scale-105", src=item.src, alt=item.alt, loading=index < 3 ? "eager" : "lazy") img.w-full.h-full.object-cover.transition-transform.rounded-lg.duration-300(class="hover:scale-105 sm:w-1/2", src=item.src, alt=item.alt, loading=index < 3 ? "eager" : "lazy")
if item.caption if item.caption
.absolute.bottom-0.left-0.right-0.bg-gradient-to-t.p-4(class="from-black/70") .absolute.bottom-0.left-0.right-0.bg-gradient-to-t.p-4(class="sm:static sm:bg-gradient-unset from-black/70 sm:text-nls-white dark:sm:text-nls-black")
p.text-white.text-sm.font-medium= item.caption p.text-white.text-xl.font-large= item.caption
else if item.type === "brand" else if item.type === "brand"
.aspect-square.flex.items-center.justify-center.mx-2.mb-4 .aspect-square.flex.items-center.justify-center.mx-2.mb-4
img.max-w-full.max-h-full.object-contain.p-4(src=item.logo, alt=item.name, loading=index < 6 ? "eager" : "lazy") img.max-w-full.max-h-full.object-contain.p-4(src=item.logo, alt=item.name, loading=index < 6 ? "eager" : "lazy")
if autoScroll
script. script.
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
setInterval(() => { setInterval(() => {
@@ -46,5 +50,5 @@ mixin Carousel(items, options)
} else { } else {
carouselNext("#{id}"); carouselNext("#{id}");
} }
}, 4700); }, #{autoScroll});
}); });

View File

@@ -164,30 +164,30 @@ head
display: none; display: none;
} }
/* Responsive breakpoints - fewer items on smaller screens */ /*!* Responsive breakpoints - fewer items on smaller screens *!*/
@media (max-width: 640px) { /*@media (max-width: 640px) {*/
.carousel-slide { /* .carousel-slide {*/
width: 100%; /* width: 100%;*/
} /* }*/
} /*}*/
@media (min-width: 641px) and (max-width: 1024px) { /*@media (min-width: 641px) and (max-width: 1024px) {*/
.carousel-slide { /* .carousel-slide {*/
width: 33.333333%; /* width: 33.333333%;*/
} /* }*/
} /*}*/
@media (min-width: 1025px) and (max-width: 1280px) { /*@media (min-width: 1025px) and (max-width: 1280px) {*/
.carousel-slide { /* .carousel-slide {*/
width: 25%; /* width: 25%;*/
} /* }*/
} /*}*/
@media (min-width: 1281px) { /*@media (min-width: 1281px) {*/
.carousel-slide { /* .carousel-slide {*/
width: 20%; /* width: 20%;*/
} /* }*/
} /*}*/
/* Smooth button hover states */ /* Smooth button hover states */
.carousel-btn { .carousel-btn {

View File

@@ -0,0 +1,18 @@
include Carousel
include Container
section#portfolio
.p-8.bg-nls-black.text-nls-white(class="sm:py-2")
+Container
+Title("h2", true)
| Portfolio
p.text-center.pb-12
| Free and Open Source Software, Customer Projects and other useful Applications
.max-w-screen.mx-auto(class="sm:max-w-80vw")
+Carousel([...portfolio, ...portfolio, ...portfolio], {
id: "portfolio-carousel",
color: "black",
category: "portfolio",
slideClasses: "w-full",
autoScroll: false
})

View File

@@ -1,11 +1,13 @@
mixin Title(tag) mixin Title(tag, isCentered)
- const width = isCentered ? "w-full text-center" : "w-3/4";
case tag case tag
when "h2" when "h2"
h2.font-bold.font-serif.text-xl.mb-2(class="sm:text-2xl w-3/4") h2.font-bold.font-serif.text-xl.mb-2(class=`sm:text-2xl ${width}`)
block block
when "h3" when "h3"
h3.font-bold.font-serif.text-xl.mb-4(class="sm:text-2xl w-3/4") h3.font-bold.font-serif.text-xl.mb-4(class=`sm:text-2xl ${width}`)
block block
default default
h1.font-bold.font-serif.text-xl.mb-4(class="sm:text-2xl w-3/4") h1.font-bold.font-serif.text-xl.mb-4(class=`sm:text-2xl ${width}`)
block block