├── .eslintrc.json
├── jsconfig.json
├── public
├── favicon.ico
├── vercel.svg
├── thirteen.svg
└── next.svg
├── next.config.js
├── components
├── Footer.js
├── Photo.js
└── ViewTransition.js
├── pages
├── api
│ └── hello.js
├── _app.js
├── _document.js
├── index.js
└── photos
│ └── [slug].js
├── README.md
├── package.json
├── .gitignore
├── photos.js
└── styles
├── globals.css
└── Home.module.css
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/domchristie/photography-view-transitions-nextjs/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: false,
4 | }
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/components/Footer.js:
--------------------------------------------------------------------------------
1 |
2 | export default function Footer () {
3 | return (
4 |
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # View Transitions Demonstration (Next.js)
2 |
3 | Using [Next.js](https://nextjs.org/), and a component that animates page navigations with View Transitions.
4 |
5 | ---
6 | © Dom Christie 2023
7 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '@/styles/globals.css'
2 | import ViewTransition from '@/components/ViewTransition'
3 |
4 | export default function App({ Component, pageProps }) {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/components/Photo.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link.js'
2 |
3 | export default function Photo ({ slug, image, alt, index }) {
4 | const style = { viewTransitionName: `photo-${index}` }
5 |
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-view-transitions-demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "eslint": "8.36.0",
13 | "eslint-config-next": "13.2.4",
14 | "next": "13.2.4",
15 | "react": "18.2.0",
16 | "react-dom": "18.2.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import photos from '@/photos'
3 | import Photo from '@/components/Photo'
4 | import Footer from '@/components/Footer'
5 |
6 | export default function Home() {
7 | return (
8 | <>
9 | Dom Christie Photography
10 |
11 |
12 |
13 | {photos.map((photo, index) => ())}
14 |
15 |
16 |
17 | >
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/ViewTransition.js:
--------------------------------------------------------------------------------
1 | import { Component, StrictMode } from 'react'
2 |
3 | export default class ViewTransition extends Component {
4 | shouldComponentUpdate () {
5 | if (!document.startViewTransition) return true // skip when not supported
6 |
7 | document.startViewTransition(() => this.#updateDOM())
8 | return false // don't update the component, we'll do this manually
9 | }
10 |
11 | #updateDOM () {
12 | // now we know the screenshot has been taken, we can force render
13 | // (which skips `shouldComponentUpdate`)
14 | this.forceUpdate()
15 | // set up a promise that will resolve when the component renders
16 | return new Promise(resolve => { this.#rendered = resolve })
17 | }
18 |
19 | render () {
20 | return {this.props.children}
21 | }
22 |
23 | #rendered = () => {}
24 |
25 | componentDidUpdate () {
26 | // resolve the `updateDOM` promise to notify the View Transition API
27 | // that the DOM has been updated
28 | this.#rendered()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/photos/[slug].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import Link from 'next/link'
3 | import photos from '@/photos'
4 | import Footer from '@/components/Footer'
5 |
6 | export default function Photo ({ index, title, flickr_url, image, alt, location, date, camera, film }) {
7 | const style = { viewTransitionName: `photo-${index}` }
8 |
9 | return (
10 | <>
11 | {`${title} | Dom Christie Photography`}
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | {title}
27 |
28 |
29 | {location}
30 |
31 |
32 |
33 | - Camera
34 | - {camera}
35 | {film && <>- Film
- {film}
>}
36 |
37 |
38 |
39 |
40 |
41 | >
42 | )
43 | }
44 |
45 | export async function getStaticProps({ params }) {
46 | const index = photos.findIndex((photo) => photo.slug === params.slug)
47 | const photo = photos[index]
48 | photo.index = index
49 |
50 | return {
51 | props: photo
52 | }
53 | }
54 |
55 | export async function getStaticPaths() {
56 | return {
57 | paths: photos.map((photo) => {
58 | return {
59 | params: {
60 | slug: photo.slug,
61 | },
62 | }
63 | }),
64 | fallback: false
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/photos.js:
--------------------------------------------------------------------------------
1 |
2 | export default [
3 | {
4 | "title": "Mustang",
5 | "slug": "mustang",
6 | "image": "https://live.staticflickr.com/65535/50187927333_12dc192ab6_b.jpg",
7 | "alt": "The hood of a red Ford Mustang",
8 | "flickr_url": "https://www.flickr.com/photos/domchristie/50187927333/",
9 | "location": "Los Feliz, Los Angeles",
10 | "date": "2020-11-26T00:00:00.000Z",
11 | "camera": "Lomo LC-A",
12 | "film": "Kodak Gold 200",
13 | "description": "Los Feliz, Los Angeles"
14 | },
15 | {
16 | "title": "Shoulder Work",
17 | "slug": "shoulder-work",
18 | "image": "https://live.staticflickr.com/65535/51050627041_6b2297d785_b.jpg",
19 | "alt": "A hilly Pacific coastal scene, accompanied by a luminous orange road sign displaying 'SHOULDER WORK'",
20 | "flickr_url": "https://www.flickr.com/photos/domchristie/51050627041/",
21 | "location": "Pacific Coast Highway, California",
22 | "date": "2020-11-26T00:00:00.000Z",
23 | "camera": "Lomo LC-A",
24 | "film": "Kodak Gold 200",
25 | "description": "Pacific Coast Highway, California"
26 | },
27 | {
28 | "title": "Photo Me",
29 | "slug": "photo-me",
30 | "image": "https://live.staticflickr.com/7922/46633591944_4d3a91c04a_b.jpg",
31 | "alt": "A person sits in a photobooth on an illuminated orange stool, with the curtain drawn",
32 | "flickr_url": "https://www.flickr.com/photos/domchristie/46633591944/",
33 | "location": "Euston Station, London",
34 | "date": "2018-11-24T00:00:00.000Z",
35 | "camera": "Ricoh GR Digital",
36 | "description": "Euston Station, London"
37 | },
38 | {
39 | "title": "Achieve Your Ambition",
40 | "slug": "achieve-your-ambition",
41 | "image": "https://live.staticflickr.com/7541/16141487959_60e2783f41_b.jpg",
42 | "alt": "An old man sleeps on a packed underground train, beneath an advert headlined, 'ACHIEVE YOUR AMBITION'",
43 | "flickr_url": "https://www.flickr.com/photos/domchristie/16141487959/",
44 | "location": "Central Line, London",
45 | "date": "2015-01-20T00:00:00.000Z",
46 | "camera": "Ricoh GR Digital",
47 | "description": "Central Line, London"
48 | },
49 | {
50 | "title": "Smelt it / Dealt it",
51 | "slug": "smelt-it-dealt-it",
52 | "image": "https://live.staticflickr.com/2925/14160806169_e6f6b10446_b.jpg",
53 | "alt": "Amongst a crowd of a dozen, the two central people are holding their noses, smelling something bad",
54 | "flickr_url": "https://www.flickr.com/photos/domchristie/14160806169/",
55 | "location": "Hong Kong",
56 | "date": "2014-05-26T00:00:00.000Z",
57 | "camera": "Olympus mju ii",
58 | "film": "Kodak Gold 200"
59 | },
60 | {
61 | "title": "Untitled",
62 | "slug": "untitled",
63 | "image": "https://live.staticflickr.com/2519/4129054724_33c64146a8_b.jpg",
64 | "alt": "Aeroplane wings above clouds, with the setting sun peaking through",
65 | "flickr_url": "https://www.flickr.com/photos/domchristie/4129054724/",
66 | "location": "In the air above Iceland",
67 | "date": "2009-11-23T00:00:00.000Z",
68 | "camera": "Lomo LC-A",
69 | "film": "Fuji Superia 400"
70 | },
71 | {
72 | "title": "Untitled 6748.13",
73 | "slug": "untitled-6748-13",
74 | "image": "https://live.staticflickr.com/2553/4128215509_350e93f6e1_b.jpg",
75 | "alt": "A pedestrian crossing sign on a typical Reykjavík street, with mountains in the background",
76 | "flickr_url": "https://www.flickr.com/photos/domchristie/4128215509/",
77 | "location": "Reykjavík",
78 | "date": "2009-11-15T00:00:00.000Z",
79 | "camera": "Lomo LC-A",
80 | "film": "Fuji Superia 400"
81 | },
82 | {
83 | "title": "Figaro",
84 | "slug": "figaro",
85 | "image": "https://live.staticflickr.com/65535/51102069848_cdb82ea7e4_b.jpg",
86 | "alt": "Close-up of a vintage car hubcap",
87 | "flickr_url": "https://www.flickr.com/photos/domchristie/51102069848/",
88 | "location": "Willesden Green, London",
89 | "date": "2007-04-15T00:00:00.000Z",
90 | "camera": "Lomo LC-A",
91 | "film": "Agfa Ultra 100",
92 | "description": "Willesden Green, London"
93 | },
94 | {
95 | "title": "Night Bus",
96 | "slug": "night-bus",
97 | "image": "https://live.staticflickr.com/3400/3289907091_f906387549_b.jpg",
98 | "alt": "A man sleeps slumped on the night bus",
99 | "flickr_url": "https://www.flickr.com/photos/domchristie/3289907091/",
100 | "location": "Between Trafalgar Square & Penge",
101 | "date": "2006-06-26T00:00:00.000Z",
102 | "camera": "Lomo LC-A",
103 | "film": "Fujicolor 400",
104 | "description": "Between Trafalgar Square & Penge"
105 | }
106 | ]
107 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
5 | #__next {
6 | -webkit-text-size-adjust: 100%;
7 | font-family: 'Bespoke sans', sans-serif;
8 | line-height: 1.15;
9 | min-height: calc(100vh - env(safe-area-inset-bottom, 0) - env(safe-area-inset-top, 0));
10 | min-height: 100dvh;
11 | display: grid;
12 | grid-template-rows: 1fr auto 1fr;
13 | }
14 |
15 | @supports (-webkit-touch-callout: none) {
16 | #__next {
17 | min-height: -webkit-fill-available;
18 | min-height: 100dvh;
19 | }
20 | }
21 |
22 | header,
23 | footer {
24 | padding: .5rem;
25 | }
26 |
27 | header {
28 | display: flex;
29 | }
30 |
31 | header a {
32 | text-decoration: none;
33 | color: inherit;
34 | }
35 |
36 | header a svg {
37 | width: 2rem;
38 | height: 2rem;
39 | }
40 |
41 | footer {
42 | margin-top: auto;
43 | line-height: 1.5rem;
44 | text-align: center;
45 | }
46 |
47 | footer a {
48 | color: inherit;
49 | }
50 |
51 | main {
52 | margin: 0 auto;
53 | }
54 |
55 | ol[aria-label="Photographs"] {
56 | margin: .375rem;
57 | padding: 0;
58 | list-style: none;
59 | display: grid;
60 | grid-template-columns: auto auto auto;
61 | grid-gap: .375rem;
62 | }
63 |
64 | ol[aria-label="Photographs"] img {
65 | display: block;
66 | max-width: 100%;
67 | width: 15rem;
68 | height: auto;
69 | contain: layout;
70 | }
71 |
72 | figure {
73 | margin: 0;
74 | padding: 0 .5rem;
75 | display: inline-flex;
76 | flex-direction: column;
77 | }
78 |
79 | figure img {
80 | display: block;
81 | max-width: 100%;
82 | max-height: 74vh;
83 | margin-right: auto;
84 | margin-left: auto;
85 | contain: layout;
86 | }
87 |
88 | figcaption {
89 | position: relative;
90 | padding-top: 1rem;
91 | margin-top: 1rem;
92 | text-align: left;
93 | line-height: 1.375;
94 | }
95 |
96 | figcaption h2,
97 | figcaption div,
98 | figcaption dl {
99 | contain: layout;
100 | }
101 |
102 | figcaption h2 {
103 | view-transition-name: photo-heading;
104 | }
105 |
106 | figcaption div {
107 | view-transition-name: photo-location-time;
108 | }
109 |
110 | figcaption dl {
111 | view-transition-name: photo-meta;
112 | }
113 |
114 | /* Enter */
115 | ::view-transition-new(photo-heading):only-child,
116 | ::view-transition-new(photo-location-time):only-child,
117 | ::view-transition-new(photo-meta):only-child {
118 | animation: 300ms ease var(--animation-delay, 0s) both fade-in,
119 | 300ms ease var(--animation-delay, 0s) both slide-up;
120 | }
121 |
122 | ::view-transition-new(photo-heading):only-child {
123 | --animation-delay: 50ms;
124 | }
125 |
126 | ::view-transition-new(photo-location-time):only-child {
127 | --animation-delay: 100ms;
128 | }
129 |
130 | ::view-transition-new(photo-meta):only-child {
131 | --animation-delay: 150ms;
132 | }
133 |
134 | /* Exit */
135 | ::view-transition-old(photo-heading):only-child,
136 | ::view-transition-old(photo-location-time):only-child,
137 | ::view-transition-old(photo-meta):only-child {
138 | animation: 200ms ease var(--animation-delay, 0s) both fade-out,
139 | 200ms ease var(--animation-delay, 0s) both slide-down;
140 | }
141 |
142 | ::view-transition-old(photo-heading):only-child {
143 | --animation-delay: 150ms;
144 | }
145 |
146 | ::view-transition-old(photo-location-time):only-child {
147 | --animation-delay: 100ms;
148 | }
149 |
150 | ::view-transition-old(photo-meta):only-child {
151 | --animation-delay: 50ms;
152 | }
153 |
154 | @keyframes fade-in {
155 | 0% {
156 | opacity: 0
157 | }
158 |
159 | 100% {
160 | opacity: 1
161 | }
162 | }
163 |
164 | @keyframes fade-out {
165 | 0% {
166 | opacity: 1
167 | }
168 |
169 | 100% {
170 | opacity: 0
171 | }
172 | }
173 |
174 | @keyframes slide-up {
175 | 0% {
176 | transform: translateY(2.5rem)
177 | }
178 |
179 | 100% {
180 | transform: translateY(0)
181 | }
182 | }
183 |
184 | @keyframes slide-down {
185 | 0% {
186 | transform: translateY(0)
187 | }
188 |
189 | 100% {
190 | transform: translateY(2.5rem)
191 | }
192 | }
193 |
194 | figcaption::before {
195 | content: '';
196 | display: block;
197 | position: absolute;
198 | top: 2px;
199 | left: 0;
200 | width: 2.25rem;
201 | height: 2px;
202 | background-color: black;
203 | }
204 |
205 | figure h2 {
206 | margin: 0;
207 | font-size: 1em;
208 | }
209 |
210 | figure h2 a {
211 | text-decoration: none;
212 | color: inherit;
213 | }
214 |
215 | figure dl {
216 | margin: 0;
217 | display: flex;
218 | align-items: center;
219 | }
220 |
221 | figure dt,
222 | figure dd {
223 | margin: 0;
224 | display: inline;
225 | font-size: 1em;
226 | color: #666;
227 | }
228 |
229 | figure dt {
230 | position: absolute;
231 | width: 1px;
232 | height: 1px;
233 | padding: 0;
234 | margin: -1px;
235 | overflow: hidden;
236 | clip: rect(0, 0, 0, 0);
237 | border: 0;
238 | }
239 |
240 | figure dd+dt+dd {
241 | margin-left: .375rem;
242 | }
243 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | align-items: center;
6 | padding: 6rem;
7 | min-height: 100vh;
8 | }
9 |
10 | .description {
11 | display: inherit;
12 | justify-content: inherit;
13 | align-items: inherit;
14 | font-size: 0.85rem;
15 | max-width: var(--max-width);
16 | width: 100%;
17 | z-index: 2;
18 | font-family: var(--font-mono);
19 | }
20 |
21 | .description a {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | gap: 0.5rem;
26 | }
27 |
28 | .description p {
29 | position: relative;
30 | margin: 0;
31 | padding: 1rem;
32 | background-color: rgba(var(--callout-rgb), 0.5);
33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34 | border-radius: var(--border-radius);
35 | }
36 |
37 | .code {
38 | font-weight: 700;
39 | font-family: var(--font-mono);
40 | }
41 |
42 | .grid {
43 | display: grid;
44 | grid-template-columns: repeat(4, minmax(25%, auto));
45 | width: var(--max-width);
46 | max-width: 100%;
47 | }
48 |
49 | .card {
50 | padding: 1rem 1.2rem;
51 | border-radius: var(--border-radius);
52 | background: rgba(var(--card-rgb), 0);
53 | border: 1px solid rgba(var(--card-border-rgb), 0);
54 | transition: background 200ms, border 200ms;
55 | }
56 |
57 | .card span {
58 | display: inline-block;
59 | transition: transform 200ms;
60 | }
61 |
62 | .card h2 {
63 | font-weight: 600;
64 | margin-bottom: 0.7rem;
65 | }
66 |
67 | .card p {
68 | margin: 0;
69 | opacity: 0.6;
70 | font-size: 0.9rem;
71 | line-height: 1.5;
72 | max-width: 30ch;
73 | }
74 |
75 | .center {
76 | display: flex;
77 | justify-content: center;
78 | align-items: center;
79 | position: relative;
80 | padding: 4rem 0;
81 | }
82 |
83 | .center::before {
84 | background: var(--secondary-glow);
85 | border-radius: 50%;
86 | width: 480px;
87 | height: 360px;
88 | margin-left: -400px;
89 | }
90 |
91 | .center::after {
92 | background: var(--primary-glow);
93 | width: 240px;
94 | height: 180px;
95 | z-index: -1;
96 | }
97 |
98 | .center::before,
99 | .center::after {
100 | content: '';
101 | left: 50%;
102 | position: absolute;
103 | filter: blur(45px);
104 | transform: translateZ(0);
105 | }
106 |
107 | .logo,
108 | .thirteen {
109 | position: relative;
110 | }
111 |
112 | .thirteen {
113 | display: flex;
114 | justify-content: center;
115 | align-items: center;
116 | width: 75px;
117 | height: 75px;
118 | padding: 25px 10px;
119 | margin-left: 16px;
120 | transform: translateZ(0);
121 | border-radius: var(--border-radius);
122 | overflow: hidden;
123 | box-shadow: 0px 2px 8px -1px #0000001a;
124 | }
125 |
126 | .thirteen::before,
127 | .thirteen::after {
128 | content: '';
129 | position: absolute;
130 | z-index: -1;
131 | }
132 |
133 | /* Conic Gradient Animation */
134 | .thirteen::before {
135 | animation: 6s rotate linear infinite;
136 | width: 200%;
137 | height: 200%;
138 | background: var(--tile-border);
139 | }
140 |
141 | /* Inner Square */
142 | .thirteen::after {
143 | inset: 0;
144 | padding: 1px;
145 | border-radius: var(--border-radius);
146 | background: linear-gradient(
147 | to bottom right,
148 | rgba(var(--tile-start-rgb), 1),
149 | rgba(var(--tile-end-rgb), 1)
150 | );
151 | background-clip: content-box;
152 | }
153 |
154 | /* Enable hover only on non-touch devices */
155 | @media (hover: hover) and (pointer: fine) {
156 | .card:hover {
157 | background: rgba(var(--card-rgb), 0.1);
158 | border: 1px solid rgba(var(--card-border-rgb), 0.15);
159 | }
160 |
161 | .card:hover span {
162 | transform: translateX(4px);
163 | }
164 | }
165 |
166 | @media (prefers-reduced-motion) {
167 | .thirteen::before {
168 | animation: none;
169 | }
170 |
171 | .card:hover span {
172 | transform: none;
173 | }
174 | }
175 |
176 | /* Mobile */
177 | @media (max-width: 700px) {
178 | .content {
179 | padding: 4rem;
180 | }
181 |
182 | .grid {
183 | grid-template-columns: 1fr;
184 | margin-bottom: 120px;
185 | max-width: 320px;
186 | text-align: center;
187 | }
188 |
189 | .card {
190 | padding: 1rem 2.5rem;
191 | }
192 |
193 | .card h2 {
194 | margin-bottom: 0.5rem;
195 | }
196 |
197 | .center {
198 | padding: 8rem 0 6rem;
199 | }
200 |
201 | .center::before {
202 | transform: none;
203 | height: 300px;
204 | }
205 |
206 | .description {
207 | font-size: 0.8rem;
208 | }
209 |
210 | .description a {
211 | padding: 1rem;
212 | }
213 |
214 | .description p,
215 | .description div {
216 | display: flex;
217 | justify-content: center;
218 | position: fixed;
219 | width: 100%;
220 | }
221 |
222 | .description p {
223 | align-items: center;
224 | inset: 0 0 auto;
225 | padding: 2rem 1rem 1.4rem;
226 | border-radius: 0;
227 | border: none;
228 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
229 | background: linear-gradient(
230 | to bottom,
231 | rgba(var(--background-start-rgb), 1),
232 | rgba(var(--callout-rgb), 0.5)
233 | );
234 | background-clip: padding-box;
235 | backdrop-filter: blur(24px);
236 | }
237 |
238 | .description div {
239 | align-items: flex-end;
240 | pointer-events: none;
241 | inset: auto 0 0;
242 | padding: 2rem;
243 | height: 200px;
244 | background: linear-gradient(
245 | to bottom,
246 | transparent 0%,
247 | rgb(var(--background-end-rgb)) 40%
248 | );
249 | z-index: 1;
250 | }
251 | }
252 |
253 | /* Tablet and Smaller Desktop */
254 | @media (min-width: 701px) and (max-width: 1120px) {
255 | .grid {
256 | grid-template-columns: repeat(2, 50%);
257 | }
258 | }
259 |
260 | @media (prefers-color-scheme: dark) {
261 | .vercelLogo {
262 | filter: invert(1);
263 | }
264 |
265 | .logo,
266 | .thirteen img {
267 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
268 | }
269 | }
270 |
271 | @keyframes rotate {
272 | from {
273 | transform: rotate(360deg);
274 | }
275 | to {
276 | transform: rotate(0deg);
277 | }
278 | }
279 |
--------------------------------------------------------------------------------