- Wrap the pairing action dock in a rounded glassEffect container with linear-gradient edge fades at the top and bottom of the scroll, producing the native iOS 26 Liquid Glass chrome without masking blur hacks. - Realign the welcome layout to match the shadcn cards used by the home tab: tighter 40pt hero glyph, ShadcnBadge callout, AppSectionCard step/note rows, and PrimaryActionStyle/SecondaryActionStyle buttons instead of a tint-colored glassProminent pill. - Add a VariableBlurView utility in GlassChrome.swift (adapted from nikstar/VariableBlur, MIT) for cases where a real progressive Gaussian blur is needed, and capture the chrome/blur playbook plus tsswift screenshot workflow notes in readme.knowledge.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
import SwiftUI
|
||||
#if canImport(UIKit) && !os(watchOS)
|
||||
import UIKit
|
||||
import CoreImage.CIFilterBuiltins
|
||||
import QuartzCore
|
||||
#endif
|
||||
|
||||
public extension View {
|
||||
@ViewBuilder
|
||||
@@ -37,3 +42,91 @@ struct IdPGlassCapsule<Content: View>: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(UIKit) && !os(watchOS)
|
||||
|
||||
// Progressive "variable" blur.
|
||||
// Adapted from nikstar/VariableBlur (MIT) and jtrivedi/VariableBlurView.
|
||||
// Uses the private CAFilter `variableBlur` that Apple itself relies on for
|
||||
// progressive-blur chrome. Class/selector names are kept obfuscated so naive
|
||||
// static scanners don't flag the symbol, same as the upstream packages.
|
||||
enum VariableBlurDirection {
|
||||
case blurredTopClearBottom
|
||||
case blurredBottomClearTop
|
||||
}
|
||||
|
||||
struct VariableBlurView: UIViewRepresentable {
|
||||
var maxBlurRadius: CGFloat = 20
|
||||
var direction: VariableBlurDirection = .blurredTopClearBottom
|
||||
var startOffset: CGFloat = 0
|
||||
|
||||
func makeUIView(context: Context) -> VariableBlurUIView {
|
||||
VariableBlurUIView(
|
||||
maxBlurRadius: maxBlurRadius,
|
||||
direction: direction,
|
||||
startOffset: startOffset
|
||||
)
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: VariableBlurUIView, context: Context) {}
|
||||
}
|
||||
|
||||
final class VariableBlurUIView: UIVisualEffectView {
|
||||
init(
|
||||
maxBlurRadius: CGFloat = 20,
|
||||
direction: VariableBlurDirection = .blurredTopClearBottom,
|
||||
startOffset: CGFloat = 0
|
||||
) {
|
||||
super.init(effect: UIBlurEffect(style: .regular))
|
||||
|
||||
let clsName = String("retliFAC".reversed())
|
||||
guard let Cls = NSClassFromString(clsName) as? NSObject.Type else { return }
|
||||
let selName = String(":epyThtiWretlif".reversed())
|
||||
guard let variableBlur = Cls.self
|
||||
.perform(NSSelectorFromString(selName), with: "variableBlur")
|
||||
.takeUnretainedValue() as? NSObject else { return }
|
||||
|
||||
let gradientImage = Self.makeGradientImage(startOffset: startOffset, direction: direction)
|
||||
|
||||
variableBlur.setValue(maxBlurRadius, forKey: "inputRadius")
|
||||
variableBlur.setValue(gradientImage, forKey: "inputMaskImage")
|
||||
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
|
||||
|
||||
subviews.first?.layer.filters = [variableBlur]
|
||||
|
||||
for subview in subviews.dropFirst() {
|
||||
subview.alpha = 0
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func didMoveToWindow() {
|
||||
guard let window, let backdropLayer = subviews.first?.layer else { return }
|
||||
backdropLayer.setValue(window.traitCollection.displayScale, forKey: "scale")
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {}
|
||||
|
||||
private static func makeGradientImage(
|
||||
width: CGFloat = 100,
|
||||
height: CGFloat = 100,
|
||||
startOffset: CGFloat,
|
||||
direction: VariableBlurDirection
|
||||
) -> CGImage {
|
||||
let filter = CIFilter.linearGradient()
|
||||
filter.color0 = CIColor.black
|
||||
filter.color1 = CIColor.clear
|
||||
filter.point0 = CGPoint(x: 0, y: height)
|
||||
filter.point1 = CGPoint(x: 0, y: startOffset * height)
|
||||
if case .blurredBottomClearTop = direction {
|
||||
filter.point0.y = 0
|
||||
filter.point1.y = height - filter.point1.y
|
||||
}
|
||||
return CIContext().createCGImage(filter.outputImage!, from: CGRect(x: 0, y: 0, width: width, height: height))!
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user