Refacto context+styles and improved lightbox

This commit is contained in:
DrMint 2022-10-23 06:19:53 +02:00
parent 8aeae06432
commit 230df12c22
67 changed files with 5968 additions and 1875 deletions

3987
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,29 +1,15 @@
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useMemo } from "react";
import { useEffect, useMemo, useState } from "react";
import { useSwipeable } from "react-swipeable"; import { useSwipeable } from "react-swipeable";
import UAParser from "ua-parser-js";
import { useIsClient } from "usehooks-ts";
import Script from "next/script";
import { layout } from "../../design.config"; import { layout } from "../../design.config";
import { Ico, Icon } from "./Ico"; import { Ico, Icon } from "./Ico";
import { ButtonGroup } from "./Inputs/ButtonGroup";
import { OrderableList } from "./Inputs/OrderableList";
import { Select } from "./Inputs/Select";
import { TextInput } from "./Inputs/TextInput";
import { MainPanel } from "./Panels/MainPanel"; import { MainPanel } from "./Panels/MainPanel";
import { Popup } from "./Popup"; import { SafariPopup } from "./Panels/SafariPopup";
import { filterHasAttributes, isDefined, isUndefined } from "helpers/others"; import { isDefined, isUndefined } from "helpers/others";
import { prettyLanguage } from "helpers/formatters";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { Button } from "components/Inputs/Button";
import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph"; import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph";
import { useIs1ColumnLayout, useIsScreenAtLeast } from "hooks/useContainerQuery";
import { useOnResize } from "hooks/useOnResize";
import { Ids } from "types/ids"; import { Ids } from "types/ids";
import { sendAnalytics } from "helpers/analytics";
import { useUserSettings } from "contexts/UserSettingsContext";
import { useLocalData } from "contexts/LocalDataContext"; import { useLocalData } from "contexts/LocalDataContext";
import { useContainerQueries } from "contexts/ContainerQueriesContext"; import { useContainerQueries } from "contexts/ContainerQueriesContext";
@ -60,48 +46,19 @@ export const AppLayout = ({
contentPanelScroolbar = true, contentPanelScroolbar = true,
}: Props): JSX.Element => { }: Props): JSX.Element => {
const { const {
configPanelOpen,
mainPanelOpen, mainPanelOpen,
mainPanelReduced, mainPanelReduced,
menuGestures, menuGestures,
subPanelOpen, subPanelOpen,
hasDisgardedSafariWarning,
setHasDisgardedSafariWarning,
setConfigPanelOpen,
setMainPanelOpen, setMainPanelOpen,
setSubPanelOpen, setSubPanelOpen,
toggleMainPanelOpen, toggleMainPanelOpen,
toggleSubPanelOpen, toggleSubPanelOpen,
} = useAppLayout(); } = useAppLayout();
const { setScreenWidth, setContentPanelWidth, setSubPanelWidth } = useContainerQueries(); const { langui } = useLocalData();
const { langui, currencies, languages } = useLocalData(); const { is1ColumnLayout, isScreenAtLeastXs } = useContainerQueries();
const {
currency,
darkMode,
dyslexic,
fontSize,
playerName,
preferredLanguages,
selectedThemeMode,
setCurrency,
setDarkMode,
setDyslexic,
setFontSize,
setPlayerName,
setPreferredLanguages,
setSelectedThemeMode,
} = useUserSettings();
const router = useRouter();
const is1ColumnLayout = useIs1ColumnLayout();
const isScreenAtLeastXs = useIsScreenAtLeast("xs");
useOnResize(Ids.Body, (width) => setScreenWidth(width));
useOnResize(Ids.ContentPanel, (width) => setContentPanelWidth(width));
useOnResize(Ids.SubPanel, (width) => setSubPanelWidth(width));
const handlers = useSwipeable({ const handlers = useSwipeable({
onSwipedLeft: (SwipeEventData) => { onSwipedLeft: (SwipeEventData) => {
@ -131,389 +88,148 @@ export const AppLayout = ({
[contentPanel, subPanel] [contentPanel, subPanel]
); );
const currencyOptions = useMemo(
() =>
filterHasAttributes(currencies, ["attributes"] as const).map(
(currentCurrency) => currentCurrency.attributes.code
),
[currencies]
);
const [currencySelect, setCurrencySelect] = useState<number>(-1);
useEffect(() => {
if (isDefined(currency)) setCurrencySelect(currencyOptions.indexOf(currency));
}, [currency, currencyOptions]);
useEffect(() => {
if (currencySelect >= 0) setCurrency(currencyOptions[currencySelect]);
}, [currencyOptions, currencySelect, setCurrency]);
const isClient = useIsClient();
const isSafari = useMemo<boolean>(() => {
if (isClient) {
const parser = new UAParser();
return parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS";
}
return false;
}, [isClient]);
return ( return (
<div <div
{...handlers}
id={Ids.Body}
className={cJoin( className={cJoin(
cIf(darkMode, "set-theme-dark", "set-theme-light"), "fixed inset-0 m-0 grid touch-pan-y bg-light p-0 [grid-template-areas:'main_sub_content']",
cIf(dyslexic, "set-theme-font-dyslexic", "set-theme-font-standard") cIf(is1ColumnLayout, "grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']")
)}> )}
style={{
gridTemplateColumns: is1ColumnLayout
? "1fr"
: `${mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu}rem ${
isDefined(subPanel) ? layout.subMenu : 0
}rem 1fr`,
}}>
<Head>
<title>{openGraph.title}</title>
<meta name="description" content={openGraph.description} />
<meta name="twitter:title" content={openGraph.title} />
<meta name="twitter:description" content={openGraph.description} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={openGraph.thumbnail.image} />
<meta property="og:title" content={openGraph.title} />
<meta property="og:description" content={openGraph.description} />
<meta property="og:image" content={openGraph.thumbnail.image} />
<meta property="og:image:secure_url" content={openGraph.thumbnail.image} />
<meta property="og:image:width" content={openGraph.thumbnail.width.toString()} />
<meta property="og:image:height" content={openGraph.thumbnail.height.toString()} />
<meta property="og:image:alt" content={openGraph.thumbnail.alt} />
<meta property="og:image:type" content="image/jpeg" />
</Head>
{/* Background when navbar is opened */}
<div <div
{...handlers}
id={Ids.Body}
className={cJoin( className={cJoin(
`fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black `absolute inset-0 transition-filter duration-500
[grid-template-areas:'main_sub_content']`,
cIf(is1ColumnLayout, `grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']`)
)}
style={{
gridTemplateColumns: is1ColumnLayout
? "1fr"
: `${mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu}rem ${
isDefined(subPanel) ? layout.subMenu : 0
}rem 1fr`,
}}>
<Head>
<title>{openGraph.title}</title>
<meta name="description" content={openGraph.description} />
<meta name="twitter:title" content={openGraph.title} />
<meta name="twitter:description" content={openGraph.description} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={openGraph.thumbnail.image} />
<meta property="og:title" content={openGraph.title} />
<meta property="og:description" content={openGraph.description} />
<meta property="og:image" content={openGraph.thumbnail.image} />
<meta property="og:image:secure_url" content={openGraph.thumbnail.image} />
<meta property="og:image:width" content={openGraph.thumbnail.width.toString()} />
<meta property="og:image:height" content={openGraph.thumbnail.height.toString()} />
<meta property="og:image:alt" content={openGraph.thumbnail.alt} />
<meta property="og:image:type" content="image/jpeg" />
</Head>
<Script
async
defer
data-website-id={process.env.NEXT_PUBLIC_UMAMI_ID}
src={`${process.env.NEXT_PUBLIC_UMAMI_URL}/umami.js`}
/>
{/* Background when navbar is opened */}
<div
className={cJoin(
`absolute inset-0 transition-[backdrop-filter] duration-500
[grid-area:content]`, [grid-area:content]`,
cIf( cIf(
(mainPanelOpen || subPanelOpen) && is1ColumnLayout, (mainPanelOpen || subPanelOpen) && is1ColumnLayout,
"z-10 [backdrop-filter:blur(2px)]", "z-10 backdrop-blur",
"pointer-events-none touch-none" "pointer-events-none touch-none"
) )
)}> )}>
<div
className={cJoin(
"absolute inset-0 bg-shade transition-opacity duration-500",
cIf((mainPanelOpen || subPanelOpen) && is1ColumnLayout, "opacity-60", "opacity-0")
)}
onClick={() => {
setMainPanelOpen(false);
setSubPanelOpen(false);
}}></div>
</div>
{/* Content panel */}
<div <div
id={Ids.ContentPanel}
className={cJoin( className={cJoin(
"texture-paper-dots bg-light [grid-area:content]", "absolute inset-0 bg-shade transition-opacity duration-500",
cIf(contentPanelScroolbar, "overflow-y-scroll") cIf((mainPanelOpen || subPanelOpen) && is1ColumnLayout, "opacity-60", "opacity-0")
)}>
{isDefined(contentPanel) ? (
contentPanel
) : (
<ContentPlaceholder
message={langui.select_option_sidebar ?? ""}
icon={Icon.ChevronLeft}
/>
)} )}
</div> onClick={() => {
setMainPanelOpen(false);
setSubPanelOpen(false);
}}></div>
</div>
{/* Sub panel */} {/* Content panel */}
{isDefined(subPanel) && ( <div
<div id={Ids.ContentPanel}
id={Ids.SubPanel} className={cJoin(
className={cJoin( "texture-paper-dots bg-light [grid-area:content]",
`texture-paper-dots z-20 overflow-y-scroll border-r-[1px] border-dark/50 cIf(contentPanelScroolbar, "overflow-y-scroll")
)}>
{isDefined(contentPanel) ? (
contentPanel
) : (
<ContentPlaceholder
message={langui.select_option_sidebar ?? ""}
icon={Icon.ChevronLeft}
/>
)}
</div>
{/* Sub panel */}
{isDefined(subPanel) && (
<div
id={Ids.SubPanel}
className={cJoin(
`texture-paper-dots z-20 overflow-y-scroll border-r border-dark/50
bg-light transition-transform duration-300 [scrollbar-width:none] bg-light transition-transform duration-300 [scrollbar-width:none]
webkit-scrollbar:w-0`, webkit-scrollbar:w-0`,
cIf( cIf(
is1ColumnLayout, is1ColumnLayout,
"justify-self-end border-r-0 [grid-area:content]", "justify-self-end border-r-0 [grid-area:content]",
"[grid-area:sub]" "[grid-area:sub]"
), ),
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l-[1px]"), cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l"),
cIf(is1ColumnLayout && !subPanelOpen && !turnSubIntoContent, "translate-x-[100vw]"), cIf(is1ColumnLayout && !subPanelOpen && !turnSubIntoContent, "translate-x-[100vw]"),
cIf(is1ColumnLayout && turnSubIntoContent, "w-full border-l-0") cIf(is1ColumnLayout && turnSubIntoContent, "w-full border-l-0")
)}>
{subPanel}
</div>
)}
{/* Main panel */}
<div
className={cJoin(
`texture-paper-dots z-30 overflow-y-scroll border-r-[1px] border-dark/50
bg-light transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
cIf(is1ColumnLayout, "justify-self-start [grid-area:content]", "[grid-area:main]"),
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full")
)}> )}>
<MainPanel /> {subPanel}
</div> </div>
)}
{/* Navbar */} {/* Main panel */}
<div <div
className={cJoin(
`texture-paper-dots z-30 overflow-y-scroll border-r border-dark/50
bg-light transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
cIf(is1ColumnLayout, "justify-self-start [grid-area:content]", "[grid-area:main]"),
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full")
)}>
<MainPanel />
</div>
{/* Navbar */}
<div
className={cJoin(
`texture-paper-dots z-10 grid grid-cols-[5rem_1fr_5rem] place-items-center
border-t border-dotted border-black bg-light [grid-area:navbar]`,
cIf(!is1ColumnLayout, "hidden")
)}>
<Ico
icon={mainPanelOpen ? Icon.Close : Icon.Menu}
className="cursor-pointer !text-2xl"
onClick={() => {
toggleMainPanelOpen();
setSubPanelOpen(false);
}}
/>
<p
className={cJoin( className={cJoin(
`texture-paper-dots z-10 grid grid-cols-[5rem_1fr_5rem] place-items-center "overflow-hidden text-center font-headers font-black",
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar]`, cIf(openGraph.title.length > 30, "max-h-14 text-xl", "max-h-16 text-2xl")
cIf(!is1ColumnLayout, "hidden")
)}> )}>
{openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
? openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
: "Accords Library"}
</p>
{isDefined(subPanel) && !turnSubIntoContent && (
<Ico <Ico
icon={mainPanelOpen ? Icon.Close : Icon.Menu} icon={subPanelOpen ? Icon.Close : subPanelIcon}
className="cursor-pointer !text-2xl" className="cursor-pointer !text-2xl"
onClick={() => { onClick={() => {
toggleMainPanelOpen(); toggleSubPanelOpen();
setSubPanelOpen(false); setMainPanelOpen(false);
}} }}
/> />
<p )}
className={cJoin(
"overflow-hidden text-center font-headers font-black",
cIf(openGraph.title.length > 30, "max-h-14 text-xl", "max-h-16 text-2xl")
)}>
{openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
? openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
: "Accords Library"}
</p>
{isDefined(subPanel) && !turnSubIntoContent && (
<Ico
icon={subPanelOpen ? Icon.Close : subPanelIcon}
className="cursor-pointer !text-2xl"
onClick={() => {
toggleSubPanelOpen();
setMainPanelOpen(false);
}}
/>
)}
</div>
<Popup state={isSafari && !hasDisgardedSafariWarning} onClose={() => null}>
<h1 className="text-2xl">Hi, you are using Safari!</h1>
<p className="max-w-lg text-center">
In most cases this wouldn&rsquo;t be a problem but our website isfor some obscure
reasonperforming terribly on Safari (WebKit). Because of that, we have decided to
display this message instead of letting you have a slow and painful experience. We are
looking into the problem, and are hoping to fix this soon.
</p>
<p>In the meanwhile, if you are using an iPhone/iPad, please try using another device.</p>
<p>If you are on macOS, please use another browser such as Firefox or Chrome.</p>
<Button
text="Let me in regardless"
className="mt-8"
onClick={() => {
setHasDisgardedSafariWarning(true);
sendAnalytics("Safari", "Disgard warning");
}}
/>
</Popup>
<Popup
state={configPanelOpen}
onClose={() => {
setConfigPanelOpen(false);
sendAnalytics("Settings", "Close settings");
}}>
<h2 className="text-2xl">{langui.settings}</h2>
<div
className={cJoin(
`mt-4 grid justify-items-center gap-16 text-center`,
cIf(!is1ColumnLayout, "grid-cols-[auto_auto]")
)}>
{router.locales && (
<div>
<h3 className="text-xl">{langui.languages}</h3>
{preferredLanguages.length > 0 && (
<OrderableList
items={preferredLanguages.map((locale) => ({
code: locale,
name: prettyLanguage(locale, languages),
}))}
insertLabels={[
{
insertAt: 0,
name: langui.primary_language ?? "Primary language",
},
{
insertAt: 1,
name: langui.secondary_language ?? "Secondary languages",
},
]}
onChange={(items) => {
const newPreferredLanguages = items.map((item) => item.code);
setPreferredLanguages(newPreferredLanguages);
sendAnalytics("Settings", "Change preferred languages");
}}
/>
)}
</div>
)}
<div
className={cJoin(
"grid place-items-center gap-8 text-center",
cIf(!is1ColumnLayout, "grid-cols-2")
)}>
<div>
<h3 className="text-xl">{langui.theme}</h3>
<ButtonGroup
buttonsProps={[
{
onClick: () => {
setDarkMode(false);
setSelectedThemeMode(true);
sendAnalytics("Settings", "Change theme (light)");
},
active: selectedThemeMode && !darkMode,
text: langui.light,
},
{
onClick: () => {
setSelectedThemeMode(false);
sendAnalytics("Settings", "Change theme (auto)");
},
active: !selectedThemeMode,
text: langui.auto,
},
{
onClick: () => {
setDarkMode(true);
setSelectedThemeMode(true);
sendAnalytics("Settings", "Change theme (dark)");
},
active: selectedThemeMode && darkMode,
text: langui.dark,
},
]}
/>
</div>
<div>
<h3 className="text-xl">{langui.currency}</h3>
<div>
<Select
options={currencyOptions}
value={currencySelect}
onChange={(newCurrency) => {
setCurrencySelect(newCurrency);
sendAnalytics(
"Settings",
`Change currency (${currencyOptions[newCurrency]})}`
);
}}
className="w-28"
/>
</div>
</div>
<div>
<h3 className="text-xl">{langui.font_size}</h3>
<ButtonGroup
buttonsProps={[
{
onClick: () => {
setFontSize((current) => current / 1.05);
sendAnalytics(
"Settings",
`Change font size (${((fontSize / 1.05) * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}%)`
);
},
icon: Icon.TextDecrease,
},
{
onClick: () => {
setFontSize(1);
sendAnalytics("Settings", "Change font size (100%)");
},
text: `${(fontSize * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}%`,
},
{
onClick: () => {
setFontSize((current) => current * 1.05);
sendAnalytics(
"Settings",
`Change font size (${(fontSize * 1.05 * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}%)`
);
},
icon: Icon.TextIncrease,
},
]}
/>
</div>
<div>
<h3 className="text-xl">{langui.font}</h3>
<div className="grid gap-2">
<Button
active={!dyslexic}
onClick={() => {
setDyslexic(false);
sendAnalytics("Settings", "Change font (Zen Maru Gothic)");
}}
className="font-zenMaruGothic"
text="Zen Maru Gothic"
/>
<Button
active={dyslexic}
onClick={() => {
setDyslexic(true);
sendAnalytics("Settings", "Change font (OpenDyslexic)");
}}
className="font-openDyslexic"
text="OpenDyslexic"
/>
</div>
</div>
<div>
<h3 className="text-xl">{langui.player_name}</h3>
<TextInput
placeholder="<player>"
className="w-48"
value={playerName}
onChange={(newName) => {
setPlayerName(newName);
sendAnalytics("Settings", "Change username");
}}
/>
</div>
</div>
</div>
</Popup>
</div> </div>
<SafariPopup />
</div> </div>
); );
}; };

View File

@ -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
)}> )}>

View File

@ -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>

View File

@ -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",

View File

@ -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

View File

@ -0,0 +1,16 @@
import { cJoin } from "helpers/className";
interface Props {
src: string;
className?: string;
}
export const ColoredSvg = ({ src, className }: Props): JSX.Element => (
<div
className={cJoin(
`transition-colors ![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center]`,
className
)}
style={{ mask: `url('${src}')`, WebkitMask: `url('${src}')` }}
/>
);

View File

@ -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>
); );

View File

@ -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>
); );

View File

@ -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,

View File

@ -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">

View File

@ -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]}

View File

@ -1,3 +1,4 @@
import { useState } from "react";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
/* /*
@ -14,21 +15,35 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Switch = ({ value, onClick, className, disabled = false }: Props): JSX.Element => ( export const Switch = ({ value, onClick, className, disabled = false }: Props): JSX.Element => {
<div const [isFocused, setIsFocused] = useState(false);
className={cJoin( return (
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
cIf(disabled, "cursor-not-allowed", "cursor-pointer"),
cIf(value, "border-none bg-mid shadow-inner-sm shadow-shade", "bg-light"),
className
)}
onClick={() => {
if (!disabled) onClick();
}}>
<div <div
className={cJoin( className={cJoin(
"absolute aspect-square rounded-full bg-dark transition-transform", `relative grid h-6 w-12 content-center rounded-full border-mid outline
cIf(value, "top-[2px] bottom-[2px] left-[2px] translate-x-[120%]", "top-0 bottom-0 left-0") outline-1 -outline-offset-1 outline-mid transition-colors`,
)}></div> cIf(disabled, "cursor-not-allowed", "cursor-pointer"),
</div> cIf(
); value,
"border-none bg-mid shadow-inner-sm shadow-shade outline-transparent",
"bg-light"
),
className
)}
onClick={() => {
if (!disabled) onClick();
}}
onPointerDown={() => setIsFocused(true)}
onPointerOut={() => setIsFocused(false)}
onPointerLeave={() => setIsFocused(false)}
onPointerUp={() => setIsFocused(false)}>
<div
className={cJoin(
"ml-1 h-4 w-4 rounded-full bg-dark transition-transform",
cIf(value, "translate-x-6"),
cIf(isFocused, cIf(value, "translate-x-5", "translate-x-1"))
)}
/>
</div>
);
};

View File

@ -1,95 +1,136 @@
import { Dispatch, SetStateAction, useCallback } from "react"; import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import Hotkeys from "react-hot-keys"; import { useState } from "react";
import { useSwipeable } from "react-swipeable"; import { useHotkeys } from "react-hotkeys-hook";
import { Img } from "./Img"; import { Img } from "./Img";
import { Button } from "./Inputs/Button"; import { Button } from "./Inputs/Button";
import { Popup } from "./Popup";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { useFullscreen } from "hooks/useFullscreen";
/* import { Ids } from "types/ids";
* import { UploadImageFragment } from "graphql/generated";
* CONSTANTS import { ImageQuality } from "helpers/img";
*/ import { isDefined } from "helpers/others";
const SENSIBILITY_SWIPE = 0.5;
/*
*
* COMPONENT
*/
interface Props { interface Props {
setState: Dispatch<SetStateAction<boolean | undefined>> | Dispatch<SetStateAction<boolean>>; onCloseRequest: () => void;
state: boolean; isVisible: boolean;
images: string[]; image?: UploadImageFragment | string;
index: number; isNextImageAvailable?: boolean;
setIndex: Dispatch<SetStateAction<number>>; isPreviousImageAvailable?: boolean;
onPressNext?: () => void;
onPressPrevious?: () => void;
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const LightBox = ({ state, setState, images, index, setIndex }: Props): JSX.Element => { export const LightBox = ({
const handlePrevious = useCallback(() => { onCloseRequest,
if (index > 0) setIndex(index - 1); isVisible,
}, [index, setIndex]); image: src,
const is3ColumnsLayout = useIs3ColumnsLayout(); isPreviousImageAvailable = false,
onPressPrevious,
isNextImageAvailable = false,
onPressNext,
}: Props): JSX.Element => {
const [currentZoom, setCurrentZoom] = useState(1);
const { isFullscreen, toggleFullscreen, exitFullscreen, requestFullscreen } = useFullscreen(
Ids.LightBox
);
const handleNext = useCallback(() => { useHotkeys(
if (index < images.length - 1) setIndex(index + 1); "left",
}, [images.length, index, setIndex]); () => onPressPrevious?.(),
{ enabled: isVisible && isPreviousImageAvailable },
[onPressPrevious]
);
const handlers = useSwipeable({ useHotkeys("f", () => requestFullscreen(), { enabled: isVisible && !isFullscreen }, [
onSwipedLeft: (SwipeEventData) => { requestFullscreen,
if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return; ]);
handleNext();
}, useHotkeys("right", () => onPressNext?.(), { enabled: isVisible && isNextImageAvailable }, [
onSwipedRight: (SwipeEventData) => { onPressNext,
if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return; ]);
handlePrevious();
}, useHotkeys("escape", onCloseRequest, { enabled: isVisible }, [onCloseRequest]);
});
return ( return (
<> <div
{state && ( id={Ids.LightBox}
<Hotkeys className={cJoin(
keyName="left,right" "fixed inset-0 z-50 grid place-content-center transition-filter duration-500",
allowRepeat cIf(isVisible, "backdrop-blur", "pointer-events-none touch-none")
onKeyDown={(keyName) => { )}>
if (keyName === "left") { <div
handlePrevious(); className={cJoin(
} else { "fixed inset-0 bg-shade transition-all duration-500",
handleNext(); cIf(isVisible, "bg-opacity-50", "bg-opacity-0")
} )}
}}> />
<Popup onClose={() => setState(false)} state={state} padding={false} fillViewport> <div
<div className={cJoin(
{...handlers} "absolute inset-8 grid transition-transform",
className={cJoin( cIf(isVisible, "scale-100", "scale-0")
`grid h-full w-full place-items-center overflow-hidden first-letter:gap-4`, )}>
cIf( <TransformWrapper
is3ColumnsLayout, onZoom={(zoom) => setCurrentZoom(zoom.state.scale)}
`grid-cols-[4em,1fr,4em] [grid-template-areas:"left_image_right"]`, panning={{ disabled: currentZoom <= 1, velocityDisabled: false }}
`grid-cols-2 [grid-template-areas:"image_image""left_right"]` doubleClick={{ disabled: true, mode: "reset" }}
) zoomAnimation={{ size: 0.1 }}
)}> velocityAnimation={{ animationTime: 0, equalToMove: true }}>
<div className="ml-4 [grid-area:left]"> {({ resetTransform }) => (
{index > 0 && <Button onClick={handlePrevious} icon={Icon.ChevronLeft} />} <>
</div> <TransformComponent
wrapperStyle={{
<Img className="max-h-full min-h-fit [grid-area:image]" src={images[index]} /> overflow: "visible",
placeSelf: "center",
<div className="mr-4 [grid-area:right]"> }}>
{index < images.length - 1 && ( {isDefined(src) && (
<Button onClick={handleNext} icon={Icon.ChevronRight} /> <Img
className={`drop-shadow-shade-2xl-shade h-[calc(100vh-4rem)] w-full
object-contain`}
src={src}
quality={ImageQuality.Large}
/>
)} )}
</TransformComponent>
{isPreviousImageAvailable && (
<div
className={`absolute top-1/2 left-0 grid gap-4 rounded-[2rem] p-4
backdrop-blur-lg`}>
<Button icon={Icon.NavigateBefore} onClick={onPressPrevious} />
</div>
)}
{isNextImageAvailable && (
<div
className={`absolute top-1/2 right-0 grid gap-4 rounded-[2rem] p-4
backdrop-blur-lg`}>
<Button icon={Icon.NavigateNext} onClick={onPressNext} />{" "}
</div>
)}
<div
className={`absolute top-0 right-0 grid gap-4 rounded-[2rem] p-4
backdrop-blur-lg`}>
<Button
onClick={() => {
resetTransform();
exitFullscreen();
onCloseRequest();
}}
icon={Icon.Close}
/>
<Button
icon={isFullscreen ? Icon.FullscreenExit : Icon.Fullscreen}
onClick={toggleFullscreen}
/>
</div> </div>
</div> </>
</Popup> )}
</Hotkeys> </TransformWrapper>
)} </div>
</> </div>
); );
}; };

View File

@ -9,14 +9,14 @@ import { cIf, cJoin } from "helpers/className";
import { slugify } from "helpers/formatters"; import { slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img"; import { getAssetURL, ImageQuality } from "helpers/img";
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others"; import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
import { AnchorShare } from "components/AnchorShare"; import { AnchorShare } from "components/AnchorShare";
import { useIntersectionList } from "hooks/useIntersectionList"; import { useIntersectionList } from "hooks/useIntersectionList";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { useDeviceSupportsHover } from "hooks/useMediaQuery"; import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { useUserSettings } from "contexts/UserSettingsContext"; import { useUserSettings } from "contexts/UserSettingsContext";
import { useLocalData } from "contexts/LocalDataContext"; import { useLocalData } from "contexts/LocalDataContext";
import { useContainerQueries } from "contexts/ContainerQueriesContext";
import { useLightBox } from "contexts/LightBoxContext";
/* /*
* *
@ -33,8 +33,8 @@ interface MarkdawnProps {
export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Element => { export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Element => {
const { playerName } = useUserSettings(); const { playerName } = useUserSettings();
const router = useRouter(); const router = useRouter();
const isContentPanelAtLeastLg = useIsContentPanelAtLeast("lg"); const { isContentPanelAtLeastLg } = useContainerQueries();
const [openLightBox, LightBox] = useLightBox(); const { showLightBox } = useLightBox();
/* eslint-disable no-irregular-whitespace */ /* eslint-disable no-irregular-whitespace */
const text = useMemo( const text = useMemo(
@ -49,175 +49,169 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
} }
return ( return (
<> <Markdown
<LightBox /> className={cJoin("formatted", className)}
<Markdown options={{
className={cJoin("formatted", className)} slugify: slugify,
options={{ overrides: {
slugify: slugify, a: {
overrides: { component: (compProps: { href: string; children: React.ReactNode }) => {
a: { if (compProps.href.startsWith("/") || compProps.href.startsWith("#")) {
component: (compProps: { href: string; children: React.ReactNode }) => {
if (compProps.href.startsWith("/") || compProps.href.startsWith("#")) {
return (
<a onClick={async () => router.push(compProps.href)}>{compProps.children}</a>
);
}
return ( return (
<a href={compProps.href} target="_blank" rel="noreferrer"> <a onClick={async () => router.push(compProps.href)}>{compProps.children}</a>
{compProps.children}
</a>
); );
}, }
}, return (
<a href={compProps.href} target="_blank" rel="noreferrer">
Header: {
component: (compProps: {
id: string;
style: React.CSSProperties;
children: string;
level: string;
}) => (
<Header
title={compProps.children}
level={parseInt(compProps.level, 10)}
slug={compProps.id}
/>
),
},
SceneBreak: {
component: (compProps: { id: string }) => (
<Header title={"* * *"} level={6} slug={compProps.id} />
),
},
IntraLink: {
component: (compProps: {
children: React.ReactNode;
target?: string;
page?: string;
}) => {
const slug = isDefinedAndNotEmpty(compProps.target)
? slugify(compProps.target)
: slugify(compProps.children?.toString());
return (
<a onClick={async () => router.replace(`${compProps.page ?? ""}#${slug}`)}>
{compProps.children}
</a>
);
},
},
Transcript: {
component: (compProps) => (
<div
className={cJoin(
"grid gap-x-6 gap-y-2",
cIf(isContentPanelAtLeastLg, "grid-cols-[auto_1fr]", "grid-cols-1")
)}>
{compProps.children} {compProps.children}
</div> </a>
), );
},
Line: {
component: (compProps) => (
<>
<strong
className={cJoin(
"!my-0 text-dark/60",
cIf(!isContentPanelAtLeastLg, "!-mb-4")
)}>
<Markdawn text={compProps.name} />
</strong>
<p className="whitespace-pre-line">{compProps.children}</p>
</>
),
},
InsetBox: {
component: (compProps) => <InsetBox className="my-12">{compProps.children}</InsetBox>,
},
li: {
component: (compProps: { children: React.ReactNode }) => (
<li
className={
isDefined(compProps.children) &&
ReactDOMServer.renderToStaticMarkup(<>{compProps.children}</>).length > 100
? "my-4"
: ""
}>
{compProps.children}
</li>
),
},
Highlight: {
component: (compProps: { children: React.ReactNode }) => (
<mark>{compProps.children}</mark>
),
},
footer: {
component: (compProps: { children: React.ReactNode }) => (
<>
<HorizontalLine />
<div className="grid gap-8">{compProps.children}</div>
</>
),
},
blockquote: {
component: (compProps: { children: React.ReactNode; cite?: string }) => (
<blockquote>
{isDefinedAndNotEmpty(compProps.cite) ? (
<>
&ldquo;{compProps.children}&rdquo;
<cite> {compProps.cite}</cite>
</>
) : (
compProps.children
)}
</blockquote>
),
},
img: {
component: (compProps: {
alt: string;
src: string;
width?: number;
height?: number;
caption?: string;
name?: string;
}) => (
<div
className="mt-8 mb-12 grid cursor-pointer place-content-center"
onClick={() => {
openLightBox([
compProps.src.startsWith("/uploads/")
? getAssetURL(compProps.src, ImageQuality.Large)
: compProps.src,
]);
}}>
<Img
src={
compProps.src.startsWith("/uploads/")
? getAssetURL(compProps.src, ImageQuality.Small)
: compProps.src
}
quality={ImageQuality.Medium}
className="drop-shadow-shade-lg"></Img>
</div>
),
}, },
}, },
}}>
{text} Header: {
</Markdown> component: (compProps: {
</> id: string;
style: React.CSSProperties;
children: string;
level: string;
}) => (
<Header
title={compProps.children}
level={parseInt(compProps.level, 10)}
slug={compProps.id}
/>
),
},
SceneBreak: {
component: (compProps: { id: string }) => (
<Header title={"* * *"} level={6} slug={compProps.id} />
),
},
IntraLink: {
component: (compProps: {
children: React.ReactNode;
target?: string;
page?: string;
}) => {
const slug = isDefinedAndNotEmpty(compProps.target)
? slugify(compProps.target)
: slugify(compProps.children?.toString());
return (
<a onClick={async () => router.replace(`${compProps.page ?? ""}#${slug}`)}>
{compProps.children}
</a>
);
},
},
Transcript: {
component: (compProps) => (
<div
className={cJoin(
"grid gap-x-6 gap-y-2",
cIf(isContentPanelAtLeastLg, "grid-cols-[auto_1fr]", "grid-cols-1")
)}>
{compProps.children}
</div>
),
},
Line: {
component: (compProps) => (
<>
<strong
className={cJoin("!my-0 text-dark/60", cIf(!isContentPanelAtLeastLg, "!-mb-4"))}>
<Markdawn text={compProps.name} />
</strong>
<p className="whitespace-pre-line">{compProps.children}</p>
</>
),
},
InsetBox: {
component: (compProps) => <InsetBox className="my-12">{compProps.children}</InsetBox>,
},
li: {
component: (compProps: { children: React.ReactNode }) => (
<li
className={
isDefined(compProps.children) &&
ReactDOMServer.renderToStaticMarkup(<>{compProps.children}</>).length > 100
? "my-4"
: ""
}>
{compProps.children}
</li>
),
},
Highlight: {
component: (compProps: { children: React.ReactNode }) => (
<mark>{compProps.children}</mark>
),
},
footer: {
component: (compProps: { children: React.ReactNode }) => (
<>
<HorizontalLine />
<div className="grid gap-8">{compProps.children}</div>
</>
),
},
blockquote: {
component: (compProps: { children: React.ReactNode; cite?: string }) => (
<blockquote>
{isDefinedAndNotEmpty(compProps.cite) ? (
<>
&ldquo;{compProps.children}&rdquo;
<cite> {compProps.cite}</cite>
</>
) : (
compProps.children
)}
</blockquote>
),
},
img: {
component: (compProps: {
alt: string;
src: string;
width?: number;
height?: number;
caption?: string;
name?: string;
}) => (
<div
className="mt-8 mb-12 grid cursor-pointer place-content-center"
onClick={() => {
showLightBox([
compProps.src.startsWith("/uploads/")
? getAssetURL(compProps.src, ImageQuality.Large)
: compProps.src,
]);
}}>
<Img
src={
compProps.src.startsWith("/uploads/")
? getAssetURL(compProps.src, ImageQuality.Small)
: compProps.src
}
quality={ImageQuality.Medium}
className="drop-shadow-shade-lg"></Img>
</div>
),
},
},
}}>
{text}
</Markdown>
); );
}; };

