Erster Upload von MX Linux
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
101
android/.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# 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
android/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build/*
|
||||
!/build/.npmkeep
|
||||
54
android/app/build.gradle
Normal file
@@ -0,0 +1,54 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
namespace = "com.medienstation.app"
|
||||
compileSdk = rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "com.medienstation.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
|
||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
21
android/app/capacitor.build.gradle
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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-camera')
|
||||
implementation project(':capacitor-filesystem')
|
||||
implementation "com.android.support:support-v4:28.+"
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
}
|
||||
|
||||
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
||||
21
android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# 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
|
||||
@@ -0,0 +1,26 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
51
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.medienstation.app">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
|
||||
<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:requestLegacyExternalStorage="true"
|
||||
tools:replace="android:appComponentFactory"
|
||||
android:appComponentFactory="androidx.core.app.CoreComponentFactory">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
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"></meta-data>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
android/app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,56 @@
|
||||
package com.medienstation.app; // Das MUSS die erste Zeile sein
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Base64;
|
||||
import androidx.print.PrintHelper;
|
||||
|
||||
import com.getcapacitor.Plugin;
|
||||
import com.getcapacitor.PluginCall;
|
||||
import com.getcapacitor.PluginMethod;
|
||||
import com.getcapacitor.annotation.CapacitorPlugin;
|
||||
|
||||
@CapacitorPlugin(name = "DirectPrinter")
|
||||
public class DirectPrinterPlugin extends Plugin {
|
||||
|
||||
@PluginMethod
|
||||
public void printBase64(PluginCall call) {
|
||||
String base64String = call.getString("data");
|
||||
String jobName = call.getString("name", "News-Print-Job");
|
||||
|
||||
if (base64String == null) {
|
||||
call.reject("Keine Daten empfangen");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Base64 Header entfernen
|
||||
if (base64String.contains(",")) {
|
||||
base64String = base64String.split(",")[1];
|
||||
}
|
||||
|
||||
// 2. String in Bytes umwandeln
|
||||
byte[] decodedString = Base64.decode(base64String, Base64.DEFAULT);
|
||||
|
||||
// 3. Bytes in ein Bitmap (Bild) umwandeln
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
|
||||
|
||||
if (bitmap == null) {
|
||||
call.reject("Bild konnte nicht dekodiert werden");
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Drucken auf dem UI Thread
|
||||
getActivity().runOnUiThread(() -> {
|
||||
PrintHelper photoPrinter = new PrintHelper(getContext());
|
||||
photoPrinter.setScaleMode(PrintHelper.SCALE_MODE_FIT);
|
||||
photoPrinter.printBitmap(jobName, bitmap);
|
||||
|
||||
call.resolve();
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
call.reject("Fehler beim Drucken: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.medienstation.app;
|
||||
|
||||
import android.os.Bundle;
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebSettings; // <--- NEU (+)
|
||||
|
||||
public class MainActivity extends BridgeActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
registerPlugin(DirectPrinterPlugin.class);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// 1. Display anlassen
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
// 2. WebView Einstellungen holen
|
||||
WebView webView = getBridge().getWebView();
|
||||
WebSettings settings = webView.getSettings();
|
||||
|
||||
// 3. WICHTIG: Autoplay erlauben! (+)
|
||||
settings.setMediaPlaybackRequiresUserGesture(false);
|
||||
|
||||
// 4. Debugging
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
}
|
||||
}
|
||||
BIN
android/app/src/main/res/drawable-land-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
android/app/src/main/res/drawable-land-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
android/app/src/main/res/drawable-port-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
android/app/src/main/res/drawable-port-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
170
android/app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
||||
<?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>
|
||||
36
android/app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<group android:scaleX="0.92"
|
||||
android:scaleY="0.92"
|
||||
android:translateX="20.48"
|
||||
android:translateY="20.48">
|
||||
<path
|
||||
android:pathData="M100,0L412,0A100,100 0,0 1,512 100L512,412A100,100 0,0 1,412 512L100,512A100,100 0,0 1,0 412L0,100A100,100 0,0 1,100 0z"
|
||||
android:fillColor="#0f172a"/>
|
||||
<path
|
||||
android:pathData="M256,120C256,120 360,120 390,200L256,256V120Z"
|
||||
android:fillColor="#facc15"/>
|
||||
<path
|
||||
android:pathData="M390,200C390,200 420,320 320,390L256,256L390,200Z"
|
||||
android:fillColor="#ef4444"/>
|
||||
<path
|
||||
android:pathData="M320,390C320,390 200,420 122,312L256,256L320,390Z"
|
||||
android:fillColor="#3b82f6"/>
|
||||
<path
|
||||
android:pathData="M122,312C122,312 90,180 190,120L256,256L122,312Z"
|
||||
android:fillColor="#10b981"/>
|
||||
<path
|
||||
android:pathData="M256,256m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M256,256m-35,0a35,35 0,1 1,70 0a35,35 0,1 1,-70 0"
|
||||
android:fillColor="#1e293b"/>
|
||||
<path
|
||||
android:pathData="M270,240m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillAlpha="0.8"/>
|
||||
</group>
|
||||
</vector>
|
||||
BIN
android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
12
android/app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#0F172A</color>
|
||||
</resources>
|
||||
7
android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">MedienStation</string>
|
||||
<string name="title_activity_main">MedienStation</string>
|
||||
<string name="package_name">com.medienstation.app</string>
|
||||
<string name="custom_url_scheme">com.medienstation.app</string>
|
||||
</resources>
|
||||
22
android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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>
|
||||
5
android/app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
29
android/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.13.2'
|
||||
classpath 'com.google.gms:google-services:4.4.4'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "variables.gradle"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
9
android/capacitor.settings.gradle
Normal file
@@ -0,0 +1,9 @@
|
||||
// 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-camera'
|
||||
project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android')
|
||||
|
||||
include ':capacitor-filesystem'
|
||||
project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
|
||||
24
android/gradle.properties
Normal file
@@ -0,0 +1,24 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
251
android/gradlew
vendored
Executable file
@@ -0,0 +1,251 @@
|
||||
#!/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\n' "$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="\\\"\\\""
|
||||
|
||||
|
||||
# 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, 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" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# 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
android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
@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=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
: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
android/settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
||||
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
android/variables.gradle
Normal file
@@ -0,0 +1,16 @@
|
||||
ext {
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 36
|
||||
targetSdkVersion = 36
|
||||
androidxActivityVersion = '1.11.0'
|
||||
androidxAppCompatVersion = '1.7.1'
|
||||
androidxCoordinatorLayoutVersion = '1.3.0'
|
||||
androidxCoreVersion = '1.17.0'
|
||||
androidxFragmentVersion = '1.8.9'
|
||||
coreSplashScreenVersion = '1.2.0'
|
||||
androidxWebkitVersion = '1.14.0'
|
||||
junitVersion = '4.13.2'
|
||||
androidxJunitVersion = '1.3.0'
|
||||
androidxEspressoCoreVersion = '3.7.0'
|
||||
cordovaAndroidVersion = '14.0.1'
|
||||
}
|
||||
6
capacitor.config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"appId": "com.medienstation.app",
|
||||
"appName": "MedienStation",
|
||||
"webDir": "public",
|
||||
"bundledWebRuntime": false
|
||||
}
|
||||
231
index.html
Normal file
@@ -0,0 +1,231 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>MedienStation Hub</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
|
||||
<link rel="stylesheet" href="js/tailwind.css">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<style>
|
||||
/* Globaler Reset */
|
||||
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }
|
||||
|
||||
body {
|
||||
background-color: #0f172a;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.font-comic { font-family: 'Comic Sans MS', 'Chalkboard SE', sans-serif; }
|
||||
.app-card { cursor: pointer; transition: transform 0.1s; }
|
||||
.app-card:active { transform: scale(0.95); filter: brightness(1.2); }
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
.fade-in { animation: fadeIn 0.5s ease-out; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }
|
||||
|
||||
/* --- FINALER FIX v7.1: GRID + GHOST MODE --- */
|
||||
#qr-modal {
|
||||
/* GRID zwingt den Inhalt mathematisch in die Mitte */
|
||||
display: grid !important;
|
||||
place-items: center;
|
||||
|
||||
position: fixed;
|
||||
top: 0; left: 0; width: 100vw; height: 100vh;
|
||||
z-index: 99999;
|
||||
|
||||
background-color: rgba(0,0,0,0.85);
|
||||
backdrop-filter: blur(5px);
|
||||
|
||||
/* GHOST MODE: Das Element ist da, aber unsichtbar.
|
||||
So kann der Browser es berechnen, bevor man es sieht. */
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* Sichtbar schalten */
|
||||
#qr-modal.modal-visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="p-4 box-border fade-in relative" onpointerdown="resetHubTimer()" ontouchstart="resetHubTimer()">
|
||||
|
||||
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 left-6 z-50 w-12 h-12 bg-white/10 text-white rounded-full font-bold border-2 border-white/30 backdrop-blur-md flex items-center justify-center shadow-lg active:scale-95">
|
||||
<span class="text-2xl">?</span>
|
||||
</button>
|
||||
|
||||
<div class="grid grid-cols-12 grid-rows-12 gap-3 h-full w-full pb-28">
|
||||
|
||||
<div class="col-span-12 row-span-3 bg-slate-800 rounded-3xl p-6 flex justify-between items-center shadow-lg border border-slate-700 relative overflow-hidden group pl-24">
|
||||
<div class="absolute -right-10 -top-10 w-64 h-64 bg-yellow-400 rounded-full blur-3xl opacity-10 group-hover:opacity-20 transition-opacity duration-500"></div>
|
||||
<div class="z-10 w-2/3 select-none" onclick="triggerAdmin()">
|
||||
<h3 class="text-slate-400 text-sm font-bold tracking-widest uppercase mb-1 cursor-pointer">Deine Mission</h3>
|
||||
<div id="mission-text" class="text-2xl md:text-4xl lg:text-5xl font-black text-white leading-tight transition-all duration-300">Wähle eine App! 👉</div>
|
||||
</div>
|
||||
<button onclick="playSound('click'); newMission()" class="z-10 bg-yellow-400 hover:bg-yellow-300 text-black font-black text-xl py-4 px-10 rounded-full shadow-lg active:translate-y-2 transition-all transform hover:scale-105">
|
||||
🎲 WÜRFELN
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/sound.html', 'Sound')" class="app-card col-span-4 row-span-5 bg-gradient-to-br from-rose-500 to-rose-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-rose-800 active:border-b-0 group">
|
||||
<div class="text-6xl mb-2 drop-shadow-md group-hover:-translate-y-2 transition-transform">🎹</div>
|
||||
<h3 class="text-2xl font-bold">Sound</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/rec.html', 'Mikro')" class="app-card col-span-2 row-span-5 bg-gradient-to-br from-red-600 to-red-700 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-red-900 active:border-b-0">
|
||||
<div class="text-5xl mb-2 drop-shadow-md">🎙️</div>
|
||||
<h3 class="text-xl font-bold">Mikro</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/gif.html', 'Loop')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-sky-500 to-sky-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-sky-800 active:border-b-0 group">
|
||||
<div class="text-6xl mb-2 drop-shadow-md group-hover:rotate-12 transition-transform">📹</div>
|
||||
<h3 class="text-2xl font-bold">Loop</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/magic.html', 'Magic')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-violet-500 to-violet-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-violet-800 active:border-b-0">
|
||||
<div class="text-6xl mb-2 drop-shadow-md">✨</div>
|
||||
<h3 class="text-2xl font-bold">Magic</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/pixel.html', 'Pixel')" class="app-card col-span-4 row-span-4 bg-gradient-to-br from-emerald-500 to-emerald-700 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-emerald-900 active:border-b-0">
|
||||
<div class="text-6xl mb-2 drop-shadow-md font-mono">👾</div>
|
||||
<h3 class="text-2xl font-bold">Pixel</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/news.html', 'News')" class="app-card col-span-4 row-span-4 bg-gradient-to-br from-blue-700 to-blue-900 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-blue-950 active:border-b-0 relative overflow-hidden">
|
||||
<div class="text-5xl mb-2 drop-shadow-md relative z-10">📰</div>
|
||||
<h3 class="text-xl font-bold relative z-10">News</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/comic.html', 'Comic')" class="app-card col-span-4 row-span-4 bg-gradient-to-r from-yellow-400 to-orange-400 rounded-3xl flex items-center justify-center gap-4 shadow-lg border-b-8 border-orange-600 active:border-b-0 group">
|
||||
<div class="text-6xl drop-shadow-md transform -rotate-6 transition-transform group-hover:rotate-0">📸</div>
|
||||
<div class="text-left text-black"><h3 class="text-3xl font-black font-comic tracking-wide">COMIC</h3></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fixed bottom-6 w-full flex flex-col items-center justify-center pointer-events-none z-40">
|
||||
<button onclick="toggleQR()" class="pointer-events-auto bg-slate-800/90 backdrop-blur border border-slate-600 px-8 py-3 rounded-full shadow-2xl flex items-center gap-4 hover:bg-slate-700 hover:scale-105 hover:border-white transition-all">
|
||||
<img src="assets/logo.png" class="h-12 w-auto object-contain" alt="Logo" onerror="this.style.display='none'">
|
||||
<div class="flex flex-col text-left">
|
||||
<span class="text-[10px] text-slate-400 font-bold uppercase tracking-widest leading-none mb-1">Ein Projekt der</span>
|
||||
<span class="text-lg font-black text-white leading-none tracking-wide">AV-MEDIENZENTRALE</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div class="bg-slate-800 border-2 border-slate-600 rounded-[2rem] max-w-4xl w-full p-8 shadow-2xl relative">
|
||||
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white text-4xl font-bold">✖</button>
|
||||
<h2 class="text-4xl font-black text-white mb-8 border-b border-slate-600 pb-4">GUIDE</h2>
|
||||
<div class="grid grid-cols-2 gap-4 text-base">
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4"><div class="text-3xl">🎹</div><div><h3 class="text-rose-400 font-bold">Sound</h3><p class="text-slate-300 text-sm">Beats & Loops.</p></div></div>
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4"><div class="text-3xl">📹</div><div><h3 class="text-sky-400 font-bold">Loop</h3><p class="text-slate-300 text-sm">Video-Loops.</p></div></div>
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4"><div class="text-3xl">✨</div><div><h3 class="text-violet-400 font-bold">Magic</h3><p class="text-slate-300 text-sm">Green Screen.</p></div></div>
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4"><div class="text-3xl">📸</div><div><h3 class="text-orange-400 font-bold">Comic</h3><p class="text-slate-300 text-sm">Foto Storys.</p></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="qr-modal" onclick="toggleQR()">
|
||||
<div onclick="event.stopPropagation()"
|
||||
style="width: 90%; max-width: 400px; background: white; padding: 40px; border-radius: 30px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.5);">
|
||||
|
||||
<h3 style="color: #0f172a; font-size: 24px; font-weight: 900; text-transform: uppercase; margin: 0 0 20px 0;">Besuche uns!</h3>
|
||||
|
||||
<div style="width: 250px; height: 250px; background: #f8fafc; border: 2px solid #e2e8f0; border-radius: 15px; margin: 0 auto 20px auto; display: flex; align-items: center; justify-content: center;">
|
||||
<img src="assets/qr.png" alt="QR Code" style="width: 230px; height: 230px; object-fit: contain;"
|
||||
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2ZmZiIvPjx0ZXh0IHg9IjUwIiB5PSI1MCIgZm9udC1zaXplPSIxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+UVIgQ29kZTwvdGV4dD48L3N2Zz4='">
|
||||
</div>
|
||||
|
||||
<p style="color: #64748b; font-weight: bold; margin-bottom: 25px;">Scan den Code mit deinem Handy.</p>
|
||||
|
||||
<button onclick="toggleQR()"
|
||||
style="width: 100%; background-color: #0f172a; color: white; border: none; padding: 15px; font-size: 18px; font-weight: 900; border-radius: 12px; cursor: pointer;">
|
||||
SCHLIESSEN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="admin-modal" class="hidden fixed inset-0 bg-black/90 z-[300] flex items-center justify-center p-4">
|
||||
<div class="bg-slate-800 p-8 rounded-3xl border-2 border-slate-600 w-full max-w-lg">
|
||||
<h2 class="text-3xl font-black text-white mb-6">⚙️ ADMIN MENU</h2>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<button onclick="location.reload()" class="bg-blue-600 text-white font-bold py-4 rounded-xl">🔄 Reload</button>
|
||||
<button onclick="closeAdmin()" class="bg-slate-600 text-white font-bold py-4 rounded-xl">Zurück</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const missions = ["Erstelle ein Geräusch: Kälte ❄️", "Comic: Streit ums Brot 🥪", "Magic: Auf dem Mond 🚀", "Werbung: Unsichtbare Socken 🧦", "Loop: Hallo ohne Worte 👋", "Witz in 3 Bildern 🤡", "Selfie: Fliegen ✨", "Elefant mit Schluckauf 🐘", "Pixel-Monster Haustier 👾", "News: Ufo gelandet 👽"];
|
||||
|
||||
const playSound = (id) => {
|
||||
const a = new Audio(`assets/sounds/${id}.mp3`);
|
||||
a.volume = 1.0;
|
||||
a.play().catch(() => {});
|
||||
};
|
||||
|
||||
function openApp(url, appName) {
|
||||
playSound('click');
|
||||
setTimeout(() => { window.location.href = url; }, 300);
|
||||
}
|
||||
|
||||
function newMission() {
|
||||
const txt = document.getElementById('mission-text');
|
||||
txt.innerText = missions[Math.floor(Math.random() * missions.length)];
|
||||
}
|
||||
|
||||
let hubIdleTimer;
|
||||
function resetHubTimer() {
|
||||
clearTimeout(hubIdleTimer);
|
||||
hubIdleTimer = setTimeout(() => {
|
||||
document.getElementById('admin-modal').classList.add('hidden');
|
||||
document.getElementById('info-modal').classList.add('hidden');
|
||||
// Modal verstecken (Klasse entfernen)
|
||||
document.getElementById('qr-modal').classList.remove('modal-visible');
|
||||
document.getElementById('mission-text').innerText = "Wähle eine App! 👉";
|
||||
tapCount = 0;
|
||||
}, 60000);
|
||||
}
|
||||
resetHubTimer();
|
||||
|
||||
function toggleInfo() {
|
||||
resetHubTimer();
|
||||
const m = document.getElementById('info-modal');
|
||||
m.classList.toggle('hidden');
|
||||
m.classList.toggle('flex');
|
||||
}
|
||||
|
||||
function toggleQR() {
|
||||
resetHubTimer();
|
||||
const modal = document.getElementById('qr-modal');
|
||||
// GHOST TOGGLE: Sichtbarkeit umschalten
|
||||
modal.classList.toggle('modal-visible');
|
||||
playSound('click');
|
||||
}
|
||||
|
||||
let tapCount = 0; let tapTimer;
|
||||
function triggerAdmin() {
|
||||
resetHubTimer(); tapCount++; clearTimeout(tapTimer); tapTimer = setTimeout(() => { tapCount = 0; }, 1000);
|
||||
if (tapCount >= 5) {
|
||||
tapCount = 0;
|
||||
if (prompt("PIN:") === "1234") {
|
||||
document.getElementById('admin-modal').classList.remove('hidden');
|
||||
document.getElementById('admin-modal').classList.add('flex');
|
||||
}
|
||||
}
|
||||
}
|
||||
function closeAdmin() {
|
||||
document.getElementById('admin-modal').classList.add('hidden');
|
||||
document.getElementById('admin-modal').classList.remove('flex');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1165
package-lock.json
generated
Normal file
23
package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "medien-station-app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"postinstall": "jetifier",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^7.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/camera": "^8.0.0",
|
||||
"@capacitor/filesystem": "^8.0.0",
|
||||
"awesome-cordova-plugins": "^5.40.0",
|
||||
"cordova-plugin-printer": "^0.8.0",
|
||||
"cordova-plugin-x-socialsharing": "^6.0.4",
|
||||
"es6-promise-plugin": "^4.2.2"
|
||||
}
|
||||
}
|
||||
174
public/apps/comic.html
Normal file
@@ -0,0 +1,174 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Comic Studio</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
|
||||
<script src="../js/tailwind.js"></script>
|
||||
<script src="https://cdn.tailwindcss.com"></script> <style>
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
|
||||
html {
|
||||
width: 100%; height: 100%;
|
||||
background-color: #facc15;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Fixiertes Layout für maximale Stabilität */
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
margin: 0;
|
||||
|
||||
background-color: #facc15;
|
||||
color: black;
|
||||
overflow: hidden !important;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
font-family: sans-serif;
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
/* OPTIMIERUNG: Näher an den Rand rutschen */
|
||||
/* Oben: Notch + 10px (statt vorher 60px pauschal) */
|
||||
padding-top: calc(10px + env(safe-area-inset-top));
|
||||
|
||||
/* Unten: Home-Balken + 20px */
|
||||
padding-bottom: max(20px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.font-comic { font-family: 'Comic Sans MS', 'Chalkboard SE', sans-serif; }
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
|
||||
/* Sprechblasen */
|
||||
.bubble-container { position: absolute; z-index: 20; display: flex; flex-direction: column; align-items: center; left: 50%; bottom: 25%; transform: translateX(-50%); }
|
||||
.bubble-handle { background-color: #3b82f6; color: white; border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: move; box-shadow: 0 4px 6px rgba(0,0,0,0.3); margin-bottom: -15px; z-index: 30; font-size: 24px; touch-action: none; border: 2px solid white; }
|
||||
.bubble-text { background: white; border: 4px solid black; border-radius: 2rem; padding: 1rem 1.5rem; min-width: 160px; max-width: 280px; text-align: center; font-family: 'Comic Sans MS', sans-serif; font-size: 1.5rem; line-height: 1.2; box-shadow: 8px 8px 0px rgba(0,0,0,0.2); outline: none; cursor: text; }
|
||||
|
||||
button:active { transform: scale(0.95); transition: transform 0.1s; }
|
||||
|
||||
@media print {
|
||||
@page { size: landscape; margin: 0mm; }
|
||||
body * { visibility: hidden; }
|
||||
#print-container, #print-container * { visibility: visible; }
|
||||
#print-container {
|
||||
position: fixed; left: 0; top: 0; width: 100vw; height: 100vh;
|
||||
display: flex !important; align-items: center; justify-content: center;
|
||||
background: white; z-index: 99999; margin: 0; padding: 0;
|
||||
}
|
||||
#print-img { width: 100%; height: 100%; object-fit: contain; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col px-2 md:px-4"
|
||||
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
|
||||
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
|
||||
|
||||
<div class="flex-none flex items-center justify-between gap-2 mb-1 bg-white/20 backdrop-blur-md p-1 rounded-3xl shadow-lg border-2 border-white/40 overflow-hidden relative z-50">
|
||||
|
||||
<div class="flex items-center gap-2 shrink-0 pl-1">
|
||||
<button onclick="goHome()" class="bg-slate-900 text-white border-4 border-slate-700 hover:border-white px-3 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none">
|
||||
🏠 <span class="hidden lg:inline">MENÜ</span>
|
||||
</button>
|
||||
<h1 class="text-lg md:text-2xl font-black font-comic text-black tracking-widest drop-shadow-sm uppercase leading-none hidden md:block">
|
||||
COMIC
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1 overflow-x-auto no-scrollbar px-2 shrink-0 justify-center">
|
||||
<button onclick="changeBg('#facc15')" class="w-10 h-10 rounded-xl bg-yellow-400 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
|
||||
<button onclick="changeBg('#ef4444')" class="w-10 h-10 rounded-xl bg-red-500 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
|
||||
<button onclick="changeBg('#3b82f6')" class="w-10 h-10 rounded-xl bg-blue-500 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
|
||||
<button onclick="changeBg('#22c55e')" class="w-10 h-10 rounded-xl bg-green-500 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
|
||||
<button onclick="changeBg('#a855f7')" class="w-10 h-10 rounded-xl bg-purple-500 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
|
||||
<button onclick="changeBg('#ffffff')" class="w-10 h-10 rounded-xl bg-white border-4 border-gray-300 hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 shrink-0 pr-1">
|
||||
<button onclick="switchCamera(); playSound('click')" class="w-12 h-12 bg-white hover:bg-gray-100 text-black rounded-full flex items-center justify-center text-xl shadow-lg border-4 border-slate-900 active:scale-95 transition" title="Kamera wechseln">
|
||||
🔄
|
||||
</button>
|
||||
|
||||
<div class="relative w-24 h-16 md:w-32 md:h-20 bg-black rounded-xl border-4 border-black shadow-xl overflow-hidden hidden sm:block">
|
||||
<video id="webcam" autoplay playsinline muted class="w-full h-full object-cover opacity-90"></video>
|
||||
<div class="absolute bottom-0 right-0 bg-red-600 text-white text-[8px] px-1 rounded-tl font-black animate-pulse pointer-events-none">LIVE</div>
|
||||
</div>
|
||||
|
||||
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 bg-slate-900 text-white rounded-full font-bold border-4 border-slate-700 hover:border-white shadow-xl flex items-center justify-center transition-transform active:scale-95 text-xl">
|
||||
?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-col md:flex-row gap-3 w-full max-w-[95rem] mx-auto mb-2 px-1 overflow-y-auto no-scrollbar z-10 relative min-h-0">
|
||||
<div class="flex-1 bg-white border-[6px] border-black relative shadow-[4px_4px_0px_0px_rgba(0,0,0,0.8)] rounded-xl overflow-hidden group min-h-[150px]" id="panel-0">
|
||||
<canvas class="w-full h-full object-cover bg-gray-100 pointer-events-none"></canvas>
|
||||
<div class="absolute top-0 left-0 bg-black text-white px-3 py-0 text-2xl font-black font-comic rounded-br-lg pointer-events-none z-10">1</div>
|
||||
<div class="bubble-container"><div class="bubble-handle">✥</div><div contenteditable="true" class="bubble-text" onfocus="clearText(this, 'Start...')" onblur="resetText(this, 'Start...')">Start...</div></div>
|
||||
<button onclick="snap(0)" class="absolute bottom-2 right-2 bg-blue-600 hover:bg-blue-500 text-white w-16 h-16 rounded-full text-3xl shadow-xl border-4 border-white z-30 active:scale-90 transition flex items-center justify-center">📸</button>
|
||||
</div>
|
||||
<div class="flex-1 bg-white border-[6px] border-black relative shadow-[4px_4px_0px_0px_rgba(0,0,0,0.8)] rounded-xl overflow-hidden group min-h-[150px]" id="panel-1">
|
||||
<canvas class="w-full h-full object-cover bg-gray-100 pointer-events-none"></canvas>
|
||||
<div class="absolute top-0 left-0 bg-black text-white px-3 py-0 text-2xl font-black font-comic rounded-br-lg pointer-events-none z-10">2</div>
|
||||
<div class="bubble-container"><div class="bubble-handle">✥</div><div contenteditable="true" class="bubble-text" onfocus="clearText(this, 'Dann...')" onblur="resetText(this, 'Dann...')">Dann...</div></div>
|
||||
<button onclick="snap(1)" class="absolute bottom-2 right-2 bg-blue-600 hover:bg-blue-500 text-white w-16 h-16 rounded-full text-3xl shadow-xl border-4 border-white z-30 active:scale-90 transition flex items-center justify-center">📸</button>
|
||||
</div>
|
||||
<div class="flex-1 bg-white border-[6px] border-black relative shadow-[4px_4px_0px_0px_rgba(0,0,0,0.8)] rounded-xl overflow-hidden group min-h-[150px]" id="panel-2">
|
||||
<canvas class="w-full h-full object-cover bg-gray-100 pointer-events-none"></canvas>
|
||||
<div class="absolute top-0 left-0 bg-black text-white px-3 py-0 text-2xl font-black font-comic rounded-br-lg pointer-events-none z-10">3</div>
|
||||
<div class="bubble-container"><div class="bubble-handle">✥</div><div contenteditable="true" class="bubble-text" onfocus="clearText(this, 'Ende!')" onblur="resetText(this, 'Ende!')">Ende!</div></div>
|
||||
<button onclick="snap(2)" class="absolute bottom-2 right-2 bg-blue-600 hover:bg-blue-500 text-white w-16 h-16 rounded-full text-3xl shadow-xl border-4 border-white z-30 active:scale-90 transition flex items-center justify-center">📸</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center flex justify-center gap-4 px-4 z-20 relative shrink-0">
|
||||
<button onclick="playSound('click'); downloadComic()" class="flex-1 max-w-xs bg-slate-900 text-white font-black font-comic text-xl md:text-2xl py-3 rounded-2xl border-b-[6px] border-slate-700 hover:border-slate-500 hover:translate-y-[-2px] active:border-b-0 active:translate-y-2 transition-all shadow-xl flex items-center justify-center gap-2">💾 SPEICHERN</button>
|
||||
<button id="btn-print" onclick="playSound('click'); window.printComic()" class="flex-1 max-w-xs bg-white text-black font-black font-comic text-xl md:text-2xl py-3 rounded-2xl border-b-[6px] border-gray-300 hover:border-gray-400 hover:translate-y-[-2px] active:border-b-0 active:translate-y-2 transition-all shadow-xl flex items-center justify-center gap-2">🖨️ DRUCKEN</button>
|
||||
</div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4" onclick="toggleInfo()">
|
||||
<div class="bg-slate-800 border-4 border-yellow-400 rounded-[2rem] max-w-3xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
|
||||
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-yellow-400 text-4xl font-bold transition">✖</button>
|
||||
<h2 class="text-4xl font-black text-white mb-8 border-b border-slate-600 pb-4 flex items-center gap-4"><span class="text-6xl">📸</span> ANLEITUNG</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 text-base">
|
||||
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4 text-blue-400">📸</div><h3 class="text-white font-bold text-xl mb-2">Fotografieren</h3><p class="text-slate-300 leading-snug">Drücke auf den blauen Kamera-Button in jedem Bildrahmen.</p></div>
|
||||
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4 text-white">💬</div><h3 class="text-white font-bold text-xl mb-2">Texten</h3><p class="text-slate-300 leading-snug">Tippe auf den Text. Du kannst die Blase am blauen Griff verschieben.</p></div>
|
||||
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4 text-yellow-400">🎨</div><h3 class="text-white font-bold text-xl mb-2">Hintergrund</h3><p class="text-slate-300 leading-snug">Wähle oben eine Farbe für deinen Comic aus!</p></div>
|
||||
</div>
|
||||
<button onclick="toggleInfo(); playSound('click')" class="mt-8 w-full bg-yellow-500 hover:bg-yellow-400 text-black font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="print-container" class="hidden"><img id="print-img" src=""></div>
|
||||
|
||||
<script>
|
||||
const _soundCache = { 'click': new Audio('../assets/sounds/click.mp3'), 'shutter': new Audio('../assets/sounds/shutter.mp3'), 'success': new Audio('../assets/sounds/success.mp3') };
|
||||
Object.values(_soundCache).forEach(s => s.load());
|
||||
window.playSound = function(id) { if (_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.currentTime = 0; s.play().catch(e => console.warn(e)); } };
|
||||
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
|
||||
const video = document.getElementById('webcam'); let currentFacingMode = 'user'; let currentStream; let idleTimer; let currentBgColor = '#facc15';
|
||||
function resetIdleTimer() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { window.location.href = '../index.html'; }, 120000); }
|
||||
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, resetIdleTimer, {passive: true})); resetIdleTimer();
|
||||
function toggleInfo() { const modal = document.getElementById('info-modal'); if (modal.classList.contains('hidden')) { modal.classList.remove('hidden'); modal.classList.add('flex'); } else { modal.classList.add('hidden'); modal.classList.remove('flex'); } }
|
||||
async function startCamera() { try { if (currentStream) { currentStream.getTracks().forEach(track => track.stop()); } const stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: currentFacingMode } }); currentStream = stream; video.srcObject = stream; } catch (err) { alert("Kamera Fehler: " + err.message); } }
|
||||
function switchCamera() { currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user'; startCamera(); }
|
||||
function snap(panelIndex) { playSound('shutter'); const panel = document.getElementById('panel-' + panelIndex); const canvas = panel.querySelector('canvas'); const ctx = canvas.getContext('2d'); canvas.width = 1280; canvas.height = 720; const vRatio = video.videoWidth / video.videoHeight; const cRatio = canvas.width / canvas.height; let sx, sy, sWidth, sHeight; if (vRatio > cRatio) { sHeight = video.videoHeight; sWidth = sHeight * cRatio; sx = (video.videoWidth - sWidth) / 2; sy = 0; } else { sWidth = video.videoWidth; sHeight = sWidth / cRatio; sx = 0; sy = (video.videoHeight - sHeight) / 2; } ctx.save(); if(currentFacingMode === 'user') { ctx.scale(-1, 1); ctx.drawImage(video, sx, sy, sWidth, sHeight, -canvas.width, 0, canvas.width, canvas.height); } else { ctx.drawImage(video, sx, sy, sWidth, sHeight, 0, 0, canvas.width, canvas.height); } ctx.restore(); panel.style.borderColor = '#3b82f6'; setTimeout(() => panel.style.borderColor = 'black', 200); }
|
||||
function changeBg(color) { playSound('click'); currentBgColor = color; document.body.style.backgroundColor = color; }
|
||||
function clearText(el, defaultText) { if (el.innerText.trim() === defaultText) el.innerText = ""; }
|
||||
function resetText(el, defaultText) { if (el.innerText.trim() === "") el.innerText = defaultText; }
|
||||
const handles = document.querySelectorAll('.bubble-handle'); let activeDrag = null; let startX, startY, initialLeft, initialTop;
|
||||
handles.forEach(handle => { handle.addEventListener('mousedown', startDrag); handle.addEventListener('touchstart', startDrag, {passive: false}); });
|
||||
function startDrag(e) { e.preventDefault(); activeDrag = e.target.parentElement; const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; startX = clientX; startY = clientY; initialLeft = activeDrag.offsetLeft; initialTop = activeDrag.offsetTop; document.addEventListener('mousemove', doDrag); document.addEventListener('mouseup', stopDrag); document.addEventListener('touchmove', doDrag, {passive: false}); document.addEventListener('touchend', stopDrag); }
|
||||
function doDrag(e) { if (!activeDrag) return; e.preventDefault(); const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; const dx = clientX - startX; const dy = clientY - startY; activeDrag.style.left = (initialLeft + dx) + 'px'; activeDrag.style.top = (initialTop + dy) + 'px'; activeDrag.style.transform = 'none'; }
|
||||
function stopDrag() { activeDrag = null; document.removeEventListener('mousemove', doDrag); document.removeEventListener('mouseup', stopDrag); document.removeEventListener('touchmove', doDrag); document.removeEventListener('touchend', stopDrag); }
|
||||
function generateComicImage() { const refPanel = document.getElementById('panel-0'); const refRect = refPanel.getBoundingClientRect(); const screenRatio = refRect.width / refRect.height; const panelW = 1280; const panelH = panelW / screenRatio; const border = 60; const gap = 40; const titleSpace = 250; const master = document.createElement('canvas'); master.width = (panelW * 3) + (gap * 2) + (border * 2); master.height = panelH + (border * 2) + titleSpace; const ctx = master.getContext('2d'); ctx.fillStyle = currentBgColor; ctx.fillRect(0, 0, master.width, master.height); ctx.fillStyle = 'black'; ctx.font = '900 120px "Comic Sans MS", sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText("MEINE FOTO-STORY", master.width / 2, titleSpace / 2); [0, 1, 2].forEach(i => { const panelDiv = document.getElementById('panel-' + i); const srcCanvas = panelDiv.querySelector('canvas'); const textDiv = panelDiv.querySelector('.bubble-text'); const panelOffsetX = border + (i * (panelW + gap)); const panelOffsetY = titleSpace; ctx.fillStyle = '#eee'; ctx.fillRect(panelOffsetX, panelOffsetY, panelW, panelH); if (srcCanvas.width > 0) { const srcW = srcCanvas.width; const srcH = srcCanvas.height; const srcRatio = srcW / srcH; let sW, sH, sX, sY; if (srcRatio > screenRatio) { sH = srcH; sW = srcH * screenRatio; sX = (srcW - sW) / 2; sY = 0; } else { sW = srcW; sH = srcW / screenRatio; sX = 0; sY = (srcH - sH) / 2; } ctx.drawImage(srcCanvas, sX, sY, sW, sH, panelOffsetX, panelOffsetY, panelW, panelH); } ctx.lineWidth = 20; ctx.strokeStyle = 'black'; ctx.strokeRect(panelOffsetX, panelOffsetY, panelW, panelH); ctx.fillStyle = 'black'; ctx.fillRect(panelOffsetX, panelOffsetY, 120, 120); ctx.fillStyle = 'white'; ctx.font = 'bold 80px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(i + 1, panelOffsetX + 60, panelOffsetY + 65); const text = textDiv.innerText.trim(); if (text) { const domPanelRect = panelDiv.getBoundingClientRect(); const domBubbleRect = textDiv.getBoundingClientRect(); const scaleX = panelW / domPanelRect.width; const scaleY = panelH / domPanelRect.height; const relX = domBubbleRect.left - domPanelRect.left; const relY = domBubbleRect.top - domPanelRect.top; const bubbleX = panelOffsetX + (relX * scaleX); const bubbleY = panelOffsetY + (relY * scaleY); drawRoundedRectBubble(ctx, bubbleX, bubbleY, text); } }); return master.toDataURL('image/png'); }
|
||||
function drawRoundedRectBubble(ctx, x, y, text) { ctx.font = 'bold 50px "Comic Sans MS", sans-serif'; const padding = 50; const lineHeight = 60; const maxWidth = 500; const words = text.split(' '); let line = ''; let lines = []; for(let n = 0; n < words.length; n++) { let testLine = line + words[n] + ' '; let metrics = ctx.measureText(testLine); if (metrics.width > maxWidth && n > 0) { lines.push(line); line = words[n] + ' '; } else { line = testLine; } } lines.push(line); let maxLineWidth = 0; lines.forEach(l => { const m = ctx.measureText(l); if(m.width > maxLineWidth) maxLineWidth = m.width; }); const boxW = maxLineWidth + (padding * 2); const boxH = (lines.length * lineHeight) + (padding * 2); ctx.fillStyle = 'rgba(0,0,0,0.2)'; roundRect(ctx, x + 15, y + 15, boxW, boxH, 40, true, false); ctx.fillStyle = 'white'; ctx.strokeStyle = 'black'; ctx.lineWidth = 10; roundRect(ctx, x, y, boxW, boxH, 40, true, true); ctx.fillStyle = 'black'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; let ty = y + padding + (lineHeight/2); const centerX = x + (boxW/2); lines.forEach(l => { ctx.fillText(l.trim(), centerX, ty); ty += lineHeight; }); }
|
||||
function roundRect(ctx, x, y, w, h, r, fill, stroke) { ctx.beginPath(); ctx.moveTo(x+r, y); ctx.arcTo(x+w, y, x+w, y+h, r); ctx.arcTo(x+w, y+h, x, y+h, r); ctx.arcTo(x, y+h, x, y, r); ctx.arcTo(x, y, x+w, y, r); ctx.closePath(); if (fill) ctx.fill(); if (stroke) ctx.stroke(); }
|
||||
function downloadComic() { playSound('success'); const dataUrl = generateComicImage(); const link = document.createElement('a'); link.download = 'mein-comic.png'; link.href = dataUrl; link.click(); }
|
||||
window.printComic = async function() { const btn = document.getElementById('btn-print'); const oldText = btn ? btn.innerText : "🖨️ DRUCKEN"; if(btn) { btn.innerText = "⏳ Sende..."; btn.disabled = true; } const dataUrl = generateComicImage(); try { if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DirectPrinter) { await window.Capacitor.Plugins.DirectPrinter.printBase64({ data: dataUrl, name: 'Comic-Studio' }); if(btn) btn.innerText = "✅ Gedruckt!"; playSound('success'); } else { console.warn("DirectPrinter Plugin nicht gefunden, nutze Browser Print."); const printImg = document.getElementById('print-img'); printImg.src = dataUrl; printImg.onload = () => { setTimeout(() => { window.print(); }, 500); }; if(btn) btn.innerText = "✅ OK"; playSound('success'); } } catch (error) { console.error("Druckfehler:", error); alert("Drucken fehlgeschlagen: " + error.message); if(btn) btn.innerText = "❌ Fehler"; } setTimeout(() => { if(btn) { btn.innerText = oldText; btn.disabled = false; } }, 3000); }
|
||||
window.onload = startCamera;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
137
public/apps/gif.html
Normal file
@@ -0,0 +1,137 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Loop Cam</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
|
||||
<script src="../js/tailwind.js"></script>
|
||||
|
||||
<style>
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
html { width: 100%; height: 100%; background-color: #0f172a; }
|
||||
body {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0; margin: 0;
|
||||
background-color: #0f172a;
|
||||
color: white;
|
||||
overflow: hidden !important;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
overscroll-behavior: none;
|
||||
font-family: sans-serif;
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
box-sizing: border-box;
|
||||
padding-top: calc(10px + env(safe-area-inset-top));
|
||||
padding-bottom: max(20px, env(safe-area-inset-bottom));
|
||||
}
|
||||
video, canvas { display: block; }
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
button:active { transform: scale(0.95); transition: transform 0.1s; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col p-2 md:p-4"
|
||||
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
|
||||
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
|
||||
|
||||
<div class="flex-none flex justify-between items-center z-50 mb-2 relative h-16">
|
||||
<button onclick="goHome()" class="bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none shrink-0 z-50">
|
||||
🏠 MENÜ
|
||||
</button>
|
||||
<div class="absolute inset-0 flex items-center justify-center pointer-events-none z-0">
|
||||
<h1 class="text-2xl md:text-4xl font-black text-white drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] tracking-widest uppercase">
|
||||
✨ LOOP CAM
|
||||
</h1>
|
||||
</div>
|
||||
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 bg-slate-800 text-white rounded-full font-bold border-4 border-slate-600 hover:border-white shadow-xl flex items-center justify-center transition-transform active:scale-95 text-xl shrink-0 z-50">
|
||||
?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-row gap-2 md:gap-4 min-h-0 items-center justify-center w-full max-w-[95rem] mx-auto overflow-hidden pb-1">
|
||||
<div class="w-20 md:w-24 flex flex-col gap-2 bg-gray-900/50 p-2 rounded-2xl border border-white/10 shadow-2xl shrink-0 h-full max-h-full overflow-hidden">
|
||||
<div class="text-[8px] font-bold text-gray-500 uppercase tracking-widest text-center w-full py-1 shrink-0">Filter</div>
|
||||
<div class="flex-1 overflow-y-auto no-scrollbar flex flex-col items-center justify-evenly w-full pb-1">
|
||||
<button onclick="setFilter('')" class="w-16 h-12 rounded-xl bg-white text-black font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-transparent flex flex-col items-center justify-center"><span>⚪</span> NORM</button>
|
||||
<button onclick="setFilter('grayscale(100%) contrast(1.2)')" class="w-16 h-12 rounded-xl bg-gray-600 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: grayscale(100%)"><span>⚫</span> S/W</button>
|
||||
<button onclick="setFilter('sepia(80%)')" class="w-16 h-12 rounded-xl bg-amber-700 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: sepia(80%)"><span>📜</span> ALT</button>
|
||||
<button onclick="setFilter('invert(100%)')" class="w-16 h-12 rounded-xl bg-blue-600 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: invert(100%)"><span>💀</span> XRAY</button>
|
||||
<button onclick="setFilter('saturate(250%)')" class="w-16 h-12 rounded-xl bg-pink-500 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: saturate(250%)"><span>🎨</span> POP</button>
|
||||
<button onclick="setFilter('hue-rotate(90deg)')" class="w-16 h-12 rounded-xl bg-emerald-500 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: hue-rotate(90deg)"><span>👽</span> ALIEN</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-col items-center h-full min-w-0 gap-2 md:gap-4 overflow-hidden">
|
||||
<div id="video-container" class="relative flex-1 w-full bg-gray-900 rounded-[1.5rem] overflow-hidden border-[6px] border-gray-800 shadow-2xl group transition-colors duration-200 flex items-center justify-center min-h-0">
|
||||
<video id="video" autoplay playsinline muted class="w-full h-full object-contain"></video>
|
||||
<canvas id="canvas" class="w-full h-full object-contain hidden"></canvas>
|
||||
<div id="countdown" class="absolute inset-0 flex items-center justify-center text-[8rem] font-black text-white hidden bg-black/50 backdrop-blur-sm z-30">3</div>
|
||||
<div id="rec-indicator" class="absolute top-4 right-4 hidden items-center gap-2 z-30 bg-red-600 text-white px-3 py-1 rounded-full font-black text-sm animate-pulse shadow-lg"><div class="w-2 h-2 bg-white rounded-full"></div> REC</div>
|
||||
<div id="saving-overlay" class="absolute inset-0 bg-black/80 z-50 hidden flex-col items-center justify-center text-white">
|
||||
<div class="text-6xl animate-spin mb-4">💾</div>
|
||||
<div class="text-2xl font-bold">Speichere...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-16 md:h-20 w-full flex-shrink-0 flex items-center justify-center relative">
|
||||
<div id="start-overlay" class="absolute w-full flex justify-center items-center gap-4 z-20 pointer-events-none">
|
||||
<button onclick="switchCamera(); playSound('click')" id="switch-btn" class="pointer-events-auto w-14 h-14 bg-slate-700 hover:bg-slate-600 text-white rounded-xl shadow-xl border-b-4 border-slate-900 active:border-b-0 active:translate-y-2 transition-all flex items-center justify-center shrink-0" title="Kamera wechseln"><span class="text-2xl">🔄</span></button>
|
||||
<button onclick="playSound('click'); startCountdown()" class="pointer-events-auto bg-sky-500 hover:bg-sky-400 text-white text-2xl font-black py-3 px-8 rounded-xl shadow-2xl border-b-4 border-sky-700 active:border-b-0 active:translate-y-2 transition-all w-full max-w-xs flex items-center justify-center gap-2"><span>🎥</span> ACTION!</button>
|
||||
</div>
|
||||
|
||||
<div id="action-buttons" class="hidden gap-2 md:gap-4 w-full justify-center z-20 pointer-events-none">
|
||||
<button onclick="reset(); playSound('click')" class="pointer-events-auto bg-yellow-500 hover:bg-yellow-400 text-black text-xl font-black py-3 px-6 rounded-xl border-b-4 border-yellow-700 active:border-b-0 active:translate-y-2 transition-all shadow-xl flex items-center justify-center gap-2 flex-1 max-w-[150px]">🔄 NOCHMAL</button>
|
||||
<button onclick="saveLoopVideo(); playSound('success')" class="pointer-events-auto bg-green-600 hover:bg-green-500 text-white text-xl font-black py-3 px-6 rounded-xl border-b-4 border-green-800 active:border-b-0 active:translate-y-2 transition-all shadow-xl flex items-center justify-center gap-2 flex-1 max-w-[150px]">💾 SPEICHERN</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-20 md:w-24 flex-shrink-0 flex flex-col justify-center gap-2 z-40 bg-gray-900/50 p-2 rounded-2xl border border-white/10 shadow-2xl h-full max-h-full overflow-hidden transition-opacity opacity-30 pointer-events-none" id="speed-controls">
|
||||
<div class="text-center text-[8px] font-bold text-gray-400 mb-1 uppercase tracking-widest shrink-0">Tempo</div>
|
||||
<div class="flex-1 flex flex-col justify-center gap-2">
|
||||
<button id="btn-slow" onclick="setSpeed(150)" class="w-full h-16 bg-green-700 hover:bg-green-600 text-white rounded-lg shadow-lg border-b-2 border-green-900 transition active:scale-95 flex flex-col items-center justify-center gap-1 opacity-50"><span class="text-xl">🐢</span><span class="text-[8px] font-black uppercase">Langsam</span></button>
|
||||
<button id="btn-norm" onclick="setSpeed(40)" class="w-full h-16 bg-blue-700 hover:bg-blue-600 text-white rounded-lg shadow-lg border-b-2 border-blue-900 transition active:scale-95 flex flex-col items-center justify-center gap-1 opacity-50"><span class="text-xl">🚶</span><span class="text-[8px] font-black uppercase">Mittel</span></button>
|
||||
<button id="btn-turbo" onclick="setSpeed(10)" class="w-full h-16 bg-red-700 hover:bg-red-600 text-white rounded-lg shadow-lg border-b-2 border-red-900 transition active:scale-95 flex flex-col items-center justify-center gap-1 opacity-50"><span class="text-xl">🚀</span><span class="text-[8px] font-black uppercase">Turbo</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
|
||||
<div class="bg-slate-800 border-4 border-sky-500 rounded-[2rem] max-w-2xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
|
||||
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-sky-400 text-4xl font-bold transition">✖</button>
|
||||
<h2 class="text-4xl font-black text-white mb-8 border-b border-slate-600 pb-4 flex items-center gap-4"><span class="text-6xl">📹</span> ANLEITUNG</h2>
|
||||
<div class="text-lg text-slate-300 space-y-4">
|
||||
<p>1. Wähle links einen <b>Filter</b>.</p>
|
||||
<p>2. Drücke unten auf <b>ACTION!</b>.</p>
|
||||
<p>3. Nach der Aufnahme kannst du rechts das <b>Tempo</b> ändern.</p>
|
||||
<p>4. Mit <b>🔄</b> wechselst du die Kamera.</p>
|
||||
</div>
|
||||
<button onclick="toggleInfo(); playSound('click')" class="mt-8 w-full bg-sky-600 hover:bg-sky-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const _soundCache = {'click': new Audio('../assets/sounds/click.mp3'), 'shutter': new Audio('../assets/sounds/shutter.mp3'), 'success': new Audio('../assets/sounds/success.mp3')};
|
||||
Object.values(_soundCache).forEach(s => s.load());
|
||||
window.playSound = function(id) { if(_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e=>{}); } };
|
||||
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
|
||||
let idleTimer;
|
||||
window.resetIdleTimer = function() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { if (document.getElementById('countdown').classList.contains('hidden') && document.getElementById('saving-overlay').classList.contains('hidden')) { window.location.href = '../index.html'; } else { window.resetIdleTimer(); } }, 120000); };
|
||||
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, window.resetIdleTimer, {passive: true})); window.resetIdleTimer();
|
||||
window.toggleInfo = function() { const modal = document.getElementById('info-modal'); modal.classList.toggle('hidden'); modal.classList.toggle('flex'); }
|
||||
let video, canvas, ctx, videoContainer, recIndicator, startBtn, resultBtns, speedWrapper, switchBtn; let frames = []; let speed = 40; let loopInterval; let currentStream; let currentFacingMode = 'user';
|
||||
window.initCam = async function() { video = document.getElementById('video'); canvas = document.getElementById('canvas'); ctx = canvas.getContext('2d'); videoContainer = document.getElementById('video-container'); recIndicator = document.getElementById('rec-indicator'); startBtn = document.getElementById('start-overlay'); resultBtns = document.getElementById('action-buttons'); speedWrapper = document.getElementById('speed-controls'); switchBtn = document.getElementById('switch-btn'); if(!video) return; try { if (currentStream && typeof currentStream.getTracks === 'function') currentStream.getTracks().forEach(track => track.stop()); const stream = await navigator.mediaDevices.getUserMedia({ video: { width: {ideal: 1280}, height: {ideal: 720}, facingMode: currentFacingMode } }); currentStream = stream; video.srcObject = stream; video.onloadedmetadata = () => { video.play(); }; video.style.transform = (currentFacingMode === 'user') ? 'scaleX(-1)' : 'scaleX(1)'; } catch (err) { alert("Kamera-Fehler: " + err.message); } }
|
||||
window.switchCamera = function() { currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user'; window.initCam(); }
|
||||
window.setFilter = function(filter) { playSound('click'); if(video) video.style.filter = filter; if(canvas) canvas.style.filter = filter; }
|
||||
window.startCountdown = function() { startBtn.classList.add('hidden'); switchBtn.classList.add('hidden'); const cd = document.getElementById('countdown'); cd.classList.remove('hidden'); cd.style.display = 'flex'; let count = 3; cd.innerText = count; const timer = setInterval(() => { count--; if (count > 0) { cd.innerText = count; } else { clearInterval(timer); cd.style.display = 'none'; playSound('shutter'); record(); } }, 800); }
|
||||
function record() { frames = []; canvas.width = video.videoWidth; canvas.height = video.videoHeight; videoContainer.classList.replace('border-gray-800', 'border-red-600'); recIndicator.classList.remove('hidden'); recIndicator.style.display = 'flex'; let startTime = Date.now(); function captureLoop() { if (Date.now() - startTime < 2000) { ctx.save(); if (currentFacingMode === 'user') { ctx.translate(canvas.width, 0); ctx.scale(-1, 1); } ctx.drawImage(video, 0, 0, canvas.width, canvas.height); ctx.restore(); frames.push(ctx.getImageData(0, 0, canvas.width, canvas.height)); requestAnimationFrame(captureLoop); } else { play(); } } captureLoop(); }
|
||||
function play() { videoContainer.classList.replace('border-red-600', 'border-gray-800'); recIndicator.classList.add('hidden'); recIndicator.style.display = ''; video.classList.add('hidden'); canvas.classList.remove('hidden'); resultBtns.classList.remove('hidden'); resultBtns.style.display = 'flex'; speedWrapper.classList.remove('opacity-30', 'pointer-events-none'); window.setSpeed(40); const loopFrames = [...frames, ...[...frames].reverse()]; startLoop(loopFrames, 0); }
|
||||
function startLoop(loopFrames, i) { clearInterval(loopInterval); loopInterval = setInterval(() => { ctx.putImageData(loopFrames[i], 0, 0); i = (i + 1) % loopFrames.length; }, speed); }
|
||||
window.setSpeed = function(ms) { playSound('click'); speed = ms; updateSpeedUI(); if (canvas && !canvas.classList.contains('hidden')) { const loopFrames = [...frames, ...[...frames].reverse()]; startLoop(loopFrames, 0); } }
|
||||
function updateSpeedUI() { const btns = { 150: document.getElementById('btn-slow'), 40: document.getElementById('btn-norm'), 10: document.getElementById('btn-turbo') }; Object.values(btns).forEach(btn => { if(btn) { btn.classList.remove('opacity-100', 'scale-105', 'border-white'); btn.classList.add('opacity-50', 'border-transparent'); } }); const active = btns[speed]; if(active) { active.classList.remove('opacity-50', 'border-transparent'); active.classList.add('opacity-100', 'scale-105', 'border-white'); } }
|
||||
window.saveLoopVideo = function() { document.getElementById('saving-overlay').classList.remove('hidden'); document.getElementById('saving-overlay').style.display = 'flex'; const stream = canvas.captureStream(30); const recorder = new MediaRecorder(stream, { mimeType: 'video/webm' }); const chunks = []; recorder.ondataavailable = e => chunks.push(e.data); recorder.onstop = () => { const blob = new Blob(chunks, { type: 'video/webm' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'mein-loop.webm'; a.click(); document.getElementById('saving-overlay').classList.add('hidden'); document.getElementById('saving-overlay').style.display = ''; }; recorder.start(); setTimeout(() => recorder.stop(), 3000); }
|
||||
window.reset = function() { clearInterval(loopInterval); video.classList.remove('hidden'); canvas.classList.add('hidden'); startBtn.classList.remove('hidden'); resultBtns.classList.add('hidden'); resultBtns.style.display = 'none'; speedWrapper.classList.add('opacity-30', 'pointer-events-none'); switchBtn.classList.remove('hidden'); window.setSpeed(40); videoContainer.classList.replace('border-red-600', 'border-gray-800'); recIndicator.classList.add('hidden'); recIndicator.style.display = ''; }
|
||||
window.addEventListener('beforeunload', () => { try { if(currentStream) currentStream.getTracks().forEach(track => track.stop()); } catch(e) {} });
|
||||
window.onload = window.initCam;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
276
public/apps/magic.html
Normal file
@@ -0,0 +1,276 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Magic Selfie</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
|
||||
<script src="../js/tailwind.js"></script>
|
||||
<script src="../js/camera_utils.js"></script>
|
||||
<script src="../js/selfie_segmentation.js"></script>
|
||||
|
||||
<style>
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
|
||||
html {
|
||||
width: 100%; height: 100%;
|
||||
background-color: #0f172a;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Fixiertes Layout */
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
margin: 0;
|
||||
|
||||
background-color: #0f172a;
|
||||
color: white;
|
||||
overflow: hidden !important;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
transition: background-color 0.5s;
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
/* Body Padding */
|
||||
padding-top: calc(10px + env(safe-area-inset-top));
|
||||
padding-bottom: max(20px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
button:active { transform: scale(0.95); transition: transform 0.1s; }
|
||||
|
||||
@media print {
|
||||
body * { visibility: hidden; }
|
||||
#print-container, #print-container * { visibility: visible; }
|
||||
#print-container { position: fixed; left: 0; top: 0; width: 100%; height: 100%; display: flex !important; align-items: center; justify-content: center; background: white; z-index: 99999; }
|
||||
#print-img { max-width: 100%; max-height: 100%; object-fit: contain; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col md:flex-row bg-slate-900" onclick="window.resetIdleTimer()" ontouchstart="window.resetIdleTimer()">
|
||||
|
||||
<button onclick="goHome()" class="absolute left-6 z-[100] bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-6 py-3 rounded-full text-xl font-black shadow-2xl flex items-center gap-3 transition-transform active:scale-95 no-underline" style="top: calc(20px + env(safe-area-inset-top));">
|
||||
🏠 MENÜ
|
||||
</button>
|
||||
|
||||
<div class="absolute w-full text-center pointer-events-none z-50 hidden md:block" style="top: calc(20px + env(safe-area-inset-top));">
|
||||
<h1 class="text-3xl font-black text-white drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] tracking-widest uppercase">
|
||||
✨ MAGIC SELFIE
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<button onclick="window.toggleInfo(); playSound('click')" class="absolute right-6 z-[100] w-14 h-14 bg-slate-800 text-white rounded-full font-bold border-4 border-slate-600 hover:border-white shadow-xl flex items-center justify-center transition-transform active:scale-95 text-2xl" style="top: calc(20px + env(safe-area-inset-top));">
|
||||
?
|
||||
</button>
|
||||
|
||||
<div class="relative flex-1 bg-black flex flex-col items-center justify-center overflow-hidden h-[60%] md:h-full w-full rounded-b-3xl md:rounded-r-3xl md:rounded-bl-none shadow-2xl z-10">
|
||||
|
||||
<div class="relative w-full h-full flex items-center justify-center p-2 md:p-8 pb-24">
|
||||
<video id="ai-video" autoplay playsinline muted class="hidden"></video>
|
||||
<canvas id="ai-canvas" class="max-w-full max-h-full object-contain shadow-2xl rounded-lg"></canvas>
|
||||
|
||||
<div id="preview-overlay" class="absolute inset-0 bg-slate-900 z-50 hidden flex-col items-center justify-center">
|
||||
<h2 class="text-3xl font-bold text-white mb-6 animate-bounce">Dein magisches Foto!</h2>
|
||||
<div class="relative max-w-[90%] max-h-[60%] border-8 border-white shadow-2xl rounded-xl overflow-hidden bg-black">
|
||||
<img id="preview-img-display" class="w-full h-full object-contain" src="">
|
||||
</div>
|
||||
<div class="flex gap-6 mt-8">
|
||||
<button onclick="window.closePreview(); playSound('click')" class="bg-yellow-500 hover:bg-yellow-400 text-black font-black py-4 px-8 rounded-2xl text-xl shadow-xl active:scale-95 transition border-b-8 border-yellow-700 active:border-b-0 active:translate-y-2 flex items-center gap-2">🔄 NOCHMAL</button>
|
||||
<button id="btn-print" onclick="window.printFromPreview(this)" class="bg-white text-slate-900 font-black py-4 px-8 rounded-2xl text-xl shadow-xl hover:bg-gray-100 active:scale-95 transition border-b-8 border-gray-400 active:border-b-0 active:translate-y-2 flex items-center gap-2">🖨️ DRUCKEN</button>
|
||||
<button onclick="window.saveFromPreview()" class="bg-violet-600 text-white font-black py-4 px-8 rounded-2xl text-xl shadow-xl hover:bg-violet-500 active:scale-95 transition border-b-8 border-violet-800 active:border-b-0 active:translate-y-2 flex items-center gap-2">💾 FOTO</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ai-loading" class="absolute inset-0 bg-black/95 flex flex-col items-center justify-center z-40 p-4 text-center">
|
||||
<div class="text-6xl animate-spin mb-4">🔮</div>
|
||||
<div class="text-2xl font-bold mb-4">Lade KI...</div>
|
||||
<div id="ai-debug-box" class="text-xs text-left font-mono bg-black/80 border border-gray-700 p-4 rounded-xl mt-4 max-w-lg w-full h-32 overflow-y-auto opacity-70"><div>[Log] App gestartet...</div></div>
|
||||
<div class="mt-8 flex gap-6">
|
||||
<button onclick="location.reload()" class="border-2 border-white/30 px-6 py-3 rounded-full hover:bg-white/10">Neu laden</button>
|
||||
<button onclick="window.drawFallback(); document.getElementById('ai-loading').style.display='none'; playSound('click')" class="bg-red-600 px-6 py-3 rounded-full font-bold hover:bg-red-500">Ohne KI starten</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-controls" class="absolute bottom-6 flex gap-4 z-30 w-full justify-center px-4">
|
||||
<button onclick="window.switchCamera(); playSound('click')" class="w-20 bg-slate-700 text-white font-black py-4 rounded-2xl text-2xl shadow-xl hover:scale-105 active:scale-95 transition border-b-8 border-slate-900 active:border-b-0 active:translate-y-2 flex items-center justify-center" title="Kamera wechseln">🔄</button>
|
||||
<button onclick="window.capturePhoto()" class="flex-1 max-w-[200px] bg-violet-600 text-white font-black py-4 rounded-2xl text-lg md:text-xl shadow-xl hover:scale-105 active:scale-95 transition border-b-8 border-violet-800 active:border-b-0 active:translate-y-2 flex items-center justify-center gap-2">✨ FOTO</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-[40%] md:h-full md:w-1/3 min-w-[280px] max-w-md bg-slate-800 p-4 border-t-4 md:border-t-0 md:border-l-4 border-slate-700 overflow-y-auto no-scrollbar z-20 flex flex-col">
|
||||
<h3 class="text-white font-black text-xl mb-4 text-center uppercase tracking-wider sticky top-0 bg-slate-800 pb-2 z-10 pt-2">🌍 Reiseziel</h3>
|
||||
<div class="grid grid-cols-2 gap-3 pb-4">
|
||||
<div onclick="window.setBg('../assets/weltraum.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/weltraum.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-indigo-900/50 z-0"><span class="text-5xl">🚀</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">WELTALL</span></div>
|
||||
<div onclick="window.setBg('../assets/paris.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/paris.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-blue-900/50 z-0"><span class="text-5xl">🇫🇷</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">PARIS</span></div>
|
||||
<div onclick="window.setBg('../assets/dschungel.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/dschungel.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-green-900/50 z-0"><span class="text-5xl">🌴</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">DSCHUNGEL</span></div>
|
||||
<div onclick="window.setBg('../assets/unterwasser.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/unterwasser.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-cyan-900/50 z-0"><span class="text-5xl">🐠</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">OZEAN</span></div>
|
||||
<div onclick="window.setBg('../assets/wolken.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/wolken.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-sky-500/50 z-0"><span class="text-5xl">☁️</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">HIMMEL</span></div>
|
||||
<div onclick="window.setBg('../assets/schloss.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/schloss.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-purple-900/50 z-0"><span class="text-5xl">🏰</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">SCHLOSS</span></div>
|
||||
<div onclick="window.setBg('../assets/dino.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/dino.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-amber-900/50 z-0"><span class="text-5xl">🦖</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">DINO</span></div>
|
||||
<div onclick="window.setBg('../assets/stadion.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/stadion.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-green-800/50 z-0"><span class="text-5xl">⚽</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">STADION</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4" onclick="window.toggleInfo()">
|
||||
<div class="bg-slate-800 border-4 border-violet-500 rounded-[2rem] max-w-2xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
|
||||
<button onclick="window.toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-violet-400 text-4xl font-bold transition">✖</button>
|
||||
<h2 class="text-4xl font-black text-white mb-8 border-b border-slate-600 pb-4 flex items-center gap-4"><span class="text-6xl">✨</span> ANLEITUNG</h2>
|
||||
<div class="text-lg text-slate-300 space-y-4">
|
||||
<p>1. Wähle rechts einen <b>Hintergrund</b>.</p>
|
||||
<p>2. Die KI schneidet dich automatisch aus.</p>
|
||||
<p>3. Drücke <b>FOTO</b> für eine Vorschau.</p>
|
||||
<p>4. Dann kannst du <b>Drucken</b> oder nochmal probieren.</p>
|
||||
</div>
|
||||
<button onclick="window.toggleInfo(); playSound('click')" class="mt-8 w-full bg-violet-600 hover:bg-violet-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="print-container" class="hidden"><img id="print-img" src=""></div>
|
||||
|
||||
<script>
|
||||
// --- AUDIO SYSTEM ---
|
||||
const _soundCache = {
|
||||
'click': new Audio('../assets/sounds/click.mp3'),
|
||||
'shutter': new Audio('../assets/sounds/shutter.mp3'),
|
||||
'success': new Audio('../assets/sounds/success.mp3')
|
||||
};
|
||||
Object.values(_soundCache).forEach(s => s.load());
|
||||
|
||||
window.playSound = function(id) {
|
||||
const baseSound = _soundCache[id];
|
||||
if (baseSound) {
|
||||
const soundClone = baseSound.cloneNode();
|
||||
soundClone.volume = 1.0;
|
||||
soundClone.play().catch(e => console.warn("Sound-Problem:", e));
|
||||
}
|
||||
};
|
||||
|
||||
// --- HOME FUNKTION ---
|
||||
window.goHome = function() {
|
||||
playSound('click');
|
||||
setTimeout(() => {
|
||||
window.location.href = '../index.html';
|
||||
}, 300);
|
||||
};
|
||||
|
||||
let idleTimer;
|
||||
function resetIdleTimer() {
|
||||
clearTimeout(idleTimer);
|
||||
idleTimer = setTimeout(() => {
|
||||
if (document.getElementById('preview-overlay').classList.contains('hidden')) { window.location.href = '../index.html'; }
|
||||
}, 90000);
|
||||
}
|
||||
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, window.resetIdleTimer, {passive: true}));
|
||||
window.resetIdleTimer();
|
||||
|
||||
window.toggleInfo = function() {
|
||||
const modal = document.getElementById('info-modal');
|
||||
if (modal.classList.contains('hidden')) { modal.classList.remove('hidden'); modal.classList.add('flex'); } else { modal.classList.add('hidden'); modal.classList.remove('flex'); }
|
||||
}
|
||||
window.logAi = function(msg, color="white") {
|
||||
const box = document.getElementById('ai-debug-box');
|
||||
if(box) { box.innerHTML += `<div><span style="color:${color}">${msg}</span></div>`; box.scrollTop = box.scrollHeight; }
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
let seg = null; const video = document.getElementById('ai-video'); const canvas = document.getElementById('ai-canvas'); const ctx = canvas.getContext('2d'); const loadingEl = document.getElementById('ai-loading');
|
||||
let isRunning = false; let bgImage = new Image(); bgImage.src = "../assets/weltraum.jpg"; let bgColor = null; let loadTimer = null; let currentFacingMode = 'user'; let currentStream;
|
||||
|
||||
async function checkFiles() {
|
||||
const files = ['selfie_segmentation.binarypb', 'selfie_segmentation_solution_simd_wasm_bin.wasm'];
|
||||
for(let f of files) { try { const r = await fetch('../models/' + f, {method: 'GET', cache: 'no-store'}); if(!r.ok) window.logAi(`WARNUNG: ${f} fehlt evtl.`, "orange"); else window.logAi(`OK: ${f}`, "green"); } catch(e) { window.logAi(`Check ignorieren: ${e.message}`, "gray"); } }
|
||||
}
|
||||
|
||||
async function initCam() {
|
||||
checkFiles();
|
||||
try {
|
||||
if(currentStream) currentStream.getTracks().forEach(t => t.stop());
|
||||
window.logAi("Starte Kamera...");
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: {ideal: 1280}, height: {ideal: 720}, facingMode: currentFacingMode } });
|
||||
currentStream = stream; video.srcObject = stream; await video.play();
|
||||
if(currentFacingMode === 'user') { video.style.transform = 'scaleX(-1)'; } else { video.style.transform = 'scaleX(1)'; }
|
||||
loadTimer = setTimeout(() => { window.logAi("Timeout! Klicke 'Ohne KI starten'.", "yellow"); }, 30000);
|
||||
window.logAi("Warte auf Videodaten...");
|
||||
waitForVideo();
|
||||
} catch(e) { window.logAi("Kamera-Fehler: " + e.message, "red"); alert("Kamera konnte nicht gestartet werden."); }
|
||||
}
|
||||
function waitForVideo() {
|
||||
if (video.readyState >= 2 && video.videoWidth > 0) { window.logAi("Video läuft. Starte KI..."); setupAI(); } else { requestAnimationFrame(waitForVideo); }
|
||||
}
|
||||
window.switchCamera = function() { currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user'; initCam(); }
|
||||
function setupAI() {
|
||||
if(seg) { loadingEl.style.display = 'none'; isRunning = true; processLoop(); return; }
|
||||
window.logAi("Lade KI-Modell...", "cyan");
|
||||
try {
|
||||
seg = new SelfieSegmentation({locateFile: (file) => `../models/${file}`});
|
||||
seg.setOptions({ modelSelection: 1, selfieMode: true });
|
||||
seg.onResults(onResults);
|
||||
isRunning = true; processLoop();
|
||||
} catch(e) { window.logAi("KI Init Fehler: " + e, "red"); }
|
||||
}
|
||||
async function processLoop() { if(!isRunning) return; if(video.videoWidth > 0 && !video.paused) { try { await seg.send({image: video}); } catch(e) {} } requestAnimationFrame(processLoop); }
|
||||
function onResults(results) {
|
||||
if(loadingEl.style.display !== 'none') { clearTimeout(loadTimer); loadingEl.style.display = 'none'; window.logAi("KI läuft!", "green"); }
|
||||
canvas.width = video.videoWidth; canvas.height = video.videoHeight;
|
||||
ctx.save(); ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(results.segmentationMask, 0, 0, canvas.width, canvas.height);
|
||||
ctx.globalCompositeOperation = 'source-in'; ctx.drawImage(results.image, 0, 0, canvas.width, canvas.height);
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
if (bgColor) { ctx.fillStyle = bgColor; ctx.fillRect(0, 0, canvas.width, canvas.height); }
|
||||
else if (bgImage.complete && bgImage.naturalWidth > 0) { ctx.drawImage(bgImage, 0, 0, canvas.width, canvas.height); }
|
||||
else { ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height); }
|
||||
ctx.restore();
|
||||
}
|
||||
window.setBg = function(path, color = null) { if (color) { bgColor = color; } else { bgColor = null; bgImage.src = path; } }
|
||||
|
||||
window.capturePhoto = function() {
|
||||
playSound('shutter');
|
||||
flashEffect(); isRunning = false; const dataUrl = canvas.toDataURL('image/png');
|
||||
document.getElementById('preview-img-display').src = dataUrl;
|
||||
document.getElementById('preview-overlay').classList.remove('hidden'); document.getElementById('preview-overlay').style.display = 'flex';
|
||||
document.getElementById('main-controls').classList.add('hidden');
|
||||
}
|
||||
window.closePreview = function() {
|
||||
document.getElementById('preview-overlay').classList.add('hidden'); document.getElementById('preview-overlay').style.display = 'none';
|
||||
document.getElementById('main-controls').classList.remove('hidden'); isRunning = true; processLoop();
|
||||
}
|
||||
window.saveFromPreview = function() {
|
||||
playSound('success');
|
||||
const dataUrl = document.getElementById('preview-img-display').src; const link = document.createElement('a'); link.download = 'magic-selfie.png'; link.href = dataUrl; link.click();
|
||||
}
|
||||
window.printFromPreview = async function(el) {
|
||||
playSound('success');
|
||||
const btn = el || document.getElementById('btn-print'); const oldText = btn ? btn.innerText : "🖨️ DRUCKEN";
|
||||
if (btn) { btn.innerText = "⏳ Sende..."; btn.disabled = true; }
|
||||
const dataUrl = document.getElementById('preview-img-display').src;
|
||||
try {
|
||||
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DirectPrinter) {
|
||||
await window.Capacitor.Plugins.DirectPrinter.printBase64({ data: dataUrl, name: 'Magic-Selfie' });
|
||||
if (btn) btn.innerText = "✅ Gedruckt!";
|
||||
} else {
|
||||
console.warn("DirectPrinter Plugin nicht gefunden, nutze Browser Print.");
|
||||
document.getElementById('print-img').src = dataUrl; setTimeout(() => window.print(), 250);
|
||||
if (btn) btn.innerText = "✅ OK";
|
||||
}
|
||||
} catch (error) { console.error("Druckfehler:", error); alert("Drucken fehlgeschlagen: " + error.message); if (btn) btn.innerText = "❌ Fehler"; }
|
||||
setTimeout(() => { if (btn) { btn.innerText = oldText; btn.disabled = false; } }, 3000);
|
||||
}
|
||||
window.takePhoto = function() { window.capturePhoto(); }
|
||||
function flashEffect() { canvas.style.filter = "brightness(10)"; setTimeout(() => canvas.style.filter = "none", 100); }
|
||||
window.drawFallback = function() {
|
||||
isRunning = false; loadingEl.style.display = 'none'; window.logAi("KI deaktiviert.");
|
||||
function loop() {
|
||||
canvas.width = video.videoWidth; canvas.height = video.videoHeight;
|
||||
ctx.save(); if(currentFacingMode === 'user') ctx.scale(-1, 1);
|
||||
ctx.drawImage(video, currentFacingMode === 'user' ? -canvas.width : 0, 0); ctx.restore();
|
||||
ctx.fillStyle = "white"; ctx.font = "20px sans-serif"; ctx.fillText("Ohne KI", 20, 40);
|
||||
requestAnimationFrame(loop);
|
||||
} loop();
|
||||
}
|
||||
initCam();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
258
public/apps/magic_old.html
Normal file
@@ -0,0 +1,258 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Magic Booth</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
|
||||
<script src="../js/tailwind.js"></script>
|
||||
<script src="../js/camera_utils.js"></script>
|
||||
<script src="../js/selfie_segmentation.js"></script>
|
||||
|
||||
<style>
|
||||
/* HIER gehört das CSS hin (unsichtbar für den Nutzer) */
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
body {
|
||||
background-color: #facc15;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
transition: background-color 0.5s;
|
||||
animation: fadeIn 0.4s ease-out; /* Die Animation */
|
||||
}
|
||||
|
||||
body { background-color: #0f172a; color: white; overflow: hidden; user-select: none; touch-action: none; }
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
button:active { transform: scale(0.95); transition: transform 0.1s; }
|
||||
|
||||
@media print {
|
||||
body * { visibility: hidden; }
|
||||
#print-container, #print-container * { visibility: visible; }
|
||||
#print-container { position: fixed; left: 0; top: 0; width: 100%; height: 100%; display: flex !important; align-items: center; justify-content: center; background: white; z-index: 99999; }
|
||||
#print-img { max-width: 100%; max-height: 100%; object-fit: contain; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="h-screen w-screen flex flex-col md:flex-row p-0 m-0 bg-slate-900" onclick="window.resetIdleTimer()" ontouchstart="window.resetIdleTimer()">
|
||||
|
||||
|
||||
<button onclick="goHome()" class="absolute top-6 left-6 z-[100] bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-8 py-4 rounded-full text-2xl font-black shadow-2xl flex items-center gap-3 transition-transform active:scale-95 no-underline">
|
||||
🏠 MENÜ
|
||||
</button>
|
||||
|
||||
<button onclick="window.toggleInfo(); playSound('click')" class="absolute top-6 right-6 z-[100] w-14 h-14 bg-slate-800 text-white rounded-full font-bold border-4 border-slate-600 hover:border-white shadow-xl flex items-center justify-center transition-transform active:scale-95 text-2xl">
|
||||
?
|
||||
</button>
|
||||
|
||||
<div class="relative flex-1 bg-black flex flex-col items-center justify-center overflow-hidden h-[60%] md:h-full w-full">
|
||||
<div class="relative w-full h-full flex items-center justify-center p-2 md:p-8 pb-24">
|
||||
<video id="ai-video" autoplay playsinline muted class="hidden"></video>
|
||||
<canvas id="ai-canvas" class="max-w-full max-h-full object-contain shadow-2xl rounded-lg"></canvas>
|
||||
|
||||
<div id="preview-overlay" class="absolute inset-0 bg-slate-900 z-50 hidden flex-col items-center justify-center">
|
||||
<h2 class="text-3xl font-bold text-white mb-6 animate-bounce">Dein magisches Foto!</h2>
|
||||
<div class="relative max-w-[90%] max-h-[60%] border-8 border-white shadow-2xl rounded-xl overflow-hidden bg-black">
|
||||
<img id="preview-img-display" class="w-full h-full object-contain" src="">
|
||||
</div>
|
||||
<div class="flex gap-6 mt-8">
|
||||
<button onclick="window.closePreview(); playSound('click')" class="bg-yellow-500 hover:bg-yellow-400 text-black font-black py-4 px-8 rounded-2xl text-xl shadow-xl active:scale-95 transition border-b-8 border-yellow-700 active:border-b-0 active:translate-y-2 flex items-center gap-2">🔄 NOCHMAL</button>
|
||||
<button id="btn-print" onclick="window.printFromPreview(this)" class="bg-white text-slate-900 font-black py-4 px-8 rounded-2xl text-xl shadow-xl hover:bg-gray-100 active:scale-95 transition border-b-8 border-gray-400 active:border-b-0 active:translate-y-2 flex items-center gap-2">🖨️ DRUCKEN</button>
|
||||
<button onclick="window.saveFromPreview()" class="bg-violet-600 text-white font-black py-4 px-8 rounded-2xl text-xl shadow-xl hover:bg-violet-500 active:scale-95 transition border-b-8 border-violet-800 active:border-b-0 active:translate-y-2 flex items-center gap-2">💾 FOTO</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ai-loading" class="absolute inset-0 bg-black/95 flex flex-col items-center justify-center z-40 p-4 text-center">
|
||||
<div class="text-6xl animate-spin mb-4">🔮</div>
|
||||
<div class="text-2xl font-bold mb-4">Lade KI...</div>
|
||||
<div id="ai-debug-box" class="text-xs text-left font-mono bg-black/80 border border-gray-700 p-4 rounded-xl mt-4 max-w-lg w-full h-32 overflow-y-auto opacity-70"><div>[Log] App gestartet...</div></div>
|
||||
<div class="mt-8 flex gap-6">
|
||||
<button onclick="location.reload()" class="border-2 border-white/30 px-6 py-3 rounded-full hover:bg-white/10">Neu laden</button>
|
||||
<button onclick="window.drawFallback(); document.getElementById('ai-loading').style.display='none'; playSound('click')" class="bg-red-600 px-6 py-3 rounded-full font-bold hover:bg-red-500">Ohne KI starten</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-controls" class="absolute bottom-6 flex gap-4 z-30 w-full justify-center px-4">
|
||||
<button onclick="window.switchCamera(); playSound('click')" class="w-20 bg-slate-700 text-white font-black py-4 rounded-2xl text-2xl shadow-xl hover:scale-105 active:scale-95 transition border-b-8 border-slate-900 active:border-b-0 active:translate-y-2 flex items-center justify-center" title="Kamera wechseln">🔄</button>
|
||||
<button onclick="window.capturePhoto()" class="flex-1 max-w-[200px] bg-violet-600 text-white font-black py-4 rounded-2xl text-lg md:text-xl shadow-xl hover:scale-105 active:scale-95 transition border-b-8 border-violet-800 active:border-b-0 active:translate-y-2 flex items-center justify-center gap-2">✨ FOTO</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-[40%] md:h-full md:w-1/3 min-w-[280px] max-w-md bg-slate-800 p-4 border-t-4 md:border-t-0 md:border-l-4 border-slate-700 overflow-y-auto no-scrollbar z-20 flex flex-col">
|
||||
<h3 class="text-white font-black text-xl mb-4 text-center uppercase tracking-wider sticky top-0 bg-slate-800 pb-2 z-10 pt-2">🌍 Reiseziel</h3>
|
||||
<div class="grid grid-cols-2 gap-3 pb-4">
|
||||
<div onclick="window.setBg('../assets/weltraum.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/weltraum.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-indigo-900/50 z-0"><span class="text-5xl">🚀</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">WELTALL</span></div>
|
||||
<div onclick="window.setBg('../assets/paris.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/paris.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-blue-900/50 z-0"><span class="text-5xl">🇫🇷</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">PARIS</span></div>
|
||||
<div onclick="window.setBg('../assets/dschungel.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/dschungel.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-green-900/50 z-0"><span class="text-5xl">🌴</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">DSCHUNGEL</span></div>
|
||||
<div onclick="window.setBg('../assets/unterwasser.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/unterwasser.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-cyan-900/50 z-0"><span class="text-5xl">🐠</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">OZEAN</span></div>
|
||||
<div onclick="window.setBg('../assets/wolken.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/wolken.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-sky-500/50 z-0"><span class="text-5xl">☁️</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">HIMMEL</span></div>
|
||||
<div onclick="window.setBg('../assets/schloss.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/schloss.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-purple-900/50 z-0"><span class="text-5xl">🏰</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">SCHLOSS</span></div>
|
||||
<div onclick="window.setBg('../assets/dino.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/dino.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-amber-900/50 z-0"><span class="text-5xl">🦖</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">DINO</span></div>
|
||||
<div onclick="window.setBg('../assets/stadion.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/stadion.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-green-800/50 z-0"><span class="text-5xl">⚽</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">STADION</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4" onclick="window.toggleInfo()">
|
||||
<div class="bg-slate-800 border-4 border-violet-500 rounded-[2rem] max-w-2xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
|
||||
<button onclick="window.toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-violet-400 text-4xl font-bold transition">✖</button>
|
||||
<h2 class="text-4xl font-black text-white mb-8 border-b border-slate-600 pb-4 flex items-center gap-4"><span class="text-6xl">✨</span> ANLEITUNG</h2>
|
||||
<div class="text-lg text-slate-300 space-y-4">
|
||||
<p>1. Wähle rechts einen <b>Hintergrund</b>.</p>
|
||||
<p>2. Die KI schneidet dich automatisch aus.</p>
|
||||
<p>3. Drücke <b>FOTO</b> für eine Vorschau.</p>
|
||||
<p>4. Dann kannst du <b>Drucken</b> oder nochmal probieren.</p>
|
||||
</div>
|
||||
<button onclick="window.toggleInfo(); playSound('click')" class="mt-8 w-full bg-violet-600 hover:bg-violet-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="print-container" class="hidden"><img id="print-img" src=""></div>
|
||||
|
||||
<script>
|
||||
// --- ROBUSTES AUDIO SYSTEM (ANTI-ABSTURZ) ---
|
||||
const _soundCache = {
|
||||
'click': new Audio('../assets/sounds/click.mp3'),
|
||||
'shutter': new Audio('../assets/sounds/shutter.mp3'),
|
||||
'success': new Audio('../assets/sounds/success.mp3')
|
||||
};
|
||||
|
||||
// Sounds vorladen
|
||||
Object.values(_soundCache).forEach(s => s.load());
|
||||
|
||||
// Abspiel-Funktion
|
||||
window.playSound = function(id) {
|
||||
const baseSound = _soundCache[id];
|
||||
if (baseSound) {
|
||||
const soundClone = baseSound.cloneNode();
|
||||
soundClone.volume = 1.0;
|
||||
soundClone.play().catch(e => console.warn("Sound-Problem:", e));
|
||||
}
|
||||
};
|
||||
|
||||
// --- HOME FUNKTION (Diese fehlte wahrscheinlich!) ---
|
||||
window.goHome = function() {
|
||||
playSound('click');
|
||||
// Kleine Verzögerung, damit der Sound noch zu hören ist
|
||||
setTimeout(() => {
|
||||
window.location.href = '../index.html';
|
||||
}, 300);
|
||||
};
|
||||
|
||||
let idleTimer;
|
||||
function resetIdleTimer() {
|
||||
clearTimeout(idleTimer);
|
||||
idleTimer = setTimeout(() => {
|
||||
if (document.getElementById('preview-overlay').classList.contains('hidden')) { window.location.href = '../index.html'; }
|
||||
}, 90000);
|
||||
}
|
||||
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, window.resetIdleTimer, {passive: true}));
|
||||
window.resetIdleTimer();
|
||||
|
||||
window.toggleInfo = function() {
|
||||
const modal = document.getElementById('info-modal');
|
||||
if (modal.classList.contains('hidden')) { modal.classList.remove('hidden'); modal.classList.add('flex'); } else { modal.classList.add('hidden'); modal.classList.remove('flex'); }
|
||||
}
|
||||
window.logAi = function(msg, color="white") {
|
||||
const box = document.getElementById('ai-debug-box');
|
||||
if(box) { box.innerHTML += `<div><span style="color:${color}">${msg}</span></div>`; box.scrollTop = box.scrollHeight; }
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
let seg = null; const video = document.getElementById('ai-video'); const canvas = document.getElementById('ai-canvas'); const ctx = canvas.getContext('2d'); const loadingEl = document.getElementById('ai-loading');
|
||||
let isRunning = false; let bgImage = new Image(); bgImage.src = "../assets/weltraum.jpg"; let bgColor = null; let loadTimer = null; let currentFacingMode = 'user'; let currentStream;
|
||||
|
||||
async function checkFiles() {
|
||||
const files = ['selfie_segmentation.binarypb', 'selfie_segmentation_solution_simd_wasm_bin.wasm'];
|
||||
for(let f of files) { try { const r = await fetch('../models/' + f, {method: 'GET', cache: 'no-store'}); if(!r.ok) window.logAi(`WARNUNG: ${f} fehlt evtl.`, "orange"); else window.logAi(`OK: ${f}`, "green"); } catch(e) { window.logAi(`Check ignorieren: ${e.message}`, "gray"); } }
|
||||
}
|
||||
|
||||
async function initCam() {
|
||||
checkFiles();
|
||||
try {
|
||||
if(currentStream) currentStream.getTracks().forEach(t => t.stop());
|
||||
window.logAi("Starte Kamera...");
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: {ideal: 1280}, height: {ideal: 720}, facingMode: currentFacingMode } });
|
||||
currentStream = stream; video.srcObject = stream; await video.play();
|
||||
if(currentFacingMode === 'user') { video.style.transform = 'scaleX(-1)'; } else { video.style.transform = 'scaleX(1)'; }
|
||||
loadTimer = setTimeout(() => { window.logAi("Timeout! Klicke 'Ohne KI starten'.", "yellow"); }, 30000);
|
||||
window.logAi("Warte auf Videodaten...");
|
||||
waitForVideo();
|
||||
} catch(e) { window.logAi("Kamera-Fehler: " + e.message, "red"); alert("Kamera konnte nicht gestartet werden."); }
|
||||
}
|
||||
function waitForVideo() {
|
||||
if (video.readyState >= 2 && video.videoWidth > 0) { window.logAi("Video läuft. Starte KI..."); setupAI(); } else { requestAnimationFrame(waitForVideo); }
|
||||
}
|
||||
window.switchCamera = function() { currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user'; initCam(); }
|
||||
function setupAI() {
|
||||
if(seg) { loadingEl.style.display = 'none'; isRunning = true; processLoop(); return; }
|
||||
window.logAi("Lade KI-Modell...", "cyan");
|
||||
try {
|
||||
seg = new SelfieSegmentation({locateFile: (file) => `../models/${file}`});
|
||||
seg.setOptions({ modelSelection: 1, selfieMode: true });
|
||||
seg.onResults(onResults);
|
||||
isRunning = true; processLoop();
|
||||
} catch(e) { window.logAi("KI Init Fehler: " + e, "red"); }
|
||||
}
|
||||
async function processLoop() { if(!isRunning) return; if(video.videoWidth > 0 && !video.paused) { try { await seg.send({image: video}); } catch(e) {} } requestAnimationFrame(processLoop); }
|
||||
function onResults(results) {
|
||||
if(loadingEl.style.display !== 'none') { clearTimeout(loadTimer); loadingEl.style.display = 'none'; window.logAi("KI läuft!", "green"); }
|
||||
canvas.width = video.videoWidth; canvas.height = video.videoHeight;
|
||||
ctx.save(); ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(results.segmentationMask, 0, 0, canvas.width, canvas.height);
|
||||
ctx.globalCompositeOperation = 'source-in'; ctx.drawImage(results.image, 0, 0, canvas.width, canvas.height);
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
if (bgColor) { ctx.fillStyle = bgColor; ctx.fillRect(0, 0, canvas.width, canvas.height); }
|
||||
else if (bgImage.complete && bgImage.naturalWidth > 0) { ctx.drawImage(bgImage, 0, 0, canvas.width, canvas.height); }
|
||||
else { ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height); }
|
||||
ctx.restore();
|
||||
}
|
||||
window.setBg = function(path, color = null) { if (color) { bgColor = color; } else { bgColor = null; bgImage.src = path; } }
|
||||
|
||||
window.capturePhoto = function() {
|
||||
playSound('shutter'); // SOUND
|
||||
flashEffect(); isRunning = false; const dataUrl = canvas.toDataURL('image/png');
|
||||
document.getElementById('preview-img-display').src = dataUrl;
|
||||
document.getElementById('preview-overlay').classList.remove('hidden'); document.getElementById('preview-overlay').style.display = 'flex';
|
||||
document.getElementById('main-controls').classList.add('hidden');
|
||||
}
|
||||
window.closePreview = function() {
|
||||
document.getElementById('preview-overlay').classList.add('hidden'); document.getElementById('preview-overlay').style.display = 'none';
|
||||
document.getElementById('main-controls').classList.remove('hidden'); isRunning = true; processLoop();
|
||||
}
|
||||
window.saveFromPreview = function() {
|
||||
playSound('success'); // SOUND
|
||||
const dataUrl = document.getElementById('preview-img-display').src; const link = document.createElement('a'); link.download = 'magic-booth.png'; link.href = dataUrl; link.click();
|
||||
}
|
||||
window.printFromPreview = async function(el) {
|
||||
playSound('success'); // SOUND
|
||||
const btn = el || document.getElementById('btn-print'); const oldText = btn ? btn.innerText : "🖨️ DRUCKEN";
|
||||
if (btn) { btn.innerText = "⏳ Sende..."; btn.disabled = true; }
|
||||
const dataUrl = document.getElementById('preview-img-display').src;
|
||||
try {
|
||||
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DirectPrinter) {
|
||||
await window.Capacitor.Plugins.DirectPrinter.printBase64({ data: dataUrl, name: 'Magic-Booth' });
|
||||
if (btn) btn.innerText = "✅ Gedruckt!";
|
||||
} else {
|
||||
console.warn("DirectPrinter Plugin nicht gefunden, nutze Browser Print.");
|
||||
document.getElementById('print-img').src = dataUrl; setTimeout(() => window.print(), 250);
|
||||
if (btn) btn.innerText = "✅ OK";
|
||||
}
|
||||
} catch (error) { console.error("Druckfehler:", error); alert("Drucken fehlgeschlagen: " + error.message); if (btn) btn.innerText = "❌ Fehler"; }
|
||||
setTimeout(() => { if (btn) { btn.innerText = oldText; btn.disabled = false; } }, 3000);
|
||||
}
|
||||
window.takePhoto = function() { window.capturePhoto(); }
|
||||
function flashEffect() { canvas.style.filter = "brightness(10)"; setTimeout(() => canvas.style.filter = "none", 100); }
|
||||
window.drawFallback = function() {
|
||||
isRunning = false; loadingEl.style.display = 'none'; window.logAi("KI deaktiviert.");
|
||||
function loop() {
|
||||
canvas.width = video.videoWidth; canvas.height = video.videoHeight;
|
||||
ctx.save(); if(currentFacingMode === 'user') ctx.scale(-1, 1);
|
||||
ctx.drawImage(video, currentFacingMode === 'user' ? -canvas.width : 0, 0); ctx.restore();
|
||||
ctx.fillStyle = "white"; ctx.font = "20px sans-serif"; ctx.fillText("Ohne KI", 20, 40);
|
||||
requestAnimationFrame(loop);
|
||||
} loop();
|
||||
}
|
||||
initCam();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
154
public/apps/news.html
Normal file
@@ -0,0 +1,154 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>News Studio</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
|
||||
<script src="../js/tailwind.js"></script>
|
||||
|
||||
<style>
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
|
||||
html { width: 100%; height: 100%; background-color: #0f172a; }
|
||||
body {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0; margin: 0;
|
||||
background-color: #0f172a;
|
||||
color: white;
|
||||
overflow: hidden !important;
|
||||
user-select: none;
|
||||
touch-action: manipulation;
|
||||
font-family: sans-serif;
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
box-sizing: border-box;
|
||||
padding-top: calc(10px + env(safe-area-inset-top));
|
||||
padding-bottom: max(20px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
button:active { transform: scale(0.95); transition: transform 0.1s; }
|
||||
.animate-blink { animation: blink 1.5s infinite; }
|
||||
@keyframes blink { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }
|
||||
.ticker-wrap { width: 100%; overflow: hidden; background-color: #fbbf24; color: black; font-weight: 900; white-space: nowrap; box-sizing: border-box; }
|
||||
.ticker { display: inline-block; padding-left: 100%; animation: ticker 25s linear infinite; }
|
||||
@keyframes ticker { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(-100%, 0, 0); } }
|
||||
.editable-input { background: transparent; border: none; color: inherit; width: 100%; text-align: center; outline: none; font-family: inherit; text-transform: uppercase; text-overflow: ellipsis; }
|
||||
.editable-input:focus { background: rgba(255,255,255,0.1); border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col"
|
||||
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
|
||||
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
|
||||
|
||||
<div class="flex-none flex justify-between items-center z-50 px-4 relative bg-slate-900 shadow-md h-16">
|
||||
<button onclick="goHome()" class="bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none shrink-0 z-50">
|
||||
🏠 MENÜ
|
||||
</button>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center pointer-events-none z-0">
|
||||
<h1 class="text-2xl md:text-4xl font-black text-blue-400 font-sans tracking-tighter drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] uppercase italic transform -skew-x-6">
|
||||
📰 NEWS
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 bg-slate-800 text-white rounded-full font-bold border-4 border-slate-600 hover:border-white shadow-xl flex items-center justify-center transition-transform active:scale-95 text-xl shrink-0 z-50">
|
||||
?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 relative w-full overflow-hidden bg-black shadow-inner">
|
||||
|
||||
<div class="absolute inset-0 z-0">
|
||||
<video id="video" autoplay playsinline muted class="w-full h-full object-cover opacity-90"></video>
|
||||
<canvas id="canvas" class="hidden"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="absolute inset-0 z-20 flex flex-col justify-between pointer-events-none pb-4">
|
||||
|
||||
<div class="w-full flex justify-end p-4 md:p-6">
|
||||
<div class="flex items-center gap-2 bg-red-600/90 px-4 py-2 rounded-lg shadow-lg border-2 border-red-400 pointer-events-auto transform rotate-1 hover:rotate-0 transition-transform">
|
||||
<div class="w-3 h-3 md:w-4 md:h-4 bg-white rounded-full animate-blink"></div>
|
||||
<span class="font-black text-lg md:text-xl tracking-widest text-white">LIVE</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full pointer-events-auto items-center">
|
||||
|
||||
<div class="w-[90%] md:max-w-5xl bg-blue-800/95 border-l-8 border-blue-500 p-2 md:p-4 mb-2 shadow-2xl transform -skew-x-6 relative group hover:bg-blue-700/95 transition-colors">
|
||||
<div class="text-blue-300 text-xs md:text-sm font-bold uppercase tracking-[0.2em] mb-1 ml-2 not-italic transform skew-x-6">Breaking News</div>
|
||||
<input type="text" id="headline-input" value="SENSATION IM JUGENDZENTRUM!" class="editable-input text-2xl md:text-4xl font-black text-white drop-shadow-md transform skew-x-6 placeholder-blue-300">
|
||||
</div>
|
||||
|
||||
<div class="self-start ml-4 md:ml-16 w-auto max-w-[80%] bg-red-600/95 text-white font-bold px-6 py-2 transform -skew-x-6 shadow-xl text-lg md:text-xl border-l-4 border-red-300 mb-4 hover:bg-red-500/95 transition-colors">
|
||||
<input type="text" value="🔴 VOR ORT: MEDIENSTATION" class="editable-input w-full text-left transform skew-x-6">
|
||||
</div>
|
||||
|
||||
<div class="ticker-wrap bg-yellow-400 py-2 md:py-3 border-y-4 border-black shadow-xl mb-4 md:mb-6">
|
||||
<div class="ticker text-xl md:text-3xl text-black">
|
||||
+++ WETTER: ES REGNET KONFETTI +++ SCHULE FÄLLT HEUTE AUS +++ EISCREME FÜR ALLE +++ KATZE WIRD BÜRGERMEISTER +++ EXTRA BLATT +++
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center gap-4 md:gap-6 pb-2 md:pb-4 w-full">
|
||||
<button onclick="switchCamera(); playSound('click')" class="bg-slate-800/90 hover:bg-slate-700 text-white w-14 h-14 md:w-16 md:h-16 rounded-full border-2 border-slate-500 shadow-xl backdrop-blur flex items-center justify-center active:scale-95 transition">
|
||||
<span class="text-2xl md:text-3xl">🔄</span>
|
||||
</button>
|
||||
<button onclick="capturePreview()" class="bg-white hover:bg-gray-200 text-black font-black h-14 md:h-16 px-8 md:px-10 rounded-full border-b-8 border-gray-400 active:border-b-0 active:translate-y-2 shadow-2xl text-lg md:text-xl flex items-center gap-3 transition active:scale-95">
|
||||
<span>📸</span> FOTO
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="preview-modal" class="hidden fixed inset-0 z-[100] bg-black/90 flex flex-col items-center justify-center p-4 backdrop-blur-md">
|
||||
<button onclick="goHome()" class="absolute top-6 left-6 z-50 bg-blue-900/90 backdrop-blur border-4 border-blue-500 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 text-white">
|
||||
🏠 MENÜ
|
||||
</button>
|
||||
<h2 class="text-white text-3xl font-black mb-4">DEIN FOTO</h2>
|
||||
<div class="relative w-full max-w-4xl h-auto border-8 border-white shadow-2xl rounded-lg overflow-hidden bg-black mb-8">
|
||||
<img id="preview-img" src="" class="w-full h-auto object-contain">
|
||||
</div>
|
||||
<div class="flex gap-6">
|
||||
<button onclick="closePreview(); playSound('click')" class="bg-red-600 hover:bg-red-500 text-white font-black py-4 px-8 rounded-2xl text-xl shadow-lg border-b-8 border-red-800 active:border-b-0 active:translate-y-2 transition flex items-center gap-2">
|
||||
🗑️ LÖSCHEN
|
||||
</button>
|
||||
<button id="btn-print-confirm" onclick="confirmPrint()" class="bg-green-600 hover:bg-green-500 text-white font-black py-4 px-12 rounded-2xl text-xl shadow-lg border-b-8 border-green-800 active:border-b-0 active:translate-y-2 transition flex items-center gap-2">
|
||||
🖨️ DRUCKEN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
|
||||
<div class="bg-slate-800 border-4 border-blue-500 rounded-[2rem] max-w-lg w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
|
||||
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-blue-400 text-4xl font-bold transition">✖</button>
|
||||
<h2 class="text-3xl font-black text-white mb-6 border-b border-slate-600 pb-4">📰 NEWS STUDIO</h2>
|
||||
<ul class="text-slate-300 space-y-3 mb-8 list-disc pl-6 text-lg">
|
||||
<li>Tippe auf die <b>blaue Schlagzeile</b> und schreibe deinen Text.</li>
|
||||
<li>Tippe auf den <b>roten Ort</b>.</li>
|
||||
<li>Drücke auf <b>FOTO</b> für eine Vorschau.</li>
|
||||
<li>Wenn es dir gefällt, drücke auf <b>DRUCKEN</b>.</li>
|
||||
</ul>
|
||||
<button onclick="toggleInfo(); playSound('click')" class="w-full bg-blue-600 hover:bg-blue-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const _soundCache = {'click': new Audio('../assets/sounds/click.mp3'), 'shutter': new Audio('../assets/sounds/shutter.mp3'), 'success': new Audio('../assets/sounds/success.mp3')};
|
||||
Object.values(_soundCache).forEach(s => s.load());
|
||||
window.playSound = function(id) { if(_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e=>{}); } };
|
||||
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
|
||||
let idleTimer;
|
||||
window.resetIdleTimer = function() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { window.location.href = '../index.html'; }, 120000); };
|
||||
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, window.resetIdleTimer, {passive: true})); window.resetIdleTimer();
|
||||
function toggleInfo() { const m = document.getElementById('info-modal'); m.classList.toggle('hidden'); m.classList.toggle('flex'); }
|
||||
let video = document.getElementById('video'); let currentStream; let currentFacingMode = 'user';
|
||||
async function initCam() { try { if (currentStream && currentStream.getTracks) currentStream.getTracks().forEach(track => track.stop()); const stream = await navigator.mediaDevices.getUserMedia({ video: { width: {ideal: 1280}, height: {ideal: 720}, facingMode: currentFacingMode } }); currentStream = stream; video.srcObject = stream; video.style.transform = (currentFacingMode === 'user') ? 'scaleX(-1)' : 'scaleX(1)'; } catch (err) { console.error("Kamera Fehler:", err); } }
|
||||
function switchCamera() { currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user'; initCam(); }
|
||||
function capturePreview() { playSound('shutter'); const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.width = video.videoWidth || 1280; canvas.height = video.videoHeight || 720; ctx.save(); if (currentFacingMode === 'user') { ctx.translate(canvas.width, 0); ctx.scale(-1, 1); } ctx.drawImage(video, 0, 0, canvas.width, canvas.height); ctx.restore(); const headline = document.getElementById('headline-input').value.toUpperCase(); ctx.fillStyle = "rgba(30, 64, 175, 0.95)"; const boxH = 140; const boxY = canvas.height - 220; const boxMargin = 60; const boxW = canvas.width - (boxMargin * 2); ctx.save(); ctx.translate(boxMargin, boxY); ctx.transform(1, 0, -0.1, 1, 0, 0); ctx.fillRect(0, 0, boxW, boxH); ctx.fillStyle = "#3b82f6"; ctx.fillRect(0, 0, 25, boxH); ctx.restore(); ctx.fillStyle = "white"; ctx.font = "900 55px sans-serif"; ctx.textAlign = "center"; ctx.fillText(headline, canvas.width / 2, boxY + 90, boxW - 100); ctx.fillStyle = "#93c5fd"; ctx.font = "bold 24px sans-serif"; ctx.textAlign = "left"; ctx.fillText("BREAKING NEWS", boxMargin + 40, boxY + 35); const redW = 550; const redY = boxY - 65; const redX = boxMargin + 40; ctx.save(); ctx.translate(redX, redY); ctx.transform(1, 0, -0.1, 1, 0, 0); ctx.fillStyle = "rgba(220, 38, 38, 0.95)"; ctx.fillRect(0, 0, redW, 60); ctx.fillStyle = "#fca5a5"; ctx.fillRect(0, 0, 10, 60); ctx.restore(); ctx.fillStyle = "white"; ctx.font = "bold 30px sans-serif"; ctx.textAlign = "left"; ctx.fillText("🔴 VOR ORT: MEDIENSTATION", redX + 30, redY + 40); ctx.fillStyle = "#fbbf24"; ctx.fillRect(0, canvas.height - 70, canvas.width, 70); ctx.fillStyle = "black"; ctx.font = "900 35px sans-serif"; ctx.textAlign = "left"; ctx.fillText("+++ WETTER: ES REGNET KONFETTI +++ SCHULE FÄLLT HEUTE AUS +++ EXTRA BLATT +++", 20, canvas.height - 22); ctx.fillStyle = "#dc2626"; ctx.fillRect(canvas.width - 160, 40, 120, 50); ctx.fillStyle = "white"; ctx.font = "900 30px sans-serif"; ctx.textAlign = "center"; ctx.fillText("LIVE", canvas.width - 100, 75); const dataUrl = canvas.toDataURL('image/png'); document.getElementById('preview-img').src = dataUrl; document.getElementById('preview-modal').classList.remove('hidden'); document.getElementById('preview-modal').classList.add('flex'); }
|
||||
function closePreview() { document.getElementById('preview-modal').classList.add('hidden'); document.getElementById('preview-modal').classList.remove('flex'); }
|
||||
async function confirmPrint() { const btn = document.getElementById('btn-print-confirm'); btn.innerText = "⏳ DRUCKE..."; const img = document.getElementById('preview-img'); const dataUrl = img.src; try { if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DirectPrinter) { await window.Capacitor.Plugins.DirectPrinter.printBase64({ data: dataUrl, name: 'News-Flash' }); } else { const link = document.createElement('a'); link.download = 'breaking-news.png'; link.href = dataUrl; link.click(); } playSound('success'); setTimeout(() => { closePreview(); btn.innerText = "🖨️ DRUCKEN"; }, 1000); } catch(e) { console.error(e); alert("Druck-Fehler: " + e.message); btn.innerText = "❌ FEHLER"; } }
|
||||
window.onload = initCam;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
118
public/apps/pixel.html
Normal file
@@ -0,0 +1,118 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Pixel Labor</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
|
||||
<script src="../js/tailwind.js"></script>
|
||||
|
||||
<style>
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
html { width: 100%; height: 100%; background-color: #0f172a; }
|
||||
body {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0; margin: 0;
|
||||
background-color: #0f172a;
|
||||
color: white;
|
||||
overflow: hidden !important;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
font-family: sans-serif;
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
box-sizing: border-box;
|
||||
padding-top: calc(10px + env(safe-area-inset-top));
|
||||
padding-bottom: max(20px, env(safe-area-inset-bottom));
|
||||
}
|
||||
.pixel-grid { display: grid; grid-template-columns: repeat(16, 1fr); grid-template-rows: repeat(16, 1fr); gap: 1px; background: #334155; border: 8px solid #10b981; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7); border-radius: 1rem; overflow: hidden; touch-action: none; width: 100%; height: 100%; aspect-ratio: 1 / 1; }
|
||||
.cell { background-color: white; cursor: pointer; transition: background-color 0.05s; }
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
button:active { transform: scale(0.95); transition: transform 0.1s; }
|
||||
@media print { body * { visibility: hidden; } #print-container, #print-container * { visibility: visible; } #print-container { position: fixed; left: 0; top: 0; width: 100%; height: 100%; display: flex !important; align-items: center; justify-content: center; background: white; z-index: 99999; } #print-img { max-width: 90%; max-height: 90%; object-fit: contain; border: 4px solid black; image-rendering: pixelated; } }
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col p-4"
|
||||
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
|
||||
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
|
||||
|
||||
<div class="flex-none flex justify-between items-center shrink-0 mb-2 px-2 relative z-50 h-16">
|
||||
<button onclick="goHome()" class="bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-6 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none shrink-0 z-50">
|
||||
🏠 MENÜ
|
||||
</button>
|
||||
<div class="absolute inset-0 flex items-center justify-center pointer-events-none z-0">
|
||||
<h1 class="text-2xl md:text-4xl font-black text-emerald-400 font-mono tracking-wider drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] uppercase">
|
||||
<PIXEL>
|
||||
</h1>
|
||||
</div>
|
||||
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 bg-slate-800 text-white rounded-full font-bold border-4 border-slate-600 hover:border-white shadow-xl flex items-center justify-center transition-transform active:scale-95 text-xl shrink-0 z-50">
|
||||
?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-col lg:flex-row items-center justify-center gap-4 min-h-0 w-full max-w-[95rem] mx-auto pb-1">
|
||||
<div class="relative w-full max-w-[650px] flex-shrink-0 flex items-center justify-center p-1 min-h-0 h-full">
|
||||
<div id="grid" class="pixel-grid max-h-full"></div>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-800 p-3 lg:p-4 rounded-[1.5rem] border-4 border-slate-700 flex flex-row lg:flex-col gap-3 shadow-2xl w-full lg:w-auto h-auto lg:h-full justify-between overflow-x-auto lg:overflow-visible lg:overflow-y-auto no-scrollbar">
|
||||
|
||||
<div class="grid grid-cols-4 lg:grid-cols-2 gap-2 flex-shrink-0 justify-items-center" id="palette">
|
||||
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-black border-4 border-slate-600 cursor-pointer active:scale-90 transition ring-4 ring-white shadow-lg" data-color="#000000" onclick="setColor(this)"></div>
|
||||
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-white border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#ffffff" onclick="setColor(this)"></div>
|
||||
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-red-500 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#ef4444" onclick="setColor(this)"></div>
|
||||
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-blue-500 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#3b82f6" onclick="setColor(this)"></div>
|
||||
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-green-500 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#22c55e" onclick="setColor(this)"></div>
|
||||
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-yellow-400 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#facc15" onclick="setColor(this)"></div>
|
||||
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-purple-500 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#a855f7" onclick="setColor(this)"></div>
|
||||
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-slate-600 border-4 border-slate-500 cursor-pointer active:scale-90 transition flex items-center justify-center text-2xl shadow-lg" data-color="white" onclick="setColor(this)" title="Radierer">🧽</div>
|
||||
</div>
|
||||
|
||||
<div class="h-1 w-full bg-slate-700 my-1 lg:block hidden rounded-full shrink-0"></div>
|
||||
<div class="w-1 h-full bg-slate-700 mx-2 lg:hidden block rounded-full shrink-0"></div>
|
||||
|
||||
<div class="flex flex-col gap-2 w-full min-w-[160px] shrink-0">
|
||||
<div class="flex gap-2">
|
||||
<button onclick="downloadArt()" class="flex-1 bg-emerald-600 hover:bg-emerald-500 text-white py-3 px-2 rounded-xl font-black text-base shadow-lg active:translate-y-1 transition border-b-4 border-emerald-800 active:border-b-0 flex items-center justify-center gap-1">💾 SAVE</button>
|
||||
<button id="btn-print" onclick="window.printArt(this)" class="flex-1 bg-white text-emerald-900 hover:bg-gray-200 py-3 px-2 rounded-xl font-black text-base shadow-lg active:translate-y-1 transition border-b-4 border-gray-300 active:border-b-0 flex items-center justify-center gap-1">🖨️ DRUCK</button>
|
||||
</div>
|
||||
<button onclick="clearGrid()" class="bg-slate-700 hover:bg-red-500/80 text-white py-3 px-4 rounded-xl font-bold transition text-base border-b-4 border-slate-900 active:border-b-0 active:translate-y-1 w-full flex items-center justify-center gap-2">🗑️ LÖSCHEN</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
|
||||
<div class="bg-slate-800 border-4 border-emerald-500 rounded-[2rem] max-w-3xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
|
||||
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-emerald-400 text-4xl font-bold transition">✖</button>
|
||||
<h2 class="text-4xl font-black text-white mb-8 border-b border-slate-600 pb-4 flex items-center gap-4"><span class="text-6xl">👾</span> ANLEITUNG</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 text-base">
|
||||
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🎨</div><h3 class="text-emerald-400 font-bold text-xl mb-2">Malen</h3><p class="text-slate-300 leading-snug">Wähle rechts eine <b>Farbe</b> und tippe oder ziehe über die Quadrate.</p></div>
|
||||
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🧽</div><h3 class="text-white font-bold text-xl mb-2">Radieren</h3><p class="text-slate-300 leading-snug">Wähle den <b>Schwamm</b> oder drücke auf "LÖSCHEN", um von vorne zu beginnen.</p></div>
|
||||
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">💾</div><h3 class="text-white font-bold text-xl mb-2">Fertig?</h3><p class="text-slate-300 leading-snug">Speichere dein Pixel-Kunstwerk oder drucke es direkt aus.</p></div>
|
||||
</div>
|
||||
<button onclick="toggleInfo(); playSound('click')" class="mt-8 w-full bg-emerald-600 hover:bg-emerald-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="print-container" class="hidden"><img id="print-img" src="" alt="Druckvorschau"></div>
|
||||
|
||||
<script>
|
||||
const _soundCache = { 'click': new Audio('../assets/sounds/click.mp3'), 'shutter': new Audio('../assets/sounds/shutter.mp3'), 'success': new Audio('../assets/sounds/success.mp3') };
|
||||
Object.values(_soundCache).forEach(s => s.load());
|
||||
window.playSound = function(id) { if (_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e => console.warn(e)); } };
|
||||
function playPaintSound() {}
|
||||
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
|
||||
const grid = document.getElementById('grid'); let currentColor = '#000000'; let isDrawing = false; let idleTimer;
|
||||
function resetIdleTimer() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { window.location.href = '../index.html'; }, 90000); }
|
||||
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, resetIdleTimer, {passive: true})); resetIdleTimer();
|
||||
function toggleInfo() { const m = document.getElementById('info-modal'); m.classList.toggle('hidden'); m.classList.toggle('flex'); }
|
||||
function initGrid() { grid.innerHTML = ''; for(let i=0; i<256; i++) { const cell = document.createElement('div'); cell.className = 'cell'; cell.addEventListener('mousedown', () => { isDrawing = true; cell.style.backgroundColor = currentColor; playPaintSound(); }); cell.addEventListener('mouseenter', () => { if(isDrawing) { cell.style.backgroundColor = currentColor; } }); cell.addEventListener('touchstart', (e) => { isDrawing = true; cell.style.backgroundColor = currentColor; playPaintSound(); }, {passive: false}); grid.appendChild(cell); } }
|
||||
grid.addEventListener('touchmove', (e) => { e.preventDefault(); if(!isDrawing) return; const t = e.touches[0]; const el = document.elementFromPoint(t.clientX, t.clientY); if(el && el.classList.contains('cell')) { el.style.backgroundColor = currentColor; } }, {passive: false});
|
||||
document.body.addEventListener('mouseup', () => isDrawing = false); document.body.addEventListener('touchend', () => isDrawing = false);
|
||||
function setColor(btn) { playSound('click'); currentColor = btn.getAttribute('data-color'); document.querySelectorAll('.color-btn').forEach(b => b.classList.remove('ring-4', 'ring-white')); btn.classList.add('ring-4', 'ring-white'); }
|
||||
function clearGrid() { playSound('click'); if(confirm("Wirklich alles löschen?")) { document.querySelectorAll('.cell').forEach(c => c.style.backgroundColor = 'white'); playSound('shutter'); } }
|
||||
function generateCanvas() { const c = document.createElement('canvas'); const ctx = c.getContext('2d'); const s = 32; c.width = 16 * s; c.height = 16 * s; document.querySelectorAll('.cell').forEach((cell, i) => { const x = (i % 16) * s; const y = Math.floor(i / 16) * s; ctx.fillStyle = cell.style.backgroundColor || 'white'; ctx.fillRect(x, y, s, s); }); return c; }
|
||||
function downloadArt() { playSound('success'); const c = generateCanvas(); const l = document.createElement('a'); l.download = 'mein-pixel-art.png'; l.href = c.toDataURL(); l.click(); }
|
||||
window.printArt = async function(el) { playSound('click'); const btn = el || document.getElementById('btn-print'); let oldText = ""; if(btn) { oldText = btn.innerHTML; btn.innerText = "⏳ Sende..."; btn.disabled = true; } const c = generateCanvas(); const d = c.toDataURL(); try { if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DirectPrinter) { await window.Capacitor.Plugins.DirectPrinter.printBase64({ data: d, name: 'Pixel-Art' }); if(btn) btn.innerText = "✅ Gedruckt!"; playSound('success'); } else { const img = document.getElementById('print-img'); img.src = d; setTimeout(() => { window.print(); }, 100); if(btn) btn.innerText = "✅ OK"; playSound('success'); } } catch (e) { alert("Fehler: " + e.message); if(btn) btn.innerText = "❌ Fehler"; } setTimeout(() => { if(btn) { btn.innerHTML = oldText || '🖨️ <span class="hidden xl:inline">DRUCK</span>'; btn.disabled = false; } }, 3000); }
|
||||
window.onload = initGrid;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
126
public/apps/rec.html
Normal file
@@ -0,0 +1,126 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Mikro Check</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
|
||||
<script src="../js/tailwind.js"></script>
|
||||
|
||||
<style>
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
html { width: 100%; height: 100%; background-color: #0f172a; }
|
||||
body {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0; margin: 0;
|
||||
background-color: #0f172a;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
touch-action: manipulation;
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
box-sizing: border-box;
|
||||
padding-top: calc(10px + env(safe-area-inset-top));
|
||||
padding-bottom: max(20px, env(safe-area-inset-bottom));
|
||||
}
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
button:active { transform: scale(0.95); transition: transform 0.1s; }
|
||||
.rec-active { animation: hardPulse 0.8s cubic-bezier(0.4, 0, 0.6, 1) infinite; background-color: #ef4444 !important; border-color: white !important; box-shadow: 0 0 30px rgba(239, 68, 68, 0.6) !important; }
|
||||
@keyframes hardPulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.1); opacity: 0.9; } }
|
||||
.playing { box-shadow: 0 0 20px #22c55e; border-color: #86efac !important; }
|
||||
.effect-active { ring: 4px solid white; transform: scale(1.1); box-shadow: 0 0 15px rgba(255,255,255,0.4); z-index: 10; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col px-4"
|
||||
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
|
||||
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
|
||||
|
||||
<div class="flex justify-between items-center mb-4 z-50 flex-none h-16">
|
||||
<button onclick="goHome()" class="bg-slate-800 border-4 border-slate-600 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 text-white">
|
||||
🏠 MENÜ
|
||||
</button>
|
||||
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 bg-slate-800 text-white rounded-full font-bold border-4 border-slate-600 hover:border-white shadow-xl flex items-center justify-center text-xl">
|
||||
?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-col gap-4 relative w-full max-w-5xl mx-auto min-h-0">
|
||||
|
||||
<div class="relative w-full h-32 flex-none bg-slate-800 rounded-3xl border-4 border-slate-700 shadow-inner flex items-center justify-center overflow-hidden">
|
||||
<canvas id="visualizer" class="w-full h-full"></canvas>
|
||||
<div id="instruction-text" class="absolute text-slate-500 font-bold text-xl text-center px-4 pointer-events-none">
|
||||
Mikrofon bereit... 🎤
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 grid grid-cols-2 gap-4 min-h-0">
|
||||
<div class="bg-slate-800 rounded-3xl border-4 border-slate-700 p-4 flex flex-col items-center justify-between shadow-lg relative overflow-hidden">
|
||||
<div class="absolute top-0 left-0 bg-slate-700 px-4 py-1 rounded-br-xl font-black text-slate-400">SPUR 1</div>
|
||||
<div id="timer-1" class="text-3xl md:text-4xl font-black font-mono text-slate-600 mt-2 transition-colors">00:00</div>
|
||||
<div class="flex flex-col items-center gap-3 w-full">
|
||||
<button id="btn-rec-1" onclick="toggleRecording(1)" class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-slate-700 border-8 border-slate-600 shadow-xl flex items-center justify-center transition-all hover:bg-slate-600 group">
|
||||
<div id="icon-rec-1" class="w-6 h-6 md:w-8 md:h-8 bg-red-500 rounded-full shadow-lg group-hover:scale-110 transition-transform"></div>
|
||||
</button>
|
||||
<div id="fx-box-1" class="hidden flex gap-2 w-full justify-center mt-2">
|
||||
<button onclick="setEffect(1, 0.6)" data-rate="0.6" class="fx-btn-1 w-12 h-12 rounded-xl bg-purple-600 text-2xl shadow-md border-b-4 border-purple-800 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Monster">🧟</button>
|
||||
<button onclick="setEffect(1, 1)" data-rate="1" class="fx-btn-1 w-12 h-12 rounded-xl bg-blue-600 text-2xl shadow-md border-b-4 border-blue-800 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Normal">🙂</button>
|
||||
<button onclick="setEffect(1, 1.7)" data-rate="1.7" class="fx-btn-1 w-12 h-12 rounded-xl bg-yellow-500 text-2xl shadow-md border-b-4 border-yellow-700 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Maus">🐭</button>
|
||||
</div>
|
||||
<button id="btn-play-1" onclick="playTrack(1)" class="hidden w-full bg-green-600 hover:bg-green-500 text-white font-black py-2 rounded-xl text-base shadow-lg border-b-4 border-green-800 active:border-b-0 active:translate-y-1 transition-all flex items-center justify-center gap-2"><span>▶️</span> ANHÖREN</button>
|
||||
<button id="btn-del-1" onclick="deleteTrack(1)" class="hidden text-slate-500 hover:text-red-400 text-xs font-bold uppercase tracking-wider py-1">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-800 rounded-3xl border-4 border-slate-700 p-4 flex flex-col items-center justify-between shadow-lg relative overflow-hidden">
|
||||
<div class="absolute top-0 left-0 bg-slate-700 px-4 py-1 rounded-br-xl font-black text-slate-400">SPUR 2</div>
|
||||
<div id="timer-2" class="text-3xl md:text-4xl font-black font-mono text-slate-600 mt-2 transition-colors">00:00</div>
|
||||
<div class="flex flex-col items-center gap-3 w-full">
|
||||
<button id="btn-rec-2" onclick="toggleRecording(2)" class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-slate-700 border-8 border-slate-600 shadow-xl flex items-center justify-center transition-all hover:bg-slate-600 group">
|
||||
<div id="icon-rec-2" class="w-6 h-6 md:w-8 md:h-8 bg-red-500 rounded-full shadow-lg group-hover:scale-110 transition-transform"></div>
|
||||
</button>
|
||||
<div id="fx-box-2" class="hidden flex gap-2 w-full justify-center mt-2">
|
||||
<button onclick="setEffect(2, 0.6)" data-rate="0.6" class="fx-btn-2 w-12 h-12 rounded-xl bg-purple-600 text-2xl shadow-md border-b-4 border-purple-800 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Monster">🧟</button>
|
||||
<button onclick="setEffect(2, 1)" data-rate="1" class="fx-btn-2 w-12 h-12 rounded-xl bg-blue-600 text-2xl shadow-md border-b-4 border-blue-800 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Normal">🙂</button>
|
||||
<button onclick="setEffect(2, 1.7)" data-rate="1.7" class="fx-btn-2 w-12 h-12 rounded-xl bg-yellow-500 text-2xl shadow-md border-b-4 border-yellow-700 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Maus">🐭</button>
|
||||
</div>
|
||||
<button id="btn-play-2" onclick="playTrack(2)" class="hidden w-full bg-blue-600 hover:bg-blue-500 text-white font-black py-2 rounded-xl text-base shadow-lg border-b-4 border-blue-800 active:border-b-0 active:translate-y-1 transition-all flex items-center justify-center gap-2"><span>▶️</span> ANHÖREN</button>
|
||||
<button id="btn-del-2" onclick="deleteTrack(2)" class="hidden text-slate-500 hover:text-red-400 text-xs font-bold uppercase tracking-wider py-1">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
|
||||
<div class="bg-slate-800 border-4 border-red-500 rounded-[2rem] max-w-lg w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
|
||||
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-red-400 text-4xl font-bold transition">✖</button>
|
||||
<h2 class="text-3xl font-black text-white mb-6 border-b border-slate-600 pb-4">🎙️ VOICE CHANGER</h2>
|
||||
<ul class="text-slate-300 space-y-3 mb-8 list-disc pl-6 text-lg">
|
||||
<li>Nimm links oder rechts etwas auf.</li>
|
||||
<li>Drücke auf <b>Stopp</b>.</li>
|
||||
<li>Jetzt erscheint Magie! ✨</li>
|
||||
<li>Wähle: 🧟 <b>Monster</b> oder 🐭 <b>Maus</b>.</li>
|
||||
<li>Hör es dir an!</li>
|
||||
</ul>
|
||||
<button onclick="toggleInfo(); playSound('click')" class="w-full bg-red-600 hover:bg-red-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const _soundCache = {'click': new Audio('../assets/sounds/click.mp3'), 'shutter': new Audio('../assets/sounds/shutter.mp3'), 'success': new Audio('../assets/sounds/success.mp3')};
|
||||
Object.values(_soundCache).forEach(s => s.load());
|
||||
window.playSound = function(id) { if(_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e=>{}); } };
|
||||
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
|
||||
function toggleInfo() { const m = document.getElementById('info-modal'); m.classList.toggle('hidden'); m.classList.toggle('flex'); }
|
||||
let micStream=null; let audioContext=null; let analyser=null; let dataArray=null; let gainNode=null;
|
||||
const tracks = { 1: { isRecording: false, mediaRecorder: null, chunks: [], audio: new Audio(), hasData: false, timerInt: null, startTime: 0, playbackRate: 1, elementSource: null }, 2: { isRecording: false, mediaRecorder: null, chunks: [], audio: new Audio(), hasData: false, timerInt: null, startTime: 0, playbackRate: 1, elementSource: null } };
|
||||
let idleTimer;
|
||||
window.resetIdleTimer = function() { clearTimeout(idleTimer); const rec = tracks[1].isRecording || tracks[2].isRecording; if(!rec) { idleTimer = setTimeout(() => { window.location.href = '../index.html'; }, 120000); } };
|
||||
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, window.resetIdleTimer, {passive: true})); window.resetIdleTimer();
|
||||
async function initAudio() { try { if(!micStream) { micStream = await navigator.mediaDevices.getUserMedia({ audio: true }); audioContext = new (window.AudioContext || window.webkitAudioContext)(); gainNode = audioContext.createGain(); gainNode.gain.value = 1.5; gainNode.connect(audioContext.destination); analyser = audioContext.createAnalyser(); const s = audioContext.createMediaStreamSource(micStream); s.connect(analyser); analyser.fftSize = 256; dataArray = new Uint8Array(analyser.frequencyBinCount); drawVisualizer(); } if (audioContext && audioContext.state === 'suspended') await audioContext.resume(); } catch (e) { alert("Mikrofon Fehler: " + e.message); } }
|
||||
function drawVisualizer() { const c = document.getElementById('visualizer'); if(!c) return; const ctx = c.getContext('2d'); c.width = c.offsetWidth; c.height = c.offsetHeight; function render() { requestAnimationFrame(render); if(!analyser) return; analyser.getByteFrequencyData(dataArray); ctx.fillStyle = '#1e293b'; ctx.fillRect(0, 0, c.width, c.height); const active = tracks[1].isRecording || tracks[2].isRecording; const alpha = active ? 1 : 0.3; let x = 0; const barW = (c.width / analyser.frequencyBinCount) * 2.5; for (let i = 0; i < analyser.frequencyBinCount; i++) { const h = dataArray[i] / 255 * c.height * 0.9; const r = active ? (h+50) : 50; const g = active ? 50 : (h+100); const b = active ? 50 : 200; ctx.fillStyle = `rgba(${r},${g},${b},${alpha})`; ctx.fillRect(x, c.height - h, barW, h); x += barW + 1; } } render(); }
|
||||
async function toggleRecording(id) { await initAudio(); const t = tracks[id]; const btn = document.getElementById(`btn-rec-${id}`); const icon = document.getElementById(`icon-rec-${id}`); const playBtn = document.getElementById(`btn-play-${id}`); const delBtn = document.getElementById(`btn-del-${id}`); const fxBox = document.getElementById(`fx-box-${id}`); const timer = document.getElementById(`timer-${id}`); if (!t.isRecording) { playSound('shutter'); if(t.hasData) deleteTrack(id); t.mediaRecorder = new MediaRecorder(micStream); t.chunks = []; t.mediaRecorder.ondataavailable = e => t.chunks.push(e.data); t.mediaRecorder.onstop = () => { const b = new Blob(t.chunks, { type: 'audio/ogg; codecs=opus' }); t.audio.src = URL.createObjectURL(b); t.audio.preservesPitch = false; t.audio.mozPreservesPitch = false; t.audio.webkitPreservesPitch = false; t.hasData = true; playBtn.classList.remove('hidden'); delBtn.classList.remove('hidden'); fxBox.classList.remove('hidden'); fxBox.classList.add('flex'); setEffect(id, 1); playSound('success'); }; t.mediaRecorder.start(); t.isRecording = true; document.getElementById('instruction-text').classList.add('hidden'); btn.classList.add('rec-active'); icon.classList.replace('bg-red-500', 'bg-white'); timer.classList.replace('text-slate-600', 'text-white'); t.startTime = Date.now(); t.timerInt = setInterval(() => { const d = Math.floor((Date.now() - t.startTime) / 1000); timer.innerText = `${Math.floor(d/60).toString().padStart(2,'0')}:${(d%60).toString().padStart(2,'0')}`; }, 1000); } else { t.mediaRecorder.stop(); t.isRecording = false; clearInterval(t.timerInt); btn.classList.remove('rec-active'); icon.classList.replace('bg-white', 'bg-red-500'); timer.classList.replace('text-white', 'text-slate-600'); } }
|
||||
function setEffect(id, rate) { playSound('click'); tracks[id].playbackRate = rate; document.querySelectorAll(`.fx-btn-${id}`).forEach(b => { b.classList.remove('effect-active', 'ring-4', 'ring-white'); if(b.getAttribute('data-rate') === rate.toString()) b.classList.add('effect-active', 'ring-4', 'ring-white'); }); }
|
||||
function playTrack(id) { const t = tracks[id]; if(!t.hasData) return; playSound('click'); const btn = document.getElementById(`btn-play-${id}`); if (audioContext && !t.elementSource) { t.elementSource = audioContext.createMediaElementSource(t.audio); t.elementSource.connect(gainNode); } if (!t.audio.paused) { t.audio.pause(); t.audio.currentTime = 0; btn.classList.remove('playing'); btn.innerHTML = "<span>▶️</span> ANHÖREN"; } else { t.audio.playbackRate = t.playbackRate; t.audio.preservesPitch = false; if (audioContext.state === 'suspended') audioContext.resume(); t.audio.play(); btn.classList.add('playing'); btn.innerHTML = "<span>⏹</span> STOP"; t.audio.onended = () => { btn.classList.remove('playing'); btn.innerHTML = "<span>▶️</span> ANHÖREN"; }; } }
|
||||
function deleteTrack(id) { playSound('click'); const t = tracks[id]; t.audio.pause(); t.chunks = []; t.hasData = false; document.getElementById(`btn-play-${id}`).classList.add('hidden'); document.getElementById(`btn-del-${id}`).classList.add('hidden'); document.getElementById(`fx-box-${id}`).classList.add('hidden'); document.getElementById(`fx-box-${id}`).classList.remove('flex'); document.getElementById(`timer-${id}`).innerText = "00:00"; }
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
161
public/apps/sound.html
Normal file
@@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Sound Labor</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
|
||||
<script src="../js/tailwind.js"></script>
|
||||
|
||||
<style>
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
|
||||
html {
|
||||
width: 100%; height: 100%;
|
||||
background-color: #0f172a;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Fixiertes Layout */
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
margin: 0;
|
||||
|
||||
background-color: #0f172a; /* Dunkelblau */
|
||||
color: white;
|
||||
overflow: hidden !important;
|
||||
user-select: none;
|
||||
touch-action: manipulation;
|
||||
font-family: sans-serif;
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
/* SAFE AREA PADDING */
|
||||
padding-top: calc(10px + env(safe-area-inset-top));
|
||||
padding-bottom: max(20px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
#pad-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
gap: 0.5rem; /* Etwas enger für mehr Platz */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pad-item {
|
||||
background-color: #1e293b;
|
||||
border-radius: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-bottom: 6px solid #0f172a;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.1s;
|
||||
}
|
||||
.pad-item:active { border-bottom-width: 0; transform: translateY(4px); }
|
||||
|
||||
button:active, a:active { transform: scale(0.95); transition: transform 0.1s; }
|
||||
.pulse-rec { animation: pulseRed 1s infinite; border-color: #ef4444 !important; box-shadow: 0 0 25px #ef4444; }
|
||||
@keyframes pulseRed { 0% { opacity: 1; } 50% { opacity: 0.6; } 100% { opacity: 1; } }
|
||||
.fx-active { ring: 4px solid white; transform: scale(1.1); box-shadow: 0 0 15px rgba(255,255,255,0.5); }
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col p-2 md:p-4"
|
||||
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
|
||||
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
|
||||
|
||||
<div class="flex-none flex justify-between items-center z-50 mb-2 relative h-16">
|
||||
<button onclick="goHome()" class="bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none shrink-0 z-50">
|
||||
🏠 MENÜ
|
||||
</button>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center pointer-events-none z-0">
|
||||
<h1 class="text-2xl md:text-4xl font-black text-white drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] tracking-widest uppercase">
|
||||
✨ SOUND
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 bg-slate-800 text-white rounded-full font-bold border-4 border-slate-600 hover:border-white shadow-xl flex items-center justify-center transition-transform active:scale-95 text-xl shrink-0 z-50">
|
||||
?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col xl:flex-row justify-center items-center gap-2 z-40 mb-2 shrink-0">
|
||||
<div id="visualizer-box" class="hidden relative w-48 h-12 bg-slate-800 rounded-xl border-2 border-red-500 overflow-hidden shadow-[0_0_20px_rgba(239,68,68,0.5)]">
|
||||
<canvas id="visualizer" class="w-full h-full"></canvas>
|
||||
<div class="absolute inset-0 flex items-center justify-center text-red-500 font-black tracking-widest text-[10px] pointer-events-none animate-pulse">● REC</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 bg-slate-800/50 p-1 rounded-xl border border-slate-700 items-center">
|
||||
<button onclick="setGlobalFx(0.6, this)" class="fx-btn w-10 h-10 rounded-lg bg-purple-600 text-xl shadow-md border-b-2 border-purple-800 active:border-b-0 active:translate-y-1 transition" title="Monster">🧟</button>
|
||||
<button onclick="setGlobalFx(1.0, this)" class="fx-btn w-10 h-10 rounded-lg bg-blue-600 text-xl shadow-md border-b-2 border-blue-800 active:border-b-0 active:translate-y-1 transition fx-active ring-4 ring-white" title="Normal">🙂</button>
|
||||
<button onclick="setGlobalFx(1.7, this)" class="fx-btn w-10 h-10 rounded-lg bg-yellow-500 text-xl shadow-md border-b-2 border-yellow-700 active:border-b-0 active:translate-y-1 transition" title="Maus">🐭</button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 bg-slate-800 p-1 rounded-2xl border-2 border-slate-700 shadow-xl w-full xl:w-auto justify-center">
|
||||
<button id="btn-rec" onclick="setMode('rec')" class="flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-black text-base transition-all border-2 border-transparent bg-slate-700 hover:bg-slate-600 text-gray-300 active:scale-95">
|
||||
<div class="w-3 h-3 rounded-full bg-red-500 shadow-sm"></div> REC
|
||||
</button>
|
||||
<button id="btn-loop" onclick="setMode('loop')" class="flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-black text-base transition-all border-2 border-transparent bg-slate-700 hover:bg-slate-600 text-gray-300 active:scale-95">
|
||||
🔁 LOOP
|
||||
</button>
|
||||
<button onclick="stopAll(); playSound('click')" class="px-4 py-2 rounded-xl font-black text-base bg-slate-700 hover:bg-red-500 hover:text-white transition-all text-gray-300 border-2 border-transparent active:scale-95">
|
||||
⏹ STOP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="status-bar" class="text-center text-slate-400 mb-1 font-mono text-sm uppercase tracking-widest font-bold transition-all duration-300 shrink-0">
|
||||
Modus: SPIELEN
|
||||
</div>
|
||||
|
||||
<div id="pad-container" class="flex-1 min-h-0 w-full max-w-5xl mx-auto z-10 pb-1"></div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
|
||||
<div class="bg-slate-800 border-4 border-rose-500 rounded-[2rem] max-w-3xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
|
||||
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-rose-400 text-4xl font-bold transition">✖</button>
|
||||
<h2 class="text-4xl font-black text-white mb-8 border-b border-slate-600 pb-4 flex items-center gap-4"><span class="text-6xl">🎹</span> ANLEITUNG</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 text-base">
|
||||
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🧟</div><h3 class="text-purple-400 font-bold text-xl mb-2">Effekte</h3><p class="text-slate-300 leading-snug">Wähle <b>VOR</b> der Aufnahme oben Monster oder Maus.</p></div>
|
||||
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🎤</div><h3 class="text-rose-400 font-bold text-xl mb-2">Aufnehmen</h3><p class="text-slate-300 leading-snug">1. Tippe <b>REC</b>.<br>2. Wähle Pad.<br>3. Sprich.<br>4. Tippe Pad zum Stop.</p></div>
|
||||
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🔁</div><h3 class="text-blue-400 font-bold text-xl mb-2">Loopen</h3><p class="text-slate-300 leading-snug">Tippe <b>LOOP</b> und wähle Pads für Dauerschleife.</p></div>
|
||||
</div>
|
||||
<button onclick="toggleInfo(); playSound('click')" class="mt-8 w-full bg-rose-600 hover:bg-rose-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const _soundCache = {'click': new Audio('../assets/sounds/click.mp3'), 'shutter': new Audio('../assets/sounds/shutter.mp3'), 'success': new Audio('../assets/sounds/success.mp3')};
|
||||
Object.values(_soundCache).forEach(s => s.load());
|
||||
window.playSound = function(id) { if(_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e=>{}); } };
|
||||
function goHome() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); }
|
||||
function toggleInfo() { const m = document.getElementById('info-modal'); m.classList.toggle('hidden'); m.classList.toggle('flex'); }
|
||||
let idleTimer;
|
||||
function resetIdleTimer() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { if (typeof pads !== 'undefined' && Array.isArray(pads) && !pads.some(p => p.isRecording)) window.location.href = '../index.html'; }, 120000); }
|
||||
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, resetIdleTimer, {passive: true})); resetIdleTimer();
|
||||
let currentRecRate = 1.0;
|
||||
function setGlobalFx(rate, btn) { playSound('click'); currentRecRate = rate; document.querySelectorAll('.fx-btn').forEach(b => b.classList.remove('fx-active', 'ring-4', 'ring-white')); btn.classList.add('fx-active', 'ring-4', 'ring-white'); }
|
||||
let audioCtx; let pads = []; let currentMode = 'play'; let analyser, dataArray, visualizerFrame;
|
||||
function init() { try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {} const container = document.getElementById('pad-container'); if(!container) return; container.innerHTML = ''; for(let i = 0; i < 12; i++) { pads.push({ buffer: null, loop: false, sourceNode: null, isRecording: false, mediaRecorder: null, chunks: [], playbackRate: 1.0 }); const el = document.createElement('div'); el.id = `pad-${i}`; el.className = `pad-item transition-all duration-100`; el.innerHTML = `<div class="text-4xl md:text-6xl font-black text-slate-700 transition-colors duration-300" id="label-${i}">${i+1}</div><div id="icon-loop-${i}" class="hidden absolute top-2 right-2 text-xl bg-blue-600 text-white rounded px-1 shadow-md">🔁</div><div id="icon-fx-${i}" class="absolute bottom-2 right-2 text-xl drop-shadow-md"></div><div id="icon-rec-${i}" class="hidden absolute top-3 left-3 w-4 h-4 bg-red-500 rounded-full animate-pulse shadow-[0_0_10px_red]"></div><div id="status-${i}" class="hidden absolute bottom-2 w-full text-center text-xs font-black uppercase tracking-widest text-emerald-400">BELEGT</div>`; el.onpointerdown = (e) => { e.preventDefault(); handlePadTouch(i); }; container.appendChild(el); } }
|
||||
window.setMode = function(mode) { playSound('click'); if (currentMode === mode) currentMode = 'play'; else currentMode = mode; updateUI(); }
|
||||
function updateUI() { const btnRec = document.getElementById('btn-rec'); const btnLoop = document.getElementById('btn-loop'); const status = document.getElementById('status-bar'); const inactive = "border-transparent bg-slate-700 text-gray-300 hover:bg-slate-600"; const activeRec = "border-red-500 text-red-400 bg-slate-900 shadow-[0_0_10px_rgba(239,68,68,0.2)]"; const activeLoop = "border-blue-500 text-blue-400 bg-slate-900 shadow-[0_0_10px_rgba(59,130,246,0.2)]"; const base = "flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-black text-base transition-all border-2 active:scale-95 "; btnRec.className = base + (currentMode === 'rec' ? activeRec : inactive); btnLoop.className = base + (currentMode === 'loop' ? activeLoop : inactive); if (currentMode === 'rec') { status.innerText = "🔴 AUFNAHME"; status.className = "text-center mb-1 font-mono text-sm uppercase tracking-widest text-red-400 animate-pulse font-bold"; } else if (currentMode === 'loop') { status.innerText = "🔁 LOOP SETZEN"; status.className = "text-center mb-1 font-mono text-sm uppercase tracking-widest text-blue-400 font-bold"; } else { status.innerText = "Modus: SPIELEN"; status.className = "text-center text-slate-400 mb-1 font-mono text-sm uppercase tracking-widest font-bold"; } }
|
||||
async function handlePadTouch(i) { resetIdleTimer(); if (audioCtx && audioCtx.state === 'suspended') await audioCtx.resume(); const pad = pads[i]; const el = document.getElementById(`pad-${i}`); if (currentMode === 'play') { if (!pad.buffer) return; if (pad.loop) { if (pad.sourceNode) { pad.sourceNode.stop(); pad.sourceNode = null; el.classList.remove('bg-blue-600', 'border-blue-800'); el.style.backgroundColor = "#1e293b"; } else { playMusicPad(i, true); el.style.backgroundColor = "#2563eb"; } } else { playMusicPad(i, false); el.style.backgroundColor = "#10b981"; setTimeout(() => el.style.backgroundColor = "#1e293b", 150); } } else if (currentMode === 'loop') { playSound('click'); if (!pad.buffer) return; pad.loop = !pad.loop; document.getElementById(`icon-loop-${i}`).classList.toggle('hidden', !pad.loop); el.style.transform = "scale(0.95)"; setTimeout(() => el.style.transform = "scale(1)", 100); } else if (currentMode === 'rec') { if (pad.isRecording) stopRecording(i); else startRecording(i); } }
|
||||
function playMusicPad(i, loop) { const pad = pads[i]; if (pad.sourceNode) { try { pad.sourceNode.stop(); } catch(e){} pad.sourceNode = null; } const source = audioCtx.createBufferSource(); source.buffer = pad.buffer; source.loop = loop; source.playbackRate.value = pad.playbackRate; source.connect(audioCtx.destination); source.start(0); if (loop) pad.sourceNode = source; }
|
||||
function startVisualizer(stream) { document.getElementById('visualizer-box').classList.remove('hidden'); const canvas = document.getElementById('visualizer'); const ctx = canvas.getContext('2d'); const source = audioCtx.createMediaStreamSource(stream); analyser = audioCtx.createAnalyser(); source.connect(analyser); analyser.fftSize = 256; dataArray = new Uint8Array(analyser.frequencyBinCount); function draw() { visualizerFrame = requestAnimationFrame(draw); analyser.getByteFrequencyData(dataArray); canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; ctx.fillStyle = '#1e293b'; ctx.fillRect(0, 0, canvas.width, canvas.height); const barWidth = (canvas.width / analyser.frequencyBinCount) * 2.5; let x = 0; for(let i = 0; i < analyser.frequencyBinCount; i++) { const barHeight = dataArray[i] / 255 * canvas.height; ctx.fillStyle = `rgb(${barHeight+50},50,50)`; ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight); x += barWidth + 1; } } draw(); }
|
||||
function stopVisualizer() { document.getElementById('visualizer-box').classList.add('hidden'); cancelAnimationFrame(visualizerFrame); }
|
||||
async function startRecording(i) { playSound('shutter'); const pad = pads[i]; const el = document.getElementById(`pad-${i}`); try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); startVisualizer(stream); pad.mediaRecorder = new MediaRecorder(stream); pad.chunks = []; pad.mediaRecorder.ondataavailable = (e) => pad.chunks.push(e.data); pad.mediaRecorder.onstop = async () => { const blob = new Blob(pad.chunks, { 'type' : 'audio/webm; codecs=opus' }); const arrayBuffer = await blob.arrayBuffer(); audioCtx.decodeAudioData(arrayBuffer, (decodedBuffer) => { pad.buffer = decodedBuffer; pad.playbackRate = currentRecRate; updatePadVisuals(i, true); }, (e) => console.error(e)); stream.getTracks().forEach(track => track.stop()); stopVisualizer(); }; pad.mediaRecorder.start(); pad.isRecording = true; document.getElementById(`icon-rec-${i}`).classList.remove('hidden'); el.classList.add('pulse-rec'); } catch (err) { alert("Mikrofon Fehler: " + err.message); } }
|
||||
function stopRecording(i) { playSound('success'); const pad = pads[i]; const el = document.getElementById(`pad-${i}`); if (pad.mediaRecorder && pad.mediaRecorder.state !== 'inactive') pad.mediaRecorder.stop(); pad.isRecording = false; document.getElementById(`icon-rec-${i}`).classList.add('hidden'); el.classList.remove('pulse-rec'); window.setMode('play'); }
|
||||
function updatePadVisuals(i, hasSound) { const el = document.getElementById(`pad-${i}`); const label = document.getElementById(`label-${i}`); const status = document.getElementById(`status-${i}`); const iconFx = document.getElementById(`icon-fx-${i}`); const pad = pads[i]; if (hasSound) { el.style.borderColor = "#10b981"; label.classList.remove('text-slate-700'); label.classList.add('text-white'); status.classList.remove('hidden'); if(pad.playbackRate < 0.9) iconFx.innerText = "🧟"; else if(pad.playbackRate > 1.2) iconFx.innerText = "🐭"; else iconFx.innerText = ""; } }
|
||||
window.stopAll = function() { if(pads && pads.length > 0) { pads.forEach((pad, i) => { if (pad.sourceNode) { try { pad.sourceNode.stop(); } catch(e){} pad.sourceNode = null; } const el = document.getElementById(`pad-${i}`); if(el) { el.style.backgroundColor = "#1e293b"; el.classList.remove('bg-blue-600'); } }); } }
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
public/assets/dino.jpg
Normal file
|
After Width: | Height: | Size: 677 KiB |
BIN
public/assets/dschungel.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/assets/logo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/assets/news.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
public/assets/ozean.jpg
Normal file
|
After Width: | Height: | Size: 681 KiB |
BIN
public/assets/paris.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
public/assets/qr.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
public/assets/schloss.jpg
Normal file
|
After Width: | Height: | Size: 903 KiB |
BIN
public/assets/sounds/FALSCH.mp3
Normal file
BIN
public/assets/sounds/RICHTIG.mp3
Normal file
BIN
public/assets/sounds/brick.mp3
Executable file
BIN
public/assets/sounds/click.mp3
Normal file
BIN
public/assets/sounds/fail.mp3
Executable file
BIN
public/assets/sounds/paddle.mp3
Executable file
BIN
public/assets/sounds/shutter.mp3
Normal file
BIN
public/assets/sounds/success.mp3
Normal file
BIN
public/assets/sounds/wall.mp3
Executable file
BIN
public/assets/stadion.jpg
Normal file
|
After Width: | Height: | Size: 875 KiB |
BIN
public/assets/unterwasser.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
public/assets/weltraum.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/assets/wolken.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
1559
public/cordova.js
vendored
Normal file
36
public/cordova_plugins.js
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
cordova.define('cordova/plugin_list', function(require, exports, module) {
|
||||
module.exports = [
|
||||
{
|
||||
"id": "cordova-plugin-printer.Printer",
|
||||
"file": "plugins/cordova-plugin-printer/www/printer.js",
|
||||
"pluginId": "cordova-plugin-printer",
|
||||
"clobbers": [
|
||||
"cordova.plugins.printer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cordova-plugin-x-socialsharing.SocialSharing",
|
||||
"file": "plugins/cordova-plugin-x-socialsharing/www/SocialSharing.js",
|
||||
"pluginId": "cordova-plugin-x-socialsharing",
|
||||
"clobbers": [
|
||||
"window.plugins.socialsharing"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "es6-promise-plugin.Promise",
|
||||
"file": "plugins/es6-promise-plugin/www/promise.js",
|
||||
"pluginId": "es6-promise-plugin",
|
||||
"runs": true
|
||||
}
|
||||
];
|
||||
module.exports.metadata =
|
||||
// TOP OF METADATA
|
||||
{
|
||||
"cordova-plugin-printer": "0.8.0",
|
||||
"cordova-plugin-x-socialsharing": "6.0.4",
|
||||
"es6-promise-plugin": "4.2.2"
|
||||
};
|
||||
// BOTTOM OF METADATA
|
||||
});
|
||||
|
||||
270
public/index.html
Normal file
@@ -0,0 +1,270 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>MedienStation Hub</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
|
||||
<script src="js/tailwind.js"></script>
|
||||
<style>
|
||||
/* Globaler Reset */
|
||||
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }
|
||||
|
||||
body {
|
||||
background-color: #0f172a;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.font-comic { font-family: 'Comic Sans MS', 'Chalkboard SE', sans-serif; }
|
||||
.app-card { cursor: pointer; transition: transform 0.1s; }
|
||||
.app-card:active { transform: scale(0.95); filter: brightness(1.2); }
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
|
||||
/* Animationen */
|
||||
.fade-in { animation: fadeIn 0.5s ease-out; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }
|
||||
|
||||
/* Grid & Ghost Mode für QR Modal */
|
||||
#qr-modal {
|
||||
display: grid !important;
|
||||
place-items: center;
|
||||
position: fixed;
|
||||
top: 0; left: 0; width: 100vw; height: 100vh;
|
||||
z-index: 99999;
|
||||
background-color: rgba(0,0,0,0.85);
|
||||
backdrop-filter: blur(5px);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
#qr-modal.modal-visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="p-4 pt-8 box-border fade-in relative" onpointerdown="resetHubTimer()" ontouchstart="resetHubTimer()">
|
||||
<div class="grid grid-cols-12 grid-rows-12 gap-3 h-full w-full pb-20">
|
||||
|
||||
<div class="col-span-12 row-span-2 bg-slate-800 rounded-3xl px-6 flex justify-between items-center shadow-lg border border-slate-700 relative overflow-hidden group gap-4">
|
||||
|
||||
<div class="absolute -right-10 -top-10 w-64 h-64 bg-yellow-400 rounded-full blur-3xl opacity-10 group-hover:opacity-20 transition-opacity duration-500 pointer-events-none"></div>
|
||||
|
||||
<div class="z-10 flex-1 select-none cursor-pointer hover:opacity-80 transition-opacity flex flex-col justify-center" onclick="triggerAdmin()">
|
||||
<h3 class="text-slate-400 text-xs font-bold tracking-[0.2em] uppercase mb-1">DEINE MISSION</h3>
|
||||
<div id="mission-text" class="text-xl md:text-3xl lg:text-4xl font-black text-white leading-tight truncate">Wähle eine App! 👉</div>
|
||||
</div>
|
||||
|
||||
<button onclick="playSound('click'); newMission()" class="shrink-0 z-10 bg-yellow-400 hover:bg-yellow-300 text-black font-black text-lg py-3 px-8 rounded-full shadow-lg active:translate-y-1 transition-all transform hover:scale-105 flex items-center gap-2">
|
||||
<span class="text-2xl">🎲</span> <span class="hidden md:inline">NEU</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/sound.html', 'Sound')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-rose-500 to-rose-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-rose-800 active:border-b-0 group">
|
||||
<div class="text-6xl mb-2 drop-shadow-md group-hover:-translate-y-2 transition-transform">🎹</div>
|
||||
<h3 class="text-2xl font-bold">Sound</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/rec.html', 'Mikro')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-red-600 to-red-700 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-red-900 active:border-b-0">
|
||||
<div class="text-6xl mb-2 drop-shadow-md">🎙️</div>
|
||||
<h3 class="text-2xl font-bold">Mikro</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/gif.html', 'Loop')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-sky-500 to-sky-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-sky-800 active:border-b-0 group">
|
||||
<div class="text-6xl mb-2 drop-shadow-md group-hover:rotate-12 transition-transform">📹</div>
|
||||
<h3 class="text-2xl font-bold">Loop</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/magic.html', 'Magic')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-violet-500 to-violet-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-violet-800 active:border-b-0">
|
||||
<div class="text-6xl mb-2 drop-shadow-md">✨</div>
|
||||
<h3 class="text-2xl font-bold">Magic</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/pixel.html', 'Pixel')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-emerald-500 to-emerald-700 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-emerald-900 active:border-b-0">
|
||||
<div class="text-6xl mb-2 drop-shadow-md font-mono">👾</div>
|
||||
<h3 class="text-2xl font-bold">Pixel</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/news.html', 'News')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-blue-700 to-blue-900 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-blue-950 active:border-b-0 relative overflow-hidden">
|
||||
<div class="text-6xl mb-2 drop-shadow-md relative z-10">📰</div>
|
||||
<h3 class="text-2xl font-bold relative z-10">News</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="openApp('apps/comic.html', 'Comic')" class="app-card col-span-3 row-span-5 bg-gradient-to-r from-yellow-400 to-orange-400 rounded-3xl flex items-center justify-center shadow-lg border-b-8 border-orange-600 active:border-b-0 group gap-2">
|
||||
<div class="text-5xl drop-shadow-md transform -rotate-6 transition-transform group-hover:rotate-0">📸</div>
|
||||
<h3 class="text-2xl font-black font-comic tracking-wide text-black">Comic</h3>
|
||||
</div>
|
||||
|
||||
<div onclick="toggleInfo(); playSound('click')" class="app-card col-span-3 row-span-5 bg-slate-700 hover:bg-slate-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-slate-900 active:border-b-0 border-2 border-slate-600">
|
||||
<div class="text-6xl mb-2 drop-shadow-md">💡</div>
|
||||
<h3 class="text-2xl font-bold text-slate-200">Infos</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fixed bottom-6 w-full flex flex-col items-center justify-center pointer-events-none z-40">
|
||||
<button onclick="toggleQR()" class="pointer-events-auto bg-slate-800/90 backdrop-blur border border-slate-600 px-8 py-3 rounded-full shadow-2xl flex items-center gap-4 hover:bg-slate-700 hover:scale-105 hover:border-white transition-all">
|
||||
<img src="assets/logo.png" class="h-12 w-auto object-contain" alt="Logo" onerror="this.style.display='none'">
|
||||
<div class="flex flex-col text-left">
|
||||
<span class="text-[10px] text-slate-400 font-bold uppercase tracking-widest leading-none mb-1">Ein Projekt der</span>
|
||||
<span class="text-lg font-black text-white leading-none tracking-wide">AV-MEDIENZENTRALE</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/90 backdrop-blur-md flex items-center justify-center p-6">
|
||||
<div class="bg-slate-800 border-2 border-slate-600 rounded-[2.5rem] w-full max-w-5xl p-8 shadow-2xl relative max-h-[90vh] overflow-y-auto no-scrollbar">
|
||||
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white text-5xl font-bold hover:text-slate-300 transition">✖</button>
|
||||
|
||||
<h2 class="text-4xl font-black text-white mb-6 border-b-2 border-slate-600 pb-2 tracking-wide uppercase">Apps & Funktionen</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-12">
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">🎹</div><div><h3 class="text-rose-400 font-black text-xl">Sound</h3><p class="text-slate-300">Baue eigene Beats & Songs.</p></div></div>
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">🎙️</div><div><h3 class="text-red-400 font-black text-xl">Mikro</h3><p class="text-slate-300">Verstelle deine Stimme (Monster/Maus).</p></div></div>
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">📹</div><div><h3 class="text-sky-400 font-black text-xl">Loop</h3><p class="text-slate-300">Drehe lustige Wackel-Videos.</p></div></div>
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">✨</div><div><h3 class="text-violet-400 font-black text-xl">Magic</h3><p class="text-slate-300">Green Screen: Beame dich weg!</p></div></div>
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">👾</div><div><h3 class="text-emerald-400 font-black text-xl">Pixel</h3><p class="text-slate-300">Male Retro-Kunstwerke.</p></div></div>
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">📰</div><div><h3 class="text-blue-400 font-black text-xl">News</h3><p class="text-slate-300">Werde Nachrichtensprecher.</p></div></div>
|
||||
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50 md:col-span-2"><div class="text-5xl shrink-0">📸</div><div><h3 class="text-orange-400 font-black text-xl">Comic</h3><p class="text-slate-300">Erstelle deine eigene Foto-Story mit Sprechblasen.</p></div></div>
|
||||
</div>
|
||||
|
||||
<h2 class="text-4xl font-black text-white mb-6 border-b-2 border-slate-600 pb-2 tracking-wide uppercase">Pädagogik & Konzept</h2>
|
||||
<div class="space-y-6 text-slate-300">
|
||||
<div>
|
||||
<h4 class="text-2xl font-bold text-yellow-400 mb-2">🎯 Das Ziel</h4>
|
||||
<p class="text-lg">Die MedienStation fördert spielerisch <b>Medienkompetenz</b>. Kinder produzieren selbst Medien (Bild, Ton, Video), statt sie nur zu konsumieren. Dabei lernen sie technische Grundlagen und hinterfragen Medieninhalte ("Fake News", Bildbearbeitung).</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-2xl font-bold text-rose-400 mb-2">💡 Reflexions-Fragen für alle Apps</h4>
|
||||
<ul class="list-disc pl-6 space-y-2 text-lg">
|
||||
<li><b>🎹 Sound:</b> Wie verändert Musik die Stimmung eines Films? Würde ein Horrorfilm mit lustiger Musik noch gruselig wirken?</li>
|
||||
<li><b>🎙️ Mikro:</b> Wie verändert eine tiefe oder hohe Stimme deine Wirkung auf andere? Fühlst du dich als "Monster" mutiger?</li>
|
||||
<li><b>📹 Loop:</b> Warum wirken Loops oft lustig? Was passiert, wenn eine Bewegung nie aufhört?</li>
|
||||
<li><b>✨ Magic:</b> Wirkte das Foto echt? Traust du Bildern im Internet jetzt noch genauso schnell?</li>
|
||||
<li><b>👾 Pixel:</b> Wie viele Quadrate (Pixel) braucht man mindestens, um ein Gesicht zu erkennen?</li>
|
||||
<li><b>📰 News:</b> Fühlt man sich wichtiger, wenn "LIVE" und "BREAKING NEWS" im Bild steht?</li>
|
||||
<li><b>📸 Comic:</b> Erzählt ein Bild mehr als 1000 Worte? Wie verändert die Sprechblase die Bedeutung des Fotos?</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="qr-modal" onclick="toggleQR()">
|
||||
<div onclick="event.stopPropagation()"
|
||||
style="width: 90%; max-width: 400px; background: white; padding: 40px; border-radius: 30px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.5);">
|
||||
<h3 style="color: #0f172a; font-size: 24px; font-weight: 900; text-transform: uppercase; margin: 0 0 20px 0;">Besuche uns!</h3>
|
||||
<div style="width: 250px; height: 250px; background: #f8fafc; border: 2px solid #e2e8f0; border-radius: 15px; margin: 0 auto 20px auto; display: flex; align-items: center; justify-content: center;">
|
||||
<img src="assets/qr.png" alt="QR Code" style="width: 230px; height: 230px; object-fit: contain;"
|
||||
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2ZmZiIvPjx0ZXh0IHg9IjUwIiB5PSI1MCIgZm9udC1zaXplPSIxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+UVIgQ29kZTwvdGV4dD48L3N2Zz4='">
|
||||
</div>
|
||||
<p style="color: #64748b; font-weight: bold; margin-bottom: 25px;">Scan den Code mit deinem Handy.</p>
|
||||
<button onclick="toggleQR()"
|
||||
style="width: 100%; background-color: #0f172a; color: white; border: none; padding: 15px; font-size: 18px; font-weight: 900; border-radius: 12px; cursor: pointer;">
|
||||
SCHLIESSEN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="admin-modal" class="hidden fixed inset-0 bg-black/90 z-[300] flex items-center justify-center p-4">
|
||||
<div class="bg-slate-800 p-8 rounded-3xl border-2 border-slate-600 w-full max-w-lg">
|
||||
<h2 class="text-3xl font-black text-white mb-6">⚙️ ADMIN MENU</h2>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<button onclick="location.reload()" class="bg-blue-600 text-white font-bold py-4 rounded-xl">🔄 Reload</button>
|
||||
<button onclick="closeAdmin()" class="bg-slate-600 text-white font-bold py-4 rounded-xl">Zurück</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// --- MISSIONEN ---
|
||||
const missions = [
|
||||
// Standard
|
||||
"Erstelle ein Geräusch: Kälte ❄️",
|
||||
"Reise mit Magic Selfie auf den Mond 🚀",
|
||||
"Werbung für 'Unsichtbare Socken' 🧦",
|
||||
"Loop: 'Hallo' ohne Worte sagen 👋",
|
||||
"Witz in 3 Comic-Bildern 🤡",
|
||||
"Magic Selfie: Du fliegst durch die Luft ✨",
|
||||
"Vertone einen Elefanten mit Schluckauf 🐘",
|
||||
"Pixel-Monster Haustier malen 👾",
|
||||
"News: Ufo gelandet! 👽",
|
||||
"Interviewe dich selbst aus der Zukunft! 🎤",
|
||||
"Erfinde einen geheimen Handschlag! 🤝",
|
||||
"Mache ein Geräusch wie ein wütender Toaster! 🍞",
|
||||
"Zeige deinen besten Roboter-Tanz 🤖",
|
||||
"News: Hausaufgaben offiziell verboten! 🚫",
|
||||
|
||||
// Neue kreative Ideen
|
||||
"Pixel-Art: Dein Traumhaus aus Süßigkeiten 🍭",
|
||||
"Sound: Ein Beat nur aus Körpergeräuschen 👏",
|
||||
"Comic: Der Tag, an dem die Schwerkraft ausfiel 🎈",
|
||||
"News: Katzen übernehmen die Weltherrschaft! 😼",
|
||||
"Loop: Ein Zaubertrick, der schiefgeht 🪄"
|
||||
];
|
||||
|
||||
const playSound = (id) => {
|
||||
const a = new Audio(`assets/sounds/${id}.mp3`);
|
||||
a.volume = 1.0;
|
||||
a.play().catch(() => {});
|
||||
};
|
||||
|
||||
function openApp(url, appName) {
|
||||
playSound('click');
|
||||
setTimeout(() => { window.location.href = url; }, 300);
|
||||
}
|
||||
|
||||
function newMission() {
|
||||
const txt = document.getElementById('mission-text');
|
||||
txt.innerText = missions[Math.floor(Math.random() * missions.length)];
|
||||
}
|
||||
|
||||
let hubIdleTimer;
|
||||
function resetHubTimer() {
|
||||
clearTimeout(hubIdleTimer);
|
||||
hubIdleTimer = setTimeout(() => {
|
||||
document.getElementById('admin-modal').classList.add('hidden');
|
||||
document.getElementById('info-modal').classList.add('hidden');
|
||||
document.getElementById('qr-modal').classList.remove('modal-visible');
|
||||
document.getElementById('mission-text').innerText = "Wähle eine App! 👉";
|
||||
tapCount = 0;
|
||||
}, 60000);
|
||||
}
|
||||
resetHubTimer();
|
||||
|
||||
function toggleInfo() {
|
||||
resetHubTimer();
|
||||
const m = document.getElementById('info-modal');
|
||||
m.classList.toggle('hidden');
|
||||
m.classList.toggle('flex');
|
||||
}
|
||||
|
||||
function toggleQR() {
|
||||
resetHubTimer();
|
||||
const modal = document.getElementById('qr-modal');
|
||||
modal.classList.toggle('modal-visible');
|
||||
playSound('click');
|
||||
}
|
||||
|
||||
let tapCount = 0; let tapTimer;
|
||||
function triggerAdmin() {
|
||||
resetHubTimer(); tapCount++; clearTimeout(tapTimer); tapTimer = setTimeout(() => { tapCount = 0; }, 1000);
|
||||
if (tapCount >= 5) {
|
||||
tapCount = 0;
|
||||
if (prompt("PIN:") === "1234") {
|
||||
document.getElementById('admin-modal').classList.remove('hidden');
|
||||
document.getElementById('admin-modal').classList.add('flex');
|
||||
}
|
||||
}
|
||||
}
|
||||
function closeAdmin() {
|
||||
document.getElementById('admin-modal').classList.add('hidden');
|
||||
document.getElementById('admin-modal').classList.remove('flex');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
87
public/js/audio.js
Normal file
@@ -0,0 +1,87 @@
|
||||
(function() {
|
||||
console.log("🔊 AUDIO SYSTEM GESTARTET");
|
||||
|
||||
// 1. Pfad bestimmen
|
||||
const isSubApp = window.location.pathname.includes('/apps/');
|
||||
// Wir probieren beide Pfade, falls einer falsch ist
|
||||
const basePath = isSubApp ? '../assets/sounds/' : 'assets/sounds/';
|
||||
|
||||
// 2. Sounds laden mit Fehlerprüfung
|
||||
const soundNames = ['click', 'shutter', 'success'];
|
||||
const audioStore = {};
|
||||
|
||||
soundNames.forEach(name => {
|
||||
const fullPath = basePath + name + '.mp3';
|
||||
const audio = new Audio();
|
||||
|
||||
// Event Listener VOR dem src setzen
|
||||
audio.addEventListener('canplaythrough', () => {
|
||||
console.log(`✅ Sound geladen: ${name} (${fullPath})`);
|
||||
});
|
||||
|
||||
audio.addEventListener('error', (e) => {
|
||||
console.error(`❌ FEHLER bei Sound ${name}:`, e);
|
||||
console.error(`Versuchter Pfad: ${fullPath}`);
|
||||
// Versuch: Absoluter Pfad als Fallback für Android
|
||||
if (!audio.src.startsWith('http') && !audio.src.startsWith('file')) {
|
||||
console.log("Versuche Fallback-Pfad...");
|
||||
}
|
||||
});
|
||||
|
||||
audio.src = fullPath;
|
||||
audio.load();
|
||||
audioStore[name] = audio;
|
||||
});
|
||||
|
||||
audioStore['click'].volume = 0.5;
|
||||
audioStore['shutter'].volume = 1.0;
|
||||
audioStore['success'].volume = 0.8;
|
||||
|
||||
// 3. Globale Play Funktion
|
||||
window.playSound = function(name) {
|
||||
const sound = audioStore[name];
|
||||
if (sound) {
|
||||
console.log(`▶️ Spiele: ${name}`);
|
||||
const clone = sound.cloneNode();
|
||||
clone.volume = sound.volume;
|
||||
|
||||
const promise = clone.play();
|
||||
if (promise !== undefined) {
|
||||
promise.then(() => {
|
||||
console.log(`🔊 ${name} abgespielt!`);
|
||||
}).catch(error => {
|
||||
console.error(`🚫 Autoplay blockiert oder Fehler bei ${name}:`, error);
|
||||
alert("Audio Fehler: " + error.message); // Damit du es am Tablet siehst
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.error(`❓ Unbekannter Sound: ${name}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 4. Klick-Listener an alle Buttons
|
||||
function initButtonSounds() {
|
||||
const buttons = document.querySelectorAll('button, a, .pad-item, .pad-btn');
|
||||
console.log(`Found ${buttons.length} clickable elements.`);
|
||||
|
||||
buttons.forEach(btn => {
|
||||
if(btn.dataset.soundAttached) return;
|
||||
btn.dataset.soundAttached = "true";
|
||||
|
||||
btn.addEventListener('touchstart', () => {
|
||||
const txt = (btn.innerText || "").toUpperCase();
|
||||
if (!txt.includes('FOTO') && !txt.includes('REC') && !txt.includes('DRUCKEN') && !txt.includes('WÜRFELN')) {
|
||||
window.playSound('click');
|
||||
}
|
||||
}, {passive: true});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initButtonSounds);
|
||||
} else {
|
||||
initButtonSounds();
|
||||
}
|
||||
|
||||
window.refreshAudio = initButtonSounds;
|
||||
})();
|
||||
93
public/js/selfie_segmentation.js
Normal file
@@ -0,0 +1,93 @@
|
||||
(function(){/*
|
||||
|
||||
Copyright The Closure Library Authors.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
'use strict';var x;function aa(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}}var ba="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};
|
||||
function ca(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");}var y=ca(this);function z(a,b){if(b)a:{var c=y;a=a.split(".");for(var d=0;d<a.length-1;d++){var e=a[d];if(!(e in c))break a;c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&ba(c,a,{configurable:!0,writable:!0,value:b})}}
|
||||
z("Symbol",function(a){function b(g){if(this instanceof b)throw new TypeError("Symbol is not a constructor");return new c(d+(g||"")+"_"+e++,g)}function c(g,f){this.h=g;ba(this,"description",{configurable:!0,writable:!0,value:f})}if(a)return a;c.prototype.toString=function(){return this.h};var d="jscomp_symbol_"+(1E9*Math.random()>>>0)+"_",e=0;return b});
|
||||
z("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;c<b.length;c++){var d=y[b[c]];"function"===typeof d&&"function"!=typeof d.prototype[a]&&ba(d.prototype,a,{configurable:!0,writable:!0,value:function(){return da(aa(this))}})}return a});function da(a){a={next:a};a[Symbol.iterator]=function(){return this};return a}
|
||||
function A(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):{next:aa(a)}}function ea(a){if(!(a instanceof Array)){a=A(a);for(var b,c=[];!(b=a.next()).done;)c.push(b.value);a=c}return a}var fa="function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(d)for(var e in d)Object.prototype.hasOwnProperty.call(d,e)&&(a[e]=d[e])}return a};z("Object.assign",function(a){return a||fa});
|
||||
var ha="function"==typeof Object.create?Object.create:function(a){function b(){}b.prototype=a;return new b},ia;if("function"==typeof Object.setPrototypeOf)ia=Object.setPrototypeOf;else{var ja;a:{var ka={a:!0},la={};try{la.__proto__=ka;ja=la.a;break a}catch(a){}ja=!1}ia=ja?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null}var ma=ia;
|
||||
function na(a,b){a.prototype=ha(b.prototype);a.prototype.constructor=a;if(ma)ma(a,b);else for(var c in b)if("prototype"!=c)if(Object.defineProperties){var d=Object.getOwnPropertyDescriptor(b,c);d&&Object.defineProperty(a,c,d)}else a[c]=b[c];a.za=b.prototype}function oa(){this.m=!1;this.j=null;this.i=void 0;this.h=1;this.v=this.s=0;this.l=null}function pa(a){if(a.m)throw new TypeError("Generator is already running");a.m=!0}oa.prototype.u=function(a){this.i=a};
|
||||
function qa(a,b){a.l={ma:b,na:!0};a.h=a.s||a.v}oa.prototype.return=function(a){this.l={return:a};this.h=this.v};function D(a,b,c){a.h=c;return{value:b}}function ra(a){this.h=new oa;this.i=a}function sa(a,b){pa(a.h);var c=a.h.j;if(c)return ta(a,"return"in c?c["return"]:function(d){return{value:d,done:!0}},b,a.h.return);a.h.return(b);return ua(a)}
|
||||
function ta(a,b,c,d){try{var e=b.call(a.h.j,c);if(!(e instanceof Object))throw new TypeError("Iterator result "+e+" is not an object");if(!e.done)return a.h.m=!1,e;var g=e.value}catch(f){return a.h.j=null,qa(a.h,f),ua(a)}a.h.j=null;d.call(a.h,g);return ua(a)}function ua(a){for(;a.h.h;)try{var b=a.i(a.h);if(b)return a.h.m=!1,{value:b.value,done:!1}}catch(c){a.h.i=void 0,qa(a.h,c)}a.h.m=!1;if(a.h.l){b=a.h.l;a.h.l=null;if(b.na)throw b.ma;return{value:b.return,done:!0}}return{value:void 0,done:!0}}
|
||||
function va(a){this.next=function(b){pa(a.h);a.h.j?b=ta(a,a.h.j.next,b,a.h.u):(a.h.u(b),b=ua(a));return b};this.throw=function(b){pa(a.h);a.h.j?b=ta(a,a.h.j["throw"],b,a.h.u):(qa(a.h,b),b=ua(a));return b};this.return=function(b){return sa(a,b)};this[Symbol.iterator]=function(){return this}}function wa(a){function b(d){return a.next(d)}function c(d){return a.throw(d)}return new Promise(function(d,e){function g(f){f.done?d(f.value):Promise.resolve(f.value).then(b,c).then(g,e)}g(a.next())})}
|
||||
function E(a){return wa(new va(new ra(a)))}
|
||||
z("Promise",function(a){function b(f){this.i=0;this.j=void 0;this.h=[];this.u=!1;var h=this.l();try{f(h.resolve,h.reject)}catch(k){h.reject(k)}}function c(){this.h=null}function d(f){return f instanceof b?f:new b(function(h){h(f)})}if(a)return a;c.prototype.i=function(f){if(null==this.h){this.h=[];var h=this;this.j(function(){h.m()})}this.h.push(f)};var e=y.setTimeout;c.prototype.j=function(f){e(f,0)};c.prototype.m=function(){for(;this.h&&this.h.length;){var f=this.h;this.h=[];for(var h=0;h<f.length;++h){var k=
|
||||
f[h];f[h]=null;try{k()}catch(l){this.l(l)}}}this.h=null};c.prototype.l=function(f){this.j(function(){throw f;})};b.prototype.l=function(){function f(l){return function(m){k||(k=!0,l.call(h,m))}}var h=this,k=!1;return{resolve:f(this.I),reject:f(this.m)}};b.prototype.I=function(f){if(f===this)this.m(new TypeError("A Promise cannot resolve to itself"));else if(f instanceof b)this.L(f);else{a:switch(typeof f){case "object":var h=null!=f;break a;case "function":h=!0;break a;default:h=!1}h?this.F(f):this.s(f)}};
|
||||
b.prototype.F=function(f){var h=void 0;try{h=f.then}catch(k){this.m(k);return}"function"==typeof h?this.M(h,f):this.s(f)};b.prototype.m=function(f){this.v(2,f)};b.prototype.s=function(f){this.v(1,f)};b.prototype.v=function(f,h){if(0!=this.i)throw Error("Cannot settle("+f+", "+h+"): Promise already settled in state"+this.i);this.i=f;this.j=h;2===this.i&&this.K();this.H()};b.prototype.K=function(){var f=this;e(function(){if(f.D()){var h=y.console;"undefined"!==typeof h&&h.error(f.j)}},1)};b.prototype.D=
|
||||
function(){if(this.u)return!1;var f=y.CustomEvent,h=y.Event,k=y.dispatchEvent;if("undefined"===typeof k)return!0;"function"===typeof f?f=new f("unhandledrejection",{cancelable:!0}):"function"===typeof h?f=new h("unhandledrejection",{cancelable:!0}):(f=y.document.createEvent("CustomEvent"),f.initCustomEvent("unhandledrejection",!1,!0,f));f.promise=this;f.reason=this.j;return k(f)};b.prototype.H=function(){if(null!=this.h){for(var f=0;f<this.h.length;++f)g.i(this.h[f]);this.h=null}};var g=new c;b.prototype.L=
|
||||
function(f){var h=this.l();f.T(h.resolve,h.reject)};b.prototype.M=function(f,h){var k=this.l();try{f.call(h,k.resolve,k.reject)}catch(l){k.reject(l)}};b.prototype.then=function(f,h){function k(p,n){return"function"==typeof p?function(q){try{l(p(q))}catch(t){m(t)}}:n}var l,m,r=new b(function(p,n){l=p;m=n});this.T(k(f,l),k(h,m));return r};b.prototype.catch=function(f){return this.then(void 0,f)};b.prototype.T=function(f,h){function k(){switch(l.i){case 1:f(l.j);break;case 2:h(l.j);break;default:throw Error("Unexpected state: "+
|
||||
l.i);}}var l=this;null==this.h?g.i(k):this.h.push(k);this.u=!0};b.resolve=d;b.reject=function(f){return new b(function(h,k){k(f)})};b.race=function(f){return new b(function(h,k){for(var l=A(f),m=l.next();!m.done;m=l.next())d(m.value).T(h,k)})};b.all=function(f){var h=A(f),k=h.next();return k.done?d([]):new b(function(l,m){function r(q){return function(t){p[q]=t;n--;0==n&&l(p)}}var p=[],n=0;do p.push(void 0),n++,d(k.value).T(r(p.length-1),m),k=h.next();while(!k.done)})};return b});
|
||||
function xa(a,b){a instanceof String&&(a+="");var c=0,d=!1,e={next:function(){if(!d&&c<a.length){var g=c++;return{value:b(g,a[g]),done:!1}}d=!0;return{done:!0,value:void 0}}};e[Symbol.iterator]=function(){return e};return e}z("Array.prototype.keys",function(a){return a?a:function(){return xa(this,function(b){return b})}});
|
||||
z("Array.prototype.fill",function(a){return a?a:function(b,c,d){var e=this.length||0;0>c&&(c=Math.max(0,e+c));if(null==d||d>e)d=e;d=Number(d);0>d&&(d=Math.max(0,e+d));for(c=Number(c||0);c<d;c++)this[c]=b;return this}});function F(a){return a?a:Array.prototype.fill}z("Int8Array.prototype.fill",F);z("Uint8Array.prototype.fill",F);z("Uint8ClampedArray.prototype.fill",F);z("Int16Array.prototype.fill",F);z("Uint16Array.prototype.fill",F);z("Int32Array.prototype.fill",F);
|
||||
z("Uint32Array.prototype.fill",F);z("Float32Array.prototype.fill",F);z("Float64Array.prototype.fill",F);z("Object.is",function(a){return a?a:function(b,c){return b===c?0!==b||1/b===1/c:b!==b&&c!==c}});z("Array.prototype.includes",function(a){return a?a:function(b,c){var d=this;d instanceof String&&(d=String(d));var e=d.length;c=c||0;for(0>c&&(c=Math.max(c+e,0));c<e;c++){var g=d[c];if(g===b||Object.is(g,b))return!0}return!1}});
|
||||
z("String.prototype.includes",function(a){return a?a:function(b,c){if(null==this)throw new TypeError("The 'this' value for String.prototype.includes must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype.includes must not be a regular expression");return-1!==this.indexOf(b,c||0)}});var ya=this||self;
|
||||
function Aa(a,b){a=a.split(".");var c=ya;a[0]in c||"undefined"==typeof c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b};function Ba(a){var b;a:{if(b=ya.navigator)if(b=b.userAgent)break a;b=""}return-1!=b.indexOf(a)};var Ca=Array.prototype.map?function(a,b){return Array.prototype.map.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=Array(c),e="string"===typeof a?a.split(""):a,g=0;g<c;g++)g in e&&(d[g]=b.call(void 0,e[g],g,a));return d};var Da={},Ea=null;function Fa(a){var b=a.length,c=3*b/4;c%3?c=Math.floor(c):-1!="=.".indexOf(a[b-1])&&(c=-1!="=.".indexOf(a[b-2])?c-2:c-1);var d=new Uint8Array(c),e=0;Ga(a,function(g){d[e++]=g});return e!==c?d.subarray(0,e):d}
|
||||
function Ga(a,b){function c(k){for(;d<a.length;){var l=a.charAt(d++),m=Ea[l];if(null!=m)return m;if(!/^[\s\xa0]*$/.test(l))throw Error("Unknown base64 encoding at char: "+l);}return k}Ha();for(var d=0;;){var e=c(-1),g=c(0),f=c(64),h=c(64);if(64===h&&-1===e)break;b(e<<2|g>>4);64!=f&&(b(g<<4&240|f>>2),64!=h&&b(f<<6&192|h))}}
|
||||
function Ha(){if(!Ea){Ea={};for(var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),b=["+/=","+/","-_=","-_.","-_"],c=0;5>c;c++){var d=a.concat(b[c].split(""));Da[c]=d;for(var e=0;e<d.length;e++){var g=d[e];void 0===Ea[g]&&(Ea[g]=e)}}}};var Ia="undefined"!==typeof Uint8Array,Ja=!(Ba("Trident")||Ba("MSIE"))&&"function"===typeof ya.btoa;
|
||||
function Ka(a){if(!Ja){var b;void 0===b&&(b=0);Ha();b=Da[b];for(var c=Array(Math.floor(a.length/3)),d=b[64]||"",e=0,g=0;e<a.length-2;e+=3){var f=a[e],h=a[e+1],k=a[e+2],l=b[f>>2];f=b[(f&3)<<4|h>>4];h=b[(h&15)<<2|k>>6];k=b[k&63];c[g++]=l+f+h+k}l=0;k=d;switch(a.length-e){case 2:l=a[e+1],k=b[(l&15)<<2]||d;case 1:a=a[e],c[g]=b[a>>2]+b[(a&3)<<4|l>>4]+k+d}return c.join("")}for(b="";10240<a.length;)b+=String.fromCharCode.apply(null,a.subarray(0,10240)),a=a.subarray(10240);b+=String.fromCharCode.apply(null,
|
||||
a);return btoa(b)}var La=RegExp("[-_.]","g");function Ma(a){switch(a){case "-":return"+";case "_":return"/";case ".":return"=";default:return""}}function Na(a){if(!Ja)return Fa(a);La.test(a)&&(a=a.replace(La,Ma));a=atob(a);for(var b=new Uint8Array(a.length),c=0;c<a.length;c++)b[c]=a.charCodeAt(c);return b}var Oa;function Pa(){return Oa||(Oa=new Uint8Array(0))}var Qa={};var Ra="function"===typeof Uint8Array.prototype.slice,G=0,H=0;function Sa(a){var b=0>a;a=Math.abs(a);var c=a>>>0;a=Math.floor((a-c)/4294967296);b&&(c=A(Ta(c,a)),b=c.next().value,a=c.next().value,c=b);G=c>>>0;H=a>>>0}var Ua="function"===typeof BigInt;function Ta(a,b){b=~b;a?a=~a+1:b+=1;return[a,b]};function Va(a,b){this.i=a>>>0;this.h=b>>>0}
|
||||
function Wa(a){if(!a)return Xa||(Xa=new Va(0,0));if(!/^-?\d+$/.test(a))return null;if(16>a.length)Sa(Number(a));else if(Ua)a=BigInt(a),G=Number(a&BigInt(4294967295))>>>0,H=Number(a>>BigInt(32)&BigInt(4294967295));else{var b=+("-"===a[0]);H=G=0;for(var c=a.length,d=b,e=(c-b)%6+b;e<=c;d=e,e+=6)d=Number(a.slice(d,e)),H*=1E6,G=1E6*G+d,4294967296<=G&&(H+=G/4294967296|0,G%=4294967296);b&&(b=A(Ta(G,H)),a=b.next().value,b=b.next().value,G=a,H=b)}return new Va(G,H)}var Xa;function Ya(a,b){return Error("Invalid wire type: "+a+" (at position "+b+")")}function Za(){return Error("Failed to read varint, encoding is invalid.")}function $a(a,b){return Error("Tried to read past the end of the data "+b+" > "+a)};function K(){throw Error("Invalid UTF8");}function ab(a,b){b=String.fromCharCode.apply(null,b);return null==a?b:a+b}var bb=void 0,cb,db="undefined"!==typeof TextDecoder,eb,fb="undefined"!==typeof TextEncoder;var gb;function hb(a){if(a!==Qa)throw Error("illegal external caller");}function ib(a,b){hb(b);this.V=a;if(null!=a&&0===a.length)throw Error("ByteString should be constructed with non-empty values");}function jb(){return gb||(gb=new ib(null,Qa))}function kb(a){hb(Qa);var b=a.V;b=null==b||Ia&&null!=b&&b instanceof Uint8Array?b:"string"===typeof b?Na(b):null;return null==b?b:a.V=b};function lb(a){if("string"===typeof a)return{buffer:Na(a),C:!1};if(Array.isArray(a))return{buffer:new Uint8Array(a),C:!1};if(a.constructor===Uint8Array)return{buffer:a,C:!1};if(a.constructor===ArrayBuffer)return{buffer:new Uint8Array(a),C:!1};if(a.constructor===ib)return{buffer:kb(a)||Pa(),C:!0};if(a instanceof Uint8Array)return{buffer:new Uint8Array(a.buffer,a.byteOffset,a.byteLength),C:!1};throw Error("Type not convertible to a Uint8Array, expected a Uint8Array, an ArrayBuffer, a base64 encoded string, a ByteString or an Array of numbers");
|
||||
};function mb(a,b){this.i=null;this.m=!1;this.h=this.j=this.l=0;nb(this,a,b)}function nb(a,b,c){c=void 0===c?{}:c;a.S=void 0===c.S?!1:c.S;b&&(b=lb(b),a.i=b.buffer,a.m=b.C,a.l=0,a.j=a.i.length,a.h=a.l)}mb.prototype.reset=function(){this.h=this.l};function L(a,b){a.h=b;if(b>a.j)throw $a(a.j,b);}
|
||||
function ob(a){var b=a.i,c=a.h,d=b[c++],e=d&127;if(d&128&&(d=b[c++],e|=(d&127)<<7,d&128&&(d=b[c++],e|=(d&127)<<14,d&128&&(d=b[c++],e|=(d&127)<<21,d&128&&(d=b[c++],e|=d<<28,d&128&&b[c++]&128&&b[c++]&128&&b[c++]&128&&b[c++]&128&&b[c++]&128)))))throw Za();L(a,c);return e}function pb(a,b){if(0>b)throw Error("Tried to read a negative byte length: "+b);var c=a.h,d=c+b;if(d>a.j)throw $a(b,a.j-c);a.h=d;return c}var qb=[];function rb(){this.h=[]}rb.prototype.length=function(){return this.h.length};rb.prototype.end=function(){var a=this.h;this.h=[];return a};function sb(a,b,c){for(;0<c||127<b;)a.h.push(b&127|128),b=(b>>>7|c<<25)>>>0,c>>>=7;a.h.push(b)}function M(a,b){for(;127<b;)a.h.push(b&127|128),b>>>=7;a.h.push(b)};function tb(a,b){if(qb.length){var c=qb.pop();nb(c,a,b);a=c}else a=new mb(a,b);this.h=a;this.j=this.h.h;this.i=this.l=-1;this.setOptions(b)}tb.prototype.setOptions=function(a){a=void 0===a?{}:a;this.ca=void 0===a.ca?!1:a.ca};tb.prototype.reset=function(){this.h.reset();this.j=this.h.h;this.i=this.l=-1};
|
||||
function ub(a){var b=a.h;if(b.h==b.j)return!1;a.j=a.h.h;var c=ob(a.h)>>>0;b=c>>>3;c&=7;if(!(0<=c&&5>=c))throw Ya(c,a.j);if(1>b)throw Error("Invalid field number: "+b+" (at position "+a.j+")");a.l=b;a.i=c;return!0}
|
||||
function vb(a){switch(a.i){case 0:if(0!=a.i)vb(a);else a:{a=a.h;for(var b=a.h,c=b+10,d=a.i;b<c;)if(0===(d[b++]&128)){L(a,b);break a}throw Za();}break;case 1:a=a.h;L(a,a.h+8);break;case 2:2!=a.i?vb(a):(b=ob(a.h)>>>0,a=a.h,L(a,a.h+b));break;case 5:a=a.h;L(a,a.h+4);break;case 3:b=a.l;do{if(!ub(a))throw Error("Unmatched start-group tag: stream EOF");if(4==a.i){if(a.l!=b)throw Error("Unmatched end-group tag");break}vb(a)}while(1);break;default:throw Ya(a.i,a.j);}}var wb=[];function xb(){this.j=[];this.i=0;this.h=new rb}function N(a,b){0!==b.length&&(a.j.push(b),a.i+=b.length)}function yb(a,b){if(b=b.R){N(a,a.h.end());for(var c=0;c<b.length;c++)N(a,kb(b[c])||Pa())}};var O="function"===typeof Symbol&&"symbol"===typeof Symbol()?Symbol():void 0;function P(a,b){if(O)return a[O]|=b;if(void 0!==a.A)return a.A|=b;Object.defineProperties(a,{A:{value:b,configurable:!0,writable:!0,enumerable:!1}});return b}function zb(a,b){O?a[O]&&(a[O]&=~b):void 0!==a.A&&(a.A&=~b)}function Q(a){var b;O?b=a[O]:b=a.A;return null==b?0:b}function R(a,b){O?a[O]=b:void 0!==a.A?a.A=b:Object.defineProperties(a,{A:{value:b,configurable:!0,writable:!0,enumerable:!1}})}
|
||||
function Ab(a){P(a,1);return a}function Bb(a,b){R(b,(a|0)&-51)}function Cb(a,b){R(b,(a|18)&-41)};var Db={};function Eb(a){return null!==a&&"object"===typeof a&&!Array.isArray(a)&&a.constructor===Object}var Fb,Gb=[];R(Gb,23);Fb=Object.freeze(Gb);function Hb(a){if(Q(a.o)&2)throw Error("Cannot mutate an immutable Message");}function Ib(a){var b=a.length;(b=b?a[b-1]:void 0)&&Eb(b)?b.g=1:(b={},a.push((b.g=1,b)))};function Jb(a){var b=a.i+a.G;return a.B||(a.B=a.o[b]={})}function S(a,b){return-1===b?null:b>=a.i?a.B?a.B[b]:void 0:a.o[b+a.G]}function U(a,b,c,d){Hb(a);Kb(a,b,c,d)}function Kb(a,b,c,d){a.j&&(a.j=void 0);b>=a.i||d?Jb(a)[b]=c:(a.o[b+a.G]=c,(a=a.B)&&b in a&&delete a[b])}function Lb(a,b,c,d){var e=S(a,b);Array.isArray(e)||(e=Fb);var g=Q(e);g&1||Ab(e);if(d)g&2||P(e,2),c&1||Object.freeze(e);else{d=!(c&2);var f=g&2;c&1||!f?d&&g&16&&!f&&zb(e,16):(e=Ab(Array.prototype.slice.call(e)),Kb(a,b,e))}return e}
|
||||
function Mb(a,b){var c=S(a,b);var d=null==c?c:"number"===typeof c||"NaN"===c||"Infinity"===c||"-Infinity"===c?Number(c):void 0;null!=d&&d!==c&&Kb(a,b,d);return d}
|
||||
function Nb(a,b,c,d,e){a.h||(a.h={});var g=a.h[c],f=Lb(a,c,3,e);if(!g){var h=f;g=[];var k=!!(Q(a.o)&16);f=!!(Q(h)&2);var l=h;!e&&f&&(h=Array.prototype.slice.call(h));for(var m=f,r=0;r<h.length;r++){var p=h[r];var n=b,q=!1;q=void 0===q?!1:q;p=Array.isArray(p)?new n(p):q?new n:void 0;if(void 0!==p){n=p.o;var t=q=Q(n);f&&(t|=2);k&&(t|=16);t!=q&&R(n,t);n=t;m=m||!!(2&n);g.push(p)}}a.h[c]=g;k=Q(h);b=k|33;b=m?b&-9:b|8;k!=b&&(m=h,Object.isFrozen(m)&&(m=Array.prototype.slice.call(m)),R(m,b),h=m);l!==h&&Kb(a,
|
||||
c,h);(e||d&&f)&&P(g,2);d&&Object.freeze(g);return g}e||(e=Object.isFrozen(g),d&&!e?Object.freeze(g):!d&&e&&(g=Array.prototype.slice.call(g),a.h[c]=g));return g}function Ob(a,b,c){var d=!!(Q(a.o)&2);b=Nb(a,b,c,d,d);a=Lb(a,c,3,d);if(!(d||Q(a)&8)){for(d=0;d<b.length;d++){c=b[d];if(Q(c.o)&2){var e=Pb(c,!1);e.j=c}else e=c;c!==e&&(b[d]=e,a[d]=e.o)}P(a,8)}return b}
|
||||
function V(a,b,c){if(null!=c&&"number"!==typeof c)throw Error("Value of float/double field must be a number|null|undefined, found "+typeof c+": "+c);U(a,b,c)}function Qb(a,b,c,d,e){Hb(a);var g=Nb(a,c,b,!1,!1);c=null!=d?d:new c;a=Lb(a,b,2,!1);void 0!=e?(g.splice(e,0,c),a.splice(e,0,c.o)):(g.push(c),a.push(c.o));c.C()&&zb(a,8);return c}function Rb(a,b){return null==a?b:a}function W(a,b,c){c=void 0===c?0:c;return Rb(Mb(a,b),c)};var Sb;function Tb(a){switch(typeof a){case "number":return isFinite(a)?a:String(a);case "object":if(a)if(Array.isArray(a)){if(0!==(Q(a)&128))return a=Array.prototype.slice.call(a),Ib(a),a}else{if(Ia&&null!=a&&a instanceof Uint8Array)return Ka(a);if(a instanceof ib){var b=a.V;return null==b?"":"string"===typeof b?b:a.V=Ka(b)}}}return a};function Ub(a,b,c,d){if(null!=a){if(Array.isArray(a))a=Vb(a,b,c,void 0!==d);else if(Eb(a)){var e={},g;for(g in a)e[g]=Ub(a[g],b,c,d);a=e}else a=b(a,d);return a}}function Vb(a,b,c,d){var e=Q(a);d=d?!!(e&16):void 0;a=Array.prototype.slice.call(a);for(var g=0;g<a.length;g++)a[g]=Ub(a[g],b,c,d);c(e,a);return a}function Wb(a){return a.ja===Db?a.toJSON():Tb(a)}function Xb(a,b){a&128&&Ib(b)};function Yb(a,b,c){c=void 0===c?Cb:c;if(null!=a){if(Ia&&a instanceof Uint8Array)return a.length?new ib(new Uint8Array(a),Qa):jb();if(Array.isArray(a)){var d=Q(a);if(d&2)return a;if(b&&!(d&32)&&(d&16||0===d))return R(a,d|2),a;a=Vb(a,Yb,d&4?Cb:c,!0);b=Q(a);b&4&&b&2&&Object.freeze(a);return a}return a.ja===Db?Zb(a):a}}
|
||||
function $b(a,b,c,d,e,g,f){if(a=a.h&&a.h[c]){d=Q(a);d&2?d=a:(g=Ca(a,Zb),Cb(d,g),Object.freeze(g),d=g);Hb(b);f=null==d?Fb:Ab([]);if(null!=d){g=!!d.length;for(a=0;a<d.length;a++){var h=d[a];g=g&&!(Q(h.o)&2);f[a]=h.o}g=(g?8:0)|1;a=Q(f);(a&g)!==g&&(Object.isFrozen(f)&&(f=Array.prototype.slice.call(f)),R(f,a|g));b.h||(b.h={});b.h[c]=d}else b.h&&(b.h[c]=void 0);Kb(b,c,f,e)}else U(b,c,Yb(d,g,f),e)}function Zb(a){if(Q(a.o)&2)return a;a=Pb(a,!0);P(a.o,2);return a}
|
||||
function Pb(a,b){var c=a.o,d=[];P(d,16);var e=a.constructor.h;e&&d.push(e);e=a.B;if(e){d.length=c.length;d.fill(void 0,d.length,c.length);var g={};d[d.length-1]=g}0!==(Q(c)&128)&&Ib(d);b=b||a.C()?Cb:Bb;g=a.constructor;Sb=d;d=new g(d);Sb=void 0;a.R&&(d.R=a.R.slice());g=!!(Q(c)&16);for(var f=e?c.length-1:c.length,h=0;h<f;h++)$b(a,d,h-a.G,c[h],!1,g,b);if(e)for(var k in e)$b(a,d,+k,e[k],!0,g,b);return d};function X(a,b,c){null==a&&(a=Sb);Sb=void 0;var d=this.constructor.i||0,e=0<d,g=this.constructor.h,f=!1;if(null==a){a=g?[g]:[];var h=48;var k=!0;e&&(d=0,h|=128);R(a,h)}else{if(!Array.isArray(a))throw Error();if(g&&g!==a[0])throw Error();var l=h=P(a,0);if(k=0!==(16&l))(f=0!==(32&l))||(l|=32);if(e)if(128&l)d=0;else{if(0<a.length){var m=a[a.length-1];if(Eb(m)&&"g"in m){d=0;l|=128;delete m.g;var r=!0,p;for(p in m){r=!1;break}r&&a.pop()}}}else if(128&l)throw Error();h!==l&&R(a,l)}this.G=(g?0:-1)-d;this.h=
|
||||
void 0;this.o=a;a:{g=this.o.length;d=g-1;if(g&&(g=this.o[d],Eb(g))){this.B=g;this.i=d-this.G;break a}void 0!==b&&-1<b?(this.i=Math.max(b,d+1-this.G),this.B=void 0):this.i=Number.MAX_VALUE}if(!e&&this.B&&"g"in this.B)throw Error('Unexpected "g" flag in sparse object of message that is not a group type.');if(c){b=k&&!f&&!0;e=this.i;var n;for(k=0;k<c.length;k++)f=c[k],f<e?(f+=this.G,(d=a[f])?ac(d,b):a[f]=Fb):(n||(n=Jb(this)),(d=n[f])?ac(d,b):n[f]=Fb)}}
|
||||
X.prototype.toJSON=function(){return Vb(this.o,Wb,Xb)};X.prototype.C=function(){return!!(Q(this.o)&2)};function ac(a,b){if(Array.isArray(a)){var c=Q(a),d=1;!b||c&2||(d|=16);(c&d)!==d&&R(a,c|d)}}X.prototype.ja=Db;X.prototype.toString=function(){return this.o.toString()};function bc(a,b,c){if(c){var d={},e;for(e in c){var g=c[e],f=g.ra;f||(d.J=g.xa||g.oa.W,g.ia?(d.aa=cc(g.ia),f=function(h){return function(k,l,m){return h.J(k,l,m,h.aa)}}(d)):g.ka?(d.Z=dc(g.da.P,g.ka),f=function(h){return function(k,l,m){return h.J(k,l,m,h.Z)}}(d)):f=d.J,g.ra=f);f(b,a,g.da);d={J:d.J,aa:d.aa,Z:d.Z}}}yb(b,a)}var ec=Symbol();function fc(a,b,c){return a[ec]||(a[ec]=function(d,e){return b(d,e,c)})}
|
||||
function gc(a){var b=a[ec];if(!b){var c=hc(a);b=function(d,e){return ic(d,e,c)};a[ec]=b}return b}function jc(a){var b=a.ia;if(b)return gc(b);if(b=a.wa)return fc(a.da.P,b,a.ka)}function kc(a){var b=jc(a),c=a.da,d=a.oa.U;return b?function(e,g){return d(e,g,c,b)}:function(e,g){return d(e,g,c)}}function lc(a,b){var c=a[b];"function"==typeof c&&0===c.length&&(c=c(),a[b]=c);return Array.isArray(c)&&(mc in c||nc in c||0<c.length&&"function"==typeof c[0])?c:void 0}
|
||||
function oc(a,b,c,d,e,g){b.P=a[0];var f=1;if(a.length>f&&"number"!==typeof a[f]){var h=a[f++];c(b,h)}for(;f<a.length;){c=a[f++];for(var k=f+1;k<a.length&&"number"!==typeof a[k];)k++;h=a[f++];k-=f;switch(k){case 0:d(b,c,h);break;case 1:(k=lc(a,f))?(f++,e(b,c,h,k)):d(b,c,h,a[f++]);break;case 2:k=f++;k=lc(a,k);e(b,c,h,k,a[f++]);break;case 3:g(b,c,h,a[f++],a[f++],a[f++]);break;case 4:g(b,c,h,a[f++],a[f++],a[f++],a[f++]);break;default:throw Error("unexpected number of binary field arguments: "+k);}}return b}
|
||||
var pc=Symbol();function cc(a){var b=a[pc];if(!b){var c=qc(a);b=function(d,e){return rc(d,e,c)};a[pc]=b}return b}function dc(a,b){var c=a[pc];c||(c=function(d,e){return bc(d,e,b)},a[pc]=c);return c}var nc=Symbol();function sc(a,b){a.push(b)}function tc(a,b,c){a.push(b,c.W)}function uc(a,b,c,d){var e=cc(d),g=qc(d).P,f=c.W;a.push(b,function(h,k,l){return f(h,k,l,g,e)})}function vc(a,b,c,d,e,g){var f=dc(d,g),h=c.W;a.push(b,function(k,l,m){return h(k,l,m,d,f)})}
|
||||
function qc(a){var b=a[nc];if(b)return b;b=oc(a,a[nc]=[],sc,tc,uc,vc);mc in a&&nc in a&&(a.length=0);return b}var mc=Symbol();function wc(a,b){a[0]=b}function xc(a,b,c,d){var e=c.U;a[b]=d?function(g,f,h){return e(g,f,h,d)}:e}function yc(a,b,c,d,e){var g=c.U,f=gc(d),h=hc(d).P;a[b]=function(k,l,m){return g(k,l,m,h,f,e)}}function zc(a,b,c,d,e,g,f){var h=c.U,k=fc(d,e,g);a[b]=function(l,m,r){return h(l,m,r,d,k,f)}}
|
||||
function hc(a){var b=a[mc];if(b)return b;b=oc(a,a[mc]={},wc,xc,yc,zc);mc in a&&nc in a&&(a.length=0);return b}
|
||||
function ic(a,b,c){for(;ub(b)&&4!=b.i;){var d=b.l,e=c[d];if(!e){var g=c[0];g&&(g=g[d])&&(e=c[d]=kc(g))}if(!e||!e(b,a,d)){e=b;d=a;g=e.j;vb(e);var f=e;if(!f.ca){e=f.h.h-g;f.h.h=g;f=f.h;if(0==e)e=jb();else{g=pb(f,e);if(f.S&&f.m)e=f.i.subarray(g,g+e);else{f=f.i;var h=g;e=g+e;e=h===e?Pa():Ra?f.slice(h,e):new Uint8Array(f.subarray(h,e))}e=0==e.length?jb():new ib(e,Qa)}(g=d.R)?g.push(e):d.R=[e]}}}return a}
|
||||
function rc(a,b,c){for(var d=c.length,e=1==d%2,g=e?1:0;g<d;g+=2)(0,c[g+1])(b,a,c[g]);bc(a,b,e?c[0]:void 0)}function Ac(a,b){return{U:a,W:b}}
|
||||
var Y=Ac(function(a,b,c){if(5!==a.i)return!1;a=a.h;var d=a.i,e=a.h,g=d[e];var f=d[e+1];var h=d[e+2];d=d[e+3];L(a,a.h+4);f=(g<<0|f<<8|h<<16|d<<24)>>>0;a=2*(f>>31)+1;g=f>>>23&255;f&=8388607;U(b,c,255==g?f?NaN:Infinity*a:0==g?a*Math.pow(2,-149)*f:a*Math.pow(2,g-150)*(f+Math.pow(2,23)));return!0},function(a,b,c){b=Mb(b,c);if(null!=b){M(a.h,8*c+5);a=a.h;var d=+b;0===d?0<1/d?G=H=0:(H=0,G=2147483648):isNaN(d)?(H=0,G=2147483647):(d=(c=0>d?-2147483648:0)?-d:d,3.4028234663852886E38<d?(H=0,G=(c|2139095040)>>>
|
||||
0):1.1754943508222875E-38>d?(d=Math.round(d/Math.pow(2,-149)),H=0,G=(c|d)>>>0):(b=Math.floor(Math.log(d)/Math.LN2),d*=Math.pow(2,-b),d=Math.round(8388608*d),16777216<=d&&++b,H=0,G=(c|b+127<<23|d&8388607)>>>0));c=G;a.h.push(c>>>0&255);a.h.push(c>>>8&255);a.h.push(c>>>16&255);a.h.push(c>>>24&255)}}),Bc=Ac(function(a,b,c){if(0!==a.i)return!1;var d=a.h,e=0,g=a=0,f=d.i,h=d.h;do{var k=f[h++];e|=(k&127)<<g;g+=7}while(32>g&&k&128);32<g&&(a|=(k&127)>>4);for(g=3;32>g&&k&128;g+=7)k=f[h++],a|=(k&127)<<g;L(d,
|
||||
h);if(128>k){d=e>>>0;k=a>>>0;if(a=k&2147483648)d=~d+1>>>0,k=~k>>>0,0==d&&(k=k+1>>>0);d=4294967296*k+(d>>>0)}else throw Za();U(b,c,a?-d:d);return!0},function(a,b,c){b=S(b,c);null!=b&&("string"===typeof b&&Wa(b),null!=b&&(M(a.h,8*c),"number"===typeof b?(a=a.h,Sa(b),sb(a,G,H)):(c=Wa(b),sb(a.h,c.i,c.h))))}),Cc=Ac(function(a,b,c){if(0!==a.i)return!1;U(b,c,ob(a.h));return!0},function(a,b,c){b=S(b,c);if(null!=b&&null!=b)if(M(a.h,8*c),a=a.h,c=b,0<=c)M(a,c);else{for(b=0;9>b;b++)a.h.push(c&127|128),c>>=7;a.h.push(1)}}),
|
||||
Dc=Ac(function(a,b,c){if(2!==a.i)return!1;var d=ob(a.h)>>>0;a=a.h;var e=pb(a,d);a=a.i;if(db){var g=a,f;(f=cb)||(f=cb=new TextDecoder("utf-8",{fatal:!0}));a=e+d;g=0===e&&a===g.length?g:g.subarray(e,a);try{var h=f.decode(g)}catch(r){if(void 0===bb){try{f.decode(new Uint8Array([128]))}catch(p){}try{f.decode(new Uint8Array([97])),bb=!0}catch(p){bb=!1}}!bb&&(cb=void 0);throw r;}}else{h=e;d=h+d;e=[];for(var k=null,l,m;h<d;)l=a[h++],128>l?e.push(l):224>l?h>=d?K():(m=a[h++],194>l||128!==(m&192)?(h--,K()):
|
||||
e.push((l&31)<<6|m&63)):240>l?h>=d-1?K():(m=a[h++],128!==(m&192)||224===l&&160>m||237===l&&160<=m||128!==((g=a[h++])&192)?(h--,K()):e.push((l&15)<<12|(m&63)<<6|g&63)):244>=l?h>=d-2?K():(m=a[h++],128!==(m&192)||0!==(l<<28)+(m-144)>>30||128!==((g=a[h++])&192)||128!==((f=a[h++])&192)?(h--,K()):(l=(l&7)<<18|(m&63)<<12|(g&63)<<6|f&63,l-=65536,e.push((l>>10&1023)+55296,(l&1023)+56320))):K(),8192<=e.length&&(k=ab(k,e),e.length=0);h=ab(k,e)}U(b,c,h);return!0},function(a,b,c){b=S(b,c);if(null!=b){var d=!1;
|
||||
d=void 0===d?!1:d;if(fb){if(d&&/(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(b))throw Error("Found an unpaired surrogate");b=(eb||(eb=new TextEncoder)).encode(b)}else{for(var e=0,g=new Uint8Array(3*b.length),f=0;f<b.length;f++){var h=b.charCodeAt(f);if(128>h)g[e++]=h;else{if(2048>h)g[e++]=h>>6|192;else{if(55296<=h&&57343>=h){if(56319>=h&&f<b.length){var k=b.charCodeAt(++f);if(56320<=k&&57343>=k){h=1024*(h-55296)+k-56320+65536;g[e++]=h>>18|240;g[e++]=h>>12&63|128;
|
||||
g[e++]=h>>6&63|128;g[e++]=h&63|128;continue}else f--}if(d)throw Error("Found an unpaired surrogate");h=65533}g[e++]=h>>12|224;g[e++]=h>>6&63|128}g[e++]=h&63|128}}b=e===g.length?g:g.subarray(0,e)}M(a.h,8*c+2);M(a.h,b.length);N(a,a.h.end());N(a,b)}}),Ec=Ac(function(a,b,c,d,e){if(2!==a.i)return!1;b=Qb(b,c,d);c=a.h.j;d=ob(a.h)>>>0;var g=a.h.h+d,f=g-c;0>=f&&(a.h.j=g,e(b,a,void 0,void 0,void 0),f=g-a.h.h);if(f)throw Error("Message parsing ended unexpectedly. Expected to read "+(d+" bytes, instead read "+
|
||||
(d-f)+" bytes, either the data ended unexpectedly or the message misreported its own length"));a.h.h=g;a.h.j=c;return!0},function(a,b,c,d,e){b=Ob(b,d,c);if(null!=b)for(d=0;d<b.length;d++){var g=a;M(g.h,8*c+2);var f=g.h.end();N(g,f);f.push(g.i);g=f;e(b[d],a);f=a;var h=g.pop();for(h=f.i+f.h.length()-h;127<h;)g.push(h&127|128),h>>>=7,f.i++;g.push(h);f.i++}});function Fc(a){return function(b,c){a:{if(wb.length){var d=wb.pop();d.setOptions(c);nb(d.h,b,c);b=d}else b=new tb(b,c);try{var e=hc(a);var g=ic(new e.P,b,e);break a}finally{e=b.h,e.i=null,e.m=!1,e.l=0,e.j=0,e.h=0,e.S=!1,b.l=-1,b.i=-1,100>wb.length&&wb.push(b)}g=void 0}return g}}function Gc(a){return function(){var b=new xb;rc(this,b,qc(a));N(b,b.h.end());for(var c=new Uint8Array(b.i),d=b.j,e=d.length,g=0,f=0;f<e;f++){var h=d[f];c.set(h,g);g+=h.length}b.j=[c];return c}};function Z(a){X.call(this,a)}na(Z,X);var Hc=[Z,1,Cc,2,Y,3,Dc,4,Dc];Z.prototype.l=Gc(Hc);function Ic(a){X.call(this,a,-1,Jc)}na(Ic,X);Ic.prototype.addClassification=function(a,b){Qb(this,1,Z,a,b);return this};var Jc=[1],Kc=Fc([Ic,1,Ec,Hc]);function Lc(a){X.call(this,a)}na(Lc,X);var Mc=[Lc,1,Y,2,Y,3,Y,4,Y,5,Y];Lc.prototype.l=Gc(Mc);function Nc(a){X.call(this,a,-1,Oc)}na(Nc,X);var Oc=[1],Pc=Fc([Nc,1,Ec,Mc]);function Qc(a){X.call(this,a)}na(Qc,X);var Rc=[Qc,1,Y,2,Y,3,Y,4,Y,5,Y,6,Bc],Sc=Fc(Rc);Qc.prototype.l=Gc(Rc);function Tc(a,b,c){c=a.createShader(0===c?a.VERTEX_SHADER:a.FRAGMENT_SHADER);a.shaderSource(c,b);a.compileShader(c);if(!a.getShaderParameter(c,a.COMPILE_STATUS))throw Error("Could not compile WebGL shader.\n\n"+a.getShaderInfoLog(c));return c};function Uc(a){return Ob(a,Z,1).map(function(b){var c=S(b,1);return{index:null==c?0:c,qa:W(b,2),label:null!=S(b,3)?Rb(S(b,3),""):void 0,displayName:null!=S(b,4)?Rb(S(b,4),""):void 0}})};function Vc(a){return{x:W(a,1),y:W(a,2),z:W(a,3),visibility:null!=Mb(a,4)?W(a,4):void 0}};function Wc(a,b){this.i=a;this.h=b;this.m=0}
|
||||
function Xc(a,b,c){Yc(a,b);if("function"===typeof a.h.canvas.transferToImageBitmap)return Promise.resolve(a.h.canvas.transferToImageBitmap());if(c)return Promise.resolve(a.h.canvas);if("function"===typeof createImageBitmap)return createImageBitmap(a.h.canvas);void 0===a.j&&(a.j=document.createElement("canvas"));return new Promise(function(d){a.j.height=a.h.canvas.height;a.j.width=a.h.canvas.width;a.j.getContext("2d",{}).drawImage(a.h.canvas,0,0,a.h.canvas.width,a.h.canvas.height);d(a.j)})}
|
||||
function Yc(a,b){var c=a.h;if(void 0===a.s){var d=Tc(c,"\n attribute vec2 aVertex;\n attribute vec2 aTex;\n varying vec2 vTex;\n void main(void) {\n gl_Position = vec4(aVertex, 0.0, 1.0);\n vTex = aTex;\n }",0),e=Tc(c,"\n precision mediump float;\n varying vec2 vTex;\n uniform sampler2D sampler0;\n void main(){\n gl_FragColor = texture2D(sampler0, vTex);\n }",1),g=c.createProgram();c.attachShader(g,d);c.attachShader(g,e);c.linkProgram(g);if(!c.getProgramParameter(g,c.LINK_STATUS))throw Error("Could not compile WebGL program.\n\n"+
|
||||
c.getProgramInfoLog(g));d=a.s=g;c.useProgram(d);e=c.getUniformLocation(d,"sampler0");a.l={O:c.getAttribLocation(d,"aVertex"),N:c.getAttribLocation(d,"aTex"),ya:e};a.v=c.createBuffer();c.bindBuffer(c.ARRAY_BUFFER,a.v);c.enableVertexAttribArray(a.l.O);c.vertexAttribPointer(a.l.O,2,c.FLOAT,!1,0,0);c.bufferData(c.ARRAY_BUFFER,new Float32Array([-1,-1,-1,1,1,1,1,-1]),c.STATIC_DRAW);c.bindBuffer(c.ARRAY_BUFFER,null);a.u=c.createBuffer();c.bindBuffer(c.ARRAY_BUFFER,a.u);c.enableVertexAttribArray(a.l.N);c.vertexAttribPointer(a.l.N,
|
||||
2,c.FLOAT,!1,0,0);c.bufferData(c.ARRAY_BUFFER,new Float32Array([0,1,0,0,1,0,1,1]),c.STATIC_DRAW);c.bindBuffer(c.ARRAY_BUFFER,null);c.uniform1i(e,0)}d=a.l;c.useProgram(a.s);c.canvas.width=b.width;c.canvas.height=b.height;c.viewport(0,0,b.width,b.height);c.activeTexture(c.TEXTURE0);a.i.bindTexture2d(b.glName);c.enableVertexAttribArray(d.O);c.bindBuffer(c.ARRAY_BUFFER,a.v);c.vertexAttribPointer(d.O,2,c.FLOAT,!1,0,0);c.enableVertexAttribArray(d.N);c.bindBuffer(c.ARRAY_BUFFER,a.u);c.vertexAttribPointer(d.N,
|
||||
2,c.FLOAT,!1,0,0);c.bindFramebuffer(c.DRAW_FRAMEBUFFER?c.DRAW_FRAMEBUFFER:c.FRAMEBUFFER,null);c.clearColor(0,0,0,0);c.clear(c.COLOR_BUFFER_BIT);c.colorMask(!0,!0,!0,!0);c.drawArrays(c.TRIANGLE_FAN,0,4);c.disableVertexAttribArray(d.O);c.disableVertexAttribArray(d.N);c.bindBuffer(c.ARRAY_BUFFER,null);a.i.bindTexture2d(0)}function Zc(a){this.h=a};var $c=new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,9,1,7,0,65,0,253,15,26,11]);function ad(a,b){return b+a}function bd(a,b){window[a]=b}function cd(a){var b=document.createElement("script");b.setAttribute("src",a);b.setAttribute("crossorigin","anonymous");return new Promise(function(c){b.addEventListener("load",function(){c()},!1);b.addEventListener("error",function(){c()},!1);document.body.appendChild(b)})}
|
||||
function dd(){return E(function(a){switch(a.h){case 1:return a.s=2,D(a,WebAssembly.instantiate($c),4);case 4:a.h=3;a.s=0;break;case 2:return a.s=0,a.l=null,a.return(!1);case 3:return a.return(!0)}})}
|
||||
function ed(a){this.h=a;this.listeners={};this.l={};this.L={};this.s={};this.v={};this.M=this.u=this.ga=!0;this.I=Promise.resolve();this.fa="";this.D={};this.locateFile=a&&a.locateFile||ad;if("object"===typeof window)var b=window.location.pathname.toString().substring(0,window.location.pathname.toString().lastIndexOf("/"))+"/";else if("undefined"!==typeof location)b=location.pathname.toString().substring(0,location.pathname.toString().lastIndexOf("/"))+"/";else throw Error("solutions can only be loaded on a web page or in a web worker");
|
||||
this.ha=b;if(a.options){b=A(Object.keys(a.options));for(var c=b.next();!c.done;c=b.next()){c=c.value;var d=a.options[c].default;void 0!==d&&(this.l[c]="function"===typeof d?d():d)}}}x=ed.prototype;x.close=function(){this.j&&this.j.delete();return Promise.resolve()};
|
||||
function fd(a){var b,c,d,e,g,f,h,k,l,m,r;return E(function(p){switch(p.h){case 1:if(!a.ga)return p.return();b=void 0===a.h.files?[]:"function"===typeof a.h.files?a.h.files(a.l):a.h.files;return D(p,dd(),2);case 2:c=p.i;if("object"===typeof window)return bd("createMediapipeSolutionsWasm",{locateFile:a.locateFile}),bd("createMediapipeSolutionsPackedAssets",{locateFile:a.locateFile}),f=b.filter(function(n){return void 0!==n.data}),h=b.filter(function(n){return void 0===n.data}),k=Promise.all(f.map(function(n){var q=
|
||||
gd(a,n.url);if(void 0!==n.path){var t=n.path;q=q.then(function(w){a.overrideFile(t,w);return Promise.resolve(w)})}return q})),l=Promise.all(h.map(function(n){return void 0===n.simd||n.simd&&c||!n.simd&&!c?cd(a.locateFile(n.url,a.ha)):Promise.resolve()})).then(function(){var n,q,t;return E(function(w){if(1==w.h)return n=window.createMediapipeSolutionsWasm,q=window.createMediapipeSolutionsPackedAssets,t=a,D(w,n(q),2);t.i=w.i;w.h=0})}),m=function(){return E(function(n){a.h.graph&&a.h.graph.url?n=D(n,
|
||||
gd(a,a.h.graph.url),0):(n.h=0,n=void 0);return n})}(),D(p,Promise.all([l,k,m]),7);if("function"!==typeof importScripts)throw Error("solutions can only be loaded on a web page or in a web worker");d=b.filter(function(n){return void 0===n.simd||n.simd&&c||!n.simd&&!c}).map(function(n){return a.locateFile(n.url,a.ha)});importScripts.apply(null,ea(d));e=a;return D(p,createMediapipeSolutionsWasm(Module),6);case 6:e.i=p.i;a.m=new OffscreenCanvas(1,1);a.i.canvas=a.m;g=a.i.GL.createContext(a.m,{antialias:!1,
|
||||
alpha:!1,va:"undefined"!==typeof WebGL2RenderingContext?2:1});a.i.GL.makeContextCurrent(g);p.h=4;break;case 7:a.m=document.createElement("canvas");r=a.m.getContext("webgl2",{});if(!r&&(r=a.m.getContext("webgl",{}),!r))return alert("Failed to create WebGL canvas context when passing video frame."),p.return();a.K=r;a.i.canvas=a.m;a.i.createContext(a.m,!0,!0,{});case 4:a.j=new a.i.SolutionWasm,a.ga=!1,p.h=0}})}
|
||||
function hd(a){var b,c,d,e,g,f,h,k;return E(function(l){if(1==l.h){if(a.h.graph&&a.h.graph.url&&a.fa===a.h.graph.url)return l.return();a.u=!0;if(!a.h.graph||!a.h.graph.url){l.h=2;return}a.fa=a.h.graph.url;return D(l,gd(a,a.h.graph.url),3)}2!=l.h&&(b=l.i,a.j.loadGraph(b));c=A(Object.keys(a.D));for(d=c.next();!d.done;d=c.next())e=d.value,a.j.overrideFile(e,a.D[e]);a.D={};if(a.h.listeners)for(g=A(a.h.listeners),f=g.next();!f.done;f=g.next())h=f.value,id(a,h);k=a.l;a.l={};a.setOptions(k);l.h=0})}
|
||||
x.reset=function(){var a=this;return E(function(b){a.j&&(a.j.reset(),a.s={},a.v={});b.h=0})};
|
||||
x.setOptions=function(a,b){var c=this;if(b=b||this.h.options){for(var d=[],e=[],g={},f=A(Object.keys(a)),h=f.next();!h.done;g={X:g.X,Y:g.Y},h=f.next())if(h=h.value,!(h in this.l&&this.l[h]===a[h])){this.l[h]=a[h];var k=b[h];void 0!==k&&(k.onChange&&(g.X=k.onChange,g.Y=a[h],d.push(function(l){return function(){var m;return E(function(r){if(1==r.h)return D(r,l.X(l.Y),2);m=r.i;!0===m&&(c.u=!0);r.h=0})}}(g))),k.graphOptionXref&&(h=Object.assign({},{calculatorName:"",calculatorIndex:0},k.graphOptionXref,
|
||||
{valueNumber:1===k.type?a[h]:0,valueBoolean:0===k.type?a[h]:!1,valueString:2===k.type?a[h]:""}),e.push(h)))}if(0!==d.length||0!==e.length)this.u=!0,this.H=(void 0===this.H?[]:this.H).concat(e),this.F=(void 0===this.F?[]:this.F).concat(d)}};
|
||||
function jd(a){var b,c,d,e,g,f,h;return E(function(k){switch(k.h){case 1:if(!a.u)return k.return();if(!a.F){k.h=2;break}b=A(a.F);c=b.next();case 3:if(c.done){k.h=5;break}d=c.value;return D(k,d(),4);case 4:c=b.next();k.h=3;break;case 5:a.F=void 0;case 2:if(a.H){e=new a.i.GraphOptionChangeRequestList;g=A(a.H);for(f=g.next();!f.done;f=g.next())h=f.value,e.push_back(h);a.j.changeOptions(e);e.delete();a.H=void 0}a.u=!1;k.h=0}})}
|
||||
x.initialize=function(){var a=this;return E(function(b){return 1==b.h?D(b,fd(a),2):3!=b.h?D(b,hd(a),3):D(b,jd(a),0)})};function gd(a,b){var c,d;return E(function(e){if(b in a.L)return e.return(a.L[b]);c=a.locateFile(b,"");d=fetch(c).then(function(g){return g.arrayBuffer()});a.L[b]=d;return e.return(d)})}x.overrideFile=function(a,b){this.j?this.j.overrideFile(a,b):this.D[a]=b};x.clearOverriddenFiles=function(){this.D={};this.j&&this.j.clearOverriddenFiles()};
|
||||
x.send=function(a,b){var c=this,d,e,g,f,h,k,l,m,r;return E(function(p){switch(p.h){case 1:if(!c.h.inputs)return p.return();d=1E3*(void 0===b||null===b?performance.now():b);return D(p,c.I,2);case 2:return D(p,c.initialize(),3);case 3:e=new c.i.PacketDataList;g=A(Object.keys(a));for(f=g.next();!f.done;f=g.next())if(h=f.value,k=c.h.inputs[h]){a:{var n=a[h];switch(k.type){case "video":var q=c.s[k.stream];q||(q=new Wc(c.i,c.K),c.s[k.stream]=q);0===q.m&&(q.m=q.i.createTexture());if("undefined"!==typeof HTMLVideoElement&&
|
||||
n instanceof HTMLVideoElement){var t=n.videoWidth;var w=n.videoHeight}else"undefined"!==typeof HTMLImageElement&&n instanceof HTMLImageElement?(t=n.naturalWidth,w=n.naturalHeight):(t=n.width,w=n.height);w={glName:q.m,width:t,height:w};t=q.h;t.canvas.width=w.width;t.canvas.height=w.height;t.activeTexture(t.TEXTURE0);q.i.bindTexture2d(q.m);t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,n);q.i.bindTexture2d(0);q=w;break a;case "detections":q=c.s[k.stream];q||(q=new Zc(c.i),c.s[k.stream]=q);
|
||||
q.data||(q.data=new q.h.DetectionListData);q.data.reset(n.length);for(w=0;w<n.length;++w){t=n[w];var v=q.data,B=v.setBoundingBox,J=w;var I=t.la;var u=new Qc;V(u,1,I.sa);V(u,2,I.ta);V(u,3,I.height);V(u,4,I.width);V(u,5,I.rotation);U(u,6,I.pa);I=u.l();B.call(v,J,I);if(t.ea)for(v=0;v<t.ea.length;++v){u=t.ea[v];B=q.data;J=B.addNormalizedLandmark;I=w;u=Object.assign({},u,{visibility:u.visibility?u.visibility:0});var C=new Lc;V(C,1,u.x);V(C,2,u.y);V(C,3,u.z);u.visibility&&V(C,4,u.visibility);u=C.l();J.call(B,
|
||||
I,u)}if(t.ba)for(v=0;v<t.ba.length;++v)B=q.data,J=B.addClassification,I=w,u=t.ba[v],C=new Z,V(C,2,u.qa),u.index&&U(C,1,u.index),u.label&&U(C,3,u.label),u.displayName&&U(C,4,u.displayName),u=C.l(),J.call(B,I,u)}q=q.data;break a;default:q={}}}l=q;m=k.stream;switch(k.type){case "video":e.pushTexture2d(Object.assign({},l,{stream:m,timestamp:d}));break;case "detections":r=l;r.stream=m;r.timestamp=d;e.pushDetectionList(r);break;default:throw Error("Unknown input config type: '"+k.type+"'");}}c.j.send(e);
|
||||
return D(p,c.I,4);case 4:e.delete(),p.h=0}})};
|
||||
function kd(a,b,c){var d,e,g,f,h,k,l,m,r,p,n,q,t,w;return E(function(v){switch(v.h){case 1:if(!c)return v.return(b);d={};e=0;g=A(Object.keys(c));for(f=g.next();!f.done;f=g.next())h=f.value,k=c[h],"string"!==typeof k&&"texture"===k.type&&void 0!==b[k.stream]&&++e;1<e&&(a.M=!1);l=A(Object.keys(c));f=l.next();case 2:if(f.done){v.h=4;break}m=f.value;r=c[m];if("string"===typeof r)return t=d,w=m,D(v,ld(a,m,b[r]),14);p=b[r.stream];if("detection_list"===r.type){if(p){var B=p.getRectList();for(var J=p.getLandmarksList(),
|
||||
I=p.getClassificationsList(),u=[],C=0;C<B.size();++C){var T=Sc(B.get(C)),od=W(T,1),pd=W(T,2),qd=W(T,3),rd=W(T,4),sd=W(T,5,0),za=void 0;za=void 0===za?0:za;T={la:{sa:od,ta:pd,height:qd,width:rd,rotation:sd,pa:Rb(S(T,6),za)},ea:Ob(Pc(J.get(C)),Lc,1).map(Vc),ba:Uc(Kc(I.get(C)))};u.push(T)}B=u}else B=[];d[m]=B;v.h=7;break}if("proto_list"===r.type){if(p){B=Array(p.size());for(J=0;J<p.size();J++)B[J]=p.get(J);p.delete()}else B=[];d[m]=B;v.h=7;break}if(void 0===p){v.h=3;break}if("float_list"===r.type){d[m]=
|
||||
p;v.h=7;break}if("proto"===r.type){d[m]=p;v.h=7;break}if("texture"!==r.type)throw Error("Unknown output config type: '"+r.type+"'");n=a.v[m];n||(n=new Wc(a.i,a.K),a.v[m]=n);return D(v,Xc(n,p,a.M),13);case 13:q=v.i,d[m]=q;case 7:r.transform&&d[m]&&(d[m]=r.transform(d[m]));v.h=3;break;case 14:t[w]=v.i;case 3:f=l.next();v.h=2;break;case 4:return v.return(d)}})}
|
||||
function ld(a,b,c){var d;return E(function(e){return"number"===typeof c||c instanceof Uint8Array||c instanceof a.i.Uint8BlobList?e.return(c):c instanceof a.i.Texture2dDataOut?(d=a.v[b],d||(d=new Wc(a.i,a.K),a.v[b]=d),e.return(Xc(d,c,a.M))):e.return(void 0)})}
|
||||
function id(a,b){for(var c=b.name||"$",d=[].concat(ea(b.wants)),e=new a.i.StringList,g=A(b.wants),f=g.next();!f.done;f=g.next())e.push_back(f.value);g=a.i.PacketListener.implement({onResults:function(h){for(var k={},l=0;l<b.wants.length;++l)k[d[l]]=h.get(l);var m=a.listeners[c];m&&(a.I=kd(a,k,b.outs).then(function(r){r=m(r);for(var p=0;p<b.wants.length;++p){var n=k[d[p]];"object"===typeof n&&n.hasOwnProperty&&n.hasOwnProperty("delete")&&n.delete()}r&&(a.I=r)}))}});a.j.attachMultiListener(e,g);e.delete()}
|
||||
x.onResults=function(a,b){this.listeners[b||"$"]=a};Aa("Solution",ed);Aa("OptionType",{BOOL:0,NUMBER:1,ua:2,0:"BOOL",1:"NUMBER",2:"STRING"});function md(a){void 0===a&&(a=0);switch(a){case 1:return"selfie_segmentation_landscape.tflite";default:return"selfie_segmentation.tflite"}}
|
||||
function nd(a){var b=this;a=a||{};this.h=new ed({locateFile:a.locateFile,files:function(c){return[{simd:!0,url:"selfie_segmentation_solution_simd_wasm_bin.js"},{simd:!1,url:"selfie_segmentation_solution_wasm_bin.js"},{data:!0,url:md(c.modelSelection)}]},graph:{url:"selfie_segmentation.binarypb"},listeners:[{wants:["segmentation_mask","image_transformed"],outs:{image:{type:"texture",stream:"image_transformed"},segmentationMask:{type:"texture",stream:"segmentation_mask"}}}],inputs:{image:{type:"video",
|
||||
stream:"input_frames_gpu"}},options:{useCpuInference:{type:0,graphOptionXref:{calculatorType:"InferenceCalculator",fieldName:"use_cpu_inference"},default:"object"!==typeof window||void 0===window.navigator?!1:"iPad Simulator;iPhone Simulator;iPod Simulator;iPad;iPhone;iPod".split(";").includes(navigator.platform)||navigator.userAgent.includes("Mac")&&"ontouchend"in document},selfieMode:{type:0,graphOptionXref:{calculatorType:"GlScalerCalculator",calculatorIndex:1,fieldName:"flip_horizontal"}},modelSelection:{type:1,
|
||||
graphOptionXref:{calculatorType:"ConstantSidePacketCalculator",calculatorName:"ConstantSidePacketCalculatorModelSelection",fieldName:"int_value"},onChange:function(c){var d,e,g;return E(function(f){if(1==f.h)return d=md(c),e="third_party/mediapipe/modules/selfie_segmentation/"+d,D(f,gd(b.h,d),2);g=f.i;b.h.overrideFile(e,g);return f.return(!0)})}}}})}x=nd.prototype;x.close=function(){this.h.close();return Promise.resolve()};x.onResults=function(a){this.h.onResults(a)};
|
||||
x.initialize=function(){var a=this;return E(function(b){return D(b,a.h.initialize(),0)})};x.reset=function(){this.h.reset()};x.send=function(a){var b=this;return E(function(c){return D(c,b.h.send(a),0)})};x.setOptions=function(a){this.h.setOptions(a)};Aa("SelfieSegmentation",nd);Aa("VERSION","0.1.1675465747");}).call(this);
|
||||