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..7f4e2a530db 100644 --- a/dotcom-rendering/src/components/CardPicture.tsx +++ b/dotcom-rendering/src/components/CardPicture.tsx @@ -1,5 +1,6 @@ import { css } from '@emotion/react'; import { breakpoints, space, until } from '@guardian/source/foundations'; +import SHA256 from 'crypto-js/sha256'; import type { ImgHTMLAttributes } from 'react'; import React from 'react'; import type { AspectRatio } from '../types/front'; @@ -193,6 +194,10 @@ const decideMobileAspectRatioStyles = (aspectRatio?: AspectRatio) => { `; }; +function sha256Hash(message: string) { + return SHA256(message).toString(); +} + export const CardPicture = ({ mainImage, alt, @@ -220,6 +225,10 @@ export const CardPicture = ({ decideMobileAspectRatioStyles(mobileAspectRatio), roundedCorners && borderRadius, isCircular && circularStyles, + alt && + css` + view-transition-name: hero-image-${sha256Hash(alt)}; + `, ]} > {sources.map((source) => { diff --git a/dotcom-rendering/src/components/Picture.tsx b/dotcom-rendering/src/components/Picture.tsx index dbb49e3292b..3c2a93c2c90 100644 --- a/dotcom-rendering/src/components/Picture.tsx +++ b/dotcom-rendering/src/components/Picture.tsx @@ -1,5 +1,6 @@ import { css } from '@emotion/react'; import { breakpoints } from '@guardian/source/foundations'; +import SHA256 from 'crypto-js/sha256'; import { Fragment, useCallback, useEffect, useState } from 'react'; import { ArticleDesign, @@ -462,6 +463,10 @@ export const Sources = ({ sources }: { sources: ImageSource[] }) => { ); }; +function sha256Hash(message: string) { + return SHA256(message).toString(); +} + export const Picture = ({ role, format, @@ -523,7 +528,15 @@ 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..2ce17a01b56 100644 --- a/dotcom-rendering/src/lib/rootStyles.ts +++ b/dotcom-rendering/src/lib/rootStyles.ts @@ -62,5 +62,15 @@ export const rootStyles = ( } } + @view-transition { + navigation: auto; + } + + ::view-transition-old(hero-image), + ::view-transition-new(hero-image) { + animation-timing-function: ease-in-out; + animation-duration: 2s; + } + ${rootAdStyles} `; 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