├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .prettierrc.cjs ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public ├── textures │ └── azulejos.webp └── vite.svg ├── src ├── index.html ├── scripts │ ├── Canvas.ts │ ├── Gui.ts │ ├── Mouse2D.ts │ ├── core │ │ ├── BackBuffer.ts │ │ ├── ExtendedMaterials.ts │ │ ├── FrameBuffer.ts │ │ └── Three.ts │ ├── entry.ts │ └── shader │ │ ├── effect.fs │ │ └── quad.vs ├── styles │ ├── components │ │ └── link.scss │ ├── entry.scss │ ├── global.scss │ ├── imports.scss │ ├── lenis.scss │ └── mixins │ │ ├── fonts.scss │ │ └── media.scss ├── typescript.svg └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # 静的コンテンツを GitHub Pages にデプロイするためのシンプルなワークフロー 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # デフォルトブランチを対象としたプッシュ時にで実行されます 6 | push: 7 | branches: ['main'] 8 | paths-ignore: ['**/README.md'] 9 | 10 | # Actions タブから手動でワークフローを実行できるようにします 11 | workflow_dispatch: 12 | 13 | # GITHUB_TOKEN のパーミッションを設定し、GitHub Pages へのデプロイを許可します 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | 19 | # 1 つの同時デプロイメントを可能にする 20 | concurrency: 21 | group: 'pages' 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | # デプロイするだけなので、単一のデプロイジョブ 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v3 34 | - name: Set up Node 35 | uses: actions/setup-node@v3 36 | with: 37 | node-version: 18 38 | cache: 'npm' 39 | - name: Install dependencies 40 | run: npm install 41 | - name: Build 42 | run: npm run build 43 | - name: Setup Pages 44 | uses: actions/configure-pages@v3 45 | - name: Upload artifact 46 | uses: actions/upload-pages-artifact@v1 47 | with: 48 | # dist リポジトリのアップロード 49 | path: './dist' 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v1 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | module.exports = { 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: false, 6 | trailingComma: 'all', 7 | singleQuote: true, 8 | printWidth: 160, 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | inspired by https://robert.leitl.dev/artifacts/azulejos/#/object/cylinder . 4 | 5 | https://nemutas.github.io/azulejo/ 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azulejo", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "azulejo", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "lil-gui": "^0.18.2", 12 | "three": "^0.160.0" 13 | }, 14 | "devDependencies": { 15 | "@types/three": "^0.160.0", 16 | "autoprefixer": "^10.4.15", 17 | "prettier": "^3.0.3", 18 | "ress": "^5.0.2", 19 | "sass": "^1.67.0", 20 | "typescript": "^5.0.2", 21 | "vite": "^4.4.5", 22 | "vite-plugin-glsl": "^1.1.2" 23 | } 24 | }, 25 | "node_modules/@esbuild/android-arm": { 26 | "version": "0.18.20", 27 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 28 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 29 | "cpu": [ 30 | "arm" 31 | ], 32 | "dev": true, 33 | "optional": true, 34 | "os": [ 35 | "android" 36 | ], 37 | "engines": { 38 | "node": ">=12" 39 | } 40 | }, 41 | "node_modules/@esbuild/android-arm64": { 42 | "version": "0.18.20", 43 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 44 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 45 | "cpu": [ 46 | "arm64" 47 | ], 48 | "dev": true, 49 | "optional": true, 50 | "os": [ 51 | "android" 52 | ], 53 | "engines": { 54 | "node": ">=12" 55 | } 56 | }, 57 | "node_modules/@esbuild/android-x64": { 58 | "version": "0.18.20", 59 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 60 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 61 | "cpu": [ 62 | "x64" 63 | ], 64 | "dev": true, 65 | "optional": true, 66 | "os": [ 67 | "android" 68 | ], 69 | "engines": { 70 | "node": ">=12" 71 | } 72 | }, 73 | "node_modules/@esbuild/darwin-arm64": { 74 | "version": "0.18.20", 75 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 76 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 77 | "cpu": [ 78 | "arm64" 79 | ], 80 | "dev": true, 81 | "optional": true, 82 | "os": [ 83 | "darwin" 84 | ], 85 | "engines": { 86 | "node": ">=12" 87 | } 88 | }, 89 | "node_modules/@esbuild/darwin-x64": { 90 | "version": "0.18.20", 91 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 92 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 93 | "cpu": [ 94 | "x64" 95 | ], 96 | "dev": true, 97 | "optional": true, 98 | "os": [ 99 | "darwin" 100 | ], 101 | "engines": { 102 | "node": ">=12" 103 | } 104 | }, 105 | "node_modules/@esbuild/freebsd-arm64": { 106 | "version": "0.18.20", 107 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 108 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 109 | "cpu": [ 110 | "arm64" 111 | ], 112 | "dev": true, 113 | "optional": true, 114 | "os": [ 115 | "freebsd" 116 | ], 117 | "engines": { 118 | "node": ">=12" 119 | } 120 | }, 121 | "node_modules/@esbuild/freebsd-x64": { 122 | "version": "0.18.20", 123 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 124 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 125 | "cpu": [ 126 | "x64" 127 | ], 128 | "dev": true, 129 | "optional": true, 130 | "os": [ 131 | "freebsd" 132 | ], 133 | "engines": { 134 | "node": ">=12" 135 | } 136 | }, 137 | "node_modules/@esbuild/linux-arm": { 138 | "version": "0.18.20", 139 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 140 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 141 | "cpu": [ 142 | "arm" 143 | ], 144 | "dev": true, 145 | "optional": true, 146 | "os": [ 147 | "linux" 148 | ], 149 | "engines": { 150 | "node": ">=12" 151 | } 152 | }, 153 | "node_modules/@esbuild/linux-arm64": { 154 | "version": "0.18.20", 155 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 156 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 157 | "cpu": [ 158 | "arm64" 159 | ], 160 | "dev": true, 161 | "optional": true, 162 | "os": [ 163 | "linux" 164 | ], 165 | "engines": { 166 | "node": ">=12" 167 | } 168 | }, 169 | "node_modules/@esbuild/linux-ia32": { 170 | "version": "0.18.20", 171 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 172 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 173 | "cpu": [ 174 | "ia32" 175 | ], 176 | "dev": true, 177 | "optional": true, 178 | "os": [ 179 | "linux" 180 | ], 181 | "engines": { 182 | "node": ">=12" 183 | } 184 | }, 185 | "node_modules/@esbuild/linux-loong64": { 186 | "version": "0.18.20", 187 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 188 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 189 | "cpu": [ 190 | "loong64" 191 | ], 192 | "dev": true, 193 | "optional": true, 194 | "os": [ 195 | "linux" 196 | ], 197 | "engines": { 198 | "node": ">=12" 199 | } 200 | }, 201 | "node_modules/@esbuild/linux-mips64el": { 202 | "version": "0.18.20", 203 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 204 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 205 | "cpu": [ 206 | "mips64el" 207 | ], 208 | "dev": true, 209 | "optional": true, 210 | "os": [ 211 | "linux" 212 | ], 213 | "engines": { 214 | "node": ">=12" 215 | } 216 | }, 217 | "node_modules/@esbuild/linux-ppc64": { 218 | "version": "0.18.20", 219 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 220 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 221 | "cpu": [ 222 | "ppc64" 223 | ], 224 | "dev": true, 225 | "optional": true, 226 | "os": [ 227 | "linux" 228 | ], 229 | "engines": { 230 | "node": ">=12" 231 | } 232 | }, 233 | "node_modules/@esbuild/linux-riscv64": { 234 | "version": "0.18.20", 235 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 236 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 237 | "cpu": [ 238 | "riscv64" 239 | ], 240 | "dev": true, 241 | "optional": true, 242 | "os": [ 243 | "linux" 244 | ], 245 | "engines": { 246 | "node": ">=12" 247 | } 248 | }, 249 | "node_modules/@esbuild/linux-s390x": { 250 | "version": "0.18.20", 251 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 252 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 253 | "cpu": [ 254 | "s390x" 255 | ], 256 | "dev": true, 257 | "optional": true, 258 | "os": [ 259 | "linux" 260 | ], 261 | "engines": { 262 | "node": ">=12" 263 | } 264 | }, 265 | "node_modules/@esbuild/linux-x64": { 266 | "version": "0.18.20", 267 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 268 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 269 | "cpu": [ 270 | "x64" 271 | ], 272 | "dev": true, 273 | "optional": true, 274 | "os": [ 275 | "linux" 276 | ], 277 | "engines": { 278 | "node": ">=12" 279 | } 280 | }, 281 | "node_modules/@esbuild/netbsd-x64": { 282 | "version": "0.18.20", 283 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 284 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 285 | "cpu": [ 286 | "x64" 287 | ], 288 | "dev": true, 289 | "optional": true, 290 | "os": [ 291 | "netbsd" 292 | ], 293 | "engines": { 294 | "node": ">=12" 295 | } 296 | }, 297 | "node_modules/@esbuild/openbsd-x64": { 298 | "version": "0.18.20", 299 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 300 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 301 | "cpu": [ 302 | "x64" 303 | ], 304 | "dev": true, 305 | "optional": true, 306 | "os": [ 307 | "openbsd" 308 | ], 309 | "engines": { 310 | "node": ">=12" 311 | } 312 | }, 313 | "node_modules/@esbuild/sunos-x64": { 314 | "version": "0.18.20", 315 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 316 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 317 | "cpu": [ 318 | "x64" 319 | ], 320 | "dev": true, 321 | "optional": true, 322 | "os": [ 323 | "sunos" 324 | ], 325 | "engines": { 326 | "node": ">=12" 327 | } 328 | }, 329 | "node_modules/@esbuild/win32-arm64": { 330 | "version": "0.18.20", 331 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 332 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 333 | "cpu": [ 334 | "arm64" 335 | ], 336 | "dev": true, 337 | "optional": true, 338 | "os": [ 339 | "win32" 340 | ], 341 | "engines": { 342 | "node": ">=12" 343 | } 344 | }, 345 | "node_modules/@esbuild/win32-ia32": { 346 | "version": "0.18.20", 347 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 348 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 349 | "cpu": [ 350 | "ia32" 351 | ], 352 | "dev": true, 353 | "optional": true, 354 | "os": [ 355 | "win32" 356 | ], 357 | "engines": { 358 | "node": ">=12" 359 | } 360 | }, 361 | "node_modules/@esbuild/win32-x64": { 362 | "version": "0.18.20", 363 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 364 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 365 | "cpu": [ 366 | "x64" 367 | ], 368 | "dev": true, 369 | "optional": true, 370 | "os": [ 371 | "win32" 372 | ], 373 | "engines": { 374 | "node": ">=12" 375 | } 376 | }, 377 | "node_modules/@rollup/pluginutils": { 378 | "version": "5.0.4", 379 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", 380 | "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", 381 | "dev": true, 382 | "dependencies": { 383 | "@types/estree": "^1.0.0", 384 | "estree-walker": "^2.0.2", 385 | "picomatch": "^2.3.1" 386 | }, 387 | "engines": { 388 | "node": ">=14.0.0" 389 | }, 390 | "peerDependencies": { 391 | "rollup": "^1.20.0||^2.0.0||^3.0.0" 392 | }, 393 | "peerDependenciesMeta": { 394 | "rollup": { 395 | "optional": true 396 | } 397 | } 398 | }, 399 | "node_modules/@types/estree": { 400 | "version": "1.0.1", 401 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", 402 | "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", 403 | "dev": true 404 | }, 405 | "node_modules/@types/stats.js": { 406 | "version": "0.17.0", 407 | "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz", 408 | "integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==", 409 | "dev": true 410 | }, 411 | "node_modules/@types/three": { 412 | "version": "0.160.0", 413 | "resolved": "https://registry.npmjs.org/@types/three/-/three-0.160.0.tgz", 414 | "integrity": "sha512-jWlbUBovicUKaOYxzgkLlhkiEQJkhCVvg4W2IYD2trqD2om3VK4DGLpHH5zQHNr7RweZK/5re/4IVhbhvxbV9w==", 415 | "dev": true, 416 | "dependencies": { 417 | "@types/stats.js": "*", 418 | "@types/webxr": "*", 419 | "fflate": "~0.6.10", 420 | "meshoptimizer": "~0.18.1" 421 | } 422 | }, 423 | "node_modules/@types/webxr": { 424 | "version": "0.5.4", 425 | "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.4.tgz", 426 | "integrity": "sha512-41gfGLTtqXZhcmoDlLDHqMJDuwAMwhHwXf9Q2job3TUBsvkNfPNI/3IWVEtLH4tyY1ElWtfwIaoNeqeEX238/Q==", 427 | "dev": true 428 | }, 429 | "node_modules/anymatch": { 430 | "version": "3.1.3", 431 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 432 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 433 | "dev": true, 434 | "dependencies": { 435 | "normalize-path": "^3.0.0", 436 | "picomatch": "^2.0.4" 437 | }, 438 | "engines": { 439 | "node": ">= 8" 440 | } 441 | }, 442 | "node_modules/autoprefixer": { 443 | "version": "10.4.15", 444 | "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", 445 | "integrity": "sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==", 446 | "dev": true, 447 | "funding": [ 448 | { 449 | "type": "opencollective", 450 | "url": "https://opencollective.com/postcss/" 451 | }, 452 | { 453 | "type": "tidelift", 454 | "url": "https://tidelift.com/funding/github/npm/autoprefixer" 455 | }, 456 | { 457 | "type": "github", 458 | "url": "https://github.com/sponsors/ai" 459 | } 460 | ], 461 | "dependencies": { 462 | "browserslist": "^4.21.10", 463 | "caniuse-lite": "^1.0.30001520", 464 | "fraction.js": "^4.2.0", 465 | "normalize-range": "^0.1.2", 466 | "picocolors": "^1.0.0", 467 | "postcss-value-parser": "^4.2.0" 468 | }, 469 | "bin": { 470 | "autoprefixer": "bin/autoprefixer" 471 | }, 472 | "engines": { 473 | "node": "^10 || ^12 || >=14" 474 | }, 475 | "peerDependencies": { 476 | "postcss": "^8.1.0" 477 | } 478 | }, 479 | "node_modules/binary-extensions": { 480 | "version": "2.2.0", 481 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 482 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 483 | "dev": true, 484 | "engines": { 485 | "node": ">=8" 486 | } 487 | }, 488 | "node_modules/braces": { 489 | "version": "3.0.2", 490 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 491 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 492 | "dev": true, 493 | "dependencies": { 494 | "fill-range": "^7.0.1" 495 | }, 496 | "engines": { 497 | "node": ">=8" 498 | } 499 | }, 500 | "node_modules/browserslist": { 501 | "version": "4.21.10", 502 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", 503 | "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", 504 | "dev": true, 505 | "funding": [ 506 | { 507 | "type": "opencollective", 508 | "url": "https://opencollective.com/browserslist" 509 | }, 510 | { 511 | "type": "tidelift", 512 | "url": "https://tidelift.com/funding/github/npm/browserslist" 513 | }, 514 | { 515 | "type": "github", 516 | "url": "https://github.com/sponsors/ai" 517 | } 518 | ], 519 | "dependencies": { 520 | "caniuse-lite": "^1.0.30001517", 521 | "electron-to-chromium": "^1.4.477", 522 | "node-releases": "^2.0.13", 523 | "update-browserslist-db": "^1.0.11" 524 | }, 525 | "bin": { 526 | "browserslist": "cli.js" 527 | }, 528 | "engines": { 529 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 530 | } 531 | }, 532 | "node_modules/caniuse-lite": { 533 | "version": "1.0.30001534", 534 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz", 535 | "integrity": "sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q==", 536 | "dev": true, 537 | "funding": [ 538 | { 539 | "type": "opencollective", 540 | "url": "https://opencollective.com/browserslist" 541 | }, 542 | { 543 | "type": "tidelift", 544 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 545 | }, 546 | { 547 | "type": "github", 548 | "url": "https://github.com/sponsors/ai" 549 | } 550 | ] 551 | }, 552 | "node_modules/chokidar": { 553 | "version": "3.5.3", 554 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 555 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 556 | "dev": true, 557 | "funding": [ 558 | { 559 | "type": "individual", 560 | "url": "https://paulmillr.com/funding/" 561 | } 562 | ], 563 | "dependencies": { 564 | "anymatch": "~3.1.2", 565 | "braces": "~3.0.2", 566 | "glob-parent": "~5.1.2", 567 | "is-binary-path": "~2.1.0", 568 | "is-glob": "~4.0.1", 569 | "normalize-path": "~3.0.0", 570 | "readdirp": "~3.6.0" 571 | }, 572 | "engines": { 573 | "node": ">= 8.10.0" 574 | }, 575 | "optionalDependencies": { 576 | "fsevents": "~2.3.2" 577 | } 578 | }, 579 | "node_modules/electron-to-chromium": { 580 | "version": "1.4.521", 581 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.521.tgz", 582 | "integrity": "sha512-88W7FAsYzc3Vy2mGCFe/YTD6kvoJpqeLRBd5NFMRMdYNqsjHYzkn/mGxaOFJ8yYfLuQqC6vpDYbN6Ps5mtIM3w==", 583 | "dev": true 584 | }, 585 | "node_modules/esbuild": { 586 | "version": "0.18.20", 587 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 588 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 589 | "dev": true, 590 | "hasInstallScript": true, 591 | "bin": { 592 | "esbuild": "bin/esbuild" 593 | }, 594 | "engines": { 595 | "node": ">=12" 596 | }, 597 | "optionalDependencies": { 598 | "@esbuild/android-arm": "0.18.20", 599 | "@esbuild/android-arm64": "0.18.20", 600 | "@esbuild/android-x64": "0.18.20", 601 | "@esbuild/darwin-arm64": "0.18.20", 602 | "@esbuild/darwin-x64": "0.18.20", 603 | "@esbuild/freebsd-arm64": "0.18.20", 604 | "@esbuild/freebsd-x64": "0.18.20", 605 | "@esbuild/linux-arm": "0.18.20", 606 | "@esbuild/linux-arm64": "0.18.20", 607 | "@esbuild/linux-ia32": "0.18.20", 608 | "@esbuild/linux-loong64": "0.18.20", 609 | "@esbuild/linux-mips64el": "0.18.20", 610 | "@esbuild/linux-ppc64": "0.18.20", 611 | "@esbuild/linux-riscv64": "0.18.20", 612 | "@esbuild/linux-s390x": "0.18.20", 613 | "@esbuild/linux-x64": "0.18.20", 614 | "@esbuild/netbsd-x64": "0.18.20", 615 | "@esbuild/openbsd-x64": "0.18.20", 616 | "@esbuild/sunos-x64": "0.18.20", 617 | "@esbuild/win32-arm64": "0.18.20", 618 | "@esbuild/win32-ia32": "0.18.20", 619 | "@esbuild/win32-x64": "0.18.20" 620 | } 621 | }, 622 | "node_modules/escalade": { 623 | "version": "3.1.1", 624 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 625 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 626 | "dev": true, 627 | "engines": { 628 | "node": ">=6" 629 | } 630 | }, 631 | "node_modules/estree-walker": { 632 | "version": "2.0.2", 633 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 634 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 635 | "dev": true 636 | }, 637 | "node_modules/fflate": { 638 | "version": "0.6.10", 639 | "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", 640 | "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", 641 | "dev": true 642 | }, 643 | "node_modules/fill-range": { 644 | "version": "7.0.1", 645 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 646 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 647 | "dev": true, 648 | "dependencies": { 649 | "to-regex-range": "^5.0.1" 650 | }, 651 | "engines": { 652 | "node": ">=8" 653 | } 654 | }, 655 | "node_modules/fraction.js": { 656 | "version": "4.3.6", 657 | "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", 658 | "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", 659 | "dev": true, 660 | "engines": { 661 | "node": "*" 662 | }, 663 | "funding": { 664 | "type": "patreon", 665 | "url": "https://github.com/sponsors/rawify" 666 | } 667 | }, 668 | "node_modules/fsevents": { 669 | "version": "2.3.3", 670 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 671 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 672 | "dev": true, 673 | "hasInstallScript": true, 674 | "optional": true, 675 | "os": [ 676 | "darwin" 677 | ], 678 | "engines": { 679 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 680 | } 681 | }, 682 | "node_modules/glob-parent": { 683 | "version": "5.1.2", 684 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 685 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 686 | "dev": true, 687 | "dependencies": { 688 | "is-glob": "^4.0.1" 689 | }, 690 | "engines": { 691 | "node": ">= 6" 692 | } 693 | }, 694 | "node_modules/immutable": { 695 | "version": "4.3.4", 696 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", 697 | "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", 698 | "dev": true 699 | }, 700 | "node_modules/is-binary-path": { 701 | "version": "2.1.0", 702 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 703 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 704 | "dev": true, 705 | "dependencies": { 706 | "binary-extensions": "^2.0.0" 707 | }, 708 | "engines": { 709 | "node": ">=8" 710 | } 711 | }, 712 | "node_modules/is-extglob": { 713 | "version": "2.1.1", 714 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 715 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 716 | "dev": true, 717 | "engines": { 718 | "node": ">=0.10.0" 719 | } 720 | }, 721 | "node_modules/is-glob": { 722 | "version": "4.0.3", 723 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 724 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 725 | "dev": true, 726 | "dependencies": { 727 | "is-extglob": "^2.1.1" 728 | }, 729 | "engines": { 730 | "node": ">=0.10.0" 731 | } 732 | }, 733 | "node_modules/is-number": { 734 | "version": "7.0.0", 735 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 736 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 737 | "dev": true, 738 | "engines": { 739 | "node": ">=0.12.0" 740 | } 741 | }, 742 | "node_modules/lil-gui": { 743 | "version": "0.18.2", 744 | "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.18.2.tgz", 745 | "integrity": "sha512-DgdrLy3/KGC0PiQLKgOcJMPItP4xY4iWgJ9+91Zaxfr8GCTmMps05QS9w9jW7yspILlbscbquwjOwxmWnSx5Uw==" 746 | }, 747 | "node_modules/meshoptimizer": { 748 | "version": "0.18.1", 749 | "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", 750 | "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", 751 | "dev": true 752 | }, 753 | "node_modules/nanoid": { 754 | "version": "3.3.6", 755 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 756 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 757 | "dev": true, 758 | "funding": [ 759 | { 760 | "type": "github", 761 | "url": "https://github.com/sponsors/ai" 762 | } 763 | ], 764 | "bin": { 765 | "nanoid": "bin/nanoid.cjs" 766 | }, 767 | "engines": { 768 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 769 | } 770 | }, 771 | "node_modules/node-releases": { 772 | "version": "2.0.13", 773 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", 774 | "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", 775 | "dev": true 776 | }, 777 | "node_modules/normalize-path": { 778 | "version": "3.0.0", 779 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 780 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 781 | "dev": true, 782 | "engines": { 783 | "node": ">=0.10.0" 784 | } 785 | }, 786 | "node_modules/normalize-range": { 787 | "version": "0.1.2", 788 | "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", 789 | "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", 790 | "dev": true, 791 | "engines": { 792 | "node": ">=0.10.0" 793 | } 794 | }, 795 | "node_modules/picocolors": { 796 | "version": "1.0.0", 797 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 798 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 799 | "dev": true 800 | }, 801 | "node_modules/picomatch": { 802 | "version": "2.3.1", 803 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 804 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 805 | "dev": true, 806 | "engines": { 807 | "node": ">=8.6" 808 | }, 809 | "funding": { 810 | "url": "https://github.com/sponsors/jonschlinkert" 811 | } 812 | }, 813 | "node_modules/postcss": { 814 | "version": "8.4.31", 815 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 816 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 817 | "dev": true, 818 | "funding": [ 819 | { 820 | "type": "opencollective", 821 | "url": "https://opencollective.com/postcss/" 822 | }, 823 | { 824 | "type": "tidelift", 825 | "url": "https://tidelift.com/funding/github/npm/postcss" 826 | }, 827 | { 828 | "type": "github", 829 | "url": "https://github.com/sponsors/ai" 830 | } 831 | ], 832 | "dependencies": { 833 | "nanoid": "^3.3.6", 834 | "picocolors": "^1.0.0", 835 | "source-map-js": "^1.0.2" 836 | }, 837 | "engines": { 838 | "node": "^10 || ^12 || >=14" 839 | } 840 | }, 841 | "node_modules/postcss-value-parser": { 842 | "version": "4.2.0", 843 | "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", 844 | "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", 845 | "dev": true 846 | }, 847 | "node_modules/prettier": { 848 | "version": "3.0.3", 849 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", 850 | "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", 851 | "dev": true, 852 | "bin": { 853 | "prettier": "bin/prettier.cjs" 854 | }, 855 | "engines": { 856 | "node": ">=14" 857 | }, 858 | "funding": { 859 | "url": "https://github.com/prettier/prettier?sponsor=1" 860 | } 861 | }, 862 | "node_modules/readdirp": { 863 | "version": "3.6.0", 864 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 865 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 866 | "dev": true, 867 | "dependencies": { 868 | "picomatch": "^2.2.1" 869 | }, 870 | "engines": { 871 | "node": ">=8.10.0" 872 | } 873 | }, 874 | "node_modules/ress": { 875 | "version": "5.0.2", 876 | "resolved": "https://registry.npmjs.org/ress/-/ress-5.0.2.tgz", 877 | "integrity": "sha512-oHBtOWo/Uc8SzQMbQNIKTcgi8wKmAs7IlNlRywmXudbOtF+c27FlOIq7tnwLDVcTywe6JXYo1pDXHO6kABwNYA==", 878 | "dev": true 879 | }, 880 | "node_modules/rollup": { 881 | "version": "3.29.1", 882 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.1.tgz", 883 | "integrity": "sha512-c+ebvQz0VIH4KhhCpDsI+Bik0eT8ZFEVZEYw0cGMVqIP8zc+gnwl7iXCamTw7vzv2MeuZFZfdx5JJIq+ehzDlg==", 884 | "dev": true, 885 | "bin": { 886 | "rollup": "dist/bin/rollup" 887 | }, 888 | "engines": { 889 | "node": ">=14.18.0", 890 | "npm": ">=8.0.0" 891 | }, 892 | "optionalDependencies": { 893 | "fsevents": "~2.3.2" 894 | } 895 | }, 896 | "node_modules/sass": { 897 | "version": "1.67.0", 898 | "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz", 899 | "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==", 900 | "dev": true, 901 | "dependencies": { 902 | "chokidar": ">=3.0.0 <4.0.0", 903 | "immutable": "^4.0.0", 904 | "source-map-js": ">=0.6.2 <2.0.0" 905 | }, 906 | "bin": { 907 | "sass": "sass.js" 908 | }, 909 | "engines": { 910 | "node": ">=14.0.0" 911 | } 912 | }, 913 | "node_modules/source-map-js": { 914 | "version": "1.0.2", 915 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 916 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 917 | "dev": true, 918 | "engines": { 919 | "node": ">=0.10.0" 920 | } 921 | }, 922 | "node_modules/three": { 923 | "version": "0.160.0", 924 | "resolved": "https://registry.npmjs.org/three/-/three-0.160.0.tgz", 925 | "integrity": "sha512-DLU8lc0zNIPkM7rH5/e1Ks1Z8tWCGRq6g8mPowdDJpw1CFBJMU7UoJjC6PefXW7z//SSl0b2+GCw14LB+uDhng==" 926 | }, 927 | "node_modules/to-regex-range": { 928 | "version": "5.0.1", 929 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 930 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 931 | "dev": true, 932 | "dependencies": { 933 | "is-number": "^7.0.0" 934 | }, 935 | "engines": { 936 | "node": ">=8.0" 937 | } 938 | }, 939 | "node_modules/typescript": { 940 | "version": "5.2.2", 941 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", 942 | "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", 943 | "dev": true, 944 | "bin": { 945 | "tsc": "bin/tsc", 946 | "tsserver": "bin/tsserver" 947 | }, 948 | "engines": { 949 | "node": ">=14.17" 950 | } 951 | }, 952 | "node_modules/update-browserslist-db": { 953 | "version": "1.0.11", 954 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", 955 | "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", 956 | "dev": true, 957 | "funding": [ 958 | { 959 | "type": "opencollective", 960 | "url": "https://opencollective.com/browserslist" 961 | }, 962 | { 963 | "type": "tidelift", 964 | "url": "https://tidelift.com/funding/github/npm/browserslist" 965 | }, 966 | { 967 | "type": "github", 968 | "url": "https://github.com/sponsors/ai" 969 | } 970 | ], 971 | "dependencies": { 972 | "escalade": "^3.1.1", 973 | "picocolors": "^1.0.0" 974 | }, 975 | "bin": { 976 | "update-browserslist-db": "cli.js" 977 | }, 978 | "peerDependencies": { 979 | "browserslist": ">= 4.21.0" 980 | } 981 | }, 982 | "node_modules/vite": { 983 | "version": "4.5.2", 984 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", 985 | "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", 986 | "dev": true, 987 | "dependencies": { 988 | "esbuild": "^0.18.10", 989 | "postcss": "^8.4.27", 990 | "rollup": "^3.27.1" 991 | }, 992 | "bin": { 993 | "vite": "bin/vite.js" 994 | }, 995 | "engines": { 996 | "node": "^14.18.0 || >=16.0.0" 997 | }, 998 | "funding": { 999 | "url": "https://github.com/vitejs/vite?sponsor=1" 1000 | }, 1001 | "optionalDependencies": { 1002 | "fsevents": "~2.3.2" 1003 | }, 1004 | "peerDependencies": { 1005 | "@types/node": ">= 14", 1006 | "less": "*", 1007 | "lightningcss": "^1.21.0", 1008 | "sass": "*", 1009 | "stylus": "*", 1010 | "sugarss": "*", 1011 | "terser": "^5.4.0" 1012 | }, 1013 | "peerDependenciesMeta": { 1014 | "@types/node": { 1015 | "optional": true 1016 | }, 1017 | "less": { 1018 | "optional": true 1019 | }, 1020 | "lightningcss": { 1021 | "optional": true 1022 | }, 1023 | "sass": { 1024 | "optional": true 1025 | }, 1026 | "stylus": { 1027 | "optional": true 1028 | }, 1029 | "sugarss": { 1030 | "optional": true 1031 | }, 1032 | "terser": { 1033 | "optional": true 1034 | } 1035 | } 1036 | }, 1037 | "node_modules/vite-plugin-glsl": { 1038 | "version": "1.1.2", 1039 | "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.1.2.tgz", 1040 | "integrity": "sha512-zmXsfc1vn2MlYve9t3FAoWuhLyoCkNS1TuQL+TkXZL7tGmBjRErp10eNYxcse5tK9oUC5MyJpNc4ElpQnx8DoA==", 1041 | "dev": true, 1042 | "dependencies": { 1043 | "@rollup/pluginutils": "^5.0.2" 1044 | }, 1045 | "engines": { 1046 | "node": ">= 16.15.1", 1047 | "npm": ">= 8.11.0" 1048 | }, 1049 | "peerDependencies": { 1050 | "vite": "^3.0.0 || ^4.0.0" 1051 | } 1052 | } 1053 | } 1054 | } 1055 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azulejo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "start": "vite", 9 | "build": "tsc && vite build", 10 | "preview": "vite preview" 11 | }, 12 | "devDependencies": { 13 | "@types/three": "^0.160.0", 14 | "autoprefixer": "^10.4.15", 15 | "prettier": "^3.0.3", 16 | "ress": "^5.0.2", 17 | "sass": "^1.67.0", 18 | "typescript": "^5.0.2", 19 | "vite": "^4.4.5", 20 | "vite-plugin-glsl": "^1.1.2" 21 | }, 22 | "dependencies": { 23 | "lil-gui": "^0.18.2", 24 | "three": "^0.160.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('autoprefixer')], 3 | } 4 | -------------------------------------------------------------------------------- /public/textures/azulejos.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemutas/azulejo/3591b5d7320e76b369d2095aa4892bf841d0618d/public/textures/azulejos.webp -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + TS 8 | 9 | 10 | 11 | 12 |
13 | 14 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/scripts/Canvas.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { RawShaderMaterial } from './core/ExtendedMaterials' 3 | import { Three } from './core/Three' 4 | import quadVs from './shader/quad.vs' 5 | import effectFs from './shader/effect.fs' 6 | 7 | export class Canvas extends Three { 8 | private mainRT: THREE.WebGLRenderTarget 9 | private mainScene: THREE.Scene 10 | private effect!: THREE.Mesh 11 | 12 | constructor(canvas: HTMLCanvasElement) { 13 | super(canvas) 14 | 15 | const dpr = this.renderer.getPixelRatio() 16 | this.mainRT = new THREE.WebGLRenderTarget(this.size.width * dpr, this.size.height * dpr) 17 | this.mainScene = new THREE.Scene() 18 | 19 | this.init() 20 | 21 | this.loadAssets().then((assets) => { 22 | this.createLights() 23 | this.createModel() 24 | this.effect = this.createEffect(assets[0]) 25 | window.addEventListener('resize', this.resize.bind(this)) 26 | this.renderer.setAnimationLoop(this.anime.bind(this)) 27 | }) 28 | } 29 | 30 | private async loadAssets() { 31 | const loader = new THREE.TextureLoader() 32 | 33 | return await Promise.all( 34 | ['azulejos'].map(async (filename) => { 35 | const texture = await loader.loadAsync(`${import.meta.env.BASE_URL}textures/${filename}.webp`) 36 | texture.userData.aspect = texture.source.data.width / texture.source.data.height 37 | texture.wrapS = THREE.RepeatWrapping 38 | texture.wrapT = THREE.RepeatWrapping 39 | return texture 40 | }), 41 | ) 42 | } 43 | 44 | private init() { 45 | this.mainScene.background = new THREE.Color('#fff') 46 | 47 | this.controls.enableDamping = true 48 | this.controls.dampingFactor = 0.03 49 | this.controls.enablePan = false 50 | } 51 | 52 | private createLights() { 53 | const amb = new THREE.AmbientLight('#fff', 0.5) 54 | this.mainScene.add(amb) 55 | 56 | const dir = new THREE.DirectionalLight('#fff', 2.0) 57 | dir.position.set(-1, 5, 3) 58 | this.mainScene.add(dir) 59 | } 60 | 61 | private createModel() { 62 | let geometry!: THREE.BufferGeometry 63 | const r = Math.random() 64 | if (r < 0.5) { 65 | // cylinder 66 | this.camera.position.set(0.76, 0.93, 0.92) 67 | geometry = new THREE.CylinderGeometry(0.3, 0.3, 1) 68 | geometry.rotateX(Math.PI * 0.5) 69 | } else { 70 | // torus 71 | this.camera.position.set(0.61, -0.53, 1.1) 72 | geometry = new THREE.TorusGeometry(0.3, 0.15, 48, 96) 73 | } 74 | const material = new THREE.MeshStandardMaterial({ color: '#fff', emissive: '#777' }) 75 | const mesh = new THREE.Mesh(geometry, material) 76 | this.mainScene.add(mesh) 77 | } 78 | 79 | private createEffect(map: THREE.Texture) { 80 | const geometry = new THREE.PlaneGeometry(2, 2) 81 | const material = new RawShaderMaterial({ 82 | uniforms: { 83 | uSource: { value: this.mainRT.texture }, 84 | uResolution: { value: [this.size.width, this.size.height] }, 85 | uMap: { value: map }, 86 | uMapPx: { value: [1 / 6, 1 / 5] }, 87 | }, 88 | vertexShader: quadVs, 89 | fragmentShader: effectFs, 90 | glslVersion: '300 es', 91 | }) 92 | const mesh = new THREE.Mesh(geometry, material) 93 | this.scene.add(mesh) 94 | return mesh 95 | } 96 | 97 | private resize() { 98 | const dpr = this.renderer.getPixelRatio() 99 | this.mainRT.setSize(this.size.width * dpr, this.size.height * dpr) 100 | this.effect.material.uniforms.uResolution.value = [this.size.width, this.size.height] 101 | } 102 | 103 | private anime() { 104 | this.updateTime() 105 | this.controls.update() 106 | 107 | this.renderer.setRenderTarget(this.mainRT) 108 | this.renderer.render(this.mainScene, this.camera) 109 | 110 | this.render() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/scripts/Gui.ts: -------------------------------------------------------------------------------- 1 | import GUI from 'lil-gui' 2 | 3 | export const gui = new GUI() 4 | gui.close() 5 | -------------------------------------------------------------------------------- /src/scripts/Mouse2D.ts: -------------------------------------------------------------------------------- 1 | class Mouse2D { 2 | readonly position: [number, number] = [99999, 99999] 3 | readonly prevPosition: [number, number] = [99999, 99999] 4 | 5 | constructor() { 6 | window.addEventListener('mousemove', this.handleMouseMove) 7 | window.addEventListener('touchmove', this.handleTouchMove) 8 | } 9 | 10 | private handleMouseMove = (e: MouseEvent) => { 11 | this.prevPosition[0] = this.position[0] 12 | this.prevPosition[1] = this.position[1] 13 | this.position[0] = (e.clientX / window.innerWidth) * 2 - 1 14 | this.position[1] = -1 * ((e.clientY / window.innerHeight) * 2 - 1) 15 | } 16 | 17 | private handleTouchMove = (e: TouchEvent) => { 18 | const { pageX, pageY } = e.touches[0] 19 | this.prevPosition[0] = this.position[0] 20 | this.prevPosition[1] = this.position[1] 21 | this.position[0] = (pageX / window.innerWidth) * 2 - 1 22 | this.position[1] = -1 * ((pageY / window.innerHeight) * 2 - 1) 23 | } 24 | 25 | lerp(t: number) { 26 | this.prevPosition[0] = this.prevPosition[0] * (1 - t) + this.position[0] * t 27 | this.prevPosition[1] = this.prevPosition[1] * (1 - t) + this.position[1] * t 28 | return [this.position[0] - this.prevPosition[0], this.position[1] - this.prevPosition[1]] 29 | } 30 | 31 | dispose() { 32 | window.removeEventListener('mousemove', this.handleMouseMove) 33 | window.removeEventListener('touchmove', this.handleTouchMove) 34 | } 35 | } 36 | 37 | export const mouse2d = new Mouse2D() 38 | -------------------------------------------------------------------------------- /src/scripts/core/BackBuffer.ts: -------------------------------------------------------------------------------- 1 | import { FrameBuffer, Options } from './FrameBuffer' 2 | 3 | export abstract class BackBuffer extends FrameBuffer { 4 | private readonly rt2: THREE.WebGLRenderTarget 5 | private prev: THREE.WebGLRenderTarget 6 | private current: THREE.WebGLRenderTarget 7 | 8 | constructor(renderer: THREE.WebGLRenderer, material: THREE.RawShaderMaterial, options?: Options) { 9 | super(renderer, material, options) 10 | 11 | this.rt2 = this.createRenderTarget() 12 | this.prev = this.renderTarget 13 | this.current = this.rt2 14 | } 15 | 16 | resize() { 17 | super.resize() 18 | this.rt2.setSize(this.size.width, this.size.height) 19 | } 20 | 21 | get backBuffer() { 22 | return this.prev.texture 23 | } 24 | 25 | private swap() { 26 | this.current = this.current === this.renderTarget ? this.rt2 : this.renderTarget 27 | this.prev = this.current === this.renderTarget ? this.rt2 : this.renderTarget 28 | } 29 | 30 | render(..._args: any) { 31 | this.renderer.setRenderTarget(this.current) 32 | this.renderer.render(this.scene, this.camera) 33 | 34 | this.swap() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/scripts/core/ExtendedMaterials.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | 3 | export class RawShaderMaterial extends THREE.RawShaderMaterial { 4 | constructor(parameters: THREE.ShaderMaterialParameters) { 5 | super(parameters) 6 | this.preprocess() 7 | } 8 | 9 | private preprocess() { 10 | if (this.glslVersion === '300 es') { 11 | this.vertexShader = this.vertexShader.replace('#version 300 es', '') 12 | this.fragmentShader = this.fragmentShader.replace('#version 300 es', '') 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/scripts/core/FrameBuffer.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | 3 | export type Options = { 4 | dpr?: number 5 | matrixAutoUpdate?: boolean 6 | size?: [number, number] 7 | } 8 | 9 | export abstract class FrameBuffer { 10 | protected readonly scene: THREE.Scene 11 | protected readonly camera: THREE.OrthographicCamera 12 | protected readonly renderTarget: THREE.WebGLRenderTarget 13 | private readonly screen: THREE.Mesh 14 | 15 | constructor( 16 | protected readonly renderer: THREE.WebGLRenderer, 17 | material: THREE.RawShaderMaterial, 18 | private options?: Options, 19 | ) { 20 | this.scene = new THREE.Scene() 21 | this.camera = new THREE.OrthographicCamera() 22 | this.renderTarget = this.createRenderTarget() 23 | this.screen = this.createScreen(material) 24 | 25 | this.setMatrixAutoUpdate(options?.matrixAutoUpdate ?? false) 26 | } 27 | 28 | private get devicePixelRatio() { 29 | return this.options?.dpr ?? this.renderer.getPixelRatio() 30 | } 31 | 32 | private setMatrixAutoUpdate(v: boolean) { 33 | this.camera.matrixAutoUpdate = v 34 | this.scene.traverse((o) => (o.matrixAutoUpdate = v)) 35 | } 36 | 37 | protected get size() { 38 | const width = (this.options?.size?.[0] ?? this.renderer.domElement.width) * this.devicePixelRatio 39 | const height = (this.options?.size?.[1] ?? this.renderer.domElement.height) * this.devicePixelRatio 40 | return { width, height } 41 | } 42 | 43 | protected createRenderTarget() { 44 | const rt = new THREE.WebGLRenderTarget(this.size.width, this.size.height, { 45 | wrapS: THREE.RepeatWrapping, 46 | wrapT: THREE.RepeatWrapping, 47 | minFilter: THREE.NearestFilter, 48 | magFilter: THREE.NearestFilter, 49 | }) 50 | return rt 51 | } 52 | 53 | private createScreen(material: THREE.RawShaderMaterial) { 54 | const geometry = new THREE.PlaneGeometry(2, 2) 55 | const mesh = new THREE.Mesh(geometry, material) 56 | this.scene.add(mesh) 57 | return mesh 58 | } 59 | 60 | get uniforms() { 61 | return this.screen.material.uniforms 62 | } 63 | 64 | resize() { 65 | this.renderTarget.setSize(this.size.width, this.size.height) 66 | } 67 | 68 | get texture() { 69 | return this.renderTarget.texture 70 | } 71 | 72 | render(..._args: any[]) { 73 | this.renderer.setRenderTarget(this.renderTarget) 74 | this.renderer.render(this.scene, this.camera) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/scripts/core/Three.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' 3 | import Stats from 'three/examples/jsm/libs/stats.module.js' 4 | 5 | export abstract class Three { 6 | readonly renderer: THREE.WebGLRenderer 7 | readonly camera: THREE.PerspectiveCamera 8 | readonly scene: THREE.Scene 9 | private clock: THREE.Clock 10 | private _stats?: Stats 11 | private _controls?: OrbitControls 12 | readonly time = { delta: 0, elapsed: 0 } 13 | 14 | constructor(canvas: HTMLCanvasElement) { 15 | this.renderer = this.createRenderer(canvas) 16 | this.camera = this.createCamera() 17 | this.scene = this.createScene() 18 | this.clock = new THREE.Clock() 19 | 20 | window.addEventListener('resize', this._resize.bind(this)) 21 | } 22 | 23 | private createRenderer(canvas: HTMLCanvasElement) { 24 | const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }) 25 | renderer.setSize(window.innerWidth, window.innerHeight) 26 | // renderer.setPixelRatio(window.devicePixelRatio) 27 | renderer.setPixelRatio(2) 28 | renderer.shadowMap.enabled = true 29 | return renderer 30 | } 31 | 32 | private createCamera() { 33 | const camera = new THREE.PerspectiveCamera(40, this.size.aspect, 0.01, 100) 34 | camera.position.z = 5 35 | return camera 36 | } 37 | 38 | private createScene() { 39 | const scene = new THREE.Scene() 40 | return scene 41 | } 42 | 43 | protected get stats() { 44 | if (!this._stats) { 45 | this._stats = new Stats() 46 | document.body.appendChild(this._stats.dom) 47 | } 48 | return this._stats 49 | } 50 | 51 | private _resize() { 52 | const { innerWidth: width, innerHeight: height } = window 53 | this.renderer.setSize(width, height) 54 | this.camera.aspect = width / height 55 | this.camera.updateProjectionMatrix() 56 | } 57 | 58 | get size() { 59 | const { width, height } = this.renderer.domElement 60 | return { width, height, aspect: width / height } 61 | } 62 | 63 | protected updateTime() { 64 | this.time.delta = this.clock.getDelta() 65 | this.time.elapsed = this.clock.getElapsedTime() 66 | } 67 | 68 | protected get controls() { 69 | if (!this._controls) { 70 | this._controls = new OrbitControls(this.camera, this.renderer.domElement) 71 | } 72 | return this._controls 73 | } 74 | 75 | protected coveredScale(imageAspect: number) { 76 | const screenAspect = this.size.aspect 77 | if (screenAspect < imageAspect) return [screenAspect / imageAspect, 1] 78 | else return [1, imageAspect / screenAspect] 79 | } 80 | 81 | protected render() { 82 | this.renderer.setRenderTarget(null) 83 | this.renderer.render(this.scene, this.camera) 84 | } 85 | 86 | dispose() { 87 | this.renderer.setAnimationLoop(null) 88 | this.renderer.dispose() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/scripts/entry.ts: -------------------------------------------------------------------------------- 1 | import { Canvas } from './Canvas' 2 | 3 | const canvas = new Canvas(document.querySelector('.webgl-canvas')!) 4 | 5 | window.addEventListener('beforeunload', () => { 6 | canvas.dispose() 7 | }) 8 | -------------------------------------------------------------------------------- /src/scripts/shader/effect.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | in vec2 vUv; 5 | out vec4 outColor; 6 | 7 | uniform sampler2D uSource; 8 | uniform vec2 uResolution; 9 | uniform sampler2D uMap; 10 | uniform vec2 uMapPx; 11 | 12 | #define sat(v) clamp(v, 0.0, 1.0) 13 | 14 | vec3 hash(vec3 v) { 15 | uvec3 x = floatBitsToUint(v + vec3(0.1, 0.2, 0.3)); 16 | x = (x >> 8 ^ x.yzx) * 0x456789ABu; 17 | x = (x >> 8 ^ x.yzx) * 0x6789AB45u; 18 | x = (x >> 8 ^ x.yzx) * 0x89AB4567u; 19 | return vec3(x) / vec3(-1u); 20 | } 21 | 22 | void main() { 23 | vec2 uv = vUv; 24 | vec2 asp = uResolution / min(uResolution.x, uResolution.y); 25 | 26 | vec2 quv = uv * asp, fuv, iuv; 27 | float i, avg = 1.0; 28 | for(; i < 6.0; i++) { 29 | fuv = fract(quv); 30 | iuv = floor(quv) / pow(2.0, i) / asp; 31 | if (2.0 <= i) { 32 | // 代表点(4点)の輝度の分散を求める 33 | vec2 px = 1.0 / pow(2.0, i) / asp; 34 | px *= 0.5; 35 | vec3 c1 = texture(uSource, iuv + px * vec2(0, 0) + px * 0.5).rgb; 36 | vec3 c2 = texture(uSource, iuv + px * vec2(1, 0) + px * 0.5).rgb; 37 | vec3 c3 = texture(uSource, iuv + px * vec2(0, 1) + px * 0.5).rgb; 38 | vec3 c4 = texture(uSource, iuv + px * vec2(1, 1) + px * 0.5).rgb; 39 | 40 | vec4 g = vec4( 41 | dot(c1, vec3(0.299, 0.587, 0.114)), 42 | dot(c2, vec3(0.299, 0.587, 0.114)), 43 | dot(c3, vec3(0.299, 0.587, 0.114)), 44 | dot(c4, vec3(0.299, 0.587, 0.114)) 45 | ); 46 | 47 | avg = dot(vec4(1), g) / 4.0; 48 | vec4 d = g - avg; 49 | 50 | // 分散 51 | float disp = dot(vec4(1), d * d) / 4.0; 52 | 53 | if (disp < 0.001) break; 54 | } 55 | quv *= 2.0; 56 | } 57 | 58 | vec3 h = hash(vec3(iuv, i + 0.1)); 59 | 60 | vec2 mapOffset; 61 | mapOffset.x = floor(avg / uMapPx.x) * uMapPx.x - uMapPx.x; 62 | mapOffset.y = floor(h.x / uMapPx.y) * uMapPx.y; 63 | 64 | // flip 65 | vec2 ruv = fuv; 66 | if (h.y < 0.3) ruv.x = 1.0 - ruv.x; 67 | if (h.y < 0.6) ruv.y = 1.0 - ruv.y; 68 | 69 | vec3 pattern = texture(uMap, mapOffset + ruv * uMapPx).rgb; 70 | if(0.95 < avg) pattern = mix(pattern, vec3(1), 0.6); // background 71 | else pattern = mix(pattern, vec3(1), 0.15); 72 | 73 | // border line 74 | vec2 auv = abs(fuv * 2.0 - 1.0); 75 | float t = 0.995 - pow(i / 3.0, 2.0) * 0.02; 76 | float b = 1.0 - step(auv.x, t) * step(auv.y, t); 77 | 78 | outColor = vec4(sat(pattern + b), 1.0); 79 | // outColor = texture(uSource, uv); 80 | } -------------------------------------------------------------------------------- /src/scripts/shader/quad.vs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec3 position; 4 | in vec2 uv; 5 | 6 | out vec2 vUv; 7 | 8 | void main() { 9 | vUv = uv; 10 | gl_Position = vec4(position, 1.0); 11 | } -------------------------------------------------------------------------------- /src/styles/components/link.scss: -------------------------------------------------------------------------------- 1 | @use '../mixins/media.scss' as *; 2 | 3 | .links { 4 | --color: #000; 5 | 6 | position: absolute; 7 | bottom: 1rem; 8 | right: 1rem; 9 | font-size: max(1.2rem, 15px); 10 | line-height: 1; 11 | color: var(--color); 12 | user-select: none; 13 | @include sp { 14 | bottom: 2rem; 15 | right: 2rem; 16 | font-size: min(3rem, 15px); 17 | } 18 | 19 | & > * { 20 | display: block; 21 | } 22 | 23 | & > *:last-child { 24 | margin-top: 0.5rem; 25 | @include sp { 26 | margin-top: 1rem; 27 | } 28 | } 29 | 30 | a { 31 | position: relative; 32 | width: fit-content; 33 | margin-left: auto; 34 | 35 | @include hoverable { 36 | &::after { 37 | content: ''; 38 | position: absolute; 39 | bottom: 0; 40 | left: 0; 41 | width: 100%; 42 | height: 1px; 43 | background: var(--color); 44 | transform: scale(0, 1); 45 | transform-origin: right top; 46 | transition: transform 0.3s; 47 | } 48 | &:hover { 49 | &::after { 50 | transform-origin: left top; 51 | transform: scale(1, 1); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/styles/entry.scss: -------------------------------------------------------------------------------- 1 | @use 'ress'; 2 | @use './lenis.scss'; 3 | @use './imports.scss'; 4 | @use './global.scss'; 5 | // components 6 | @use './components/link.scss'; 7 | -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | @use './mixins/media.scss' as *; 2 | @use './mixins/fonts.scss' as fonts; 3 | 4 | html { 5 | font-size: calc(1000vw / 1500); 6 | @include sp { 7 | font-size: calc(1000vw / 750); 8 | } 9 | height: 100%; 10 | background: #ebeae8; 11 | @include fonts.poppins; 12 | } 13 | 14 | body, 15 | main { 16 | position: relative; 17 | width: 100%; 18 | height: 100%; 19 | } 20 | 21 | a { 22 | color: inherit; 23 | text-decoration: none; 24 | } 25 | 26 | ul { 27 | list-style: none; 28 | } 29 | 30 | .webgl-canvas { 31 | position: fixed; 32 | top: 0; 33 | width: 100%; 34 | height: 100lvh; 35 | } 36 | -------------------------------------------------------------------------------- /src/styles/imports.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300&display=swap'); 2 | -------------------------------------------------------------------------------- /src/styles/lenis.scss: -------------------------------------------------------------------------------- 1 | html.lenis { 2 | height: auto; 3 | } 4 | 5 | .lenis.lenis-smooth { 6 | scroll-behavior: auto !important; 7 | } 8 | 9 | .lenis.lenis-smooth [data-lenis-prevent] { 10 | overscroll-behavior: contain; 11 | } 12 | 13 | .lenis.lenis-stopped { 14 | overflow: hidden; 15 | } 16 | 17 | .lenis.lenis-scrolling iframe { 18 | pointer-events: none; 19 | } 20 | -------------------------------------------------------------------------------- /src/styles/mixins/fonts.scss: -------------------------------------------------------------------------------- 1 | @mixin poppins($weight: 300) { 2 | font-family: 'Poppins', sans-serif; 3 | font-weight: $weight; 4 | } 5 | -------------------------------------------------------------------------------- /src/styles/mixins/media.scss: -------------------------------------------------------------------------------- 1 | @mixin pc { 2 | @media screen and (min-width: 751px) { 3 | @content; 4 | } 5 | } 6 | 7 | @mixin sp { 8 | @media screen and (max-width: 750px) { 9 | @content; 10 | } 11 | } 12 | 13 | @mixin hoverable { 14 | @media (hover: hover) and (pointer: fine) { 15 | @content; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/typescript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2023", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import glsl from 'vite-plugin-glsl' 3 | 4 | export default defineConfig(() => { 5 | return { 6 | root: './src', 7 | publicDir: '../public', 8 | base: '/azulejo/', 9 | build: { 10 | outDir: '../dist', 11 | }, 12 | plugins: [glsl()], 13 | server: { 14 | host: true, 15 | }, 16 | } 17 | }) 18 | --------------------------------------------------------------------------------