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": {
"@fontsource/material-icons": "^4.5.4",
"@fontsource/material-icons-outlined": "^4.5.4",
"@fontsource/opendyslexic": "^4.5.4",
"@fontsource/share-tech-mono": "^4.5.9",
"@fontsource/vollkorn": "^4.5.12",
"@fontsource/zen-maru-gothic": "^4.5.13",
"@tippyjs/react": "^4.2.6",
"@types/ua-parser-js": "^0.7.36",
"autoprefixer": "^10.4.12",
"graphql-request": "^5.0.0",
"markdown-to-jsx": "^7.1.7",
"meilisearch": "^0.28.0",
"next": "^12.3.1",
"nodemailer": "^6.8.0",
"npm": "^8.19.2",
"rc-slider": "^10.0.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hot-keys": "^2.7.2",
"react-hotkeys-hook": "^3.4.7",
"react-swipeable": "^7.0.0",
"react-zoom-pan-pinch": "^2.1.3",
"string-natural-compare": "^3.0.1",
@ -56,6 +58,7 @@
"@types/string-natural-compare": "^3.0.2",
"@types/throttle-debounce": "^5.0.0",
"@types/turndown": "^5.0.1",
"@types/ua-parser-js": "^0.7.36",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1",
"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 { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
import { useMemo } from "react";
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 { 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 { Popup } from "./Popup";
import { filterHasAttributes, isDefined, isUndefined } from "helpers/others";
import { prettyLanguage } from "helpers/formatters";
import { SafariPopup } from "./Panels/SafariPopup";
import { isDefined, isUndefined } from "helpers/others";
import { cIf, cJoin } from "helpers/className";
import { useAppLayout } from "contexts/AppLayoutContext";
import { Button } from "components/Inputs/Button";
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 { sendAnalytics } from "helpers/analytics";
import { useUserSettings } from "contexts/UserSettingsContext";
import { useLocalData } from "contexts/LocalDataContext";
import { useContainerQueries } from "contexts/ContainerQueriesContext";
@ -60,48 +46,19 @@ export const AppLayout = ({
contentPanelScroolbar = true,
}: Props): JSX.Element => {
const {
configPanelOpen,
mainPanelOpen,
mainPanelReduced,
menuGestures,
subPanelOpen,
hasDisgardedSafariWarning,
setHasDisgardedSafariWarning,
setConfigPanelOpen,
setMainPanelOpen,
setSubPanelOpen,
toggleMainPanelOpen,
toggleSubPanelOpen,
} = useAppLayout();
const { setScreenWidth, setContentPanelWidth, setSubPanelWidth } = useContainerQueries();
const { langui } = useLocalData();
const { langui, currencies, languages } = useLocalData();
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 { is1ColumnLayout, isScreenAtLeastXs } = useContainerQueries();
const handlers = useSwipeable({
onSwipedLeft: (SwipeEventData) => {
@ -131,47 +88,13 @@ export const AppLayout = ({
[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 (
<div
className={cJoin(
cIf(darkMode, "set-theme-dark", "set-theme-light"),
cIf(dyslexic, "set-theme-font-dyslexic", "set-theme-font-standard")
)}>
<div
{...handlers}
id={Ids.Body}
className={cJoin(
`fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black
[grid-template-areas:'main_sub_content']`,
cIf(is1ColumnLayout, `grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']`)
"fixed inset-0 m-0 grid touch-pan-y bg-light p-0 [grid-template-areas:'main_sub_content']",
cIf(is1ColumnLayout, "grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']")
)}
style={{
gridTemplateColumns: is1ColumnLayout
@ -199,21 +122,14 @@ export const AppLayout = ({
<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
`absolute inset-0 transition-filter duration-500
[grid-area:content]`,
cIf(
(mainPanelOpen || subPanelOpen) && is1ColumnLayout,
"z-10 [backdrop-filter:blur(2px)]",
"z-10 backdrop-blur",
"pointer-events-none touch-none"
)
)}>
@ -250,7 +166,7 @@ export const AppLayout = ({
<div
id={Ids.SubPanel}
className={cJoin(
`texture-paper-dots z-20 overflow-y-scroll border-r-[1px] border-dark/50
`texture-paper-dots z-20 overflow-y-scroll border-r border-dark/50
bg-light transition-transform duration-300 [scrollbar-width:none]
webkit-scrollbar:w-0`,
cIf(
@ -258,7 +174,7 @@ export const AppLayout = ({
"justify-self-end border-r-0 [grid-area:content]",
"[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 && turnSubIntoContent, "w-full border-l-0")
)}>
@ -269,7 +185,7 @@ export const AppLayout = ({
{/* Main panel */}
<div
className={cJoin(
`texture-paper-dots z-30 overflow-y-scroll border-r-[1px] border-dark/50
`texture-paper-dots z-30 overflow-y-scroll border-r border-dark/50
bg-light transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
cIf(is1ColumnLayout, "justify-self-start [grid-area:content]", "[grid-area:main]"),
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
@ -282,7 +198,7 @@ export const AppLayout = ({
<div
className={cJoin(
`texture-paper-dots z-10 grid grid-cols-[5rem_1fr_5rem] place-items-center
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar]`,
border-t border-dotted border-black bg-light [grid-area:navbar]`,
cIf(!is1ColumnLayout, "hidden")
)}>
<Ico
@ -313,207 +229,7 @@ export const AppLayout = ({
/>
)}
</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>
<SafariPopup />
</div>
);
};

View File

@ -15,8 +15,8 @@ interface Props {
export const Chip = ({ className, text }: Props): JSX.Element => (
<div
className={cJoin(
`grid place-content-center place-items-center whitespace-nowrap rounded-full border-[1px]
px-1.5 pb-[0.14rem] text-xs opacity-70 transition-[color,_opacity,_border-color]
`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]
hover:opacity-100`,
className
)}>

View File

@ -22,10 +22,10 @@ const ChroniclePreview = ({ date, url, title, isActive }: Props): JSX.Element =>
href={url}
className={cJoin(
`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
hover:shadow-shade hover:outline-[transparent] hover:active:shadow-inner
-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: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">
<p>{date.year}</p>

View File

@ -38,7 +38,7 @@ const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element
</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 }}>
{filterHasAttributes(chronicles, [
"attributes.contents",

View File

@ -234,7 +234,7 @@ export const Terminal = ({
return (
<div
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")
)}>
<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 => (
<div
className={cJoin("my-8 h-0 w-full border-t-[3px] border-dotted border-black", className)}></div>
<div className={cJoin("my-8 h-0 w-full border-t-2 border-dotted border-black", className)}></div>
);

View File

@ -1,5 +1,5 @@
import { MouseEventHandler } from "react";
import { cJoin } from "helpers/className";
import { cIf, cJoin } from "helpers/className";
/*
*
@ -10,14 +10,19 @@ interface Props {
className?: string;
onClick?: MouseEventHandler<HTMLSpanElement> | undefined;
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
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}
</span>
);

View File

@ -58,7 +58,7 @@ export const Button = ({
onFocus={(event) => event.target.blur()}
className={cJoin(
`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`,
cIf(
active,

View File

@ -57,7 +57,7 @@ export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Ele
updateOrder(sourceIndex, targetIndex);
}}
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"
draggable>
<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
ref={ref}
className={cJoin(
"relative text-center transition-[filter]",
"relative text-center transition-filter",
cIf(isOpened, "z-10 drop-shadow-shade-lg"),
className
)}>
<div
className={cJoin(
`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
transition-all hover:bg-mid hover:outline-[transparent]`,
cIf(isOpened, "rounded-b-none bg-highlight outline-[transparent]")
rounded-[1em] bg-light p-1 outline outline-1 -outline-offset-1 outline-mid
transition-all hover:bg-mid hover:outline-transparent`,
cIf(isOpened, "rounded-b-none bg-highlight outline-transparent")
)}>
<p onClick={tryToggling} className="w-full">
{value === -1 ? "—" : options[value]}

View File

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

View File

@ -9,14 +9,14 @@ import { cIf, cJoin } from "helpers/className";
import { slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img";
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
import { AnchorShare } from "components/AnchorShare";
import { useIntersectionList } from "hooks/useIntersectionList";
import { Ico, Icon } from "components/Ico";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { useUserSettings } from "contexts/UserSettingsContext";
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 => {
const { playerName } = useUserSettings();
const router = useRouter();
const isContentPanelAtLeastLg = useIsContentPanelAtLeast("lg");
const [openLightBox, LightBox] = useLightBox();
const { isContentPanelAtLeastLg } = useContainerQueries();
const { showLightBox } = useLightBox();
/* eslint-disable no-irregular-whitespace */
const text = useMemo(
@ -49,8 +49,6 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
}
return (
<>
<LightBox />
<Markdown
className={cJoin("formatted", className)}
options={{
@ -125,10 +123,7 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
component: (compProps) => (
<>
<strong
className={cJoin(
"!my-0 text-dark/60",
cIf(!isContentPanelAtLeastLg, "!-mb-4")
)}>
className={cJoin("!my-0 text-dark/60", cIf(!isContentPanelAtLeastLg, "!-mb-4"))}>
<Markdawn text={compProps.name} />
</strong>
<p className="whitespace-pre-line">{compProps.children}</p>
@ -196,7 +191,7 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
<div
className="mt-8 mb-12 grid cursor-pointer place-content-center"
onClick={() => {
openLightBox([
showLightBox([
compProps.src.startsWith("/uploads/")
? getAssetURL(compProps.src, ImageQuality.Large)
: compProps.src,
@ -217,7 +212,6 @@ export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Eleme
}}>
{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
hover:shadow-shade hover:active:shadow-inner hover:active:shadow-shade`,
cIf(icon, "text-left", "text-center"),
cIf(
border,
"outline outline-2 outline-offset-[-2px] outline-mid hover:outline-[transparent]"
),
cIf(border, "outline outline-2 -outline-offset-2 outline-mid hover:outline-transparent"),
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 && (
<div>

View File

@ -4,9 +4,9 @@ import { Button } from "components/Inputs/Button";
import { useAppLayout } from "contexts/AppLayoutContext";
import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
import { isDefined } from "helpers/others";
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 => {
const { setSubPanelOpen } = useAppLayout();
const { langui } = useLocalData();
const is3ColumnsLayout = useIs3ColumnsLayout();
const { is3ColumnsLayout } = useContainerQueries();
return (
<>

View File

@ -1,5 +1,5 @@
import { useContainerQueries } from "contexts/ContainerQueriesContext";
import { cIf, cJoin } from "helpers/className";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
/*
*
@ -25,7 +25,7 @@ export const ContentPanel = ({
children,
className,
}: Props): JSX.Element => {
const isContentPanelAtLeast3xl = useIsContentPanelAtLeast("3xl");
const { isContentPanelAtLeast3xl } = useContainerQueries();
return (
<div className="grid h-full">
<main

View File

@ -8,9 +8,10 @@ import { Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others";
import { Link } from "components/Inputs/Link";
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
import { sendAnalytics } from "helpers/analytics";
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 => {
const is3ColumnsLayout = useIs3ColumnsLayout();
const { is3ColumnsLayout } = useContainerQueries();
const { mainPanelReduced, toggleMainPanelReduced, setConfigPanelOpen } = useAppLayout();
const { langui } = useLocalData();
@ -51,14 +52,14 @@ export const MainPanel = (): JSX.Element => {
)}
<div>
<div className="grid place-items-center">
<Link href="/" className="flex w-full justify-center">
<div
<Link href="/" className="flex w-full cursor-pointer justify-center">
<ColoredSvg
src="/icons/accords.svg"
className={cJoin(
`mb-4 aspect-square cursor-pointer bg-black transition-colors
[mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat]
![mask-position:center] hover:bg-dark`,
"mb-4 aspect-square bg-black hover:bg-dark",
cIf(mainPanelReduced && is3ColumnsLayout, "w-12", "w-1/2")
)}></div>
)}
/>
</Link>
{(!mainPanelReduced || !is3ColumnsLayout) && (
@ -148,7 +149,7 @@ export const MainPanel = (): JSX.Element => {
<NavOption
url="/archives"
icon={Icon.Inventory}
icon={Icon.Inventory2}
title={langui.archives}
reduced={mainPanelReduced && is3ColumnsLayout}
/>
@ -160,7 +161,7 @@ export const MainPanel = (): JSX.Element => {
reduced={mainPanelReduced && is3ColumnsLayout}
/>
{mainPanelReduced && is3ColumnsLayout ? "" : <HorizontalLine />}
{(!mainPanelReduced || !is3ColumnsLayout) && <HorizontalLine />}
<div className={cJoin("text-center", cIf(mainPanelReduced && is3ColumnsLayout, "hidden"))}>
{isDefinedAndNotEmpty(langui.licensing_notice) && (
@ -172,22 +173,19 @@ export const MainPanel = (): JSX.Element => {
<a
onClick={() => sendAnalytics("MainPanel", "Visit license")}
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/">
<div
className="aspect-square w-6 bg-black transition-colors
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center]
[mask:url('/icons/creative-commons-brands.svg')] group-hover:bg-dark"
<ColoredSvg
className="h-6 w-6 bg-black group-hover:bg-dark"
src="/icons/creative-commons-brands.svg"
/>
<div
className="aspect-square w-6 bg-black transition-colors
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center]
[mask:url('/icons/creative-commons-by-brands.svg')] group-hover:bg-dark"
<ColoredSvg
className="h-6 w-6 bg-black group-hover:bg-dark"
src="/icons/creative-commons-by-brands.svg"
/>
<div
className="aspect-square w-6 bg-black transition-colors
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center]
[mask:url('/icons/creative-commons-sa-brands.svg')] group-hover:bg-dark"
<ColoredSvg
className="h-6 w-6 bg-black group-hover:bg-dark"
src="/icons/creative-commons-sa-brands.svg"
/>
</a>
</div>
@ -200,30 +198,36 @@ export const MainPanel = (): JSX.Element => {
<a
aria-label="Browse our GitHub repository, which include this website source code"
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"
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
aria-label="Follow us on 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"
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
aria-label="Join our Discord server!"
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"
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>

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 { useIsSubPanelAtLeast } from "hooks/useContainerQuery";
/*
*
@ -13,12 +13,12 @@ interface Props {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const SubPanel = ({ children }: Props): JSX.Element => {
const isSubPanelAtLeastSm = useIsSubPanelAtLeast("2xs");
const { isSubPanelAtLeastXs } = useContainerQueries();
return (
<div
className={cJoin(
"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}
</div>

View File

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

View File

@ -32,7 +32,6 @@ interface Props {
topChips?: string[];
bottomChips?: string[];
keepInfoVisible?: boolean;
stackNumber?: number;
metadata?: {
releaseDate?: DatePickerFragment | null;
releaseDateFormat?: Intl.DateTimeFormatOptions["dateStyle"];
@ -62,7 +61,6 @@ export const PreviewCard = ({
title,
subtitle,
description,
stackNumber = 0,
topChips,
bottomChips,
keepInfoVisible,
@ -116,32 +114,6 @@ export const PreviewCard = ({
href={href}
className="group grid cursor-pointer items-end text-left transition-transform
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 ? (
<div
className="relative"
@ -159,13 +131,7 @@ export const PreviewCard = ({
src={thumbnail}
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" && (
<>
<div
@ -190,15 +156,8 @@ export const PreviewCard = ({
className={cJoin(
"relative w-full bg-light",
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
className={cJoin(

View File

@ -6,9 +6,9 @@ import { Ico, Icon } from "./Ico";
import { cJoin } from "helpers/className";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
import { Ids } from "types/ids";
import { useLocalData } from "contexts/LocalDataContext";
import { useContainerQueries } from "contexts/ContainerQueriesContext";
interface Group<T> {
name: string;
@ -191,7 +191,7 @@ export const SmartList = <T,>({
)}
<div
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`,
className
)}>
@ -222,7 +222,7 @@ export const SmartList = <T,>({
*/
const DefaultRenderWhenEmpty = () => {
const is3ColumnsLayout = useIs3ColumnsLayout();
const { is3ColumnsLayout } = useContainerQueries();
const { langui } = useLocalData();
return (
<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 { GetContentTextQuery, UploadImageFragment } from "graphql/generated";
import { prettyInlineTitle, prettySlug, slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img";
import { ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
import { useLocalData } from "contexts/LocalDataContext";
import { useLightBox } from "contexts/LightBoxContext";
/*
*
@ -42,12 +42,11 @@ export const ThumbnailHeader = ({
description,
languageSwitcher,
}: Props): JSX.Element => {
const [openLightBox, LightBox] = useLightBox();
const { langui } = useLocalData();
const { showLightBox } = useLightBox();
return (
<>
<LightBox />
<div className="mb-12 grid place-items-center gap-12">
<div className="drop-shadow-shade-lg">
{thumbnail ? (
@ -55,9 +54,7 @@ export const ThumbnailHeader = ({
className="cursor-pointer rounded-xl"
src={thumbnail}
quality={ImageQuality.Medium}
onClick={() => {
openLightBox([getAssetURL(thumbnail.url, ImageQuality.Large)]);
}}
onClick={() => showLightBox([thumbnail])}
/>
) : (
<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 { useSmartLanguage } from "hooks/useSmartLanguage";
import { Button } from "components/Inputs/Button";
import { useIsContentPanelNoMoreThan } from "hooks/useContainerQuery";
import { cIf, cJoin } from "helpers/className";
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 isContentPanelNoMoreThanMd = useIsContentPanelNoMoreThan("md");
const { isContentPanelAtLeastMd } = useContainerQueries();
const { langui } = useLocalData();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: translations,
@ -78,7 +78,7 @@ const DefinitionCard = ({ source, translations = [], index, categories }: Props)
<div
className={cJoin(
"mt-3 flex place-items-center gap-2",
cIf(isContentPanelNoMoreThanMd, "flex-col text-center")
cIf(!isContentPanelAtLeastMd, "flex-col text-center")
)}>
<p>{langui.source}: </p>
<Button href={source.url} size="small" text={source.name} />

View File

@ -8,7 +8,7 @@ import React, {
useState,
} from "react";
import { useRouter } from "next/router";
import { useLocalStorage, useSessionStorage } from "usehooks-ts";
import { useLocalStorage } from "usehooks-ts";
import { isDefined } from "helpers/others";
import { RequiredNonNullable } from "types/types";
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
@ -34,11 +34,6 @@ interface AppLayoutState {
menuGestures: boolean;
toggleMenuGestures: () => void;
setMenuGestures: Dispatch<SetStateAction<AppLayoutState["menuGestures"]>>;
hasDisgardedSafariWarning: boolean;
setHasDisgardedSafariWarning: Dispatch<
SetStateAction<AppLayoutState["hasDisgardedSafariWarning"]>
>;
}
const initialState: RequiredNonNullable<AppLayoutState> = {
@ -61,9 +56,6 @@ const initialState: RequiredNonNullable<AppLayoutState> = {
menuGestures: true,
toggleMenuGestures: () => null,
setMenuGestures: () => null,
hasDisgardedSafariWarning: false,
setHasDisgardedSafariWarning: () => null,
};
const AppLayoutContext = createContext<AppLayoutState>(initialState);
@ -99,11 +91,6 @@ export const AppContextProvider = ({ children }: Props): JSX.Element => {
const [menuGestures, setMenuGestures] = useState(false);
const [hasDisgardedSafariWarning, setHasDisgardedSafariWarning] = useSessionStorage(
"hasDisgardedSafariWarning",
initialState.hasDisgardedSafariWarning
);
const toggleSubPanelOpen = () => {
setSubPanelOpen((current) => (isDefined(current) ? !current : current));
};
@ -148,13 +135,11 @@ export const AppContextProvider = ({ children }: Props): JSX.Element => {
mainPanelReduced,
mainPanelOpen,
menuGestures,
hasDisgardedSafariWarning,
setSubPanelOpen,
setConfigPanelOpen,
setMainPanelReduced,
setMainPanelOpen,
setMenuGestures,
setHasDisgardedSafariWarning,
toggleSubPanelOpen,
toggleConfigPanelOpen,
toggleMainPanelReduced,

View File

@ -1,33 +1,86 @@
import React, {
createContext,
Dispatch,
ReactNode,
SetStateAction,
useContext,
useState,
} from "react";
import React, { createContext, ReactNode, useContext, useMemo, useState } from "react";
import { useUserSettings } from "./UserSettingsContext";
import { useOnResize } from "hooks/useOnResize";
import { Ids } from "types/ids";
import { RequiredNonNullable } from "types/types";
interface ContainerQueriesState {
screenWidth: number;
setScreenWidth: Dispatch<SetStateAction<ContainerQueriesState["screenWidth"]>>;
type Size =
| "2xl"
| "2xs"
| "3xl"
| "4xl"
| "5xl"
| "6xl"
| "7xl"
| "lg"
| "md"
| "sm"
| "xl"
| "xs";
contentPanelWidth: number;
setContentPanelWidth: Dispatch<SetStateAction<ContainerQueriesState["contentPanelWidth"]>>;
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: 19,
"2xs": 16,
};
subPanelWidth: number;
setSubPanelWidth: Dispatch<SetStateAction<ContainerQueriesState["subPanelWidth"]>>;
}
type ContainerQueriesContainers = "contentPanel" | "screen" | "subPanel";
type ContainerQueriesKeys =
| "is1ColumnLayout"
| "is3ColumnsLayout"
| `is${Capitalize<ContainerQueriesContainers>}AtLeast${Capitalize<Size>}`;
type ContainerQueriesState = Record<ContainerQueriesKeys, boolean>;
const initialState: RequiredNonNullable<ContainerQueriesState> = {
screenWidth: 0,
setScreenWidth: () => null,
contentPanelWidth: 0,
setContentPanelWidth: () => null,
subPanelWidth: 0,
setSubPanelWidth: () => null,
is1ColumnLayout: false,
is3ColumnsLayout: false,
isContentPanelAtLeast2xl: false,
isContentPanelAtLeast2xs: false,
isContentPanelAtLeast3xl: false,
isContentPanelAtLeast4xl: false,
isContentPanelAtLeast5xl: false,
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);
@ -43,17 +96,220 @@ export const ContainerQueriesContextProvider = ({ children }: Props): JSX.Elemen
const [contentPanelWidth, setContentPanelWidth] = 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 (
<ContainerQueriesContext.Provider
value={{
screenWidth,
contentPanelWidth,
subPanelWidth,
setScreenWidth,
setContentPanelWidth,
setSubPanelWidth,
...screenAtLeasts,
...contentPanelAtLeasts,
...subPanelAtLeasts,
...columnLayouts,
}}>
{children}
</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 { getDefaultPreferredLanguages } from "helpers/locales";
import { useDarkMode } from "hooks/useDarkMode";
import { SettingsPopup } from "components/Panels/SettingsPopup";
interface UserSettingsState {
fontSize: number;
@ -134,6 +135,32 @@ export const UserSettingsProvider = ({ children }: Props): JSX.Element => {
}
}, [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 (
<UserSettingsContext.Provider
value={{
@ -155,6 +182,7 @@ export const UserSettingsProvider = ({ children }: Props): JSX.Element => {
toggleSelectedThemeMode,
toggleDyslexic,
}}>
<SettingsPopup />
{children}
</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 = (
id: string
): { isFullscreen: boolean; toggleFullscreen: () => void } => {
): {
isFullscreen: boolean;
requestFullscreen: () => void;
exitFullscreen: () => void;
toggleFullscreen: () => void;
} => {
const [isFullscreen, setIsFullscreen] = useState(false);
const isClient = useIsClient();
const elem = useMemo(() => (isClient ? document.querySelector(`#${id}`) : null), [id, isClient]);
const toggleFullscreen = useCallback(() => {
if (elem) {
if (isFullscreen) {
document.exitFullscreen();
} else {
elem.requestFullscreen();
}
}
}, [elem, isFullscreen]);
const requestFullscreen = useCallback(() => elem?.requestFullscreen(), [elem]);
const exitFullscreen = useCallback(
async () => isFullscreen && document.exitFullscreen(),
[isFullscreen]
);
const toggleFullscreen = useCallback(
() => (isFullscreen ? exitFullscreen() : requestFullscreen()),
[exitFullscreen, isFullscreen, requestFullscreen]
);
useEffect(() => {
const onFullscreenChanged = () => {
@ -28,5 +34,5 @@ export const useFullscreen = (
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-outlined";
import "@fontsource/opendyslexic/400.css";
import "@fontsource/share-tech-mono/400.css";
import "@fontsource/opendyslexic/700.css";
import "@fontsource/vollkorn/700.css";
import "@fontsource/zen-maru-gothic/500.css";
import "@fontsource/zen-maru-gothic/900.css";
import type { AppProps } from "next/app";
import Script from "next/script";
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 { UserSettingsProvider as UserSettingsContextProvider } from "contexts/UserSettingsContext";
import { LocalDataContextProvider } from "contexts/LocalDataContext";
import { ContainerQueriesContextProvider } from "contexts/ContainerQueriesContext";
import { LightBoxContextProvider } from "contexts/LightBoxContext";
const AccordsLibraryApp = (props: AppProps): JSX.Element => (
<AppContextProvider>
<ContainerQueriesContextProvider>
<LocalDataContextProvider>
<AppContextProvider>
<UserSettingsContextProvider>
<ContainerQueriesContextProvider>
<TerminalContextProvider>
<LightBoxContextProvider>
<Script
async
defer
data-website-id={process.env.NEXT_PUBLIC_UMAMI_ID}
src={`${process.env.NEXT_PUBLIC_UMAMI_URL}/umami.js`}
/>
<props.Component {...props.pageProps} />
</LightBoxContextProvider>
</TerminalContextProvider>
</UserSettingsContextProvider>
</LocalDataContextProvider>
</ContainerQueriesContextProvider>
</UserSettingsContextProvider>
</AppContextProvider>
</LocalDataContextProvider>
);
export default AccordsLibraryApp;

View File

@ -6,9 +6,9 @@ import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps"
import { cIf, cJoin } from "helpers/className";
import { randomInt } from "helpers/numbers";
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
import { useIs1ColumnLayout } from "hooks/useContainerQuery";
import { sendAnalytics } from "helpers/analytics";
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 router = useRouter();
const { langui } = useLocalData();
const is1ColumnLayout = useIs1ColumnLayout();
const { is1ColumnLayout } = useContainerQueries();
const [formResponse, setFormResponse] = useState("");
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 { SmartList } from "components/SmartList";
import { cIf } from "helpers/className";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { TextInput } from "components/Inputs/TextInput";
import { getLangui } from "graphql/fetchLocalData";
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 { langui } = useLocalData();
const hoverable = useDeviceSupportsHover();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { isContentPanelAtLeast4xl } = useContainerQueries();
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 { HorizontalLine } from "components/HorizontalLine";
import { cIf } from "helpers/className";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { getLangui } from "graphql/fetchLocalData";
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 { langui } = useLocalData();
const hoverable = useDeviceSupportsHover();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { isContentPanelAtLeast4xl } = useContainerQueries();
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 { getVideoFile } from "helpers/videos";
import { getOpenGraph } from "helpers/openGraph";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { getLangui } from "graphql/fetchLocalData";
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 isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { isContentPanelAtLeast4xl } = useContainerQueries();
const { setSubPanelOpen } = useAppLayout();
const { langui } = useLocalData();
const router = useRouter();

View File

@ -29,11 +29,11 @@ import { getOpenGraph } from "helpers/openGraph";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { getDescription } from "helpers/description";
import { TranslatedPreviewLine } from "components/PreviewLine";
import { useIs1ColumnLayout, useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { cIf } from "helpers/className";
import { getLangui } from "graphql/fetchLocalData";
import { Ids } from "types/ids";
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 isContentPanelAtLeast2xl = useIsContentPanelAtLeast("2xl");
const is1ColumnLayout = useIs1ColumnLayout();
const { isContentPanelAtLeast2xl, is1ColumnLayout } = useContainerQueries();
const { langui, languages } = useLocalData();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({

View File

@ -22,11 +22,11 @@ import { SelectiveNonNullable } from "types/SelectiveNonNullable";
import { getOpenGraph } from "helpers/openGraph";
import { HorizontalLine } from "components/HorizontalLine";
import { TranslatedPreviewCard } from "components/PreviewCard";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { cJoin, cIf } from "helpers/className";
import { getLangui } from "graphql/fetchLocalData";
import { sendAnalytics } from "helpers/analytics";
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 hoverable = useDeviceSupportsHover();
const { langui } = useLocalData();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { isContentPanelAtLeast4xl } = useContainerQueries();
const [groupingMethod, setGroupingMethod] = useState<number>(
DEFAULT_FILTERS_STATE.groupingMethod

View File

@ -19,10 +19,10 @@ import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { TranslatedPreviewCard } from "components/PreviewCard";
import { HorizontalLine } from "components/HorizontalLine";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { cJoin, cIf } from "helpers/className";
import { getLangui } from "graphql/fetchLocalData";
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 { langui } = useLocalData();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { isContentPanelAtLeast4xl } = useContainerQueries();
const subPanel = useMemo(
() => (

View File

@ -159,7 +159,7 @@ const Editor = (props: Props): JSX.Element => {
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<Popup onClose={() => setConverterOpened(false)} state={converterOpened}>
<Popup isVisible={converterOpened} onCloseRequest={() => setConverterOpened(false)}>
<div className="text-center">
<h2 className="mt-4">Convert HTML to markdown</h2>
<p>

View File

@ -29,7 +29,7 @@ import {
prettySlug,
prettyURL,
} from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img";
import { ImageQuality } from "helpers/img";
import { convertMmToInch } from "helpers/numbers";
import {
filterDefined,
@ -38,7 +38,6 @@ import {
isDefinedAndNotEmpty,
sortRangedContent,
} from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { isUntangibleGroupItem } from "helpers/libraryItem";
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
@ -50,11 +49,12 @@ import { getOpenGraph } from "helpers/openGraph";
import { getDescription } from "helpers/description";
import { useIntersectionList } from "hooks/useIntersectionList";
import { HorizontalLine } from "components/HorizontalLine";
import { useIsContentPanelNoMoreThan } from "hooks/useContainerQuery";
import { getLangui } from "graphql/fetchLocalData";
import { Ids } from "types/ids";
import { useUserSettings } from "contexts/UserSettingsContext";
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 { currency } = useUserSettings();
const { langui, currencies } = useLocalData();
const isContentPanelNoMoreThan3xl = useIsContentPanelNoMoreThan("3xl");
const isContentPanelNoMoreThanSm = useIsContentPanelNoMoreThan("sm");
const { isContentPanelAtLeast3xl, isContentPanelAtLeastSm } = useContainerQueries();
const hoverable = useDeviceSupportsHover();
const router = useRouter();
const [openLightBox, LightBox] = useLightBox();
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false);
const { showLightBox } = useLightBox();
useScrollTopOnChange(Ids.ContentPanel, [item]);
const currentIntersection = useIntersectionList(intersectionIds);
@ -158,8 +158,6 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<LightBox />
<ReturnButton
href="/library/"
title={langui.library}
@ -170,18 +168,16 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
<div
className={cJoin(
"relative h-[50vh] w-full cursor-pointer drop-shadow-shade-xl",
cIf(isContentPanelNoMoreThan3xl, "h-[60vh]", "mb-16")
)}
onClick={() => {
if (item.thumbnail?.data?.attributes) {
openLightBox([getAssetURL(item.thumbnail.data.attributes.url, ImageQuality.Large)]);
}
}}>
cIf(isContentPanelAtLeast3xl, "mb-16", "h-[60vh]")
)}>
{item.thumbnail?.data?.attributes ? (
<Img
src={item.thumbnail.data.attributes}
quality={ImageQuality.Large}
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>
@ -254,12 +250,12 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
className="relative aspect-square cursor-pointer
transition-transform hover:scale-[1.02]"
onClick={() => {
const images: string[] = filterHasAttributes(item.gallery?.data, [
"attributes",
] as const).map((image) =>
getAssetURL(image.attributes.url, ImageQuality.Large)
showLightBox(
filterHasAttributes(item.gallery?.data, ["attributes"] as const).map(
(image) => image.attributes
),
index
);
openLightBox(images, index);
}}>
<Img
className="h-full w-full rounded-lg
@ -280,7 +276,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
<div
className={cJoin(
"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] && (
<div className="grid place-content-start place-items-center">
@ -337,25 +333,25 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
<div
className={cJoin(
"grid gap-4",
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
cIf(!isContentPanelAtLeast3xl, "place-items-center")
)}>
<h3 className="text-xl">{langui.size}</h3>
<div
className={cJoin(
"grid w-full",
cIf(
isContentPanelNoMoreThanSm,
"grid-flow-row place-content-center gap-8",
"grid-flow-col place-content-between"
isContentPanelAtLeastSm,
"grid-flow-col place-content-between",
"grid-flow-row place-content-center gap-8"
)
)}>
<div
className={cJoin(
"grid gap-x-4",
cIf(
isContentPanelNoMoreThan3xl,
"place-items-center",
"grid-flow-col place-items-start"
isContentPanelAtLeast3xl,
"grid-flow-col place-items-start",
"place-items-center"
)
)}>
<p className="font-bold">{langui.width}:</p>
@ -368,9 +364,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
className={cJoin(
"grid gap-x-4",
cIf(
isContentPanelNoMoreThan3xl,
"place-items-center",
"grid-flow-col place-items-start"
isContentPanelAtLeast3xl,
"grid-flow-col place-items-start",
"place-items-center"
)
)}>
<p className="font-bold">{langui.height}:</p>
@ -384,9 +380,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
className={cJoin(
"grid gap-x-4",
cIf(
isContentPanelNoMoreThan3xl,
"place-items-center",
"grid-flow-col place-items-start"
isContentPanelAtLeast3xl,
"grid-flow-col place-items-start",
"place-items-center"
)
)}>
<p className="font-bold">{langui.thickness}:</p>
@ -405,7 +401,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
<div
className={cJoin(
"grid gap-4",
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
cIf(!isContentPanelAtLeast3xl, "place-items-center")
)}>
<h3 className="text-xl">{langui.type_information}</h3>
<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) &&
rangedContent.attributes.scan_set.length > 0
}
condensed={isContentPanelNoMoreThan3xl}
condensed={!isContentPanelAtLeast3xl}
/>
)
)}
@ -566,9 +562,8 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
</ContentPanel>
),
[
LightBox,
langui,
isContentPanelNoMoreThan3xl,
isContentPanelAtLeast3xl,
item.thumbnail?.data?.attributes,
item.subitem_of?.data,
item.title,
@ -588,13 +583,13 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
router.locale,
currencies,
currency,
isContentPanelNoMoreThanSm,
isContentPanelAtLeastSm,
isVariantSet,
hoverable,
toggleKeepInfoVisible,
keepInfoVisible,
displayOpenScans,
openLightBox,
showLightBox,
]
);

View File

@ -1,6 +1,6 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
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 { useRouter } from "next/router";
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 { Img } from "components/Img";
import { getAssetFilename, ImageQuality } from "helpers/img";
import { useIs1ColumnLayout, useIsContentPanelNoMoreThan } from "hooks/useContainerQuery";
import { cIf, cJoin } from "helpers/className";
import { clamp, isInteger } from "helpers/numbers";
import { SubPanel } from "components/Panels/SubPanel";
@ -45,6 +44,7 @@ import { useFullscreen } from "hooks/useFullscreen";
import { useUserSettings } from "contexts/UserSettingsContext";
import { useLocalData } from "contexts/LocalDataContext";
import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings";
import { useContainerQueries } from "contexts/ContainerQueriesContext";
const CUSTOM_DARK_DROPSHADOW = `
drop-shadow(0 0 0.5em rgb(var(--theme-color-shade) / 30%))
@ -90,7 +90,7 @@ const LibrarySlug = ({
item,
...otherProps
}: Props): JSX.Element => {
const is1ColumnLayout = useIs1ColumnLayout();
const { is1ColumnLayout } = useContainerQueries();
const { langui } = useLocalData();
const { darkMode } = useUserSettings();
const {
@ -114,7 +114,7 @@ const LibrarySlug = ({
);
const router = useRouter();
const { isFullscreen, toggleFullscreen } = useFullscreen(Ids.ContentPanel);
const { isFullscreen, toggleFullscreen, requestFullscreen } = useFullscreen(Ids.ContentPanel);
const effectiveDisplayMode = useMemo(
() =>
@ -173,6 +173,19 @@ const LibrarySlug = ({
[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(
() =>
pages[
@ -426,12 +439,6 @@ const LibrarySlug = ({
() => (
<ContentPanel width={ContentPanelWidthSizes.Full} className="grid place-content-center !p-0">
<div className={cJoin("mb-12 grid", cIf(is1ColumnLayout, "!p-0", "!p-8"))}>
<Hotkeys
keyName="left,right"
allowRepeat
onKeyDown={(keyName) => {
handlePageNavigation(keyName as "left" | "right");
}}>
<TransformWrapper
onZoom={(zoom) => setCurrentZoom(zoom.state.scale)}
panning={{ disabled: currentZoom <= 1, velocityDisabled: false }}
@ -540,7 +547,6 @@ const LibrarySlug = ({
)}
</TransformComponent>
</TransformWrapper>
</Hotkeys>
</div>
<div
className={cJoin(
@ -883,7 +889,7 @@ interface ScanSetProps {
}
const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps): JSX.Element => {
const is1ColumnLayout = useIsContentPanelNoMoreThan("2xl");
const { is1ColumnLayout } = useContainerQueries();
const { langui } = useLocalData();
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: scanSet,
@ -1028,7 +1034,7 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps):
<div
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`,
cIf(
is1ColumnLayout,

View File

@ -28,12 +28,12 @@ import { SelectiveNonNullable } from "types/SelectiveNonNullable";
import { getOpenGraph } from "helpers/openGraph";
import { compareDate } from "helpers/date";
import { HorizontalLine } from "components/HorizontalLine";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { cIf, cJoin } from "helpers/className";
import { getLangui } from "graphql/fetchLocalData";
import { sendAnalytics } from "helpers/analytics";
import { useLocalData } from "contexts/LocalDataContext";
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 { langui, currencies } = useLocalData();
const { libraryItemUserStatus } = useLibraryItemUserStatus();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { isContentPanelAtLeast4xl } = useContainerQueries();
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 { HorizontalLine } from "components/HorizontalLine";
import { cIf } from "helpers/className";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { getLangui } from "graphql/fetchLocalData";
import { sendAnalytics } from "helpers/analytics";
import { useIsTerminalMode } from "hooks/useIsTerminalMode";
import { Terminal } from "components/Cli/Terminal";
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 isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { isContentPanelAtLeast4xl } = useContainerQueries();
const { langui } = useLocalData();
const hoverable = useDeviceSupportsHover();
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 { useSmartLanguage } from "hooks/useSmartLanguage";
import { prettySlug, sJoin } from "helpers/formatters";
import { useLightBox } from "hooks/useLightBox";
import { getAssetURL, ImageQuality } from "helpers/img";
import { ImageQuality } from "helpers/img";
import { getOpenGraph } from "helpers/openGraph";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { getDescription } from "helpers/description";
import { cIf, cJoin } from "helpers/className";
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
import { getLangui } from "graphql/fetchLocalData";
import { Terminal } from "components/Cli/Terminal";
import { prettyTerminalBoxedTitle, prettyTerminalUnderlinedTitle } from "helpers/terminal";
import { useIsTerminalMode } from "hooks/useIsTerminalMode";
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 router = useRouter();
const isTerminalMode = useIsTerminalMode();
const { showLightBox } = useLightBox();
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
items: page.translations,
languageExtractor: useCallback(
@ -48,9 +49,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
[]
),
});
const [openLightBox, LightBox] = useLightBox();
const is3ColumnsLayout = useIs3ColumnsLayout();
const { is3ColumnsLayout } = useContainerQueries();
const subPanel = useMemo(
() => (
@ -64,8 +63,6 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
const contentPanel = useMemo(
() => (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<LightBox />
<ReturnButton
href={`/wiki`}
title={langui.wiki}
@ -98,10 +95,8 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
quality={ImageQuality.Medium}
className="w-full cursor-pointer"
onClick={() => {
if (page.thumbnail?.data?.attributes?.url) {
openLightBox([
getAssetURL(page.thumbnail.data.attributes.url, ImageQuality.Large),
]);
if (page.thumbnail?.data?.attributes) {
showLightBox([page.thumbnail.data.attributes]);
}
}}
/>
@ -183,16 +178,18 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
),
[
LanguageSwitcher,
LightBox,
is3ColumnsLayout,
languageSwitcherProps,
langui,
openLightBox,
langui.categories,
langui.summary,
langui.tags,
langui.wiki,
page.categories?.data,
page.definitions,
page.tags?.data,
page.thumbnail?.data?.attributes,
selectedTranslation,
showLightBox,
]
);

View File

@ -22,13 +22,13 @@ import { SelectiveNonNullable } from "types/SelectiveNonNullable";
import { prettySlug } from "helpers/formatters";
import { getOpenGraph } from "helpers/openGraph";
import { TranslatedPreviewCard } from "components/PreviewCard";
import { useIsContentPanelAtLeast } from "hooks/useContainerQuery";
import { cIf } from "helpers/className";
import { getLangui } from "graphql/fetchLocalData";
import { sendAnalytics } from "helpers/analytics";
import { Terminal } from "components/Cli/Terminal";
import { useIsTerminalMode } from "hooks/useIsTerminalMode";
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 hoverable = useDeviceSupportsHover();
const { langui } = useLocalData();
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
const { isContentPanelAtLeast4xl } = useContainerQueries();
const isTerminalMode = useIsTerminalMode();
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",
ContentPanel = "contentPanel495922447721572",
SubPanel = "subPanelz9e8rs2d3f18zer98ze",
LightBox = "lightBoxqsd564az89e732s1",
}

View File

@ -27,10 +27,43 @@ module.exports = {
hoverable: { raw: "(hover: hover)" },
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: {
boxShadow: {
"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: [