View File

@ -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>

View File

@ -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 (
<> <>

View File

@ -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

View File

@ -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>

View File

@ -0,0 +1,45 @@
import { useMemo } from "react";
import UAParser from "ua-parser-js";
import { useIsClient, useSessionStorage } from "usehooks-ts";
import { Button } from "components/Inputs/Button";
import { Popup } from "components/Popup";
import { sendAnalytics } from "helpers/analytics";
export const SafariPopup = (): JSX.Element => {
const [hasDisgardedSafariWarning, setHasDisgardedSafariWarning] = useSessionStorage(
"hasDisgardedSafariWarning",
false
);
const isClient = useIsClient();
const isSafari = useMemo<boolean>(() => {
if (isClient) {
const parser = new UAParser();
return parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS";
}
return false;
}, [isClient]);
return (
<Popup isVisible={isSafari && !hasDisgardedSafariWarning}>
<h1 className="text-2xl">Hi, you are using Safari!</h1>
<p className="max-w-lg text-center">
In most cases this wouldn&rsquo;t be a problem but our website isfor some obscure
reasonperforming 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>
);
};

View File

@ -0,0 +1,237 @@
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
import { Icon } from "components/Ico";
import { Button } from "components/Inputs/Button";
import { ButtonGroup } from "components/Inputs/ButtonGroup";
import { OrderableList } from "components/Inputs/OrderableList";
import { Select } from "components/Inputs/Select";
import { TextInput } from "components/Inputs/TextInput";
import { Popup } from "components/Popup";
import { useAppLayout } from "contexts/AppLayoutContext";
import { useLocalData } from "contexts/LocalDataContext";
import { useUserSettings } from "contexts/UserSettingsContext";
import { sendAnalytics } from "helpers/analytics";
import { cJoin, cIf } from "helpers/className";
import { prettyLanguage } from "helpers/formatters";
import { filterHasAttributes, isDefined } from "helpers/others";
import { useContainerQueries } from "contexts/ContainerQueriesContext";
export const SettingsPopup = (): JSX.Element => {
const {
currency,
darkMode,
dyslexic,
fontSize,
playerName,
preferredLanguages,
selectedThemeMode,
setCurrency,
setDarkMode,
setDyslexic,
setFontSize,
setPlayerName,
setPreferredLanguages,
setSelectedThemeMode,
} = useUserSettings();
const { langui, currencies, languages } = useLocalData();
const { is1ColumnLayout } = useContainerQueries();
const router = useRouter();
const { configPanelOpen, setConfigPanelOpen } = useAppLayout();
const currencyOptions = useMemo(
() =>
filterHasAttributes(currencies, ["attributes"] as const).map(
(currentCurrency) => currentCurrency.attributes.code
),
[currencies]
);
const [currencySelect, setCurrencySelect] = useState<number>(-1);
useEffect(() => {
if (isDefined(currency)) setCurrencySelect(currencyOptions.indexOf(currency));
}, [currency, currencyOptions]);
useEffect(() => {
if (currencySelect >= 0) setCurrency(currencyOptions[currencySelect]);
}, [currencyOptions, currencySelect, setCurrency]);
return (
<Popup
isVisible={configPanelOpen}
onCloseRequest={() => {
setConfigPanelOpen(false);
sendAnalytics("Settings", "Close settings");
}}>
<h2 className="text-2xl">{langui.settings}</h2>
<div
className={cJoin(
`mt-4 grid justify-items-center gap-16 text-center`,
cIf(!is1ColumnLayout, "grid-cols-[auto_auto]")
)}>
{router.locales && (
<div>
<h3 className="text-xl">{langui.languages}</h3>
{preferredLanguages.length > 0 && (
<OrderableList
items={preferredLanguages.map((locale) => ({
code: locale,
name: prettyLanguage(locale, languages),
}))}
insertLabels={[
{
insertAt: 0,
name: langui.primary_language ?? "Primary language",
},
{
insertAt: 1,
name: langui.secondary_language ?? "Secondary languages",
},
]}
onChange={(items) => {
const newPreferredLanguages = items.map((item) => item.code);
setPreferredLanguages(newPreferredLanguages);
sendAnalytics("Settings", "Change preferred languages");
}}
/>
)}
</div>
)}
<div
className={cJoin(
"grid place-items-center gap-8 text-center",
cIf(!is1ColumnLayout, "grid-cols-2")
)}>
<div>
<h3 className="text-xl">{langui.theme}</h3>
<ButtonGroup
buttonsProps={[
{
onClick: () => {
setDarkMode(false);
setSelectedThemeMode(true);
sendAnalytics("Settings", "Change theme (light)");
},
active: selectedThemeMode && !darkMode,
text: langui.light,
},
{
onClick: () => {
setSelectedThemeMode(false);
sendAnalytics("Settings", "Change theme (auto)");
},
active: !selectedThemeMode,
text: langui.auto,
},
{
onClick: () => {
setDarkMode(true);
setSelectedThemeMode(true);
sendAnalytics("Settings", "Change theme (dark)");
},
active: selectedThemeMode && darkMode,
text: langui.dark,
},
]}
/>
</div>
<div>
<h3 className="text-xl">{langui.currency}</h3>
<div>
<Select
options={currencyOptions}
value={currencySelect}
onChange={(newCurrency) => {
setCurrencySelect(newCurrency);
sendAnalytics("Settings", `Change currency (${currencyOptions[newCurrency]})}`);
}}
className="w-28"
/>
</div>
</div>
<div>
<h3 className="text-xl">{langui.font_size}</h3>
<ButtonGroup
buttonsProps={[
{
onClick: () => {
setFontSize((current) => current / 1.05);
sendAnalytics(
"Settings",
`Change font size (${((fontSize / 1.05) * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}%)`
);
},
icon: Icon.TextDecrease,
},
{
onClick: () => {
setFontSize(1);
sendAnalytics("Settings", "Change font size (100%)");
},
text: `${(fontSize * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}%`,
},
{
onClick: () => {
setFontSize((current) => current * 1.05);
sendAnalytics(
"Settings",
`Change font size (${(fontSize * 1.05 * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}%)`
);
},
icon: Icon.TextIncrease,
},
]}
/>
</div>
<div>
<h3 className="text-xl">{langui.font}</h3>
<div className="grid gap-2">
<Button
active={!dyslexic}
onClick={() => {
setDyslexic(false);
sendAnalytics("Settings", "Change font (Zen Maru Gothic)");
}}
className="font-zenMaruGothic"
text="Zen Maru Gothic"
/>
<Button
active={dyslexic}
onClick={() => {
setDyslexic(true);
sendAnalytics("Settings", "Change font (OpenDyslexic)");
}}
className="font-openDyslexic"
text="OpenDyslexic"
/>
</div>
</div>
<div>
<h3 className="text-xl">{langui.player_name}</h3>
<TextInput
placeholder="<player>"
className="w-48"
value={playerName}
onChange={(newName) => {
setPlayerName(newName);
sendAnalytics("Settings", "Change username");
}}
/>
</div>
</div>
</div>
</Popup>
);
};

View File

@ -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>

View File

@ -1,5 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import Hotkeys from "react-hot-keys"; import { useHotkeys } from "react-hotkeys-hook";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
@ -9,8 +9,8 @@ import { cIf, cJoin } from "helpers/className";
*/ */
interface Props { interface Props {
onClose: () => void; onCloseRequest?: () => void;
state: boolean; isVisible: boolean;
children: React.ReactNode; children: React.ReactNode;
fillViewport?: boolean; fillViewport?: boolean;
hideBackground?: boolean; hideBackground?: boolean;
@ -20,8 +20,8 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Popup = ({ export const Popup = ({
onClose, onCloseRequest,
state, isVisible,
children, children,
fillViewport, fillViewport,
hideBackground = false, hideBackground = false,
@ -29,36 +29,36 @@ export const Popup = ({
}: Props): JSX.Element => { }: Props): JSX.Element => {
const { setMenuGestures } = useAppLayout(); const { setMenuGestures } = useAppLayout();
useHotkeys("escape", () => onCloseRequest?.(), {}, [onCloseRequest]);
useEffect(() => { useEffect(() => {
setMenuGestures(!state); setMenuGestures(!isVisible);
}, [setMenuGestures, state]); }, [setMenuGestures, isVisible]);
return ( return (
<Hotkeys keyName="escape" allowRepeat onKeyDown={onClose}> <div
className={cJoin(
"fixed inset-0 z-50 grid place-content-center transition-filter duration-500",
cIf(isVisible, "backdrop-blur", "pointer-events-none touch-none")
)}>
<div <div
className={cJoin( className={cJoin(
"fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500", "fixed inset-0 bg-shade transition-all duration-500",
cIf(state, "[backdrop-filter:blur(2px)]", "pointer-events-none touch-none") cIf(isVisible, "bg-opacity-50", "bg-opacity-0")
)}> )}
<div onClick={onCloseRequest}
className={cJoin( />
"fixed inset-0 bg-shade transition-all duration-500",
cIf(state, "bg-opacity-50", "bg-opacity-0")
)}
onClick={onClose}
/>
<div <div
className={cJoin( className={cJoin(
"grid place-items-center gap-4 transition-transform", "grid place-items-center gap-4 transition-transform",
cIf(padding, "p-10"), cIf(padding, "p-10"),
cIf(state, "scale-100", "scale-0"), cIf(isVisible, "scale-100", "scale-0"),
cIf(fillViewport, "absolute inset-10", "relative max-h-[80vh] overflow-y-auto"), cIf(fillViewport, "absolute inset-10", "relative max-h-[80vh] overflow-y-auto"),
cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade") cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade")
)}> )}>
{children} {children}
</div>
</div> </div>
</Hotkeys> </div>
); );
}; };

View File

@ -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(

View File

@ -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">

View File

@ -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>

View File

@ -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} />

View File

@ -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,

View File

@ -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;
};

View File

@ -0,0 +1,58 @@
import React, { createContext, ReactNode, useCallback, useContext, useState } from "react";
import { UploadImageFragment } from "graphql/generated";
import { RequiredNonNullable } from "types/types";
import { LightBox } from "components/LightBox";
import { filterDefined } from "helpers/others";
type LightBoxImagesNullable = (UploadImageFragment | string | null | undefined)[];
type LightBoxImages = (UploadImageFragment | string)[];
interface LightBoxState {
showLightBox: (images: LightBoxImagesNullable, index?: number) => void;
}
const initialState: RequiredNonNullable<LightBoxState> = {
showLightBox: () => null,
};
const LightBoxContext = createContext<LightBoxState>(initialState);
export const useLightBox = (): LightBoxState => useContext(LightBoxContext);
interface Props {
children: ReactNode;
}
export const LightBoxContextProvider = ({ children }: Props): JSX.Element => {
const [isLightBoxVisible, setLightBoxVisibility] = useState(false);
const [lightBoxImages, setLightBoxImages] = useState<LightBoxImages>([]);
const [lightBoxIndex, setLightBoxIndex] = useState(0);
const showLightBox = useCallback((images: LightBoxImagesNullable, index = 0) => {
const filteredImages = filterDefined(images);
setLightBoxIndex(index);
setLightBoxImages(filteredImages);
setLightBoxVisibility(true);
}, []);
const closeLightBox = useCallback(() => {
setLightBoxVisibility(false);
setTimeout(() => setLightBoxImages([]), 100);
}, []);
return (
<LightBoxContext.Provider value={{ showLightBox }}>
<LightBox
isVisible={isLightBoxVisible}
onCloseRequest={closeLightBox}
image={lightBoxImages[lightBoxIndex]}
isNextImageAvailable={lightBoxIndex < lightBoxImages.length - 1}
isPreviousImageAvailable={lightBoxIndex > 0}
onPressNext={() => setLightBoxIndex((current) => current + 1)}
onPressPrevious={() => setLightBoxIndex((current) => current - 1)}
/>
{children}
</LightBoxContext.Provider>
);
};

View File

@ -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>
); );

View File

@ -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;
};

