diff --git a/SuperPaste/Sources/Views/MainWindow/ReadyView.swift b/SuperPaste/Sources/Views/MainWindow/ReadyView.swift index 25729f6..04e93fa 100644 --- a/SuperPaste/Sources/Views/MainWindow/ReadyView.swift +++ b/SuperPaste/Sources/Views/MainWindow/ReadyView.swift @@ -3,7 +3,6 @@ import SwiftUI /// View displayed when SuperPaste is fully configured and ready to use. struct ReadyView: View { @EnvironmentObject var appState: AppState - @Environment(\.openSettings) private var openSettings @ObservedObject private var updateController = UpdateController.shared @AppStorage("launchAtLogin") private var launchAtLogin = false @AppStorage("hasTriedOnce") private var hasTriedOnce = false @@ -267,19 +266,14 @@ struct ReadyView: View { } } Spacer() - Button { - // The legacy showSettingsWindow: selector is dead on - // macOS 14+; openSettings is the supported route. - openSettings() - NSApp.activate(ignoringOtherApps: true) - } label: { + // Same route as the menu bar's Settings item, which is the + // one path that reliably opens the window on macOS 14+. + SettingsLink { Image(systemName: "gear") Text("Settings") } } .font(.subheadline) - Text("AI by Anthropic · OpenAI · OpenRouter") - .font(.caption).foregroundColor(.secondary) } } diff --git a/SuperPaste/Sources/Views/Settings/AboutPage.swift b/SuperPaste/Sources/Views/Settings/AboutPage.swift index 9dde5af..dbddffb 100644 --- a/SuperPaste/Sources/Views/Settings/AboutPage.swift +++ b/SuperPaste/Sources/Views/Settings/AboutPage.swift @@ -41,10 +41,6 @@ struct AboutPage: View { .font(.caption.weight(.medium)) .foregroundColor(.secondary) - Text("AI by Anthropic") - .font(.caption) - .foregroundStyle(.tertiary) - Link("Created by sparker.ai", destination: URL(string: "https://sparker.ai")!) .font(.caption) .foregroundStyle(.tertiary) diff --git a/scripts/generate-dmg-background.swift b/scripts/generate-dmg-background.swift new file mode 100644 index 0000000..bc4b4c7 --- /dev/null +++ b/scripts/generate-dmg-background.swift @@ -0,0 +1,129 @@ +#!/usr/bin/env swift +// +// generate-dmg-background.swift +// Draws the background for the DMG install window: a light canvas with a +// blue arrow pointing from the app icon (left) to the Applications alias +// (right) and a one-line instruction underneath. +// +// Light on purpose: Finder renders icon labels in dark text, which would be +// unreadable on the site's dark palette. +// +// Usage: swift scripts/generate-dmg-background.swift +// +// Writes a multi-resolution TIFF (1x + 2x) so the window is sharp on Retina. +// Geometry must match the icon positions in scripts/release.sh. +// + +import AppKit + +let width = 600 +let height = 400 +// Finder icon centers (top-left origin) — keep in sync with release.sh. +let appCenterX: CGFloat = 150 +let applicationsCenterX: CGFloat = 450 +let iconCenterY: CGFloat = 185 + +let brandBlue = CGColor(red: 0.10, green: 0.50, blue: 0.95, alpha: 1.0) + +guard CommandLine.arguments.count == 2 else { + FileHandle.standardError.write("usage: generate-dmg-background.swift \n".data(using: .utf8)!) + exit(1) +} +let outputPath = CommandLine.arguments[1] + +func render(scale: Int) -> NSBitmapImageRep { + let rep = NSBitmapImageRep( + bitmapDataPlanes: nil, + pixelsWide: width * scale, + pixelsHigh: height * scale, + bitsPerSample: 8, + samplesPerPixel: 4, + hasAlpha: true, + isPlanar: false, + colorSpaceName: .deviceRGB, + bytesPerRow: 0, + bitsPerPixel: 0 + )! + // Point size stays 600x400 so Finder lays the 2x rep out at 1x geometry. + rep.size = NSSize(width: width, height: height) + + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep) + let ctx = NSGraphicsContext.current!.cgContext + + let w = CGFloat(width), h = CGFloat(height) + + // Soft vertical gradient. + let gradient = CGGradient( + colorsSpace: CGColorSpaceCreateDeviceRGB(), + colors: [ + CGColor(red: 0.97, green: 0.98, blue: 1.00, alpha: 1), + CGColor(red: 0.91, green: 0.94, blue: 0.98, alpha: 1), + ] as CFArray, + locations: [0, 1] + )! + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0, y: h), end: CGPoint(x: 0, y: 0), options: []) + + // Arrow between the two icons (CG origin is bottom-left). + let y = h - iconCenterY + let startX = appCenterX + 80 + let endX = applicationsCenterX - 80 + let headLength: CGFloat = 22 + let headHalfWidth: CGFloat = 16 + + ctx.setStrokeColor(brandBlue) + ctx.setLineWidth(6) + ctx.setLineCap(.round) + ctx.move(to: CGPoint(x: startX, y: y)) + ctx.addLine(to: CGPoint(x: endX - headLength + 2, y: y)) + ctx.strokePath() + + ctx.setFillColor(brandBlue) + ctx.move(to: CGPoint(x: endX, y: y)) + ctx.addLine(to: CGPoint(x: endX - headLength, y: y + headHalfWidth)) + ctx.addLine(to: CGPoint(x: endX - headLength, y: y - headHalfWidth)) + ctx.closePath() + ctx.fillPath() + + // Instruction text. + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .center + let title = NSAttributedString( + string: "Drag SuperPaste into Applications to install", + attributes: [ + .font: NSFont.systemFont(ofSize: 17, weight: .semibold), + .foregroundColor: NSColor(calibratedWhite: 0.15, alpha: 1), + .paragraphStyle: paragraph, + ] + ) + title.draw(in: NSRect(x: 0, y: 70, width: w, height: 26)) + + let subtitle = NSAttributedString( + string: "Then open it from your Applications folder.", + attributes: [ + .font: NSFont.systemFont(ofSize: 13), + .foregroundColor: NSColor(calibratedWhite: 0.40, alpha: 1), + .paragraphStyle: paragraph, + ] + ) + subtitle.draw(in: NSRect(x: 0, y: 46, width: w, height: 20)) + + NSGraphicsContext.restoreGraphicsState() + return rep +} + +let image = NSImage(size: NSSize(width: width, height: height)) +image.addRepresentation(render(scale: 1)) +image.addRepresentation(render(scale: 2)) + +guard let tiff = image.tiffRepresentation(using: .lzw, factor: 0) else { + FileHandle.standardError.write("failed to encode TIFF\n".data(using: .utf8)!) + exit(1) +} +do { + try tiff.write(to: URL(fileURLWithPath: outputPath)) +} catch { + FileHandle.standardError.write("failed to write \(outputPath): \(error)\n".data(using: .utf8)!) + exit(1) +} +print("Wrote \(outputPath)") diff --git a/scripts/release.sh b/scripts/release.sh index d3ff81f..8a12c54 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -60,12 +60,56 @@ else fi echo "==> Creating DMG" +# Build a read-write image first so Finder can lay out the install window +# (app on the left, arrow, Applications alias on the right), then compress. STAGING="$DIST/dmg-staging" -mkdir -p "$STAGING" +RW_DMG="$DIST/SuperPaste-rw.dmg" +VOLNAME="SuperPaste" +mkdir -p "$STAGING/.background" cp -R "$APP" "$STAGING/" ln -s /Applications "$STAGING/Applications" -hdiutil create -volname "SuperPaste" -srcfolder "$STAGING" -ov -format UDZO "$DMG" >/dev/null -rm -rf "$STAGING" +swift "$REPO/scripts/generate-dmg-background.swift" "$STAGING/.background/background.tiff" >/dev/null + +# A stale mount with the same name would make Finder style the wrong volume. +if [ -d "/Volumes/$VOLNAME" ]; then + hdiutil detach "/Volumes/$VOLNAME" -force >/dev/null || true +fi + +hdiutil create -volname "$VOLNAME" -srcfolder "$STAGING" -ov -format UDRW "$RW_DMG" >/dev/null +MOUNT_DIR=$(hdiutil attach "$RW_DMG" -readwrite -noverify -noautoopen | grep -o '/Volumes/.*$' | head -1) + +# Icon positions must match scripts/generate-dmg-background.swift. +if ! osascript </dev/null +hdiutil convert "$RW_DMG" -format UDZO -imagekey zlib-level=9 -o "$DMG" -ov >/dev/null +rm -rf "$STAGING" "$RW_DMG" # Sign the DMG container itself (app inside is already signed) so even # container-level Gatekeeper checks pass. Must happen before notarization. diff --git a/website/success.html b/website/success.html index e5219d2..967603a 100644 --- a/website/success.html +++ b/website/success.html @@ -175,7 +175,7 @@

You're in. Two minutes to your first paste.

1
-

Find your license key. Polar just emailed it to you — it's also in your Polar purchases page.

+

Get your license key from your SuperPaste customer portal — open it and copy the key under your subscription. (If it asks you to sign in, use the email you just paid with.)

2
@@ -196,5 +196,16 @@

You're in. Two minutes to your first paste.

© 2026 SuperPaste  ·  Privacy  ·  Terms  ·  Refunds + +