HydroFlux 0.0.1
This commit is contained in:
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
9
.idea/Motivation_App.iml
generated
Normal file
9
.idea/Motivation_App.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
1186
.idea/caches/deviceStreaming.xml
generated
Normal file
1186
.idea/caches/deviceStreaming.xml
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
.idea/deviceManager.xml
generated
Normal file
13
.idea/deviceManager.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DeviceTable">
|
||||||
|
<option name="columnSorters">
|
||||||
|
<list>
|
||||||
|
<ColumnSorterState>
|
||||||
|
<option name="column" value="Name" />
|
||||||
|
<option name="order" value="ASCENDING" />
|
||||||
|
</ColumnSorterState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/Motivation_App.iml" filepath="$PROJECT_DIR$/.idea/Motivation_App.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
15
Hydroflux/.gitignore
vendored
Normal file
15
Hydroflux/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
||||||
3
Hydroflux/.idea/.gitignore
generated
vendored
Normal file
3
Hydroflux/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
6
Hydroflux/.idea/AndroidProjectSystem.xml
generated
Normal file
6
Hydroflux/.idea/AndroidProjectSystem.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
123
Hydroflux/.idea/codeStyles/Project.xml
generated
Normal file
123
Hydroflux/.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<JetCodeStyleSettings>
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</JetCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="kotlin">
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
5
Hydroflux/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
Hydroflux/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
6
Hydroflux/.idea/compiler.xml
generated
Normal file
6
Hydroflux/.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="21" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
18
Hydroflux/.idea/deploymentTargetSelector.xml
generated
Normal file
18
Hydroflux/.idea/deploymentTargetSelector.xml
generated
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2026-01-17T04:31:29.584970200Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=27151FDH20068C" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
13
Hydroflux/.idea/deviceManager.xml
generated
Normal file
13
Hydroflux/.idea/deviceManager.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DeviceTable">
|
||||||
|
<option name="columnSorters">
|
||||||
|
<list>
|
||||||
|
<ColumnSorterState>
|
||||||
|
<option name="column" value="Name" />
|
||||||
|
<option name="order" value="ASCENDING" />
|
||||||
|
</ColumnSorterState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
19
Hydroflux/.idea/gradle.xml
generated
Normal file
19
Hydroflux/.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
Hydroflux/.idea/migrations.xml
generated
Normal file
10
Hydroflux/.idea/migrations.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
Hydroflux/.idea/misc.xml
generated
Normal file
9
Hydroflux/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
17
Hydroflux/.idea/runConfigurations.xml
generated
Normal file
17
Hydroflux/.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
1
Hydroflux/app/.gitignore
vendored
Normal file
1
Hydroflux/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
60
Hydroflux/app/build.gradle.kts
Normal file
60
Hydroflux/app/build.gradle.kts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.david.hydroflux"
|
||||||
|
compileSdk = 36
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.david.hydroflux.v2"
|
||||||
|
minSdk = 28
|
||||||
|
targetSdk = 36
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
|
implementation("androidx.health.connect:connect-client:1.1.0") // Official Stable Release
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
implementation(libs.androidx.compose.ui)
|
||||||
|
implementation(libs.androidx.compose.ui.graphics)
|
||||||
|
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||||
|
implementation(libs.androidx.compose.material3)
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
|
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||||
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
}
|
||||||
21
Hydroflux/app/proguard-rules.pro
vendored
Normal file
21
Hydroflux/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,24 @@
|
|||||||
|
package com.david.hydroflux
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("com.david.hydroflux", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Hydroflux/app/src/main/AndroidManifest.xml
Normal file
55
Hydroflux/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<!-- Health Connect Permissions -->
|
||||||
|
<uses-permission android:name="android.permission.health.READ_STEPS"/>
|
||||||
|
<uses-permission android:name="android.permission.health.READ_SLEEP"/>
|
||||||
|
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
|
||||||
|
|
||||||
|
<!-- Required to interact with Health Connect on Android 11+ -->
|
||||||
|
<queries>
|
||||||
|
<package android:name="com.google.android.apps.healthdata" />
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.Hydroflux">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.Hydroflux">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Required for Health Connect Visibility -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
|
||||||
|
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".PermissionsRationaleActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="Health Access Rationale"
|
||||||
|
android:theme="@style/Theme.Hydroflux">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
641
Hydroflux/app/src/main/assests/css/style.css
Normal file
641
Hydroflux/app/src/main/assests/css/style.css
Normal file
@@ -0,0 +1,641 @@
|
|||||||
|
:root {
|
||||||
|
/* Futuristic Palette */
|
||||||
|
--bg-dark: #050508;
|
||||||
|
--bg-panel: rgba(20, 20, 35, 0.4);
|
||||||
|
--primary-cyan: #00f3ff;
|
||||||
|
--secondary-purple: #bc13fe;
|
||||||
|
--text-main: #e0e0e0;
|
||||||
|
--text-muted: #8a8a9b;
|
||||||
|
--glass-border: rgba(255, 255, 255, 0.08);
|
||||||
|
--neon-shadow: 0 0 10px rgba(0, 243, 255, 0.5);
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--spacing-sm: 8px;
|
||||||
|
--spacing-md: 16px;
|
||||||
|
--spacing-lg: 24px;
|
||||||
|
|
||||||
|
/* Font */
|
||||||
|
--font-heading: 'Orbitron', sans-serif;
|
||||||
|
--font-body: 'Outfit', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
/* Remove mobile tap highlight */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-dark);
|
||||||
|
color: var(--text-main);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(circle at 10% 20%, rgba(188, 19, 254, 0.1) 0%, transparent 40%),
|
||||||
|
radial-gradient(circle at 90% 80%, rgba(0, 243, 255, 0.08) 0%, transparent 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-text {
|
||||||
|
text-shadow: 0 0 8px rgba(0, 243, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glassmorphism Utilities */
|
||||||
|
.glass-header {
|
||||||
|
background: rgba(5, 5, 8, 0.7);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border-bottom: 1px solid var(--glass-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-panel {
|
||||||
|
background: var(--bg-panel);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
margin: var(--spacing-md);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-nav {
|
||||||
|
background: rgba(10, 10, 16, 0.8);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
border-top: 1px solid var(--glass-border);
|
||||||
|
padding: var(--spacing-md) var(--spacing-lg);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: max(16px, env(safe-area-inset-bottom));
|
||||||
|
/* Safe area for iOS */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content Area */
|
||||||
|
#main-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
/* Space for nav */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive Elements */
|
||||||
|
.nav-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn.active {
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
background: rgba(0, 243, 255, 0.1);
|
||||||
|
box-shadow: 0 0 15px rgba(0, 243, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes pulse-glow {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 5px rgba(0, 243, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 20px rgba(0, 243, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 5px rgba(0, 243, 255, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Water Tracker Module Styles --- */
|
||||||
|
|
||||||
|
.water-tracker-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-container {
|
||||||
|
position: relative;
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4px solid var(--bg-panel);
|
||||||
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
/* Clips the wave */
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.water-circle {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wave Animation */
|
||||||
|
.wave {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
/* Dynamic */
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: rgba(0, 243, 255, 0.4);
|
||||||
|
border-radius: 40%;
|
||||||
|
animation: rotate 6s linear infinite;
|
||||||
|
transition: top 1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 243, 255, 0.3);
|
||||||
|
/* Lighter top layer */
|
||||||
|
border-radius: 40%;
|
||||||
|
animation: rotate 10s linear infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 2;
|
||||||
|
/* Above wave */
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.water-percentage {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.water-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-row {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Controls */
|
||||||
|
.controls-area {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottle-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottle-selector input {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border: 1px solid var(--primary-cyan);
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 243, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottle-selector input:focus {
|
||||||
|
box-shadow: 0 0 15px rgba(0, 243, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
color: var(--text-muted);
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.secondary {
|
||||||
|
border-color: rgba(255, 50, 50, 0.3);
|
||||||
|
color: #ff4d4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:active {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn {
|
||||||
|
position: relative;
|
||||||
|
background: rgba(0, 243, 255, 0.1);
|
||||||
|
border: 1px solid var(--primary-cyan);
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0 40px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 243, 255, 0.2), inset 0 0 20px rgba(0, 243, 255, 0.1);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(0, 243, 255, 0.4), transparent);
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn:hover {
|
||||||
|
background: rgba(0, 243, 255, 0.2);
|
||||||
|
box-shadow: 0 0 30px rgba(0, 243, 255, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 8px rgba(0, 243, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn svg {
|
||||||
|
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Streak Module Styles --- */
|
||||||
|
.streak-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--secondary-purple);
|
||||||
|
letter-spacing: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.streak-counter {
|
||||||
|
background: radial-gradient(circle, rgba(188, 19, 254, 0.1) 0%, transparent 70%);
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.streak-days {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 6rem;
|
||||||
|
color: white;
|
||||||
|
line-height: 1;
|
||||||
|
text-shadow: 0 0 20px rgba(188, 19, 254, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.streak-label {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.streak-detail {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-card {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-left: 3px solid var(--primary-cyan);
|
||||||
|
padding: 20px;
|
||||||
|
font-style: italic;
|
||||||
|
color: #dedede;
|
||||||
|
margin: 20px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-btn {
|
||||||
|
position: relative;
|
||||||
|
background: rgba(255, 50, 50, 0.1);
|
||||||
|
border: 1px solid #ff4d4d;
|
||||||
|
color: #ff4d4d;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
padding: 15px 40px;
|
||||||
|
border-radius: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
margin-top: 30px;
|
||||||
|
box-shadow: 0 0 10px rgba(255, 50, 50, 0.2), inset 0 0 20px rgba(255, 50, 50, 0.1);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-btn:hover {
|
||||||
|
background: rgba(255, 50, 50, 0.2);
|
||||||
|
box-shadow: 0 0 30px rgba(255, 50, 50, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 8px rgba(255, 50, 50, 0.8);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Connect Button (Neon Cyan Variant) --- */
|
||||||
|
.connect-glow-btn {
|
||||||
|
position: relative;
|
||||||
|
background: rgba(0, 243, 255, 0.1);
|
||||||
|
border: 1px solid var(--primary-cyan);
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
padding: 8px 24px;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 0 10px rgba(0, 243, 255, 0.2), inset 0 0 10px rgba(0, 243, 255, 0.1);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-glow-btn:hover {
|
||||||
|
background: rgba(0, 243, 255, 0.2);
|
||||||
|
box-shadow: 0 0 20px rgba(0, 243, 255, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 5px rgba(0, 243, 255, 0.8);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-glow-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Concentric Rings --- */
|
||||||
|
.rings-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: radial-gradient(circle, rgba(255, 255, 255, 0.02) 0%, transparent 70%);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concentric-svg {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-bg {
|
||||||
|
fill: none;
|
||||||
|
stroke: rgba(255, 255, 255, 0.05);
|
||||||
|
/* Proper track color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-progress {
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
transition: stroke-dashoffset 1.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-progress.cyan {
|
||||||
|
stroke: var(--primary-cyan);
|
||||||
|
filter: drop-shadow(0 0 5px var(--primary-cyan));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-progress.purple {
|
||||||
|
stroke: var(--secondary-purple);
|
||||||
|
filter: drop-shadow(0 0 5px var(--secondary-purple));
|
||||||
|
}
|
||||||
|
|
||||||
|
.rings-legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.cyan {
|
||||||
|
background: var(--primary-cyan);
|
||||||
|
box-shadow: 0 0 5px var(--primary-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.purple {
|
||||||
|
background: var(--secondary-purple);
|
||||||
|
box-shadow: 0 0 5px var(--secondary-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Combined Stat Card & Charts --- */
|
||||||
|
.stat-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-sub {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-box {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cyan-box {
|
||||||
|
background: rgba(0, 243, 255, 0.1);
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple-box {
|
||||||
|
background: rgba(188, 19, 254, 0.1);
|
||||||
|
color: var(--secondary-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Re-using chart styles but refining for the box */
|
||||||
|
.chart-container.small {
|
||||||
|
height: 100px;
|
||||||
|
margin-top: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar {
|
||||||
|
width: 6px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: height 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar.cyan {
|
||||||
|
background: var(--primary-cyan);
|
||||||
|
box-shadow: 0 0 5px rgba(0, 243, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar.purple {
|
||||||
|
background: var(--secondary-purple);
|
||||||
|
box-shadow: 0 0 5px rgba(188, 19, 254, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-day {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
74
Hydroflux/app/src/main/assests/index.html
Normal file
74
Hydroflux/app/src/main/assests/index.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<title>HydroFlux</title>
|
||||||
|
<meta name="description" content="Futuristic Hydration & Fitness Tracker">
|
||||||
|
<meta name="theme-color" content="#0a0a12">
|
||||||
|
|
||||||
|
<!-- PWA Application Settings -->
|
||||||
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="HydroFlux">
|
||||||
|
|
||||||
|
<!-- Fonts: Orbitron for headers, Outfit for body text -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Outfit:wght@300;400;600&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/style.css?v=3">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<!-- Main Layout injected here -->
|
||||||
|
<header class="glass-header">
|
||||||
|
<h1 class="glow-text">HYDRO<span class="highlight">FLUX</span></h1>
|
||||||
|
<div id="connection-status" class="status-indicator"></div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main-content">
|
||||||
|
<!-- Dynamic Content -->
|
||||||
|
<section id="water-section" class="glass-panel">
|
||||||
|
<!-- Water Tracker injected via JS -->
|
||||||
|
<div class="loader">Loading Core...</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="streak-section" class="glass-panel" style="display: none;">
|
||||||
|
<!-- Streak Tracker injected via JS -->
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="fitness-section" class="glass-panel" style="display: none;">
|
||||||
|
<h2>FITNESS DATA</h2>
|
||||||
|
<p style="text-align:center; color: var(--text-muted); margin-top: 20px;">Coming Soon</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<nav class="glass-nav">
|
||||||
|
<button class="nav-btn active" data-view="water">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="nav-btn" data-view="streak">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path
|
||||||
|
d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="nav-btn" data-view="fitness">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="js/app.js?v=2"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
28
Hydroflux/app/src/main/assests/js/app.js
Normal file
28
Hydroflux/app/src/main/assests/js/app.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
console.log('HydroFlux Initialized');
|
||||||
|
|
||||||
|
// Simple Navigation Logic
|
||||||
|
document.querySelectorAll('.nav-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
// Toggle Active State
|
||||||
|
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
|
||||||
|
e.currentTarget.classList.add('active');
|
||||||
|
|
||||||
|
const view = e.currentTarget.dataset.view;
|
||||||
|
|
||||||
|
// Hide all sections
|
||||||
|
document.querySelectorAll('section').forEach(el => el.style.display = 'none');
|
||||||
|
|
||||||
|
// Show target section
|
||||||
|
const target = document.getElementById(`${view}-section`);
|
||||||
|
if (target) target.style.display = 'block';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize Modules
|
||||||
|
import { WaterTracker } from './modules/water.js?v=2';
|
||||||
|
import { StreakTracker } from './modules/streak.js?v=2';
|
||||||
|
import { FitnessDashboard } from './modules/fitness.js?v=2';
|
||||||
|
|
||||||
|
const waterTracker = new WaterTracker('water-section');
|
||||||
|
const streakTracker = new StreakTracker('streak-section');
|
||||||
|
const fitnessDashboard = new FitnessDashboard('fitness-section');
|
||||||
153
Hydroflux/app/src/main/assests/js/modules/fitness.js
Normal file
153
Hydroflux/app/src/main/assests/js/modules/fitness.js
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
export class FitnessDashboard {
|
||||||
|
constructor(containerId) {
|
||||||
|
this.container = document.getElementById(containerId);
|
||||||
|
// Mock Data
|
||||||
|
this.data = {
|
||||||
|
steps: { current: 8432, goal: 10000 },
|
||||||
|
sleep: { current: 6.5, goal: 8 },
|
||||||
|
history: {
|
||||||
|
steps: [4500, 7200, 10500, 8900, 6000, 11200, 8432],
|
||||||
|
sleep: [5.5, 6.0, 7.5, 8.2, 5.0, 9.0, 6.5]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
// Delay animation to allow DOM paint
|
||||||
|
setTimeout(() => this.animate(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const stepPercent = Math.min((this.data.steps.current / this.data.steps.goal) * 100, 100);
|
||||||
|
const sleepPercent = Math.min((this.data.sleep.current / this.data.sleep.goal) * 100, 100);
|
||||||
|
|
||||||
|
// Ring Config
|
||||||
|
const center = 100;
|
||||||
|
const radiusOuter = 80;
|
||||||
|
const radiusInner = 55;
|
||||||
|
|
||||||
|
const circumOuter = 2 * Math.PI * radiusOuter;
|
||||||
|
const circumInner = 2 * Math.PI * radiusInner;
|
||||||
|
|
||||||
|
this.container.innerHTML = `
|
||||||
|
<div class="fitness-container">
|
||||||
|
<h2 class="section-title">DAILY ACTIVITY</h2>
|
||||||
|
|
||||||
|
<!-- Top: Concentric Rings -->
|
||||||
|
<div class="rings-wrapper">
|
||||||
|
<svg class="concentric-svg" viewBox="0 0 200 200">
|
||||||
|
<!-- Outer Track (Steps) -->
|
||||||
|
<circle class="ring-bg" cx="${center}" cy="${center}" r="${radiusOuter}" stroke-width="18"></circle>
|
||||||
|
<!-- Inner Track (Sleep) -->
|
||||||
|
<circle class="ring-bg" cx="${center}" cy="${center}" r="${radiusInner}" stroke-width="18"></circle>
|
||||||
|
|
||||||
|
<!-- Outer Progress (Steps - Cyan) -->
|
||||||
|
<circle class="ring-progress cyan" cx="${center}" cy="${center}" r="${radiusOuter}"
|
||||||
|
stroke-width="18"
|
||||||
|
stroke-dasharray="${circumOuter}"
|
||||||
|
stroke-dashoffset="${circumOuter}"
|
||||||
|
data-offset="${circumOuter - (stepPercent / 100) * circumOuter}"></circle>
|
||||||
|
|
||||||
|
<!-- Inner Progress (Sleep - Purple) -->
|
||||||
|
<circle class="ring-progress purple" cx="${center}" cy="${center}" r="${radiusInner}"
|
||||||
|
stroke-width="18"
|
||||||
|
stroke-dasharray="${circumInner}"
|
||||||
|
stroke-dashoffset="${circumInner}"
|
||||||
|
data-offset="${circumInner - (sleepPercent / 100) * circumInner}"></circle>
|
||||||
|
|
||||||
|
<!-- Icons/Center -->
|
||||||
|
<image href="https://fonts.gstatic.com/s/i/materialicons/bolt/v5/24px.svg" x="90" y="90" height="20" width="20" style="filter: invert(1); opacity: 0.5;" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- Legend to explain the rings -->
|
||||||
|
<div class="rings-legend">
|
||||||
|
<div class="legend-item">
|
||||||
|
<span class="dot cyan"></span> STEPS
|
||||||
|
</div>
|
||||||
|
<div class="legend-item">
|
||||||
|
<span class="dot purple"></span> SLEEP
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Middle: Device -->
|
||||||
|
<div class="device-card">
|
||||||
|
<div class="device-info">
|
||||||
|
<span class="device-name">TicWatch Pro 5</span>
|
||||||
|
<span class="device-status">Disconnected</span>
|
||||||
|
</div>
|
||||||
|
<button id="connect-watch-btn" class="connect-glow-btn">LINK</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Box 1: Steps (Current + History) -->
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-header">
|
||||||
|
<div>
|
||||||
|
<span class="stat-label">STEPS</span>
|
||||||
|
<div class="stat-value">${this.data.steps.current}</div>
|
||||||
|
<div class="stat-sub">Goal: ${this.data.steps.goal}</div>
|
||||||
|
</div>
|
||||||
|
<div class="icon-box cyan-box">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-divider"></div>
|
||||||
|
|
||||||
|
<div class="chart-container small">
|
||||||
|
${this.generateBars(this.data.history.steps, 12000, 'cyan')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Box 2: Sleep (Current + History) -->
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-header">
|
||||||
|
<div>
|
||||||
|
<span class="stat-label">SLEEP</span>
|
||||||
|
<div class="stat-value">${this.data.sleep.current}h</div>
|
||||||
|
<div class="stat-sub">Goal: ${this.data.sleep.goal}h</div>
|
||||||
|
</div>
|
||||||
|
<div class="icon-box purple-box">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-divider"></div>
|
||||||
|
|
||||||
|
<div class="chart-container small">
|
||||||
|
${this.generateBars(this.data.history.sleep, 10, 'purple')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.attachEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateBars(data, max, colorClass) {
|
||||||
|
const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
|
||||||
|
return data.map((val, index) => {
|
||||||
|
const height = Math.min((val / max) * 100, 100);
|
||||||
|
return `
|
||||||
|
<div class="chart-column">
|
||||||
|
<div class="chart-bar ${colorClass}" style="height: ${height}%"></div>
|
||||||
|
<span class="chart-day">${days[index]}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
animate() {
|
||||||
|
this.container.querySelectorAll('.ring-progress').forEach(ring => {
|
||||||
|
ring.style.strokeDashoffset = ring.dataset.offset;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
attachEvents() {
|
||||||
|
const btn = this.container.querySelector('#connect-watch-btn');
|
||||||
|
if (btn) {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
alert("Placeholder: This feature would connect to the WearOS API in the native app.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
Hydroflux/app/src/main/assests/js/modules/streak.js
Normal file
102
Hydroflux/app/src/main/assests/js/modules/streak.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
export class StreakTracker {
|
||||||
|
constructor(containerId) {
|
||||||
|
this.container = document.getElementById(containerId);
|
||||||
|
this.STORAGE_KEY = 'hydroflux_streak';
|
||||||
|
this.quotes = [
|
||||||
|
"The only easy day was yesterday.",
|
||||||
|
"Discipline is doing what needs to be done, even if you don't want to.",
|
||||||
|
"Your future self is watching you right now through memories.",
|
||||||
|
"Pain is temporary. Quitting lasts forever.",
|
||||||
|
"Suffering is the currency of success.",
|
||||||
|
"Don't stop when you're tired. Stop when you're done.",
|
||||||
|
"You are stronger than your urges.",
|
||||||
|
"Focus on the goal, not the obstacle."
|
||||||
|
];
|
||||||
|
|
||||||
|
this.loadState();
|
||||||
|
this.render();
|
||||||
|
this.startTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadState() {
|
||||||
|
const saved = localStorage.getItem(this.STORAGE_KEY);
|
||||||
|
if (saved) {
|
||||||
|
this.startDate = new Date(parseInt(saved));
|
||||||
|
} else {
|
||||||
|
this.startDate = new Date();
|
||||||
|
this.saveState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveState() {
|
||||||
|
localStorage.setItem(this.STORAGE_KEY, this.startDate.getTime().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
resetStreak() {
|
||||||
|
if (confirm("Are you sure you want to reset your streak?")) {
|
||||||
|
this.startDate = new Date();
|
||||||
|
this.saveState();
|
||||||
|
this.updateUI();
|
||||||
|
|
||||||
|
// Haptic Bad Feedback
|
||||||
|
if (navigator.vibrate) navigator.vibrate([100, 50, 100]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDuration() {
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now - this.startDate;
|
||||||
|
|
||||||
|
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||||
|
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||||
|
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
|
||||||
|
return { days, hours, minutes };
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUI() {
|
||||||
|
const { days, hours, minutes } = this.getDuration();
|
||||||
|
|
||||||
|
const daysEl = this.container.querySelector('.streak-days');
|
||||||
|
const detailEl = this.container.querySelector('.streak-detail');
|
||||||
|
|
||||||
|
if (daysEl) daysEl.textContent = days;
|
||||||
|
if (detailEl) detailEl.textContent = `${hours}h ${minutes}m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
startTimer() {
|
||||||
|
setInterval(() => this.updateUI(), 60000); // Update every minute
|
||||||
|
}
|
||||||
|
|
||||||
|
getRandomQuote() {
|
||||||
|
return this.quotes[Math.floor(Math.random() * this.quotes.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.container.innerHTML = `
|
||||||
|
<div class="streak-container">
|
||||||
|
<h2 class="section-title">ABSTINENCE STREAK</h2>
|
||||||
|
|
||||||
|
<div class="streak-counter">
|
||||||
|
<div class="streak-days glow-text">0</div>
|
||||||
|
<div class="streak-label">DAYS</div>
|
||||||
|
<div class="streak-detail">${0}h ${0}m</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="quote-card">
|
||||||
|
"${this.getRandomQuote()}"
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="reset-streak-btn" class="danger-btn">
|
||||||
|
RESET STREAK
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.updateUI();
|
||||||
|
|
||||||
|
this.container.querySelector('#reset-streak-btn').addEventListener('click', () => {
|
||||||
|
this.resetStreak();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
184
Hydroflux/app/src/main/assests/js/modules/water.js
Normal file
184
Hydroflux/app/src/main/assests/js/modules/water.js
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
|
||||||
|
export class WaterTracker {
|
||||||
|
constructor(containerId) {
|
||||||
|
this.container = document.getElementById(containerId);
|
||||||
|
this.state = {
|
||||||
|
current: 0,
|
||||||
|
goal: 3000, // mL
|
||||||
|
bottleSize: 500, // mL
|
||||||
|
};
|
||||||
|
this.STORAGE_KEY = 'hydroflux_data';
|
||||||
|
this.loadState();
|
||||||
|
this.render();
|
||||||
|
this.attachEvents();
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadState() {
|
||||||
|
const saved = localStorage.getItem(this.STORAGE_KEY);
|
||||||
|
if (saved) {
|
||||||
|
const parsed = JSON.parse(saved);
|
||||||
|
this.state = { ...this.state, ...parsed };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveState() {
|
||||||
|
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.state));
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
addWater() {
|
||||||
|
this.state.current += this.state.bottleSize;
|
||||||
|
this.saveState();
|
||||||
|
if (navigator.vibrate) navigator.vibrate(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeWater() {
|
||||||
|
this.state.current = Math.max(0, this.state.current - this.state.bottleSize);
|
||||||
|
this.saveState();
|
||||||
|
if (navigator.vibrate) navigator.vibrate(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBottleSize(size) {
|
||||||
|
if (!size || size <= 0) return;
|
||||||
|
this.state.bottleSize = size;
|
||||||
|
this.saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPercentage() {
|
||||||
|
return Math.min(100, Math.max(0, (this.state.current / this.state.goal) * 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUI() {
|
||||||
|
// Update Text
|
||||||
|
const currentEl = this.container.querySelector('.water-count');
|
||||||
|
const percentageEl = this.container.querySelector('.water-percentage');
|
||||||
|
|
||||||
|
if (currentEl) currentEl.textContent = `${this.state.current} / ${this.state.goal} mL`;
|
||||||
|
if (percentageEl) percentageEl.textContent = `${Math.round(this.getPercentage())}%`;
|
||||||
|
|
||||||
|
// Update Wave Animation Height
|
||||||
|
const wave = this.container.querySelector('.wave');
|
||||||
|
if (wave) {
|
||||||
|
wave.style.top = `${100 - this.getPercentage()}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.container.innerHTML = `
|
||||||
|
<div class="water-tracker-container">
|
||||||
|
<!-- Circular Progress -->
|
||||||
|
<div class="circle-container">
|
||||||
|
<div class="water-circle">
|
||||||
|
<div class="wave"></div>
|
||||||
|
<div class="circle-content">
|
||||||
|
<span class="water-percentage">0%</span>
|
||||||
|
<span class="water-label">HYDRATION</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Display -->
|
||||||
|
<div class="stats-row">
|
||||||
|
<span class="water-count">0 / 3000 mL</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls -->
|
||||||
|
<div class="controls-area">
|
||||||
|
<div class="bottle-selector">
|
||||||
|
<label>Bottle (mL):</label>
|
||||||
|
<input type="number" id="bottle-size-input" value="${this.state.bottleSize}" min="1" max="5000">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button id="remove-water-btn" class="icon-btn secondary" aria-label="Remove Drink">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M5 12h14"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button id="add-water-btn" class="glow-btn">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M12 5v14M5 12h14"/>
|
||||||
|
</svg>
|
||||||
|
DRINK
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Notification Toggle -->
|
||||||
|
<button id="notify-btn" class="icon-btn" title="Enable Reminders">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
|
||||||
|
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.checkNotificationStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
attachEvents() {
|
||||||
|
this.container.querySelector('#add-water-btn').addEventListener('click', () => {
|
||||||
|
this.addWater();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.container.querySelector('#remove-water-btn').addEventListener('click', () => {
|
||||||
|
this.removeWater();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.container.querySelector('#notify-btn').addEventListener('click', (e) => {
|
||||||
|
this.toggleNotifications(e.currentTarget);
|
||||||
|
});
|
||||||
|
|
||||||
|
const input = this.container.querySelector('#bottle-size-input');
|
||||||
|
input.addEventListener('change', (e) => {
|
||||||
|
this.setBottleSize(parseInt(e.target.value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Notification Logic ---
|
||||||
|
|
||||||
|
toggleNotifications(btn) {
|
||||||
|
if (!("Notification" in window)) {
|
||||||
|
alert("This browser does not support desktop notifications");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Notification.permission === "granted") {
|
||||||
|
alert("Reminders are active! We'll check every hour.");
|
||||||
|
} else if (Notification.permission !== "denied") {
|
||||||
|
Notification.requestPermission().then(permission => {
|
||||||
|
if (permission === "granted") {
|
||||||
|
this.startReminderLoop();
|
||||||
|
btn.style.color = "var(--primary-cyan)";
|
||||||
|
new Notification("HydroFlux", { body: "Smart Hydration Reminders Enabled!" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNotificationStatus() {
|
||||||
|
if (Notification.permission === "granted") {
|
||||||
|
const btn = this.container.querySelector('#notify-btn');
|
||||||
|
if (btn) btn.style.color = "var(--primary-cyan)";
|
||||||
|
this.startReminderLoop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startReminderLoop() {
|
||||||
|
// Clear existing to avoid duplicates
|
||||||
|
if (this.reminderInterval) clearInterval(this.reminderInterval);
|
||||||
|
|
||||||
|
// Check every minute if it's been > 1 hour since last drink
|
||||||
|
this.reminderInterval = setInterval(() => {
|
||||||
|
// Pseudo-logic check since we don't store timestamp in this simple version yet
|
||||||
|
// In a real app, you'd check this.state.lastDrinkTime
|
||||||
|
new Notification("HydroFlux Needs You", {
|
||||||
|
body: "Remember to drink water!",
|
||||||
|
icon: "/icon.png"
|
||||||
|
});
|
||||||
|
}, 3600000); // 1 Hour
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Hydroflux/app/src/main/assests/manifest.json
Normal file
17
Hydroflux/app/src/main/assests/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "HydroFlux Motivation",
|
||||||
|
"short_name": "HydroFlux",
|
||||||
|
"background_color": "#050508",
|
||||||
|
"theme_color": "#050508",
|
||||||
|
"display": "standalone",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"scope": "/",
|
||||||
|
"start_url": "/",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.svg",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/svg+xml"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
40
Hydroflux/app/src/main/assets/css/fitness_stats.css
Normal file
40
Hydroflux/app/src/main/assets/css/fitness_stats.css
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/* --- New Fitness Stats Display --- */
|
||||||
|
.rings-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 40px;
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item.cyan .stat-number {
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
text-shadow: 0 0 10px rgba(0, 243, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item.purple .stat-number {
|
||||||
|
color: var(--secondary-purple);
|
||||||
|
text-shadow: 0 0 10px rgba(188, 19, 254, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label-small {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
955
Hydroflux/app/src/main/assets/css/style.css
Normal file
955
Hydroflux/app/src/main/assets/css/style.css
Normal file
@@ -0,0 +1,955 @@
|
|||||||
|
:root {
|
||||||
|
/* Futuristic Palette */
|
||||||
|
--bg-dark: #050508;
|
||||||
|
--bg-panel: rgba(20, 20, 35, 0.4);
|
||||||
|
--primary-cyan: #00f3ff;
|
||||||
|
--secondary-purple: #bc13fe;
|
||||||
|
--text-main: #e0e0e0;
|
||||||
|
--text-muted: #8a8a9b;
|
||||||
|
--glass-border: rgba(255, 255, 255, 0.08);
|
||||||
|
--neon-shadow: 0 0 10px rgba(0, 243, 255, 0.5);
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--spacing-sm: 8px;
|
||||||
|
--spacing-md: 16px;
|
||||||
|
--spacing-lg: 24px;
|
||||||
|
|
||||||
|
/* Font */
|
||||||
|
--font-heading: 'Orbitron', sans-serif;
|
||||||
|
--font-body: 'Outfit', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
/* Remove mobile tap highlight */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-dark);
|
||||||
|
color: var(--text-main);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(circle at 10% 20%, rgba(188, 19, 254, 0.1) 0%, transparent 40%),
|
||||||
|
radial-gradient(circle at 90% 80%, rgba(0, 243, 255, 0.08) 0%, transparent 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-text {
|
||||||
|
text-shadow: 0 0 8px rgba(0, 243, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glassmorphism Utilities */
|
||||||
|
.glass-header {
|
||||||
|
background: rgba(5, 5, 8, 0.7);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border-bottom: 1px solid var(--glass-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-panel {
|
||||||
|
background: var(--bg-panel);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
margin: var(--spacing-md);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-nav {
|
||||||
|
background: rgba(10, 10, 16, 0.8);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
border-top: 1px solid var(--glass-border);
|
||||||
|
padding: var(--spacing-md) var(--spacing-lg);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: max(16px, env(safe-area-inset-bottom));
|
||||||
|
/* Safe area for iOS */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content Area */
|
||||||
|
#main-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
/* Space for nav */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive Elements */
|
||||||
|
.nav-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn.active {
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
background: rgba(0, 243, 255, 0.1);
|
||||||
|
box-shadow: 0 0 15px rgba(0, 243, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes pulse-glow {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 5px rgba(0, 243, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 20px rgba(0, 243, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 5px rgba(0, 243, 255, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Water Tracker Module Styles --- */
|
||||||
|
|
||||||
|
.water-tracker-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-container {
|
||||||
|
position: relative;
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4px solid var(--bg-panel);
|
||||||
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
/* Clips the wave */
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.water-circle {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wave Animation */
|
||||||
|
.wave {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
/* Dynamic */
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: rgba(0, 243, 255, 0.4);
|
||||||
|
border-radius: 40%;
|
||||||
|
animation: rotate 6s linear infinite;
|
||||||
|
transition: top 1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 243, 255, 0.3);
|
||||||
|
/* Lighter top layer */
|
||||||
|
border-radius: 40%;
|
||||||
|
animation: rotate 10s linear infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 2;
|
||||||
|
/* Above wave */
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.water-percentage {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.water-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-row {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Controls */
|
||||||
|
.controls-area {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottle-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottle-selector input {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border: 1px solid var(--primary-cyan);
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 243, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottle-selector input:focus {
|
||||||
|
box-shadow: 0 0 15px rgba(0, 243, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
color: var(--text-muted);
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn.secondary {
|
||||||
|
border-color: rgba(255, 50, 50, 0.3);
|
||||||
|
color: #ff4d4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:active {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn {
|
||||||
|
position: relative;
|
||||||
|
background: rgba(0, 243, 255, 0.1);
|
||||||
|
border: 1px solid var(--primary-cyan);
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0 40px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 243, 255, 0.2), inset 0 0 20px rgba(0, 243, 255, 0.1);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(0, 243, 255, 0.4), transparent);
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn:hover {
|
||||||
|
background: rgba(0, 243, 255, 0.2);
|
||||||
|
box-shadow: 0 0 30px rgba(0, 243, 255, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 8px rgba(0, 243, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-btn svg {
|
||||||
|
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Streak Module Styles --- */
|
||||||
|
.streak-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--secondary-purple);
|
||||||
|
letter-spacing: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.streak-counter {
|
||||||
|
background: radial-gradient(circle, rgba(188, 19, 254, 0.1) 0%, transparent 70%);
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.streak-days {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 6rem;
|
||||||
|
color: white;
|
||||||
|
line-height: 1;
|
||||||
|
text-shadow: 0 0 20px rgba(188, 19, 254, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.streak-label {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.streak-detail {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-card {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-left: 3px solid var(--primary-cyan);
|
||||||
|
padding: 20px;
|
||||||
|
font-style: italic;
|
||||||
|
color: #dedede;
|
||||||
|
margin: 20px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-btn {
|
||||||
|
position: relative;
|
||||||
|
background: rgba(255, 50, 50, 0.1);
|
||||||
|
border: 1px solid #ff4d4d;
|
||||||
|
color: #ff4d4d;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
padding: 30px 20px 15px;
|
||||||
|
/* Increased top padding for camera cutout */
|
||||||
|
border-radius: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
margin-top: 30px;
|
||||||
|
box-shadow: 0 0 10px rgba(255, 50, 50, 0.2), inset 0 0 20px rgba(255, 50, 50, 0.1);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-btn:hover {
|
||||||
|
background: rgba(255, 50, 50, 0.2);
|
||||||
|
box-shadow: 0 0 30px rgba(255, 50, 50, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 8px rgba(255, 50, 50, 0.8);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Connect Button (Neon Cyan Variant) --- */
|
||||||
|
.connect-glow-btn {
|
||||||
|
position: relative;
|
||||||
|
background: rgba(0, 243, 255, 0.1);
|
||||||
|
border: 1px solid var(--primary-cyan);
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
padding: 8px 24px;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 0 10px rgba(0, 243, 255, 0.2), inset 0 0 10px rgba(0, 243, 255, 0.1);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-glow-btn:hover {
|
||||||
|
background: rgba(0, 243, 255, 0.2);
|
||||||
|
box-shadow: 0 0 20px rgba(0, 243, 255, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 5px rgba(0, 243, 255, 0.8);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-glow-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Concentric Rings --- */
|
||||||
|
.rings-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: radial-gradient(circle, rgba(255, 255, 255, 0.02) 0%, transparent 70%);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concentric-svg {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-bg {
|
||||||
|
fill: none;
|
||||||
|
stroke: rgba(255, 255, 255, 0.05);
|
||||||
|
/* Proper track color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-progress {
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
transition: stroke-dashoffset 1.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-progress.cyan {
|
||||||
|
stroke: var(--primary-cyan);
|
||||||
|
filter: drop-shadow(0 0 5px var(--primary-cyan));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-progress.purple {
|
||||||
|
stroke: var(--secondary-purple);
|
||||||
|
filter: drop-shadow(0 0 5px var(--secondary-purple));
|
||||||
|
}
|
||||||
|
|
||||||
|
.rings-legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.cyan {
|
||||||
|
background: var(--primary-cyan);
|
||||||
|
box-shadow: 0 0 5px var(--primary-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.purple {
|
||||||
|
background: var(--secondary-purple);
|
||||||
|
box-shadow: 0 0 5px var(--secondary-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Combined Stat Card & Charts --- */
|
||||||
|
.stat-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-sub {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-box {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cyan-box {
|
||||||
|
background: rgba(0, 243, 255, 0.1);
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple-box {
|
||||||
|
background: rgba(188, 19, 254, 0.1);
|
||||||
|
color: var(--secondary-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Re-using chart styles but refining for the box */
|
||||||
|
.chart-container.small {
|
||||||
|
height: 100px;
|
||||||
|
margin-top: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar {
|
||||||
|
width: 6px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: height 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar.cyan {
|
||||||
|
background: var(--primary-cyan);
|
||||||
|
box-shadow: 0 0 5px rgba(0, 243, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar.purple {
|
||||||
|
background: var(--secondary-purple);
|
||||||
|
box-shadow: 0 0 5px rgba(188, 19, 254, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-day {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Calendar Module Styles --- */
|
||||||
|
.calendar-wrapper {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 350px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-nav-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 10px;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-nav-btn:hover {
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-day-label {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-day {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
position: relative;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-day.active {
|
||||||
|
background: rgba(188, 19, 254, 0.2);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(188, 19, 254, 0.2);
|
||||||
|
border: 1px solid rgba(188, 19, 254, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-day.start-date {
|
||||||
|
background: rgba(0, 243, 255, 0.2);
|
||||||
|
border-color: var(--primary-cyan);
|
||||||
|
box-shadow: 0 0 10px rgba(0, 243, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-day.today::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Modal Styles --- */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
background: rgba(10, 10, 20, 0.95);
|
||||||
|
/* Darker for readability */
|
||||||
|
border: 1px solid var(--primary-cyan);
|
||||||
|
box-shadow: 0 0 30px rgba(0, 243, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: 1px solid var(--glass-border);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn-small {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-group label {
|
||||||
|
display: block;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-input {
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
color: #fff;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-input:focus {
|
||||||
|
border-color: var(--primary-cyan);
|
||||||
|
box-shadow: 0 0 15px rgba(0, 243, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Fitness Schedule Styles --- */
|
||||||
|
.workout-card {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-card {
|
||||||
|
box-shadow: 0 0 20px rgba(0, 243, 255, 0.1);
|
||||||
|
border-color: rgba(0, 243, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-badge {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workout-type {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 1.8rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercise-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercise-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercise-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ex-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ex-meta {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
color: var(--primary-cyan);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ex-note {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-list {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sch-day {
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sch-type {
|
||||||
|
font-weight: 600;
|
||||||
|
}/ | ||||||