View File

@ -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 };
}; };

View File

@ -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}
/>
),
];
};

View File

@ -1,29 +1,49 @@
import "@fontsource/material-icons"; import "@fontsource/material-icons";
import "@fontsource/material-icons-outlined";
import "@fontsource/opendyslexic/400.css"; import "@fontsource/opendyslexic/400.css";
import "@fontsource/share-tech-mono/400.css"; import "@fontsource/share-tech-mono/400.css";
import "@fontsource/opendyslexic/700.css"; import "@fontsource/opendyslexic/700.css";
import "@fontsource/vollkorn/700.css"; import "@fontsource/vollkorn/700.css";
import "@fontsource/zen-maru-gothic/500.css"; import "@fontsource/zen-maru-gothic/500.css";
import "@fontsource/zen-maru-gothic/900.css"; import "@fontsource/zen-maru-gothic/900.css";
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
import Script from "next/script";
import { AppContextProvider } from "contexts/AppLayoutContext"; import { AppContextProvider } from "contexts/AppLayoutContext";
import "tailwind.css";
import "styles/animations.css";
import "styles/custom-classes.css";
import "styles/debug.css";
import "styles/formatted.css";
import "styles/others.css";
import "styles/rc-slider.css";
import "styles/tippy.css";
import { TerminalContextProvider } from "contexts/TerminalContext"; import { TerminalContextProvider } from "contexts/TerminalContext";
import { UserSettingsProvider as UserSettingsContextProvider } from "contexts/UserSettingsContext"; import { UserSettingsProvider as UserSettingsContextProvider } from "contexts/UserSettingsContext";
import { LocalDataContextProvider } from "contexts/LocalDataContext"; import { LocalDataContextProvider } from "contexts/LocalDataContext";
import { ContainerQueriesContextProvider } from "contexts/ContainerQueriesContext"; import { ContainerQueriesContextProvider } from "contexts/ContainerQueriesContext";
import { LightBoxContextProvider } from "contexts/LightBoxContext";
const AccordsLibraryApp = (props: AppProps): JSX.Element => ( const AccordsLibraryApp = (props: AppProps): JSX.Element => (
<AppContextProvider> <LocalDataContextProvider>
<ContainerQueriesContextProvider> <AppContextProvider>
<LocalDataContextProvider> <UserSettingsContextProvider>
<UserSettingsContextProvider> <ContainerQueriesContextProvider>
<TerminalContextProvider> <TerminalContextProvider>
<props.Component {...props.pageProps} /> <LightBoxContextProvider>
<Script
async
defer
data-website-id={process.env.NEXT_PUBLIC_UMAMI_ID}
src={`${process.env.NEXT_PUBLIC_UMAMI_URL}/umami.js`}
/>
<props.Component {...props.pageProps} />
</LightBoxContextProvider>
</TerminalContextProvider> </TerminalContextProvider>
</UserSettingsContextProvider> </ContainerQueriesContextProvider>
</LocalDataContextProvider> </UserSettingsContextProvider>
</ContainerQueriesContextProvider> </AppContextProvider>
</AppContextProvider> </LocalDataContextProvider>
); );
export default AccordsLibraryApp; export default AccordsLibraryApp;

