diff --git a/docs/AppStoreMetadata.md b/docs/AppStoreMetadata.md index de12d31..9123a88 100644 --- a/docs/AppStoreMetadata.md +++ b/docs/AppStoreMetadata.md @@ -37,8 +37,10 @@ Your data remains in Apple Health. LogWeight does not run its own weight databas ## Privacy URL -- Canonical policy source: `Docs/Privacy.md` -- App Store Connect requires a public HTTPS URL (host this text before submission). +- Canonical policy source: `docs/Privacy.md` +- Static page for hosting: `support/privacy/index.html`, served at `https://juuronina.de/LogWeight/support/privacy/` +- Linked from the support page (no external GitHub link) — keep in sync with `docs/Privacy.md`. +- App Store Connect requires a public HTTPS URL (publish this page before submission). ## Support URL diff --git a/docs/BuildAndDeploy.md b/docs/BuildAndDeploy.md index 5c11582..4e28db4 100644 --- a/docs/BuildAndDeploy.md +++ b/docs/BuildAndDeploy.md @@ -120,7 +120,7 @@ For a **major** semver bump, use one of: 3. When ready for a new user-facing version, merge the **Release PR** from release-please on `main` (updates `MARKETING_VERSION` and `CHANGELOG.md`; creates `vX.Y.Z` on GitHub). 4. Pull `main`, run `xcodegen generate`, archive in Xcode (_Product → Archive_). 5. Upload to App Store Connect. -6. Add a privacy policy URL in App Store Connect — see `docs/Privacy.md` for the canonical statement. +6. Add a privacy policy URL in App Store Connect — `support/privacy/index.html`, published at `https://juuronina.de/LogWeight/support/privacy/` (see `docs/AppStoreMetadata.md`), using `docs/Privacy.md` as the canonical statement. 7. Fill App Store listing fields from `docs/AppStoreMetadata.md`. 8. Use localized listing variants from `docs/AppStoreMetadata.localized.md`. 9. Walk through `docs/AppStoreReleaseChecklist.md` before pressing Submit for Review. diff --git a/support/assets/logo.png b/support/assets/logo.png new file mode 100644 index 0000000..20b4dc2 Binary files /dev/null and b/support/assets/logo.png differ diff --git a/support/assets/style.css b/support/assets/style.css new file mode 100644 index 0000000..c1db217 --- /dev/null +++ b/support/assets/style.css @@ -0,0 +1,256 @@ +:root { + color-scheme: light dark; + --teal: #275561; + --teal-strong: #1d424c; + --header-grad-a: #1e434c; + --header-grad-b: #2f6672; + --white-glyph: #f5f5f5; + --paper: #faf9f6; + --surface: #ffffff; + --ink: #1b1f1e; + --muted: #5c6663; + --accent: var(--teal); + --border: rgba(27, 31, 30, 0.14); + --font-body: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif; + --font-mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace; +} + +@media (prefers-color-scheme: dark) { + :root { + --header-grad-a: #234c56; + --header-grad-b: #3c7986; + --paper: #0e2126; + --surface: #163138; + --ink: #f1f4f3; + --muted: #9fb4b1; + --accent: #7fb8c4; + --border: rgba(255, 255, 255, 0.14); + } +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + background: var(--paper); + color: var(--ink); + font: 16px/1.6 var(--font-body); + -webkit-font-smoothing: antialiased; +} + +a { + color: var(--accent); + text-decoration-color: var(--border); + text-underline-offset: 2px; +} + +a:hover { + text-decoration-thickness: 2px; +} + +a:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; + border-radius: 2px; +} + +/* letterhead bar */ +.site-header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.6rem 1.25rem; + max-width: 44rem; + margin: 1.5rem auto 0; + padding: 0.85rem 1.1rem; + background: linear-gradient(135deg, var(--header-grad-a), var(--header-grad-b)); + border-radius: 10px; +} + +.wordmark { + display: inline-flex; + align-items: center; + font-size: 1.05rem; + font-weight: 700; + letter-spacing: -0.01em; + color: var(--white-glyph); + text-decoration: none; +} + +.mark { + display: inline-block; + width: 26px; + height: 26px; + margin-right: 9px; + border-radius: 7px; + border: 1px solid rgba(245, 245, 245, 0.2); + vertical-align: middle; +} + +.site-nav { + display: flex; + gap: 1.1rem; + font-size: 0.88rem; +} + +.site-nav a { + color: rgba(245, 245, 245, 0.72); + text-decoration: none; + padding-bottom: 2px; + border-bottom: 1px solid transparent; +} + +.site-nav a:hover { + color: var(--white-glyph); +} + +.site-nav a.active { + color: var(--white-glyph); + border-bottom-color: var(--white-glyph); +} + +.site-nav a:focus-visible { + outline-color: var(--white-glyph); +} + +main { + max-width: 44rem; + margin: 0 auto; + padding: 2rem 1.25rem 4rem; +} + +.eyebrow { + display: block; + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--accent); + margin-bottom: 0.5rem; +} + +h1 { + font-size: 1.9rem; + font-weight: 700; + line-height: 1.2; + letter-spacing: -0.01em; + margin: 0 0 0.75rem; +} + +h2 { + font-size: 1.15rem; + font-weight: 700; + line-height: 1.3; + margin: 2rem 0 0.75rem; + padding-top: 1.25rem; + border-top: 1px solid var(--border); +} + +.card h2 { + border-top: none; + padding-top: 0; + margin-top: 0; +} + +h3 { + font-size: 0.98rem; + font-weight: 700; + margin: 1.5rem 0 0.5rem; +} + +p, +li { + margin: 0.75rem 0; + color: var(--ink); +} + +.updated, +.intro { + color: var(--muted); +} + +.updated { + font-size: 0.9rem; +} + +ul, +ol { + padding-left: 1.3rem; +} + +li::marker { + color: var(--muted); +} + +code { + font-family: var(--font-mono); + font-size: 0.9em; + background: var(--border); + padding: 0.1em 0.35em; + border-radius: 4px; +} + +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 1.5rem 1.5rem 1.75rem; + margin: 1.5rem 0; +} + +.card-head { + display: flex; + align-items: center; + gap: 0.6rem; + margin-bottom: 0.25rem; +} + +.card-head h2 { + margin: 0; +} + +.chip { + font-family: var(--font-mono); + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.04em; + color: var(--white-glyph); + background: var(--teal); + border-radius: 4px; + padding: 0.15rem 0.4rem; +} + +article.doc { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 1.75rem 1.75rem 2rem; +} + +.page-footer { + max-width: 44rem; + margin: 0 auto; + padding: 0 1.25rem 3rem; + color: var(--muted); + font-size: 0.85rem; +} + +@media (max-width: 30rem) { + article.doc, + .card { + padding: 1.25rem; + } + + h1 { + font-size: 1.6rem; + } +} + +@media (prefers-reduced-motion: reduce) { + * { + transition: none !important; + } +} diff --git a/support/index.html b/support/index.html index bb8276f..48361aa 100644 --- a/support/index.html +++ b/support/index.html @@ -3,106 +3,82 @@ - LogWeight Support - + LogWeight Support | Hilfe + -

