draft: true
hidden: true
---
-
-{{<section>}}
- {{<media-block
- title="Lead by a **dedicated community**"
- media="/img/flatline/team-work.svg"
- mediaMobilePosition="bottom"
- alt="Illustration of three people carrying a large pie chart with one segment separated, symbolizing teamwork and data analysis."
- class="block-space"
- >}}
- The Xen community is a dynamic and collaborative ecosystem comprised of developers, researchers, and enthusiasts dedicated to advancing open-source virtualization technology.
-With diverse backgrounds and expertise, members actively contribute to Xen's evolution, fostering innovation, sharing knowledge, and supporting one another through continuous development efforts.
-
- <p class="mg-t-md">
- <a href="/contribute/get-started" class="btn btn-primary">Get started <i class="fas fa-arrow-right"></i></a>
- </p>
- {{</media-block>}}
-{{</section>}}
-
-{{<section>}}
-
- {{<media-block
- title="Lead by a **dedicated community**"
- media="/img/flatline/team-work.svg"
- mediaPosition="right"
- mediaMobilePosition="bottom"
- alt="Illustration of three people carrying a large pie chart with one segment separated, symbolizing teamwork and data analysis."
- class="block-space"
- >}}
- The Xen community is a dynamic and collaborative ecosystem comprised of developers, researchers, and enthusiasts dedicated to advancing open-source virtualization technology.
-With diverse backgrounds and expertise, members actively contribute to Xen's evolution, fostering innovation, sharing knowledge, and supporting one another through continuous development efforts.
-
- <p class="mg-t-md">
- <a href="/contribute/get-started" class="btn btn-primary">Get started <i class="fas fa-arrow-right"></i></a>
- </p>
- {{</media-block>}}
-{{</section>}}
-
-
-{{<section>}}
- {{<media-block
- title="Why Xen Project?"
- media=`{{<youtube id="uuBhqwbaObE" title="Xen Project's Progress Toward Safety Certification" >}}`
- animate="true"
-
- >}}
-
-The Xen Project Hypervisor is uniquely placed to support a new range of use cases, building on top of 14 years of usage within the data center. In particular, its isolation and security features, flexible virtualization mode and architecture, driver disaggregation, and ARM support (only 47K lines of code) make it a perfect fit for embedded applications.
-
-{{</media-block>}}
-{{</section>}}
-
-{{<section>}}
- {{<media-block
- title="Basic concepts"
- media="https://www.slideshare.net/slideshow/embed_code/key/hzJl1EbWmxfFUN"
- alt="Slide for Unikraft's basic concepts on slideshare.net"
- animate="true"
- >}}
-The high-level goal of Unikraft is to be able to build unikernels targeted at specific applications without requiring the time-consuming, expert work that building such a unikernel requires today. An additional goal (or hope) of Unikraft is that all developers interested in unikernel development would contribute by supplying libraries rather than working on independent projects with different code bases as it is done now.
-{{</media-block>}}
-{{</section>}}
-
-{{<section>}}
- {{<media-block
- title="What is HVMI?"
- media="https://xenproject.org/wp-content/uploads/sites/79/2020/07/github-hvmi-v2_Kek0TiK6.compressed.mp4"
- alt="Video of a presentation about HVMI"
- animate="true"
- >}}
-HVMI stands for Hypervisor-based Memory Introspection. The technology leverages Virtual Machine Introspection (VMI) APIs in the Xen and KVM hypervisors. By gaining introspection of the raw memory of running guest virtual machines, HVMI can apply security logic to detect and prevent the use of common attack techniques, such as buffer overflows, heap spray, code injection, and so-on.
-{{</media-block>}}
-{{</section>}}
+{{<section container="full">}}
+{{<carousel class="mg-t-lg carousel-container-width">}}
+{{<getpages "projects" "hidden">}}
+{{</carousel>}}
+{{</section>}}
\ No newline at end of file
"carousel-container",
"carousel-container-width",
"carousel-content",
+ "carousel-content-inner",
"carousel-item",
"col",
"color-txt-base",
"container",
"container-full",
"content-markdown",
- "date",
"description",
"download-search",
"fa",
@use "sass:math";
.carousel-container {
- --min-height: 100px;
--content-padding: 12px;
- --item-width: auto;
- --item-position: 0;
--max-width: none;
- --number-of-items: 1;
- --space-between-items: 40px;
- --height: auto;
- padding-bottom: 40px;
+ --gap: 40px;
+ --cols: 1;
+ --items-before: 1;
+ --items-after: 1;
+ --total-cols: calc(var(--cols) + var(--items-before) + var(--items-after));
+ --item-width: calc((100% - (var(--total-cols) - 1) * var(--gap)) / var(--total-cols));
+ --mask-size: var(--item-width);
+ --negative-margin-multiplier: 3;
+ --carousel-negative-margin: calc((var(--item-width) + var(--gap)) * -1 * var(--negative-margin-multiplier));
+ --color-background: var(--color-surface-secondary);
overflow: hidden;
@include phone {
- --number-of-items: 2;
+ //--cols: 2;
}
@include tablet {
- --number-of-items: 2;
+ --cols: 2;
--max-width: calc(var(--container-width) + var(--content-padding) * 2);
--content-padding: 80px;
+ --negative-margin-multiplier: 1.66;
}
@include tablet-up {
- --number-of-items: 3;
+ --cols: 3;
}
@include desktop {
- --number-of-items: 3;
+ --cols: 3;
}
.carousel-content {
margin: 0 auto;
}
+ .carousel-item--clone {
+ visibility: hidden;
+ pointer-events: none;
+ }
+
+ .carousel-item {
+ scroll-snap-align: start;
+
+ &--hidden {
+ pointer-events: none;
+ }
+ }
+
+ .carousel-content {
+ width: 100%;
+ position: relative;
+ }
+
+ .carousel-content-inner {
+ --opacity: 0.2;
+ --mask-image: linear-gradient(
+ to right,
+ transparent,
+ rgba(0, 0, 0, var(--opacity)) calc(var(--mask-size) - 5%),
+ black calc(var(--mask-size) + 5%),
+ black calc(100% - var(--mask-size) - 5%),
+ rgba(0, 0, 0, var(--opacity)) calc(100% - var(--mask-size) + 5%),
+ transparent
+ );
+ mask-image: var(--mask-image);
+ -webkit-mask-image: var(--mask-image);
+ margin-left: var(--carousel-negative-margin);
+ margin-right: var(--carousel-negative-margin);
+ overflow: hidden;
+ }
+
.carousel {
display: flex;
flex-wrap: wrap;
- gap: var(--space-between-items);
- align-items: stretch;
- position: relative;
- height: var(--height);
+ gap: var(--gap);
+ overflow-x: scroll;
+ scroll-snap-type: x mandatory;
+ scrollbar-width: none;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ html.has-js & {
+ flex-wrap: nowrap;
+ }
&-item {
- --index: 0;
- --transform: calc(var(--item-position) * (var(--index)));
- flex: 1 0 calc(100% / var(--number-of-items) - var(--space-between-items));
- top: 0;
- min-height: var(--min-height);
- left: 0;
width: var(--item-width);
+ min-width: var(--item-width);
+ max-width: var(--item-width);
+ flex: 1 1 var(--item-width);
}
}
font-size: 1.25rem;
}
}
-
-.carousel-initialized .carousel-item {
- position: absolute;
- transform: translateX(var(--transform));
- opacity: 0;
- display: none;
- transition:
- transform 0.5s ease,
- opacity 0.8s ease;
- height: 100%;
-}
&:has(video) {
--media-h-padding: 0 !important;
}
- // &::before {
- // content: "";
- // position: absolute;
- // top: 0;
- // left: 50%;
- // width: 1px;
- // height: 100%;
- // background-color: red;
- // transform: translateX(-50%);
- // }
&.image-small {
.media-block__media {
(() => {
- const /* The `selector` constant is storing a CSS selector string that is used to select elements in
- the DOM. In this code snippet, the `selector` constant is set to `".carousel-container"`,
- which means it is targeting elements with the class name "carousel-container". This selector
- is then used to find and initialize carousel functionality on those elements in the
- document. */
- selector = ".carousel-container";
+ document.documentElement.classList.add("has-js");
+ const selector = ".carousel-container";
+ const itemsContainerSelector = ".carousel";
const itemSelector = ".carousel-item";
- const itemsBefore = 2;
- const itemsAfter = 1;
const { debounce } = window.XenSiteUtils;
const carousel = async (element) => {
- const uniqueid = Math.random().toString(36).substring(2, 15);
- element.classList.add("carousel-container-" + uniqueid);
- const infiniteLoop = true;
- const itemsBefore = 2;
- const itemsAfter = 2;
-
- const carouselElement = element.querySelector(".carousel");
- let prev = element.querySelector(".prev");
- let next = element.querySelector(".next");
- let items = await waitForElements(carouselElement, itemSelector);
-
- // add attributes to first and last items
- items[0].dataset.first = true;
- items[items.length - 1].dataset.last = true;
-
- const carouselClone = carouselElement.cloneNode(true);
- carouselClone.classList.add("carousel-clone");
- carouselClone.style.setProperty("position", "absolute");
- carouselClone.style.setProperty("left", "0");
- carouselClone.style.setProperty("top", "-100px");
- carouselClone.style.setProperty("width", "100%");
- carouselClone.style.setProperty("overflow", "hidden");
- carouselClone.style.setProperty("flex-wrap", "wrap");
- carouselClone.style.setProperty("pointer-events", "none");
- carouselClone.style.setProperty("visibility", "hidden");
- carouselClone.style.setProperty("height", "100");
-
- const getItemInformations = () => {
- carouselElement.before(carouselClone);
- carouselClone.style.setProperty("width", carouselElement.offsetWidth + "px");
- const items = carouselClone.querySelectorAll(itemSelector);
- if (!items.length) return 0;
- const item2 = items[1];
- let occupiedSpace = item2.offsetLeft;
-
- if (occupiedSpace === 0 && item2.offsetTop > 0) occupiedSpace = item2.offsetWidth + 40;
- const width = item2.offsetWidth;
- const height = [...items].reduce((acc, item) => (acc < item.offsetHeight ? item.offsetHeight : acc), 0);
- carouselClone.remove();
-
- return {
- occupiedSpace,
- width,
- height,
- };
- };
-
- // element.classList.add("carousel-start-init");
-
- // add the clone the last item to the first place and for the first item to the last place
- // generic function to clone N last items to the first place and N first items to the last place
-
- const cloneItems = (items, clones) => {
- for (let i = 0; i <= clones; i++) {
- carouselElement.appendChild(items[i].cloneNode(true));
- }
- };
- cloneItems(items, itemsBefore);
-
- const cloneItemsReverse = (items, clones) => {
- for (let i = 0; i <= clones; i++) {
- carouselElement.prepend(items[items.length - 1 - i].cloneNode(true));
- }
- };
-
- cloneItemsReverse(items, itemsAfter);
-
- const moveNext = function () {
- if (
- !infiniteLoop &&
- element.querySelectorAll(itemSelector)[element.querySelectorAll(itemSelector).length - 1].dataset.last
- )
- return;
- let items = element.querySelectorAll(itemSelector);
- carouselElement.appendChild(items[0]);
- };
- next.addEventListener("click", moveNext);
-
- const movePrev = function () {
- let items = element.querySelectorAll(itemSelector);
- const item = items[itemsBefore];
- if (!infiniteLoop && item.dataset.first) return;
- carouselElement.prepend(items[items.length - 1]);
- };
- prev.addEventListener("click", movePrev);
-
- // mobile
- let startX, moveX;
-
- carouselElement.addEventListener("touchstart", (e) => {
- startX = e.touches[0].clientX;
+ const itemsContainer = element.querySelector(itemsContainerSelector);
+ const items = element.querySelectorAll(itemSelector);
+
+ const firstItem = items[0].cloneNode(true);
+ firstItem.innerHTML = "";
+ firstItem.classList.add("carousel-item--clone");
+ itemsContainer.prepend(firstItem);
+ const lastItem = firstItem.cloneNode(true);
+ itemsContainer.append(lastItem);
+
+ element.querySelector(".carousel-button.prev").addEventListener("click", () => {
+ itemsContainer.scrollBy({
+ left: -firstItem.clientWidth,
+ behavior: "smooth",
+ });
});
- carouselElement.addEventListener("touchend", (e) => {
- moveX = e.changedTouches[0].clientX - startX;
- if (Math.abs(moveX) > 50) {
- if (moveX > 0) {
- movePrev();
- } else {
- moveNext();
- }
- }
+ element.querySelector(".carousel-button.next").addEventListener("click", () => {
+ itemsContainer.scrollBy({
+ left: firstItem.clientWidth,
+ behavior: "smooth",
+ });
});
+ };
- /** The rules are generated from the element width and container width
- * The rules are :
- * - nth-child(1) is opacity:0 and index -2
- * - nth-child(2) is opacity:0 and index -1
- * - nth-child(3) is opacity:1 and index 0
- * - nth-child(4) is opacity:1 and index 1
- * - ...
- * - nth-child(maxItems) is opacity:1 and index maxItems - 1
- *
- */
- let styleTag;
- let lastWindowWidth = -1;
- const generateStyles = (element) => {
- const windowWidth = window.innerWidth;
- if (windowWidth === lastWindowWidth) return;
- lastWindowWidth = windowWidth;
- const rules = [];
-
- // add carousel styles generated from the element width
- if (!styleTag) {
- styleTag = document.createElement("style");
- document.head.appendChild(styleTag);
- }
-
- const { occupiedSpace, width: itemWidth, height } = getItemInformations();
-
- if (occupiedSpace < 100) {
- console.error("Error in the carousel, no item width detected");
- return;
- }
+ [...document.querySelectorAll(selector)].forEach((elm) => {
+ carousel(elm);
+ });
- rules.push(`
- .carousel-container-${uniqueid} {
- --item-width: ${itemWidth}px;
- --item-position: ${occupiedSpace}px;
- --height: ${height}px;
- }
- `);
+ function updateCarouselTabIndexes() {
+ const items = document.querySelectorAll(".carousel-item");
- const carouselWidth = carouselElement.offsetWidth;
- const maxItems = Math.floor(carouselWidth / occupiedSpace) + 1 + itemsBefore + itemsAfter;
+ items.forEach((item) => {
+ const rect = item.getBoundingClientRect();
+ const isVisible = rect.left >= 0 && rect.right <= window.innerWidth;
- let opacity = 0;
- for (let i = 1; i <= maxItems; i++) {
- if ((i >= itemsBefore && i < maxItems - itemsAfter) || i - itemsBefore === 0) {
- opacity = 1;
- } else if (i === maxItems - itemsAfter || i === itemsBefore - 1) {
- opacity = 0.2;
- } else {
- opacity = 0;
- }
+ item.classList.toggle("carousel-item--hidden", !isVisible);
+ const links = item.querySelectorAll("a");
- const index = i - itemsBefore;
- rules.push(`
- .carousel-item:nth-child(${i}) {
- --index: ${index};
- opacity: ${opacity};
- display: flex;
+ links.forEach((link) => {
+ if (link.getAttribute("aria-hidden") !== "true") {
+ if (isVisible) {
+ link.removeAttribute("tabindex");
+ } else {
+ link.setAttribute("tabindex", "-1");
}
- `);
- }
-
- styleTag.innerHTML = rules.join("\n");
- };
-
- window.addEventListener(
- "resize",
- debounce(() => {
- generateStyles(element);
- }, 50),
- );
- generateStyles(element);
-
- carouselElement.classList.add("carousel-initialized");
- };
-
- /**
- * Waits for elements matching the selector to be present in the DOM within the given element.
- * @param {HTMLElement} element - The parent element to observe for the selector.
- * @param {string} selector - The CSS selector to match the elements.
- * @returns {Promise<NodeListOf<Element>>} A promise that resolves with the matched elements.
- */
- const waitForElements = (element, selector) => {
- return new Promise((resolve) => {
- const items = element.querySelectorAll(selector);
- if (items.length) {
- resolve(items);
- return;
- }
-
- const observer = new MutationObserver((mutations) => {
- const items = element.querySelectorAll(selector);
- if (items.length) {
- observer.disconnect();
- resolve(items);
}
});
-
- observer.observe(element, {
- childList: true,
- subtree: true,
- });
});
- };
+ }
- [...document.querySelectorAll(selector)].forEach((elm) => {
- carousel(elm);
- });
+ document.querySelector(".carousel").addEventListener("scroll", updateCarouselTabIndexes);
+ updateCarouselTabIndexes();
})();
--- /dev/null
+(() => {
+ const /* The `selector` constant is storing a CSS selector string that is used to select elements in
+ the DOM. In this code snippet, the `selector` constant is set to `".carousel-container"`,
+ which means it is targeting elements with the class name "carousel-container". This selector
+ is then used to find and initialize carousel functionality on those elements in the
+ document. */
+ selector = ".carousel-container";
+ const itemSelector = ".carousel-item";
+ const itemsBefore = 2;
+ const itemsAfter = 1;
+
+ const { debounce } = window.XenSiteUtils;
+
+ const carousel = async (element) => {
+ const uniqueid = Math.random().toString(36).substring(2, 15);
+ element.classList.add("carousel-container-" + uniqueid);
+ const infiniteLoop = true;
+ const itemsBefore = 2;
+ const itemsAfter = 2;
+
+ const carouselElement = element.querySelector(".carousel");
+ let prev = element.querySelector(".prev");
+ let next = element.querySelector(".next");
+ let items = await waitForElements(carouselElement, itemSelector);
+
+ // add attributes to first and last items
+ items[0].dataset.first = true;
+ items[items.length - 1].dataset.last = true;
+
+ const carouselClone = carouselElement.cloneNode(true);
+ carouselClone.classList.add("carousel-clone");
+ carouselClone.style.setProperty("position", "absolute");
+ carouselClone.style.setProperty("left", "0");
+ carouselClone.style.setProperty("top", "-100px");
+ carouselClone.style.setProperty("width", "100%");
+ carouselClone.style.setProperty("overflow", "hidden");
+ carouselClone.style.setProperty("flex-wrap", "wrap");
+ carouselClone.style.setProperty("pointer-events", "none");
+ carouselClone.style.setProperty("visibility", "hidden");
+ carouselClone.style.setProperty("height", "100");
+
+ const getItemInformations = () => {
+ carouselElement.before(carouselClone);
+ carouselClone.style.setProperty("width", carouselElement.offsetWidth + "px");
+ const items = carouselClone.querySelectorAll(itemSelector);
+ if (!items.length) return 0;
+ const item2 = items[1];
+ let occupiedSpace = item2.offsetLeft;
+
+ if (occupiedSpace === 0 && item2.offsetTop > 0) occupiedSpace = item2.offsetWidth + 40;
+ const width = item2.offsetWidth;
+ const height = [...items].reduce((acc, item) => (acc < item.offsetHeight ? item.offsetHeight : acc), 0);
+ carouselClone.remove();
+
+ return {
+ occupiedSpace,
+ width,
+ height,
+ };
+ };
+
+ // element.classList.add("carousel-start-init");
+
+ // add the clone the last item to the first place and for the first item to the last place
+ // generic function to clone N last items to the first place and N first items to the last place
+
+ const cloneItems = (items, clones) => {
+ for (let i = 0; i <= clones; i++) {
+ carouselElement.appendChild(items[i].cloneNode(true));
+ }
+ };
+ cloneItems(items, itemsBefore);
+
+ const cloneItemsReverse = (items, clones) => {
+ for (let i = 0; i <= clones; i++) {
+ carouselElement.prepend(items[items.length - 1 - i].cloneNode(true));
+ }
+ };
+
+ cloneItemsReverse(items, itemsAfter);
+
+ const moveNext = function () {
+ if (
+ !infiniteLoop &&
+ element.querySelectorAll(itemSelector)[element.querySelectorAll(itemSelector).length - 1].dataset.last
+ )
+ return;
+ let items = element.querySelectorAll(itemSelector);
+ carouselElement.appendChild(items[0]);
+ };
+ next.addEventListener("click", moveNext);
+
+ const movePrev = function () {
+ let items = element.querySelectorAll(itemSelector);
+ const item = items[itemsBefore];
+ if (!infiniteLoop && item.dataset.first) return;
+ carouselElement.prepend(items[items.length - 1]);
+ };
+ prev.addEventListener("click", movePrev);
+
+ // mobile
+ let startX, moveX;
+
+ carouselElement.addEventListener("touchstart", (e) => {
+ startX = e.touches[0].clientX;
+ });
+
+ carouselElement.addEventListener("touchend", (e) => {
+ moveX = e.changedTouches[0].clientX - startX;
+ if (Math.abs(moveX) > 50) {
+ if (moveX > 0) {
+ movePrev();
+ } else {
+ moveNext();
+ }
+ }
+ });
+
+ /** The rules are generated from the element width and container width
+ * The rules are :
+ * - nth-child(1) is opacity:0 and index -2
+ * - nth-child(2) is opacity:0 and index -1
+ * - nth-child(3) is opacity:1 and index 0
+ * - nth-child(4) is opacity:1 and index 1
+ * - ...
+ * - nth-child(maxItems) is opacity:1 and index maxItems - 1
+ *
+ */
+ let styleTag;
+ let lastWindowWidth = -1;
+ const generateStyles = (element) => {
+ const windowWidth = window.innerWidth;
+ if (windowWidth === lastWindowWidth) return;
+ lastWindowWidth = windowWidth;
+ const rules = [];
+
+ // add carousel styles generated from the element width
+ if (!styleTag) {
+ styleTag = document.createElement("style");
+ document.head.appendChild(styleTag);
+ }
+
+ const { occupiedSpace, width: itemWidth, height } = getItemInformations();
+
+ if (occupiedSpace < 100) {
+ console.error("Error in the carousel, no item width detected");
+ return;
+ }
+
+ rules.push(`
+ .carousel-container-${uniqueid} {
+ --item-width: ${itemWidth}px;
+ --item-position: ${occupiedSpace}px;
+ --height: ${height}px;
+ }
+ `);
+
+ const carouselWidth = carouselElement.offsetWidth;
+ const maxItems = Math.floor(carouselWidth / occupiedSpace) + 1 + itemsBefore + itemsAfter;
+
+ let opacity = 0;
+ for (let i = 1; i <= maxItems; i++) {
+ if ((i >= itemsBefore && i < maxItems - itemsAfter) || i - itemsBefore === 0) {
+ opacity = 1;
+ } else if (i === maxItems - itemsAfter || i === itemsBefore - 1) {
+ opacity = 0.2;
+ } else {
+ opacity = 0;
+ }
+
+ const index = i - itemsBefore;
+ rules.push(`
+ .carousel-item:nth-child(${i}) {
+ --index: ${index};
+ opacity: ${opacity};
+ display: flex;
+ }
+ `);
+ }
+
+ styleTag.innerHTML = rules.join("\n");
+ };
+
+ window.addEventListener(
+ "resize",
+ debounce(() => {
+ generateStyles(element);
+ }, 50),
+ );
+ generateStyles(element);
+
+ carouselElement.classList.add("carousel-initialized");
+ };
+
+ /**
+ * Waits for elements matching the selector to be present in the DOM within the given element.
+ * @param {HTMLElement} element - The parent element to observe for the selector.
+ * @param {string} selector - The CSS selector to match the elements.
+ * @returns {Promise<NodeListOf<Element>>} A promise that resolves with the matched elements.
+ */
+ const waitForElements = (element, selector) => {
+ return new Promise((resolve) => {
+ const items = element.querySelectorAll(selector);
+ if (items.length) {
+ resolve(items);
+ return;
+ }
+
+ const observer = new MutationObserver((mutations) => {
+ const items = element.querySelectorAll(selector);
+ if (items.length) {
+ observer.disconnect();
+ resolve(items);
+ }
+ });
+
+ observer.observe(element, {
+ childList: true,
+ subtree: true,
+ });
+ });
+ };
+
+ [...document.querySelectorAll(selector)].forEach((elm) => {
+ carousel(elm);
+ });
+})();
};
};
+ /**
+ * Waits for elements matching the selector to be present in the DOM within the given element.
+ * @param {HTMLElement} element - The parent element to observe for the selector.
+ * @param {string} selector - The CSS selector to match the elements.
+ * @returns {Promise<NodeListOf<Element>>} A promise that resolves with the matched elements.
+ */
+ const waitForElements = (element, selector) => {
+ return new Promise((resolve) => {
+ const items = element.querySelectorAll(selector);
+ if (items.length) {
+ resolve(items);
+ return;
+ }
+
+ const observer = new MutationObserver((mutations) => {
+ const items = element.querySelectorAll(selector);
+ if (items.length) {
+ observer.disconnect();
+ resolve(items);
+ }
+ });
+
+ observer.observe(element, {
+ childList: true,
+ subtree: true,
+ });
+ });
+ };
+
window.XenSiteUtils = {
formatDate,
debounce,
+ waitForElements,
};
})();
<div class="carousel-container {{ with .class }}{{ . }}{{ end }}">
<div class="carousel-content">
- <div class="carousel">
- {{ partial "carousel-items.html" (dict "items" .content) }}
+ <div class="carousel-content-inner">
+ <div class="carousel">
+ {{ partial "carousel-items.html" (dict "items" .content) }}
+ </div>
</div>
<div class="carousel-buttons">
<button class="carousel-button prev">