From d72790db798c1438551b9fdea1942f781cab857f Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 22 Aug 2026 22:43:41 -0400 Subject: [PATCH 1/2] fix(tipcard): scale the name with the card width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The card already derives every other metric from its width — the code, the avatar, the corner radius, the name's padding — but the name itself was pinned at appDisplayXS's 20, so it read oversized on the 242-wide You-page card and undersized on the 302-wide full-screen one. Figma scales it with the rest of the card: 17 on the 269-wide base, 19.1 on the 302-wide full-screen card (node 9277:121421), 15.3 on the 242-wide You-page card (node 9276:4645). Derive it from the same width the other metrics come from, keeping appDisplayXS's family and weight. --- Flipcash/Core/Screens/Main/Tips/TipcardView.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Flipcash/Core/Screens/Main/Tips/TipcardView.swift b/Flipcash/Core/Screens/Main/Tips/TipcardView.swift index 53c2a6842..1edf3b608 100644 --- a/Flipcash/Core/Screens/Main/Tips/TipcardView.swift +++ b/Flipcash/Core/Screens/Main/Tips/TipcardView.swift @@ -17,6 +17,13 @@ struct TipcardView: View { /// on the You page (node 9276:4641) and full screen (node 9277:121417). static let aspectRatio: CGFloat = 333.0 / 269.0 + /// The name's size as a fraction of the card's width. Figma draws the name + /// at 17 on the 269-wide card and scales it with the card, so the 302-wide + /// full-screen card gets 19.1 (node 9277:121421) and the 242-wide You-page + /// card 15.3 (node 9276:4645). A fixed size instead left the name looking + /// oversized on the small card and undersized on the big one. + static let nameFraction: CGFloat = 17.0 / 269.0 + /// Explicit because a rendered tree has no container to size against. let size: CGSize let name: String @@ -66,7 +73,7 @@ struct TipcardView: View { .truncationMode(.tail) .multilineTextAlignment(.center) .padding(.horizontal, size.width * 0.08) - .font(.appDisplayXS) + .font(.default(size: nameFontSize, weight: .bold)) .foregroundStyle(Color.textMain) .padding(.top, size.height * 0.06) } @@ -85,6 +92,10 @@ struct TipcardView: View { } } + private var nameFontSize: CGFloat { + size.width * Self.nameFraction + } + private var codeDimension: CGFloat { size.width * 0.68 } From 6f03c2ea47a41298d57b21cc75b3b85efb0a7599 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 22 Aug 2026 22:56:19 -0400 Subject: [PATCH 2/2] fix(you): expand the tip card as one figure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The card was laid out at a new size on each side of the expansion, so every metric it derives from that size — code, avatar, corner radius, padding — animated as an independent value, and the name's font size, which isn't animatable at all, snapped. The parts arrived at different moments and overlapped mid-flight. Draw the card once at the widest size it ever reaches (302) and run the expansion as a single scaleEffect. The whole figure travels together, and because the scale only ever goes down from the drawn size, the card is never sampled up. --- .../Core/Screens/Main/You/YouScreen.swift | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Flipcash/Core/Screens/Main/You/YouScreen.swift b/Flipcash/Core/Screens/Main/You/YouScreen.swift index cb66dee8b..5da65e7f8 100644 --- a/Flipcash/Core/Screens/Main/You/YouScreen.swift +++ b/Flipcash/Core/Screens/Main/You/YouScreen.swift @@ -113,12 +113,13 @@ struct YouScreen: View { .frame(width: Self.collapsedCardSize.width, height: Self.collapsedCardSize.height) .overlay { TipcardView( - size: cardSize, + size: Self.drawnCardSize, name: name, avatar: nil, codeData: codeData, tintOpacity: 0.36 ) + .scaleEffect(cardScale) .offset(y: cardOffset) } .onGeometryChange(for: CGRect.self) { $0.frame(in: .global) } action: { cardSlotFrame = $0 } @@ -256,9 +257,21 @@ struct YouScreen: View { height: cardWidth * TipcardView.aspectRatio ) - private var cardSize: CGSize { - let width = isExpanded ? expandedCardWidth : Self.cardWidth - return CGSize(width: width, height: width * TipcardView.aspectRatio) + /// The size the card is always drawn at, whatever it currently measures on + /// screen — the widest it ever gets, so scaling it to size only ever + /// samples the drawing down. + private static let drawnCardSize = CGSize( + width: maxExpandedCardWidth, + height: maxExpandedCardWidth * TipcardView.aspectRatio + ) + + /// The expansion runs as one scale on the drawn card rather than a new size + /// for it to lay itself out at. Laid out, each of the card's metrics is a + /// separate animatable value — and the name's font size is not animatable + /// at all — so the parts arrived at their new sizes at different moments + /// and overlapped mid-flight. Scaled, the card travels as one figure. + private var cardScale: CGFloat { + (isExpanded ? expandedCardWidth : Self.cardWidth) / Self.drawnCardSize.width } /// The expanded width: the design's 302, narrowed only if the screen can't