LogWeight Support

-

Deutsch / English

+ + +
+ Support +

LogWeight Support

+

Deutsch / English — choose your language below.

+ +
+
DE

Deutsch

+

+ Du brauchst Hilfe mit LogWeight? Kontaktiere den Support direkt per + E-Mail: + support@juuronina.de +

+ +
-
-

Deutsch

-

- Du brauchst Hilfe mit LogWeight? Kontaktiere den Support direkt per - E-Mail: - support@juuronina.de -

- -
+
+
EN

English

+

+ Need help with LogWeight? Contact support directly by email: + support@juuronina.de +

+ +
+
-
-

English

-

- Need help with LogWeight? Contact support directly by email: - support@juuronina.de -

- -
+ diff --git a/support/instructions/index.html b/support/instructions/index.html index a64ae08..566b1cd 100644 --- a/support/instructions/index.html +++ b/support/instructions/index.html @@ -4,35 +4,33 @@ LogWeight Support Instructions - + -

LogWeight Support Instructions

-

- These steps use the Health app on iPhone or iPad. On Apple Watch, manage - Health data through the paired iPhone's Health app. -

-
    -
  1. Open the Health app on your device.
  2. -
  3. For export, tap your profile photo, then tap Export All Health Data. This exports all Health data, not only LogWeight entries.
  4. -
  5. For deletion, delete values in Health and revoke LogWeight access in Settings → Health.
  6. -