View File

@ -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");

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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({

View File

@ -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

View File

@ -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(
() => ( () => (

View File

@ -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>

View File

@ -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,
] ]
); );

View File

@ -1,6 +1,6 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; import { Fragment, useCallback, useEffect, useMemo, useState } from "react";
import Hotkeys from "react-hot-keys"; import { useHotkeys } from "react-hotkeys-hook";
import Slider from "rc-slider"; import Slider from "rc-slider";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
@ -23,7 +23,6 @@ import { getLangui } from "graphql/fetchLocalData";
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel"; import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import { Img } from "components/Img"; import { Img } from "components/Img";
import { getAssetFilename, ImageQuality } from "helpers/img"; import { getAssetFilename, ImageQuality } from "helpers/img";
import { useIs1ColumnLayout, useIsContentPanelNoMoreThan } from "hooks/useContainerQuery";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { clamp, isInteger } from "helpers/numbers"; import { clamp, isInteger } from "helpers/numbers";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
@ -45,6 +44,7 @@ import { useFullscreen } from "hooks/useFullscreen";
import { useUserSettings } from "contexts/UserSettingsContext"; import { useUserSettings } from "contexts/UserSettingsContext";
import { useLocalData } from "contexts/LocalDataContext"; import { useLocalData } from "contexts/LocalDataContext";
import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings"; import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings";
import { useContainerQueries } from "contexts/ContainerQueriesContext";
const CUSTOM_DARK_DROPSHADOW = ` const CUSTOM_DARK_DROPSHADOW = `
drop-shadow(0 0 0.5em rgb(var(--theme-color-shade) / 30%)) drop-shadow(0 0 0.5em rgb(var(--theme-color-shade) / 30%))
@ -90,7 +90,7 @@ const LibrarySlug = ({
item, item,
...otherProps ...otherProps
}: Props): JSX.Element => { }: Props): JSX.Element => {
const is1ColumnLayout = useIs1ColumnLayout(); const { is1ColumnLayout } = useContainerQueries();
const { langui } = useLocalData(); const { langui } = useLocalData();
const { darkMode } = useUserSettings(); const { darkMode } = useUserSettings();
const { const {
@ -114,7 +114,7 @@ const LibrarySlug = ({
); );
const router = useRouter(); const router = useRouter();
const { isFullscreen, toggleFullscreen } = useFullscreen(Ids.ContentPanel); const { isFullscreen, toggleFullscreen, requestFullscreen } = useFullscreen(Ids.ContentPanel);
const effectiveDisplayMode = useMemo( const effectiveDisplayMode = useMemo(
() => () =>
@ -173,6 +173,19 @@ const LibrarySlug = ({
[changeCurrentPageIndex, effectiveDisplayMode, pageOrder] [changeCurrentPageIndex, effectiveDisplayMode, pageOrder]
); );
useHotkeys("left", () => handlePageNavigation("left"), { enabled: !isGalleryMode }, [
handlePageNavigation,
]);
useHotkeys("up", () => setIsGalleryMode(true), { enabled: !isGalleryMode }, [setIsGalleryMode]);
useHotkeys("down", () => setIsGalleryMode(false), { enabled: isGalleryMode }, [setIsGalleryMode]);
useHotkeys("f", () => requestFullscreen(), { enabled: !isFullscreen }, [requestFullscreen]);
useHotkeys("right", () => handlePageNavigation("right"), { enabled: !isGalleryMode }, [
handlePageNavigation,
]);
const firstPage = useMemo( const firstPage = useMemo(
() => () =>
pages[ pages[
@ -426,121 +439,114 @@ const LibrarySlug = ({
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full} className="grid place-content-center !p-0"> <ContentPanel width={ContentPanelWidthSizes.Full} className="grid place-content-center !p-0">
<div className={cJoin("mb-12 grid", cIf(is1ColumnLayout, "!p-0", "!p-8"))}> <div className={cJoin("mb-12 grid", cIf(is1ColumnLayout, "!p-0", "!p-8"))}>
<Hotkeys <TransformWrapper
keyName="left,right" onZoom={(zoom) => setCurrentZoom(zoom.state.scale)}
allowRepeat panning={{ disabled: currentZoom <= 1, velocityDisabled: false }}
onKeyDown={(keyName) => { doubleClick={{ disabled: true, mode: "reset" }}
handlePageNavigation(keyName as "left" | "right"); zoomAnimation={{ size: 0.1 }}
}}> velocityAnimation={{ animationTime: 0, equalToMove: true }}>
<TransformWrapper <TransformComponent
onZoom={(zoom) => setCurrentZoom(zoom.state.scale)} wrapperStyle={{ overflow: "visible", placeSelf: "center" }}
panning={{ disabled: currentZoom <= 1, velocityDisabled: false }} contentStyle={{
doubleClick={{ disabled: true, mode: "reset" }} height: "100%",
zoomAnimation={{ size: 0.1 }} gridAutoFlow: "column",
velocityAnimation={{ animationTime: 0, equalToMove: true }}> display: "grid",
<TransformComponent placeContent: "center",
wrapperStyle={{ overflow: "visible", placeSelf: "center" }} filter: filterSettings.dropShadow
contentStyle={{ ? darkMode
height: "100%", ? CUSTOM_DARK_DROPSHADOW
gridAutoFlow: "column", : CUSTOM_LIGHT_DROPSHADOW
display: "grid", : undefined,
placeContent: "center", }}>
filter: filterSettings.dropShadow {effectiveDisplayMode === "single" ? (
? darkMode <div
? CUSTOM_DARK_DROPSHADOW className={cJoin(
: CUSTOM_LIGHT_DROPSHADOW "relative grid grid-flow-col",
: undefined, cIf(currentZoom <= 1, "cursor-pointer", "cursor-move")
}}> )}>
{effectiveDisplayMode === "single" ? ( <Img
style={{ maxHeight: pageHeight, width: "auto" }}
src={firstPage}
quality={pageQuality}
/>
<PageFilters page="single" bookType={bookType} options={filterSettings} />
<div
className="absolute left-0 top-0 bottom-0 w-1/2"
onClick={() => currentZoom <= 1 && handlePageNavigation("left")}
/>
<div
className="absolute right-0 top-0 bottom-0 w-1/2"
onClick={() => currentZoom <= 1 && handlePageNavigation("right")}
/>
</div>
) : (
<>
<div <div
className={cJoin( className={cJoin(
"relative grid grid-flow-col", "relative grid grid-flow-col",
cIf(currentZoom <= 1, "cursor-pointer", "cursor-move") cIf(currentZoom <= 1, "cursor-pointer", "cursor-move")
)}> )}
onClick={() => currentZoom <= 1 && handlePageNavigation("left")}
style={{
clipPath: leftSideClipPath,
}}>
{isSidePagesEnabled && (
<div
style={{
width: leftSidePagesWidth,
backgroundImage: `url(/reader/sidepages-${bookType}.webp)`,
backgroundSize: `${
(SIDEPAGES_PAGE_COUNT_ON_TEXTURE / leftSidePagesCount) * 100
}% 100%`,
}}
/>
)}
<Img <Img
style={{ maxHeight: pageHeight, width: "auto" }} style={{ maxHeight: pageHeight, width: "auto" }}
src={firstPage} src={pageOrder === PageOrder.LeftToRight ? firstPage : secondPage}
quality={pageQuality} quality={pageQuality}
/> />
<PageFilters page="single" bookType={bookType} options={filterSettings} /> <PageFilters page="left" bookType={bookType} options={filterSettings} />
<div
className="absolute left-0 top-0 bottom-0 w-1/2"
onClick={() => currentZoom <= 1 && handlePageNavigation("left")}
/>
<div
className="absolute right-0 top-0 bottom-0 w-1/2"
onClick={() => currentZoom <= 1 && handlePageNavigation("right")}
/>
</div> </div>
) : ( <div
<> className={cJoin(
<div "relative grid grid-flow-col",
className={cJoin( cIf(currentZoom <= 1, "cursor-pointer", "cursor-move")
"relative grid grid-flow-col", )}
cIf(currentZoom <= 1, "cursor-pointer", "cursor-move") onClick={() => currentZoom <= 1 && handlePageNavigation("right")}
style={{
clipPath: rightSideClipPath,
}}>
<Img
style={{ maxHeight: pageHeight, width: "auto" }}
className={cIf(
is1ColumnLayout,
`max-h-[calc(100vh-5rem)]`,
"max-h-[calc(100vh-4rem)]"
)} )}
onClick={() => currentZoom <= 1 && handlePageNavigation("left")} src={pageOrder === PageOrder.LeftToRight ? secondPage : firstPage}
style={{ quality={pageQuality}
clipPath: leftSideClipPath, />
}}> {isSidePagesEnabled && (
{isSidePagesEnabled && ( <div
<div style={{
style={{ width: rightSidePagesWidth,
width: leftSidePagesWidth, backgroundImage: `url(/reader/sidepages-${bookType}.webp)`,
backgroundImage: `url(/reader/sidepages-${bookType}.webp)`, backgroundPositionX: "right",
backgroundSize: `${ backgroundSize: `${
(SIDEPAGES_PAGE_COUNT_ON_TEXTURE / leftSidePagesCount) * 100 (SIDEPAGES_PAGE_COUNT_ON_TEXTURE / rightSidePagesCount) * 100
}% 100%`, }% 100%`,
}} }}
/>
)}
<Img
style={{ maxHeight: pageHeight, width: "auto" }}
src={pageOrder === PageOrder.LeftToRight ? firstPage : secondPage}
quality={pageQuality}
/> />
<PageFilters page="left" bookType={bookType} options={filterSettings} /> )}
</div>
<div
className={cJoin(
"relative grid grid-flow-col",
cIf(currentZoom <= 1, "cursor-pointer", "cursor-move")
)}
onClick={() => currentZoom <= 1 && handlePageNavigation("right")}
style={{
clipPath: rightSideClipPath,
}}>
<Img
style={{ maxHeight: pageHeight, width: "auto" }}
className={cIf(
is1ColumnLayout,
`max-h-[calc(100vh-5rem)]`,
"max-h-[calc(100vh-4rem)]"
)}
src={pageOrder === PageOrder.LeftToRight ? secondPage : firstPage}
quality={pageQuality}
/>
{isSidePagesEnabled && (
<div
style={{
width: rightSidePagesWidth,
backgroundImage: `url(/reader/sidepages-${bookType}.webp)`,
backgroundPositionX: "right",
backgroundSize: `${
(SIDEPAGES_PAGE_COUNT_ON_TEXTURE / rightSidePagesCount) * 100
}% 100%`,
}}
/>
)}
<PageFilters page="right" bookType={bookType} options={filterSettings} /> <PageFilters page="right" bookType={bookType} options={filterSettings} />
</div> </div>
</> </>
)} )}
</TransformComponent> </TransformComponent>
</TransformWrapper> </TransformWrapper>
</Hotkeys>
</div> </div>
<div <div
className={cJoin( className={cJoin(
@ -883,7 +889,7 @@ interface ScanSetProps {
} }
const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps): JSX.Element => { const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps): JSX.Element => {
const is1ColumnLayout = useIsContentPanelNoMoreThan("2xl"); const { is1ColumnLayout } = useContainerQueries();
const { langui } = useLocalData(); const { langui } = useLocalData();
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: scanSet, items: scanSet,
@ -1028,8 +1034,8 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps):
<div <div
className={cJoin( className={cJoin(
`grid items-end gap-8 border-b-[3px] border-dotted pb-12 `grid items-end gap-8 border-b-2 border-dotted pb-12
last-of-type:border-0`, last-of-type:border-0`,
cIf( cIf(
is1ColumnLayout, is1ColumnLayout,
"grid-cols-2 gap-[4vmin]", "grid-cols-2 gap-[4vmin]",

View File

@ -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);

View File

@ -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);

View File

@ -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,
] ]
); );

View File

@ -22,13 +22,13 @@ import { SelectiveNonNullable } from "types/SelectiveNonNullable";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { getOpenGraph } from "helpers/openGraph"; import { getOpenGraph } from "helpers/openGraph";
import { TranslatedPreviewCard } from "components/PreviewCard"; import { TranslatedPreviewCard } from "components/PreviewCard";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { cIf } from "helpers/className"; import { cIf } from "helpers/className";
import { getLangui } from "graphql/fetchLocalData"; import { getLangui } from "graphql/fetchLocalData";
import { sendAnalytics } from "helpers/analytics"; import { sendAnalytics } from "helpers/analytics";
import { Terminal } from "components/Cli/Terminal"; import { Terminal } from "components/Cli/Terminal";
import { useIsTerminalMode } from "hooks/useIsTerminalMode"; import { useIsTerminalMode } from "hooks/useIsTerminalMode";
import { useLocalData } from "contexts/LocalDataContext"; import { useLocalData } from "contexts/LocalDataContext";
import { useContainerQueries } from "contexts/ContainerQueriesContext";
/* /*
* *
@ -53,7 +53,7 @@ interface Props extends AppLayoutRequired {
const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => { const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => {
const hoverable = useDeviceSupportsHover(); const hoverable = useDeviceSupportsHover();
const { langui } = useLocalData(); const { langui } = useLocalData();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); const { isContentPanelAtLeast4xl } = useContainerQueries();
const isTerminalMode = useIsTerminalMode(); const isTerminalMode = useIsTerminalMode();
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);

32
src/styles/animations.css Normal file
View File

@ -0,0 +1,32 @@
.animation-carret {
animation-name: blink;
animation-duration: 1s;
animation-timing-function: step-end;
animation-iteration-count: infinite;
}
.animate-zoom-in {
animation-name: zoom-in;
animation-duration: 2s;
animation-timing-function: ease-in-out;
animation-iteration-count: 1;
}
@keyframes zoom-in {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes blink {
from,
to {
border-bottom-style: solid;
}
50% {
border-bottom-style: none;
}
}

View File

@ -0,0 +1,4 @@
.texture-paper-dots {
@apply bg-[length:10cm] bg-local [background-image:var(--theme-texture-dots)]
[background-blend-mode:var(--theme-texture-dots-blend)];
}

11
src/styles/debug.css Normal file
View File

@ -0,0 +1,11 @@
.false {
@apply border-2 border-[red] text-[red] outline-dotted outline-2 outline-[red];
}
.undefined {
@apply border-2 border-[green] text-[green] outline-dotted outline-2 outline-[green];
}
.null {
@apply border-2 border-[blue] text-[blue] outline-dotted outline-2 outline-[blue];
}

89
src/styles/formatted.css Normal file
View File

@ -0,0 +1,89 @@
.formatted h1,
.formatted h2,
.formatted h3,
.formatted h4,
.formatted h5,
.formatted h6 {
@apply flex justify-center gap-3 text-center;
}
.formatted h1 {
@apply my-16 text-4xl;
}
.formatted h1 + h2 {
@apply -mt-10;
}
.formatted h2 {
@apply my-12 text-3xl;
}
.formatted h2 + h3 {
@apply -mt-8;
}
.formatted h3 {
@apply my-8 text-2xl;
}
.formatted h3 + h4 {
@apply -mt-6;
}
.formatted h4 {
@apply my-6 text-xl;
}
.formatted h5 {
@apply my-4 text-lg;
}
.formatted p,
.formatted strong {
@apply my-2 text-justify;
}
.formatted strong {
@apply font-black;
}
.formatted footer {
@apply border-t-2 border-dotted pt-6;
}
.formatted footer > div {
@apply my-2 rounded-xl px-6 py-4;
}
.formatted footer > div:target {
@apply bg-mid shadow-inner-sm shadow-shade;
}
.formatted li::marker {
@apply text-dark;
}
.formatted code {
@apply font-mono;
}
.formatted pre > code {
@apply block whitespace-pre-line;
}
.formatted ul {
@apply list-disc pl-4;
}
.formatted ol {
@apply list-decimal pl-4;
}
.formatted blockquote {
@apply my-8 rounded-lg border-2 border-mid p-5 text-center;
}
.formatted blockquote cite {
@apply block text-dark;
}

80
src/styles/others.css Normal file
View File

@ -0,0 +1,80 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
#__next {
@apply bg-light font-body font-medium text-black;
}
* {
@apply box-border scroll-m-[40vh] scroll-smooth ![-webkit-tap-highlight-color:transparent];
}
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-headers font-black;
}
a {
@apply cursor-pointer underline decoration-dark decoration-dotted
underline-offset-2 transition-colors hover:text-dark;
}
*::selection {
@apply bg-dark text-light;
}
mark {
@apply bg-mid px-2;
}
/* SCROLLBARS STYLING */
* {
@apply [scrollbar-color:theme(colors.dark/1)_transparent] [scrollbar-width:thin];
}
*::-webkit-scrollbar {
@apply w-3;
}
*::-webkit-scrollbar-track {
@apply bg-light;
}
*::-webkit-scrollbar-thumb {
@apply rounded-full border-2 border-solid border-light bg-dark;
}
/* INPUT */
input,
textarea {
@apply rounded-full bg-light p-2 text-center text-dark outline outline-1 -outline-offset-1
outline-mid transition-all placeholder:text-dark placeholder:opacity-60 hover:bg-mid
hover:outline-transparent;
}
input::placeholder {
@apply text-dark opacity-60;
}
input:focus-visible,
textarea:focus-within {
@apply bg-mid shadow-inner-sm shadow-shade outline-none;
}
textarea {
@apply rounded-2xl p-6 text-left;
}
input[type="submit"] {
@apply grid cursor-pointer place-content-center place-items-center rounded-full border
border-dark px-4 pt-[0.4rem] pb-[0.5rem] text-dark outline-none transition-all hover:bg-dark
hover:text-light hover:drop-shadow-shade-lg active:border-black active:bg-black
active:text-light active:drop-shadow-black-lg;
}

218
src/styles/rc-slider.css Normal file
View File

@ -0,0 +1,218 @@
.rc-slider {
position: relative;
width: 100%;
height: 14px;
padding: 5px 0;
border-radius: 6px;
touch-action: none;
box-sizing: border-box;
}
.rc-slider * {
box-sizing: border-box;
}
.rc-slider-rail {
@apply h-2 rounded-full bg-mid/80;
position: absolute;
width: 100%;
}
.rc-slider-track {
@apply h-2 rounded-full bg-mid bg-dark/20 shadow-inner-sm shadow-shade;
position: absolute;
}
.rc-slider-handle {
@apply -mt-1 h-4 w-4 rounded-full bg-dark transition-shadow;
position: absolute;
cursor: grab;
touch-action: pan-x;
}
.rc-slider-handle-dragging.rc-slider-handle-dragging.rc-slider-handle-dragging {
@apply shadow-sm shadow-shade;
}
.rc-slider-mark {
position: absolute;
top: 18px;
left: 0;
width: 100%;
font-size: 12px;
}
.rc-slider-mark-text {
position: absolute;
display: inline-block;
color: #999;
text-align: center;
vertical-align: middle;
cursor: pointer;
}
.rc-slider-mark-text-active {
color: #666;
}
.rc-slider-step {
position: absolute;
width: 100%;
height: 4px;
background: transparent;
pointer-events: none;
}
.rc-slider-dot {
position: absolute;
bottom: -2px;
width: 8px;
height: 8px;
vertical-align: middle;
background-color: #fff;
border: 2px solid #e9e9e9;
border-radius: 50%;
cursor: pointer;
}
.rc-slider-dot-active {
border-color: #96dbfa;
}
.rc-slider-dot-reverse {
margin-right: -4px;
}
.rc-slider-disabled {
background-color: #e9e9e9;
}
.rc-slider-disabled .rc-slider-track {
background-color: #ccc;
}
.rc-slider-disabled .rc-slider-handle,
.rc-slider-disabled .rc-slider-dot {
background-color: #fff;
border-color: #ccc;
box-shadow: none;
cursor: not-allowed;
}
.rc-slider-disabled .rc-slider-mark-text,
.rc-slider-disabled .rc-slider-dot {
cursor: not-allowed !important;
}
.rc-slider-vertical {
width: 14px;
height: 100%;
padding: 0 5px;
}
.rc-slider-vertical .rc-slider-rail {
width: 4px;
height: 100%;
}
.rc-slider-vertical .rc-slider-track {
bottom: 0;
left: 5px;
width: 4px;
}
.rc-slider-vertical .rc-slider-handle {
margin-top: 0;
margin-left: -5px;
touch-action: pan-y;
}
.rc-slider-vertical .rc-slider-mark {
top: 0;
left: 18px;
height: 100%;
}
.rc-slider-vertical .rc-slider-step {
width: 4px;
height: 100%;
}
.rc-slider-vertical .rc-slider-dot {
margin-left: -2px;
}
.rc-slider-tooltip-zoom-down-enter,
.rc-slider-tooltip-zoom-down-appear {
display: block !important;
animation-duration: 0.3s;
animation-fill-mode: both;
animation-play-state: paused;
}
.rc-slider-tooltip-zoom-down-leave {
display: block !important;
animation-duration: 0.3s;
animation-fill-mode: both;
animation-play-state: paused;
}
.rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active,
.rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active {
animation-name: rcSliderTooltipZoomDownIn;
animation-play-state: running;
}
.rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active {
animation-name: rcSliderTooltipZoomDownOut;
animation-play-state: running;
}
.rc-slider-tooltip-zoom-down-enter,
.rc-slider-tooltip-zoom-down-appear {
transform: scale(0, 0);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
.rc-slider-tooltip-zoom-down-leave {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
}
@keyframes rcSliderTooltipZoomDownIn {
0% {
transform: scale(0, 0);
transform-origin: 50% 100%;
opacity: 0;
}
100% {
transform: scale(1, 1);
transform-origin: 50% 100%;
}
}
@keyframes rcSliderTooltipZoomDownOut {
0% {
transform: scale(1, 1);
transform-origin: 50% 100%;
}
100% {
transform: scale(0, 0);
transform-origin: 50% 100%;
opacity: 0;
}
}
.rc-slider-tooltip {
position: absolute;
top: -9999px;
left: -9999px;
visibility: visible;
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider-tooltip * {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider-tooltip-hidden {
display: none;
}
.rc-slider-tooltip-placement-top {
padding: 4px 0 8px 0;
}
.rc-slider-tooltip-inner {
min-width: 24px;
height: 24px;
padding: 6px 2px;
color: #fff;
font-size: 12px;
line-height: 1;
text-align: center;
text-decoration: none;
background-color: #6c6c6c;
border-radius: 6px;
box-shadow: 0 0 4px #d9d9d9;
}
.rc-slider-tooltip-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow {
bottom: 4px;
left: 50%;
margin-left: -4px;
border-width: 4px 4px 0;
border-top-color: #6c6c6c;
}

63
src/styles/tippy.css Normal file
View File

@ -0,0 +1,63 @@
.tippy-box[data-animation="fade"][data-state="hidden"] {
@apply opacity-0;
}
[data-tippy-root] {
max-width: calc(100vw - 10px);
}
.tippy-box {
@apply relative rounded-lg bg-light transition-[transform,visibility,opacity]
drop-shadow-shade-xl;
}
.tippy-box[data-placement^="top"] > .tippy-arrow {
@apply bottom-0;
}
.tippy-box[data-placement^="top"] > .tippy-arrow:before {
bottom: -7px;
left: 0;
border-width: 8px 8px 0;
border-top-color: initial;
transform-origin: center top;
}
.tippy-box[data-placement^="bottom"] > .tippy-arrow {
top: 0;
}
.tippy-box[data-placement^="bottom"] > .tippy-arrow:before {
top: -7px;
left: 0;
border-width: 0 8px 8px;
border-bottom-color: initial;
transform-origin: center bottom;
}
.tippy-box[data-placement^="left"] > .tippy-arrow {
right: 0;
}
.tippy-box[data-placement^="left"] > .tippy-arrow:before {
border-width: 8px 0 8px 8px;
border-left-color: initial;
right: -7px;
transform-origin: center left;
}
.tippy-box[data-placement^="right"] > .tippy-arrow {
left: 0;
}
.tippy-box[data-placement^="right"] > .tippy-arrow:before {
left: -7px;
border-width: 8px 8px 8px 0;
border-right-color: initial;
transform-origin: center right;
}
.tippy-box[data-inertia][data-state="visible"] {
transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11);
}
.tippy-arrow {
@apply h-4 w-4 text-light;
}
.tippy-arrow:before {
content: "";
position: absolute;
border-color: transparent;
border-style: solid;
}
.tippy-content {
@apply relative z-10 px-6 py-4;
}

View File

@ -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;
}

View File

@ -2,4 +2,5 @@ export enum Ids {
Body = "bodyqs65d4a98d56az48z64d", Body = "bodyqs65d4a98d56az48z64d",
ContentPanel = "contentPanel495922447721572", ContentPanel = "contentPanel495922447721572",
SubPanel = "subPanelz9e8rs2d3f18zer98ze", SubPanel = "subPanelz9e8rs2d3f18zer98ze",
LightBox = "lightBoxqsd564az89e732s1",
} }

View File

@ -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: [