@deriv-com/dds-marketing 0.2.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.llm/components/accordion.md +9 -1
- package/.llm/components/accordionitem.md +9 -1
- package/.llm/components/aspectratio.md +9 -1
- package/.llm/components/badge.md +9 -1
- package/.llm/components/bottomsheet.md +9 -1
- package/.llm/components/breadcrumb.md +9 -1
- package/.llm/components/breadcrumbitem.md +9 -1
- package/.llm/components/button.md +55 -27
- package/.llm/components/buttonicon.md +16 -8
- package/.llm/components/card.md +16 -2
- package/.llm/components/cardbody.md +29 -3
- package/.llm/components/cardbutton.md +16 -2
- package/.llm/components/checkbox.md +9 -1
- package/.llm/components/ctabanner.md +186 -0
- package/.llm/components/ctabannerslider.md +204 -0
- package/.llm/components/divider.md +9 -1
- package/.llm/components/dropdownmenu.md +15 -7
- package/.llm/components/faqaccordion.md +217 -0
- package/.llm/components/featurebullets.md +254 -0
- package/.llm/components/featurecards.md +313 -0
- package/.llm/components/featurescroll.md +284 -0
- package/.llm/components/featuresplit.md +249 -0
- package/.llm/components/filterchip.md +9 -1
- package/.llm/components/footer.md +9 -1
- package/.llm/components/footergroup.md +9 -1
- package/.llm/components/grid.md +9 -1
- package/.llm/components/icon.md +21 -3
- package/.llm/components/landinghero.md +254 -0
- package/.llm/components/link.md +13 -5
- package/.llm/components/logocolumns.md +235 -0
- package/.llm/components/logorows.md +212 -0
- package/.llm/components/menuitem.md +9 -1
- package/.llm/components/navbar.md +80 -16
- package/.llm/components/navbaritem.md +10 -2
- package/.llm/components/navbarmenu.md +9 -1
- package/.llm/components/navbarmenugroup.md +9 -1
- package/.llm/components/navbarpanel.md +10 -2
- package/.llm/components/navbarswitcher.md +10 -2
- package/.llm/components/navbarswitcheroption.md +9 -1
- package/.llm/components/option.md +9 -1
- package/.llm/components/pageheader.md +231 -0
- package/.llm/components/pagination.md +27 -90
- package/.llm/components/paginationdots.md +215 -0
- package/.llm/components/producthero.md +248 -0
- package/.llm/components/removablechip.md +9 -1
- package/.llm/components/row.md +9 -1
- package/.llm/components/scroller.md +9 -1
- package/.llm/components/searchinput.md +9 -1
- package/.llm/components/segmentedcontrol.md +9 -1
- package/.llm/components/segmentedcontrolitem.md +9 -1
- package/.llm/components/select.md +9 -1
- package/.llm/components/selectchip.md +57 -16
- package/.llm/components/spinner.md +9 -1
- package/.llm/components/stack.md +9 -1
- package/.llm/components/statusdot.md +9 -1
- package/.llm/components/subdomainnavbar.md +9 -1
- package/.llm/components/tab.md +11 -3
- package/.llm/components/table.md +9 -1
- package/.llm/components/tablebody.md +9 -1
- package/.llm/components/tablecell.md +9 -1
- package/.llm/components/tablehead.md +9 -1
- package/.llm/components/tableheadercell.md +9 -1
- package/.llm/components/tablerow.md +9 -1
- package/.llm/components/tablist.md +9 -1
- package/.llm/components/testimonialrow.md +235 -0
- package/.llm/components/testimonialslider.md +254 -0
- package/.llm/components/textarea.md +9 -1
- package/.llm/components/textinput.md +9 -1
- package/.llm/components/tooltip.md +9 -1
- package/.llm/components/typography.md +9 -1
- package/.llm/digest/button.md +11 -6
- package/.llm/digest/buttonicon.md +2 -2
- package/.llm/digest/card.md +1 -0
- package/.llm/digest/cardbody.md +3 -1
- package/.llm/digest/cardbutton.md +1 -0
- package/.llm/digest/ctabanner.md +44 -0
- package/.llm/digest/ctabannerslider.md +45 -0
- package/.llm/digest/dropdownmenu.md +1 -1
- package/.llm/digest/faqaccordion.md +43 -0
- package/.llm/digest/featurebullets.md +52 -0
- package/.llm/digest/featurecards.md +49 -0
- package/.llm/digest/featurescroll.md +52 -0
- package/.llm/digest/featuresplit.md +54 -0
- package/.llm/digest/landinghero.md +53 -0
- package/.llm/digest/logocolumns.md +55 -0
- package/.llm/digest/logorows.md +48 -0
- package/.llm/digest/navbar.md +18 -3
- package/.llm/digest/pageheader.md +49 -0
- package/.llm/digest/pagination.md +6 -17
- package/.llm/digest/paginationdots.md +43 -0
- package/.llm/digest/producthero.md +53 -0
- package/.llm/digest/selectchip.md +6 -1
- package/.llm/digest/testimonialrow.md +51 -0
- package/.llm/digest/testimonialslider.md +51 -0
- package/.llm/index.md +24 -3
- package/.llm/rules.md +43 -9
- package/.llm/tokens.md +56 -12
- package/AGENTS.md +32 -8
- package/README.md +41 -21
- package/dist/components/banner-data.d.ts +38 -0
- package/dist/components/banner-data.js +15 -0
- package/dist/components/blocks/block-reveal.css +90 -0
- package/dist/components/blocks/cta-banner/CtaBanner.css +66 -0
- package/dist/components/blocks/cta-banner/CtaBanner.d.ts +22 -0
- package/dist/components/blocks/cta-banner/CtaBanner.js +31 -0
- package/dist/components/blocks/cta-banner/CtaBanner.types.d.ts +29 -0
- package/dist/components/blocks/cta-banner/CtaBanner.types.js +13 -0
- package/dist/components/blocks/cta-banner/cta-banner-impl.css +29 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.css +90 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.d.ts +39 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.js +60 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.d.ts +41 -0
- package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.js +15 -0
- package/dist/components/blocks/cta-banner-slider/cta-banner-slider-impl.css +73 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.css +54 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.d.ts +20 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.js +28 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.types.d.ts +25 -0
- package/dist/components/blocks/faq-accordion/FaqAccordion.types.js +10 -0
- package/dist/components/blocks/faq-accordion/faq-accordion-impl.css +54 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.css +139 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.d.ts +31 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.js +55 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.types.d.ts +39 -0
- package/dist/components/blocks/feature-bullets/FeatureBullets.types.js +16 -0
- package/dist/components/blocks/feature-bullets/feature-bullets-impl.css +104 -0
- package/dist/components/blocks/feature-cards/FeatureCards.css +84 -0
- package/dist/components/blocks/feature-cards/FeatureCards.d.ts +24 -0
- package/dist/components/blocks/feature-cards/FeatureCards.js +66 -0
- package/dist/components/blocks/feature-cards/FeatureCards.types.d.ts +33 -0
- package/dist/components/blocks/feature-cards/FeatureCards.types.js +14 -0
- package/dist/components/blocks/feature-cards/feature-cards-impl.css +245 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.css +137 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.d.ts +39 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.js +167 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.types.d.ts +40 -0
- package/dist/components/blocks/feature-scroll/FeatureScroll.types.js +17 -0
- package/dist/components/blocks/feature-scroll/feature-scroll-impl.css +292 -0
- package/dist/components/blocks/feature-split/FeatureSplit.css +95 -0
- package/dist/components/blocks/feature-split/FeatureSplit.d.ts +30 -0
- package/dist/components/blocks/feature-split/FeatureSplit.js +38 -0
- package/dist/components/blocks/feature-split/FeatureSplit.types.d.ts +39 -0
- package/dist/components/blocks/feature-split/FeatureSplit.types.js +14 -0
- package/dist/components/blocks/feature-split/feature-split-impl.css +83 -0
- package/dist/components/blocks/landing-hero/LandingHero.css +85 -0
- package/dist/components/blocks/landing-hero/LandingHero.d.ts +41 -0
- package/dist/components/blocks/landing-hero/LandingHero.js +54 -0
- package/dist/components/blocks/landing-hero/LandingHero.types.d.ts +37 -0
- package/dist/components/blocks/landing-hero/LandingHero.types.js +15 -0
- package/dist/components/blocks/landing-hero/landing-hero-impl.css +355 -0
- package/dist/components/blocks/logo-columns/LogoColumns.css +161 -0
- package/dist/components/blocks/logo-columns/LogoColumns.d.ts +23 -0
- package/dist/components/blocks/logo-columns/LogoColumns.js +46 -0
- package/dist/components/blocks/logo-columns/LogoColumns.types.d.ts +46 -0
- package/dist/components/blocks/logo-columns/LogoColumns.types.js +18 -0
- package/dist/components/blocks/logo-columns/logo-columns-impl.css +188 -0
- package/dist/components/blocks/logo-rows/LogoRows.css +122 -0
- package/dist/components/blocks/logo-rows/LogoRows.d.ts +20 -0
- package/dist/components/blocks/logo-rows/LogoRows.js +43 -0
- package/dist/components/blocks/logo-rows/LogoRows.types.d.ts +36 -0
- package/dist/components/blocks/logo-rows/LogoRows.types.js +16 -0
- package/dist/components/blocks/logo-rows/logo-rows-impl.css +179 -0
- package/dist/components/blocks/page-header/PageHeader.css +79 -0
- package/dist/components/blocks/page-header/PageHeader.d.ts +34 -0
- package/dist/components/blocks/page-header/PageHeader.js +43 -0
- package/dist/components/blocks/page-header/PageHeader.types.d.ts +32 -0
- package/dist/components/blocks/page-header/PageHeader.types.js +14 -0
- package/dist/components/blocks/page-header/page-header-impl.css +176 -0
- package/dist/components/blocks/product-hero/ProductHero.css +86 -0
- package/dist/components/blocks/product-hero/ProductHero.d.ts +35 -0
- package/dist/components/blocks/product-hero/ProductHero.js +48 -0
- package/dist/components/blocks/product-hero/ProductHero.types.d.ts +37 -0
- package/dist/components/blocks/product-hero/ProductHero.types.js +15 -0
- package/dist/components/blocks/product-hero/product-hero-impl.css +354 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.css +245 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.d.ts +20 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.js +128 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.types.d.ts +54 -0
- package/dist/components/blocks/testimonial-row/TestimonialRow.types.js +26 -0
- package/dist/components/blocks/testimonial-row/testimonial-row-impl.css +195 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.css +181 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.d.ts +22 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.js +36 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.d.ts +49 -0
- package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.js +21 -0
- package/dist/components/blocks/testimonial-slider/testimonial-slider-impl.css +202 -0
- package/dist/components/blocks/use-reveal.d.ts +46 -0
- package/dist/components/blocks/use-reveal.js +120 -0
- package/dist/components/blocks/use-rotation.d.ts +62 -0
- package/dist/components/blocks/use-rotation.js +176 -0
- package/dist/components/core/accordion-item/AccordionItem.css +6 -5
- package/dist/components/core/bottom-sheet/BottomSheet.css +1 -1
- package/dist/components/core/button/Button.css +63 -16
- package/dist/components/core/button/Button.d.ts +14 -12
- package/dist/components/core/button/Button.js +43 -14
- package/dist/components/core/button/Button.types.d.ts +9 -14
- package/dist/components/core/button/button-impl.css +25 -0
- package/dist/components/core/button-icon/ButtonIcon.css +31 -15
- package/dist/components/core/button-icon/ButtonIcon.d.ts +2 -2
- package/dist/components/core/button-icon/ButtonIcon.js +2 -2
- package/dist/components/core/button-icon/ButtonIcon.types.d.ts +4 -4
- package/dist/components/core/card/Card.css +13 -4
- package/dist/components/core/card/Card.types.d.ts +2 -1
- package/dist/components/core/card-body/CardBody.css +11 -6
- package/dist/components/core/card-body/CardBody.d.ts +1 -1
- package/dist/components/core/card-body/CardBody.js +2 -2
- package/dist/components/core/card-body/CardBody.types.d.ts +4 -1
- package/dist/components/core/card-body/CardBody.types.js +1 -0
- package/dist/components/core/card-button/CardButton.css +13 -4
- package/dist/components/core/card-button/CardButton.types.d.ts +2 -1
- package/dist/components/core/checkbox/Checkbox.css +8 -1
- package/dist/components/core/dropdown-menu/DropdownMenu.css +13 -12
- package/dist/components/core/dropdown-menu/DropdownMenu.d.ts +5 -3
- package/dist/components/core/dropdown-menu/DropdownMenu.js +5 -3
- package/dist/components/core/filter-chip/FilterChip.css +2 -1
- package/dist/components/core/footer/footer-impl.css +1 -1
- package/dist/components/core/footer-group/FooterGroup.css +8 -7
- package/dist/components/core/icon/Icon.css +1 -1
- package/dist/components/core/link/Link.css +1 -1
- package/dist/components/core/link/Link.js +5 -5
- package/dist/components/core/menu-item/MenuItem.css +1 -1
- package/dist/components/core/navbar/Navbar.css +7 -8
- package/dist/components/core/navbar/Navbar.d.ts +17 -1
- package/dist/components/core/navbar/Navbar.js +139 -12
- package/dist/components/core/navbar/Navbar.types.d.ts +23 -4
- package/dist/components/core/navbar/Navbar.types.js +1 -1
- package/dist/components/core/navbar/navbar-impl.css +10 -3
- package/dist/components/core/navbar/navbar-material.css +96 -0
- package/dist/components/core/navbar-item/NavbarItem.css +4 -4
- package/dist/components/core/navbar-menu/NavbarMenu.css +7 -5
- package/dist/components/core/navbar-menu/NavbarMenu.d.ts +1 -0
- package/dist/components/core/navbar-menu/NavbarMenu.js +1 -0
- package/dist/components/core/navbar-menu/navbar-menu-impl.css +7 -7
- package/dist/components/core/navbar-switcher/NavbarSwitcher.css +6 -5
- package/dist/components/core/navbar-switcher/NavbarSwitcher.d.ts +1 -0
- package/dist/components/core/navbar-switcher/NavbarSwitcher.js +2 -1
- package/dist/components/core/option/Option.css +1 -1
- package/dist/components/core/pagination/Pagination.css +1 -75
- package/dist/components/core/pagination/Pagination.d.ts +4 -4
- package/dist/components/core/pagination/Pagination.js +11 -51
- package/dist/components/core/pagination/Pagination.types.d.ts +5 -17
- package/dist/components/core/pagination/Pagination.types.js +1 -3
- package/dist/components/core/pagination-dots/PaginationDots.css +60 -0
- package/dist/components/core/pagination-dots/PaginationDots.d.ts +25 -0
- package/dist/components/core/pagination-dots/PaginationDots.js +104 -0
- package/dist/components/core/pagination-dots/PaginationDots.types.d.ts +38 -0
- package/dist/components/core/pagination-dots/PaginationDots.types.js +9 -0
- package/dist/components/core/removable-chip/RemovableChip.css +3 -3
- package/dist/components/core/search-input/SearchInput.css +1 -1
- package/dist/components/core/segmented-control/SegmentedControl.css +1 -1
- package/dist/components/core/segmented-control-item/SegmentedControlItem.css +1 -1
- package/dist/components/core/select/Select.css +2 -1
- package/dist/components/core/select-chip/SelectChip.css +14 -7
- package/dist/components/core/select-chip/SelectChip.d.ts +1 -1
- package/dist/components/core/select-chip/SelectChip.js +7 -4
- package/dist/components/core/select-chip/SelectChip.types.d.ts +7 -1
- package/dist/components/core/select-chip/SelectChip.types.js +1 -1
- package/dist/components/core/select-chip/select-chip-impl.css +14 -3
- package/dist/components/core/status-dot/StatusDot.css +1 -1
- package/dist/components/core/subdomain-navbar/SubdomainNavbar.css +3 -3
- package/dist/components/core/tab/Tab.css +1 -1
- package/dist/components/core/tab/Tab.types.d.ts +1 -1
- package/dist/components/core/tab-list/TabList.css +1 -1
- package/dist/components/core/table/table-impl.css +36 -14
- package/dist/components/core/table-header-cell/TableHeaderCell.css +5 -5
- package/dist/components/core/text-area/TextArea.css +1 -1
- package/dist/components/core/text-input/TextInput.css +1 -1
- package/dist/components/core/tooltip/Tooltip.css +1 -1
- package/dist/components/core/typography/Typography.css +5 -5
- package/dist/components/index.d.ts +35 -0
- package/dist/components/index.js +30 -0
- package/dist/components/logo-data.d.ts +24 -0
- package/dist/components/logo-data.js +11 -0
- package/dist/components/scroll-data.d.ts +64 -0
- package/dist/components/scroll-data.js +17 -0
- package/dist/components/testimonial-row-data.d.ts +51 -0
- package/dist/components/testimonial-row-data.js +11 -0
- package/dist/components/testimonial-slider-data.d.ts +26 -0
- package/dist/components/testimonial-slider-data.js +10 -0
- package/dist/marketing.css +385 -14
- package/package.json +1 -6
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useLayoutEffect, useRef, useState } from 'react';
|
|
4
|
+
import './FeatureScroll.css';
|
|
5
|
+
import './feature-scroll-impl.css';
|
|
6
|
+
import '../block-reveal.css';
|
|
7
|
+
import { arriving, useReveal } from '../use-reveal.js';
|
|
8
|
+
import { Link } from '../../core/link/Link.js';
|
|
9
|
+
import { featureScrollDefaults, featureScrollParts, } from './FeatureScroll.types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Where the fold is, in pixels — the stylesheet's `48rem`, read against the page's own root font
|
|
12
|
+
* size, so the two agree when a page or a reader has changed it. See `showing` below.
|
|
13
|
+
*/
|
|
14
|
+
const FOLD_REM = 48;
|
|
15
|
+
const foldsAt = () => FOLD_REM * (Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16);
|
|
16
|
+
/**
|
|
17
|
+
* A section that gives a set of things one screen each, with the picture pinned beside the words.
|
|
18
|
+
*
|
|
19
|
+
* Two mechanisms, and which does what is the thing to understand here. Holding a picture in place
|
|
20
|
+
* while words move past it is `position: sticky`, and nothing in this file measures it. Knowing
|
|
21
|
+
* *which* picture belongs to the words a reader has reached is a question about where each title
|
|
22
|
+
* has got to, and no stylesheet can ask it — so that much is below, and only that much.
|
|
23
|
+
*
|
|
24
|
+
* The rule the reference design settles on: a stop's picture is the one showing once the *next*
|
|
25
|
+
* stop's title has risen to the line the pictures are pinned at. Read as "the last title at or
|
|
26
|
+
* above the line" plus one, clamped to the end. It means the square changes at the moment a reader
|
|
27
|
+
* reaches a new heading rather than at some fraction of the way through, which is the only moment
|
|
28
|
+
* they would notice either way.
|
|
29
|
+
*
|
|
30
|
+
* Throttled to one read per frame through `requestAnimationFrame`, on a passive listener, and the
|
|
31
|
+
* read itself is `getBoundingClientRect` on the titles — no layout is written back, so the loop
|
|
32
|
+
* cannot thrash. `useLayoutEffect` rather than `useEffect` so the first measurement lands before
|
|
33
|
+
* the browser paints; the effect otherwise ran after the first frame and the section opened on
|
|
34
|
+
* step one for that frame however far down the page a reader had already scrolled.
|
|
35
|
+
*
|
|
36
|
+
* It measures the section's own width rather than the window's, because the fold is a container
|
|
37
|
+
* query on the section and the two disagree wherever a frame stands in for a window. The fold is
|
|
38
|
+
* the stylesheet's own `48rem`, turned into pixels against the root font size each time it is
|
|
39
|
+
* asked: a fixed 768 agreed with it only while the root stayed at sixteen, and a page that set it
|
|
40
|
+
* smaller pinned the picture on the first stop for the width in between.
|
|
41
|
+
*
|
|
42
|
+
* The parts of a stop are in the order a phone reads them, which is the order they are written:
|
|
43
|
+
* name, picture, line, way onward. At full width each stop gives up its box — `display: contents`
|
|
44
|
+
* — and the grid places the words in one column and every picture in the other, stacked, so the
|
|
45
|
+
* change is a change in one square rather than one picture leaving and another arriving. The
|
|
46
|
+
* reference design ships two DOM trees to get that; one tree means a reader's assistive technology
|
|
47
|
+
* and anybody reading the markup meet each stop once.
|
|
48
|
+
*/
|
|
49
|
+
export function FeatureScroll({ ground = featureScrollDefaults.ground, title, steps, mediaPosition = featureScrollDefaults.mediaPosition, }) {
|
|
50
|
+
const titleId = useId();
|
|
51
|
+
const [heading, revealed] = useReveal();
|
|
52
|
+
const root = useRef(null);
|
|
53
|
+
const titles = useRef([]);
|
|
54
|
+
const band = useRef(null);
|
|
55
|
+
const [showing, setShowing] = useState(0);
|
|
56
|
+
const count = steps.length;
|
|
57
|
+
useLayoutEffect(() => {
|
|
58
|
+
if (count === 0)
|
|
59
|
+
return;
|
|
60
|
+
/**
|
|
61
|
+
* The line the pictures are pinned at, read off the pinned band itself.
|
|
62
|
+
*
|
|
63
|
+
* Asked of the element rather than held here, because the offset is the stylesheet's — it is
|
|
64
|
+
* `size.bar-height`, so that a picture comes to rest under a navigation bar rather than behind
|
|
65
|
+
* one. A copy of the number in this file would be a second answer to drift from it, and the
|
|
66
|
+
* band already knows.
|
|
67
|
+
*/
|
|
68
|
+
const line = () => {
|
|
69
|
+
const pinned = band.current;
|
|
70
|
+
if (pinned === null)
|
|
71
|
+
return 0;
|
|
72
|
+
const top = Number.parseFloat(getComputedStyle(pinned).insetBlockStart);
|
|
73
|
+
return Number.isFinite(top) ? top : 0;
|
|
74
|
+
};
|
|
75
|
+
let frame = 0;
|
|
76
|
+
const read = () => {
|
|
77
|
+
frame = 0;
|
|
78
|
+
const section = root.current;
|
|
79
|
+
if (section === null)
|
|
80
|
+
return;
|
|
81
|
+
// The fold is the section's own width, which is what the stylesheet asks. Below it every
|
|
82
|
+
// stop carries its own picture and there is nothing to choose between.
|
|
83
|
+
if (section.clientWidth < foldsAt())
|
|
84
|
+
return;
|
|
85
|
+
const at = line();
|
|
86
|
+
let reached = -1;
|
|
87
|
+
for (let i = 0; i < count; i += 1) {
|
|
88
|
+
const heading = titles.current[i];
|
|
89
|
+
if (heading === null || heading === undefined)
|
|
90
|
+
continue;
|
|
91
|
+
if (heading.getBoundingClientRect().top <= at)
|
|
92
|
+
reached = i;
|
|
93
|
+
}
|
|
94
|
+
const next = Math.min(count - 1, reached + 1);
|
|
95
|
+
setShowing((held) => (held === next ? held : next));
|
|
96
|
+
};
|
|
97
|
+
const schedule = () => {
|
|
98
|
+
if (frame !== 0)
|
|
99
|
+
return;
|
|
100
|
+
frame = window.requestAnimationFrame(read);
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Heard on the way down, because a scroll does not come back up.
|
|
104
|
+
*
|
|
105
|
+
* A `scroll` event fired at an element does not bubble, so a listener on the window hears only
|
|
106
|
+
* the window's own scrolling. Wherever the section sits inside something that scrolls — a
|
|
107
|
+
* pane, a dialog, the workbench's own stage — the pictures simply never changed, while the
|
|
108
|
+
* same section on a page of its own worked perfectly. That is a difference no reader should be
|
|
109
|
+
* able to find, and the reference design has it too: it listens on the window alone.
|
|
110
|
+
*
|
|
111
|
+
* The capture phase is what catches them. Every scroll passes through the window on its way to
|
|
112
|
+
* its target whether or not it comes back, so one listener here answers for every scroller on
|
|
113
|
+
* the page, and the read itself is unchanged — `getBoundingClientRect` is measured against the
|
|
114
|
+
* viewport, which is the right frame however the section came to move within it.
|
|
115
|
+
*
|
|
116
|
+
* The flag has to be given to the removal as well: a capturing listener and a bubbling one are
|
|
117
|
+
* two different registrations, and taking off the wrong one leaves the other listening.
|
|
118
|
+
*/
|
|
119
|
+
const heard = { passive: true, capture: true };
|
|
120
|
+
read();
|
|
121
|
+
window.addEventListener('scroll', schedule, heard);
|
|
122
|
+
window.addEventListener('resize', schedule, { passive: true });
|
|
123
|
+
return () => {
|
|
124
|
+
window.removeEventListener('scroll', schedule, { capture: true });
|
|
125
|
+
window.removeEventListener('resize', schedule);
|
|
126
|
+
if (frame !== 0)
|
|
127
|
+
window.cancelAnimationFrame(frame);
|
|
128
|
+
};
|
|
129
|
+
}, [count]);
|
|
130
|
+
return (_jsx("section", { className: featureScrollParts.root, "data-ground": ground, "data-media-position": mediaPosition, "aria-labelledby": titleId, ref: root, children: _jsxs("div", { className: featureScrollParts.inner, children: [_jsx("h2", { className: featureScrollParts.heading, ref: heading, "data-revealed": revealed, ...arriving(0), id: titleId, children: title }), _jsx("div", { className: featureScrollParts.steps, style: { '--dds-featurescroll-stops': count }, children: steps.map((step, index) => (
|
|
131
|
+
/* Keyed by the title. An index would key the set by position, so filtering a stop out
|
|
132
|
+
would renumber every stop under it and React would reconcile the wrong ones — and
|
|
133
|
+
these are the content, so two the same would be a duplicate stop rather than a key
|
|
134
|
+
collision worth tolerating. */
|
|
135
|
+
_jsx(Stop, { step: step, showing: index === showing, onTitle: (held) => {
|
|
136
|
+
titles.current[index] = held;
|
|
137
|
+
}, onBand: index === 0 ? band : undefined }, step.title))) })] }) }));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* One stop, which arrives on its own below the fold and not at all above it.
|
|
141
|
+
*
|
|
142
|
+
* A component of its own for one reason: it holds a `useReveal` each, and a hook cannot be called
|
|
143
|
+
* inside a loop. Private to this file, because a stop is not a thing a page composes — the section
|
|
144
|
+
* takes its set as data, and this is how one record is drawn.
|
|
145
|
+
*
|
|
146
|
+
* Two arrivals rather than one, and the gap between them is the layout's rather than a number. The
|
|
147
|
+
* words and the picture each watch for themselves, and on a phone the picture sits under the line
|
|
148
|
+
* it belongs to — so it comes up a beat later because it reaches the viewport a beat later. A delay
|
|
149
|
+
* would have been a guess about how far apart they are; this is the distance itself.
|
|
150
|
+
*
|
|
151
|
+
* Above the fold neither moves, and the stylesheet says so. What moves there is the picture, which
|
|
152
|
+
* is pinned beside the words and exchanged as a reader passes each title — the section's own
|
|
153
|
+
* motion, and enough of it. It is also the one place a transform cannot go: the band is
|
|
154
|
+
* `position: sticky`, and the crossfade owns its opacity.
|
|
155
|
+
*
|
|
156
|
+
* Below the fold there is no pinning and nothing is exchanged: the stops are ordinary blocks in a
|
|
157
|
+
* column, so without this the only thing on a phone that moved was the section's heading — one
|
|
158
|
+
* flourish at the top and six screens of nothing after it.
|
|
159
|
+
*/
|
|
160
|
+
function Stop({ step, showing, onTitle, onBand, }) {
|
|
161
|
+
const [words, wordsHere] = useReveal();
|
|
162
|
+
const [picture, pictureHere] = useReveal();
|
|
163
|
+
return (_jsxs("div", { className: featureScrollParts.step, children: [_jsxs("div", { className: featureScrollParts.content, "data-revealed": wordsHere, children: [_jsx("h3", { className: featureScrollParts.title, ...arriving(0), ref: (held) => {
|
|
164
|
+
words(held);
|
|
165
|
+
onTitle(held);
|
|
166
|
+
}, children: step.title }), _jsx("p", { className: featureScrollParts.description, ...arriving(1), children: step.description }), step.href !== undefined && step.linkLabel !== undefined ? (_jsx("div", { className: featureScrollParts.link, ...arriving(2), children: _jsx(Link, { href: step.href, children: step.linkLabel }) })) : null] }), _jsx("div", { className: featureScrollParts.media, "data-showing": showing, "data-revealed": pictureHere, "aria-hidden": "true", ref: onBand, children: _jsx("img", { className: featureScrollParts.picture, src: step.image, alt: "", ...arriving(0), ref: picture }) })] }));
|
|
167
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { FeatureScrollStep } from '../../scroll-data.js';
|
|
2
|
+
export interface FeatureScrollOwnProps {
|
|
3
|
+
/**
|
|
4
|
+
* `page` — The default, and the page's own white. A section that has nothing to separate itself from what is above it — most of them — and the ground a reader is already on
|
|
5
|
+
* `subtle` — A quiet step off the page, for a band that wants to read as its own section without changing the page's voice. The one to reach for first when two sections need telling apart. Painted as the band's tint laid over the page's own ground, so it is the same grey whatever is behind the section — in dark mode the tint alone took on whatever it was set against, and on a light surface it was light ink on white.
|
|
6
|
+
* `strong` — `color.bg.brand-strong`, the dark band. It stops the page rather than dividing it, so it is for a section a reader should arrive at — and one per page is usually one more than enough
|
|
7
|
+
*/
|
|
8
|
+
ground?: 'page' | 'subtle' | 'strong';
|
|
9
|
+
/**
|
|
10
|
+
* `end` — The default. The words are read first, which is the order a reader’s eye takes a two-up row in
|
|
11
|
+
* `start` — The pictures lead, for a section following one that put its own the other way — two scrolls facing the same way read as one very long column
|
|
12
|
+
*/
|
|
13
|
+
mediaPosition?: 'end' | 'start';
|
|
14
|
+
/** Always. What the set is a set of, in the fewest words that say it — 'All your markets in one place'. It is the only thing a reader sees before committing to several screens of scrolling, so it has to say what is coming */
|
|
15
|
+
title: string;
|
|
16
|
+
/** Always. The stops, in the order a reader meets them. Three at least — the section is built on each one having a screen, and a pair is worth one screen with both on it.
|
|
17
|
+
|
|
18
|
+
Every stop is the same shape, which is what makes the set a set: a name, a line, a picture, and a way onward where there is one. */
|
|
19
|
+
steps: FeatureScrollStep[];
|
|
20
|
+
}
|
|
21
|
+
export type FeatureScrollProps = FeatureScrollOwnProps;
|
|
22
|
+
/** The class each part carries. Apply these — do not retype them. */
|
|
23
|
+
export declare const featureScrollParts: {
|
|
24
|
+
readonly root: "dds-featurescroll";
|
|
25
|
+
readonly inner: "dds-featurescroll__inner";
|
|
26
|
+
readonly heading: "dds-featurescroll__heading";
|
|
27
|
+
readonly steps: "dds-featurescroll__steps";
|
|
28
|
+
readonly step: "dds-featurescroll__step";
|
|
29
|
+
readonly content: "dds-featurescroll__content";
|
|
30
|
+
readonly title: "dds-featurescroll__title";
|
|
31
|
+
readonly description: "dds-featurescroll__description";
|
|
32
|
+
readonly link: "dds-featurescroll__link";
|
|
33
|
+
readonly media: "dds-featurescroll__media";
|
|
34
|
+
readonly picture: "dds-featurescroll__picture";
|
|
35
|
+
};
|
|
36
|
+
/** Generated so defaults cannot drift from the contract. */
|
|
37
|
+
export declare const featureScrollDefaults: {
|
|
38
|
+
readonly ground: "page";
|
|
39
|
+
readonly mediaPosition: "end";
|
|
40
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// GENERATED from feature-scroll.contract.json. Do not edit.
|
|
2
|
+
/** The class each part carries. Apply these — do not retype them. */
|
|
3
|
+
export const featureScrollParts = {
|
|
4
|
+
root: 'dds-featurescroll',
|
|
5
|
+
inner: 'dds-featurescroll__inner',
|
|
6
|
+
heading: 'dds-featurescroll__heading',
|
|
7
|
+
steps: 'dds-featurescroll__steps',
|
|
8
|
+
step: 'dds-featurescroll__step',
|
|
9
|
+
content: 'dds-featurescroll__content',
|
|
10
|
+
title: 'dds-featurescroll__title',
|
|
11
|
+
description: 'dds-featurescroll__description',
|
|
12
|
+
link: 'dds-featurescroll__link',
|
|
13
|
+
media: 'dds-featurescroll__media',
|
|
14
|
+
picture: 'dds-featurescroll__picture',
|
|
15
|
+
};
|
|
16
|
+
/** Generated so defaults cannot drift from the contract. */
|
|
17
|
+
export const featureScrollDefaults = { ground: 'page', mediaPosition: 'end' };
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Scoped to this package, like the generated sheet beside it.
|
|
3
|
+
*
|
|
4
|
+
* `:where([data-dds-context="marketing"])` confines these rules without contributing specificity,
|
|
5
|
+
* so each is decided against the generated rule on its own terms.
|
|
6
|
+
*
|
|
7
|
+
* Every query here is `@container` rather than `@media`. The section declares `container-type`,
|
|
8
|
+
* so the set folds on the band's own width — which is what the rest of this package does, and
|
|
9
|
+
* what makes the workbench's viewport control mean something: it resizes the stage rather than
|
|
10
|
+
* the browser, and a section asking the window would hold its desktop layout at every setting.
|
|
11
|
+
*
|
|
12
|
+
* `48rem` longhand throughout because `var()` is not permitted in a container query condition,
|
|
13
|
+
* and it is the width the rest of this package folds at.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* One column below the fold, and the picture between the name and the line about it.
|
|
18
|
+
*
|
|
19
|
+
* The row direction is the contract's to say — it varies by a styling axis, which is what
|
|
20
|
+
* `structure[]` is for, and the generated sheet carries both values. What is left here is the
|
|
21
|
+
* fold, which a binding cannot express: `structure` has no width to attach a rule to.
|
|
22
|
+
*
|
|
23
|
+
* Written with the `data-media-position` attribute so it carries the weight of the generated rules
|
|
24
|
+
* keyed on it — `.dds-featurescroll[data-media-position="end"] .dds-featurescroll__content` and its
|
|
25
|
+
* kin, a class, an attribute and a class — and wins over anything at that weight by loading later.
|
|
26
|
+
*/
|
|
27
|
+
@container (max-width: 47.9375rem) {
|
|
28
|
+
:where([data-dds-context="marketing"])
|
|
29
|
+
.dds-featurescroll[data-media-position]
|
|
30
|
+
.dds-featurescroll__step {
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* The text column stops being a box so the picture can sit inside its order.
|
|
36
|
+
*
|
|
37
|
+
* A phone has one column, and the picture is what tells a reader which of six stops they have
|
|
38
|
+
* reached — put after the words it arrives once they have already decided whether to read them.
|
|
39
|
+
* The name, the picture, then the line and the way onward is the reference design's own order,
|
|
40
|
+
* and the only other way to get it is a second copy of the markup for the second layout.
|
|
41
|
+
*
|
|
42
|
+
* `display: contents` takes the box out and leaves its children to the row above, so the four
|
|
43
|
+
* parts become four flex items and `order` can interleave them. The children keep their own
|
|
44
|
+
* styling; what is lost is anything the box itself drew, which here is one `row-gap` that is
|
|
45
|
+
* `space.gap.none` at every width anyway.
|
|
46
|
+
*/
|
|
47
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__content {
|
|
48
|
+
display: contents;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__title {
|
|
52
|
+
order: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/*
|
|
56
|
+
* The band hugs its picture and the picture runs the column.
|
|
57
|
+
*
|
|
58
|
+
* There is one column below the fold, nothing to pin against, and no band to be the height of —
|
|
59
|
+
* so the air the band holds the picture off its edges by would be a hole in the stack, where the
|
|
60
|
+
* gap between the parts already does that job.
|
|
61
|
+
*/
|
|
62
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__media {
|
|
63
|
+
order: 2;
|
|
64
|
+
padding-block: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__picture {
|
|
68
|
+
inline-size: 100%;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__description {
|
|
72
|
+
order: 3;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__link {
|
|
76
|
+
order: 4;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* The steps between the parts come off the parts and onto the stack.
|
|
81
|
+
*
|
|
82
|
+
* At full width the column carries them on each part's own `margin-block-end`, because
|
|
83
|
+
* title-to-description and description-to-what-follows are two different roles. Below the fold
|
|
84
|
+
* there is a fifth thing in the order — the picture — and a margin belonging to the title would
|
|
85
|
+
* land under the picture instead of over it. So the row's own gap governs here, and the margins
|
|
86
|
+
* that would add to it are cleared.
|
|
87
|
+
*/
|
|
88
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__title,
|
|
89
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__description {
|
|
90
|
+
margin-block-end: 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* A screen each at full width, the picture pinned for the length of it, and the set closed up.
|
|
96
|
+
*
|
|
97
|
+
* The height is the whole idea rather than a decoration on it: a reader arrives at one stop at a
|
|
98
|
+
* time and scrolls on to the next, which is what a set too long to compare needs and what a grid
|
|
99
|
+
* of six cards cannot give. `100cqh` rather than `100svh` for the reason LandingHero gives — with
|
|
100
|
+
* no ancestor declaring size containment the two are the same number, and the container unit is
|
|
101
|
+
* what lets a frame standing in for a window say otherwise. The section's own `container-type` is
|
|
102
|
+
* `inline-size`, which answers no question about height, so the unit passes straight through it.
|
|
103
|
+
*
|
|
104
|
+
* `position: sticky` and nothing else. The reference design measures scroll position in React,
|
|
105
|
+
* keeps an active index and crossfades between a stack of absolutely-placed pictures; the dissolve
|
|
106
|
+
* is what that buys and three listeners on a marketing page is what it costs. The browser already
|
|
107
|
+
* knows where the box is.
|
|
108
|
+
*
|
|
109
|
+
* It pins at `size.bar-height`, which is what a collapsed navigation bar takes — so the picture
|
|
110
|
+
* comes to rest just under the bar rather than behind it. On a page with no bar the offset is a
|
|
111
|
+
* margin, which is what a picture at the very top of the window wants anyway.
|
|
112
|
+
*
|
|
113
|
+
* The square is capped at the band it has to fit inside: the bar's height above it and the same
|
|
114
|
+
* again below, so a short window compacts the picture instead of running it past the fold. Both
|
|
115
|
+
* axes take the same cap because the shape is the point — capping the height alone would clamp a
|
|
116
|
+
* square into a letterbox, and the picture inside is cropped to fill whatever it is given.
|
|
117
|
+
*
|
|
118
|
+
* The gap between stops goes to nothing here. Below the fold they are ordinary blocks and want air
|
|
119
|
+
* between them, which is the gap the contract binds; at full width a picture is released at the
|
|
120
|
+
* moment the next arrives, and a gap there opens a stretch of scrolling with no picture pinned at
|
|
121
|
+
* all — which reads as the section having ended and started again.
|
|
122
|
+
*/
|
|
123
|
+
@container (min-width: 48rem) {
|
|
124
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__heading {
|
|
125
|
+
/* `margin-inline: auto` as well as the alignment, because the heading is held to
|
|
126
|
+
`size.measure.heading` — centred text inside a box still pinned to the start edge is a
|
|
127
|
+
heading that looks centred only while it is short enough not to reach the cap. */
|
|
128
|
+
margin-inline: auto;
|
|
129
|
+
text-align: center;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/*
|
|
133
|
+
* Two columns, and every picture in one of them.
|
|
134
|
+
*
|
|
135
|
+
* The rows are named rather than numbered so the contract can place the two halves: `structure`
|
|
136
|
+
* may hold a keyword and may not hold a 2, on the argument that a ratio between siblings is a
|
|
137
|
+
* layout decision — and the names say which half is which where a number would only say where.
|
|
138
|
+
*
|
|
139
|
+
* The row count arrives as a custom property because `grid-row: 1 / -1` cannot reach the end of
|
|
140
|
+
* an implicit grid: with no explicit rows, `-1` is the end of the explicit one, which is the
|
|
141
|
+
* first row. Stated, the pictures span every row and land on top of one another.
|
|
142
|
+
*/
|
|
143
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__steps {
|
|
144
|
+
display: grid;
|
|
145
|
+
grid-template-columns: [words] 1fr [picture] 1fr;
|
|
146
|
+
grid-template-rows: repeat(var(--dds-featurescroll-stops, 1), auto);
|
|
147
|
+
row-gap: 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* A stop gives up its box, so the grid can place what is inside it.
|
|
152
|
+
*
|
|
153
|
+
* This is what lets one tree serve both layouts. A picture inside its own stop can only be
|
|
154
|
+
* pinned within it — released a screen later, arriving from below, travelling rather than
|
|
155
|
+
* changing. Placed by the grid instead, every picture is in one column across every row, so
|
|
156
|
+
* they overlap and one can be exchanged for another in the same square.
|
|
157
|
+
*
|
|
158
|
+
* The stop stays in the markup and keeps its four parts together; what it gives up is a box
|
|
159
|
+
* nothing needed at this width.
|
|
160
|
+
*/
|
|
161
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__step {
|
|
162
|
+
display: contents;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
* Nothing arrives on its own up here, whatever it was marked.
|
|
167
|
+
*
|
|
168
|
+
* Below the fold the words and then the picture come up as a reader reaches them, because there
|
|
169
|
+
* is nothing else moving in a column of stacked blocks. Above it the picture is pinned beside
|
|
170
|
+
* the words and exchanged as each title is passed — that is the section's motion, and a stop
|
|
171
|
+
* sliding in under it would be two arrivals for one thing.
|
|
172
|
+
*
|
|
173
|
+
* There is a harder reason for the picture. It sits inside a `position: sticky` band, and a
|
|
174
|
+
* transform on it is a transform the pinning has to survive. `none` rather than a zero value, so
|
|
175
|
+
* no transform exists at all.
|
|
176
|
+
*
|
|
177
|
+
* The attribute stays on both elements and is simply not read here, which is what keeps the
|
|
178
|
+
* component free of a width it cannot measure: the fold is a container query, and no observer
|
|
179
|
+
* can see one.
|
|
180
|
+
*/
|
|
181
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll [data-revealed] [data-reveal-at] {
|
|
182
|
+
opacity: 1;
|
|
183
|
+
translate: none;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/*
|
|
187
|
+
* The words take a screen each, which is the rhythm the whole section is built on: a reader
|
|
188
|
+
* arrives at one stop at a time and scrolls on to the next. Centred in it so they sit level with
|
|
189
|
+
* the picture rather than at the head of a screen-tall row.
|
|
190
|
+
*/
|
|
191
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__content {
|
|
192
|
+
justify-content: center;
|
|
193
|
+
min-block-size: 100cqh;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
* Every band pinned at one line, and all of them stacked.
|
|
198
|
+
*
|
|
199
|
+
* `grid-row` spans the whole set, so the containing block a sticky box measures its room against
|
|
200
|
+
* is the entire list rather than one stop — which is what keeps a picture pinned from the first
|
|
201
|
+
* stop to the last instead of being released at each boundary.
|
|
202
|
+
*
|
|
203
|
+
* `align-self: start` because the band is a screen tall and its area is the whole list; stretched
|
|
204
|
+
* it would be as tall as the section and there would be nothing to pin.
|
|
205
|
+
*
|
|
206
|
+
* `100cqh` rather than `100svh` for the reason LandingHero gives: with no ancestor declaring size
|
|
207
|
+
* containment the two are the same number, and the container unit is what lets a frame standing
|
|
208
|
+
* in for a window say otherwise.
|
|
209
|
+
*/
|
|
210
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__media {
|
|
211
|
+
align-self: start;
|
|
212
|
+
block-size: calc(100cqh - var(--size-bar-height));
|
|
213
|
+
grid-row: 1 / -1;
|
|
214
|
+
inset-block-start: var(--size-bar-height);
|
|
215
|
+
opacity: 0;
|
|
216
|
+
position: sticky;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/*
|
|
220
|
+
* And one of them is shown.
|
|
221
|
+
*
|
|
222
|
+
* The only thing the component's own listener writes. Which stop's picture belongs to the words
|
|
223
|
+
* a reader has reached is a question about where each title has got to, and no query asks it —
|
|
224
|
+
* everything else here, the pinning and the dissolve, is this stylesheet's.
|
|
225
|
+
*
|
|
226
|
+
* The duration and the curve are bound on the part, so the generated sheet writes the
|
|
227
|
+
* reduced-motion block that turns this into a cut.
|
|
228
|
+
*/
|
|
229
|
+
:where([data-dds-context="marketing"])
|
|
230
|
+
.dds-featurescroll
|
|
231
|
+
.dds-featurescroll__media[data-showing="true"] {
|
|
232
|
+
opacity: 1;
|
|
233
|
+
/*
|
|
234
|
+
* On top, and that is what makes this a dissolve rather than a dip.
|
|
235
|
+
*
|
|
236
|
+
* The arriving picture fades in over whatever is beneath it, so at every moment of the change
|
|
237
|
+
* the square is covered by something opaque. Fading both at once instead leaves them near half
|
|
238
|
+
* way together, the section's own ground shows through the pair, and the square brightens and
|
|
239
|
+
* darkens — a pulse, from nothing but two opacities crossing.
|
|
240
|
+
*
|
|
241
|
+
* It has to be the shown one rather than the later one in the document, because a reader
|
|
242
|
+
* scrolls back as well as forward. Going up, the arriving picture is earlier in the markup and
|
|
243
|
+
* would fade in underneath the one it is replacing: invisible until that one left, which is a
|
|
244
|
+
* cut with a pause in front of it.
|
|
245
|
+
*
|
|
246
|
+
* A bare integer, which is the one kind of value this system keeps out of its tokens: a
|
|
247
|
+
* stacking order inside a component is not a decision any brand or mode has an opinion about.
|
|
248
|
+
*/
|
|
249
|
+
z-index: 1;
|
|
250
|
+
/* No wait on the way in. The pair of delays is the whole mechanism — see the rule below. */
|
|
251
|
+
transition-delay: 0s;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/*
|
|
255
|
+
* The one leaving holds until it has been covered, then goes.
|
|
256
|
+
*
|
|
257
|
+
* This is the other half of the dissolve. The arriving picture takes the whole duration to reach
|
|
258
|
+
* full opacity, and for all of it the one underneath stays exactly as it was — so there is never
|
|
259
|
+
* a frame where the square is anything less than a complete picture. Only once it is hidden
|
|
260
|
+
* behind the new one does it drop, which costs nothing to look at because nobody can see it.
|
|
261
|
+
*
|
|
262
|
+
* The delay is the arriving picture's own duration, read from the same role, so the two cannot
|
|
263
|
+
* drift apart into a gap or an overlap.
|
|
264
|
+
*
|
|
265
|
+
* There is no scale here any more. It read as a pulse, which is what it was: a square this large
|
|
266
|
+
* carries a change of picture on its own, and a sixth of a second of zoom on top of it is the
|
|
267
|
+
* section drawing attention to its own machinery.
|
|
268
|
+
*/
|
|
269
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__media {
|
|
270
|
+
transition-delay: var(--motion-duration-media-hover);
|
|
271
|
+
/* Named here rather than in the contract because `staticCss` takes keywords, and this is the
|
|
272
|
+
one property the pair of rules is about. The duration and the curve stay bound there, which
|
|
273
|
+
is what the generated reduced-motion block is written from — so a reader who asked for less
|
|
274
|
+
still gets a cut. */
|
|
275
|
+
transition-property: opacity;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/*
|
|
279
|
+
* The largest square the band can hold.
|
|
280
|
+
*
|
|
281
|
+
* Both bounds in one measure, because a square capped on one axis alone is not a square: giving
|
|
282
|
+
* it a `max-block-size` clamps the height and leaves the width, and the browser hands back a
|
|
283
|
+
* flattened picture. `min()` picks whichever runs out first — the half it sits in, or the band's
|
|
284
|
+
* own inner height, which is the band less the air it holds the picture off its edges by.
|
|
285
|
+
*/
|
|
286
|
+
:where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__picture {
|
|
287
|
+
inline-size: min(
|
|
288
|
+
100%,
|
|
289
|
+
calc(100cqh - var(--size-bar-height) - var(--space-gap-lg) * 2)
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* GENERATED from featuresplit.contract.json. Do not edit. */
|
|
2
|
+
|
|
3
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
container-type: inline-size;
|
|
6
|
+
display: flex;
|
|
7
|
+
inline-size: 100%;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__inner {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
inline-size: 100%;
|
|
16
|
+
min-inline-size: 0;
|
|
17
|
+
max-inline-size: var(--size-content-max);
|
|
18
|
+
padding-block: var(--space-section-default);
|
|
19
|
+
padding-inline: var(--space-inset-page);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__split {
|
|
23
|
+
align-items: stretch;
|
|
24
|
+
display: flex;
|
|
25
|
+
min-inline-size: 0;
|
|
26
|
+
column-gap: var(--space-gap-3xl);
|
|
27
|
+
row-gap: var(--space-gap-2xl);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__content {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
min-inline-size: 0;
|
|
34
|
+
row-gap: var(--space-gap-none);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__title {
|
|
38
|
+
margin-block: 0;
|
|
39
|
+
text-wrap: balance;
|
|
40
|
+
font-family: var(--font-h2-family);
|
|
41
|
+
font-size: var(--font-h2-size);
|
|
42
|
+
font-weight: var(--font-h2-weight);
|
|
43
|
+
letter-spacing: var(--font-h2-letter-spacing);
|
|
44
|
+
line-height: var(--font-h2-line-height);
|
|
45
|
+
margin-block-end: var(--space-heading-title-description);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__description {
|
|
49
|
+
margin-block: 0;
|
|
50
|
+
font-family: var(--font-body-md-family);
|
|
51
|
+
font-size: var(--font-body-md-size);
|
|
52
|
+
font-weight: var(--font-body-md-weight);
|
|
53
|
+
letter-spacing: var(--font-body-md-letter-spacing);
|
|
54
|
+
line-height: var(--font-body-md-line-height);
|
|
55
|
+
margin-block-end: var(--space-heading-description-actions);
|
|
56
|
+
max-inline-size: var(--size-measure-description);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__link {
|
|
60
|
+
display: flex;
|
|
61
|
+
margin-block-start: var(--space-gap-none);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__media {
|
|
65
|
+
min-inline-size: 0;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
position: relative;
|
|
68
|
+
aspect-ratio: var(--aspect-square);
|
|
69
|
+
border-radius: var(--radius-container);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit[data-media-position="end"] .dds-featuresplit__split {
|
|
73
|
+
flex-direction: row;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit[data-media-position="start"] .dds-featuresplit__split {
|
|
77
|
+
flex-direction: row-reverse;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit[data-ground="page"] {
|
|
81
|
+
background-color: var(--color-bg-page);
|
|
82
|
+
color: var(--color-fg-default);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit[data-ground="strong"] {
|
|
86
|
+
background-color: var(--color-bg-brand-strong);
|
|
87
|
+
color: var(--color-fg-on-brand-strong);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
:where([data-dds-context="marketing"]) .dds-featuresplit[data-ground="subtle"] {
|
|
91
|
+
background-color: var(--color-bg-page);
|
|
92
|
+
background-image: linear-gradient(var(--color-bg-band-subtle), var(--color-bg-band-subtle));
|
|
93
|
+
color: var(--color-fg-default);
|
|
94
|
+
}
|
|
95
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import './FeatureSplit.css';
|
|
2
|
+
import './feature-split-impl.css';
|
|
3
|
+
import '../block-reveal.css';
|
|
4
|
+
import { type FeatureSplitProps } from './FeatureSplit.types.js';
|
|
5
|
+
export type { FeatureSplitProps };
|
|
6
|
+
/**
|
|
7
|
+
* A section putting one claim, a line explaining it and a way onward beside a single picture.
|
|
8
|
+
*
|
|
9
|
+
* The plainest block this package has, and that is what it is for: a page that has said what
|
|
10
|
+
* something is needs somewhere to say one more thing about it without opening a list, a grid or a
|
|
11
|
+
* set of cards.
|
|
12
|
+
*
|
|
13
|
+
* A split rather than a stack, because the picture is the other half of the argument — the same
|
|
14
|
+
* words over a full-width photograph read as a caption, and beside it they read as a claim with
|
|
15
|
+
* evidence.
|
|
16
|
+
*
|
|
17
|
+
* The steps inside the column are carried by each part's own `margin-block-end` rather than by a
|
|
18
|
+
* gap on the column, because title-to-description and description-to-what-follows are two
|
|
19
|
+
* different roles that a single gap could only ever be one of. `content` binds `space.gap.none`
|
|
20
|
+
* to say the absence is deliberate rather than forgotten.
|
|
21
|
+
*
|
|
22
|
+
* The source template carried `showDescription` and `showLink` beside the values themselves. Both
|
|
23
|
+
* are gone: absence is the switch, which is the rule Footer set and `given` exists for. A flag
|
|
24
|
+
* saying whether to draw a value the caller already decided not to pass is two ways to say one
|
|
25
|
+
* thing, and they disagree the first time somebody sets only one of them.
|
|
26
|
+
*
|
|
27
|
+
* The title carries the section's accessible name through `aria-labelledby`. A `<section>` is only
|
|
28
|
+
* a region landmark once it has a name.
|
|
29
|
+
*/
|
|
30
|
+
export declare function FeatureSplit({ ground, mediaPosition, title, description, link, media, }: FeatureSplitProps): import("react").JSX.Element;
|