diff --git a/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..04056a5
--- /dev/null
+++ b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "vision",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Contents.json b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Contents.json
new file mode 100644
index 0000000..bb816da
--- /dev/null
+++ b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Contents.json
@@ -0,0 +1,14 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "layers" : [
+ {
+ "filename" : "Front.solidimagestacklayer"
+ },
+ {
+ "filename" : "Back.solidimagestacklayer"
+ }
+ ]
+}
diff --git a/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..04056a5
--- /dev/null
+++ b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "vision",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/trivit Vision/Images.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/trivit Vision/Images.xcassets/Contents.json b/trivit Vision/Images.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/trivit Vision/Images.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/trivit Vision/Info.plist b/trivit Vision/Info.plist
new file mode 100644
index 0000000..165cfc2
--- /dev/null
+++ b/trivit Vision/Info.plist
@@ -0,0 +1,29 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ Trivit
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 5.0.1
+ CFBundleVersion
+ 2
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+
+
+
diff --git a/trivit Vision/Models/Trivit.swift b/trivit Vision/Models/Trivit.swift
new file mode 100644
index 0000000..d64fd09
--- /dev/null
+++ b/trivit Vision/Models/Trivit.swift
@@ -0,0 +1,56 @@
+//
+// Trivit.swift
+// trivit Vision
+//
+// Simplified SwiftData model for visionOS (matches watch app pattern)
+//
+
+import Foundation
+import SwiftData
+
+@Model
+final class Trivit: Equatable {
+ var id: UUID
+ var title: String
+ var count: Int
+ var colorIndex: Int
+ var isCollapsed: Bool
+ var createdAt: Date
+ var sortOrder: Int
+
+ init(
+ id: UUID = UUID(),
+ title: String = "New Trivit",
+ count: Int = 0,
+ colorIndex: Int = 0,
+ isCollapsed: Bool = true,
+ createdAt: Date = Date(),
+ sortOrder: Int = 0
+ ) {
+ self.id = id
+ self.title = title
+ self.count = count
+ self.colorIndex = colorIndex
+ self.isCollapsed = isCollapsed
+ self.createdAt = createdAt
+ self.sortOrder = sortOrder
+ }
+
+ func increment() {
+ count += 1
+ }
+
+ func decrement() {
+ if count > 0 {
+ count -= 1
+ }
+ }
+
+ func reset() {
+ count = 0
+ }
+
+ static func == (lhs: Trivit, rhs: Trivit) -> Bool {
+ lhs.id == rhs.id
+ }
+}
diff --git a/trivit Vision/Theme/TrivitColors.swift b/trivit Vision/Theme/TrivitColors.swift
new file mode 100644
index 0000000..cdae667
--- /dev/null
+++ b/trivit Vision/Theme/TrivitColors.swift
@@ -0,0 +1,62 @@
+//
+// TrivitColors.swift
+// trivit Vision
+//
+// Color themes for the visionOS app - matching iOS app design
+//
+
+import SwiftUI
+
+struct TrivitColors {
+ static let colorCount = 10
+
+ // Main color palette (flat design inspired) - same as iOS app
+ static let palette: [Color] = [
+ Color(hex: "1ABC9C"), // Turquoise
+ Color(hex: "2ECC71"), // Emerald
+ Color(hex: "3498DB"), // Peter River
+ Color(hex: "9B59B6"), // Amethyst
+ Color(hex: "E74C3C"), // Alizarin
+ Color(hex: "F39C12"), // Orange
+ Color(hex: "E91E63"), // Pink
+ Color(hex: "00BCD4"), // Cyan
+ Color(hex: "8BC34A"), // Light Green
+ Color(hex: "FF5722"), // Deep Orange
+ ]
+
+ static func color(at index: Int) -> Color {
+ let safeIndex = abs(index) % palette.count
+ return palette[safeIndex]
+ }
+
+ static func randomColorIndex() -> Int {
+ Int.random(in: 0..> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
+ case 6: // RGB (24-bit)
+ (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
+ case 8: // ARGB (32-bit)
+ (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
+ default:
+ (a, r, g, b) = (1, 1, 1, 0)
+ }
+ self.init(
+ .sRGB,
+ red: Double(r) / 255,
+ green: Double(g) / 255,
+ blue: Double(b) / 255,
+ opacity: Double(a) / 255
+ )
+ }
+}
diff --git a/trivit Vision/TrivitVisionApp.entitlements b/trivit Vision/TrivitVisionApp.entitlements
new file mode 100644
index 0000000..2158655
--- /dev/null
+++ b/trivit Vision/TrivitVisionApp.entitlements
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.developer.icloud-services
+
+ CloudKit
+
+ com.apple.developer.icloud-container-identifiers
+
+ iCloud.com.wouterdevriendt.trivit
+
+
+
diff --git a/trivit Vision/TrivitVisionApp.swift b/trivit Vision/TrivitVisionApp.swift
new file mode 100644
index 0000000..b6a8d2d
--- /dev/null
+++ b/trivit Vision/TrivitVisionApp.swift
@@ -0,0 +1,64 @@
+//
+// TrivitVisionApp.swift
+// trivit Vision
+//
+// visionOS app entry point - syncs with iOS via CloudKit
+//
+
+import SwiftUI
+import SwiftData
+import os.log
+
+private let logger = Logger(subsystem: "com.wouterdevriendt.trivit.vision", category: "VisionApp")
+
+@main
+struct TrivitVisionApp: App {
+ // Check for sample data mode (for screenshots)
+ private static var isSampleDataMode: Bool {
+ ProcessInfo.processInfo.arguments.contains("-SampleDataMode")
+ }
+
+ var sharedModelContainer: ModelContainer = {
+ let schema = Schema([Trivit.self])
+
+ let modelConfiguration = ModelConfiguration(
+ schema: schema,
+ isStoredInMemoryOnly: isSampleDataMode,
+ cloudKitDatabase: isSampleDataMode ? .none : .automatic
+ )
+
+ do {
+ logger.info("🥽 Creating ModelContainer for visionOS app")
+ let container = try ModelContainer(for: schema, configurations: [modelConfiguration])
+
+ if isSampleDataMode {
+ logger.info("🥽 Sample data mode - creating sample trivits")
+ let context = container.mainContext
+ let sampleTrivits = [
+ Trivit(title: "Glasses of water", count: 7, colorIndex: 0, sortOrder: 0),
+ Trivit(title: "Push-ups done", count: 42, colorIndex: 1, sortOrder: 1),
+ Trivit(title: "Books read", count: 3, colorIndex: 2, sortOrder: 2),
+ Trivit(title: "Meditation", count: 15, colorIndex: 4, sortOrder: 3),
+ Trivit(title: "Coffee cups", count: 5, colorIndex: 5, sortOrder: 4),
+ Trivit(title: "Steps walked", count: 12, colorIndex: 3, sortOrder: 5),
+ ]
+ for trivit in sampleTrivits {
+ context.insert(trivit)
+ }
+ try? context.save()
+ }
+
+ return container
+ } catch {
+ logger.error("🥽 Failed to create ModelContainer: \(error)")
+ fatalError("Could not create ModelContainer: \(error)")
+ }
+ }()
+
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ .modelContainer(sharedModelContainer)
+ }
+}
diff --git a/trivit Vision/Views/AddTrivitView.swift b/trivit Vision/Views/AddTrivitView.swift
new file mode 100644
index 0000000..e5d6b94
--- /dev/null
+++ b/trivit Vision/Views/AddTrivitView.swift
@@ -0,0 +1,84 @@
+//
+// AddTrivitView.swift
+// trivit Vision
+//
+// Sheet for creating a new counter
+//
+
+import SwiftUI
+import SwiftData
+
+struct AddTrivitView: View {
+ @Environment(\.modelContext) private var modelContext
+ @Environment(\.dismiss) private var dismiss
+ @Query(sort: \Trivit.sortOrder) private var trivits: [Trivit]
+ @State private var title = ""
+ @State private var selectedColorIndex = 0
+
+ var body: some View {
+ NavigationStack {
+ Form {
+ Section("Name") {
+ TextField("Counter name", text: $title)
+ }
+
+ Section("Color") {
+ LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 5), spacing: 12) {
+ ForEach(0.. 0 {
+ TallyMarksView(count: trivit.count)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .frame(minHeight: 24)
+ }
+
+ // +/- buttons
+ HStack(spacing: 16) {
+ Button {
+ trivit.decrement()
+ } label: {
+ Image(systemName: "minus")
+ .font(.title3.weight(.semibold))
+ .frame(width: 44, height: 44)
+ }
+ .buttonStyle(.bordered)
+ .disabled(trivit.count == 0)
+ .hoverEffect(.highlight)
+
+ Spacer()
+
+ Button {
+ trivit.increment()
+ } label: {
+ Image(systemName: "plus")
+ .font(.title3.weight(.semibold))
+ .frame(width: 44, height: 44)
+ }
+ .buttonStyle(.borderedProminent)
+ .tint(accentColor)
+ .hoverEffect(.highlight)
+ }
+ }
+ .padding(20)
+ }
+ .glassBackgroundEffect()
+ .clipShape(RoundedRectangle(cornerRadius: 16))
+ .hoverEffect(.highlight)
+ .contextMenu {
+ Button {
+ isEditing = true
+ } label: {
+ Label("Rename", systemImage: "pencil")
+ }
+ Button {
+ trivit.colorIndex = (trivit.colorIndex + 1) % TrivitColors.colorCount
+ } label: {
+ Label("Change Color", systemImage: "paintpalette")
+ }
+ Button(role: .destructive) {
+ showResetConfirmation = true
+ } label: {
+ Label("Reset Count", systemImage: "arrow.counterclockwise")
+ }
+ Divider()
+ Button(role: .destructive) {
+ modelContext.delete(trivit)
+ } label: {
+ Label("Delete", systemImage: "trash")
+ }
+ }
+ .confirmationDialog("Reset Counter?", isPresented: $showResetConfirmation) {
+ Button("Reset to Zero", role: .destructive) {
+ trivit.reset()
+ }
+ Button("Cancel", role: .cancel) {}
+ }
+ }
+}
+
+#Preview {
+ CounterCardView(trivit: Trivit(title: "Push-ups", count: 42, colorIndex: 1))
+ .frame(width: 320)
+ .modelContainer(for: [Trivit.self], inMemory: true)
+}
diff --git a/trivit Vision/Views/TallyMarksView.swift b/trivit Vision/Views/TallyMarksView.swift
new file mode 100644
index 0000000..303c3be
--- /dev/null
+++ b/trivit Vision/Views/TallyMarksView.swift
@@ -0,0 +1,71 @@
+//
+// TallyMarksView.swift
+// trivit Vision
+//
+// Western-style tally marks for visionOS spatial UI
+//
+
+import SwiftUI
+
+struct TallyMarksView: View {
+ let count: Int
+ private let groupsPerRow = 10
+
+ var body: some View {
+ let fullGroups = count / 5
+ let remainder = count % 5
+ let totalGroups = fullGroups + (remainder > 0 ? 1 : 0)
+ let rows = max(1, (totalGroups + groupsPerRow - 1) / groupsPerRow)
+
+ VStack(alignment: .leading, spacing: 8) {
+ ForEach(0..