diff --git a/web/src/app.css b/web/src/app.css index 3a4d29b466..5dcefe4173 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -74,6 +74,19 @@ --immich-dark-bg: 10 10 10; --immich-dark-fg: 229 231 235; --immich-dark-gray: 33 33 33; + + /* transitions */ + --immich-split-viewer-nav: enabled; + + /* view transition variables */ + --vt-duration-default: 250ms; + --vt-duration-hero: 280ms; + --vt-duration-viewer-navigation: 270ms; + --vt-duration-slideshow: 1s; + --vt-viewer-slide-easing: cubic-bezier(0.2, 0, 0, 1); + --vt-viewer-slide-distance: 15%; + --vt-viewer-opacity-start: 0.1; + --vt-viewer-blur-max: 4px; } button:not(:disabled), @@ -175,3 +188,318 @@ @apply bg-subtle rounded-lg; } } + +@layer base { + ::view-transition { + background: var(--color-black); + animation-duration: var(--vt-duration-default); + } + + ::view-transition-old(*), + ::view-transition-new(*) { + mix-blend-mode: normal; + animation-duration: inherit; + } + + ::view-transition-old(*) { + animation-name: fadeOut; + animation-fill-mode: forwards; + } + ::view-transition-new(*) { + animation-name: fadeIn; + animation-fill-mode: forwards; + } + + ::view-transition-old(root) { + animation: var(--vt-duration-default) 0s fadeOut forwards; + } + ::view-transition-new(root) { + animation: var(--vt-duration-default) 0s fadeIn forwards; + } + html:active-view-transition-type(slideshow) { + &::view-transition-old(root) { + animation: var(--vt-duration-slideshow) 0s fadeOut forwards; + } + &::view-transition-new(root) { + animation: var(--vt-duration-slideshow) 0s fadeIn forwards; + } + } + html:active-view-transition-type(viewer-nav) { + &::view-transition-old(root) { + animation: var(--vt-duration-hero) 0s fadeOut forwards; + } + &::view-transition-new(root) { + animation: var(--vt-duration-hero) 0s fadeIn forwards; + } + } + ::view-transition-old(info) { + animation: var(--vt-duration-default) 0s flyOutRight forwards; + } + ::view-transition-new(info) { + animation: var(--vt-duration-default) 0s flyInRight forwards; + } + + ::view-transition-group(detail-panel) { + z-index: 1; + } + ::view-transition-old(detail-panel), + ::view-transition-new(detail-panel) { + animation: none; + } + ::view-transition-group(letterbox-left), + ::view-transition-group(letterbox-right), + ::view-transition-group(letterbox-top), + ::view-transition-group(letterbox-bottom) { + animation-duration: var(--vt-duration-viewer-navigation); + z-index: 4; + } + + ::view-transition-image-pair(letterbox-left), + ::view-transition-image-pair(letterbox-right), + ::view-transition-image-pair(letterbox-top), + ::view-transition-image-pair(letterbox-bottom) { + isolation: auto; + } + + ::view-transition-old(letterbox-left), + ::view-transition-old(letterbox-right), + ::view-transition-old(letterbox-top), + ::view-transition-old(letterbox-bottom), + ::view-transition-new(letterbox-left), + ::view-transition-new(letterbox-right), + ::view-transition-new(letterbox-top), + ::view-transition-new(letterbox-bottom) { + animation: none; + width: 100%; + height: 100%; + object-fit: fill; + background-color: var(--color-black); + } + + ::view-transition-group(exclude-leftbutton), + ::view-transition-group(exclude-rightbutton), + ::view-transition-group(exclude) { + animation: none; + z-index: 5; + } + ::view-transition-old(exclude-leftbutton), + ::view-transition-old(exclude-rightbutton), + ::view-transition-old(exclude) { + visibility: hidden; + } + ::view-transition-new(exclude-leftbutton), + ::view-transition-new(exclude-rightbutton), + ::view-transition-new(exclude) { + animation: none; + z-index: 5; + } + + ::view-transition-group(hero) { + animation-duration: var(--vt-duration-hero); + animation-timing-function: cubic-bezier(0.2, 0, 0, 1); + } + ::view-transition-old(hero) { + animation: none; + align-content: center; + } + ::view-transition-new(hero) { + animation: none; + align-content: center; + } + ::view-transition-old(next), + ::view-transition-old(next-old) { + animation: var(--vt-duration-viewer-navigation) var(--vt-viewer-slide-easing) flyOutLeft forwards; + overflow: hidden; + } + + ::view-transition-new(next), + ::view-transition-new(next-new) { + animation: var(--vt-duration-viewer-navigation) var(--vt-viewer-slide-easing) flyInRight forwards; + overflow: hidden; + } + + ::view-transition-old(previous) { + animation: var(--vt-duration-viewer-navigation) var(--vt-viewer-slide-easing) flyOutRight forwards; + } + ::view-transition-old(previous-old) { + animation: var(--vt-duration-viewer-navigation) var(--vt-viewer-slide-easing) flyOutRight forwards; + overflow: hidden; + z-index: -1; + } + + ::view-transition-new(previous) { + animation: var(--vt-duration-viewer-navigation) var(--vt-viewer-slide-easing) flyInLeft forwards; + } + + ::view-transition-new(previous-new) { + animation: var(--vt-duration-viewer-navigation) var(--vt-viewer-slide-easing) flyInLeft forwards; + overflow: hidden; + } + + @keyframes flyInLeft { + from { + transform: translateX(calc(-1 * var(--vt-viewer-slide-distance))); + opacity: var(--vt-viewer-opacity-start); + filter: blur(var(--vt-viewer-blur-max)); + } + to { + opacity: 1; + filter: blur(0); + } + } + + @keyframes flyOutLeft { + from { + opacity: 1; + filter: blur(0); + } + to { + transform: translateX(calc(-1 * var(--vt-viewer-slide-distance))); + opacity: var(--vt-viewer-opacity-start); + filter: blur(var(--vt-viewer-blur-max)); + } + } + + @keyframes flyInRight { + from { + transform: translateX(var(--vt-viewer-slide-distance)); + opacity: var(--vt-viewer-opacity-start); + filter: blur(var(--vt-viewer-blur-max)); + } + to { + opacity: 1; + filter: blur(0); + } + } + + @keyframes flyOutRight { + from { + opacity: 1; + filter: blur(0); + } + to { + transform: translateX(var(--vt-viewer-slide-distance)); + opacity: var(--vt-viewer-opacity-start); + filter: blur(var(--vt-viewer-blur-max)); + } + } + + /* cubic fade curves so combined opacity stays close to 1.0 during crossfade */ + @keyframes fadeIn { + from { + opacity: 0; + } + 50% { + opacity: 0.85; + } + to { + opacity: 1; + } + } + @keyframes fadeOut { + from { + opacity: 1; + } + 50% { + opacity: 0.85; + } + to { + opacity: 0; + } + } + + /* Reduced motion: when system preference is set */ + @media (prefers-reduced-motion: reduce) { + ::view-transition-group(hero) { + animation-name: none; + } + + ::view-transition-old(hero) { + animation: none; + display: none; + } + + ::view-transition-new(hero) { + animation: none; + } + + html:active-view-transition-type(viewer) { + &::view-transition-old(hero) { + animation: none; + display: none; + } + &::view-transition-new(hero) { + animation: var(--vt-duration-default) 0s fadeIn forwards; + } + } + + html:active-view-transition-type(timeline) { + &::view-transition-old(hero) { + animation: var(--vt-duration-default) 0s fadeOut forwards; + } + &::view-transition-new(hero) { + animation: var(--vt-duration-default) 0s fadeIn forwards; + } + } + + ::view-transition-group(letterbox-left), + ::view-transition-group(letterbox-right), + ::view-transition-group(letterbox-top), + ::view-transition-group(letterbox-bottom) { + z-index: 100; + } + + ::view-transition-image-pair(letterbox-left), + ::view-transition-image-pair(letterbox-right), + ::view-transition-image-pair(letterbox-top), + ::view-transition-image-pair(letterbox-bottom) { + isolation: auto; + } + + ::view-transition-old(letterbox-left), + ::view-transition-old(letterbox-right), + ::view-transition-old(letterbox-top), + ::view-transition-old(letterbox-bottom), + ::view-transition-new(letterbox-left), + ::view-transition-new(letterbox-right), + ::view-transition-new(letterbox-top), + ::view-transition-new(letterbox-bottom) { + animation: none; + width: 100%; + height: 100%; + object-fit: fill; + } + + ::view-transition-group(previous), + ::view-transition-group(previous-old), + ::view-transition-group(next), + ::view-transition-group(next-old) { + width: 100% !important; + height: 100% !important; + transform: none !important; + } + + ::view-transition-old(previous), + ::view-transition-old(previous-old), + ::view-transition-old(next), + ::view-transition-old(next-old) { + animation: var(--vt-duration-viewer-navigation) fadeOut forwards; + transform-origin: center; + height: 100%; + width: 100%; + object-fit: contain; + overflow: hidden; + } + + ::view-transition-new(previous), + ::view-transition-new(previous-new), + ::view-transition-new(next), + ::view-transition-new(next-new) { + animation: var(--vt-duration-viewer-navigation) fadeIn forwards; + transform-origin: center; + height: 100%; + width: 100%; + object-fit: contain; + } + } +} diff --git a/web/src/lib/components/AdaptiveImage.svelte b/web/src/lib/components/AdaptiveImage.svelte index ec2eb2a24a..15e89729be 100644 --- a/web/src/lib/components/AdaptiveImage.svelte +++ b/web/src/lib/components/AdaptiveImage.svelte @@ -1,12 +1,14 @@ -