Перенос рендера иконок на сторону сервера #8

Merged
rndtrash merged 1 commit from feature/no-js-icons into master 2026-02-14 04:36:25 +03:00
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"
},
"devDependencies": {
"@iconify/svelte": "^5.2.1",
"@iconify/css-svelte": "^1.0.0",
"@iconify/json": "^2.2.439",
"@react2svelte/swipeable": "^0.1.4",
"@svelte-put/dragscroll": "^4.0.0",
"@sveltejs/adapter-auto": "^7.0.0",
@ -41,9 +42,24 @@
"tailwindcss": "^4.0.9",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"unplugin-icons": "^23.0.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": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
@ -2252,10 +2268,10 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@iconify/svelte": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/@iconify/svelte/-/svelte-5.2.1.tgz",
"integrity": "sha512-zHmsIPmnIhGd5gc95bNN5FL+GifwMZv7M2rlZEpa7IXYGFJm/XGHdWf6PWQa6OBoC+R69WyiPO9NAj5wjfjbow==",
"node_modules/@iconify/css-svelte": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@iconify/css-svelte/-/css-svelte-1.0.0.tgz",
"integrity": "sha512-S9kZ+U/hQ+QVHZVLKu06xiVi0rII4DJCh2Z6M1m7IOyECBD7A1emk5pRx4fWG9Zp7ZOHL8dWO90sxtsaOPZBJw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2265,7 +2281,18 @@
"url": "https://github.com/sponsors/cyberalien"
},
"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": {
@ -2275,6 +2302,18 @@
"dev": true,
"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": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@ -2455,19 +2494,6 @@
"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": {
"version": "4.57.1",
"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": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.0.tgz",
"integrity": "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.1.tgz",
"integrity": "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@ -2866,9 +2892,9 @@
}
},
"node_modules/@sveltejs/kit": {
"version": "2.50.2",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz",
"integrity": "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==",
"version": "2.51.0",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.51.0.tgz",
"integrity": "sha512-BkcfxVVYmfxHGYLugemb7TWW+y/MNZbxXHXJ141lyyi2ozq0Q4kbqoV0cBGlh7F0vNtCMvfr7UPnxIaBjcP9gg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3275,6 +3301,13 @@
"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": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
@ -3686,6 +3719,13 @@
"dev": true,
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -4170,6 +4210,13 @@
"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": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -4917,6 +4964,24 @@
"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": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
@ -5085,19 +5150,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": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -5121,6 +5173,38 @@
"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": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@ -5190,8 +5274,7 @@
"https://github.com/sponsors/sxzz",
"https://opencollective.com/debug"
],
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/optionator": {
"version": "0.9.4",
@ -5243,6 +5326,13 @@
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -5283,6 +5373,13 @@
"dev": true,
"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": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -5291,18 +5388,30 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"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": ">=12"
"node": ">=8.6"
},
"funding": {
"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": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@ -5590,6 +5699,23 @@
"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": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -5957,9 +6083,9 @@
}
},
"node_modules/svelte": {
"version": "5.50.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.50.2.tgz",
"integrity": "sha512-WCxzm3BBf+Ase6RwiDPR4G36cM4Kb0NuhmLK6x44I+D6reaxizDDg8kBkk4jT/19+Rgmc44eZkOvMO6daoSFIw==",
"version": "5.51.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.51.0.tgz",
"integrity": "sha512-TOtjs5SjPi1iCXJ7aHwQBY+PEyk0koIc1FCKNFyS9kQuN4FBma9nRuxM4f4A0b5SCdej812vf2Rcql2I0s9FZg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -5967,6 +6093,7 @@
"@jridgewell/sourcemap-codec": "^1.5.0",
"@sveltejs/acorn-typescript": "^1.0.5",
"@types/estree": "^1.0.5",
"@types/trusted-types": "^2.0.7",
"acorn": "^8.12.1",
"aria-query": "^5.3.1",
"axobject-query": "^4.1.0",
@ -5984,9 +6111,9 @@
}
},
"node_modules/svelte-check": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.6.tgz",
"integrity": "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.0.tgz",
"integrity": "sha512-gB3FdEPb8tPO3Y7Dzc6d/Pm/KrXAhK+0Fk+LkcysVtupvAh6Y/IrBCEZNupq57oh0hcwlxCUamu/rq7GtvfSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -6159,6 +6286,16 @@
"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": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@ -6176,6 +6313,19 @@
"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": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -6232,6 +6382,13 @@
"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": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@ -6339,6 +6496,72 @@
"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": {
"version": "1.2.3",
"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": {
"version": "1.1.1",
"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": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -6571,6 +6814,22 @@
"dev": true,
"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": {
"version": "0.1.0",
"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"
},
"devDependencies": {
"@iconify/svelte": "^5.2.1",
"@iconify/css-svelte": "^1.0.0",
"@iconify/json": "^2.2.439",
"@react2svelte/swipeable": "^0.1.4",
"@svelte-put/dragscroll": "^4.0.0",
"@sveltejs/adapter-auto": "^7.0.0",
@ -41,6 +42,7 @@
"tailwindcss": "^4.0.9",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"unplugin-icons": "^23.0.1",
"vite": "^7.3.1"
},
"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 {
DurationFormatConstructor,
DurationFormatOptions as _DurationFormatOptions,
DurationInput as _DurationInput,
} from '@formatjs/intl-durationformat/src/types';
import type { Component } from 'svelte';
import 'unplugin-icons/types/svelte';
declare global {
// rndtrash: Терпим. https://github.com/microsoft/TypeScript/issues/60608
@ -15,7 +21,7 @@ declare global {
namespace App {
interface Route {
label: string;
icon: string;
icon?: Component | string;
href: string;
}

View file

@ -9,13 +9,12 @@
<script lang="ts">
import {
BLOG_POST_FRESHNESS_MILLIS,
blogPostTypeToIcon,
blogPostTypeToIconComponent,
blogPostTypeToString,
EventStatus,
postEventStatus
} from '$lib/util/Blogs';
import DateWidget from '$lib/components/DateWidget.svelte';
import Icon from '@iconify/svelte';
import { onMount } from 'svelte';
let {
@ -24,7 +23,7 @@
fullHeight = false
}: { 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 isPostUpdated = $state(false);
let isPostFresh = $derived(isPostNew || isPostUpdated);
@ -35,6 +34,8 @@
);
let eventHasStarted = $derived(eventStatus === EventStatus.InProgress);
const PostTypeIcon = $derived(blogPostTypeToIconComponent(type));
onMount(() => {
const dateNow = new Date().valueOf();
isPostNew = dateNow - new Date(post.date!).valueOf() <= BLOG_POST_FRESHNESS_MILLIS;
@ -150,7 +151,7 @@
/>
{/if}
<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')}>
{blogPostTypeToString(type)}
</span>

View file

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

View file

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

View file

@ -1,18 +1,27 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import type { Component, Snippet } from 'svelte';
export let bgStrong: string;
export let bgBleak: string;
export let icon: string;
export let caption: string;
let {
icon: IconComponent,
bgStrong,
bgBleak,
caption,
children
}: {
icon: Component;
bgStrong: string;
bgBleak: string;
caption: string;
children: Snippet;
} = $props();
</script>
<section class="flex flex-col sm:flex-row">
<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>
</div>
<div class="{bgBleak} p-4 sm:grow">
<slot />
{@render children()}
</div>
</section>

View file

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

View file

@ -1,24 +1,29 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { isLinkLocal, tryGetIcon } from '$lib/util/LinkResolver';
import HoverIcon from './HoverIcon.svelte';
import HoverIcon from '$lib/components/HoverIcon.svelte';
export let href: string;
let className: string = '';
export { className as class };
export let customIcon: string | null = null;
let {
href,
class: klass = '',
customIcon = null,
children
}: { href: string; class?: string; customIcon?: string | null; children: Snippet } = $props();
const iconSrc = $derived(customIcon ?? tryGetIcon(href));
</script>
<a
{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'}
>
<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
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>
</a>
</a>

View file

@ -1,23 +1,32 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { MediaQuery } from 'svelte/reactivity';
import HoverIcon from './HoverIcon.svelte';
import { isLinkLocal, tryGetIcon } from '$lib/util/LinkResolver';
export let href: string;
let className: string = '';
export { className as class };
export let customIcon: string | null = null;
let {
href,
class: klass = '',
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);
</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
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 class="text-emerald-900 underline">
<slot />
{@render children()}
</span>
</a>
</a>

View file

@ -1,12 +1,11 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import IconBlock from './IconBlock.svelte';
import WarningIcon from '~icons/material-symbols/warning';
let { children }: { children: Snippet } = $props();
</script>
<IconBlock
bgStrong="bg-yellow-500"
bgBleak="bg-yellow-50"
icon="material-symbols:warning"
caption="Внимание"
>
<slot />
<IconBlock bgStrong="bg-yellow-500" bgBleak="bg-yellow-50" icon={WarningIcon} caption="Внимание">
{@render children()}
</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 BLOG_POST_FRESHNESS_MILLIS = 3 * 24 * 60 * 60 * 1000; // 3 дня
@ -97,16 +99,20 @@ const bptToString: Record<App.BlogPostType, string> = {
'update': 'Обновление'
};
const bptToIcon: Record<App.BlogPostType, string> = {
'article': 'material-symbols:article',
'event': 'material-symbols:event',
'update': 'material-symbols:autorenew'
import ArticleIcon from '~icons/material-symbols/article';
import EventIcon from '~icons/material-symbols/event';
import UpdateIcon from '~icons/material-symbols/autorenew';
const bptToIcon: Record<App.BlogPostType, Component> = {
'article': ArticleIcon,
'event': EventIcon,
'update': UpdateIcon
};
export function blogPostTypeToString(type: App.BlogPostType): string {
return bptToString[type] ?? bptToString['article'];
}
export function blogPostTypeToIcon(type: App.BlogPostType): string {
export function blogPostTypeToIconComponent(type: App.BlogPostType): Component {
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: чашки на иконках выглядят страшненько, пока что пусть лучше будет голая ссылка
const icons: Record<string, string> = {
none: 'material-symbols:link',
'steamcommunity.com': 'simple-icons:steam',
'steampowered.com': 'simple-icons:steam',
't.me': 'simple-icons:telegram',
'twitter.com': 'simple-icons:x',
'x.com': 'simple-icons:x',
'github.com': 'simple-icons:github',
'youtube.com': 'simple-icons:youtube',
'itch.io': 'simple-icons:itchdotio',
'discord.com': 'simple-icons:discord',
'discord.gg': 'simple-icons:discord',
'gamebanana.com': 'simple-icons:gamebanana',
'bsky.app': 'simple-icons:bluesky',
'bsky.social': 'simple-icons:bluesky',
const icons: Record<string, LinkIcon> = {
none: DefaultLinkIcon,
'steamcommunity.com': SteamIcon,
'steampowered.com': SteamIcon,
't.me': TelegramIcon,
'twitter.com': TwitterIcon,
'x.com': TwitterIcon,
'github.com': GitHubIcon,
'youtube.com': YouTubeIcon,
'itch.io': ItchIoIcon,
'discord.com': DiscordIcon,
'discord.gg': DiscordIcon,
'gamebanana.com': GameBananaIcon,
'bsky.app': BlueSkyIcon,
'bsky.social': BlueSkyIcon,
// https://хамяк.рф
'xn--80auf8a2c.xn--p1ai': 'fluent-emoji-high-contrast:hamster',
'xn--80auf8a2c.xn--p1ai': HamsterIcon,
// 'teasanctuary.ru': '/icons/tea-sanctuary-white.svg',
'hl.teasanctuary.ru': '/icons/half-life.svg',
'git.teasanctuary.ru': 'devicon-plain:git',
'git.teasanctuary.ru': GitIcon,
// localhost: '/icons/tea-sanctuary-white.svg',
email: 'material-symbols:alternate-email',
rss: 'material-symbols:rss-feed'
email: EmailIcon,
rss: RssIcon
};
// Особые случаи, когда одним доменом второго уровня не ограничишься (например, randomtrash.itch.io)
@ -51,7 +71,9 @@ const specialResolvers: Record<string, (url: URL) => string> = {
'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;
if (href.startsWith('mailto:'))
return 'email';
@ -66,15 +88,16 @@ function getIconFromUrl(url: URL): string | undefined {
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;
try {
url = new URL(link, document.baseURI);
url = new URL(link, baseURI);
} catch {
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 '$src/syntax-highlight.css'; // https://github.com/PrismJS/prism-themes
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[] = [
{ label: 'главная', icon: 'material-symbols:emoji-food-beverage', href: '/' },
{ label: 'команда', icon: 'material-symbols:groups', href: '/team' },
{ label: 'блог', icon: 'material-symbols:newspaper', href: '/blog' },
{ label: 'проекты', icon: 'material-symbols:work', href: '/projects' },
{ label: 'контакты', icon: 'material-symbols:chat', href: '/contact' }
{
label: 'главная',
icon: TeaIcon,
href: '/'
},
{
label: 'команда',
icon: TeamIcon,
href: '/team'
},
{
label: 'блог',
icon: BlogIcon,
href: '/blog'
},
{
label: 'проекты',
icon: ProjectsIcon,
href: '/projects'
},
{
label: 'контакты',
icon: ContactsIcon,
href: '/contact'
}
];
</script>
@ -33,7 +62,7 @@
<NavBar {routes} />
<div class="flex grow-1 flex-col overflow-auto">
<div class="relative grow-1">
<slot />
{@render children()}
</div>
<footer class="bg-emerald-950">

View file

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

View file

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