diff --git a/dotcom-rendering/package.json b/dotcom-rendering/package.json
index 77ee33e9863..fb6b8adaf2f 100644
--- a/dotcom-rendering/package.json
+++ b/dotcom-rendering/package.json
@@ -82,6 +82,7 @@
"@types/clean-css": "4.2.11",
"@types/compression": "1.7.5",
"@types/connect": "3.4.38",
+ "@types/crypto-js": "4.2.2",
"@types/dompurify": "3.0.2",
"@types/express": "4.17.21",
"@types/he": "1.2.0",
@@ -126,6 +127,7 @@
"compression": "1.7.4",
"constructs": "10.4.2",
"cpy": "11.0.0",
+ "crypto-js": "4.2.0",
"css-loader": "7.1.2",
"curlyquotes": "1.5.5",
"dompurify": "3.2.4",
diff --git a/dotcom-rendering/src/components/CardPicture.tsx b/dotcom-rendering/src/components/CardPicture.tsx
index 0184e20cebf..961a97eee39 100644
--- a/dotcom-rendering/src/components/CardPicture.tsx
+++ b/dotcom-rendering/src/components/CardPicture.tsx
@@ -2,6 +2,7 @@ import { css } from '@emotion/react';
import { breakpoints, space, until } from '@guardian/source/foundations';
import type { ImgHTMLAttributes } from 'react';
import React from 'react';
+import { viewTransitionStyles } from '../lib/view-transition';
import type { AspectRatio } from '../types/front';
import type { ImageSizeType } from './Card/components/ImageWrapper';
import type { ImageWidthType } from './Picture';
@@ -220,6 +221,7 @@ export const CardPicture = ({
decideMobileAspectRatioStyles(mobileAspectRatio),
roundedCorners && borderRadius,
isCircular && circularStyles,
+ viewTransitionStyles('hero-image', alt),
]}
>
{sources.map((source) => {
diff --git a/dotcom-rendering/src/components/DecideContainer.tsx b/dotcom-rendering/src/components/DecideContainer.tsx
index 85e33692f1b..ce126867d32 100644
--- a/dotcom-rendering/src/components/DecideContainer.tsx
+++ b/dotcom-rendering/src/components/DecideContainer.tsx
@@ -29,7 +29,7 @@ import { FlexibleSpecial } from './FlexibleSpecial';
import { Island } from './Island';
import { NavList } from './NavList';
import { ScrollableFeature } from './ScrollableFeature.importable';
-import { ScrollableHighlights } from './ScrollableHighlights.importable';
+// import { ScrollableHighlights } from './ScrollableHighlights.importable';
import { ScrollableMedium } from './ScrollableMedium.importable';
import { ScrollableSmall } from './ScrollableSmall.importable';
import { StaticFeatureTwo } from './StaticFeatureTwo';
@@ -242,9 +242,10 @@ export const DecideContainer = ({
return ;
case 'scrollable/highlights':
return (
-
-
-
+ <>>
+ //
+ //
+ //
);
case 'flexible/special':
return (
diff --git a/dotcom-rendering/src/components/Picture.tsx b/dotcom-rendering/src/components/Picture.tsx
index dbb49e3292b..1108247d0e1 100644
--- a/dotcom-rendering/src/components/Picture.tsx
+++ b/dotcom-rendering/src/components/Picture.tsx
@@ -7,6 +7,7 @@ import {
type ArticleFormat,
} from '../lib/articleFormat';
import { generateImageURL } from '../lib/image';
+import { viewTransitionStyles } from '../lib/view-transition';
import type { RoleType } from '../types/content';
import type { AspectRatio } from '../types/front';
import type { Loading } from './CardPicture';
@@ -523,7 +524,12 @@ export const Picture = ({
const fallbackSource = getFallbackSource(sources);
return (
-
+
{/* Immersive Main Media images get additional sources specifically for when in portrait orientation */}
{format.display === ArticleDisplay.Immersive && isMainMedia && (
<>
diff --git a/dotcom-rendering/src/lib/rootStyles.ts b/dotcom-rendering/src/lib/rootStyles.ts
index bb486305302..69882e9829d 100644
--- a/dotcom-rendering/src/lib/rootStyles.ts
+++ b/dotcom-rendering/src/lib/rootStyles.ts
@@ -62,5 +62,72 @@ export const rootStyles = (
}
}
+ @view-transition {
+ navigation: auto;
+ }
+
+ /* ::view-transition-old(root) {
+ animation: 0.2s ease-in both slide-in;
+ }
+
+ ::view-transition-new(root) {
+ animation: 0.2s ease-in both slide-in;
+ } */
+
+ /* ::view-transition-old(root) {
+ animation: 0.5s ease-in both spin;
+ }
+
+ ::view-transition-new(root) {
+ animation: 0.5s ease-in both spin;
+ } */
+
+ /* ::view-transition-old(root) {
+ animation: 10.5s ease-in both spin;
+ }
+
+ ::view-transition-new(root) {
+ animation: 10.5s ease-in both spin;
+ } */
+
+ /* Create a custom animation */
+ @keyframes slide-out {
+ from {
+ transform: translateX(0%);
+ }
+
+ to {
+ transform: translateX(-100%);
+ }
+ }
+
+ @keyframes slide-in {
+ from {
+ transform: translateX(100%);
+ }
+
+ to {
+ transform: translateX(0%);
+ }
+ }
+
+ @keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+ }
+
+ @keyframes zoom {
+ from {
+ transform: scale(0.95);
+ }
+ to {
+ transform: scale(1);
+ }
+ }
+
${rootAdStyles}
`;
diff --git a/dotcom-rendering/src/lib/view-transition.ts b/dotcom-rendering/src/lib/view-transition.ts
new file mode 100644
index 00000000000..e64439198c7
--- /dev/null
+++ b/dotcom-rendering/src/lib/view-transition.ts
@@ -0,0 +1,19 @@
+import type { SerializedStyles } from '@emotion/react';
+import { css } from '@emotion/react';
+import { SHA256 } from 'crypto-js';
+
+function sha256Hash(message: string) {
+ return SHA256(message).toString();
+}
+
+export const viewTransitionStyles = (
+ prefix: string,
+ stringToHash: string | undefined,
+): SerializedStyles => {
+ const name = `${prefix}-${sha256Hash(
+ stringToHash ?? global.crypto.randomUUID(),
+ )}`;
+ return css`
+ /* view-transition-name: ${name}; */
+ `;
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 254bbaac647..7e7c7700c1e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -460,6 +460,9 @@ importers:
'@types/connect':
specifier: 3.4.38
version: 3.4.38
+ '@types/crypto-js':
+ specifier: 4.2.2
+ version: 4.2.2
'@types/dompurify':
specifier: 3.0.2
version: 3.0.2
@@ -592,6 +595,9 @@ importers:
cpy:
specifier: 11.0.0
version: 11.0.0
+ crypto-js:
+ specifier: 4.2.0
+ version: 4.2.0
css-loader:
specifier: 7.1.2
version: 7.1.2(webpack@5.99.7)
@@ -4074,7 +4080,7 @@ packages:
'@typescript-eslint/parser': 6.18.0(eslint@8.56.0)(typescript@5.5.3)
eslint: 8.56.0
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.18.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint@8.56.0)
tslib: 2.6.2
typescript: 5.5.3
transitivePeerDependencies:
@@ -6122,7 +6128,7 @@ packages:
react-docgen-typescript: 2.2.2(typescript@5.5.3)
tslib: 2.6.2
typescript: 5.5.3
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
transitivePeerDependencies:
- supports-color
dev: false
@@ -6766,6 +6772,10 @@ packages:
'@types/node': 22.14.1
dev: false
+ /@types/crypto-js@4.2.2:
+ resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
+ dev: false
+
/@types/debug@4.1.12:
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
dependencies:
@@ -7691,8 +7701,8 @@ packages:
webpack: ^5.82.0
webpack-cli: 6.x.x
dependencies:
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.1)(webpack@5.99.7)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack-dev-server@5.2.1)(webpack@5.99.7)
dev: false
/@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.99.7):
@@ -7702,8 +7712,8 @@ packages:
webpack: ^5.82.0
webpack-cli: 6.x.x
dependencies:
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.1)(webpack@5.99.7)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack-dev-server@5.2.1)(webpack@5.99.7)
dev: false
/@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack-dev-server@5.2.1)(webpack@5.99.7):
@@ -7717,8 +7727,8 @@ packages:
webpack-dev-server:
optional: true
dependencies:
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.1)(webpack@5.99.7)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack-dev-server@5.2.1)(webpack@5.99.7)
webpack-dev-server: 5.2.1(webpack-cli@6.0.1)(webpack@5.99.7)
dev: false
@@ -8392,7 +8402,7 @@ packages:
dependencies:
'@babel/core': 7.27.1
find-up: 5.0.0
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
dev: false
/babel-loader@9.2.1(@babel/core@7.27.1)(webpack@5.99.7):
@@ -9584,7 +9594,7 @@ packages:
postcss-modules-values: 4.0.0(postcss@8.4.47)
postcss-value-parser: 4.2.0
semver: 7.5.4
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
dev: false
/css-loader@7.1.2(webpack@5.99.7):
@@ -10631,7 +10641,7 @@ packages:
enhanced-resolve: 5.18.1
eslint: 8.56.0
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint@8.56.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.2
is-core-module: 2.16.1
@@ -11589,7 +11599,7 @@ packages:
semver: 7.5.4
tapable: 2.2.1
typescript: 5.5.3
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
dev: false
/form-data-encoder@2.1.4:
@@ -17156,7 +17166,7 @@ packages:
peerDependencies:
webpack: ^5.0.0
dependencies:
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
dev: false
/stylelint-config-recommended@14.0.0(stylelint@16.5.0):
@@ -17649,7 +17659,7 @@ packages:
semver: 7.5.4
source-map: 0.7.4
typescript: 5.5.3
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
dev: false
/ts-node@10.9.2(@swc/core@1.11.13)(@types/node@16.18.68)(typescript@5.1.6):
@@ -18402,7 +18412,7 @@ packages:
mime-types: 2.1.35
range-parser: 1.2.1
schema-utils: 4.3.2
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
dev: false
/webpack-dev-middleware@7.4.2(webpack@5.99.7):
@@ -18462,8 +18472,8 @@ packages:
serve-index: 1.9.1
sockjs: 0.3.24
spdy: 4.0.2
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.1)(webpack@5.99.7)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack-dev-server@5.2.1)(webpack@5.99.7)
webpack-dev-middleware: 7.4.2(webpack@5.99.7)
ws: 8.18.1
transitivePeerDependencies:
@@ -18508,7 +18518,7 @@ packages:
webpack: ^5.47.0
dependencies:
tapable: 2.2.1
- webpack: 5.99.7(@swc/core@1.11.13)(esbuild@0.18.20)(webpack-cli@6.0.1)
+ webpack: 5.99.7(esbuild@0.18.20)(webpack-cli@6.0.1)
webpack-sources: 2.3.1
dev: false
patched: true