27 Commits
Author SHA1 Message Date
Quinta0 9995df8ef1 Deploying to gh-pages from @ Quinta0/valgo@08af17e268 🚀 2026-07-27 17:54:49 +00:00
Quinta0 ab0c90c8fb Deploying to gh-pages from @ Quinta0/valgo@6b2424ac90 🚀 2026-07-27 17:53:24 +00:00
Quinta0 d45e298033 Deploying to gh-pages from @ Quinta0/valgo@762c4f7d06 🚀 2026-07-27 17:35:36 +00:00
Quinta0 73e6079694 Deploying to gh-pages from @ Quinta0/valgo@cddb48157c 🚀 2026-07-05 21:11:53 +00:00
Quinta0 82e4764e0e Deploying to gh-pages from @ Quinta0/valgo@4e1084c385 🚀 2025-09-09 15:05:34 +00:00
Quinta0 ee42d1ad75 Deploying to gh-pages from @ Quinta0/valgo@517405e31d 🚀 2025-09-09 15:05:12 +00:00
Quinta0 fb68171151 Deploying to gh-pages from @ Quinta0/valgo@011609e161 🚀 2025-06-23 17:51:43 +00:00
Quinta0 f5fdfce763 Deploying to gh-pages from @ Quinta0/valgo@3ca2d7882c 🚀 2025-06-23 17:51:35 +00:00
Quinta0 a05e9546e5 Deploying to gh-pages from @ Quinta0/valgo@5354bd9191 🚀 2025-06-23 17:49:46 +00:00
Quinta0 799189c218 Deploying to gh-pages from @ Quinta0/valgo@9a825b3fbe 🚀 2025-06-23 17:49:33 +00:00
Quinta0 677ca2df88 Deploying to gh-pages from @ Quinta0/valgo@f9cf60c13b 🚀 2024-10-11 12:22:33 +00:00
Quinta0 ac056670c4 Deploying to gh-pages from @ Quinta0/valgo@27cced4b60 🚀 2024-10-11 12:22:24 +00:00
Quinta0 ef7abf1d65 Deploying to gh-pages from @ Quinta0/valgo@2bcd5e6321 🚀 2024-07-02 13:18:58 +00:00
Quinta0 8d81c67840 Deploying to gh-pages from @ Quinta0/valgo@90da860cf6 🚀 2024-07-02 13:15:38 +00:00
Quinta0 2e5b4b856e Deploying to gh-pages from @ Quinta0/valgo@384686149d 🚀 2024-06-30 22:05:55 +00:00
Quinta0 c48c20b5b4 Deploying to gh-pages from @ Quinta0/valgo@5ea5c93fb1 🚀 2024-06-30 20:37:57 +00:00
Quinta0 cd84a7f04f Deploying to gh-pages from @ Quinta0/valgo@b56aefa455 🚀 2024-06-30 14:19:46 +00:00
Quinta0 7d0ab0c56d Deploying to gh-pages from @ Quinta0/valgo@c6271b0e25 🚀 2024-06-30 14:15:33 +00:00
Quinta0 6124ef460a Deploying to gh-pages from @ Quinta0/valgo@c731d02a65 🚀 2024-06-30 13:04:32 +00:00
Quinta0 6170ca0362 Deploying to gh-pages from @ Quinta0/valgo@1a421a1b5e 🚀 2024-06-30 01:33:43 +00:00
Quinta0 0f14da334c Deploying to gh-pages from @ Quinta0/valgo@a6a1e10d18 🚀 2024-06-29 21:48:25 +00:00
Quinta0 db4f98e401 Deploying to gh-pages from @ Quinta0/valgo@1df5786367 🚀 2024-06-29 15:19:40 +00:00
Quinta0 02bb8b2092 Deploying to gh-pages from @ Quinta0/valgo@0a62722f3d 🚀 2024-06-29 14:58:48 +00:00
Quinta0 d3f9dd8760 Deploying to gh-pages from @ Quinta0/valgo@0a62722f3d 🚀 2024-06-29 14:49:38 +00:00
Quinta0 4e0a3304d7 Deploying to gh-pages from @ Quinta0/valgo@0a62722f3d 🚀 2024-06-29 14:44:29 +00:00
Quinta0 1386f5da38 Deploying to gh-pages from @ Quinta0/valgo@0a62722f3d 🚀 2024-06-29 14:41:02 +00:00
Quinta0 4c87e2c3b8 Initial gh-pages commit 2024-06-29 14:41:02 +00:00
72 changed files with 44 additions and 10483 deletions
-3
View File
@@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}
-35
View File
@@ -1,35 +0,0 @@
name: Deploy to GitHub Pages
on:
push:
branches: [main]
permissions:
contents: write
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build the project
run: npm run build
- name: Create .nojekyll file
run: touch out/.nojekyll
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: out
-93
View File
@@ -1,93 +0,0 @@
# Sample workflow for building and deploying a Next.js site to GitHub Pages
#
# To get started with Next.js see: https://nextjs.org/docs/getting-started
#
name: Deploy Next.js site to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect package manager
id: detect-package-manager
run: |
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=yarn" >> $GITHUB_OUTPUT
exit 0
elif [ -f "${{ github.workspace }}/package.json" ]; then
echo "manager=npm" >> $GITHUB_OUTPUT
echo "command=ci" >> $GITHUB_OUTPUT
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
exit 0
else
echo "Unable to determine package manager"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: ${{ steps.detect-package-manager.outputs.manager }}
- name: Setup Pages
uses: actions/configure-pages@v5
with:
# Automatically inject basePath in your Next.js configuration file and disable
# server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized).
#
# You may remove this line if you want to manage the configuration yourself.
static_site_generator: next
- name: Restore cache
uses: actions/cache@v4
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- name: Install dependencies
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
- name: Build with Next.js
run: ${{ steps.detect-package-manager.outputs.runner }} next build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./out
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
-36
View File
@@ -1,36 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
-5
View File
@@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
-15
View File
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/valgo.iml" filepath="$PROJECT_DIR$/.idea/valgo.iml" />
</modules>
</component>
</project>
-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
View File
+1
View File
File diff suppressed because one or more lines are too long
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Quinta
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-82
View File
@@ -1,82 +0,0 @@
# VALGO
![VALGO](image.png)
## Live Demo
Check out the live demo [here](https://quinta0.github.io/valgo/).
## About The Project
Valgo is an interactive, narrated visualizer for nine classic sorting algorithms, redesigned in a 3Blue1Brown-inspired style: a dark, grid-lined canvas, smooth per-step animation, a live "what's happening and why" commentary track, and a growth-curve chart that makes Big-O visually concrete.
Every algorithm runs once, up front, recording a full sequence of steps (compare, swap, pivot selection, merge, bucket placement...). The player then replays that sequence at any speed, and can be paused or scrubbed like a video — nothing is re-computed on the fly.
### Features
- Nine algorithms, each with color-coded state (comparing, swapping, pivot, sorted) and a sliding pointer that always shows where the algorithm is "looking"
- A synced narration panel explaining each step in plain language, in all 10 supported languages
- A growth-curve chart plotting O(n²), O(n log n), and O(n + k) side by side, with a live marker at the current array size
- Scrubbable timeline, adjustable speed, and live comparison/write counters
- Adjustable array size, shuffle, and reset
- Responsive, dark, 3Blue1Brown-inspired visual design
### Supported Algorithms
- Bubble Sort
- Insertion Sort
- Merge Sort
- Heap Sort
- Quick Sort
## Built With
- [Next.js](https://nextjs.org/)
- [React](https://reactjs.org/)
- [TypeScript](https://www.typescriptlang.org/)
- [Tailwind CSS](https://tailwindcss.com/)
## Getting Started
To get a local copy up and running, follow these simple steps.
### Prerequisites
- Node.js (v14 or later)
- npm
### Installation
1. Clone the repo
```sh
git clone https://github.com/Quinta0/valgo.git
```
2. Navigate to the project directory
```sh
cd valgo
```
3. Install dependencies
```sh
npm install
```
4. Start the development server
```sh
npm run dev
```
5. Open [http://localhost:3000](http://localhost:3000) in your browser
### Usage
- Select a sorting algorithm from the dropdown menu.
- Adjust the array size using the slider if desired.
- Click the "Start" button to begin the visualization.
- Use the "Pause" and "Resume" buttons to control the animation.
- Click "Reset" to generate a new random array.
- Read the algorithm explanation provided below the visualization.
### Contributing
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
1. Fork the Project
2. Create your Feature Branch `git checkout -b feature/AmazingFeature`
3. Commit your Changes `git commit -m 'Add some AmazingFeature'`
4. Push to the Branch `git push origin feature/AmazingFeature`
5. Open a Pull Request
### License
Distributed under the MIT License. See LICENSE for more information.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[492],{4585:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"styles",{enumerable:!0,get:function(){return l}});let l={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{display:"inline-block"},h1:{display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},h2:{fontSize:14,fontWeight:400,lineHeight:"49px",margin:0}};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8779:(e,t,l)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found/page",function(){return l(9520)}])},8886:(e,t,l)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTTPAccessErrorFallback",{enumerable:!0,get:function(){return o}});let r=l(5155),n=l(4585);function o(e){let{status:t,message:l}=e;return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)("title",{children:t+": "+l}),(0,r.jsx)("div",{style:n.styles.error,children:(0,r.jsxs)("div",{children:[(0,r.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,r.jsx)("h1",{className:"next-error-h1",style:n.styles.h1,children:t}),(0,r.jsx)("div",{style:n.styles.desc,children:(0,r.jsx)("h2",{style:n.styles.h2,children:l})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9520:(e,t,l)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return o}});let r=l(5155),n=l(8886),o=function(){return(0,r.jsx)("html",{children:(0,r.jsx)("body",{children:(0,r.jsx)(n.HTTPAccessErrorFallback,{status:404,message:"This page could not be found."})})})};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{e.O(0,[441,255,358],()=>e(e.s=8779)),_N_E=e.O()}]);
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[177],{1267:(e,a,s)=>{Promise.resolve().then(s.t.bind(s,7102,23)),Promise.resolve().then(s.t.bind(s,9632,23)),Promise.resolve().then(s.t.bind(s,1290,23))},1290:()=>{},7102:e=>{e.exports={style:{fontFamily:"'Geist', 'Geist Fallback'",fontStyle:"normal"},className:"__className_246ccd",variable:"__variable_246ccd"}},9632:e=>{e.exports={style:{fontFamily:"'Geist Mono', 'Geist Mono Fallback'",fontStyle:"normal"},className:"__className_c29908",variable:"__variable_c29908"}}},e=>{e.O(0,[379,441,255,358],()=>e(e.s=1267)),_N_E=e.O()}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[358],{555:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,7150,23)),Promise.resolve().then(n.t.bind(n,1959,23)),Promise.resolve().then(n.t.bind(n,7989,23)),Promise.resolve().then(n.t.bind(n,3886,23)),Promise.resolve().then(n.t.bind(n,9766,23)),Promise.resolve().then(n.t.bind(n,5278,23)),Promise.resolve().then(n.t.bind(n,8924,23)),Promise.resolve().then(n.t.bind(n,4431,23)),Promise.resolve().then(n.bind(n,622))},9393:()=>{}},e=>{var s=s=>e(e.s=s);e.O(0,[441,255],()=>(s(1666),s(555))),_N_E=e.O()}]);
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[636],{326:(_,n,p)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return p(5139)}])}},_=>{var n=n=>_(_.s=n);_.O(0,[593,792],()=>(n(326),n(6763))),_N_E=_.O()}]);
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[731],{2164:(_,n,e)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return e(1646)}])}},_=>{_.O(0,[636,593,792],()=>_(_.s=2164)),_N_E=_.O()}]);
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
(()=>{"use strict";var e={},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var i=t[o]={exports:{}},a=!0;try{e[o](i,i.exports,r),a=!1}finally{a&&delete t[o]}return i.exports}r.m=e,(()=>{var e=[];r.O=(t,o,n,i)=>{if(o){i=i||0;for(var a=e.length;a>0&&e[a-1][2]>i;a--)e[a]=e[a-1];e[a]=[o,n,i];return}for(var u=1/0,a=0;a<e.length;a++){for(var[o,n,i]=e[a],l=!0,c=0;c<o.length;c++)(!1&i||u>=i)&&Object.keys(r.O).every(e=>r.O[e](o[c]))?o.splice(c--,1):(l=!1,i<u&&(u=i));if(l){e.splice(a--,1);var d=n();void 0!==d&&(t=d)}}return t}})(),(()=>{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;r.t=function(o,n){if(1&n&&(o=this(o)),8&n||"object"==typeof o&&o&&(4&n&&o.__esModule||16&n&&"function"==typeof o.then))return o;var i=Object.create(null);r.r(i);var a={};e=e||[null,t({}),t([]),t(t)];for(var u=2&n&&o;"object"==typeof u&&!~e.indexOf(u);u=t(u))Object.getOwnPropertyNames(u).forEach(e=>a[e]=()=>o[e]);return a.default=()=>o,r.d(i,a),i}})(),r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,o)=>(r.f[o](e,t),t),[])),r.u=e=>{},r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={},t="_N_E:";r.l=(o,n,i,a)=>{if(e[o])return void e[o].push(n);if(void 0!==i)for(var u,l,c=document.getElementsByTagName("script"),d=0;d<c.length;d++){var f=c[d];if(f.getAttribute("src")==o||f.getAttribute("data-webpack")==t+i){u=f;break}}u||(l=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,r.nc&&u.setAttribute("nonce",r.nc),u.setAttribute("data-webpack",t+i),u.src=r.tu(o)),e[o]=[n];var s=(t,r)=>{u.onerror=u.onload=null,clearTimeout(p);var n=e[o];if(delete e[o],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(e=>e(r)),t)return t(r)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=s.bind(null,u.onerror),u.onload=s.bind(null,u.onload),l&&document.head.appendChild(u)}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:e=>e},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("nextjs#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="/_next/",(()=>{var e={68:0,379:0};r.f.j=(t,o)=>{var n=r.o(e,t)?e[t]:void 0;if(0!==n)if(n)o.push(n[2]);else if(/^(379|68)$/.test(t))e[t]=0;else{var i=new Promise((r,o)=>n=e[t]=[r,o]);o.push(n[2]=i);var a=r.p+r.u(t),u=Error();r.l(a,o=>{if(r.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var i=o&&("load"===o.type?"missing":o.type),a=o&&o.target&&o.target.src;u.message="Loading chunk "+t+" failed.\n("+i+": "+a+")",u.name="ChunkLoadError",u.type=i,u.request=a,n[1](u)}},"chunk-"+t,t)}},r.O.j=t=>0===e[t];var t=(t,o)=>{var n,i,[a,u,l]=o,c=0;if(a.some(t=>0!==e[t])){for(n in u)r.o(u,n)&&(r.m[n]=u[n]);if(l)var d=l(r)}for(t&&t(o);c<a.length;c++)i=a[c],r.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return r.O(d)},o=self.webpackChunk_N_E=self.webpackChunk_N_E||[];o.forEach(t.bind(null,0)),o.push=t.bind(null,o.push.bind(o))})(),r.nc=void 0})();
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
self.__BUILD_MANIFEST=function(e,r,t){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:3,errorRate:1e-4,numBits:58,numHashes:14,bitArray:[1,1,0,e,0,e,e,r,e,e,r,e,e,e,r,e,r,r,e,r,r,r,e,r,r,r,r,r,e,r,e,e,e,e,r,e,e,r,e,e,e,r,e,r,e,r,r,e,e,e,r,r,e,e,e,r,e,e]},__routerFilterDynamic:{numItems:r,errorRate:1e-4,numBits:r,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-cb2a52f75f2162e2.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -0,0 +1 @@
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
-80
View File
@@ -1,80 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* Monochrome chrome: the shell carries no color of its own, so the
five algorithm-state colors below are the only color on the page. */
--background: 224 15% 6%;
--foreground: 210 20% 95%;
--primary: 210 25% 96%; /* near-white, inverted CTA fill */
--primary-foreground: 224 20% 9%;
--secondary: 224 12% 15%;
--secondary-foreground: 210 15% 90%;
--accent: 224 12% 17%; /* neutral hover surface, not a brand color */
--accent-foreground: 210 20% 95%;
--destructive: 358 65% 55%;
--destructive-foreground: 210 20% 96%;
--muted: 224 12% 13%;
--muted-foreground: 216 10% 62%;
--card: 224 14% 9%;
--card-foreground: 210 20% 95%;
--popover: 224 16% 8%;
--popover-foreground: 210 20% 95%;
--border: 224 12% 18%;
--input: 224 12% 18%;
--ring: 205 90% 62%; /* the one deliberate accent: focus only */
--radius: 0.75rem;
/* Algorithm state colors — one meaning each, used everywhere. */
--state-idle: 205 40% 50%;
--state-compare: 43 90% 60%;
--state-swap: 358 75% 59%;
--state-sorted: 152 55% 48%;
--state-pivot: 271 70% 66%;
--state-active-range: 205 60% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
background-image:
linear-gradient(hsl(var(--border) / 0.35) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--border) / 0.35) 1px, transparent 1px);
background-size: 44px 44px;
background-position: center -1px;
}
::selection {
background: hsl(var(--accent) / 0.35);
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: hsl(var(--border));
border-radius: 999px;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
.bg-grid-fade {
-webkit-mask-image: radial-gradient(ellipse at center, black 40%, transparent 85%);
mask-image: radial-gradient(ellipse at center, black 40%, transparent 85%);
}
}
-41
View File
@@ -1,41 +0,0 @@
import { ReactNode } from 'react';
import { Geist, Geist_Mono } from 'next/font/google';
import { cn } from '@/lib/utils';
import './globals.css';
const fontSans = Geist({
subsets: ['latin'],
display: 'swap',
variable: '--font-sans',
});
const fontMono = Geist_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-mono',
});
export const metadata = {
title: 'Valgo - Watch how sorting actually works',
description: 'An animated, narrated visualizer for nine classic sorting algorithms.',
};
type LayoutProps = {
children: ReactNode;
};
export default function Layout({ children }: LayoutProps) {
return (
<html lang="en" className="dark">
<body
className={cn(
'antialiased font-sans',
fontSans.variable,
fontMono.variable
)}
>
{children}
</body>
</html>
);
}
-239
View File
@@ -1,239 +0,0 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { motion } from "framer-motion";
import { AlgorithmKey, LanguageCode, Step } from "@/lib/types";
import { runAlgorithm } from "@/lib/algorithms";
import { algorithmDescriptions } from "@/lib/descriptions";
import { complexityClass } from "@/lib/complexity";
import { t, narrate } from "@/lib/i18n";
import Bars from "@/components/Bars";
import GrowthChart from "@/components/GrowthChart";
import Narration from "@/components/Narration";
import StatsPanel from "@/components/StatsPanel";
import PlaybackControls, { Speed } from "@/components/PlaybackControls";
import { AlgorithmPicker } from "@/components/Pickers";
import SiteFooter from "@/components/SiteFooter";
import Panel from "@/components/Panel";
const SPEED_MS: Record<Speed, number> = { slow: 260, normal: 120, fast: 55, turbo: 18 };
function randomArray(size: number): number[] {
return Array.from({ length: size }, () => Math.floor(Math.random() * 100) + 1);
}
// Deterministic so the server-rendered array matches the client's first
// render; the real random array is generated client-side after mount.
function seededArray(size: number): number[] {
return Array.from({ length: size }, (_, i) => ((i * 37) % 100) + 1);
}
function BrandMark(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 20 20" fill="none" {...props}>
<rect x="2" y="11" width="4" height="7" rx="1" fill="currentColor" opacity="0.5" />
<rect x="8" y="6" width="4" height="12" rx="1" fill="currentColor" opacity="0.75" />
<rect x="14" y="2" width="4" height="16" rx="1" fill="currentColor" />
</svg>
);
}
export default function Page() {
const [algorithm, setAlgorithm] = useState<AlgorithmKey>("bubble");
const [language, setLanguage] = useState<LanguageCode>("en");
const [arraySize, setArraySize] = useState(24);
const [baseArray, setBaseArray] = useState<number[]>(() => seededArray(24));
const [steps, setSteps] = useState<Step[]>([]);
const [stepIndex, setStepIndex] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const [speed, setSpeed] = useState<Speed>("normal");
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
setBaseArray(randomArray(arraySize));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// (Re)compute the full step-by-step story whenever the algorithm or the
// underlying data changes. Everything downstream just plays it back.
useEffect(() => {
setSteps(runAlgorithm(algorithm, baseArray));
setStepIndex(0);
setIsPlaying(false);
}, [algorithm, baseArray]);
useEffect(() => {
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}, []);
useEffect(() => {
if (!isPlaying) return;
if (stepIndex >= steps.length - 1) {
setIsPlaying(false);
return;
}
timeoutRef.current = setTimeout(() => {
setStepIndex((i) => Math.min(i + 1, steps.length - 1));
}, SPEED_MS[speed]);
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}, [isPlaying, stepIndex, steps.length, speed]);
const handleTogglePlay = useCallback(() => {
if (stepIndex >= steps.length - 1) {
setStepIndex(0);
}
setIsPlaying((p) => !p);
}, [stepIndex, steps.length]);
const handleShuffle = useCallback(() => {
setBaseArray(randomArray(arraySize));
}, [arraySize]);
const handleReset = useCallback(() => {
setBaseArray(randomArray(arraySize));
}, [arraySize]);
const handleArraySizeChange = useCallback((n: number) => {
setArraySize(n);
setBaseArray(randomArray(n));
}, []);
const handleScrub = useCallback((i: number) => {
setIsPlaying(false);
setStepIndex(Math.min(i, steps.length - 1));
}, [steps.length]);
const currentStep: Step | undefined = steps[stepIndex];
const description = algorithmDescriptions[algorithm]?.[language] ?? algorithmDescriptions[algorithm]?.en;
const cclass = complexityClass[algorithm];
const pivotIndex = useMemo(() => {
if (!currentStep) return undefined;
if (currentStep.action === "select-pivot" || currentStep.action === "pivot-placed") return currentStep.indices[0];
return undefined;
}, [currentStep]);
const narration = useMemo(() => {
if (!currentStep) return "";
return narrate(currentStep, algorithm, description?.title ?? algorithm, language);
}, [currentStep, algorithm, description, language]);
const growthLabels = {
"n+k": "O(n + k)",
nlogn: "O(n log n)",
"n2-adaptive": "O(n log\u00B2 n)",
n2: "O(n\u00B2)",
} as const;
return (
<div className="min-h-screen flex flex-col">
<header className="sticky top-0 z-40 border-b border-border bg-background/85 backdrop-blur-md">
<div className="container mx-auto max-w-6xl px-4 md:px-6 h-16 flex items-center justify-between gap-4">
<div className="flex items-center gap-2.5 min-w-0">
<BrandMark className="h-5 w-5 shrink-0 text-foreground" />
<h1 className="text-base font-semibold tracking-tight truncate">{t(language, "brand")}</h1>
<span className="hidden md:inline text-sm text-muted-foreground ml-2 pl-3 border-l border-border truncate">
{t(language, "tagline")}
</span>
</div>
<AlgorithmPicker value={algorithm} onChange={setAlgorithm} triggerLabel={t(language, "pickAlgorithm")} />
</div>
</header>
<main className="flex-1">
<motion.section
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
className="border-b border-border bg-card/30"
>
<div className="container mx-auto max-w-6xl px-4 md:px-6 py-8 md:py-10">
<Bars
values={currentStep?.array ?? baseArray}
indices={currentStep?.indices ?? []}
action={currentStep?.action}
sorted={currentStep?.sorted ?? []}
range={currentStep?.range}
pivotIndex={pivotIndex}
stepDurationMs={SPEED_MS[speed]}
/>
</div>
</motion.section>
<div className="container mx-auto max-w-6xl px-4 md:px-6 py-8 md:py-10">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<Narration text={narration} stepIndex={stepIndex} title={t(language, "narrationTitle")} />
<PlaybackControls
isPlaying={isPlaying}
onTogglePlay={handleTogglePlay}
onShuffle={handleShuffle}
onReset={handleReset}
arraySize={arraySize}
onArraySizeChange={handleArraySizeChange}
speed={speed}
onSpeedChange={setSpeed}
stepIndex={stepIndex}
stepCount={Math.max(steps.length - 1, 0)}
onScrub={handleScrub}
labels={{
play: t(language, "play"), pause: t(language, "pause"),
shuffle: t(language, "shuffle"), reset: t(language, "reset"),
arraySize: t(language, "arraySize"), elements: t(language, "elements"),
speed: t(language, "speed"), speedSlow: t(language, "speedSlow"),
speedNormal: t(language, "speedNormal"), speedFast: t(language, "speedFast"),
speedTurbo: t(language, "speedTurbo"),
}}
/>
</div>
<div className="space-y-6">
<StatsPanel
title={t(language, "statsTitle")}
comparisons={currentStep?.comparisons ?? 0}
writes={currentStep?.writes ?? 0}
stepLabel={t(language, "step")}
stepIndex={stepIndex}
stepCount={Math.max(steps.length - 1, 0)}
sortedLabel={t(language, "sorted")}
sortedCount={currentStep?.sorted.length ?? 0}
total={baseArray.length}
/>
{description && (
<Panel>
<h2 className="text-xl font-semibold tracking-tight">{description.title}</h2>
<div className="mt-1 space-y-0.5">
<div className="font-mono text-xs text-muted-foreground">{description.timeComplexity}</div>
<div className="font-mono text-xs text-muted-foreground">{description.spaceComplexity}</div>
</div>
<p className="mt-4 text-sm text-muted-foreground leading-relaxed">{description.description}</p>
</Panel>
)}
<Panel>
<p className="text-xs uppercase tracking-widest text-muted-foreground mb-1">{t(language, "growthTitle")}</p>
<p className="text-xs text-muted-foreground mb-4">{t(language, "growthCaption")}</p>
<GrowthChart active={cclass} n={arraySize} maxN={100} labels={growthLabels} />
</Panel>
</div>
</div>
</div>
</main>
<SiteFooter
year={2024}
rightsLabel={t(language, "footerRights")}
licenseLabel={t(language, "license")}
viewLicenseLabel={t(language, "viewLicense")}
lang={language}
onLangChange={setLanguage}
/>
</div>
);
}
-17
View File
@@ -1,17 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
-119
View File
@@ -1,119 +0,0 @@
"use client";
import { motion } from "framer-motion";
import { ActionType } from "@/lib/types";
interface BarsProps {
values: number[];
indices: number[];
action: ActionType | undefined;
sorted: number[];
range?: [number, number];
pivotIndex?: number;
stepDurationMs: number;
height?: number;
}
const stateColor = {
idle: "hsl(var(--state-idle))",
compare: "hsl(var(--state-compare))",
swap: "hsl(var(--state-swap))",
sorted: "hsl(var(--state-sorted))",
pivot: "hsl(var(--state-pivot))",
} as const;
function colorFor(
i: number,
indices: number[],
action: ActionType | undefined,
sorted: Set<number>,
range: [number, number] | undefined,
pivotIndex: number | undefined
): string {
if (sorted.has(i)) return stateColor.sorted;
if (pivotIndex === i) return stateColor.pivot;
if (indices.includes(i)) {
if (action === "swap" || action === "shift" || action === "merge-write" || action === "overwrite") return stateColor.swap;
return stateColor.compare;
}
if (range && i >= range[0] && i <= range[1]) return "hsl(var(--state-active-range) / 0.55)";
return stateColor.idle;
}
export default function Bars({ values, indices, action, sorted, range, pivotIndex, stepDurationMs, height = 320 }: BarsProps) {
const max = Math.max(...values, 1);
const sortedSet = new Set(sorted);
const n = values.length;
const showLabels = n <= 36;
const barGapPct = n > 0 ? Math.min(1.2, 40 / n) : 0.6;
const primary = indices[0];
const secondary = indices[1];
const pointerDuration = Math.max(0.08, Math.min(0.35, stepDurationMs / 1000));
return (
<div className="relative w-full select-none" style={{ height: height + 40 }}>
<div
className="absolute bottom-10 left-0 right-0 flex items-end justify-center gap-[var(--gap)] px-2"
style={{ height, ["--gap" as any]: `${barGapPct}%` }}
>
{values.map((value, i) => {
const color = colorFor(i, indices, action, sortedSet, range, pivotIndex);
const isFocused = indices.includes(i) || pivotIndex === i;
return (
<motion.div
key={i}
className="relative flex-1 rounded-t-[3px] min-w-[2px]"
style={{ backgroundColor: color }}
animate={{
height: `${(value / max) * 100}%`,
backgroundColor: color,
boxShadow: isFocused ? `0 0 16px ${color}` : "0 0 0 rgba(0,0,0,0)",
}}
transition={{ duration: Math.max(0.08, Math.min(0.4, stepDurationMs / 1000)), ease: "easeInOut" }}
>
{showLabels && (
<span
className="absolute -top-5 left-1/2 -translate-x-1/2 text-[10px] font-mono tabular-nums"
style={{ color }}
>
{value}
</span>
)}
</motion.div>
);
})}
</div>
{/* sliding pointers: show where the algorithm's "attention" is right now */}
<div className="absolute bottom-0 left-0 right-0 h-8 px-2">
{n > 0 && primary !== undefined && (
<motion.div
className="absolute bottom-0 flex flex-col items-center"
style={{ width: `${100 / n}%` }}
animate={{ left: `${(primary / n) * 100}%` }}
transition={{ duration: pointerDuration, ease: "easeInOut" }}
>
<div
className="h-0 w-0 border-x-[6px] border-x-transparent border-b-[8px]"
style={{ borderBottomColor: action === "swap" ? stateColor.swap : stateColor.compare }}
/>
</motion.div>
)}
{n > 0 && secondary !== undefined && secondary !== primary && (
<motion.div
className="absolute bottom-0 flex flex-col items-center"
style={{ width: `${100 / n}%` }}
animate={{ left: `${(secondary / n) * 100}%` }}
transition={{ duration: pointerDuration, ease: "easeInOut" }}
>
<div
className="h-0 w-0 border-x-[6px] border-x-transparent border-b-[8px]"
style={{ borderBottomColor: action === "swap" ? stateColor.swap : stateColor.compare }}
/>
</motion.div>
)}
</div>
</div>
);
}
-91
View File
@@ -1,91 +0,0 @@
"use client";
import { motion } from "framer-motion";
import { complexityCurves, curvePoints } from "@/lib/complexity";
import { ComplexityClass } from "@/lib/types";
interface GrowthChartProps {
active: ComplexityClass;
n: number;
maxN: number;
labels: Record<ComplexityClass, string>;
}
const CURVES: { key: ComplexityClass; color: string }[] = [
{ key: "n+k", color: "hsl(var(--state-sorted))" },
{ key: "nlogn", color: "hsl(var(--primary))" },
{ key: "n2-adaptive", color: "hsl(var(--state-pivot))" },
{ key: "n2", color: "hsl(var(--state-swap))" },
];
const W = 320;
const H = 160;
const PAD = 10;
function toPath(points: { n: number; v: number }[], maxN: number) {
return points
.map((p, i) => {
const x = PAD + (p.n / maxN) * (W - 2 * PAD);
const y = H - PAD - p.v * (H - 2 * PAD);
return `${i === 0 ? "M" : "L"} ${x.toFixed(1)} ${y.toFixed(1)}`;
})
.join(" ");
}
export default function GrowthChart({ active, n, maxN, labels }: GrowthChartProps) {
return (
<div className="w-full">
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-auto overflow-visible">
{/* axes */}
<line x1={PAD} y1={H - PAD} x2={W - PAD} y2={H - PAD} stroke="hsl(var(--border))" strokeWidth={1} />
<line x1={PAD} y1={PAD} x2={PAD} y2={H - PAD} stroke="hsl(var(--border))" strokeWidth={1} />
{CURVES.map(({ key, color }) => {
const pts = curvePoints(complexityCurves[key], maxN);
const isActive = key === active;
return (
<motion.path
key={key}
d={toPath(pts, maxN)}
fill="none"
stroke={color}
strokeWidth={isActive ? 2.75 : 1.25}
strokeOpacity={isActive ? 1 : 0.28}
strokeLinecap="round"
initial={false}
animate={{ strokeOpacity: isActive ? 1 : 0.28, strokeWidth: isActive ? 2.75 : 1.25 }}
transition={{ duration: 0.4 }}
/>
);
})}
{/* marker at current n on the active curve */}
{(() => {
const pts = curvePoints(complexityCurves[active], maxN, 200);
const closest = pts.reduce((a, b) => (Math.abs(b.n - n) < Math.abs(a.n - n) ? b : a));
const x = PAD + (closest.n / maxN) * (W - 2 * PAD);
const y = H - PAD - closest.v * (H - 2 * PAD);
const color = CURVES.find((c) => c.key === active)?.color ?? "hsl(var(--primary))";
return (
<motion.circle
cx={x}
cy={y}
r={4}
fill={color}
animate={{ cx: x, cy: y }}
transition={{ duration: 0.4 }}
/>
);
})()}
</svg>
<div className="mt-3 flex flex-wrap gap-x-4 gap-y-1.5 text-xs font-mono">
{CURVES.map(({ key, color }) => (
<div key={key} className="flex items-center gap-1.5" style={{ opacity: key === active ? 1 : 0.45 }}>
<span className="inline-block h-2 w-2 rounded-full" style={{ backgroundColor: color }} />
<span style={{ color: key === active ? color : "hsl(var(--muted-foreground))" }}>{labels[key]}</span>
</div>
))}
</div>
</div>
);
}
-32
View File
@@ -1,32 +0,0 @@
"use client";
import { AnimatePresence, motion } from "framer-motion";
import Panel from "@/components/Panel";
interface NarrationProps {
text: string;
stepIndex: number;
title: string;
}
export default function Narration({ text, stepIndex, title }: NarrationProps) {
return (
<Panel>
<p className="text-xs uppercase tracking-widest text-muted-foreground mb-2">{title}</p>
<div className="min-h-[3.5rem]">
<AnimatePresence mode="wait">
<motion.p
key={stepIndex}
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -6 }}
transition={{ duration: 0.22 }}
className="text-lg leading-relaxed text-balance"
>
{text}
</motion.p>
</AnimatePresence>
</div>
</Panel>
);
}
-11
View File
@@ -1,11 +0,0 @@
import { HTMLAttributes } from "react";
import { cn } from "@/lib/utils";
export default function Panel({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("rounded-xl border border-border bg-card/60 backdrop-blur-sm p-5 md:p-6", className)}
{...props}
/>
);
}
-82
View File
@@ -1,82 +0,0 @@
"use client";
import { ChevronDown, Globe } from "lucide-react";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { AlgorithmKey, LanguageCode } from "@/lib/types";
const ALGORITHMS: { key: AlgorithmKey; label: string }[] = [
{ key: "bubble", label: "Bubble Sort" },
{ key: "insertion", label: "Insertion Sort" },
{ key: "merge", label: "Merge Sort" },
{ key: "heap", label: "Heap Sort" },
{ key: "quick", label: "Quick Sort" },
{ key: "radix", label: "Radix Sort" },
{ key: "counting", label: "Counting Sort" },
{ key: "bucket", label: "Bucket Sort" },
{ key: "shell", label: "Shell Sort" },
];
const LANGUAGES: LanguageCode[] = ["en", "fr", "it", "de", "es", "ja", "no", "sv", "ua", "ru"];
interface AlgorithmPickerProps {
value: AlgorithmKey;
onChange: (key: AlgorithmKey) => void;
triggerLabel: string;
}
export function AlgorithmPicker({ value, onChange, triggerLabel }: AlgorithmPickerProps) {
const current = ALGORITHMS.find((a) => a.key === value)?.label ?? triggerLabel;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" className="gap-2 justify-between min-w-[180px]">
{current}
<ChevronDown className="h-4 w-4 opacity-60" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuRadioGroup value={value} onValueChange={(v) => onChange(v as AlgorithmKey)}>
{ALGORITHMS.map((a) => (
<DropdownMenuRadioItem key={a.key} value={a.key}>
{a.label}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}
interface LanguagePickerProps {
value: LanguageCode;
onChange: (lang: LanguageCode) => void;
}
export function LanguagePicker({ value, onChange }: LanguagePickerProps) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="gap-1.5">
<Globe className="h-3.5 w-3.5" />
{value.toUpperCase()}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuRadioGroup value={value} onValueChange={(v) => onChange(v as LanguageCode)}>
{LANGUAGES.map((lang) => (
<DropdownMenuRadioItem key={lang} value={lang}>
{lang.toUpperCase()}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}
-101
View File
@@ -1,101 +0,0 @@
"use client";
import { Play, Pause, Shuffle, RotateCcw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Slider } from "@/components/ui/slider";
import Panel from "@/components/Panel";
export type Speed = "slow" | "normal" | "fast" | "turbo";
interface PlaybackControlsProps {
isPlaying: boolean;
onTogglePlay: () => void;
onShuffle: () => void;
onReset: () => void;
arraySize: number;
onArraySizeChange: (n: number) => void;
speed: Speed;
onSpeedChange: (s: Speed) => void;
stepIndex: number;
stepCount: number;
onScrub: (i: number) => void;
labels: {
play: string; pause: string; shuffle: string; reset: string;
arraySize: string; elements: string; speed: string;
speedSlow: string; speedNormal: string; speedFast: string; speedTurbo: string;
};
disabled?: boolean;
}
const SPEEDS: Speed[] = ["slow", "normal", "fast", "turbo"];
export default function PlaybackControls({
isPlaying, onTogglePlay, onShuffle, onReset,
arraySize, onArraySizeChange, speed, onSpeedChange,
stepIndex, stepCount, onScrub, labels, disabled,
}: PlaybackControlsProps) {
const speedLabel: Record<Speed, string> = {
slow: labels.speedSlow, normal: labels.speedNormal, fast: labels.speedFast, turbo: labels.speedTurbo,
};
return (
<Panel className="space-y-5">
<div className="flex flex-wrap items-center gap-2">
<Button onClick={onTogglePlay} size="lg" className="gap-2 min-w-[112px]">
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
{isPlaying ? labels.pause : labels.play}
</Button>
<Button onClick={onShuffle} variant="secondary" className="gap-2">
<Shuffle className="h-4 w-4" />
{labels.shuffle}
</Button>
<Button onClick={onReset} variant="outline" className="gap-2">
<RotateCcw className="h-4 w-4" />
{labels.reset}
</Button>
<div className="ml-auto flex items-center rounded-lg border border-border overflow-hidden">
{SPEEDS.map((s) => (
<button
key={s}
onClick={() => onSpeedChange(s)}
className={`px-3 py-1.5 text-xs font-medium transition-colors ${
speed === s ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-secondary"
}`}
>
{speedLabel[s]}
</button>
))}
</div>
</div>
{stepCount > 0 && (
<div>
<input
type="range"
min={0}
max={stepCount}
value={stepIndex}
onChange={(e) => onScrub(Number(e.target.value))}
className="w-full accent-primary h-1.5 cursor-pointer"
/>
</div>
)}
<div>
<div className="flex justify-between text-xs text-muted-foreground mb-2">
<span>{labels.arraySize}</span>
<span className="font-mono">{arraySize} {labels.elements}</span>
</div>
<Slider
value={[arraySize]}
onValueChange={(v) => onArraySizeChange(v[0])}
min={5}
max={100}
step={1}
disabled={disabled}
/>
</div>
</Panel>
);
}
-96
View File
@@ -1,96 +0,0 @@
"use client";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { LanguagePicker } from "@/components/Pickers";
import { LanguageCode } from "@/lib/types";
function GitHubIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 .5C5.48.5.5 5.48.5 12c0 5.08 3.29 9.35 7.85 10.86.58.1.79-.24.79-.54 0-.27-.01-.99-.01-1.94-3.19.69-3.86-1.54-3.86-1.54-.53-1.36-1.29-1.72-1.29-1.72-1.05-.72.08-.71.08-.71 1.16.08 1.77 1.2 1.77 1.2 1.03 1.77 2.69 1.26 3.34.96.1-.75.4-1.26.73-1.55-2.55-.29-5.23-1.28-5.23-5.72 0-1.26.45-2.29 1.2-3.1-.12-.29-.52-1.45.12-3.02 0 0 .97-.31 3.17 1.18.92-.26 1.91-.39 2.89-.39.98 0 1.97.13 2.89.39 2.2-1.49 3.17-1.18 3.17-1.18.64 1.57.24 2.73.12 3.02.75.81 1.2 1.84 1.2 3.1 0 4.46-2.69 5.43-5.25 5.71.41.35.78 1.03.78 2.08 0 1.5-.01 2.72-.01 3.08 0 .3.21.65.8.54C20.71 21.35 24 17.08 24 12c0-6.52-4.98-11.5-12-11.5z"/>
</svg>
);
}
function GmailIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 12.713l11.985-8.677a.868.868 0 0 0-.491-.148H.506c-.177 0-.344.055-.491.148L12 12.713zm0 1.431L.035 5.596A.875.875 0 0 0 0 6.125v11.75c0 .478.387.875.875.875h22.25c.478 0 .875-.387.875-.875V6.125a.875.875 0 0 0-.035-.529L12 14.144z"/>
</svg>
);
}
function LinkedInIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.761 0 5-2.239 5-5v-14c0-2.761-2.239-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.5c-.966 0-1.75-.784-1.75-1.75s.784-1.75 1.75-1.75 1.75.784 1.75 1.75-.784 1.75-1.75 1.75zm13.5 12.5h-3v-5.5c0-1.379-1.121-2.5-2.5-2.5s-2.5 1.121-2.5 2.5v5.5h-3v-11h3v1.474c.809-1.161 2.201-1.974 3.5-1.974 2.481 0 4.5 2.019 4.5 4.5v7z"/>
</svg>
);
}
interface SiteFooterProps {
year: number;
rightsLabel: string;
licenseLabel: string;
viewLicenseLabel: string;
lang: LanguageCode;
onLangChange: (l: LanguageCode) => void;
}
export default function SiteFooter({ year, rightsLabel, licenseLabel, viewLicenseLabel, lang, onLangChange }: SiteFooterProps) {
return (
<footer className="border-t border-border bg-card/40 py-6 mt-16">
<div className="container mx-auto max-w-6xl px-4 md:px-6 flex flex-wrap items-center justify-between gap-4">
<p className="text-sm text-muted-foreground">&copy; {year} Quintavalle Pietro. {rightsLabel}</p>
<nav className="flex items-center gap-4">
<LanguagePicker value={lang} onChange={onLangChange} />
<Dialog>
<DialogTrigger className="text-sm text-muted-foreground hover:text-foreground transition-colors">
{viewLicenseLabel}
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{licenseLabel}</DialogTitle>
<DialogDescription asChild>
<div className="space-y-3 text-sm">
<p className="font-semibold text-foreground">Copyright (c) 2024 Quintavalle Pietro</p>
<p>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the &quot;Software&quot;), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
</p>
<p>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
</p>
<p>
THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</p>
</div>
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
<a href="https://github.com/Quinta0" target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-foreground transition-colors">
<GitHubIcon className="w-5 h-5" />
</a>
<a href="mailto:0pietroquintavalle0@gmail.com" className="text-muted-foreground hover:text-foreground transition-colors">
<GmailIcon className="w-5 h-5" />
</a>
<a href="https://www.linkedin.com/in/pietro-quintavalle-996b96267/" target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-foreground transition-colors">
<LinkedInIcon className="w-5 h-5" />
</a>
</nav>
</div>
</footer>
);
}
-54
View File
@@ -1,54 +0,0 @@
"use client";
import { motion } from "framer-motion";
import Panel from "@/components/Panel";
interface StatItemProps {
label: string;
value: number | string;
}
function StatItem({ label, value }: StatItemProps) {
return (
<div className="flex flex-col">
<span className="text-[11px] uppercase tracking-widest text-muted-foreground">{label}</span>
<motion.span
key={String(value)}
initial={{ opacity: 0.4 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.15 }}
className="text-2xl font-mono tabular-nums font-semibold text-foreground"
>
{value}
</motion.span>
</div>
);
}
interface StatsPanelProps {
title: string;
comparisons: number;
writes: number;
stepLabel: string;
stepIndex: number;
stepCount: number;
sortedLabel: string;
sortedCount: number;
total: number;
}
export default function StatsPanel({
title, comparisons, writes, stepLabel, stepIndex, stepCount, sortedLabel, sortedCount, total,
}: StatsPanelProps) {
return (
<Panel>
<p className="text-xs uppercase tracking-widest text-muted-foreground mb-3">{title}</p>
<div className="grid grid-cols-2 gap-4">
<StatItem label="Comparisons" value={comparisons} />
<StatItem label="Writes" value={writes} />
<StatItem label={stepLabel} value={`${stepIndex} / ${stepCount}`} />
<StatItem label={sortedLabel} value={`${sortedCount} / ${total}`} />
</div>
</Panel>
);
}
-56
View File
@@ -1,56 +0,0 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
-122
View File
@@ -1,122 +0,0 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
-200
View File
@@ -1,200 +0,0 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
-28
View File
@@ -1,28 +0,0 @@
"use client"
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { cn } from "@/lib/utils"
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
"relative flex w-full touch-none select-none items-center",
className
)}
{...props}
>
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
))
Slider.displayName = SliderPrimitive.Root.displayName
export { Slider }
View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

