From e608a0c300f5ca05a8da61cd5c50ab69376e3693 Mon Sep 17 00:00:00 2001 From: DanielCliftonGuardian <110032454+DanielCliftonGuardian@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:26:05 +0100 Subject: [PATCH 1/3] Add sponsorship to puzzles hub containers --- .../components/PuzzlesDirectory.stories.tsx | 8 +++ .../src/components/PuzzlesDirectory.test.tsx | 38 +++++++++++++ .../src/components/PuzzlesDirectory.tsx | 57 +++++++++++++++---- 3 files changed, 91 insertions(+), 12 deletions(-) diff --git a/dotcom-rendering/src/components/PuzzlesDirectory.stories.tsx b/dotcom-rendering/src/components/PuzzlesDirectory.stories.tsx index ab4693cf7e4..8bcfe3134fc 100644 --- a/dotcom-rendering/src/components/PuzzlesDirectory.stories.tsx +++ b/dotcom-rendering/src/components/PuzzlesDirectory.stories.tsx @@ -129,6 +129,14 @@ export const AdFree = () => ( ); +export const SponsoredContainer = () => ( + +); + export const Sponsored = () => { const page = createPuzzlesPage({ layout, isAdFreeUser: true }); return ( diff --git a/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx b/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx index cca9bd7125e..a477439e43c 100644 --- a/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx +++ b/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx @@ -1,11 +1,13 @@ import '@testing-library/jest-dom'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import type { ReactNode } from 'react'; +import { puzzlesBranding } from '../../fixtures/manual/puzzlesBranding'; import type { PuzzleContainer, PuzzleItem, PuzzlesLayoutType, } from '../types/puzzlesPage'; +import { ConfigProvider } from './ConfigContext'; import { Island } from './Island'; import { getPuzzleUrl, PuzzlesDirectory } from './PuzzlesDirectory'; @@ -34,6 +36,42 @@ const section = ( }); describe('PuzzlesDirectory', () => { + it('shows sponsorship only on the sponsored container', () => { + render( + + + , + ); + + const [crosswords, wordGames] = screen.getAllByRole('region'); + expect(crosswords).toHaveTextContent('Supported by'); + expect( + screen.getByRole('img', { name: 'logo for Example sponsor' }), + ).toBeInTheDocument(); + expect( + screen.getByRole('link', { name: 'About this content' }), + ).toHaveAttribute('href', 'https://example.com/about'); + expect( + screen.getByRole('heading', { level: 2, name: 'Crosswords' }), + ).toHaveAttribute('id', 'crosswords-title'); + expect(wordGames).not.toHaveTextContent('Supported by'); + }); it('renders puzzle tile titles as H3s with descriptive crossword text', () => { render( >; }; const sectionStyles = (isFeatured: boolean, hideTopRule: boolean) => css` @@ -70,21 +74,12 @@ const sectionStyles = (isFeatured: boolean, hideTopRule: boolean) => css` const titleStyles = css` min-width: 0; - margin: 0; padding: 6px 10px 12px; - ${headlineBold24}; - color: var(--puzzles-headline-colour, ${palette.neutral[7]}); - line-height: 1; - overflow-wrap: break-word; ${from.mobileMedium} { padding-right: 20px; padding-left: 20px; } - ${from.tablet} { - ${headlineBold28}; - } - ${from.desktop} { /* Include the 1px side border in the 20px inset, as the cards do. */ padding-left: 19px; @@ -95,6 +90,18 @@ const titleStyles = css` } `; +const headingStyles = css` + margin: 0; + ${headlineBold24}; + color: var(--puzzles-headline-colour, ${palette.neutral[7]}); + line-height: 1; + overflow-wrap: break-word; + + ${from.tablet} { + ${headlineBold28}; + } +`; + const headingColumnStyles = css` ${from.leftCol} { position: relative; @@ -230,16 +237,31 @@ const hasContent = (container: PuzzleContainer): boolean => container.content.items.some((row) => row.length > 0) || container.content.nestedContainers.some(hasContent); +const toCollectionBranding = (branding: Branding): CollectionBranding => ({ + kind: branding.brandingType?.name ?? 'sponsored', + branding, + isFrontBranding: false, + isContainerBranding: true, + hasMultipleBranding: false, +}); + const DirectorySection = ({ container, hideTopRule, + branding, }: { container: PuzzleContainer; hideTopRule: boolean; + branding?: Branding; }) => { if (!hasContent(container)) { return null; } + const heading = ( +

+ {container.title} +

+ ); return (
-

- {container.title} -

+
+ {branding ? ( + + ) : ( + heading + )} +
( <> {layout.containers.map((container) => { @@ -328,6 +360,7 @@ export const PuzzlesDirectory = ({ } return ( Date: Tue, 20 Oct 2026 13:57:37 +0100 Subject: [PATCH 2/3] Match Figma layout for puzzles container sponsorship --- .../src/components/PuzzlesBranding.tsx | 98 ++++++++++++++----- .../src/components/PuzzlesDirectory.test.tsx | 38 +++---- .../src/components/PuzzlesDirectory.tsx | 46 ++++----- 3 files changed, 108 insertions(+), 74 deletions(-) diff --git a/dotcom-rendering/src/components/PuzzlesBranding.tsx b/dotcom-rendering/src/components/PuzzlesBranding.tsx index 5c76e3c2384..0cf19188f58 100644 --- a/dotcom-rendering/src/components/PuzzlesBranding.tsx +++ b/dotcom-rendering/src/components/PuzzlesBranding.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/react'; +import { css, type SerializedStyles } from '@emotion/react'; import { from, palette, @@ -76,42 +76,78 @@ const aboutLinkStyles = css` color: var(--puzzles-text-colour, ${palette.neutral[7]}); `; +const containerLockupStyles = css` + display: flex; + align-items: center; + gap: 7px; + white-space: nowrap; + color: ${palette.neutral[7]}; + a { + text-decoration: none; + } + ${from.leftCol} { + flex-direction: column; + align-items: flex-start; + margin-top: ${space[3]}px; + } +`; + +const containerLogoStyles = css` + display: block; + width: auto; + height: auto; + max-width: 100px; + max-height: 40px; + object-fit: contain; + ${from.leftCol} { + max-width: 120px; + max-height: 60px; + } +`; + type Props = { branding: Branding }; -export const PuzzlesBranding = ({ branding }: Props) => { +const SponsorLogoLink = ({ + branding, + imageStyles, +}: Props & { imageStyles: SerializedStyles }) => { const { ophanComponentName, ophanComponentLink } = getOphanComponents({ branding, locationPrefix: 'front-container', }); return ( -
-
- {branding.logo.label} - - {branding.sponsorName} - -
-
+ {branding.sponsorName} + ); }; +export const PuzzlesBranding = ({ branding }: Props) => ( +
+
+ {branding.logo.label} + +
+
+); + export const PuzzlesBrandingAboutLink = ({ branding }: Props) => ( ); + +export const PuzzlesContainerBranding = ({ branding }: Props) => ( +
+ {branding.logo.label} + +
+); diff --git a/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx b/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx index a477439e43c..c764f3259df 100644 --- a/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx +++ b/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx @@ -7,7 +7,6 @@ import type { PuzzleItem, PuzzlesLayoutType, } from '../types/puzzlesPage'; -import { ConfigProvider } from './ConfigContext'; import { Island } from './Island'; import { getPuzzleUrl, PuzzlesDirectory } from './PuzzlesDirectory'; @@ -38,35 +37,28 @@ const section = ( describe('PuzzlesDirectory', () => { it('shows sponsorship only on the sponsored container', () => { render( - - - , + renderAds={false} + containerBranding={{ crosswords: puzzlesBranding }} + />, ); const [crosswords, wordGames] = screen.getAllByRole('region'); expect(crosswords).toHaveTextContent('Supported by'); expect( - screen.getByRole('img', { name: 'logo for Example sponsor' }), - ).toBeInTheDocument(); + screen.getByRole('link', { + name: 'Visit the Example sponsor website', + }), + ).toHaveAttribute('href', 'https://example.com'); expect( - screen.getByRole('link', { name: 'About this content' }), - ).toHaveAttribute('href', 'https://example.com/about'); + screen.queryByRole('link', { name: 'About this content' }), + ).not.toBeInTheDocument(); expect( screen.getByRole('heading', { level: 2, name: 'Crosswords' }), ).toHaveAttribute('id', 'crosswords-title'); diff --git a/dotcom-rendering/src/components/PuzzlesDirectory.tsx b/dotcom-rendering/src/components/PuzzlesDirectory.tsx index c671109bd7e..01fdcccbcbc 100644 --- a/dotcom-rendering/src/components/PuzzlesDirectory.tsx +++ b/dotcom-rendering/src/components/PuzzlesDirectory.tsx @@ -4,16 +4,17 @@ import { headlineBold24, headlineBold28, palette, + space, } from '@guardian/source/foundations'; import { ArticleDisplay } from '../lib/articleFormat'; import { puzzlesContainerStyles } from '../lib/puzzlesContainerStyles'; -import type { Branding, CollectionBranding } from '../types/branding'; +import type { Branding } from '../types/branding'; import type { PuzzleContainer, PuzzlesLayoutType } from '../types/puzzlesPage'; import { AdSlot } from './AdSlot.web'; -import { FrontSectionTitle } from './FrontSectionTitle'; import { Island } from './Island'; import { getPuzzleUrl, Rows } from './PuzzleCard'; import { PuzzlesArchiveMenu } from './PuzzlesArchiveMenu.island'; +import { PuzzlesContainerBranding } from './PuzzlesBranding'; import { PuzzlesSupporting } from './PuzzlesSupporting'; export { getPuzzleUrl }; @@ -90,7 +91,19 @@ const titleStyles = css` } `; +// Title left with the sponsor right on one row, stacked under the title from leftCol. +const brandedTitleStyles = css` + display: flex; + align-items: center; + justify-content: space-between; + gap: ${space[2]}px; + ${from.leftCol} { + display: block; + } +`; + const headingStyles = css` + min-width: 0; margin: 0; ${headlineBold24}; color: var(--puzzles-headline-colour, ${palette.neutral[7]}); @@ -237,14 +250,6 @@ const hasContent = (container: PuzzleContainer): boolean => container.content.items.some((row) => row.length > 0) || container.content.nestedContainers.some(hasContent); -const toCollectionBranding = (branding: Branding): CollectionBranding => ({ - kind: branding.brandingType?.name ?? 'sponsored', - branding, - isFrontBranding: false, - isContainerBranding: true, - hasMultipleBranding: false, -}); - const DirectorySection = ({ container, hideTopRule, @@ -257,11 +262,6 @@ const DirectorySection = ({ if (!hasContent(container)) { return null; } - const heading = ( -

- {container.title} -

- ); return (
-
- {branding ? ( - - ) : ( - heading +
+

+ {container.title} +

+ {branding && ( + )}
From c130e892f1ccb953c62dfa8a92813d01ae331bb6 Mon Sep 17 00:00:00 2001 From: DanielCliftonGuardian <110032454+DanielCliftonGuardian@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:26:18 +0100 Subject: [PATCH 3/3] Update PuzzlesBranding.tsx --- dotcom-rendering/src/components/PuzzlesBranding.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotcom-rendering/src/components/PuzzlesBranding.tsx b/dotcom-rendering/src/components/PuzzlesBranding.tsx index 0cf19188f58..e394e246617 100644 --- a/dotcom-rendering/src/components/PuzzlesBranding.tsx +++ b/dotcom-rendering/src/components/PuzzlesBranding.tsx @@ -81,7 +81,7 @@ const containerLockupStyles = css` align-items: center; gap: 7px; white-space: nowrap; - color: ${palette.neutral[7]}; + color: var(--puzzles-text-colour, ${palette.neutral[7]}); a { text-decoration: none; }