-

Return to LogWeight Support

+ + +
+ Instructions +

Export & Delete Your Data

+

LogWeight keeps no copy of your data — everything lives in Apple Health.

+

These steps use the Health app on iPhone or iPad. On Apple Watch, manage Health data through the paired iPhone's Health app.

+ +
+
    +
  1. Open the Health app on your device.
  2. +
  3. For export, tap your profile photo, then tap Export All Health Data. This exports all Health data, not only LogWeight entries.
  4. +
  5. For deletion, delete values in Health and revoke LogWeight access in Settings → Health.
  6. +
+
+
+ + diff --git a/support/privacy/index.html b/support/privacy/index.html new file mode 100644 index 0000000..11a6535 --- /dev/null +++ b/support/privacy/index.html @@ -0,0 +1,104 @@ + + + + + + LogWeight Privacy Policy + + + + + +
+ Privacy policy +

Privacy

+

Last updated: 2026-07-10 · Canonical source: docs/Privacy.md

+ +
+

LogWeight handles body weight, which is GDPR Art. 9 special-category personal data. The privacy posture is documented here so reviewers, auditors, and future contributors share the same understanding.

+ +

Data flow

+
    +
  1. The user enters a weight in the app.
  2. +
  3. The value is held in memory (@State) only for the lifetime of the entry surface.
  4. +
  5. On Save, the value is written to HKHealthStore via HKQuantityTypeIdentifier.bodyMass.
  6. +
  7. The local copy is discarded; subsequent reads go back through HealthKit.
  8. +
+ +

The app:

+ + +

Cross-device synchronisation, when the user has more than one device, is handled by Apple Health itself.

+ +

Lawful basis

+

The lawful basis for processing under GDPR is explicit consent, gathered through the standard HealthKit authorisation sheet. The user can revoke consent at any time in Settings → Health → Data Access & Devices → LogWeight. Once revoked, future writes will fail and the app surfaces a calm prompt to re-enable.

+ +

Data subject rights

+

Because the app holds no copy of the user's data:

+ + +

Apple Privacy Manifest

+

App/iOS/Resources/PrivacyInfo.xcprivacy declares:

+ + +

On-screen redaction

+

The app implements two layers of on-screen redaction:

+
    +
  1. .privacySensitive() — the SwiftUI modifier that asks the OS to redact content in system-generated screenshots (app switcher, Siri suggestions, Lock-Screen previews).
  2. +
  3. PrivacyRedactionModifier — a custom modifier that overlays an opaque view when the app is backgrounded OR when UIScreen.main.isCaptured reports an active screen recording.
  4. +
+ +

Known limitations of UIScreen.isCaptured

+

UIScreen.isCaptured is documented by Apple to not fire reliably in every capture scenario. We acknowledge:

+ + +

These are Apple-framework limitations, not defects in LogWeight. The .privacySensitive() modifier handles every system-generated capture scenario regardless of these gaps.

+ +

Data export and deletion (operational)

+

If a user requests their data:

+ + +

If a user requests deletion:

+ + +

Logging

+

SecurityLog (in LogWeightCore) is the only logging facade. It exposes event(_: StaticString) and error(_: StaticString, code: Int) — and nothing else. There is no API path through which a weight value, a user identifier, a sample identifier, or a date can be logged.

+
+
+ + + +