Refacto context+styles and improved lightbox
							
								
								
									
										3987
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						| @ -18,22 +18,24 @@ | |||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@fontsource/material-icons": "^4.5.4", |     "@fontsource/material-icons": "^4.5.4", | ||||||
|  |     "@fontsource/material-icons-outlined": "^4.5.4", | ||||||
|     "@fontsource/opendyslexic": "^4.5.4", |     "@fontsource/opendyslexic": "^4.5.4", | ||||||
|     "@fontsource/share-tech-mono": "^4.5.9", |     "@fontsource/share-tech-mono": "^4.5.9", | ||||||
|     "@fontsource/vollkorn": "^4.5.12", |     "@fontsource/vollkorn": "^4.5.12", | ||||||
|     "@fontsource/zen-maru-gothic": "^4.5.13", |     "@fontsource/zen-maru-gothic": "^4.5.13", | ||||||
|     "@tippyjs/react": "^4.2.6", |     "@tippyjs/react": "^4.2.6", | ||||||
|     "@types/ua-parser-js": "^0.7.36", |  | ||||||
|     "autoprefixer": "^10.4.12", |     "autoprefixer": "^10.4.12", | ||||||
|     "graphql-request": "^5.0.0", |     "graphql-request": "^5.0.0", | ||||||
|     "markdown-to-jsx": "^7.1.7", |     "markdown-to-jsx": "^7.1.7", | ||||||
|     "meilisearch": "^0.28.0", |     "meilisearch": "^0.28.0", | ||||||
|     "next": "^12.3.1", |     "next": "^12.3.1", | ||||||
|     "nodemailer": "^6.8.0", |     "nodemailer": "^6.8.0", | ||||||
|  |     "npm": "^8.19.2", | ||||||
|     "rc-slider": "^10.0.1", |     "rc-slider": "^10.0.1", | ||||||
|     "react": "18.2.0", |     "react": "18.2.0", | ||||||
|     "react-dom": "18.2.0", |     "react-dom": "18.2.0", | ||||||
|     "react-hot-keys": "^2.7.2", |     "react-hot-keys": "^2.7.2", | ||||||
|  |     "react-hotkeys-hook": "^3.4.7", | ||||||
|     "react-swipeable": "^7.0.0", |     "react-swipeable": "^7.0.0", | ||||||
|     "react-zoom-pan-pinch": "^2.1.3", |     "react-zoom-pan-pinch": "^2.1.3", | ||||||
|     "string-natural-compare": "^3.0.1", |     "string-natural-compare": "^3.0.1", | ||||||
| @ -56,6 +58,7 @@ | |||||||
|     "@types/string-natural-compare": "^3.0.2", |     "@types/string-natural-compare": "^3.0.2", | ||||||
|     "@types/throttle-debounce": "^5.0.0", |     "@types/throttle-debounce": "^5.0.0", | ||||||
|     "@types/turndown": "^5.0.1", |     "@types/turndown": "^5.0.1", | ||||||
|  |     "@types/ua-parser-js": "^0.7.36", | ||||||
|     "@typescript-eslint/eslint-plugin": "^5.40.1", |     "@typescript-eslint/eslint-plugin": "^5.40.1", | ||||||
|     "@typescript-eslint/parser": "^5.40.1", |     "@typescript-eslint/parser": "^5.40.1", | ||||||
|     "dotenv": "^16.0.3", |     "dotenv": "^16.0.3", | ||||||
|  | |||||||
| Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.4 KiB | 
| @ -1 +1 @@ | |||||||
| <svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="creative-commons" class="svg-inline--fa fa-creative-commons" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M245.8 214.9l-33.22 17.28c-9.43-19.58-25.24-19.93-27.46-19.93-22.13 0-33.22 14.61-33.22 43.84 0 23.57 9.21 43.84 33.22 43.84 14.47 0 24.65-7.09 30.57-21.26l30.55 15.5c-6.17 11.51-25.69 38.98-65.1 38.98-22.6 0-73.96-10.32-73.96-77.05 0-58.69 43-77.06 72.63-77.06 30.72-.01 52.7 11.95 65.99 35.86zm143.1 0l-32.78 17.28c-9.5-19.77-25.72-19.93-27.9-19.93-22.14 0-33.22 14.61-33.22 43.84 0 23.55 9.23 43.84 33.22 43.84 14.45 0 24.65-7.09 30.54-21.26l31 15.5c-2.1 3.75-21.39 38.98-65.09 38.98-22.69 0-73.96-9.87-73.96-77.05 0-58.67 42.97-77.06 72.63-77.06 30.71-.01 52.58 11.95 65.56 35.86zM247.6 8.05C104.7 8.05 0 123.1 0 256c0 138.5 113.6 248 247.6 248 129.9 0 248.4-100.9 248.4-248 0-137.9-106.6-248-248.4-248zm.87 450.8c-112.5 0-203.7-93.04-203.7-202.8 0-105.4 85.43-203.3 203.7-203.3 112.5 0 202.8 89.46 202.8 203.3-.01 121.7-99.68 202.8-202.8 202.8z"></path></svg> | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M245.8 214.9l-33.22 17.28c-9.43-19.58-25.24-19.93-27.46-19.93-22.13 0-33.22 14.61-33.22 43.84 0 23.57 9.21 43.84 33.22 43.84 14.47 0 24.65-7.09 30.57-21.26l30.55 15.5c-6.17 11.51-25.69 38.98-65.1 38.98-22.6 0-73.96-10.32-73.96-77.05 0-58.69 43-77.06 72.63-77.06 30.72-.01 52.7 11.95 65.99 35.86zm143.1 0l-32.78 17.28c-9.5-19.77-25.72-19.93-27.9-19.93-22.14 0-33.22 14.61-33.22 43.84 0 23.55 9.23 43.84 33.22 43.84 14.45 0 24.65-7.09 30.54-21.26l31 15.5c-2.1 3.75-21.39 38.98-65.09 38.98-22.69 0-73.96-9.87-73.96-77.05 0-58.67 42.97-77.06 72.63-77.06 30.71-.01 52.58 11.95 65.56 35.86zM247.6 8.05C104.7 8.05 0 123.1 0 256c0 138.5 113.6 248 247.6 248C377.5 504 496 403.1 496 256 496 118.1 389.4 8 247.6 8zm.87 450.8c-112.5 0-203.7-93.04-203.7-202.8 0-105.4 85.43-203.3 203.7-203.3 112.5 0 202.8 89.46 202.8 203.3-.01 121.7-99.68 202.8-202.8 202.8z"/></svg> | ||||||
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 925 B | 
| @ -1 +1 @@ | |||||||
| <svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="creative-commons-by" class="svg-inline--fa fa-creative-commons-by" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M314.9 194.4v101.4h-28.3v120.5h-77.1V295.9h-28.3V194.4c0-4.4 1.6-8.2 4.6-11.3 3.1-3.1 6.9-4.7 11.3-4.7H299c4.1 0 7.8 1.6 11.1 4.7 3.1 3.2 4.8 6.9 4.8 11.3zm-101.5-63.7c0-23.3 11.5-35 34.5-35s34.5 11.7 34.5 35c0 23-11.5 34.5-34.5 34.5s-34.5-11.5-34.5-34.5zM247.6 8C389.4 8 496 118.1 496 256c0 147.1-118.5 248-248.4 248C113.6 504 0 394.5 0 256 0 123.1 104.7 8 247.6 8zm.8 44.7C130.2 52.7 44.7 150.6 44.7 256c0 109.8 91.2 202.8 203.7 202.8 103.2 0 202.8-81.1 202.8-202.8 .1-113.8-90.2-203.3-202.8-203.3z"></path></svg> | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M314.9 194.4v101.4h-28.3v120.5h-77.1V295.9h-28.3V194.4c0-4.4 1.6-8.2 4.6-11.3 3.1-3.1 6.9-4.7 11.3-4.7H299c4.1 0 7.8 1.6 11.1 4.7 3.1 3.2 4.8 6.9 4.8 11.3zm-101.5-63.7c0-23.3 11.5-35 34.5-35s34.5 11.7 34.5 35c0 23-11.5 34.5-34.5 34.5s-34.5-11.5-34.5-34.5zM247.6 8C389.4 8 496 118.1 496 256c0 147.1-118.5 248-248.4 248C113.6 504 0 394.5 0 256 0 123.1 104.7 8 247.6 8zm.8 44.7C130.2 52.7 44.7 150.6 44.7 256c0 109.8 91.2 202.8 203.7 202.8 103.2 0 202.8-81.1 202.8-202.8.1-113.8-90.2-203.3-202.8-203.3z"/></svg> | ||||||
| Before Width: | Height: | Size: 750 B After Width: | Height: | Size: 579 B | 
| @ -1 +1 @@ | |||||||
| <svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="creative-commons-sa" class="svg-inline--fa fa-creative-commons-sa" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M247.6 8C389.4 8 496 118.1 496 256c0 147.1-118.5 248-248.4 248C113.6 504 0 394.5 0 256 0 123.1 104.7 8 247.6 8zm.8 44.7C130.2 52.7 44.7 150.6 44.7 256c0 109.8 91.2 202.8 203.7 202.8 103.2 0 202.8-81.1 202.8-202.8 .1-113.8-90.2-203.3-202.8-203.3zM137.7 221c13-83.9 80.5-95.7 108.9-95.7 99.8 0 127.5 82.5 127.5 134.2 0 63.6-41 132.9-128.9 132.9-38.9 0-99.1-20-109.4-97h62.5c1.5 30.1 19.6 45.2 54.5 45.2 23.3 0 58-18.2 58-82.8 0-82.5-49.1-80.6-56.7-80.6-33.1 0-51.7 14.6-55.8 43.8h18.2l-49.2 49.2-49-49.2h19.4z"></path></svg> | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M247.6 8C389.4 8 496 118.1 496 256c0 147.1-118.5 248-248.4 248C113.6 504 0 394.5 0 256 0 123.1 104.7 8 247.6 8zm.8 44.7C130.2 52.7 44.7 150.6 44.7 256c0 109.8 91.2 202.8 203.7 202.8 103.2 0 202.8-81.1 202.8-202.8.1-113.8-90.2-203.3-202.8-203.3zM137.7 221c13-83.9 80.5-95.7 108.9-95.7 99.8 0 127.5 82.5 127.5 134.2 0 63.6-41 132.9-128.9 132.9-38.9 0-99.1-20-109.4-97h62.5c1.5 30.1 19.6 45.2 54.5 45.2 23.3 0 58-18.2 58-82.8 0-82.5-49.1-80.6-56.7-80.6-33.1 0-51.7 14.6-55.8 43.8h18.2l-49.2 49.2-49-49.2h19.4z"/></svg> | ||||||
| Before Width: | Height: | Size: 757 B After Width: | Height: | Size: 586 B | 
| @ -1 +1 @@ | |||||||
| <svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="discord" class="svg-inline--fa fa-discord" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M524.5 69.84a1.5 1.5 0 0 0 -.764-.7A485.1 485.1 0 0 0 404.1 32.03a1.816 1.816 0 0 0 -1.923 .91 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.14-30.6 1.89 1.89 0 0 0 -1.924-.91A483.7 483.7 0 0 0 116.1 69.14a1.712 1.712 0 0 0 -.788 .676C39.07 183.7 18.19 294.7 28.43 404.4a2.016 2.016 0 0 0 .765 1.375A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.063-.676A348.2 348.2 0 0 0 208.1 430.4a1.86 1.86 0 0 0 -1.019-2.588 321.2 321.2 0 0 1 -45.87-21.85 1.885 1.885 0 0 1 -.185-3.126c3.082-2.309 6.166-4.711 9.109-7.137a1.819 1.819 0 0 1 1.9-.256c96.23 43.92 200.4 43.92 295.5 0a1.812 1.812 0 0 1 1.924 .233c2.944 2.426 6.027 4.851 9.132 7.16a1.884 1.884 0 0 1 -.162 3.126 301.4 301.4 0 0 1 -45.89 21.83 1.875 1.875 0 0 0 -1 2.611 391.1 391.1 0 0 0 30.01 48.81 1.864 1.864 0 0 0 2.063 .7A486 486 0 0 0 610.7 405.7a1.882 1.882 0 0 0 .765-1.352C623.7 277.6 590.9 167.5 524.5 69.84zM222.5 337.6c-28.97 0-52.84-26.59-52.84-59.24S193.1 219.1 222.5 219.1c29.67 0 53.31 26.82 52.84 59.24C275.3 310.1 251.9 337.6 222.5 337.6zm195.4 0c-28.97 0-52.84-26.59-52.84-59.24S388.4 219.1 417.9 219.1c29.67 0 53.31 26.82 52.84 59.24C470.7 310.1 447.5 337.6 417.9 337.6z"></path></svg> | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M524.5 69.84a1.5 1.5 0 00-.764-.7A485.1 485.1 0 00404.1 32.03a1.816 1.816 0 00-1.923.91 337.5 337.5 0 00-14.9 30.6 447.8 447.8 0 00-134.4 0 309.5 309.5 0 00-15.14-30.6 1.89 1.89 0 00-1.924-.91A483.7 483.7 0 00116.1 69.14a1.712 1.712 0 00-.788.676C39.07 183.7 18.19 294.7 28.43 404.4a2.016 2.016 0 00.765 1.375A487.7 487.7 0 00176 479.9a1.9 1.9 0 002.063-.676A348.2 348.2 0 00208.1 430.4a1.86 1.86 0 00-1.019-2.588 321.2 321.2 0 01-45.87-21.85 1.885 1.885 0 01-.185-3.126 251.047 251.047 0 009.109-7.137 1.819 1.819 0 011.9-.256c96.23 43.92 200.4 43.92 295.5 0a1.812 1.812 0 011.924.233 234.533 234.533 0 009.132 7.16 1.884 1.884 0 01-.162 3.126 301.4 301.4 0 01-45.89 21.83 1.875 1.875 0 00-1 2.611 391.1 391.1 0 0030.01 48.81 1.864 1.864 0 002.063.7A486 486 0 00610.7 405.7a1.882 1.882 0 00.765-1.352C623.7 277.6 590.9 167.5 524.5 69.84zm-302 267.76c-28.97 0-52.84-26.59-52.84-59.24s23.44-59.26 52.84-59.26c29.67 0 53.31 26.82 52.84 59.24-.04 31.76-23.44 59.26-52.84 59.26zm195.4 0c-28.97 0-52.84-26.59-52.84-59.24s23.34-59.26 52.84-59.26c29.67 0 53.31 26.82 52.84 59.24-.04 31.76-23.24 59.26-52.84 59.26z"/></svg> | ||||||
| Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.2 KiB | 
| @ -1 +1 @@ | |||||||
| <svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg> | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg> | ||||||
| Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"/></svg> | ||||||
| Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 871 B | 
| @ -1,29 +1,15 @@ | |||||||
| import Head from "next/head"; | import Head from "next/head"; | ||||||
| import { useRouter } from "next/router"; | import { useMemo } from "react"; | ||||||
| import { useEffect, useMemo, useState } from "react"; |  | ||||||
| import { useSwipeable } from "react-swipeable"; | import { useSwipeable } from "react-swipeable"; | ||||||
| import UAParser from "ua-parser-js"; |  | ||||||
| import { useIsClient } from "usehooks-ts"; |  | ||||||
| import Script from "next/script"; |  | ||||||
| import { layout } from "../../design.config"; | import { layout } from "../../design.config"; | ||||||
| import { Ico, Icon } from "./Ico"; | import { Ico, Icon } from "./Ico"; | ||||||
| import { ButtonGroup } from "./Inputs/ButtonGroup"; |  | ||||||
| import { OrderableList } from "./Inputs/OrderableList"; |  | ||||||
| import { Select } from "./Inputs/Select"; |  | ||||||
| import { TextInput } from "./Inputs/TextInput"; |  | ||||||
| import { MainPanel } from "./Panels/MainPanel"; | import { MainPanel } from "./Panels/MainPanel"; | ||||||
| import { Popup } from "./Popup"; | import { SafariPopup } from "./Panels/SafariPopup"; | ||||||
| import { filterHasAttributes, isDefined, isUndefined } from "helpers/others"; | import { isDefined, isUndefined } from "helpers/others"; | ||||||
| import { prettyLanguage } from "helpers/formatters"; |  | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | import { useAppLayout } from "contexts/AppLayoutContext"; | ||||||
| import { Button } from "components/Inputs/Button"; |  | ||||||
| import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph"; | import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph"; | ||||||
| import { useIs1ColumnLayout, useIsScreenAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { useOnResize } from "hooks/useOnResize"; |  | ||||||
| import { Ids } from "types/ids"; | import { Ids } from "types/ids"; | ||||||
| import { sendAnalytics } from "helpers/analytics"; |  | ||||||
| import { useUserSettings } from "contexts/UserSettingsContext"; |  | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
| import { useContainerQueries } from "contexts/ContainerQueriesContext"; | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| @ -60,48 +46,19 @@ export const AppLayout = ({ | |||||||
|   contentPanelScroolbar = true, |   contentPanelScroolbar = true, | ||||||
| }: Props): JSX.Element => { | }: Props): JSX.Element => { | ||||||
|   const { |   const { | ||||||
|     configPanelOpen, |  | ||||||
|     mainPanelOpen, |     mainPanelOpen, | ||||||
|     mainPanelReduced, |     mainPanelReduced, | ||||||
|     menuGestures, |     menuGestures, | ||||||
|     subPanelOpen, |     subPanelOpen, | ||||||
|     hasDisgardedSafariWarning, |  | ||||||
|     setHasDisgardedSafariWarning, |  | ||||||
|     setConfigPanelOpen, |  | ||||||
|     setMainPanelOpen, |     setMainPanelOpen, | ||||||
|     setSubPanelOpen, |     setSubPanelOpen, | ||||||
|     toggleMainPanelOpen, |     toggleMainPanelOpen, | ||||||
|     toggleSubPanelOpen, |     toggleSubPanelOpen, | ||||||
|   } = useAppLayout(); |   } = useAppLayout(); | ||||||
| 
 | 
 | ||||||
|   const { setScreenWidth, setContentPanelWidth, setSubPanelWidth } = useContainerQueries(); |   const { langui } = useLocalData(); | ||||||
| 
 | 
 | ||||||
|   const { langui, currencies, languages } = useLocalData(); |   const { is1ColumnLayout, isScreenAtLeastXs } = useContainerQueries(); | ||||||
| 
 |  | ||||||
|   const { |  | ||||||
|     currency, |  | ||||||
|     darkMode, |  | ||||||
|     dyslexic, |  | ||||||
|     fontSize, |  | ||||||
|     playerName, |  | ||||||
|     preferredLanguages, |  | ||||||
|     selectedThemeMode, |  | ||||||
|     setCurrency, |  | ||||||
|     setDarkMode, |  | ||||||
|     setDyslexic, |  | ||||||
|     setFontSize, |  | ||||||
|     setPlayerName, |  | ||||||
|     setPreferredLanguages, |  | ||||||
|     setSelectedThemeMode, |  | ||||||
|   } = useUserSettings(); |  | ||||||
| 
 |  | ||||||
|   const router = useRouter(); |  | ||||||
|   const is1ColumnLayout = useIs1ColumnLayout(); |  | ||||||
|   const isScreenAtLeastXs = useIsScreenAtLeast("xs"); |  | ||||||
| 
 |  | ||||||
|   useOnResize(Ids.Body, (width) => setScreenWidth(width)); |  | ||||||
|   useOnResize(Ids.ContentPanel, (width) => setContentPanelWidth(width)); |  | ||||||
|   useOnResize(Ids.SubPanel, (width) => setSubPanelWidth(width)); |  | ||||||
| 
 | 
 | ||||||
|   const handlers = useSwipeable({ |   const handlers = useSwipeable({ | ||||||
|     onSwipedLeft: (SwipeEventData) => { |     onSwipedLeft: (SwipeEventData) => { | ||||||
| @ -131,389 +88,148 @@ export const AppLayout = ({ | |||||||
|     [contentPanel, subPanel] |     [contentPanel, subPanel] | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   const currencyOptions = useMemo( |  | ||||||
|     () => |  | ||||||
|       filterHasAttributes(currencies, ["attributes"] as const).map( |  | ||||||
|         (currentCurrency) => currentCurrency.attributes.code |  | ||||||
|       ), |  | ||||||
|     [currencies] |  | ||||||
|   ); |  | ||||||
| 
 |  | ||||||
|   const [currencySelect, setCurrencySelect] = useState<number>(-1); |  | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     if (isDefined(currency)) setCurrencySelect(currencyOptions.indexOf(currency)); |  | ||||||
|   }, [currency, currencyOptions]); |  | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     if (currencySelect >= 0) setCurrency(currencyOptions[currencySelect]); |  | ||||||
|   }, [currencyOptions, currencySelect, setCurrency]); |  | ||||||
| 
 |  | ||||||
|   const isClient = useIsClient(); |  | ||||||
| 
 |  | ||||||
|   const isSafari = useMemo<boolean>(() => { |  | ||||||
|     if (isClient) { |  | ||||||
|       const parser = new UAParser(); |  | ||||||
|       return parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS"; |  | ||||||
|     } |  | ||||||
|     return false; |  | ||||||
|   }, [isClient]); |  | ||||||
| 
 |  | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|  |       {...handlers} | ||||||
|  |       id={Ids.Body} | ||||||
|       className={cJoin( |       className={cJoin( | ||||||
|         cIf(darkMode, "set-theme-dark", "set-theme-light"), |         "fixed inset-0 m-0 grid touch-pan-y bg-light p-0 [grid-template-areas:'main_sub_content']", | ||||||
|         cIf(dyslexic, "set-theme-font-dyslexic", "set-theme-font-standard") |         cIf(is1ColumnLayout, "grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']") | ||||||
|       )}> |       )} | ||||||
|  |       style={{ | ||||||
|  |         gridTemplateColumns: is1ColumnLayout | ||||||
|  |           ? "1fr" | ||||||
|  |           : `${mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu}rem ${ | ||||||
|  |               isDefined(subPanel) ? layout.subMenu : 0 | ||||||
|  |             }rem 1fr`,
 | ||||||
|  |       }}> | ||||||
|  |       <Head> | ||||||
|  |         <title>{openGraph.title}</title> | ||||||
|  |         <meta name="description" content={openGraph.description} /> | ||||||
|  | 
 | ||||||
|  |         <meta name="twitter:title" content={openGraph.title} /> | ||||||
|  |         <meta name="twitter:description" content={openGraph.description} /> | ||||||
|  |         <meta name="twitter:card" content="summary_large_image" /> | ||||||
|  |         <meta name="twitter:image" content={openGraph.thumbnail.image} /> | ||||||
|  | 
 | ||||||
|  |         <meta property="og:title" content={openGraph.title} /> | ||||||
|  |         <meta property="og:description" content={openGraph.description} /> | ||||||
|  |         <meta property="og:image" content={openGraph.thumbnail.image} /> | ||||||
|  |         <meta property="og:image:secure_url" content={openGraph.thumbnail.image} /> | ||||||
|  |         <meta property="og:image:width" content={openGraph.thumbnail.width.toString()} /> | ||||||
|  |         <meta property="og:image:height" content={openGraph.thumbnail.height.toString()} /> | ||||||
|  |         <meta property="og:image:alt" content={openGraph.thumbnail.alt} /> | ||||||
|  |         <meta property="og:image:type" content="image/jpeg" /> | ||||||
|  |       </Head> | ||||||
|  | 
 | ||||||
|  |       {/* Background when navbar is opened */} | ||||||
|       <div |       <div | ||||||
|         {...handlers} |  | ||||||
|         id={Ids.Body} |  | ||||||
|         className={cJoin( |         className={cJoin( | ||||||
|           `fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black
 |           `absolute inset-0 transition-filter duration-500
 | ||||||
|         [grid-template-areas:'main_sub_content']`,
 |  | ||||||
|           cIf(is1ColumnLayout, `grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']`) |  | ||||||
|         )} |  | ||||||
|         style={{ |  | ||||||
|           gridTemplateColumns: is1ColumnLayout |  | ||||||
|             ? "1fr" |  | ||||||
|             : `${mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu}rem ${ |  | ||||||
|                 isDefined(subPanel) ? layout.subMenu : 0 |  | ||||||
|               }rem 1fr`,
 |  | ||||||
|         }}> |  | ||||||
|         <Head> |  | ||||||
|           <title>{openGraph.title}</title> |  | ||||||
|           <meta name="description" content={openGraph.description} /> |  | ||||||
| 
 |  | ||||||
|           <meta name="twitter:title" content={openGraph.title} /> |  | ||||||
|           <meta name="twitter:description" content={openGraph.description} /> |  | ||||||
|           <meta name="twitter:card" content="summary_large_image" /> |  | ||||||
|           <meta name="twitter:image" content={openGraph.thumbnail.image} /> |  | ||||||
| 
 |  | ||||||
|           <meta property="og:title" content={openGraph.title} /> |  | ||||||
|           <meta property="og:description" content={openGraph.description} /> |  | ||||||
|           <meta property="og:image" content={openGraph.thumbnail.image} /> |  | ||||||
|           <meta property="og:image:secure_url" content={openGraph.thumbnail.image} /> |  | ||||||
|           <meta property="og:image:width" content={openGraph.thumbnail.width.toString()} /> |  | ||||||
|           <meta property="og:image:height" content={openGraph.thumbnail.height.toString()} /> |  | ||||||
|           <meta property="og:image:alt" content={openGraph.thumbnail.alt} /> |  | ||||||
|           <meta property="og:image:type" content="image/jpeg" /> |  | ||||||
|         </Head> |  | ||||||
| 
 |  | ||||||
|         <Script |  | ||||||
|           async |  | ||||||
|           defer |  | ||||||
|           data-website-id={process.env.NEXT_PUBLIC_UMAMI_ID} |  | ||||||
|           src={`${process.env.NEXT_PUBLIC_UMAMI_URL}/umami.js`} |  | ||||||
|         /> |  | ||||||
| 
 |  | ||||||
|         {/* Background when navbar is opened */} |  | ||||||
|         <div |  | ||||||
|           className={cJoin( |  | ||||||
|             `absolute inset-0 transition-[backdrop-filter] duration-500
 |  | ||||||
|             [grid-area:content]`,
 |             [grid-area:content]`,
 | ||||||
|             cIf( |           cIf( | ||||||
|               (mainPanelOpen || subPanelOpen) && is1ColumnLayout, |             (mainPanelOpen || subPanelOpen) && is1ColumnLayout, | ||||||
|               "z-10 [backdrop-filter:blur(2px)]", |             "z-10 backdrop-blur", | ||||||
|               "pointer-events-none touch-none" |             "pointer-events-none touch-none" | ||||||
|             ) |           ) | ||||||
|           )}> |         )}> | ||||||
|           <div |  | ||||||
|             className={cJoin( |  | ||||||
|               "absolute inset-0 bg-shade transition-opacity duration-500", |  | ||||||
|               cIf((mainPanelOpen || subPanelOpen) && is1ColumnLayout, "opacity-60", "opacity-0") |  | ||||||
|             )} |  | ||||||
|             onClick={() => { |  | ||||||
|               setMainPanelOpen(false); |  | ||||||
|               setSubPanelOpen(false); |  | ||||||
|             }}></div> |  | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         {/* Content panel */} |  | ||||||
|         <div |         <div | ||||||
|           id={Ids.ContentPanel} |  | ||||||
|           className={cJoin( |           className={cJoin( | ||||||
|             "texture-paper-dots bg-light [grid-area:content]", |             "absolute inset-0 bg-shade transition-opacity duration-500", | ||||||
|             cIf(contentPanelScroolbar, "overflow-y-scroll") |             cIf((mainPanelOpen || subPanelOpen) && is1ColumnLayout, "opacity-60", "opacity-0") | ||||||
|           )}> |  | ||||||
|           {isDefined(contentPanel) ? ( |  | ||||||
|             contentPanel |  | ||||||
|           ) : ( |  | ||||||
|             <ContentPlaceholder |  | ||||||
|               message={langui.select_option_sidebar ?? ""} |  | ||||||
|               icon={Icon.ChevronLeft} |  | ||||||
|             /> |  | ||||||
|           )} |           )} | ||||||
|         </div> |           onClick={() => { | ||||||
|  |             setMainPanelOpen(false); | ||||||
|  |             setSubPanelOpen(false); | ||||||
|  |           }}></div> | ||||||
|  |       </div> | ||||||
| 
 | 
 | ||||||
|         {/* Sub panel */} |       {/* Content panel */} | ||||||
|         {isDefined(subPanel) && ( |       <div | ||||||
|           <div |         id={Ids.ContentPanel} | ||||||
|             id={Ids.SubPanel} |         className={cJoin( | ||||||
|             className={cJoin( |           "texture-paper-dots bg-light [grid-area:content]", | ||||||
|               `texture-paper-dots z-20 overflow-y-scroll border-r-[1px] border-dark/50
 |           cIf(contentPanelScroolbar, "overflow-y-scroll") | ||||||
|  |         )}> | ||||||
|  |         {isDefined(contentPanel) ? ( | ||||||
|  |           contentPanel | ||||||
|  |         ) : ( | ||||||
|  |           <ContentPlaceholder | ||||||
|  |             message={langui.select_option_sidebar ?? ""} | ||||||
|  |             icon={Icon.ChevronLeft} | ||||||
|  |           /> | ||||||
|  |         )} | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       {/* Sub panel */} | ||||||
|  |       {isDefined(subPanel) && ( | ||||||
|  |         <div | ||||||
|  |           id={Ids.SubPanel} | ||||||
|  |           className={cJoin( | ||||||
|  |             `texture-paper-dots z-20 overflow-y-scroll border-r border-dark/50
 | ||||||
|               bg-light transition-transform duration-300 [scrollbar-width:none] |               bg-light transition-transform duration-300 [scrollbar-width:none] | ||||||
|               webkit-scrollbar:w-0`,
 |               webkit-scrollbar:w-0`,
 | ||||||
|               cIf( |             cIf( | ||||||
|                 is1ColumnLayout, |               is1ColumnLayout, | ||||||
|                 "justify-self-end border-r-0 [grid-area:content]", |               "justify-self-end border-r-0 [grid-area:content]", | ||||||
|                 "[grid-area:sub]" |               "[grid-area:sub]" | ||||||
|               ), |             ), | ||||||
|               cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l-[1px]"), |             cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l"), | ||||||
|               cIf(is1ColumnLayout && !subPanelOpen && !turnSubIntoContent, "translate-x-[100vw]"), |             cIf(is1ColumnLayout && !subPanelOpen && !turnSubIntoContent, "translate-x-[100vw]"), | ||||||
|               cIf(is1ColumnLayout && turnSubIntoContent, "w-full border-l-0") |             cIf(is1ColumnLayout && turnSubIntoContent, "w-full border-l-0") | ||||||
|             )}> |  | ||||||
|             {subPanel} |  | ||||||
|           </div> |  | ||||||
|         )} |  | ||||||
| 
 |  | ||||||
|         {/* Main panel */} |  | ||||||
|         <div |  | ||||||
|           className={cJoin( |  | ||||||
|             `texture-paper-dots z-30 overflow-y-scroll border-r-[1px] border-dark/50
 |  | ||||||
|             bg-light transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
 |  | ||||||
|             cIf(is1ColumnLayout, "justify-self-start [grid-area:content]", "[grid-area:main]"), |  | ||||||
|             cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"), |  | ||||||
|             cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full") |  | ||||||
|           )}> |           )}> | ||||||
|           <MainPanel /> |           {subPanel} | ||||||
|         </div> |         </div> | ||||||
|  |       )} | ||||||
| 
 | 
 | ||||||
|         {/* Navbar */} |       {/* Main panel */} | ||||||
|         <div |       <div | ||||||
|  |         className={cJoin( | ||||||
|  |           `texture-paper-dots z-30 overflow-y-scroll border-r border-dark/50
 | ||||||
|  |             bg-light transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
 | ||||||
|  |           cIf(is1ColumnLayout, "justify-self-start [grid-area:content]", "[grid-area:main]"), | ||||||
|  |           cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"), | ||||||
|  |           cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full") | ||||||
|  |         )}> | ||||||
|  |         <MainPanel /> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       {/* Navbar */} | ||||||
|  |       <div | ||||||
|  |         className={cJoin( | ||||||
|  |           `texture-paper-dots z-10 grid grid-cols-[5rem_1fr_5rem] place-items-center
 | ||||||
|  |             border-t border-dotted border-black bg-light [grid-area:navbar]`,
 | ||||||
|  |           cIf(!is1ColumnLayout, "hidden") | ||||||
|  |         )}> | ||||||
|  |         <Ico | ||||||
|  |           icon={mainPanelOpen ? Icon.Close : Icon.Menu} | ||||||
|  |           className="cursor-pointer !text-2xl" | ||||||
|  |           onClick={() => { | ||||||
|  |             toggleMainPanelOpen(); | ||||||
|  |             setSubPanelOpen(false); | ||||||
|  |           }} | ||||||
|  |         /> | ||||||
|  |         <p | ||||||
|           className={cJoin( |           className={cJoin( | ||||||
|             `texture-paper-dots z-10 grid grid-cols-[5rem_1fr_5rem] place-items-center
 |             "overflow-hidden text-center font-headers font-black", | ||||||
|             border-t-[1px] border-dotted border-black bg-light [grid-area:navbar]`,
 |             cIf(openGraph.title.length > 30, "max-h-14 text-xl", "max-h-16 text-2xl") | ||||||
|             cIf(!is1ColumnLayout, "hidden") |  | ||||||
|           )}> |           )}> | ||||||
|  |           {openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length) | ||||||
|  |             ? openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length) | ||||||
|  |             : "Accord’s Library"} | ||||||
|  |         </p> | ||||||
|  |         {isDefined(subPanel) && !turnSubIntoContent && ( | ||||||
|           <Ico |           <Ico | ||||||
|             icon={mainPanelOpen ? Icon.Close : Icon.Menu} |             icon={subPanelOpen ? Icon.Close : subPanelIcon} | ||||||
|             className="cursor-pointer !text-2xl" |             className="cursor-pointer !text-2xl" | ||||||
|             onClick={() => { |             onClick={() => { | ||||||
|               toggleMainPanelOpen(); |               toggleSubPanelOpen(); | ||||||
|               setSubPanelOpen(false); |               setMainPanelOpen(false); | ||||||
|             }} |             }} | ||||||
|           /> |           /> | ||||||
|           <p |         )} | ||||||
|             className={cJoin( |  | ||||||
|               "overflow-hidden text-center font-headers font-black", |  | ||||||
|               cIf(openGraph.title.length > 30, "max-h-14 text-xl", "max-h-16 text-2xl") |  | ||||||
|             )}> |  | ||||||
|             {openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length) |  | ||||||
|               ? openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length) |  | ||||||
|               : "Accord’s Library"} |  | ||||||
|           </p> |  | ||||||
|           {isDefined(subPanel) && !turnSubIntoContent && ( |  | ||||||
|             <Ico |  | ||||||
|               icon={subPanelOpen ? Icon.Close : subPanelIcon} |  | ||||||
|               className="cursor-pointer !text-2xl" |  | ||||||
|               onClick={() => { |  | ||||||
|                 toggleSubPanelOpen(); |  | ||||||
|                 setMainPanelOpen(false); |  | ||||||
|               }} |  | ||||||
|             /> |  | ||||||
|           )} |  | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         <Popup state={isSafari && !hasDisgardedSafariWarning} onClose={() => null}> |  | ||||||
|           <h1 className="text-2xl">Hi, you are using Safari!</h1> |  | ||||||
|           <p className="max-w-lg text-center"> |  | ||||||
|             In most cases this wouldn’t be a problem but our website is—for some obscure |  | ||||||
|             reason—performing terribly on Safari (WebKit). Because of that, we have decided to |  | ||||||
|             display this message instead of letting you have a slow and painful experience. We are |  | ||||||
|             looking into the problem, and are hoping to fix this soon. |  | ||||||
|           </p> |  | ||||||
|           <p>In the meanwhile, if you are using an iPhone/iPad, please try using another device.</p> |  | ||||||
|           <p>If you are on macOS, please use another browser such as Firefox or Chrome.</p> |  | ||||||
| 
 |  | ||||||
|           <Button |  | ||||||
|             text="Let me in regardless" |  | ||||||
|             className="mt-8" |  | ||||||
|             onClick={() => { |  | ||||||
|               setHasDisgardedSafariWarning(true); |  | ||||||
|               sendAnalytics("Safari", "Disgard warning"); |  | ||||||
|             }} |  | ||||||
|           /> |  | ||||||
|         </Popup> |  | ||||||
| 
 |  | ||||||
|         <Popup |  | ||||||
|           state={configPanelOpen} |  | ||||||
|           onClose={() => { |  | ||||||
|             setConfigPanelOpen(false); |  | ||||||
|             sendAnalytics("Settings", "Close settings"); |  | ||||||
|           }}> |  | ||||||
|           <h2 className="text-2xl">{langui.settings}</h2> |  | ||||||
| 
 |  | ||||||
|           <div |  | ||||||
|             className={cJoin( |  | ||||||
|               `mt-4 grid justify-items-center gap-16 text-center`, |  | ||||||
|               cIf(!is1ColumnLayout, "grid-cols-[auto_auto]") |  | ||||||
|             )}> |  | ||||||
|             {router.locales && ( |  | ||||||
|               <div> |  | ||||||
|                 <h3 className="text-xl">{langui.languages}</h3> |  | ||||||
|                 {preferredLanguages.length > 0 && ( |  | ||||||
|                   <OrderableList |  | ||||||
|                     items={preferredLanguages.map((locale) => ({ |  | ||||||
|                       code: locale, |  | ||||||
|                       name: prettyLanguage(locale, languages), |  | ||||||
|                     }))} |  | ||||||
|                     insertLabels={[ |  | ||||||
|                       { |  | ||||||
|                         insertAt: 0, |  | ||||||
|                         name: langui.primary_language ?? "Primary language", |  | ||||||
|                       }, |  | ||||||
|                       { |  | ||||||
|                         insertAt: 1, |  | ||||||
|                         name: langui.secondary_language ?? "Secondary languages", |  | ||||||
|                       }, |  | ||||||
|                     ]} |  | ||||||
|                     onChange={(items) => { |  | ||||||
|                       const newPreferredLanguages = items.map((item) => item.code); |  | ||||||
|                       setPreferredLanguages(newPreferredLanguages); |  | ||||||
|                       sendAnalytics("Settings", "Change preferred languages"); |  | ||||||
|                     }} |  | ||||||
|                   /> |  | ||||||
|                 )} |  | ||||||
|               </div> |  | ||||||
|             )} |  | ||||||
|             <div |  | ||||||
|               className={cJoin( |  | ||||||
|                 "grid place-items-center gap-8 text-center", |  | ||||||
|                 cIf(!is1ColumnLayout, "grid-cols-2") |  | ||||||
|               )}> |  | ||||||
|               <div> |  | ||||||
|                 <h3 className="text-xl">{langui.theme}</h3> |  | ||||||
|                 <ButtonGroup |  | ||||||
|                   buttonsProps={[ |  | ||||||
|                     { |  | ||||||
|                       onClick: () => { |  | ||||||
|                         setDarkMode(false); |  | ||||||
|                         setSelectedThemeMode(true); |  | ||||||
|                         sendAnalytics("Settings", "Change theme (light)"); |  | ||||||
|                       }, |  | ||||||
|                       active: selectedThemeMode && !darkMode, |  | ||||||
|                       text: langui.light, |  | ||||||
|                     }, |  | ||||||
|                     { |  | ||||||
|                       onClick: () => { |  | ||||||
|                         setSelectedThemeMode(false); |  | ||||||
|                         sendAnalytics("Settings", "Change theme (auto)"); |  | ||||||
|                       }, |  | ||||||
|                       active: !selectedThemeMode, |  | ||||||
|                       text: langui.auto, |  | ||||||
|                     }, |  | ||||||
|                     { |  | ||||||
|                       onClick: () => { |  | ||||||
|                         setDarkMode(true); |  | ||||||
|                         setSelectedThemeMode(true); |  | ||||||
|                         sendAnalytics("Settings", "Change theme (dark)"); |  | ||||||
|                       }, |  | ||||||
|                       active: selectedThemeMode && darkMode, |  | ||||||
|                       text: langui.dark, |  | ||||||
|                     }, |  | ||||||
|                   ]} |  | ||||||
|                 /> |  | ||||||
|               </div> |  | ||||||
| 
 |  | ||||||
|               <div> |  | ||||||
|                 <h3 className="text-xl">{langui.currency}</h3> |  | ||||||
|                 <div> |  | ||||||
|                   <Select |  | ||||||
|                     options={currencyOptions} |  | ||||||
|                     value={currencySelect} |  | ||||||
|                     onChange={(newCurrency) => { |  | ||||||
|                       setCurrencySelect(newCurrency); |  | ||||||
|                       sendAnalytics( |  | ||||||
|                         "Settings", |  | ||||||
|                         `Change currency (${currencyOptions[newCurrency]})}` |  | ||||||
|                       ); |  | ||||||
|                     }} |  | ||||||
|                     className="w-28" |  | ||||||
|                   /> |  | ||||||
|                 </div> |  | ||||||
|               </div> |  | ||||||
| 
 |  | ||||||
|               <div> |  | ||||||
|                 <h3 className="text-xl">{langui.font_size}</h3> |  | ||||||
|                 <ButtonGroup |  | ||||||
|                   buttonsProps={[ |  | ||||||
|                     { |  | ||||||
|                       onClick: () => { |  | ||||||
|                         setFontSize((current) => current / 1.05); |  | ||||||
|                         sendAnalytics( |  | ||||||
|                           "Settings", |  | ||||||
|                           `Change font size (${((fontSize / 1.05) * 100).toLocaleString(undefined, { |  | ||||||
|                             maximumFractionDigits: 0, |  | ||||||
|                           })}%)` |  | ||||||
|                         ); |  | ||||||
|                       }, |  | ||||||
|                       icon: Icon.TextDecrease, |  | ||||||
|                     }, |  | ||||||
|                     { |  | ||||||
|                       onClick: () => { |  | ||||||
|                         setFontSize(1); |  | ||||||
|                         sendAnalytics("Settings", "Change font size (100%)"); |  | ||||||
|                       }, |  | ||||||
|                       text: `${(fontSize * 100).toLocaleString(undefined, { |  | ||||||
|                         maximumFractionDigits: 0, |  | ||||||
|                       })}%`,
 |  | ||||||
|                     }, |  | ||||||
|                     { |  | ||||||
|                       onClick: () => { |  | ||||||
|                         setFontSize((current) => current * 1.05); |  | ||||||
|                         sendAnalytics( |  | ||||||
|                           "Settings", |  | ||||||
|                           `Change font size (${(fontSize * 1.05 * 100).toLocaleString(undefined, { |  | ||||||
|                             maximumFractionDigits: 0, |  | ||||||
|                           })}%)` |  | ||||||
|                         ); |  | ||||||
|                       }, |  | ||||||
|                       icon: Icon.TextIncrease, |  | ||||||
|                     }, |  | ||||||
|                   ]} |  | ||||||
|                 /> |  | ||||||
|               </div> |  | ||||||
| 
 |  | ||||||
|               <div> |  | ||||||
|                 <h3 className="text-xl">{langui.font}</h3> |  | ||||||
|                 <div className="grid gap-2"> |  | ||||||
|                   <Button |  | ||||||
|                     active={!dyslexic} |  | ||||||
|                     onClick={() => { |  | ||||||
|                       setDyslexic(false); |  | ||||||
|                       sendAnalytics("Settings", "Change font (Zen Maru Gothic)"); |  | ||||||
|                     }} |  | ||||||
|                     className="font-zenMaruGothic" |  | ||||||
|                     text="Zen Maru Gothic" |  | ||||||
|                   /> |  | ||||||
|                   <Button |  | ||||||
|                     active={dyslexic} |  | ||||||
|                     onClick={() => { |  | ||||||
|                       setDyslexic(true); |  | ||||||
|                       sendAnalytics("Settings", "Change font (OpenDyslexic)"); |  | ||||||
|                     }} |  | ||||||
|                     className="font-openDyslexic" |  | ||||||
|                     text="OpenDyslexic" |  | ||||||
|                   /> |  | ||||||
|                 </div> |  | ||||||
|               </div> |  | ||||||
| 
 |  | ||||||
|               <div> |  | ||||||
|                 <h3 className="text-xl">{langui.player_name}</h3> |  | ||||||
|                 <TextInput |  | ||||||
|                   placeholder="<player>" |  | ||||||
|                   className="w-48" |  | ||||||
|                   value={playerName} |  | ||||||
|                   onChange={(newName) => { |  | ||||||
|                     setPlayerName(newName); |  | ||||||
|                     sendAnalytics("Settings", "Change username"); |  | ||||||
|                   }} |  | ||||||
|                 /> |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|         </Popup> |  | ||||||
|       </div> |       </div> | ||||||
|  |       <SafariPopup /> | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -15,8 +15,8 @@ interface Props { | |||||||
| export const Chip = ({ className, text }: Props): JSX.Element => ( | export const Chip = ({ className, text }: Props): JSX.Element => ( | ||||||
|   <div |   <div | ||||||
|     className={cJoin( |     className={cJoin( | ||||||
|       `grid place-content-center place-items-center whitespace-nowrap rounded-full border-[1px]
 |       `grid place-content-center place-items-center whitespace-nowrap rounded-full border
 | ||||||
|       px-1.5 pb-[0.14rem] text-xs opacity-70 transition-[color,_opacity,_border-color] |       px-1.5 pb-[0.14rem] text-xs opacity-70 transition-[color,opacity,border-color] | ||||||
|       hover:opacity-100`,
 |       hover:opacity-100`,
 | ||||||
|       className |       className | ||||||
|     )}> |     )}> | ||||||
|  | |||||||
| @ -22,10 +22,10 @@ const ChroniclePreview = ({ date, url, title, isActive }: Props): JSX.Element => | |||||||
|     href={url} |     href={url} | ||||||
|     className={cJoin( |     className={cJoin( | ||||||
|       `flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5 text-left align-top outline outline-2
 |       `flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5 text-left align-top outline outline-2
 | ||||||
|       outline-offset-[-2px] outline-mid transition-all hover:bg-mid hover:shadow-inner-sm |       -outline-offset-2 outline-mid transition-all hover:bg-mid hover:shadow-inner-sm | ||||||
|       hover:shadow-shade hover:outline-[transparent] hover:active:shadow-inner |       hover:shadow-shade hover:outline-transparent hover:active:shadow-inner | ||||||
|       hover:active:shadow-shade`,
 |       hover:active:shadow-shade`,
 | ||||||
|       cIf(isActive, "bg-mid shadow-inner-sm shadow-shade outline-[transparent]") |       cIf(isActive, "bg-mid shadow-inner-sm shadow-shade outline-transparent") | ||||||
|     )}> |     )}> | ||||||
|     <div className="text-right"> |     <div className="text-right"> | ||||||
|       <p>{date.year}</p> |       <p>{date.year}</p> | ||||||
|  | |||||||
| @ -38,7 +38,7 @@ const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div |       <div | ||||||
|         className="grid gap-4 overflow-hidden transition-[max-height] duration-500" |         className="grid gap-4 overflow-hidden transition-height duration-500" | ||||||
|         style={{ maxHeight: isOpen ? `${8 * chronicles.length}rem` : 0 }}> |         style={{ maxHeight: isOpen ? `${8 * chronicles.length}rem` : 0 }}> | ||||||
|         {filterHasAttributes(chronicles, [ |         {filterHasAttributes(chronicles, [ | ||||||
|           "attributes.contents", |           "attributes.contents", | ||||||
|  | |||||||
| @ -234,7 +234,7 @@ export const Terminal = ({ | |||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       className={cJoin( |       className={cJoin( | ||||||
|         "h-screen overflow-hidden bg-light text-black set-theme-font-standard", |         "h-screen overflow-hidden bg-light set-theme-font-standard", | ||||||
|         cIf(darkMode, "set-theme-dark", "set-theme-light") |         cIf(darkMode, "set-theme-dark", "set-theme-light") | ||||||
|       )}> |       )}> | ||||||
|       <div |       <div | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								src/components/ColoredSvg.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | |||||||
|  | import { cJoin } from "helpers/className"; | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|  |   src: string; | ||||||
|  |   className?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const ColoredSvg = ({ src, className }: Props): JSX.Element => ( | ||||||
|  |   <div | ||||||
|  |     className={cJoin( | ||||||
|  |       `transition-colors ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center]`, | ||||||
|  |       className | ||||||
|  |     )} | ||||||
|  |     style={{ mask: `url('${src}')`, WebkitMask: `url('${src}')` }} | ||||||
|  |   /> | ||||||
|  | ); | ||||||
| @ -12,6 +12,5 @@ interface Props { | |||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
| 
 | 
 | ||||||
| export const HorizontalLine = ({ className }: Props): JSX.Element => ( | export const HorizontalLine = ({ className }: Props): JSX.Element => ( | ||||||
|   <div |   <div className={cJoin("my-8 h-0 w-full border-t-2 border-dotted border-black", className)}></div> | ||||||
|     className={cJoin("my-8 h-0 w-full border-t-[3px] border-dotted border-black", className)}></div> |  | ||||||
| ); | ); | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { MouseEventHandler } from "react"; | import { MouseEventHandler } from "react"; | ||||||
| import { cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -10,14 +10,19 @@ interface Props { | |||||||
|   className?: string; |   className?: string; | ||||||
|   onClick?: MouseEventHandler<HTMLSpanElement> | undefined; |   onClick?: MouseEventHandler<HTMLSpanElement> | undefined; | ||||||
|   icon: Icon; |   icon: Icon; | ||||||
|  |   isFilled?: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
| 
 | 
 | ||||||
| export const Ico = ({ onClick, icon, className }: Props): JSX.Element => ( | export const Ico = ({ onClick, icon, className, isFilled = true }: Props): JSX.Element => ( | ||||||
|   <span |   <span | ||||||
|     onClick={onClick} |     onClick={onClick} | ||||||
|     className={cJoin("material-icons [font-size:inherit] [line-height:inherit]", className)}> |     className={cJoin( | ||||||
|  |       "[font-size:inherit] [line-height:inherit]", | ||||||
|  |       cIf(isFilled, "material-icons", "material-icons-outlined"), | ||||||
|  |       className | ||||||
|  |     )}> | ||||||
|     {icon} |     {icon} | ||||||
|   </span> |   </span> | ||||||
| ); | ); | ||||||
|  | |||||||
| @ -58,7 +58,7 @@ export const Button = ({ | |||||||
|         onFocus={(event) => event.target.blur()} |         onFocus={(event) => event.target.blur()} | ||||||
|         className={cJoin( |         className={cJoin( | ||||||
|           `group grid cursor-pointer select-none grid-flow-col place-content-center 
 |           `group grid cursor-pointer select-none grid-flow-col place-content-center 
 | ||||||
|           place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4 |           place-items-center gap-2 rounded-full border border-dark py-3 px-4 | ||||||
|           leading-none text-dark transition-all`,
 |           leading-none text-dark transition-all`,
 | ||||||
|           cIf( |           cIf( | ||||||
|             active, |             active, | ||||||
|  | |||||||
| @ -57,7 +57,7 @@ export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Ele | |||||||
|               updateOrder(sourceIndex, targetIndex); |               updateOrder(sourceIndex, targetIndex); | ||||||
|             }} |             }} | ||||||
|             className="grid cursor-grab select-none grid-cols-[auto_1fr] place-content-center gap-2 |             className="grid cursor-grab select-none grid-cols-[auto_1fr] place-content-center gap-2 | ||||||
|             rounded-full border-[1px] border-dark bg-light px-1 py-2 pr-4 text-dark transition-all |             rounded-full border border-dark bg-light px-1 py-2 pr-4 text-dark transition-all | ||||||
|             hover:bg-dark hover:text-light hover:drop-shadow-shade-lg" |             hover:bg-dark hover:text-light hover:drop-shadow-shade-lg" | ||||||
|             draggable> |             draggable> | ||||||
|             <div className="grid grid-rows-[.8em_.8em] place-items-center"> |             <div className="grid grid-rows-[.8em_.8em] place-items-center"> | ||||||
|  | |||||||
| @ -34,16 +34,16 @@ export const Select = ({ className, value, options, allowEmpty, onChange }: Prop | |||||||
|     <div |     <div | ||||||
|       ref={ref} |       ref={ref} | ||||||
|       className={cJoin( |       className={cJoin( | ||||||
|         "relative text-center transition-[filter]", |         "relative text-center transition-filter", | ||||||
|         cIf(isOpened, "z-10 drop-shadow-shade-lg"), |         cIf(isOpened, "z-10 drop-shadow-shade-lg"), | ||||||
|         className |         className | ||||||
|       )}> |       )}> | ||||||
|       <div |       <div | ||||||
|         className={cJoin( |         className={cJoin( | ||||||
|           `grid cursor-pointer grid-flow-col grid-cols-[1fr_auto_auto] place-items-center
 |           `grid cursor-pointer grid-flow-col grid-cols-[1fr_auto_auto] place-items-center
 | ||||||
|           rounded-[1em] bg-light p-1 outline outline-2 outline-offset-[-2px] outline-mid |            rounded-[1em] bg-light p-1 outline outline-1 -outline-offset-1 outline-mid | ||||||
|           transition-all hover:bg-mid hover:outline-[transparent]`,
 |            transition-all hover:bg-mid hover:outline-transparent`,
 | ||||||
|           cIf(isOpened, "rounded-b-none bg-highlight outline-[transparent]") |           cIf(isOpened, "rounded-b-none bg-highlight outline-transparent") | ||||||
|         )}> |         )}> | ||||||
|         <p onClick={tryToggling} className="w-full"> |         <p onClick={tryToggling} className="w-full"> | ||||||
|           {value === -1 ? "—" : options[value]} |           {value === -1 ? "—" : options[value]} | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { useState } from "react"; | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| @ -14,21 +15,35 @@ interface Props { | |||||||
| 
 | 
 | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
| 
 | 
 | ||||||
| export const Switch = ({ value, onClick, className, disabled = false }: Props): JSX.Element => ( | export const Switch = ({ value, onClick, className, disabled = false }: Props): JSX.Element => { | ||||||
|   <div |   const [isFocused, setIsFocused] = useState(false); | ||||||
|     className={cJoin( |   return ( | ||||||
|       "relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors", |  | ||||||
|       cIf(disabled, "cursor-not-allowed", "cursor-pointer"), |  | ||||||
|       cIf(value, "border-none bg-mid shadow-inner-sm shadow-shade", "bg-light"), |  | ||||||
|       className |  | ||||||
|     )} |  | ||||||
|     onClick={() => { |  | ||||||
|       if (!disabled) onClick(); |  | ||||||
|     }}> |  | ||||||
|     <div |     <div | ||||||
|       className={cJoin( |       className={cJoin( | ||||||
|         "absolute aspect-square rounded-full bg-dark transition-transform", |         `relative grid h-6 w-12 content-center rounded-full border-mid outline
 | ||||||
|         cIf(value, "top-[2px] bottom-[2px] left-[2px] translate-x-[120%]", "top-0 bottom-0 left-0") |         outline-1 -outline-offset-1 outline-mid transition-colors`,
 | ||||||
|       )}></div> |         cIf(disabled, "cursor-not-allowed", "cursor-pointer"), | ||||||
|   </div> |         cIf( | ||||||
| ); |           value, | ||||||
|  |           "border-none bg-mid shadow-inner-sm shadow-shade outline-transparent", | ||||||
|  |           "bg-light" | ||||||
|  |         ), | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       onClick={() => { | ||||||
|  |         if (!disabled) onClick(); | ||||||
|  |       }} | ||||||
|  |       onPointerDown={() => setIsFocused(true)} | ||||||
|  |       onPointerOut={() => setIsFocused(false)} | ||||||
|  |       onPointerLeave={() => setIsFocused(false)} | ||||||
|  |       onPointerUp={() => setIsFocused(false)}> | ||||||
|  |       <div | ||||||
|  |         className={cJoin( | ||||||
|  |           "ml-1 h-4 w-4 rounded-full bg-dark transition-transform", | ||||||
|  |           cIf(value, "translate-x-6"), | ||||||
|  |           cIf(isFocused, cIf(value, "translate-x-5", "translate-x-1")) | ||||||
|  |         )} | ||||||
|  |       /> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -1,95 +1,136 @@ | |||||||
| import { Dispatch, SetStateAction, useCallback } from "react"; | import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"; | ||||||
| import Hotkeys from "react-hot-keys"; | import { useState } from "react"; | ||||||
| import { useSwipeable } from "react-swipeable"; | import { useHotkeys } from "react-hotkeys-hook"; | ||||||
| import { Img } from "./Img"; | import { Img } from "./Img"; | ||||||
| import { Button } from "./Inputs/Button"; | import { Button } from "./Inputs/Button"; | ||||||
| import { Popup } from "./Popup"; |  | ||||||
| import { Icon } from "components/Ico"; | import { Icon } from "components/Ico"; | ||||||
| import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; |  | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| 
 | import { useFullscreen } from "hooks/useFullscreen"; | ||||||
| /* | import { Ids } from "types/ids"; | ||||||
|  *                                         ╭─────────────╮ | import { UploadImageFragment } from "graphql/generated"; | ||||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | import { ImageQuality } from "helpers/img"; | ||||||
|  */ | import { isDefined } from "helpers/others"; | ||||||
| 
 |  | ||||||
| const SENSIBILITY_SWIPE = 0.5; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
|  *                                        ╭─────────────╮ |  | ||||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── |  | ||||||
|  */ |  | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   setState: Dispatch<SetStateAction<boolean | undefined>> | Dispatch<SetStateAction<boolean>>; |   onCloseRequest: () => void; | ||||||
|   state: boolean; |   isVisible: boolean; | ||||||
|   images: string[]; |   image?: UploadImageFragment | string; | ||||||
|   index: number; |   isNextImageAvailable?: boolean; | ||||||
|   setIndex: Dispatch<SetStateAction<number>>; |   isPreviousImageAvailable?: boolean; | ||||||
|  |   onPressNext?: () => void; | ||||||
|  |   onPressPrevious?: () => void; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
| 
 | 
 | ||||||
| export const LightBox = ({ state, setState, images, index, setIndex }: Props): JSX.Element => { | export const LightBox = ({ | ||||||
|   const handlePrevious = useCallback(() => { |   onCloseRequest, | ||||||
|     if (index > 0) setIndex(index - 1); |   isVisible, | ||||||
|   }, [index, setIndex]); |   image: src, | ||||||
|   const is3ColumnsLayout = useIs3ColumnsLayout(); |   isPreviousImageAvailable = false, | ||||||
|  |   onPressPrevious, | ||||||
|  |   isNextImageAvailable = false, | ||||||
|  |   onPressNext, | ||||||
|  | }: Props): JSX.Element => { | ||||||
|  |   const [currentZoom, setCurrentZoom] = useState(1); | ||||||
|  |   const { isFullscreen, toggleFullscreen, exitFullscreen, requestFullscreen } = useFullscreen( | ||||||
|  |     Ids.LightBox | ||||||
|  |   ); | ||||||
| 
 | 
 | ||||||
|   const handleNext = useCallback(() => { |   useHotkeys( | ||||||
|     if (index < images.length - 1) setIndex(index + 1); |     "left", | ||||||
|   }, [images.length, index, setIndex]); |     () => onPressPrevious?.(), | ||||||
|  |     { enabled: isVisible && isPreviousImageAvailable }, | ||||||
|  |     [onPressPrevious] | ||||||
|  |   ); | ||||||
| 
 | 
 | ||||||
|   const handlers = useSwipeable({ |   useHotkeys("f", () => requestFullscreen(), { enabled: isVisible && !isFullscreen }, [ | ||||||
|     onSwipedLeft: (SwipeEventData) => { |     requestFullscreen, | ||||||
|       if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return; |   ]); | ||||||
|       handleNext(); | 
 | ||||||
|     }, |   useHotkeys("right", () => onPressNext?.(), { enabled: isVisible && isNextImageAvailable }, [ | ||||||
|     onSwipedRight: (SwipeEventData) => { |     onPressNext, | ||||||
|       if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return; |   ]); | ||||||
|       handlePrevious(); | 
 | ||||||
|     }, |   useHotkeys("escape", onCloseRequest, { enabled: isVisible }, [onCloseRequest]); | ||||||
|   }); |  | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <div | ||||||
|       {state && ( |       id={Ids.LightBox} | ||||||
|         <Hotkeys |       className={cJoin( | ||||||
|           keyName="left,right" |         "fixed inset-0 z-50 grid place-content-center transition-filter duration-500", | ||||||
|           allowRepeat |         cIf(isVisible, "backdrop-blur", "pointer-events-none touch-none") | ||||||
|           onKeyDown={(keyName) => { |       )}> | ||||||
|             if (keyName === "left") { |       <div | ||||||
|               handlePrevious(); |         className={cJoin( | ||||||
|             } else { |           "fixed inset-0 bg-shade transition-all duration-500", | ||||||
|               handleNext(); |           cIf(isVisible, "bg-opacity-50", "bg-opacity-0") | ||||||
|             } |         )} | ||||||
|           }}> |       /> | ||||||
|           <Popup onClose={() => setState(false)} state={state} padding={false} fillViewport> |       <div | ||||||
|             <div |         className={cJoin( | ||||||
|               {...handlers} |           "absolute inset-8 grid transition-transform", | ||||||
|               className={cJoin( |           cIf(isVisible, "scale-100", "scale-0") | ||||||
|                 `grid h-full w-full place-items-center overflow-hidden first-letter:gap-4`, |         )}> | ||||||
|                 cIf( |         <TransformWrapper | ||||||
|                   is3ColumnsLayout, |           onZoom={(zoom) => setCurrentZoom(zoom.state.scale)} | ||||||
|                   `grid-cols-[4em,1fr,4em] [grid-template-areas:"left_image_right"]`, |           panning={{ disabled: currentZoom <= 1, velocityDisabled: false }} | ||||||
|                   `grid-cols-2 [grid-template-areas:"image_image""left_right"]` |           doubleClick={{ disabled: true, mode: "reset" }} | ||||||
|                 ) |           zoomAnimation={{ size: 0.1 }} | ||||||
|               )}> |           velocityAnimation={{ animationTime: 0, equalToMove: true }}> | ||||||
|               <div className="ml-4 [grid-area:left]"> |           {({ resetTransform }) => ( | ||||||
|                 {index > 0 && <Button onClick={handlePrevious} icon={Icon.ChevronLeft} />} |             <> | ||||||
|               </div> |               <TransformComponent | ||||||
| 
 |                 wrapperStyle={{ | ||||||
|               <Img className="max-h-full min-h-fit [grid-area:image]" src={images[index]} /> |                   overflow: "visible", | ||||||
| 
 |                   placeSelf: "center", | ||||||
|               <div className="mr-4 [grid-area:right]"> |                 }}> | ||||||
|                 {index < images.length - 1 && ( |                 {isDefined(src) && ( | ||||||
|                   <Button onClick={handleNext} icon={Icon.ChevronRight} /> |                   <Img | ||||||
|  |                     className={`drop-shadow-shade-2xl-shade h-[calc(100vh-4rem)] w-full
 | ||||||
|  |                     object-contain`}
 | ||||||
|  |                     src={src} | ||||||
|  |                     quality={ImageQuality.Large} | ||||||
|  |                   /> | ||||||
|                 )} |                 )} | ||||||
|  |               </TransformComponent> | ||||||
|  | 
 | ||||||
|  |               {isPreviousImageAvailable && ( | ||||||
|  |                 <div | ||||||
|  |                   className={`absolute top-1/2 left-0 grid gap-4 rounded-[2rem] p-4
 | ||||||
|  |                   backdrop-blur-lg`}>
 | ||||||
|  |                   <Button icon={Icon.NavigateBefore} onClick={onPressPrevious} /> | ||||||
|  |                 </div> | ||||||
|  |               )} | ||||||
|  | 
 | ||||||
|  |               {isNextImageAvailable && ( | ||||||
|  |                 <div | ||||||
|  |                   className={`absolute top-1/2 right-0 grid gap-4 rounded-[2rem] p-4
 | ||||||
|  |                     backdrop-blur-lg`}>
 | ||||||
|  |                   <Button icon={Icon.NavigateNext} onClick={onPressNext} />{" "} | ||||||
|  |                 </div> | ||||||
|  |               )} | ||||||
|  | 
 | ||||||
|  |               <div | ||||||
|  |                 className={`absolute top-0 right-0 grid gap-4 rounded-[2rem] p-4
 | ||||||
|  |                 backdrop-blur-lg`}>
 | ||||||
|  |                 <Button | ||||||
|  |                   onClick={() => { | ||||||
|  |                     resetTransform(); | ||||||
|  |                     exitFullscreen(); | ||||||
|  |                     onCloseRequest(); | ||||||
|  |                   }} | ||||||
|  |                   icon={Icon.Close} | ||||||
|  |                 /> | ||||||
|  |                 <Button | ||||||
|  |                   icon={isFullscreen ? Icon.FullscreenExit : Icon.Fullscreen} | ||||||
|  |                   onClick={toggleFullscreen} | ||||||
|  |                 /> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </> | ||||||
|           </Popup> |           )} | ||||||
|         </Hotkeys> |         </TransformWrapper> | ||||||
|       )} |       </div> | ||||||
|     </> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -9,14 +9,14 @@ import { cIf, cJoin } from "helpers/className"; | |||||||
| import { slugify } from "helpers/formatters"; | import { slugify } from "helpers/formatters"; | ||||||
| import { getAssetURL, ImageQuality } from "helpers/img"; | import { getAssetURL, ImageQuality } from "helpers/img"; | ||||||
| import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others"; | import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others"; | ||||||
| import { useLightBox } from "hooks/useLightBox"; |  | ||||||
| import { AnchorShare } from "components/AnchorShare"; | import { AnchorShare } from "components/AnchorShare"; | ||||||
| import { useIntersectionList } from "hooks/useIntersectionList"; | import { useIntersectionList } from "hooks/useIntersectionList"; | ||||||
| import { Ico, Icon } from "components/Ico"; | import { Ico, Icon } from "components/Ico"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||||
| import { useUserSettings } from "contexts/UserSettingsContext"; | import { useUserSettings } from "contexts/UserSettingsContext"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
|  | import { useLightBox } from "contexts/LightBoxContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -33,8 +33,8 @@ interface MarkdawnProps { | |||||||
| export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Element => { | export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Element => { | ||||||
|   const { playerName } = useUserSettings(); |   const { playerName } = useUserSettings(); | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|   const isContentPanelAtLeastLg = useIsContentPanelAtLeast("lg"); |   const { isContentPanelAtLeastLg } = useContainerQueries(); | ||||||
|   const [openLightBox, LightBox] = useLightBox(); |   const { showLightBox } = useLightBox(); | ||||||
| 
 | 
 | ||||||
|   /* eslint-disable no-irregular-whitespace */ |   /* eslint-disable no-irregular-whitespace */ | ||||||
|   const text = useMemo( |   const text = useMemo( | ||||||
| @ -49,175 +49,169 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <Markdown | ||||||
|       <LightBox /> |       className={cJoin("formatted", className)} | ||||||
|       <Markdown |       options={{ | ||||||
|         className={cJoin("formatted", className)} |         slugify: slugify, | ||||||
|         options={{ |         overrides: { | ||||||
|           slugify: slugify, |           a: { | ||||||
|           overrides: { |             component: (compProps: { href: string; children: React.ReactNode }) => { | ||||||
|             a: { |               if (compProps.href.startsWith("/") || compProps.href.startsWith("#")) { | ||||||
|               component: (compProps: { href: string; children: React.ReactNode }) => { |  | ||||||
|                 if (compProps.href.startsWith("/") || compProps.href.startsWith("#")) { |  | ||||||
|                   return ( |  | ||||||
|                     <a onClick={async () => router.push(compProps.href)}>{compProps.children}</a> |  | ||||||
|                   ); |  | ||||||
|                 } |  | ||||||
|                 return ( |                 return ( | ||||||
|                   <a href={compProps.href} target="_blank" rel="noreferrer"> |                   <a onClick={async () => router.push(compProps.href)}>{compProps.children}</a> | ||||||
|                     {compProps.children} |  | ||||||
|                   </a> |  | ||||||
|                 ); |                 ); | ||||||
|               }, |               } | ||||||
|             }, |               return ( | ||||||
| 
 |                 <a href={compProps.href} target="_blank" rel="noreferrer"> | ||||||
|             Header: { |  | ||||||
|               component: (compProps: { |  | ||||||
|                 id: string; |  | ||||||
|                 style: React.CSSProperties; |  | ||||||
|                 children: string; |  | ||||||
|                 level: string; |  | ||||||
|               }) => ( |  | ||||||
|                 <Header |  | ||||||
|                   title={compProps.children} |  | ||||||
|                   level={parseInt(compProps.level, 10)} |  | ||||||
|                   slug={compProps.id} |  | ||||||
|                 /> |  | ||||||
|               ), |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             SceneBreak: { |  | ||||||
|               component: (compProps: { id: string }) => ( |  | ||||||
|                 <Header title={"* * *"} level={6} slug={compProps.id} /> |  | ||||||
|               ), |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             IntraLink: { |  | ||||||
|               component: (compProps: { |  | ||||||
|                 children: React.ReactNode; |  | ||||||
|                 target?: string; |  | ||||||
|                 page?: string; |  | ||||||
|               }) => { |  | ||||||
|                 const slug = isDefinedAndNotEmpty(compProps.target) |  | ||||||
|                   ? slugify(compProps.target) |  | ||||||
|                   : slugify(compProps.children?.toString()); |  | ||||||
|                 return ( |  | ||||||
|                   <a onClick={async () => router.replace(`${compProps.page ?? ""}#${slug}`)}> |  | ||||||
|                     {compProps.children} |  | ||||||
|                   </a> |  | ||||||
|                 ); |  | ||||||
|               }, |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             Transcript: { |  | ||||||
|               component: (compProps) => ( |  | ||||||
|                 <div |  | ||||||
|                   className={cJoin( |  | ||||||
|                     "grid gap-x-6 gap-y-2", |  | ||||||
|                     cIf(isContentPanelAtLeastLg, "grid-cols-[auto_1fr]", "grid-cols-1") |  | ||||||
|                   )}> |  | ||||||
|                   {compProps.children} |                   {compProps.children} | ||||||
|                 </div> |                 </a> | ||||||
|               ), |               ); | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             Line: { |  | ||||||
|               component: (compProps) => ( |  | ||||||
|                 <> |  | ||||||
|                   <strong |  | ||||||
|                     className={cJoin( |  | ||||||
|                       "!my-0 text-dark/60", |  | ||||||
|                       cIf(!isContentPanelAtLeastLg, "!-mb-4") |  | ||||||
|                     )}> |  | ||||||
|                     <Markdawn text={compProps.name} /> |  | ||||||
|                   </strong> |  | ||||||
|                   <p className="whitespace-pre-line">{compProps.children}</p> |  | ||||||
|                 </> |  | ||||||
|               ), |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             InsetBox: { |  | ||||||
|               component: (compProps) => <InsetBox className="my-12">{compProps.children}</InsetBox>, |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             li: { |  | ||||||
|               component: (compProps: { children: React.ReactNode }) => ( |  | ||||||
|                 <li |  | ||||||
|                   className={ |  | ||||||
|                     isDefined(compProps.children) && |  | ||||||
|                     ReactDOMServer.renderToStaticMarkup(<>{compProps.children}</>).length > 100 |  | ||||||
|                       ? "my-4" |  | ||||||
|                       : "" |  | ||||||
|                   }> |  | ||||||
|                   {compProps.children} |  | ||||||
|                 </li> |  | ||||||
|               ), |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             Highlight: { |  | ||||||
|               component: (compProps: { children: React.ReactNode }) => ( |  | ||||||
|                 <mark>{compProps.children}</mark> |  | ||||||
|               ), |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             footer: { |  | ||||||
|               component: (compProps: { children: React.ReactNode }) => ( |  | ||||||
|                 <> |  | ||||||
|                   <HorizontalLine /> |  | ||||||
|                   <div className="grid gap-8">{compProps.children}</div> |  | ||||||
|                 </> |  | ||||||
|               ), |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             blockquote: { |  | ||||||
|               component: (compProps: { children: React.ReactNode; cite?: string }) => ( |  | ||||||
|                 <blockquote> |  | ||||||
|                   {isDefinedAndNotEmpty(compProps.cite) ? ( |  | ||||||
|                     <> |  | ||||||
|                       “{compProps.children}” |  | ||||||
|                       <cite>— {compProps.cite}</cite> |  | ||||||
|                     </> |  | ||||||
|                   ) : ( |  | ||||||
|                     compProps.children |  | ||||||
|                   )} |  | ||||||
|                 </blockquote> |  | ||||||
|               ), |  | ||||||
|             }, |  | ||||||
| 
 |  | ||||||
|             img: { |  | ||||||
|               component: (compProps: { |  | ||||||
|                 alt: string; |  | ||||||
|                 src: string; |  | ||||||
|                 width?: number; |  | ||||||
|                 height?: number; |  | ||||||
|                 caption?: string; |  | ||||||
|                 name?: string; |  | ||||||
|               }) => ( |  | ||||||
|                 <div |  | ||||||
|                   className="mt-8 mb-12 grid cursor-pointer place-content-center" |  | ||||||
|                   onClick={() => { |  | ||||||
|                     openLightBox([ |  | ||||||
|                       compProps.src.startsWith("/uploads/") |  | ||||||
|                         ? getAssetURL(compProps.src, ImageQuality.Large) |  | ||||||
|                         : compProps.src, |  | ||||||
|                     ]); |  | ||||||
|                   }}> |  | ||||||
|                   <Img |  | ||||||
|                     src={ |  | ||||||
|                       compProps.src.startsWith("/uploads/") |  | ||||||
|                         ? getAssetURL(compProps.src, ImageQuality.Small) |  | ||||||
|                         : compProps.src |  | ||||||
|                     } |  | ||||||
|                     quality={ImageQuality.Medium} |  | ||||||
|                     className="drop-shadow-shade-lg"></Img> |  | ||||||
|                 </div> |  | ||||||
|               ), |  | ||||||
|             }, |             }, | ||||||
|           }, |           }, | ||||||
|         }}> | 
 | ||||||
|         {text} |           Header: { | ||||||
|       </Markdown> |             component: (compProps: { | ||||||
|     </> |               id: string; | ||||||
|  |               style: React.CSSProperties; | ||||||
|  |               children: string; | ||||||
|  |               level: string; | ||||||
|  |             }) => ( | ||||||
|  |               <Header | ||||||
|  |                 title={compProps.children} | ||||||
|  |                 level={parseInt(compProps.level, 10)} | ||||||
|  |                 slug={compProps.id} | ||||||
|  |               /> | ||||||
|  |             ), | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           SceneBreak: { | ||||||
|  |             component: (compProps: { id: string }) => ( | ||||||
|  |               <Header title={"* * *"} level={6} slug={compProps.id} /> | ||||||
|  |             ), | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           IntraLink: { | ||||||
|  |             component: (compProps: { | ||||||
|  |               children: React.ReactNode; | ||||||
|  |               target?: string; | ||||||
|  |               page?: string; | ||||||
|  |             }) => { | ||||||
|  |               const slug = isDefinedAndNotEmpty(compProps.target) | ||||||
|  |                 ? slugify(compProps.target) | ||||||
|  |                 : slugify(compProps.children?.toString()); | ||||||
|  |               return ( | ||||||
|  |                 <a onClick={async () => router.replace(`${compProps.page ?? ""}#${slug}`)}> | ||||||
|  |                   {compProps.children} | ||||||
|  |                 </a> | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           Transcript: { | ||||||
|  |             component: (compProps) => ( | ||||||
|  |               <div | ||||||
|  |                 className={cJoin( | ||||||
|  |                   "grid gap-x-6 gap-y-2", | ||||||
|  |                   cIf(isContentPanelAtLeastLg, "grid-cols-[auto_1fr]", "grid-cols-1") | ||||||
|  |                 )}> | ||||||
|  |                 {compProps.children} | ||||||
|  |               </div> | ||||||
|  |             ), | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           Line: { | ||||||
|  |             component: (compProps) => ( | ||||||
|  |               <> | ||||||
|  |                 <strong | ||||||
|  |                   className={cJoin("!my-0 text-dark/60", cIf(!isContentPanelAtLeastLg, "!-mb-4"))}> | ||||||
|  |                   <Markdawn text={compProps.name} /> | ||||||
|  |                 </strong> | ||||||
|  |                 <p className="whitespace-pre-line">{compProps.children}</p> | ||||||
|  |               </> | ||||||
|  |             ), | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           InsetBox: { | ||||||
|  |             component: (compProps) => <InsetBox className="my-12">{compProps.children}</InsetBox>, | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           li: { | ||||||
|  |             component: (compProps: { children: React.ReactNode }) => ( | ||||||
|  |               <li | ||||||
|  |                 className={ | ||||||
|  |                   isDefined(compProps.children) && | ||||||
|  |                   ReactDOMServer.renderToStaticMarkup(<>{compProps.children}</>).length > 100 | ||||||
|  |                     ? "my-4" | ||||||
|  |                     : "" | ||||||
|  |                 }> | ||||||
|  |                 {compProps.children} | ||||||
|  |               </li> | ||||||
|  |             ), | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           Highlight: { | ||||||
|  |             component: (compProps: { children: React.ReactNode }) => ( | ||||||
|  |               <mark>{compProps.children}</mark> | ||||||
|  |             ), | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           footer: { | ||||||
|  |             component: (compProps: { children: React.ReactNode }) => ( | ||||||
|  |               <> | ||||||
|  |                 <HorizontalLine /> | ||||||
|  |                 <div className="grid gap-8">{compProps.children}</div> | ||||||
|  |               </> | ||||||
|  |             ), | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           blockquote: { | ||||||
|  |             component: (compProps: { children: React.ReactNode; cite?: string }) => ( | ||||||
|  |               <blockquote> | ||||||
|  |                 {isDefinedAndNotEmpty(compProps.cite) ? ( | ||||||
|  |                   <> | ||||||
|  |                     “{compProps.children}” | ||||||
|  |                     <cite>— {compProps.cite}</cite> | ||||||
|  |                   </> | ||||||
|  |                 ) : ( | ||||||
|  |                   compProps.children | ||||||
|  |                 )} | ||||||
|  |               </blockquote> | ||||||
|  |             ), | ||||||
|  |           }, | ||||||
|  | 
 | ||||||
|  |           img: { | ||||||
|  |             component: (compProps: { | ||||||
|  |               alt: string; | ||||||
|  |               src: string; | ||||||
|  |               width?: number; | ||||||
|  |               height?: number; | ||||||
|  |               caption?: string; | ||||||
|  |               name?: string; | ||||||
|  |             }) => ( | ||||||
|  |               <div | ||||||
|  |                 className="mt-8 mb-12 grid cursor-pointer place-content-center" | ||||||
|  |                 onClick={() => { | ||||||
|  |                   showLightBox([ | ||||||
|  |                     compProps.src.startsWith("/uploads/") | ||||||
|  |                       ? getAssetURL(compProps.src, ImageQuality.Large) | ||||||
|  |                       : compProps.src, | ||||||
|  |                   ]); | ||||||
|  |                 }}> | ||||||
|  |                 <Img | ||||||
|  |                   src={ | ||||||
|  |                     compProps.src.startsWith("/uploads/") | ||||||
|  |                       ? getAssetURL(compProps.src, ImageQuality.Small) | ||||||
|  |                       : compProps.src | ||||||
|  |                   } | ||||||
|  |                   quality={ImageQuality.Medium} | ||||||
|  |                   className="drop-shadow-shade-lg"></Img> | ||||||
|  |               </div> | ||||||
|  |             ), | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }}> | ||||||
|  |       {text} | ||||||
|  |     </Markdown> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -61,13 +61,10 @@ export const NavOption = ({ | |||||||
|           justify-center gap-x-5 rounded-2xl p-4 transition-all hover:bg-mid hover:shadow-inner-sm |           justify-center gap-x-5 rounded-2xl p-4 transition-all hover:bg-mid hover:shadow-inner-sm | ||||||
|           hover:shadow-shade hover:active:shadow-inner hover:active:shadow-shade`,
 |           hover:shadow-shade hover:active:shadow-inner hover:active:shadow-shade`,
 | ||||||
|           cIf(icon, "text-left", "text-center"), |           cIf(icon, "text-left", "text-center"), | ||||||
|           cIf( |           cIf(border, "outline outline-2 -outline-offset-2 outline-mid hover:outline-transparent"), | ||||||
|             border, |  | ||||||
|             "outline outline-2 outline-offset-[-2px] outline-mid hover:outline-[transparent]" |  | ||||||
|           ), |  | ||||||
|           cIf(isActive, "bg-mid shadow-inner-sm shadow-shade") |           cIf(isActive, "bg-mid shadow-inner-sm shadow-shade") | ||||||
|         )}> |         )}> | ||||||
|         {icon && <Ico icon={icon} className="mt-[-.1em] !text-2xl" />} |         {icon && <Ico icon={icon} className="mt-[-.1em] !text-2xl" isFilled={isActive} />} | ||||||
| 
 | 
 | ||||||
|         {!reduced && ( |         {!reduced && ( | ||||||
|           <div> |           <div> | ||||||
|  | |||||||
| @ -4,9 +4,9 @@ import { Button } from "components/Inputs/Button"; | |||||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | import { useAppLayout } from "contexts/AppLayoutContext"; | ||||||
| import { TranslatedProps } from "types/TranslatedProps"; | import { TranslatedProps } from "types/TranslatedProps"; | ||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; |  | ||||||
| import { isDefined } from "helpers/others"; | import { isDefined } from "helpers/others"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -26,7 +26,7 @@ interface Props { | |||||||
| export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props): JSX.Element => { | export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props): JSX.Element => { | ||||||
|   const { setSubPanelOpen } = useAppLayout(); |   const { setSubPanelOpen } = useAppLayout(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const is3ColumnsLayout = useIs3ColumnsLayout(); |   const { is3ColumnsLayout } = useContainerQueries(); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -25,7 +25,7 @@ export const ContentPanel = ({ | |||||||
|   children, |   children, | ||||||
|   className, |   className, | ||||||
| }: Props): JSX.Element => { | }: Props): JSX.Element => { | ||||||
|   const isContentPanelAtLeast3xl = useIsContentPanelAtLeast("3xl"); |   const { isContentPanelAtLeast3xl } = useContainerQueries(); | ||||||
|   return ( |   return ( | ||||||
|     <div className="grid h-full"> |     <div className="grid h-full"> | ||||||
|       <main |       <main | ||||||
|  | |||||||
| @ -8,9 +8,10 @@ import { Icon } from "components/Ico"; | |||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { isDefinedAndNotEmpty } from "helpers/others"; | import { isDefinedAndNotEmpty } from "helpers/others"; | ||||||
| import { Link } from "components/Inputs/Link"; | import { Link } from "components/Inputs/Link"; | ||||||
| import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; |  | ||||||
| import { sendAnalytics } from "helpers/analytics"; | import { sendAnalytics } from "helpers/analytics"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { ColoredSvg } from "components/ColoredSvg"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -18,7 +19,7 @@ import { useLocalData } from "contexts/LocalDataContext"; | |||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| export const MainPanel = (): JSX.Element => { | export const MainPanel = (): JSX.Element => { | ||||||
|   const is3ColumnsLayout = useIs3ColumnsLayout(); |   const { is3ColumnsLayout } = useContainerQueries(); | ||||||
|   const { mainPanelReduced, toggleMainPanelReduced, setConfigPanelOpen } = useAppLayout(); |   const { mainPanelReduced, toggleMainPanelReduced, setConfigPanelOpen } = useAppLayout(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
| 
 | 
 | ||||||
| @ -51,14 +52,14 @@ export const MainPanel = (): JSX.Element => { | |||||||
|       )} |       )} | ||||||
|       <div> |       <div> | ||||||
|         <div className="grid place-items-center"> |         <div className="grid place-items-center"> | ||||||
|           <Link href="/" className="flex w-full justify-center"> |           <Link href="/" className="flex w-full cursor-pointer justify-center"> | ||||||
|             <div |             <ColoredSvg | ||||||
|  |               src="/icons/accords.svg" | ||||||
|               className={cJoin( |               className={cJoin( | ||||||
|                 `mb-4 aspect-square cursor-pointer bg-black transition-colors
 |                 "mb-4 aspect-square bg-black hover:bg-dark", | ||||||
|                 [mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat] |  | ||||||
|                 ![mask-position:center] hover:bg-dark`,
 |  | ||||||
|                 cIf(mainPanelReduced && is3ColumnsLayout, "w-12", "w-1/2") |                 cIf(mainPanelReduced && is3ColumnsLayout, "w-12", "w-1/2") | ||||||
|               )}></div> |               )} | ||||||
|  |             /> | ||||||
|           </Link> |           </Link> | ||||||
| 
 | 
 | ||||||
|           {(!mainPanelReduced || !is3ColumnsLayout) && ( |           {(!mainPanelReduced || !is3ColumnsLayout) && ( | ||||||
| @ -148,7 +149,7 @@ export const MainPanel = (): JSX.Element => { | |||||||
| 
 | 
 | ||||||
|       <NavOption |       <NavOption | ||||||
|         url="/archives" |         url="/archives" | ||||||
|         icon={Icon.Inventory} |         icon={Icon.Inventory2} | ||||||
|         title={langui.archives} |         title={langui.archives} | ||||||
|         reduced={mainPanelReduced && is3ColumnsLayout} |         reduced={mainPanelReduced && is3ColumnsLayout} | ||||||
|       /> |       /> | ||||||
| @ -160,7 +161,7 @@ export const MainPanel = (): JSX.Element => { | |||||||
|         reduced={mainPanelReduced && is3ColumnsLayout} |         reduced={mainPanelReduced && is3ColumnsLayout} | ||||||
|       /> |       /> | ||||||
| 
 | 
 | ||||||
|       {mainPanelReduced && is3ColumnsLayout ? "" : <HorizontalLine />} |       {(!mainPanelReduced || !is3ColumnsLayout) && <HorizontalLine />} | ||||||
| 
 | 
 | ||||||
|       <div className={cJoin("text-center", cIf(mainPanelReduced && is3ColumnsLayout, "hidden"))}> |       <div className={cJoin("text-center", cIf(mainPanelReduced && is3ColumnsLayout, "hidden"))}> | ||||||
|         {isDefinedAndNotEmpty(langui.licensing_notice) && ( |         {isDefinedAndNotEmpty(langui.licensing_notice) && ( | ||||||
| @ -172,22 +173,19 @@ export const MainPanel = (): JSX.Element => { | |||||||
|           <a |           <a | ||||||
|             onClick={() => sendAnalytics("MainPanel", "Visit license")} |             onClick={() => sendAnalytics("MainPanel", "Visit license")} | ||||||
|             aria-label="Read more about the license we use for this website" |             aria-label="Read more about the license we use for this website" | ||||||
|             className="group grid grid-flow-col place-content-center gap-1 transition-[filter]" |             className="group grid grid-flow-col place-content-center gap-1 transition-filter" | ||||||
|             href="https://creativecommons.org/licenses/by-sa/4.0/"> |             href="https://creativecommons.org/licenses/by-sa/4.0/"> | ||||||
|             <div |             <ColoredSvg | ||||||
|               className="aspect-square w-6 bg-black transition-colors |               className="h-6 w-6 bg-black group-hover:bg-dark" | ||||||
|               ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] |               src="/icons/creative-commons-brands.svg" | ||||||
|               [mask:url('/icons/creative-commons-brands.svg')] group-hover:bg-dark" |  | ||||||
|             /> |             /> | ||||||
|             <div |             <ColoredSvg | ||||||
|               className="aspect-square w-6 bg-black transition-colors |               className="h-6 w-6 bg-black group-hover:bg-dark" | ||||||
|               ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] |               src="/icons/creative-commons-by-brands.svg" | ||||||
|               [mask:url('/icons/creative-commons-by-brands.svg')] group-hover:bg-dark" |  | ||||||
|             /> |             /> | ||||||
|             <div |             <ColoredSvg | ||||||
|               className="aspect-square w-6 bg-black transition-colors |               className="h-6 w-6 bg-black group-hover:bg-dark" | ||||||
|               ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] |               src="/icons/creative-commons-sa-brands.svg" | ||||||
|               [mask:url('/icons/creative-commons-sa-brands.svg')] group-hover:bg-dark" |  | ||||||
|             /> |             /> | ||||||
|           </a> |           </a> | ||||||
|         </div> |         </div> | ||||||
| @ -200,30 +198,36 @@ export const MainPanel = (): JSX.Element => { | |||||||
|           <a |           <a | ||||||
|             aria-label="Browse our GitHub repository, which include this website source code" |             aria-label="Browse our GitHub repository, which include this website source code" | ||||||
|             onClick={() => sendAnalytics("MainPanel", "Visit GitHub")} |             onClick={() => sendAnalytics("MainPanel", "Visit GitHub")} | ||||||
|             className="aspect-square w-10 bg-black |  | ||||||
|               transition-colors ![mask-size:contain] ![mask-repeat:no-repeat] |  | ||||||
|               ![mask-position:center] [mask:url('/icons/github-brands.svg')] hover:bg-dark" |  | ||||||
|             href="https://github.com/Accords-Library" |             href="https://github.com/Accords-Library" | ||||||
|             target="_blank" |             target="_blank" | ||||||
|             rel="noopener noreferrer"></a> |             rel="noopener noreferrer"> | ||||||
|  |             <ColoredSvg | ||||||
|  |               className="h-10 w-10 bg-black hover:bg-dark" | ||||||
|  |               src="/icons/github-brands.svg" | ||||||
|  |             /> | ||||||
|  |           </a> | ||||||
|           <a |           <a | ||||||
|             aria-label="Follow us on Twitter" |             aria-label="Follow us on Twitter" | ||||||
|             onClick={() => sendAnalytics("MainPanel", "Visit Twitter")} |             onClick={() => sendAnalytics("MainPanel", "Visit Twitter")} | ||||||
|             className="aspect-square w-10 |  | ||||||
|               bg-black transition-colors ![mask-size:contain] ![mask-repeat:no-repeat] |  | ||||||
|               ![mask-position:center] [mask:url('/icons/twitter-brands.svg')] hover:bg-dark" |  | ||||||
|             href="https://twitter.com/AccordsLibrary" |             href="https://twitter.com/AccordsLibrary" | ||||||
|             target="_blank" |             target="_blank" | ||||||
|             rel="noopener noreferrer"></a> |             rel="noopener noreferrer"> | ||||||
|  |             <ColoredSvg | ||||||
|  |               className="h-10 w-10 bg-black hover:bg-dark" | ||||||
|  |               src="/icons/twitter-brands.svg" | ||||||
|  |             /> | ||||||
|  |           </a> | ||||||
|           <a |           <a | ||||||
|             aria-label="Join our Discord server!" |             aria-label="Join our Discord server!" | ||||||
|             onClick={() => sendAnalytics("MainPanel", "Visit Discord")} |             onClick={() => sendAnalytics("MainPanel", "Visit Discord")} | ||||||
|             className="aspect-square w-10 |  | ||||||
|               bg-black transition-colors ![mask-size:contain] ![mask-repeat:no-repeat] |  | ||||||
|               ![mask-position:center] [mask:url('/icons/discord-brands.svg')] hover:bg-dark" |  | ||||||
|             href="/discord" |             href="/discord" | ||||||
|             target="_blank" |             target="_blank" | ||||||
|             rel="noopener noreferrer"></a> |             rel="noopener noreferrer"> | ||||||
|  |             <ColoredSvg | ||||||
|  |               className="h-10 w-10 bg-black hover:bg-dark" | ||||||
|  |               src="/icons/discord-brands.svg" | ||||||
|  |             /> | ||||||
|  |           </a> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
							
								
								
									
										45
									
								
								src/components/Panels/SafariPopup.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,45 @@ | |||||||
|  | import { useMemo } from "react"; | ||||||
|  | import UAParser from "ua-parser-js"; | ||||||
|  | import { useIsClient, useSessionStorage } from "usehooks-ts"; | ||||||
|  | import { Button } from "components/Inputs/Button"; | ||||||
|  | import { Popup } from "components/Popup"; | ||||||
|  | import { sendAnalytics } from "helpers/analytics"; | ||||||
|  | 
 | ||||||
|  | export const SafariPopup = (): JSX.Element => { | ||||||
|  |   const [hasDisgardedSafariWarning, setHasDisgardedSafariWarning] = useSessionStorage( | ||||||
|  |     "hasDisgardedSafariWarning", | ||||||
|  |     false | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const isClient = useIsClient(); | ||||||
|  |   const isSafari = useMemo<boolean>(() => { | ||||||
|  |     if (isClient) { | ||||||
|  |       const parser = new UAParser(); | ||||||
|  |       return parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS"; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   }, [isClient]); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Popup isVisible={isSafari && !hasDisgardedSafariWarning}> | ||||||
|  |       <h1 className="text-2xl">Hi, you are using Safari!</h1> | ||||||
|  |       <p className="max-w-lg text-center"> | ||||||
|  |         In most cases this wouldn’t be a problem but our website is—for some obscure | ||||||
|  |         reason—performing terribly on Safari (WebKit). Because of that, we have decided to display | ||||||
|  |         this message instead of letting you have a slow and painful experience. We are looking into | ||||||
|  |         the problem, and are hoping to fix this soon. | ||||||
|  |       </p> | ||||||
|  |       <p>In the meanwhile, if you are using an iPhone/iPad, please try using another device.</p> | ||||||
|  |       <p>If you are on macOS, please use another browser such as Firefox or Chrome.</p> | ||||||
|  | 
 | ||||||
|  |       <Button | ||||||
|  |         text="Let me in regardless" | ||||||
|  |         className="mt-8" | ||||||
|  |         onClick={() => { | ||||||
|  |           setHasDisgardedSafariWarning(true); | ||||||
|  |           sendAnalytics("Safari", "Disgard warning"); | ||||||
|  |         }} | ||||||
|  |       /> | ||||||
|  |     </Popup> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
							
								
								
									
										237
									
								
								src/components/Panels/SettingsPopup.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,237 @@ | |||||||
|  | import { useRouter } from "next/router"; | ||||||
|  | import { useEffect, useMemo, useState } from "react"; | ||||||
|  | import { Icon } from "components/Ico"; | ||||||
|  | import { Button } from "components/Inputs/Button"; | ||||||
|  | import { ButtonGroup } from "components/Inputs/ButtonGroup"; | ||||||
|  | import { OrderableList } from "components/Inputs/OrderableList"; | ||||||
|  | import { Select } from "components/Inputs/Select"; | ||||||
|  | import { TextInput } from "components/Inputs/TextInput"; | ||||||
|  | import { Popup } from "components/Popup"; | ||||||
|  | import { useAppLayout } from "contexts/AppLayoutContext"; | ||||||
|  | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useUserSettings } from "contexts/UserSettingsContext"; | ||||||
|  | import { sendAnalytics } from "helpers/analytics"; | ||||||
|  | import { cJoin, cIf } from "helpers/className"; | ||||||
|  | import { prettyLanguage } from "helpers/formatters"; | ||||||
|  | import { filterHasAttributes, isDefined } from "helpers/others"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
|  | 
 | ||||||
|  | export const SettingsPopup = (): JSX.Element => { | ||||||
|  |   const { | ||||||
|  |     currency, | ||||||
|  |     darkMode, | ||||||
|  |     dyslexic, | ||||||
|  |     fontSize, | ||||||
|  |     playerName, | ||||||
|  |     preferredLanguages, | ||||||
|  |     selectedThemeMode, | ||||||
|  |     setCurrency, | ||||||
|  |     setDarkMode, | ||||||
|  |     setDyslexic, | ||||||
|  |     setFontSize, | ||||||
|  |     setPlayerName, | ||||||
|  |     setPreferredLanguages, | ||||||
|  |     setSelectedThemeMode, | ||||||
|  |   } = useUserSettings(); | ||||||
|  | 
 | ||||||
|  |   const { langui, currencies, languages } = useLocalData(); | ||||||
|  |   const { is1ColumnLayout } = useContainerQueries(); | ||||||
|  |   const router = useRouter(); | ||||||
|  |   const { configPanelOpen, setConfigPanelOpen } = useAppLayout(); | ||||||
|  | 
 | ||||||
|  |   const currencyOptions = useMemo( | ||||||
|  |     () => | ||||||
|  |       filterHasAttributes(currencies, ["attributes"] as const).map( | ||||||
|  |         (currentCurrency) => currentCurrency.attributes.code | ||||||
|  |       ), | ||||||
|  |     [currencies] | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const [currencySelect, setCurrencySelect] = useState<number>(-1); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (isDefined(currency)) setCurrencySelect(currencyOptions.indexOf(currency)); | ||||||
|  |   }, [currency, currencyOptions]); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (currencySelect >= 0) setCurrency(currencyOptions[currencySelect]); | ||||||
|  |   }, [currencyOptions, currencySelect, setCurrency]); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Popup | ||||||
|  |       isVisible={configPanelOpen} | ||||||
|  |       onCloseRequest={() => { | ||||||
|  |         setConfigPanelOpen(false); | ||||||
|  |         sendAnalytics("Settings", "Close settings"); | ||||||
|  |       }}> | ||||||
|  |       <h2 className="text-2xl">{langui.settings}</h2> | ||||||
|  | 
 | ||||||
|  |       <div | ||||||
|  |         className={cJoin( | ||||||
|  |           `mt-4 grid justify-items-center gap-16 text-center`, | ||||||
|  |           cIf(!is1ColumnLayout, "grid-cols-[auto_auto]") | ||||||
|  |         )}> | ||||||
|  |         {router.locales && ( | ||||||
|  |           <div> | ||||||
|  |             <h3 className="text-xl">{langui.languages}</h3> | ||||||
|  |             {preferredLanguages.length > 0 && ( | ||||||
|  |               <OrderableList | ||||||
|  |                 items={preferredLanguages.map((locale) => ({ | ||||||
|  |                   code: locale, | ||||||
|  |                   name: prettyLanguage(locale, languages), | ||||||
|  |                 }))} | ||||||
|  |                 insertLabels={[ | ||||||
|  |                   { | ||||||
|  |                     insertAt: 0, | ||||||
|  |                     name: langui.primary_language ?? "Primary language", | ||||||
|  |                   }, | ||||||
|  |                   { | ||||||
|  |                     insertAt: 1, | ||||||
|  |                     name: langui.secondary_language ?? "Secondary languages", | ||||||
|  |                   }, | ||||||
|  |                 ]} | ||||||
|  |                 onChange={(items) => { | ||||||
|  |                   const newPreferredLanguages = items.map((item) => item.code); | ||||||
|  |                   setPreferredLanguages(newPreferredLanguages); | ||||||
|  |                   sendAnalytics("Settings", "Change preferred languages"); | ||||||
|  |                 }} | ||||||
|  |               /> | ||||||
|  |             )} | ||||||
|  |           </div> | ||||||
|  |         )} | ||||||
|  |         <div | ||||||
|  |           className={cJoin( | ||||||
|  |             "grid place-items-center gap-8 text-center", | ||||||
|  |             cIf(!is1ColumnLayout, "grid-cols-2") | ||||||
|  |           )}> | ||||||
|  |           <div> | ||||||
|  |             <h3 className="text-xl">{langui.theme}</h3> | ||||||
|  |             <ButtonGroup | ||||||
|  |               buttonsProps={[ | ||||||
|  |                 { | ||||||
|  |                   onClick: () => { | ||||||
|  |                     setDarkMode(false); | ||||||
|  |                     setSelectedThemeMode(true); | ||||||
|  |                     sendAnalytics("Settings", "Change theme (light)"); | ||||||
|  |                   }, | ||||||
|  |                   active: selectedThemeMode && !darkMode, | ||||||
|  |                   text: langui.light, | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   onClick: () => { | ||||||
|  |                     setSelectedThemeMode(false); | ||||||
|  |                     sendAnalytics("Settings", "Change theme (auto)"); | ||||||
|  |                   }, | ||||||
|  |                   active: !selectedThemeMode, | ||||||
|  |                   text: langui.auto, | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   onClick: () => { | ||||||
|  |                     setDarkMode(true); | ||||||
|  |                     setSelectedThemeMode(true); | ||||||
|  |                     sendAnalytics("Settings", "Change theme (dark)"); | ||||||
|  |                   }, | ||||||
|  |                   active: selectedThemeMode && darkMode, | ||||||
|  |                   text: langui.dark, | ||||||
|  |                 }, | ||||||
|  |               ]} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div> | ||||||
|  |             <h3 className="text-xl">{langui.currency}</h3> | ||||||
|  |             <div> | ||||||
|  |               <Select | ||||||
|  |                 options={currencyOptions} | ||||||
|  |                 value={currencySelect} | ||||||
|  |                 onChange={(newCurrency) => { | ||||||
|  |                   setCurrencySelect(newCurrency); | ||||||
|  |                   sendAnalytics("Settings", `Change currency (${currencyOptions[newCurrency]})}`); | ||||||
|  |                 }} | ||||||
|  |                 className="w-28" | ||||||
|  |               /> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div> | ||||||
|  |             <h3 className="text-xl">{langui.font_size}</h3> | ||||||
|  |             <ButtonGroup | ||||||
|  |               buttonsProps={[ | ||||||
|  |                 { | ||||||
|  |                   onClick: () => { | ||||||
|  |                     setFontSize((current) => current / 1.05); | ||||||
|  |                     sendAnalytics( | ||||||
|  |                       "Settings", | ||||||
|  |                       `Change font size (${((fontSize / 1.05) * 100).toLocaleString(undefined, { | ||||||
|  |                         maximumFractionDigits: 0, | ||||||
|  |                       })}%)` | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                   icon: Icon.TextDecrease, | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   onClick: () => { | ||||||
|  |                     setFontSize(1); | ||||||
|  |                     sendAnalytics("Settings", "Change font size (100%)"); | ||||||
|  |                   }, | ||||||
|  |                   text: `${(fontSize * 100).toLocaleString(undefined, { | ||||||
|  |                     maximumFractionDigits: 0, | ||||||
|  |                   })}%`,
 | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   onClick: () => { | ||||||
|  |                     setFontSize((current) => current * 1.05); | ||||||
|  |                     sendAnalytics( | ||||||
|  |                       "Settings", | ||||||
|  |                       `Change font size (${(fontSize * 1.05 * 100).toLocaleString(undefined, { | ||||||
|  |                         maximumFractionDigits: 0, | ||||||
|  |                       })}%)` | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                   icon: Icon.TextIncrease, | ||||||
|  |                 }, | ||||||
|  |               ]} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div> | ||||||
|  |             <h3 className="text-xl">{langui.font}</h3> | ||||||
|  |             <div className="grid gap-2"> | ||||||
|  |               <Button | ||||||
|  |                 active={!dyslexic} | ||||||
|  |                 onClick={() => { | ||||||
|  |                   setDyslexic(false); | ||||||
|  |                   sendAnalytics("Settings", "Change font (Zen Maru Gothic)"); | ||||||
|  |                 }} | ||||||
|  |                 className="font-zenMaruGothic" | ||||||
|  |                 text="Zen Maru Gothic" | ||||||
|  |               /> | ||||||
|  |               <Button | ||||||
|  |                 active={dyslexic} | ||||||
|  |                 onClick={() => { | ||||||
|  |                   setDyslexic(true); | ||||||
|  |                   sendAnalytics("Settings", "Change font (OpenDyslexic)"); | ||||||
|  |                 }} | ||||||
|  |                 className="font-openDyslexic" | ||||||
|  |                 text="OpenDyslexic" | ||||||
|  |               /> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  | 
 | ||||||
|  |           <div> | ||||||
|  |             <h3 className="text-xl">{langui.player_name}</h3> | ||||||
|  |             <TextInput | ||||||
|  |               placeholder="<player>" | ||||||
|  |               className="w-48" | ||||||
|  |               value={playerName} | ||||||
|  |               onChange={(newName) => { | ||||||
|  |                 setPlayerName(newName); | ||||||
|  |                 sendAnalytics("Settings", "Change username"); | ||||||
|  |               }} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </Popup> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -1,5 +1,5 @@ | |||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { useIsSubPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -13,12 +13,12 @@ interface Props { | |||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
| 
 | 
 | ||||||
| export const SubPanel = ({ children }: Props): JSX.Element => { | export const SubPanel = ({ children }: Props): JSX.Element => { | ||||||
|   const isSubPanelAtLeastSm = useIsSubPanelAtLeast("2xs"); |   const { isSubPanelAtLeastXs } = useContainerQueries(); | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       className={cJoin( |       className={cJoin( | ||||||
|         "grid gap-y-2 text-center", |         "grid gap-y-2 text-center", | ||||||
|         cIf(isSubPanelAtLeastSm, "px-10 pt-10 pb-20", "p-4") |         cIf(isSubPanelAtLeastXs, "px-10 pt-10 pb-20", "p-4") | ||||||
|       )}> |       )}> | ||||||
|       {children} |       {children} | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { useEffect } from "react"; | import { useEffect } from "react"; | ||||||
| import Hotkeys from "react-hot-keys"; | import { useHotkeys } from "react-hotkeys-hook"; | ||||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | import { useAppLayout } from "contexts/AppLayoutContext"; | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| 
 | 
 | ||||||
| @ -9,8 +9,8 @@ import { cIf, cJoin } from "helpers/className"; | |||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   onClose: () => void; |   onCloseRequest?: () => void; | ||||||
|   state: boolean; |   isVisible: boolean; | ||||||
|   children: React.ReactNode; |   children: React.ReactNode; | ||||||
|   fillViewport?: boolean; |   fillViewport?: boolean; | ||||||
|   hideBackground?: boolean; |   hideBackground?: boolean; | ||||||
| @ -20,8 +20,8 @@ interface Props { | |||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
| 
 | 
 | ||||||
| export const Popup = ({ | export const Popup = ({ | ||||||
|   onClose, |   onCloseRequest, | ||||||
|   state, |   isVisible, | ||||||
|   children, |   children, | ||||||
|   fillViewport, |   fillViewport, | ||||||
|   hideBackground = false, |   hideBackground = false, | ||||||
| @ -29,36 +29,36 @@ export const Popup = ({ | |||||||
| }: Props): JSX.Element => { | }: Props): JSX.Element => { | ||||||
|   const { setMenuGestures } = useAppLayout(); |   const { setMenuGestures } = useAppLayout(); | ||||||
| 
 | 
 | ||||||
|  |   useHotkeys("escape", () => onCloseRequest?.(), {}, [onCloseRequest]); | ||||||
|  | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setMenuGestures(!state); |     setMenuGestures(!isVisible); | ||||||
|   }, [setMenuGestures, state]); |   }, [setMenuGestures, isVisible]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Hotkeys keyName="escape" allowRepeat onKeyDown={onClose}> |     <div | ||||||
|  |       className={cJoin( | ||||||
|  |         "fixed inset-0 z-50 grid place-content-center transition-filter duration-500", | ||||||
|  |         cIf(isVisible, "backdrop-blur", "pointer-events-none touch-none") | ||||||
|  |       )}> | ||||||
|       <div |       <div | ||||||
|         className={cJoin( |         className={cJoin( | ||||||
|           "fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500", |           "fixed inset-0 bg-shade transition-all duration-500", | ||||||
|           cIf(state, "[backdrop-filter:blur(2px)]", "pointer-events-none touch-none") |           cIf(isVisible, "bg-opacity-50", "bg-opacity-0") | ||||||
|         )}> |         )} | ||||||
|         <div |         onClick={onCloseRequest} | ||||||
|           className={cJoin( |       /> | ||||||
|             "fixed inset-0 bg-shade transition-all duration-500", |  | ||||||
|             cIf(state, "bg-opacity-50", "bg-opacity-0") |  | ||||||
|           )} |  | ||||||
|           onClick={onClose} |  | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|         <div |       <div | ||||||
|           className={cJoin( |         className={cJoin( | ||||||
|             "grid place-items-center gap-4 transition-transform", |           "grid place-items-center gap-4 transition-transform", | ||||||
|             cIf(padding, "p-10"), |           cIf(padding, "p-10"), | ||||||
|             cIf(state, "scale-100", "scale-0"), |           cIf(isVisible, "scale-100", "scale-0"), | ||||||
|             cIf(fillViewport, "absolute inset-10", "relative max-h-[80vh] overflow-y-auto"), |           cIf(fillViewport, "absolute inset-10", "relative max-h-[80vh] overflow-y-auto"), | ||||||
|             cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade") |           cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade") | ||||||
|           )}> |         )}> | ||||||
|           {children} |         {children} | ||||||
|         </div> |  | ||||||
|       </div> |       </div> | ||||||
|     </Hotkeys> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -32,7 +32,6 @@ interface Props { | |||||||
|   topChips?: string[]; |   topChips?: string[]; | ||||||
|   bottomChips?: string[]; |   bottomChips?: string[]; | ||||||
|   keepInfoVisible?: boolean; |   keepInfoVisible?: boolean; | ||||||
|   stackNumber?: number; |  | ||||||
|   metadata?: { |   metadata?: { | ||||||
|     releaseDate?: DatePickerFragment | null; |     releaseDate?: DatePickerFragment | null; | ||||||
|     releaseDateFormat?: Intl.DateTimeFormatOptions["dateStyle"]; |     releaseDateFormat?: Intl.DateTimeFormatOptions["dateStyle"]; | ||||||
| @ -62,7 +61,6 @@ export const PreviewCard = ({ | |||||||
|   title, |   title, | ||||||
|   subtitle, |   subtitle, | ||||||
|   description, |   description, | ||||||
|   stackNumber = 0, |  | ||||||
|   topChips, |   topChips, | ||||||
|   bottomChips, |   bottomChips, | ||||||
|   keepInfoVisible, |   keepInfoVisible, | ||||||
| @ -116,32 +114,6 @@ export const PreviewCard = ({ | |||||||
|       href={href} |       href={href} | ||||||
|       className="group grid cursor-pointer items-end text-left transition-transform |       className="group grid cursor-pointer items-end text-left transition-transform | ||||||
|       drop-shadow-shade-xl hover:scale-[1.02]"> |       drop-shadow-shade-xl hover:scale-[1.02]"> | ||||||
|       {stackNumber > 0 && ( |  | ||||||
|         <> |  | ||||||
|           <div |  | ||||||
|             className={cJoin( |  | ||||||
|               `absolute inset-0 scale-[.85] overflow-hidden bg-light brightness-[0.8] sepia-[0.5]
 |  | ||||||
|               transition-[top_transform] group-hover:-top-9`,
 |  | ||||||
|               cIf(thumbnailRounded, "rounded-md") |  | ||||||
|             )}> |  | ||||||
|             {thumbnail && ( |  | ||||||
|               <Img className="opacity-30" src={thumbnail} quality={ImageQuality.Medium} /> |  | ||||||
|             )} |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <div |  | ||||||
|             className={cJoin( |  | ||||||
|               `absolute inset-0 overflow-hidden bg-light brightness-[0.9] sepia-[0.2]
 |  | ||||||
|               transition-[top_transform] group-hover:-top-4 group-hover:scale-[.94]`,
 |  | ||||||
|               cIf(thumbnailRounded, "rounded-md") |  | ||||||
|             )}> |  | ||||||
|             {thumbnail && ( |  | ||||||
|               <Img className="opacity-70" src={thumbnail} quality={ImageQuality.Medium} /> |  | ||||||
|             )} |  | ||||||
|           </div> |  | ||||||
|         </> |  | ||||||
|       )} |  | ||||||
| 
 |  | ||||||
|       {thumbnail ? ( |       {thumbnail ? ( | ||||||
|         <div |         <div | ||||||
|           className="relative" |           className="relative" | ||||||
| @ -159,13 +131,7 @@ export const PreviewCard = ({ | |||||||
|             src={thumbnail} |             src={thumbnail} | ||||||
|             quality={ImageQuality.Medium} |             quality={ImageQuality.Medium} | ||||||
|           /> |           /> | ||||||
|           {stackNumber > 0 && ( | 
 | ||||||
|             <div |  | ||||||
|               className="absolute right-2 top-2 rounded-full bg-black |  | ||||||
|                 bg-opacity-60 px-2 text-light"> |  | ||||||
|               {stackNumber} |  | ||||||
|             </div> |  | ||||||
|           )} |  | ||||||
|           {hoverlay && hoverlay.__typename === "Video" && ( |           {hoverlay && hoverlay.__typename === "Video" && ( | ||||||
|             <> |             <> | ||||||
|               <div |               <div | ||||||
| @ -190,15 +156,8 @@ export const PreviewCard = ({ | |||||||
|           className={cJoin( |           className={cJoin( | ||||||
|             "relative w-full bg-light", |             "relative w-full bg-light", | ||||||
|             cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none") |             cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none") | ||||||
|           )}> |  | ||||||
|           {stackNumber > 0 && ( |  | ||||||
|             <div |  | ||||||
|               className="absolute right-2 top-2 rounded-full bg-black |  | ||||||
|                 bg-opacity-60 px-2 text-light"> |  | ||||||
|               {stackNumber} |  | ||||||
|             </div> |  | ||||||
|           )} |           )} | ||||||
|         </div> |         /> | ||||||
|       )} |       )} | ||||||
|       <div |       <div | ||||||
|         className={cJoin( |         className={cJoin( | ||||||
|  | |||||||
| @ -6,9 +6,9 @@ import { Ico, Icon } from "./Ico"; | |||||||
| import { cJoin } from "helpers/className"; | import { cJoin } from "helpers/className"; | ||||||
| import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; | import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; | ||||||
| import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; | import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; | ||||||
| import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; |  | ||||||
| import { Ids } from "types/ids"; | import { Ids } from "types/ids"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| interface Group<T> { | interface Group<T> { | ||||||
|   name: string; |   name: string; | ||||||
| @ -191,7 +191,7 @@ export const SmartList = <T,>({ | |||||||
|                   )} |                   )} | ||||||
|                   <div |                   <div | ||||||
|                     className={cJoin( |                     className={cJoin( | ||||||
|                       `grid items-start gap-8 border-b-[3px] border-dotted pb-12
 |                       `grid items-start gap-8 border-b-2 border-dotted pb-12
 | ||||||
|                       last-of-type:border-0`,
 |                       last-of-type:border-0`,
 | ||||||
|                       className |                       className | ||||||
|                     )}> |                     )}> | ||||||
| @ -222,7 +222,7 @@ export const SmartList = <T,>({ | |||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| const DefaultRenderWhenEmpty = () => { | const DefaultRenderWhenEmpty = () => { | ||||||
|   const is3ColumnsLayout = useIs3ColumnsLayout(); |   const { is3ColumnsLayout } = useContainerQueries(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   return ( |   return ( | ||||||
|     <div className="grid h-full place-content-center"> |     <div className="grid h-full place-content-center"> | ||||||
|  | |||||||
| @ -4,10 +4,10 @@ import { InsetBox } from "components/InsetBox"; | |||||||
| import { Markdawn } from "components/Markdown/Markdawn"; | import { Markdawn } from "components/Markdown/Markdawn"; | ||||||
| import { GetContentTextQuery, UploadImageFragment } from "graphql/generated"; | import { GetContentTextQuery, UploadImageFragment } from "graphql/generated"; | ||||||
| import { prettyInlineTitle, prettySlug, slugify } from "helpers/formatters"; | import { prettyInlineTitle, prettySlug, slugify } from "helpers/formatters"; | ||||||
| import { getAssetURL, ImageQuality } from "helpers/img"; | import { ImageQuality } from "helpers/img"; | ||||||
| import { filterHasAttributes } from "helpers/others"; | import { filterHasAttributes } from "helpers/others"; | ||||||
| import { useLightBox } from "hooks/useLightBox"; |  | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useLightBox } from "contexts/LightBoxContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -42,12 +42,11 @@ export const ThumbnailHeader = ({ | |||||||
|   description, |   description, | ||||||
|   languageSwitcher, |   languageSwitcher, | ||||||
| }: Props): JSX.Element => { | }: Props): JSX.Element => { | ||||||
|   const [openLightBox, LightBox] = useLightBox(); |  | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|  |   const { showLightBox } = useLightBox(); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <LightBox /> |  | ||||||
|       <div className="mb-12 grid place-items-center gap-12"> |       <div className="mb-12 grid place-items-center gap-12"> | ||||||
|         <div className="drop-shadow-shade-lg"> |         <div className="drop-shadow-shade-lg"> | ||||||
|           {thumbnail ? ( |           {thumbnail ? ( | ||||||
| @ -55,9 +54,7 @@ export const ThumbnailHeader = ({ | |||||||
|               className="cursor-pointer rounded-xl" |               className="cursor-pointer rounded-xl" | ||||||
|               src={thumbnail} |               src={thumbnail} | ||||||
|               quality={ImageQuality.Medium} |               quality={ImageQuality.Medium} | ||||||
|               onClick={() => { |               onClick={() => showLightBox([thumbnail])} | ||||||
|                 openLightBox([getAssetURL(thumbnail.url, ImageQuality.Large)]); |  | ||||||
|               }} |  | ||||||
|             /> |             /> | ||||||
|           ) : ( |           ) : ( | ||||||
|             <div className="aspect-[4/3] w-96 rounded-xl bg-light"></div> |             <div className="aspect-[4/3] w-96 rounded-xl bg-light"></div> | ||||||
|  | |||||||
| @ -4,9 +4,9 @@ import { ToolTip } from "components/ToolTip"; | |||||||
| import { getStatusDescription } from "helpers/others"; | import { getStatusDescription } from "helpers/others"; | ||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| import { Button } from "components/Inputs/Button"; | import { Button } from "components/Inputs/Button"; | ||||||
| import { useIsContentPanelNoMoreThan } from "hooks/useContainerQuery"; |  | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                        ╭─────────────╮ |  *                                        ╭─────────────╮ | ||||||
| @ -30,7 +30,7 @@ interface Props { | |||||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||||
| 
 | 
 | ||||||
| const DefinitionCard = ({ source, translations = [], index, categories }: Props): JSX.Element => { | const DefinitionCard = ({ source, translations = [], index, categories }: Props): JSX.Element => { | ||||||
|   const isContentPanelNoMoreThanMd = useIsContentPanelNoMoreThan("md"); |   const { isContentPanelAtLeastMd } = useContainerQueries(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ |   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ | ||||||
|     items: translations, |     items: translations, | ||||||
| @ -78,7 +78,7 @@ const DefinitionCard = ({ source, translations = [], index, categories }: Props) | |||||||
|         <div |         <div | ||||||
|           className={cJoin( |           className={cJoin( | ||||||
|             "mt-3 flex place-items-center gap-2", |             "mt-3 flex place-items-center gap-2", | ||||||
|             cIf(isContentPanelNoMoreThanMd, "flex-col text-center") |             cIf(!isContentPanelAtLeastMd, "flex-col text-center") | ||||||
|           )}> |           )}> | ||||||
|           <p>{langui.source}: </p> |           <p>{langui.source}: </p> | ||||||
|           <Button href={source.url} size="small" text={source.name} /> |           <Button href={source.url} size="small" text={source.name} /> | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ import React, { | |||||||
|   useState, |   useState, | ||||||
| } from "react"; | } from "react"; | ||||||
| import { useRouter } from "next/router"; | import { useRouter } from "next/router"; | ||||||
| import { useLocalStorage, useSessionStorage } from "usehooks-ts"; | import { useLocalStorage } from "usehooks-ts"; | ||||||
| import { isDefined } from "helpers/others"; | import { isDefined } from "helpers/others"; | ||||||
| import { RequiredNonNullable } from "types/types"; | import { RequiredNonNullable } from "types/types"; | ||||||
| import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage"; | import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage"; | ||||||
| @ -34,11 +34,6 @@ interface AppLayoutState { | |||||||
|   menuGestures: boolean; |   menuGestures: boolean; | ||||||
|   toggleMenuGestures: () => void; |   toggleMenuGestures: () => void; | ||||||
|   setMenuGestures: Dispatch<SetStateAction<AppLayoutState["menuGestures"]>>; |   setMenuGestures: Dispatch<SetStateAction<AppLayoutState["menuGestures"]>>; | ||||||
| 
 |  | ||||||
|   hasDisgardedSafariWarning: boolean; |  | ||||||
|   setHasDisgardedSafariWarning: Dispatch< |  | ||||||
|     SetStateAction<AppLayoutState["hasDisgardedSafariWarning"]> |  | ||||||
|   >; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const initialState: RequiredNonNullable<AppLayoutState> = { | const initialState: RequiredNonNullable<AppLayoutState> = { | ||||||
| @ -61,9 +56,6 @@ const initialState: RequiredNonNullable<AppLayoutState> = { | |||||||
|   menuGestures: true, |   menuGestures: true, | ||||||
|   toggleMenuGestures: () => null, |   toggleMenuGestures: () => null, | ||||||
|   setMenuGestures: () => null, |   setMenuGestures: () => null, | ||||||
| 
 |  | ||||||
|   hasDisgardedSafariWarning: false, |  | ||||||
|   setHasDisgardedSafariWarning: () => null, |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const AppLayoutContext = createContext<AppLayoutState>(initialState); | const AppLayoutContext = createContext<AppLayoutState>(initialState); | ||||||
| @ -99,11 +91,6 @@ export const AppContextProvider = ({ children }: Props): JSX.Element => { | |||||||
| 
 | 
 | ||||||
|   const [menuGestures, setMenuGestures] = useState(false); |   const [menuGestures, setMenuGestures] = useState(false); | ||||||
| 
 | 
 | ||||||
|   const [hasDisgardedSafariWarning, setHasDisgardedSafariWarning] = useSessionStorage( |  | ||||||
|     "hasDisgardedSafariWarning", |  | ||||||
|     initialState.hasDisgardedSafariWarning |  | ||||||
|   ); |  | ||||||
| 
 |  | ||||||
|   const toggleSubPanelOpen = () => { |   const toggleSubPanelOpen = () => { | ||||||
|     setSubPanelOpen((current) => (isDefined(current) ? !current : current)); |     setSubPanelOpen((current) => (isDefined(current) ? !current : current)); | ||||||
|   }; |   }; | ||||||
| @ -148,13 +135,11 @@ export const AppContextProvider = ({ children }: Props): JSX.Element => { | |||||||
|         mainPanelReduced, |         mainPanelReduced, | ||||||
|         mainPanelOpen, |         mainPanelOpen, | ||||||
|         menuGestures, |         menuGestures, | ||||||
|         hasDisgardedSafariWarning, |  | ||||||
|         setSubPanelOpen, |         setSubPanelOpen, | ||||||
|         setConfigPanelOpen, |         setConfigPanelOpen, | ||||||
|         setMainPanelReduced, |         setMainPanelReduced, | ||||||
|         setMainPanelOpen, |         setMainPanelOpen, | ||||||
|         setMenuGestures, |         setMenuGestures, | ||||||
|         setHasDisgardedSafariWarning, |  | ||||||
|         toggleSubPanelOpen, |         toggleSubPanelOpen, | ||||||
|         toggleConfigPanelOpen, |         toggleConfigPanelOpen, | ||||||
|         toggleMainPanelReduced, |         toggleMainPanelReduced, | ||||||
|  | |||||||
| @ -1,33 +1,86 @@ | |||||||
| import React, { | import React, { createContext, ReactNode, useContext, useMemo, useState } from "react"; | ||||||
|   createContext, | import { useUserSettings } from "./UserSettingsContext"; | ||||||
|   Dispatch, | import { useOnResize } from "hooks/useOnResize"; | ||||||
|   ReactNode, | import { Ids } from "types/ids"; | ||||||
|   SetStateAction, |  | ||||||
|   useContext, |  | ||||||
|   useState, |  | ||||||
| } from "react"; |  | ||||||
| import { RequiredNonNullable } from "types/types"; | import { RequiredNonNullable } from "types/types"; | ||||||
| 
 | 
 | ||||||
| interface ContainerQueriesState { | type Size = | ||||||
|   screenWidth: number; |   | "2xl" | ||||||
|   setScreenWidth: Dispatch<SetStateAction<ContainerQueriesState["screenWidth"]>>; |   | "2xs" | ||||||
|  |   | "3xl" | ||||||
|  |   | "4xl" | ||||||
|  |   | "5xl" | ||||||
|  |   | "6xl" | ||||||
|  |   | "7xl" | ||||||
|  |   | "lg" | ||||||
|  |   | "md" | ||||||
|  |   | "sm" | ||||||
|  |   | "xl" | ||||||
|  |   | "xs"; | ||||||
| 
 | 
 | ||||||
|   contentPanelWidth: number; | const sizes: Record<Size, number> = { | ||||||
|   setContentPanelWidth: Dispatch<SetStateAction<ContainerQueriesState["contentPanelWidth"]>>; |   "2xl": 42, | ||||||
|  |   "3xl": 48, | ||||||
|  |   "4xl": 56, | ||||||
|  |   "5xl": 64, | ||||||
|  |   "6xl": 72, | ||||||
|  |   "7xl": 80, | ||||||
|  |   lg: 32, | ||||||
|  |   md: 28, | ||||||
|  |   sm: 24, | ||||||
|  |   xl: 36, | ||||||
|  |   xs: 19, | ||||||
|  |   "2xs": 16, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
|   subPanelWidth: number; | type ContainerQueriesContainers = "contentPanel" | "screen" | "subPanel"; | ||||||
|   setSubPanelWidth: Dispatch<SetStateAction<ContainerQueriesState["subPanelWidth"]>>; | 
 | ||||||
| } | type ContainerQueriesKeys = | ||||||
|  |   | "is1ColumnLayout" | ||||||
|  |   | "is3ColumnsLayout" | ||||||
|  |   | `is${Capitalize<ContainerQueriesContainers>}AtLeast${Capitalize<Size>}`; | ||||||
|  | 
 | ||||||
|  | type ContainerQueriesState = Record<ContainerQueriesKeys, boolean>; | ||||||
| 
 | 
 | ||||||
| const initialState: RequiredNonNullable<ContainerQueriesState> = { | const initialState: RequiredNonNullable<ContainerQueriesState> = { | ||||||
|   screenWidth: 0, |   is1ColumnLayout: false, | ||||||
|   setScreenWidth: () => null, |   is3ColumnsLayout: false, | ||||||
| 
 |   isContentPanelAtLeast2xl: false, | ||||||
|   contentPanelWidth: 0, |   isContentPanelAtLeast2xs: false, | ||||||
|   setContentPanelWidth: () => null, |   isContentPanelAtLeast3xl: false, | ||||||
| 
 |   isContentPanelAtLeast4xl: false, | ||||||
|   subPanelWidth: 0, |   isContentPanelAtLeast5xl: false, | ||||||
|   setSubPanelWidth: () => null, |   isContentPanelAtLeast6xl: false, | ||||||
|  |   isContentPanelAtLeast7xl: false, | ||||||
|  |   isContentPanelAtLeastLg: false, | ||||||
|  |   isContentPanelAtLeastMd: false, | ||||||
|  |   isContentPanelAtLeastSm: false, | ||||||
|  |   isContentPanelAtLeastXl: false, | ||||||
|  |   isContentPanelAtLeastXs: false, | ||||||
|  |   isScreenAtLeast2xl: false, | ||||||
|  |   isScreenAtLeast2xs: false, | ||||||
|  |   isScreenAtLeast3xl: false, | ||||||
|  |   isScreenAtLeast4xl: false, | ||||||
|  |   isScreenAtLeast5xl: false, | ||||||
|  |   isScreenAtLeast6xl: false, | ||||||
|  |   isScreenAtLeast7xl: false, | ||||||
|  |   isScreenAtLeastLg: false, | ||||||
|  |   isScreenAtLeastMd: false, | ||||||
|  |   isScreenAtLeastSm: false, | ||||||
|  |   isScreenAtLeastXl: false, | ||||||
|  |   isScreenAtLeastXs: false, | ||||||
|  |   isSubPanelAtLeast2xl: false, | ||||||
|  |   isSubPanelAtLeast2xs: false, | ||||||
|  |   isSubPanelAtLeast3xl: false, | ||||||
|  |   isSubPanelAtLeast4xl: false, | ||||||
|  |   isSubPanelAtLeast5xl: false, | ||||||
|  |   isSubPanelAtLeast6xl: false, | ||||||
|  |   isSubPanelAtLeast7xl: false, | ||||||
|  |   isSubPanelAtLeastLg: false, | ||||||
|  |   isSubPanelAtLeastMd: false, | ||||||
|  |   isSubPanelAtLeastSm: false, | ||||||
|  |   isSubPanelAtLeastXl: false, | ||||||
|  |   isSubPanelAtLeastXs: false, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const ContainerQueriesContext = createContext<ContainerQueriesState>(initialState); | const ContainerQueriesContext = createContext<ContainerQueriesState>(initialState); | ||||||
| @ -43,17 +96,220 @@ export const ContainerQueriesContextProvider = ({ children }: Props): JSX.Elemen | |||||||
|   const [contentPanelWidth, setContentPanelWidth] = useState(0); |   const [contentPanelWidth, setContentPanelWidth] = useState(0); | ||||||
|   const [subPanelWidth, setSubPanelWidth] = useState(0); |   const [subPanelWidth, setSubPanelWidth] = useState(0); | ||||||
| 
 | 
 | ||||||
|  |   useOnResize(Ids.Body, (width) => setScreenWidth(width)); | ||||||
|  |   useOnResize(Ids.ContentPanel, (width) => setContentPanelWidth(width)); | ||||||
|  |   useOnResize(Ids.SubPanel, (width) => setSubPanelWidth(width)); | ||||||
|  | 
 | ||||||
|  |   const { fontSize } = useUserSettings(); | ||||||
|  | 
 | ||||||
|  |   const screenAtLeasts = useMemo( | ||||||
|  |     () => ({ | ||||||
|  |       isScreenAtLeast2xs: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         screenWidth, | ||||||
|  |         createAtLeastMediaQuery("2xs") | ||||||
|  |       ), | ||||||
|  |       isScreenAtLeastXs: applyContainerQuery(fontSize, screenWidth, createAtLeastMediaQuery("xs")), | ||||||
|  |       isScreenAtLeastSm: applyContainerQuery(fontSize, screenWidth, createAtLeastMediaQuery("sm")), | ||||||
|  |       isScreenAtLeastMd: applyContainerQuery(fontSize, screenWidth, createAtLeastMediaQuery("md")), | ||||||
|  |       isScreenAtLeastLg: applyContainerQuery(fontSize, screenWidth, createAtLeastMediaQuery("lg")), | ||||||
|  |       isScreenAtLeastXl: applyContainerQuery(fontSize, screenWidth, createAtLeastMediaQuery("xl")), | ||||||
|  |       isScreenAtLeast2xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         screenWidth, | ||||||
|  |         createAtLeastMediaQuery("2xl") | ||||||
|  |       ), | ||||||
|  |       isScreenAtLeast3xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         screenWidth, | ||||||
|  |         createAtLeastMediaQuery("3xl") | ||||||
|  |       ), | ||||||
|  |       isScreenAtLeast4xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         screenWidth, | ||||||
|  |         createAtLeastMediaQuery("4xl") | ||||||
|  |       ), | ||||||
|  |       isScreenAtLeast5xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         screenWidth, | ||||||
|  |         createAtLeastMediaQuery("5xl") | ||||||
|  |       ), | ||||||
|  |       isScreenAtLeast6xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         screenWidth, | ||||||
|  |         createAtLeastMediaQuery("6xl") | ||||||
|  |       ), | ||||||
|  |       isScreenAtLeast7xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         screenWidth, | ||||||
|  |         createAtLeastMediaQuery("7xl") | ||||||
|  |       ), | ||||||
|  |     }), | ||||||
|  |     [screenWidth, fontSize] | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const columnLayouts = useMemo( | ||||||
|  |     () => ({ | ||||||
|  |       is1ColumnLayout: !screenAtLeasts.isScreenAtLeast5xl, | ||||||
|  |       is3ColumnsLayout: screenAtLeasts.isScreenAtLeast5xl, | ||||||
|  |     }), | ||||||
|  |     [screenAtLeasts.isScreenAtLeast5xl] | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const subPanelAtLeasts = useMemo( | ||||||
|  |     () => ({ | ||||||
|  |       isSubPanelAtLeast2xs: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("2xs") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeastXs: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("xs") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeastSm: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("sm") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeastMd: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("md") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeastLg: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("lg") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeastXl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("xl") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeast2xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("2xl") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeast3xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("3xl") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeast4xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("4xl") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeast5xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("5xl") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeast6xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("6xl") | ||||||
|  |       ), | ||||||
|  |       isSubPanelAtLeast7xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         subPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("7xl") | ||||||
|  |       ), | ||||||
|  |     }), | ||||||
|  |     [subPanelWidth, fontSize] | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const contentPanelAtLeasts = useMemo( | ||||||
|  |     () => ({ | ||||||
|  |       isContentPanelAtLeast2xs: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("2xs") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeastXs: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("xs") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeastSm: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("sm") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeastMd: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("md") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeastLg: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("lg") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeastXl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("xl") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeast2xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("2xl") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeast3xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("3xl") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeast4xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("4xl") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeast5xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("5xl") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeast6xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("6xl") | ||||||
|  |       ), | ||||||
|  |       isContentPanelAtLeast7xl: applyContainerQuery( | ||||||
|  |         fontSize, | ||||||
|  |         contentPanelWidth, | ||||||
|  |         createAtLeastMediaQuery("7xl") | ||||||
|  |       ), | ||||||
|  |     }), | ||||||
|  |     [contentPanelWidth, fontSize] | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <ContainerQueriesContext.Provider |     <ContainerQueriesContext.Provider | ||||||
|       value={{ |       value={{ | ||||||
|         screenWidth, |         ...screenAtLeasts, | ||||||
|         contentPanelWidth, |         ...contentPanelAtLeasts, | ||||||
|         subPanelWidth, |         ...subPanelAtLeasts, | ||||||
|         setScreenWidth, |         ...columnLayouts, | ||||||
|         setContentPanelWidth, |  | ||||||
|         setSubPanelWidth, |  | ||||||
|       }}> |       }}> | ||||||
|       {children} |       {children} | ||||||
|     </ContainerQueriesContext.Provider> |     </ContainerQueriesContext.Provider> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | type MediaQuery = { value: number; unit: "px" | "rem"; rule: "max" | "min" }; | ||||||
|  | 
 | ||||||
|  | const createAtLeastMediaQuery = (size: Size): MediaQuery => ({ | ||||||
|  |   value: sizes[size], | ||||||
|  |   unit: "rem", | ||||||
|  |   rule: "min", | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const applyContainerQuery = (fontSize: number, width: number, query: MediaQuery): boolean => { | ||||||
|  |   const breakpoint = query.value * (query.unit === "rem" ? 16 : 1) * fontSize; | ||||||
|  |   return query.rule === "min" ? width >= breakpoint : width < breakpoint; | ||||||
|  | }; | ||||||
|  | |||||||
							
								
								
									
										58
									
								
								src/contexts/LightBoxContext.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,58 @@ | |||||||
|  | import React, { createContext, ReactNode, useCallback, useContext, useState } from "react"; | ||||||
|  | import { UploadImageFragment } from "graphql/generated"; | ||||||
|  | import { RequiredNonNullable } from "types/types"; | ||||||
|  | import { LightBox } from "components/LightBox"; | ||||||
|  | import { filterDefined } from "helpers/others"; | ||||||
|  | 
 | ||||||
|  | type LightBoxImagesNullable = (UploadImageFragment | string | null | undefined)[]; | ||||||
|  | type LightBoxImages = (UploadImageFragment | string)[]; | ||||||
|  | 
 | ||||||
|  | interface LightBoxState { | ||||||
|  |   showLightBox: (images: LightBoxImagesNullable, index?: number) => void; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const initialState: RequiredNonNullable<LightBoxState> = { | ||||||
|  |   showLightBox: () => null, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const LightBoxContext = createContext<LightBoxState>(initialState); | ||||||
|  | 
 | ||||||
|  | export const useLightBox = (): LightBoxState => useContext(LightBoxContext); | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|  |   children: ReactNode; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const LightBoxContextProvider = ({ children }: Props): JSX.Element => { | ||||||
|  |   const [isLightBoxVisible, setLightBoxVisibility] = useState(false); | ||||||
|  |   const [lightBoxImages, setLightBoxImages] = useState<LightBoxImages>([]); | ||||||
|  |   const [lightBoxIndex, setLightBoxIndex] = useState(0); | ||||||
|  | 
 | ||||||
|  |   const showLightBox = useCallback((images: LightBoxImagesNullable, index = 0) => { | ||||||
|  |     const filteredImages = filterDefined(images); | ||||||
|  |     setLightBoxIndex(index); | ||||||
|  |     setLightBoxImages(filteredImages); | ||||||
|  |     setLightBoxVisibility(true); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   const closeLightBox = useCallback(() => { | ||||||
|  |     setLightBoxVisibility(false); | ||||||
|  |     setTimeout(() => setLightBoxImages([]), 100); | ||||||
|  |   }, []); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <LightBoxContext.Provider value={{ showLightBox }}> | ||||||
|  |       <LightBox | ||||||
|  |         isVisible={isLightBoxVisible} | ||||||
|  |         onCloseRequest={closeLightBox} | ||||||
|  |         image={lightBoxImages[lightBoxIndex]} | ||||||
|  |         isNextImageAvailable={lightBoxIndex < lightBoxImages.length - 1} | ||||||
|  |         isPreviousImageAvailable={lightBoxIndex > 0} | ||||||
|  |         onPressNext={() => setLightBoxIndex((current) => current + 1)} | ||||||
|  |         onPressPrevious={() => setLightBoxIndex((current) => current - 1)} | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  |       {children} | ||||||
|  |     </LightBoxContext.Provider> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -13,6 +13,7 @@ import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; | |||||||
| import { RequiredNonNullable } from "types/types"; | import { RequiredNonNullable } from "types/types"; | ||||||
| import { getDefaultPreferredLanguages } from "helpers/locales"; | import { getDefaultPreferredLanguages } from "helpers/locales"; | ||||||
| import { useDarkMode } from "hooks/useDarkMode"; | import { useDarkMode } from "hooks/useDarkMode"; | ||||||
|  | import { SettingsPopup } from "components/Panels/SettingsPopup"; | ||||||
| 
 | 
 | ||||||
| interface UserSettingsState { | interface UserSettingsState { | ||||||
|   fontSize: number; |   fontSize: number; | ||||||
| @ -134,6 +135,32 @@ export const UserSettingsProvider = ({ children }: Props): JSX.Element => { | |||||||
|     } |     } | ||||||
|   }, [fontSize]); |   }, [fontSize]); | ||||||
| 
 | 
 | ||||||
|  |   useLayoutEffect(() => { | ||||||
|  |     const next = document.getElementById("__next"); | ||||||
|  |     if (isDefined(next)) { | ||||||
|  |       if (darkMode) { | ||||||
|  |         next.classList.add("set-theme-dark"); | ||||||
|  |         next.classList.remove("set-theme-light"); | ||||||
|  |       } else { | ||||||
|  |         next.classList.add("set-theme-light"); | ||||||
|  |         next.classList.remove("set-theme-dark"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, [darkMode]); | ||||||
|  | 
 | ||||||
|  |   useLayoutEffect(() => { | ||||||
|  |     const next = document.getElementById("__next"); | ||||||
|  |     if (isDefined(next)) { | ||||||
|  |       if (dyslexic) { | ||||||
|  |         next.classList.add("set-theme-font-dyslexic"); | ||||||
|  |         next.classList.remove("set-theme-font-standard"); | ||||||
|  |       } else { | ||||||
|  |         next.classList.add("set-theme-font-standard"); | ||||||
|  |         next.classList.remove("set-theme-font-dyslexic"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, [dyslexic]); | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <UserSettingsContext.Provider |     <UserSettingsContext.Provider | ||||||
|       value={{ |       value={{ | ||||||
| @ -155,6 +182,7 @@ export const UserSettingsProvider = ({ children }: Props): JSX.Element => { | |||||||
|         toggleSelectedThemeMode, |         toggleSelectedThemeMode, | ||||||
|         toggleDyslexic, |         toggleDyslexic, | ||||||
|       }}> |       }}> | ||||||
|  |       <SettingsPopup /> | ||||||
|       {children} |       {children} | ||||||
|     </UserSettingsContext.Provider> |     </UserSettingsContext.Provider> | ||||||
|   ); |   ); | ||||||
|  | |||||||
| @ -1,113 +0,0 @@ | |||||||
| import { useContainerQueries } from "contexts/ContainerQueriesContext"; |  | ||||||
| import { useUserSettings } from "contexts/UserSettingsContext"; |  | ||||||
| 
 |  | ||||||
| type MediaQuery = { value: number; unit: "px" | "rem"; rule: "max" | "min" }; |  | ||||||
| 
 |  | ||||||
| type Size = |  | ||||||
|   | "2xl" |  | ||||||
|   | "2xs" |  | ||||||
|   | "3xl" |  | ||||||
|   | "4xl" |  | ||||||
|   | "5xl" |  | ||||||
|   | "6xl" |  | ||||||
|   | "7xl" |  | ||||||
|   | "lg" |  | ||||||
|   | "md" |  | ||||||
|   | "sm" |  | ||||||
|   | "xl" |  | ||||||
|   | "xs"; |  | ||||||
| 
 |  | ||||||
| const sizes: Record<Size, number> = { |  | ||||||
|   "2xl": 42, |  | ||||||
|   "3xl": 48, |  | ||||||
|   "4xl": 56, |  | ||||||
|   "5xl": 64, |  | ||||||
|   "6xl": 72, |  | ||||||
|   "7xl": 80, |  | ||||||
|   lg: 32, |  | ||||||
|   md: 28, |  | ||||||
|   sm: 24, |  | ||||||
|   xl: 36, |  | ||||||
|   xs: 20, |  | ||||||
|   "2xs": 16, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const useIsScreenAtLeast = (size: Size): boolean => { |  | ||||||
|   const { screenWidth } = useContainerQueries(); |  | ||||||
|   return useApplyContainerQuery(screenWidth, { |  | ||||||
|     value: sizes[size], |  | ||||||
|     unit: "rem", |  | ||||||
|     rule: "min", |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ts-unused-exports:disable-next-line
 |  | ||||||
| export const useIsScreenNoMoreThan = (size: Size): boolean => { |  | ||||||
|   const { screenWidth } = useContainerQueries(); |  | ||||||
|   return useApplyContainerQuery(screenWidth, { |  | ||||||
|     value: sizes[size], |  | ||||||
|     unit: "rem", |  | ||||||
|     rule: "max", |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const useIsContentPanelAtLeast = (size: Size): boolean => { |  | ||||||
|   const { contentPanelWidth } = useContainerQueries(); |  | ||||||
|   return useApplyContainerQuery(contentPanelWidth, { |  | ||||||
|     value: sizes[size], |  | ||||||
|     unit: "rem", |  | ||||||
|     rule: "min", |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const useIsContentPanelNoMoreThan = (size: Size): boolean => { |  | ||||||
|   const { contentPanelWidth } = useContainerQueries(); |  | ||||||
|   return useApplyContainerQuery(contentPanelWidth, { |  | ||||||
|     value: sizes[size], |  | ||||||
|     unit: "rem", |  | ||||||
|     rule: "max", |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const useIsSubPanelAtLeast = (size: Size): boolean => { |  | ||||||
|   const { subPanelWidth } = useContainerQueries(); |  | ||||||
|   return useApplyContainerQuery(subPanelWidth, { |  | ||||||
|     value: sizes[size], |  | ||||||
|     unit: "rem", |  | ||||||
|     rule: "min", |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // ts-unused-exports:disable-next-line
 |  | ||||||
| export const useIsSubPanelNoMoreThan = (size: Size): boolean => { |  | ||||||
|   const { subPanelWidth } = useContainerQueries(); |  | ||||||
|   return useApplyContainerQuery(subPanelWidth, { |  | ||||||
|     value: sizes[size], |  | ||||||
|     unit: "rem", |  | ||||||
|     rule: "max", |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const useIs3ColumnsLayout = (): boolean => { |  | ||||||
|   const { screenWidth } = useContainerQueries(); |  | ||||||
|   return useApplyContainerQuery(screenWidth, { |  | ||||||
|     value: sizes["5xl"], |  | ||||||
|     unit: "rem", |  | ||||||
|     rule: "min", |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const useIs1ColumnLayout = (): boolean => { |  | ||||||
|   const { screenWidth } = useContainerQueries(); |  | ||||||
|   return useApplyContainerQuery(screenWidth, { |  | ||||||
|     value: sizes["5xl"], |  | ||||||
|     unit: "rem", |  | ||||||
|     rule: "max", |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const useApplyContainerQuery = (width: number, query: MediaQuery) => { |  | ||||||
|   const { fontSize } = useUserSettings(); |  | ||||||
|   const breakpoint = query.value * (query.unit === "rem" ? 16 : 1) * fontSize; |  | ||||||
|   return query.rule === "min" ? width >= breakpoint : width < breakpoint; |  | ||||||
| }; |  | ||||||
| @ -4,21 +4,27 @@ import { isDefined } from "helpers/others"; | |||||||
| 
 | 
 | ||||||
| export const useFullscreen = ( | export const useFullscreen = ( | ||||||
|   id: string |   id: string | ||||||
| ): { isFullscreen: boolean; toggleFullscreen: () => void } => { | ): { | ||||||
|  |   isFullscreen: boolean; | ||||||
|  |   requestFullscreen: () => void; | ||||||
|  |   exitFullscreen: () => void; | ||||||
|  |   toggleFullscreen: () => void; | ||||||
|  | } => { | ||||||
|   const [isFullscreen, setIsFullscreen] = useState(false); |   const [isFullscreen, setIsFullscreen] = useState(false); | ||||||
|   const isClient = useIsClient(); |   const isClient = useIsClient(); | ||||||
| 
 | 
 | ||||||
|   const elem = useMemo(() => (isClient ? document.querySelector(`#${id}`) : null), [id, isClient]); |   const elem = useMemo(() => (isClient ? document.querySelector(`#${id}`) : null), [id, isClient]); | ||||||
| 
 | 
 | ||||||
|   const toggleFullscreen = useCallback(() => { |   const requestFullscreen = useCallback(() => elem?.requestFullscreen(), [elem]); | ||||||
|     if (elem) { |   const exitFullscreen = useCallback( | ||||||
|       if (isFullscreen) { |     async () => isFullscreen && document.exitFullscreen(), | ||||||
|         document.exitFullscreen(); |     [isFullscreen] | ||||||
|       } else { |   ); | ||||||
|         elem.requestFullscreen(); | 
 | ||||||
|       } |   const toggleFullscreen = useCallback( | ||||||
|     } |     () => (isFullscreen ? exitFullscreen() : requestFullscreen()), | ||||||
|   }, [elem, isFullscreen]); |     [exitFullscreen, isFullscreen, requestFullscreen] | ||||||
|  |   ); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const onFullscreenChanged = () => { |     const onFullscreenChanged = () => { | ||||||
| @ -28,5 +34,5 @@ export const useFullscreen = ( | |||||||
|     return () => removeEventListener("fullscreenchange", onFullscreenChanged); |     return () => removeEventListener("fullscreenchange", onFullscreenChanged); | ||||||
|   }, []); |   }, []); | ||||||
| 
 | 
 | ||||||
|   return { isFullscreen, toggleFullscreen }; |   return { isFullscreen, requestFullscreen, exitFullscreen, toggleFullscreen }; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,25 +0,0 @@ | |||||||
| import { useState } from "react"; |  | ||||||
| import { LightBox } from "components/LightBox"; |  | ||||||
| 
 |  | ||||||
| export const useLightBox = (): [(images: string[], index?: number) => void, () => JSX.Element] => { |  | ||||||
|   const [lightboxOpen, setLightboxOpen] = useState(false); |  | ||||||
|   const [lightboxImages, setLightboxImages] = useState([""]); |  | ||||||
|   const [lightboxIndex, setLightboxIndex] = useState(0); |  | ||||||
| 
 |  | ||||||
|   return [ |  | ||||||
|     (images: string[], index = 0) => { |  | ||||||
|       setLightboxOpen(true); |  | ||||||
|       setLightboxImages(images); |  | ||||||
|       setLightboxIndex(index); |  | ||||||
|     }, |  | ||||||
|     () => ( |  | ||||||
|       <LightBox |  | ||||||
|         state={lightboxOpen} |  | ||||||
|         setState={setLightboxOpen} |  | ||||||
|         images={lightboxImages} |  | ||||||
|         index={lightboxIndex} |  | ||||||
|         setIndex={setLightboxIndex} |  | ||||||
|       /> |  | ||||||
|     ), |  | ||||||
|   ]; |  | ||||||
| }; |  | ||||||
| @ -1,29 +1,49 @@ | |||||||
| import "@fontsource/material-icons"; | import "@fontsource/material-icons"; | ||||||
|  | import "@fontsource/material-icons-outlined"; | ||||||
| import "@fontsource/opendyslexic/400.css"; | import "@fontsource/opendyslexic/400.css"; | ||||||
| import "@fontsource/share-tech-mono/400.css"; | import "@fontsource/share-tech-mono/400.css"; | ||||||
| import "@fontsource/opendyslexic/700.css"; | import "@fontsource/opendyslexic/700.css"; | ||||||
| import "@fontsource/vollkorn/700.css"; | import "@fontsource/vollkorn/700.css"; | ||||||
| import "@fontsource/zen-maru-gothic/500.css"; | import "@fontsource/zen-maru-gothic/500.css"; | ||||||
| import "@fontsource/zen-maru-gothic/900.css"; | import "@fontsource/zen-maru-gothic/900.css"; | ||||||
|  | 
 | ||||||
| import type { AppProps } from "next/app"; | import type { AppProps } from "next/app"; | ||||||
|  | import Script from "next/script"; | ||||||
| import { AppContextProvider } from "contexts/AppLayoutContext"; | import { AppContextProvider } from "contexts/AppLayoutContext"; | ||||||
| import "tailwind.css"; | 
 | ||||||
|  | import "styles/animations.css"; | ||||||
|  | import "styles/custom-classes.css"; | ||||||
|  | import "styles/debug.css"; | ||||||
|  | import "styles/formatted.css"; | ||||||
|  | import "styles/others.css"; | ||||||
|  | import "styles/rc-slider.css"; | ||||||
|  | import "styles/tippy.css"; | ||||||
|  | 
 | ||||||
| import { TerminalContextProvider } from "contexts/TerminalContext"; | import { TerminalContextProvider } from "contexts/TerminalContext"; | ||||||
| import { UserSettingsProvider as UserSettingsContextProvider } from "contexts/UserSettingsContext"; | import { UserSettingsProvider as UserSettingsContextProvider } from "contexts/UserSettingsContext"; | ||||||
| import { LocalDataContextProvider } from "contexts/LocalDataContext"; | import { LocalDataContextProvider } from "contexts/LocalDataContext"; | ||||||
| import { ContainerQueriesContextProvider } from "contexts/ContainerQueriesContext"; | import { ContainerQueriesContextProvider } from "contexts/ContainerQueriesContext"; | ||||||
|  | import { LightBoxContextProvider } from "contexts/LightBoxContext"; | ||||||
| 
 | 
 | ||||||
| const AccordsLibraryApp = (props: AppProps): JSX.Element => ( | const AccordsLibraryApp = (props: AppProps): JSX.Element => ( | ||||||
|   <AppContextProvider> |   <LocalDataContextProvider> | ||||||
|     <ContainerQueriesContextProvider> |     <AppContextProvider> | ||||||
|       <LocalDataContextProvider> |       <UserSettingsContextProvider> | ||||||
|         <UserSettingsContextProvider> |         <ContainerQueriesContextProvider> | ||||||
|           <TerminalContextProvider> |           <TerminalContextProvider> | ||||||
|             <props.Component {...props.pageProps} /> |             <LightBoxContextProvider> | ||||||
|  |               <Script | ||||||
|  |                 async | ||||||
|  |                 defer | ||||||
|  |                 data-website-id={process.env.NEXT_PUBLIC_UMAMI_ID} | ||||||
|  |                 src={`${process.env.NEXT_PUBLIC_UMAMI_URL}/umami.js`} | ||||||
|  |               /> | ||||||
|  |               <props.Component {...props.pageProps} /> | ||||||
|  |             </LightBoxContextProvider> | ||||||
|           </TerminalContextProvider> |           </TerminalContextProvider> | ||||||
|         </UserSettingsContextProvider> |         </ContainerQueriesContextProvider> | ||||||
|       </LocalDataContextProvider> |       </UserSettingsContextProvider> | ||||||
|     </ContainerQueriesContextProvider> |     </AppContextProvider> | ||||||
|   </AppContextProvider> |   </LocalDataContextProvider> | ||||||
| ); | ); | ||||||
| export default AccordsLibraryApp; | export default AccordsLibraryApp; | ||||||
|  | |||||||
| @ -6,9 +6,9 @@ import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps" | |||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { randomInt } from "helpers/numbers"; | import { randomInt } from "helpers/numbers"; | ||||||
| import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; | import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; | ||||||
| import { useIs1ColumnLayout } from "hooks/useContainerQuery"; |  | ||||||
| import { sendAnalytics } from "helpers/analytics"; | import { sendAnalytics } from "helpers/analytics"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
| @ -18,7 +18,7 @@ import { useLocalData } from "contexts/LocalDataContext"; | |||||||
| const AboutUs = (props: PostStaticProps): JSX.Element => { | const AboutUs = (props: PostStaticProps): JSX.Element => { | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const is1ColumnLayout = useIs1ColumnLayout(); |   const { is1ColumnLayout } = useContainerQueries(); | ||||||
|   const [formResponse, setFormResponse] = useState(""); |   const [formResponse, setFormResponse] = useState(""); | ||||||
|   const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">("stale"); |   const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">("stale"); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,10 +20,10 @@ import { compareDate } from "helpers/date"; | |||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { SmartList } from "components/SmartList"; | import { SmartList } from "components/SmartList"; | ||||||
| import { cIf } from "helpers/className"; | import { cIf } from "helpers/className"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { TextInput } from "components/Inputs/TextInput"; | import { TextInput } from "components/Inputs/TextInput"; | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -47,7 +47,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => { | |||||||
|   const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true); |   const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const hoverable = useDeviceSupportsHover(); |   const hoverable = useDeviceSupportsHover(); | ||||||
|   const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); |   const { isContentPanelAtLeast4xl } = useContainerQueries(); | ||||||
| 
 | 
 | ||||||
|   const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); |   const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -21,9 +21,9 @@ import { getOpenGraph } from "helpers/openGraph"; | |||||||
| import { compareDate } from "helpers/date"; | import { compareDate } from "helpers/date"; | ||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { cIf } from "helpers/className"; | import { cIf } from "helpers/className"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -46,7 +46,7 @@ interface Props extends AppLayoutRequired { | |||||||
| const Videos = ({ videos, ...otherProps }: Props): JSX.Element => { | const Videos = ({ videos, ...otherProps }: Props): JSX.Element => { | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const hoverable = useDeviceSupportsHover(); |   const hoverable = useDeviceSupportsHover(); | ||||||
|   const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); |   const { isContentPanelAtLeast4xl } = useContainerQueries(); | ||||||
| 
 | 
 | ||||||
|   const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true); |   const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -17,9 +17,9 @@ import { prettyDate, prettyShortenNumber } from "helpers/formatters"; | |||||||
| import { filterHasAttributes, isDefined } from "helpers/others"; | import { filterHasAttributes, isDefined } from "helpers/others"; | ||||||
| import { getVideoFile } from "helpers/videos"; | import { getVideoFile } from "helpers/videos"; | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
| @ -31,7 +31,7 @@ interface Props extends AppLayoutRequired { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const Video = ({ video, ...otherProps }: Props): JSX.Element => { | const Video = ({ video, ...otherProps }: Props): JSX.Element => { | ||||||
|   const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); |   const { isContentPanelAtLeast4xl } = useContainerQueries(); | ||||||
|   const { setSubPanelOpen } = useAppLayout(); |   const { setSubPanelOpen } = useAppLayout(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|  | |||||||
| @ -29,11 +29,11 @@ import { getOpenGraph } from "helpers/openGraph"; | |||||||
| import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; | import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; | ||||||
| import { getDescription } from "helpers/description"; | import { getDescription } from "helpers/description"; | ||||||
| import { TranslatedPreviewLine } from "components/PreviewLine"; | import { TranslatedPreviewLine } from "components/PreviewLine"; | ||||||
| import { useIs1ColumnLayout, useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { cIf } from "helpers/className"; | import { cIf } from "helpers/className"; | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { Ids } from "types/ids"; | import { Ids } from "types/ids"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
| @ -45,8 +45,7 @@ interface Props extends AppLayoutRequired { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const Content = ({ content, ...otherProps }: Props): JSX.Element => { | const Content = ({ content, ...otherProps }: Props): JSX.Element => { | ||||||
|   const isContentPanelAtLeast2xl = useIsContentPanelAtLeast("2xl"); |   const { isContentPanelAtLeast2xl, is1ColumnLayout } = useContainerQueries(); | ||||||
|   const is1ColumnLayout = useIs1ColumnLayout(); |  | ||||||
|   const { langui, languages } = useLocalData(); |   const { langui, languages } = useLocalData(); | ||||||
| 
 | 
 | ||||||
|   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ |   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ | ||||||
|  | |||||||
| @ -22,11 +22,11 @@ import { SelectiveNonNullable } from "types/SelectiveNonNullable"; | |||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { cJoin, cIf } from "helpers/className"; | import { cJoin, cIf } from "helpers/className"; | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { sendAnalytics } from "helpers/analytics"; | import { sendAnalytics } from "helpers/analytics"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -51,7 +51,7 @@ interface Props extends AppLayoutRequired { | |||||||
| const Contents = ({ contents, ...otherProps }: Props): JSX.Element => { | const Contents = ({ contents, ...otherProps }: Props): JSX.Element => { | ||||||
|   const hoverable = useDeviceSupportsHover(); |   const hoverable = useDeviceSupportsHover(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); |   const { isContentPanelAtLeast4xl } = useContainerQueries(); | ||||||
| 
 | 
 | ||||||
|   const [groupingMethod, setGroupingMethod] = useState<number>( |   const [groupingMethod, setGroupingMethod] = useState<number>( | ||||||
|     DEFAULT_FILTERS_STATE.groupingMethod |     DEFAULT_FILTERS_STATE.groupingMethod | ||||||
|  | |||||||
| @ -19,10 +19,10 @@ import { TranslatedProps } from "types/TranslatedProps"; | |||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { cJoin, cIf } from "helpers/className"; | import { cJoin, cIf } from "helpers/className"; | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
| @ -37,7 +37,7 @@ interface Props extends AppLayoutRequired { | |||||||
| 
 | 
 | ||||||
| const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Element => { | const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Element => { | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); |   const { isContentPanelAtLeast4xl } = useContainerQueries(); | ||||||
| 
 | 
 | ||||||
|   const subPanel = useMemo( |   const subPanel = useMemo( | ||||||
|     () => ( |     () => ( | ||||||
|  | |||||||
| @ -159,7 +159,7 @@ const Editor = (props: Props): JSX.Element => { | |||||||
|   const contentPanel = useMemo( |   const contentPanel = useMemo( | ||||||
|     () => ( |     () => ( | ||||||
|       <ContentPanel width={ContentPanelWidthSizes.Full}> |       <ContentPanel width={ContentPanelWidthSizes.Full}> | ||||||
|         <Popup onClose={() => setConverterOpened(false)} state={converterOpened}> |         <Popup isVisible={converterOpened} onCloseRequest={() => setConverterOpened(false)}> | ||||||
|           <div className="text-center"> |           <div className="text-center"> | ||||||
|             <h2 className="mt-4">Convert HTML to markdown</h2> |             <h2 className="mt-4">Convert HTML to markdown</h2> | ||||||
|             <p> |             <p> | ||||||
|  | |||||||
| @ -29,7 +29,7 @@ import { | |||||||
|   prettySlug, |   prettySlug, | ||||||
|   prettyURL, |   prettyURL, | ||||||
| } from "helpers/formatters"; | } from "helpers/formatters"; | ||||||
| import { getAssetURL, ImageQuality } from "helpers/img"; | import { ImageQuality } from "helpers/img"; | ||||||
| import { convertMmToInch } from "helpers/numbers"; | import { convertMmToInch } from "helpers/numbers"; | ||||||
| import { | import { | ||||||
|   filterDefined, |   filterDefined, | ||||||
| @ -38,7 +38,6 @@ import { | |||||||
|   isDefinedAndNotEmpty, |   isDefinedAndNotEmpty, | ||||||
|   sortRangedContent, |   sortRangedContent, | ||||||
| } from "helpers/others"; | } from "helpers/others"; | ||||||
| import { useLightBox } from "hooks/useLightBox"; |  | ||||||
| import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; | import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; | ||||||
| import { isUntangibleGroupItem } from "helpers/libraryItem"; | import { isUntangibleGroupItem } from "helpers/libraryItem"; | ||||||
| import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | import { useDeviceSupportsHover } from "hooks/useMediaQuery"; | ||||||
| @ -50,11 +49,12 @@ import { getOpenGraph } from "helpers/openGraph"; | |||||||
| import { getDescription } from "helpers/description"; | import { getDescription } from "helpers/description"; | ||||||
| import { useIntersectionList } from "hooks/useIntersectionList"; | import { useIntersectionList } from "hooks/useIntersectionList"; | ||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { useIsContentPanelNoMoreThan } from "hooks/useContainerQuery"; |  | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { Ids } from "types/ids"; | import { Ids } from "types/ids"; | ||||||
| import { useUserSettings } from "contexts/UserSettingsContext"; | import { useUserSettings } from "contexts/UserSettingsContext"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
|  | import { useLightBox } from "contexts/LightBoxContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -76,13 +76,13 @@ interface Props extends AppLayoutRequired { | |||||||
| const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | ||||||
|   const { currency } = useUserSettings(); |   const { currency } = useUserSettings(); | ||||||
|   const { langui, currencies } = useLocalData(); |   const { langui, currencies } = useLocalData(); | ||||||
|   const isContentPanelNoMoreThan3xl = useIsContentPanelNoMoreThan("3xl"); |   const { isContentPanelAtLeast3xl, isContentPanelAtLeastSm } = useContainerQueries(); | ||||||
|   const isContentPanelNoMoreThanSm = useIsContentPanelNoMoreThan("sm"); |  | ||||||
|   const hoverable = useDeviceSupportsHover(); |   const hoverable = useDeviceSupportsHover(); | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|   const [openLightBox, LightBox] = useLightBox(); |  | ||||||
|   const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false); |   const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false); | ||||||
| 
 | 
 | ||||||
|  |   const { showLightBox } = useLightBox(); | ||||||
|  | 
 | ||||||
|   useScrollTopOnChange(Ids.ContentPanel, [item]); |   useScrollTopOnChange(Ids.ContentPanel, [item]); | ||||||
|   const currentIntersection = useIntersectionList(intersectionIds); |   const currentIntersection = useIntersectionList(intersectionIds); | ||||||
| 
 | 
 | ||||||
| @ -158,8 +158,6 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|   const contentPanel = useMemo( |   const contentPanel = useMemo( | ||||||
|     () => ( |     () => ( | ||||||
|       <ContentPanel width={ContentPanelWidthSizes.Full}> |       <ContentPanel width={ContentPanelWidthSizes.Full}> | ||||||
|         <LightBox /> |  | ||||||
| 
 |  | ||||||
|         <ReturnButton |         <ReturnButton | ||||||
|           href="/library/" |           href="/library/" | ||||||
|           title={langui.library} |           title={langui.library} | ||||||
| @ -170,18 +168,16 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|           <div |           <div | ||||||
|             className={cJoin( |             className={cJoin( | ||||||
|               "relative h-[50vh] w-full cursor-pointer drop-shadow-shade-xl", |               "relative h-[50vh] w-full cursor-pointer drop-shadow-shade-xl", | ||||||
|               cIf(isContentPanelNoMoreThan3xl, "h-[60vh]", "mb-16") |               cIf(isContentPanelAtLeast3xl, "mb-16", "h-[60vh]") | ||||||
|             )} |             )}> | ||||||
|             onClick={() => { |  | ||||||
|               if (item.thumbnail?.data?.attributes) { |  | ||||||
|                 openLightBox([getAssetURL(item.thumbnail.data.attributes.url, ImageQuality.Large)]); |  | ||||||
|               } |  | ||||||
|             }}> |  | ||||||
|             {item.thumbnail?.data?.attributes ? ( |             {item.thumbnail?.data?.attributes ? ( | ||||||
|               <Img |               <Img | ||||||
|                 src={item.thumbnail.data.attributes} |                 src={item.thumbnail.data.attributes} | ||||||
|                 quality={ImageQuality.Large} |                 quality={ImageQuality.Large} | ||||||
|                 className="h-full w-full object-contain" |                 className="h-full w-full object-contain" | ||||||
|  |                 onClick={() => { | ||||||
|  |                   showLightBox([item.thumbnail?.data?.attributes]); | ||||||
|  |                 }} | ||||||
|               /> |               /> | ||||||
|             ) : ( |             ) : ( | ||||||
|               <div className="aspect-[21/29.7] w-full rounded-xl bg-light"></div> |               <div className="aspect-[21/29.7] w-full rounded-xl bg-light"></div> | ||||||
| @ -254,12 +250,12 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|                         className="relative aspect-square cursor-pointer |                         className="relative aspect-square cursor-pointer | ||||||
|                       transition-transform hover:scale-[1.02]" |                       transition-transform hover:scale-[1.02]" | ||||||
|                         onClick={() => { |                         onClick={() => { | ||||||
|                           const images: string[] = filterHasAttributes(item.gallery?.data, [ |                           showLightBox( | ||||||
|                             "attributes", |                             filterHasAttributes(item.gallery?.data, ["attributes"] as const).map( | ||||||
|                           ] as const).map((image) => |                               (image) => image.attributes | ||||||
|                             getAssetURL(image.attributes.url, ImageQuality.Large) |                             ), | ||||||
|  |                             index | ||||||
|                           ); |                           ); | ||||||
|                           openLightBox(images, index); |  | ||||||
|                         }}> |                         }}> | ||||||
|                         <Img |                         <Img | ||||||
|                           className="h-full w-full rounded-lg |                           className="h-full w-full rounded-lg | ||||||
| @ -280,7 +276,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|               <div |               <div | ||||||
|                 className={cJoin( |                 className={cJoin( | ||||||
|                   "grid place-items-center gap-y-8", |                   "grid place-items-center gap-y-8", | ||||||
|                   cIf(!isContentPanelNoMoreThan3xl, "grid-flow-col place-content-between") |                   cIf(isContentPanelAtLeast3xl, "grid-flow-col place-content-between") | ||||||
|                 )}> |                 )}> | ||||||
|                 {item.metadata?.[0] && ( |                 {item.metadata?.[0] && ( | ||||||
|                   <div className="grid place-content-start place-items-center"> |                   <div className="grid place-content-start place-items-center"> | ||||||
| @ -337,25 +333,25 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|                 <div |                 <div | ||||||
|                   className={cJoin( |                   className={cJoin( | ||||||
|                     "grid gap-4", |                     "grid gap-4", | ||||||
|                     cIf(isContentPanelNoMoreThan3xl, "place-items-center") |                     cIf(!isContentPanelAtLeast3xl, "place-items-center") | ||||||
|                   )}> |                   )}> | ||||||
|                   <h3 className="text-xl">{langui.size}</h3> |                   <h3 className="text-xl">{langui.size}</h3> | ||||||
|                   <div |                   <div | ||||||
|                     className={cJoin( |                     className={cJoin( | ||||||
|                       "grid w-full", |                       "grid w-full", | ||||||
|                       cIf( |                       cIf( | ||||||
|                         isContentPanelNoMoreThanSm, |                         isContentPanelAtLeastSm, | ||||||
|                         "grid-flow-row place-content-center gap-8", |                         "grid-flow-col place-content-between", | ||||||
|                         "grid-flow-col place-content-between" |                         "grid-flow-row place-content-center gap-8" | ||||||
|                       ) |                       ) | ||||||
|                     )}> |                     )}> | ||||||
|                     <div |                     <div | ||||||
|                       className={cJoin( |                       className={cJoin( | ||||||
|                         "grid gap-x-4", |                         "grid gap-x-4", | ||||||
|                         cIf( |                         cIf( | ||||||
|                           isContentPanelNoMoreThan3xl, |                           isContentPanelAtLeast3xl, | ||||||
|                           "place-items-center", |                           "grid-flow-col place-items-start", | ||||||
|                           "grid-flow-col place-items-start" |                           "place-items-center" | ||||||
|                         ) |                         ) | ||||||
|                       )}> |                       )}> | ||||||
|                       <p className="font-bold">{langui.width}:</p> |                       <p className="font-bold">{langui.width}:</p> | ||||||
| @ -368,9 +364,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|                       className={cJoin( |                       className={cJoin( | ||||||
|                         "grid gap-x-4", |                         "grid gap-x-4", | ||||||
|                         cIf( |                         cIf( | ||||||
|                           isContentPanelNoMoreThan3xl, |                           isContentPanelAtLeast3xl, | ||||||
|                           "place-items-center", |                           "grid-flow-col place-items-start", | ||||||
|                           "grid-flow-col place-items-start" |                           "place-items-center" | ||||||
|                         ) |                         ) | ||||||
|                       )}> |                       )}> | ||||||
|                       <p className="font-bold">{langui.height}:</p> |                       <p className="font-bold">{langui.height}:</p> | ||||||
| @ -384,9 +380,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|                         className={cJoin( |                         className={cJoin( | ||||||
|                           "grid gap-x-4", |                           "grid gap-x-4", | ||||||
|                           cIf( |                           cIf( | ||||||
|                             isContentPanelNoMoreThan3xl, |                             isContentPanelAtLeast3xl, | ||||||
|                             "place-items-center", |                             "grid-flow-col place-items-start", | ||||||
|                             "grid-flow-col place-items-start" |                             "place-items-center" | ||||||
|                           ) |                           ) | ||||||
|                         )}> |                         )}> | ||||||
|                         <p className="font-bold">{langui.thickness}:</p> |                         <p className="font-bold">{langui.thickness}:</p> | ||||||
| @ -405,7 +401,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|                   <div |                   <div | ||||||
|                     className={cJoin( |                     className={cJoin( | ||||||
|                       "grid gap-4", |                       "grid gap-4", | ||||||
|                       cIf(isContentPanelNoMoreThan3xl, "place-items-center") |                       cIf(!isContentPanelAtLeast3xl, "place-items-center") | ||||||
|                     )}> |                     )}> | ||||||
|                     <h3 className="text-xl">{langui.type_information}</h3> |                     <h3 className="text-xl">{langui.type_information}</h3> | ||||||
|                     <div className="flex flex-wrap place-content-between gap-x-8"> |                     <div className="flex flex-wrap place-content-between gap-x-8"> | ||||||
| @ -555,7 +551,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|                         isDefined(rangedContent.attributes.scan_set) && |                         isDefined(rangedContent.attributes.scan_set) && | ||||||
|                         rangedContent.attributes.scan_set.length > 0 |                         rangedContent.attributes.scan_set.length > 0 | ||||||
|                       } |                       } | ||||||
|                       condensed={isContentPanelNoMoreThan3xl} |                       condensed={!isContentPanelAtLeast3xl} | ||||||
|                     /> |                     /> | ||||||
|                   ) |                   ) | ||||||
|                 )} |                 )} | ||||||
| @ -566,9 +562,8 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|       </ContentPanel> |       </ContentPanel> | ||||||
|     ), |     ), | ||||||
|     [ |     [ | ||||||
|       LightBox, |  | ||||||
|       langui, |       langui, | ||||||
|       isContentPanelNoMoreThan3xl, |       isContentPanelAtLeast3xl, | ||||||
|       item.thumbnail?.data?.attributes, |       item.thumbnail?.data?.attributes, | ||||||
|       item.subitem_of?.data, |       item.subitem_of?.data, | ||||||
|       item.title, |       item.title, | ||||||
| @ -588,13 +583,13 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { | |||||||
|       router.locale, |       router.locale, | ||||||
|       currencies, |       currencies, | ||||||
|       currency, |       currency, | ||||||
|       isContentPanelNoMoreThanSm, |       isContentPanelAtLeastSm, | ||||||
|       isVariantSet, |       isVariantSet, | ||||||
|       hoverable, |       hoverable, | ||||||
|       toggleKeepInfoVisible, |       toggleKeepInfoVisible, | ||||||
|       keepInfoVisible, |       keepInfoVisible, | ||||||
|       displayOpenScans, |       displayOpenScans, | ||||||
|       openLightBox, |       showLightBox, | ||||||
|     ] |     ] | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||||
| import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; | import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; | ||||||
| import Hotkeys from "react-hot-keys"; | import { useHotkeys } from "react-hotkeys-hook"; | ||||||
| import Slider from "rc-slider"; | import Slider from "rc-slider"; | ||||||
| import { useRouter } from "next/router"; | import { useRouter } from "next/router"; | ||||||
| import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; | import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; | ||||||
| @ -23,7 +23,6 @@ import { getLangui } from "graphql/fetchLocalData"; | |||||||
| import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel"; | import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel"; | ||||||
| import { Img } from "components/Img"; | import { Img } from "components/Img"; | ||||||
| import { getAssetFilename, ImageQuality } from "helpers/img"; | import { getAssetFilename, ImageQuality } from "helpers/img"; | ||||||
| import { useIs1ColumnLayout, useIsContentPanelNoMoreThan } from "hooks/useContainerQuery"; |  | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { clamp, isInteger } from "helpers/numbers"; | import { clamp, isInteger } from "helpers/numbers"; | ||||||
| import { SubPanel } from "components/Panels/SubPanel"; | import { SubPanel } from "components/Panels/SubPanel"; | ||||||
| @ -45,6 +44,7 @@ import { useFullscreen } from "hooks/useFullscreen"; | |||||||
| import { useUserSettings } from "contexts/UserSettingsContext"; | import { useUserSettings } from "contexts/UserSettingsContext"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
| import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings"; | import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| const CUSTOM_DARK_DROPSHADOW = ` | const CUSTOM_DARK_DROPSHADOW = ` | ||||||
| drop-shadow(0 0    0.5em rgb(var(--theme-color-shade) / 30%)) | drop-shadow(0 0    0.5em rgb(var(--theme-color-shade) / 30%)) | ||||||
| @ -90,7 +90,7 @@ const LibrarySlug = ({ | |||||||
|   item, |   item, | ||||||
|   ...otherProps |   ...otherProps | ||||||
| }: Props): JSX.Element => { | }: Props): JSX.Element => { | ||||||
|   const is1ColumnLayout = useIs1ColumnLayout(); |   const { is1ColumnLayout } = useContainerQueries(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const { darkMode } = useUserSettings(); |   const { darkMode } = useUserSettings(); | ||||||
|   const { |   const { | ||||||
| @ -114,7 +114,7 @@ const LibrarySlug = ({ | |||||||
|   ); |   ); | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
| 
 | 
 | ||||||
|   const { isFullscreen, toggleFullscreen } = useFullscreen(Ids.ContentPanel); |   const { isFullscreen, toggleFullscreen, requestFullscreen } = useFullscreen(Ids.ContentPanel); | ||||||
| 
 | 
 | ||||||
|   const effectiveDisplayMode = useMemo( |   const effectiveDisplayMode = useMemo( | ||||||
|     () => |     () => | ||||||
| @ -173,6 +173,19 @@ const LibrarySlug = ({ | |||||||
|     [changeCurrentPageIndex, effectiveDisplayMode, pageOrder] |     [changeCurrentPageIndex, effectiveDisplayMode, pageOrder] | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|  |   useHotkeys("left", () => handlePageNavigation("left"), { enabled: !isGalleryMode }, [ | ||||||
|  |     handlePageNavigation, | ||||||
|  |   ]); | ||||||
|  | 
 | ||||||
|  |   useHotkeys("up", () => setIsGalleryMode(true), { enabled: !isGalleryMode }, [setIsGalleryMode]); | ||||||
|  |   useHotkeys("down", () => setIsGalleryMode(false), { enabled: isGalleryMode }, [setIsGalleryMode]); | ||||||
|  | 
 | ||||||
|  |   useHotkeys("f", () => requestFullscreen(), { enabled: !isFullscreen }, [requestFullscreen]); | ||||||
|  | 
 | ||||||
|  |   useHotkeys("right", () => handlePageNavigation("right"), { enabled: !isGalleryMode }, [ | ||||||
|  |     handlePageNavigation, | ||||||
|  |   ]); | ||||||
|  | 
 | ||||||
|   const firstPage = useMemo( |   const firstPage = useMemo( | ||||||
|     () => |     () => | ||||||
|       pages[ |       pages[ | ||||||
| @ -426,121 +439,114 @@ const LibrarySlug = ({ | |||||||
|     () => ( |     () => ( | ||||||
|       <ContentPanel width={ContentPanelWidthSizes.Full} className="grid place-content-center !p-0"> |       <ContentPanel width={ContentPanelWidthSizes.Full} className="grid place-content-center !p-0"> | ||||||
|         <div className={cJoin("mb-12 grid", cIf(is1ColumnLayout, "!p-0", "!p-8"))}> |         <div className={cJoin("mb-12 grid", cIf(is1ColumnLayout, "!p-0", "!p-8"))}> | ||||||
|           <Hotkeys |           <TransformWrapper | ||||||
|             keyName="left,right" |             onZoom={(zoom) => setCurrentZoom(zoom.state.scale)} | ||||||
|             allowRepeat |             panning={{ disabled: currentZoom <= 1, velocityDisabled: false }} | ||||||
|             onKeyDown={(keyName) => { |             doubleClick={{ disabled: true, mode: "reset" }} | ||||||
|               handlePageNavigation(keyName as "left" | "right"); |             zoomAnimation={{ size: 0.1 }} | ||||||
|             }}> |             velocityAnimation={{ animationTime: 0, equalToMove: true }}> | ||||||
|             <TransformWrapper |             <TransformComponent | ||||||
|               onZoom={(zoom) => setCurrentZoom(zoom.state.scale)} |               wrapperStyle={{ overflow: "visible", placeSelf: "center" }} | ||||||
|               panning={{ disabled: currentZoom <= 1, velocityDisabled: false }} |               contentStyle={{ | ||||||
|               doubleClick={{ disabled: true, mode: "reset" }} |                 height: "100%", | ||||||
|               zoomAnimation={{ size: 0.1 }} |                 gridAutoFlow: "column", | ||||||
|               velocityAnimation={{ animationTime: 0, equalToMove: true }}> |                 display: "grid", | ||||||
|               <TransformComponent |                 placeContent: "center", | ||||||
|                 wrapperStyle={{ overflow: "visible", placeSelf: "center" }} |                 filter: filterSettings.dropShadow | ||||||
|                 contentStyle={{ |                   ? darkMode | ||||||
|                   height: "100%", |                     ? CUSTOM_DARK_DROPSHADOW | ||||||
|                   gridAutoFlow: "column", |                     : CUSTOM_LIGHT_DROPSHADOW | ||||||
|                   display: "grid", |                   : undefined, | ||||||
|                   placeContent: "center", |               }}> | ||||||
|                   filter: filterSettings.dropShadow |               {effectiveDisplayMode === "single" ? ( | ||||||
|                     ? darkMode |                 <div | ||||||
|                       ? CUSTOM_DARK_DROPSHADOW |                   className={cJoin( | ||||||
|                       : CUSTOM_LIGHT_DROPSHADOW |                     "relative grid grid-flow-col", | ||||||
|                     : undefined, |                     cIf(currentZoom <= 1, "cursor-pointer", "cursor-move") | ||||||
|                 }}> |                   )}> | ||||||
|                 {effectiveDisplayMode === "single" ? ( |                   <Img | ||||||
|  |                     style={{ maxHeight: pageHeight, width: "auto" }} | ||||||
|  |                     src={firstPage} | ||||||
|  |                     quality={pageQuality} | ||||||
|  |                   /> | ||||||
|  |                   <PageFilters page="single" bookType={bookType} options={filterSettings} /> | ||||||
|  |                   <div | ||||||
|  |                     className="absolute left-0 top-0 bottom-0 w-1/2" | ||||||
|  |                     onClick={() => currentZoom <= 1 && handlePageNavigation("left")} | ||||||
|  |                   /> | ||||||
|  |                   <div | ||||||
|  |                     className="absolute right-0 top-0 bottom-0 w-1/2" | ||||||
|  |                     onClick={() => currentZoom <= 1 && handlePageNavigation("right")} | ||||||
|  |                   /> | ||||||
|  |                 </div> | ||||||
|  |               ) : ( | ||||||
|  |                 <> | ||||||
|                   <div |                   <div | ||||||
|                     className={cJoin( |                     className={cJoin( | ||||||
|                       "relative grid grid-flow-col", |                       "relative grid grid-flow-col", | ||||||
|                       cIf(currentZoom <= 1, "cursor-pointer", "cursor-move") |                       cIf(currentZoom <= 1, "cursor-pointer", "cursor-move") | ||||||
|                     )}> |                     )} | ||||||
|  |                     onClick={() => currentZoom <= 1 && handlePageNavigation("left")} | ||||||
|  |                     style={{ | ||||||
|  |                       clipPath: leftSideClipPath, | ||||||
|  |                     }}> | ||||||
|  |                     {isSidePagesEnabled && ( | ||||||
|  |                       <div | ||||||
|  |                         style={{ | ||||||
|  |                           width: leftSidePagesWidth, | ||||||
|  |                           backgroundImage: `url(/reader/sidepages-${bookType}.webp)`, | ||||||
|  |                           backgroundSize: `${ | ||||||
|  |                             (SIDEPAGES_PAGE_COUNT_ON_TEXTURE / leftSidePagesCount) * 100 | ||||||
|  |                           }% 100%`,
 | ||||||
|  |                         }} | ||||||
|  |                       /> | ||||||
|  |                     )} | ||||||
|  | 
 | ||||||
|                     <Img |                     <Img | ||||||
|                       style={{ maxHeight: pageHeight, width: "auto" }} |                       style={{ maxHeight: pageHeight, width: "auto" }} | ||||||
|                       src={firstPage} |                       src={pageOrder === PageOrder.LeftToRight ? firstPage : secondPage} | ||||||
|                       quality={pageQuality} |                       quality={pageQuality} | ||||||
|                     /> |                     /> | ||||||
|                     <PageFilters page="single" bookType={bookType} options={filterSettings} /> |                     <PageFilters page="left" bookType={bookType} options={filterSettings} /> | ||||||
|                     <div |  | ||||||
|                       className="absolute left-0 top-0 bottom-0 w-1/2" |  | ||||||
|                       onClick={() => currentZoom <= 1 && handlePageNavigation("left")} |  | ||||||
|                     /> |  | ||||||
|                     <div |  | ||||||
|                       className="absolute right-0 top-0 bottom-0 w-1/2" |  | ||||||
|                       onClick={() => currentZoom <= 1 && handlePageNavigation("right")} |  | ||||||
|                     /> |  | ||||||
|                   </div> |                   </div> | ||||||
|                 ) : ( |                   <div | ||||||
|                   <> |                     className={cJoin( | ||||||
|                     <div |                       "relative grid grid-flow-col", | ||||||
|                       className={cJoin( |                       cIf(currentZoom <= 1, "cursor-pointer", "cursor-move") | ||||||
|                         "relative grid grid-flow-col", |                     )} | ||||||
|                         cIf(currentZoom <= 1, "cursor-pointer", "cursor-move") |                     onClick={() => currentZoom <= 1 && handlePageNavigation("right")} | ||||||
|  |                     style={{ | ||||||
|  |                       clipPath: rightSideClipPath, | ||||||
|  |                     }}> | ||||||
|  |                     <Img | ||||||
|  |                       style={{ maxHeight: pageHeight, width: "auto" }} | ||||||
|  |                       className={cIf( | ||||||
|  |                         is1ColumnLayout, | ||||||
|  |                         `max-h-[calc(100vh-5rem)]`, | ||||||
|  |                         "max-h-[calc(100vh-4rem)]" | ||||||
|                       )} |                       )} | ||||||
|                       onClick={() => currentZoom <= 1 && handlePageNavigation("left")} |                       src={pageOrder === PageOrder.LeftToRight ? secondPage : firstPage} | ||||||
|                       style={{ |                       quality={pageQuality} | ||||||
|                         clipPath: leftSideClipPath, |                     /> | ||||||
|                       }}> |                     {isSidePagesEnabled && ( | ||||||
|                       {isSidePagesEnabled && ( |                       <div | ||||||
|                         <div |                         style={{ | ||||||
|                           style={{ |                           width: rightSidePagesWidth, | ||||||
|                             width: leftSidePagesWidth, |                           backgroundImage: `url(/reader/sidepages-${bookType}.webp)`, | ||||||
|                             backgroundImage: `url(/reader/sidepages-${bookType}.webp)`, |                           backgroundPositionX: "right", | ||||||
|                             backgroundSize: `${ |                           backgroundSize: `${ | ||||||
|                               (SIDEPAGES_PAGE_COUNT_ON_TEXTURE / leftSidePagesCount) * 100 |                             (SIDEPAGES_PAGE_COUNT_ON_TEXTURE / rightSidePagesCount) * 100 | ||||||
|                             }% 100%`,
 |                           }% 100%`,
 | ||||||
|                           }} |                         }} | ||||||
|                         /> |  | ||||||
|                       )} |  | ||||||
| 
 |  | ||||||
|                       <Img |  | ||||||
|                         style={{ maxHeight: pageHeight, width: "auto" }} |  | ||||||
|                         src={pageOrder === PageOrder.LeftToRight ? firstPage : secondPage} |  | ||||||
|                         quality={pageQuality} |  | ||||||
|                       /> |                       /> | ||||||
|                       <PageFilters page="left" bookType={bookType} options={filterSettings} /> |                     )} | ||||||
|                     </div> |  | ||||||
|                     <div |  | ||||||
|                       className={cJoin( |  | ||||||
|                         "relative grid grid-flow-col", |  | ||||||
|                         cIf(currentZoom <= 1, "cursor-pointer", "cursor-move") |  | ||||||
|                       )} |  | ||||||
|                       onClick={() => currentZoom <= 1 && handlePageNavigation("right")} |  | ||||||
|                       style={{ |  | ||||||
|                         clipPath: rightSideClipPath, |  | ||||||
|                       }}> |  | ||||||
|                       <Img |  | ||||||
|                         style={{ maxHeight: pageHeight, width: "auto" }} |  | ||||||
|                         className={cIf( |  | ||||||
|                           is1ColumnLayout, |  | ||||||
|                           `max-h-[calc(100vh-5rem)]`, |  | ||||||
|                           "max-h-[calc(100vh-4rem)]" |  | ||||||
|                         )} |  | ||||||
|                         src={pageOrder === PageOrder.LeftToRight ? secondPage : firstPage} |  | ||||||
|                         quality={pageQuality} |  | ||||||
|                       /> |  | ||||||
|                       {isSidePagesEnabled && ( |  | ||||||
|                         <div |  | ||||||
|                           style={{ |  | ||||||
|                             width: rightSidePagesWidth, |  | ||||||
|                             backgroundImage: `url(/reader/sidepages-${bookType}.webp)`, |  | ||||||
|                             backgroundPositionX: "right", |  | ||||||
|                             backgroundSize: `${ |  | ||||||
|                               (SIDEPAGES_PAGE_COUNT_ON_TEXTURE / rightSidePagesCount) * 100 |  | ||||||
|                             }% 100%`,
 |  | ||||||
|                           }} |  | ||||||
|                         /> |  | ||||||
|                       )} |  | ||||||
| 
 | 
 | ||||||
|                       <PageFilters page="right" bookType={bookType} options={filterSettings} /> |                     <PageFilters page="right" bookType={bookType} options={filterSettings} /> | ||||||
|                     </div> |                   </div> | ||||||
|                   </> |                 </> | ||||||
|                 )} |               )} | ||||||
|               </TransformComponent> |             </TransformComponent> | ||||||
|             </TransformWrapper> |           </TransformWrapper> | ||||||
|           </Hotkeys> |  | ||||||
|         </div> |         </div> | ||||||
|         <div |         <div | ||||||
|           className={cJoin( |           className={cJoin( | ||||||
| @ -883,7 +889,7 @@ interface ScanSetProps { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps): JSX.Element => { | const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps): JSX.Element => { | ||||||
|   const is1ColumnLayout = useIsContentPanelNoMoreThan("2xl"); |   const { is1ColumnLayout } = useContainerQueries(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ |   const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ | ||||||
|     items: scanSet, |     items: scanSet, | ||||||
| @ -1028,8 +1034,8 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps): | |||||||
| 
 | 
 | ||||||
|           <div |           <div | ||||||
|             className={cJoin( |             className={cJoin( | ||||||
|               `grid items-end gap-8 border-b-[3px] border-dotted pb-12
 |               `grid items-end gap-8 border-b-2 border-dotted pb-12
 | ||||||
|             last-of-type:border-0`,
 |                last-of-type:border-0`,
 | ||||||
|               cIf( |               cIf( | ||||||
|                 is1ColumnLayout, |                 is1ColumnLayout, | ||||||
|                 "grid-cols-2 gap-[4vmin]", |                 "grid-cols-2 gap-[4vmin]", | ||||||
|  | |||||||
| @ -28,12 +28,12 @@ import { SelectiveNonNullable } from "types/SelectiveNonNullable"; | |||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { compareDate } from "helpers/date"; | import { compareDate } from "helpers/date"; | ||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { sendAnalytics } from "helpers/analytics"; | import { sendAnalytics } from "helpers/analytics"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
| import { useLibraryItemUserStatus } from "hooks/useLibraryItemUserStatus"; | import { useLibraryItemUserStatus } from "hooks/useLibraryItemUserStatus"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -64,7 +64,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => { | |||||||
|   const hoverable = useDeviceSupportsHover(); |   const hoverable = useDeviceSupportsHover(); | ||||||
|   const { langui, currencies } = useLocalData(); |   const { langui, currencies } = useLocalData(); | ||||||
|   const { libraryItemUserStatus } = useLibraryItemUserStatus(); |   const { libraryItemUserStatus } = useLibraryItemUserStatus(); | ||||||
|   const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); |   const { isContentPanelAtLeast4xl } = useContainerQueries(); | ||||||
| 
 | 
 | ||||||
|   const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); |   const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -21,12 +21,12 @@ import { compareDate } from "helpers/date"; | |||||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||||
| import { HorizontalLine } from "components/HorizontalLine"; | import { HorizontalLine } from "components/HorizontalLine"; | ||||||
| import { cIf } from "helpers/className"; | import { cIf } from "helpers/className"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { sendAnalytics } from "helpers/analytics"; | import { sendAnalytics } from "helpers/analytics"; | ||||||
| import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | ||||||
| import { Terminal } from "components/Cli/Terminal"; | import { Terminal } from "components/Cli/Terminal"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -48,7 +48,7 @@ interface Props extends AppLayoutRequired { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const News = ({ posts, ...otherProps }: Props): JSX.Element => { | const News = ({ posts, ...otherProps }: Props): JSX.Element => { | ||||||
|   const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); |   const { isContentPanelAtLeast4xl } = useContainerQueries(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const hoverable = useDeviceSupportsHover(); |   const hoverable = useDeviceSupportsHover(); | ||||||
|   const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); |   const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); | ||||||
|  | |||||||
| @ -14,18 +14,18 @@ import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/ot | |||||||
| import { WikiPageWithTranslations } from "types/types"; | import { WikiPageWithTranslations } from "types/types"; | ||||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||||
| import { prettySlug, sJoin } from "helpers/formatters"; | import { prettySlug, sJoin } from "helpers/formatters"; | ||||||
| import { useLightBox } from "hooks/useLightBox"; | import { ImageQuality } from "helpers/img"; | ||||||
| import { getAssetURL, ImageQuality } from "helpers/img"; |  | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; | import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; | ||||||
| import { getDescription } from "helpers/description"; | import { getDescription } from "helpers/description"; | ||||||
| import { cIf, cJoin } from "helpers/className"; | import { cIf, cJoin } from "helpers/className"; | ||||||
| import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; |  | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { Terminal } from "components/Cli/Terminal"; | import { Terminal } from "components/Cli/Terminal"; | ||||||
| import { prettyTerminalBoxedTitle, prettyTerminalUnderlinedTitle } from "helpers/terminal"; | import { prettyTerminalBoxedTitle, prettyTerminalUnderlinedTitle } from "helpers/terminal"; | ||||||
| import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
|  | import { useLightBox } from "contexts/LightBoxContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                           ╭────────╮ |  *                                           ╭────────╮ | ||||||
| @ -40,6 +40,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => { | |||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|   const isTerminalMode = useIsTerminalMode(); |   const isTerminalMode = useIsTerminalMode(); | ||||||
|  |   const { showLightBox } = useLightBox(); | ||||||
|   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ |   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ | ||||||
|     items: page.translations, |     items: page.translations, | ||||||
|     languageExtractor: useCallback( |     languageExtractor: useCallback( | ||||||
| @ -48,9 +49,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => { | |||||||
|       [] |       [] | ||||||
|     ), |     ), | ||||||
|   }); |   }); | ||||||
| 
 |   const { is3ColumnsLayout } = useContainerQueries(); | ||||||
|   const [openLightBox, LightBox] = useLightBox(); |  | ||||||
|   const is3ColumnsLayout = useIs3ColumnsLayout(); |  | ||||||
| 
 | 
 | ||||||
|   const subPanel = useMemo( |   const subPanel = useMemo( | ||||||
|     () => ( |     () => ( | ||||||
| @ -64,8 +63,6 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => { | |||||||
|   const contentPanel = useMemo( |   const contentPanel = useMemo( | ||||||
|     () => ( |     () => ( | ||||||
|       <ContentPanel width={ContentPanelWidthSizes.Large}> |       <ContentPanel width={ContentPanelWidthSizes.Large}> | ||||||
|         <LightBox /> |  | ||||||
| 
 |  | ||||||
|         <ReturnButton |         <ReturnButton | ||||||
|           href={`/wiki`} |           href={`/wiki`} | ||||||
|           title={langui.wiki} |           title={langui.wiki} | ||||||
| @ -98,10 +95,8 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => { | |||||||
|                     quality={ImageQuality.Medium} |                     quality={ImageQuality.Medium} | ||||||
|                     className="w-full cursor-pointer" |                     className="w-full cursor-pointer" | ||||||
|                     onClick={() => { |                     onClick={() => { | ||||||
|                       if (page.thumbnail?.data?.attributes?.url) { |                       if (page.thumbnail?.data?.attributes) { | ||||||
|                         openLightBox([ |                         showLightBox([page.thumbnail.data.attributes]); | ||||||
|                           getAssetURL(page.thumbnail.data.attributes.url, ImageQuality.Large), |  | ||||||
|                         ]); |  | ||||||
|                       } |                       } | ||||||
|                     }} |                     }} | ||||||
|                   /> |                   /> | ||||||
| @ -183,16 +178,18 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => { | |||||||
|     ), |     ), | ||||||
|     [ |     [ | ||||||
|       LanguageSwitcher, |       LanguageSwitcher, | ||||||
|       LightBox, |  | ||||||
|       is3ColumnsLayout, |       is3ColumnsLayout, | ||||||
|       languageSwitcherProps, |       languageSwitcherProps, | ||||||
|       langui, |       langui.categories, | ||||||
|       openLightBox, |       langui.summary, | ||||||
|  |       langui.tags, | ||||||
|  |       langui.wiki, | ||||||
|       page.categories?.data, |       page.categories?.data, | ||||||
|       page.definitions, |       page.definitions, | ||||||
|       page.tags?.data, |       page.tags?.data, | ||||||
|       page.thumbnail?.data?.attributes, |       page.thumbnail?.data?.attributes, | ||||||
|       selectedTranslation, |       selectedTranslation, | ||||||
|  |       showLightBox, | ||||||
|     ] |     ] | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -22,13 +22,13 @@ import { SelectiveNonNullable } from "types/SelectiveNonNullable"; | |||||||
| import { prettySlug } from "helpers/formatters"; | import { prettySlug } from "helpers/formatters"; | ||||||
| import { getOpenGraph } from "helpers/openGraph"; | import { getOpenGraph } from "helpers/openGraph"; | ||||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||||
| import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; |  | ||||||
| import { cIf } from "helpers/className"; | import { cIf } from "helpers/className"; | ||||||
| import { getLangui } from "graphql/fetchLocalData"; | import { getLangui } from "graphql/fetchLocalData"; | ||||||
| import { sendAnalytics } from "helpers/analytics"; | import { sendAnalytics } from "helpers/analytics"; | ||||||
| import { Terminal } from "components/Cli/Terminal"; | import { Terminal } from "components/Cli/Terminal"; | ||||||
| import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | ||||||
| import { useLocalData } from "contexts/LocalDataContext"; | import { useLocalData } from "contexts/LocalDataContext"; | ||||||
|  | import { useContainerQueries } from "contexts/ContainerQueriesContext"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  *                                         ╭─────────────╮ |  *                                         ╭─────────────╮ | ||||||
| @ -53,7 +53,7 @@ interface Props extends AppLayoutRequired { | |||||||
| const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => { | const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => { | ||||||
|   const hoverable = useDeviceSupportsHover(); |   const hoverable = useDeviceSupportsHover(); | ||||||
|   const { langui } = useLocalData(); |   const { langui } = useLocalData(); | ||||||
|   const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); |   const { isContentPanelAtLeast4xl } = useContainerQueries(); | ||||||
|   const isTerminalMode = useIsTerminalMode(); |   const isTerminalMode = useIsTerminalMode(); | ||||||
| 
 | 
 | ||||||
|   const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); |   const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								src/styles/animations.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,32 @@ | |||||||
|  | .animation-carret { | ||||||
|  |   animation-name: blink; | ||||||
|  |   animation-duration: 1s; | ||||||
|  |   animation-timing-function: step-end; | ||||||
|  |   animation-iteration-count: infinite; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .animate-zoom-in { | ||||||
|  |   animation-name: zoom-in; | ||||||
|  |   animation-duration: 2s; | ||||||
|  |   animation-timing-function: ease-in-out; | ||||||
|  |   animation-iteration-count: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @keyframes zoom-in { | ||||||
|  |   0% { | ||||||
|  |     transform: scale(0); | ||||||
|  |   } | ||||||
|  |   100% { | ||||||
|  |     transform: scale(1); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @keyframes blink { | ||||||
|  |   from, | ||||||
|  |   to { | ||||||
|  |     border-bottom-style: solid; | ||||||
|  |   } | ||||||
|  |   50% { | ||||||
|  |     border-bottom-style: none; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								src/styles/custom-classes.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | |||||||
|  | .texture-paper-dots { | ||||||
|  |   @apply bg-[length:10cm] bg-local [background-image:var(--theme-texture-dots)] | ||||||
|  |   [background-blend-mode:var(--theme-texture-dots-blend)]; | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								src/styles/debug.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | |||||||
|  | .false { | ||||||
|  |   @apply border-2 border-[red] text-[red] outline-dotted outline-2 outline-[red]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .undefined { | ||||||
|  |   @apply border-2 border-[green] text-[green] outline-dotted outline-2 outline-[green]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .null { | ||||||
|  |   @apply border-2 border-[blue] text-[blue] outline-dotted outline-2 outline-[blue]; | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								src/styles/formatted.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,89 @@ | |||||||
|  | .formatted h1, | ||||||
|  | .formatted h2, | ||||||
|  | .formatted h3, | ||||||
|  | .formatted h4, | ||||||
|  | .formatted h5, | ||||||
|  | .formatted h6 { | ||||||
|  |   @apply flex justify-center gap-3 text-center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted h1 { | ||||||
|  |   @apply my-16 text-4xl; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted h1 + h2 { | ||||||
|  |   @apply -mt-10; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted h2 { | ||||||
|  |   @apply my-12 text-3xl; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted h2 + h3 { | ||||||
|  |   @apply -mt-8; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted h3 { | ||||||
|  |   @apply my-8 text-2xl; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted h3 + h4 { | ||||||
|  |   @apply -mt-6; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted h4 { | ||||||
|  |   @apply my-6 text-xl; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted h5 { | ||||||
|  |   @apply my-4 text-lg; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted p, | ||||||
|  | .formatted strong { | ||||||
|  |   @apply my-2 text-justify; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted strong { | ||||||
|  |   @apply font-black; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted footer { | ||||||
|  |   @apply border-t-2 border-dotted pt-6; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted footer > div { | ||||||
|  |   @apply my-2 rounded-xl px-6 py-4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted footer > div:target { | ||||||
|  |   @apply bg-mid shadow-inner-sm shadow-shade; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted li::marker { | ||||||
|  |   @apply text-dark; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted code { | ||||||
|  |   @apply font-mono; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted pre > code { | ||||||
|  |   @apply block whitespace-pre-line; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted ul { | ||||||
|  |   @apply list-disc pl-4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted ol { | ||||||
|  |   @apply list-decimal pl-4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted blockquote { | ||||||
|  |   @apply my-8 rounded-lg border-2 border-mid p-5 text-center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .formatted blockquote cite { | ||||||
|  |   @apply block text-dark; | ||||||
|  | } | ||||||
							
								
								
									
										80
									
								
								src/styles/others.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,80 @@ | |||||||
|  | @tailwind base; | ||||||
|  | @tailwind components; | ||||||
|  | @tailwind utilities; | ||||||
|  | 
 | ||||||
|  | #__next { | ||||||
|  |   @apply bg-light font-body font-medium text-black; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | * { | ||||||
|  |   @apply box-border scroll-m-[40vh] scroll-smooth ![-webkit-tap-highlight-color:transparent]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1, | ||||||
|  | h2, | ||||||
|  | h3, | ||||||
|  | h4, | ||||||
|  | h5, | ||||||
|  | h6 { | ||||||
|  |   @apply font-headers font-black; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a { | ||||||
|  |   @apply cursor-pointer underline decoration-dark decoration-dotted | ||||||
|  |   underline-offset-2 transition-colors hover:text-dark; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::selection { | ||||||
|  |   @apply bg-dark text-light; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | mark { | ||||||
|  |   @apply bg-mid px-2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* SCROLLBARS STYLING */ | ||||||
|  | 
 | ||||||
|  | * { | ||||||
|  |   @apply [scrollbar-color:theme(colors.dark/1)_transparent] [scrollbar-width:thin]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::-webkit-scrollbar { | ||||||
|  |   @apply w-3; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::-webkit-scrollbar-track { | ||||||
|  |   @apply bg-light; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | *::-webkit-scrollbar-thumb { | ||||||
|  |   @apply rounded-full border-2 border-solid border-light bg-dark; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* INPUT */ | ||||||
|  | 
 | ||||||
|  | input, | ||||||
|  | textarea { | ||||||
|  |   @apply rounded-full bg-light p-2 text-center text-dark outline outline-1 -outline-offset-1 | ||||||
|  |   outline-mid transition-all placeholder:text-dark placeholder:opacity-60 hover:bg-mid | ||||||
|  |   hover:outline-transparent; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input::placeholder { | ||||||
|  |   @apply text-dark opacity-60; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input:focus-visible, | ||||||
|  | textarea:focus-within { | ||||||
|  |   @apply bg-mid shadow-inner-sm shadow-shade outline-none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | textarea { | ||||||
|  |   @apply rounded-2xl p-6 text-left; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type="submit"] { | ||||||
|  |   @apply grid cursor-pointer place-content-center place-items-center rounded-full border | ||||||
|  |   border-dark px-4 pt-[0.4rem] pb-[0.5rem] text-dark outline-none transition-all hover:bg-dark | ||||||
|  |   hover:text-light hover:drop-shadow-shade-lg active:border-black active:bg-black | ||||||
|  |   active:text-light active:drop-shadow-black-lg; | ||||||
|  | } | ||||||
							
								
								
									
										218
									
								
								src/styles/rc-slider.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,218 @@ | |||||||
|  | .rc-slider { | ||||||
|  |   position: relative; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 14px; | ||||||
|  |   padding: 5px 0; | ||||||
|  |   border-radius: 6px; | ||||||
|  |   touch-action: none; | ||||||
|  |   box-sizing: border-box; | ||||||
|  | } | ||||||
|  | .rc-slider * { | ||||||
|  |   box-sizing: border-box; | ||||||
|  | } | ||||||
|  | .rc-slider-rail { | ||||||
|  |   @apply h-2 rounded-full bg-mid/80; | ||||||
|  |   position: absolute; | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  | .rc-slider-track { | ||||||
|  |   @apply h-2 rounded-full bg-mid bg-dark/20 shadow-inner-sm shadow-shade; | ||||||
|  |   position: absolute; | ||||||
|  | } | ||||||
|  | .rc-slider-handle { | ||||||
|  |   @apply -mt-1 h-4 w-4 rounded-full bg-dark transition-shadow; | ||||||
|  |   position: absolute; | ||||||
|  |   cursor: grab; | ||||||
|  |   touch-action: pan-x; | ||||||
|  | } | ||||||
|  | .rc-slider-handle-dragging.rc-slider-handle-dragging.rc-slider-handle-dragging { | ||||||
|  |   @apply shadow-sm shadow-shade; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .rc-slider-mark { | ||||||
|  |   position: absolute; | ||||||
|  |   top: 18px; | ||||||
|  |   left: 0; | ||||||
|  |   width: 100%; | ||||||
|  |   font-size: 12px; | ||||||
|  | } | ||||||
|  | .rc-slider-mark-text { | ||||||
|  |   position: absolute; | ||||||
|  |   display: inline-block; | ||||||
|  |   color: #999; | ||||||
|  |   text-align: center; | ||||||
|  |   vertical-align: middle; | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  | .rc-slider-mark-text-active { | ||||||
|  |   color: #666; | ||||||
|  | } | ||||||
|  | .rc-slider-step { | ||||||
|  |   position: absolute; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 4px; | ||||||
|  |   background: transparent; | ||||||
|  |   pointer-events: none; | ||||||
|  | } | ||||||
|  | .rc-slider-dot { | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: -2px; | ||||||
|  |   width: 8px; | ||||||
|  |   height: 8px; | ||||||
|  |   vertical-align: middle; | ||||||
|  |   background-color: #fff; | ||||||
|  |   border: 2px solid #e9e9e9; | ||||||
|  |   border-radius: 50%; | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  | .rc-slider-dot-active { | ||||||
|  |   border-color: #96dbfa; | ||||||
|  | } | ||||||
|  | .rc-slider-dot-reverse { | ||||||
|  |   margin-right: -4px; | ||||||
|  | } | ||||||
|  | .rc-slider-disabled { | ||||||
|  |   background-color: #e9e9e9; | ||||||
|  | } | ||||||
|  | .rc-slider-disabled .rc-slider-track { | ||||||
|  |   background-color: #ccc; | ||||||
|  | } | ||||||
|  | .rc-slider-disabled .rc-slider-handle, | ||||||
|  | .rc-slider-disabled .rc-slider-dot { | ||||||
|  |   background-color: #fff; | ||||||
|  |   border-color: #ccc; | ||||||
|  |   box-shadow: none; | ||||||
|  |   cursor: not-allowed; | ||||||
|  | } | ||||||
|  | .rc-slider-disabled .rc-slider-mark-text, | ||||||
|  | .rc-slider-disabled .rc-slider-dot { | ||||||
|  |   cursor: not-allowed !important; | ||||||
|  | } | ||||||
|  | .rc-slider-vertical { | ||||||
|  |   width: 14px; | ||||||
|  |   height: 100%; | ||||||
|  |   padding: 0 5px; | ||||||
|  | } | ||||||
|  | .rc-slider-vertical .rc-slider-rail { | ||||||
|  |   width: 4px; | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  | .rc-slider-vertical .rc-slider-track { | ||||||
|  |   bottom: 0; | ||||||
|  |   left: 5px; | ||||||
|  |   width: 4px; | ||||||
|  | } | ||||||
|  | .rc-slider-vertical .rc-slider-handle { | ||||||
|  |   margin-top: 0; | ||||||
|  |   margin-left: -5px; | ||||||
|  |   touch-action: pan-y; | ||||||
|  | } | ||||||
|  | .rc-slider-vertical .rc-slider-mark { | ||||||
|  |   top: 0; | ||||||
|  |   left: 18px; | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  | .rc-slider-vertical .rc-slider-step { | ||||||
|  |   width: 4px; | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  | .rc-slider-vertical .rc-slider-dot { | ||||||
|  |   margin-left: -2px; | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-zoom-down-enter, | ||||||
|  | .rc-slider-tooltip-zoom-down-appear { | ||||||
|  |   display: block !important; | ||||||
|  |   animation-duration: 0.3s; | ||||||
|  |   animation-fill-mode: both; | ||||||
|  |   animation-play-state: paused; | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-zoom-down-leave { | ||||||
|  |   display: block !important; | ||||||
|  |   animation-duration: 0.3s; | ||||||
|  |   animation-fill-mode: both; | ||||||
|  |   animation-play-state: paused; | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active, | ||||||
|  | .rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active { | ||||||
|  |   animation-name: rcSliderTooltipZoomDownIn; | ||||||
|  |   animation-play-state: running; | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active { | ||||||
|  |   animation-name: rcSliderTooltipZoomDownOut; | ||||||
|  |   animation-play-state: running; | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-zoom-down-enter, | ||||||
|  | .rc-slider-tooltip-zoom-down-appear { | ||||||
|  |   transform: scale(0, 0); | ||||||
|  |   animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-zoom-down-leave { | ||||||
|  |   animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); | ||||||
|  | } | ||||||
|  | @keyframes rcSliderTooltipZoomDownIn { | ||||||
|  |   0% { | ||||||
|  |     transform: scale(0, 0); | ||||||
|  |     transform-origin: 50% 100%; | ||||||
|  |     opacity: 0; | ||||||
|  |   } | ||||||
|  |   100% { | ||||||
|  |     transform: scale(1, 1); | ||||||
|  |     transform-origin: 50% 100%; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @keyframes rcSliderTooltipZoomDownOut { | ||||||
|  |   0% { | ||||||
|  |     transform: scale(1, 1); | ||||||
|  |     transform-origin: 50% 100%; | ||||||
|  |   } | ||||||
|  |   100% { | ||||||
|  |     transform: scale(0, 0); | ||||||
|  |     transform-origin: 50% 100%; | ||||||
|  |     opacity: 0; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip { | ||||||
|  |   position: absolute; | ||||||
|  |   top: -9999px; | ||||||
|  |   left: -9999px; | ||||||
|  |   visibility: visible; | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip * { | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-hidden { | ||||||
|  |   display: none; | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-placement-top { | ||||||
|  |   padding: 4px 0 8px 0; | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-inner { | ||||||
|  |   min-width: 24px; | ||||||
|  |   height: 24px; | ||||||
|  |   padding: 6px 2px; | ||||||
|  |   color: #fff; | ||||||
|  |   font-size: 12px; | ||||||
|  |   line-height: 1; | ||||||
|  |   text-align: center; | ||||||
|  |   text-decoration: none; | ||||||
|  |   background-color: #6c6c6c; | ||||||
|  |   border-radius: 6px; | ||||||
|  |   box-shadow: 0 0 4px #d9d9d9; | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-arrow { | ||||||
|  |   position: absolute; | ||||||
|  |   width: 0; | ||||||
|  |   height: 0; | ||||||
|  |   border-color: transparent; | ||||||
|  |   border-style: solid; | ||||||
|  | } | ||||||
|  | .rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow { | ||||||
|  |   bottom: 4px; | ||||||
|  |   left: 50%; | ||||||
|  |   margin-left: -4px; | ||||||
|  |   border-width: 4px 4px 0; | ||||||
|  |   border-top-color: #6c6c6c; | ||||||
|  | } | ||||||
							
								
								
									
										63
									
								
								src/styles/tippy.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,63 @@ | |||||||
|  | .tippy-box[data-animation="fade"][data-state="hidden"] { | ||||||
|  |   @apply opacity-0; | ||||||
|  | } | ||||||
|  | [data-tippy-root] { | ||||||
|  |   max-width: calc(100vw - 10px); | ||||||
|  | } | ||||||
|  | .tippy-box { | ||||||
|  |   @apply relative rounded-lg bg-light transition-[transform,visibility,opacity] | ||||||
|  |   drop-shadow-shade-xl; | ||||||
|  | } | ||||||
|  | .tippy-box[data-placement^="top"] > .tippy-arrow { | ||||||
|  |   @apply bottom-0; | ||||||
|  | } | ||||||
|  | .tippy-box[data-placement^="top"] > .tippy-arrow:before { | ||||||
|  |   bottom: -7px; | ||||||
|  |   left: 0; | ||||||
|  |   border-width: 8px 8px 0; | ||||||
|  |   border-top-color: initial; | ||||||
|  |   transform-origin: center top; | ||||||
|  | } | ||||||
|  | .tippy-box[data-placement^="bottom"] > .tippy-arrow { | ||||||
|  |   top: 0; | ||||||
|  | } | ||||||
|  | .tippy-box[data-placement^="bottom"] > .tippy-arrow:before { | ||||||
|  |   top: -7px; | ||||||
|  |   left: 0; | ||||||
|  |   border-width: 0 8px 8px; | ||||||
|  |   border-bottom-color: initial; | ||||||
|  |   transform-origin: center bottom; | ||||||
|  | } | ||||||
|  | .tippy-box[data-placement^="left"] > .tippy-arrow { | ||||||
|  |   right: 0; | ||||||
|  | } | ||||||
|  | .tippy-box[data-placement^="left"] > .tippy-arrow:before { | ||||||
|  |   border-width: 8px 0 8px 8px; | ||||||
|  |   border-left-color: initial; | ||||||
|  |   right: -7px; | ||||||
|  |   transform-origin: center left; | ||||||
|  | } | ||||||
|  | .tippy-box[data-placement^="right"] > .tippy-arrow { | ||||||
|  |   left: 0; | ||||||
|  | } | ||||||
|  | .tippy-box[data-placement^="right"] > .tippy-arrow:before { | ||||||
|  |   left: -7px; | ||||||
|  |   border-width: 8px 8px 8px 0; | ||||||
|  |   border-right-color: initial; | ||||||
|  |   transform-origin: center right; | ||||||
|  | } | ||||||
|  | .tippy-box[data-inertia][data-state="visible"] { | ||||||
|  |   transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11); | ||||||
|  | } | ||||||
|  | .tippy-arrow { | ||||||
|  |   @apply h-4 w-4 text-light; | ||||||
|  | } | ||||||
|  | .tippy-arrow:before { | ||||||
|  |   content: ""; | ||||||
|  |   position: absolute; | ||||||
|  |   border-color: transparent; | ||||||
|  |   border-style: solid; | ||||||
|  | } | ||||||
|  | .tippy-content { | ||||||
|  |   @apply relative z-10 px-6 py-4; | ||||||
|  | } | ||||||
							
								
								
									
										510
									
								
								src/tailwind.css
									
									
									
									
									
								
							
							
						
						| @ -1,510 +0,0 @@ | |||||||
| @tailwind base; |  | ||||||
| @tailwind components; |  | ||||||
| @tailwind utilities; |  | ||||||
| 
 |  | ||||||
| * { |  | ||||||
|   @apply box-border scroll-m-[40vh] scroll-smooth font-body font-medium; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| h1, |  | ||||||
| h2, |  | ||||||
| h3, |  | ||||||
| h4, |  | ||||||
| h5, |  | ||||||
| h6 { |  | ||||||
|   @apply font-headers font-black; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| a { |  | ||||||
|   @apply cursor-pointer underline decoration-dark decoration-dotted |  | ||||||
|   underline-offset-2 transition-colors hover:text-dark; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| *::selection { |  | ||||||
|   @apply bg-dark text-light; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| mark { |  | ||||||
|   @apply bg-mid px-2 text-black; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* SCROLLBARS STYLING */ |  | ||||||
| 
 |  | ||||||
| * { |  | ||||||
|   @apply [scrollbar-color:theme(colors.dark/1)_transparent] [scrollbar-width:thin]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| *::-webkit-scrollbar { |  | ||||||
|   @apply w-3; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| *::-webkit-scrollbar-track { |  | ||||||
|   @apply bg-light; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| *::-webkit-scrollbar-thumb { |  | ||||||
|   @apply rounded-full border-[3px] border-solid border-light bg-dark; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* CHANGE FORMATTED DEFAULTS */ |  | ||||||
| 
 |  | ||||||
| .formatted h1, |  | ||||||
| .formatted h2, |  | ||||||
| .formatted h3, |  | ||||||
| .formatted h4, |  | ||||||
| .formatted h5, |  | ||||||
| .formatted h6 { |  | ||||||
|   @apply flex justify-center gap-3 text-center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted h1 { |  | ||||||
|   @apply my-16 text-4xl; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted h1 + h2 { |  | ||||||
|   @apply -mt-10; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted h2 { |  | ||||||
|   @apply my-12 text-3xl; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted h2 + h3 { |  | ||||||
|   @apply -mt-8; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted h3 { |  | ||||||
|   @apply my-8 text-2xl; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted h3 + h4 { |  | ||||||
|   @apply -mt-6; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted h4 { |  | ||||||
|   @apply my-6 text-xl; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted h5 { |  | ||||||
|   @apply my-4 text-lg; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted p, |  | ||||||
| .formatted strong { |  | ||||||
|   @apply my-2 text-justify; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted strong { |  | ||||||
|   @apply font-black; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted footer { |  | ||||||
|   @apply border-t-[3px] border-dotted pt-6; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted footer > div { |  | ||||||
|   @apply my-2 rounded-xl px-6 py-4; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted footer > div:target { |  | ||||||
|   @apply bg-mid shadow-inner-sm shadow-shade; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted li::marker { |  | ||||||
|   @apply text-dark; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted code { |  | ||||||
|   @apply font-mono; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted pre > code { |  | ||||||
|   @apply block whitespace-pre-line; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted ul { |  | ||||||
|   @apply list-disc pl-4; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted ol { |  | ||||||
|   @apply list-decimal pl-4; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted blockquote { |  | ||||||
|   @apply my-8 rounded-lg border-2 border-mid p-5 text-center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .formatted blockquote cite { |  | ||||||
|   @apply block text-dark; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* INPUT */ |  | ||||||
| 
 |  | ||||||
| input, |  | ||||||
| textarea { |  | ||||||
|   @apply rounded-full bg-light p-2 text-center text-dark outline outline-2 outline-offset-[-2px] |  | ||||||
|   outline-mid transition-all placeholder:text-dark placeholder:opacity-60 hover:bg-mid |  | ||||||
|   hover:outline-[transparent]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| input::placeholder { |  | ||||||
|   @apply text-dark opacity-60; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| input:focus-visible, |  | ||||||
| textarea:focus-within { |  | ||||||
|   @apply bg-mid shadow-inner-sm shadow-shade outline-none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| textarea { |  | ||||||
|   @apply rounded-2xl p-6 text-left; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| input[type="submit"] { |  | ||||||
|   @apply grid cursor-pointer place-content-center place-items-center rounded-full border-[1px] |  | ||||||
|   border-dark px-4 pt-[0.4rem] pb-[0.5rem] text-dark outline-none transition-all hover:bg-dark |  | ||||||
|   hover:text-light hover:drop-shadow-shade-lg active:border-black active:bg-black |  | ||||||
|   active:text-light active:drop-shadow-black-lg; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .texture-paper-dots { |  | ||||||
|   @apply bg-[length:10cm] bg-local [background-image:var(--theme-texture-dots)] |  | ||||||
|   [background-blend-mode:var(--theme-texture-dots-blend)]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* ANIMATION */ |  | ||||||
| 
 |  | ||||||
| .animation-carret { |  | ||||||
|   animation-name: blink; |  | ||||||
|   animation-duration: 1s; |  | ||||||
|   animation-timing-function: step-end; |  | ||||||
|   animation-iteration-count: infinite; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .animate-zoom-in { |  | ||||||
|   animation-name: zoom-in; |  | ||||||
|   animation-duration: 2s; |  | ||||||
|   animation-timing-function: ease-in-out; |  | ||||||
|   animation-iteration-count: 1; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @keyframes zoom-in { |  | ||||||
|   0% { |  | ||||||
|     transform: scale(0); |  | ||||||
|   } |  | ||||||
|   100% { |  | ||||||
|     transform: scale(1); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @keyframes blink { |  | ||||||
|   from, |  | ||||||
|   to { |  | ||||||
|     border-bottom-style: solid; |  | ||||||
|   } |  | ||||||
|   50% { |  | ||||||
|     border-bottom-style: none; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* DEBUGGING */ |  | ||||||
| .false { |  | ||||||
|   @apply border-2 border-[red] text-[red] outline-dotted outline-2 outline-[red]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .undefined { |  | ||||||
|   @apply border-2 border-[green] text-[green] outline-dotted outline-2 outline-[green]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .null { |  | ||||||
|   @apply border-2 border-[blue] text-[blue] outline-dotted outline-2 outline-[blue]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* TIPPY */ |  | ||||||
| 
 |  | ||||||
| .tippy-box[data-animation="fade"][data-state="hidden"] { |  | ||||||
|   @apply opacity-0; |  | ||||||
| } |  | ||||||
| [data-tippy-root] { |  | ||||||
|   max-width: calc(100vw - 10px); |  | ||||||
| } |  | ||||||
| .tippy-box { |  | ||||||
|   @apply relative rounded-lg bg-light transition-[transform,_visibility,_opacity] |  | ||||||
|   drop-shadow-shade-xl; |  | ||||||
| } |  | ||||||
| .tippy-box[data-placement^="top"] > .tippy-arrow { |  | ||||||
|   @apply bottom-0; |  | ||||||
| } |  | ||||||
| .tippy-box[data-placement^="top"] > .tippy-arrow:before { |  | ||||||
|   bottom: -7px; |  | ||||||
|   left: 0; |  | ||||||
|   border-width: 8px 8px 0; |  | ||||||
|   border-top-color: initial; |  | ||||||
|   transform-origin: center top; |  | ||||||
| } |  | ||||||
| .tippy-box[data-placement^="bottom"] > .tippy-arrow { |  | ||||||
|   top: 0; |  | ||||||
| } |  | ||||||
| .tippy-box[data-placement^="bottom"] > .tippy-arrow:before { |  | ||||||
|   top: -7px; |  | ||||||
|   left: 0; |  | ||||||
|   border-width: 0 8px 8px; |  | ||||||
|   border-bottom-color: initial; |  | ||||||
|   transform-origin: center bottom; |  | ||||||
| } |  | ||||||
| .tippy-box[data-placement^="left"] > .tippy-arrow { |  | ||||||
|   right: 0; |  | ||||||
| } |  | ||||||
| .tippy-box[data-placement^="left"] > .tippy-arrow:before { |  | ||||||
|   border-width: 8px 0 8px 8px; |  | ||||||
|   border-left-color: initial; |  | ||||||
|   right: -7px; |  | ||||||
|   transform-origin: center left; |  | ||||||
| } |  | ||||||
| .tippy-box[data-placement^="right"] > .tippy-arrow { |  | ||||||
|   left: 0; |  | ||||||
| } |  | ||||||
| .tippy-box[data-placement^="right"] > .tippy-arrow:before { |  | ||||||
|   left: -7px; |  | ||||||
|   border-width: 8px 8px 8px 0; |  | ||||||
|   border-right-color: initial; |  | ||||||
|   transform-origin: center right; |  | ||||||
| } |  | ||||||
| .tippy-box[data-inertia][data-state="visible"] { |  | ||||||
|   transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11); |  | ||||||
| } |  | ||||||
| .tippy-arrow { |  | ||||||
|   @apply h-4 w-4 text-light; |  | ||||||
| } |  | ||||||
| .tippy-arrow:before { |  | ||||||
|   content: ""; |  | ||||||
|   position: absolute; |  | ||||||
|   border-color: transparent; |  | ||||||
|   border-style: solid; |  | ||||||
| } |  | ||||||
| .tippy-content { |  | ||||||
|   @apply relative z-10 px-6 py-4; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* RC SLIDER */ |  | ||||||
| 
 |  | ||||||
| .rc-slider { |  | ||||||
|   position: relative; |  | ||||||
|   width: 100%; |  | ||||||
|   height: 14px; |  | ||||||
|   padding: 5px 0; |  | ||||||
|   border-radius: 6px; |  | ||||||
|   touch-action: none; |  | ||||||
|   box-sizing: border-box; |  | ||||||
|   -webkit-tap-highlight-color: rgba(0, 0, 0, 0); |  | ||||||
| } |  | ||||||
| .rc-slider * { |  | ||||||
|   box-sizing: border-box; |  | ||||||
|   -webkit-tap-highlight-color: rgba(0, 0, 0, 0); |  | ||||||
| } |  | ||||||
| .rc-slider-rail { |  | ||||||
|   @apply h-2 rounded-full bg-mid/80; |  | ||||||
|   position: absolute; |  | ||||||
|   width: 100%; |  | ||||||
| } |  | ||||||
| .rc-slider-track { |  | ||||||
|   @apply h-2 rounded-full bg-mid bg-dark/20 shadow-inner-sm shadow-shade; |  | ||||||
|   position: absolute; |  | ||||||
| } |  | ||||||
| .rc-slider-handle { |  | ||||||
|   @apply -mt-1 h-4 w-4 rounded-full bg-dark transition-shadow; |  | ||||||
|   position: absolute; |  | ||||||
|   cursor: grab; |  | ||||||
|   touch-action: pan-x; |  | ||||||
| } |  | ||||||
| .rc-slider-handle-dragging.rc-slider-handle-dragging.rc-slider-handle-dragging { |  | ||||||
|   @apply shadow-sm shadow-shade; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .rc-slider-mark { |  | ||||||
|   position: absolute; |  | ||||||
|   top: 18px; |  | ||||||
|   left: 0; |  | ||||||
|   width: 100%; |  | ||||||
|   font-size: 12px; |  | ||||||
| } |  | ||||||
| .rc-slider-mark-text { |  | ||||||
|   position: absolute; |  | ||||||
|   display: inline-block; |  | ||||||
|   color: #999; |  | ||||||
|   text-align: center; |  | ||||||
|   vertical-align: middle; |  | ||||||
|   cursor: pointer; |  | ||||||
| } |  | ||||||
| .rc-slider-mark-text-active { |  | ||||||
|   color: #666; |  | ||||||
| } |  | ||||||
| .rc-slider-step { |  | ||||||
|   position: absolute; |  | ||||||
|   width: 100%; |  | ||||||
|   height: 4px; |  | ||||||
|   background: transparent; |  | ||||||
|   pointer-events: none; |  | ||||||
| } |  | ||||||
| .rc-slider-dot { |  | ||||||
|   position: absolute; |  | ||||||
|   bottom: -2px; |  | ||||||
|   width: 8px; |  | ||||||
|   height: 8px; |  | ||||||
|   vertical-align: middle; |  | ||||||
|   background-color: #fff; |  | ||||||
|   border: 2px solid #e9e9e9; |  | ||||||
|   border-radius: 50%; |  | ||||||
|   cursor: pointer; |  | ||||||
| } |  | ||||||
| .rc-slider-dot-active { |  | ||||||
|   border-color: #96dbfa; |  | ||||||
| } |  | ||||||
| .rc-slider-dot-reverse { |  | ||||||
|   margin-right: -4px; |  | ||||||
| } |  | ||||||
| .rc-slider-disabled { |  | ||||||
|   background-color: #e9e9e9; |  | ||||||
| } |  | ||||||
| .rc-slider-disabled .rc-slider-track { |  | ||||||
|   background-color: #ccc; |  | ||||||
| } |  | ||||||
| .rc-slider-disabled .rc-slider-handle, |  | ||||||
| .rc-slider-disabled .rc-slider-dot { |  | ||||||
|   background-color: #fff; |  | ||||||
|   border-color: #ccc; |  | ||||||
|   box-shadow: none; |  | ||||||
|   cursor: not-allowed; |  | ||||||
| } |  | ||||||
| .rc-slider-disabled .rc-slider-mark-text, |  | ||||||
| .rc-slider-disabled .rc-slider-dot { |  | ||||||
|   cursor: not-allowed !important; |  | ||||||
| } |  | ||||||
| .rc-slider-vertical { |  | ||||||
|   width: 14px; |  | ||||||
|   height: 100%; |  | ||||||
|   padding: 0 5px; |  | ||||||
| } |  | ||||||
| .rc-slider-vertical .rc-slider-rail { |  | ||||||
|   width: 4px; |  | ||||||
|   height: 100%; |  | ||||||
| } |  | ||||||
| .rc-slider-vertical .rc-slider-track { |  | ||||||
|   bottom: 0; |  | ||||||
|   left: 5px; |  | ||||||
|   width: 4px; |  | ||||||
| } |  | ||||||
| .rc-slider-vertical .rc-slider-handle { |  | ||||||
|   margin-top: 0; |  | ||||||
|   margin-left: -5px; |  | ||||||
|   touch-action: pan-y; |  | ||||||
| } |  | ||||||
| .rc-slider-vertical .rc-slider-mark { |  | ||||||
|   top: 0; |  | ||||||
|   left: 18px; |  | ||||||
|   height: 100%; |  | ||||||
| } |  | ||||||
| .rc-slider-vertical .rc-slider-step { |  | ||||||
|   width: 4px; |  | ||||||
|   height: 100%; |  | ||||||
| } |  | ||||||
| .rc-slider-vertical .rc-slider-dot { |  | ||||||
|   margin-left: -2px; |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-zoom-down-enter, |  | ||||||
| .rc-slider-tooltip-zoom-down-appear { |  | ||||||
|   display: block !important; |  | ||||||
|   animation-duration: 0.3s; |  | ||||||
|   animation-fill-mode: both; |  | ||||||
|   animation-play-state: paused; |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-zoom-down-leave { |  | ||||||
|   display: block !important; |  | ||||||
|   animation-duration: 0.3s; |  | ||||||
|   animation-fill-mode: both; |  | ||||||
|   animation-play-state: paused; |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active, |  | ||||||
| .rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active { |  | ||||||
|   animation-name: rcSliderTooltipZoomDownIn; |  | ||||||
|   animation-play-state: running; |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active { |  | ||||||
|   animation-name: rcSliderTooltipZoomDownOut; |  | ||||||
|   animation-play-state: running; |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-zoom-down-enter, |  | ||||||
| .rc-slider-tooltip-zoom-down-appear { |  | ||||||
|   transform: scale(0, 0); |  | ||||||
|   animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-zoom-down-leave { |  | ||||||
|   animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); |  | ||||||
| } |  | ||||||
| @keyframes rcSliderTooltipZoomDownIn { |  | ||||||
|   0% { |  | ||||||
|     transform: scale(0, 0); |  | ||||||
|     transform-origin: 50% 100%; |  | ||||||
|     opacity: 0; |  | ||||||
|   } |  | ||||||
|   100% { |  | ||||||
|     transform: scale(1, 1); |  | ||||||
|     transform-origin: 50% 100%; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @keyframes rcSliderTooltipZoomDownOut { |  | ||||||
|   0% { |  | ||||||
|     transform: scale(1, 1); |  | ||||||
|     transform-origin: 50% 100%; |  | ||||||
|   } |  | ||||||
|   100% { |  | ||||||
|     transform: scale(0, 0); |  | ||||||
|     transform-origin: 50% 100%; |  | ||||||
|     opacity: 0; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip { |  | ||||||
|   position: absolute; |  | ||||||
|   top: -9999px; |  | ||||||
|   left: -9999px; |  | ||||||
|   visibility: visible; |  | ||||||
|   box-sizing: border-box; |  | ||||||
|   -webkit-tap-highlight-color: rgba(0, 0, 0, 0); |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip * { |  | ||||||
|   box-sizing: border-box; |  | ||||||
|   -webkit-tap-highlight-color: rgba(0, 0, 0, 0); |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-hidden { |  | ||||||
|   display: none; |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-placement-top { |  | ||||||
|   padding: 4px 0 8px 0; |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-inner { |  | ||||||
|   min-width: 24px; |  | ||||||
|   height: 24px; |  | ||||||
|   padding: 6px 2px; |  | ||||||
|   color: #fff; |  | ||||||
|   font-size: 12px; |  | ||||||
|   line-height: 1; |  | ||||||
|   text-align: center; |  | ||||||
|   text-decoration: none; |  | ||||||
|   background-color: #6c6c6c; |  | ||||||
|   border-radius: 6px; |  | ||||||
|   box-shadow: 0 0 4px #d9d9d9; |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-arrow { |  | ||||||
|   position: absolute; |  | ||||||
|   width: 0; |  | ||||||
|   height: 0; |  | ||||||
|   border-color: transparent; |  | ||||||
|   border-style: solid; |  | ||||||
| } |  | ||||||
| .rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow { |  | ||||||
|   bottom: 4px; |  | ||||||
|   left: 50%; |  | ||||||
|   margin-left: -4px; |  | ||||||
|   border-width: 4px 4px 0; |  | ||||||
|   border-top-color: #6c6c6c; |  | ||||||
| } |  | ||||||
| @ -2,4 +2,5 @@ export enum Ids { | |||||||
|   Body = "bodyqs65d4a98d56az48z64d", |   Body = "bodyqs65d4a98d56az48z64d", | ||||||
|   ContentPanel = "contentPanel495922447721572", |   ContentPanel = "contentPanel495922447721572", | ||||||
|   SubPanel = "subPanelz9e8rs2d3f18zer98ze", |   SubPanel = "subPanelz9e8rs2d3f18zer98ze", | ||||||
|  |   LightBox = "lightBoxqsd564az89e732s1", | ||||||
| } | } | ||||||
|  | |||||||
| @ -27,10 +27,43 @@ module.exports = { | |||||||
|       hoverable: { raw: "(hover: hover)" }, |       hoverable: { raw: "(hover: hover)" }, | ||||||
|       notHoverable: { raw: "(hover: none)" }, |       notHoverable: { raw: "(hover: none)" }, | ||||||
|     }, |     }, | ||||||
|  |     backdropBlur: { | ||||||
|  |       none: "0", | ||||||
|  |       sm: "0.1rem", | ||||||
|  |       DEFAULT: "0.2rem", | ||||||
|  |       md: "0.5rem", | ||||||
|  |       lg: "1rem", | ||||||
|  |     }, | ||||||
|  |     borderWidth: { | ||||||
|  |       0: "0", | ||||||
|  |       2: "0.2rem", | ||||||
|  |       4: "0.4rem", | ||||||
|  |       8: "0.8rem", | ||||||
|  |       DEFAULT: "0.1rem", | ||||||
|  |     }, | ||||||
|  |     outlineWidth: { | ||||||
|  |       0: "0", | ||||||
|  |       1: "0.15rem", | ||||||
|  |       2: "0.17rem", | ||||||
|  |     }, | ||||||
|  |     outlineOffset: { | ||||||
|  |       0: "0", | ||||||
|  |       1: "0.15rem", | ||||||
|  |       2: "0.17rem", | ||||||
|  |     }, | ||||||
|     extend: { |     extend: { | ||||||
|       boxShadow: { |       boxShadow: { | ||||||
|         "inner-sm": "inset 0 1px 4px -2px", |         "inner-sm": "inset 0 1px 4px -2px", | ||||||
|       }, |       }, | ||||||
|  |       transitionProperty: { | ||||||
|  |         height: "height, max-height, min-height", | ||||||
|  |         filter: "filter, backdrop-filter", | ||||||
|  |         colors: | ||||||
|  |           "color, background-color, border-color, text-decoration-color, fill, stroke, outline-color", | ||||||
|  |       }, | ||||||
|  |       outlineColor: { | ||||||
|  |         transparent: "transparent", | ||||||
|  |       }, | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   plugins: [ |   plugins: [ | ||||||
|  | |||||||
 DrMint
						DrMint