Refacto context+styles and improved lightbox
|
@ -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,47 +88,13 @@ 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
|
|
||||||
className={cJoin(
|
|
||||||
cIf(darkMode, "set-theme-dark", "set-theme-light"),
|
|
||||||
cIf(dyslexic, "set-theme-font-dyslexic", "set-theme-font-standard")
|
|
||||||
)}>
|
|
||||||
<div
|
<div
|
||||||
{...handlers}
|
{...handlers}
|
||||||
id={Ids.Body}
|
id={Ids.Body}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black
|
"fixed inset-0 m-0 grid touch-pan-y bg-light p-0 [grid-template-areas:'main_sub_content']",
|
||||||
[grid-template-areas:'main_sub_content']`,
|
cIf(is1ColumnLayout, "grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']")
|
||||||
cIf(is1ColumnLayout, `grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']`)
|
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
gridTemplateColumns: is1ColumnLayout
|
gridTemplateColumns: is1ColumnLayout
|
||||||
|
@ -199,21 +122,14 @@ export const AppLayout = ({
|
||||||
<meta property="og:image:type" content="image/jpeg" />
|
<meta property="og:image:type" content="image/jpeg" />
|
||||||
</Head>
|
</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 */}
|
{/* Background when navbar is opened */}
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`absolute inset-0 transition-[backdrop-filter] duration-500
|
`absolute inset-0 transition-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"
|
||||||
)
|
)
|
||||||
)}>
|
)}>
|
||||||
|
@ -250,7 +166,7 @@ export const AppLayout = ({
|
||||||
<div
|
<div
|
||||||
id={Ids.SubPanel}
|
id={Ids.SubPanel}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`texture-paper-dots z-20 overflow-y-scroll border-r-[1px] border-dark/50
|
`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(
|
||||||
|
@ -258,7 +174,7 @@ export const AppLayout = ({
|
||||||
"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")
|
||||||
)}>
|
)}>
|
||||||
|
@ -269,7 +185,7 @@ export const AppLayout = ({
|
||||||
{/* Main panel */}
|
{/* Main panel */}
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`texture-paper-dots z-30 overflow-y-scroll border-r-[1px] border-dark/50
|
`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`,
|
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, "justify-self-start [grid-area:content]", "[grid-area:main]"),
|
||||||
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
||||||
|
@ -282,7 +198,7 @@ export const AppLayout = ({
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`texture-paper-dots z-10 grid grid-cols-[5rem_1fr_5rem] place-items-center
|
`texture-paper-dots z-10 grid grid-cols-[5rem_1fr_5rem] place-items-center
|
||||||
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar]`,
|
border-t border-dotted border-black bg-light [grid-area:navbar]`,
|
||||||
cIf(!is1ColumnLayout, "hidden")
|
cIf(!is1ColumnLayout, "hidden")
|
||||||
)}>
|
)}>
|
||||||
<Ico
|
<Ico
|
||||||
|
@ -313,207 +229,7 @@ export const AppLayout = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<SafariPopup />
|
||||||
<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>
|
</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
|
||||||
|
|
|
@ -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 => {
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
|
`relative grid h-6 w-12 content-center rounded-full border-mid outline
|
||||||
|
outline-1 -outline-offset-1 outline-mid transition-colors`,
|
||||||
cIf(disabled, "cursor-not-allowed", "cursor-pointer"),
|
cIf(disabled, "cursor-not-allowed", "cursor-pointer"),
|
||||||
cIf(value, "border-none bg-mid shadow-inner-sm shadow-shade", "bg-light"),
|
cIf(
|
||||||
|
value,
|
||||||
|
"border-none bg-mid shadow-inner-sm shadow-shade outline-transparent",
|
||||||
|
"bg-light"
|
||||||
|
),
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!disabled) onClick();
|
if (!disabled) onClick();
|
||||||
}}>
|
}}
|
||||||
|
onPointerDown={() => setIsFocused(true)}
|
||||||
|
onPointerOut={() => setIsFocused(false)}
|
||||||
|
onPointerLeave={() => setIsFocused(false)}
|
||||||
|
onPointerUp={() => setIsFocused(false)}>
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"absolute aspect-square rounded-full bg-dark transition-transform",
|
"ml-1 h-4 w-4 rounded-full bg-dark transition-transform",
|
||||||
cIf(value, "top-[2px] bottom-[2px] left-[2px] translate-x-[120%]", "top-0 bottom-0 left-0")
|
cIf(value, "translate-x-6"),
|
||||||
)}></div>
|
cIf(isFocused, cIf(value, "translate-x-5", "translate-x-1"))
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</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 (
|
||||||
<>
|
|
||||||
{state && (
|
|
||||||
<Hotkeys
|
|
||||||
keyName="left,right"
|
|
||||||
allowRepeat
|
|
||||||
onKeyDown={(keyName) => {
|
|
||||||
if (keyName === "left") {
|
|
||||||
handlePrevious();
|
|
||||||
} else {
|
|
||||||
handleNext();
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<Popup onClose={() => setState(false)} state={state} padding={false} fillViewport>
|
|
||||||
<div
|
<div
|
||||||
{...handlers}
|
id={Ids.LightBox}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`grid h-full w-full place-items-center overflow-hidden first-letter:gap-4`,
|
"fixed inset-0 z-50 grid place-content-center transition-filter duration-500",
|
||||||
cIf(
|
cIf(isVisible, "backdrop-blur", "pointer-events-none touch-none")
|
||||||
is3ColumnsLayout,
|
|
||||||
`grid-cols-[4em,1fr,4em] [grid-template-areas:"left_image_right"]`,
|
|
||||||
`grid-cols-2 [grid-template-areas:"image_image""left_right"]`
|
|
||||||
)
|
|
||||||
)}>
|
)}>
|
||||||
<div className="ml-4 [grid-area:left]">
|
<div
|
||||||
{index > 0 && <Button onClick={handlePrevious} icon={Icon.ChevronLeft} />}
|
className={cJoin(
|
||||||
</div>
|
"fixed inset-0 bg-shade transition-all duration-500",
|
||||||
|
cIf(isVisible, "bg-opacity-50", "bg-opacity-0")
|
||||||
<Img className="max-h-full min-h-fit [grid-area:image]" src={images[index]} />
|
|
||||||
|
|
||||||
<div className="mr-4 [grid-area:right]">
|
|
||||||
{index < images.length - 1 && (
|
|
||||||
<Button onClick={handleNext} icon={Icon.ChevronRight} />
|
|
||||||
)}
|
)}
|
||||||
</div>
|
/>
|
||||||
</div>
|
<div
|
||||||
</Popup>
|
className={cJoin(
|
||||||
</Hotkeys>
|
"absolute inset-8 grid transition-transform",
|
||||||
|
cIf(isVisible, "scale-100", "scale-0")
|
||||||
|
)}>
|
||||||
|
<TransformWrapper
|
||||||
|
onZoom={(zoom) => setCurrentZoom(zoom.state.scale)}
|
||||||
|
panning={{ disabled: currentZoom <= 1, velocityDisabled: false }}
|
||||||
|
doubleClick={{ disabled: true, mode: "reset" }}
|
||||||
|
zoomAnimation={{ size: 0.1 }}
|
||||||
|
velocityAnimation={{ animationTime: 0, equalToMove: true }}>
|
||||||
|
{({ resetTransform }) => (
|
||||||
|
<>
|
||||||
|
<TransformComponent
|
||||||
|
wrapperStyle={{
|
||||||
|
overflow: "visible",
|
||||||
|
placeSelf: "center",
|
||||||
|
}}>
|
||||||
|
{isDefined(src) && (
|
||||||
|
<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>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
</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,8 +49,6 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<LightBox />
|
|
||||||
<Markdown
|
<Markdown
|
||||||
className={cJoin("formatted", className)}
|
className={cJoin("formatted", className)}
|
||||||
options={{
|
options={{
|
||||||
|
@ -125,10 +123,7 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
|
||||||
component: (compProps) => (
|
component: (compProps) => (
|
||||||
<>
|
<>
|
||||||
<strong
|
<strong
|
||||||
className={cJoin(
|
className={cJoin("!my-0 text-dark/60", cIf(!isContentPanelAtLeastLg, "!-mb-4"))}>
|
||||||
"!my-0 text-dark/60",
|
|
||||||
cIf(!isContentPanelAtLeastLg, "!-mb-4")
|
|
||||||
)}>
|
|
||||||
<Markdawn text={compProps.name} />
|
<Markdawn text={compProps.name} />
|
||||||
</strong>
|
</strong>
|
||||||
<p className="whitespace-pre-line">{compProps.children}</p>
|
<p className="whitespace-pre-line">{compProps.children}</p>
|
||||||
|
@ -196,7 +191,7 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
|
||||||
<div
|
<div
|
||||||
className="mt-8 mb-12 grid cursor-pointer place-content-center"
|
className="mt-8 mb-12 grid cursor-pointer place-content-center"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openLightBox([
|
showLightBox([
|
||||||
compProps.src.startsWith("/uploads/")
|
compProps.src.startsWith("/uploads/")
|
||||||
? getAssetURL(compProps.src, ImageQuality.Large)
|
? getAssetURL(compProps.src, ImageQuality.Large)
|
||||||
: compProps.src,
|
: compProps.src,
|
||||||
|
@ -217,7 +212,6 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
|
||||||
}}>
|
}}>
|
||||||
{text}
|
{text}
|
||||||
</Markdown>
|
</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>
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500",
|
"fixed inset-0 z-50 grid place-content-center transition-filter duration-500",
|
||||||
cIf(state, "[backdrop-filter:blur(2px)]", "pointer-events-none touch-none")
|
cIf(isVisible, "backdrop-blur", "pointer-events-none touch-none")
|
||||||
)}>
|
)}>
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"fixed inset-0 bg-shade transition-all duration-500",
|
"fixed inset-0 bg-shade transition-all duration-500",
|
||||||
cIf(state, "bg-opacity-50", "bg-opacity-0")
|
cIf(isVisible, "bg-opacity-50", "bg-opacity-0")
|
||||||
)}
|
)}
|
||||||
onClick={onClose}
|
onClick={onCloseRequest}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<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>
|
</div>
|
||||||
</Hotkeys>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -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>
|
|
||||||
<ContainerQueriesContextProvider>
|
|
||||||
<LocalDataContextProvider>
|
<LocalDataContextProvider>
|
||||||
|
<AppContextProvider>
|
||||||
<UserSettingsContextProvider>
|
<UserSettingsContextProvider>
|
||||||
|
<ContainerQueriesContextProvider>
|
||||||
<TerminalContextProvider>
|
<TerminalContextProvider>
|
||||||
|
<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} />
|
<props.Component {...props.pageProps} />
|
||||||
|
</LightBoxContextProvider>
|
||||||
</TerminalContextProvider>
|
</TerminalContextProvider>
|
||||||
</UserSettingsContextProvider>
|
|
||||||
</LocalDataContextProvider>
|
|
||||||
</ContainerQueriesContextProvider>
|
</ContainerQueriesContextProvider>
|
||||||
|
</UserSettingsContextProvider>
|
||||||
</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,12 +439,6 @@ 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
|
|
||||||
keyName="left,right"
|
|
||||||
allowRepeat
|
|
||||||
onKeyDown={(keyName) => {
|
|
||||||
handlePageNavigation(keyName as "left" | "right");
|
|
||||||
}}>
|
|
||||||
<TransformWrapper
|
<TransformWrapper
|
||||||
onZoom={(zoom) => setCurrentZoom(zoom.state.scale)}
|
onZoom={(zoom) => setCurrentZoom(zoom.state.scale)}
|
||||||
panning={{ disabled: currentZoom <= 1, velocityDisabled: false }}
|
panning={{ disabled: currentZoom <= 1, velocityDisabled: false }}
|
||||||
|
@ -540,7 +547,6 @@ const LibrarySlug = ({
|
||||||
)}
|
)}
|
||||||
</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,7 +1034,7 @@ 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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)];
|
||||||
|
}
|
|
@ -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];
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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: [
|
||||||
|
|