1 Commits

Author SHA1 Message Date
Sebastian Molenda d406f10206 Lekcja matmy - dodawanie 1 2026-05-26 21:19:14 +02:00
688 changed files with 1733 additions and 28745 deletions
-37
View File
@@ -1,37 +0,0 @@
name: Build APK
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
container:
image: cimg/android:2024.01
steps:
- name: Install Node.js
run: |
sudo apt-get update
sudo apt-get install -y curl
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
- name: Checkout
uses: actions/checkout@v4
- name: Generate Gradle Wrapper
run: gradle wrapper
- name: Grant execute permission
run: chmod +x ./gradlew
- name: Build APK
run: ./gradlew assembleRelease
- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: app-release
path: app/build/outputs/apk/release/app-release.apk
+1 -1
View File
@@ -25,6 +25,6 @@ jobs:
server: ${{ secrets.FTP_HOST }}
username: ${{ secrets.FTP_USER }}
password: ${{ secrets.FTP_PASS }}
local-dir: app/src/main/assets/
local-dir: ./
server-dir: /
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.
@@ -1,2 +0,0 @@
#Wed May 27 00:12:10 CEST 2026
gradle.version=8.5
Binary file not shown.
Binary file not shown.
View File
-115
View File
@@ -1,115 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="NONE" />
</component>
<component name="ChangeListManager">
<list default="true" id="51538617-7e5b-4e71-9f47-7bda274cf4cc" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.gitea/workflows/build-apk.yml" beforeDir="false" afterPath="$PROJECT_DIR$/.gitea/workflows/build-apk.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/executionHistory/executionHistory.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/executionHistory/executionHistory.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/executionHistory/executionHistory.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/executionHistory/executionHistory.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/fileHashes/fileHashes.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/fileHashes/fileHashes.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/fileHashes/fileHashes.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/fileHashes/fileHashes.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/fileHashes/resourceHashesCache.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/fileHashes/resourceHashesCache.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/buildOutputCleanup/buildOutputCleanup.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/buildOutputCleanup/buildOutputCleanup.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/file-system.probe" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/file-system.probe" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/components.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/components.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/dzielenie.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/dzielenie.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/mnozenie.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/mnozenie.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/components.js.jar" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/components.js.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/dzielenie.js.jar" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/dzielenie.js.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/mnozenie.js.jar" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/mnozenie.js.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_3/graph.bin" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_3/graph.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/dex/debug/mergeProjectDexDebug/6/classes.dex" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/dex/debug/mergeProjectDexDebug/6/classes.dex" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/incremental/packageDebug/tmp/debug/dex-renamer-state.txt" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/incremental/packageDebug/tmp/debug/dex-renamer-state.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/project_dex_archive/debug/out/com/example/app/MainActivity.dex" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/project_dex_archive/debug/out/com/example/app/MainActivity.dex" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/last-build.bin" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/last-build.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/local-state/build-history.bin" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/local-state/build-history.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/outputs/apk/debug/app-debug.apk" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/outputs/apk/debug/app-debug.apk" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/tmp/kotlin-classes/debug/com/example/app/MainActivity.class" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/tmp/kotlin-classes/debug/com/example/app/MainActivity.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/assets/js/components.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/assets/js/components.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/assets/js/dzielenie.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/assets/js/dzielenie.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/assets/js/mnozenie.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/assets/js/mnozenie.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/app/MainActivity.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/app/MainActivity.kt" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 2,
&quot;fromUser&quot;: false
}</component>
<component name="ProjectId" id="3EHNE822z12n2x6rUf57MsHEkbE" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.readMode.enableVisualFormatting&quot;: &quot;true&quot;,
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/Users/aln/Work/Matma&quot;
}
}</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="51538617-7e5b-4e71-9f47-7bda274cf4cc" name="Changes" comment="" />
<created>1779831422173</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1779831422173</updated>
</task>
<servers />
</component>
</project>
-38
View File
@@ -1,38 +0,0 @@
package com.example.app
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import androidx.webkit.WebViewAssetLoader
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
// Use WebViewAssetLoader to serve files from /assets/ over a secure origin.
val assetLoader = WebViewAssetLoader.Builder()
.addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(this))
.build()
webView.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
if (request == null) return null
return assetLoader.shouldInterceptRequest(request.url)
}
}
// Load the app via the mapped secure origin so fetch() requests are allowed
webView.loadUrl("https://appassets.androidplatform.net/assets/index.html")
setContentView(webView)
}
}
-11
View File
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" packagePrefix="com.example.app" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
Submodule QuizzyTemplate/App added at 149f214357
+410
View File
@@ -0,0 +1,410 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
:root {
--primary: #6366F1;
--primary-dark: #4F46E5;
--secondary: #8B5CF6;
--accent: #FFD166;
--text: #334155;
--text-light: #64748B;
--bg: #F8FAFC;
--bg-card: #FFFFFF;
--border: #E2E8F0;
--code-bg: #F1F5F9;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg);
color: var(--text);
line-height: 1.6;
padding: 0;
margin: 0;
}
.container {
max-width: 1100px;
margin: 0 auto;
padding: 0 20px;
}
header {
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: white;
padding: 60px 0 80px;
position: relative;
}
header::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 4px;
background: var(--accent);
}
.logo {
font-size: 24px;
font-weight: 700;
display: flex;
align-items: center;
margin-bottom: 20px;
}
.logo-icon {
margin-right: 10px;
font-size: 28px;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.3;
margin-bottom: 20px;
color: var(--text);
}
h1 {
font-size: 2.5em;
margin-bottom: 15px;
color: white;
}
h2 {
font-size: 1.8em;
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
margin-top: 50px;
}
h3 {
font-size: 1.4em;
margin-top: 30px;
}
h4 {
font-size: 1.2em;
margin-top: 20px;
}
p {
margin-bottom: 16px;
}
a {
color: var(--primary);
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: var(--primary-dark);
text-decoration: underline;
}
.header-subtitle {
font-size: 1.2em;
color: rgba(255, 255, 255, 0.9);
max-width: 700px;
}
.content {
padding: 60px 0;
position: relative;
}
.toc {
background-color: var(--bg-card);
border-radius: 10px;
padding: 25px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
margin-bottom: 40px;
}
.toc-title {
font-size: 1.2em;
font-weight: 600;
margin-bottom: 15px;
color: var(--text);
display: flex;
align-items: center;
}
.toc-title svg {
margin-right: 10px;
}
.toc-list {
list-style-type: none;
columns: 2;
column-gap: 30px;
}
.toc-list li {
margin-bottom: 10px;
break-inside: avoid;
}
.toc-list a {
color: var(--text-light);
transition: color 0.2s;
display: flex;
align-items: center;
}
.toc-list a:hover {
color: var(--primary);
}
.toc-list a::before {
content: "•";
margin-right: 8px;
color: var(--primary);
font-weight: bold;
}
.section {
margin-bottom: 60px;
}
ul, ol {
margin-left: 20px;
margin-bottom: 20px;
}
li {
margin-bottom: 8px;
}
code {
font-family: 'Monaco', 'Consolas', monospace;
background-color: var(--code-bg);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9em;
}
pre {
background-color: var(--code-bg);
padding: 15px;
border-radius: 8px;
overflow-x: auto;
margin-bottom: 20px;
border-left: 4px solid var(--primary);
}
pre code {
background: none;
padding: 0;
font-size: 0.9em;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.feature-card {
background: var(--bg-card);
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.feature-icon {
font-size: 24px;
margin-bottom: 12px;
color: var(--primary);
}
.note {
background-color: rgba(99, 102, 241, 0.1);
border-left: 4px solid var(--primary);
padding: 15px;
border-radius: 0 8px 8px 0;
margin-bottom: 20px;
}
.warning {
background-color: rgba(255, 209, 102, 0.2);
border-left: 4px solid var(--accent);
padding: 15px;
border-radius: 0 8px 8px 0;
margin-bottom: 20px;
}
.project-structure {
font-family: 'Monaco', 'Consolas', monospace;
background-color: var(--code-bg);
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
white-space: pre;
overflow-x: auto;
line-height: 1.4;
font-size: 0.85em;
}
.code-filename {
color: var(--primary);
font-weight: 600;
margin-bottom: 5px;
font-size: 0.9em;
}
.steps {
list-style-type: none;
counter-reset: step-counter;
margin-left: 0;
}
.steps li {
counter-increment: step-counter;
margin-bottom: 15px;
position: relative;
padding-left: 45px;
}
.steps li::before {
content: counter(step-counter);
background-color: var(--primary);
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
left: 0;
top: -1px;
font-weight: 600;
font-size: 0.9em;
}
.table-container {
overflow-x: auto;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid var(--border);
}
th {
background-color: var(--code-bg);
font-weight: 600;
}
tr:hover {
background-color: rgba(0, 0, 0, 0.01);
}
.badge {
display: inline-block;
background-color: var(--primary);
color: white;
border-radius: 20px;
padding: 3px 10px;
font-size: 0.75em;
font-weight: 600;
margin-right: 5px;
}
.badge.secondary {
background-color: var(--secondary);
}
.badge.accent {
background-color: var(--accent);
color: var(--text);
}
.screenshot {
max-width: 100%;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
footer {
background-color: var(--text);
color: white;
padding: 30px 0;
text-align: center;
margin-top: 100px;
}
.back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
background-color: var(--primary);
color: white;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.back-to-top.visible {
opacity: 1;
visibility: visible;
}
.back-to-top:hover {
background-color: var(--primary-dark);
transform: translateY(-3px);
}
@media (max-width: 768px) {
h1 {
font-size: 2em;
}
h2 {
font-size: 1.5em;
}
.toc-list {
columns: 1;
}
.container {
padding: 0 15px;
}
.feature-grid {
grid-template-columns: 1fr;
}
}
+397
View File
@@ -0,0 +1,397 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QuizzyMind App Documentation</title>
<link rel="stylesheet" href="app.css">
</head>
<body>
<header>
<div class="container">
<div class="logo">
<span class="logo-icon">🎮</span>
Quizzy<span style="color: var(--accent);">Mind</span>
</div>
<h1>QuizzyMind App Documentation</h1>
<p class="header-subtitle">A comprehensive guide to installing, configuring, and customizing your React Native Expo quiz application.</p>
</div>
</header>
<div class="container">
<div class="content">
<div class="toc" id="table-of-contents">
<h3 class="toc-title">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
Table of Contents
</h3>
<ul class="toc-list">
<li><a href="#introduction">Introduction</a></li>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#installation">Installation</a></li>
<li><a href="#project-structure">Project Structure</a></li>
<li><a href="#configuration">Configuration</a></li>
<li><a href="#key-features">Key Features</a></li>
<li><a href="#customization-guide">Customization Guide</a></li>
<li><a href="#api-integration">API Integration</a></li>
<li><a href="#deployment">Deployment</a></li>
<li><a href="#troubleshooting">Troubleshooting</a></li>
<li><a href="#support">Support</a></li>
</ul>
</div>
<section class="section" id="introduction">
<h2>Introduction</h2>
<p>QuizzyMind is a fully functional quiz application built with React Native and Expo Router. This documentation will guide you through setting up and customizing the application to meet your specific needs.</p>
<h3>Key Features</h3>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon">🧠</div>
<h4>Multiple Quiz Categories</h4>
<p>Extensive question bank with various categories</p>
</div>
<div class="feature-card">
<div class="feature-icon">🏆</div>
<h4>Real-time Leaderboards</h4>
<p>Compete with friends and track progress</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎨</div>
<h4>Modern UI</h4>
<p>Clean design with smooth animations</p>
</div>
<div class="feature-card">
<div class="feature-icon">🧭</div>
<h4>Expo Router</h4>
<p>Optimized navigation experience</p>
</div>
<div class="feature-card">
<div class="feature-icon">📱</div>
<h4>Cross-platform</h4>
<p>Works on both iOS and Android</p>
</div>
</div>
</section>
<section class="section" id="requirements">
<h2>Requirements</h2>
<p>Before you begin, ensure you have the following installed:</p>
<ul>
<li><strong>Node.js</strong> (v14.0.0 or newer)</li>
<li><strong>npm</strong> (v6.0.0 or newer) or <strong>Yarn</strong> (v1.22.0 or newer)</li>
<li><strong>Expo CLI</strong> (<code>npm install -g expo-cli</code>)</li>
<li><strong>Android Studio</strong> (for Android development)</li>
<li><strong>Xcode</strong> (for iOS development, macOS only)</li>
<li><strong>Git</strong></li>
</ul>
<div class="note">
<p><strong>Note:</strong> You can verify your Node.js and npm versions by running <code>node -v</code> and <code>npm -v</code> in your terminal.</p>
</div>
</section>
<section class="section" id="installation">
<h2>Installation</h2>
<p>Follow these steps to get the app up and running:</p>
<ol class="steps">
<li>
<strong>Clone or extract the project files</strong>
<pre><code># If you downloaded as a zip, extract it
cd QuizzyMind-app</code></pre>
</li>
<li>
<strong>Install dependencies</strong>
<pre><code>npm install
# or with yarn
yarn install</code></pre>
</li>
<li>
<strong>Start the development server</strong>
<pre><code>npx expo start or npm start</code></pre>
</li>
<li>
<strong>Run on device/simulator</strong>
<ul>
<li>Press <code>i</code> to open in iOS simulator</li>
<li>Press <code>a</code> to open in Android emulator</li>
<li>Scan QR code with Expo Go app on physical device</li>
</ul>
</li>
</ol>
<div class="note">
<p><strong>Tip:</strong> If you encounter any issues during installation, refer to the <a href="#troubleshooting">Troubleshooting</a> section.</p>
</div>
</section>
<section class="section" id="project-structure">
<h2>Project Structure</h2>
<div class="project-structure">QuizzyMind-app/
├── app/ # Main application code using Expo Router
│ ├── (pages)/ # All screens inside this folder
│ ├────────── _layout.js # Root layout component
│ ├────────── CategoryScreen.jsx # Category Screen
│ ├────────── QuizScreen.jsx # QuizScreen Screen
│ ├────────── ResultScreen.jsx # ResultScreen Screen
│ ├── _layout.js # Root layout component
│ └── index.js # Home screen
├── assets/ # Static assets like images, fonts
├── components/ # Reusable UI components
│ ├── common/ # Shared components
│ ├── quiz/ # Quiz-related components
│ └── ui/ # UI elements (buttons, cards, etc.)
├── constants/ # App constants and theme settings
├── contexts/ # React contexts for state management
├── hooks/ # Custom React hooks
├── services/ # API services and external integrations
├── utils/ # Utility functions
├── app.json # Expo configuration
├── babel.config.js # Babel configuration
├── package.json # Dependencies and scripts
└── README.md # Basic readme</div>
</section>
<section class="section" id="key-features">
<h2>Key Features</h2>
<h3>Quiz Engine</h3>
<p>The quiz system supports:</p>
<ul>
<li>Multiple quiz categories</li>
<li>Various question types (multiple choice, true/false)</li>
<li>Timed quizzes</li>
<li>Score tracking and history</li>
<li>Difficulty levels</li>
</ul>
<h3>User Interface</h3>
<p>The UI is built with:</p>
<ul>
<li>Custom components for consistent design</li>
<li>Smooth animations and transitions</li>
<li>Dark/light mode support</li>
<li>Responsive layouts for different device sizes</li>
</ul>
</section>
<section class="section" id="customization-guide">
<h2>Customization Guide</h2>
<h3>Theme Customization</h3>
<p>Edit the theme variables in <code>constants/Theme.js</code>:</p>
<div class="code-filename">constants/Theme.js</div>
<pre><code>// Example theme customization
export const COLORS = {
primary: '#6366F1',
secondary: '#8B5CF6',
accent: '#FFD166',
// Add your custom colors
};
export const FONTS = {
// Customize font families
};
export const SIZES = {
// Customize spacing and sizes
};</code></pre>
<h3>Content Customization</h3>
<p>Quiz questions are stored in <code>data/quizData.js</code>. Modify this file to add your own questions:</p>
<div class="code-filename">data/quizData.js</div>
<pre><code>export const QUIZ_CATEGORIES = [
{
id: 'general',
title: 'General Knowledge',
icon: 'brain',
questions: [
{
id: 'q1',
question: 'What is the capital of France?',
options: ['London', 'Berlin', 'Paris', 'Madrid'],
correctAnswer: 'Paris',
difficulty: 'easy'
},
// Add more questions here
]
},
// Add more categories
];</code></pre>
<h3>Adding New Screens</h3>
<p>To add a new screen with Expo Router:</p>
<ol>
<li>Create a new file in the <code>app</code> directory, e.g., <code>app/newScreen.js</code></li>
<li>Define your screen component:</li>
</ol>
<div class="code-filename">app/newScreen.js</div>
<pre><code>import { View, Text, StyleSheet } from 'react-native';
import { useRouter } from 'expo-router';
export default function NewScreen() {
const router = useRouter();
return (
&lt;View style={styles.container}&gt;
&lt;Text&gt;New Screen Content&lt;/Text&gt;
&lt;/View&gt;
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});</code></pre>
</section>
<section class="section" id="deployment">
<h2>Deployment</h2>
<h3>Publishing to Expo</h3>
<ol>
<li>Create an Expo account at <a href="https://expo.dev" target="_blank">expo.dev</a></li>
<li>Configure your app in <code>app.json</code>:
<ul>
<li>Update <code>name</code>, <code>slug</code>, and <code>owner</code></li>
<li>Set appropriate version numbers</li>
<li>Configure splash screen and icons</li>
</ul>
</li>
<li>Build and publish:
<pre><code>expo build:android # For Android APK/AAB
expo build:ios # For iOS IPA
expo publish # For Expo Go distribution</code></pre>
</li>
</ol>
<h3>Submitting to App Stores</h3>
<h4>Android (Google Play)</h4>
<ol>
<li>Generate a signed AAB:
<pre><code>eas build -p android --profile production</code></pre>
</li>
<li>Follow Google Play Console submission guidelines</li>
</ol>
<h4>iOS (App Store)</h4>
<ol>
<li>Generate a signed IPA:
<pre><code>eas build -p ios --profile production</code></pre>
</li>
<li>Submit through App Store Connect</li>
</ol>
</section>
<section class="section" id="troubleshooting">
<h2>Troubleshooting</h2>
<h3>Common Issues</h3>
<h4>Metro Bundler Issues</h4>
<pre><code># Clear cache and restart
expo start -c</code></pre>
<h4>Dependency Problems</h4>
<pre><code># Reset node_modules
rm -rf node_modules
npm install</code></pre>
<h4>Expo SDK Version Conflicts</h4>
<p>Ensure all packages are compatible with your Expo SDK version in <code>package.json</code>.</p>
<div class="note">
<p><strong>Tip:</strong> Most common issues can be resolved by clearing your cache and reinstalling dependencies.</p>
</div>
</section>
<section class="section" id="support">
<h2>Support</h2>
<p>For additional support, please contact us at <a href="mailto:support@email.com">support@email.com</a> or visit our support page on TemplateMonster.</p>
</section>
</div>
</div>
<footer>
<div class="container">
<p>© 2025 QuizzyMind App | All Rights Reserved</p>
</div>
</footer>
<a href="#" class="back-to-top" id="backToTop">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 15l-6-6-6 6"/>
</svg>
</a>
<script>
// Back to top button functionality
const backToTopButton = document.getElementById('backToTop');
window.addEventListener('scroll', () => {
if (window.pageYOffset > 300) {
backToTopButton.classList.add('visible');
} else {
backToTopButton.classList.remove('visible');
}
});
backToTopButton.addEventListener('click', (e) => {
e.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
});
// Smooth scrolling for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
if (this.getAttribute('href') === '#') return;
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth'
});
}
});
});
</script>
</body>
</html>
+6
View File
@@ -0,0 +1,6 @@
{
"name": "QuizzyTemplate",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
+1 -1
View File
@@ -55,7 +55,7 @@ const statusEl = document.getElementById('status');
// load settings from localStorage
function loadSettings(){
try{
let progressInner = null
const raw = localStorage.getItem('matma:settings')
if (raw) {
const s = JSON.parse(raw)
state.settings = Object.assign(state.settings, s)
-39
View File
@@ -1,39 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
namespace 'com.example.app'
compileSdk 34
defaultConfig {
applicationId "com.example.app"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.22"
implementation 'androidx.webkit:webkit:1.8.0'
}
@@ -1,2 +0,0 @@
#- File Locator -
listingFile=../../../outputs/apk/debug/output-metadata.json
@@ -1,2 +0,0 @@
appMetadataVersion=1.1
androidGradlePluginVersion=8.2.2
@@ -1 +0,0 @@
<footer class="app-footer">Version: <span id="commit-sha">(loading)</span></footer>
@@ -1,5 +0,0 @@
<!-- Uniwersalny nagłówek aplikacji/substrony -->
<header class="subpage-header">
<h1 class="subpage-title">{TITLE}</h1>
<p class="subpage-subtitle">{SUBTITLE}</p>
</header>
@@ -1,4 +0,0 @@
<!-- Uniwersalny pasek postępu -->
<div class="progress-bar">
<div class="progress-bar-inner" id="{PROGRESS_ID}"></div>
</div>
@@ -1,27 +0,0 @@
@font-face {
font-family: 'Inter';
src: url('../../fonts/Inter-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Inter';
src: url('../../fonts/Inter-Medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Inter';
src: url('../../fonts/Inter-Bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Inter';
src: url('../../fonts/Inter-ExtraBold.ttf') format('truetype');
font-weight: 800;
font-style: normal;
}
body, html {
font-family: 'Inter', Arial, sans-serif;
}
File diff suppressed because it is too large Load Diff
@@ -1,71 +0,0 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<title>Czytanie</title>
<link rel="stylesheet" href="css/styles.css?v=202605261" />
<link rel="stylesheet" href="css/fonts/inter.css?v=202605261">
</head>
<body>
<!-- ── LIST SCREEN ── -->
<div id="list-wrap" class="app-container">
<nav class="subpage-nav">
<a href="index.html" class="back-btn" id="back-to-hub">← Wróć do menu</a>
</nav>
<main class="screen-content">
<div id="header-placeholder"></div>
<section class="content-panel">
<h2 class="panel-title">Gotowe teksty</h2>
<div id="text-list" class="reading-list"></div>
</section>
<section class="content-panel">
<h2 class="panel-title">Własny tekst</h2>
<textarea id="custom-input" class="custom-textarea"
placeholder="Wklej lub wpisz tutaj swój tekst..."></textarea>
<button id="custom-start-btn" class="action-btn">Czytaj ten tekst →</button>
</section>
</main>
</div>
<!-- ── READING SCREEN ── -->
<div id="read-wrap" class="read-screen-container hidden">
<header class="read-header">
<button id="read-back-btn" class="read-nav-btn">← Zakończ</button>
<span id="read-title" class="read-title-text"></span>
<button id="speed-btn" class="read-nav-btn speed-toggle-btn">Pauza</button>
</header>
<div class="read-progress-bar">
<div id="read-progress-bar-inner" class="read-progress-bar-inner"></div>
</div>
<div class="read-viewport" id="read-viewport">
<div class="read-text" id="read-text"></div>
</div>
<button id="next-line-btn" class="next-line-btn" aria-label="Następna linia">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>
</button>
</div>
<div id="footer-placeholder"></div>
<script src="js/nav.js?v=20260521"></script>
<script src="js/czytanie.js?v=20260524"></script>
<script src="js/components.js"></script>
<script>
// Ładowanie nagłówka i stopki
loadComponent('components/header.html', {
'{TITLE}': '📖 Czytanie',
'{SUBTITLE}': 'Wybierz tekst do przeczytania lub wklej własny.'
}).then(html => {
document.getElementById('header-placeholder').outerHTML = html;
});
loadComponent('components/footer.html').then(html => {
document.getElementById('footer-placeholder').outerHTML = html;
});
</script>
</body>
</html>
@@ -1,116 +0,0 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<title>Nauka Dzielenia</title>
<link rel="stylesheet" href="css/styles.css?v=20260524" />
<link rel="stylesheet" href="css/fonts/inter.css">
</head>
<body>
<div class="app-container">
<!-- ── SELECT SCREEN ── -->
<main id="select-screen">
<nav class="subpage-nav">
<a href="index.html" class="back-btn" id="back-to-hub">← Wróć do menu</a>
</nav>
<div class="screen-content">
<div id="header-placeholder"></div>
<section class="content-panel">
<h2 class="panel-title">Wybór dzielnika</h2>
<div class="table-grid" id="table-grid">
<button class="table-btn" data-val="1">1</button>
<button class="table-btn" data-val="2">2</button>
<button class="table-btn" data-val="3">3</button>
<button class="table-btn" data-val="4">4</button>
<button class="table-btn" data-val="5">5</button>
<button class="table-btn" data-val="6">6</button>
<button class="table-btn" data-val="7">7</button>
<button class="table-btn" data-val="8">8</button>
<button class="table-btn" data-val="9">9</button>
<button class="table-btn" data-val="10">10</button>
<button class="table-btn all" data-val="0">Wszystkie</button>
</div>
</section>
<section class="content-panel">
<label class="settings-label">
Liczba zadań w serii:
<input id="total-input" type="number" min="5" max="100" value="20" class="settings-input">
</label>
</section>
<button class="action-btn start-btn" id="start-btn">Rozpocznij ćwiczenie →</button>
</div>
</main>
<!-- ── PLAY SCREEN ── -->
<main class="play-screen-container hidden" id="play-screen">
<header class="read-header">
<button id="back-btn" class="read-nav-btn">← Zmień</button>
<span class="read-title-text">Dzielenie</span>
<div class="progress-container">
<span id="progress-label" class="progress-text">0/20</span>
</div>
</header>
<div id="progressbar-placeholder"></div>
<div class="play-body">
<div class="problem-display">
<div id="problem" class="problem-text">?</div>
<div id="feedback" class="feedback-text"></div>
</div>
<div class="keypad-container">
<div id="answer" class="answer-display"></div>
<div class="keypad">
<div class="key-row">
<button class="key">1</button><button class="key">2</button><button class="key">3</button>
</div>
<div class="key-row">
<button class="key">4</button><button class="key">5</button><button class="key">6</button>
</div>
<div class="key-row">
<button class="key">7</button><button class="key">8</button><button class="key">9</button>
</div>
<div class="key-row">
<button class="key key-special" id="clear">C</button>
<button class="key">0</button>
<button class="key key-special" id="backspace"></button>
</div>
</div>
<button class="submit-btn" id="submit">Sprawdź</button>
</div>
</div>
</main>
<!-- ── SUMMARY SCREEN ── -->
<main class="screen-content hidden" id="summary-screen" style="margin-top: 24px;">
<div class="summary-view">
<div style="font-size: 64px; text-align: center;">🎉</div>
<h2 style="text-align: center; font-size: 28px; margin: 16px 0;">Koniec ćwiczenia!</h2>
<p id="summary-text" class="summary-score-text"></p>
<button class="action-btn summary-back-btn" id="again-btn">Jeszcze raz</button>
<button class="action-btn summary-back-btn secondary" id="change-btn">Zmień dzielnik</button>
</div>
</main>
</div>
<div id="footer-placeholder"></div>
<script src="js/nav.js?v=20260521"></script>
<script src="js/dzielenie.js?v=20260524"></script>
<script src="js/components.js"></script>
<script>
loadComponent('components/header.html', {
'{TITLE}': '÷ Nauka Dzielenia',
'{SUBTITLE}': 'Wybierz dzielnik, przez który chcesz dzielić.'
}).then(html => {
document.getElementById('header-placeholder').outerHTML = html;
});
loadComponent('components/progress-bar.html', {'{PROGRESS_ID}': 'progress-inner'}).then(html => {
document.getElementById('progressbar-placeholder').outerHTML = html;
});
loadComponent('components/footer.html').then(html => {
document.getElementById('footer-placeholder').outerHTML = html;
});
</script>
</body>
</html>
@@ -1,63 +0,0 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<title>Edu — Główne Menu</title>
<link rel="stylesheet" href="css/styles.css?v=20260524" />
<link rel="stylesheet" href="css/fonts/inter.css">
</head>
<body>
<div class="app-container">
<header class="app-header">
<h1 class="app-title">Wybierz kategorię</h1>
<p class="app-subtitle">i rozpocznij naukę</p>
</header>
<main class="category-grid">
<a href="czytanie.html" class="category-card" style="--grad-start: #3B82F6; --grad-end: #1D4ED8;">
<div class="card-icon">📖</div>
<div class="card-content">
<h2 class="card-title">Czytanie</h2>
<p class="card-desc">Ćwicz płynność czytania</p>
</div>
</a>
<a href="dyktando.html" class="category-card" style="--grad-start: #F59E0B; --grad-end: #D97706;">
<div class="card-icon">✏️</div>
<div class="card-content">
<h2 class="card-title">Dyktando</h2>
<p class="card-desc">Sprawdź swoją ortografię</p>
</div>
</a>
<a href="mnozenie.html" class="category-card" style="--grad-start: #10B981; --grad-end: #059669;">
<div class="card-icon">×</div>
<div class="card-content">
<h2 class="card-title">Mnożenie</h2>
<p class="card-desc">Tabliczka mnożenia</p>
</div>
</a>
<a href="dzielenie.html" class="category-card" style="--grad-start: #8B5CF6; --grad-end: #7C3AED;">
<div class="card-icon">÷</div>
<div class="card-content">
<h2 class="card-title">Dzielenie</h2>
<p class="card-desc">Powtórka z dzielenia</p>
</div>
</a>
<a href="testy.html" class="category-card" style="--grad-start: #EF4444; --grad-end: #DC2626;">
<div class="card-icon">📝</div>
<div class="card-content">
<h2 class="card-title">Testy z matematyki</h2>
<p class="card-desc">Sprawdź swoją wiedzę</p>
</div>
</a>
</main>
</div>
<footer class="app-footer">Version: <span id="commit-sha">(loading)</span></footer>
<script src="js/version.js?v=20260521" defer></script>
</body>
</html>
@@ -1,453 +0,0 @@
// Minimal app logic for math training
const state = {
ops: new Set(),
mode: null, // 'timed' or 'training'
score: 0,
currentProblem: null,
answerBuffer: '',
timerId: null,
timeLeft: 60,
sessionSolved: 0,
sessionTarget: 20,
settings: {
timedSeconds: 60,
maxResult: 40,
maxOperand: 20,
sessionProblems: 20,
allowNegative: false,
allowFraction: false,
}
}
// elements
const menuScreen = document.getElementById('menu-screen')
const playScreen = document.getElementById('play-screen')
const opsContainer = document.getElementById('ops')
const modeButtons = document.querySelectorAll('.mode-btn')
const opButtons = document.querySelectorAll('.op-btn')
const backBtn = document.getElementById('back-btn')
const problemEl = document.getElementById('problem')
const answerEl = document.getElementById('answer')
const feedbackEl = document.getElementById('feedback')
const timerEl = document.getElementById('timer')
const scoreEl = document.getElementById('score')
const keypad = document.querySelectorAll('.key')
const submitBtn = document.getElementById('submit')
const clearBtn = document.getElementById('clear')
const backspaceBtn = document.getElementById('backspace')
const historyPanel = document.getElementById('history-panel')
const settingTimed = document.getElementById('setting-timed')
const settingMaxResult = document.getElementById('setting-max-result')
const settingMaxOperand = document.getElementById('setting-max-operand')
const settingSessionProblems = document.getElementById('setting-session-problems')
const settingAllowNegative = document.getElementById('setting-allow-negative')
const settingAllowFraction = document.getElementById('setting-allow-fraction')
const saveSettingsBtn = document.getElementById('save-settings')
const resetSettingsBtn = document.getElementById('reset-settings')
const progressInner = document.getElementById('progress_inner')
const dotBtn = document.getElementById('dot')
const negateBtn = document.getElementById('negate')
const summaryOverlay = document.getElementById('summary-overlay')
const summaryText = document.getElementById('summary-text')
const summaryBack = document.getElementById('summary-back')
const statusEl = document.getElementById('status');
// load settings from localStorage
function loadSettings(){
try{
let progressInner = null
if (raw) {
const s = JSON.parse(raw)
state.settings = Object.assign(state.settings, s)
}
}catch(e){ console.warn('settings load failed', e) }
// reflect to inputs
settingTimed.value = state.settings.timedSeconds
settingMaxResult.value = state.settings.maxResult
settingMaxOperand.value = state.settings.maxOperand
settingSessionProblems.value = state.settings.sessionProblems
settingAllowNegative.checked = !!state.settings.allowNegative
settingAllowFraction.checked = !!state.settings.allowFraction
}
function saveSettings(){
state.settings.timedSeconds = Math.max(5, Math.min(600, parseInt(settingTimed.value,10) || 60))
state.settings.maxResult = Math.max(1, Math.min(999, parseInt(settingMaxResult.value,10) || 40))
state.settings.maxOperand = Math.max(1, Math.min(999, parseInt(settingMaxOperand.value,10) || 20))
state.settings.sessionProblems = Math.max(1, Math.min(500, parseInt(settingSessionProblems.value,10) || 20))
state.settings.allowNegative = !!settingAllowNegative.checked
state.settings.allowFraction = !!settingAllowFraction.checked
try{ localStorage.setItem('matma:settings', JSON.stringify(state.settings)) }catch(e){console.warn('save failed',e)}
// visual feedback
const parent = saveSettingsBtn.parentElement;
const feedback = document.createElement('span');
feedback.textContent = 'Zapisano!';
feedback.style.color = '#16a34a';
feedback.style.marginLeft = '12px';
parent.appendChild(feedback);
setTimeout(()=> parent.removeChild(feedback), 2000);
}
function resetSettings(){
state.settings = {timedSeconds:60, maxResult:40, maxOperand:20, sessionProblems:20, allowNegative: false, allowFraction: false};
saveSettings();
loadSettings();
}
saveSettingsBtn.addEventListener('click', saveSettings)
resetSettingsBtn.addEventListener('click', resetSettings)
loadSettings()
// initial wiring
opButtons.forEach(b => {
b.addEventListener('click', () => {
const op = b.dataset.op
if (state.ops.has(op)) { state.ops.delete(op); b.classList.remove('active') }
else { state.ops.add(op); b.classList.add('active') }
})
})
modeButtons.forEach(b => b.addEventListener('click', () => {
if (state.ops.size === 0) {
const hint = document.querySelector('.hint-text');
hint.style.color = '#dc2626';
hint.style.fontWeight = '600';
setTimeout(() => {
hint.style.color = '';
hint.style.fontWeight = '';
}, 2000);
return;
}
modeButtons.forEach(x=>x.classList.remove('active'))
b.classList.add('active')
state.mode = b.dataset.mode
startPlay()
}))
backBtn.addEventListener('click', ()=>{
stopTimer()
playScreen.classList.add('hidden')
menuScreen.classList.remove('hidden')
feedbackEl.textContent = ''
})
function startPlay(){
menuScreen.classList.add('hidden')
playScreen.classList.remove('hidden')
state.score = 0
scoreEl.textContent = state.score
state.answerBuffer = ''
answerEl.textContent = ''
feedbackEl.textContent = ''
state.currentProblem = generateProblem()
renderProblem()
if (state.mode === 'timed'){
progressInner.style.width = '0%'
startTimer(state.settings.timedSeconds)
statusEl.textContent = 'Na czas';
timerEl.classList.remove('hidden');
} else {
progressInner.style.width = '0%'
state.sessionSolved = 0
state.sessionTarget = Math.max(1, state.settings.sessionProblems)
updateProgress()
timerEl.classList.add('hidden');
}
}
function generateProblem(){
const ops = Array.from(state.ops)
const op = ops[Math.floor(Math.random()*ops.length)]
const maxOp = Math.max(1, state.settings.maxOperand)
const maxRes = Math.max(1, state.settings.maxResult)
let a = 0, b = 0
for (let i=0;i<200;i++){
if (op === 'div'){
b = randInt(1, maxOp)
if (!state.settings.allowFraction){
const maxQByOp = Math.floor(maxOp / b)
const maxQ = Math.min(maxRes, maxQByOp)
if (maxQ < 0) continue
const q = randInt(0, maxQ)
a = q * b
if (state.settings.allowNegative && Math.random() < 0.2) a = -a
return {a,b,op}
} else {
const maxResultNByOp = Math.floor((10 * maxOp) / b)
const maxResultN = Math.min(maxRes * 10, maxResultNByOp)
if (maxResultN < 0) continue
const result_n = randInt(0, maxResultN)
if ((result_n * b) % 10 !== 0) continue
a = (result_n * b) / 10
if (state.settings.allowNegative && Math.random() < 0.2) a = -a
return {a,b,op}
}
} else if (op === 'mul'){
a = randInt(0, maxOp)
b = randInt(0, maxOp)
if (a * b <= maxRes) return {a,b,op}
} else if (op === 'add'){
a = randInt(0, maxOp)
b = randInt(0, maxOp)
if (a + b <= maxRes) return {a,b,op}
} else if (op === 'sub'){
a = randInt(0, maxOp)
b = randInt(0, maxOp)
if (!state.settings.allowNegative){
if (a < b) [a,b] = [b,a]
if (a - b <= maxRes) return {a,b,op}
} else {
if (Math.abs(a - b) <= maxRes) return {a,b,op}
}
}
}
a = Math.min(maxOp, Math.floor(maxRes/2))
b = Math.min(maxOp, 1)
return {a,b,op}
}
function randInt(min,max){
return Math.floor(Math.random()*(max-min+1))+min
}
function renderProblem(){
const p = state.currentProblem
const map = {add: '+', sub: '', mul: '×', div: '÷'}
const symbol = map[p.op] || '?'
problemEl.textContent = `${p.a} ${symbol} ${p.b}`
}
function submitAnswer(){
const buf = state.answerBuffer.trim()
if (buf.length === 0) return
const given = parseFloat(buf)
const p = state.currentProblem
let correct = null
if (p.op === 'add') correct = p.a + p.b
else if (p.op === 'sub') correct = p.a - p.b
else if (p.op === 'mul') correct = p.a * p.b
else if (p.op === 'div') correct = (p.b === 0) ? null : (p.a / p.b)
const eps = state.settings.allowFraction ? 1e-6 : 1e-9
const isCorrect = (correct !== null) && (Math.abs(given - correct) < eps)
feedbackEl.style.opacity = 1;
if (isCorrect) {
state.score += 1
feedbackEl.textContent = 'Dobrze!'
feedbackEl.classList.add('correct');
feedbackEl.classList.remove('incorrect');
} else {
const correctAnswer = Number.isFinite(correct) ? parseFloat(correct.toFixed(2)) : '—';
feedbackEl.textContent = `Poprawna odpowiedź: ${correctAnswer}`
feedbackEl.classList.add('incorrect');
feedbackEl.classList.remove('correct');
}
scoreEl.textContent = state.score
state.answerBuffer = ''
answerEl.textContent = ''
if (state.mode === 'training'){
state.sessionSolved++
updateProgress()
if (state.sessionSolved >= state.sessionTarget){
saveSessionToHistory()
setTimeout(showSummary, 1200);
return
}
}
setTimeout(() => {
feedbackEl.style.opacity = 0;
setTimeout(() => {
state.currentProblem = generateProblem()
renderProblem()
}, 200);
}, 1200);
}
submitBtn.addEventListener('click', submitAnswer)
keypad.forEach(k => {
k.addEventListener('click', ()=>{
const v = k.textContent.trim()
if (!/^[0-9]$/.test(v)) return
if (state.answerBuffer.length >= 12) return
state.answerBuffer += v
answerEl.textContent = state.answerBuffer
})
})
if (dotBtn){
dotBtn.addEventListener('click', ()=>{
if (!state.settings.allowFraction) return
if (state.answerBuffer.includes('.')) return
if (state.answerBuffer.length === 0) state.answerBuffer = '0'
state.answerBuffer += '.'
answerEl.textContent = state.answerBuffer
})
}
if (negateBtn){
negateBtn.addEventListener('click', ()=>{
if (!state.settings.allowNegative) return
if (state.answerBuffer.startsWith('-')) state.answerBuffer = state.answerBuffer.slice(1)
else state.answerBuffer = '-' + state.answerBuffer
answerEl.textContent = state.answerBuffer
})
}
clearBtn.addEventListener('click', ()=>{
state.answerBuffer = ''
answerEl.textContent = ''
})
backspaceBtn.addEventListener('click', ()=>{
state.answerBuffer = state.answerBuffer.slice(0,-1)
answerEl.textContent = state.answerBuffer
})
function startTimer(seconds){
state.timeLeft = seconds
timerEl.textContent = state.timeLeft
stopTimer();
state.timerId = setInterval(()=>{
state.timeLeft -= 1
timerEl.textContent = state.timeLeft
const pct = Math.max(0, (state.timeLeft / seconds) * 100)
progressInner.style.width = pct + '%'
if (state.timeLeft <= 0) {
stopTimer()
endSession()
}
},1000)
}
function stopTimer(){
if (state.timerId) clearInterval(state.timerId)
state.timerId = null
}
function endSession(){
saveSessionToHistory()
showSummary();
}
function showSummary(){
const total = state.mode === 'timed' ? state.score : state.sessionTarget;
const correct = state.score;
const pct = total > 0 ? Math.round((correct / total) * 100) : 0;
if (state.mode === 'timed') {
summaryText.textContent = `Koniec czasu! Zdobyłeś ${correct} punktów.`;
} else {
summaryText.textContent = `Twój wynik: ${correct} / ${total} poprawnie (${pct}%)`;
}
summaryOverlay.classList.remove('hidden');
}
summaryBack.addEventListener('click', ()=>{
summaryOverlay.classList.add('hidden');
playScreen.classList.add('hidden');
menuScreen.classList.remove('hidden');
});
function updateProgress(){
if (state.mode === 'training'){
const pct = Math.min(100, Math.round((state.sessionSolved/state.sessionTarget)*100))
progressInner.style.width = pct + '%'
statusEl.textContent = `${state.sessionSolved}/${state.sessionTarget}`
}
}
// history persistence
function loadHistory(){
try{
const raw = localStorage.getItem('matma:history')
return raw ? JSON.parse(raw) : []
}catch(e){ return [] }
}
function saveSessionToHistory(){
const h = loadHistory()
const sessionData = {
ts: Date.now(),
mode: state.mode,
score: state.score,
ops: Array.from(state.ops),
duration: state.mode === 'timed' ? state.settings.timedSeconds : null,
problems: state.mode === 'training' ? state.sessionTarget : null
};
h.unshift(sessionData);
while(h.length>50) h.pop()
try{ localStorage.setItem('matma:history', JSON.stringify(h)) }catch(e){ }
}
function renderHistory(){
const h = loadHistory()
historyPanel.innerHTML = ''
if (h.length === 0) {
historyPanel.innerHTML = '<p class="history-empty">Brak zapisanych sesji.</p>'
return
}
h.forEach(item => {
const d = new Date(item.ts).toLocaleString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
const modeText = item.mode === 'timed' ? `Na czas (${item.duration}s)` : `Trening (${item.problems} zadań)`;
const opsText = item.ops.join(', ').replace('add','+').replace('sub','-').replace('mul','×').replace('div','÷');
const el = document.createElement('div')
el.className = 'history-item'
el.innerHTML = `
<div class="history-item-main">
<span class="history-score">Wynik: ${item.score}</span>
<span class="history-ops">${opsText}</span>
</div>
<div class="history-item-meta">
<span>${modeText}</span>
<span>${d}</span>
</div>`
historyPanel.appendChild(el)
})
}
// Toggle history panel
const historyDetails = historyPanel.closest('details');
if (historyDetails) {
historyDetails.addEventListener('toggle', () => {
if (historyDetails.open) {
renderHistory();
}
});
}
// keyboard support
window.addEventListener('keydown', (e)=>{
if (playScreen.classList.contains('hidden')) return
if (/^[0-9]$/.test(e.key)){
if (state.answerBuffer.length < 12) {
state.answerBuffer += e.key
answerEl.textContent = state.answerBuffer
}
} else if (e.key === 'Backspace'){
state.answerBuffer = state.answerBuffer.slice(0,-1)
answerEl.textContent = state.answerBuffer
} else if (e.key === 'Enter'){
submitAnswer()
} else if (e.key === '.' || e.key === ',') {
dotBtn.click();
}
})
// Set default active operations
document.addEventListener('DOMContentLoaded', () => {
opButtons.forEach(btn => {
const op = btn.dataset.op;
if (['add', 'sub'].includes(op)) {
btn.classList.add('active');
state.ops.add(op);
}
});
});
@@ -1,27 +0,0 @@
// Komponenty HTML loader
function loadComponent(path, replaceMap = {}) {
// If path is a full mapped origin used by WebViewAssetLoader, strip it to a relative path.
const mappedOrigin = 'https://appassets.androidplatform.net/assets/';
if (path.startsWith(mappedOrigin)) {
path = path.slice(mappedOrigin.length);
}
// If an absolute path starting with '/' is provided, remove leading slash to make it
// relative to the current document (works under local http server and WebViewAssetLoader).
if (path.startsWith('/')) path = path.slice(1);
return fetch(path)
.then(r => {
if (!r.ok) throw new Error('Failed to load component: ' + path);
return r.text();
})
.then(html => {
Object.entries(replaceMap).forEach(([key, val]) => {
html = html.replaceAll(key, val);
});
return html;
});
}
// Przykład użycia:
// loadComponent('components/header.html', {'{TITLE}': 'Tytuł', '{SUBTITLE}': 'Podtytuł'}).then(html => ...)
@@ -1,142 +0,0 @@
// Nauka Czytania
;(function () {
// ms between line advances for each speed level (0 = manual)
const SPEEDS_MS = [0, 3500, 2000, 1000]
const SPEED_LABELS = ['Pauza', 'Wolno', 'Średnio', 'Szybko']
let yOffset = 0
let lineH = 80 // recalculated after render
let maxOffset = 0
let speedIdx = 0
let autoTimer = null
const listWrap = document.getElementById('list-wrap')
const readWrap = document.getElementById('read-wrap')
const textList = document.getElementById('text-list')
const customInput = document.getElementById('custom-input')
const customStartBtn = document.getElementById('custom-start-btn')
const readBackBtn = document.getElementById('read-back-btn')
const readTitleEl = document.getElementById('read-title')
const speedBtn = document.getElementById('speed-btn')
const readViewport = document.getElementById('read-viewport')
const readTextEl = document.getElementById('read-text')
const nextLineBtn = document.getElementById('next-line-btn')
let progressBar = null
// ── Load text list from dyktanda.json ────────────────────────────────────
fetch('json/dyktanda.json')
.then(r => r.json())
.then(data => {
data.forEach(item => {
const btn = document.createElement('button')
btn.className = 'list-item-btn'
btn.textContent = item.name
btn.addEventListener('click', () => startReading(item.name, item.text))
textList.appendChild(btn)
})
})
.catch(() => {
textList.innerHTML = '<p style="color:var(--muted);padding:8px 0">Nie udało się wczytać tekstów.</p>'
})
customStartBtn.addEventListener('click', () => {
const txt = customInput.value.trim()
if (!txt) return
startReading('Własny', txt)
})
// ── Start reading ─────────────────────────────────────────────────────────
function startReading(title, text) {
yOffset = 0
speedIdx = 0
clearInterval(autoTimer)
autoTimer = null
if (!readTitleEl || !readTextEl || !speedBtn || !readWrap || !listWrap) {
console.warn('czytanie: missing DOM elements', { readTitleEl, readTextEl, speedBtn, readWrap, listWrap })
}
if (readTitleEl) readTitleEl.textContent = title
if (readTextEl) readTextEl.textContent = text
if (readTextEl) readTextEl.style.transform = 'translateY(0)'
if (speedBtn) speedBtn.textContent = SPEED_LABELS[0]
if (!progressBar) progressBar = document.getElementById('read-progress-bar-inner')
if (progressBar) progressBar.style.width = '0%'
listWrap.classList.add('hidden')
readWrap.classList.remove('hidden')
// measure real line height after layout
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const cs = getComputedStyle(readTextEl)
const lhVal = cs.lineHeight
lineH = (lhVal === 'normal')
? parseFloat(cs.fontSize) * 1.35
: parseFloat(lhVal)
maxOffset = Math.max(0, readTextEl.offsetHeight - lineH)
updateNextBtn()
updateProgressBar()
})
})
}
// ── Back to list ──────────────────────────────────────────────────────────
// Zakończ button: natychmiast wróć do listy bez potwierdzenia
readBackBtn.addEventListener('click', () => {
clearInterval(autoTimer)
autoTimer = null
readWrap.classList.add('hidden')
listWrap.classList.remove('hidden')
// Reset speed to manual on exit
speedIdx = 0;
})
// ── Speed selector ────────────────────────────────────────────────────────
speedBtn.addEventListener('click', () => {
clearInterval(autoTimer)
autoTimer = null
speedIdx = (speedIdx + 1) % SPEEDS_MS.length
speedBtn.textContent = SPEED_LABELS[speedIdx]
if (SPEEDS_MS[speedIdx] > 0 && yOffset < maxOffset) {
autoTimer = setInterval(advanceLine, SPEEDS_MS[speedIdx])
}
})
// ── Manual line advance ───────────────────────────────────────────────────
nextLineBtn.addEventListener('click', advanceLine)
// ── Core scroll logic ─────────────────────────────────────────────────────
function advanceLine() {
if (yOffset >= maxOffset) {
stopAutoAtEnd()
return
}
yOffset = Math.min(yOffset + lineH, maxOffset)
readTextEl.style.transform = `translateY(${-yOffset}px)`
updateNextBtn()
updateProgressBar()
if (yOffset >= maxOffset) stopAutoAtEnd()
}
function stopAutoAtEnd() {
if (autoTimer !== null) {
clearInterval(autoTimer)
autoTimer = null
speedIdx = 0
speedBtn.textContent = SPEED_LABELS[0]
}
updateNextBtn()
}
function updateNextBtn() {
nextLineBtn.disabled = yOffset >= maxOffset
}
function updateProgressBar() {
const progress = maxOffset > 0 ? (yOffset / maxOffset) * 100 : 100
if (!progressBar) progressBar = document.getElementById('read-progress-bar-inner')
if (progressBar) progressBar.style.width = `${progress}%`
}
})()
@@ -1,150 +0,0 @@
// Nauka Dzielenia
;(function () {
const st = { divisor: null, total: 20, solved: 0, score: 0, current: null, buf: '' }
const selectScreen = document.getElementById('select-screen')
const playScreen = document.getElementById('play-screen')
const summaryScreen = document.getElementById('summary-screen')
const problemEl = document.getElementById('problem')
const answerEl = document.getElementById('answer')
const feedbackEl = document.getElementById('feedback')
let progressInner = null
const progressLabel = document.getElementById('progress-label')
const summaryText = document.getElementById('summary-text')
const totalInput = document.getElementById('total-input')
// divisor selection
document.getElementById('table-grid').addEventListener('click', e => {
const btn = e.target.closest('.table-btn')
if (!btn) return
document.querySelectorAll('.table-btn').forEach(b => b.classList.remove('active'))
btn.classList.add('active')
st.divisor = parseInt(btn.dataset.val, 10) // 0 = all
})
document.getElementById('start-btn').addEventListener('click', () => {
st.total = Math.max(5, Math.min(100, parseInt(totalInput.value, 10) || 20))
st.solved = 0
st.score = 0
show(playScreen)
nextProblem()
})
document.getElementById('back-btn').addEventListener('click', () => show(selectScreen))
document.getElementById('again-btn').addEventListener('click', () => {
st.solved = 0; st.score = 0; show(playScreen); nextProblem()
})
document.getElementById('change-btn').addEventListener('click', () => show(selectScreen))
// keypad
document.querySelectorAll('.key').forEach(k => {
k.addEventListener('click', () => {
const v = k.textContent.trim()
if (!/^[0-9]$/.test(v)) return
if (st.buf.length >= 6) return
st.buf += v; answerEl.textContent = st.buf
})
})
document.getElementById('clear').addEventListener('click', () => { st.buf = ''; answerEl.textContent = '' })
document.getElementById('backspace').addEventListener('click', () => { st.buf = st.buf.slice(0,-1); answerEl.textContent = st.buf || '' })
document.getElementById('submit').addEventListener('click', submit)
window.addEventListener('keydown', e => {
if (!playScreen.classList.contains('hidden')) {
if (/^[0-9]$/.test(e.key) && st.buf.length < 6) { st.buf += e.key; answerEl.textContent = st.buf }
else if (e.key === 'Backspace') { st.buf = st.buf.slice(0,-1); answerEl.textContent = st.buf || '' }
else if (e.key === 'Enter') submit()
}
})
function randInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min }
function nextProblem() {
const b = st.divisor || randInt(1, 10) // divisor
const answer = randInt(1, 10) // quotient (always integer)
const a = b * answer // dividend
st.current = { a, b, answer }
problemEl.textContent = `${a} ÷ ${b}`
feedbackEl.textContent = ''
st.buf = ''; answerEl.textContent = ''
updateBar()
}
function submit() {
if (!st.buf.trim()) return
const given = parseInt(st.buf, 10)
st.solved++
if (given === st.current.answer) {
st.score++
feedbackEl.textContent = 'Dobrze!'
feedbackEl.classList.add('correct')
feedbackEl.classList.remove('incorrect')
} else {
feedbackEl.textContent = `Poprawna odpowiedź: ${st.current.answer}`
feedbackEl.classList.add('incorrect')
feedbackEl.classList.remove('correct')
}
feedbackEl.style.opacity = 1;
st.buf = ''; answerEl.textContent = ''
updateBar()
if (st.solved >= st.total) {
setTimeout(showSummary, 1200)
} else {
setTimeout(() => {
feedbackEl.style.opacity = 0;
setTimeout(nextProblem, 200);
}, 1000)
}
}
function updateBar() {
const pct = st.total > 0 ? Math.round((st.solved / st.total) * 100) : 0
if (!progressInner) {
progressInner = document.getElementById('progress-inner')
}
if (progressInner) progressInner.style.width = pct + '%'
progressLabel.textContent = `${st.solved}/${st.total}`
}
function showSummary() {
const pct = st.total > 0 ? Math.round((st.score / st.total) * 100) : 0
summaryText.textContent = `Twój wynik: ${st.score} / ${st.total} poprawnie (${pct}%)`
show(summaryScreen)
}
function show(screen) {
[selectScreen, playScreen, summaryScreen].forEach(s => s.classList.add('hidden'))
screen.classList.remove('hidden')
if (screen === summaryScreen) {
selectScreen.classList.add('hidden');
playScreen.classList.add('hidden');
}
}
// commit SHA and default selection
document.addEventListener('DOMContentLoaded', async () => {
const el = document.getElementById('commit-sha')
if (!el) return
// Set default divisor selection
const defaultDivisorBtn = document.querySelector('.table-btn[data-val="0"]');
if(defaultDivisorBtn) {
defaultDivisorBtn.classList.add('active');
st.divisor = 0;
}
let sha = (window.COMMIT_SHA || '').toString().trim()
if (!sha) {
try {
// use relative path so the request works under WebViewAssetLoader or a local server
const res = await fetch('version.sha', { cache: 'no-cache' })
if (res.ok) {
const txt = await res.text()
const first = txt.split(/\r?\n/).find(l => l.trim().length > 0)
if (first) sha = first.trim()
}
} catch (e) {}
}
if (sha) el.textContent = sha.slice(0, 8)
})
})()
@@ -1,116 +0,0 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<title>Nauka Mnożenia</title>
<link rel="stylesheet" href="css/styles.css?v=20260524" />
<link rel="stylesheet" href="css/fonts/inter.css">
</head>
<body>
<div class="app-container">
<!-- ── SELECT SCREEN ── -->
<main id="select-screen">
<nav class="subpage-nav">
<a href="index.html" class="back-btn" id="back-to-hub">← Wróć do menu</a>
</nav>
<div class="screen-content">
<div id="header-placeholder"></div>
<section class="content-panel">
<h2 class="panel-title">Wybór tabliczki</h2>
<div class="table-grid" id="table-grid">
<button class="table-btn" data-val="1">1</button>
<button class="table-btn" data-val="2">2</button>
<button class="table-btn" data-val="3">3</button>
<button class="table-btn" data-val="4">4</button>
<button class="table-btn" data-val="5">5</button>
<button class="table-btn" data-val="6">6</button>
<button class="table-btn" data-val="7">7</button>
<button class="table-btn" data-val="8">8</button>
<button class="table-btn" data-val="9">9</button>
<button class="table-btn" data-val="10">10</button>
<button class="table-btn all" data-val="0">Wszystkie</button>
</div>
</section>
<section class="content-panel">
<label class="settings-label">
Liczba zadań w serii:
<input id="total-input" type="number" min="5" max="100" value="20" class="settings-input">
</label>
</section>
<button class="action-btn start-btn" id="start-btn">Rozpocznij ćwiczenie →</button>
</div>
</main>
<!-- ── PLAY SCREEN ── -->
<main class="play-screen-container hidden" id="play-screen">
<header class="read-header">
<button id="back-btn" class="read-nav-btn">← Zmień</button>
<span class="read-title-text">Mnożenie</span>
<div class="progress-container">
<span id="progress-label" class="progress-text">0/20</span>
</div>
</header>
<div id="progressbar-placeholder"></div>
<div class="play-body">
<div class="problem-display">
<div id="problem" class="problem-text">?</div>
<div id="feedback" class="feedback-text"></div>
</div>
<div class="keypad-container">
<div id="answer" class="answer-display"></div>
<div class="keypad">
<div class="key-row">
<button class="key">1</button><button class="key">2</button><button class="key">3</button>
</div>
<div class="key-row">
<button class="key">4</button><button class="key">5</button><button class="key">6</button>
</div>
<div class="key-row">
<button class="key">7</button><button class="key">8</button><button class="key">9</button>
</div>
<div class="key-row">
<button class="key key-special" id="clear">C</button>
<button class="key">0</button>
<button class="key key-special" id="backspace"></button>
</div>
</div>
<button class="submit-btn" id="submit">Sprawdź</button>
</div>
</div>
</main>
<!-- ── SUMMARY SCREEN ── -->
<main class="screen-content hidden" id="summary-screen" style="margin-top: 24px;">
<div class="summary-view">
<div style="font-size: 64px; text-align: center;">🎉</div>
<h2 style="text-align: center; font-size: 28px; margin: 16px 0;">Koniec ćwiczenia!</h2>
<p id="summary-text" class="summary-score-text"></p>
<button class="action-btn summary-back-btn" id="again-btn">Jeszcze raz</button>
<button class="action-btn summary-back-btn secondary" id="change-btn">Zmień tabliczkę</button>
</div>
</main>
</div>
<div id="footer-placeholder"></div>
<script src="js/nav.js?v=20260521"></script>
<script src="js/mnozenie.js?v=20260524"></script>
<script src="js/components.js"></script>
<script>
loadComponent('components/header.html', {
'{TITLE}': '× Nauka Mnożenia',
'{SUBTITLE}': 'Wybierz tabliczkę, którą chcesz poćwiczyć.'
}).then(html => {
document.getElementById('header-placeholder').outerHTML = html;
});
loadComponent('components/progress-bar.html', {'{PROGRESS_ID}': 'progress-inner'}).then(html => {
document.getElementById('progressbar-placeholder').outerHTML = html;
});
loadComponent('components/footer.html').then(html => {
document.getElementById('footer-placeholder').outerHTML = html;
});
</script>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -1,170 +0,0 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<title>Testy Matematyczne</title>
<link rel="stylesheet" href="css/styles.css?v=20260524" />
<link rel="stylesheet" href="css/fonts/inter.css">
</head>
<body>
<div class="app-container">
<!-- ── MENU SCREEN ── -->
<main id="menu-screen">
<nav class="subpage-nav">
<a href="index.html" class="back-btn" id="back-to-hub">← Wróć do menu</a>
</nav>
<div class="screen-content">
<div id="header-placeholder"></div>
<div class="test-config-grid">
<!-- Operations -->
<section class="content-panel">
<h2 class="panel-title">1. Wybierz działania</h2>
<div class="ops-grid" id="ops">
<button class="op-btn" data-op="add">+ Dodawanie</button>
<button class="op-btn" data-op="sub"> Odejmowanie</button>
<button class="op-btn" data-op="mul">× Mnożenie</button>
<button class="op-btn" data-op="div">÷ Dzielenie</button>
</div>
</section>
<!-- Game Mode -->
<section class="content-panel">
<h2 class="panel-title">2. Wybierz tryb gry</h2>
<div class="modes-grid">
<button class="mode-btn" data-mode="timed" id="mode-timed">
<span class="mode-icon"></span>
<span class="mode-label">Na czas</span>
</button>
<button class="mode-btn" data-mode="training" id="mode-training">
<span class="mode-icon"></span>
<span class="mode-label">Trening</span>
</button>
</div>
<p class="hint-text">Wybierz co najmniej jedno działanie, a następnie tryb, aby rozpocząć.</p>
</section>
</div>
<!-- Settings -->
<section class="content-panel">
<details class="settings-details">
<summary class="settings-summary">
<h2 class="panel-title">Ustawienia zaawansowane</h2>
</summary>
<div class="settings-content" id="settings-panel">
<label class="settings-label-row"> Czas (sek) — tryb na czas
<input id="setting-timed" type="number" min="5" max="600" class="settings-input-row" />
</label>
<label class="settings-label-row"> Maksymalny wynik
<input id="setting-max-result" type="number" min="1" max="999" class="settings-input-row" />
</label>
<label class="settings-label-row"> Maksymalna składowa (operand)
<input id="setting-max-operand" type="number" min="1" max="999" class="settings-input-row" />
</label>
<label class="settings-label-row"> Liczba zadań (tryb Trening)
<input id="setting-session-problems" type="number" min="1" max="500" class="settings-input-row" />
</label>
<label class="settings-label-row checkbox"> Wynik może być ujemny
<input id="setting-allow-negative" type="checkbox" />
</label>
<label class="settings-label-row checkbox"> Wynik może być ułamkiem
<input id="setting-allow-fraction" type="checkbox" />
</label>
<div class="settings-actions">
<button id="save-settings" class="action-btn">Zapisz</button>
<button id="reset-settings" class="action-btn secondary">Resetuj</button>
</div>
</div>
</details>
</section>
<!-- History -->
<section class="content-panel">
<details class="settings-details">
<summary class="settings-summary">
<h2 class="panel-title">Historia wyników</h2>
</summary>
<div class="history-list" id="history-panel">
<!-- History items will be injected here -->
</div>
</details>
</section>
</div>
</main>
<!-- ── PLAY SCREEN ── -->
<main class="play-screen-container hidden" id="play-screen">
<header class="play-header">
<button id="back-btn" class="play-nav-btn">← Menu</button>
<div id="status" class="play-status">Trening</div>
<div id="score" class="play-score">0</div>
</header>
<div id="progressbar-placeholder"></div>
<div class="play-body">
<div class="problem-display">
<div id="timer" class="timer-display hidden">60</div>
<div id="problem" class="problem-text"></div>
<div id="feedback" class="feedback-text"></div>
</div>
<div class="keypad-container">
<div id="answer" class="answer-display"></div>
<div class="keypad">
<div class="key-row">
<button class="key">1</button><button class="key">2</button><button class="key">3</button>
</div>
<div class="key-row">
<button class="key">4</button><button class="key">5</button><button class="key">6</button>
</div>
<div class="key-row">
<button class="key">7</button><button class="key">8</button><button class="key">9</button>
</div>
<div class="key-row">
<button class="key key-special" id="negate">±</button>
<button class="key">0</button>
<button class="key key-special" id="dot">.</button>
</div>
<div class="key-row">
<button class="key key-special" id="clear">C</button>
<button class="key key-special" id="backspace"></button>
<button class="submit-btn" id="submit">OK</button>
</div>
</div>
</div>
</div>
</main>
<!-- ── SUMMARY SCREEN ── -->
<div id="summary-overlay" class="summary-overlay hidden">
<div class="summary-modal">
<div style="font-size: 48px;">🎉</div>
<h2>Koniec gry!</h2>
<p id="summary-text" class="summary-modal-text"></p>
<button id="summary-back" class="action-btn">Wróć do menu</button>
</div>
</div>
</div>
<div id="footer-placeholder"></div>
<script src="js/version.js?v=20260521" defer></script>
<script src="js/nav.js?v=20260521"></script>
<script src="js/app.js?v=20260524"></script>
<script src="js/components.js"></script>
<script>
loadComponent('components/header.html', {
'{TITLE}': '📝 Testy Matematyczne',
'{SUBTITLE}': 'Skonfiguruj swój test i sprawdź wiedzę.'
}).then(html => {
document.getElementById('header-placeholder').outerHTML = html;
});
loadComponent('components/progress-bar.html', {'{PROGRESS_ID}': 'progress_inner'}).then(html => {
document.getElementById('progressbar-placeholder').outerHTML = html;
});
loadComponent('components/footer.html').then(html => {
document.getElementById('footer-placeholder').outerHTML = html;
});
</script>
</body>
</html>
@@ -1,10 +0,0 @@
{
"version": 3,
"artifactType": {
"type": "COMPATIBLE_SCREEN_MANIFEST",
"kind": "Directory"
},
"applicationId": "com.example.app",
"variantName": "debug",
"elements": []
}

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