+1
View File
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
1:"$Sreact.fragment"
2:I[9766,[],""]
3:I[8924,[],""]
4:I[1959,[],"ClientPageRoot"]
5:I[2871,["367","static/chunks/367-3b480e80856df32c.js","974","static/chunks/app/page-4f7a9ffe96029c39.js"],"default"]
8:I[4431,[],"OutletBoundary"]
a:I[5278,[],"AsyncMetadataOutlet"]
c:I[4431,[],"ViewportBoundary"]
e:I[4431,[],"MetadataBoundary"]
f:"$Sreact.suspense"
11:I[7150,[],""]
:HL["/_next/static/media/22a5144ee8d83bca-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["/_next/static/media/7d4881bb7e1bf84d-s.p.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["/_next/static/css/d2c9f90acdc3eb15.css","style"]
0:{"P":null,"b":"yL3k1BZBHodz5x6rSYGmq","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/d2c9f90acdc3eb15.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"dark","children":["$","body",null,{"className":"antialiased font-sans __variable_246ccd __variable_c29908","children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],null,["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],["$","meta",null,{"name":"next-size-adjust","content":""}]],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[]],"s":false,"S":true}
6:{}
7:"$0:f:0:1:2:children:1:props:children:0:props:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
9:null
12:I[622,[],"IconMark"]
b:{"metadata":[["$","title","0",{"children":"Valgo - Watch how sorting actually works"}],["$","meta","1",{"name":"description","content":"An animated, narrated visualizer for nine classic sorting algorithms."}],["$","link","2",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","$L12","3",{}]],"error":null,"digest":"$undefined"}
10:"$b:metadata"
-380
View File
@@ -1,380 +0,0 @@
import { ActionType, AlgorithmKey, Step } from "./types";
// Every algorithm below is a *recorder*: it runs the real algorithm once,
// synchronously, and pushes a Step for every meaningful move. The UI then
// plays that list back at whatever speed the person chooses, and can
// scrub, pause, or step through it frame by frame. This is what lets a
// 100-element quicksort be paused mid-partition without re-running anything.
class Recorder {
private steps: Step[] = [];
private sortedSet = new Set<number>();
comparisons = 0;
writes = 0;
push(array: number[], action: ActionType, indices: number[], opts?: { range?: [number, number]; meta?: Record<string, number> }) {
this.steps.push({
array: [...array],
action,
indices,
sorted: Array.from(this.sortedSet).sort((a, b) => a - b),
range: opts?.range,
meta: opts?.meta,
comparisons: this.comparisons,
writes: this.writes,
});
}
compare(array: number[], i: number, j: number) {
this.comparisons++;
this.push(array, "compare", [i, j]);
}
swap(array: number[], i: number, j: number) {
[array[i], array[j]] = [array[j], array[i]];
this.writes += 2;
this.push(array, "swap", [i, j]);
}
overwrite(array: number[], i: number, value: number) {
array[i] = value;
this.writes++;
this.push(array, "overwrite", [i]);
}
markSorted(array: number[], indices: number[]) {
indices.forEach((i) => this.sortedSet.add(i));
this.push(array, "mark-sorted", indices);
}
isSorted(i: number) {
return this.sortedSet.has(i);
}
finish(array: number[]) {
for (let i = 0; i < array.length; i++) this.sortedSet.add(i);
this.push(array, "done", []);
}
result() {
return this.steps;
}
}
function bubbleSort(input: number[]): Step[] {
const rec = new Recorder();
const a = [...input];
rec.push(a, "init", []);
const n = a.length;
for (let i = 0; i < n; i++) {
let swapped = false;
for (let j = 0; j < n - i - 1; j++) {
rec.compare(a, j, j + 1);
if (a[j] > a[j + 1]) {
rec.swap(a, j, j + 1);
swapped = true;
}
}
rec.markSorted(a, [n - i - 1]);
if (!swapped) break;
}
rec.finish(a);
return rec.result();
}
function insertionSort(input: number[]): Step[] {
const rec = new Recorder();
const a = [...input];
rec.push(a, "init", []);
rec.markSorted(a, [0]);
for (let i = 1; i < a.length; i++) {
const key = a[i];
let j = i - 1;
while (j >= 0) {
rec.compare(a, j, j + 1);
if (a[j] <= key) break;
a[j + 1] = a[j];
rec.writes++;
rec.push(a, "shift", [j, j + 1]);
j--;
}
a[j + 1] = key;
rec.writes++;
rec.push(a, "overwrite", [j + 1]);
rec.markSorted(a, [i]);
}
rec.finish(a);
return rec.result();
}
function heapSort(input: number[]): Step[] {
const rec = new Recorder();
const a = [...input];
rec.push(a, "init", []);
const n = a.length;
function heapify(size: number, root: number) {
let largest = root;
const left = 2 * root + 1;
const right = 2 * root + 2;
if (left < size) {
rec.compare(a, left, largest);
if (a[left] > a[largest]) largest = left;
}
if (right < size) {
rec.compare(a, right, largest);
if (a[right] > a[largest]) largest = right;
}
if (largest !== root) {
rec.swap(a, root, largest);
heapify(size, largest);
}
}
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) heapify(n, i);
for (let i = n - 1; i > 0; i--) {
rec.swap(a, 0, i);
rec.markSorted(a, [i]);
heapify(i, 0);
}
rec.markSorted(a, [0]);
rec.finish(a);
return rec.result();
}
function quickSort(input: number[]): Step[] {
const rec = new Recorder();
const a = [...input];
rec.push(a, "init", []);
function partition(low: number, high: number) {
const pivot = a[high];
rec.push(a, "select-pivot", [high], { range: [low, high], meta: { pivot } });
let i = low - 1;
for (let j = low; j < high; j++) {
rec.compare(a, j, high);
if (a[j] < pivot) {
i++;
if (i !== j) rec.swap(a, i, j);
}
}
rec.swap(a, i + 1, high);
rec.markSorted(a, [i + 1]);
rec.push(a, "pivot-placed", [i + 1]);
return i + 1;
}
function sort(low: number, high: number) {
if (low < high) {
rec.push(a, "partition-range", [], { range: [low, high] });
const pi = partition(low, high);
sort(low, pi - 1);
sort(pi + 1, high);
} else if (low === high) {
rec.markSorted(a, [low]);
}
}
sort(0, a.length - 1);
rec.finish(a);
return rec.result();
}
function mergeSort(input: number[]): Step[] {
const rec = new Recorder();
const a = [...input];
rec.push(a, "init", []);
function merge(left: number, mid: number, right: number) {
rec.push(a, "merge-range", [], { range: [left, right], meta: { mid } });
const L = a.slice(left, mid + 1);
const R = a.slice(mid + 1, right + 1);
let i = 0, j = 0, k = left;
while (i < L.length && j < R.length) {
rec.comparisons++;
if (L[i] <= R[j]) {
a[k] = L[i++];
} else {
a[k] = R[j++];
}
rec.writes++;
rec.push(a, "merge-write", [k], { range: [left, right] });
k++;
}
while (i < L.length) {
a[k] = L[i++];
rec.writes++;
rec.push(a, "merge-write", [k], { range: [left, right] });
k++;
}
while (j < R.length) {
a[k] = R[j++];
rec.writes++;
rec.push(a, "merge-write", [k], { range: [left, right] });
k++;
}
if (left === 0 && right === a.length - 1) {
rec.markSorted(a, Array.from({ length: a.length }, (_, idx) => idx));
}
}
function sort(left: number, right: number) {
if (left >= right) return;
const mid = Math.floor((left + right) / 2);
sort(left, mid);
sort(mid + 1, right);
merge(left, mid, right);
}
sort(0, a.length - 1);
rec.finish(a);
return rec.result();
}
function radixSort(input: number[]): Step[] {
const rec = new Recorder();
const a = [...input];
rec.push(a, "init", []);
if (a.length === 0) {
rec.finish(a);
return rec.result();
}
const max = Math.max(...a);
for (let exp = 1; Math.floor(max / exp) > 0; exp *= 10) {
const output = new Array(a.length).fill(0);
const count = new Array(10).fill(0);
rec.push(a, "gap-set", [], { meta: { digit: exp } });
for (let i = 0; i < a.length; i++) {
const d = Math.floor(a[i] / exp) % 10;
count[d]++;
rec.push(a, "count", [i], { meta: { digit: d } });
}
for (let i = 1; i < 10; i++) count[i] += count[i - 1];
for (let i = a.length - 1; i >= 0; i--) {
const d = Math.floor(a[i] / exp) % 10;
output[count[d] - 1] = a[i];
count[d]--;
}
for (let i = 0; i < a.length; i++) {
a[i] = output[i];
rec.writes++;
rec.push(a, "overwrite", [i], { meta: { digit: exp } });
}
}
rec.finish(a);
return rec.result();
}
function countingSort(input: number[]): Step[] {
const rec = new Recorder();
const a = [...input];
rec.push(a, "init", []);
if (a.length === 0) {
rec.finish(a);
return rec.result();
}
const min = Math.min(...a);
const max = Math.max(...a);
const range = max - min + 1;
const count = new Array(range).fill(0);
const output = new Array(a.length).fill(0);
for (let i = 0; i < a.length; i++) {
count[a[i] - min]++;
rec.push(a, "count", [i], { meta: { bucket: a[i] - min } });
}
for (let i = 1; i < count.length; i++) count[i] += count[i - 1];
for (let i = a.length - 1; i >= 0; i--) {
output[count[a[i] - min] - 1] = a[i];
count[a[i] - min]--;
}
for (let i = 0; i < a.length; i++) {
a[i] = output[i];
rec.writes++;
rec.markSorted(a, [i]);
}
rec.finish(a);
return rec.result();
}
function bucketSort(input: number[]): Step[] {
const rec = new Recorder();
const a = [...input];
rec.push(a, "init", []);
if (a.length === 0) {
rec.finish(a);
return rec.result();
}
const bucketSize = 5;
const min = Math.min(...a);
const max = Math.max(...a);
const bucketCount = Math.floor((max - min) / bucketSize) + 1;
const buckets: number[][] = Array.from({ length: bucketCount }, () => []);
for (let i = 0; i < a.length; i++) {
const idx = Math.floor((a[i] - min) / bucketSize);
buckets[idx].push(a[i]);
rec.push(a, "bucket-place", [i], { meta: { bucket: idx } });
}
for (let i = 0; i < buckets.length; i++) {
buckets[i].sort((x, y) => x - y);
rec.push(a, "bucket-sorted", [], { meta: { bucket: i } });
}
let idx = 0;
for (let i = 0; i < buckets.length; i++) {
for (let j = 0; j < buckets[i].length; j++) {
a[idx] = buckets[i][j];
rec.writes++;
rec.markSorted(a, [idx]);
idx++;
}
}
rec.finish(a);
return rec.result();
}
function shellSort(input: number[]): Step[] {
const rec = new Recorder();
const a = [...input];
rec.push(a, "init", []);
const n = a.length;
for (let gap = Math.floor(n / 2); gap > 0; gap = Math.floor(gap / 2)) {
rec.push(a, "gap-set", [], { meta: { gap } });
for (let i = gap; i < n; i++) {
const temp = a[i];
let j = i;
while (j >= gap) {
rec.compare(a, j - gap, j);
if (a[j - gap] <= temp) break;
a[j] = a[j - gap];
rec.writes++;
rec.push(a, "shift", [j - gap, j], { meta: { gap } });
j -= gap;
}
a[j] = temp;
rec.writes++;
rec.push(a, "overwrite", [j], { meta: { gap } });
}
}
rec.finish(a);
return rec.result();
}
export const algorithmRunners: Record<AlgorithmKey, (input: number[]) => Step[]> = {
bubble: bubbleSort,
insertion: insertionSort,
heap: heapSort,
quick: quickSort,
merge: mergeSort,
radix: radixSort,
counting: countingSort,
bucket: bucketSort,
shell: shellSort,
};
export function runAlgorithm(key: AlgorithmKey, input: number[]): Step[] {
return algorithmRunners[key](input);
}
-32
View File
@@ -1,32 +0,0 @@
import { AlgorithmKey, ComplexityClass } from "./types";
export const complexityClass: Record<AlgorithmKey, ComplexityClass> = {
bubble: "n2",
insertion: "n2",
heap: "nlogn",
quick: "nlogn",
merge: "nlogn",
radix: "n+k",
counting: "n+k",
bucket: "n+k",
shell: "n2-adaptive",
};
// A tiny sample of points used to draw each growth curve on a shared
// 0..100 axis, normalized to the same visual scale so n^2 vs n log n vs
// n is instantly, viscerally comparable — the whole point of drawing it.
export function curvePoints(fn: (n: number) => number, maxN = 100, steps = 40) {
const raw = Array.from({ length: steps + 1 }, (_, i) => {
const n = (i / steps) * maxN;
return { n, v: fn(Math.max(n, 1)) };
});
const maxV = Math.max(...raw.map((p) => p.v));
return raw.map((p) => ({ n: p.n, v: p.v / maxV }));
}
export const complexityCurves: Record<ComplexityClass, (n: number) => number> = {
n2: (n) => n * n,
nlogn: (n) => n * Math.log2(Math.max(n, 2)),
"n+k": (n) => n,
"n2-adaptive": (n) => n * Math.log2(Math.max(n, 2)) * 1.3,
};
-580
View File
@@ -1,580 +0,0 @@
import { AlgorithmKey, LanguageCode } from "./types";
export const languages: LanguageCode[] = ["en", "fr", "it", "de", "es", "ja", "no", "sv", "ua", "ru"];
interface AlgorithmDescription {
title: string;
timeComplexity: string;
spaceComplexity: string;
description: string;
}
type Translations = {
[key in AlgorithmKey]: {
[lang in LanguageCode]: AlgorithmDescription;
};
};
// Verbatim descriptions carried over from the original Valgo, in all ten
// languages it already supported. The new step-by-step narration (see
// narration.ts) is additive on top of these reference-card descriptions.
export const algorithmDescriptions: Translations = {
bubble: {
en: {
title: "Bubble Sort",
timeComplexity: "Time: O(n^2)",
spaceComplexity: "Space: O(1)",
description: "Bubble sort is a simple sorting algorithm that repeatedly steps through the list, compares adjacent elements and swaps them if they are in the wrong order. The pass through the list is repeated until the list is sorted."
},
fr: {
title: "Bubble Sort",
timeComplexity: "Temps : O(n^2)",
spaceComplexity: "Espace : O(1)",
description: "Le tri à bulles est un algorithme de tri simple qui parcourt à plusieurs reprises la liste, compare les éléments adjacents et les échange s'ils sont dans le mauvais ordre. Le passage à travers la liste est répété jusqu'à ce que la liste soit triée."
},
it: {
title: "Bubble Sort",
timeComplexity: "Tempo: O(n^2)",
spaceComplexity: "Spazio: O(1)",
description: "Il bubble sort è un algoritmo di ordinamento semplice che scorre ripetutamente la lista, confronta gli elementi adiacenti e li scambia se sono nell'ordine sbagliato. Il passaggio attraverso la lista viene ripetuto fino a quando la lista non è ordinata."
},
de: {
title: "Bubble Sort",
timeComplexity: "Zeit: O(n^2)",
spaceComplexity: "Platz: O(1)",
description: "Bubble Sort ist ein einfacher Sortieralgorithmus, der wiederholt durch die Liste geht, benachbarte Elemente vergleicht und sie vertauscht, wenn sie in der falschen Reihenfolge sind. Der Durchgang durch die Liste wird wiederholt, bis die Liste sortiert ist."
},
es: {
title: "Bubble Sort",
timeComplexity: "Tiempo: O(n^2)",
spaceComplexity: "Espacio: O(1)",
description: "El ordenamiento de burbuja es un algoritmo de ordenamiento simple que recorre repetidamente la lista, compara los elementos adyacentes y los intercambia si están en el orden incorrecto. El recorrido a través de la lista se repite hasta que la lista está ordenada."
},
ja: {
title: "Bubble Sort",
timeComplexity: "時間: O(n^2)",
spaceComplexity: "空間: O(1)",
description: "バブルソートは、リストを繰り返しステップして、隣接する要素を比較し、それらが間違った順序にある場合に入れ替える単純なソートアルゴリズムです。リストを通過する操作はリストがソートされるまで繰り返されます。"
},
no: {
title: "Bubble Sort",
timeComplexity: "Tid: O(n^2)",
spaceComplexity: "Plass: O(1)",
description: "Bubble sort er en enkel sorteringsalgoritme som gjentatte ganger går gjennom listen, sammenligner tilstøtende elementer og bytter dem hvis de er i feil rekkefølge. Gjennomgangen av listen gjentas til listen er sortert."
},
sv: {
title: "Bubble Sort",
timeComplexity: "Tid: O(n^2)",
spaceComplexity: "Utrymme: O(1)",
description: "Bubble sort är en enkel sorteringsalgoritm som upprepade gånger går igenom listan, jämför intilliggande element och byter dem om de är i fel ordning. Genomgången av listan upprepas tills listan är sorterad."
},
ua: {
title: "Bubble Sort",
timeComplexity: "Час: O(n^2)",
spaceComplexity: "Місце: O(1)",
description: "Сортування бульбашкою - це простий алгоритм сортування, який повторно проходить по списку, порівнює суміжні елементи та змінює їх місцями, якщо вони знаходяться в неправильному порядку. Прохід по списку повторюється до тих пір, поки список не буде відсортований."
},
ru: {
title: "Bubble Sort",
timeComplexity: "Время: O(n^2)",
spaceComplexity: "Место: O(1)",
description: "Сортировка пузырьком - это простой алгоритм сортировки, который многократно проходит по списку, сравнивает смежные элементы и меняет их местами, если они находятся в неправильном порядке. Проход по списку повторяется до тех пор, пока список не будет отсортирован."
}
},
insertion: {
en: {
title: "Insertion Sort",
timeComplexity: "Time: O(n^2)",
spaceComplexity: "Space: O(1)",
description: "Insertion sort is a simple sorting algorithm that builds the final sorted array one item at a time. It is much less efficient on large lists than more advanced algorithms such as quicksort, heapsort, or merge sort."
},
fr: {
title: "Insertion Sort",
timeComplexity: "Temps : O(n^2)",
spaceComplexity: "Espace : O(1)",
description: "Le tri par insertion est un algorithme de tri simple qui construit le tableau trié final un élément à la fois. Il est beaucoup moins efficace sur les grandes listes que des algorithmes plus avancés comme le tri rapide, le tri par tas ou le tri fusion."
},
it: {
title: "Insertion Sort",
timeComplexity: "Tempo: O(n^2)",
spaceComplexity: "Spazio: O(1)",
description: "L'insertion sort è un algoritmo di ordinamento semplice che costruisce l'array ordinato finale un elemento alla volta. È molto meno efficiente su grandi liste rispetto ad algoritmi più avanzati come il quicksort, l'heapsort o il merge sort."
},
de: {
title: "Insertion Sort",
timeComplexity: "Zeit: O(n^2)",
spaceComplexity: "Platz: O(1)",
description: "Insertion Sort ist ein einfacher Sortieralgorithmus, der das endgültig sortierte Array ein Element nach dem anderen aufbaut. Es ist viel weniger effizient bei großen Listen als fortschrittlichere Algorithmen wie Quicksort, Heapsort oder Mergesort."
},
es: {
title: "Insertion Sort",
timeComplexity: "Tiempo: O(n^2)",
spaceComplexity: "Espacio: O(1)",
description: "El ordenamiento por inserción es un algoritmo de ordenamiento simple que construye la matriz ordenada final un elemento a la vez. Es mucho menos eficiente en listas grandes que algoritmos más avanzados como el quicksort, el heapsort o el mergesort."
},
ja: {
title: "Insertion Sort",
timeComplexity: "時間: O(n^2)",
spaceComplexity: "空間: O(1)",
description: "挿入ソートは、最終的にソートされた配列を一度に1つの項目ずつ構築する単純なソートアルゴリズムです。クイックソート、ヒープソート、マージソートなどの高度なアルゴリズムよりも大きなリストでは効率が大幅に低下します。"
},
no: {
title: "Insertion Sort",
timeComplexity: "Tid: O(n^2)",
spaceComplexity: "Plass: O(1)",
description: "Insertion sort er en enkel sorteringsalgoritme som bygger den endelige sorterte matrisen ett element om gangen. Det er mye mindre effektivt på store lister enn mer avanserte algoritmer som quicksort, heapsort eller mergesort."
},
sv: {
title: "Insertion Sort",
timeComplexity: "Tid: O(n^2)",
spaceComplexity: "Utrymme: O(1)",
description: "Insertion sort är en enkel sorteringsalgoritm som bygger den slutgiltiga sorterade matrisen ett element i taget. Det är mycket mindre effektivt på stora listor än mer avancerade algoritmer som quicksort, heapsort eller mergesort."
},
ua: {
title: "Insertion Sort",
timeComplexity: "Час: O(n^2)",
spaceComplexity: "Місце: O(1)",
description: "Сортування вставкою - це простий алгоритм сортування, який будує остаточно відсортований масив по одному елементу за раз. Він набагато менш ефективний на великих списках, ніж більш просунуті алгоритми, такі як швидке сортування, сортування купою або сортування злиттям."
},
ru: {
title: "Insertion Sort",
timeComplexity: "Время: O(n^2)",
spaceComplexity: "Место: O(1)",
description: "Сортировка вставками - это простой алгоритм сортировки, который строит окончательно отсортированный массив по одному элементу за раз. Он гораздо менее эффективен на больших списках, чем более продвинутые алгоритмы, такие как быстрая сортировка, сортировка кучей или сортировка слиянием."
}
},
merge: {
en: {
title: "Merge Sort",
timeComplexity: "Time: O(n log n)",
spaceComplexity: "Space: O(n)",
description: "Merge sort is an efficient, stable, and comparison-based sorting algorithm. Most implementations produce a stable sort, meaning that the order of equal elements is the same in the input and output. It works by recursively splitting the list into two halves, sorting each half, and then merging the sorted halves back together."
},
fr: {
title: "Merge Sort",
timeComplexity: "Temps : O(n log n)",
spaceComplexity: "Espace : O(n)",
description: "Le tri fusion est un algorithme de tri efficace, stable et basé sur la comparaison. La plupart des implémentations produisent un tri stable, ce qui signifie que l'ordre des éléments égaux est le même dans l'entrée et la sortie. Il fonctionne en divisant récursivement la liste en deux moitiés, en triant chaque moitié, puis en fusionnant les moitiés triées."
},
it: {
title: "Merge Sort",
timeComplexity: "Tempo: O(n log n)",
spaceComplexity: "Spazio: O(n)",
description: "Il merge sort è un algoritmo di ordinamento efficiente, stabile e basato sul confronto. La maggior parte delle implementazioni produce un ordinamento stabile, il che significa che l'ordine degli elementi uguali è lo stesso nell'input e nell'output. Funziona dividendo ricorsivamente la lista in due metà, ordinando ciascuna metà e poi unendo le metà ordinate."
},
de: {
title: "Merge Sort",
timeComplexity: "Zeit: O(n log n)",
spaceComplexity: "Platz: O(n)",
description: "Mergesort ist ein effizienter, stabiler und vergleichsbasierter Sortieralgorithmus. Die meisten Implementierungen produzieren eine stabile Sortierung, was bedeutet, dass die Reihenfolge der gleichen Elemente im Eingabedaten und Ausgabedaten gleich ist. Es funktioniert, indem die Liste rekursiv in zwei Hälften geteilt wird, jede Hälfte sortiert und dann die sortierten Hälften wieder zusammengeführt werden."
},
es: {
title: "Merge Sort",
timeComplexity: "Tiempo: O(n log n)",
spaceComplexity: "Espacio: O(n)",
description: "El mergesort es un algoritmo de ordenamiento eficiente, estable y basado en comparaciones. La mayoría de las implementaciones producen un ordenamiento estable, lo que significa que el orden de los elementos iguales es el mismo en la entrada y en la salida. Funciona dividiendo recursivamente la lista en dos mitades, ordenando cada mitad y luego fusionando las mitades ordenadas."
},
ja: {
title: "Merge Sort",
timeComplexity: "時間: O(n log n)",
spaceComplexity: "空間: O(n)",
description: "マージソートは、効率的で安定した比較ベースのソートアルゴリズムです。ほとんどの実装は安定したソートを生成し、入力と出力で同じ要素の順序が同じになります。リストを再帰的に2つの半分に分割し、それぞれをソートしてからソートされた半分を再度マージすることで動作します。"
},
no: {
title: "Merge Sort",
timeComplexity: "Tid: O(n log n)",
spaceComplexity: "Plass: O(n)",
description: "Merge sort er en effektiv, stabil og sammenligningsbasert sorteringsalgoritme. De fleste implementeringer produserer en stabil sortering, noe som betyr at rekkefølgen av like elementer er den samme i inn- og utdata. Den fungerer ved å rekursivt dele listen i to halvdeler, sortere hver halv og deretter slå de sorterte halvdelene sammen igjen."
},
sv: {
title: "Merge Sort",
timeComplexity: "Tid: O(n log n)",
spaceComplexity: "Utrymme: O(n)",
description: "Merge sort är en effektiv, stabil och jämförelsebaserad sorteringsalgoritm. De flesta implementationer producerar en stabil sortering, vilket betyder att ordningen på lika element är densamma i input och output. Det fungerar genom att rekursivt dela upp listan i två halvor, sortera varje halv och sedan slå ihop de sorterade halvorna."
},
ua: {
title: "Merge Sort",
timeComplexity: "Час: O(n log n)",
spaceComplexity: "Місце: O(n)",
description: "Сортування злиттям - це ефективний, стабільний алгоритм сортування на основі порівнянь. Більшість реалізацій забезпечують стабільне сортування, що означає, що порядок однакових елементів однаковий на вході та на виході. Він працює, рекурсивно розділяючи список на дві половини, сортує кожну половину, а потім зливає відсортовані половини разом."
},
ru: {
title: "Merge Sort",
timeComplexity: "Время: O(n log n)",
spaceComplexity: "Место: O(n)",
description: "Сортировка слиянием - это эффективный, стабильный и основанный на сравнениях алгоритм сортировки. Большинство реализаций обеспечивают стабильную сортировку, что означает, что порядок одинаковых элементов на входе и на выходе одинаков. Он работает, рекурсивно разделяя список на две половины, сортируя каждую половину, а затем объединяя отсортированные половины вместе."
}
},
heap: {
en: {
title: "Heap Sort",
timeComplexity: "Time: O(n log n)",
spaceComplexity: "Space: O(1)",
description: "Heap sort is a comparison-based sorting algorithm that uses a binary heap data structure. It divides its input into a sorted and an unsorted region, and it iteratively shrinks the unsorted region by extracting the largest element and moving it to the sorted region."
},
fr: {
title: "Heap Sort",
timeComplexity: "Temps : O(n log n)",
spaceComplexity: "Espace : O(1)",
description: "Le tri par tas est un algorithme de tri basé sur la comparaison qui utilise une structure de données de tas binaire. Il divise son entrée en une région triée et une région non triée, et il réduit de manière itérative la région non triée en extrayant le plus grand élément et en le déplaçant vers la région triée."
},
it: {
title: "Heap Sort",
timeComplexity: "Tempo: O(n log n)",
spaceComplexity: "Spazio: O(1)",
description: "L'heapsort è un algoritmo di ordinamento basato sul confronto che utilizza una struttura dati heap binaria. Divide il suo input in una regione ordinata e una non ordinata e riduce iterativamente la regione non ordinata estraendo l'elemento più grande e spostandolo nella regione ordinata."
},
de: {
title: "Heap Sort",
timeComplexity: "Zeit: O(n log n)",
spaceComplexity: "Platz: O(1)",
description: "Heapsort ist ein vergleichsbasierter Sortieralgorithmus, der eine binäre Heap-Datenstruktur verwendet. Es teilt seine Eingabe in einen sortierten und einen unsortierten Bereich und verkleinert den unsortierten Bereich iterativ, indem das größte Element extrahiert und in den sortierten Bereich verschoben wird."
},
es: {
title: "Heap Sort",
timeComplexity: "Tiempo: O(n log n)",
spaceComplexity: "Espacio: O(1)",
description: "El heapsort es un algoritmo de ordenamiento basado en comparaciones que utiliza una estructura de datos de montón binario. Divide su entrada en una región ordenada y una no ordenada, y reduce iterativamente la región no ordenada extrayendo el elemento más grande y moviéndolo a la región ordenada."
},
ja: {
title: "Heap Sort",
timeComplexity: "時間: O(n log n)",
spaceComplexity: "空間: O(1)",
description: "ヒープソートは、バイナリヒープデータ構造を使用する比較ベースのソートアルゴリズムです。入力をソート済みと未ソートの領域に分割し、最も大きな要素を抽出してソート済み領域に移動することで未ソート領域を反復的に縮小します。"
},
no: {
title: "Heap Sort",
timeComplexity: "Tid: O(n log n)",
spaceComplexity: "Plass: O(1)",
description: "Heap sort er en sammenligningsbasert sorteringsalgoritme som bruker en binær heap-datastruktur. Den deler inn dataene i et sortert og et usortert område, og krymper det usorterte området iterativt ved å trekke ut det største elementet og flytte det til det sorterte området."
},
sv: {
title: "Heap Sort",
timeComplexity: "Tid: O(n log n)",
spaceComplexity: "Utrymme: O(1)",
description: "Heap sort är en jämförelsebaserad sorteringsalgoritm som använder en binär heap-datastruktur. Den delar upp sin input i en sorterad och en osorterad region, och krymper iterativt den osorterade regionen genom att extrahera det största elementet och flytta det till den sorterade regionen."
},
ua: {
title: "Heap Sort",
timeComplexity: "Час: O(n log n)",
spaceComplexity: "Місце: O(1)",
description: "Сортування купою - це алгоритм сортування на основі порівнянь, який використовує структуру даних бінарної купи. Він ділить свій вхід на відсортовану та невідсортовану область і ітеративно зменшує невідсортовану область, витягуючи найбільший елемент і переміщуючи його до відсортованої області."
},
ru: {
title: "Heap Sort",
timeComplexity: "Время: O(n log n)",
spaceComplexity: "Место: O(1)",
description: "Сортировка кучей - это алгоритм сортировки на основе сравнений, который использует структуру данных бинарной кучи. Он делит свой вход на отсортированную и неотсортированную область и итеративно сокращает неотсортированную область, извлекая наибольший элемент и перемещая его в отсортированную область."
}
},
quick: {
en: {
title: "Quick Sort",
timeComplexity: "Time: O(n log n) on average, O(n^2) in the worst case",
spaceComplexity: "Space: O(log n)",
description: "Quick sort is an efficient sorting algorithm that uses a divide-and-conquer approach to sort elements. It works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays according to whether they are less than or greater than the pivot. The sub-arrays are then sorted recursively."
},
fr: {
title: "Quick Sort",
timeComplexity: "Temps : O(n log n) en moyenne, O(n^2) dans le pire des cas",
spaceComplexity: "Espace : O(log n)",
description: "Le tri rapide est un algorithme de tri efficace qui utilise une approche de diviser pour mieux régner pour trier les éléments. Il fonctionne en sélectionnant un élément 'pivot' dans le tableau et en partitionnant les autres éléments en deux sous-tableaux selon qu'ils sont inférieurs ou supérieurs au pivot. Les sous-tableaux sont ensuite triés de manière récursive."
},
it: {
title: "Quick Sort",
timeComplexity: "Tempo: O(n log n) in media, O(n^2) nel peggiore dei casi",
spaceComplexity: "Spazio: O(log n)",
description: "Il quick sort è un algoritmo di ordinamento efficiente che utilizza un approccio divide et impera per ordinare gli elementi. Funziona selezionando un elemento 'pivot' dall'array e partizionando gli altri elementi in due sotto-array a seconda che siano inferiori o superiori al pivot. I sotto-array vengono poi ordinati ricorsivamente."
},
de: {
title: "Quick Sort",
timeComplexity: "Zeit: O(n log n) im Durchschnitt, O(n^2) im schlimmsten Fall",
spaceComplexity: "Platz: O(log n)",
description: "Quicksort ist ein effizienter Sortieralgorithmus, der einen Divide-and-Conquer-Ansatz verwendet, um Elemente zu sortieren. Es funktioniert, indem ein 'Pivot'-Element aus dem Array ausgewählt und die anderen Elemente in zwei Unterarrays partitioniert werden, je nachdem, ob sie kleiner oder größer als das Pivot sind. Die Unterarrays werden dann rekursiv sortiert."
},
es: {
title: "Quick Sort",
timeComplexity: "Tiempo: O(n log n) en promedio, O(n^2) en el peor de los casos",
spaceComplexity: "Espacio: O(log n)",
description: "El quicksort es un algoritmo de ordenamiento eficiente que utiliza un enfoque de divide y vencerás para ordenar los elementos. Funciona seleccionando un elemento 'pivote' del array y particionando los otros elementos en dos sub-arrays según si son menores o mayores que el pivote. Los sub-arrays se ordenan luego recursivamente."
},
ja: {
title: "Quick Sort",
timeComplexity: "時間: 平均O(n log n)、最悪O(n^2)",
spaceComplexity: "空間: O(log n)",
description: "クイックソートは、要素をソートするために分割統治法を使用する効率的なソートアルゴリズムです。配列から「ピボット」要素を選択し、他の要素をピボットより小さいか大きいかによって2つのサブ配列に分割することで機能します。サブ配列は再帰的にソートされます。"
},
no: {
title: "Quick Sort",
timeComplexity: "Tid: O(n log n) i gjennomsnitt, O(n^2) i verste fall",
spaceComplexity: "Plass: O(log n)",
description: "Quick sort er en effektiv sorteringsalgoritme som bruker en del og hersk-tilnærming for å sortere elementer. Den fungerer ved å velge et 'pivot'-element fra matrisen og partisjonere de andre elementene i to underarrayer i henhold til om de er mindre enn eller større enn pivot. Underarrayene sorteres deretter rekursivt."
},
sv: {
title: "Quick Sort",
timeComplexity: "Tid: O(n log n) i genomsnitt, O(n^2) i värsta fall",
spaceComplexity: "Utrymme: O(log n)",
description: "Quick sort är en effektiv sorteringsalgoritm som använder en dela-och-härska-metod för att sortera element. Det fungerar genom att välja ett 'pivot'-element från matrisen och partitionera de andra elementen i två underarrayer beroende på om de är mindre än eller större än pivot. Underarrayerna sorteras sedan rekursivt."
},
ua: {
title: "Quick Sort",
timeComplexity: "Час: O(n log n) в середньому, O(n^2) в гіршому випадку",
spaceComplexity: "Місце: O(log n)",
description: "Швидке сортування - це ефективний алгоритм сортування, який використовує підхід поділу і завоювання для сортування елементів. Він працює, вибираючи 'опорний' елемент з масиву і розділяючи інші елементи на два підмасиви відповідно до того, чи менші вони або більші за опорний. Підмасиви потім сортуються рекурсивно."
},
ru: {
title: "Quick Sort",
timeComplexity: "Время: O(n log n) в среднем, O(n^2) в худшем случае",
spaceComplexity: "Место: O(log n)",
description: "Быстрая сортировка - это эффективный алгоритм сортировки, который использует подход разделяй и властвуй для сортировки элементов. Он работает, выбирая 'опорный' элемент из массива и разделяя другие элементы на два подмассива в зависимости от того, меньше они или больше опорного. Подмассивы затем сортируются рекурсивно."
}
},
radix: {
en: {
title: "Radix Sort",
timeComplexity: "Time: O(nk) where n is the number of elements and k is the number of digits in the largest number",
spaceComplexity: "Space: O(n + k)",
description: "Radix Sort is a non-comparative sorting algorithm that sorts integers by processing individual digits. It processes each digit from the least significant to the most significant, using a stable counting sort to sort digits at each level."
},
fr: {
title: "Tri Radix",
timeComplexity: "Temps : O(nk) où n est le nombre d'éléments et k est le nombre de chiffres du plus grand nombre",
spaceComplexity: "Espace : O(n + k)",
description: "Le tri Radix est un algorithme de tri non comparatif qui trie les entiers en traitant les chiffres individuels. Il traite chaque chiffre du moins significatif au plus significatif, en utilisant un tri comptant stable pour trier les chiffres à chaque niveau."
},
it: {
title: "Radix Sort",
timeComplexity: "Tempo: O(nk) dove n è il numero di elementi e k è il numero di cifre nel numero più grande",
spaceComplexity: "Spazio: O(n + k)",
description: "Il Radix Sort è un algoritmo di ordinamento non comparativo che ordina gli interi elaborando le singole cifre. Elabora ciascuna cifra dal meno significativo al più significativo, utilizzando un ordinamento per conteggio stabile per ordinare le cifre a ciascun livello."
},
de: {
title: "Radix Sort",
timeComplexity: "Zeit: O(nk), wobei n die Anzahl der Elemente und k die Anzahl der Ziffern der größten Zahl ist",
spaceComplexity: "Platz: O(n + k)",
description: "Radix Sort ist ein nicht-vergleichender Sortieralgorithmus, der Ganzzahlen durch Verarbeitung einzelner Ziffern sortiert. Er verarbeitet jede Ziffer von der am wenigsten signifikanten zur am meisten signifikanten und verwendet ein stabiles Zählersortieren, um die Ziffern auf jeder Ebene zu sortieren."
},
es: {
title: "Radix Sort",
timeComplexity: "Tiempo: O(nk) donde n es el número de elementos y k es el número de dígitos en el número más grande",
spaceComplexity: "Espacio: O(n + k)",
description: "El Radix Sort es un algoritmo de ordenamiento no comparativo que ordena enteros procesando dígitos individuales. Procesa cada dígito desde el menos significativo hasta el más significativo, utilizando un conteo estable para ordenar los dígitos en cada nivel."
},
ja: {
title: "基数ソート",
timeComplexity: "時間: O(nk) ここで n は要素数、k は最大数の桁数",
spaceComplexity: "空間: O(n + k)",
description: "基数ソートは、個々の数字を処理することで整数をソートする非比較ソートアルゴリズムです。各桁を最下位から最上位まで処理し、安定したカウントソートを使用して各レベルの桁をソートします。"
},
no: {
title: "Radix Sort",
timeComplexity: "Tid: O(nk) der n er antall elementer og k er antall sifre i det største tallet",
spaceComplexity: "Plass: O(n + k)",
description: "Radix Sort er en ikke-sammenlignende sorteringsalgoritme som sorterer heltall ved å behandle individuelle sifre. Den behandler hvert siffer fra minst signifikant til mest signifikant, og bruker en stabil tellesortering for å sortere sifrene på hvert nivå."
},
sv: {
title: "Radix Sort",
timeComplexity: "Tid: O(nk) där n är antalet element och k är antalet siffror i det största numret",
spaceComplexity: "Utrymme: O(n + k)",
description: "Radix Sort är en icke-jämförande sorteringsalgoritm som sorterar heltal genom att bearbeta individuella siffror. Den bearbetar varje siffra från minst signifikant till mest signifikant, med en stabil räkningssortering för att sortera siffrorna på varje nivå."
},
ua: {
title: "Радикс Сортування",
timeComplexity: "Час: O(nk), де n - кількість елементів, а k - кількість цифр у найбільшому числі",
spaceComplexity: "Місце: O(n + k)",
description: "Радикс сортування - це алгоритм сортування без порівняння, який сортує цілі числа, обробляючи окремі цифри. Він обробляє кожну цифру від найменш значущої до найбільш значущої, використовуючи стабільне підрахункове сортування для сортування цифр на кожному рівні."
},
ru: {
title: "Поразрядная сортировка",
timeComplexity: "Время: O(nk), где n - количество элементов, а k - количество цифр в наибольшем числе",
spaceComplexity: "Место: O(n + k)",
description: "Поразрядная сортировка - это не сравнительный алгоритм сортировки, который сортирует целые числа путем обработки отдельных цифр. Он обрабатывает каждую цифру от наименее значимой до наиболее значимой, используя стабильную сортировку подсчетом для сортировки цифр на каждом уровне."
}
},
counting: {
en: {
title: "Counting Sort",
timeComplexity: "Time: O(n + k) where n is the number of elements and k is the range of the input",
spaceComplexity: "Space: O(k)",
description: "Counting Sort is a non-comparative sorting algorithm that works by counting the number of occurrences of each distinct element in the input. The count is then used to place the elements in the correct position in the output array."
},
fr: {
title: "Tri par dénombrement",
timeComplexity: "Temps : O(n + k) où n est le nombre d'éléments et k est la plage de l'entrée",
spaceComplexity: "Espace : O(k)",
description: "Le tri par dénombrement est un algorithme de tri non comparatif qui fonctionne en comptant le nombre d'occurrences de chaque élément distinct dans l'entrée. Le compte est ensuite utilisé pour placer les éléments dans la position correcte dans le tableau de sortie."
},
it: {
title: "Counting Sort",
timeComplexity: "Tempo: O(n + k) dove n è il numero di elementi e k è l'intervallo dell'input",
spaceComplexity: "Spazio: O(k)",
description: "Il Counting Sort è un algoritmo di ordinamento non comparativo che funziona contando il numero di occorrenze di ciascun elemento distinto nell'input. Il conteggio viene quindi utilizzato per posizionare gli elementi nella posizione corretta nell'array di output."
},
de: {
title: "Counting Sort",
timeComplexity: "Zeit: O(n + k), wobei n die Anzahl der Elemente und k der Bereich der Eingabe ist",
spaceComplexity: "Platz: O(k)",
description: "Counting Sort ist ein nicht-vergleichender Sortieralgorithmus, der funktioniert, indem die Anzahl der Vorkommen jedes einzelnen Elements in der Eingabe gezählt wird. Die Zählung wird dann verwendet, um die Elemente an der richtigen Position im Ausgabearray zu platzieren."
},
es: {
title: "Counting Sort",
timeComplexity: "Tiempo: O(n + k) donde n es el número de elementos y k es el rango de la entrada",
spaceComplexity: "Espacio: O(k)",
description: "Counting Sort es un algoritmo de ordenamiento no comparativo que funciona contando el número de ocurrencias de cada elemento distinto en la entrada. Luego, el conteo se utiliza para colocar los elementos en la posición correcta en la matriz de salida."
},
ja: {
title: "計数ソート",
timeComplexity: "時間: O(n + k) ここで n は要素数、k は入力範囲",
spaceComplexity: "空間: O(k)",
description: "計数ソートは、入力内の各異なる要素の出現回数をカウントすることで機能する非比較ソートアルゴリズムです。次に、カウントを使用して、要素を出力配列の正しい位置に配置します。"
},
no: {
title: "Counting Sort",
timeComplexity: "Tid: O(n + k) der n er antall elementer og k er området for inngangen",
spaceComplexity: "Plass: O(k)",
description: "Counting Sort er en ikke-sammenlignende sorteringsalgoritme som fungerer ved å telle antall forekomster av hvert distinkte element i inngangen. Deretter brukes tellingen til å plassere elementene i riktig posisjon i utgangsmatrisen."
},
sv: {
title: "Counting Sort",
timeComplexity: "Tid: O(n + k) där n är antalet element och k är inmatningsområdet",
spaceComplexity: "Utrymme: O(k)",
description: "Counting Sort är en icke-jämförande sorteringsalgoritm som fungerar genom att räkna antalet förekomster av varje distinkt element i ingången. Sedan används räkningen för att placera elementen i rätt position i utmatningsmatrisen."
},
ua: {
title: "Підрахункове сортування",
timeComplexity: "Час: O(n + k), де n - кількість елементів, а k - діапазон введення",
spaceComplexity: "Місце: O(k)",
description: "Підрахункове сортування - це алгоритм сортування без порівняння, який працює шляхом підрахунку кількості появ кожного окремого елемента у вхідних даних. Потім підрахунок використовується для розміщення елементів у правильній позиції у вихідному масиві."
},
ru: {
title: "Сортировка подсчетом",
timeComplexity: "Время: O(n + k), где n - количество элементов, а k - диапазон входных данных",
spaceComplexity: "Место: O(k)",
description: "Сортировка подсчетом - это не сравнительный алгоритм сортировки, который работает путем подсчета количества вхождений каждого отдельного элемента во входных данных. Затем подсчет используется для размещения элементов в правильной позиции в выходном массиве."
}
},
bucket: {
en: {
title: "Bucket Sort",
timeComplexity: "Time: O(n + k) where n is the number of elements and k is the number of buckets",
spaceComplexity: "Space: O(n + k)",
description: "Bucket Sort is a sorting algorithm that works by distributing the elements of an array into a number of buckets. Each bucket is then sorted individually, either using a different sorting algorithm or recursively applying the bucket sort."
},
fr: {
title: "Tri par compartiments",
timeComplexity: "Temps : O(n + k) où n est le nombre d'éléments et k est le nombre de compartiments",
spaceComplexity: "Espace : O(n + k)",
description: "Le tri par compartiments est un algorithme de tri qui fonctionne en distribuant les éléments d'un tableau dans un certain nombre de compartiments. Chaque compartiment est ensuite trié individuellement, soit en utilisant un autre algorithme de tri, soit en appliquant récursivement le tri par compartiments."
},
it: {
title: "Bucket Sort",
timeComplexity: "Tempo: O(n + k) dove n è il numero di elementi e k è il numero di bucket",
spaceComplexity: "Spazio: O(n + k)",
description: "Il Bucket Sort è un algoritmo di ordinamento che funziona distribuendo gli elementi di un array in un certo numero di bucket. Ogni bucket viene quindi ordinato singolarmente, utilizzando un altro algoritmo di ordinamento o applicando ricorsivamente il bucket sort."
},
de: {
title: "Bucket Sort",
timeComplexity: "Zeit: O(n + k), wobei n die Anzahl der Elemente und k die Anzahl der Buckets ist",
spaceComplexity: "Platz: O(n + k)",
description: "Bucket Sort ist ein Sortieralgorithmus, der funktioniert, indem die Elemente eines Arrays auf eine Anzahl von Buckets verteilt werden. Jeder Bucket wird dann einzeln sortiert, entweder unter Verwendung eines anderen Sortieralgorithmus oder durch rekursive Anwendung des Bucket-Sortiers."
},
es: {
title: "Bucket Sort",
timeComplexity: "Tiempo: O(n + k) donde n es el número de elementos y k es el número de cubetas",
spaceComplexity: "Espacio: O(n + k)",
description: "El Bucket Sort es un algoritmo de ordenamiento que funciona distribuyendo los elementos de un array en un número de cubetas. Cada cubeta se ordena individualmente, ya sea utilizando otro algoritmo de ordenamiento o aplicando recursivamente el bucket sort."
},
ja: {
title: "バケットソート",
timeComplexity: "時間: O(n + k) ここで n は要素数、k はバケットの数",
spaceComplexity: "空間: O(n + k)",
description: "バケットソートは、配列の要素を複数のバケットに分配することで機能するソートアルゴリズムです。各バケットは、他のソートアルゴリズムを使用するか、バケットソートを再帰的に適用して個別にソートされます。"
},
no: {
title: "Bucket Sort",
timeComplexity: "Tid: O(n + k) der n er antall elementer og k er antall bøtter",
spaceComplexity: "Plass: O(n + k)",
description: "Bucket Sort er en sorteringsalgoritme som fungerer ved å fordele elementene i en matrise i et antall bøtter. Hver bøtte sorteres deretter individuelt, enten ved å bruke en annen sorteringsalgoritme eller ved å bruke bucket sort rekursivt."
},
sv: {
title: "Bucket Sort",
timeComplexity: "Tid: O(n + k) där n är antalet element och k är antalet hinkar",
spaceComplexity: "Utrymme: O(n + k)",
description: "Bucket Sort är en sorteringsalgoritm som fungerar genom att distribuera elementen i en array i ett antal hinkar. Varje hink sorteras sedan individuellt, antingen med en annan sorteringsalgoritm eller genom att rekursivt tillämpa bucket sort."
},
ua: {
title: "Сортування по кошиках",
timeComplexity: "Час: O(n + k), де n - кількість елементів, а k - кількість кошиків",
spaceComplexity: "Місце: O(n + k)",
description: "Сортування по кошиках - це алгоритм сортування, який працює шляхом розподілу елементів масиву на кілька кошиків. Кожен кошик потім сортується окремо, або за допомогою іншого алгоритму сортування, або рекурсивно застосовуючи сортування по кошиках."
},
ru: {
title: "Сортировка по карманам",
timeComplexity: "Время: O(n + k), где n - количество элементов, а k - количество карманов",
spaceComplexity: "Место: O(n + k)",
description: "Сортировка по карманам - это алгоритм сортировки, который работает путем распределения элементов массива по ряду карманов. Каждый карман затем сортируется отдельно, либо с использованием другого алгоритма сортировки, либо рекурсивным применением сортировки по карманам."
}
},
shell: {
en: {
title: "Shell Sort",
timeComplexity: "Time: O(n^2) in the worst case, but can be much better with a good gap sequence",
spaceComplexity: "Space: O(1)",
description: "Shell Sort is an in-place comparison sort that generalizes insertion sort to allow the exchange of items that are far apart. The algorithm starts by sorting pairs of elements far apart from each other, then progressively reducing the gap between elements to be compared."
},
fr: {
title: "Tri de Shell",
timeComplexity: "Temps : O(n^2) dans le pire des cas, mais peut être bien meilleur avec une bonne séquence de gaps",
spaceComplexity: "Espace : O(1)",
description: "Le tri de Shell est un tri par comparaison en place qui généralise le tri par insertion pour permettre l'échange d'éléments éloignés les uns des autres. L'algorithme commence par trier des paires d'éléments éloignés les uns des autres, puis réduit progressivement l'écart entre les éléments à comparer."
},
it: {
title: "Shell Sort",
timeComplexity: "Tempo: O(n^2) nel peggiore dei casi, ma può essere molto migliore con una buona sequenza di gap",
spaceComplexity: "Spazio: O(1)",
description: "Il Shell Sort è un ordinamento in loco per confronto che generalizza l'ordinamento per inserzione per consentire lo scambio di elementi distanti tra loro. L'algoritmo inizia ordinando coppie di elementi distanti tra loro, quindi riduce progressivamente la distanza tra gli elementi da confrontare."
},
de: {
title: "Shell Sort",
timeComplexity: "Zeit: O(n^2) im schlimmsten Fall, kann aber mit einer guten Gap-Sequenz viel besser sein",
spaceComplexity: "Platz: O(1)",
description: "Shell Sort ist ein In-Place-Vergleichssortieralgorithmus, der den Insertionssort verallgemeinert, um den Austausch von Elementen zu ermöglichen, die weit voneinander entfernt sind. Der Algorithmus beginnt mit dem Sortieren von Paaren von Elementen, die weit voneinander entfernt sind, und reduziert dann nach und nach den Abstand zwischen den zu vergleichenden Elementen."
},
es: {
title: "Shell Sort",
timeComplexity: "Tiempo: O(n^2) en el peor de los casos, pero puede ser mucho mejor con una buena secuencia de intervalos",
spaceComplexity: "Espacio: O(1)",
description: "El Shell Sort es un ordenamiento in situ por comparación que generaliza el ordenamiento por inserción para permitir el intercambio de elementos que están muy separados. El algoritmo comienza ordenando pares de elementos muy separados entre sí, y luego reduce progresivamente el intervalo entre los elementos a comparar."
},
ja: {
title: "シェルソート",
timeComplexity: "時間: 最悪の場合 O(n^2) ですが、適切なギャップシーケンスを使用すると大幅に改善できます",
spaceComplexity: "空間: O(1)",
description: "シェルソートは、離れた項目の交換を可能にする挿入ソートを一般化したインプレース比較ソートです。このアルゴリズムは、互いに遠く離れた要素のペアをソートすることから始まり、比較する要素間のギャップを徐々に減らしていきます。"
},
no: {
title: "Shell Sort",
timeComplexity: "Tid: O(n^2) i verste fall, men kan være mye bedre med en god gapsekvens",
spaceComplexity: "Plass: O(1)",
description: "Shell Sort er en in-place sammenligningssortering som generaliserer innsettingssortering for å tillate bytte av elementer som er langt fra hverandre. Algoritmen starter med å sortere par av elementer langt fra hverandre, og reduserer deretter gradvis gapet mellom elementene som skal sammenlignes."
},
sv: {
title: "Shell Sort",
timeComplexity: "Tid: O(n^2) i värsta fall, men kan vara mycket bättre med en bra gapsekvens",
spaceComplexity: "Utrymme: O(1)",
description: "Shell Sort är en in-place-jämförelsesortering som generaliserar insättningssortering för att möjliggöra byte av objekt som är långt ifrån varandra. Algoritmen börjar med att sortera par av element långt ifrån varandra och minskar sedan successivt gapet mellan elementen som ska jämföras."
},
ua: {
title: "Сортування Шелла",
timeComplexity: "Час: O(n^2) у найгіршому випадку, але може бути набагато кращим із хорошою послідовністю проміжків",
spaceComplexity: "Місце: O(1)",
description: "Сортування Шелла - це порівняльне сортування на місці, яке узагальнює сортування вставками, щоб дозволити обмін елементами, які знаходяться далеко один від одного. Алгоритм починає з сортування пар елементів, які знаходяться далеко один від одного, а потім поступово зменшує проміжок між елементами для порівняння."
},
ru: {
title: "Сортировка Шелла",
timeComplexity: "Время: O(n^2) в худшем случае, но может быть намного лучше с хорошей последовательностью промежутков",
spaceComplexity: "Место: O(1)",
description: "Сортировка Шелла - это сортировка на месте методом сравнения, которая обобщает сортировку вставками, чтобы позволить обмен элементами, которые находятся далеко друг от друга. Алгоритм начинается с сортировки пар элементов, находящихся далеко друг от друга, затем постепенно уменьшает разрыв между сравниваемыми элементами."
}
}
};
-443
View File
@@ -1,443 +0,0 @@
import { ActionType, AlgorithmKey, LanguageCode, Step } from "./types";
export type UIKey =
| "brand"
| "tagline"
| "play"
| "pause"
| "resume"
| "reset"
| "shuffle"
| "algorithm"
| "language"
| "arraySize"
| "elements"
| "speed"
| "speedSlow"
| "speedNormal"
| "speedFast"
| "speedTurbo"
| "comparisons"
| "writes"
| "step"
| "of"
| "sorted"
| "license"
| "viewLicense"
| "close"
| "footerRights"
| "growthTitle"
| "growthCaption"
| "narrationTitle"
| "statsTitle"
| "pickAlgorithm"
| "done";
const UI: Record<LanguageCode, Record<UIKey, string>> = {
en: {
brand: "Valgo",
tagline: "Watch how sorting actually works.",
play: "Play", pause: "Pause", resume: "Resume", reset: "New shuffle",
shuffle: "Shuffle", algorithm: "Algorithm", language: "Language",
arraySize: "Array size", elements: "elements",
speed: "Speed", speedSlow: "Slow", speedNormal: "Normal", speedFast: "Fast", speedTurbo: "Turbo",
comparisons: "Comparisons", writes: "Writes", step: "Step", of: "of", sorted: "Sorted",
license: "MIT License", viewLicense: "License", close: "Close",
footerRights: "All rights reserved.",
growthTitle: "How the work grows", growthCaption: "Same n, wildly different amounts of work.",
narrationTitle: "What's happening", statsTitle: "Live stats",
pickAlgorithm: "Choose an algorithm", done: "Sorted! Every value found its place.",
},
fr: {
brand: "Valgo",
tagline: "Regardez comment le tri fonctionne vraiment.",
play: "Lecture", pause: "Pause", resume: "Reprendre", reset: "Nouveau mélange",
shuffle: "Mélanger", algorithm: "Algorithme", language: "Langue",
arraySize: "Taille du tableau", elements: "éléments",
speed: "Vitesse", speedSlow: "Lent", speedNormal: "Normal", speedFast: "Rapide", speedTurbo: "Turbo",
comparisons: "Comparaisons", writes: "Écritures", step: "Étape", of: "sur", sorted: "Triés",
license: "Licence MIT", viewLicense: "Licence", close: "Fermer",
footerRights: "Tous droits réservés.",
growthTitle: "Comment le travail augmente", growthCaption: "Même n, quantités de travail très différentes.",
narrationTitle: "Ce qui se passe", statsTitle: "Statistiques en direct",
pickAlgorithm: "Choisir un algorithme", done: "Trié ! Chaque valeur a trouvé sa place.",
},
it: {
brand: "Valgo",
tagline: "Guarda come funziona davvero l'ordinamento.",
play: "Avvia", pause: "Pausa", resume: "Riprendi", reset: "Nuovo mescolamento",
shuffle: "Mescola", algorithm: "Algoritmo", language: "Lingua",
arraySize: "Dimensione array", elements: "elementi",
speed: "Velocità", speedSlow: "Lenta", speedNormal: "Normale", speedFast: "Veloce", speedTurbo: "Turbo",
comparisons: "Confronti", writes: "Scritture", step: "Passo", of: "di", sorted: "Ordinati",
license: "Licenza MIT", viewLicense: "Licenza", close: "Chiudi",
footerRights: "Tutti i diritti riservati.",
growthTitle: "Come cresce il lavoro", growthCaption: "Stesso n, quantità di lavoro molto diverse.",
narrationTitle: "Cosa sta succedendo", statsTitle: "Statistiche live",
pickAlgorithm: "Scegli un algoritmo", done: "Ordinato! Ogni valore ha trovato il suo posto.",
},
de: {
brand: "Valgo",
tagline: "Sieh dir an, wie Sortieren wirklich funktioniert.",
play: "Abspielen", pause: "Pause", resume: "Fortsetzen", reset: "Neu mischen",
shuffle: "Mischen", algorithm: "Algorithmus", language: "Sprache",
arraySize: "Array-Größe", elements: "Elemente",
speed: "Geschwindigkeit", speedSlow: "Langsam", speedNormal: "Normal", speedFast: "Schnell", speedTurbo: "Turbo",
comparisons: "Vergleiche", writes: "Schreibvorgänge", step: "Schritt", of: "von", sorted: "Sortiert",
license: "MIT-Lizenz", viewLicense: "Lizenz", close: "Schließen",
footerRights: "Alle Rechte vorbehalten.",
growthTitle: "Wie die Arbeit wächst", growthCaption: "Gleiches n, sehr unterschiedlicher Aufwand.",
narrationTitle: "Was gerade passiert", statsTitle: "Live-Statistik",
pickAlgorithm: "Algorithmus wählen", done: "Fertig sortiert! Jeder Wert hat seinen Platz gefunden.",
},
es: {
brand: "Valgo",
tagline: "Mira cómo funciona realmente el ordenamiento.",
play: "Reproducir", pause: "Pausa", resume: "Reanudar", reset: "Nueva mezcla",
shuffle: "Mezclar", algorithm: "Algoritmo", language: "Idioma",
arraySize: "Tamaño del array", elements: "elementos",
speed: "Velocidad", speedSlow: "Lenta", speedNormal: "Normal", speedFast: "Rápida", speedTurbo: "Turbo",
comparisons: "Comparaciones", writes: "Escrituras", step: "Paso", of: "de", sorted: "Ordenados",
license: "Licencia MIT", viewLicense: "Licencia", close: "Cerrar",
footerRights: "Todos los derechos reservados.",
growthTitle: "Cómo crece el trabajo", growthCaption: "Mismo n, cantidades de trabajo muy distintas.",
narrationTitle: "Qué está pasando", statsTitle: "Estadísticas en vivo",
pickAlgorithm: "Elige un algoritmo", done: "¡Ordenado! Cada valor encontró su lugar.",
},
ja: {
brand: "Valgo",
tagline: "ソートが実際にどう動くかを見てみましょう。",
play: "再生", pause: "一時停止", resume: "再開", reset: "シャッフル",
shuffle: "シャッフル", algorithm: "アルゴリズム", language: "言語",
arraySize: "配列サイズ", elements: "要素",
speed: "速度", speedSlow: "遅い", speedNormal: "普通", speedFast: "速い", speedTurbo: "高速",
comparisons: "比較回数", writes: "書き込み回数", step: "ステップ", of: "/", sorted: "ソート済み",
license: "MITライセンス", viewLicense: "ライセンス", close: "閉じる",
footerRights: "全著作権所有。",
growthTitle: "処理量の増え方", growthCaption: "同じ n でも、処理量は大きく異なります。",
narrationTitle: "今起きていること", statsTitle: "リアルタイム統計",
pickAlgorithm: "アルゴリズムを選択", done: "完了!すべての値が正しい位置に収まりました。",
},
no: {
brand: "Valgo",
tagline: "Se hvordan sortering faktisk fungerer.",
play: "Spill av", pause: "Pause", resume: "Fortsett", reset: "Stokk på nytt",
shuffle: "Stokk", algorithm: "Algoritme", language: "Språk",
arraySize: "Matrisestørrelse", elements: "elementer",
speed: "Hastighet", speedSlow: "Sakte", speedNormal: "Normal", speedFast: "Rask", speedTurbo: "Turbo",
comparisons: "Sammenligninger", writes: "Skrivinger", step: "Steg", of: "av", sorted: "Sortert",
license: "MIT-lisens", viewLicense: "Lisens", close: "Lukk",
footerRights: "Alle rettigheter forbeholdt.",
growthTitle: "Hvordan arbeidet vokser", growthCaption: "Samme n, svært ulik mengde arbeid.",
narrationTitle: "Hva skjer nå", statsTitle: "Sanntidsstatistikk",
pickAlgorithm: "Velg en algoritme", done: "Ferdig sortert! Hver verdi fant sin plass.",
},
sv: {
brand: "Valgo",
tagline: "Se hur sortering faktiskt fungerar.",
play: "Spela", pause: "Pausa", resume: "Fortsätt", reset: "Blanda igen",
shuffle: "Blanda", algorithm: "Algoritm", language: "Språk",
arraySize: "Array-storlek", elements: "element",
speed: "Hastighet", speedSlow: "Långsam", speedNormal: "Normal", speedFast: "Snabb", speedTurbo: "Turbo",
comparisons: "Jämförelser", writes: "Skrivningar", step: "Steg", of: "av", sorted: "Sorterade",
license: "MIT-licens", viewLicense: "Licens", close: "Stäng",
footerRights: "Alla rättigheter förbehållna.",
growthTitle: "Hur arbetet växer", growthCaption: "Samma n, väldigt olika mängd arbete.",
narrationTitle: "Vad som händer", statsTitle: "Livestatistik",
pickAlgorithm: "Välj en algoritm", done: "Klart! Varje värde hittade sin plats.",
},
ua: {
brand: "Valgo",
tagline: "Подивіться, як насправді працює сортування.",
play: "Старт", pause: "Пауза", resume: "Продовжити", reset: "Перемішати",
shuffle: "Перемішати", algorithm: "Алгоритм", language: "Мова",
arraySize: "Розмір масиву", elements: "елементів",
speed: "Швидкість", speedSlow: "Повільно", speedNormal: "Звичайно", speedFast: "Швидко", speedTurbo: "Турбо",
comparisons: "Порівняння", writes: "Записи", step: "Крок", of: "з", sorted: "Відсортовано",
license: "Ліцензія MIT", viewLicense: "Ліцензія", close: "Закрити",
footerRights: "Усі права захищено.",
growthTitle: "Як зростає обсяг роботи", growthCaption: "Те саме n, зовсім різний обсяг роботи.",
narrationTitle: "Що відбувається", statsTitle: "Статистика наживо",
pickAlgorithm: "Оберіть алгоритм", done: "Відсортовано! Кожне значення знайшло своє місце.",
},
ru: {
brand: "Valgo",
tagline: "Посмотрите, как на самом деле работает сортировка.",
play: "Старт", pause: "Пауза", resume: "Продолжить", reset: "Перемешать",
shuffle: "Перемешать", algorithm: "Алгоритм", language: "Язык",
arraySize: "Размер массива", elements: "элементов",
speed: "Скорость", speedSlow: "Медленно", speedNormal: "Обычно", speedFast: "Быстро", speedTurbo: "Турбо",
comparisons: "Сравнения", writes: "Записи", step: "Шаг", of: "из", sorted: "Отсортировано",
license: "Лицензия MIT", viewLicense: "Лицензия", close: "Закрыть",
footerRights: "Все права защищены.",
growthTitle: "Как растёт объём работы", growthCaption: "Одно и то же n, совершенно разный объём работы.",
narrationTitle: "Что происходит", statsTitle: "Статистика в реальном времени",
pickAlgorithm: "Выберите алгоритм", done: "Отсортировано! Каждое значение нашло своё место.",
},
};
export function t(lang: LanguageCode, key: UIKey): string {
return UI[lang]?.[key] ?? UI.en[key];
}
// ---- Step narration -------------------------------------------------
// One shared vocabulary of "moves" is narrated slightly differently
// depending on which algorithm is doing the moving, so the commentary
// always explains *why*, not just *what*.
type Vars = Record<string, string | number>;
function fmt(template: string, vars: Vars): string {
return template.replace(/\{(\w+)\}/g, (_, k) => String(vars[k] ?? ""));
}
const TEMPLATES: Record<LanguageCode, Record<string, string>> = {
en: {
init: "A freshly shuffled array of {n} numbers. Watch {algo} bring order to it.",
compare: "Comparing positions {a} and {b}: {va} vs {vb}.",
swapYes: "{va} > {vb}, so they're out of order — swap positions {a} and {b}.",
overwrite: "Writing {va} into position {a}.",
shift: "Sliding {va} rightward to open a gap.",
selectPivot: "Choosing {va} (position {a}) as the pivot.",
partitionRange: "Partitioning the range [{low}, {high}] around the pivot.",
pivotPlaced: "The pivot is home — {va} is now permanently in its sorted spot at {a}.",
mergeRange: "Merging two sorted halves: [{low}..{mid}] and [{mid1}..{high}].",
mergeWrite: "The smaller candidate, {va}, goes into position {a}.",
markSorted: "Position {a} is locked in as sorted.",
shellGap: "Shrinking the gap to {gap} — comparisons now leap across the array instead of crawling.",
radixDigit: "Now sorting by the digit worth {digit}, least significant first.",
bucketPlace: "{va} drops into bucket {bucket}, grouped by value range.",
bucketSorted: "Sorting the contents of bucket {bucket} on its own.",
countingTally: "Tallying how many times {va} appears.",
done: "Sorted! Every element found its final place.",
},
fr: {
init: "Un tableau de {n} nombres mélangés. Regardez {algo} y mettre de l'ordre.",
compare: "Comparaison des positions {a} et {b} : {va} contre {vb}.",
swapYes: "{va} > {vb}, donc c'est dans le mauvais ordre — échange des positions {a} et {b}.",
overwrite: "Écriture de {va} en position {a}.",
shift: "Décalage de {va} vers la droite pour laisser de la place.",
selectPivot: "Choix de {va} (position {a}) comme pivot.",
partitionRange: "Partition de la plage [{low}, {high}] autour du pivot.",
pivotPlaced: "Le pivot est à sa place — {va} est désormais définitivement trié en position {a}.",
mergeRange: "Fusion de deux moitiés déjà triées : [{low}..{mid}] et [{mid1}..{high}].",
mergeWrite: "Le plus petit candidat, {va}, est placé en position {a}.",
markSorted: "La position {a} est désormais fixée comme triée.",
shellGap: "Réduction de l'écart à {gap} — les comparaisons sautent maintenant à travers le tableau.",
radixDigit: "Tri maintenant selon le chiffre de rang {digit}, du moins significatif d'abord.",
bucketPlace: "{va} est placé dans le compartiment {bucket}, selon sa valeur.",
bucketSorted: "Tri du contenu du compartiment {bucket} indépendamment.",
countingTally: "Comptage du nombre d'occurrences de {va}.",
done: "Trié ! Chaque élément a trouvé sa place finale.",
},
it: {
init: "Un array di {n} numeri appena mescolato. Guarda {algo} metterlo in ordine.",
compare: "Confronto tra le posizioni {a} e {b}: {va} contro {vb}.",
swapYes: "{va} > {vb}, quindi sono nell'ordine sbagliato — scambio le posizioni {a} e {b}.",
overwrite: "Scrivo {va} nella posizione {a}.",
shift: "Sposto {va} verso destra per fare spazio.",
selectPivot: "Scelgo {va} (posizione {a}) come pivot.",
partitionRange: "Partiziono l'intervallo [{low}, {high}] attorno al pivot.",
pivotPlaced: "Il pivot è a casa — {va} è ora definitivamente ordinato in posizione {a}.",
mergeRange: "Fondo due metà già ordinate: [{low}..{mid}] e [{mid1}..{high}].",
mergeWrite: "Il candidato più piccolo, {va}, va in posizione {a}.",
markSorted: "La posizione {a} è ora fissata come ordinata.",
shellGap: "Riduco il gap a {gap} — i confronti ora saltano attraverso l'array invece di procedere passo passo.",
radixDigit: "Ora ordino in base alla cifra di peso {digit}, a partire dalla meno significativa.",
bucketPlace: "{va} finisce nel bucket {bucket}, in base al suo valore.",
bucketSorted: "Ordino il contenuto del bucket {bucket} per conto suo.",
countingTally: "Conto quante volte compare {va}.",
done: "Ordinato! Ogni elemento ha trovato il suo posto finale.",
},
de: {
init: "Ein frisch gemischtes Array aus {n} Zahlen. Sieh zu, wie {algo} Ordnung schafft.",
compare: "Vergleiche Position {a} und {b}: {va} gegen {vb}.",
swapYes: "{va} > {vb}, also falsche Reihenfolge — tausche Position {a} und {b}.",
overwrite: "Schreibe {va} an Position {a}.",
shift: "Verschiebe {va} nach rechts, um Platz zu schaffen.",
selectPivot: "Wähle {va} (Position {a}) als Pivot.",
partitionRange: "Partitioniere den Bereich [{low}, {high}] um den Pivot herum.",
pivotPlaced: "Der Pivot ist angekommen — {va} steht jetzt endgültig sortiert an Position {a}.",
mergeRange: "Verschmelze zwei bereits sortierte Hälften: [{low}..{mid}] und [{mid1}..{high}].",
mergeWrite: "Der kleinere Kandidat, {va}, kommt an Position {a}.",
markSorted: "Position {a} ist nun endgültig sortiert.",
shellGap: "Verringere den Abstand auf {gap} — Vergleiche springen jetzt über das Array statt zu kriechen.",
radixDigit: "Jetzt wird nach der Ziffernstelle {digit} sortiert, zuerst die niedrigstwertige.",
bucketPlace: "{va} kommt in Eimer {bucket}, gruppiert nach Wertebereich.",
bucketSorted: "Sortiere den Inhalt von Eimer {bucket} für sich.",
countingTally: "Zähle, wie oft {va} vorkommt.",
done: "Fertig sortiert! Jedes Element hat seinen endgültigen Platz gefunden.",
},
es: {
init: "Un array recién mezclado de {n} números. Observa cómo {algo} le pone orden.",
compare: "Comparando las posiciones {a} y {b}: {va} frente a {vb}.",
swapYes: "{va} > {vb}, así que están desordenados — intercambio las posiciones {a} y {b}.",
overwrite: "Escribiendo {va} en la posición {a}.",
shift: "Deslizo {va} hacia la derecha para abrir hueco.",
selectPivot: "Elijo {va} (posición {a}) como pivote.",
partitionRange: "Particiono el rango [{low}, {high}] alrededor del pivote.",
pivotPlaced: "El pivote llegó a casa — {va} ya está definitivamente ordenado en la posición {a}.",
mergeRange: "Fusionando dos mitades ya ordenadas: [{low}..{mid}] y [{mid1}..{high}].",
mergeWrite: "El candidato más pequeño, {va}, va a la posición {a}.",
markSorted: "La posición {a} queda fijada como ordenada.",
shellGap: "Reduzco el intervalo a {gap} — las comparaciones ahora saltan por el array en vez de arrastrarse.",
radixDigit: "Ahora ordenando por el dígito de valor {digit}, empezando por el menos significativo.",
bucketPlace: "{va} cae en el cubo {bucket}, agrupado por rango de valor.",
bucketSorted: "Ordenando el contenido del cubo {bucket} por separado.",
countingTally: "Contando cuántas veces aparece {va}.",
done: "¡Ordenado! Cada elemento encontró su lugar final.",
},
ja: {
init: "{n} 個の数をシャッフルした配列です。{algo} が整えていく様子を見てみましょう。",
compare: "位置 {a} と {b} を比較: {va} と {vb}。",
swapYes: "{va} > {vb} のため順序が逆です — 位置 {a} と {b} を交換します。",
overwrite: "{va} を位置 {a} に書き込みます。",
shift: "{va} を右にずらして隙間を作ります。",
selectPivot: "{va}(位置 {a})をピボットに選びます。",
partitionRange: "ピボットを中心に範囲 [{low}, {high}] を分割します。",
pivotPlaced: "ピボットが定位置に到着 — {va} は位置 {a} で確定的にソート済みです。",
mergeRange: "すでにソート済みの2つの半分 [{low}..{mid}] と [{mid1}..{high}] を結合します。",
mergeWrite: "小さい方の候補 {va} を位置 {a} に書き込みます。",
markSorted: "位置 {a} はソート済みとして確定しました。",
shellGap: "間隔を {gap} に縮めます — 比較が一歩ずつではなく配列を飛び越えて行われます。",
radixDigit: "桁の重み {digit} で並べ替え中です(下位桁から順に)。",
bucketPlace: "{va} は値の範囲に応じてバケット {bucket} に入ります。",
bucketSorted: "バケット {bucket} の中身を個別にソートします。",
countingTally: "{va} が何回出現するかを数えています。",
done: "完了!すべての要素が最終的な位置に収まりました。",
},
no: {
init: "En nyblandet matrise med {n} tall. Se hvordan {algo} skaper orden.",
compare: "Sammenligner posisjon {a} og {b}: {va} mot {vb}.",
swapYes: "{va} > {vb}, altså feil rekkefølge — bytter posisjon {a} og {b}.",
overwrite: "Skriver {va} til posisjon {a}.",
shift: "Flytter {va} mot høyre for å lage plass.",
selectPivot: "Velger {va} (posisjon {a}) som pivot.",
partitionRange: "Partisjonerer området [{low}, {high}] rundt pivoten.",
pivotPlaced: "Pivoten er hjemme — {va} er nå permanent sortert på posisjon {a}.",
mergeRange: "Slår sammen to sorterte halvdeler: [{low}..{mid}] og [{mid1}..{high}].",
mergeWrite: "Den minste kandidaten, {va}, går til posisjon {a}.",
markSorted: "Posisjon {a} er nå låst som sortert.",
shellGap: "Krymper gapet til {gap} — sammenligninger hopper nå over matrisen i stedet for å krype.",
radixDigit: "Sorterer nå etter sifferverdi {digit}, minst betydningsfulle først.",
bucketPlace: "{va} havner i bøtte {bucket}, gruppert etter verdiområde.",
bucketSorted: "Sorterer innholdet i bøtte {bucket} for seg selv.",
countingTally: "Teller hvor mange ganger {va} forekommer.",
done: "Ferdig sortert! Hvert element fant sin endelige plass.",
},
sv: {
init: "En nyblandad array med {n} tal. Se hur {algo} skapar ordning.",
compare: "Jämför position {a} och {b}: {va} mot {vb}.",
swapYes: "{va} > {vb}, så de är i fel ordning — byter position {a} och {b}.",
overwrite: "Skriver {va} till position {a}.",
shift: "Flyttar {va} åt höger för att göra plats.",
selectPivot: "Väljer {va} (position {a}) som pivot.",
partitionRange: "Partitionerar intervallet [{low}, {high}] runt pivoten.",
pivotPlaced: "Pivoten är hemma — {va} är nu permanent sorterad på position {a}.",
mergeRange: "Slår ihop två sorterade halvor: [{low}..{mid}] och [{mid1}..{high}].",
mergeWrite: "Den minsta kandidaten, {va}, hamnar på position {a}.",
markSorted: "Position {a} är nu låst som sorterad.",
shellGap: "Minskar gapet till {gap} — jämförelser hoppar nu över arrayen istället för att krypa.",
radixDigit: "Sorterar nu efter siffervärdet {digit}, minst signifikant först.",
bucketPlace: "{va} hamnar i hink {bucket}, grupperad efter värdeintervall.",
bucketSorted: "Sorterar innehållet i hink {bucket} för sig.",
countingTally: "Räknar hur många gånger {va} förekommer.",
done: "Klart sorterat! Varje element hittade sin slutgiltiga plats.",
},
ua: {
init: "Щойно перемішаний масив із {n} чисел. Подивіться, як {algo} наводить лад.",
compare: "Порівнюємо позиції {a} і {b}: {va} проти {vb}.",
swapYes: "{va} > {vb}, тож порядок неправильний — міняємо місцями позиції {a} і {b}.",
overwrite: "Записуємо {va} у позицію {a}.",
shift: "Зсуваємо {va} праворуч, щоб звільнити місце.",
selectPivot: "Обираємо {va} (позиція {a}) як опорний елемент.",
partitionRange: "Розбиваємо діапазон [{low}, {high}] навколо опорного елемента.",
pivotPlaced: "Опорний елемент на місці — {va} тепер остаточно відсортовано на позиції {a}.",
mergeRange: "Зливаємо дві вже відсортовані половини: [{low}..{mid}] і [{mid1}..{high}].",
mergeWrite: "Менший кандидат, {va}, іде на позицію {a}.",
markSorted: "Позиція {a} тепер зафіксована як відсортована.",
shellGap: "Зменшуємо проміжок до {gap} — порівняння тепер перестрибують масив, а не повзуть.",
radixDigit: "Тепер сортуємо за розрядом {digit}, спочатку наймолодшим.",
bucketPlace: "{va} потрапляє в кошик {bucket} за діапазоном значення.",
bucketSorted: "Сортуємо вміст кошика {bucket} окремо.",
countingTally: "Рахуємо, скільки разів трапляється {va}.",
done: "Відсортовано! Кожен елемент знайшов своє остаточне місце.",
},
ru: {
init: "Только что перемешанный массив из {n} чисел. Смотрите, как {algo} наводит порядок.",
compare: "Сравниваем позиции {a} и {b}: {va} против {vb}.",
swapYes: "{va} > {vb}, значит порядок неверный — меняем местами позиции {a} и {b}.",
overwrite: "Записываем {va} в позицию {a}.",
shift: "Сдвигаем {va} вправо, чтобы освободить место.",
selectPivot: "Выбираем {va} (позиция {a}) в качестве опорного элемента.",
partitionRange: "Разбиваем диапазон [{low}, {high}] вокруг опорного элемента.",
pivotPlaced: "Опорный элемент на месте — {va} теперь окончательно отсортирован на позиции {a}.",
mergeRange: "Сливаем две уже отсортированные половины: [{low}..{mid}] и [{mid1}..{high}].",
mergeWrite: "Меньший кандидат, {va}, идёт на позицию {a}.",
markSorted: "Позиция {a} теперь зафиксирована как отсортированная.",
shellGap: "Уменьшаем промежуток до {gap} — сравнения теперь перепрыгивают через массив, а не ползут.",
radixDigit: "Теперь сортируем по разряду {digit}, начиная с младшего.",
bucketPlace: "{va} попадает в корзину {bucket} по диапазону значения.",
bucketSorted: "Сортируем содержимое корзины {bucket} отдельно.",
countingTally: "Считаем, сколько раз встречается {va}.",
done: "Отсортировано! Каждый элемент нашёл своё окончательное место.",
},
};
const algoNameKey: Record<AlgorithmKey, UIKey | null> = {
bubble: null, insertion: null, heap: null, quick: null, merge: null,
radix: null, counting: null, bucket: null, shell: null,
};
export function narrate(step: Step, algo: AlgorithmKey, algoTitle: string, lang: LanguageCode): string {
const templates = TEMPLATES[lang] ?? TEMPLATES.en;
const arr = step.array;
const [a, b] = step.indices;
const va = a !== undefined ? arr[a] : undefined;
const vb = b !== undefined ? arr[b] : undefined;
const vars: Vars = {
n: arr.length, algo: algoTitle,
a, b, va: va ?? "", vb: vb ?? "",
low: step.range?.[0] ?? "", high: step.range?.[1] ?? "",
mid: step.meta?.mid ?? "", mid1: (step.meta?.mid ?? 0) + 1,
gap: step.meta?.gap ?? "", digit: step.meta?.digit ?? "",
bucket: step.meta?.bucket ?? "",
};
switch (step.action as ActionType) {
case "init":
return fmt(templates.init, vars);
case "compare":
return fmt(templates.compare, vars);
case "swap":
return fmt(templates.swapYes, vars);
case "overwrite":
return fmt(templates.overwrite, vars);
case "shift":
return fmt(templates.shift, vars);
case "select-pivot":
return fmt(templates.selectPivot, vars);
case "partition-range":
return fmt(templates.partitionRange, vars);
case "pivot-placed":
return fmt(templates.pivotPlaced, vars);
case "merge-range":
return fmt(templates.mergeRange, vars);
case "merge-write":
return fmt(templates.mergeWrite, vars);
case "mark-sorted":
return fmt(templates.markSorted, vars);
case "gap-set":
return algo === "radix" ? fmt(templates.radixDigit, vars) : fmt(templates.shellGap, vars);
case "count":
return fmt(templates.countingTally, vars);
case "bucket-place":
return fmt(templates.bucketPlace, vars);
case "bucket-sorted":
return fmt(templates.bucketSorted, vars);
case "done":
return templates.done;
default:
return "";
}
}
-60
View File
@@ -1,60 +0,0 @@
export type AlgorithmKey =
| "bubble"
| "insertion"
| "heap"
| "quick"
| "merge"
| "radix"
| "counting"
| "bucket"
| "shell";
export type LanguageCode = "en" | "fr" | "it" | "de" | "es" | "ja" | "no" | "sv" | "ua" | "ru";
// The kind of thing happening at a single frame of the algorithm.
// Kept small & generic on purpose: every algorithm's story can be told
// as a sequence of these primitive "moves", which is what lets us
// narrate, color, and scrub through any of the nine algorithms with
// one shared player.
export type ActionType =
| "init"
| "compare"
| "swap"
| "overwrite"
| "select-pivot"
| "partition-range"
| "pivot-placed"
| "merge-range"
| "merge-write"
| "mark-sorted"
| "gap-set"
| "bucket-place"
| "bucket-sorted"
| "count"
| "shift"
| "done";
export interface Step {
array: number[];
action: ActionType;
// indices the action is centered on (e.g. the two being compared/swapped)
indices: number[];
// indices that are permanently in their final sorted position, cumulative
sorted: number[];
// an auxiliary highlighted range, e.g. the [low, high] partition window
range?: [number, number];
// free-form extra data used for narration (pivot value, gap size, bucket index...)
meta?: Record<string, number>;
comparisons: number;
writes: number;
}
export interface AlgorithmDescription {
title: string;
timeComplexity: string;
spaceComplexity: string;
description: string;
// a rough big-O class used to plot the growth-curve visual, shared across languages
}
export type ComplexityClass = "n2" | "nlogn" | "n+k" | "n2-adaptive";
-6
View File
@@ -1,6 +0,0 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
-6
View File
@@ -1,6 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
};
export default nextConfig;
-7109
View File
File diff suppressed because it is too large Load Diff
-41
View File
@@ -1,41 +0,0 @@
{
"name": "valgo",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"postbuild": "touch out/.nojekyll",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.3.19",
"lucide-react": "^0.399.0",
"next": "16.3.0",
"react": "^18",
"react-dom": "^18",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "15.5.18",
"gh-pages": "^6.1.1",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
},
"overrides": {
"nanoid": "3.3.18"
}
}
-8
View File
@@ -1,8 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
-92
View File
@@ -1,92 +0,0 @@
import { fontFamily } from "tailwindcss/defaultTheme";
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ["class"],
content: [
"./components/**/*.{js,ts,jsx,tsx}",
"./app/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans],
mono: ["var(--font-mono)", ...fontFamily.mono],
},
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
state: {
idle: 'hsl(var(--state-idle))',
compare: 'hsl(var(--state-compare))',
swap: 'hsl(var(--state-swap))',
sorted: 'hsl(var(--state-sorted))',
pivot: 'hsl(var(--state-pivot))',
range: 'hsl(var(--state-active-range))',
},
},
borderRadius: {
xl: `calc(var(--radius) + 4px)`,
lg: `var(--radius)`,
md: `calc(var(--radius) - 2px)`,
sm: `calc(var(--radius) - 4px)`
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" }
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" }
},
"pulse-glow": {
"0%, 100%": { opacity: "1" },
"50%": { opacity: "0.6" },
},
"fade-up": {
from: { opacity: "0", transform: "translateY(6px)" },
to: { opacity: "1", transform: "translateY(0)" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"pulse-glow": "pulse-glow 1.1s ease-in-out infinite",
"fade-up": "fade-up 0.35s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
};
-41
View File
@@ -1,41 +0,0 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": [
"./*"
]
},
"target": "ES2017"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}