diff --git a/components.d.ts b/components.d.ts index cecd03e..5253f1d 100644 --- a/components.d.ts +++ b/components.d.ts @@ -8,21 +8,32 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + ALU: typeof import('./src/components/flow/shapes/ALU.vue')['default'] + ALUNode: typeof import('./src/components/flow/ALUNode.vue')['default'] + BusNode: typeof import('./src/components/flow/BusNode.vue')['default'] Button: typeof import('primevue/button')['default'] Card: typeof import('primevue/card')['default'] + copy: typeof import('./src/components/flow/LogicNode copy.vue')['default'] Dialog: typeof import('primevue/dialog')['default'] Divider: typeof import('primevue/divider')['default'] + ExtenderNode: typeof import('./src/components/flow/ExtenderNode.vue')['default'] LC3: typeof import('./src/components/LC3.vue')['default'] + LogicNode: typeof import('./src/components/flow/LogicNode.vue')['default'] MdiInformationOutline: typeof import('~icons/mdi/information-outline')['default'] MdiPause: typeof import('~icons/mdi/pause')['default'] MdiPlay: typeof import('~icons/mdi/play')['default'] - MdiSkipNext: typeof import('~icons/mdi/skip-next')['default'] MdiStepBackward: typeof import('~icons/mdi/step-backward')['default'] MdiStepForward: typeof import('~icons/mdi/step-forward')['default'] Menubar: typeof import('primevue/menubar')['default'] - Pseudocode: typeof import('./src/components/Pseudocode.vue')['default'] + Mux: typeof import('./src/components/flow/shapes/Mux.vue')['default'] + MuxNode: typeof import('./src/components/flow/MuxNode.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] SiGithub: typeof import('~icons/simple-icons/github')['default'] + SiteNav: typeof import('./src/components/SiteNav.vue')['default'] Slider: typeof import('primevue/slider')['default'] + TriState: typeof import('./src/components/flow/shapes/TriState.vue')['default'] + TriStateNode: typeof import('./src/components/flow/TriStateNode.vue')['default'] } export interface ComponentCustomProperties { Tooltip: typeof import('primevue/tooltip')['default'] diff --git a/package-lock.json b/package-lock.json index 5699686..5adbd0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,14 @@ "dependencies": { "@primevue/themes": "^4.3.4", "@tailwindcss/vite": "^4.1.5", + "@vue-flow/background": "^1.3.2", + "@vue-flow/core": "^1.43.1", "dedent-js": "^1.0.1", "primevue": "^4.3.4", "tailwindcss": "^4.1.5", "tailwindcss-primeui": "^0.6.1", - "vue": "^3.5.13" + "vue": "^3.5.13", + "vue-router": "^4.5.1" }, "devDependencies": { "@iconify-json/mdi": "^1.2.3", @@ -1238,6 +1241,12 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", @@ -1281,6 +1290,31 @@ "vscode-uri": "^3.0.8" } }, + "node_modules/@vue-flow/background": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@vue-flow/background/-/background-1.3.2.tgz", + "integrity": "sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==", + "license": "MIT", + "peerDependencies": { + "@vue-flow/core": "^1.23.0", + "vue": "^3.3.0" + } + }, + "node_modules/@vue-flow/core": { + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/@vue-flow/core/-/core-1.43.1.tgz", + "integrity": "sha512-GL5sFR5Gy8PtyPHfpClyXML1WzcclnmV57Md6W0PQie15S6Y2Xqi28TKSV1E/uvM2tToorvsULg+yDVWLPHR4g==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "^10.5.0", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, "node_modules/@vue/compiler-core": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", @@ -1342,6 +1376,12 @@ "he": "^1.2.0" } }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, "node_modules/@vue/language-core": { "version": "2.2.10", "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.10.tgz", @@ -1436,6 +1476,94 @@ } } }, + "node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -1564,6 +1692,111 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -2700,6 +2933,21 @@ } } }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/vue-tsc": { "version": "2.2.10", "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.10.tgz", @@ -2723,20 +2971,6 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "dev": true, "license": "MIT" - }, - "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } } } } diff --git a/package.json b/package.json index 52ea1f8..e18c246 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,13 @@ "dependencies": { "@primevue/themes": "^4.3.4", "@tailwindcss/vite": "^4.1.5", + "@vue-flow/background": "^1.3.2", + "@vue-flow/core": "^1.43.1", "dedent-js": "^1.0.1", "primevue": "^4.3.4", "tailwindcss": "^4.1.5", "tailwindcss-primeui": "^0.6.1", - "vue": "^3.5.13" + "vue": "^3.5.13", + "vue-router": "^4.5.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb2a32c..5c11737 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: '@tailwindcss/vite': specifier: ^4.1.5 version: 4.1.5(vite@6.3.5(@types/node@22.15.15)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0)) + '@vue-flow/background': + specifier: ^1.3.2 + version: 1.3.2(@vue-flow/core@1.43.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)) + '@vue-flow/core': + specifier: ^1.43.1 + version: 1.43.1(vue@3.5.13(typescript@5.8.3)) dedent-js: specifier: ^1.0.1 version: 1.0.1 @@ -486,6 +492,9 @@ packages: '@types/node@22.15.15': resolution: {integrity: sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==} + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@vitejs/plugin-vue@5.2.3': resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -502,6 +511,17 @@ packages: '@volar/typescript@2.4.13': resolution: {integrity: sha512-Ukz4xv84swJPupZeoFsQoeJEOm7U9pqsEnaGGgt5ni3SCTa22m8oJP5Nng3Wed7Uw5RBELdLxxORX8YhJPyOgQ==} + '@vue-flow/background@1.3.2': + resolution: {integrity: sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==} + peerDependencies: + '@vue-flow/core': ^1.23.0 + vue: ^3.3.0 + + '@vue-flow/core@1.43.1': + resolution: {integrity: sha512-GL5sFR5Gy8PtyPHfpClyXML1WzcclnmV57Md6W0PQie15S6Y2Xqi28TKSV1E/uvM2tToorvsULg+yDVWLPHR4g==} + peerDependencies: + vue: ^3.3.0 + '@vue/compiler-core@3.5.13': resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} @@ -553,6 +573,15 @@ packages: vue: optional: true + '@vueuse/core@10.11.1': + resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} + + '@vueuse/metadata@10.11.1': + resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} + + '@vueuse/shared@10.11.1': + resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + acorn@8.14.1: resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} @@ -592,6 +621,44 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -949,6 +1016,17 @@ packages: vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + vue-tsc@2.2.10: resolution: {integrity: sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==} hasBin: true @@ -1262,6 +1340,8 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/web-bluetooth@0.0.20': {} + '@vitejs/plugin-vue@5.2.3(vite@6.3.5(@types/node@22.15.15)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0))(vue@3.5.13(typescript@5.8.3))': dependencies: vite: 6.3.5(@types/node@22.15.15)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.0) @@ -1279,6 +1359,21 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 + '@vue-flow/background@1.3.2(@vue-flow/core@1.43.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@vue-flow/core': 1.43.1(vue@3.5.13(typescript@5.8.3)) + vue: 3.5.13(typescript@5.8.3) + + '@vue-flow/core@1.43.1(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.8.3)) + d3-drag: 3.0.0 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + vue: 3.5.13(typescript@5.8.3) + transitivePeerDependencies: + - '@vue/composition-api' + '@vue/compiler-core@3.5.13': dependencies: '@babel/parser': 7.27.2 @@ -1356,6 +1451,25 @@ snapshots: typescript: 5.8.3 vue: 3.5.13(typescript@5.8.3) + '@vueuse/core@10.11.1(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.1 + '@vueuse/shared': 10.11.1(vue@3.5.13(typescript@5.8.3)) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@10.11.1': {} + + '@vueuse/shared@10.11.1(vue@3.5.13(typescript@5.8.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.13(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + acorn@8.14.1: {} alien-signals@1.0.13: {} @@ -1395,6 +1509,42 @@ snapshots: csstype@3.1.3: {} + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + de-indent@1.0.2: {} debug@4.4.0: @@ -1710,6 +1860,10 @@ snapshots: vscode-uri@3.1.0: {} + vue-demi@0.14.10(vue@3.5.13(typescript@5.8.3)): + dependencies: + vue: 3.5.13(typescript@5.8.3) + vue-tsc@2.2.10(typescript@5.8.3): dependencies: '@volar/typescript': 2.4.13 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index efc037a..4e768fe 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ onlyBuiltDependencies: - esbuild + - vue-demi diff --git a/src/App.vue b/src/App.vue index 7d65b05..480232c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,365 +1,21 @@ - - - -
- -
- -
- -
- - - - -
-
+ - -
-
-
- Speed: - -
- -
-
- -
- Step -
- - -
-
- -
- Cycle  - {{ Math.min(wireState.cycle + 1, macroCycleCount ?? Infinity) }} - - -  of  - {{ macroCycleCount ?? '-' }} - - -
- - - -
- - - - -
- - + diff --git a/src/components/LC3.vue b/src/components/LC3.vue index 34f2fd5..36bc23e 100644 --- a/src/components/LC3.vue +++ b/src/components/LC3.vue @@ -1,5 +1,13 @@ \ No newline at end of file diff --git a/src/components/SiteNav.vue b/src/components/SiteNav.vue new file mode 100644 index 0000000..9defbc1 --- /dev/null +++ b/src/components/SiteNav.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/components/flow/ALUNode.vue b/src/components/flow/ALUNode.vue new file mode 100644 index 0000000..af7c19d --- /dev/null +++ b/src/components/flow/ALUNode.vue @@ -0,0 +1,62 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/flow/BusNode.vue b/src/components/flow/BusNode.vue new file mode 100644 index 0000000..0f4255d --- /dev/null +++ b/src/components/flow/BusNode.vue @@ -0,0 +1,89 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/flow/LC3Components.ts b/src/components/flow/LC3Components.ts new file mode 100644 index 0000000..d7b0a6f --- /dev/null +++ b/src/components/flow/LC3Components.ts @@ -0,0 +1,329 @@ +import type { LC3Node } from './types.d'; +import type { Edge } from '@vue-flow/core'; + +export const initialNodes: LC3Node[] = [ + { + id: "marMux", + type: "mux", + position: { x: -100, y: 100 }, + data: { + label: "MARMUX", + selectorLeftUp: true} + }, + { + id: "gateMarMux", + type: "tristate", + position: { x: -25, y: 0 }, + data: { label: "GateMARMUX" } + }, + { + id: "gatePc", + type: "tristate", + position: { x: 250, y: 0 }, + data: { label: "GatePC" } + }, + { + id: "pc", + type: "logic", + position: { x: 200, y: 100 }, + data: { + label: "PC", + width: 120, + height: 40, + componentType: 'register' + } + }, + { + id: "pcMux", + type: "mux", + position: { x: 200, y: 200 }, + data: { + label: "PCMUX", + inputSize: 3, + selectorLeftUp: true} + }, + { + id: "pcAdder", + type: "logic", + position: { x: 400, y: 150 }, + data: { + label: "+1", + width: 50, + height: 50, + orientation: 'up', + componentType: 'extender' + } + }, + { + id: 'regFile', + type: "logic", + position: { x: 600, y: 100 }, + data: { + label: 'Register File', + width: 120, + height: 180, + componentType: "regfile" + } + }, + { + id: 'zext8', + type: "logic", + position: { x: -250, y: 300 }, + data: { + label: 'ZEXT', + width: 80, + height: 35, + orientation: 'up', + componentType: 'extender' + } + }, + { + id: 'sext5', + type: "logic", + position: { x: 200, y: 500 }, + data: { + label: 'SEXT', + width: 80, + height: 35, + componentType: 'extender' + } + }, + { + id: 'sext6', + type: "logic", + position: { x: -150, y: 500 }, + data: { + label: 'SEXT', + width: 80, + height: 35, + componentType: 'extender' + } + }, + { + id: 'sext9', + type: "logic", + position: { x: -150, y: 550 }, + data: { + label: 'SEXT', + width: 80, + height: 35, + componentType: 'extender' + } + }, + { + id: 'sext11', + type: "logic", + position: { x: -150, y: 600 }, + data: { + label: 'SEXT', + width: 80, + height: 35, + componentType: 'extender' + } + }, + { + id: 'ir', + type: "logic", + position: { x: -150, y: 700 }, + data: { + label: 'IR', + width: 120, + height: 40, + componentType: "register" + } + }, + { + id: 'marAdder', + type: "alu", + position: { x: 0, y: 300 }, + data: { label: '+' } + }, + { + id: 'addr1Mux', + type: "mux", + position: { x: 100, y: 400 }, + data: { label: 'ADDR1MUX' } + }, + { + id: 'addr2Mux', + type: "mux", + position: { x: -100, y: 400 }, + data: { + label: 'ADDR2MUX', + inputSize: 4, + selectorLeftUp: true} + }, + { + id: 'fsm', + type: "logic", + position: { x: 300, y: 500 }, + data: { + label: "Finite State Machine", + width: 100, + height: 200, + componentType: "fsm" + } + }, + { + id: 'sr2mux', + type: "mux", + position: { x: 550, y: 500 }, + data: { + label: "SR2MUX", + orientation: 'down', + } + }, + { + id: 'alu', + type: "alu", + position: { x: 600, y: 600 }, + data: { + label: "ALU", + orientation: "down", + selector: true + } + }, + { + id: 'gateAlu', + type: 'tristate', + position: { x: 600, y: 700 }, + data: { + label: "GateALU", + orientation: "down" + } + }, + { + id: 'logic', + type: "logic", + position: { x: 100, y: 700}, + data: { + label: "Logic", + width: 80, + height: 35, + orientation: 'up', + componentType: 'extender' + } + }, + { + id: 'nzp', + type: "logic", + position: { x: 100, y: 650 }, + data: { + label: "NZP", + width: 80, + height: 35, + orientation: "left", + componentType: "register" + } + }, + { + id: 'ioInput', + type: "logic", + position: { x: 350, y: 800 }, + data: { + label: 'Input', + width: 100, + height: 50, + orientation: 'left' + } + }, + { + id: 'ioOutput', + type: "logic", + position: { x: 500, y: 800 }, + data: { + label: 'Output', + width: 100, + height: 50, + orientation: 'left' + } + }, + { + id: 'sr1Mux', + type: "mux", + position: { x: 300, y: 900 }, + data: { + label: "SR1MUX", + orientation: "right", + selectorLeftUp: true} + }, + { + id: 'drmux', + type: "mux", + position: { x: 500, y: 900 }, + data: { + label: "DRMUX", + orientation: "right", + selectorLeftUp: true} + }, + { + id: "memory", + type: "logic", + position: { x: -100, y: 800 }, + data: { + label: 'Memory', + width: 150, + height: 100, + componentType: 'memory' + }, + }, + { + id: "mar", + type: "logic", + position: { x: 100, y: 750 }, + data: { + label: "MAR", + width: 120, + height: 40, + orientation: 'left', + componentType: 'register' + } + }, + { + id: "mdr", + type: "logic", + position: { x: -300, y: 750 }, + data: { + label: "MDR", + width: 120, + height: 40, + componentType: 'mdr' + } + }, + { + id: "mdrMux", + type: "mux", + position: {x: -300, y: 825 }, + data: { label: "MDRMUX" } + }, + { + id: "gateMdr", + type: "tristate", + position: { x: -250, y: 700 }, + data: { label: "GateMDR" } + }, + { + id: "bus", + type: "bus", + position: { x: -150, y: 0 }, + data: { + label: "Bus", + width: 900, + height: 750 + } + } +]; + +export const initialEdges: Edge[] = [ + //TODO: create all edges + // example edge + { + id: 'e1', + source: 'zext8', + target: 'marMux', + sourceHandle: 'output', + targetHandle: 'input-0', + type: 'step', + animated: true + }, +]; \ No newline at end of file diff --git a/src/components/flow/LogicNode.vue b/src/components/flow/LogicNode.vue new file mode 100644 index 0000000..a8b0ba7 --- /dev/null +++ b/src/components/flow/LogicNode.vue @@ -0,0 +1,127 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/flow/MuxNode.vue b/src/components/flow/MuxNode.vue new file mode 100644 index 0000000..8db3525 --- /dev/null +++ b/src/components/flow/MuxNode.vue @@ -0,0 +1,72 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/flow/TriStateNode.vue b/src/components/flow/TriStateNode.vue new file mode 100644 index 0000000..384c48c --- /dev/null +++ b/src/components/flow/TriStateNode.vue @@ -0,0 +1,54 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/flow/shapes/ALU.vue b/src/components/flow/shapes/ALU.vue new file mode 100644 index 0000000..09a0982 --- /dev/null +++ b/src/components/flow/shapes/ALU.vue @@ -0,0 +1,48 @@ + + + + + \ No newline at end of file diff --git a/src/components/flow/shapes/Mux.vue b/src/components/flow/shapes/Mux.vue new file mode 100644 index 0000000..78bd341 --- /dev/null +++ b/src/components/flow/shapes/Mux.vue @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/src/components/flow/shapes/TriState.vue b/src/components/flow/shapes/TriState.vue new file mode 100644 index 0000000..d19e2ef --- /dev/null +++ b/src/components/flow/shapes/TriState.vue @@ -0,0 +1,38 @@ + + + + + \ No newline at end of file diff --git a/src/components/flow/shapes/index.ts b/src/components/flow/shapes/index.ts new file mode 100644 index 0000000..3ca6423 --- /dev/null +++ b/src/components/flow/shapes/index.ts @@ -0,0 +1,81 @@ +import { Position } from "@vue-flow/core"; + +export type VerticalOrientation = "up" | "down"; +export type HorizontalOrientation = "left" | "right"; +export type Orientation = VerticalOrientation | HorizontalOrientation; + +/** + * Given a function which generates points, draw it to fit within the specified bounding box + * and rotate it to match the orientation. + * @param draw The function to generate the points. + * This creates the points on the main-cross axes, where "main" represents the axis parallel to the input/output ports, + * and "cross" represents the axis perpendicular to the input/output ports. + * + * Alternatively, you can think of it as a function which draws the component facing right. + * @param orientation The orientation to draw the component. + * @param width The width (x-axis length) of the bounding box where the component should be drawn. + * @param height The height (y-axis length) of the bounding box where the component should be drawn. + * @returns the new points in the x-y coordinate system. + */ +export function drawOriented( + draw: (mainAxis: number, crossAxis: number) => readonly (readonly [main: number, cross: number])[], + orientation: Orientation, + width: number, + height: number +): [x: number, y: number][] { + const vertical = orientation === "up" || orientation === "down"; + const flip = orientation === "up" || orientation === "left"; + const [mainAxis, crossAxis] = vertical ? [height, width] : [width, height]; + + return draw(mainAxis, crossAxis) + .map(([m, c]) => { + // If we need to flip, then we need to invert the main axis coordinate. + if (flip) m = mainAxis - m; + // If vertical, [main axis, cross axis] is [y, x], + // If horizontal, [main axis, cross axis] is [x, y]. + return vertical ? [c, m] : [m, c]; + }); +} + +// TODO: doc +function rotatePosition(position: Position, n: number) { + switch (((n % 4) + 4) % 4) { + case 0: return position; + case 1: switch (position) { + case Position.Top: return Position.Right; + case Position.Right: return Position.Bottom; + case Position.Bottom: return Position.Left; + case Position.Left: return Position.Top; + } + case 2: switch (position) { + case Position.Top: return Position.Bottom; + case Position.Right: return Position.Left; + case Position.Bottom: return Position.Top; + case Position.Left: return Position.Right; + } + case 3: switch (position) { + case Position.Top: return Position.Left; + case Position.Right: return Position.Top; + case Position.Bottom: return Position.Right; + case Position.Left: return Position.Bottom; + } + } + + throw new Error("Bad argument"); +} +function asRotations(orientation: Orientation) { + if (orientation === "right") return 0; + if (orientation === "down") return 1; + if (orientation === "left") return 2; + if (orientation === "up") return 3; + return 0; +} +export function computeHandleOriented( + properties: T, + orientation: Orientation +): T { + return { ...properties, side: rotatePosition(properties.side, asRotations(orientation)) }; +} +export function getCrossProperty(side: Position): "top" | "left" { + return side == Position.Left || side == Position.Right ? "top" : "left"; +} \ No newline at end of file diff --git a/src/components/flow/types.d.ts b/src/components/flow/types.d.ts new file mode 100644 index 0000000..d277c81 --- /dev/null +++ b/src/components/flow/types.d.ts @@ -0,0 +1,20 @@ +import type { Component } from "vue"; +import type { Node, NodeProps } from '@vue-flow/core'; + +import type ALUNode from "./ALUNode.vue"; +import type LogicNode from "./LogicNode.vue"; +import type MuxNode from "./MuxNode.vue"; +import type TriStateNode from "./TriStateNode.vue"; +import type ExtenderNode from "./ExtenderNode.vue"; +import type BusNode from "./BusNode.vue"; + +type NodeTypeOf = C extends Component> + ? Node + : never; + +export type LC3Node = + | NodeTypeOf<"alu", typeof ALUNode> + | NodeTypeOf<"logic", typeof LogicNode> + | NodeTypeOf<"mux", typeof MuxNode> + | NodeTypeOf<"tristate", typeof TriStateNode> + | NodeTypeOf<"bus", typeof BusNode>; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 43be534..c4a7737 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,16 @@ import { createApp } from 'vue'; +import router from './router'; import App from './App.vue'; import PrimeVue from 'primevue/config'; import Aura from '@primevue/themes/aura'; import './style.css'; +import '@vue-flow/core/dist/style.css'; +import '@vue-flow/core/dist/theme-default.css'; import { definePreset } from '@primevue/themes'; import type { PrimeVueConfiguration } from 'primevue'; createApp(App) + .use(router) .use(PrimeVue, { theme: { preset: definePreset(Aura, { diff --git a/src/pages/Home.vue b/src/pages/Home.vue new file mode 100644 index 0000000..fd5d8e6 --- /dev/null +++ b/src/pages/Home.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/pages/NotFound.vue b/src/pages/NotFound.vue new file mode 100644 index 0000000..7001bfe --- /dev/null +++ b/src/pages/NotFound.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/projects/IEEE/App.vue b/src/projects/IEEE/App.vue new file mode 100644 index 0000000..ecbe55f --- /dev/null +++ b/src/projects/IEEE/App.vue @@ -0,0 +1,715 @@ + + + + + diff --git a/src/projects/IEEE/style.css b/src/projects/IEEE/style.css new file mode 100644 index 0000000..0a7c9ac --- /dev/null +++ b/src/projects/IEEE/style.css @@ -0,0 +1,858 @@ +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap'); + +.ieee-root * { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.ieee-root :root, .ieee-root { + --gt-navy: #003057; + --gt-gold: #B3A369; + --gt-light-gold: #E8D5A3; + --gt-dark-navy: #001a33; + --gt-accent: #FFD700; + --surface: rgba(0, 48, 87, 0.95); + --surface-light: rgba(179, 163, 105, 0.1); + --text-primary: #ffffff; + --text-secondary: #E8D5A3; + --border-color: rgba(179, 163, 105, 0.3); + --shadow: rgba(0, 26, 51, 0.4); +} + +.ieee-root body, .ieee-root { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background: linear-gradient(135deg, var(--gt-dark-navy) 0%, var(--gt-navy) 50%, #002244 100%); + background-attachment: fixed; + min-height: 100vh; + padding: 24px; + color: var(--text-primary); + position: relative; +} + +.ieee-root body::before, .ieee-root::before { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient(ellipse at 20% 20%, rgba(179, 163, 105, 0.05) 0%, transparent 50%), + radial-gradient(ellipse at 80% 80%, rgba(255, 215, 0, 0.03) 0%, transparent 50%); + pointer-events: none; + z-index: -1; +} + +.ieee-root .container { + max-width: 1300px; + margin: 0 auto; + background: var(--surface); + backdrop-filter: blur(20px); + border-radius: 24px; + padding: 40px; + box-shadow: + 0 24px 48px var(--shadow), + 0 0 0 1px var(--border-color), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + position: relative; + overflow: hidden; +} + +.ieee-root .container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, var(--gt-gold) 0%, var(--gt-accent) 50%, var(--gt-gold) 100%); + animation: shimmer 3s ease-in-out infinite; +} + +@keyframes shimmer { + 0%, 100% { opacity: 0.8; } + 50% { opacity: 1; } +} + +.ieee-root h1 { + text-align: center; + font-size: clamp(2rem, 5vw, 3.5rem); + font-weight: 700; + margin-bottom: 48px; + letter-spacing: -0.02em; + background: linear-gradient( + 45deg, + var(--gt-gold) 0%, + var(--gt-accent) 25%, + var(--gt-light-gold) 50%, + var(--gt-accent) 75%, + var(--gt-gold) 100% + ); + background-size: 400% 400%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: gradientFlow 4s ease-in-out infinite; +} + +@keyframes gradientFlow { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } +} + +.ieee-root h1 .gt-badge { + display: inline-block; + font-size: 0.4em; + background: var(--gt-gold); + color: var(--gt-navy); + padding: 4px 8px; + border-radius: 12px; + margin-left: 12px; + font-weight: 600; + vertical-align: super; +} + +.ieee-root .input-section { + background: var(--surface-light); + backdrop-filter: blur(10px); + padding: 32px; + border-radius: 18px; + margin-bottom: 40px; + border: 1px solid var(--border-color); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.2), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + position: relative; +} + +.ieee-root .input-section::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent 0%, var(--gt-gold) 50%, transparent 100%); + border-radius: 18px 18px 0 0; +} + +.ieee-root .input-group { + display: flex; + gap: 20px; + align-items: flex-end; + flex-wrap: wrap; +} + +.ieee-root .input-label { + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + min-width: 300px; +} + +.ieee-root label { + font-weight: 600; + color: var(--gt-light-gold); + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 8px; +} + +.ieee-root label::before { + content: '▶'; + color: var(--gt-gold); + font-size: 0.7em; +} + +.ieee-root input[type="number"], .ieee-root input[type="text"] { + flex: 1; + padding: 16px 20px; + border: 2px solid var(--border-color); + border-radius: 12px; + font-size: 16px; + font-family: 'JetBrains Mono', monospace; + background: rgba(0, 48, 87, 0.4); + color: var(--text-primary); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + min-width: 250px; + backdrop-filter: blur(10px); +} + +.ieee-root input:focus { + outline: none; + border-color: var(--gt-gold); + box-shadow: + 0 0 0 4px rgba(179, 163, 105, 0.2), + 0 8px 24px rgba(179, 163, 105, 0.1); + transform: translateY(-1px); +} + +.ieee-root input::placeholder { + color: var(--text-secondary); + opacity: 0.7; +} + +.ieee-root .format-btn { + padding: 8px 16px; + border: 2px solid #7f53ac; + background: #23243a; + color: #7f53ac; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 500; +} + +.ieee-root .format-btn.active { + background: #7f53ac; + color: #fff; +} + +.ieee-root .format-btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(127, 83, 172, 0.3); +} + +.ieee-root .visualization { + margin-top: 30px; +} + +.ieee-root .ieee-representation { + background: linear-gradient(135deg, var(--gt-navy) 0%, var(--gt-dark-navy) 100%); + padding: 36px; + border-radius: 20px; + margin-bottom: 32px; + color: var(--text-primary); + font-family: 'JetBrains Mono', monospace; + border: 1px solid var(--border-color); + box-shadow: + 0 16px 40px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + position: relative; + overflow: hidden; +} + +.ieee-root .ieee-representation::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, transparent 0%, rgba(179, 163, 105, 0.05) 50%, transparent 100%); + pointer-events: none; +} + +.ieee-root .ieee-representation h3 { + color: var(--gt-light-gold); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + position: relative; + z-index: 1; +} + +/* Bit labels row aligned with bit boxes */ +.ieee-root .bit-labels { + display: grid; + grid-auto-flow: column; + grid-auto-columns: 32px; /* match .bit width */ + gap: 3px; + padding: 0 20px 4px 10px; /* mirror bit-display horizontal padding */ + overflow-x: auto; + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + text-align: center; + color: var(--gt-light-gold); + user-select: none; +} +.ieee-root .bit-labels .bit-label { line-height: 1; opacity: 0.85; } +.ieee-root .bit-labels::-webkit-scrollbar { height: 6px; } +.ieee-root .bit-labels::-webkit-scrollbar-thumb { background: rgba(179,163,105,0.4); border-radius: 4px; } +.ieee-root .bit-labels::-webkit-scrollbar-track { background: rgba(255,255,255,0.08); } + +.ieee-root .bit-display { + display: flex; + flex-wrap: nowrap; + gap: 3px; + justify-content: space-between; + margin: 12px 0; + padding: 20px 20px 20px 10px; + background: rgba(0, 0, 0, 0.2); + border-radius: 12px; + border: 1px solid var(--border-color); + overflow-x: auto; + min-width: 0; + /* Remove fixed width so it can scroll and match labels grid */ +} + +.ieee-root .bit { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 14px; + font-family: 'JetBrains Mono', monospace; + border-radius: 8px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border: 2px solid transparent; + position: relative; + cursor: pointer; +} + +/* Responsive shrinking of bit boxes & labels */ +@media (max-width: 1150px) { + .ieee-root .bit, .ieee-root .bit-label { width:28px; height:28px; font-size:12px; } + .ieee-root .bit-label { line-height:28px; } +} +@media (max-width: 980px) { + .ieee-root .bit, .ieee-root .bit-label { width:24px; height:24px; font-size:11px; } + .ieee-root .bit-label { line-height:24px; } +} +@media (max-width: 860px) { + .ieee-root .bit, .ieee-root .bit-label { width:20px; height:20px; font-size:10px; } + .ieee-root .bit-label { line-height:20px; } +} +@media (max-width: 720px) { + .ieee-root .bit, .ieee-root .bit-label { width:18px; height:18px; font-size:9px; } + .ieee-root .bit-label { line-height:18px; } +} + +/* Bracket visualization under bit display */ +.ieee-root .bit-brackets { + display: flex; + width: 100%; + margin: 4px 0 14px 0; + font-family: 'JetBrains Mono', monospace; + font-size: 0.7rem; + color: var(--gt-light-gold); + position: relative; + padding: 0 20px 0 10px; /* match bit-display horizontal padding */ +} +.ieee-root .bit-brackets .bracket-segment { + position: relative; + text-align: center; + line-height: 1.2; + padding-top: 6px; +} +.ieee-root .bit-brackets .bracket-segment::before { + content: ''; + position: absolute; + top: 0; + left: 4%; + right: 4%; + height: 4px; + border: 2px solid var(--gt-gold); + border-bottom: none; + border-radius: 6px 6px 0 0; + opacity: 0.6; +} +.ieee-root .bit-brackets .bracket-segment.sign::before { border-color: #EF4444; } +.ieee-root .bit-brackets .bracket-segment.exponent::before { border-color: var(--gt-light-gold); } +.ieee-root .bit-brackets .bracket-segment.mantissa::before { border-color: #3B82F6; } + +.ieee-root .normalized-breakdown { + background: rgba(0,0,0,0.25); + border: 1px solid var(--border-color); + border-radius: 10px; + padding: 14px 18px; + margin: 8px 0 18px 0; + font-family: 'JetBrains Mono', monospace; + font-size: 0.85rem; + line-height: 1.4; +} +.ieee-root .normalized-breakdown code { color: var(--gt-light-gold); font-weight: 600; } +.ieee-root .normalized-breakdown .piece-label { color: var(--gt-gold); font-weight: 600; } +.ieee-root .normalized-breakdown .arrow { color: var(--gt-light-gold); padding: 0 4px; } + +/* Exponent algebra visualization */ +.ieee-root .exp-algebra { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; background: rgba(0,48,87,0.45); border-left: 3px solid var(--gt-gold); } +.ieee-root .exp-algebra .exp-label { color: var(--gt-gold); font-weight:600; margin-right:6px; } +.ieee-root .exp-algebra .exp-bits { color: var(--gt-light-gold); font-weight:600; } +.ieee-root .exp-algebra .exp-bias { color: #3B82F6; font-weight:600; } +.ieee-root .exp-algebra .exp-actual { color: #EF4444; font-weight:600; } +.ieee-root .exp-algebra .exp-implied { color: #10B981; font-weight:600; } +.ieee-root .exp-algebra .exp-origin { color: #F59E0B; font-weight:600; } +.ieee-root .exp-algebra .exp-binary { color: #8B5CF6; font-weight:600; } +.ieee-root .exp-algebra .exp-context { margin-bottom:6px; padding-bottom:0; } +.ieee-root .mantissa-context { font-family:'JetBrains Mono', monospace; font-size:0.75rem; margin-top:8px; background:rgba(0,48,87,0.35); padding:10px 12px; border-radius:8px; border:1px solid var(--border-color); } +.ieee-root .mantissa-context code { color: var(--gt-light-gold); font-weight:600; } +.ieee-root .bias-info-btn { margin-left:8px; } +.ieee-root .bias-popup { display:none; margin-top:10px; background:rgba(0,48,87,0.5); border:1px solid var(--border-color); padding:12px 14px; border-radius:8px; font-size:0.75rem; line-height:1.3; font-family:'JetBrains Mono', monospace; } +.ieee-root .bias-popup.open { display:block; } +.ieee-root .bias-popup code { color:var(--gt-light-gold); font-weight:600; } + + +.ieee-root .bit:hover { + transform: translateY(-2px) scale(1.05); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); +} + +.ieee-root .sign-bit { + background: linear-gradient(135deg, #DC2626, #B91C1C); + color: white; + border-color: #EF4444; +} + +.ieee-root .exponent-bit { + background: linear-gradient(135deg, var(--gt-gold), #B8860B); + color: var(--gt-navy); + border-color: var(--gt-light-gold); + font-weight: 700; +} + +.ieee-root .mantissa-bit { + background: linear-gradient(135deg, #1E40AF, #1D4ED8); + color: white; + border-color: #3B82F6; +} + +.ieee-root .calculations { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 24px; + margin-top: 32px; +} + +.ieee-root .calc-section { + background: var(--surface-light); + backdrop-filter: blur(10px); + padding: 28px; + border-radius: 16px; + border: 1px solid var(--border-color); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + position: relative; + transition: transform 0.3s ease; +} + +.ieee-root .calc-section:hover { + transform: translateY(-4px); +} + +.ieee-root .calc-section::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 100%; + border-radius: 16px 0 0 16px; +} + +.ieee-root .calc-title { + font-weight: 700; + color: var(--gt-light-gold); + margin-bottom: 20px; + font-size: 1.1rem; + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 8px; +} + +.ieee-root .calc-title::before { + content: '■'; + color: var(--gt-gold); + font-size: 0.8em; +} + +.ieee-root .calc-step { + margin: 12px 0; + padding: 14px 16px; + background: rgba(0, 48, 87, 0.3); + border-radius: 10px; + font-family: 'JetBrains Mono', monospace; + font-size: 14px; + color: var(--text-primary); + border-left: 3px solid var(--gt-gold); + transition: all 0.3s ease; +} + +.ieee-root .calc-step:hover { + background: rgba(0, 48, 87, 0.5); + transform: translateX(4px); +} + +.result { + background: linear-gradient(135deg, var(--gt-navy) 0%, var(--gt-dark-navy) 50%, var(--gt-navy) 100%); + color: var(--text-primary); + padding: 24px 32px; + border-radius: 16px; + text-align: center; + font-size: 1.3rem; + font-weight: 600; + font-family: 'JetBrains Mono', monospace; + margin-top: 32px; + border: 2px solid var(--gt-gold); + box-shadow: + 0 12px 32px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + position: relative; + overflow: hidden; +} + +.result::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(179, 163, 105, 0.1), transparent); + animation: sweep 3s ease-in-out infinite; +} + +@keyframes sweep { + 0% { left: -100%; } + 50% { left: 100%; } + 100% { left: 100%; } +} + +/* Override earlier flex override: enforce exact grid so labels line up with bits without scrolling */ +.ieee-root .bit-labels { /* row of 32 labels */ + --bit-size: 32px; + --bit-gap: 3px; + display: grid !important; + grid-template-columns: repeat(32, var(--bit-size)); + gap: var(--bit-gap); + padding: 0 20px 4px 10px; /* match left/right with bit row */ + margin-bottom: 8px; + font-size: 10px; + color: var(--gt-light-gold); + font-family: 'JetBrains Mono', monospace; + overflow: visible; + justify-content: start; +} +.ieee-root .bit-labels .bit-label { width: var(--bit-size); text-align:center; font-weight:500; line-height:1; } + +.ieee-root .bit-display { + --bit-size: 32px; + --bit-gap: 3px; + display: grid !important; + grid-template-columns: repeat(32, var(--bit-size)); + gap: var(--bit-gap); + padding: 20px 20px 20px 10px; + background: rgba(0,0,0,0.2); + border-radius: 12px; + border: 1px solid var(--border-color); + overflow: visible; /* no horizontal scroll needed */ +} + +.legend { + display: flex; + justify-content: center; + gap: 32px; + margin: 32px 0; + flex-wrap: wrap; + padding: 20px; + background: rgba(0, 0, 0, 0.2); + border-radius: 12px; + border: 1px solid var(--border-color); +} + +.legend-item { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 16px; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + transition: all 0.3s ease; +} + +.legend-item:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateY(-2px); +} + +.legend-color { + width: 24px; + height: 24px; + border-radius: 6px; + border: 2px solid rgba(255, 255, 255, 0.2); +} + +.legend-item span { + font-weight: 500; + color: var(--text-secondary); + font-size: 0.9rem; +} + +/* Help button and popup styles */ +.help-btn { + background: var(--gt-gold); + color: var(--gt-navy); + border: none; + border-radius: 50%; + width: 20px; + height: 20px; + font-size: 12px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + margin-left: 8px; +} + +.help-btn:hover { + background: var(--gt-light-gold); + transform: scale(1.1); +} + +.help-popup { + position: relative; + background: var(--surface); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 0; + margin-top: 16px; + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + overflow: hidden; + animation: slideDown 0.3s ease-out; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.help-header { + background: linear-gradient(135deg, var(--gt-navy), var(--gt-dark-navy)); + padding: 16px 20px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--border-color); +} + +.help-header h4 { + color: var(--gt-light-gold); + margin: 0; + font-size: 1.1rem; + font-weight: 600; +} + +.close-btn { + background: none; + border: none; + color: var(--gt-light-gold); + font-size: 20px; + cursor: pointer; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.3s ease; +} + +.close-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--gt-gold); +} + +.help-content { + padding: 20px; +} + +.help-section { + margin-bottom: 20px; +} + +.help-section:last-of-type { + margin-bottom: 16px; +} + +.help-section h5 { + color: var(--gt-gold); + font-size: 1rem; + font-weight: 600; + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 6px; +} + +.help-section h5::before { + content: '▶'; + font-size: 0.8em; + color: var(--gt-light-gold); +} + +.help-section ul { + list-style: none; + padding: 0; + margin: 0; +} + +.help-section li { + padding: 6px 0; + color: var(--text-primary); + display: flex; + align-items: center; + gap: 8px; +} + +.help-section li::before { + content: '•'; + color: var(--gt-gold); + font-weight: bold; +} + +.help-section code { + background: rgba(0, 48, 87, 0.4); + color: var(--gt-light-gold); + padding: 2px 6px; + border-radius: 4px; + font-family: 'JetBrains Mono', monospace; + font-size: 0.9em; + border: 1px solid var(--border-color); +} + +.help-note { + background: rgba(179, 163, 105, 0.1); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 12px; + margin-top: 16px; + font-size: 0.9rem; + color: var(--text-secondary); +} + +.help-note strong { + color: var(--gt-gold); +} + +/* Mode toggle styles */ +.mode-toggle { + display: flex; + gap: 0; + border-radius: 8px; + overflow: hidden; + border: 2px solid var(--border-color); + background: rgba(0, 48, 87, 0.4); +} + +.mode-btn { + padding: 10px 20px; + border: none; + background: transparent; + color: var(--text-secondary); + font-size: 0.9rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + min-width: 80px; +} + +.mode-btn:hover { + background: rgba(179, 163, 105, 0.1); + color: var(--gt-light-gold); +} + +.mode-btn.active { + background: var(--gt-gold); + color: var(--gt-navy); + font-weight: 600; +} + +.mode-btn.active:hover { + background: var(--gt-light-gold); + color: var(--gt-navy); +} + +@media (max-width: 768px) { + body { + padding: 16px; + } + + .container { + padding: 24px; + margin: 0; + border-radius: 16px; + } + + h1 { + font-size: 2rem; + margin-bottom: 32px; + } + + .input-section { + padding: 24px; + } + + .input-group { + flex-direction: column; + align-items: stretch; + gap: 16px; + } + + .input-label { + min-width: auto; + } + + input[type="number"], input[type="text"] { + min-width: auto; + } + + .bit { + width: 28px; + height: 28px; + font-size: 12px; + } + + .bit-label { + width: 28px; + } + + .calculations { + grid-template-columns: 1fr; + gap: 16px; + } + + .legend { + gap: 16px; + padding: 16px; + } + + .legend-item { + padding: 6px 12px; + } +} + +@media (max-width: 480px) { + .bit-display { + gap: 2px; + padding: 16px; + } + + .bit { + width: 24px; + height: 24px; + font-size: 11px; + } + + .bit-label { + width: 24px; + font-size: 10px; + } +} diff --git a/src/projects/Kmap/App.vue b/src/projects/Kmap/App.vue new file mode 100644 index 0000000..ae69b97 --- /dev/null +++ b/src/projects/Kmap/App.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/src/projects/Kmap/Index.vue b/src/projects/Kmap/Index.vue new file mode 100644 index 0000000..e69de29 diff --git a/src/projects/Kmap/components/KMapCell.vue b/src/projects/Kmap/components/KMapCell.vue new file mode 100644 index 0000000..9295404 --- /dev/null +++ b/src/projects/Kmap/components/KMapCell.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/projects/Kmap/components/KMapGrid.vue b/src/projects/Kmap/components/KMapGrid.vue new file mode 100644 index 0000000..ca1ff01 --- /dev/null +++ b/src/projects/Kmap/components/KMapGrid.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/projects/Kmap/components/TruthTable.vue b/src/projects/Kmap/components/TruthTable.vue new file mode 100644 index 0000000..9c46548 --- /dev/null +++ b/src/projects/Kmap/components/TruthTable.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/projects/Kmap/composables/useKMapLogic.js b/src/projects/Kmap/composables/useKMapLogic.js new file mode 100644 index 0000000..1f48cbc --- /dev/null +++ b/src/projects/Kmap/composables/useKMapLogic.js @@ -0,0 +1,282 @@ +export function useKMapLogic() { + + const findGroups = (ones, variables) => { + if (ones.length === 0) return [] + const oneNums = ones.map(t => parseInt(t, 2)) + // Generate all power-of-two sized groups (prime implicant candidates) + const candidateGroups = findAllPossibleGroups(oneNums, variables) + // Keep only groups that are not contained within a larger candidate with identical expression (prime implicants) + const withExpr = candidateGroups.map(g => ({ cells: g, expr: groupToExpression(g, variables) })) + const primes = withExpr.filter((g, i, arr) => { + return !arr.some(other => other !== g && other.cells.length > g.cells.length && g.cells.every(c => other.cells.includes(c)) && other.expr === g.expr) + }) + + // Build coverage table: which primes cover which minterms + const minterms = [...new Set(oneNums)].sort((a,b)=>a-b) + const coverage = primes.map(p => new Set(p.cells)) + + const chosen = [] + const uncovered = new Set(minterms) + + // 1. Essential prime implicants: minterms covered by only one prime + const markEssentials = () => { + let added = false + minterms.forEach(m => { + if (!uncovered.has(m)) return + const covering = primes.filter((p, idx) => coverage[idx].has(m)) + if (covering.length === 1) { + const pi = covering[0] + if (!chosen.includes(pi)) { + chosen.push(pi) + // remove all cells it covers + pi.cells.forEach(c => uncovered.delete(c)) + added = true + } + } + }) + return added + } + while (markEssentials()) {/* repeat until stable */} + + // 2. If uncovered remain, choose primes that cover most uncovered (heuristic set cover) + while (uncovered.size > 0) { + let best = null + let bestCover = 0 + primes.forEach(p => { + if (chosen.includes(p)) return + const coverCount = p.cells.filter(c => uncovered.has(c)).length + if (coverCount > bestCover || (coverCount === bestCover && p.cells.length > (best?.cells.length||0))) { + best = p + bestCover = coverCount + } + }) + if (!best || bestCover === 0) break + chosen.push(best) + best.cells.forEach(c => uncovered.delete(c)) + } + + // 3. Remove any chosen implicant whose expression is duplicate and whose cells are fully covered by others + for (let i = chosen.length -1; i >=0; i--) { + const pi = chosen[i] + const otherCells = new Set() + chosen.forEach((o,j)=> { if (j!==i) o.cells.forEach(c=>otherCells.add(c)) }) + if (pi.cells.every(c => otherCells.has(c))) { + // ensure its expression not unique to final expression set + const exprCount = chosen.filter(o=>o.expr===pi.expr).length + if (exprCount>1) chosen.splice(i,1) + } + } + + // Sort by descending size then expression for stable UI + chosen.sort((a,b)=> b.cells.length - a.cells.length || a.expr.localeCompare(b.expr)) + + return chosen.map(p => ({ + terms: p.cells.map(num => num.toString(2).padStart(variables, '0')), + size: p.cells.length, + expression: p.expr + })) + } + + const findAllPossibleGroups = (ones, variables) => { + const allGroups = [] + const maxSize = Math.pow(2, variables) + + for (let size = maxSize; size >= 1; size /= 2) { + const groupsOfSize = findAllGroupsOfSize(ones, size, variables) + allGroups.push(...groupsOfSize) + } + + return allGroups + } + + const findAllGroupsOfSize = (ones, targetSize, variables) => { + let patterns = {} + + if (variables === 2) { + patterns = { + 1: ones.map(num => [num]), + 2: [ + [0, 1], [2, 3], // horizontal pairs + [0, 2], [1, 3] // vertical pairs + ], + 4: [[0, 1, 2, 3]] + } + } else if (variables === 3) { + patterns = { + 1: ones.map(num => [num]), + 2: [ + [0, 1], [1, 3], [3, 2], [2, 0], // top row adjacencies + [4, 5], [5, 7], [7, 6], [6, 4], // bottom row adjacencies + [0, 4], [1, 5], [2, 6], [3, 7] // vertical pairs + ], + 4: [ + [0, 1, 3, 2], [4, 5, 7, 6], // horizontal rectangles + [0, 1, 4, 5], [1, 3, 5, 7], [3, 2, 7, 6], [2, 0, 6, 4], // vertical rectangles + [0, 2, 4, 6], [1, 3, 5, 7] // diagonal patterns + ], + 8: [[0, 1, 2, 3, 4, 5, 6, 7]] + } + } else if (variables === 4) { + patterns = { + 1: ones.map(num => [num]), + 2: generate4VarPairs(), + 4: generate4VarQuads(), + 8: generate4VarOctets(), + 16: [[...Array(16).keys()]] + } + } + + const validGroups = [] + for (let pattern of patterns[targetSize] || []) { + if (pattern.every(num => ones.includes(num))) { + validGroups.push(pattern) + } + } + return validGroups + } + + const generate4VarPairs = () => { + const pairs = [] + const gray = [0, 1, 3, 2] + + // Horizontal pairs + for (let row = 0; row < 4; row++) { + for (let col = 0; col < 4; col++) { + const a = (gray[row] << 2) | gray[col] + const b = (gray[row] << 2) | gray[(col + 1) % 4] + pairs.push([a, b]) + } + } + // Vertical pairs + for (let col = 0; col < 4; col++) { + for (let row = 0; row < 4; row++) { + const a = (gray[row] << 2) | gray[col] + const b = (gray[(row + 1) % 4] << 2) | gray[col] + pairs.push([a, b]) + } + } + return pairs + } + + const generate4VarQuads = () => { + const quads = [] + const gray = [0, 1, 3, 2] + + // 2x2 blocks + for (let row = 0; row < 4; row++) { + for (let col = 0; col < 4; col++) { + quads.push([ + (gray[row] << 2) | gray[col], + (gray[row] << 2) | gray[(col + 1) % 4], + (gray[(row + 1) % 4] << 2) | gray[col], + (gray[(row + 1) % 4] << 2) | gray[(col + 1) % 4] + ]) + } + } + + // 1x4 horizontal (entire row) + for (let row = 0; row < 4; row++) { + quads.push([ + (gray[row] << 2) | gray[0], + (gray[row] << 2) | gray[1], + (gray[row] << 2) | gray[2], + (gray[row] << 2) | gray[3] + ]) + } + + // 4x1 vertical (entire column) + for (let col = 0; col < 4; col++) { + quads.push([ + (gray[0] << 2) | gray[col], + (gray[1] << 2) | gray[col], + (gray[2] << 2) | gray[col], + (gray[3] << 2) | gray[col] + ]) + } + + return quads + } + + const generate4VarOctets = () => { + const octets = [] + const gray = [0, 1, 3, 2] + + // 2x4 blocks (two rows) + for (let row = 0; row < 4; row++) { + const octet = [] + for (let dr = 0; dr < 2; dr++) { + for (let col = 0; col < 4; col++) { + octet.push((gray[(row + dr) % 4] << 2) | gray[col]) + } + } + octets.push(octet) + } + + // 4x2 blocks (two columns) + for (let col = 0; col < 4; col++) { + const octet = [] + for (let row = 0; row < 4; row++) { + for (let dc = 0; dc < 2; dc++) { + octet.push((gray[row] << 2) | gray[(col + dc) % 4]) + } + } + octets.push(octet) + } + + return octets + } + + const groupToExpression = (group, variables) => { + if (group.length === 1) { + return termToExpression(group[0].toString(2).padStart(variables, '0'), variables) + } + + const firstTerm = group[0].toString(2).padStart(variables, '0') + const constants = [] + + for (let i = 0; i < variables; i++) { + const bit = firstTerm[i] + if (group.every(num => { + const term = num.toString(2).padStart(variables, '0') + return term[i] === bit + })) { + constants.push({ + variable: ['A', 'B', 'C', 'D'][i], + value: bit + }) + } + } + + if (constants.length === 0) return '1' + + return constants.map(c => c.value === '1' ? c.variable : c.variable + "'").join('') + } + + const termToExpression = (term, variables) => { + const variableNames = ['A', 'B', 'C', 'D'].slice(0, variables) + return term.split('').map((bit, index) => { + return bit === '1' ? variableNames[index] : variableNames[index] + "'" + }).join('') + } + + const generateSimplifiedExpression = (kmap, groups, variables) => { + const ones = Object.keys(kmap).filter(key => kmap[key] === '1') + + if (ones.length === 0) { + return 'F = 0' + } + + if (groups.length === 0) { + const minterms = ones.map(term => termToExpression(term, variables)) + return 'F = ' + minterms.join(' + ') + } else { + const expressions = groups.map(group => group.expression) + return 'F = ' + expressions.join(' + ') + } + } + + return { + findGroups, + generateSimplifiedExpression + } +} diff --git a/src/projects/Kmap/style.css b/src/projects/Kmap/style.css new file mode 100644 index 0000000..4ce5f48 --- /dev/null +++ b/src/projects/Kmap/style.css @@ -0,0 +1,757 @@ +.kmap-root :root, .kmap-root{ + --gt-navy: #003057; + --gt-gold: #B3A369; + --gt-tech-gold: #EAAA00; + --gt-light-gold: #F7F3E9; + --gt-dark-navy: #001B35; + --gt-accent: #8BB8E8; + --shadow: rgba(0, 48, 87, 0.15); + --light-shadow: rgba(0, 48, 87, 0.08); +} + +.kmap-root * { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.kmap-root body, .kmap-root{ + font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, var(--gt-light-gold) 0%, #ffffff 100%); + color: var(--gt-navy); + line-height: 1.6; + min-height: 100vh; +} + +.kmap-root .container { + max-width: 1400px; + margin: 0 auto; + padding: 1rem; +} + +.kmap-root header { + text-align: center; + margin-bottom: 1.5rem; + background: linear-gradient(135deg, var(--gt-navy) 0%, var(--gt-dark-navy) 100%); + color: white; + padding: 1.5rem; + border-radius: 12px; + box-shadow: 0 6px 24px var(--shadow); + position: relative; + overflow: visible; + min-height: 80px; +} + +.kmap-root h1 { + font-size: 2rem; + font-weight: 700; + margin-bottom: 0.25rem; + background: linear-gradient(45deg, white, var(--gt-tech-gold)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.kmap-root .subtitle { + font-size: 0.9rem; + opacity: 0.9; + font-weight: 400; +} + +.kmap-root .controls { + background: white; + padding: 1.5rem; + border-radius: 10px; + margin-bottom: 1.5rem; + box-shadow: 0 3px 15px var(--light-shadow); + border: 1px solid rgba(179, 163, 105, 0.2); +} + +.kmap-root .control-group { + margin-bottom: 1rem; +} + +.kmap-root .control-group:last-child { + margin-bottom: 0; +} + +.kmap-root .control-group label { + display: block; + font-weight: 600; + color: var(--gt-navy); + margin-bottom: 0.5rem; + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.kmap-root .variable-controls, +.kmap-root .action-controls { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; +} + +.kmap-root .variable-controls .p-button, +.kmap-root .action-controls .p-button { + flex: 1; + min-width: 120px; +} + +.kmap-root .p-button { + /* Base PrimeVue Button styling with GT theme */ + background: linear-gradient(135deg, var(--gt-gold) 0%, var(--gt-tech-gold) 100%) !important; + color: var(--gt-navy) !important; + border: 2px solid var(--gt-gold) !important; + padding: 0.6rem 1.2rem !important; + border-radius: 6px !important; + cursor: pointer !important; + font-weight: 600 !important; + font-size: 0.8rem !important; + transition: all 0.2s ease-in-out !important; + text-transform: uppercase !important; + letter-spacing: 0.5px !important; + box-shadow: 0 2px 4px rgba(179, 163, 105, 0.2) !important; + outline: none !important; + position: relative !important; + overflow: hidden !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + vertical-align: bottom !important; + text-decoration: none !important; + user-select: none !important; + min-width: auto !important; + font-family: inherit !important; + line-height: 1 !important; +} + +.kmap-root .p-button:enabled:hover { + background: linear-gradient(135deg, var(--gt-tech-gold) 0%, #d4af37 100%) !important; + border-color: var(--gt-tech-gold) !important; + box-shadow: 0 4px 8px rgba(179, 163, 105, 0.3) !important; + transform: translateY(-1px) !important; +} + +.kmap-root .p-button:focus-visible { + outline: 2px solid var(--gt-tech-gold) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px rgba(234, 170, 0, 0.2) !important; +} + +.kmap-root .p-button:enabled:active { + transform: translateY(0) !important; + box-shadow: 0 2px 4px rgba(179, 163, 105, 0.2) !important; +} + +.kmap-root .p-button.p-button-primary, +.kmap-root .p-button.active { + background: linear-gradient(135deg, var(--gt-navy) 0%, var(--gt-dark-navy) 100%) !important; + color: white !important; + border-color: var(--gt-navy) !important; + box-shadow: 0 3px 6px rgba(0, 48, 87, 0.3) !important; +} + +.kmap-root .p-button.p-button-primary:enabled:hover, +.kmap-root .p-button.active:enabled:hover { + background: linear-gradient(135deg, var(--gt-dark-navy) 0%, #001428 100%) !important; + border-color: var(--gt-dark-navy) !important; + box-shadow: 0 4px 8px rgba(0, 48, 87, 0.4) !important; + transform: translateY(-1px) !important; +} + +.kmap-root .p-button.p-button-primary:focus-visible, +.kmap-root .p-button.active:focus-visible { + outline: 2px solid var(--gt-navy) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px rgba(0, 48, 87, 0.2) !important; +} + +.kmap-root .p-button.p-button-secondary { + background: transparent !important; + color: var(--gt-navy) !important; + border: 2px solid var(--gt-navy) !important; +} + +.kmap-root .p-button.p-button-secondary:enabled:hover { + background: var(--gt-navy) !important; + color: white !important; + border-color: var(--gt-navy) !important; +} + +.kmap-root .p-button .p-button-label { + font-weight: inherit !important; + text-transform: inherit !important; + letter-spacing: inherit !important; +} + +.kmap-root button[data-pc-name="button"], +.kmap-root .p-component.p-button, +.kmap-root [data-pc-name="button"] { + /* Ensure all PrimeVue buttons get proper styling */ + background: linear-gradient(135deg, var(--gt-gold) 0%, var(--gt-tech-gold) 100%) !important; + color: var(--gt-navy) !important; + border: 2px solid var(--gt-gold) !important; + padding: 0.75rem 1.5rem !important; + border-radius: 6px !important; + cursor: pointer !important; + font-weight: 600 !important; + font-size: 0.9rem !important; + transition: all 0.2s ease-in-out !important; + text-transform: uppercase !important; + letter-spacing: 0.5px !important; + box-shadow: 0 2px 4px rgba(179, 163, 105, 0.2) !important; + outline: none !important; + position: relative !important; + overflow: hidden !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + vertical-align: bottom !important; + text-decoration: none !important; + user-select: none !important; + min-width: auto !important; + font-family: inherit !important; + line-height: 1 !important; +} + +.kmap-root button[data-pc-name="button"]:hover, +.kmap-root .p-component.p-button:hover, +.kmap-root [data-pc-name="button"]:hover { + background: linear-gradient(135deg, var(--gt-tech-gold) 0%, #d4af37 100%) !important; + border-color: var(--gt-tech-gold) !important; + box-shadow: 0 4px 8px rgba(179, 163, 105, 0.3) !important; + transform: translateY(-1px) !important; +} + +/* For active/primary state buttons */ +.kmap-root button[data-pc-name="button"].active, +.kmap-root button[data-pc-name="button"][data-p-severity="primary"], +.kmap-root .p-component.p-button.active, +.kmap-root .p-component.p-button[data-p-severity="primary"] { + background: linear-gradient(135deg, var(--gt-navy) 0%, var(--gt-dark-navy) 100%) !important; + color: white !important; + border-color: var(--gt-navy) !important; + box-shadow: 0 3px 6px rgba(0, 48, 87, 0.3) !important; +} + +.kmap-root button[data-pc-name="button"].active:hover, +.kmap-root button[data-pc-name="button"][data-p-severity="primary"]:hover, +.kmap-root .p-component.p-button.active:hover, +.kmap-root .p-component.p-button[data-p-severity="primary"]:hover { + background: linear-gradient(135deg, var(--gt-dark-navy) 0%, #001428 100%) !important; + border-color: var(--gt-dark-navy) !important; + box-shadow: 0 4px 8px rgba(0, 48, 87, 0.4) !important; + transform: translateY(-1px) !important; +} + +/* Legacy button styles (keeping for any remaining HTML buttons) */ +.kmap-root .btn { + background: linear-gradient(135deg, var(--gt-gold) 0%, var(--gt-tech-gold) 100%); + color: var(--gt-navy); + border: 2px solid var(--gt-gold); + padding: 0.75rem 1.5rem; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + font-size: 0.9rem; + transition: all 0.2s ease-in-out; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: 0 2px 4px rgba(179, 163, 105, 0.2); + outline: none; + position: relative; + overflow: hidden; + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: bottom; + text-decoration: none; + user-select: none; + min-width: auto; +} + +.kmap-root .btn:hover { + /* PrimeVue hover effect */ + background: linear-gradient(135deg, var(--gt-tech-gold) 0%, #d4af37 100%); + border-color: var(--gt-tech-gold); + box-shadow: 0 4px 8px rgba(179, 163, 105, 0.3); + transform: translateY(-1px); +} + +.kmap-root .btn:focus { + /* PrimeVue focus effect */ + outline: 2px solid var(--gt-tech-gold); + outline-offset: 2px; + box-shadow: 0 0 0 4px rgba(234, 170, 0, 0.2); +} + +.kmap-root .btn:active { + /* PrimeVue active effect */ + transform: translateY(0); + box-shadow: 0 2px 4px rgba(179, 163, 105, 0.2); +} + +.kmap-root .btn.active { + /* PrimeVue primary variant for active state */ + background: linear-gradient(135deg, var(--gt-navy) 0%, var(--gt-dark-navy) 100%); + color: white; + border-color: var(--gt-navy); + box-shadow: 0 3px 6px rgba(0, 48, 87, 0.3); +} + +.kmap-root .btn.active:hover { + background: linear-gradient(135deg, var(--gt-dark-navy) 0%, #001428 100%); + border-color: var(--gt-dark-navy); + box-shadow: 0 4px 8px rgba(0, 48, 87, 0.4); + transform: translateY(-1px); +} + +.kmap-root .btn.active:focus { + outline: 2px solid var(--gt-navy); + outline-offset: 2px; + box-shadow: 0 0 0 4px rgba(0, 48, 87, 0.2); +} + +.kmap-root .btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.4); + transition: width 0.6s, height 0.6s, top 0.6s, left 0.6s; + transform: translate(-50%, -50%); + z-index: 0; +} + +.kmap-root .btn:active::before { + width: 300px; + height: 300px; + top: 50%; + left: 50%; +} + +.kmap-root .btn > * { + position: relative; + z-index: 1; +} + +/* PrimeVue secondary button variant */ +.kmap-root .btn.btn-secondary { + background: transparent; + color: var(--gt-navy); + border: 2px solid var(--gt-navy); +} + +.kmap-root .btn.btn-secondary:hover { + background: var(--gt-navy); + color: white; + border-color: var(--gt-navy); +} + +/* PrimeVue outlined button variant */ +.kmap-root .btn.btn-outlined { + background: transparent; + color: var(--gt-gold); + border: 2px solid var(--gt-gold); +} + +.kmap-root .btn.btn-outlined:hover { + background: var(--gt-gold); + color: var(--gt-navy); + border-color: var(--gt-gold); +} + +/* PrimeVue button sizes */ +.kmap-root .btn.btn-small { + padding: 0.5rem 1rem; + font-size: 0.8rem; +} + +.kmap-root .btn.btn-large { + padding: 1rem 2rem; + font-size: 1rem; +} + +/* PrimeVue disabled state */ +.kmap-root .btn:disabled, +.kmap-root .btn.btn-disabled { + opacity: 0.6; + cursor: not-allowed; + pointer-events: none; +} + +/* PrimeVue loading state */ +.kmap-root .btn.btn-loading { + pointer-events: none; + position: relative; +} + +.kmap-root .btn.btn-loading::after { + content: ''; + position: absolute; + width: 16px; + height: 16px; + margin: auto; + border: 2px solid transparent; + border-top-color: currentColor; + border-radius: 50%; + animation: btn-loading-spin 1s linear infinite; +} + +@keyframes btn-loading-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.kmap-root .main-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; + margin-bottom: 1rem; +} + +.kmap-root .kmap-section, .kmap-root .analysis-section { + background: white; + border-radius: 10px; + padding: 1.5rem; + box-shadow: 0 3px 15px var(--light-shadow); + border: 1px solid rgba(179, 163, 105, 0.2); +} + +.kmap-root .section-title { + color: var(--gt-navy); + font-size: 1.3rem; + font-weight: 700; + margin-bottom: 1rem; + text-align: center; + position: relative; + padding-bottom: 0.4rem; +} + +.kmap-root .section-title::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 60px; + height: 3px; + background: linear-gradient(90deg, var(--gt-gold) 0%, var(--gt-tech-gold) 100%); + border-radius: 2px; +} + +/* K-map instructions styling */ +.kmap-root .kmap-instructions { + text-align: center; + margin-top: 1rem; + color: #666; + font-size: 0.9rem; + line-height: 1.5; +} + +.kmap-root .kmap-instructions small { + opacity: 0.8; +} + +/* K-map groupings title */ +.kmap-root .groupings-title { + margin-bottom: 1rem; + color: var(--gt-navy); + font-size: 1.1rem; + font-weight: 600; +} + +/* Expression title */ +.kmap-root .expression-title { + margin-bottom: 1rem; + color: var(--gt-navy); + font-weight: 600; +} + +/* K-Map Styles */ +.kmap-root .kmap { + display: grid; + gap: 1px; + background: var(--gt-gold); + border-radius: 8px; + overflow: hidden; + margin: 0 auto; + box-shadow: 0 4px 16px var(--light-shadow); +} + +.kmap-root .kmap-2var { + grid-template-columns: auto 1fr 1fr; + grid-template-rows: auto 1fr 1fr; + max-width: 300px; +} + +.kmap-root .kmap-3var { + grid-template-columns: auto repeat(4, 1fr); + grid-template-rows: auto repeat(2, 1fr); + max-width: 400px; +} + +.kmap-root .kmap-4var { + grid-template-columns: auto repeat(4, 1fr); + grid-template-rows: auto repeat(4, 1fr); + max-width: 450px; +} + +.kmap-root .kmap-header, .kmap-root .kmap-label { + background: var(--gt-navy); + color: white; + padding: 0.75rem; + font-weight: 600; + text-align: center; + font-size: 0.85rem; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid rgba(179, 163, 105, 0.3); +} + +.kmap-root .kmap-cell { + background: white; + padding: 0.75rem; + text-align: center; + font-weight: 700; + font-size: 1rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + min-height: 40px; + position: relative; + border: 2px solid transparent; + z-index: 1; +} + +.kmap-root .kmap-cell:hover { + background: var(--gt-light-gold); + border-color: var(--gt-gold); + transform: scale(1.02); + z-index: 2; + box-shadow: 0 2px 8px var(--light-shadow); +} + +.kmap-root .kmap-cell.value-1 { + background: linear-gradient(135deg, var(--gt-tech-gold) 0%, var(--gt-gold) 100%); + color: var(--gt-navy); + border-color: var(--gt-gold); + box-shadow: 0 2px 8px rgba(179, 163, 105, 0.3); +} + +.kmap-root .kmap-cell.value-0 { + background: #f8fafc; + color: #64748b; + border-color: rgba(179, 163, 105, 0.2); +} + +/* Truth Table */ +.kmap-root .truth-table-container { + overflow-x: auto; + border-radius: 8px; + box-shadow: 0 2px 8px var(--light-shadow); + margin-bottom: 1.5rem; +} + +.kmap-root .truth-table { + width: 100%; + border-collapse: collapse; + background: white; + color: var(--gt-navy); +} + +.kmap-root .truth-table th { + background: var(--gt-navy); + color: white; + padding: 0.75rem; + font-weight: 600; + text-align: center; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.kmap-root .truth-table td { + padding: 0.75rem; + text-align: center; + border-bottom: 1px solid rgba(179, 163, 105, 0.2); + font-family: 'Courier New', monospace; + font-weight: 500; + color: var(--gt-navy); +} + +.kmap-root .truth-table tr:nth-child(even) { + background: var(--gt-light-gold); +} + +.kmap-root .truth-table tr:hover { + background: rgba(179, 163, 105, 0.1); +} + +/* Groups Display */ +.kmap-root .groups-container { + background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(247, 243, 233, 0.95) 100%); + border-radius: 10px; + padding: 1.5rem; + margin-bottom: 1rem; + border: 2px solid var(--gt-gold); + box-shadow: 0 3px 15px var(--light-shadow); + backdrop-filter: blur(10px); +} + +.kmap-root .group-item { + background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%); + padding: 1rem; + border-radius: 6px; + margin-bottom: 0.75rem; + border-left: 3px solid var(--gt-tech-gold); + box-shadow: 0 2px 8px rgba(0, 48, 87, 0.1); + transition: all 0.3s ease; + border: 1px solid rgba(179, 163, 105, 0.2); +} + +.kmap-root .group-item:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 48, 87, 0.15); + border-left-color: var(--gt-navy); +} + +.kmap-root .group-item:last-child { + margin-bottom: 0; +} + +.kmap-root .group-item strong { + color: var(--gt-navy); + font-weight: 600; + font-size: 1rem; +} + +.kmap-root .group-item em { + color: var(--gt-tech-navy); + font-weight: 500; + font-style: normal; +} + + +.kmap-root .group-item.no-groupings { + background: linear-gradient(135deg, rgba(255, 243, 224, 0.8) 0%, rgba(224, 228, 5, 0.8) 100%); + border-left-color: #ff9800; + text-align: center; + font-style: italic; + color: #795548; +} + +.kmap-root .expression-display { + background: linear-gradient(135deg, var(--gt-navy) 0%, var(--gt-dark-navy) 100%); + color: white; + padding: 1rem; + border-radius: 6px; + font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 1rem; + font-weight: 600; + text-align: center; + box-shadow: 0 3px 12px var(--shadow); + border: 2px solid var(--gt-gold); +} + +.kmap-root .fade-in { + animation: fadeInUp 0.5s ease-out forwards; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .kmap-root .container { + padding: 1rem; + } + + .kmap-root .main-content { + grid-template-columns: 1fr; + gap: 1.5rem; + } + + .kmap-root h1 { + font-size: 2rem; + } + + .kmap-root .variable-controls, .kmap-root .action-controls { + justify-content: center; + } + + .kmap-root .kmap { + font-size: 0.9rem; + } + + .kmap-root .kmap-cell { + min-height: 40px; + padding: 0.75rem; + font-size: 1rem; + } +} + +@media (max-width: 480px) { + .kmap-root .kmap-4var { + max-width: 100%; + } + + .kmap-root .btn { + padding: 0.6rem 1.2rem; + font-size: 0.8rem; + } + + .kmap-root .kmap-cell { + font-size: 0.9rem; + } +} + +/* Fallback styling for any button elements */ +.kmap-root button { + background: linear-gradient(135deg, var(--gt-gold) 0%, var(--gt-tech-gold) 100%); + color: var(--gt-navy); + border: 2px solid var(--gt-gold); + padding: 0.75rem 1.5rem; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + font-size: 0.9rem; + transition: all 0.2s ease-in-out; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: 0 2px 4px rgba(179, 163, 105, 0.2); + outline: none; + position: relative; + overflow: hidden; + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: bottom; + text-decoration: none; + user-select: none; + min-width: auto; + font-family: inherit; + line-height: 1; +} + +.kmap-root button:hover { + background: linear-gradient(135deg, var(--gt-tech-gold) 0%, #d4af37 100%); + border-color: var(--gt-tech-gold); + box-shadow: 0 4px 8px rgba(179, 163, 105, 0.3); + transform: translateY(-1px); +} diff --git a/src/projects/LC3/Lc3Tool.vue b/src/projects/LC3/Lc3Tool.vue new file mode 100644 index 0000000..69fbc85 --- /dev/null +++ b/src/projects/LC3/Lc3Tool.vue @@ -0,0 +1,259 @@ + + + diff --git a/src/components/Pseudocode.vue b/src/projects/LC3/Pseudocode.vue similarity index 97% rename from src/components/Pseudocode.vue rename to src/projects/LC3/Pseudocode.vue index 8f418f3..224429c 100644 --- a/src/components/Pseudocode.vue +++ b/src/projects/LC3/Pseudocode.vue @@ -1,6 +1,6 @@