From 07fe3005a19276d632a077418d07a3127e73fb08 Mon Sep 17 00:00:00 2001 From: Dotoryman <134985085+Dotoryman@users.noreply.github.com> Date: Thu, 24 Sep 2026 23:32:16 +0900 Subject: [PATCH 1/2] Ground acorns inside the glass jar floor --- CHANGELOG.md | 8 +++ DotoryCount.xcodeproj/project.pbxproj | 24 +++---- .../Features/Home/PhotographicJarScene.swift | 72 ++++++++++++++----- README.md | 4 +- 4 files changed, 76 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48c2288..df5b053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to DotoryCount are recorded in this file. +## [0.4.2] - 2026-09-24 + +- Place the acorn pile behind the photographed front half of the glass floor + with a soft curved occlusion edge, so bottom acorns no longer show through + the thick base or appear outside the jar. +- Add a contact shadow along the inner floor to ground sparse and full piles. +- Bumped the app to version 0.4.2 (build 10). + ## [0.4.1] - 2026-09-24 - Keep the iPhone experience in portrait, where the photographic jar and date diff --git a/DotoryCount.xcodeproj/project.pbxproj b/DotoryCount.xcodeproj/project.pbxproj index 5110ddb..08c9f02 100644 --- a/DotoryCount.xcodeproj/project.pbxproj +++ b/DotoryCount.xcodeproj/project.pbxproj @@ -396,7 +396,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 10; DEVELOPMENT_TEAM = W395S42ZJ9; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -409,7 +409,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.4.1; + MARKETING_VERSION = 0.4.2; PRODUCT_BUNDLE_IDENTIFIER = com.dotoryman.dotorycount; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -428,7 +428,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 10; DEVELOPMENT_TEAM = W395S42ZJ9; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -441,7 +441,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.4.1; + MARKETING_VERSION = 0.4.2; PRODUCT_BUNDLE_IDENTIFIER = com.dotoryman.dotorycount; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -459,10 +459,10 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 10; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.0; - MARKETING_VERSION = 0.4.1; + MARKETING_VERSION = 0.4.2; PRODUCT_BUNDLE_IDENTIFIER = com.dotoryman.dotorycount.tests; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = NO; @@ -480,10 +480,10 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 10; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.0; - MARKETING_VERSION = 0.4.1; + MARKETING_VERSION = 0.4.2; PRODUCT_BUNDLE_IDENTIFIER = com.dotoryman.dotorycount.tests; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = NO; @@ -500,9 +500,9 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 10; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 0.4.1; + MARKETING_VERSION = 0.4.2; PRODUCT_BUNDLE_IDENTIFIER = com.dotoryman.dotorycount.uitests; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = NO; @@ -519,9 +519,9 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 10; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 0.4.1; + MARKETING_VERSION = 0.4.2; PRODUCT_BUNDLE_IDENTIFIER = com.dotoryman.dotorycount.uitests; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = NO; diff --git a/DotoryCount/Features/Home/PhotographicJarScene.swift b/DotoryCount/Features/Home/PhotographicJarScene.swift index 2c77475..9132c76 100644 --- a/DotoryCount/Features/Home/PhotographicJarScene.swift +++ b/DotoryCount/Features/Home/PhotographicJarScene.swift @@ -144,9 +144,9 @@ struct PhotographicJarScene: View { .frame(width: width, height: height) Ellipse() - .fill(.black.opacity(0.20)) - .frame(width: 510 * scale, height: 108 * scale) - .blur(radius: 31 * scale) + .fill(.black.opacity(0.32)) + .frame(width: 510 * scale, height: 82 * scale) + .blur(radius: 18 * scale) .position(x: 470 * scale, y: 1282 * scale) Ellipse() @@ -155,30 +155,46 @@ struct PhotographicJarScene: View { .blur(radius: 54 * scale) .position(x: 470 * scale, y: 1130 * scale) - ForEach(settledIndices, id: \.self) { index in - acorn(at: PhotographicJarStages.placements[index], - golden: isGolden(index: index), scale: scale) - } + ZStack(alignment: .topLeading) { + ForEach(settledIndices, id: \.self) { index in + acorn(at: PhotographicJarStages.placements[index], + golden: isGolden(index: index), scale: scale) + } - if stage > 0 { - let placement = PhotographicJarStages.placements[stage - 1] - TimelineView(.animation(minimumInterval: 1.0 / 60, paused: !isAnimating)) { timeline in - let elapsed = timeline.date.timeIntervalSince(dropStartedAt) - let motion = isAnimating ? min(max(elapsed / JarDropTiming.duration, 0), 1) : 1 - let offset = fallingOffset(placement, progress: motion) - acorn(at: placement, - golden: isGolden(index: stage - 1), - scale: scale, - offset: offset, - extraRotation: fallingRotation(progress: motion)) + if stage > 0 { + let placement = PhotographicJarStages.placements[stage - 1] + TimelineView(.animation(minimumInterval: 1.0 / 60, paused: !isAnimating)) { timeline in + let elapsed = timeline.date.timeIntervalSince(dropStartedAt) + let motion = isAnimating ? min(max(elapsed / JarDropTiming.duration, 0), 1) : 1 + let offset = fallingOffset(placement, progress: motion) + acorn(at: placement, + golden: isGolden(index: stage - 1), + scale: scale, + offset: offset, + extraRotation: fallingRotation(progress: motion)) + } } } + .frame(width: width, height: height) + .mask( + JarInteriorFloorMask() + .fill(.white) + .blur(radius: 18 * scale) + .frame(width: width, height: height) + ) Image("JarGlassFront") .resizable() .frame(width: width, height: height) .allowsHitTesting(false) + Ellipse() + .fill(.black.opacity(0.15)) + .frame(width: 500 * scale, height: 42 * scale) + .blur(radius: 17 * scale) + .position(x: 470 * scale, y: 1283 * scale) + .allowsHitTesting(false) + LinearGradient( colors: [.clear, .white.opacity(0.035), .cyan.opacity(0.07)], startPoint: .top, @@ -293,3 +309,23 @@ struct PhotographicJarScene: View { } } } + +/// The front half of the thick glass base must hide any nut silhouette that +/// passes below the inner floor. The curve follows the jar's photographed oval. +private struct JarInteriorFloorMask: Shape { + func path(in rect: CGRect) -> Path { + let scale = rect.width / 941 + var path = Path() + path.move(to: .zero) + path.addLine(to: CGPoint(x: rect.width, y: 0)) + for step in stride(from: 941, through: 0, by: -5) { + let x = CGFloat(step) + let distance = x - 470 + let floorY = 1284 - 0.00055 * distance * distance + path.addLine(to: CGPoint(x: x * scale, y: floorY * scale)) + } + path.addLine(to: CGPoint(x: 0, y: (1284 - 0.00055 * 470 * 470) * scale)) + path.closeSubpath() + return path + } +} diff --git a/README.md b/README.md index 1a45d19..956894c 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ An iOS app that records meaningful time by adding one acorn to a jar each day. **언어 및 기술 · Languages & Technologies:** Swift · SwiftUI · SwiftData -v0.4.1: 사실적인 36단계 도토리 병, 낙하 애니메이션, 편리해진 기준일 선택. 아이폰은 세로 화면에 맞춰 표시됩니다. +v0.4.2: 사실적인 36단계 도토리 병, 자연스러운 유리 바닥 깊이, 낙하 애니메이션, 편리해진 기준일 선택. 아이폰은 세로 화면에 맞춰 표시됩니다. -v0.4.1: A 36-stage photographic jar, animated acorn drops, easier date selection, and a portrait iPhone layout. +v0.4.2: A 36-stage photographic jar with grounded acorns, animated drops, easier date selection, and a portrait iPhone layout. [변경 이력 · Changelog](CHANGELOG.md) · [개발 계획 · Roadmap](ROADMAP.md) From 4045e3e4ae136cf979214a1d4cff3629dcd148cb Mon Sep 17 00:00:00 2001 From: Dotoryman <134985085+Dotoryman@users.noreply.github.com> Date: Thu, 24 Sep 2026 23:43:25 +0900 Subject: [PATCH 2/2] Render acorns in front of the glass floor --- CHANGELOG.md | 6 +-- .../Features/Home/PhotographicJarScene.swift | 43 ++++++++----------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df5b053..088cde8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,9 @@ All notable changes to DotoryCount are recorded in this file. ## [0.4.2] - 2026-09-24 -- Place the acorn pile behind the photographed front half of the glass floor - with a soft curved occlusion edge, so bottom acorns no longer show through - the thick base or appear outside the jar. +- Keep the photographed glass floor behind the acorns while retaining the + front rim and side reflections. A softly feathered oval removes only the + foreground floor overlay that made acorns appear behind the base. - Add a contact shadow along the inner floor to ground sparse and full piles. - Bumped the app to version 0.4.2 (build 10). diff --git a/DotoryCount/Features/Home/PhotographicJarScene.swift b/DotoryCount/Features/Home/PhotographicJarScene.swift index 9132c76..19783e3 100644 --- a/DotoryCount/Features/Home/PhotographicJarScene.swift +++ b/DotoryCount/Features/Home/PhotographicJarScene.swift @@ -176,23 +176,16 @@ struct PhotographicJarScene: View { } } .frame(width: width, height: height) - .mask( - JarInteriorFloorMask() - .fill(.white) - .blur(radius: 18 * scale) - .frame(width: width, height: height) - ) Image("JarGlassFront") .resizable() .frame(width: width, height: height) - .allowsHitTesting(false) - - Ellipse() - .fill(.black.opacity(0.15)) - .frame(width: 500 * scale, height: 42 * scale) - .blur(radius: 17 * scale) - .position(x: 470 * scale, y: 1283 * scale) + .mask( + JarForegroundGlassMask() + .fill(style: FillStyle(eoFill: true)) + .blur(radius: 14 * scale) + .frame(width: width, height: height) + ) .allowsHitTesting(false) LinearGradient( @@ -310,22 +303,20 @@ struct PhotographicJarScene: View { } } -/// The front half of the thick glass base must hide any nut silhouette that -/// passes below the inner floor. The curve follows the jar's photographed oval. -private struct JarInteriorFloorMask: Shape { +/// The photographed bottom is already in JarEmptyBase behind the acorns. Keep +/// the front rim and side refractions, but leave the central inner floor clear +/// so the nuts sit visibly *on* that surface instead of behind another copy. +private struct JarForegroundGlassMask: Shape { func path(in rect: CGRect) -> Path { let scale = rect.width / 941 var path = Path() - path.move(to: .zero) - path.addLine(to: CGPoint(x: rect.width, y: 0)) - for step in stride(from: 941, through: 0, by: -5) { - let x = CGFloat(step) - let distance = x - 470 - let floorY = 1284 - 0.00055 * distance * distance - path.addLine(to: CGPoint(x: x * scale, y: floorY * scale)) - } - path.addLine(to: CGPoint(x: 0, y: (1284 - 0.00055 * 470 * 470) * scale)) - path.closeSubpath() + path.addRect(rect) + path.addEllipse(in: CGRect( + x: 165 * scale, + y: 1215 * scale, + width: 610 * scale, + height: 170 * scale + )) return path } }