style: add sunset palette and soft background glows

This commit is contained in:
2026-10-11 19:46:20 +02:00
parent 62e813f58f
commit c7e35cec44
3 changed files with 77 additions and 28 deletions
+2 -2
View File
@@ -37,9 +37,9 @@ Search is case- and accent-insensitive. Ranking is exact name, prefix, substring
## Native UI
The launcher now follows the Raycast reference: a 750 × 474-point, titlebar-free dark panel with rounded corners, native translucency and shadow, a large borderless search field, compact icon rows, neutral rounded selection, right-aligned application labels, and a bottom-right launch pill with a Return key hint. It deliberately uses dark appearance for this initial visual direction.
The launcher keeps its Raycast-inspired 750 × 474-point, titlebar-free rounded panel, large borderless search field, compact icon rows, right-aligned application labels, and bottom-right launch pill. Its Sunset palette uses a dark brown background, soft amber and rust glows, warm cream text, and orange accents on the caret, selection, and action outline.
AppKit still provides text editing, table navigation, scrolling, application icons, controls, menus, fonts, semantic colors, and visual-effect materials. Custom styling is limited to layout, rounded surfaces, and selection drawing; increased-contrast settings add a selection outline. No third-party UI dependencies, copied Raycast branding, or nonfunctional Actions menu.
AppKit still provides text editing, table navigation, scrolling, application icons, controls, menus, fonts, and visual-effect materials. The background uses cached native radial gradients that fade to transparent: static soft blobs, without animations, timers, image assets, blur filters, or third-party UI dependencies. Decorative drawing does not intercept clicks. Increased-contrast settings strengthen the selection outline, and text colors are tuned for readability across the glows and selection.
- `Sources/Conjure`: application lifecycle, native window, and application launching.
- `Sources/ConjureKit`: filesystem discovery and search ranking.
+45
View File
@@ -0,0 +1,45 @@
import AppKit
@MainActor
enum SunsetPalette {
static let background = NSColor(srgbRed: 0.12, green: 0.09, blue: 0.08, alpha: 0.96)
static let text = NSColor(srgbRed: 1.00, green: 0.89, blue: 0.71, alpha: 1)
static let secondaryText = NSColor(srgbRed: 0.85, green: 0.78, blue: 0.65, alpha: 1)
static let accent = NSColor(srgbRed: 0.95, green: 0.58, blue: 0.23, alpha: 1)
static let selectionFill = accent.withAlphaComponent(0.12)
static let selectionBorder = accent.withAlphaComponent(0.30)
static let ember = NSColor(srgbRed: 0.76, green: 0.27, blue: 0.18, alpha: 1)
}
@MainActor
final class LauncherBackground: NSView {
private let amber = NSGradient(colors: [
SunsetPalette.accent.withAlphaComponent(0.18),
SunsetPalette.accent.withAlphaComponent(0.065),
SunsetPalette.accent.withAlphaComponent(0),
])!
private let ember = NSGradient(colors: [
SunsetPalette.ember.withAlphaComponent(0.26),
SunsetPalette.ember.withAlphaComponent(0.10),
SunsetPalette.ember.withAlphaComponent(0),
])!
override func hitTest(_ point: NSPoint) -> NSView? { nil }
override func draw(_ dirtyRect: NSRect) {
SunsetPalette.background.setFill()
dirtyRect.fill()
let upperGlow = NSPoint(x: bounds.width * 0.58, y: bounds.height * 0.94)
ember.draw(fromCenter: upperGlow, radius: 0, toCenter: upperGlow,
radius: bounds.width * 0.40, options: [])
let lowerGlow = NSPoint(x: bounds.width * 0.88, y: bounds.height * 0.10)
amber.draw(fromCenter: lowerGlow, radius: 0, toCenter: lowerGlow,
radius: bounds.width * 0.55, options: [])
let leftGlow = NSPoint(x: bounds.width * 0.08, y: bounds.height * 0.65)
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current?.cgContext.setAlpha(0.45)
amber.draw(fromCenter: leftGlow, radius: 0, toCenter: leftGlow,
radius: bounds.width * 0.32, options: [])
NSGraphicsContext.restoreGraphicsState()
}
}
+29 -25
View File
@@ -47,7 +47,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
NSApp.activate()
window?.makeKeyAndOrderFront(nil)
window?.makeFirstResponder(searchField)
(searchField.currentEditor() as? NSTextView)?.insertionPointColor = .labelColor
(searchField.currentEditor() as? NSTextView)?.insertionPointColor = SunsetPalette.accent
refreshApplications()
}
@@ -66,14 +66,18 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
content.layer?.masksToBounds = true
content.layer?.borderWidth = 0.5
window.contentView = content
let tint = NSView(frame: content.bounds)
tint.autoresizingMask = [.width, .height]
tint.wantsLayer = true
content.addSubview(tint)
let background = LauncherBackground(frame: content.bounds)
background.autoresizingMask = [.width, .height]
background.setAccessibilityElement(false)
content.addSubview(background)
searchField.placeholderString = "Search for apps…"
searchField.font = .systemFont(ofSize: 20)
searchField.textColor = .labelColor
let searchFont = NSFont.systemFont(ofSize: 20)
searchField.font = searchFont
searchField.placeholderAttributedString = NSAttributedString(
string: "Search for apps…",
attributes: [.foregroundColor: SunsetPalette.secondaryText, .font: searchFont]
)
searchField.textColor = SunsetPalette.text
searchField.isBordered = false
searchField.drawsBackground = false
searchField.focusRingType = .none
@@ -88,7 +92,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
let symbol = NSImageView()
symbol.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
symbol.contentTintColor = .tertiaryLabelColor
symbol.contentTintColor = SunsetPalette.accent
symbol.setAccessibilityElement(false)
let header = NSStackView(views: [symbol, searchField])
header.orientation = .horizontal
@@ -97,7 +101,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
let sectionLabel = NSTextField(labelWithString: "Applications")
sectionLabel.font = .systemFont(ofSize: 12, weight: .semibold)
sectionLabel.textColor = .secondaryLabelColor
sectionLabel.textColor = SunsetPalette.secondaryText
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("application"))
column.resizingMask = .autoresizingMask
@@ -134,14 +138,14 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
scrollView.horizontalScrollElasticity = .none
statusLabel.font = .systemFont(ofSize: 12)
statusLabel.textColor = .secondaryLabelColor
statusLabel.textColor = SunsetPalette.secondaryText
statusLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
emptyLabel.textColor = .secondaryLabelColor
emptyLabel.textColor = SunsetPalette.secondaryText
emptyLabel.alignment = .center
openButton.isBordered = false
openButton.font = .systemFont(ofSize: 14, weight: .medium)
openButton.contentTintColor = .labelColor
openButton.contentTintColor = SunsetPalette.text
openButton.keyEquivalent = "\r"
openButton.target = self
openButton.action = #selector(openSelection)
@@ -149,7 +153,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
openButton.toolTip = "Open the selected application (Return). Use ↑ and ↓ to select."
let returnKey = NSTextField(labelWithString: "↵")
returnKey.font = .systemFont(ofSize: 14)
returnKey.textColor = .secondaryLabelColor
returnKey.textColor = SunsetPalette.secondaryText
returnKey.alignment = .center
returnKey.wantsLayer = true
returnKey.layer?.cornerRadius = 5
@@ -208,11 +212,11 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
returnKey.heightAnchor.constraint(equalToConstant: 18),
])
window.effectiveAppearance.performAsCurrentDrawingAppearance {
let border = NSColor.separatorColor.cgColor
let border = SunsetPalette.secondaryText.withAlphaComponent(0.22).cgColor
content.layer?.borderColor = border
returnKey.layer?.borderColor = border
actionPill.layer?.borderColor = border
tint.layer?.backgroundColor = NSColor.textBackgroundColor.withAlphaComponent(0.85).cgColor
returnKey.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor
actionPill.layer?.borderColor = SunsetPalette.accent.withAlphaComponent(0.35).cgColor
actionPill.layer?.backgroundColor = SunsetPalette.accent.withAlphaComponent(0.08).cgColor
}
window.initialFirstResponder = searchField
}
@@ -315,10 +319,11 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
icon.setAccessibilityElement(false)
let label = NSTextField(labelWithString: "")
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = SunsetPalette.text
label.lineBreakMode = .byTruncatingTail
let kind = NSTextField(labelWithString: "Application")
kind.font = .systemFont(ofSize: 14)
kind.textColor = .secondaryLabelColor
kind.textColor = SunsetPalette.secondaryText
kind.setContentCompressionResistancePriority(.required, for: .horizontal)
kind.setAccessibilityElement(false)
cell.imageView = icon
@@ -418,13 +423,12 @@ private final class LauncherPanel: NSPanel {
@MainActor
private final class LauncherRowView: NSTableRowView {
override func drawSelection(in dirtyRect: NSRect) {
let selection = NSBezierPath(roundedRect: bounds, xRadius: 10, yRadius: 10)
NSColor.unemphasizedSelectedContentBackgroundColor.withAlphaComponent(0.55).setFill()
let selection = NSBezierPath(roundedRect: bounds.insetBy(dx: 0.75, dy: 0.75), xRadius: 10, yRadius: 10)
SunsetPalette.selectionFill.setFill()
selection.fill()
if NSWorkspace.shared.accessibilityDisplayShouldIncreaseContrast {
NSColor.labelColor.setStroke()
selection.lineWidth = 1
let increasedContrast = NSWorkspace.shared.accessibilityDisplayShouldIncreaseContrast
(increasedContrast ? SunsetPalette.accent : SunsetPalette.selectionBorder).setStroke()
selection.lineWidth = increasedContrast ? 1.5 : 0.5
selection.stroke()
}
}
}