diff --git a/dotcom-rendering/src/components/PuzzlesBranding.tsx b/dotcom-rendering/src/components/PuzzlesBranding.tsx
index 5c76e3c2384..e394e246617 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: var(--puzzles-text-colour, ${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}
-
-
-
-
-
+
+
);
};
+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.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..c764f3259df 100644
--- a/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx
+++ b/dotcom-rendering/src/components/PuzzlesDirectory.test.tsx
@@ -1,6 +1,7 @@
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,
@@ -34,6 +35,35 @@ 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('link', {
+ name: 'Visit the Example sponsor website',
+ }),
+ ).toHaveAttribute('href', 'https://example.com');
+ expect(
+ screen.queryByRole('link', { name: 'About this content' }),
+ ).not.toBeInTheDocument();
+ 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 +75,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 +91,30 @@ 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]});
+ line-height: 1;
+ overflow-wrap: break-word;
+
+ ${from.tablet} {
+ ${headlineBold28};
+ }
+`;
+
const headingColumnStyles = css`
${from.leftCol} {
position: relative;
@@ -233,9 +253,11 @@ const hasContent = (container: PuzzleContainer): boolean =>
const DirectorySection = ({
container,
hideTopRule,
+ branding,
}: {
container: PuzzleContainer;
hideTopRule: boolean;
+ branding?: Branding;
}) => {
if (!hasContent(container)) {
return null;
@@ -247,9 +269,14 @@ const DirectorySection = ({
id={container.id}
>
-
- {container.title}
-
+
+
+ {container.title}
+
+ {branding && (
+
+ )}
+
(
<>
{layout.containers.map((container) => {
@@ -328,6 +356,7 @@ export const PuzzlesDirectory = ({
}
return (