diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2149134
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ClockulaApp.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ClockulaApp.kt
new file mode 100644
index 0000000..202e47c
--- /dev/null
+++ b/app/src/main/java/de/jeanlucmakiola/clockula/ClockulaApp.kt
@@ -0,0 +1,34 @@
+package de.jeanlucmakiola.clockula
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+import de.jeanlucmakiola.floret.crash.CrashConfig
+import de.jeanlucmakiola.floret.crash.CrashReporter
+
+/**
+ * Application entry point. Registered as android:name=".ClockulaApp".
+ *
+ * For now this is Hilt init plus crash capture. From M3 it also owns the launch-
+ * time alarm re-sync: an alarm's next fire time is resolved against the current
+ * zone, never stored absolutely (docs/PLAN.md §4), so anything that could have
+ * moved while the app was closed has to be re-resolved before the user can see
+ * a stale one.
+ */
+@HiltAndroidApp
+class ClockulaApp : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+ // Install first thing so startup crashes are captured too (privacy-
+ // respecting, on-device; the user submits the report by hand).
+ CrashReporter.install(
+ this,
+ CrashConfig(
+ appLabel = getString(R.string.app_name),
+ newIssueUrl = getString(R.string.report_issue_url),
+ chooseIssueUrl = getString(R.string.report_issue_url),
+ issueTitle = getString(R.string.crash_report_issue_title),
+ ),
+ )
+ }
+}
diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/MainActivity.kt b/app/src/main/java/de/jeanlucmakiola/clockula/MainActivity.kt
new file mode 100644
index 0000000..8009bf1
--- /dev/null
+++ b/app/src/main/java/de/jeanlucmakiola/clockula/MainActivity.kt
@@ -0,0 +1,103 @@
+package de.jeanlucmakiola.clockula
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import dagger.hilt.android.AndroidEntryPoint
+import de.jeanlucmakiola.clockula.ui.crash.CrashReportActivity
+import de.jeanlucmakiola.clockula.ui.theme.ClockulaTheme
+import de.jeanlucmakiola.floret.crash.CrashReportDialog
+import de.jeanlucmakiola.floret.crash.CrashReporter
+import de.jeanlucmakiola.floret.crash.submitCrashReport
+
+/**
+ * Single activity. M0 scaffolding: it brings up the theme and the crash surface
+ * and nothing else. The four-tab shell and the live pill (docs/PLAN.md §9) land
+ * in M4, and the theme starts following stored preferences once the data layer
+ * exists in M2 — until then [ClockulaTheme]'s own defaults (follow-system,
+ * dynamic colour) stand in.
+ */
+@AndroidEntryPoint
+class MainActivity : ComponentActivity() {
+
+ // A captured crash report awaiting the user's decision, surfaced as a dialog
+ // over the app on the next launch (the single-crash path). A startup
+ // crash-loop is handled out of band, before setContent — see below.
+ private var pendingCrashReport by mutableStateOf(null)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // If the app keeps crashing as it starts, the main UI can't be trusted
+ // to come up. Route to the standalone report screen instead of
+ // re-entering the crashing graph.
+ if (CrashReporter.isCrashLoop(this)) {
+ startActivity(Intent(this, CrashReportActivity::class.java))
+ finish()
+ return
+ }
+
+ enableEdgeToEdge()
+
+ // Surface a single captured crash as a dialog on the next launch.
+ if (CrashReporter.shouldPrompt(this)) pendingCrashReport = CrashReporter.pendingReport(this)
+
+ setContent {
+ ClockulaTheme {
+ Placeholder(Modifier.fillMaxSize())
+ pendingCrashReport?.let { report ->
+ CrashReportDialog(
+ report = report,
+ onSend = {
+ submitCrashReport(this@MainActivity, report)
+ CrashReporter.clearReport(this@MainActivity)
+ pendingCrashReport = null
+ },
+ onDismiss = {
+ CrashReporter.dismissPrompt(this@MainActivity)
+ pendingCrashReport = null
+ },
+ )
+ }
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ // A successful start breaks any loop; reset the timing trail so a later
+ // ordinary crash isn't mistaken for a loop.
+ CrashReporter.markHealthy(this)
+ }
+}
+
+/** Stands in for the app shell until M4. Proves the theme resolves and renders. */
+@Composable
+private fun Placeholder(modifier: Modifier = Modifier) {
+ Surface(modifier, color = MaterialTheme.colorScheme.background) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text(
+ text = stringResourceAppName(),
+ style = MaterialTheme.typography.displaySmall,
+ color = MaterialTheme.colorScheme.primary,
+ )
+ }
+ }
+}
+
+@Composable
+private fun stringResourceAppName(): String =
+ androidx.compose.ui.res.stringResource(R.string.app_name)
diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/crash/CrashReportActivity.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/crash/CrashReportActivity.kt
new file mode 100644
index 0000000..f43e960
--- /dev/null
+++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/crash/CrashReportActivity.kt
@@ -0,0 +1,61 @@
+package de.jeanlucmakiola.clockula.ui.crash
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.ui.Modifier
+import de.jeanlucmakiola.clockula.ui.theme.ClockulaTheme
+import de.jeanlucmakiola.floret.crash.CrashReportDialog
+import de.jeanlucmakiola.floret.crash.CrashReporter
+import de.jeanlucmakiola.floret.crash.submitCrashReport
+
+/**
+ * Standalone surface for a captured crash report. [MainActivity] routes here
+ * when it detects a startup crash-loop (see [CrashReporter.isCrashLoop]): the
+ * main UI can't be trusted to start, so this screen stays clear of the app's
+ * Hilt graph, DataStore-backed theme and Compose content — it only reads the
+ * report file and shows the kit's report dialog. Plain [ClockulaTheme] defaults
+ * (follow-system, dynamic colour) avoid touching anything that might be the
+ * cause of the crash. The reusable machinery lives in floret-kit's core-crash.
+ */
+class CrashReportActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val report = CrashReporter.pendingReport(this)
+ if (report == null) {
+ finish()
+ return
+ }
+ enableEdgeToEdge()
+ setContent {
+ ClockulaTheme {
+ // Opaque backdrop so the dialog doesn't float over a bare task.
+ Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.surface) {}
+ CrashReportDialog(
+ report = report,
+ onSend = {
+ submitCrashReport(this, report)
+ CrashReporter.clearReport(this)
+ finish()
+ },
+ onDismiss = {
+ CrashReporter.clearReport(this)
+ finish()
+ },
+ )
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ // Reaching this screen breaks the loop; reset the timing trail so a
+ // later ordinary crash isn't mistaken for a loop.
+ CrashReporter.markHealthy(this)
+ }
+}
diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/theme/Color.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/theme/Color.kt
new file mode 100644
index 0000000..6ebe076
--- /dev/null
+++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/theme/Color.kt
@@ -0,0 +1,51 @@
+package de.jeanlucmakiola.clockula.ui.theme
+
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Seed color anchoring the entire palette. A muted sage, completing the family's
+ * colour cycle: Calendula's cool slate ([0xFF5C6B7A]) and Agendula's warm mauve
+ * ([0xFF7A5C6B]) are the same three bytes rotated, and this is the third
+ * rotation.
+ *
+ * The byte rotation is not a coincidence of notation — it is exactly a 120° hue
+ * rotation. The three seeds sit at 210°, 330° and 90° at identical saturation
+ * and lightness, so the family reads as one palette turned, not three colours
+ * picked. The cycle closes at three; a fourth app needs a different idea.
+ */
+val ClockulaSeed: Color = Color(0xFF6B7A5C)
+
+/**
+ * Fallback light scheme for devices without dynamic color (API < 31) or when the
+ * user disables it. Dynamic color (API 31+) is the real path; these are
+ * Agendula's hand-picked approximation with its hue rotated the same 120°, so
+ * the siblings' schemes stay structurally identical rather than independently
+ * eyeballed.
+ */
+val ClockulaLightFallback = lightColorScheme(
+ primary = Color(0xFF5C6A4E),
+ onPrimary = Color(0xFFFFFFFF),
+ primaryContainer = Color(0xFFE8F6D9),
+ onPrimaryContainer = Color(0xFF1D2A10),
+ secondary = Color(0xFF627055),
+ onSecondary = Color(0xFFFFFFFF),
+ background = Color(0xFFFCFEFB),
+ onBackground = Color(0xFF1C1F1A),
+ surface = Color(0xFFFCFEFB),
+ onSurface = Color(0xFF1C1F1A),
+)
+
+val ClockulaDarkFallback = darkColorScheme(
+ primary = Color(0xFFCEE2BA),
+ onPrimary = Color(0xFF334025),
+ primaryContainer = Color(0xFF4A583B),
+ onPrimaryContainer = Color(0xFFE8F6D9),
+ secondary = Color(0xFFCAD8BC),
+ onSecondary = Color(0xFF313A28),
+ background = Color(0xFF141611),
+ onBackground = Color(0xFFE4E9E0),
+ surface = Color(0xFF141611),
+ onSurface = Color(0xFFE4E9E0),
+)
diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/theme/Theme.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/theme/Theme.kt
new file mode 100644
index 0000000..a9dea2e
--- /dev/null
+++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/theme/Theme.kt
@@ -0,0 +1,28 @@
+package de.jeanlucmakiola.clockula.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.runtime.Composable
+import de.jeanlucmakiola.floret.identity.FloretExpressiveTheme
+
+/**
+ * Clockula's theme: the family's [FloretExpressiveTheme] machinery (dynamic
+ * colour on API 31+, system light/dark, the standard motion scheme) fed with
+ * Clockula's own identity — its seed-derived fallback schemes
+ * ([ClockulaLightFallback] / [ClockulaDarkFallback]) and [ClockulaTypography].
+ * The mechanics live in floret-kit; the look stays here.
+ */
+@Composable
+fun ClockulaTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit,
+) {
+ FloretExpressiveTheme(
+ lightScheme = ClockulaLightFallback,
+ darkScheme = ClockulaDarkFallback,
+ darkTheme = darkTheme,
+ dynamicColor = dynamicColor,
+ typography = ClockulaTypography,
+ content = content,
+ )
+}
diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/theme/Type.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/theme/Type.kt
new file mode 100644
index 0000000..5f89ce9
--- /dev/null
+++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/theme/Type.kt
@@ -0,0 +1,10 @@
+package de.jeanlucmakiola.clockula.ui.theme
+
+import androidx.compose.material3.Typography
+
+/**
+ * Default Material 3 Expressive typography. A clock app is mostly one enormous
+ * number, so the readout scale is the real typographic decision here — it gets
+ * settled against a working stopwatch in M7, not guessed at in the scaffolding.
+ */
+val ClockulaTypography = Typography()
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..5912048
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..749e2e1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_notification.xml b/app/src/main/res/drawable/ic_notification.xml
new file mode 100644
index 0000000..8ebb832
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notification.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..b3e26b4
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..b3e26b4
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..080f756
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #FF6B7A5C
+
+ #FF6B7A5C
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..bbab5db
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+ Clockula
+ A modern Material 3 Expressive clock app.\nComing into bloom.
+
+
+ Crash report
+ https://codeberg.org/jlmakiola/clockula/issues/new
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..939bd76
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..87d1f20
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..c6ba7b9
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml
new file mode 100644
index 0000000..7381289
--- /dev/null
+++ b/app/src/main/res/xml/locales_config.xml
@@ -0,0 +1,11 @@
+
+
+
+
+