Compare commits

..

2 commits

16 changed files with 527 additions and 159 deletions

359
package-lock.json generated
View file

@ -11,7 +11,8 @@
"@formatjs/intl-durationformat": "^0.10.1" "@formatjs/intl-durationformat": "^0.10.1"
}, },
"devDependencies": { "devDependencies": {
"@iconify/svelte": "^5.2.1", "@iconify/css-svelte": "^1.0.0",
"@iconify/json": "^2.2.439",
"@react2svelte/swipeable": "^0.1.4", "@react2svelte/swipeable": "^0.1.4",
"@svelte-put/dragscroll": "^4.0.0", "@svelte-put/dragscroll": "^4.0.0",
"@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/adapter-auto": "^7.0.0",
@ -41,9 +42,24 @@
"tailwindcss": "^4.0.9", "tailwindcss": "^4.0.9",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"unplugin-icons": "^23.0.1",
"vite": "^7.3.1" "vite": "^7.3.1"
} }
}, },
"node_modules/@antfu/install-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
"integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"package-manager-detector": "^1.3.0",
"tinyexec": "^1.0.1"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.29.0", "version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
@ -2252,10 +2268,10 @@
"url": "https://github.com/sponsors/nzakas" "url": "https://github.com/sponsors/nzakas"
} }
}, },
"node_modules/@iconify/svelte": { "node_modules/@iconify/css-svelte": {
"version": "5.2.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@iconify/svelte/-/svelte-5.2.1.tgz", "resolved": "https://registry.npmjs.org/@iconify/css-svelte/-/css-svelte-1.0.0.tgz",
"integrity": "sha512-zHmsIPmnIhGd5gc95bNN5FL+GifwMZv7M2rlZEpa7IXYGFJm/XGHdWf6PWQa6OBoC+R69WyiPO9NAj5wjfjbow==", "integrity": "sha512-S9kZ+U/hQ+QVHZVLKu06xiVi0rII4DJCh2Z6M1m7IOyECBD7A1emk5pRx4fWG9Zp7ZOHL8dWO90sxtsaOPZBJw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2265,7 +2281,18 @@
"url": "https://github.com/sponsors/cyberalien" "url": "https://github.com/sponsors/cyberalien"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": ">5.0.0" "svelte": ">4.0.0"
}
},
"node_modules/@iconify/json": {
"version": "2.2.439",
"resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.439.tgz",
"integrity": "sha512-DFpo7ZqntngeDaVFtsj+AydUrJoEPW0FIpMrmngo3iHxLXpqcNe5lhztPqvVBoXllbohomcA1YRjRO/ljo7AnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@iconify/types": "*",
"pathe": "^2.0.3"
} }
}, },
"node_modules/@iconify/types": { "node_modules/@iconify/types": {
@ -2275,6 +2302,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@iconify/utils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz",
"integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@antfu/install-pkg": "^1.1.0",
"@iconify/types": "^2.0.0",
"mlly": "^1.8.0"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@ -2455,19 +2494,6 @@
"node": ">= 8.0.0" "node": ">= 8.0.0"
} }
}, },
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.57.1", "version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
@ -2846,9 +2872,9 @@
} }
}, },
"node_modules/@sveltejs/adapter-auto": { "node_modules/@sveltejs/adapter-auto": {
"version": "7.0.0", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.0.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.1.tgz",
"integrity": "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==", "integrity": "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
@ -2866,9 +2892,9 @@
} }
}, },
"node_modules/@sveltejs/kit": { "node_modules/@sveltejs/kit": {
"version": "2.50.2", "version": "2.51.0",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.51.0.tgz",
"integrity": "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==", "integrity": "sha512-BkcfxVVYmfxHGYLugemb7TWW+y/MNZbxXHXJ141lyyi2ozq0Q4kbqoV0cBGlh7F0vNtCMvfr7UPnxIaBjcP9gg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -3275,6 +3301,13 @@
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
} }
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/unist": { "node_modules/@types/unist": {
"version": "2.0.11", "version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
@ -3686,6 +3719,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/confbox": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
"dev": true,
"license": "MIT"
},
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -4170,6 +4210,13 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/exsolve": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -4917,6 +4964,24 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/local-pkg": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
"integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
"dev": true,
"license": "MIT",
"dependencies": {
"mlly": "^1.7.4",
"pkg-types": "^2.3.0",
"quansync": "^0.2.11"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/locate-character": { "node_modules/locate-character": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
@ -5085,19 +5150,6 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -5121,6 +5173,38 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/mlly": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
"integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.15.0",
"pathe": "^2.0.3",
"pkg-types": "^1.3.1",
"ufo": "^1.6.1"
}
},
"node_modules/mlly/node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"dev": true,
"license": "MIT"
},
"node_modules/mlly/node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.1.8",
"mlly": "^1.7.4",
"pathe": "^2.0.1"
}
},
"node_modules/mri": { "node_modules/mri": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@ -5190,8 +5274,7 @@
"https://github.com/sponsors/sxzz", "https://github.com/sponsors/sxzz",
"https://opencollective.com/debug" "https://opencollective.com/debug"
], ],
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
@ -5243,6 +5326,13 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/package-manager-detector": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz",
"integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
"dev": true,
"license": "MIT"
},
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -5283,6 +5373,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -5291,18 +5388,30 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "4.0.3", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=8.6"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/pkg-types": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.2.2",
"exsolve": "^1.0.7",
"pathe": "^2.0.3"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.6", "version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@ -5590,6 +5699,23 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/antfu"
},
{
"type": "individual",
"url": "https://github.com/sponsors/sxzz"
}
],
"license": "MIT"
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -5957,9 +6083,9 @@
} }
}, },
"node_modules/svelte": { "node_modules/svelte": {
"version": "5.50.2", "version": "5.51.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.50.2.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.51.0.tgz",
"integrity": "sha512-WCxzm3BBf+Ase6RwiDPR4G36cM4Kb0NuhmLK6x44I+D6reaxizDDg8kBkk4jT/19+Rgmc44eZkOvMO6daoSFIw==", "integrity": "sha512-TOtjs5SjPi1iCXJ7aHwQBY+PEyk0koIc1FCKNFyS9kQuN4FBma9nRuxM4f4A0b5SCdej812vf2Rcql2I0s9FZg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -5967,6 +6093,7 @@
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
"@sveltejs/acorn-typescript": "^1.0.5", "@sveltejs/acorn-typescript": "^1.0.5",
"@types/estree": "^1.0.5", "@types/estree": "^1.0.5",
"@types/trusted-types": "^2.0.7",
"acorn": "^8.12.1", "acorn": "^8.12.1",
"aria-query": "^5.3.1", "aria-query": "^5.3.1",
"axobject-query": "^4.1.0", "axobject-query": "^4.1.0",
@ -5984,9 +6111,9 @@
} }
}, },
"node_modules/svelte-check": { "node_modules/svelte-check": {
"version": "4.3.6", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.6.tgz", "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.0.tgz",
"integrity": "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==", "integrity": "sha512-gB3FdEPb8tPO3Y7Dzc6d/Pm/KrXAhK+0Fk+LkcysVtupvAh6Y/IrBCEZNupq57oh0hcwlxCUamu/rq7GtvfSEg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -6159,6 +6286,16 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/tinyexec": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.15", "version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@ -6176,6 +6313,19 @@
"url": "https://github.com/sponsors/SuperchupuDev" "url": "https://github.com/sponsors/SuperchupuDev"
} }
}, },
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -6232,6 +6382,13 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/ufo": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
"dev": true,
"license": "MIT"
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "7.16.0", "version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@ -6339,6 +6496,72 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/unplugin": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
"integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
"acorn": "^8.15.0",
"picomatch": "^4.0.3",
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/unplugin-icons": {
"version": "23.0.1",
"resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-23.0.1.tgz",
"integrity": "sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@antfu/install-pkg": "^1.1.0",
"@iconify/utils": "^3.1.0",
"local-pkg": "^1.1.2",
"obug": "^2.1.1",
"unplugin": "^2.3.11"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@svgr/core": ">=7.0.0",
"@svgx/core": "^1.0.1",
"@vue/compiler-sfc": "^3.0.2",
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"@svgr/core": {
"optional": true
},
"@svgx/core": {
"optional": true
},
"@vue/compiler-sfc": {
"optional": true
},
"svelte": {
"optional": true
}
}
},
"node_modules/unplugin/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@ -6477,6 +6700,19 @@
} }
} }
}, },
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/vitefu": { "node_modules/vitefu": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
@ -6498,6 +6734,13 @@
} }
} }
}, },
"node_modules/webpack-virtual-modules": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"dev": true,
"license": "MIT"
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -6571,6 +6814,22 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"extraneous": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View file

