├── .browserslistrc ├── .github └── workflows │ ├── ci.yml │ └── codeql.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── .yarn ├── patches │ └── nuxt-npm-3.5.1-37f7512905.patch └── releases │ └── yarn-3.6.0.cjs ├── .yarnrc.yml ├── CC-BY-NC-SA-4.0 ├── LICENSE ├── README.md ├── app.vue ├── assets ├── img │ ├── action.svg │ ├── arrow-left.svg │ ├── arrow-link.svg │ ├── arrow-outer-link.svg │ ├── arrow-up.svg │ ├── circle.svg │ ├── contact-text.svg │ ├── globe.svg │ ├── gsap.svg │ ├── heart.png │ ├── mail-link.svg │ ├── menu-icon.svg │ ├── nextjs.svg │ ├── nuxtjs.svg │ ├── rectangle.svg │ ├── sass.svg │ ├── source.svg │ ├── triangle.svg │ ├── ukraine-flag.png │ └── warning.icon.svg ├── shaders │ ├── colors.js │ ├── fragment-ukraine.glsl │ ├── fragment.glsl │ ├── utils │ │ └── noise.glsl │ └── vertex.glsl └── styles │ ├── fonts.css │ └── global.css ├── components ├── Ukraine-Flag-Stripe.vue ├── V-About-Me.vue ├── V-Contact.vue ├── V-Error-Background.vue ├── V-Footer-Link.vue ├── V-Footer.vue ├── V-H2.vue ├── V-Header-Background.vue ├── V-Header.vue ├── V-Loader.vue ├── V-Menu.vue ├── V-Navbar.vue ├── V-Overlay.vue ├── V-Pointer.vue ├── V-Projects-Item.vue ├── V-Projects.vue ├── V-Scroll-Down.vue └── content │ ├── Project-Banner.vue │ ├── Project-Header.vue │ ├── Project-Image.vue │ ├── Project-Main.vue │ ├── Project-Next.vue │ ├── Project-Section.vue │ ├── Project-Title.vue │ ├── Prose-A.vue │ └── Svg-Icon.vue ├── composables ├── use-current-section.js ├── use-dark-mode.js ├── use-emitter.js ├── use-gsap.js ├── use-icons.js ├── use-images-loaded.js ├── use-menu-toggle.js └── use-reduced-motion.js ├── content ├── about-me.md └── project │ ├── portfolio.md │ └── studrecruit.md ├── error.vue ├── lib ├── constants.js └── greeting.js ├── nuxt.config.js ├── package.json ├── pages ├── index.vue └── project │ └── [slug].vue ├── plugins ├── gsap.js ├── hoverable.js └── smooth-scroll.client.js ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── copyrighted-c552f044f4e41c2b.html ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── favicon.svg ├── fonts │ ├── e-Ukraine-Light.woff2 │ ├── e-Ukraine-Thin.woff2 │ └── e-Ukraine-UltraLight.woff2 ├── humans.txt ├── img │ ├── portfolio-logo.webp │ ├── portfolio.webp │ ├── studrecruit-collage.webp │ ├── studrecruit-colors.webp │ ├── studrecruit-fonts.webp │ ├── studrecruit-img.png │ ├── studrecruit-logo.webp │ ├── studrecruit-phone-frame.webp │ └── studrecruit.webp ├── logo.png ├── maskable_icon.png ├── robots.txt └── site.webmanifest ├── server └── routes │ ├── _headers.js │ └── sitemap.xml.js ├── tsconfig.json └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | >0.2%, not dead, not op_mini all -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: ['v5-nemo'] 6 | pull_request: 7 | branches: ['v5-nemo'] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | timeout-minutes: 10 13 | 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest] 17 | node: [16] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node }} 24 | cache: 'yarn' 25 | 26 | - name: Install dependencies 27 | run: yarn --immutable 28 | 29 | - name: Build 30 | run: yarn generate 31 | 32 | - name: Cache Nuxt 33 | uses: actions/cache@v3 34 | with: 35 | path: '.nuxt' 36 | key: ${{ matrix.os }}-node-v${{ matrix.node }}-${{ github.sha }} 37 | 38 | # test: 39 | # runs-on: ${{ matrix.os }} 40 | # timeout-minutes: 10 41 | 42 | # strategy: 43 | # matrix: 44 | # os: [ubuntu-latest] 45 | # node: [16] 46 | 47 | # steps: 48 | # - uses: actions/checkout@v3 49 | # - uses: actions/setup-node@v3 50 | # with: 51 | # node-version: ${{ matrix.node }} 52 | # cache: 'yarn' 53 | 54 | # - name: Install dependencies 55 | # run: yarn --immutable 56 | 57 | # - uses: cypress-io/github-action@v5 58 | # env: 59 | # NODE_ENV: 'production' 60 | # # re-enable PR comment bot 61 | # COMMIT_INFO_SHA: ${{ github.event.pull_request.head.sha }} 62 | # with: 63 | # install: false 64 | # build: 'yarn generate' 65 | # start: 'yarn start' 66 | # wait-on: 'http://localhost:3000' 67 | # config: 'baseUrl=http://localhost:3000' 68 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: 'CodeQL' 13 | 14 | on: 15 | push: 16 | branches: ['v5-nemo'] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ['v5-nemo'] 20 | schedule: 21 | - cron: '00 15 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ['javascript'] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@v2 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # If the Autobuild fails above, remove it and uncomment the following three lines. 64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 65 | 66 | # - run: | 67 | # echo "Run, Build Application using script" 68 | # ./location_of_script_within_repo/buildscript.sh 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v2 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | .nuxt 4 | .nitro 5 | .cache 6 | .output 7 | .env 8 | dist 9 | 10 | # Logs 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | .pnpm-debug.log* 18 | 19 | # Diagnostic reports (https://nodejs.org/api/report.html) 20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | *.lcov 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # Snowpack dependency directory (https://snowpack.dev/) 55 | web_modules/ 56 | 57 | # TypeScript cache 58 | *.tsbuildinfo 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Optional stylelint cache 67 | .stylelintcache 68 | 69 | # Microbundle cache 70 | .rpt2_cache/ 71 | .rts2_cache_cjs/ 72 | .rts2_cache_es/ 73 | .rts2_cache_umd/ 74 | 75 | # Optional REPL history 76 | .node_repl_history 77 | 78 | # Output of 'npm pack' 79 | *.tgz 80 | 81 | # Yarn Integrity file 82 | .yarn-integrity 83 | 84 | # dotenv environment variable files 85 | .env 86 | .env.development.local 87 | .env.test.local 88 | .env.production.local 89 | .env.local 90 | 91 | # parcel-bundler cache (https://parceljs.org/) 92 | .cache 93 | .parcel-cache 94 | 95 | # Next.js build output 96 | .next 97 | out 98 | 99 | # Nuxt.js build / generate output 100 | .nuxt 101 | dist 102 | 103 | # Gatsby files 104 | .cache/ 105 | # Comment in the public line in if your project uses Gatsby and not Next.js 106 | # https://nextjs.org/blog/next-9-1#public-directory-support 107 | # public 108 | 109 | # vuepress build output 110 | .vuepress/dist 111 | 112 | # vuepress v2.x temp and cache directory 113 | .temp 114 | .cache 115 | 116 | # Docusaurus cache and generated files 117 | .docusaurus 118 | 119 | # Serverless directories 120 | .serverless/ 121 | 122 | # FuseBox cache 123 | .fusebox/ 124 | 125 | # DynamoDB Local files 126 | .dynamodb/ 127 | 128 | # TernJS port file 129 | .tern-port 130 | 131 | # Stores VSCode versions used for testing VSCode extensions 132 | .vscode-test 133 | 134 | # yarn v2 135 | .pnp.* 136 | .yarn/* 137 | !.yarn/patches 138 | !.yarn/plugins 139 | !.yarn/releases 140 | !.yarn/sdks 141 | !.yarn/versions -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | public 2 | .nuxt 3 | .output 4 | dist 5 | .yarn -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "singleQuote": true, 5 | "endOfLine": "crlf" 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.enable": true, 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | } 6 | -------------------------------------------------------------------------------- /.yarn/patches/nuxt-npm-3.5.1-37f7512905.patch: -------------------------------------------------------------------------------- 1 | diff --git a/dist/head/runtime/plugins/unhead.js b/dist/head/runtime/plugins/unhead.js 2 | index cd263df22045e1da8da5a6b83b5a8af3538f25de..4d57491fc97bae12bde97a969c175027617fb547 100644 3 | --- a/dist/head/runtime/plugins/unhead.js 4 | +++ b/dist/head/runtime/plugins/unhead.js 5 | @@ -6,7 +6,7 @@ export default defineNuxtPlugin({ 6 | name: "nuxt:head", 7 | setup(nuxtApp) { 8 | const createHead = process.server ? createServerHead : createClientHead; 9 | - const head = createHead(); 10 | + const head = createHead({ experimentalHashHydration: true }); 11 | head.push(appHead); 12 | nuxtApp.vueApp.use(head); 13 | if (process.client) { 14 | diff --git a/dist/index.mjs b/dist/index.mjs 15 | index 4e0ebc606fab6a670b32454d4e9c28e6e9045826..a8b9b080396913d2602df7dae5943c774f56a0e9 100644 16 | --- a/dist/index.mjs 17 | +++ b/dist/index.mjs 18 | @@ -2374,7 +2374,7 @@ async function initNitro(nuxt) { 19 | } 20 | }); 21 | nitroConfig.srcDir = resolve(nuxt.options.rootDir, nuxt.options.srcDir, nitroConfig.srcDir); 22 | - const head = createHeadCore(); 23 | + const head = createHeadCore({ experimentalHashHydration: true }); 24 | head.push(nuxt.options.app.head); 25 | const headChunk = await renderSSRHead(head); 26 | nitroConfig.virtual["#head-static"] = `export default ${JSON.stringify(headChunk)}`; 27 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.6.0.cjs 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct-single.svg)](https://stand-with-ukraine.pp.ua/) 2 | 3 | # Portfolio 4 | 5 | Portfolio logo by Bogdan Kostyuk 7 | 8 | This is my portfolio, where you can find little summery of my developer road. Also there are some of 9 | projects that i am using quite often 10 | 11 | > BTW: [previous portfolio](https://next.portfolio-5iw.pages.dev/) 12 | 13 | ## License 14 | 15 | - Code is under [CC0 1.0](./LICENSE) 16 | - Images and projects contents are under [CC BY-NC-SA 4.0.](./CC-BY-NC-SA-4.0) 17 | 18 | ## Build with 19 | 20 | - [Nuxt.js](https://v3.nuxtjs.org) - 3-nd version, this is like a core of the website. 21 | 22 | - [GSAP](https://greensock.com/gsap/) - for smoooooth transitions 23 | 24 | - [OGL](https://github.com/oframe/ogl) - background on landing page (Earlier was using [Three.js](https://threejs.org/) - also really good tool, but decided to learn something new. Also you should definitely check out [metaballs.js](https://www.npmjs.com/package/metaballs-js) and [particles.js](https://vincentgarreau.com/particles.js/)) 25 | 26 | - [LocomotiveScroll](https://github.com/locomotivemtl/locomotive-scroll) - this makes the page scroll really smoothly on PC 27 | 28 | > as backend 29 | > 30 | > Nothing used as backend just added email forwarding to my [domain provider](https://porkbun.com/) 31 | 32 | --- 33 | 34 | ## Usage 35 | 36 | 1. Clone or fork this repo 37 | 38 | ```shell 39 | $ git clone https://github.com/logotip4ik/portfolio 40 | ``` 41 | 42 | 2. Install all the dependencies 43 | 44 | ```shell 45 | $ yarn 46 | ``` 47 | 48 | or 49 | 50 | ``` 51 | $ npm install 52 | ``` 53 | 54 | 3. Run the local server! 55 | ```shell 56 | $ yarn dev 57 | ``` 58 | or 59 | ```shell 60 | $ npm run dev 61 | ``` 62 | 63 | --- 64 | 65 | ### Star if you liked it 😜 66 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 89 | -------------------------------------------------------------------------------- /assets/img/action.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/img/arrow-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/img/arrow-outer-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/img/arrow-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/img/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/gsap.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/assets/img/heart.png -------------------------------------------------------------------------------- /assets/img/mail-link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/menu-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/nextjs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/nuxtjs.svg: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/img/rectangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/sass.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/source.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/ukraine-flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/assets/img/ukraine-flag.png -------------------------------------------------------------------------------- /assets/img/warning.icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/shaders/colors.js: -------------------------------------------------------------------------------- 1 | // NAMINGS: 2 | // First color - background 3 | // Second color - primary 4 | // Third color - secondary 5 | 6 | // VALUES: 7 | // r g b 8 | // [235, 235, 235] 9 | 10 | export const WhitePinkGreen = { 11 | color1: { 12 | dark: [3, 3, 3], 13 | light: [247, 247, 247], 14 | }, 15 | color2: { 16 | dark: [255, 230, 237], 17 | light: [255, 181, 202], 18 | }, 19 | color3: { 20 | dark: [125, 179, 132], 21 | light: [106, 168, 114], 22 | }, 23 | }; 24 | 25 | export const WhiteGreenPink = { 26 | color1: { 27 | dark: [0, 0, 0], 28 | light: [235, 235, 235], 29 | }, 30 | color2: { 31 | dark: [125, 179, 132], 32 | light: [106, 168, 114], 33 | }, 34 | color3: { 35 | dark: [255, 230, 237], 36 | light: [255, 181, 202], 37 | }, 38 | }; 39 | 40 | // Flag of Ukraine 41 | export const BlueYellowWhite = { 42 | color1: { 43 | dark: [0, 87, 183], 44 | light: [0, 87, 183], 45 | }, 46 | color2: { 47 | dark: [255, 215, 0], 48 | light: [255, 215, 0], 49 | }, 50 | color3: { 51 | dark: [3, 3, 3], 52 | light: [235, 235, 235], 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /assets/shaders/fragment-ukraine.glsl: -------------------------------------------------------------------------------- 1 | uniform float time; 2 | uniform float randomSeed; 3 | uniform float objectOpacity; 4 | uniform float noisePower; 5 | uniform float pixelRatio; 6 | uniform vec2 resolution; 7 | uniform vec3 color1; 8 | uniform vec3 color2; 9 | uniform vec3 color3; 10 | 11 | varying vec3 vPosition; 12 | 13 | vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);} 14 | vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} 15 | 16 | float snoise(vec3 v){ 17 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 18 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 19 | 20 | // First corner 21 | vec3 i = floor(v + dot(v, C.yyy) ); 22 | vec3 x0 = v - i + dot(i, C.xxx) ; 23 | 24 | // Other corners 25 | vec3 g = step(x0.yzx, x0.xyz); 26 | vec3 l = 1.0 - g; 27 | vec3 i1 = min( g.xyz, l.zxy ); 28 | vec3 i2 = max( g.xyz, l.zxy ); 29 | 30 | // x0 = x0 - 0. + 0.0 * C 31 | vec3 x1 = x0 - i1 + 1.0 * C.xxx; 32 | vec3 x2 = x0 - i2 + 2.0 * C.xxx; 33 | vec3 x3 = x0 - 1. + 3.0 * C.xxx; 34 | 35 | // Permutations 36 | i = mod(i, 289.0 ); 37 | vec4 p = permute( permute( permute( 38 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 39 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 40 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 41 | 42 | // Gradients 43 | // ( N*N points uniformly over a square, mapped onto an octahedron.) 44 | float n_ = 1.0/7.0; // N=7 45 | vec3 ns = n_ * D.wyz - D.xzx; 46 | 47 | vec4 j = p - 49.0 * floor(p * ns.z *ns.z); // mod(p,N*N) 48 | 49 | vec4 x_ = floor(j * ns.z); 50 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 51 | 52 | vec4 x = x_ *ns.x + ns.yyyy; 53 | vec4 y = y_ *ns.x + ns.yyyy; 54 | vec4 h = 1.0 - abs(x) - abs(y); 55 | 56 | vec4 b0 = vec4( x.xy, y.xy ); 57 | vec4 b1 = vec4( x.zw, y.zw ); 58 | 59 | vec4 s0 = floor(b0)*2.0 + 1.0; 60 | vec4 s1 = floor(b1)*2.0 + 1.0; 61 | vec4 sh = -step(h, vec4(0.0)); 62 | 63 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 64 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 65 | 66 | vec3 p0 = vec3(a0.xy,h.x); 67 | vec3 p1 = vec3(a0.zw,h.y); 68 | vec3 p2 = vec3(a1.xy,h.z); 69 | vec3 p3 = vec3(a1.zw,h.w); 70 | 71 | //Normalise gradients 72 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 73 | p0 *= norm.x; 74 | p1 *= norm.y; 75 | p2 *= norm.z; 76 | p3 *= norm.w; 77 | 78 | // Mix final noise value 79 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 80 | m = m * m; 81 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 82 | dot(p2,x2), dot(p3,x3) ) ); 83 | } 84 | 85 | float lines(vec2 uv, float offset) { 86 | return smoothstep( 87 | 0.0, 88 | 0.5 + offset * 0.5, 89 | abs(0.55 * (sin(uv.x * 12.0) + offset * 2.0)) 90 | ); 91 | } 92 | 93 | vec3 normalizeRGBColor(vec3 color) { 94 | return color / 255.0; 95 | } 96 | 97 | mat2 getRotationMatrix(float angle) { 98 | return mat2( 99 | cos(angle), -sin(angle), 100 | sin(angle), cos(angle) 101 | ); 102 | } 103 | 104 | float rand(vec2 p) { 105 | vec2 k1 = vec2( 106 | 23.14069263277926, // e^pi (Gelfond's constant) 107 | 2.665144142690225 // 2^sqrt(2) (Gelfond–Schneider constant) 108 | ); 109 | return fract( 110 | cos(dot(p, k1)) * 12345.6789 111 | ); 112 | } 113 | 114 | void main() { 115 | float shaderZoom = 0.25; 116 | 117 | // if (resolution.x > 700.0) shaderZoom = 0.25; 118 | // else shaderZoom = 0.3; 119 | 120 | vec3 _color1 = normalizeRGBColor(color1); 121 | vec3 _color2 = normalizeRGBColor(color2); 122 | vec3 _color3 = normalizeRGBColor(color3); 123 | 124 | float noise = snoise(vPosition + time * 0.175 + randomSeed * 100.0) * (noisePower * 0.125); 125 | 126 | vec2 baseUv = getRotationMatrix(noise + -1.525) * vPosition.xy * shaderZoom; 127 | 128 | float firstPattern = lines(baseUv, 0.35); 129 | // float secondPattern = lines(baseUv, 0.05, 15.0); 130 | 131 | vec3 resColor = mix(_color1, _color2, firstPattern); 132 | // vec3 resColor = mix(firstColor, _color1, secondPattern); 133 | 134 | float grainStrength = 0.075; 135 | if (pixelRatio > 2.0) grainStrength = 0.135; 136 | 137 | vec2 uvNoise = vPosition.xy; 138 | uvNoise.y *= rand(vec2(uvNoise.y, randomSeed)); 139 | vec3 grain = vec3(rand(uvNoise) * grainStrength); 140 | 141 | resColor += grain; 142 | 143 | gl_FragColor = vec4(resColor, 1.0) * objectOpacity; 144 | } 145 | -------------------------------------------------------------------------------- /assets/shaders/fragment.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform float time; 4 | uniform float randomSeed; 5 | uniform float objectOpacity; 6 | uniform float noisePower; 7 | uniform float pixelRatio; 8 | uniform vec2 resolution; 9 | uniform vec3 color1; 10 | uniform vec3 color2; 11 | uniform vec3 color3; 12 | 13 | varying vec3 vPosition; 14 | 15 | #include utils/noise; 16 | 17 | float lines(vec2 uv, float offset) { 18 | return smoothstep( 19 | 0.0, 20 | 0.5 + offset * 0.5, 21 | abs(0.55 * (sin(uv.x * 12.0) + offset * 2.0)) 22 | ); 23 | } 24 | 25 | vec3 normalizeRGBColor(vec3 color) { 26 | return color / 255.0; 27 | } 28 | 29 | mat2 getRotationMatrix(float angle) { 30 | return mat2( 31 | cos(angle), -sin(angle), 32 | sin(angle), cos(angle) 33 | ); 34 | } 35 | 36 | void main() { 37 | float shaderZoom = 0.0; 38 | 39 | if (resolution.x > 700.0) shaderZoom = 0.25; 40 | else shaderZoom = 0.4; 41 | 42 | // vec3 color1 = vec3(0.0, 0.0, 0.0); 43 | // vec3 color2 = vec3(255.0, 230.0, 237.0); 44 | // LIGHTGREEN-ish 45 | // vec3 color3 = vec3(230.0, 255.0, 233.0); 46 | // GREEN-ish 47 | // vec3 color3 = vec3(125.0, 179.0, 132.0); 48 | // DARK-GREEN-ish 49 | // vec3 color3 = vec3(89.0, 128.0, 94.0); 50 | // NOT-SO-DARK-GREEN-ish 51 | // vec3 color3 = vec3(51.0, 128.0, 61.0); 52 | // DESATURATED-GREEN-ish 53 | // vec3 color3 = vec3(143.0, 204.0, 151.0); 54 | // BLACK-ish 55 | // vec3 color3 = vec3(64.0, 57.0, 59.0); 56 | // PINK-BLACK-ish 57 | // vec3 color3 = vec3(128.0, 115.0, 118.0); 58 | // LIGHTBLUE+GRAY-ish 59 | // vec3 color3 = vec3(152.0, 172.0, 179.0); 60 | 61 | vec3 _color1 = normalizeRGBColor(color1); 62 | vec3 _color2 = normalizeRGBColor(color2); 63 | vec3 _color3 = normalizeRGBColor(color3); 64 | 65 | float noise = snoise(vPosition + time * 0.175 + randomSeed * 100.0) * (noisePower * 0.55); 66 | 67 | vec2 baseUv = getRotationMatrix(noise + -1.0) * vPosition.xy * shaderZoom; 68 | 69 | float firstPattern = lines(baseUv, 0.5); 70 | float secondPattern = lines(baseUv, 0.05); 71 | 72 | vec3 firstColor = mix(_color3, _color2, firstPattern); 73 | vec3 resultingPattern = mix(firstColor, _color1, secondPattern); 74 | 75 | float grainStrength = 0.075; 76 | if (pixelRatio > 1.8) grainStrength = 0.125; 77 | 78 | vec2 uvNoise = vPosition.xy; 79 | uvNoise.y *= rand(vec2(uvNoise.y, randomSeed)); 80 | vec3 grain = vec3(rand(uvNoise) * grainStrength); 81 | 82 | resultingPattern += grain; 83 | 84 | vec3 resColor = mix(_color1, resultingPattern, objectOpacity); 85 | 86 | gl_FragColor = vec4(resColor, 1); 87 | } 88 | -------------------------------------------------------------------------------- /assets/shaders/utils/noise.glsl: -------------------------------------------------------------------------------- 1 | vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);} 2 | vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} 3 | 4 | float snoise(vec3 v){ 5 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 6 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 7 | 8 | // First corner 9 | vec3 i = floor(v + dot(v, C.yyy) ); 10 | vec3 x0 = v - i + dot(i, C.xxx) ; 11 | 12 | // Other corners 13 | vec3 g = step(x0.yzx, x0.xyz); 14 | vec3 l = 1.0 - g; 15 | vec3 i1 = min( g.xyz, l.zxy ); 16 | vec3 i2 = max( g.xyz, l.zxy ); 17 | 18 | // x0 = x0 - 0. + 0.0 * C 19 | vec3 x1 = x0 - i1 + 1.0 * C.xxx; 20 | vec3 x2 = x0 - i2 + 2.0 * C.xxx; 21 | vec3 x3 = x0 - 1. + 3.0 * C.xxx; 22 | 23 | // Permutations 24 | i = mod(i, 289.0 ); 25 | vec4 p = permute( permute( permute( 26 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 27 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 28 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 29 | 30 | // Gradients 31 | // ( N*N points uniformly over a square, mapped onto an octahedron.) 32 | float n_ = 1.0/7.0; // N=7 33 | vec3 ns = n_ * D.wyz - D.xzx; 34 | 35 | vec4 j = p - 49.0 * floor(p * ns.z *ns.z); // mod(p,N*N) 36 | 37 | vec4 x_ = floor(j * ns.z); 38 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 39 | 40 | vec4 x = x_ *ns.x + ns.yyyy; 41 | vec4 y = y_ *ns.x + ns.yyyy; 42 | vec4 h = 1.0 - abs(x) - abs(y); 43 | 44 | vec4 b0 = vec4( x.xy, y.xy ); 45 | vec4 b1 = vec4( x.zw, y.zw ); 46 | 47 | vec4 s0 = floor(b0)*2.0 + 1.0; 48 | vec4 s1 = floor(b1)*2.0 + 1.0; 49 | vec4 sh = -step(h, vec4(0.0)); 50 | 51 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 52 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 53 | 54 | vec3 p0 = vec3(a0.xy,h.x); 55 | vec3 p1 = vec3(a0.zw,h.y); 56 | vec3 p2 = vec3(a1.xy,h.z); 57 | vec3 p3 = vec3(a1.zw,h.w); 58 | 59 | //Normalise gradients 60 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 61 | p0 *= norm.x; 62 | p1 *= norm.y; 63 | p2 *= norm.z; 64 | p3 *= norm.w; 65 | 66 | // Mix final noise value 67 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 68 | m = m * m; 69 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 70 | dot(p2,x2), dot(p3,x3) ) ); 71 | } 72 | 73 | float rand(vec2 p) { 74 | vec2 k1 = vec2( 75 | 23.14069263277926, // e^pi (Gelfond's constant) 76 | 2.665144142690225 // 2^sqrt(2) (Gelfond–Schneider constant) 77 | ); 78 | return fract( 79 | cos(dot(p, k1)) * 12345.6789 80 | ); 81 | } -------------------------------------------------------------------------------- /assets/shaders/vertex.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | attribute vec2 uv; 3 | 4 | uniform mat4 projectionMatrix; 5 | uniform mat4 modelViewMatrix; 6 | 7 | varying vec2 vUv; 8 | varying vec3 vPosition; 9 | 10 | void main() { 11 | vUv = uv; 12 | vPosition = position; 13 | 14 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 15 | } 16 | -------------------------------------------------------------------------------- /assets/styles/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'e-Ukraine'; 3 | font-style: normal; 4 | font-weight: 200; 5 | font-display: swap; 6 | src: url('/fonts/e-Ukraine-UltraLight.woff2') format('woff2'); 7 | } 8 | 9 | @font-face { 10 | font-family: 'e-Ukraine'; 11 | font-style: normal; 12 | font-weight: 100; 13 | font-display: swap; 14 | src: url('/fonts/e-Ukraine-Thin.woff2') format('woff2'); 15 | } 16 | -------------------------------------------------------------------------------- /assets/styles/global.css: -------------------------------------------------------------------------------- 1 | /* @link https://utopia.fyi/type/calculator?c=320,16,1.2,1140,28,1.25,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,3xs-3xl */ 2 | 3 | :root { 4 | --step--2: clamp(0.69rem, calc(0.53rem + 0.83vw), 1.12rem); 5 | --step--1: clamp(0.83rem, calc(0.61rem + 1.11vw), 1.4rem); 6 | --step-0: clamp(1rem, calc(0.71rem + 1.46vw), 1.75rem); 7 | --step-1: clamp(1.2rem, calc(0.81rem + 1.93vw), 2.19rem); 8 | --step-2: clamp(1.44rem, calc(0.93rem + 2.53vw), 2.73rem); 9 | --step-3: clamp(1.73rem, calc(1.07rem + 3.3vw), 3.42rem); 10 | --step-4: clamp(2.07rem, calc(1.22rem + 4.29vw), 4.27rem); 11 | --step-5: clamp(2.49rem, calc(1.37rem + 5.57vw), 5.34rem); 12 | 13 | --surface-color: #030303; 14 | --ff-color: #f7f7f7; 15 | --primary-color: #ffe6ed; 16 | 17 | color-scheme: light dark; 18 | } 19 | 20 | ::selection { 21 | color: var(--surface-color); 22 | background-color: var(--primary-color); 23 | } 24 | 25 | *, 26 | *::before, 27 | *::after { 28 | box-sizing: border-box; 29 | 30 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 31 | } 32 | 33 | ::-webkit-scrollbar { 34 | display: none; 35 | } 36 | 37 | html, 38 | body { 39 | font-family: 'e-Ukraine', sans-serif; 40 | letter-spacing: -0.5px; 41 | -webkit-font-smoothing: antialiased; 42 | -webkit-text-size-adjust: 100%; 43 | text-rendering: optimizeLegibility; 44 | color: var(--ff-color); 45 | 46 | min-height: 100%; 47 | 48 | background-color: var(--surface-color); 49 | 50 | transition: background-color 500ms; 51 | 52 | scrollbar-width: none; 53 | } 54 | 55 | a, 56 | button { 57 | /* NOTE: Tells browser not to wait for potential second tap */ 58 | touch-action: manipulation; 59 | } 60 | 61 | h1 { 62 | font-size: var(--step-5); 63 | } 64 | 65 | h2 { 66 | font-size: var(--step-4); 67 | } 68 | 69 | h3 { 70 | font-size: var(--step-3); 71 | } 72 | 73 | h4 { 74 | font-size: var(--step-2); 75 | } 76 | 77 | h5 { 78 | font-size: var(--step-1); 79 | } 80 | 81 | @media (prefers-color-scheme: light) { 82 | :root { 83 | --surface-color: #f7f7f7; 84 | --ff-color: #030303; 85 | } 86 | 87 | ::selection { 88 | color: var(--ff-color); 89 | background-color: var(--primary-color); 90 | } 91 | } 92 | 93 | .c-scrollbar { 94 | z-index: 9; 95 | } 96 | -------------------------------------------------------------------------------- /components/Ukraine-Flag-Stripe.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 79 | -------------------------------------------------------------------------------- /components/V-About-Me.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 52 | 53 | 111 | -------------------------------------------------------------------------------- /components/V-Contact.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 68 | 69 | 120 | -------------------------------------------------------------------------------- /components/V-Error-Background.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /components/V-Footer-Link.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 74 | 75 | 147 | -------------------------------------------------------------------------------- /components/V-Footer.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 101 | 102 | 314 | -------------------------------------------------------------------------------- /components/V-H2.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 53 | 54 | 74 | -------------------------------------------------------------------------------- /components/V-Header-Background.vue: -------------------------------------------------------------------------------- 1 | 164 | 165 | 168 | -------------------------------------------------------------------------------- /components/V-Header.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 92 | 93 | 250 | -------------------------------------------------------------------------------- /components/V-Loader.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 45 | 46 | 108 | -------------------------------------------------------------------------------- /components/V-Menu.vue: -------------------------------------------------------------------------------- 1 | 130 | 131 | 185 | 186 | 347 | -------------------------------------------------------------------------------- /components/V-Navbar.vue: -------------------------------------------------------------------------------- 1 | 183 | 184 | 230 | 231 | 426 | -------------------------------------------------------------------------------- /components/V-Overlay.vue: -------------------------------------------------------------------------------- 1 | 107 | 108 | 142 | 143 | 273 | -------------------------------------------------------------------------------- /components/V-Pointer.vue: -------------------------------------------------------------------------------- 1 | 143 | 144 | 159 | 160 | 201 | -------------------------------------------------------------------------------- /components/V-Projects-Item.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 108 | 109 | 242 | -------------------------------------------------------------------------------- /components/V-Projects.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 86 | -------------------------------------------------------------------------------- /components/V-Scroll-Down.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 94 | -------------------------------------------------------------------------------- /components/content/Project-Banner.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 33 | 34 | 85 | -------------------------------------------------------------------------------- /components/content/Project-Header.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 45 | 46 | 85 | -------------------------------------------------------------------------------- /components/content/Project-Image.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 77 | 78 | 115 | -------------------------------------------------------------------------------- /components/content/Project-Main.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /components/content/Project-Next.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 37 | 38 | 119 | -------------------------------------------------------------------------------- /components/content/Project-Section.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 28 | 29 | 102 | -------------------------------------------------------------------------------- /components/content/Project-Title.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 54 | 55 | 85 | -------------------------------------------------------------------------------- /components/content/Prose-A.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | 21 | 63 | -------------------------------------------------------------------------------- /components/content/Svg-Icon.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 28 | -------------------------------------------------------------------------------- /composables/use-current-section.js: -------------------------------------------------------------------------------- 1 | const section = shallowRef(0); 2 | 3 | export function useCurrentSection() { 4 | return section; 5 | } 6 | -------------------------------------------------------------------------------- /composables/use-dark-mode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {boolean} [defaultValue=true] what will be returned on server side 3 | * @returns {import('vue').Ref} 4 | * */ 5 | export function useDarkMode(defaultValue = true) { 6 | if (typeof window === 'undefined') return ref(defaultValue); 7 | 8 | const media = window.matchMedia('(prefers-color-scheme: dark)'); 9 | 10 | const isDarkMode = ref(media.matches); 11 | 12 | const unregister = on( 13 | media, 14 | 'change', 15 | (media) => (isDarkMode.value = media.matches), 16 | true, 17 | ); 18 | 19 | onBeforeUnmount(() => { 20 | unregister(); 21 | }); 22 | 23 | return isDarkMode; 24 | } 25 | -------------------------------------------------------------------------------- /composables/use-emitter.js: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt'; 2 | 3 | const emitter = mitt(); 4 | 5 | export function useEmitter() { 6 | return { 7 | ...emitter, 8 | 9 | once: (eventName, cb) => { 10 | const handler = () => { 11 | if (typeof cb === 'function') cb(); 12 | 13 | emitter.off(eventName, handler); 14 | }; 15 | 16 | emitter.on(eventName, handler); 17 | }, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /composables/use-gsap.js: -------------------------------------------------------------------------------- 1 | export function useGsap() { 2 | const nuxtApp = useNuxtApp(); 3 | 4 | return { 5 | gsap: nuxtApp.$gsap, 6 | ScrollTrigger: nuxtApp.$ScrollTrigger, 7 | ScrollToPlugin: nuxtApp.$ScrollToPlugin, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /composables/use-icons.js: -------------------------------------------------------------------------------- 1 | export function useIcons() { 2 | return useState('svg-icons', () => ({})); 3 | } 4 | -------------------------------------------------------------------------------- /composables/use-images-loaded.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import('vue').Ref} refEl 3 | * @param { () => any } callback 4 | */ 5 | export function useImagesLoaded(refEl, callback) { 6 | const stop = watch( 7 | () => unref(refEl), 8 | (el) => { 9 | if (!el) return; 10 | 11 | stop(); 12 | 13 | waitForImages(el).then(callback); 14 | }, 15 | { immediate: true }, 16 | ); 17 | } 18 | 19 | /** 20 | * @param {HTMLElement} wrapper 21 | * @return {Promise} 22 | */ 23 | function waitForImages(wrapper) { 24 | const images = wrapper.querySelectorAll('img'); 25 | 26 | return new Promise((resolve) => { 27 | let numberOfLoadedImages = 0; 28 | 29 | const loadListener = (imageOrLoadEvent) => { 30 | if (images.length == ++numberOfLoadedImages) { 31 | resolve(); 32 | 33 | const image = imageOrLoadEvent.removeEventListener 34 | ? imageOrLoadEvent 35 | : imageOrLoadEvent.target; 36 | 37 | image.removeEventListener('load', loadListener, true); 38 | } 39 | }; 40 | 41 | images.forEach((image) => { 42 | // In my case lazy images could be just ignored, but if needed you can create 43 | // `new Image` with appropriate src and wait for load event on this image 44 | if (image.complete || image.getAttribute('loading') === 'lazy') 45 | loadListener(image); 46 | else image.addEventListener('load', loadListener, true); 47 | }); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /composables/use-menu-toggle.js: -------------------------------------------------------------------------------- 1 | const isMenuShowing = shallowRef(false); 2 | 3 | export function useMenuToggle() { 4 | return isMenuShowing; 5 | } 6 | -------------------------------------------------------------------------------- /composables/use-reduced-motion.js: -------------------------------------------------------------------------------- 1 | /** @returns {import('vue').Ref} */ 2 | export function useReducedMotion() { 3 | if (typeof window === 'undefined') return ref(false); 4 | 5 | const media = window.matchMedia('(prefers-reduced-motion: reduce)'); 6 | 7 | const prefersReducedMotion = ref(media.matches); 8 | 9 | const unregister = on( 10 | media, 11 | 'change', 12 | (media) => (prefersReducedMotion.value = media.matches), 13 | true, 14 | ); 15 | 16 | onBeforeUnmount(() => { 17 | unregister(); 18 | }); 19 | 20 | return prefersReducedMotion; 21 | } 22 | -------------------------------------------------------------------------------- /content/about-me.md: -------------------------------------------------------------------------------- 1 | My name is Bogdan Kostyuk and I am Ukrainian based Front End developer, passionate about UI effects, animations and code. Mostly in use are NuxtJS and Next.js, SCSS for styling and GreenSock for smooth animations, also recently got my hands on custom shaders with ThreeJS and OGL. 2 | -------------------------------------------------------------------------------- /content/project/portfolio.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'This website, as you might already notice, is my portfolio. I am using it to showcase what I learn and what I can do. It was built with the cutting-edge framework Nuxt' 3 | source: 'https://github.com/logotip4ik/portfolio' 4 | live: 'https://bogdankostyuk.xyz' 5 | tags: 6 | - Website 7 | - SEO 8 | - Nuxt.js 9 | - three.js 10 | - gsap 11 | - SCSS 12 | image: '/img/portfolio-logo.webp' 13 | previewImage: '/img/portfolio.webp' 14 | createdAt: '2022-03-31T00:00:00+03:00' 15 | --- 16 | 17 | ::project-header 18 | :::project-title 19 | Portfolio Version 3 20 | ::: 21 | 22 | #source 23 | [source](https://github.com/logotip4ik/portfolio) 24 | :: 25 | 26 | ::project-main 27 | :::project-section{type="fwidth"} 28 | ::::project-image{type="fwidth" :src="image" alt="My Portfolio Website Logo" preload} 29 | ::: 30 | 31 | :::project-section{type="text"} 32 | This website, as you might already notice, is my portfolio. I am using it to showcase what I learn and what I can do. It was built with the cutting-edge framework [Nuxt3](https://v3.nuxtjs.org){no-lowercase size-inherit} (pretty cool phrase, isn't it :smile: ?). [Locomotive Scroll](https://locomotivemtl.github.io/locomotive-scroll/){no-lowercase size-inherit} was used for creating smooth scroll on desktop screen, it can also do so on mobile, but I found it wasn't performing well enough. Also, pretty obvious is GreenSock's tool - [GSAP](https://greensock.com/gsap/){no-lowercase size-inherit}. Last but not least important tool is [OGL](https://github.com/oframe/ogl){no-lowercase size-inherit}. This is like ThreeJS, but _a lot_ smaller in size and delivers pretty much every tool you need to build some 3d scene. 33 | 34 | While building this website, I faced a lot of different and exceptional issues. One was breaking everything after moving one line of code up or down :sweat_smile:. Another was, how to reconcile Nuxt3, GSAP, and Locomotive Scroll in one package, so the route transition looks soft and native. But after trials and errors, something proper was created. Feel free to break everything :joy:. 35 | 36 | - [github](https://github.com/logotip4ik) 37 | - [twitter](https://twitter.com/BogdanKostyuk_) 38 | - [telegram](https://t.me/bogdankostyuk) 39 | - [linkedin](https://www.linkedin.com/in/bogdankostyuk) 40 | - [blog](https://blog.bogdankostyuk.xyz) 41 | - [email](mailto:contact@bogdankostyuk.xyz) 42 | 43 |
44 | 45 | Would love to hear from you :wink: 46 | ::: 47 | 48 | :::project-section{type="project-next"} 49 | ::::project-next 50 | :: 51 | -------------------------------------------------------------------------------- /content/project/studrecruit.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'LPNU StudRecruit' 3 | description: This project was build exclusively for Student Recruiting Department. It is a student organization that consolidates the students' union and its government. The recruitment department is one of the 4 | live: 'https://studrecruit.lpnu.ua' 5 | tags: 6 | - 'Website' 7 | - 'SEO' 8 | - 'Next.js' 9 | - 'gsap' 10 | - 'SCSS' 11 | image: '/img/studrecruit-logo.webp' 12 | previewImage: '/img/studrecruit.webp' 13 | createdAt: '2022-04-01T00:00:00+03:00' 14 | --- 15 | 16 | ::project-header 17 | :::project-title 18 | LPNU Student Recruiting 19 | ::: 20 | 21 | #live 22 | [see live](https://studrecruit.lpnu.ua) 23 | :: 24 | 25 | ::project-main 26 | :::project-section{type="fwidth"} 27 | ::::project-image{type="fwidth" :src="image" alt="Student Recruiting Department Logo" preload} 28 | ::: 29 | 30 | :::project-section{type="text"} 31 | This project was build exclusively for Student Recruiting Department. It is a student organization that consolidates the students' union and its government. The recruitment department is one of the functioning departments of the Students' and Ph.D. students' government and primary trade-union organization of Lviv Polytechnic National University that provides employment opportunities for students. 32 | 33 | - [tg](https://t.me/recruiting_nulp) 34 | - [ig](https://www.instagram.com/recruiting.nulp/) 35 | 36 | ::: 37 | 38 | :::project-section 39 | ::::project-image{src="/img/studrecruit-collage.webp" alt="some student recruiting website screens combined into collage"} 40 | ::: 41 | 42 | :::project-section{type="2-col" style="--gap: 2rem;--col-1: 75%"} 43 | ::::project-image{src="/img/studrecruit-fonts.webp" alt="student recruit website typography"} 44 | :::: 45 | 46 | ::::project-image{src="/img/studrecruit-phone-frame.webp" alt="student recruit website in phone frame"} 47 | :::: 48 | ::: 49 | 50 | :::project-section 51 | ::::project-image{src="/img/studrecruit-colors.webp" alt="student recruit website colors"} 52 | ::: 53 | 54 | :::project-section{type="project-next"} 55 | ::::project-next 56 | :: 57 | -------------------------------------------------------------------------------- /error.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 88 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | export const pointerModifiersWhitelist = [ 2 | 'link', 3 | 'outer-link', 4 | 'mail', 5 | 'action', 6 | ]; 7 | 8 | export const MAX_DPR = 1.5; 9 | 10 | export const socialLinks = [ 11 | { label: 'linkedin', href: 'https://www.linkedin.com/in/bogdankostyuk' }, 12 | { label: 'telegram', href: 'https://t.me/bogdankostyuk' }, 13 | { label: 'github', href: 'https://github.com/logotip4ik' }, 14 | { label: 'email', href: 'mailto:contact@bogdankostyuk.xyz' }, 15 | { label: 'blog', href: 'https://blog.bogdankostyuk.xyz' }, 16 | ]; 17 | -------------------------------------------------------------------------------- /lib/greeting.js: -------------------------------------------------------------------------------- 1 | export function logGreeting() { 2 | // eslint-disable-next-line 3 | console.log( 4 | '%cBogdan Kostyuk', 5 | 'background-color: #030303;border-radius: 0.125rem;padding: 5px 10px;font-family: "Arial", sans-serif;font-size: 2rem;color: white', 6 | ); 7 | // eslint-disable-next-line 8 | console.log( 9 | "%cHello😁 curios friend! If you have any questions, let's get in touch at contact@bogdankostyuk.xyz! Or if you are looking for a source📦 here you are: https://github.com/logotip4ik/portfolio", 10 | 'background-color: #030303;border-radius: 0.125rem;padding: 5px 10px;font-size:1rem;color: white;line-height:1.75', 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | import { isDevelopment } from 'std-env'; 2 | import GLSL from 'vite-plugin-glsl'; 3 | import SVGLoader from 'vite-svg-loader'; 4 | 5 | // https://v3.nuxtjs.org/api/configuration/nuxt.config 6 | export default defineNuxtConfig({ 7 | app: { 8 | head: { 9 | htmlAttrs: { lang: 'en', dir: 'ltr' }, 10 | titleTemplate: '%s | Bogdan Kostyuk', 11 | meta: [ 12 | { lang: 'en' }, 13 | { language: 'English' }, 14 | { property: 'name', name: 'name', content: 'Bogdan Kostyuk' }, 15 | { charset: 'utf-8' }, 16 | { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' }, 17 | { 'http-equiv': 'Reply-to', content: 'contact@bogdankostyuk.xyz' }, 18 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 19 | { name: 'format-detection', content: 'telephone=no' }, 20 | { name: 'robots', content: 'all' }, 21 | { name: 'theme-color', content: 'var(--surface-color)' }, 22 | { name: 'apple-mobile-web-app-capable', content: 'yes' }, 23 | { name: 'copyrighted-site-verification', content: 'c552f044f4e41c2b' }, 24 | { 25 | property: 'og:site_name', 26 | name: 'og:site_name', 27 | content: 'Bogdan Kostyuk', 28 | }, 29 | { property: 'og:locale', name: 'og:locale', content: 'en' }, 30 | { property: 'og:type', name: 'og:type', content: 'website' }, 31 | ], 32 | link: [ 33 | { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }, 34 | { 35 | rel: 'icon', 36 | type: 'image/x-icon', 37 | href: '/favicon.ico', 38 | sizes: 'any', 39 | }, 40 | { 41 | rel: 'icon', 42 | type: 'image/png', 43 | sizes: '32x32', 44 | href: '/favicon-32x32.png', 45 | }, 46 | { 47 | rel: 'icon', 48 | type: 'image/png', 49 | sizes: '16x16', 50 | href: '/favicon-16x16.png', 51 | }, 52 | { 53 | rel: 'apple-touch-icon', 54 | sizes: '180x180', 55 | href: '/apple-touch-icon.png', 56 | }, 57 | { rel: 'manifest', href: '/site.webmanifest' }, 58 | ], 59 | }, 60 | }, 61 | 62 | runtimeConfig: { 63 | public: { 64 | base: isDevelopment 65 | ? 'http://localhost:3000' 66 | : 'https://bogdankostyuk.xyz', 67 | }, 68 | }, 69 | 70 | imports: { 71 | imports: [{ name: 'on', from: 'rad-event-listener' }], 72 | }, 73 | 74 | routeRules: { 75 | '/sitemap.xml': { prerender: true }, 76 | '/_headers': { prerender: true }, 77 | }, 78 | 79 | sourcemap: isDevelopment, 80 | 81 | css: [ 82 | 'normalize.css/normalize.css', 83 | 'locomotive-scroll/dist/locomotive-scroll.css', 84 | '~/assets/styles/fonts.css', 85 | '~/assets/styles/global.css', 86 | ], 87 | 88 | build: { 89 | transpile: ['gsap', 'std-env'], 90 | }, 91 | 92 | nitro: { 93 | prerender: { 94 | crawlLinks: true, 95 | concurrency: 4, 96 | }, 97 | }, 98 | 99 | modules: ['@nuxt/content', '@nuxtjs/fontaine'], 100 | 101 | fontMetrics: { 102 | fonts: [ 103 | { 104 | family: 'e-Ukraine', 105 | src: '/fonts/e-Ukraine-Thin.woff2', 106 | fallbacks: ['Arial'], 107 | }, 108 | ], 109 | }, 110 | 111 | vite: { 112 | plugins: [SVGLoader({ svgo: false }), GLSL({ compress: !isDevelopment })], 113 | }, 114 | }); 115 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "npx serve ./.output/public", 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "NODE_ENV=production nuxt generate", 8 | "preview": "nuxt preview", 9 | "prettier:fix": "prettier --write ." 10 | }, 11 | "devDependencies": { 12 | "@nuxt/content": "^2.7.2", 13 | "@nuxtjs/fontaine": "^0.4.1", 14 | "nuxt": "^3.6.5", 15 | "prettier": "^3.0.0", 16 | "sass": "1.64.1", 17 | "typescript": "^5.1.6", 18 | "vite-plugin-glsl": "1.1.2", 19 | "vite-svg-loader": "^4.0.0" 20 | }, 21 | "dependencies": { 22 | "gsap": "^3.12.2", 23 | "locomotive-scroll": "^4.1.4", 24 | "mitt": "^3.0.1", 25 | "normalize.css": "^8.0.1", 26 | "ogl": "^0.0.117", 27 | "rad-event-listener": "^0.2.4", 28 | "sitemap": "^7.1.1", 29 | "split-type": "0.3.3" 30 | }, 31 | "packageManager": "yarn@3.6.0", 32 | "resolutions": { 33 | "nuxt@^3.6.5": "patch:nuxt@npm%3A3.6.5#./.yarn/patches/nuxt-npm-3.5.1-37f7512905.patch", 34 | "vite": "^4.4.0-beta.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 76 | 77 | 92 | -------------------------------------------------------------------------------- /pages/project/[slug].vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 40 | 41 | 51 | -------------------------------------------------------------------------------- /plugins/gsap.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | import { ScrollTrigger } from 'gsap/ScrollTrigger'; 3 | import { ScrollToPlugin } from 'gsap/ScrollToPlugin'; 4 | 5 | export default defineNuxtPlugin({ 6 | parallel: true, 7 | order: -1, // ensures that this plugin runs before smooth-scroll 8 | setup() { 9 | gsap.registerPlugin(ScrollToPlugin, ScrollTrigger); 10 | 11 | return { provide: { gsap, ScrollToPlugin, ScrollTrigger } }; 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /plugins/hoverable.js: -------------------------------------------------------------------------------- 1 | import { pointerModifiersWhitelist } from '~/lib/constants'; 2 | 3 | export default defineNuxtPlugin({ 4 | parallel: true, 5 | setup(nuxtApp) { 6 | const unregisterMap = new WeakMap(); 7 | 8 | nuxtApp.vueApp.directive('hoverable', { 9 | mounted: (...args) => addEventListeners(...args), 10 | updated: (...args) => addEventListeners(...args), 11 | 12 | beforeUnmount(el) { 13 | unregisterMap[el].forEach((func) => func()); 14 | 15 | unregisterMap[el] = []; 16 | }, 17 | }); 18 | 19 | function addEventListeners(el, { modifiers }) { 20 | const modifier = Object.keys(modifiers)[0] || 'link'; 21 | 22 | if (!pointerModifiersWhitelist.includes(modifier)) 23 | throw new Error(`not valid modifier: '${modifier}' at ${el}`); 24 | 25 | el.style.pointer = 'none'; 26 | 27 | const emitter = useEmitter(); 28 | 29 | unregisterMap[el] ||= []; 30 | 31 | unregisterMap[el].push( 32 | on(el, 'pointerenter', () => { 33 | emitter.emit(`pointer:${modifier}:active`); 34 | }), 35 | ); 36 | 37 | unregisterMap[el].push( 38 | on(el, 'pointerleave', () => { 39 | emitter.emit(`pointer:${modifier}:inactive`); 40 | }), 41 | ); 42 | } 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /plugins/smooth-scroll.client.js: -------------------------------------------------------------------------------- 1 | // import ASScroll from "@ashthornton/asscroll"; 2 | import { gsap } from 'gsap'; 3 | import LocomotiveScroll from 'locomotive-scroll'; 4 | 5 | const LOCOMOTIVE_SCROLL_BREAK_POINT = 1024; 6 | const SCROLL_TO_DURATION_IN_SECONDS = 1.5; 7 | 8 | // NOTE: replace with lenis scroll, 9 | // will need to completely rework scroll related animation 10 | // NOTE: reinitializing smooth scroll after each route transition 11 | // could result in better ux (locomotive scroll only?) 12 | export default defineNuxtPlugin({ 13 | parallel: true, 14 | setup(nuxtApp) { 15 | // not server rendered stuff means error, since wea are prerendering everything 16 | const hasError = !nuxtApp.payload.serverRendered; 17 | 18 | const $ScrollTrigger = nuxtApp.$ScrollTrigger; 19 | 20 | const scrollerEl = document.getElementById('__nuxt'); 21 | 22 | const locomotiveScroll = new LocomotiveScroll({ 23 | el: scrollerEl, 24 | smooth: !hasError, 25 | }); 26 | 27 | locomotiveScroll.on('scroll', $ScrollTrigger.update); 28 | 29 | $ScrollTrigger.scrollerProxy(locomotiveScroll.el, { 30 | scrollTop(value) { 31 | return arguments.length 32 | ? locomotiveScroll.scrollTo(value, { disableLerp: true, duration: 0 }) 33 | : locomotiveScroll.scroll.instance.scroll.y; 34 | }, 35 | getBoundingClientRect() { 36 | return { 37 | top: 0, 38 | left: 0, 39 | width: window.innerWidth, 40 | height: window.innerHeight, 41 | }; 42 | }, 43 | pinType: locomotiveScroll.el.style.transform ? 'transform' : 'fixed', 44 | }); 45 | 46 | $ScrollTrigger.addEventListener('refresh', () => locomotiveScroll.update()); 47 | 48 | if (window.innerWidth >= LOCOMOTIVE_SCROLL_BREAK_POINT) 49 | $ScrollTrigger.defaults({ scroller: locomotiveScroll.el }); 50 | 51 | return { 52 | provide: { smoothScroll: makeLocomotiveScrollAdaptor(locomotiveScroll) }, 53 | }; 54 | }, 55 | }); 56 | 57 | function makeLocomotiveScrollAdaptor(locomotiveScroll) { 58 | const scroll = { x: 0, y: 0 }; 59 | 60 | locomotiveScroll.on('scroll', ({ scroll: { x, y } }) => { 61 | scroll.x = x; 62 | scroll.y = y; 63 | }); 64 | 65 | return { 66 | on: (evName, evCallback) => 67 | locomotiveScroll.on(evName, evCallback.bind(null, { scroll })), 68 | scrollY: () => 69 | window.innerWidth >= LOCOMOTIVE_SCROLL_BREAK_POINT 70 | ? scroll.y 71 | : window.scrollY, 72 | update: () => locomotiveScroll.update(), 73 | enable: () => 74 | window.innerWidth >= LOCOMOTIVE_SCROLL_BREAK_POINT 75 | ? locomotiveScroll.start() 76 | : enable(), 77 | disable: () => 78 | window.innerWidth >= LOCOMOTIVE_SCROLL_BREAK_POINT 79 | ? locomotiveScroll.stop() 80 | : disable(), 81 | scrollTo: ( 82 | selectorOrNumber, 83 | durationInSeconds = SCROLL_TO_DURATION_IN_SECONDS, 84 | ) => 85 | // prettier-ignore 86 | window.innerWidth >= LOCOMOTIVE_SCROLL_BREAK_POINT 87 | ? locomotiveScroll.scrollTo(selectorOrNumber, { 88 | duration: durationInSeconds * 1000, 89 | // https://easings.net/#easeOutExpo 90 | easing: [0.645, 0.045, 0.355, 1.0], 91 | disableLerp: durationInSeconds * 1000 <= 100 92 | }) 93 | : gsap.to(window, { 94 | scrollTo: { y: selectorOrNumber, autoKill: true }, 95 | duration: durationInSeconds, 96 | ease: 'power3.inOut', 97 | }), 98 | }; 99 | } 100 | 101 | /** @param {import('@ashthornton/asscroll').default} asscroll */ 102 | // eslint-disable-next-line 103 | function makeASScrollAdaptor(asscroll) { 104 | return { 105 | scrollY: () => 106 | asscroll.isScrollJacking ? asscroll.currentPos : window.scrollY, 107 | update: () => asscroll.update(), 108 | enable: () => 109 | asscroll.isScrollJacking 110 | ? asscroll.enable() 111 | : (document.body.style.overflow = 'auto'), 112 | disable: () => 113 | asscroll.isScrollJacking 114 | ? asscroll.disable() 115 | : (document.body.style.overflow = 'hidden'), 116 | }; 117 | } 118 | 119 | function preventDefault(e) { 120 | e.preventDefault(); 121 | } 122 | 123 | const wheelOpt = { passive: false }; 124 | let wheelEvent = 'wheel'; 125 | 126 | if (typeof window !== 'undefined') { 127 | wheelEvent = 128 | 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel'; 129 | } 130 | 131 | function disable() { 132 | window.addEventListener(wheelEvent, preventDefault, wheelOpt); 133 | window.addEventListener('touchmove', preventDefault, wheelOpt); 134 | } 135 | 136 | function enable() { 137 | window.removeEventListener(wheelEvent, preventDefault, wheelOpt); 138 | window.removeEventListener('touchmove', preventDefault, wheelOpt); 139 | } 140 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/copyrighted-c552f044f4e41c2b.html: -------------------------------------------------------------------------------- 1 | copyrighted-site-verification=c552f044f4e41c2b 2 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/fonts/e-Ukraine-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/fonts/e-Ukraine-Light.woff2 -------------------------------------------------------------------------------- /public/fonts/e-Ukraine-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/fonts/e-Ukraine-Thin.woff2 -------------------------------------------------------------------------------- /public/fonts/e-Ukraine-UltraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/fonts/e-Ukraine-UltraLight.woff2 -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | /* TEAM */ 2 | Developer: Bogdan Kostyuk. 3 | Telegram: https://t.me/bogdankostyuk. 4 | Location: Ukraine, if you wanna know more, write me 😁. 5 | 6 | /* THANKS */ 7 | Patterns: taken from: https://products.ls.graphics/paaatterns 8 | 9 | /* SITE */ 10 | Software: Nuxt.js, Gsap, Three.js, Scss, Locomotive Scroll 11 | Language: English 12 | IDE: Visual Studio Code, Figma -------------------------------------------------------------------------------- /public/img/portfolio-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/img/portfolio-logo.webp -------------------------------------------------------------------------------- /public/img/portfolio.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/img/portfolio.webp -------------------------------------------------------------------------------- /public/img/studrecruit-collage.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/img/studrecruit-collage.webp -------------------------------------------------------------------------------- /public/img/studrecruit-colors.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/img/studrecruit-colors.webp -------------------------------------------------------------------------------- /public/img/studrecruit-fonts.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/img/studrecruit-fonts.webp -------------------------------------------------------------------------------- /public/img/studrecruit-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/img/studrecruit-img.png -------------------------------------------------------------------------------- /public/img/studrecruit-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/img/studrecruit-logo.webp -------------------------------------------------------------------------------- /public/img/studrecruit-phone-frame.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/img/studrecruit-phone-frame.webp -------------------------------------------------------------------------------- /public/img/studrecruit.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/img/studrecruit.webp -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/logo.png -------------------------------------------------------------------------------- /public/maskable_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logotip4ik/portfolio/f9ee705f83150a77ecddefc85859476f5d7d2dc5/public/maskable_icon.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bogdan Kostyuk", 3 | "short_name": "BK", 4 | "description": "Portfolio website created by Ukrainian developer Bogdan Kostyuk", 5 | "icons": [ 6 | { 7 | "src": "/android-chrome-192x192.png", 8 | "sizes": "192x192", 9 | "type": "image/png" 10 | }, 11 | { 12 | "src": "/android-chrome-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "/maskable_icon.png", 18 | "sizes": "512x512", 19 | "type": "image/png", 20 | "purpose": "maskable" 21 | } 22 | ], 23 | "display": "standalone", 24 | "start_url": ".", 25 | "background_color": "#030303" 26 | } 27 | -------------------------------------------------------------------------------- /server/routes/_headers.js: -------------------------------------------------------------------------------- 1 | export default defineEventHandler((event) => { 2 | // NOTE: by default nitro will prerender this as html ¯\_(ツ)_/¯ 3 | setHeader(event, 'Content-Type', 'text/plain'); 4 | 5 | return `/* 6 | \tX-Robots-Tag: all 7 | \tX-Frame-Options: DENY 8 | \tX-Content-Type-Options: nosniff 9 | \tReferrer-Policy: no-referrer 10 | \tPermissions-Policy: document-domain=() 11 | \tCache-Control: private, must-revalidate, max-age=0 12 | 13 | /_nuxt/* 14 | \tCache-Control: public, immutable, max-age=604800, stale-while-revalidate=16070400`; 15 | }); 16 | -------------------------------------------------------------------------------- /server/routes/sitemap.xml.js: -------------------------------------------------------------------------------- 1 | import { SitemapStream, streamToPromise } from 'sitemap'; 2 | import { serverQueryContent } from '#content/server'; 3 | 4 | export default defineEventHandler(async (event) => { 5 | const defaults = { lastmod: new Date().toISOString(), changefreq: 'weekly' }; 6 | 7 | // Fetching all the documents 8 | const docs = await serverQueryContent(event).find(); 9 | 10 | const sitemap = new SitemapStream({ hostname: 'https://bogdankostyuk.xyz' }); 11 | 12 | sitemap.write({ url: '/', ...defaults }); 13 | 14 | for (const doc of docs) 15 | if (doc._path.includes('project')) 16 | sitemap.write({ url: doc._path, ...defaults }); 17 | 18 | sitemap.end(); 19 | 20 | return streamToPromise(sitemap); 21 | }); 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://v3.nuxtjs.org/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | --------------------------------------------------------------------------------