Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions SuperPaste/Sources/Views/MainWindow/ReadyView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand Down
4 changes: 0 additions & 4 deletions SuperPaste/Sources/Views/Settings/AboutPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
129 changes: 129 additions & 0 deletions scripts/generate-dmg-background.swift
Original file line number Diff line number Diff line change
@@ -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 <output.tiff>
//
// 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 <output.tiff>\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)")
50 changes: 47 additions & 3 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<EOF
tell application "Finder"
tell disk "$VOLNAME"
open
set current view of container window to icon view
set toolbar visible of container window to false
set statusbar visible of container window to false
set bounds of container window to {200, 120, 800, 548}
set viewOptions to the icon view options of container window
set arrangement of viewOptions to not arranged
set icon size of viewOptions to 128
set text size of viewOptions to 13
set background picture of viewOptions to file ".background:background.tiff"
set position of item "SuperPaste.app" of container window to {150, 185}
set position of item "Applications" of container window to {450, 185}
update without registering applications
delay 1
close
end tell
end tell
EOF
then
echo " ⚠️ Finder layout failed (grant Terminal Automation access to Finder)."
echo " The DMG still works but opens without the drag-to-install window."
fi

chmod -Rf go-w "$MOUNT_DIR" || true
sync
hdiutil detach "$MOUNT_DIR" >/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.
Expand Down
13 changes: 12 additions & 1 deletion website/success.html
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ <h1>You're in. Two minutes to your first paste.</h1>
<div class="steps">
<div class="step">
<div class="step-num">1</div>
<p><strong>Find your license key.</strong> Polar just emailed it to you — it's also in your <a href="https://polar.sh/purchases">Polar purchases page</a>.</p>
<p><strong>Get your license key</strong> from your <a id="portal-link" href="https://polar.sh/superpaste/portal">SuperPaste customer portal</a> — open it and copy the key under your subscription. (If it asks you to sign in, use the email you just paid with.)</p>
</div>
<div class="step">
<div class="step-num">2</div>
Expand All @@ -196,5 +196,16 @@ <h1>You're in. Two minutes to your first paste.</h1>
© 2026 SuperPaste &nbsp;·&nbsp; <a href="/privacy">Privacy</a> &nbsp;·&nbsp; <a href="/terms">Terms</a> &nbsp;·&nbsp; <a href="/refunds">Refunds</a>
</footer>

<script>
// Polar appends a short-lived customer_session_token to the success URL;
// forwarding it signs the buyer straight into the portal, no email code.
(function () {
var token = new URLSearchParams(location.search).get('customer_session_token');
if (!token) return;
var link = document.getElementById('portal-link');
link.href = 'https://polar.sh/superpaste/portal?customer_session_token=' + encodeURIComponent(token);
})();
</script>

</body>
</html>
Loading