/* * : M3 Expressive's carousel — multi-browse, hero, uncontained, the uncontained * multi-aspect-ratio layout (added November 2025) and full-screen, on a native scroll container * with CSS scroll snap. resources/js/carousel.js ports Compose's keyline maths (Arrangement.kt, * Keylines.kt, KeylineList.kt, Strategy.kt, KeylineSnapPosition.kt, Carousel.kt, androidx Compose * Material 3 @ commit 7ac433e44e797de53af85226797862687f37735f, Apache-2.0) and writes the masks * and sizes as inline custom properties every scroll frame; this file draws everything the script * does not — the row's layout per layout, and the controls — and carousel-item.css draws the * item and its mask (docs/reference/m3/components-actions-communication-containment.md * § Carousel; docs/audits/m3-alignment/containment.md). * * [data-md-carousel] the row wrapper; its value is the layout * [data-md-carousel-probe] hidden, measures the preferred item width for the script * [data-md-carousel-scroller] the scroll container (role="region") * [data-md-carousel-item] one slide — carousel-item.css * [data-md-carousel-controls] previous/next, "auto" (pointer: fine only) or "always" * [data-md-carousel-previous], [data-md-carousel-next] * * Specs table: 16dp leading/trailing padding, 8dp top/bottom and between items, for every layout * but uncontained (leading only) and full-screen (0, edge to edge — C-12); a `>` combinator * throughout, never a bare descendant one, because a carousel item can itself hold a nested * carousel (a `multi-aspect` gallery inside a `hero` slide) whose own root would otherwise match * its parent's rules too. Full-screen scrolls vertically, one edge-to-edge item at a time, capped * at the 840px medium window it is meant for (C-11); reduced motion needs no rule here at all — * the script writes zero inset, shift and full opacity itself for every layout (C-05). * * The previous/next icons mirror in RTL by scaling the whole button, as the row's arrow keys and * scroll direction already do (`resources/js/carousel.js`'s own `state.rtl`), rather than through * ``: `` has no prop to reach it, and flipping a round icon * button whole reads the same as flipping only its glyph. */ @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; @import './button.css'; @import './carousel-item.css'; @layer material.components { [data-md-carousel] { position: relative; } [data-md-carousel] > [data-md-carousel-probe] { position: absolute; inset-inline-start: 0; top: 0; height: 0; width: var(--material-carousel-item-width); visibility: hidden; pointer-events: none; } [data-md-carousel] > [data-md-carousel-scroller] { display: flex; scrollbar-width: none; &::-webkit-scrollbar { display: none; } } [data-md-carousel='full-screen'] > [data-md-carousel-scroller] { margin-inline: auto; height: var(--material-carousel-height); max-width: 840px; flex-direction: column; gap: var(--md-sys-measurement-space200); overflow-x: hidden; overflow-y: auto; overscroll-behavior-y: contain; scroll-snap-type: y mandatory; } [data-md-carousel]:not([data-md-carousel='full-screen']) > [data-md-carousel-scroller] { height: calc(var(--material-carousel-height) + 16px); gap: var(--md-sys-measurement-space100); overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; padding-block: var(--md-sys-measurement-space100); } [data-md-carousel='multi-aspect'] > [data-md-carousel-scroller] { padding-inline-start: var(--material-carousel-pad); } [data-md-carousel='multi-browse'] > [data-md-carousel-scroller], [data-md-carousel='hero'] > [data-md-carousel-scroller] { scroll-snap-type: x mandatory; } [data-md-carousel] > [data-md-carousel-controls] { display: none; margin-top: 12px; justify-content: flex-end; gap: var(--md-sys-measurement-space100); } [data-md-carousel] > [data-md-carousel-controls='always'] { display: flex; } @media (pointer: fine) { [data-md-carousel] > [data-md-carousel-controls='auto'] { display: flex; } } [data-md-carousel]:not([data-md-carousel='full-screen']) > [data-md-carousel-controls] > [data-md-carousel-previous]:dir(rtl), [data-md-carousel]:not([data-md-carousel='full-screen']) > [data-md-carousel-controls] > [data-md-carousel-next]:dir(rtl) { transform: scaleX(-1); } }