From f53d25e0a53680672dab54eee91723a93f347c79 Mon Sep 17 00:00:00 2001 From: openhands Date: Mon, 20 Jul 2026 14:06:43 +0200 Subject: [PATCH] feat: add view transitions, smooth scroll, bundle analyzer, SSE radio stream, and AnimatePresence animations --- next.config.ts | 9 +- package.json | 4 + pnpm-lock.yaml | 164 +++++++++++++++++++++++ src/app/layout.tsx | 7 +- src/components/admin/confirm-dialog.tsx | 124 ++++++++++------- src/components/cms-info-popup.tsx | 105 ++++++++------- src/components/public/photo-lightbox.tsx | 151 +++++++++++---------- src/components/public/radio-player.tsx | 66 ++++----- src/components/smooth-scroll.tsx | 30 +++++ src/lib/use-event-source.ts | 67 +++++++++ update-Nitrov3.sh | 21 +-- 11 files changed, 519 insertions(+), 229 deletions(-) create mode 100644 src/components/smooth-scroll.tsx create mode 100644 src/lib/use-event-source.ts diff --git a/next.config.ts b/next.config.ts index 355b8e48..62e5a44b 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,7 @@ import type { NextConfig } from "next"; import { withSentryConfig } from "@sentry/nextjs"; import createNextIntlPlugin from "next-intl/plugin"; +import withBundleAnalyzer from "@next/bundle-analyzer"; const securityHeaders = [ { key: "X-DNS-Prefetch-Control", value: "on" }, @@ -90,7 +91,11 @@ const config = withNextIntl(nextConfig); // so CI/prod compile stays quiet (runtime DSN still works independently). const sentryAuthToken = process.env.SENTRY_AUTH_TOKEN; -export default withSentryConfig(config, { +const withBA = withBundleAnalyzer({ + enabled: process.env.ANALYZE === "true", +}); + +export default withBA(withSentryConfig(config, { org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, authToken: sentryAuthToken, @@ -102,4 +107,4 @@ export default withSentryConfig(config, { release: { create: Boolean(sentryAuthToken), }, -}); +})); diff --git a/package.json b/package.json index 7ab6e011..e00aa6cc 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "biome:check": "biome check --write .", "biome:lint": "biome lint .", "biome:format": "biome format --write .", + "analyze": "ANALYZE=true pnpm build", "test": "vitest run", "db:migrate": "tsx scripts/apply-migrations.ts", "db:migrate:status": "tsx scripts/apply-migrations.ts --status", @@ -41,6 +42,7 @@ "jpeg-js": "^0.4.4", "json5": "^2.2.3", "jszip": "^3.10.1", + "lenis": "^1.3.25", "lucide-react": "^1.25.0", "lzma": "^2.3.2", "music-metadata": "^11.14.0", @@ -48,6 +50,7 @@ "next": "^16.2.10", "next-auth": "5.0.0-beta.31", "next-intl": "^4.13.2", + "next-view-transitions": "^0.3.5", "nodemailer": "^9.0.3", "otplib": "^12.0.1", "pino": "^10.3.1", @@ -65,6 +68,7 @@ }, "devDependencies": { "@biomejs/biome": "2.5.4", + "@next/bundle-analyzer": "^16.2.10", "@tailwindcss/forms": "^0.5.11", "@tailwindcss/postcss": "^4.3.3", "@tailwindcss/typography": "^0.5.20", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c71da323..4e3d0632 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,6 +73,9 @@ importers: jszip: specifier: ^3.10.1 version: 3.10.1 + lenis: + specifier: ^1.3.25 + version: 1.3.25(react@19.2.7) lucide-react: specifier: ^1.25.0 version: 1.25.0(react@19.2.7) @@ -94,6 +97,9 @@ importers: next-intl: specifier: ^4.13.2 version: 4.13.2(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(typescript@5.9.3) + next-view-transitions: + specifier: ^0.3.5 + version: 0.3.5(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) nodemailer: specifier: ^9.0.3 version: 9.0.3 @@ -140,6 +146,9 @@ importers: '@biomejs/biome': specifier: 2.5.4 version: 2.5.4 + '@next/bundle-analyzer': + specifier: ^16.2.10 + version: 16.2.10 '@tailwindcss/forms': specifier: ^0.5.11 version: 0.5.11(tailwindcss@4.3.3) @@ -381,6 +390,10 @@ packages: '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + '@discoveryjs/json-ext@0.5.7': + resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} + engines: {node: '>=10.0.0'} + '@dnd-kit/accessibility@3.1.1': resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} peerDependencies: @@ -1106,6 +1119,9 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@next/bundle-analyzer@16.2.10': + resolution: {integrity: sha512-KcepWhb3IVniZgm00GSSCQDEUQqZXuXtuXRh8J6e3Un342TcQ77iK4DedeEkct+fcx7yFEDL2J6z4Jeho5JDAw==} + '@next/env@16.2.10': resolution: {integrity: sha512-zLPxg9M0MEHmygpj5OuxjQ+vHMiy/K7cSp74G8ecYolmgUWw0RwN02tF56npup/+qaI8JB97hQgS/r2Hb6QwVA==} @@ -1548,6 +1564,9 @@ packages: '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@prisma/adapter-mariadb@7.8.0': resolution: {integrity: sha512-mWsgcfbUjxB3qSzRlLs8E03vsKrqXzYK2zpx3e8u6wIgeHJM/sE46cuOGcYvHiZGmeQLCd3xL6YSSGM9QOLI6w==} @@ -2506,6 +2525,10 @@ packages: peerDependencies: acorn: ^8.14.0 + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + acorn@8.17.0: resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} engines: {node: '>=0.4.0'} @@ -2651,6 +2674,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -2689,6 +2716,9 @@ packages: dayjs@1.11.21: resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -2764,6 +2794,9 @@ packages: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + effect@3.20.0: resolution: {integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==} @@ -2959,6 +2992,10 @@ packages: graphmatch@1.1.1: resolution: {integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==} + gzip-size@6.0.0: + resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} + engines: {node: '>=10'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2973,6 +3010,9 @@ packages: resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==} engines: {node: '>=16.9.0'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} @@ -3091,6 +3131,20 @@ packages: launder@1.7.1: resolution: {integrity: sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw==} + lenis@1.3.25: + resolution: {integrity: sha512-mOKxayErlaONK8fm4LN3XNd99Qu4plTpn9h9qf8wxzjGrJDzuD84FYzZ81HCd6ZsWp++VWVwOzL286Pf2s2u4A==} + peerDependencies: + '@nuxt/kit': '>=3.0.0' + react: '>=17.0.0' + vue: '>=3.0.0' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + react: + optional: true + vue: + optional: true + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -3294,6 +3348,10 @@ packages: motion-utils@12.39.0: resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3356,6 +3414,13 @@ packages: typescript: optional: true + next-view-transitions@0.3.5: + resolution: {integrity: sha512-yP8OPNydLmKpmE94hLurLGEzPsUy1uyl9iSv8oxaC2JwhSXTD86SVwk1NMMQT7Ado4kMENDJ7fNBIXHy3GU/Lg==} + peerDependencies: + next: '>=14.0.0' + react: '>=18.2.0 || ^19.0.0' + react-dom: '>=18.2.0 || ^19.0.0' + next@16.2.10: resolution: {integrity: sha512-2som5AVXb3kE6Yjine3/mNbBayYF58eguBWIVVUdr1y/L426xyVEgYxgBG+1QC34P2x5E+tcDup6XkuOAX3dCA==} engines: {node: '>=20.9.0'} @@ -3410,6 +3475,10 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + opener@1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} + hasBin: true + otplib@12.0.1: resolution: {integrity: sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==} @@ -3728,6 +3797,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + smol-toml@1.7.0: resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} engines: {node: '>= 18'} @@ -3862,6 +3935,10 @@ packages: resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -4015,6 +4092,11 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webpack-bundle-analyzer@4.10.1: + resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==} + engines: {node: '>= 10.13.0'} + hasBin: true + webpack-sources@3.5.1: resolution: {integrity: sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==} engines: {node: '>=10.13.0'} @@ -4048,6 +4130,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@7.5.13: + resolution: {integrity: sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -4269,6 +4363,8 @@ snapshots: '@borewit/text-codec@0.2.2': {} + '@discoveryjs/json-ext@0.5.7': {} + '@dnd-kit/accessibility@3.1.1(react@19.2.7)': dependencies: react: 19.2.7 @@ -4758,6 +4854,13 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@next/bundle-analyzer@16.2.10': + dependencies: + webpack-bundle-analyzer: 4.10.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@next/env@16.2.10': {} '@next/swc-darwin-arm64@16.2.10': @@ -5041,6 +5144,8 @@ snapshots: '@pinojs/redact@0.4.0': {} + '@polka/url@1.0.0-next.29': {} + '@prisma/adapter-mariadb@7.8.0': dependencies: '@prisma/driver-adapter-utils': 7.8.0 @@ -5951,6 +6056,10 @@ snapshots: dependencies: acorn: 8.17.0 + acorn-walk@8.3.5: + dependencies: + acorn: 8.17.0 + acorn@8.17.0: {} agent-base@6.0.2: @@ -6080,6 +6189,8 @@ snapshots: commander@2.20.3: {} + commander@7.2.0: {} + commondir@1.0.1: {} confbox@0.2.4: {} @@ -6106,6 +6217,8 @@ snapshots: dayjs@1.11.21: {} + debounce@1.2.1: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -6166,6 +6279,8 @@ snapshots: dotenv@17.4.2: {} + duplexer@0.1.2: {} + effect@3.20.0: dependencies: '@standard-schema/spec': 1.1.0 @@ -6369,6 +6484,10 @@ snapshots: graphmatch@1.1.1: {} + gzip-size@6.0.0: + dependencies: + duplexer: 0.1.2 + has-flag@4.0.0: {} hash-wasm@4.12.0: {} @@ -6377,6 +6496,8 @@ snapshots: hono@4.12.27: {} + html-escaper@2.0.2: {} + htmlparser2@10.1.0: dependencies: domelementtype: 2.3.0 @@ -6512,6 +6633,10 @@ snapshots: dependencies: dayjs: 1.11.21 + lenis@1.3.25(react@19.2.7): + optionalDependencies: + react: 19.2.7 + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -6640,6 +6765,8 @@ snapshots: motion-utils@12.39.0: {} + mrmime@2.0.1: {} + ms@2.1.3: {} music-metadata@11.14.0: @@ -6718,6 +6845,12 @@ snapshots: transitivePeerDependencies: - '@swc/helpers' + next-view-transitions@0.3.5(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + next: 16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@next/env': 16.2.10 @@ -6763,6 +6896,8 @@ snapshots: dependencies: wrappy: 1.0.2 + opener@1.5.2: {} + otplib@12.0.1: dependencies: '@otplib/core': 12.0.1 @@ -7188,6 +7323,12 @@ snapshots: signal-exit@4.1.0: {} + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + smol-toml@1.7.0: {} sonic-boom@4.2.1: @@ -7294,6 +7435,8 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + totalist@3.0.1: {} + tr46@0.0.3: {} tslib@2.8.1: {} @@ -7427,6 +7570,25 @@ snapshots: webidl-conversions@3.0.1: {} + webpack-bundle-analyzer@4.10.1: + dependencies: + '@discoveryjs/json-ext': 0.5.7 + acorn: 8.17.0 + acorn-walk: 8.3.5 + commander: 7.2.0 + debounce: 1.2.1 + escape-string-regexp: 4.0.0 + gzip-size: 6.0.0 + html-escaper: 2.0.2 + is-plain-object: 5.0.0 + opener: 1.5.2 + picocolors: 1.1.1 + sirv: 2.0.4 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + webpack-sources@3.5.1: {} webpack@5.108.4(lightningcss@1.32.0)(postcss@8.5.19): @@ -7485,6 +7647,8 @@ snapshots: wrappy@1.0.2: {} + ws@7.5.13: {} + yallist@3.1.1: {} yaml@2.9.0: {} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ab9b19a2..9dc6ae66 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,8 +4,10 @@ import Script from "next/script"; import { NextIntlClientProvider } from "next-intl"; import { getLocale, getMessages } from "next-intl/server"; import type { ReactNode } from "react"; +import { ViewTransitions } from "next-view-transitions"; import { PwaRegister } from "@/components/pwa-register"; import { ThemeVars } from "@/components/theme-vars"; +import { SmoothScroll } from "@/components/smooth-scroll"; import { enforceSiteAccess } from "@/lib/access-guard"; import { siteSettings } from "@/lib/services/site-settings"; import "./globals.css"; @@ -80,8 +82,11 @@ export default async function RootLayout({ > - {children} + + {children} + + diff --git a/src/components/admin/confirm-dialog.tsx b/src/components/admin/confirm-dialog.tsx index 90adc189..369513d4 100644 --- a/src/components/admin/confirm-dialog.tsx +++ b/src/components/admin/confirm-dialog.tsx @@ -1,9 +1,11 @@ "use client"; +import { AnimatePresence, motion } from "framer-motion"; import { useCallback, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; +import { modalContentVariants, modalOverlayVariants } from "@/lib/motion"; export type ConfirmDialogProps = { open: boolean; @@ -57,61 +59,83 @@ export function ConfirmDialog({ } } - if (!open || !mounted) return null; + if (!mounted) return null; + + const isOpen = open; return createPortal( -
- - -
- - , +
+

+ {title} +

+

+ {description} +

+
+
+ + +
+ + + ) : null} + , document.body, ); } diff --git a/src/components/cms-info-popup.tsx b/src/components/cms-info-popup.tsx index f8c7f05a..6161a46b 100644 --- a/src/components/cms-info-popup.tsx +++ b/src/components/cms-info-popup.tsx @@ -1,5 +1,6 @@ "use client"; +import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useState } from "react"; const cmsDetails = [ @@ -38,54 +39,66 @@ export function CmsInfoPopup() { > EpicNext Info - {open ? ( -
setOpen(false)} - > -
event.stopPropagation()} + + {open ? ( + setOpen(false)} + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + transition={{ duration: 0.2 }} > - -
CMS information
-

EpicNext

-
Modern Next.js CMS
-

- A modern web layer for the hotel community, focused on fast - navigation, clean administration, and a coherent EpicNext - experience. -

-
- {cmsDetails.map(([label, value]) => ( -
-
{label}
-
{value}
-
- ))} -
-
- {credits.map(([name, role]) => ( -
- {name} - {role} -
- ))} -
-
-
- ) : null} + +
CMS information
+

EpicNext

+
Modern Next.js CMS
+

+ A modern web layer for the hotel community, focused on fast + navigation, clean administration, and a coherent EpicNext + experience. +

+
+ {cmsDetails.map(([label, value]) => ( +
+
{label}
+
{value}
+
+ ))} +
+
+ {credits.map(([name, role]) => ( +
+ {name} + {role} +
+ ))} +
+ + + ) : null} + ); } diff --git a/src/components/public/photo-lightbox.tsx b/src/components/public/photo-lightbox.tsx index 71508203..401c09da 100644 --- a/src/components/public/photo-lightbox.tsx +++ b/src/components/public/photo-lightbox.tsx @@ -1,5 +1,6 @@ "use client"; +import { AnimatePresence, motion } from "framer-motion"; import { useCallback, useEffect, useState } from "react"; /** @@ -83,86 +84,94 @@ export function PhotoLightbox({ photos }: { photos: LightboxPhoto[] }) { ))} - {active ? ( -
- - {/* Stop propagation so clicking the image itself doesn't close. */} -
e.stopPropagation()} - style={{ - margin: 0, - maxWidth: "min(92vw, 1100px)", - maxHeight: "90vh", + position: "fixed", + inset: 0, + zIndex: 1000, display: "flex", - flexDirection: "column", alignItems: "center", - gap: "0.75rem", - cursor: "default", + justifyContent: "center", + padding: "1.5rem", + background: "rgba(0, 0, 0, 0.82)", + backdropFilter: "blur(8px)", + cursor: "zoom-out", }} > - {/* eslint-disable-next-line @next/next/no-img-element */} - {active.alt} -
- {active.date} · {active.user} -
-
-
- ) : null} + ✕ + + {/* Stop propagation so clicking the image itself doesn't close. */} +
e.stopPropagation()} + style={{ + margin: 0, + maxWidth: "min(92vw, 1100px)", + maxHeight: "90vh", + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: "0.75rem", + cursor: "default", + }} + > + {/* eslint-disable-next-line @next/next/no-img-element */} + {active.alt} +
+ {active.date} · {active.user} +
+
+ + ) : null} + ); } diff --git a/src/components/public/radio-player.tsx b/src/components/public/radio-player.tsx index 2630235a..b97ba4c0 100644 --- a/src/components/public/radio-player.tsx +++ b/src/components/public/radio-player.tsx @@ -9,12 +9,9 @@ import { useCallback, useEffect, useRef, useState } from "react"; * radio via a hidden