import AppKit import CoreGraphics import Foundation enum SplitLockVariant: CaseIterable { case balancedGate case glassCore case bracketTrace case shieldLock case signalKeyhole var title: String { switch self { case .balancedGate: return "Balanced Gate" case .glassCore: return "Glass Core" case .bracketTrace: return "Bracket Trace" case .shieldLock: return "Shield Lock" case .signalKeyhole: return "Signal Keyhole" } } var summary: String { switch self { case .balancedGate: return "Closest to the original idea. Balanced lock and readable traces." case .glassCore: return "More premium and layered. Feels the most iOS 26-native." case .bracketTrace: return "Leans developer-tool. The right half reads closer to code." case .shieldLock: return "Feels safer and more consumer-trust oriented than technical." case .signalKeyhole: return "Adds a pulse/keyhole center so inspection feels active, not static." } } var accent: NSColor { switch self { case .balancedGate: return NSColor(hex: 0x4F86FF) case .glassCore: return NSColor(hex: 0x67B8FF) case .bracketTrace: return NSColor(hex: 0x5A9DFF) case .shieldLock: return NSColor(hex: 0x41A6FF) case .signalKeyhole: return NSColor(hex: 0x4DE3FF) } } } enum LockStyle { case flat case glossy } struct Page { static let size = CGSize(width: 960, height: 1400) } let outputDirectory = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) .appendingPathComponent("Design/icon_concepts", isDirectory: true) let pdfURL = outputDirectory.appendingPathComponent("split_lock_variants.pdf") let pngURL = outputDirectory.appendingPathComponent("split_lock_variants_preview.png") try FileManager.default.createDirectory(at: outputDirectory, withIntermediateDirectories: true) renderPDF(to: pdfURL) renderPreview(to: pngURL) print("Generated:") print(pdfURL.path) print(pngURL.path) func renderPDF(to url: URL) { var mediaBox = CGRect(origin: .zero, size: Page.size) guard let context = CGContext(url as CFURL, mediaBox: &mediaBox, nil) else { fatalError("Unable to create PDF context") } beginPDFPage(context, mediaBox) { drawOverviewPage(in: mediaBox) } beginPDFPage(context, mediaBox) { drawLargeIconsPage(in: mediaBox) } context.closePDF() } func renderPreview(to url: URL) { let previewSize = CGSize(width: 1500, height: 2188) let image = NSImage(size: previewSize) image.lockFocus() NSGraphicsContext.current?.imageInterpolation = .high let scale = previewSize.width / Page.size.width let context = NSGraphicsContext.current?.cgContext context?.saveGState() context?.scaleBy(x: scale, y: scale) drawOverviewPage(in: CGRect(origin: .zero, size: Page.size)) context?.restoreGState() image.unlockFocus() guard let tiff = image.tiffRepresentation, let rep = NSBitmapImageRep(data: tiff), let png = rep.representation(using: .png, properties: [:]) else { fatalError("Unable to create PNG preview") } try? png.write(to: url) } func beginPDFPage(_ context: CGContext, _ mediaBox: CGRect, draw: () -> Void) { context.beginPDFPage(nil) let graphicsContext = NSGraphicsContext(cgContext: context, flipped: false) NSGraphicsContext.saveGraphicsState() NSGraphicsContext.current = graphicsContext draw() NSGraphicsContext.restoreGraphicsState() context.endPDFPage() } func drawOverviewPage(in rect: CGRect) { fill(rect, color: NSColor(hex: 0xF5F7FC)) drawText( "Split Lock Explorations", in: CGRect(x: 68, y: rect.height - 116, width: rect.width - 136, height: 40), font: .systemFont(ofSize: 34, weight: .bold), color: NSColor(hex: 0x0F172A) ) drawText( "Five directions built from the Split Lock concept. Each card shows the glossy primary icon with a small flat simplification inset.", in: CGRect(x: 68, y: rect.height - 160, width: rect.width - 136, height: 34), font: .systemFont(ofSize: 16, weight: .medium), color: NSColor(hex: 0x475467) ) let cardWidth = (rect.width - 68 * 2 - 26) / 2 let cardHeight: CGFloat = 300 let layouts: [(SplitLockVariant, CGRect)] = [ (.balancedGate, CGRect(x: 68, y: 900, width: cardWidth, height: cardHeight)), (.glassCore, CGRect(x: 68 + cardWidth + 26, y: 900, width: cardWidth, height: cardHeight)), (.bracketTrace, CGRect(x: 68, y: 574, width: cardWidth, height: cardHeight)), (.shieldLock, CGRect(x: 68 + cardWidth + 26, y: 574, width: cardWidth, height: cardHeight)), (.signalKeyhole, CGRect(x: 68, y: 248, width: cardWidth, height: cardHeight)) ] for (variant, frame) in layouts { drawVariantCard(variant: variant, in: frame) } drawDecisionCard( in: CGRect(x: 68 + cardWidth + 26, y: 248, width: cardWidth, height: cardHeight) ) } func drawLargeIconsPage(in rect: CGRect) { fill(rect, color: NSColor(hex: 0xF8FAFD)) drawText( "Large Icon Comparison", in: CGRect(x: 68, y: rect.height - 114, width: rect.width - 136, height: 38), font: .systemFont(ofSize: 32, weight: .bold), color: NSColor(hex: 0x0F172A) ) drawText( "This page removes most of the annotation so you can judge silhouette, weight, and app-store readability.", in: CGRect(x: 68, y: rect.height - 156, width: rect.width - 136, height: 28), font: .systemFont(ofSize: 15, weight: .medium), color: NSColor(hex: 0x475467) ) let rowHeight: CGFloat = 220 for (index, variant) in SplitLockVariant.allCases.enumerated() { let y = rect.height - 250 - CGFloat(index) * (rowHeight + 16) let frame = CGRect(x: 68, y: y, width: rect.width - 136, height: rowHeight) drawSurface(frame, fill: NSColor.white, stroke: NSColor(hex: 0xD9E0EC)) drawIconBackground(in: CGRect(x: frame.minX + 30, y: frame.minY + 26, width: 168, height: 168)) drawSplitLock(variant: variant, style: .glossy, in: CGRect(x: frame.minX + 42, y: frame.minY + 38, width: 144, height: 144)) drawText( variant.title, in: CGRect(x: frame.minX + 228, y: frame.maxY - 62, width: frame.width - 252, height: 30), font: .systemFont(ofSize: 26, weight: .bold), color: NSColor(hex: 0x111827) ) drawText( variant.summary, in: CGRect(x: frame.minX + 228, y: frame.maxY - 118, width: frame.width - 252, height: 60), font: .systemFont(ofSize: 16, weight: .medium), color: NSColor(hex: 0x475467) ) drawPill( "glossy primary", in: CGRect(x: frame.minX + 228, y: frame.minY + 28, width: 112, height: 28), fill: variant.accent.withAlphaComponent(0.14), textColor: NSColor(hex: 0x275FD7) ) } } func drawVariantCard(variant: SplitLockVariant, in frame: CGRect) { drawSurface(frame, fill: NSColor.white, stroke: NSColor(hex: 0xD9E0EC)) let iconFrame = CGRect(x: frame.minX + 24, y: frame.minY + 62, width: 158, height: 158) drawIconBackground(in: iconFrame) drawSplitLock(variant: variant, style: .glossy, in: iconFrame.insetBy(dx: 14, dy: 14)) let flatFrame = CGRect(x: frame.minX + 30, y: frame.minY + 196, width: 58, height: 58) drawMiniBadge(flatFrame, variant: variant) drawText( variant.title, in: CGRect(x: frame.minX + 204, y: frame.maxY - 70, width: frame.width - 226, height: 28), font: .systemFont(ofSize: 22, weight: .bold), color: NSColor(hex: 0x111827) ) drawText( variant.summary, in: CGRect(x: frame.minX + 204, y: frame.maxY - 138, width: frame.width - 226, height: 78), font: .systemFont(ofSize: 14, weight: .medium), color: NSColor(hex: 0x475467) ) drawPill( "flat inset", in: CGRect(x: frame.minX + 100, y: frame.minY + 208, width: 72, height: 22), fill: NSColor(hex: 0xEDF2FF), textColor: NSColor(hex: 0x3A63D1) ) } func drawDecisionCard(in frame: CGRect) { drawSurface(frame, fill: NSColor(hex: 0x17223A), stroke: NSColor(hex: 0x17223A)) drawText( "What To Pick For", in: CGRect(x: frame.minX + 26, y: frame.maxY - 68, width: frame.width - 52, height: 28), font: .systemFont(ofSize: 24, weight: .bold), color: .white ) let bullets = [ "Most balanced: Balanced Gate", "Most premium: Glass Core", "Most dev-tool: Bracket Trace", "Most trustworthy: Shield Lock", "Most active / dynamic: Signal Keyhole" ] for (index, bullet) in bullets.enumerated() { let rowY = frame.maxY - 118 - CGFloat(index) * 34 fillEllipse(CGRect(x: frame.minX + 28, y: rowY + 6, width: 10, height: 10), color: NSColor(hex: 0x6DA1FF)) drawText( bullet, in: CGRect(x: frame.minX + 48, y: rowY, width: frame.width - 72, height: 22), font: .systemFont(ofSize: 15, weight: .semibold), color: NSColor.white.withAlphaComponent(0.94) ) } drawText( "Pick one and I’ll iterate on thickness, color, background, or make it more minimal.", in: CGRect(x: frame.minX + 26, y: frame.minY + 28, width: frame.width - 52, height: 42), font: .systemFont(ofSize: 15, weight: .medium), color: NSColor.white.withAlphaComponent(0.82) ) } func drawIconBackground(in rect: CGRect) { let path = NSBezierPath(roundedRect: rect, xRadius: rect.width * 0.23, yRadius: rect.height * 0.23) let gradient = NSGradient(colors: [ NSColor(hex: 0xF6F9FF), NSColor(hex: 0xDFE9FF), NSColor(hex: 0xD1DEFF) ]) gradient?.draw(in: path, angle: 90) let highlight = NSBezierPath(roundedRect: rect.insetBy(dx: 16, dy: 16).offsetBy(dx: 0, dy: 20), xRadius: 34, yRadius: 34) NSColor.white.withAlphaComponent(0.24).setFill() highlight.fill() NSColor.white.withAlphaComponent(0.74).setStroke() path.lineWidth = 1.6 path.stroke() } func drawMiniBadge(_ rect: CGRect, variant: SplitLockVariant) { let path = NSBezierPath(roundedRect: rect, xRadius: rect.width * 0.28, yRadius: rect.height * 0.28) let gradient = NSGradient(colors: [NSColor(hex: 0xFCFDFF), NSColor(hex: 0xEEF3FF)]) gradient?.draw(in: path, angle: 90) NSColor.white.withAlphaComponent(0.8).setStroke() path.lineWidth = 1.2 path.stroke() drawSplitLock(variant: variant, style: .flat, in: rect.insetBy(dx: 8, dy: 8)) } func drawSplitLock(variant: SplitLockVariant, style: LockStyle, in rect: CGRect) { switch variant { case .balancedGate: drawBalancedGate(style: style, in: rect) case .glassCore: drawGlassCore(style: style, in: rect) case .bracketTrace: drawBracketTrace(style: style, in: rect) case .shieldLock: drawShieldLock(style: style, in: rect) case .signalKeyhole: drawSignalKeyhole(style: style, in: rect) } } func drawBalancedGate(style: LockStyle, in rect: CGRect) { drawBaseLock(style: style, in: rect, bodyInset: 0, shackleLift: 0) drawTraceLines( in: rect, color: NSColor(hex: style == .flat ? 0x5EA4FF : 0x7EB8FF), spread: 26, weight: style == .flat ? 6 : 7 ) } func drawGlassCore(style: LockStyle, in rect: CGRect) { drawBaseLock(style: style, in: rect, bodyInset: 4, shackleLift: 6) let glowRect = CGRect(x: rect.midX - 18, y: rect.midY - 14, width: 36, height: 36) if style == .glossy { fillEllipse(glowRect.insetBy(dx: -10, dy: -10), color: NSColor(hex: 0x84DAFF).withAlphaComponent(0.16)) } fillEllipse(glowRect, color: NSColor(hex: style == .flat ? 0x6CCFFF : 0x86E5FF)) drawTraceLines( in: rect, color: NSColor(hex: style == .flat ? 0x74B0FF : 0xA5CCFF), spread: 20, weight: style == .flat ? 5 : 6 ) } func drawBracketTrace(style: LockStyle, in rect: CGRect) { drawBaseLock(style: style, in: rect, bodyInset: 0, shackleLift: 0) let bracket = NSBezierPath() bracket.move(to: CGPoint(x: rect.midX + 10, y: rect.midY + 34)) bracket.line(to: CGPoint(x: rect.maxX - 30, y: rect.midY + 34)) bracket.line(to: CGPoint(x: rect.maxX - 30, y: rect.midY + 10)) bracket.move(to: CGPoint(x: rect.midX + 10, y: rect.midY - 34)) bracket.line(to: CGPoint(x: rect.maxX - 30, y: rect.midY - 34)) bracket.line(to: CGPoint(x: rect.maxX - 30, y: rect.midY - 10)) bracket.lineWidth = style == .flat ? 6 : 7 bracket.lineCapStyle = .round bracket.lineJoinStyle = .round NSColor(hex: style == .flat ? 0x67A8FF : 0x8FC1FF).setStroke() bracket.stroke() drawTraceLines( in: rect, color: NSColor(hex: style == .flat ? 0x4F86FF : 0x69A8FF), spread: 24, weight: style == .flat ? 5 : 6 ) } func drawShieldLock(style: LockStyle, in rect: CGRect) { let shield = NSBezierPath() shield.move(to: CGPoint(x: rect.midX, y: rect.maxY - 6)) shield.curve( to: CGPoint(x: rect.minX + 32, y: rect.midY + 18), controlPoint1: CGPoint(x: rect.midX - 40, y: rect.maxY - 8), controlPoint2: CGPoint(x: rect.minX + 26, y: rect.maxY - 26) ) shield.line(to: CGPoint(x: rect.midX, y: rect.minY + 6)) shield.line(to: CGPoint(x: rect.maxX - 32, y: rect.midY + 18)) shield.curve( to: CGPoint(x: rect.midX, y: rect.maxY - 6), controlPoint1: CGPoint(x: rect.maxX - 26, y: rect.maxY - 26), controlPoint2: CGPoint(x: rect.midX + 40, y: rect.maxY - 8) ) shield.close() if style == .glossy { NSColor.white.withAlphaComponent(0.18).setFill() shield.fill() } else { NSColor(hex: 0xEDF4FF).setFill() shield.fill() } drawBaseLock(style: style, in: rect.insetBy(dx: 10, dy: 10), bodyInset: 2, shackleLift: 2) drawTraceLines( in: rect.insetBy(dx: 10, dy: 10), color: NSColor(hex: style == .flat ? 0x5F9FFF : 0x8CC1FF), spread: 22, weight: style == .flat ? 5 : 6 ) } func drawSignalKeyhole(style: LockStyle, in rect: CGRect) { drawBaseLock(style: style, in: rect, bodyInset: 1, shackleLift: 0) let keyholeCircle = CGRect(x: rect.midX - 14, y: rect.midY + 2, width: 28, height: 28) let keyholeStem = NSBezierPath(roundedRect: CGRect(x: rect.midX - 7, y: rect.midY - 24, width: 14, height: 30), xRadius: 7, yRadius: 7) if style == .glossy { fillEllipse(keyholeCircle.insetBy(dx: -8, dy: -8), color: NSColor(hex: 0x7FE4FF).withAlphaComponent(0.16)) } fillEllipse(keyholeCircle, color: NSColor(hex: style == .flat ? 0x143A6F : 0xF8FBFF)) let stemColor = style == .flat ? NSColor(hex: 0x143A6F) : NSColor(hex: 0xF8FBFF) stemColor.setFill() keyholeStem.fill() drawTraceLines( in: rect, color: NSColor(hex: style == .flat ? 0x49CFFF : 0x5CE7FF), spread: 28, weight: style == .flat ? 5 : 6 ) } func drawBaseLock(style: LockStyle, in rect: CGRect, bodyInset: CGFloat, shackleLift: CGFloat) { let bodyRect = CGRect( x: rect.midX - 42 + bodyInset, y: rect.midY - 34, width: 96 - bodyInset * 2, height: 76 ) let body = NSBezierPath(roundedRect: bodyRect, xRadius: 22, yRadius: 22) switch style { case .flat: NSColor(hex: 0xF7FAFF).setFill() body.fill() NSColor(hex: 0x1E3A72).setStroke() body.lineWidth = 5.5 body.stroke() case .glossy: let gradient = NSGradient(colors: [ NSColor.white.withAlphaComponent(0.92), NSColor(hex: 0xE0EBFF).withAlphaComponent(0.88) ]) gradient?.draw(in: body, angle: 90) NSColor.white.withAlphaComponent(0.82).setStroke() body.lineWidth = 5 body.stroke() } let shackle = NSBezierPath() shackle.move(to: CGPoint(x: rect.midX - 32, y: bodyRect.maxY - 1)) shackle.curve( to: CGPoint(x: rect.midX + 12, y: bodyRect.maxY + 40 + shackleLift), controlPoint1: CGPoint(x: rect.midX - 34, y: bodyRect.maxY + 42 + shackleLift), controlPoint2: CGPoint(x: rect.midX + 12, y: bodyRect.maxY + 42 + shackleLift) ) shackle.lineWidth = style == .flat ? 11 : 12 shackle.lineCapStyle = .round NSColor(hex: style == .flat ? 0x1E3A72 : 0xF8FBFF).setStroke() shackle.stroke() let leftFillRect = CGRect(x: bodyRect.minX, y: bodyRect.minY, width: bodyRect.width * 0.52, height: bodyRect.height) let leftFill = NSBezierPath(roundedRect: leftFillRect, xRadius: 18, yRadius: 18) NSColor(hex: style == .flat ? 0x76A8FF : 0xBCD4FF).setFill() leftFill.fill() } func drawTraceLines(in rect: CGRect, color: NSColor, spread: CGFloat, weight: CGFloat) { let traces: [(CGFloat, CGFloat)] = [(spread, 0.94), (0, 1.0), (-spread, 0.92)] for (offset, scale) in traces { let start = CGPoint(x: rect.midX + 6, y: rect.midY + offset * 0.32) let end = CGPoint(x: rect.maxX - 20, y: rect.midY + offset) let path = NSBezierPath() path.move(to: start) path.curve( to: end, controlPoint1: CGPoint(x: start.x + 22, y: start.y), controlPoint2: CGPoint(x: end.x - 18, y: end.y) ) path.lineWidth = weight * scale path.lineCapStyle = .round color.setStroke() path.stroke() fillEllipse(CGRect(x: end.x - 5, y: end.y - 5, width: 10, height: 10), color: color) } } func drawSurface(_ rect: CGRect, fill: NSColor, stroke: NSColor) { let shadow = NSShadow() shadow.shadowColor = NSColor.black.withAlphaComponent(0.08) shadow.shadowOffset = CGSize(width: 0, height: -7) shadow.shadowBlurRadius = 18 NSGraphicsContext.saveGraphicsState() shadow.set() let path = NSBezierPath(roundedRect: rect, xRadius: 26, yRadius: 26) fill.setFill() path.fill() NSGraphicsContext.restoreGraphicsState() let strokePath = NSBezierPath(roundedRect: rect, xRadius: 26, yRadius: 26) stroke.setStroke() strokePath.lineWidth = 1.1 strokePath.stroke() } func drawPill(_ title: String, in rect: CGRect, fill: NSColor, textColor: NSColor) { let path = NSBezierPath(roundedRect: rect, xRadius: rect.height / 2, yRadius: rect.height / 2) fill.setFill() path.fill() drawText(title, in: rect.offsetBy(dx: 0, dy: 2), font: .systemFont(ofSize: 11, weight: .bold), color: textColor, alignment: .center) } func drawText(_ text: String, in rect: CGRect, font: NSFont, color: NSColor, alignment: NSTextAlignment = .left) { let paragraph = NSMutableParagraphStyle() paragraph.alignment = alignment paragraph.lineBreakMode = .byWordWrapping let attributes: [NSAttributedString.Key: Any] = [ .font: font, .foregroundColor: color, .paragraphStyle: paragraph ] NSString(string: text).draw(with: rect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes) } func fill(_ rect: CGRect, color: NSColor) { color.setFill() NSBezierPath(rect: rect).fill() } func fillEllipse(_ rect: CGRect, color: NSColor) { color.setFill() NSBezierPath(ovalIn: rect).fill() } extension NSColor { convenience init(hex: Int, alpha: CGFloat = 1.0) { let red = CGFloat((hex >> 16) & 0xFF) / 255.0 let green = CGFloat((hex >> 8) & 0xFF) / 255.0 let blue = CGFloat(hex & 0xFF) / 255.0 self.init(calibratedRed: red, green: green, blue: blue, alpha: alpha) } }