Login-Page für Newcomer + freigestelltes App-Icon im Hero

- icon_transparent.py: blauer Sky-Hintergrund per RGB-Schwelle entfernt
  (b > 0.55, b > r+0.05 …), trimmt auf Content-Bbox
- AppIconHero.imageset mit dem freigestellten Icon
- LoginView komplett neu strukturiert als ScrollView:
  - Hero: freigestelltes App-Icon (kein SF Symbol mehr) + Titel + Tagline
  - Pitch-Karte: vier Feature-Highlights (Gassi-Tracking, Community,
    Tagebuch, Giftköder-Alarm) für Leute, die banyaro nicht kennen
  - "Schon angemeldet?"-Karte mit dem klassischen Login-Form
  - "Neu hier?"-Karte mit kostenlos/DSGVO/DE-Hosting-Pitch und großem
    Register-Button mit person.crop.circle.badge.plus-Icon
This commit is contained in:
rene 2026-05-30 11:42:32 +02:00
parent 500a645bfd
commit 4ee84d5a1a
3 changed files with 123 additions and 38 deletions

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon-hero.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -7,42 +7,94 @@ struct LoginView: View {
@State private var password = "" @State private var password = ""
var body: some View { var body: some View {
VStack(spacing: 24) { ScrollView {
Spacer() VStack(spacing: 28) {
hero
pitch
loginCard
registerCard
}
.padding(.horizontal, 24)
.padding(.vertical, 32)
}
.scrollDismissesKeyboard(.interactively)
}
Image(systemName: "pawprint.fill") // MARK: - Hero
.font(.system(size: 80))
.foregroundStyle(Color.accentColor)
VStack(spacing: 6) { private var hero: some View {
VStack(spacing: 12) {
Image("AppIconHero")
.resizable()
.scaledToFit()
.frame(height: 120)
Text("Ban Yaro Go") Text("Ban Yaro Go")
.font(.largeTitle.bold()) .font(.largeTitle.bold())
Text("Melde dich mit deinem banyaro-Account an.") Text("Die deutschsprachige Hunde-Plattform — jetzt mit nativem GPS-Tracking.")
.font(.callout) .font(.callout)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} }
}
// MARK: - Pitch (für Neue)
private var pitch: some View {
VStack(alignment: .leading, spacing: 14) {
feature(icon: "map.fill", title: "Gassi-Touren aufzeichnen", subtitle: "GPS-Tracking auch im Hintergrund — mit Pause, Live Activity und HealthKit-Sync.")
feature(icon: "person.2.fill", title: "Hunde-Community", subtitle: "Gassi-Treffen, Tierärzte und Orte in deiner Nähe.")
feature(icon: "book.fill", title: "Tagebuch & Impfpass", subtitle: "Alles rund um deinen Hund an einem Ort.")
feature(icon: "exclamationmark.shield.fill", title: "Giftköder-Alarm", subtitle: "Warnungen aus deiner Region direkt aufs iPhone.")
}
.padding(18)
.frame(maxWidth: .infinity, alignment: .leading)
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 16))
}
private func feature(icon: String, title: String, subtitle: String) -> some View {
HStack(alignment: .top, spacing: 12) {
Image(systemName: icon)
.font(.title3)
.foregroundStyle(Color.accentColor)
.frame(width: 28)
VStack(alignment: .leading, spacing: 2) {
Text(title).font(.subheadline.bold())
Text(subtitle)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
// MARK: - Login (für bestehende User)
private var loginCard: some View {
VStack(spacing: 12) { VStack(spacing: 12) {
HStack {
Text("Schon angemeldet?")
.font(.headline)
Spacer()
}
TextField("E-Mail", text: $email) TextField("E-Mail", text: $email)
.textContentType(.emailAddress) .textContentType(.emailAddress)
.keyboardType(.emailAddress) .keyboardType(.emailAddress)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
.autocorrectionDisabled() .autocorrectionDisabled()
.padding() .padding()
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 12)) .background(.background, in: RoundedRectangle(cornerRadius: 12))
SecureField("Passwort", text: $password) SecureField("Passwort", text: $password)
.textContentType(.password) .textContentType(.password)
.padding() .padding()
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 12)) .background(.background, in: RoundedRectangle(cornerRadius: 12))
}
if let error = auth.errorMessage { if let error = auth.errorMessage {
Text(error) Text(error)
.font(.footnote) .font(.footnote)
.foregroundStyle(.red) .foregroundStyle(.red)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
} }
Button { Button {
@ -65,31 +117,43 @@ struct LoginView: View {
.background(Color.accentColor, in: RoundedRectangle(cornerRadius: 12)) .background(Color.accentColor, in: RoundedRectangle(cornerRadius: 12))
.foregroundStyle(.white) .foregroundStyle(.white)
.disabled(auth.isLoggingIn || email.isEmpty || password.isEmpty) .disabled(auth.isLoggingIn || email.isEmpty || password.isEmpty)
registrationHint
Spacer()
} }
.padding(.horizontal, 28) .padding(18)
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 16))
} }
private var registrationHint: some View { // MARK: - Register (für Neue)
VStack(spacing: 6) {
Text("Noch kein Account?") private var registerCard: some View {
VStack(spacing: 10) {
Text("Neu hier?")
.font(.headline)
Text("banyaro.app ist **kostenlos**, **DSGVO-konform** und wird in Deutschland gehostet — kein App-Store-Konto nötig.")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center)
if let url = URL(string: "https://banyaro.app/#settings?tab=register") { if let url = URL(string: "https://banyaro.app/#settings?tab=register") {
Link(destination: url) { Link(destination: url) {
HStack(spacing: 6) { HStack(spacing: 8) {
Text("Kostenlos bei banyaro.app registrieren") Image(systemName: "person.crop.circle.badge.plus")
.font(.footnote.bold()) Text("Kostenlos registrieren").bold()
Image(systemName: "arrow.up.right.square") Image(systemName: "arrow.up.right")
.font(.caption) .font(.caption.bold())
} }
.frame(maxWidth: .infinity, minHeight: 50)
}
.background(Color.accentColor.opacity(0.15), in: RoundedRectangle(cornerRadius: 12))
.foregroundStyle(Color.accentColor) .foregroundStyle(Color.accentColor)
} }
Text("Öffnet die Registrierung im Browser. Danach mit den neuen Zugangsdaten oben einloggen.")
.font(.caption2)
.foregroundStyle(.tertiary)
.multilineTextAlignment(.center)
} }
} .padding(18)
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 16))
} }
} }