174 Commits

Author SHA1 Message Date
jakob.scheid 2aa6b3794c feat(legal): create empty loader module 2026-05-29 23:39:25 +02:00
jakob.scheid 36bcf32a05 Move legal modules into a feature directory 2026-05-29 23:35:54 +02:00
jakob.scheid eaa8968f64 Fix formatting in the locale files 2026-05-29 21:03:59 +02:00
johannes.vos 0a36bfe4a5 add languages - titel and placeholder 2026-05-29 19:30:35 +02:00
johannes.vos 085dc065bb add placeholder in privacy 2026-05-29 18:58:08 +02:00
johannes.vos a50531593b add router link to privacy 2026-05-29 18:57:40 +02:00
johannes.vos 09da4c5577 create imprint.vue&privacypolicy.vue in views/legal 2026-05-29 18:48:26 +02:00
jakob.scheid 3b8c387c44 Merge pull request 'Move the search bar in the search results view into the navigation bar' (#82) from chore/searchbar into main
Deploy on dev / Deploy on dev (push) Successful in 33s
Reviewed-on: #82
Reviewed-by: Jakob Scheid
2026-05-29 18:30:05 +02:00
jakob.scheid d461da90f2 Make search query model cleaner 2026-05-29 18:28:02 +02:00
jakob.scheid 295bfc19e6 Increase navbar vertical padding 2026-05-29 18:16:28 +02:00
jakob.scheid 7a7f698b44 Set navbar height to prevent layout shifting when the search bar is shown when the search results view is visible 2026-05-29 18:13:39 +02:00
jakob.scheid 2fd010ddfa Remove redundant search bar component 2026-05-29 18:08:21 +02:00
jakob.scheid f72a2bf2b1 Remove unnecessary whitespaces 2026-05-29 16:18:40 +02:00
johannes.vos 926010f128 chore(search-results): clean up unused search props and imports 2026-05-29 15:56:47 +02:00
johannes.vos 98c954e361 feat(navbar): show search input only on search results page 2026-05-29 15:55:38 +02:00
johannes.vos 325551d253 copy/paste searchbar.vue code to searchbar-searchresults.vue (placeholder) 2026-05-29 15:42:09 +02:00
johannes.vos a33bc047fa move Searchbar-SearchResults.vue file to nav/components 2026-05-29 15:41:33 +02:00
johannes.vos 1421789e43 create empty Searchbar-SearchResults.vue file 2026-05-29 15:37:40 +02:00
jakob.scheid 27a22ce569 Merge pull request 'add settings configuration parser' (#78) from feature/settings-config-parser into main
Deploy on dev / Deploy on dev (push) Successful in 37s
Reviewed-on: #78
Reviewed-by: Jakob Schei <1+jakob.scheid@noreply.localhost>
2026-05-29 14:03:15 +02:00
jakob.scheid b28af20d11 Merge branch 'main' into feature/settings-config-parser 2026-05-29 14:02:59 +02:00
jakob.scheid c81de2dce3 Merge pull request 'Bug/Link-color' (#81) from bug/link-color into main
Deploy on dev / Deploy on dev (push) Successful in 32s
Reviewed-on: #81
Reviewed-by: Jakob Scheid
2026-05-29 13:22:44 +02:00
johannes.vos 1b60984b5c add style - make links white/black 2026-05-28 21:48:22 +02:00
johannes.vos 733bc2b16a add class to link 2026-05-28 21:47:46 +02:00
jakob.scheid 11800f6ef4 feat(settings): Remove old JSDoc typedefs 2026-05-26 19:57:36 +02:00
jakob.scheid b47ab0355e feat(settings): Remove parameter 'url' from the loading function of the settings composable 2026-05-26 19:55:45 +02:00
jakob.scheid 34a7cb3f2c feat(settings): Make setting default value optional 2026-05-26 19:51:45 +02:00
johannes.vos da76d14d15 WIP 2026-05-26 14:55:18 +02:00
johannes.vos 547bc241da feat(settings): use type field as discriminant in JSDoc type definitions 2026-05-26 14:55:17 +02:00
johannes.vos 4ef6008976 feat(settings): default allowMultiple to false if not specified 2026-05-26 14:55:11 +02:00
johannes.vos 035aa1aa77 feat(settings): replace fetch with dynamic import for settings.json 2026-05-26 14:53:52 +02:00
johannes.vos 462ae00506 feat(settings): move settings.json to src and remove example content 2026-05-26 14:53:52 +02:00
jakob.scheid 5d2064134b feat(settings): Make setting default value optional 2026-05-26 12:21:54 +02:00
johannes.vos aeb348fcfd feat(settings): add example settings.json to public/ 2026-05-26 11:37:34 +02:00
johannes.vos 37ca0baa6d add emty public/settings.json 2026-05-26 11:36:26 +02:00
johannes.vos a165c13d7c feat(settings): add useSettingsConfig composable 2026-05-26 11:35:35 +02:00
johannes.vos 1378813783 add emtpy useSettingsConfig.js 2026-05-26 11:35:26 +02:00
johannes.vos e376e9b362 feat(settings): add settings config loader/parser 2026-05-26 11:34:56 +02:00
johannes.vos d09514f71f add emtpy settingsParser.js 2026-05-26 11:34:40 +02:00
johannes.vos f6d72dbea3 feat(settings): add settings config validator 2026-05-26 11:34:14 +02:00
johannes.vos 0d7814d655 add emty settingsValidator.js 2026-05-26 11:33:58 +02:00
johannes.vos c387c1486f feat(settings): add JSDoc type definitions for settings config 2026-05-26 11:32:51 +02:00
johannes.vos 878b558603 add emty settingsConfig.js 2026-05-26 11:31:41 +02:00
jakob.scheid 0251d5c577 Merge pull request 'Restructure assets directory' (#76) from refactor/restructure-assets-directory into main
Deploy on dev / Deploy on dev (push) Successful in 32s
Reviewed-on: #76
Reviewed-by: Jakob Gregory
2026-05-23 13:29:04 +02:00
jakob.scheid 280dd9d7c0 Remove .gitkeep in the assets directory 2026-05-23 13:21:38 +02:00
jakob.scheid 8e2fc6bfa5 Move image assets into a subdirectory for images in the assets directory 2026-05-23 13:21:21 +02:00
jakob.scheid 69d752f79b Merge pull request 'Add base settings page' (#74) from feature/base-settings-page into main
Deploy on dev / Deploy on dev (push) Successful in 30s
Reviewed-on: #74
Reviewed-by: Jakob Gregory
2026-05-23 13:16:37 +02:00
jakob.scheid 4d62ca2b75 Merge branch 'main' into feature/base-settings-page 2026-05-23 13:16:26 +02:00
jakob.scheid f5b88df831 Merge pull request 'Remove license placeholders' (#75) from bugfix/remove-placeholders-in-license-files into main
Deploy on dev / Deploy on dev (push) Successful in 35s
Reviewed-on: #75
Reviewed-by: Jakob Gregory
2026-05-23 13:14:02 +02:00
jakob.scheid bd25409d9f Add license header to left sidebar layout 2026-05-23 12:08:05 +02:00
jakob.scheid fe20a618fe Add license header to sidebar 2026-05-23 12:07:45 +02:00
jakob.scheid 17b49cdb6e Add license header to settings view 2026-05-23 12:07:32 +02:00
jakob.scheid 287d7ad713 Add padding to the settings view header 2026-05-23 12:03:03 +02:00
jakob.scheid d3e4d54c57 Add padding to the sidebar 2026-05-23 11:58:53 +02:00
jakob.scheid ead8821b0a Use semantic HTML elements in the left sidebar layout 2026-05-23 11:57:23 +02:00
jakob.scheid d3e8a0125d Use semantic HTML elements in the sidebar 2026-05-23 11:56:55 +02:00
jakob.scheid 34b7cfbf5a Use semantic HTML elements in the settings view 2026-05-23 11:53:47 +02:00
jakob.scheid 1ebf5780d2 Move main content padding frm the app root component to the views 2026-05-23 11:51:49 +02:00
jakob.scheid f354c1867c Add left sidebar layout top border 2026-05-23 11:46:28 +02:00
jakob.scheid 0583de0dfa Add settings view page wrapper 2026-05-23 11:43:53 +02:00
jakob.scheid 2a5e79b55d Add padding to the main content in the left sidebar layout 2026-05-23 11:42:45 +02:00
jakob.scheid 4df51a970d Include sidebar in the left sidebar layout 2026-05-23 11:42:25 +02:00
jakob.scheid 7013c60a20 Remove top margin from footer 2026-05-23 11:40:58 +02:00
jakob.scheid 3623265ae0 Remove main content bottom padding 2026-05-23 11:40:32 +02:00
jakob.scheid fbefb2e7fd Rename base layout to left sidebar layout 2026-05-23 11:40:03 +02:00
jakob.scheid b0d3258369 Remove main content wrapper 2026-05-23 11:26:06 +02:00
jakob.scheid f65c3d58aa Add containers to components without a content container 2026-05-23 11:25:48 +02:00
jakob.scheid 33e9ca7cf1 Add check whether there is a sidebar component in the base layout 2026-05-23 10:45:28 +02:00
jakob.scheid def3a84e59 Use base layout in the app root component 2026-05-23 10:44:58 +02:00
jakob.scheid b48a487d2f Rename left sidebar layout to base layout 2026-05-23 03:54:53 +02:00
jakob.scheid 76815c6a93 Add sidebar to the settings view 2026-05-23 03:32:32 +02:00
jakob.scheid aa47015d3a Add sidebar right border 2026-05-23 03:30:18 +02:00
jakob.scheid 989664422c Add sidebar container 2026-05-23 03:28:59 +02:00
jakob.scheid 2ad95385d2 Add CSS grid container in the left sidebar layout 2026-05-23 03:27:44 +02:00
jakob.scheid b00aaabbb1 Add sidebar component slot 2026-05-23 03:13:52 +02:00
jakob.scheid 5104c4e8e7 Add left sidebar layout content 2026-05-23 03:13:34 +02:00
jakob.scheid d1c428eccd Rename file 2026-05-23 03:10:37 +02:00
jakob.scheid ed2b3224b4 Remove sidebar section component 2026-05-23 03:09:15 +02:00
jakob.scheid 6cc94b651f Bugfix: Fix spelling mistake in the sidebar section component 2026-05-23 03:07:40 +02:00
jakob.scheid 16ee164d76 Add sidebar section boilerplate 2026-05-23 02:41:04 +02:00
jakob.scheid 2989817278 Use sidebar component in left sidebar layout 2026-05-23 02:38:56 +02:00
jakob.scheid 0a65bcdae1 Add left sidebar layout boilerplate 2026-05-23 02:36:33 +02:00
jakob.scheid d5601b8fdb Add sidebar boilerplate 2026-05-23 02:35:16 +02:00
jakob.scheid be96541b3e Replace hard-coded settings link text in the footer with a translation 2026-05-23 02:29:06 +02:00
jakob.scheid ad8bbfa666 Update footer flexbox 2026-05-23 02:27:43 +02:00
jakob.scheid b324b93141 Reduce footer height 2026-05-23 02:25:58 +02:00
jakob.scheid 7bcdf77ca8 Add link to settings page in the footer 2026-05-23 02:25:36 +02:00
jakob.scheid 6726600c8e Add settings view heading 2026-05-23 02:22:40 +02:00
jakob.scheid 027ee2d191 Add settings route 2026-05-23 02:21:37 +02:00
jakob.scheid 6d5c7e4270 Add translations for 'settings' 2026-05-23 02:17:53 +02:00
jakob.scheid 8901967ab0 Add settings view boilerplate 2026-05-23 02:13:41 +02:00
jakob.scheid e177ed279c Remove license placeholder in the search results view 2026-05-23 02:10:02 +02:00
jakob.scheid 923942f1b1 Remove license placeholder in the footer 2026-05-23 02:09:39 +02:00
jakob.scheid 539991fac7 Remove license placeholder in the not found view 2026-05-23 02:09:18 +02:00
jakob.scheid 9e049bc5c9 Remove license placeholder in the logo graphic 2026-05-23 02:09:07 +02:00
jakob.scheid 495c2379ce Merge pull request 'Change Colors' (#65) from bug/colors into main
Deploy on dev / Deploy on dev (push) Successful in 31s
Reviewed-on: #65
Reviewed-by: Jakob Scheid
2026-05-23 02:05:53 +02:00
jakob.scheid c50fe25d04 Add CSS variables for light hover background 2026-05-22 21:13:05 +02:00
jakob.scheid 1300ab46a0 Use the --border CSS variable in the language switch button component 2026-05-22 17:51:56 +02:00
jakob.scheid 28fcfa1f92 Use --border variable in the footer 2026-05-22 17:50:13 +02:00
jakob.scheid f808525d56 Merge branch 'main' into bug/colors 2026-05-22 17:46:56 +02:00
jakob.scheid 1c7a0c0e58 Use the CSS variable for dark in the navbar right links 2026-05-22 17:46:35 +02:00
jakob.scheid 0eccd33919 Add CSS variable for blue box shadow 2026-05-22 17:45:46 +02:00
jakob.scheid 1f67e95735 Add CSS variable for gray box shadow 2026-05-22 17:44:06 +02:00
jakob.scheid 3c82baca67 Use variable for the search results view error message background 2026-05-22 17:35:22 +02:00
jakob.scheid 6d95601399 Add CSS variable for borders 2026-05-22 17:33:03 +02:00
jakob.scheid 9aecc195a9 Merge pull request 'Add more Languages and switch button' (#70) from feature/add-languages into main
Deploy on dev / Deploy on dev (push) Successful in 29s
Reviewed-on: #70
Reviewed-by: Jakob Scheid
2026-05-22 17:19:25 +02:00
jakob.scheid 2bf433621a Remove flags from language labels 2026-05-22 17:18:15 +02:00
jakob.scheid abbfd0ad9d Include flags in the language name translations instead of hard-coding it in the language switch button component 2026-05-22 15:20:08 +02:00
jakob.scheid 619d4065b3 Align right navbar links vertically centered 2026-05-22 14:20:49 +02:00
jakob.scheid 3ca938d65b Update border color of language button 2026-05-22 14:19:12 +02:00
jakob.scheid 666ecb4e98 Update border color of language dropdown 2026-05-22 14:17:25 +02:00
jakob.scheid 3af6cb3c16 Remove CSS font-size property 2026-05-21 23:33:22 +02:00
jakob.scheid 006bb5136e Add feature to close the language switching dropdown when the user clicks outside of it 2026-05-21 23:28:42 +02:00
jakob.scheid 258a4025bc Use translations in the translation files instead of hard-coded strings for the language names 2026-05-21 22:32:39 +02:00
jakob.scheid 420f47dca4 Format SUPPORTED_LANGUAGES more readable 2026-05-21 22:06:23 +02:00
jakob.scheid 6f164a0256 Remove unsupported languages from SUPPORTED_LANGUAGES 2026-05-21 22:06:02 +02:00
jakob.scheid b30aea57ad Add list with languages written from right to left 2026-05-21 22:04:58 +02:00
jakob.scheid be19a3c29e Speak English in a comment 2026-05-21 21:59:59 +02:00
jakob.scheid f08acad086 Address the user formally in the German translations 2026-05-21 21:57:05 +02:00
jakob.scheid 90f99e4240 Rename 'src/features/language/' to 'src/features/i18n/' 2026-05-21 15:45:11 +02:00
johannes.vos 46d92675eb save lanugage in local storage 2026-05-20 20:33:07 +02:00
johannes.vos dc532d3848 implement switch button 2026-05-20 20:31:45 +02:00
johannes.vos cb42c9d368 remove lanugages 2026-05-20 20:31:24 +02:00
johannes.vos 6fa3ba6298 add switch button 2026-05-20 20:26:51 +02:00
johannes.vos 7c8cf6406d add content in langauge files 2026-05-20 20:25:26 +02:00
johannes.vos 010d29c74e add emty LangaugeSwitchButton.bue file 2026-05-20 20:22:10 +02:00
johannes.vos 5a04e2a2f1 Add suported languages in i18n.js 2026-05-20 20:20:57 +02:00
johannes.vos a7ff5e2bf4 Add emty langauge json files 2026-05-20 20:17:25 +02:00
jakob.scheid 9e4c9febdb Merge pull request 'Use vue-i18n' (#68) from feature/vue-i18n into main
Deploy on dev / Deploy on dev (push) Successful in 32s
Reviewed-on: #68
Reviewed-by: Johannes D. Vos
2026-05-20 17:35:02 +02:00
jakob.scheid 2b136e30c8 Add translation for the slogan 2026-05-19 21:56:19 +02:00
jakob.scheid 16e69ff072 Add translations for the color scheme button 2026-05-19 21:53:33 +02:00
jakob.scheid c8f8022f44 Add translation for the back to search link on the not found view 2026-05-19 21:47:49 +02:00
jakob.scheid ffaa6200c3 Add translation for the page not found error 2026-05-19 21:46:58 +02:00
jakob.scheid a60526e6d7 Add translations for search errors 2026-05-19 21:43:54 +02:00
jakob.scheid 5d7181bea2 Add translations for error messages 2026-05-19 21:27:58 +02:00
jakob.scheid dd0f560bc8 Add translations for the search bar 2026-05-19 21:14:07 +02:00
jakob.scheid ced76720cc Add English translations JSON file 2026-05-19 21:07:44 +02:00
jakob.scheid 94fc328737 Add error handling if a locale to be loaded does not exist 2026-05-19 21:06:45 +02:00
jakob.scheid 2eb187ec1a Add function to load languages dynamically 2026-05-19 21:05:09 +02:00
jakob.scheid 2882f78990 Adapt the language to the browser language 2026-05-19 19:47:58 +02:00
jakob.scheid 78cde4641c Fix: Replace placeholer in the license header of 'src/i18n.js' with the actual values 2026-05-19 19:43:14 +02:00
jakob.scheid f37d403636 Add base i18n 2026-05-19 19:39:56 +02:00
jakob.scheid 09c645e657 Add dependency 'vue-i18n' 2026-05-19 19:25:59 +02:00
johannes.vos 66483a3a6a Add Border 2026-05-19 19:11:27 +02:00
johannes.vos d5c714cbd8 Change Colors of Footer 2026-05-19 19:09:51 +02:00
johannes.vos d8a4c0023b Change Colors of Footer 2026-05-19 19:07:24 +02:00
johannes.vos 5b8c13c8cf Remove Background 2026-05-19 18:59:39 +02:00
johannes.vos b172e3cf2b Change Backgorund Color 2026-05-19 18:57:18 +02:00
johannes.vos 46a2caf845 Make Border of Button visible / lighter in DarkMode 2026-05-19 18:55:01 +02:00
johannes.vos 63d14ddefd Change Black Varbiable to gray 2026-05-19 18:49:00 +02:00
jakob.scheid ba45c0d488 Merge pull request 'Add option to adapt color scheme to the system color scheme' (#61) from feature/dynamic-dark-mode into main
Deploy on dev / Deploy on dev (push) Successful in 37s
Reviewed-on: #61
Reviewed-by: Jakob Gregory
2026-05-18 16:55:40 +02:00
jakob.scheid a7d1cc0f62 Set text color of inputs according to the color scheme 2026-05-17 21:05:31 +02:00
jakob.scheid 28fe027517 Set CSS property 'color-scheme' according to the current color scheme 2026-05-17 21:01:48 +02:00
jakob.scheid 05b6a5d513 Remove duplicated CSS rule '#app' 2026-05-17 20:59:37 +02:00
jakob.scheid c78357b61d Use the term 'color scheme' instead of 'color theme' 2026-05-17 20:52:55 +02:00
jakob.scheid 678c41e990 Set dark mode dynamically 2026-05-17 20:43:13 +02:00
jakob.scheid 6fb3d95cd5 Set color scheme button text color to dark 2026-05-17 20:41:44 +02:00
jakob.scheid 7f5cbf5665 Update color variables stylesheet 2026-05-17 20:40:53 +02:00
jakob.scheid 555bdb0cfb Remove appHasDarkClass function 2026-05-17 20:38:19 +02:00
jakob.scheid cd67bf486f Add color theme button 'auto' state 2026-05-17 17:54:09 +02:00
jakob.scheid c1525cd2f2 Add auto fallbacks 2026-05-17 17:34:35 +02:00
jakob.scheid 5d98f27b6b Make color theme switch more flexible 2026-05-17 17:33:22 +02:00
jakob.scheid 170f188435 Move color theme toggle button into a separate component 2026-05-17 15:24:53 +02:00
jakob.scheid c7fd0fe132 Merge pull request 'Add license headers' (#58) from chore/license-headers into main
Deploy on dev / Deploy on dev (push) Successful in 28s
Reviewed-on: #58
Reviewed-by: Johannes D. Vos
2026-05-17 14:37:40 +02:00
jakob.scheid 3313ed40e6 Add license headers 2026-05-17 14:30:50 +02:00
jakob.scheid 50c0ff4a8c Merge pull request 'Add dev deployment workflow #49' (#57) from chore/gitea-actions-workflow into main
Deploy on dev / Deploy on dev (push) Successful in 27s
Reviewed-on: #57
Reviewed-by: Johannes D. Vos
2026-05-17 14:23:08 +02:00
jakob.scheid 5e6ff65e68 Merge branch 'main' into chore/gitea-actions-workflow 2026-05-17 13:29:40 +02:00
jakob.scheid 8d2937d4a0 Add dev deployment workflow upload 2026-05-17 13:19:04 +02:00
jakob.scheid 7273a732b1 Add dev deployment workflow build step 2026-05-17 13:18:49 +02:00
jakob.scheid 62c6784e2c Add dev deployment workflow dependencies installation step 2026-05-17 13:18:37 +02:00
jakob.scheid c4d32c2815 Add dev deployment workflow boilerplate 2026-05-17 13:18:21 +02:00
jakob.scheid ceda686d03 Merge pull request 'use logo' (#52) from feature/use-logo into main
Reviewed-on: #52
Reviewed-by: Jakob Scheid
2026-05-15 18:53:03 +02:00
jakob.scheid 792b4b80b8 Remove comment 2026-05-15 18:50:28 +02:00
johannes.vos 22e50adaa5 fix: import logo in SearchView and use :src binding 2026-05-15 18:22:47 +02:00
johannes.vos 8eab67b78f fix: import logo in SearchView and use :src binding 2026-05-15 18:22:47 +02:00
46 changed files with 1665 additions and 105 deletions
+45
View File
@@ -0,0 +1,45 @@
name: Deploy on dev
on:
push:
branches:
- main
jobs:
deploy:
name: Deploy on dev
runs-on: node-minio
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
set -euo pipefail
npm install
- name: Build
run: |
set -euo pipefail
npm run build
- name: Set alias
run: mc alias set minio ${{ vars.S3_DEV_URL }} ${{ secrets.S3_DEV_ACCESS_KEY }} ${{ secrets.S3_DEV_SECRET_KEY }}
- name: Upload build artifacts to S3
run: |
set -euo pipefail
BUCKET=${{ vars.S3_DEV_BUCKET_NAME }}
BUILD_ID=$(date +%s)-$(git rev-parse --short HEAD)
echo "Build ID: $BUILD_ID"
printf "Copying files ... "
mc cp --recursive ./dist/ "minio/$BUCKET/builds/$BUILD_ID/"
echo "done"
printf "Update current build pointer ... "
echo "$BUILD_ID" | mc pipe "minio/$BUCKET/current"
echo "done"
+71
View File
@@ -364,6 +364,16 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-----------
The following npm package may be included in this product:
- @vue/devtools-api@6.6.4
This package contains the following license:
MIT
-----------
The following npm package may be included in this product:
- json5@2.2.3
@@ -1371,6 +1381,67 @@ SOFTWARE.
-----------
The following npm packages may be included in this product:
- @intlify/core-base@11.4.4
- @intlify/message-compiler@11.4.4
- @intlify/shared@11.4.4
- vue-i18n@11.4.4
These packages each contain the following license:
The MIT License (MIT)
Copyright (c) 2020 kazuya kawaguchi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----------
The following npm package may be included in this product:
- @intlify/devtools-types@11.4.4
This package contains the following license:
The MIT License (MIT)
Copyright (c) 2024 kazuya kawaguchi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----------
The following npm package may be included in this product:
- hookable@5.5.3
+84
View File
@@ -10,6 +10,7 @@
"dependencies": {
"terser": "^5.47.1",
"vue": "^3.5.32",
"vue-i18n": "^11.4.4",
"vue-router": "^5.0.6"
},
"devDependencies": {
@@ -106,6 +107,63 @@
"tslib": "^2.4.0"
}
},
"node_modules/@intlify/core-base": {
"version": "11.4.4",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.4.tgz",
"integrity": "sha512-w/vItlylrAmhebkIbVl5YY8XMCtj8Mb2g70ttxktMYuf5AuRahgEHL2iLgLIsZBIbTSgs4hkUo7ucCL0uTJvOg==",
"dependencies": {
"@intlify/devtools-types": "11.4.4",
"@intlify/message-compiler": "11.4.4",
"@intlify/shared": "11.4.4"
},
"engines": {
"node": ">= 22"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/devtools-types": {
"version": "11.4.4",
"resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.4.tgz",
"integrity": "sha512-PcBLmGmDQsTSVV911P8upzpcLJO1CNVYi/IH6bGnLR2nA+0L963+kXN1ZrisTEnbtw2ewN6HMMSldqzjronA0Q==",
"dependencies": {
"@intlify/core-base": "11.4.4",
"@intlify/shared": "11.4.4"
},
"engines": {
"node": ">= 22"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "11.4.4",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.4.tgz",
"integrity": "sha512-vn0OAV9pYkJlPPmgnsSm5eAG3mL0+9C/oaded2JY9jmxBbhmUXT3TcAUY8WRgLY9Hte7lkUJKpXrVlYjMXBD2w==",
"dependencies": {
"@intlify/shared": "11.4.4",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 22"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "11.4.4",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.4.tgz",
"integrity": "sha512-QRUCHqda1U6aR14FR0vvXD4+4gj6+fm0AhAozvSuRCw0fCvrmCugWpgiR4xH2NI6s8am6N9p5OhirplsX8ZS3g==",
"engines": {
"node": ">= 22"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -2105,6 +2163,32 @@
}
}
},
"node_modules/vue-i18n": {
"version": "11.4.4",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.4.tgz",
"integrity": "sha512-gIbXVSFQV4jcSJxfwdZ5zSZmZ+12CnX0K3vBkRSd6Zn+HSzCp+QwUgPwpD/uN0oKNKI9RzlUXPKVedEuMgNG0A==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "11.4.4",
"@intlify/devtools-types": "11.4.4",
"@intlify/shared": "11.4.4",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 22"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-i18n/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=="
},
"node_modules/vue-router": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.6.tgz",
+1
View File
@@ -11,6 +11,7 @@
"dependencies": {
"terser": "^5.47.1",
"vue": "^3.5.32",
"vue-i18n": "^11.4.4",
"vue-router": "^5.0.6"
},
"devDependencies": {
+17 -10
View File
@@ -17,20 +17,25 @@ limitations under the License.
<script setup>
import Navbar from './features/nav/components/Navbar.vue';
import Footer from './features/footer/components/Footer.vue';
import { useColorScheme } from './features/colorScheme/composables/useColorScheme';
import { ref, provide, watch } from 'vue';
const isDark = ref(localStorage.getItem('theme') === 'dark');
provide('isDark', isDark);
watch(isDark, val => localStorage.setItem('theme', val ? 'dark' : 'light'));
const { getColorScheme, updateColorScheme } = useColorScheme();
const colorScheme = ref(getColorScheme());
provide('colorScheme', colorScheme);
watch(colorScheme, val => updateColorScheme(val))
</script>
<template>
<div :class="{ dark: isDark }" class="app-wrapper">
<div
:style="{ colorScheme: colorScheme === 'auto' ? 'normal' : (colorScheme === 'dark' ? 'dark' : 'light')}"
:class="{ dark: colorScheme === 'dark', 'color-scheme-auto': colorScheme === 'auto' }"
id="app-wrapper"
>
<Navbar />
<div class="main-content">
<router-view />
</div>
<router-view class="main-content" />
<Footer />
</div>
@@ -40,13 +45,15 @@ watch(isDark, val => localStorage.setItem('theme', val ? 'dark' : 'light'));
.main-content {
--main-content-padding-x: 30px;
--main-content-padding-y: 40px;
padding: var(--main-content-padding-y) var(--main-content-padding-x);
width: calc(100% - var(--main-content-padding-x) * 2);
--main-content-padding: var(--main-content-padding-y) var(--main-content-padding-x) 0;
flex-grow: 1;
}
.app-wrapper {
#app-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--light-bg);
color: var(--dark);
}
</style>
View File
@@ -1,3 +1,19 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<svg xmlns="http://www.w3.org/2000/svg" width="758.75" height="192.5">
<defs><linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" style="stop-color:#4ba8eb"/><stop offset="100%" style="stop-color:#043485"/></linearGradient></defs>
<path stroke="url(#gradient)" stroke-width="15" fill="none" d=" M94 51 a43.75 43.75 0 0 0 -43.75 -43.75 a43.75 43.75 0 0 0 -43.75 43.75 a43.75 43.75 0 0 0 43.75 43.75 a43.75 43.75 0 0 1 43.75 43.75 a43.75 43.75 0 0 1 -43.75 43.75 a43.75 43.75 0 0 1 -43.75 -43.75 M138.75 95.25 h43.75 a43.75 43.75 0 0 0 43.75 -43.75 a43.75 43.75 0 0 0 -43.75 -43.75 a43.75 43.75 0 0 0 -43.75 43.75 v43.75 v43.75 a43.75 43.75 0 0 0 43.75 43.75 a43.75 43.75 0 0 0 43.75 -43.75 M270 95.25 h43.75 a43.75 43.75 0 0 0 43.75 -43.75 a43.75 43.75 0 0 0 -43.75 -43.75 a43.75 43.75 0 0 0 -43.75 43.75 v43.75 v43.75 a43.75 43.75 0 0 0 43.75 43.75 a43.75 43.75 0 0 0 43.75 -43.75 M401.25 0 v192.5 m0 -96.125 l87.5 -87.5 m-87.5 87.5 l87.5 87.5 M532.5 192.5 v-192.5 m0 51.25 a43.75 43.75 0 0 1 43.75 -43.75 a43.75 43.75 0 0 1 43.75 43.75 M663.75 50.25 a43.75 43.75 0 0 1 43.75 -43.75 a43.75 43.75 0 0 1 43.75 43.75 v43.75 v43.75 v43.75 m0 -43.75 a43.75 43.75 0 0 1 -43.75 43.75 a43.75 43.75 0 0 1 -43.75 -43.75 a43.75 43.75 0 0 1 43.75 -43.75 a43.75 43.75 0 0 1 43.75 43.75 v50"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,70 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import { inject } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const colorScheme = inject('colorScheme');
const colorSchemeNextMapper = {
'light': 'dark',
'dark': 'auto',
'auto': 'light'
};
const colorSchemeIconMapper = {
'dark': '⏾',
'light': '☀',
'auto': '◐'
};
const getTooltipTranslation = function (colorScheme) {
return t(`preferences.colorScheme.switch.${colorSchemeNextMapper[colorScheme]}`);
};
</script>
<template>
<button class="color-scheme-button"
@click="colorScheme = colorSchemeNextMapper[colorScheme]"
:aria-label="getTooltipTranslation(colorScheme)"
:title="getTooltipTranslation(colorScheme)"
>
{{ colorSchemeIconMapper[colorSchemeNextMapper[colorScheme]] }}
</button>
</template>
<style scoped>
.color-scheme-button {
background: none;
border: 1.5px solid var(--border);
border-radius: 50%;
width: 36px;
height: 36px;
cursor: pointer;
font-size: 1rem;
display: flex;
align-items: center;
justify-content: center;
color: var(--dark);
}
.color-scheme-button:hover {
background: var(--light-hover);
}
</style>
@@ -0,0 +1,39 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export const useColorScheme = function () {
const getColorScheme = function () {
let colorScheme = localStorage.getItem('colorScheme') || 'auto';
if (!(colorScheme === 'dark' || colorScheme === 'light' || colorScheme === 'auto')) {
colorScheme = 'auto';
};
return colorScheme;
};
const updateColorScheme = function (newScheme) {
let actualNewScheme = newScheme;
if (!(actualNewScheme === 'dark' || actualNewScheme === 'light' || actualNewScheme === 'auto')) {
actualNewScheme = 'auto';
};
localStorage.setItem('colorScheme', actualNewScheme);
};
return {
getColorScheme,
updateColorScheme
};
};
+41 -4
View File
@@ -1,4 +1,24 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const startYear = 2026
const currentYear = new Date().getFullYear()
@@ -12,19 +32,36 @@ const copyrightPeriod =
<template>
<footer class="global-footer">
<div class="footer-segment">
<RouterLink to="settings" class="link">
{{ t('preferences.settings') }}
</RouterLink>
</div>
<div class="footer-segment">
<div class="copyright-note">
&copy; {{ copyrightPeriod }} Seekra
</div>
</div>
</footer>
</template>
<style scoped>
.global-footer {
--padding-y: 22px;
--padding-y: 16px;
}
.footer-segment {
padding: var(--padding-y);
background-color: var(--light-bg);
border-top: 1px solid var(--border);
}
.global-footer a {
color: var(--dark);
}
.copyright-note {
display: flex;
justify-content: center;
padding: var(--padding-y);
background-color: var(--light-d-1);
margin-top: var(--padding-y);
}
</style>
@@ -0,0 +1,151 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { loadLanguage, LANGUAGES_RTL, SUPPORTED_LANGUAGES } from '@/i18n';
const { t, locale } = useI18n();
const isOpen = ref(false);
const languageDropdown = ref(null);
async function selectLanguage(code) {
await loadLanguage(code);
localStorage.setItem('locale', code);
document.documentElement.lang = code;
document.documentElement.dir = LANGUAGES_RTL.includes(code) ? 'rtl' : 'ltr';
close();
};
const close = function () {
document.removeEventListener('click', closeWrapperOnClickOutsite);
isOpen.value = false;
};
const closeWrapperOnClickOutsite = function (e) {
if (languageDropdown.value) {
if (!languageDropdown.value.contains(e.target)) {
close();
};
};
};
const open = function () {
if (!isOpen.value) {
isOpen.value = true;
setTimeout(() => {
document.addEventListener('click', closeWrapperOnClickOutsite);
}, 0);
};
};
</script>
<template>
<div class="language-switch" tabindex="-1">
<button
class="language-button button"
@click="open"
:aria-expanded="isOpen"
aria-haspopup="listbox"
>
<span class="lang-code">{{ t(`preferences.locale.languages.${locale}`) }}</span>
<span class="chevron" :class="{ open: isOpen }"></span>
</button>
<ul v-if="isOpen" ref="languageDropdown" class="language-dropdown" role="listbox">
<li
v-for="lang in SUPPORTED_LANGUAGES"
:key="lang"
role="option"
:aria-selected="lang === locale"
:class="{ active: lang === locale }"
@click="selectLanguage(lang)"
>
<span class="lang-label">{{ t(`preferences.locale.languages.${lang}`) }}</span>
</li>
</ul>
</div>
</template>
<style scoped>
.language-switch {
position: relative;
}
.language-button {
display: flex;
align-items: center;
gap: 6px;
background: none;
border: 1px solid var(--border);
border-radius: 6px;
padding: 4px 10px;
cursor: pointer;
color: var(--dark);
}
.language-button:hover {
background-color: var(--light-hover);
}
.chevron {
font-size: 0.75rem;
transition: transform 0.2s;
display: inline-block;
}
.chevron.open {
transform: rotate(180deg);
}
.language-dropdown {
position: absolute;
right: 0;
top: calc(100% + 6px);
background-color: var(--light-bg);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
list-style: none;
margin: 0;
padding: 6px 0;
min-width: 160px;
z-index: 100;
}
.language-dropdown li {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 16px;
cursor: pointer;
font-size: 0.9rem;
}
.language-dropdown li:hover {
background-color: var(--light-hover);
}
.language-dropdown li.active {
font-weight: bold;
}
.flag {
font-size: 1.1rem;
}
</style>
+15
View File
@@ -0,0 +1,15 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
+15
View File
@@ -0,0 +1,15 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
@@ -0,0 +1,69 @@
```vue
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
// 1. ALLE Sprachen importieren (Verhindert den ReferenceError)
import de from '@/legal/privacy/de.md?raw';
import en from '@/legal/privacy/en.md?raw';
import fr from '@/legal/privacy/fr.md?raw';
import es from '@/legal/privacy/es.md?raw';
import it from '@/legal/privacy/it.md?raw';
import pt from '@/legal/privacy/pt.md?raw';
const { locale } = useI18n();
const content = computed(() => {
const map = {
de,
en,
fr,
es,
it,
pt
};
// Falls eine Sprache mal nicht existiert, nutzen wir 'de' oder 'en' als Fallback
return map[locale.value] || de;
});
</script>
<template>
<main class="privacy-policy-content main-content-padding">
<h1>{{ $t('legal.privacy.title') }}</h1>
<div class="markdown-body">{{ content }}</div>
</main>
</template>
<style scoped>
.privacy-policy-content {
max-width: 900px;
margin: 0 auto;
padding-top: 40px;
padding-bottom: 40px;
}
/* Sorgt dafür, dass die Zeilenumbrüche aus den .md Dateien erhalten bleiben */
.markdown-body {
white-space: pre-wrap;
font-family: inherit;
line-height: 1.6;
}
</style>
+39 -26
View File
@@ -13,23 +13,46 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import { inject } from 'vue';
const isDark = inject('isDark');
import { ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import ColorSchemeButton from '@/features/colorScheme/components/ColorSchemeButton.vue';
import LanguageSwitchButton from '@/features/i18n/components/LanguageSwitchButton.vue';
import logo from '@/assets/images/logo.svg';
import Searchbar from '@/features/search/components/Searchbar.vue';
const route = useRoute();
const searchQueryModel = defineModel();
watch(() => route.name, name => {
searchQueryModel.value = name === 'searchResults' ? route.query.q || '' : '';
});
watch(() => route.query.q, q => {
if (route.name === 'searchResults') {
searchQueryModel.value = q || '';
}
});
</script>
<template>
<nav class="global-nav">
<RouterLink to="/" class="link button link">
<img src="@/assets/logo.svg" alt="Seekra" class="nav-logo" />
<img :src="logo" alt="Seekra" class="nav-logo" />
</RouterLink>
<Searchbar
v-if="route.name === 'searchResults'"
class="search-bar"
v-model="searchQueryModel"
auto-submit
/>
<ul class="right-links">
<li>
<button class="dark-mode-toggle"
@click="isDark = !isDark"
:aria-label="isDark ? 'Light mode' : 'Dark mode'">
<span v-if="isDark"></span>
<span v-else></span>
</button>
<LanguageSwitchButton />
</li>
<li>
<ColorSchemeButton />
</li>
</ul>
</nav>
@@ -40,7 +63,8 @@ const isDark = inject('isDark');
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 40px;
padding: 18px 40px;
height: 42px;
}
.global-nav .right-links {
@@ -49,34 +73,23 @@ const isDark = inject('isDark');
list-style: none;
margin: 0;
padding: 0;
align-items: center;
}
.global-nav .right-links a {
text-decoration: none;
color: #000;
color: var(--dark);
}
.global-nav .right-links a:hover{
text-decoration: underline;
}
.dark-mode-toggle {
background: none;
border: 1.5px solid var(--light-d-3);
border-radius: 50%;
width: 36px;
height: 36px;
cursor: pointer;
font-size: 1rem;
display: flex;
align-items: center;
justify-content: center;
}
.dark-mode-toggle:hover {
background: var(--light-d-2);
}
.nav-logo {
height: 24px;
width: auto;
}
.search-bar {
width: 70%;
}
</style>
+10 -5
View File
@@ -18,6 +18,9 @@ limitations under the License.
const searchQuery = defineModel();
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const router = useRouter()
const props = defineProps(['autoSubmit'])
@@ -33,18 +36,20 @@ const submitSearch = function () {
</script>
<template>
<div>
<form @submit.prevent="submitSearch">
<div class="search-wrapper">
<input
v-model="searchQuery"
type="search"
placeholder="Search..."
:placeholder="t('search.searchBar.placeholder')"
required
/>
<button type="submit" class="search-button">Search</button>
<button type="submit" class="search-button">{{ t('search.searchBar.submit') }}</button>
</div>
</form>
</template>
</div>
</template>
<style scoped>
.search-wrapper {
@@ -54,8 +59,8 @@ const submitSearch = function () {
--padding-left: calc(var(--content-height) + var(--padding));
display: flex;
align-items: center;
border: 1.5px solid var(--light-d-3);
box-shadow: 0 0px 32px rgba(69, 98, 190, 0.25);
border: 1.5px solid var(--border);
box-shadow: 0 0px 32px var(--blue-box-shadow);
border-radius: calc(var(--content-height) * 0.5 + var(--submit-button-padding-y) + var(--padding));
padding: var(--padding);
padding-left: var(--padding-left);
+25 -12
View File
@@ -1,23 +1,36 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import Searchbar from '@/features/search/components/Searchbar.vue';
import { useI18n } from 'vue-i18n';
const props = defineProps(['searchQuery']);
const searchQueryModel = defineModel();
searchQueryModel.value = props.searchQuery;
const { t } = useI18n();
</script>
<template>
<Searchbar class="search-bar" v-model="searchQueryModel" auto-submit />
<div class="main-content-padding">
<div class="search-results-error-message-container">
<div class="search-results-error-message">
<p>Search is not available right now.</p>
<p>Please try again to another time.</p>
<p>{{ t('search.error.searchNotAvailable') }}</p>
<p>{{ t('error.tryAgainToAnotherTime') }}</p>
</div>
</div>
<div class="search-results-container">
</div>
</div>
</template>
<style scoped>
@@ -48,16 +61,16 @@ searchQueryModel.value = props.searchQuery;
.search-results-error-message {
padding: var(--error-message-padding);
font-size: 18px;
box-shadow: 0px 0px 42px #cdcdcdb3;
box-shadow: 0px 0px 42px var(--gray-box-shadow);
border-radius: 28px;
text-align: center;
background-color: #f7f7f7;
background-color: var(--light-bg);
animation: fade-in 0.8s ease-out;
height: var(--error-message-height);
display: flex;
justify-content: center;
flex-direction: column;
outline: 1px solid var(--light-d-2);
outline: 1px solid var(--border);
}
@media (max-width: 48rem) {
@@ -0,0 +1,48 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { ref, readonly } from 'vue';
import { loadSettingsConfig } from '../utils/settingsParser.js';
const config = ref(null);
const error = ref(null);
const loading = ref(false);
/**
* Provides reactive access to the parsed settings configuration.
* The config is loaded once and shared across all consumers.
*/
export function useSettingsConfig() {
async function load() {
loading.value = true;
error.value = null;
try {
config.value = await loadSettingsConfig();
} catch (e) {
error.value = e.message;
config.value = null;
} finally {
loading.value = false;
}
}
return {
config: readonly(config),
error: readonly(error),
loading: readonly(loading),
load,
};
}
+3
View File
@@ -0,0 +1,3 @@
{
"contents": []
}
@@ -0,0 +1,68 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @typedef {'bool' | 'number' | 'string' | 'selection' | 'section'} SettingType
*/
/**
* @typedef {Object} SelectionOption
* @property {string} name
* @property {string} i18n
*/
/**
* @typedef {Object} BaseSettingConfig
* @property {SettingType} type
* @property {string} name
* @property {string} i18n
* @property {string} [description]
*/
/**
* @typedef {BaseSettingConfig & {
* default: string | string[],
* allowMultiple: boolean,
* options: SelectionOption[]
* }} SelectionSettingConfig
*/
/**
* @typedef {Object} SectionSettingConfig
* @property {'section'} type
* @property {string} name
* @property {string} [i18n]
* @property {string} [description]
* @property {SettingConfigEntry[]} content
*/
/**
* @typedef {BoolSettingConfig | NumberSettingConfig | StringSettingConfig | SelectionSettingConfig | SectionSettingConfig} SettingConfigEntry
*/
/**
* @typedef {Object} SettingsConfig
* @property {SettingConfigEntry[]} contents
*/
/**
* @typedef {{ type: 'bool', name: string, i18n: string, description?: string, default: boolean }} BoolSettingConfig
* @typedef {{ type: 'number', name: string, i18n: string, description?: string, default: number }} NumberSettingConfig
* @typedef {{ type: 'string', name: string, i18n: string, description?: string, default: string }} StringSettingConfig
* @typedef {{ type: 'selection', name: string, i18n: string, description?: string, default: string | string[], allowMultiple?: boolean, options: SelectionOption[] }} SelectionSettingConfig
* @typedef {{ type: 'section', name: string, i18n?: string, description?: string, content: SettingConfigEntry[] }} SectionSettingConfig
* @typedef {BoolSettingConfig | NumberSettingConfig | StringSettingConfig | SelectionSettingConfig | SectionSettingConfig} SettingConfigEntry
* @typedef {{ contents: SettingConfigEntry[] }} SettingsConfig
*/
@@ -0,0 +1,38 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { validateSettingsConfig } from './settingsValidator.js';
/**
* Loads and parses the settings configuration via dynamic import.
* @returns {Promise<import('../types/settingsConfig').SettingsConfig>}
*/
export async function loadSettingsConfig() {
let raw;
try {
raw = (await import('../settings.json')).default;
} catch (e) {
throw new Error(`[settings] Failed to load settings.json: ${e.message}`);
}
const result = validateSettingsConfig(raw);
if (!result.valid) {
throw new Error(result.error);
}
return result.config;
}
@@ -0,0 +1,99 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const VALID_TYPES = ['bool', 'number', 'string', 'selection', 'section'];
function assertString(value, path) {
if (typeof value !== 'string' || value.trim() === '') {
throw new Error(`[settings] "${path}" must be a non-empty string`);
}
}
function assertType(value, path) {
if (!VALID_TYPES.includes(value)) {
throw new Error(
`[settings] "${path}" has invalid type "${value}". Must be one of: ${VALID_TYPES.join(', ')}`
);
}
}
function validateSelectionOptions(options, path) {
if (!Array.isArray(options) || options.length === 0) {
throw new Error(`[settings] "${path}.options" must be a non-empty array`);
}
options.forEach((opt, i) => {
assertString(opt.name, `${path}.options[${i}].name`);
assertString(opt.i18n, `${path}.options[${i}].i18n`);
});
}
function validateEntry(entry, path) {
assertType(entry.type, `${path}.type`);
if (entry.type === 'section') {
assertString(entry.name, `${path}.name`);
if (!Array.isArray(entry.content)) {
throw new Error(`[settings] "${path}.content" must be an array`);
}
entry.content.forEach((child, i) =>
validateEntry(child, `${path}.content[${i}]`)
);
return;
}
assertString(entry.name, `${path}.name`);
assertString(entry.i18n, `${path}.i18n`);
if (entry.default !== undefined) {
if (entry.type === 'bool' && typeof entry.default !== 'boolean') {
throw new Error(`[settings] "${path}.default" must be a boolean`);
}
if (entry.type === 'number' && typeof entry.default !== 'number') {
throw new Error(`[settings] "${path}.default" must be a number`);
}
if (entry.type === 'string' && typeof entry.default !== 'string') {
throw new Error(`[settings] "${path}.default" must be a string`);
}
if (entry.type === 'selection') {
validateSelectionOptions(entry.options, path);
if (typeof entry.allowMultiple !== 'boolean') {
throw new Error(`[settings] "${path}.allowMultiple" must be a boolean`);
}
}
}
}
/**
* Validates a raw settings config object.
* @param {unknown} raw
* @returns {{ valid: true, config: import('../types/settingsConfig').SettingsConfig } | { valid: false, error: string }}
*/
export function validateSettingsConfig(raw) {
try {
if (!raw || typeof raw !== 'object') {
throw new Error('[settings] Config must be an object');
}
if (!Array.isArray(raw.contents)) {
throw new Error('[settings] "contents" must be an array');
}
raw.contents.forEach((entry, i) =>
validateEntry(entry, `contents[${i}]`)
);
return { valid: true, config: raw };
} catch (e) {
return { valid: false, error: e.message };
}
}
@@ -0,0 +1,56 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import LeftSidebarLayout from '@/layouts/LeftSidebarLayout.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div class="settings-page-wrapper">
<header class="header">
<h1>
{{ t('preferences.settings') }}
</h1>
</header>
<LeftSidebarLayout class="layout">
<template #sidebar>
</template>
</LeftSidebarLayout>
</div>
</template>
<style scoped>
.layout {
flex-grow: 1;
}
.settings-page-wrapper {
display: flex;
flex-direction: column;
}
.header {
padding: var(--main-content-padding-y) var(--main-content-padding-x);
}
.header h1 {
margin: 0;
}
</style>
@@ -0,0 +1,28 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<template>
<nav class="sidebar">
<slot />
</nav>
</template>
<style scoped>
.sidebar {
border-right: 1px solid var(--border);
padding: 20px;
}
</style>
+60
View File
@@ -0,0 +1,60 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { createI18n } from 'vue-i18n';
import getCurrentLanguage from './utils/currentLanguage';
export const fallbackLocale = 'en';
export const LANGUAGES_RTL = [
'ar'
];
export const SUPPORTED_LANGUAGES = [
'en',
'de',
'fr',
'es',
'it',
'pt'
];
export const i18n = createI18n({
legacy: false,
locale: getCurrentLanguage(),
fallbackLocale: fallbackLocale,
messages: {}
});
const loadedLanguages = new Set();
export async function loadLanguage (locale) {
if (!SUPPORTED_LANGUAGES.includes(locale)) {
locale = fallbackLocale;
}
if (loadedLanguages.has(locale)) {
i18n.global.locale.value = locale;
return;
};
const messages = (await import(`./locales/${locale}.json`)).default;
i18n.global.setLocaleMessage(locale, messages);
i18n.global.locale.value = locale;
loadedLanguages.add(locale);
};
+42
View File
@@ -0,0 +1,42 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import Sidebar from '@/features/sidebar/components/Sidebar.vue';
</script>
<template>
<div class="layout-container">
<Sidebar>
<slot name="sidebar" />
</Sidebar>
<main class="main-content">
<slot />
</main>
</div>
</template>
<style scoped>
.layout-container {
display: grid;
grid-template-columns: min(24%, 280px) 1fr;
border-top: 1px solid var(--border);
}
.main-content {
padding: 20px;
}
</style>
+3
View File
@@ -0,0 +1,3 @@
# Datenschutzerklärung
Hier steht deine Datenschutzerklärung auf Deutsch.
+3
View File
@@ -0,0 +1,3 @@
# Privacy Policy
Here is your privacy policy in English.
+3
View File
@@ -0,0 +1,3 @@
# Política de privacidad
Aquí tienes tu política de privacidad en español.
+3
View File
@@ -0,0 +1,3 @@
# Politique de confidentialité
Voici ta politique de confidentialité en français.
+3
View File
@@ -0,0 +1,3 @@
# Informativa sulla privacy
Qui trovi la tua informativa sulla privacy in italiano.
+3
View File
@@ -0,0 +1,3 @@
# Política de Privacidade
Aqui está a tua Política de Privacidade em português.
+47
View File
@@ -0,0 +1,47 @@
{
"search": {
"searchBar": {
"submit": "Suchen",
"placeholder": "Suchen..."
},
"error": {
"searchNotAvailable": "Die Suche ist momentan nicht verfügbar."
}
},
"error": {
"tryAgainToAnotherTime": "Bitte versuchen Sie es zu einem anderen Zeitpunkt erneut.",
"pageNotFound": "Die gesuchte Seite existiert nicht. Bitte überprüfen Sie die URL oder kehren Sie zur Suchseite zurück."
},
"links": {
"back": {
"search": "Zurück zur Suche"
}
},
"preferences": {
"settings": "Einstellungen",
"colorScheme": {
"switch": {
"light": "Zum hellen Modus wechseln",
"dark": "Zum dunklen Modus wechseln",
"auto": "Zum System-Farbschema wechseln"
}
},
"locale": {
"languages": {
"en": "English",
"de": "Deutsch",
"fr": "Français",
"es": "Español",
"it": "Italiano",
"pt": "Português"
}
}
},
"slogan": "Gebaut zum Suchen.",
"legal": {
"privacy": {
"title": "Datenschutzerklärung"
}
}
}
+47
View File
@@ -0,0 +1,47 @@
{
"search": {
"searchBar": {
"submit": "Search",
"placeholder": "Search..."
},
"error": {
"searchNotAvailable": "Search is not available right now."
}
},
"error": {
"tryAgainToAnotherTime": "Please try again to another time.",
"pageNotFound": "The page you are looking for does not exist. Please check the URL or return to the search page."
},
"links": {
"back": {
"search": "Back to Search"
}
},
"preferences": {
"settings": "Settings",
"colorScheme": {
"switch": {
"light": "Switch to light mode",
"dark": "Switch to dark mode",
"auto": "Switch to the system color scheme"
}
},
"locale": {
"languages": {
"en": "English",
"de": "Deutsch",
"fr": "Français",
"es": "Español",
"it": "Italiano",
"pt": "Português"
}
}
},
"slogan": "Built to search.",
"legal": {
"privacy": {
"title": "Privacy Policy"
}
}
}
+47
View File
@@ -0,0 +1,47 @@
{
"search": {
"searchBar": {
"submit": "Buscar",
"placeholder": "Buscar..."
},
"error": {
"searchNotAvailable": "La búsqueda no está disponible en este momento."
}
},
"error": {
"tryAgainToAnotherTime": "Por favor, inténtalo de nuevo más tarde.",
"pageNotFound": "La página que buscas no existe. Comprueba la URL o vuelve a la página de búsqueda."
},
"links": {
"back": {
"search": "Volver a la búsqueda"
}
},
"preferences": {
"settings": "Ajustes",
"colorScheme": {
"switch": {
"light": "Cambiar al modo claro",
"dark": "Cambiar al modo oscuro",
"auto": "Usar el esquema de color del sistema"
}
},
"locale": {
"languages": {
"en": "English",
"de": "Deutsch",
"fr": "Français",
"es": "Español",
"it": "Italiano",
"pt": "Português"
}
}
},
"slogan": "Hecho para buscar.",
"legal": {
"privacy": {
"title": "Política de Privacidad"
}
}
}
+47
View File
@@ -0,0 +1,47 @@
{
"search": {
"searchBar": {
"submit": "Rechercher",
"placeholder": "Rechercher..."
},
"error": {
"searchNotAvailable": "La recherche n'est pas disponible pour le moment."
}
},
"error": {
"tryAgainToAnotherTime": "Veuillez réessayer ultérieurement.",
"pageNotFound": "La page que vous recherchez n'existe pas. Vérifiez l'URL ou retournez à la page de recherche."
},
"links": {
"back": {
"search": "Retour à la recherche"
}
},
"preferences": {
"settings": "Paramètres",
"colorScheme": {
"switch": {
"light": "Passer en mode clair",
"dark": "Passer en mode sombre",
"auto": "Utiliser le thème système"
}
},
"locale": {
"languages": {
"en": "English",
"de": "Deutsch",
"fr": "Français",
"es": "Español",
"it": "Italiano",
"pt": "Português"
}
}
},
"slogan": "Conçu pour chercher.",
"legal": {
"privacy": {
"title": "Politique de Confidentialité"
}
}
}
+47
View File
@@ -0,0 +1,47 @@
{
"search": {
"searchBar": {
"submit": "Cerca",
"placeholder": "Cerca..."
},
"error": {
"searchNotAvailable": "La ricerca non è disponibile al momento."
}
},
"error": {
"tryAgainToAnotherTime": "Per favore riprova più tardi.",
"pageNotFound": "La pagina che cerchi non esiste. Controlla l'URL o torna alla pagina di ricerca."
},
"links": {
"back": {
"search": "Torna alla ricerca"
}
},
"preferences": {
"settings": "Impostazioni",
"colorScheme": {
"switch": {
"light": "Passa alla modalità chiara",
"dark": "Passa alla modalità scura",
"auto": "Usa la combinazione colori di sistema"
}
},
"locale": {
"languages": {
"en": "English",
"de": "Deutsch",
"fr": "Français",
"es": "Español",
"it": "Italiano",
"pt": "Português"
}
}
},
"slogan": "Costruito per cercare.",
"legal": {
"privacy": {
"title": "Politica di Privacy"
}
}
}
+47
View File
@@ -0,0 +1,47 @@
{
"search": {
"searchBar": {
"submit": "Pesquisar",
"placeholder": "Pesquisar..."
},
"error": {
"searchNotAvailable": "A pesquisa não está disponível no momento."
}
},
"error": {
"tryAgainToAnotherTime": "Por favor, tente novamente mais tarde.",
"pageNotFound": "A página que você procura não existe. Verifique o URL ou volte à página de pesquisa."
},
"links": {
"back": {
"search": "Voltar à pesquisa"
}
},
"preferences": {
"settings": "Configurações",
"colorScheme": {
"switch": {
"light": "Mudar para modo claro",
"dark": "Mudar para modo escuro",
"auto": "Usar esquema de cores do sistema"
}
},
"locale": {
"languages": {
"en": "English",
"de": "Deutsch",
"fr": "Français",
"es": "Español",
"it": "Italiano",
"pt": "Português"
}
}
},
"slogan": "Feito para pesquisar.",
"legal": {
"privacy": {
"title": "Política de Privacidade"
}
}
}
+9 -1
View File
@@ -13,10 +13,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { createApp } from 'vue'
import App from './App.vue'
import { i18n, loadLanguage } from './i18n';
import getCurrentLanguage from './utils/currentLanguage';
import router from './router'
import './styles/common.css'
import './styles/variables/colors.css'
createApp(App).use(router).mount('#app')
await loadLanguage(getCurrentLanguage());
createApp(App)
.use(router)
.use(i18n)
.mount('#app')
+20
View File
@@ -15,10 +15,13 @@ limitations under the License.
*/
import { createRouter, createWebHistory } from 'vue-router';
import { i18n } from '@/i18n';
import SearchView from '../views/SearchView.vue';
import SearchResultsView from '@/features/search/views/SearchResultsView.vue';
import SettingsView from '@/features/settings/views/SettingsView.vue';
import NotFound from '../views/NotFound.vue';
import PrivacyPolicyView from '@/features/legal/views/PrivacyPolicyView.vue';
const routes = [
{
@@ -37,11 +40,28 @@ const routes = [
title: (route) => route.query.q
}
},
{
path: '/settings',
name: 'settings',
component: SettingsView,
meta: {
title: () => i18n.global.t('preferences.settings')
}
},
{
path: '/:pathMatch(.*)*',
name: 'notFound',
component: NotFound
},
{
path: '/privacy',
name: 'privacyPolicy',
component: PrivacyPolicyView,
meta: {
title: () => 'Privacy Policy'
}
},
];
const router = createRouter({
+9 -6
View File
@@ -19,12 +19,6 @@ body {
font-size: 16px;
}
#app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.link {
text-decoration: none;
}
@@ -32,3 +26,12 @@ body {
.link:hover:not(.button-link) {
text-decoration: underline;
}
input {
color: var(--dark);
}
.main-content-padding {
padding: var(--main-content-padding);
width: calc(100% - var(--main-content-padding-x) * 2);
}
+88 -16
View File
@@ -1,4 +1,20 @@
:root {
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#app-wrapper {
--primary-color-l-8: oklch(0.897 0.202 260);
--primary-color-l-7: oklch(0.847 0.202 260);
--primary-color-l-6: oklch(0.797 0.202 260);
@@ -58,20 +74,76 @@
--light-d-8: var(--white-d-8);
--dark-bg: var(--black-l-2);
--light-bg: oklch(1 0 0);
}
.dark {
color-scheme: dark;
background-color: var(--dark-bg);
color: var(--white-d-1);
--light-bg: var(--white);
--light: var(--black-l-8);
--light-d-1: var(--black-l-7);
--light-d-2: var(--black-l-6);
--light-d-3: var(--black-l-5);
--light-d-4: var(--black-l-4);
--light-d-5: var(--black-l-3);
--light-d-6: var(--black-l-2);
--light-d-7: var(--black-l-1);
--light-d-8: var(--black);
--border: var(--white-d-3);
--gray-box-shadow: oklch(0.8 0.0001 271 / 0.7);
--blue-box-shadow: oklch(0.52 0.15 268 / 0.25);
--light-hover: var(--light-d-2);
}
@media (prefers-color-scheme: dark) {
#app-wrapper.color-scheme-auto {
--dark-l-8: var(--white-d-8);
--dark-l-7: var(--white-d-7);
--dark-l-6: var(--white-d-6);
--dark-l-5: var(--white-d-5);
--dark-l-4: var(--white-d-4);
--dark-l-3: var(--white-d-3);
--dark-l-2: var(--white-d-2);
--dark-l-1: var(--white-d-1);
--dark: var(--white);
--light: var(--black);
--light-d-1: oklch(0.10 0 0);
--light-d-2: var(--black-l-2);
--light-d-3: var(--black-l-3);
--light-d-4: var(--black-l-4);
--light-d-5: var(--black-l-5);
--light-d-6: var(--black-l-6);
--light-d-7: var(--black-l-7);
--light-d-8: var(--black-l-8);
--dark-bg: var(--white);
--light-bg: oklch(0.18 0 0);
--border: var(--black-l-6);
--gray-box-shadow: oklch(0.25 0.0001 271 / 0.7);
--light-hover: var(--light-d-5);
}
}
#app-wrapper.dark {
--dark-l-8: var(--white-d-8);
--dark-l-7: var(--white-d-7);
--dark-l-6: var(--white-d-6);
--dark-l-5: var(--white-d-5);
--dark-l-4: var(--white-d-4);
--dark-l-3: var(--white-d-3);
--dark-l-2: var(--white-d-2);
--dark-l-1: var(--white-d-1);
--dark: var(--white);
--light: var(--black);
--light-d-1: oklch(0.10 0 0);
--light-d-2: var(--black-l-2);
--light-d-3: var(--black-l-3);
--light-d-4: var(--black-l-4);
--light-d-5: var(--black-l-5);
--light-d-6: var(--black-l-6);
--light-d-7: var(--black-l-7);
--light-d-8: var(--black-l-8);
--dark-bg: var(--white);
--light-bg: oklch(0.18 0 0);
--border: var(--black-l-6);
--gray-box-shadow: oklch(0.25 0.0001 271 / 0.7);
--light-hover: var(--light-d-5);
}
+22
View File
@@ -0,0 +1,22 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export default function getCurrentLanguage () {
const saved = localStorage.getItem('locale');
if (saved) return saved;
const locale = new Intl.Locale(navigator.language);
return locale.language;
};
+27 -3
View File
@@ -1,9 +1,33 @@
<!--
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div class="not-found">
<div class="not-found main-content-padding">
<span class="error-message">
The page you are looking for does not exist. Please check the URL or return to the search page.
{{ t('error.pageNotFound') }}
</span>
<RouterLink to="/" id="link">Back to Search</RouterLink>
<RouterLink to="/" id="link">
{{ t('links.back.search') }}
</RouterLink>
</div>
</template>
+6 -4
View File
@@ -16,10 +16,12 @@ limitations under the License.
<script setup>
import Searchbar from '../features/search/components/Searchbar.vue';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import logo from '@/assets/images/logo.svg';
const { t } = useI18n();
const router = useRouter();
const searchQuery = ref('');
@@ -30,11 +32,11 @@ const submitSearch = function () {
</script>
<template>
<div class="search-content">
<div class="search-content main-content-padding">
<header class="global-header">
<img src="@/assets/logo.svg" alt="Seekra" class="header-logo" />
<img :src="logo" alt="Seekra" class="header-logo" />
<span class="slogan">
Built to search.
{{ t('slogan') }}
</span>
</header>
+16
View File
@@ -1,3 +1,19 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';