@ -11,7 +11,8 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
}, },
"devDependencies": { "devDependencies": {
"@iconify/svelte": "^5.2.1", "@iconify/css-svelte": "^1.0.0",
"@iconify/json": "^2.2.439",
"@react2svelte/swipeable": "^0.1.4", "@react2svelte/swipeable": "^0.1.4",
"@svelte-put/dragscroll": "^4.0.0", "@svelte-put/dragscroll": "^4.0.0",
"@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/adapter-auto": "^7.0.0",
@ -41,6 +42,7 @@
"tailwindcss": "^4.0.9", "tailwindcss": "^4.0.9",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"unplugin-icons": "^23.0.1",
"vite": "^7.3.1" "vite": "^7.3.1"
}, },
"type": "module", "type": "module",

8
src/app.d.ts vendored
View file

@ -1,8 +1,14 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />
/// <reference types="unplugin-icons/types/svelte" />
import type { import type {
DurationFormatConstructor, DurationFormatConstructor,
DurationFormatOptions as _DurationFormatOptions, DurationFormatOptions as _DurationFormatOptions,
DurationInput as _DurationInput, DurationInput as _DurationInput,
} from '@formatjs/intl-durationformat/src/types'; } from '@formatjs/intl-durationformat/src/types';
import type { Component } from 'svelte';
import 'unplugin-icons/types/svelte';
declare global { declare global {
// rndtrash: Терпим. https://github.com/microsoft/TypeScript/issues/60608 // rndtrash: Терпим. https://github.com/microsoft/TypeScript/issues/60608
@ -15,7 +21,7 @@ declare global {
namespace App { namespace App {
interface Route { interface Route {
label: string; label: string;
icon: string; icon?: Component | string;
href: string; href: string;
} }

View file

@ -9,13 +9,12 @@
<script lang="ts"> <script lang="ts">
import { import {
BLOG_POST_FRESHNESS_MILLIS, BLOG_POST_FRESHNESS_MILLIS,
blogPostTypeToIcon, blogPostTypeToIconComponent,
blogPostTypeToString, blogPostTypeToString,
EventStatus, EventStatus,
postEventStatus postEventStatus
} from '$lib/util/Blogs'; } from '$lib/util/Blogs';
import DateWidget from '$lib/components/DateWidget.svelte'; import DateWidget from '$lib/components/DateWidget.svelte';
import Icon from '@iconify/svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let { let {
@ -24,7 +23,7 @@
fullHeight = false fullHeight = false
}: { post: App.BlogPost; size?: BlogCardSize; fullHeight?: boolean } = $props(); }: { post: App.BlogPost; size?: BlogCardSize; fullHeight?: boolean } = $props();
const type: App.BlogPostType = post.type ?? 'article'; const type: App.BlogPostType = $derived(post.type ?? 'article');
let isPostNew = $state(false); let isPostNew = $state(false);
let isPostUpdated = $state(false); let isPostUpdated = $state(false);
let isPostFresh = $derived(isPostNew || isPostUpdated); let isPostFresh = $derived(isPostNew || isPostUpdated);
@ -35,6 +34,8 @@
); );
let eventHasStarted = $derived(eventStatus === EventStatus.InProgress); let eventHasStarted = $derived(eventStatus === EventStatus.InProgress);
const PostTypeIcon = $derived(blogPostTypeToIconComponent(type));
onMount(() => { onMount(() => {
const dateNow = new Date().valueOf(); const dateNow = new Date().valueOf();
isPostNew = dateNow - new Date(post.date!).valueOf() <= BLOG_POST_FRESHNESS_MILLIS; isPostNew = dateNow - new Date(post.date!).valueOf() <= BLOG_POST_FRESHNESS_MILLIS;
@ -150,7 +151,7 @@
/> />
{/if} {/if}
<div class="flex items-center gap-2 p-1 text-lg font-bold"> <div class="flex items-center gap-2 p-1 text-lg font-bold">
<Icon icon={blogPostTypeToIcon(type)} width={28} height={28} /> <PostTypeIcon width={28} height={28} />
<span class={shortClass('hidden', 'not-md:hidden')}> <span class={shortClass('hidden', 'not-md:hidden')}>
{blogPostTypeToString(type)} {blogPostTypeToString(type)}
</span> </span>

View file

@ -1,6 +1,11 @@
<script lang="ts"> <script lang="ts">
import Icon from '@iconify/svelte';
import { dateFormatLong, dateFormatShort } from '$lib/util/Dates'; import { dateFormatLong, dateFormatShort } from '$lib/util/Dates';
import type { Component } from 'svelte';
import PublishedIcon from '~icons/material-symbols/calendar-today';
import UpdatedIcon from '~icons/material-symbols/update';
import EventStartIcon from '~icons/material-symbols/rocket-launch';
import EventFinishIcon from '~icons/material-symbols/sports-score';
let { let {
dateString, dateString,
@ -17,13 +22,13 @@
} = $props(); } = $props();
const typeToIcon = { const typeToIcon = {
published: 'material-symbols:calendar-today', published: PublishedIcon,
updated: 'material-symbols:update', updated: UpdatedIcon,
eventStart: 'material-symbols:rocket-launch', eventStart: EventStartIcon,
eventFinish: 'material-symbols:sports-score' eventFinish: EventFinishIcon
}; };
const icon = type ? typeToIcon[type] : undefined; const IconComponent: Component | undefined = $derived(type ? typeToIcon[type] : undefined);
const highlightClasses = (classes: string) => (highlight ? classes : ''); const highlightClasses = (classes: string) => (highlight ? classes : '');
</script> </script>
@ -38,8 +43,8 @@
)} )}
{highlightClasses('text-slate-50')}" {highlightClasses('text-slate-50')}"
> >
{#if icon} {#if IconComponent}
<Icon {icon} width={28} height={28} /> <IconComponent width={28} height={28} />
{/if} {/if}
<span class="text-nowrap"> <span class="text-nowrap">
{dateString {dateString

View file

@ -1,12 +1,23 @@
<script lang="ts"> <script lang="ts">
import Icon from '@iconify/svelte'; import type { Component } from 'svelte';
let className: string = ''; let {
export { className as class }; class: klass = '',
export let src: string | null = null; src,
export let alt: string = ""; alt = '',
export let size: number = 32; size = 32,
export let black: boolean = false; black = false
}: {
class?: string;
src?: Component | string;
alt?: string;
size?: number;
black?: boolean;
} = $props();
const IconComponent: Component | undefined = $derived(
typeof src === 'function' ? src : undefined
);
function isUrl(src?: string) { function isUrl(src?: string) {
return src?.startsWith('/') || src?.startsWith('http'); return src?.startsWith('/') || src?.startsWith('http');
@ -14,16 +25,14 @@
</script> </script>
<span <span
class="{className} {black ? 'fill-slate-950' : 'fill-slate-50'} hover-icon" class="{klass} {black ? 'fill-slate-950' : 'fill-slate-50'} hover-icon"
style:width="{size}px" style:width="{size}px"
style:height="{size}px" style:height="{size}px"
> >
{#if src} {#if IconComponent}
{#if isUrl(src)} <IconComponent width={size} height={size} color={black ? '#020618' : '#f8fafc'} />
{:else if typeof src === 'string' && isUrl(src)}
<img {src} {alt} width={size} height={size} /> <img {src} {alt} width={size} height={size} />
{:else}
<Icon width={size} height={size} icon={src} color={black ? '#020618' : '#f8fafc'} />
{/if}
{:else} {:else}
{alt ?? 'Без иконки'} {alt ?? 'Без иконки'}
{/if} {/if}

View file

@ -1,18 +1,27 @@
<script lang="ts"> <script lang="ts">
import Icon from '@iconify/svelte'; import type { Component, Snippet } from 'svelte';
export let bgStrong: string; let {
export let bgBleak: string; icon: IconComponent,
export let icon: string; bgStrong,
export let caption: string; bgBleak,
caption,
children
}: {
icon: Component;
bgStrong: string;
bgBleak: string;
caption: string;
children: Snippet;
} = $props();
</script> </script>
<section class="flex flex-col sm:flex-row"> <section class="flex flex-col sm:flex-row">
<div class="flex flex-row items-center gap-2 {bgStrong} p-2 text-slate-50"> <div class="flex flex-row items-center gap-2 {bgStrong} p-2 text-slate-50">
<Icon width={32} height={32} {icon} color={'#f8fafc'} /> <IconComponent width={32} height={32} color={'#f8fafc'} />
<span class="sm:hidden">{caption}</span> <span class="sm:hidden">{caption}</span>
</div> </div>
<div class="{bgBleak} p-4 sm:grow"> <div class="{bgBleak} p-4 sm:grow">
<slot /> {@render children()}
</div> </div>
</section> </section>

View file

@ -1,12 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { Snippet } from 'svelte';
import IconBlock from './IconBlock.svelte'; import IconBlock from './IconBlock.svelte';
import InfoIcon from '~icons/material-symbols/info';
let { children }: { children: Snippet } = $props();
</script> </script>
<IconBlock <IconBlock bgStrong="bg-blue-500" bgBleak="bg-blue-50" icon={InfoIcon} caption="Примечание">
bgStrong="bg-blue-500" {@render children()}
bgBleak="bg-blue-50"
icon="material-symbols:info"
caption="Примечание"
>
<slot />
</IconBlock> </IconBlock>

View file

@ -1,24 +1,29 @@
<script lang="ts"> <script lang="ts">
import type { Snippet } from 'svelte';
import { isLinkLocal, tryGetIcon } from '$lib/util/LinkResolver'; import { isLinkLocal, tryGetIcon } from '$lib/util/LinkResolver';
import HoverIcon from './HoverIcon.svelte'; import HoverIcon from '$lib/components/HoverIcon.svelte';
export let href: string; let {
let className: string = ''; href,
export { className as class }; class: klass = '',
export let customIcon: string | null = null; customIcon = null,
children
}: { href: string; class?: string; customIcon?: string | null; children: Snippet } = $props();
const iconSrc = $derived(customIcon ?? tryGetIcon(href));
</script> </script>
<a <a
{href} {href}
class="{className} flex flex-row drop-shadow-2xl transition-all hover:scale-110" class="{klass} flex flex-row drop-shadow-2xl transition-all hover:scale-110"
target={isLinkLocal(href) ? '_self' : '_blank'} target={isLinkLocal(href) ? '_self' : '_blank'}
> >
<div class="shrink-0 rounded-l-xl bg-slate-800 p-2"> <div class="shrink-0 rounded-l-xl bg-slate-800 p-2">
<HoverIcon src={customIcon ?? tryGetIcon(href)} class="text-sm uppercase" /> <HoverIcon src={iconSrc} class="text-sm uppercase" />
</div> </div>
<div <div
class="flex shrink-0 grow flex-nowrap items-center justify-center rounded-r-xl bg-slate-100 p-2 text-2xl text-nowrap text-slate-950" class="flex shrink-0 grow flex-nowrap items-center justify-center rounded-r-xl bg-slate-100 p-2 text-2xl text-nowrap text-slate-950"
> >
<slot /> {@render children()}
</div> </div>
</a> </a>

View file

@ -1,23 +1,32 @@
<script lang="ts"> <script lang="ts">
import type { Snippet } from 'svelte';
import { MediaQuery } from 'svelte/reactivity'; import { MediaQuery } from 'svelte/reactivity';
import HoverIcon from './HoverIcon.svelte'; import HoverIcon from './HoverIcon.svelte';
import { isLinkLocal, tryGetIcon } from '$lib/util/LinkResolver'; import { isLinkLocal, tryGetIcon } from '$lib/util/LinkResolver';
export let href: string; let {
let className: string = ''; href,
export { className as class }; class: klass = '',
export let customIcon: string | null = null; customIcon = null,
children
}: { href: string; class?: string; customIcon?: string | null; children: Snippet } = $props();
const iconSrc = $derived(customIcon ?? tryGetIcon(href));
const sm = new MediaQuery('width >= 40rem', false); const sm = new MediaQuery('width >= 40rem', false);
</script> </script>
<a {href} class="{className} group inline-block no-underline" target={isLinkLocal(href) ? '_self' : '_blank'}> <a
{href}
class="{klass} group inline-block no-underline"
target={isLinkLocal(href) ? '_self' : '_blank'}
>
<span <span
class="inline-block size-6 rounded-sm bg-emerald-800 p-0.5 align-bottom transition-all group-hover:scale-110 sm:size-8 sm:rounded-xl sm:p-1" class="inline-block size-6 rounded-sm bg-emerald-800 p-0.5 align-bottom transition-all group-hover:scale-110 sm:size-8 sm:rounded-xl sm:p-1"
> >
<HoverIcon src={customIcon ?? tryGetIcon(href)} size={sm.current ? 24 : 20} /> <HoverIcon src={iconSrc} size={sm.current ? 24 : 20} />
</span> </span>
<span class="text-emerald-900 underline"> <span class="text-emerald-900 underline">
<slot /> {@render children()}
</span> </span>
</a> </a>

View file

@ -1,12 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { Snippet } from 'svelte';
import IconBlock from './IconBlock.svelte'; import IconBlock from './IconBlock.svelte';
import WarningIcon from '~icons/material-symbols/warning';
let { children }: { children: Snippet } = $props();
</script> </script>
<IconBlock <IconBlock bgStrong="bg-yellow-500" bgBleak="bg-yellow-50" icon={WarningIcon} caption="Внимание">
bgStrong="bg-yellow-500" {@render children()}
bgBleak="bg-yellow-50"
icon="material-symbols:warning"
caption="Внимание"
>
<slot />
</IconBlock> </IconBlock>

View file

@ -1,3 +1,5 @@
import type { Component } from "svelte";
export const THUMBNAIL_DEFAULT = "https://teasanctuary.ru/common/background-day.webp"; export const THUMBNAIL_DEFAULT = "https://teasanctuary.ru/common/background-day.webp";
export const BLOG_POST_FRESHNESS_MILLIS = 3 * 24 * 60 * 60 * 1000; // 3 дня export const BLOG_POST_FRESHNESS_MILLIS = 3 * 24 * 60 * 60 * 1000; // 3 дня
@ -97,16 +99,20 @@ const bptToString: Record<App.BlogPostType, string> = {
'update': 'Обновление' 'update': 'Обновление'
}; };
const bptToIcon: Record<App.BlogPostType, string> = { import ArticleIcon from '~icons/material-symbols/article';
'article': 'material-symbols:article', import EventIcon from '~icons/material-symbols/event';
'event': 'material-symbols:event', import UpdateIcon from '~icons/material-symbols/autorenew';
'update': 'material-symbols:autorenew'
const bptToIcon: Record<App.BlogPostType, Component> = {
'article': ArticleIcon,
'event': EventIcon,
'update': UpdateIcon
}; };
export function blogPostTypeToString(type: App.BlogPostType): string { export function blogPostTypeToString(type: App.BlogPostType): string {
return bptToString[type] ?? bptToString['article']; return bptToString[type] ?? bptToString['article'];
} }
export function blogPostTypeToIcon(type: App.BlogPostType): string { export function blogPostTypeToIconComponent(type: App.BlogPostType): Component {
return bptToIcon[type] ?? bptToIcon['article']; return bptToIcon[type] ?? bptToIcon['article'];
} }

View file

@ -1,27 +1,47 @@
import type { Component } from "svelte";
import { browser } from '$app/environment';
import DefaultLinkIcon from '~icons/material-symbols/link';
import SteamIcon from '~icons/simple-icons/steam';
import TelegramIcon from '~icons/simple-icons/telegram';
import TwitterIcon from '~icons/simple-icons/x';
import GitHubIcon from '~icons/simple-icons/github';
import YouTubeIcon from '~icons/simple-icons/youtube';
import ItchIoIcon from '~icons/simple-icons/itchdotio';
import DiscordIcon from '~icons/simple-icons/discord';
import GameBananaIcon from '~icons/simple-icons/gamebanana';
import BlueSkyIcon from '~icons/simple-icons/bluesky';
import HamsterIcon from '~icons/fluent-emoji-high-contrast/hamster';
import GitIcon from '~icons/devicon-plain/git';
import EmailIcon from '~icons/material-symbols/alternate-email';
import RssIcon from '~icons/material-symbols/rss-feed';
type LinkIcon = Component | string;
// TODO: чашки на иконках выглядят страшненько, пока что пусть лучше будет голая ссылка // TODO: чашки на иконках выглядят страшненько, пока что пусть лучше будет голая ссылка
const icons: Record<string, string> = { const icons: Record<string, LinkIcon> = {
none: 'material-symbols:link', none: DefaultLinkIcon,
'steamcommunity.com': 'simple-icons:steam', 'steamcommunity.com': SteamIcon,
'steampowered.com': 'simple-icons:steam', 'steampowered.com': SteamIcon,
't.me': 'simple-icons:telegram', 't.me': TelegramIcon,
'twitter.com': 'simple-icons:x', 'twitter.com': TwitterIcon,
'x.com': 'simple-icons:x', 'x.com': TwitterIcon,
'github.com': 'simple-icons:github', 'github.com': GitHubIcon,
'youtube.com': 'simple-icons:youtube', 'youtube.com': YouTubeIcon,
'itch.io': 'simple-icons:itchdotio', 'itch.io': ItchIoIcon,
'discord.com': 'simple-icons:discord', 'discord.com': DiscordIcon,
'discord.gg': 'simple-icons:discord', 'discord.gg': DiscordIcon,
'gamebanana.com': 'simple-icons:gamebanana', 'gamebanana.com': GameBananaIcon,
'bsky.app': 'simple-icons:bluesky', 'bsky.app': BlueSkyIcon,
'bsky.social': 'simple-icons:bluesky', 'bsky.social': BlueSkyIcon,
// https://хамяк.рф // https://хамяк.рф
'xn--80auf8a2c.xn--p1ai': 'fluent-emoji-high-contrast:hamster', 'xn--80auf8a2c.xn--p1ai': HamsterIcon,
// 'teasanctuary.ru': '/icons/tea-sanctuary-white.svg', // 'teasanctuary.ru': '/icons/tea-sanctuary-white.svg',
'hl.teasanctuary.ru': '/icons/half-life.svg', 'hl.teasanctuary.ru': '/icons/half-life.svg',
'git.teasanctuary.ru': 'devicon-plain:git', 'git.teasanctuary.ru': GitIcon,
// localhost: '/icons/tea-sanctuary-white.svg', // localhost: '/icons/tea-sanctuary-white.svg',
email: 'material-symbols:alternate-email', email: EmailIcon,
rss: 'material-symbols:rss-feed' rss: RssIcon
}; };
// Особые случаи, когда одним доменом второго уровня не ограничишься (например, randomtrash.itch.io) // Особые случаи, когда одним доменом второго уровня не ограничишься (например, randomtrash.itch.io)
@ -51,7 +71,9 @@ const specialResolvers: Record<string, (url: URL) => string> = {
'steampowered.com': (url) => 'steampowered.com', 'steampowered.com': (url) => 'steampowered.com',
} }
function getIconFromUrl(url: URL): string | undefined { function getIconNameFromUrl(url?: URL): string | undefined {
if (!url) return undefined;
const href = url.href; const href = url.href;
if (href.startsWith('mailto:')) if (href.startsWith('mailto:'))
return 'email'; return 'email';
@ -66,15 +88,16 @@ function getIconFromUrl(url: URL): string | undefined {
return hostname; return hostname;
} }
export function tryGetIcon(link: string): string { const baseURI = browser ? document.baseURI : 'https://teasanctuary.ru';
export function tryGetIcon(link: string): LinkIcon {
let url: URL; let url: URL;
try { try {
url = new URL(link, document.baseURI); url = new URL(link, baseURI);
} catch { } catch {
return icons['none']; return icons['none'];
} }
return icons[getIconFromUrl(url) ?? ''] ?? icons['none']; return icons[getIconNameFromUrl(url) ?? ''] ?? icons['none'];
} }
/** /**

View file

@ -3,13 +3,42 @@
import { page } from '$app/state'; import { page } from '$app/state';
import '$src/syntax-highlight.css'; // https://github.com/PrismJS/prism-themes import '$src/syntax-highlight.css'; // https://github.com/PrismJS/prism-themes
import NavBar from '$lib/components/NavBar.svelte'; import NavBar from '$lib/components/NavBar.svelte';
import type { Snippet } from 'svelte';
import TeaIcon from '~icons/material-symbols/emoji-food-beverage';
import TeamIcon from '~icons/material-symbols/groups';
import BlogIcon from '~icons/material-symbols/newspaper';
import ProjectsIcon from '~icons/material-symbols/work';
import ContactsIcon from '~icons/material-symbols/chat';
let { children }: { children: Snippet } = $props();
const routes: App.Route[] = [ const routes: App.Route[] = [
{ label: 'главная', icon: 'material-symbols:emoji-food-beverage', href: '/' }, {
{ label: 'команда', icon: 'material-symbols:groups', href: '/team' }, label: 'главная',
{ label: 'блог', icon: 'material-symbols:newspaper', href: '/blog' }, icon: TeaIcon,
{ label: 'проекты', icon: 'material-symbols:work', href: '/projects' }, href: '/'
{ label: 'контакты', icon: 'material-symbols:chat', href: '/contact' } },
{
label: 'команда',
icon: TeamIcon,
href: '/team'
},
{
label: 'блог',
icon: BlogIcon,
href: '/blog'
},
{
label: 'проекты',
icon: ProjectsIcon,
href: '/projects'
},
{
label: 'контакты',
icon: ContactsIcon,
href: '/contact'
}
]; ];
</script> </script>
@ -33,7 +62,7 @@
<NavBar {routes} /> <NavBar {routes} />
<div class="flex grow-1 flex-col overflow-auto"> <div class="flex grow-1 flex-col overflow-auto">
<div class="relative grow-1"> <div class="relative grow-1">
<slot /> {@render children()}
</div> </div>
<footer class="bg-emerald-950"> <footer class="bg-emerald-950">

View file

@ -2,10 +2,10 @@
import { page } from '$app/state'; import { page } from '$app/state';
import DateWidget from '$lib/components/DateWidget.svelte'; import DateWidget from '$lib/components/DateWidget.svelte';
import InfoBlock from '$lib/components/InfoBlock.svelte'; import InfoBlock from '$lib/components/InfoBlock.svelte';
import Icon from '@iconify/svelte'; import PersonIcon from '~icons/material-symbols/person';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { import {
blogPostTypeToIcon, blogPostTypeToIconComponent,
blogPostTypeToString, blogPostTypeToString,
EventStatus, EventStatus,
postEventStatus postEventStatus
@ -16,16 +16,19 @@
let { data }: { data: PageData } = $props(); let { data }: { data: PageData } = $props();
const isPublic = !!data.blogPost.date; const isPublic = $derived(!!data.blogPost.date);
const authors = const authors = $derived(
data.blogPost.authors == null data.blogPost.authors == null
? [] ? []
: typeof data.blogPost.authors === 'string' : typeof data.blogPost.authors === 'string'
? [data.blogPost.authors] ? [data.blogPost.authors]
: data.blogPost.authors; : data.blogPost.authors
const type: App.BlogPostType = data.blogPost.type ?? 'article'; );
const type: App.BlogPostType = $derived(data.blogPost.type ?? 'article');
let eventStatus = $state(EventStatus.NotEvent); let eventStatus = $state(EventStatus.NotEvent);
const PostTypeIcon = $derived(blogPostTypeToIconComponent(type));
onMount(() => { onMount(() => {
// HACK: rndtrash: NodeJS использует другой тип данных для таймаутов // HACK: rndtrash: NodeJS использует другой тип данных для таймаутов
let eventTimeout: ReturnType<typeof setTimeout> | undefined = undefined; let eventTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
@ -81,7 +84,7 @@
<DateWidget dateString={data.blogPost.dateChanged} type="updated" /> <DateWidget dateString={data.blogPost.dateChanged} type="updated" />
{/if} {/if}
<div class="flex items-center gap-2 p-1 text-lg font-bold"> <div class="flex items-center gap-2 p-1 text-lg font-bold">
<Icon icon={blogPostTypeToIcon(type)} width={28} height={28} /> <PostTypeIcon width={28} height={28} />
<span> <span>
{blogPostTypeToString(type)} {blogPostTypeToString(type)}
</span> </span>
@ -89,7 +92,7 @@
{#each authors as author} {#each authors as author}
<!-- TODO: rndtrash: из-за 404 не даёт собрать сайт. href="/team/{author}" --> <!-- TODO: rndtrash: из-за 404 не даёт собрать сайт. href="/team/{author}" -->
<a class="flex items-center gap-2 p-1 text-lg font-bold" href="#"> <a class="flex items-center gap-2 p-1 text-lg font-bold" href="#">
<Icon icon="material-symbols:person" width={28} height={28} /> <PersonIcon width={28} height={28} />
<span class="underline"> <span class="underline">
{author} {author}
</span> </span>
@ -156,7 +159,7 @@
class="prose class="prose
sm:prose-xl sm:prose-xl
prose-slate prose-slate
prose-code:break-words prose-code:wrap-break-word
prose-pre:drop-shadow-md prose-pre:drop-shadow-md
prose-headings:font-disket prose-headings:font-disket
prose-headings:mb-4 prose-headings:mb-4

View file

@ -2,10 +2,14 @@ import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import type { UserConfig } from 'vite'; import type { UserConfig } from 'vite';
import legacy from '@vitejs/plugin-legacy'; import legacy from '@vitejs/plugin-legacy';
import Icons from 'unplugin-icons/vite';
export default { export default {
plugins: [ plugins: [
sveltekit(), sveltekit(),
Icons({
compiler: 'svelte',
}),
tailwindcss(), tailwindcss(),
legacy({ legacy({
renderLegacyChunks: false renderLegacyChunks: false