4 Commits

Author SHA1 Message Date
Crylia b1d4f9f71b feat: add vocabulary browser with filters, audio playback, and example sentences
Release Build / build-docker (push) Successful in 2m44s
Release Build / build-android-and-release (push) Failing after 1m27s
- Add Vocabularies page with searchable, level-grouped grid
- Implement multi-faceted filter system (word type, verb class, transitivity, reading ending, character count)
- Add male/female voice audio playback via WaniKani pronunciation data
- Display context sentences from WaniKani API
- Collapsible mnemonics section in detail modal
- Component kanji chips navigate to Collection with pre-filled search
- Vocabulary sync uses upsert to keep data fresh (audio, sentences, normalized POS)
- Add Vocabulary model, controller, service, and API route
- Add seed data with 28 vocabulary entries including transitive/intransitive verb pairs
- Full i18n support (EN, DE, JA) for all new UI elements
2026-04-28 21:57:34 +02:00
Crylia c1651550d1 chore: sync all remaining changes
Release Build / build-docker (push) Successful in 52s
Release Build / build-android-and-release (push) Failing after 1m1s
- Remove android build directory (moved to CI)
- Update package dependencies
- Misc server and client improvements
2026-04-18 22:40:25 +02:00
Crylia 64368b1f29 fix: include lesson stepper styles in build, restrict scramble to locale changes only
- Lesson stepper CSS was not committed (only in working tree), causing vertical layout in production
- ScrambleText now only animates when locale changes, not on any text prop change (e.g. on/off toggle)
2026-04-18 22:38:08 +02:00
Crylia 049eaa1807 feat: add sound effects, improved stroke recognition, instant locale switching with scramble text animation
Release Build / build-docker (push) Successful in 1m9s
Release Build / build-android-and-release (push) Successful in 7m14s
- Add SoundManager with Web Audio API synthesized sounds (stroke error, drawing complete, session complete)
- Add sound effects toggle in settings (persisted via localStorage)
- Improve stroke recognition: curvature similarity, max point deviation, stroke length ratio checks
- Loosen thresholds for long strokes and first-stroke positioning
- Implement instant locale switching (no save required)
- Add ScrambleText component with character-by-character morphing animation
- Apply ScrambleText globally across all views, widgets, and dialogs
- Add i18n keys for sound effects settings (EN/DE/JA)
2026-04-18 22:19:36 +02:00
125 changed files with 3654 additions and 1500 deletions
-101
View File
@@ -1,101 +0,0 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
-2
View File
@@ -1,2 +0,0 @@
/build/*
!/build/.npmkeep
-90
View File
@@ -1,90 +0,0 @@
apply plugin: 'com.android.application'
android {
namespace "com.zenkanji.app"
compileSdk rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.zenkanji.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
signingConfigs {
release {
if (file("my-release-key.jks").exists()) {
storeFile file("my-release-key.jks")
if (project.hasProperty("RELEASE_KEY_PASSWORD")) {
storePassword RELEASE_KEY_PASSWORD
keyPassword RELEASE_KEY_PASSWORD
} else if (System.getenv("RELEASE_KEY_PASSWORD") != null) {
storePassword System.getenv("RELEASE_KEY_PASSWORD")
keyPassword System.getenv("RELEASE_KEY_PASSWORD")
} else {
storePassword "missing_password"
keyPassword "missing_password"
}
if (project.hasProperty("RELEASE_KEY_ALIAS")) {
keyAlias RELEASE_KEY_ALIAS
} else if (System.getenv("RELEASE_KEY_ALIAS") != null) {
keyAlias System.getenv("RELEASE_KEY_ALIAS")
} else {
keyAlias "my-key-alias"
}
}
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "ZenKanji-${variant.versionName}.apk"
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
configurations.all {
resolutionStrategy {
force 'org.jetbrains.kotlin:kotlin-stdlib:1.8.22'
force 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.22'
force 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22'
}
}
-20
View File
@@ -1,20 +0,0 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
implementation project(':capgo-capacitor-updater')
}
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}
-21
View File
@@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -1,26 +0,0 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.getcapacitor.app", appContext.getPackageName());
}
}
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
@@ -1,5 +0,0 @@
package com.zenkanji.app;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

@@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
@@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
@@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">Zen Kanji</string>
<string name="title_activity_main">Zen Kanji</string>
<string name="package_name">com.zenkanji.app</string>
<string name="custom_url_scheme">com.zenkanji.app</string>
</resources>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
</style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item>
</style>
</resources>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>
@@ -1,18 +0,0 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
-24
View File
@@ -1,24 +0,0 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.2'
classpath 'com.google.gms:google-services:4.4.2'
}
}
apply from: "variables.gradle"
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
-9
View File
@@ -1,9 +0,0 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
include ':capgo-capacitor-updater'
project(':capgo-capacitor-updater').projectDir = new File('../node_modules/@capgo/capacitor-updater/android')
-2
View File
@@ -1,2 +0,0 @@
org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
Binary file not shown.
@@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-252
View File
@@ -1,252 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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
#
# https://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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
-94
View File
@@ -1,94 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
-5
View File
@@ -1,5 +0,0 @@
include ':app'
include ':capacitor-cordova-android-plugins'
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
apply from: 'capacitor.settings.gradle'
-16
View File
@@ -1,16 +0,0 @@
ext {
minSdkVersion = 23
compileSdkVersion = 35
targetSdkVersion = 35
androidxActivityVersion = '1.9.2'
androidxAppCompatVersion = '1.7.0'
androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.15.0'
androidxFragmentVersion = '1.8.4'
coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.12.1'
junitVersion = '4.13.2'
androidxJunitVersion = '1.2.1'
androidxEspressoCoreVersion = '3.6.1'
cordovaAndroidVersion = '10.1.1'
}
+240 -240
View File
@@ -15,7 +15,7 @@
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@mdi/font": "^7.3.67",
"capacitor": "^0.5.6",
"capacitor": "^0.1.4",
"eslint-plugin-jsonc": "^2.21.0",
"eslint-plugin-vue": "^9.33.0",
"globals": "^16.5.0",
@@ -651,9 +651,9 @@
}
},
"node_modules/@capacitor/cli": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.4.4.tgz",
"integrity": "sha512-J7ciBE7GlJ70sr2s8oz1+H4ZdNk4MGG41fsakUlDHWva5UWgFIZYMiEdDvGbYazAYTaxN3lVZpH9zil9FfZj+Q==",
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.5.0.tgz",
"integrity": "sha512-mlohsvLZjWrO5eAVTn1+dABNQwQawcphVp6NQVJZ3I4x2BAoNmJj53QflX7PYGUipL9gF9EM9Yiku3m1McxFZg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -665,13 +665,13 @@
"env-paths": "^2.2.0",
"fs-extra": "^11.2.0",
"kleur": "^4.1.5",
"native-run": "^2.0.1",
"native-run": "^2.0.3",
"open": "^8.4.0",
"plist": "^3.1.0",
"prompts": "^2.4.2",
"rimraf": "^6.0.1",
"semver": "^7.6.3",
"tar": "^6.1.11",
"tar": "^7.5.3",
"tslib": "^2.8.1",
"xml2js": "^0.6.2"
},
@@ -1551,27 +1551,17 @@
"node": ">=16.0.0"
}
},
"node_modules/@isaacs/balanced-match": {
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"dev": true,
"license": "MIT",
"license": "ISC",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
"minipass": "^7.0.4"
},
"engines": {
"node": "20 || >=22"
"node": ">=18.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
@@ -2038,9 +2028,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz",
"integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"cpu": [
"arm"
],
@@ -2051,9 +2041,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz",
"integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"cpu": [
"arm64"
],
@@ -2064,9 +2054,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz",
"integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"cpu": [
"arm64"
],
@@ -2077,9 +2067,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz",
"integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"cpu": [
"x64"
],
@@ -2090,9 +2080,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz",
"integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"cpu": [
"arm64"
],
@@ -2103,9 +2093,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz",
"integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"cpu": [
"x64"
],
@@ -2116,9 +2106,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz",
"integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"cpu": [
"arm"
],
@@ -2129,9 +2119,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz",
"integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"cpu": [
"arm"
],
@@ -2142,9 +2132,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz",
"integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [
"arm64"
],
@@ -2155,9 +2145,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz",
"integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"cpu": [
"arm64"
],
@@ -2168,9 +2158,22 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz",
"integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [
"loong64"
],
@@ -2181,9 +2184,22 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz",
"integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"cpu": [
"ppc64"
],
@@ -2194,9 +2210,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz",
"integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"cpu": [
"riscv64"
],
@@ -2207,9 +2223,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz",
"integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"cpu": [
"riscv64"
],
@@ -2220,9 +2236,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz",
"integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"cpu": [
"s390x"
],
@@ -2233,9 +2249,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz",
"integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"cpu": [
"x64"
],
@@ -2246,9 +2262,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz",
"integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"cpu": [
"x64"
],
@@ -2258,10 +2274,23 @@
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz",
"integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [
"arm64"
],
@@ -2272,9 +2301,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz",
"integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [
"arm64"
],
@@ -2285,9 +2314,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz",
"integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"cpu": [
"ia32"
],
@@ -2298,9 +2327,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz",
"integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
@@ -2311,9 +2340,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz",
"integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [
"x64"
],
@@ -2693,9 +2722,9 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
@@ -3286,14 +3315,24 @@
"license": "CC-BY-4.0"
},
"node_modules/capacitor": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/capacitor/-/capacitor-0.5.6.tgz",
"integrity": "sha512-xjyv2ztJxD1VayTMGIkh+TNfpdJU9BT3jO4Y5kEsh5MpF+zEdBQYLC/pFD9lNyS6mi8Pm3b/bj0ZUSOKblkfcw==",
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/capacitor/-/capacitor-0.1.4.tgz",
"integrity": "sha512-wJjJmi0h0Rcob3WiaZTSEj50W2AhiZH+7C+7nE6zqrxb4rBLZDcvZ+i568LR7ioWCsI+8TAiIKYVKQ0uScmYUw==",
"dependencies": {
"immutable": "^3.7.3",
"lodash": "^4.17.21"
"lodash": "^2.4.1",
"signals": "=1.0.0"
}
},
"node_modules/capacitor/node_modules/lodash": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz",
"integrity": "sha512-Kak1hi6/hYHGVPmdyiZijoQyz5x2iGVzs6w9GYB/HiXEtylY7tIoYEROMjvM1d9nXJqPOrG2MNPMn01bJ+S0Rw==",
"engines": [
"node",
"rhino"
],
"license": "MIT"
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -3336,13 +3375,13 @@
}
},
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=10"
"node": ">=18"
}
},
"node_modules/color-convert": {
@@ -5067,32 +5106,6 @@
"node": ">=14.14"
}
},
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -5258,17 +5271,40 @@
"node": ">=10.13.0"
}
},
"node_modules/glob/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
"integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/brace-expansion": "^5.0.0"
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "20 || >=22"
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -5584,13 +5620,11 @@
}
},
"node_modules/immutable": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
"integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
"integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
"devOptional": true,
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
@@ -6480,9 +6514,9 @@
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/lodash.merge": {
@@ -6617,9 +6651,9 @@
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -6649,30 +6683,16 @@
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
"node": ">= 18"
}
},
"node_modules/mitt": {
@@ -6681,19 +6701,6 @@
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true,
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/moo": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
@@ -6744,9 +6751,9 @@
}
},
"node_modules/native-run": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.1.tgz",
"integrity": "sha512-XfG1FBZLM50J10xH9361whJRC9SHZ0Bub4iNRhhI61C8Jv0e1ud19muex6sNKB51ibQNUJNuYn25MuYET/rE6w==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.3.tgz",
"integrity": "sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7722,9 +7729,9 @@
}
},
"node_modules/rollup": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz",
"integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -7737,28 +7744,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.53.5",
"@rollup/rollup-android-arm64": "4.53.5",
"@rollup/rollup-darwin-arm64": "4.53.5",
"@rollup/rollup-darwin-x64": "4.53.5",
"@rollup/rollup-freebsd-arm64": "4.53.5",
"@rollup/rollup-freebsd-x64": "4.53.5",
"@rollup/rollup-linux-arm-gnueabihf": "4.53.5",
"@rollup/rollup-linux-arm-musleabihf": "4.53.5",
"@rollup/rollup-linux-arm64-gnu": "4.53.5",
"@rollup/rollup-linux-arm64-musl": "4.53.5",
"@rollup/rollup-linux-loong64-gnu": "4.53.5",
"@rollup/rollup-linux-ppc64-gnu": "4.53.5",
"@rollup/rollup-linux-riscv64-gnu": "4.53.5",
"@rollup/rollup-linux-riscv64-musl": "4.53.5",
"@rollup/rollup-linux-s390x-gnu": "4.53.5",
"@rollup/rollup-linux-x64-gnu": "4.53.5",
"@rollup/rollup-linux-x64-musl": "4.53.5",
"@rollup/rollup-openharmony-arm64": "4.53.5",
"@rollup/rollup-win32-arm64-msvc": "4.53.5",
"@rollup/rollup-win32-ia32-msvc": "4.53.5",
"@rollup/rollup-win32-x64-gnu": "4.53.5",
"@rollup/rollup-win32-x64-msvc": "4.53.5",
"@rollup/rollup-android-arm-eabi": "4.59.0",
"@rollup/rollup-android-arm64": "4.59.0",
"@rollup/rollup-darwin-arm64": "4.59.0",
"@rollup/rollup-darwin-x64": "4.59.0",
"@rollup/rollup-freebsd-arm64": "4.59.0",
"@rollup/rollup-freebsd-x64": "4.59.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
"@rollup/rollup-linux-arm64-musl": "4.59.0",
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
"@rollup/rollup-linux-loong64-musl": "4.59.0",
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
"@rollup/rollup-linux-x64-gnu": "4.59.0",
"@rollup/rollup-linux-x64-musl": "4.59.0",
"@rollup/rollup-openbsd-x64": "4.59.0",
"@rollup/rollup-openharmony-arm64": "4.59.0",
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
"@rollup/rollup-win32-x64-gnu": "4.59.0",
"@rollup/rollup-win32-x64-msvc": "4.59.0",
"fsevents": "~2.3.2"
}
},
@@ -7901,13 +7911,6 @@
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/sass/node_modules/immutable": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
"devOptional": true,
"license": "MIT"
},
"node_modules/sax": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz",
@@ -8080,6 +8083,11 @@
"dev": true,
"license": "ISC"
},
"node_modules/signals": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/signals/-/signals-1.0.0.tgz",
"integrity": "sha512-dE3lBiqgrgIvpGHYBy6/kiYKfh0HXRmbg0ocakBKiOefbal6ZeTtNlQlxsu9ADkNzv5OmRwRKu+IaTPSqJdZDg=="
},
"node_modules/sirv": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
@@ -8741,9 +8749,9 @@
}
},
"node_modules/table/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8765,31 +8773,20 @@
"license": "MIT"
},
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz",
"integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.1.0",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tar/node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=8"
"node": ">=18"
}
},
"node_modules/text-table": {
@@ -9815,11 +9812,14 @@
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"dev": true,
"license": "ISC"
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/yauzl": {
"version": "2.10.0",
+1 -1
View File
@@ -20,7 +20,7 @@
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@mdi/font": "^7.3.67",
"capacitor": "^0.5.6",
"capacitor": "^0.1.4",
"eslint-plugin-jsonc": "^2.21.0",
"eslint-plugin-vue": "^9.33.0",
"globals": "^16.5.0",
+191 -42
View File
@@ -12,7 +12,8 @@
.d-flex.flex-column.h-100.pa-4
.d-flex.align-center.mb-6.px-2
img.mr-3(:src="logo" height="32" width="32" alt="Logo")
span.text-h6.font-weight-bold {{ $t('nav.menu') }}
span.text-h6.font-weight-bold
ScrambleText(:text="$t('nav.menu')")
v-list.pa-0(nav bg-color="transparent")
v-list-item.mb-2(
@@ -22,7 +23,8 @@
)
template(v-slot:prepend)
v-icon(color="#00cec9" icon="mdi-view-dashboard")
v-list-item-title.font-weight-bold {{ $t('nav.dashboard') }}
v-list-item-title.font-weight-bold
ScrambleText(:text="$t('nav.dashboard')")
v-list-item.mb-2(
to="/collection"
@@ -31,7 +33,18 @@
)
template(v-slot:prepend)
v-icon(color="#00cec9" icon="mdi-bookshelf")
v-list-item-title.font-weight-bold {{ $t('nav.collection') }}
v-list-item-title.font-weight-bold
ScrambleText(:text="$t('nav.collection')")
v-list-item.mb-2(
to="/vocabularies"
rounded="lg"
:active="$route.path === '/vocabularies'"
)
template(v-slot:prepend)
v-icon(color="#00cec9" icon="mdi-translate")
v-list-item-title.font-weight-bold
ScrambleText(:text="$t('nav.vocabularies')")
v-divider.my-4.border-subtle
@@ -43,7 +56,7 @@
@click="showSettings = true; drawer = false"
)
v-icon(start icon="mdi-cog")
| {{ $t('nav.settings') }}
ScrambleText(:text="$t('nav.settings')")
v-btn.justify-start.px-4(
variant="text"
@@ -53,7 +66,7 @@
@click="manualSync"
)
v-icon(start icon="mdi-sync")
| {{ $t('nav.sync') }}
ScrambleText(:text="$t('nav.sync')")
v-btn.justify-start.px-4.text-red-lighten-2(
variant="text"
@@ -61,7 +74,7 @@
@click="handleLogout"
)
v-icon(start icon="mdi-logout")
| {{ $t('nav.logout') }}
ScrambleText(:text="$t('nav.logout')")
.parallax-bg(
:style="parallaxStyle"
:class="{ 'zooming': viewState === 'zooming' }"
@@ -103,13 +116,22 @@
to="/"
variant="text"
:color="$route.path === '/' ? '#00cec9' : 'grey'"
) {{ $t('nav.dashboard') }}
)
ScrambleText(:text="$t('nav.dashboard')")
v-btn.mx-1(
to="/collection"
variant="text"
:color="$route.path === '/collection' ? '#00cec9' : 'grey'"
) {{ $t('nav.collection') }}
)
ScrambleText(:text="$t('nav.collection')")
v-btn.mx-1(
to="/vocabularies"
variant="text"
:color="$route.path === '/vocabularies' ? '#00cec9' : 'grey'"
)
ScrambleText(:text="$t('nav.vocabularies')")
v-divider.mx-2.my-auto(vertical length="20" color="grey-darken-2")
@@ -169,8 +191,10 @@
.login-content
img.mb-4(:src="logo" height="64" width="64")
h1.text-h5.font-weight-bold.mb-2 {{ $t('hero.welcome') }}
p.text-grey-lighten-1.text-body-2.mb-6 {{ $t('login.instruction') }}
h1.text-h5.font-weight-bold.mb-2
ScrambleText(:text="$t('hero.welcome')")
p.text-grey-lighten-1.text-body-2.mb-6
ScrambleText(:text="$t('login.instruction')")
v-text-field.mb-2(
v-model="inputKey"
@@ -197,14 +221,17 @@
height="44"
:loading="viewState === 'authenticating'"
@click="triggerLogin"
) {{ $t('login.button') }}
)
ScrambleText(:text="$t('login.button')")
v-dialog(v-model="showSettings" max-width="340")
v-card.rounded-xl.pa-4.border-subtle(color="#1e1e24")
v-card-title.text-center.font-weight-bold {{ $t('settings.title') }}
v-card-title.text-center.font-weight-bold
ScrambleText(:text="$t('settings.title')")
v-card-text.pt-4
.text-caption.text-grey.mb-2 {{ $t('settings.batchSize') }}
.text-caption.text-grey.mb-2
ScrambleText(:text="$t('settings.batchSize')")
v-slider(
v-model="tempBatchSize"
:min="5"
@@ -215,24 +242,51 @@
track-color="grey-darken-3"
)
.text-center.text-h5.font-weight-bold.text-teal-accent-3.mb-6
| {{ tempBatchSize }} {{ $t('settings.items') }}
| {{ tempBatchSize }}
|
ScrambleText(:text="$t('settings.items')")
.text-caption.text-grey.mb-2 {{ $t('settings.drawingTolerance') }}
v-slider(
v-model="tempDrawingAccuracy"
:min="1"
:max="20"
:step="1"
thumb-label
color="#00cec9"
track-color="grey-darken-3"
.text-caption.text-grey.mb-2
ScrambleText(:text="$t('settings.strokeStrictness')")
.d-flex.justify-center.gap-2.mb-2
v-btn(
v-for="p in presetNames"
:key="p"
size="small"
:variant="tempPreset === p ? 'flat' : 'outlined'"
:color="tempPreset === p ? '#00cec9' : 'grey'"
@click="selectPreset(p)"
) {{ p.charAt(0).toUpperCase() + p.slice(1) }}
v-btn.mb-3(
variant="text"
size="x-small"
color="grey"
:prepend-icon="showAdvanced ? 'mdi-chevron-up' : 'mdi-chevron-down'"
@click="showAdvanced = !showAdvanced"
)
.d-flex.justify-space-between.text-caption.text-grey-lighten-1.mb-6.px-1
span {{ $t('settings.strict') }} (5)
span.font-weight-bold.text-body-1(color="#00cec9") {{ tempDrawingAccuracy }}
span {{ $t('settings.loose') }} (20)
ScrambleText(:text="$t('settings.advanced')")
.text-caption.text-grey.mb-2 {{ $t('settings.language') }}
v-expand-transition
.advanced-sliders(v-show="showAdvanced")
.slider-row(v-for="facet in facetSliders" :key="facet.key")
.d-flex.justify-space-between.align-center.mb-1
.text-caption.text-grey {{ facet.label }}
.text-caption.font-weight-bold.text-teal-accent-3 {{ facet.value }}%
v-slider(
:model-value="facet.value"
@update:model-value="v => updateFacetSlider(facet.key, v)"
:min="5"
:max="95"
:step="5"
color="#00cec9"
track-color="grey-darken-3"
density="compact"
hide-details
)
.text-caption.text-grey.mb-2
ScrambleText(:text="$t('settings.language')")
.lang-switch-wrapper.border-subtle.mb-2
.lang-glider(:style="{ transform: `translateX(${localeIndex * 100}%)` }")
@@ -240,15 +294,29 @@
v-for="l in availableLocales"
:key="l"
:class="{ 'active': tempLocale === l }"
@click="tempLocale = l"
@click="switchLocale(l)"
) {{ l.toUpperCase() }}
.d-flex.align-center.justify-space-between.mt-4.px-1
.text-caption.text-grey
ScrambleText(:text="$t('settings.soundEffects')")
v-switch(
v-model="soundEnabled"
color="cyan"
density="compact"
hide-details
inset
)
template(v-slot:label)
ScrambleText(:text="soundEnabled ? $t('settings.on') : $t('settings.off')")
v-card-actions.mt-2
v-btn.text-white(
block
color="#2f3542"
@click="saveSettings"
) {{ $t('settings.save') }}
)
ScrambleText(:text="$t('settings.save')")
v-dialog(v-model="showLogoutDialog" max-width="320")
v-card.rounded-xl.pa-4.border-subtle(color="#1e1e24")
@@ -256,10 +324,11 @@
v-avatar.mb-3(color="red-darken-4" size="48")
v-icon(icon="mdi-logout" size="24" color="red-lighten-1")
h3.text-h6.font-weight-bold.mb-2 {{ $t('nav.logout') }}?
h3.text-h6.font-weight-bold.mb-2
ScrambleText(:text="$t('nav.logout') + '?'")
p.text-body-2.text-grey-lighten-1.px-2.mb-4
| {{ $t('alerts.logoutConfirm') }}
ScrambleText(:text="$t('alerts.logoutConfirm')")
v-card-actions.d-flex.gap-2.pa-0.mt-2
v-btn.flex-grow-1.text-capitalize(
@@ -267,30 +336,37 @@
color="grey"
height="40"
@click="showLogoutDialog = false"
) {{ $t('common.cancel') }}
)
ScrambleText(:text="$t('common.cancel')")
v-btn.flex-grow-1.text-capitalize(
color="red-accent-2"
variant="flat"
height="40"
@click="confirmLogout"
) {{ $t('nav.logout') }}
)
ScrambleText(:text="$t('nav.logout')")
v-snackbar(v-model="snackbar.show" :color="snackbar.color" timeout="3000")
| {{ snackbar.text }}
template(v-slot:actions)
v-btn(variant="text" @click="snackbar.show = false") {{ $t('common.close') }}
v-btn(variant="text" @click="snackbar.show = false")
ScrambleText(:text="$t('common.close')")
</template>
<script setup>
/* eslint-disable no-unused-vars */
import {
ref, watch, onMounted, computed, onUnmounted,
ref, watch, onMounted, computed, onUnmounted, reactive,
} from 'vue';
import { useI18n } from 'vue-i18n';
import { App as CapacitorApp } from '@capacitor/app';
import { useAppStore } from '@/stores/appStore';
import { checkForUpdates } from '@/utils/autoUpdate';
import { SoundManager } from '@/utils/SoundManager';
import {
PRESET_NAMES, PRESETS, FACET_KEYS, thresholdToSlider, sliderToThreshold,
} from '@/utils/StrokeConfig.js';
import logo from '@/assets/icon.svg';
const drawer = ref(false);
@@ -308,11 +384,53 @@ const showLogoutDialog = ref(false);
const snackbar = ref({ show: false, text: '', color: 'success' });
const tempBatchSize = ref(store.batchSize);
const tempDrawingAccuracy = ref(store.drawingAccuracy);
const tempPreset = ref(store.strokeStrictness.preset || 'medium');
const tempOverrides = reactive({});
const showAdvanced = ref(false);
const presetNames = PRESET_NAMES;
// Human-readable facet labels
const facetLabels = {
proximityThreshold: 'Position',
directionThreshold: 'Direction',
shapeThreshold: 'Shape',
lengthThreshold: 'Length',
curvatureThreshold: 'Curvature',
};
const facetSliders = computed(() => FACET_KEYS.map((key) => {
const preset = PRESETS[tempPreset.value] || PRESETS.medium;
const raw = tempOverrides[key] !== undefined ? tempOverrides[key] : preset[key];
return {
key,
label: facetLabels[key] || key,
value: thresholdToSlider(raw),
};
}));
function selectPreset(p) {
tempPreset.value = p;
// Clear all overrides when selecting a named preset
FACET_KEYS.forEach((k) => { delete tempOverrides[k]; });
}
function updateFacetSlider(key, sliderVal) {
tempOverrides[key] = sliderToThreshold(sliderVal);
// If overrides differ from current preset, mark as custom
const preset = PRESETS[tempPreset.value] || PRESETS.medium;
const isCustom = FACET_KEYS.some((k) => {
if (tempOverrides[k] === undefined) return false;
return Math.abs(tempOverrides[k] - preset[k]) > 0.001;
});
if (isCustom) tempPreset.value = 'custom';
}
const availableLocales = ['en', 'de', 'ja'];
const tempLocale = ref(locale.value);
const localeIndex = computed(() => availableLocales.indexOf(tempLocale.value));
const soundEnabled = ref(true);
watch(inputKey, () => { errorMsg.value = ''; });
const backgroundKanjis = [
{
@@ -391,6 +509,7 @@ const handleMouseMove = (e) => {
};
onMounted(() => {
SoundManager.init();
CapacitorApp.addListener('appStateChange', ({ isActive }) => {
if (isActive) checkForUpdates();
});
@@ -419,8 +538,16 @@ const parallaxStyle = computed(() => {
watch(showSettings, (isOpen) => {
if (isOpen) {
tempBatchSize.value = store.batchSize;
tempDrawingAccuracy.value = store.drawingAccuracy;
tempPreset.value = store.strokeStrictness.preset || 'medium';
// Restore any saved overrides
FACET_KEYS.forEach((k) => { delete tempOverrides[k]; });
if (store.strokeStrictness.overrides) {
Object.entries(store.strokeStrictness.overrides).forEach(([k, v]) => {
tempOverrides[k] = v;
});
}
tempLocale.value = locale.value;
soundEnabled.value = !SoundManager.isMuted;
}
});
@@ -464,20 +591,42 @@ async function triggerLogin() {
}
}
function switchLocale(l) {
tempLocale.value = l;
locale.value = l;
localStorage.setItem('zen_locale', l);
}
function saveSettings() {
showSettings.value = false;
setTimeout(() => {
if (locale.value !== tempLocale.value) {
locale.value = tempLocale.value;
localStorage.setItem('zen_locale', locale.value);
// Build the overrides object (only include keys that differ from preset)
const overrides = {};
const preset = PRESETS[tempPreset.value];
if (preset) {
FACET_KEYS.forEach((k) => {
if (tempOverrides[k] !== undefined && Math.abs(tempOverrides[k] - preset[k]) > 0.001) {
overrides[k] = tempOverrides[k];
}
});
} else {
// Custom preset — keep all overrides
FACET_KEYS.forEach((k) => {
if (tempOverrides[k] !== undefined) overrides[k] = tempOverrides[k];
});
}
store.saveSettings({
batchSize: tempBatchSize.value,
drawingAccuracy: tempDrawingAccuracy.value,
strokeStrictness: {
preset: tempPreset.value,
overrides,
},
});
SoundManager.setMuted(!soundEnabled.value);
store.fetchQueue();
}, 300);
}
+89
View File
@@ -0,0 +1,89 @@
<template lang="pug">
span {{ displayText }}
</template>
<script setup>
import { ref, watch, onBeforeUnmount } from 'vue';
import { useI18n } from 'vue-i18n';
const props = defineProps({
text: { type: String, required: true },
});
const { locale } = useI18n();
const displayText = ref(props.text);
let frameId = null;
let lastLocale = locale.value;
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZアイウエオカキクケコ漢字書道';
const DURATION = 400;
const STEPS_PER_CHAR = 3;
function scrambleTo(newText) {
if (frameId) cancelAnimationFrame(frameId);
const oldText = displayText.value;
const maxLen = Math.max(oldText.length, newText.length);
const startTime = performance.now();
const charDelay = DURATION / (maxLen || 1);
const tick = (now) => {
const elapsed = now - startTime;
let result = '';
for (let i = 0; i < maxLen; i++) {
const charSettleTime = i * charDelay;
const charElapsed = elapsed - charSettleTime;
if (i >= newText.length && elapsed > charSettleTime + charDelay) {
continue;
}
if (charElapsed >= charDelay) {
if (i < newText.length) result += newText[i];
} else if (charElapsed > 0) {
const step = Math.floor((charElapsed / charDelay) * STEPS_PER_CHAR);
if (step >= STEPS_PER_CHAR - 1 && i < newText.length) {
result += newText[i];
} else {
result += CHARS[Math.floor(Math.random() * CHARS.length)];
}
} else {
result += i < oldText.length ? oldText[i] : ' ';
}
}
displayText.value = result;
if (elapsed < DURATION + charDelay) {
frameId = requestAnimationFrame(tick);
} else {
displayText.value = newText;
frameId = null;
}
};
frameId = requestAnimationFrame(tick);
}
// Only scramble when locale changes, otherwise update silently
watch(() => props.text, (newVal) => {
if (locale.value !== lastLocale) {
lastLocale = locale.value;
scrambleTo(newVal);
} else {
displayText.value = newVal;
}
});
// Also watch locale directly to catch the change
watch(locale, () => {
// The text prop will update right after locale changes,
// so we just mark it and let the text watcher handle animation
lastLocale = '__pending__';
});
onBeforeUnmount(() => {
if (frameId) cancelAnimationFrame(frameId);
});
</script>
@@ -14,8 +14,10 @@
div
.text-subtitle-1.font-weight-bold.d-flex.align-center.text-white(
style="line-height: 1.2"
) {{ title }}
.text-caption.text-grey(v-if="subtitle") {{ subtitle }}
)
ScrambleText(:text="title")
.text-caption.text-grey(v-if="subtitle")
ScrambleText(:text="subtitle")
div.d-flex.align-center
slot(name="header-right")
@@ -7,9 +7,11 @@
div
.text-h3.font-weight-bold.text-white {{ accuracyPercent }}%
.text-caption.text-grey.mt-1
| {{ accuracy?.correct || 0 }} {{ $t('stats.correct') }}
| {{ accuracy?.correct || 0 }}
ScrambleText(:text="$t('stats.correct')")
span.mx-1 /
| {{ accuracy?.total || 0 }} {{ $t('stats.total') }}
| {{ accuracy?.total || 0 }}
ScrambleText(:text="$t('stats.total')")
v-progress-circular(
:model-value="accuracyPercent"
@@ -3,7 +3,7 @@
.d-flex.align-center.justify-space-between.mb-4
.text-subtitle-1.font-weight-bold.d-flex.align-center
v-icon(color="secondary" start size="small") mdi-chart-bar
| {{ $t('stats.srsDistribution') }}
ScrambleText(:text="$t('stats.srsDistribution')")
v-chip.font-weight-bold(
size="x-small"
color="white"
@@ -7,7 +7,7 @@
div(v-for="(count, hour) in forecast" :key="hour")
.d-flex.justify-space-between.align-center.mb-2.py-2.border-b-subtle(v-if="count > 0")
span.text-body-2.text-grey-lighten-1
| {{ hour === 0 ? $t('stats.availableNow') : $t('stats.inHours', { n: hour }, hour) }}
ScrambleText(:text="hour === 0 ? $t('stats.availableNow') : $t('stats.inHours', { n: hour }, hour)")
v-chip.font-weight-bold.text-primary(
size="small"
@@ -15,7 +15,7 @@
) {{ count }}
.fill-height.d-flex.align-center.justify-center.text-grey.text-center.pa-4(v-else)
| {{ $t('stats.noIncoming') }}
ScrambleText(:text="$t('stats.noIncoming')")
</template>
<script setup>
@@ -24,7 +24,7 @@ import { computed } from 'vue';
import DashboardWidget from './DashboardWidget.vue';
const props = defineProps({
forecast: { type: Object, default: () => ({}) },
forecast: { type: Array, default: () => [] },
});
const hasUpcoming = computed(() => props.forecast && props.forecast.some((c) => c > 0));
@@ -24,7 +24,7 @@
) {{ ghost.accuracy }}%
.text-center.py-2.text-caption.text-grey(v-else)
| {{ $t('stats.noGhosts') }}
ScrambleText(:text="$t('stats.noGhosts')")
</template>
<script setup>
@@ -17,7 +17,8 @@
)
.text-caption.text-medium-emphasis.mt-2.text-right
| {{ masteredCount }} / {{ totalItems }} {{ $t('stats.items') }}
| {{ masteredCount }} / {{ totalItems }}
ScrambleText(:text="$t('stats.items')")
</template>
<script setup>
@@ -33,7 +34,7 @@ const totalItems = computed(
() => Object.values(props.distribution || {}).reduce((a, b) => a + b, 0),
);
const masteredCount = computed(
() => (props.distribution || {})[6] || 0,
() => [6, 7, 8, 9, 10].reduce((sum, lvl) => sum + ((props.distribution || {})[lvl] || 0), 0),
);
const masteryPercent = computed(
() => (totalItems.value === 0 ? 0 : Math.round((masteredCount.value / totalItems.value) * 100)),
@@ -5,13 +5,15 @@
)
template(#header-right)
.legend-container
span.text-caption.text-medium-emphasis.mr-1 {{ $t('stats.less') }}
span.text-caption.text-medium-emphasis.mr-1
ScrambleText(:text="$t('stats.less')")
.legend-box.level-0
.legend-box.level-1
.legend-box.level-2
.legend-box.level-3
.legend-box.level-4
span.text-caption.text-medium-emphasis.ml-1 {{ $t('stats.more') }}
span.text-caption.text-medium-emphasis.ml-1
ScrambleText(:text="$t('stats.more')")
.heatmap-container
.heatmap-year
@@ -25,7 +27,8 @@
)
.text-center
.font-weight-bold {{ formatDate(day.date) }}
div {{ $t('stats.reviewsCount', { count: day.count }) }}
div
ScrambleText(:text="$t('stats.reviewsCount', { count: day.count })")
</template>
<script setup>
@@ -19,7 +19,8 @@
.d-flex.align-end.mb-3
.text-h3.font-weight-bold.text-white.mr-2(style="line-height: 1")
| {{ streak?.current || 0 }}
.text-body-1.text-grey.mb-1 {{ $t('stats.days') }}
.text-body-1.text-grey.mb-1
ScrambleText(:text="$t('stats.days')")
.d-flex.justify-space-between.align-center.px-1
.d-flex.flex-column.align-center(
@@ -1,9 +1,11 @@
<template lang="pug">
.grid-area-full.text-center.mb-8.zen-wrapper
.text-h3.font-weight-bold.mb-2.text-white.letter-spacing-small {{ $t('hero.welcome') }}
.text-h3.font-weight-bold.mb-2.text-white.letter-spacing-small
ScrambleText(:text="$t('hero.welcome')")
.text-subtitle-1.text-grey-lighten-1.mb-10(
style="max-width: 600px; margin-inline: auto;"
) {{ $t('hero.subtitle') }}
)
ScrambleText(:text="$t('hero.subtitle')")
.d-flex.justify-center.align-stretch.flex-wrap.ga-6.mb-8
@@ -21,9 +23,9 @@
.d-flex.flex-column.align-start.mr-auto
.text-purple-lighten-1.text-h6.font-weight-bold.line-height-tight
| {{ $t('hero.lessons') }}
ScrambleText(:text="$t('hero.lessons')")
.text-purple-accent-1.text-caption.font-weight-regular.opacity-80
| {{ $t('hero.newContent') }}
ScrambleText(:text="$t('hero.newContent')")
.zen-counter.bg-purple-accent-2.text-black.ml-4
| {{ lessonCount }}
@@ -43,9 +45,9 @@
.d-flex.flex-column.align-start.mr-auto
.text-cyan-lighten-1.text-h6.font-weight-bold.line-height-tight
| {{ queueLength > 0 ? $t('hero.start') : $t('hero.noReviews') }}
ScrambleText(:text="queueLength > 0 ? $t('hero.start') : $t('hero.noReviews')")
.text-cyan-accent-1.text-caption.font-weight-regular.opacity-80
| {{ queueLength > 0 ? $t('hero.readyToReview') : $t('hero.allCaughtUp') }}
ScrambleText(:text="queueLength > 0 ? $t('hero.readyToReview') : $t('hero.allCaughtUp')")
v-fade-transition
.zen-counter.bg-cyan-accent-2.text-black.ml-4(v-if="queueLength > 0")
@@ -59,14 +61,16 @@
variant="text"
prepend-icon="mdi-sort-ascending"
:ripple="false"
) {{ $t('hero.prioritize', { count: lowerLevelCount }) }}
)
ScrambleText(:text="$t('hero.prioritize', { count: lowerLevelCount })")
v-fade-transition
.text-caption.text-grey-darken-1.font-weight-medium.d-flex.align-center.mt-2(
v-if="queueLength === 0 && !hasLowerLevels && nextReviewTime"
)
v-icon.mr-2(size="small" icon="mdi-clock-outline")
span {{ $t('hero.nextIn') }} {{ nextReviewTime }}
span
ScrambleText(:text="$t('hero.nextIn') + ' ' + nextReviewTime")
</template>
<script setup>
@@ -79,14 +83,14 @@ const props = defineProps({
lessonCount: { type: Number, default: 0 },
hasLowerLevels: { type: Boolean, default: false },
lowerLevelCount: { type: Number, default: 0 },
forecast: { type: Object, default: () => ({}) },
forecast: { type: Array, default: () => [] },
});
defineEmits(['start']);
const { t } = useI18n();
const nextReviewTime = computed(() => {
if (!props.forecast || Object.keys(props.forecast).length === 0) return null;
if (!props.forecast || props.forecast.length === 0) return null;
try {
const idx = props.forecast.findIndex((c) => c > 0);
if (idx === -1) return null;
+16 -6
View File
@@ -79,7 +79,9 @@ function handlePointerUp(e) {
if (!controller) return;
if (e.cancelable) e.preventDefault();
e.target.releasePointerCapture(e.pointerId);
try {
e.target.releasePointerCapture(e.pointerId);
} catch (_) { /* pointer was never captured */ }
controller.endStroke();
}
@@ -87,7 +89,7 @@ function handlePointerUp(e) {
onMounted(() => {
controller = new KanjiController({
size: props.size,
accuracy: store.drawingAccuracy,
config: store.resolvedStrokeConfig,
onComplete: () => emit('complete'),
onMistake: (needsHint) => {
isShaking.value = true;
@@ -110,7 +112,10 @@ onMounted(() => {
});
onBeforeUnmount(() => {
controller = null;
if (controller) {
controller.reset();
controller = null;
}
});
watch(() => props.char, (newChar) => {
@@ -129,13 +134,18 @@ watch(() => props.size, (newSize) => {
if (controller) controller.resize(newSize);
});
watch(() => store.drawingAccuracy, (newVal) => {
if (controller) controller.setAccuracy(newVal);
watch(() => store.resolvedStrokeConfig, (newConfig) => {
if (controller) controller.setConfig(newConfig);
});
defineExpose({
reset: () => controller?.reset(),
showHint: () => controller?.showHint(),
drawGuide: () => controller?.showHint(),
getLastEvaluation: () => controller?.getLastEvaluation(),
drawGuide: (enableAutoHint) => {
if (!controller) return;
if (enableAutoHint) controller.setAutoHint(true);
controller.showHint();
},
});
</script>
@@ -0,0 +1,66 @@
/**
* useStrokeEvaluation.js
*
* Vue composable that provides reactive access to stroke evaluation
* results and the resolved config from the store.
*
* Usage in a component:
* const { config, lastResult, hasFeedback } = useStrokeEvaluation();
*
* The `lastResult` ref is updated whenever a KanjiCanvas emits an
* evaluation result via its `getLastEvaluation()` exposed method.
*/
import { computed, ref } from 'vue';
import { useAppStore } from '@/stores/appStore';
export function useStrokeEvaluation() {
const store = useAppStore();
/** Resolved frozen config object, reactively derived from the store. */
const config = computed(() => store.resolvedStrokeConfig);
/** Last evaluation result, manually updated by the parent component. */
const lastResult = ref(null);
/**
* Call this after each stroke (on @complete or @mistake) to capture
* the detailed score breakdown from the canvas controller.
*
* @param {import('@/components/kanji/KanjiCanvas.vue').default} canvasRef
*/
function captureResult(canvasRef) {
if (canvasRef?.getLastEvaluation) {
lastResult.value = canvasRef.getLastEvaluation();
}
}
/** Whether we have a non-null result to display. */
const hasFeedback = computed(() => lastResult.value !== null);
/** Per-facet pass/fail booleans for UI badges. */
const facetResults = computed(() => {
if (!lastResult.value) return null;
const { scores, thresholds } = lastResult.value;
return {
proximity: scores.proximity >= thresholds.proximity,
direction: scores.direction >= thresholds.direction,
shape: scores.shape >= thresholds.shape,
length: scores.length >= thresholds.length,
curvature: scores.curvature >= thresholds.curvature,
};
});
function clear() {
lastResult.value = null;
}
return {
config,
lastResult,
hasFeedback,
facetResults,
captureResult,
clear,
};
}
+5
View File
@@ -17,6 +17,7 @@ import Dashboard from './views/Dashboard.vue';
import Collection from './views/Collection.vue';
import Review from './views/Review.vue';
import Lesson from './views/Lesson.vue';
import Vocabularies from './views/Vocabularies.vue';
const app = createApp(App);
const pinia = createPinia();
@@ -26,6 +27,7 @@ const router = createRouter({
routes: [
{ path: '/', component: Dashboard },
{ path: '/collection', component: Collection },
{ path: '/vocabularies', component: Vocabularies },
{ path: '/review', component: Review },
{ path: '/lesson', component: Lesson },
{ path: '/:pathMatch(.*)*', redirect: '/' },
@@ -51,8 +53,11 @@ const vuetify = createVuetify({
icons: { defaultSet: 'mdi', aliases, sets: { mdi } },
});
import ScrambleText from '@/components/ScrambleText.vue';
app.use(pinia);
app.use(router);
app.use(vuetify);
app.use(i18n);
app.component('ScrambleText', ScrambleText);
app.mount('#app');
+123
View File
@@ -10,6 +10,7 @@ const messages = {
dashboard: 'Dashboard',
review: 'Review',
collection: 'Collection',
vocabularies: 'Vocabularies',
settings: 'Settings',
sync: 'Sync',
logout: 'Logout',
@@ -44,8 +45,13 @@ const messages = {
learned: "You've learned {n} new kanji.",
components: 'Components',
observe: 'Observe Stroke Order',
replay: 'Replay',
trace: 'Trace',
drawStep: 'Draw ({n}/{total})',
sectionMeaning: 'Meaning',
sectionMnemonic: 'Memory Aid',
sectionReadings: 'Readings',
learnedKanji: 'Kanji Learned',
backToDashboard: 'Back to Dashboard',
},
hero: {
@@ -70,6 +76,7 @@ const messages = {
next24: 'Next 24h',
availableNow: 'Available Now',
inHours: 'In {n} hour | In {n} hours',
inDays: 'In {n} day | In {n} days',
noIncoming: 'No reviews incoming for 24 hours.',
items: 'items',
reviewsCount: '{count} reviews',
@@ -93,9 +100,14 @@ const messages = {
items: 'Items',
language: 'Language',
drawingTolerance: 'Drawing Tolerance',
strokeStrictness: 'Stroke Strictness',
advanced: 'Advanced',
strict: 'Strict',
loose: 'Loose',
save: 'Save & Close',
soundEffects: 'Sound Effects',
on: 'On',
off: 'Off',
},
review: {
meaning: 'Meaning',
@@ -109,6 +121,9 @@ const messages = {
next: 'NEXT',
sessionComplete: 'Session Complete!',
levelup: 'You leveled up your Kanji skills.',
levelsUp: '{n} leveled up',
levelsDown: '{n} leveled down',
results: 'Results',
back: 'Back to Collection',
caughtUp: 'All Caught Up!',
noReviews: 'No reviews available right now.',
@@ -130,6 +145,32 @@ const messages = {
startLesson: 'Start Lesson',
redoLesson: 'Redo Lesson',
},
vocabulary: {
searchLabel: 'Search Vocabulary...',
placeholder: "e.g. '大人', 'otona', 'adult'",
loading: 'Loading Vocabularies...',
noMatches: 'No matches found',
tryDifferent: 'Try searching for a different word or reading.',
levelHeader: 'LEVEL',
levelLabel: 'Level',
primary: 'Primary',
mnemonics: 'Mnemonics',
meaningMnemonic: 'Meaning Mnemonic',
readingMnemonic: 'Reading Mnemonic',
components: 'Component Kanji',
exampleSentences: 'Example Sentences',
filters: 'Filters',
filterType: 'Word Type',
filterVerbClass: 'Verb Class',
filterTransitivity: 'Transitivity',
filterEnding: 'Reading Ending',
filterLength: 'Character Count',
clearAll: 'Clear All',
matchCount: '{n} results',
listenMale: 'Male',
listenFemale: 'Female',
close: 'Close',
},
},
de: {
common: {
@@ -140,6 +181,7 @@ const messages = {
dashboard: 'Übersicht',
review: 'Lernen',
collection: 'Sammlung',
vocabularies: 'Vokabeln',
settings: 'Einstellungen',
sync: 'Sync',
logout: 'Abmelden',
@@ -174,8 +216,13 @@ const messages = {
learned: 'Du hast {n} neue Kanji gelernt.',
components: 'Komponenten',
observe: 'Strichfolge beobachten',
replay: 'Wiederholen',
trace: 'Nachzeichnen',
drawStep: 'Zeichnen ({n}/{total})',
sectionMeaning: 'Bedeutung',
sectionMnemonic: 'Merkhilfe',
sectionReadings: 'Lesungen',
learnedKanji: 'Gelernte Kanji',
backToDashboard: 'Zurück zur Übersicht',
},
hero: {
@@ -200,6 +247,7 @@ const messages = {
next24: 'Nächste 24h',
availableNow: 'Jetzt verfügbar',
inHours: 'In {n} Stunde | In {n} Stunden',
inDays: 'In {n} Tag | In {n} Tagen',
noIncoming: 'Keine Reviews in den nächsten 24h.',
items: 'Einträge',
reviewsCount: '{count} Reviews',
@@ -223,9 +271,14 @@ const messages = {
items: 'Einträge',
language: 'Sprache',
drawingTolerance: 'Zeichentoleranz',
strokeStrictness: 'Strichgenauigkeit',
advanced: 'Erweitert',
strict: 'Strikt',
loose: 'Locker',
save: 'Speichern & Schließen',
soundEffects: 'Soundeffekte',
on: 'An',
off: 'Aus',
},
review: {
meaning: 'Bedeutung',
@@ -239,6 +292,9 @@ const messages = {
next: 'WEITER',
sessionComplete: 'Sitzung beendet!',
levelup: 'Du hast deine Kanji-Skills verbessert.',
levelsUp: '{n} aufgestiegen',
levelsDown: '{n} abgestiegen',
results: 'Ergebnisse',
back: 'Zurück zur Sammlung',
caughtUp: 'Alles erledigt!',
noReviews: 'Gerade keine Reviews verfügbar.',
@@ -260,6 +316,32 @@ const messages = {
startLesson: 'Lektion starten',
redoLesson: 'Lektion wiederholen',
},
vocabulary: {
searchLabel: 'Vokabel suchen...',
placeholder: "z.B. '大人', 'otona', 'Erwachsener'",
loading: 'Lade Vokabeln...',
noMatches: 'Keine Treffer',
tryDifferent: 'Versuche einen anderen Suchbegriff.',
levelHeader: 'STUFE',
levelLabel: 'Stufe',
primary: 'Primär',
mnemonics: 'Eselsbrücken',
meaningMnemonic: 'Bedeutungs-Eselsbrücke',
readingMnemonic: 'Lesungs-Eselsbrücke',
components: 'Kanji-Bestandteile',
exampleSentences: 'Beispielsätze',
filters: 'Filter',
filterType: 'Wortart',
filterVerbClass: 'Verbklasse',
filterTransitivity: 'Transitivität',
filterEnding: 'Lesung-Endung',
filterLength: 'Zeichenanzahl',
clearAll: 'Zurücksetzen',
matchCount: '{n} Ergebnisse',
listenMale: 'Männlich',
listenFemale: 'Weiblich',
close: 'Schließen',
},
},
ja: {
common: {
@@ -270,6 +352,7 @@ const messages = {
dashboard: 'ダッシュボード',
review: '復習',
collection: 'コレクション',
vocabularies: '単語',
settings: '設定',
sync: '同期',
logout: 'ログアウト',
@@ -304,8 +387,13 @@ const messages = {
learned: '{n}個の新しい漢字を覚えました。',
components: '構成要素',
observe: '書き順を見る',
replay: 'もう一度',
trace: 'なぞる',
drawStep: '書く ({n}/{total})',
sectionMeaning: '意味',
sectionMnemonic: '覚え方',
sectionReadings: '読み方',
learnedKanji: '学んだ漢字',
backToDashboard: 'ダッシュボードに戻る',
},
hero: {
@@ -330,6 +418,7 @@ const messages = {
next24: '今後24時間',
availableNow: '今すぐ可能',
inHours: '{n}時間後',
inDays: '{n}日後',
noIncoming: '24時間以内のレビューはありません。',
items: '個',
reviewsCount: '{count} レビュー',
@@ -353,9 +442,14 @@ const messages = {
items: '個',
language: '言語 (Language)',
drawingTolerance: '描画許容範囲',
strokeStrictness: '筆画の厳密さ',
advanced: '詳細設定',
strict: '厳しい',
loose: '甘い',
save: '保存して閉じる',
soundEffects: '効果音',
on: 'オン',
off: 'オフ',
},
review: {
meaning: '意味',
@@ -369,6 +463,9 @@ const messages = {
next: '次へ',
sessionComplete: 'セッション完了!',
levelup: '漢字力がアップしました。',
levelsUp: '{n}個昇格',
levelsDown: '{n}個降格',
results: '結果',
back: 'コレクションに戻る',
caughtUp: '完了しました!',
noReviews: '現在レビューするものはありません。',
@@ -390,6 +487,32 @@ const messages = {
startLesson: 'レッスン開始',
redoLesson: 'レッスンをやり直す',
},
vocabulary: {
searchLabel: '単語を検索...',
placeholder: "例: '大人', 'おとな', 'adult'",
loading: '単語を読み込み中...',
noMatches: '見つかりませんでした',
tryDifferent: '別のキーワードで検索してください。',
levelHeader: 'レベル',
levelLabel: 'レベル',
primary: '主要',
mnemonics: '覚え方',
meaningMnemonic: '意味の覚え方',
readingMnemonic: '読みの覚え方',
components: '構成漢字',
exampleSentences: '例文',
filters: 'フィルター',
filterType: '品詞',
filterVerbClass: '動詞の種類',
filterTransitivity: '自他',
filterEnding: '読みの語尾',
filterLength: '文字数',
clearAll: 'クリア',
matchCount: '{n}件',
listenMale: '男性',
listenFemale: '女性',
close: '閉じる',
},
},
};
+57 -8
View File
@@ -1,4 +1,5 @@
import { defineStore } from 'pinia';
import { createConfig, migrateOldAccuracy } from '@/utils/StrokeConfig.js';
const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000';
@@ -9,6 +10,7 @@ export const useAppStore = defineStore('app', {
queue: [],
lessonQueue: [],
collection: [],
vocabularies: [],
stats: {
distribution: {},
forecast: [],
@@ -19,10 +21,25 @@ export const useAppStore = defineStore('app', {
ghosts: [],
},
batchSize: parseInt(localStorage.getItem('zen_batch_size'), 10) || 20,
drawingAccuracy: parseInt(localStorage.getItem('zen_drawing_accuracy'), 10) || 10,
strokeStrictness: (() => {
// Try new format first, then migrate from old drawingAccuracy
const saved = localStorage.getItem('zen_stroke_strictness');
if (saved) {
try { return JSON.parse(saved); } catch (e) { /* fall through */ }
}
const oldVal = parseInt(localStorage.getItem('zen_drawing_accuracy'), 10);
if (oldVal) return migrateOldAccuracy(oldVal);
return { preset: 'medium', overrides: {} };
})(),
loading: false,
}),
getters: {
resolvedStrokeConfig(state) {
return createConfig(state.strokeStrictness.preset, state.strokeStrictness.overrides);
},
},
actions: {
async login(apiKey) {
const res = await fetch(`${BASE_URL}/api/auth/login`, {
@@ -39,10 +56,16 @@ export const useAppStore = defineStore('app', {
if (data.user.settings) {
this.batchSize = data.user.settings.batchSize || 20;
this.drawingAccuracy = data.user.settings.drawingAccuracy || 10;
// Handle both old and new settings format from server
if (data.user.settings.strokeStrictness) {
this.strokeStrictness = data.user.settings.strokeStrictness;
} else if (data.user.settings.drawingAccuracy) {
this.strokeStrictness = migrateOldAccuracy(data.user.settings.drawingAccuracy);
}
localStorage.setItem('zen_batch_size', this.batchSize);
localStorage.setItem('zen_drawing_accuracy', this.drawingAccuracy);
localStorage.setItem('zen_stroke_strictness', JSON.stringify(this.strokeStrictness));
}
localStorage.setItem('zen_token', data.token);
@@ -70,10 +93,22 @@ export const useAppStore = defineStore('app', {
this.token = '';
this.user = null;
this.queue = [];
this.stats = {};
this.lessonQueue = [];
this.collection = [];
this.vocabularies = [];
this.stats = {
distribution: {},
forecast: [],
queueLength: 0,
lessonCount: 0,
streak: {},
accuracy: {},
ghosts: [],
};
localStorage.removeItem('zen_token');
localStorage.removeItem('zen_batch_size');
localStorage.removeItem('zen_drawing_accuracy');
localStorage.removeItem('zen_stroke_strictness');
localStorage.removeItem('zen_drawing_accuracy'); // clean up old key
},
getHeaders() {
@@ -151,6 +186,20 @@ export const useAppStore = defineStore('app', {
this.collection = await res.json();
},
async fetchVocabularies() {
if (!this.token) return;
const res = await fetch(`${BASE_URL}/api/vocabulary`, { headers: this.getHeaders() });
if (res.status === 401) {
await this.logout();
return;
}
const data = await res.json();
this.vocabularies = Array.isArray(data) ? data : [];
},
async submitReview(subjectId, success) {
const res = await fetch(`${BASE_URL}/api/review`, {
method: 'POST',
@@ -186,9 +235,9 @@ export const useAppStore = defineStore('app', {
this.batchSize = settings.batchSize;
localStorage.setItem('zen_batch_size', settings.batchSize);
}
if (settings.drawingAccuracy !== undefined) {
this.drawingAccuracy = settings.drawingAccuracy;
localStorage.setItem('zen_drawing_accuracy', settings.drawingAccuracy);
if (settings.strokeStrictness !== undefined) {
this.strokeStrictness = settings.strokeStrictness;
localStorage.setItem('zen_stroke_strictness', JSON.stringify(settings.strokeStrictness));
}
await fetch(`${BASE_URL}/api/settings`, {
+7
View File
@@ -67,3 +67,10 @@ html,
opacity: 0;
transform: translateY(-15px);
}
// Fix for flickering on mobile during transitions
.v-navigation-drawer--temporary,
.v-dialog > .v-card {
-webkit-transform: translateZ(0);
transform: translateZ(0);
}
+1 -1
View File
@@ -99,7 +99,7 @@
.readings-container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
gap: $spacing-sm;
text-align: left;
+1
View File
@@ -1,4 +1,5 @@
@use 'dashboard';
@use 'collection';
@use 'vocabularies';
@use 'review';
@use 'lesson';
+148
View File
@@ -19,3 +19,151 @@
.lesson-canvas-wrapper {
margin: 0 auto;
}
// ── Phase Stepper ──────────────────────────────────
.lesson-stepper {
display: flex;
align-items: flex-start;
gap: 0;
}
.lesson-stepper__step {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
flex-shrink: 0;
}
.lesson-stepper__dot {
width: 1.75rem;
height: 1.75rem;
border-radius: $radius-circle;
display: flex;
align-items: center;
justify-content: center;
font-size: $font-xs;
font-weight: $weight-bold;
background: $color-surface-light;
color: $color-text-grey;
border: $border-width-md solid transparent;
transition: all $duration-normal $ease-default;
.is-active & {
background: $color-primary;
color: $color-text-dark;
border-color: $color-primary;
box-shadow: $shadow-glow-base;
}
.is-done & {
background: $color-success;
color: $color-text-white;
border-color: $color-success;
}
}
.lesson-stepper__label {
font-size: 0.6rem;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: $weight-bold;
color: $color-text-grey;
opacity: $opacity-inactive;
transition: opacity $duration-normal $ease-default;
white-space: nowrap;
.is-active & {
opacity: $opacity-active;
color: $color-primary;
}
.is-done & {
opacity: 0.6;
color: $color-success;
}
}
.lesson-stepper__line {
flex: 1;
height: $border-width-md;
background: $color-surface-light;
border-radius: $radius-pill;
transition: background $duration-normal $ease-default;
margin: 0.875rem $spacing-xs 0;
&.is-done {
background: $color-success;
}
}
// ── Practice Dots ──────────────────────────────────
.practice-dots {
display: flex;
gap: $spacing-sm;
justify-content: center;
}
.practice-dot {
width: 0.625rem;
height: 0.625rem;
border-radius: $radius-circle;
background: $color-surface-light;
transition: all $duration-normal $ease-out-back;
&.is-filled {
background: $color-primary;
box-shadow: 0 0 0.5rem $color-primary;
transform: scale(1.2);
}
}
// ── Mnemonic Box ───────────────────────────────────
.mnemonic-box {
width: 100%;
background: $bg-glass-subtle;
border: $border-width-sm solid $color-border;
}
// ── Lesson Summary ─────────────────────────────────
.lesson-summary-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr));
gap: $spacing-sm;
}
.lesson-summary-item {
display: flex;
flex-direction: column;
align-items: center;
padding: $spacing-md $spacing-sm;
background: $bg-glass-subtle;
border-radius: $radius-lg;
border: $border-width-sm solid $color-border;
transition: border-color $duration-normal $ease-default;
&:hover {
border-color: $color-primary;
}
}
.lesson-summary-char {
font-size: $font-xl;
font-weight: $weight-bold;
margin-bottom: $spacing-xs;
}
.lesson-summary-meaning {
font-size: $font-xs;
text-align: center;
}
// ── Utility ────────────────────────────────────────
.stepper-spacer {
width: 2.5rem;
}
+322
View File
@@ -0,0 +1,322 @@
@use '../abstracts' as *;
// ── Page layout ──────────────────────────────────────────────
.vocab-page {
max-width: $max-width-desktop;
}
// ── Toolbar ──────────────────────────────────────────────────
.vocab-toolbar {
display: flex;
gap: $spacing-sm;
align-items: stretch;
position: sticky;
top: $spacing-sm;
z-index: $z-sticky;
margin-bottom: $spacing-md;
.vocab-search {
flex: 1;
box-shadow: $shadow-md;
border-radius: $radius-sm;
}
.vocab-filter-toggle {
height: auto !important;
min-width: 7rem;
border-radius: $radius-sm;
font-weight: $weight-bold;
font-size: $font-xs;
text-transform: none;
letter-spacing: $tracking-normal;
}
}
// ── Active filter chips bar ──────────────────────────────────
.vocab-active-filters {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: $spacing-xs 0;
margin-bottom: $spacing-sm;
}
// ── Filter panel ─────────────────────────────────────────────
.vocab-filter-panel {
background: $bg-glass-dark;
border: $border-width-sm solid $color-border;
border-radius: $radius-lg;
padding: $spacing-md $spacing-lg;
margin-bottom: $spacing-lg;
.filter-section {
margin-bottom: $spacing-md;
&:last-of-type {
margin-bottom: $spacing-sm;
}
.filter-label {
font-size: $font-xs;
font-weight: $weight-bold;
color: $color-text-grey;
text-transform: uppercase;
letter-spacing: $tracking-wider;
margin-bottom: $spacing-xs;
}
.filter-chips {
display: flex;
flex-wrap: wrap;
gap: $spacing-xs;
}
}
.filter-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: $spacing-sm;
border-top: $border-width-sm solid $color-border;
}
}
// ── Level header ─────────────────────────────────────────────
.vocab-level-header {
display: flex;
align-items: center;
gap: $spacing-sm;
margin-bottom: $spacing-md;
.vocab-level-badge {
font-size: $font-xs;
font-weight: $weight-bold;
color: $color-primary;
text-transform: uppercase;
letter-spacing: $tracking-wider;
white-space: nowrap;
padding: $spacing-2xs $spacing-sm;
background: rgba(0, 206, 201, 0.1);
border-radius: $radius-sm;
border: $border-width-sm solid rgba(0, 206, 201, 0.2);
}
.vocab-level-line {
flex: 1;
height: 1px;
background: $color-border;
}
.vocab-level-count {
font-size: $font-xs;
color: $color-text-grey;
white-space: nowrap;
}
}
// ── Grid ─────────────────────────────────────────────────────
.vocab-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
gap: $spacing-sm;
@media (max-width: $breakpoint-sm) {
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}
}
// ── Card ─────────────────────────────────────────────────────
.vocab-card {
@include card-base;
@include hover-lift;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: $spacing-md $spacing-sm $spacing-sm;
cursor: pointer;
min-height: 5.5rem;
overflow: hidden;
border: $border-width-sm solid transparent;
transition:
transform $duration-fast $ease-default,
background $duration-fast $ease-default,
box-shadow $duration-fast $ease-default,
border-color $duration-fast $ease-default;
&:hover {
border-color: rgba(0, 206, 201, 0.3);
}
.vc-top {
display: flex;
flex-direction: column;
align-items: center;
gap: $spacing-2xs;
margin-bottom: $spacing-xs;
}
.vc-char {
font-size: $font-lg;
font-weight: $weight-bold;
color: $color-primary;
line-height: $leading-tight;
text-align: center;
word-break: break-all;
overflow-wrap: anywhere;
}
.vc-reading {
font-size: $font-xs;
color: $color-text-grey;
text-align: center;
opacity: 0.7;
}
.vc-bottom {
display: flex;
flex-direction: column;
align-items: center;
gap: $spacing-2xs;
}
.vc-meaning {
font-size: $font-xs;
color: $color-text-grey;
text-align: center;
line-height: $leading-normal;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.vc-tags {
display: flex;
gap: $spacing-2xs;
justify-content: center;
flex-wrap: wrap;
.vc-tag {
font-size: 0.5625rem;
font-weight: $weight-bold;
color: rgba(0, 206, 201, 0.7);
background: rgba(0, 206, 201, 0.08);
padding: 0 $spacing-xs;
border-radius: $radius-xs;
line-height: 1.4rem;
letter-spacing: $tracking-wide;
text-transform: uppercase;
}
}
}
// ── Detail modal ─────────────────────────────────────────────
.vocab-detail-char {
font-family: 'Noto Serif JP', serif;
letter-spacing: $tracking-wide;
color: $color-primary;
}
.vocab-readings-container {
display: flex;
flex-wrap: wrap;
gap: $spacing-sm;
justify-content: center;
.vocab-reading-group {
display: flex;
align-items: center;
background: $bg-glass-dark;
padding: $spacing-xs $spacing-md;
border-radius: $radius-md;
.vocab-reading-value {
font-size: $font-md;
color: $color-text-grey;
&.is-primary {
color: $color-text-white;
font-weight: $weight-bold;
}
}
}
}
.vocab-mnemonic-section {
background: $bg-glass-dark;
padding: $spacing-md;
border-radius: $radius-md;
.mnemonic-text {
line-height: $leading-loose;
}
}
.vocab-components-section {
text-align: center;
}
.vocab-collapsible {
.vocab-collapse-toggle {
text-transform: none;
letter-spacing: $tracking-normal;
font-size: $font-sm;
border: $border-width-sm solid $color-border;
border-radius: $radius-md;
}
.vocab-collapse-content {
margin-top: $spacing-sm;
}
}
.vocab-audio-row {
display: flex;
justify-content: center;
gap: $spacing-sm;
}
.vocab-audio-btn {
transition: all $duration-fast $ease-default;
text-transform: none !important;
letter-spacing: $tracking-normal !important;
&:hover:not(:disabled) {
box-shadow: $shadow-glow-base;
}
}
.vocab-sentences-section {
.vocab-sentence {
background: $bg-glass-dark;
padding: $spacing-md;
border-radius: $radius-md;
margin-bottom: $spacing-sm;
&:last-child {
margin-bottom: 0;
}
.vocab-sentence-ja {
font-size: $font-md;
color: $color-text-white;
margin-bottom: $spacing-xs;
line-height: $leading-normal;
}
.vocab-sentence-en {
font-size: $font-sm;
color: $color-text-grey;
line-height: $leading-normal;
font-style: italic;
}
}
}

Some files were not shown because too many files have changed in this diff Show More