From 6d27e427217f15aacd29b52abbc8cddc6b88a60e Mon Sep 17 00:00:00 2001 From: Femke Reunes Date: Tue, 28 Jul 2026 15:50:12 +0200 Subject: [PATCH 1/5] ARC-3793: start on highlight text block --- .../ContentBlockRenderer.const.tsx | 2 + .../BlockHighlightText.editorconfig.ts | 57 +++++ .../BlockHighlightText/BlockHighlightText.tsx | 65 +++++ .../BlockHighlightText/BlockHighligtText.scss | 224 ++++++++++++++++++ .../blocks/BlockHighlightText/index.ts | 2 + .../const/content-block-config-map.ts | 2 + .../const/content-block-initial-state-map.ts | 2 + .../const/get-content-block-type-options.ts | 10 +- .../content-page/types/content-block.types.ts | 1 + 9 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.editorconfig.ts create mode 100644 ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx create mode 100644 ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss create mode 100644 ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/index.ts diff --git a/ui/src/react-admin/modules/content-page/components/ContentBlockRenderer/ContentBlockRenderer.const.tsx b/ui/src/react-admin/modules/content-page/components/ContentBlockRenderer/ContentBlockRenderer.const.tsx index d62f1ea1..82a2eb42 100644 --- a/ui/src/react-admin/modules/content-page/components/ContentBlockRenderer/ContentBlockRenderer.const.tsx +++ b/ui/src/react-admin/modules/content-page/components/ContentBlockRenderer/ContentBlockRenderer.const.tsx @@ -10,6 +10,7 @@ import { BlockEventbrite } from '~content-blocks/BlockEventbrite'; import { BlockHeading } from '~content-blocks/BlockHeading'; import { BlockHetArchiefHeaderSearch } from '~content-blocks/BlockHetArchiefHeaderSearch/BlockHetArchiefHeaderSearch'; import { BlockHetArchiefImageTextBackground } from '~content-blocks/BlockHetArchiefImageTextBackground'; +import { BlockHighlightText } from '~content-blocks/BlockHighlightText'; import { BlockHomepageBanner } from '~content-blocks/BlockHomepageBanner'; import { BlockIFrame } from '~content-blocks/BlockIFrame'; import { BlockImage } from '~content-blocks/BlockImage'; @@ -89,6 +90,7 @@ export function GET_BLOCK_COMPONENT( [ContentBlockType.HetArchiefImageTextBackground]: BlockHetArchiefImageTextBackground, [ContentBlockType.OverviewWithCarousel]: BlockOverviewWithCarousel, [ContentBlockType.HomepageBanner]: BlockHomepageBanner, + [ContentBlockType.HighlightText]: BlockHighlightText, // Avo specific blocks [ContentBlockType.MediaGrid]: loadComponentFromConfig(ContentBlockType.MediaGrid), diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.editorconfig.ts b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.editorconfig.ts new file mode 100644 index 00000000..d2fea143 --- /dev/null +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.editorconfig.ts @@ -0,0 +1,57 @@ +import { BLOCK_FIELD_DEFAULTS, BLOCK_STATE_DEFAULTS, TEXT_FIELD } from '~content-blocks/defaults'; +import { GET_BACKGROUND_COLOR_OPTIONS_ARCHIEF } from '~modules/content-page/const/get-color-options.ts'; +import { + Color, + type ContentBlockConfig, + ContentBlockEditor, + ContentBlockType, + type DefaultContentBlockState, +} from '~modules/content-page/types/content-block.types'; +import { RICH_TEXT_EDITOR_OPTIONS_FULL } from '~shared/consts/rich-text-editor.consts.ts'; +import { tText } from '~shared/helpers/translation-functions'; +import { validateRequiredValue } from '~shared/helpers/validation.ts'; +import { HET_ARCHIEF } from '~shared/types'; + +const INITIAL_CONTENT_HIGHLIGHT_TEXT_COMPONENTS_STATE = () => ({ + content: '', + backgroundColor: Color.Transparent, + highlightColor: Color.OceanGreen, +}); + +export const INITIAL_CONTENT_HIGHLIGHT_TEXT_BLOCK_STATE = (): DefaultContentBlockState => ({ + ...BLOCK_STATE_DEFAULTS(), + fullWidth: true, +}); + +export const CONTENT_HIGHLIGHT_TEXT_CONFIG = (position = 0): ContentBlockConfig => ({ + position, + name: tText('Highlight text'), + type: ContentBlockType.HighlightText, + components: { + state: INITIAL_CONTENT_HIGHLIGHT_TEXT_COMPONENTS_STATE(), + fields: { + content: TEXT_FIELD({ + editorType: ContentBlockEditor.RICH_TEXT_EDITOR, + editorProps: { + controls: RICH_TEXT_EDITOR_OPTIONS_FULL, + }, + validator: (value: string) => + validateRequiredValue(value, tText('Tekst is verplicht', undefined, [HET_ARCHIEF])), + }), + highlightColor: { + label: tText('Highlightkleur', undefined, [HET_ARCHIEF]), + editorType: ContentBlockEditor.ColorSelect, + editorProps: { + options: GET_BACKGROUND_COLOR_OPTIONS_ARCHIEF(), + defaultValue: GET_BACKGROUND_COLOR_OPTIONS_ARCHIEF()[2], + }, + }, + }, + }, + block: { + state: INITIAL_CONTENT_HIGHLIGHT_TEXT_BLOCK_STATE(), + fields: { + ...BLOCK_FIELD_DEFAULTS(), + }, + }, +}); diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx new file mode 100644 index 00000000..e6f7a9a2 --- /dev/null +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx @@ -0,0 +1,65 @@ +import React, { type CSSProperties, type FunctionComponent, type ReactElement } from 'react'; +import type { DefaultComponentProps } from '~modules/shared/types/components'; +import './BlockHighligtText.scss'; +import { Container } from '@viaa/avo2-components'; +import clsx from 'clsx'; +import { BlockHeading } from '~content-blocks/BlockHeading'; +import type { AlignOption } from '~modules/content-page/types/content-block.types'; +import { ContentPageWidth } from '~modules/content-page/types/content-pages.types.ts'; +import Html from '~shared/components/Html/Html.tsx'; + +export interface BlockHighlightTextProps extends DefaultComponentProps { + content: string; + highlightColor: string; + backgroundColor: string; + pageWidth?: string; +} + +export const BlockHighlightText: FunctionComponent = ({ + content, + highlightColor, + backgroundColor, + pageWidth, +}): ReactElement => { + return ( +
+ + {/* No-op on desktop (patterns sit beside the text via CSS); on mobile this + is the clip box that caps the pattern at 1/3 screen width - see .scss. + Placed here (before the title) only for mobile's source order. */} +
+ + {/* Carries the highlight background-color and the text's own breathing-room + padding, separate from &__content's padding (which only reserves space + for the patterns above/below it on mobile) - see .scss. */} + +
+ + +
+ ); +}; diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss new file mode 100644 index 00000000..60ff404d --- /dev/null +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss @@ -0,0 +1,224 @@ +@use "sass:math"; +@use "sass:list"; +@use "../../../../shared/styles/settings/variables" as variables; +@use "../../../../shared/styles/settings/colors" as colors; + +// Shared shape sizing for the decorative pattern strips (used by --left and --right). +$pattern-height: 12rem; +// Mobile shrinks the strip - shapes are still drawn at the desktop $pattern-height (so +// they keep their proportions) and scaled down visually via transform, see &__pattern. +$pattern-height-mobile: 8rem; +$pattern-mobile-scale: math.div($pattern-height-mobile, $pattern-height); + +$pattern-rect-width: math.div($pattern-height, 2); +$pattern-circle-diameter: $pattern-height; +$pattern-circle-radius: math.div($pattern-circle-diameter, 2); +$pattern-circle-antialias-edge: $pattern-circle-radius + 0.02rem; +$pattern-cycle-width: $pattern-rect-width * 2 + $pattern-circle-diameter * 3; +// Must stay a multiple of $pattern-cycle-width (see &__pattern) - generous safety margin +// so the strip always reaches this component's own edge on any realistic viewport. +$pattern-repeat-count: 20; +// How far the mobile pattern slots overshoot &__content's edge, so they can still bleed +// past avo2 Container's (smaller, but non-zero) gutter to the true screen edge. +$pattern-mobile-overshoot: 15rem; + +// Renders the repeating tile (3 circles + 2 rects) for one pattern strip. $circle-centers +// is a 3-item list of x-offsets; $rect-ranges is a 2-item list of [start, end] pairs. Both +// sides share this shape/size - only the ORDER (which offsets are passed in) differs. +@mixin pattern-tile($circle-centers, $rect-ranges) { + background-image: + radial-gradient( + circle at list.nth($circle-centers, 1) 50%, + var(--pattern-color, transparent) $pattern-circle-radius, + transparent $pattern-circle-antialias-edge + ), + radial-gradient( + circle at list.nth($circle-centers, 2) 50%, + var(--pattern-color, transparent) $pattern-circle-radius, + transparent $pattern-circle-antialias-edge + ), + radial-gradient( + circle at list.nth($circle-centers, 3) 50%, + var(--pattern-color, transparent) $pattern-circle-radius, + transparent $pattern-circle-antialias-edge + ), + linear-gradient( + to right, + transparent 0 list.nth(list.nth($rect-ranges, 1), 1), + var(--pattern-color, transparent) list.nth(list.nth($rect-ranges, 1), 1) list.nth(list.nth($rect-ranges, 1), 2), + transparent list.nth(list.nth($rect-ranges, 1), 2) list.nth(list.nth($rect-ranges, 2), 1), + var(--pattern-color, transparent) list.nth(list.nth($rect-ranges, 2), 1) list.nth(list.nth($rect-ranges, 2), 2), + // Without this trailing stop, a plain linear-gradient holds its LAST color for + // the rest of the gradient's length - the 2nd rect would bleed solid color all + // the way to the tile's edge instead of stopping, when that edge doesn't happen + // to land exactly on $pattern-cycle-width. + transparent list.nth(list.nth($rect-ranges, 2), 2) $pattern-cycle-width + ); +} + +.c-block-highlight-text { + // The pattern strips escape all the way to this element's edges (and beyond) - clip + // them here. clip-path (not overflow-x: hidden) avoids the overflow-x/y coupling rule + // that would otherwise turn overflow-y into "auto" and risk a vertical scrollbar. + clip-path: inset(0); + width: 100% !important; + max-width: none !important; + + @media (max-width: variables.$g-bp4) { + padding: 0 calc(#{variables.$g-spacer-unit} * 2.5) !important; + } + + // Purely a positioning/reservation box - carries no background of its own, so the + // space it reserves for the patterns (see &-inner below and &__pattern-slot) stays + // visually "outside" the highlighted box, which is what the patterns need to touch. + &__content { + position: relative; + width: 100%; + display: flex; + flex-direction: column; + padding: 0; + + @media (max-width: variables.$g-bp4) { + // Reserves normal-flow space for the pattern slots above/below the text, sized + // to match &__pattern-slot's height exactly (so the patterns touch &-inner with + // no gap) - without this, .c-block-highlight-text's clip-path would end right at + // &-inner and clip the slots away entirely. + padding: $pattern-height-mobile 0; + } + + // The actual highlighted box - its own padding gives the text breathing room from + // the box's edges, independent of &__content's padding above (which only reserves + // space for the patterns). This is the box the patterns sit flush against. + &-text { + position: relative; + min-height: $pattern-height; + background-color: var(--pattern-color, transparent); + padding: variables.$g-spacer-unit * 6; + + font-family: "SofiaPro"; + font-size: 18px; + font-style: normal; + font-weight: 500; + line-height: 28px; + + @media (max-width: variables.$g-bp4) { + padding: calc(#{variables.$g-spacer-unit} * 3) calc(#{variables.$g-spacer-unit} * 2.5) !important; + margin: -1px 0; // avoiding some minor space between the pattern and the text + } + } + } + + // Patterns sit flush against the text box's own edge (not Container's) - touching, no gap - + // regardless of alignment or actual text width. + // + // width MUST be a multiple of $pattern-cycle-width: Chrome has a rendering bug where a + // repeating multi-layer gradient tears in the middle of the element when its width + // isn't an exact multiple of background-size (100vw reproduces this reliably). + // + // --left/--right are two DIFFERENT tiles (not one tile phase-shifted via + // background-position - that proved unreliable), each built so the desired near-text + // shape sits naturally at the tile's own edge, anchored via a plain + // background-position-x: right / left (no offset): + // - left: rect, circle, circle, rect, [circle, rect, circle, circle, rect, ...] + // - right: circle, circle, rect, circle, [rect, circle, circle, rect, circle, ...] + &__pattern { + position: absolute; + // Always drawn at the desktop $pattern-height so the shapes keep their proportions - + // the mobile media query below shrinks the whole box (incl. its background) visually + // via transform instead of redrawing it at a smaller size. + height: $pattern-height; + width: $pattern-cycle-width * $pattern-repeat-count; + background-repeat: repeat-x; + background-position-y: top; + background-size: $pattern-cycle-width $pattern-height; + + // Aligned to &__content's top edge, not stretched to its full (padded) height. + &--left { + top: 0; + right: 100%; + background-position-x: right; + @include pattern-tile( + ( + $pattern-rect-width + $pattern-circle-radius, + $pattern-rect-width + $pattern-circle-diameter + $pattern-circle-radius, + $pattern-rect-width * 2 + $pattern-circle-diameter * 2 + $pattern-circle-radius, + ), + ( + (0, $pattern-rect-width), + ($pattern-rect-width + $pattern-circle-diameter * 2, $pattern-rect-width * 2 + $pattern-circle-diameter * 2), + ) + ); + } + + // Aligned to &__content's bottom edge, not stretched from its full (padded) height. + &--right { + bottom: 0; + left: 100%; + background-position-x: left; + @include pattern-tile( + ( + $pattern-circle-radius, + $pattern-circle-diameter + $pattern-circle-radius, + $pattern-circle-diameter * 2 + $pattern-rect-width + $pattern-circle-radius, + ), + ( + ($pattern-circle-diameter * 2, $pattern-circle-diameter * 2 + $pattern-rect-width), + ($pattern-circle-diameter * 3 + $pattern-rect-width, $pattern-cycle-width), + ) + ); + } + + // Below $g-bp4 the patterns move from beside the text to above/below it (see + // &__pattern-slot). Same tile/anchor as desktop - only the anchor's landmark moves + // from "near text" to the slot's inner (40% or 30%) edge. The whole box (drawn at + // desktop size) is scaled down to $pattern-height-mobile, anchored at the corner + // that must stay flush against the slot's edges. + @media (max-width: variables.$g-bp4) { + transform: scale($pattern-mobile-scale); + + &--left { + right: 0; + left: auto; + transform-origin: top right; + } + + &--right { + left: 0; + right: auto; + transform-origin: bottom left; + } + } + } + + // No-op on desktop (unpositioned - &__pattern's position: absolute reaches past it to + // &__content). Below $g-bp4 it's the positioned, overflow-clipping box that caps + // &__pattern at exactly 40% (top) / 30% (bottom) of &__content's width. This cap can't + // live on &__pattern itself: capping its width there would make it a non-multiple of + // $pattern-cycle-width again (see the Chrome bug above). + &__pattern-slot { + @media (max-width: variables.$g-bp4) { + position: absolute; + overflow: hidden; + height: $pattern-height-mobile; + + // top/bottom: 0 land exactly against &__content's reserved padding (which is + // now sized to $pattern-height-mobile with no extra gap - see &__content), so + // the slot's inner edge touches the text with no offsets needed to resolve. + // + // left/right overshoot by $pattern-mobile-overshoot to reach past avo2 + // Container's gutter to the true screen edge; width compensates so the INNER + // edge (the anchor landmark) stays exactly at 40% / 30%. + &--top { + top: 0; + left: -$pattern-mobile-overshoot; + width: calc(40% + #{$pattern-mobile-overshoot}); + } + + &--bottom { + bottom: 0; + right: -$pattern-mobile-overshoot; + width: calc(30% + #{$pattern-mobile-overshoot}); + } + } + } +} diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/index.ts b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/index.ts new file mode 100644 index 00000000..e05950ce --- /dev/null +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/index.ts @@ -0,0 +1,2 @@ +export * from './BlockHighlightText.editorconfig.ts'; +export { BlockHighlightText } from './BlockHighlightText.tsx'; diff --git a/ui/src/react-admin/modules/content-page/const/content-block-config-map.ts b/ui/src/react-admin/modules/content-page/const/content-block-config-map.ts index b65c1214..468e50f5 100644 --- a/ui/src/react-admin/modules/content-page/const/content-block-config-map.ts +++ b/ui/src/react-admin/modules/content-page/const/content-block-config-map.ts @@ -11,6 +11,7 @@ import { EVENTBRITE_BLOCK_CONFIG } from '~content-blocks/BlockEventbrite'; import { HEADING_BLOCK_CONFIG } from '~content-blocks/BlockHeading'; import { HET_ARCHIEF_HEADER_SEARCH_BLOCK_CONFIG } from '~content-blocks/BlockHetArchiefHeaderSearch/BlockHetArchiefHeaderSearch.editorconfig'; import { HET_ARCHIEF_IMAGE_TEXT_BACKGROUND_BLOCK_CONFIG } from '~content-blocks/BlockHetArchiefImageTextBackground'; +import { CONTENT_HIGHLIGHT_TEXT_CONFIG } from '~content-blocks/BlockHighlightText'; import { CONTENT_HOMEPAGE_BANNER_CONFIG } from '~content-blocks/BlockHomepageBanner'; import { IFRAME_BLOCK_CONFIG } from '~content-blocks/BlockIFrame'; import { IMAGE_BLOCK_CONFIG } from '~content-blocks/BlockImage'; @@ -89,4 +90,5 @@ export const CONTENT_BLOCK_CONFIG_MAP: Record< [ContentBlockType.ScrollDownNudge]: CONTENT_SCROLL_DOWN_NUDGE_CONFIG, [ContentBlockType.OverviewWithCarousel]: CONTENT_OVERVIEW_WITH_CAROUSEL_CONFIG, [ContentBlockType.HomepageBanner]: CONTENT_HOMEPAGE_BANNER_CONFIG, + [ContentBlockType.HighlightText]: CONTENT_HIGHLIGHT_TEXT_CONFIG, }; diff --git a/ui/src/react-admin/modules/content-page/const/content-block-initial-state-map.ts b/ui/src/react-admin/modules/content-page/const/content-block-initial-state-map.ts index 2f689b91..41661234 100644 --- a/ui/src/react-admin/modules/content-page/const/content-block-initial-state-map.ts +++ b/ui/src/react-admin/modules/content-page/const/content-block-initial-state-map.ts @@ -11,6 +11,7 @@ import { INITIAL_EVENTBRITE_COMPONENTS_STATE } from '~content-blocks/BlockEventb import { INITIAL_HEADING_COMPONENTS_STATE } from '~content-blocks/BlockHeading'; import { INITIAL_HET_ARCHIEF_HEADER_SEARCH_BLOCK_STATE } from '~content-blocks/BlockHetArchiefHeaderSearch/BlockHetArchiefHeaderSearch.editorconfig'; import { INITIAL_HET_ARCHIEF_IMAGE_TEXT_BACKGROUND_BLOCK_STATE } from '~content-blocks/BlockHetArchiefImageTextBackground'; +import { INITIAL_CONTENT_HIGHLIGHT_TEXT_BLOCK_STATE } from '~content-blocks/BlockHighlightText'; import { INITIAL_CONTENT_HOMEPAGE_BANNER_BLOCK_STATE } from '~content-blocks/BlockHomepageBanner'; import { INITIAL_IFRAME_COMPONENTS_STATE } from '~content-blocks/BlockIFrame'; import { INITIAL_IMAGE_COMPONENTS_STATE } from '~content-blocks/BlockImage'; @@ -92,4 +93,5 @@ export const CONTENT_BLOCK_INITIAL_STATE_MAP: { [ContentBlockType.ScrollDownNudge]: INITIAL_SCROLL_DOWN_NUDGE_BLOCK_STATE, [ContentBlockType.OverviewWithCarousel]: INITIAL_CONTENT_OVERVIEW_WITH_CAROUSEL_BLOCK_STATE, [ContentBlockType.HomepageBanner]: INITIAL_CONTENT_HOMEPAGE_BANNER_BLOCK_STATE, + [ContentBlockType.HighlightText]: INITIAL_CONTENT_HIGHLIGHT_TEXT_BLOCK_STATE, }; diff --git a/ui/src/react-admin/modules/content-page/const/get-content-block-type-options.ts b/ui/src/react-admin/modules/content-page/const/get-content-block-type-options.ts index e4c81af7..e06d5e6f 100644 --- a/ui/src/react-admin/modules/content-page/const/get-content-block-type-options.ts +++ b/ui/src/react-admin/modules/content-page/const/get-content-block-type-options.ts @@ -186,12 +186,20 @@ export const GET_CONTENT_BLOCK_TYPE_OPTIONS: () => SelectOption[] = () = }, { label: tText( - 'modules/content-page/const/get-content-block-type-options___homepage=-banner', + 'modules/content-page/const/get-content-block-type-options___homepage-banner', {}, [HET_ARCHIEF] ), value: ContentBlockType.HomepageBanner, }, + { + label: tText( + 'modules/content-page/const/get-content-block-type-options___highlight-text', + {}, + [HET_ARCHIEF] + ), + value: ContentBlockType.HighlightText, + }, ]; // Only show the content blocks that the client enabled through the config object diff --git a/ui/src/react-admin/modules/content-page/types/content-block.types.ts b/ui/src/react-admin/modules/content-page/types/content-block.types.ts index 28ec3b58..f190659c 100644 --- a/ui/src/react-admin/modules/content-page/types/content-block.types.ts +++ b/ui/src/react-admin/modules/content-page/types/content-block.types.ts @@ -188,6 +188,7 @@ export enum ContentBlockType { ScrollDownNudge = 'SCROLL_DOWN_NUDGE', OverviewWithCarousel = 'OVERVIEW_WITH_CAROUSEL', HomepageBanner = 'HOMEPAGE_BANNER', + HighlightText = 'HIGHLIGHT_TEXT', } export enum ContentBlockEditor { From a07c9ba503b9d65aab31d61e3917c02046fdf3a6 Mon Sep 17 00:00:00 2001 From: Femke Reunes Date: Tue, 28 Jul 2026 16:09:36 +0200 Subject: [PATCH 2/5] ARC-3793: Adding mixins for common stuff --- .../BlockHighlightText/BlockHighligtText.scss | 157 +++++------------- .../BlockHomepageBanner.scss | 140 +++++----------- .../BlockOverviewWithCarousel.scss | 18 +- .../shared/styles/mixins/_pattern.scss | 75 +++++++++ .../shared/styles/mixins/_typography.scss | 49 ++++++ .../shared/styles/utilities/_text.scss | 5 +- 6 files changed, 216 insertions(+), 228 deletions(-) create mode 100644 ui/src/react-admin/modules/shared/styles/mixins/_pattern.scss create mode 100644 ui/src/react-admin/modules/shared/styles/mixins/_typography.scss diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss index 60ff404d..a36b2eaf 100644 --- a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss @@ -1,65 +1,28 @@ @use "sass:math"; -@use "sass:list"; @use "../../../../shared/styles/settings/variables" as variables; -@use "../../../../shared/styles/settings/colors" as colors; +@use "../../../../shared/styles/mixins/pattern" as pattern; +@use "../../../../shared/styles/mixins/typography" as typography; -// Shared shape sizing for the decorative pattern strips (used by --left and --right). $pattern-height: 12rem; -// Mobile shrinks the strip - shapes are still drawn at the desktop $pattern-height (so -// they keep their proportions) and scaled down visually via transform, see &__pattern. +// Mobile shrinks the strip - shapes are still drawn at the desktop $pattern-height (so they +// keep their proportions) and scaled down visually via transform, see &__pattern. $pattern-height-mobile: 8rem; $pattern-mobile-scale: math.div($pattern-height-mobile, $pattern-height); - -$pattern-rect-width: math.div($pattern-height, 2); -$pattern-circle-diameter: $pattern-height; -$pattern-circle-radius: math.div($pattern-circle-diameter, 2); -$pattern-circle-antialias-edge: $pattern-circle-radius + 0.02rem; -$pattern-cycle-width: $pattern-rect-width * 2 + $pattern-circle-diameter * 3; -// Must stay a multiple of $pattern-cycle-width (see &__pattern) - generous safety margin -// so the strip always reaches this component's own edge on any realistic viewport. $pattern-repeat-count: 20; -// How far the mobile pattern slots overshoot &__content's edge, so they can still bleed -// past avo2 Container's (smaller, but non-zero) gutter to the true screen edge. +// How far the mobile pattern slots overshoot &__content's edge, so they can bleed past avo2 +// Container's (smaller, but non-zero) mobile gutter to the true screen edge. $pattern-mobile-overshoot: 15rem; -// Renders the repeating tile (3 circles + 2 rects) for one pattern strip. $circle-centers -// is a 3-item list of x-offsets; $rect-ranges is a 2-item list of [start, end] pairs. Both -// sides share this shape/size - only the ORDER (which offsets are passed in) differs. -@mixin pattern-tile($circle-centers, $rect-ranges) { - background-image: - radial-gradient( - circle at list.nth($circle-centers, 1) 50%, - var(--pattern-color, transparent) $pattern-circle-radius, - transparent $pattern-circle-antialias-edge - ), - radial-gradient( - circle at list.nth($circle-centers, 2) 50%, - var(--pattern-color, transparent) $pattern-circle-radius, - transparent $pattern-circle-antialias-edge - ), - radial-gradient( - circle at list.nth($circle-centers, 3) 50%, - var(--pattern-color, transparent) $pattern-circle-radius, - transparent $pattern-circle-antialias-edge - ), - linear-gradient( - to right, - transparent 0 list.nth(list.nth($rect-ranges, 1), 1), - var(--pattern-color, transparent) list.nth(list.nth($rect-ranges, 1), 1) list.nth(list.nth($rect-ranges, 1), 2), - transparent list.nth(list.nth($rect-ranges, 1), 2) list.nth(list.nth($rect-ranges, 2), 1), - var(--pattern-color, transparent) list.nth(list.nth($rect-ranges, 2), 1) list.nth(list.nth($rect-ranges, 2), 2), - // Without this trailing stop, a plain linear-gradient holds its LAST color for - // the rest of the gradient's length - the 2nd rect would bleed solid color all - // the way to the tile's edge instead of stopping, when that edge doesn't happen - // to land exactly on $pattern-cycle-width. - transparent list.nth(list.nth($rect-ranges, 2), 2) $pattern-cycle-width - ); -} +// Used below to build each side's shape-order (circle-centers/rect-ranges) - pattern-tile +// derives the rest (radius, antialias edge, cycle width) from $pattern-height itself. +$pattern-rect-width: pattern.pattern-rect-width($pattern-height); +$pattern-circle-diameter: $pattern-height; +$pattern-circle-radius: pattern.pattern-circle-radius($pattern-height); +$pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); .c-block-highlight-text { - // The pattern strips escape all the way to this element's edges (and beyond) - clip - // them here. clip-path (not overflow-x: hidden) avoids the overflow-x/y coupling rule - // that would otherwise turn overflow-y into "auto" and risk a vertical scrollbar. + // clip-path (not overflow-x: hidden) avoids the overflow-x/y coupling rule that would + // otherwise turn overflow-y into "auto" and risk a vertical scrollbar. clip-path: inset(0); width: 100% !important; max-width: none !important; @@ -68,9 +31,9 @@ $pattern-mobile-overshoot: 15rem; padding: 0 calc(#{variables.$g-spacer-unit} * 2.5) !important; } - // Purely a positioning/reservation box - carries no background of its own, so the - // space it reserves for the patterns (see &-inner below and &__pattern-slot) stays - // visually "outside" the highlighted box, which is what the patterns need to touch. + // Reservation box only - carries no background of its own, so the space it reserves for + // the patterns (mobile only, see &-text and &__pattern-slot) reads as "outside" &-text, + // which is what the patterns need to touch. &__content { position: relative; width: 100%; @@ -79,65 +42,44 @@ $pattern-mobile-overshoot: 15rem; padding: 0; @media (max-width: variables.$g-bp4) { - // Reserves normal-flow space for the pattern slots above/below the text, sized - // to match &__pattern-slot's height exactly (so the patterns touch &-inner with - // no gap) - without this, .c-block-highlight-text's clip-path would end right at - // &-inner and clip the slots away entirely. + // Sized to match &__pattern-slot's height exactly so the patterns touch &-text with + // no gap - without this, .c-block-highlight-text's clip-path would cut the slots away. padding: $pattern-height-mobile 0; } - // The actual highlighted box - its own padding gives the text breathing room from - // the box's edges, independent of &__content's padding above (which only reserves - // space for the patterns). This is the box the patterns sit flush against. + // The actual highlighted box - its own padding gives the text breathing room from the + // box's edges, independent of &__content's (pattern-reservation-only) padding above. &-text { position: relative; min-height: $pattern-height; background-color: var(--pattern-color, transparent); padding: variables.$g-spacer-unit * 6; - - font-family: "SofiaPro"; - font-size: 18px; - font-style: normal; - font-weight: 500; - line-height: 28px; + @include typography.sofia-pro-body(); @media (max-width: variables.$g-bp4) { padding: calc(#{variables.$g-spacer-unit} * 3) calc(#{variables.$g-spacer-unit} * 2.5) !important; - margin: -1px 0; // avoiding some minor space between the pattern and the text + margin: -1px 0; // closes a hairline gap between the pattern and the text } } } - // Patterns sit flush against the text box's own edge (not Container's) - touching, no gap - - // regardless of alignment or actual text width. - // - // width MUST be a multiple of $pattern-cycle-width: Chrome has a rendering bug where a - // repeating multi-layer gradient tears in the middle of the element when its width - // isn't an exact multiple of background-size (100vw reproduces this reliably). - // - // --left/--right are two DIFFERENT tiles (not one tile phase-shifted via - // background-position - that proved unreliable), each built so the desired near-text - // shape sits naturally at the tile's own edge, anchored via a plain - // background-position-x: right / left (no offset): - // - left: rect, circle, circle, rect, [circle, rect, circle, circle, rect, ...] - // - right: circle, circle, rect, circle, [rect, circle, circle, rect, circle, ...] + // --left/--right are two DIFFERENT tiles (not one tile phase-shifted via background-position + // - that proved unreliable), each built so the desired near-text shape sits naturally at the + // tile's own edge, anchored via a plain background-position-x: right / left (no offset): + // - left: circle, rect, circle, circle, rect (nearest text -> farthest) + // - right: circle, circle, rect, circle, rect (nearest text -> farthest) &__pattern { - position: absolute; + @include pattern.pattern-strip($pattern-height, $pattern-repeat-count); // Always drawn at the desktop $pattern-height so the shapes keep their proportions - - // the mobile media query below shrinks the whole box (incl. its background) visually - // via transform instead of redrawing it at a smaller size. + // the mobile media query below shrinks the whole box (incl. background) via transform. height: $pattern-height; - width: $pattern-cycle-width * $pattern-repeat-count; - background-repeat: repeat-x; - background-position-y: top; - background-size: $pattern-cycle-width $pattern-height; - // Aligned to &__content's top edge, not stretched to its full (padded) height. + // Aligned to &-text's top edge, not stretched to &__content's full (padded) height. &--left { top: 0; right: 100%; background-position-x: right; - @include pattern-tile( + @include pattern.pattern-tile( ( $pattern-rect-width + $pattern-circle-radius, $pattern-rect-width + $pattern-circle-diameter + $pattern-circle-radius, @@ -146,16 +88,17 @@ $pattern-mobile-overshoot: 15rem; ( (0, $pattern-rect-width), ($pattern-rect-width + $pattern-circle-diameter * 2, $pattern-rect-width * 2 + $pattern-circle-diameter * 2), - ) + ), + $pattern-height ); } - // Aligned to &__content's bottom edge, not stretched from its full (padded) height. + // Aligned to &-text's bottom edge, not stretched from &__content's full (padded) height. &--right { bottom: 0; left: 100%; background-position-x: left; - @include pattern-tile( + @include pattern.pattern-tile( ( $pattern-circle-radius, $pattern-circle-diameter + $pattern-circle-radius, @@ -164,15 +107,14 @@ $pattern-mobile-overshoot: 15rem; ( ($pattern-circle-diameter * 2, $pattern-circle-diameter * 2 + $pattern-rect-width), ($pattern-circle-diameter * 3 + $pattern-rect-width, $pattern-cycle-width), - ) + ), + $pattern-height ); } - // Below $g-bp4 the patterns move from beside the text to above/below it (see - // &__pattern-slot). Same tile/anchor as desktop - only the anchor's landmark moves - // from "near text" to the slot's inner (40% or 30%) edge. The whole box (drawn at - // desktop size) is scaled down to $pattern-height-mobile, anchored at the corner - // that must stay flush against the slot's edges. + // Below $g-bp4 the pattern moves from beside the text to above/below it (see + // &__pattern-slot) - drawn at desktop size and scaled down, anchored at the corner that + // must stay flush against the slot's edges. @media (max-width: variables.$g-bp4) { transform: scale($pattern-mobile-scale); @@ -190,24 +132,17 @@ $pattern-mobile-overshoot: 15rem; } } - // No-op on desktop (unpositioned - &__pattern's position: absolute reaches past it to - // &__content). Below $g-bp4 it's the positioned, overflow-clipping box that caps - // &__pattern at exactly 40% (top) / 30% (bottom) of &__content's width. This cap can't - // live on &__pattern itself: capping its width there would make it a non-multiple of - // $pattern-cycle-width again (see the Chrome bug above). + // No-op on desktop. Below $g-bp4 it's the positioned, overflow-clipping box that caps + // &__pattern at 40% (top) / 30% (bottom) of &__content's width - the cap can't live on + // &__pattern itself without breaking the Chrome tiling-bug workaround above. &__pattern-slot { @media (max-width: variables.$g-bp4) { position: absolute; overflow: hidden; height: $pattern-height-mobile; - // top/bottom: 0 land exactly against &__content's reserved padding (which is - // now sized to $pattern-height-mobile with no extra gap - see &__content), so - // the slot's inner edge touches the text with no offsets needed to resolve. - // - // left/right overshoot by $pattern-mobile-overshoot to reach past avo2 - // Container's gutter to the true screen edge; width compensates so the INNER - // edge (the anchor landmark) stays exactly at 40% / 30%. + // left/right overshoot to reach past avo2 Container's gutter to the true screen + // edge; width compensates so the inner (anchor) edge stays exactly at 40% / 30%. &--top { top: 0; left: -$pattern-mobile-overshoot; diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHomepageBanner/BlockHomepageBanner.scss b/ui/src/react-admin/modules/content-page/components/blocks/BlockHomepageBanner/BlockHomepageBanner.scss index ab1de890..356808c0 100644 --- a/ui/src/react-admin/modules/content-page/components/blocks/BlockHomepageBanner/BlockHomepageBanner.scss +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHomepageBanner/BlockHomepageBanner.scss @@ -1,58 +1,27 @@ -@use "sass:math"; -@use "sass:list"; @use "../../../../shared/styles/settings/variables" as variables; @use "../../../../shared/styles/settings/colors" as colors; +@use "../../../../shared/styles/mixins/pattern" as pattern; +@use "../../../../shared/styles/mixins/typography" as typography; -// Shared shape sizing for the decorative pattern strips (used by --left and --right). $pattern-height: 8rem; $pattern-distance-vertical: variables.$g-spacer-unit * 3; $pattern-distance-horizontal: variables.$g-spacer-unit * 5; - -$pattern-rect-width: 4rem; -$pattern-circle-diameter: $pattern-height; -$pattern-circle-radius: math.div($pattern-circle-diameter, 2); -$pattern-circle-antialias-edge: $pattern-circle-radius + 0.02rem; -$pattern-cycle-width: $pattern-rect-width * 2 + $pattern-circle-diameter * 3; -// Must stay a multiple of $pattern-cycle-width (see &__pattern) - generous safety margin -// so the strip always reaches this component's own edge on any realistic viewport. +// Generous enough that the strip always reaches this component's own edge on any viewport. $pattern-repeat-count: 20; -// How far the mobile pattern slots overshoot &__content's edge, so they can still bleed -// past avo2 Container's (smaller, but non-zero) gutter to the true screen edge. +// How far the mobile pattern slots overshoot &__content's edge, so they can bleed past avo2 +// Container's (smaller, but non-zero) mobile gutter to the true screen edge. $pattern-mobile-overshoot: 10rem; -// Renders the repeating tile (3 circles + 2 rects) for one pattern strip. $circle-centers -// is a 3-item list of x-offsets; $rect-ranges is a 2-item list of [start, end] pairs. Both -// sides share this shape/size - only the ORDER (which offsets are passed in) differs. -@mixin pattern-tile($circle-centers, $rect-ranges) { - background-image: - radial-gradient( - circle at list.nth($circle-centers, 1) 50%, - var(--pattern-color, transparent) $pattern-circle-radius, - transparent $pattern-circle-antialias-edge - ), - radial-gradient( - circle at list.nth($circle-centers, 2) 50%, - var(--pattern-color, transparent) $pattern-circle-radius, - transparent $pattern-circle-antialias-edge - ), - radial-gradient( - circle at list.nth($circle-centers, 3) 50%, - var(--pattern-color, transparent) $pattern-circle-radius, - transparent $pattern-circle-antialias-edge - ), - linear-gradient( - to right, - transparent 0 list.nth(list.nth($rect-ranges, 1), 1), - var(--pattern-color, transparent) list.nth(list.nth($rect-ranges, 1), 1) list.nth(list.nth($rect-ranges, 1), 2), - transparent list.nth(list.nth($rect-ranges, 1), 2) list.nth(list.nth($rect-ranges, 2), 1), - var(--pattern-color, transparent) list.nth(list.nth($rect-ranges, 2), 1) list.nth(list.nth($rect-ranges, 2), 2) - ); -} +// Used below to build each side's shape-order (circle-centers/rect-ranges) - pattern-tile +// derives the rest (radius, antialias edge, cycle width) from $pattern-height itself. +$pattern-rect-width: pattern.pattern-rect-width($pattern-height); +$pattern-circle-diameter: $pattern-height; +$pattern-circle-radius: pattern.pattern-circle-radius($pattern-height); +$pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); .c-block-homepage-banner { - // The pattern strips escape all the way to this element's edges (and beyond) - clip - // them here. clip-path (not overflow-x: hidden) avoids the overflow-x/y coupling rule - // that would otherwise turn overflow-y into "auto" and risk a vertical scrollbar. + // clip-path (not overflow-x: hidden) avoids the overflow-x/y coupling rule that would + // otherwise turn overflow-y into "auto" and risk a vertical scrollbar. clip-path: inset(0); width: 100% !important; max-width: none !important; @@ -65,13 +34,11 @@ $pattern-mobile-overshoot: 10rem; flex-direction: column; @media (max-width: variables.$g-bp4) { - // Mobile pattern slots size themselves as a % of this element, so it needs to - // span the full screen width here, not the desktop 75% cap. + // Mobile pattern slots size themselves as a % of this element, so it needs to span + // the full screen width here, not the desktop 75% cap. max-width: 100%; - // Reserves normal-flow space for the pattern slots above/below the text. - // &__pattern-slot is position: absolute so its height doesn't count towards - // this element's own height - without this padding, .c-block-homepage-banner's - // clip-path would end right at the text and clip the slots away entirely. + // &__pattern-slot is position: absolute so this is what reserves its space in + // normal flow - without it the clip-path above would cut the slots away entirely. padding: calc($pattern-height + $pattern-distance-vertical) 0; } @@ -90,7 +57,7 @@ $pattern-mobile-overshoot: 10rem; &--right { margin-left: auto; - .c-block-homepage-banner__pattern--left{ + .c-block-homepage-banner__pattern--left { right: calc(100% + $pattern-distance-horizontal + $pattern-distance-horizontal); } } @@ -98,55 +65,31 @@ $pattern-mobile-overshoot: 10rem; &-title { padding-bottom: variables.$g-spacer-unit * 4; color: colors.$color-gray-1000; - - /* H4 */ - font-family: "SofiaPro" !important; - font-size: 3.2rem !important; - font-style: normal !important; - font-weight: 800 !important; - line-height: 4rem !important; width: 100%; white-space: wrap; + @include typography.sofia-pro-heading-xl(true); } &-text { color: colors.$neutral; - - /* H4 */ - font-family: "SofiaPro" !important; - font-size: 1.8rem !important; - font-style: normal !important; - font-weight: 500 !important; - line-height: 2.8rem !important; + @include typography.sofia-pro-body(true); } } - // Anchored off the text box's own edge (not Container's), so the $pattern-distance-horizontal gap holds - // regardless of alignment or actual (up to 75%) text width. - // - // width MUST be a multiple of $pattern-cycle-width: Chrome has a rendering bug where a - // repeating multi-layer gradient tears in the middle of the element when its width - // isn't an exact multiple of background-size (100vw reproduces this reliably). - // - // --left/--right are two DIFFERENT tiles (not one tile phase-shifted via - // background-position - that proved unreliable), each built so the desired near-text - // shape sits naturally at the tile's own edge, anchored via a plain - // background-position-x: right / left (no offset): - // - left: rect, circle, rect, [circle, circle, rect, circle, ...] - // - right: circle, rect, circle, circle, [rect, circle, circle, rect, circle, ...] + // --left/--right are two DIFFERENT tiles (not one tile phase-shifted via background-position + // - that proved unreliable), each built so the desired near-text shape sits naturally at the + // tile's own edge, anchored via a plain background-position-x: right / left (no offset): + // - left: rect, circle, rect, circle, circle (nearest text -> farthest) + // - right: circle, rect, circle, circle, rect (nearest text -> farthest) &__pattern { - position: absolute; + @include pattern.pattern-strip($pattern-height, $pattern-repeat-count); top: 0; bottom: 0; - width: $pattern-cycle-width * $pattern-repeat-count; - background-repeat: repeat-x; - background-position-y: top; - background-size: $pattern-cycle-width $pattern-height; &--left { right: calc(100% + $pattern-distance-horizontal); background-position-x: right; - @include pattern-tile( + @include pattern.pattern-tile( ( $pattern-circle-radius, $pattern-circle-diameter + $pattern-circle-radius, @@ -155,14 +98,15 @@ $pattern-mobile-overshoot: 10rem; ( ($pattern-circle-diameter * 2, $pattern-circle-diameter * 2 + $pattern-rect-width), ($pattern-circle-diameter * 3 + $pattern-rect-width, $pattern-cycle-width), - ) + ), + $pattern-height ); } &--right { left: calc(100% + $pattern-distance-horizontal); background-position-x: left; - @include pattern-tile( + @include pattern.pattern-tile( ( $pattern-circle-radius, $pattern-circle-diameter + $pattern-rect-width + $pattern-circle-radius, @@ -171,14 +115,14 @@ $pattern-mobile-overshoot: 10rem; ( ($pattern-circle-diameter, $pattern-circle-diameter + $pattern-rect-width), ($pattern-circle-diameter * 3 + $pattern-rect-width, $pattern-cycle-width), - ) + ), + $pattern-height ); } // Below $g-bp4 the patterns move from beside the text to above/below it (see - // &__pattern-slot). Same tile/anchor as desktop - only the anchor's landmark moves - // from "near text" to the slot's inner (1/3 or 2/3) edge; top/bottom: 0 is already - // set above. + // &__pattern-slot) - same tile/anchor as desktop, only the anchor's landmark moves from + // "near text" to the slot's inner (1/3 or 2/3) edge; top/bottom: 0 already set above. @media (max-width: variables.$g-bp4) { &--left { right: 0; @@ -193,23 +137,17 @@ $pattern-mobile-overshoot: 10rem; } // No-op on desktop (unpositioned - &__pattern's position: absolute reaches past it to - // &__content). Below $g-bp4 it's the positioned, overflow-clipping box that caps - // &__pattern at exactly 1/3 (top) / 2/3 (bottom) of &__content's width. This cap can't - // live on &__pattern itself: capping its width there would make it a non-multiple of - // $pattern-cycle-width again (see the Chrome bug above). + // &__content). Below $g-bp4 it's the positioned, overflow-clipping box that caps &__pattern + // at exactly 1/3 (top) / 2/3 (bottom) of &__content's width - the cap can't live on + // &__pattern itself without breaking the Chrome tiling-bug workaround above. &__pattern-slot { @media (max-width: variables.$g-bp4) { position: absolute; overflow: hidden; height: $pattern-height; - // top/bottom: 0 land inside &__content's reserved padding, not against its - // content-box - literal zero offsets, so (unlike the desktop calc(100% + $pattern-distance-horizontal) - // trick) they don't need &__content to have a definite height to resolve. - // - // left/right overshoot by $pattern-mobile-overshoot to reach past avo2 - // Container's gutter to the true screen edge; width compensates so the INNER - // edge (the anchor landmark) stays exactly at 1/3 / 2/3. + // left/right overshoot to reach past avo2 Container's gutter to the true screen + // edge; width compensates so the inner (anchor) edge stays exactly at 1/3 / 2/3. &--top { top: 0; left: -$pattern-mobile-overshoot; diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockOverviewWithCarousel/BlockOverviewWithCarousel.scss b/ui/src/react-admin/modules/content-page/components/blocks/BlockOverviewWithCarousel/BlockOverviewWithCarousel.scss index 2277674d..cdde94b8 100644 --- a/ui/src/react-admin/modules/content-page/components/blocks/BlockOverviewWithCarousel/BlockOverviewWithCarousel.scss +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockOverviewWithCarousel/BlockOverviewWithCarousel.scss @@ -1,6 +1,7 @@ @use "sass:math"; @use "../../../../shared/styles/settings/variables" as variables; @use "../../../../shared/styles/settings/colors" as colors; +@use "../../../../shared/styles/mixins/typography" as typography; $slide-width: 28rem; @@ -22,13 +23,8 @@ $slide-width: 28rem; &-title { color: colors.$color-gray-1000; - font-family: "SofiaPro"; - font-size: 2.4rem; - font-style: normal; - font-weight: 800; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + @include typography.sofia-pro-heading-lg(); + @include typography.truncate(); width: fit-content; } @@ -178,7 +174,7 @@ $slide-width: 28rem; min-height: 0; display: flex; align-items: end; - padding: calc(variables.$g-spacer-unit * 2) calc(variables.$g-spacer-unit * 3); + padding: calc(#{variables.$g-spacer-unit} * 2) calc(#{variables.$g-spacer-unit} * 3); } &-text { @@ -191,11 +187,7 @@ $slide-width: 28rem; width: 100%; color: colors.$color-gray-1000; - font-family: "SofiaPro"; - font-size: 1.8rem; - font-style: normal; - font-weight: 800; - line-height: 2.8rem; + @include typography.sofia-pro-heading-md(); } } } diff --git a/ui/src/react-admin/modules/shared/styles/mixins/_pattern.scss b/ui/src/react-admin/modules/shared/styles/mixins/_pattern.scss new file mode 100644 index 00000000..ac1b37ee --- /dev/null +++ b/ui/src/react-admin/modules/shared/styles/mixins/_pattern.scss @@ -0,0 +1,75 @@ +@use "sass:math"; +@use "sass:list"; + +// Shared geometry for the repeating decorative pattern strip (3 circles + 2 rects) used by +// content blocks like BlockHomepageBanner and BlockHighlightText. Everything is derived from +// a single $height so blocks only need to tune one number to get a proportional strip. +@function pattern-rect-width($height) { + @return math.div($height, 2); +} + +@function pattern-circle-radius($height) { + @return math.div($height, 2); +} + +@function pattern-antialias-edge($height) { + @return pattern-circle-radius($height) + 0.02rem; +} + +// Must stay a multiple of this cycle width wherever it's used as a background-size: Chrome has +// a rendering bug where a repeating multi-layer gradient tears mid-strip when the element's +// width isn't an exact multiple of its background-size (100vw reproduces this reliably). +@function pattern-cycle-width($height) { + @return pattern-rect-width($height) * 2 + $height * 3; +} + +// Common base for a pattern strip element - position/tiling only, no shape or side (--left / +// --right) concerns. $repeat-count just needs to be generous enough that the strip always +// reaches the block's own edge on any realistic viewport. +@mixin pattern-strip($height, $repeat-count: 20) { + $cycle-width: pattern-cycle-width($height); + + position: absolute; + width: $cycle-width * $repeat-count; + background-repeat: repeat-x; + background-position-y: top; + background-size: $cycle-width $height; +} + +// Renders the repeating tile (3 circles + 2 rects) for one pattern strip. $circle-centers is a +// 3-item list of x-offsets; $rect-ranges is a 2-item list of [start, end] pairs, both in the +// same unit space as $height (see the pattern-* functions above). Two strips of the same +// $height can look completely different just by passing a different shape order here. +@mixin pattern-tile($circle-centers, $rect-ranges, $height) { + $circle-radius: pattern-circle-radius($height); + $antialias-edge: pattern-antialias-edge($height); + $cycle-width: pattern-cycle-width($height); + + background-image: + radial-gradient( + circle at list.nth($circle-centers, 1) 50%, + var(--pattern-color, transparent) $circle-radius, + transparent $antialias-edge + ), + radial-gradient( + circle at list.nth($circle-centers, 2) 50%, + var(--pattern-color, transparent) $circle-radius, + transparent $antialias-edge + ), + radial-gradient( + circle at list.nth($circle-centers, 3) 50%, + var(--pattern-color, transparent) $circle-radius, + transparent $antialias-edge + ), + linear-gradient( + to right, + transparent 0 list.nth(list.nth($rect-ranges, 1), 1), + var(--pattern-color, transparent) list.nth(list.nth($rect-ranges, 1), 1) list.nth(list.nth($rect-ranges, 1), 2), + transparent list.nth(list.nth($rect-ranges, 1), 2) list.nth(list.nth($rect-ranges, 2), 1), + var(--pattern-color, transparent) list.nth(list.nth($rect-ranges, 2), 1) list.nth(list.nth($rect-ranges, 2), 2), + // Without this trailing stop, a plain linear-gradient holds its LAST color for the rest + // of its length - the 2nd rect would bleed solid color all the way to the tile's edge + // instead of stopping, whenever that edge doesn't land exactly on $cycle-width. + transparent list.nth(list.nth($rect-ranges, 2), 2) $cycle-width + ); +} diff --git a/ui/src/react-admin/modules/shared/styles/mixins/_typography.scss b/ui/src/react-admin/modules/shared/styles/mixins/_typography.scss new file mode 100644 index 00000000..c1f3782d --- /dev/null +++ b/ui/src/react-admin/modules/shared/styles/mixins/_typography.scss @@ -0,0 +1,49 @@ +// Shared text stylings for the "SofiaPro" typeface used across content blocks. Centralised so +// the same size/weight combo (e.g. body copy) reads identically wherever it's used, instead of +// each block re-declaring its own font-family/size/weight/line-height by hand. +$font-family: "SofiaPro", sans-serif; + +@mixin sofia-pro($size, $weight: 500, $line-height: null, $important: false) { + @if $important { + font-family: $font-family !important; + font-size: $size !important; + font-style: normal !important; + font-weight: $weight !important; + @if $line-height { + line-height: $line-height !important; + } + } @else { + font-family: $font-family; + font-size: $size; + font-style: normal; + font-weight: $weight; + @if $line-height { + line-height: $line-height; + } + } +} + +// Presets for the combos already repeated across content blocks - reach for `sofia-pro(...)` +// directly instead if a new block needs a size/weight that doesn't match one of these. +@mixin sofia-pro-heading-xl($important: false) { + @include sofia-pro(3.2rem, 800, 4rem, $important); +} + +@mixin sofia-pro-heading-lg($important: false) { + @include sofia-pro(2.4rem, 800, null, $important); +} + +@mixin sofia-pro-heading-md($important: false) { + @include sofia-pro(1.8rem, 800, 2.8rem, $important); +} + +@mixin sofia-pro-body($important: false) { + @include sofia-pro(1.8rem, 500, 2.8rem, $important); +} + +// Single-line truncation with an ellipsis - shared with .u-truncate (utilities/_text.scss). +@mixin truncate() { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/ui/src/react-admin/modules/shared/styles/utilities/_text.scss b/ui/src/react-admin/modules/shared/styles/utilities/_text.scss index 42ad0782..f7e970bf 100644 --- a/ui/src/react-admin/modules/shared/styles/utilities/_text.scss +++ b/ui/src/react-admin/modules/shared/styles/utilities/_text.scss @@ -1,5 +1,6 @@ @use '../../styles/settings/colors' as colors; @use '../../styles/settings/variables' as variables; +@use '../mixins/typography' as typography; /* ========================================================================== Utility: Text @@ -59,7 +60,5 @@ $u-text-danger-color: colors.$color-error !default; } .u-truncate { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + @include typography.truncate(); } From a2131f50b4a5025abc54de97457371a3536ee36d Mon Sep 17 00:00:00 2001 From: Femke Reunes Date: Tue, 28 Jul 2026 16:23:05 +0200 Subject: [PATCH 3/5] ARC-3793: Optimize pattern mixin --- .../BlockHighlightText/BlockHighlightText.tsx | 2 - .../BlockHighlightText/BlockHighligtText.scss | 42 ++------ .../BlockHomepageBanner.scss | 46 ++------- .../BlockOverviewWithCarousel.scss | 9 +- .../shared/styles/mixins/_pattern.scss | 97 +++++++++++++++---- 5 files changed, 92 insertions(+), 104 deletions(-) diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx index e6f7a9a2..3c2c5810 100644 --- a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx @@ -3,8 +3,6 @@ import type { DefaultComponentProps } from '~modules/shared/types/components'; import './BlockHighligtText.scss'; import { Container } from '@viaa/avo2-components'; import clsx from 'clsx'; -import { BlockHeading } from '~content-blocks/BlockHeading'; -import type { AlignOption } from '~modules/content-page/types/content-block.types'; import { ContentPageWidth } from '~modules/content-page/types/content-pages.types.ts'; import Html from '~shared/components/Html/Html.tsx'; diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss index a36b2eaf..57293824 100644 --- a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss @@ -13,13 +13,6 @@ $pattern-repeat-count: 20; // Container's (smaller, but non-zero) mobile gutter to the true screen edge. $pattern-mobile-overshoot: 15rem; -// Used below to build each side's shape-order (circle-centers/rect-ranges) - pattern-tile -// derives the rest (radius, antialias edge, cycle width) from $pattern-height itself. -$pattern-rect-width: pattern.pattern-rect-width($pattern-height); -$pattern-circle-diameter: $pattern-height; -$pattern-circle-radius: pattern.pattern-circle-radius($pattern-height); -$pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); - .c-block-highlight-text { // clip-path (not overflow-x: hidden) avoids the overflow-x/y coupling rule that would // otherwise turn overflow-y into "auto" and risk a vertical scrollbar. @@ -63,11 +56,10 @@ $pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); } } - // --left/--right are two DIFFERENT tiles (not one tile phase-shifted via background-position - // - that proved unreliable), each built so the desired near-text shape sits naturally at the - // tile's own edge, anchored via a plain background-position-x: right / left (no offset): - // - left: circle, rect, circle, circle, rect (nearest text -> farthest) - // - right: circle, circle, rect, circle, rect (nearest text -> farthest) + // The strip is always the same 5-shape cycle - rect, circle, circle, rect, circle - just + // entered at a different point per side (see pattern.pattern-side): + // - left: enters at shape 5, nearest text -> circle, rect, circle, circle, rect + // - right: enters at shape 2, nearest text -> circle, circle, rect, circle, rect &__pattern { @include pattern.pattern-strip($pattern-height, $pattern-repeat-count); // Always drawn at the desktop $pattern-height so the shapes keep their proportions - @@ -79,18 +71,7 @@ $pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); top: 0; right: 100%; background-position-x: right; - @include pattern.pattern-tile( - ( - $pattern-rect-width + $pattern-circle-radius, - $pattern-rect-width + $pattern-circle-diameter + $pattern-circle-radius, - $pattern-rect-width * 2 + $pattern-circle-diameter * 2 + $pattern-circle-radius, - ), - ( - (0, $pattern-rect-width), - ($pattern-rect-width + $pattern-circle-diameter * 2, $pattern-rect-width * 2 + $pattern-circle-diameter * 2), - ), - $pattern-height - ); + @include pattern.pattern-side(left, 5, $pattern-height); } // Aligned to &-text's bottom edge, not stretched from &__content's full (padded) height. @@ -98,18 +79,7 @@ $pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); bottom: 0; left: 100%; background-position-x: left; - @include pattern.pattern-tile( - ( - $pattern-circle-radius, - $pattern-circle-diameter + $pattern-circle-radius, - $pattern-circle-diameter * 2 + $pattern-rect-width + $pattern-circle-radius, - ), - ( - ($pattern-circle-diameter * 2, $pattern-circle-diameter * 2 + $pattern-rect-width), - ($pattern-circle-diameter * 3 + $pattern-rect-width, $pattern-cycle-width), - ), - $pattern-height - ); + @include pattern.pattern-side(right, 2, $pattern-height); } // Below $g-bp4 the pattern moves from beside the text to above/below it (see diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHomepageBanner/BlockHomepageBanner.scss b/ui/src/react-admin/modules/content-page/components/blocks/BlockHomepageBanner/BlockHomepageBanner.scss index 356808c0..2609090d 100644 --- a/ui/src/react-admin/modules/content-page/components/blocks/BlockHomepageBanner/BlockHomepageBanner.scss +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHomepageBanner/BlockHomepageBanner.scss @@ -12,13 +12,6 @@ $pattern-repeat-count: 20; // Container's (smaller, but non-zero) mobile gutter to the true screen edge. $pattern-mobile-overshoot: 10rem; -// Used below to build each side's shape-order (circle-centers/rect-ranges) - pattern-tile -// derives the rest (radius, antialias edge, cycle width) from $pattern-height itself. -$pattern-rect-width: pattern.pattern-rect-width($pattern-height); -$pattern-circle-diameter: $pattern-height; -$pattern-circle-radius: pattern.pattern-circle-radius($pattern-height); -$pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); - .c-block-homepage-banner { // clip-path (not overflow-x: hidden) avoids the overflow-x/y coupling rule that would // otherwise turn overflow-y into "auto" and risk a vertical scrollbar. @@ -76,11 +69,10 @@ $pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); } } - // --left/--right are two DIFFERENT tiles (not one tile phase-shifted via background-position - // - that proved unreliable), each built so the desired near-text shape sits naturally at the - // tile's own edge, anchored via a plain background-position-x: right / left (no offset): - // - left: rect, circle, rect, circle, circle (nearest text -> farthest) - // - right: circle, rect, circle, circle, rect (nearest text -> farthest) + // The strip is always the same 5-shape cycle - rect, circle, circle, rect, circle - just + // entered at a different point per side (see pattern.pattern-side): + // - left: enters at shape 1, nearest text -> rect, circle, rect, circle, circle + // - right: enters at shape 5, nearest text -> circle, rect, circle, circle, rect &__pattern { @include pattern.pattern-strip($pattern-height, $pattern-repeat-count); top: 0; @@ -89,35 +81,13 @@ $pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); &--left { right: calc(100% + $pattern-distance-horizontal); background-position-x: right; - @include pattern.pattern-tile( - ( - $pattern-circle-radius, - $pattern-circle-diameter + $pattern-circle-radius, - $pattern-circle-diameter * 2 + $pattern-rect-width + $pattern-circle-radius, - ), - ( - ($pattern-circle-diameter * 2, $pattern-circle-diameter * 2 + $pattern-rect-width), - ($pattern-circle-diameter * 3 + $pattern-rect-width, $pattern-cycle-width), - ), - $pattern-height - ); + @include pattern.pattern-side(left, 1, $pattern-height); } &--right { left: calc(100% + $pattern-distance-horizontal); background-position-x: left; - @include pattern.pattern-tile( - ( - $pattern-circle-radius, - $pattern-circle-diameter + $pattern-rect-width + $pattern-circle-radius, - $pattern-circle-diameter * 2 + $pattern-rect-width + $pattern-circle-radius, - ), - ( - ($pattern-circle-diameter, $pattern-circle-diameter + $pattern-rect-width), - ($pattern-circle-diameter * 3 + $pattern-rect-width, $pattern-cycle-width), - ), - $pattern-height - ); + @include pattern.pattern-side(right, 5, $pattern-height); } // Below $g-bp4 the patterns move from beside the text to above/below it (see @@ -125,12 +95,12 @@ $pattern-cycle-width: pattern.pattern-cycle-width($pattern-height); // "near text" to the slot's inner (1/3 or 2/3) edge; top/bottom: 0 already set above. @media (max-width: variables.$g-bp4) { &--left { - right: 0; + right: 0 !important; left: auto; } &--right { - left: 0; + left: 0 !important; right: auto; } } diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockOverviewWithCarousel/BlockOverviewWithCarousel.scss b/ui/src/react-admin/modules/content-page/components/blocks/BlockOverviewWithCarousel/BlockOverviewWithCarousel.scss index cdde94b8..a3f876bb 100644 --- a/ui/src/react-admin/modules/content-page/components/blocks/BlockOverviewWithCarousel/BlockOverviewWithCarousel.scss +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockOverviewWithCarousel/BlockOverviewWithCarousel.scss @@ -55,9 +55,7 @@ $slide-width: 28rem; .c-button__label { font-weight: 700; // since sm buttons loose this - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + @include typography.truncate(); } } } @@ -148,10 +146,7 @@ $slide-width: 28rem; } .c-block-overview-with-carousel__slide-text { - font-size: 3.2rem; - font-style: normal; - font-weight: 800; - line-height: 4.0rem; + @include typography.sofia-pro-heading-xl(); } } diff --git a/ui/src/react-admin/modules/shared/styles/mixins/_pattern.scss b/ui/src/react-admin/modules/shared/styles/mixins/_pattern.scss index ac1b37ee..6d8e94ba 100644 --- a/ui/src/react-admin/modules/shared/styles/mixins/_pattern.scss +++ b/ui/src/react-admin/modules/shared/styles/mixins/_pattern.scss @@ -1,33 +1,86 @@ @use "sass:math"; @use "sass:list"; -// Shared geometry for the repeating decorative pattern strip (3 circles + 2 rects) used by -// content blocks like BlockHomepageBanner and BlockHighlightText. Everything is derived from -// a single $height so blocks only need to tune one number to get a proportional strip. -@function pattern-rect-width($height) { +// Shared geometry + drawing for the repeating decorative pattern strip used by content blocks +// like BlockHomepageBanner and BlockHighlightText. The strip is always the same 5-shape cycle - +// rect, circle, circle, rect, circle - repeated; blocks only ever vary $height, which side of +// the content the strip is on, and which shape in the cycle sits nearest that content. +$_pattern-canon: rect, circle, circle, rect, circle; + +@function _pattern-rect-width($height) { @return math.div($height, 2); } -@function pattern-circle-radius($height) { +@function _pattern-circle-radius($height) { @return math.div($height, 2); } -@function pattern-antialias-edge($height) { - @return pattern-circle-radius($height) + 0.02rem; +@function _pattern-antialias-edge($height) { + @return _pattern-circle-radius($height) + 0.02rem; } // Must stay a multiple of this cycle width wherever it's used as a background-size: Chrome has // a rendering bug where a repeating multi-layer gradient tears mid-strip when the element's // width isn't an exact multiple of its background-size (100vw reproduces this reliably). -@function pattern-cycle-width($height) { - @return pattern-rect-width($height) * 2 + $height * 3; +@function _pattern-cycle-width($height) { + @return _pattern-rect-width($height) * 2 + $height * 3; +} + +// Floor modulo (Sass % is unreliable with negative operands) - keeps cycle indices in 1..$m. +@function _pattern-mod1($n, $m) { + @return $n - math.floor(math.div($n, $m)) * $m; +} + +// The 5 shapes in ascending (left-to-right) x order for a strip anchored on $side, starting +// $start (1-5) shapes into $_pattern-canon at the edge that touches the content: +// - right-anchored ("left" tiles, touching at their right edge): read the cycle BACKWARDS +// moving away from that edge, so ascending x is the cycle read FORWARDS from $start. +// - left-anchored ("right" tiles, touching at their left edge): read the cycle FORWARDS +// moving away from that edge, so ascending x already IS that forward reading from $start. +@function _pattern-ascending-shapes($side, $start) { + $away: (); + @for $i from 0 through 4 { + $step: if($side == right, $i, -$i); + $idx: _pattern-mod1($start - 1 + $step, 5) + 1; + $away: list.append($away, list.nth($_pattern-canon, $idx)); + } + @if $side == left { + // list.append can't prepend, so build the reversed list back-to-front + $ascending: (); + @for $i from 5 through 1 { + $ascending: list.append($ascending, list.nth($away, $i)); + } + @return $ascending; + } + @return $away; +} + +// Lays the 5 shapes from _pattern-ascending-shapes out left-to-right, returning the +// (circle-centers, rect-ranges) pair that _pattern-tile needs. +@function _pattern-positions($side, $start, $height) { + $rect-width: _pattern-rect-width($height); + $circle-centers: (); + $rect-ranges: (); + $cursor: 0; + + @each $shape in _pattern-ascending-shapes($side, $start) { + @if $shape == rect { + $rect-ranges: list.append($rect-ranges, ($cursor, $cursor + $rect-width), $separator: comma); + $cursor: $cursor + $rect-width; + } @else { + $circle-centers: list.append($circle-centers, $cursor + _pattern-circle-radius($height)); + $cursor: $cursor + $height; + } + } + + @return $circle-centers, $rect-ranges; } -// Common base for a pattern strip element - position/tiling only, no shape or side (--left / -// --right) concerns. $repeat-count just needs to be generous enough that the strip always -// reaches the block's own edge on any realistic viewport. +// Common base for a pattern strip element - position/tiling only, no shape or side concerns. +// $repeat-count just needs to be generous enough that the strip always reaches the block's own +// edge on any realistic viewport. @mixin pattern-strip($height, $repeat-count: 20) { - $cycle-width: pattern-cycle-width($height); + $cycle-width: _pattern-cycle-width($height); position: absolute; width: $cycle-width * $repeat-count; @@ -36,14 +89,16 @@ background-size: $cycle-width $height; } -// Renders the repeating tile (3 circles + 2 rects) for one pattern strip. $circle-centers is a -// 3-item list of x-offsets; $rect-ranges is a 2-item list of [start, end] pairs, both in the -// same unit space as $height (see the pattern-* functions above). Two strips of the same -// $height can look completely different just by passing a different shape order here. -@mixin pattern-tile($circle-centers, $rect-ranges, $height) { - $circle-radius: pattern-circle-radius($height); - $antialias-edge: pattern-antialias-edge($height); - $cycle-width: pattern-cycle-width($height); +// Draws the repeating tile (3 circles + 2 rects) for one side of a pattern strip. $side is +// `left` or `right` (which edge of the strip touches the content); $start (1-5) picks which +// shape of the fixed rect/circle/circle/rect/circle cycle sits nearest that content. +@mixin pattern-side($side, $start, $height) { + $circle-radius: _pattern-circle-radius($height); + $antialias-edge: _pattern-antialias-edge($height); + $cycle-width: _pattern-cycle-width($height); + $positions: _pattern-positions($side, $start, $height); + $circle-centers: list.nth($positions, 1); + $rect-ranges: list.nth($positions, 2); background-image: radial-gradient( From 43b68844715c53130a9ff96ca120a9adcf17e44c Mon Sep 17 00:00:00 2001 From: Femke Reunes Date: Tue, 28 Jul 2026 16:43:22 +0200 Subject: [PATCH 4/5] ARC-3793: Fix the -1px hack --- .../BlockHighlightText/BlockHighligtText.scss | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss index 57293824..4cf6679e 100644 --- a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighligtText.scss @@ -1,13 +1,9 @@ -@use "sass:math"; @use "../../../../shared/styles/settings/variables" as variables; @use "../../../../shared/styles/mixins/pattern" as pattern; @use "../../../../shared/styles/mixins/typography" as typography; $pattern-height: 12rem; -// Mobile shrinks the strip - shapes are still drawn at the desktop $pattern-height (so they -// keep their proportions) and scaled down visually via transform, see &__pattern. $pattern-height-mobile: 8rem; -$pattern-mobile-scale: math.div($pattern-height-mobile, $pattern-height); $pattern-repeat-count: 20; // How far the mobile pattern slots overshoot &__content's edge, so they can bleed past avo2 // Container's (smaller, but non-zero) mobile gutter to the true screen edge. @@ -51,7 +47,6 @@ $pattern-mobile-overshoot: 15rem; @media (max-width: variables.$g-bp4) { padding: calc(#{variables.$g-spacer-unit} * 3) calc(#{variables.$g-spacer-unit} * 2.5) !important; - margin: -1px 0; // closes a hairline gap between the pattern and the text } } } @@ -62,8 +57,6 @@ $pattern-mobile-overshoot: 15rem; // - right: enters at shape 2, nearest text -> circle, circle, rect, circle, rect &__pattern { @include pattern.pattern-strip($pattern-height, $pattern-repeat-count); - // Always drawn at the desktop $pattern-height so the shapes keep their proportions - - // the mobile media query below shrinks the whole box (incl. background) via transform. height: $pattern-height; // Aligned to &-text's top edge, not stretched to &__content's full (padded) height. @@ -82,22 +75,21 @@ $pattern-mobile-overshoot: 15rem; @include pattern.pattern-side(right, 2, $pattern-height); } - // Below $g-bp4 the pattern moves from beside the text to above/below it (see - // &__pattern-slot) - drawn at desktop size and scaled down, anchored at the corner that - // must stay flush against the slot's edges. + // Below $g-bp4 the pattern moves from beside the text to above/below it (see &__pattern-slot). @media (max-width: variables.$g-bp4) { - transform: scale($pattern-mobile-scale); + @include pattern.pattern-strip($pattern-height-mobile, $pattern-repeat-count); + height: $pattern-height-mobile; &--left { right: 0; left: auto; - transform-origin: top right; + @include pattern.pattern-side(left, 5, $pattern-height-mobile); } &--right { left: 0; right: auto; - transform-origin: bottom left; + @include pattern.pattern-side(right, 2, $pattern-height-mobile); } } } From 9b77a00d5adcc0e68af84abbcc1297ced0a81116 Mon Sep 17 00:00:00 2001 From: Femke Reunes Date: Tue, 28 Jul 2026 16:53:13 +0200 Subject: [PATCH 5/5] ARC-3793: Some comment clean up --- .../BlockHighlightText/BlockHighlightText.tsx | 6 ----- .../BlockHighlightText/BlockHighligtText.scss | 3 +-- .../BlockHomepageBanner.scss | 14 ++++------ .../shared/styles/mixins/_pattern.scss | 27 +++++++++---------- 4 files changed, 19 insertions(+), 31 deletions(-) diff --git a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx index 3c2c5810..13bfbb27 100644 --- a/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx +++ b/ui/src/react-admin/modules/content-page/components/blocks/BlockHighlightText/BlockHighlightText.tsx @@ -38,18 +38,12 @@ export const BlockHighlightText: FunctionComponent = ({ } className={clsx('c-block-highlight-text__content')} > - {/* No-op on desktop (patterns sit beside the text via CSS); on mobile this - is the clip box that caps the pattern at 1/3 screen width - see .scss. - Placed here (before the title) only for mobile's source order. */}
- {/* Carries the highlight background-color and the text's own breathing-room - padding, separate from &__content's padding (which only reserves space - for the patterns above/below it on mobile) - see .scss. */}