diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift
index 8c2bb5d..8f3673d 100644
--- a/DynamicIsland/ContentView.swift
+++ b/DynamicIsland/ContentView.swift
@@ -80,6 +80,8 @@ struct ContentView: View {
@Default(.enableCapsLockIndicator) var enableCapsLockIndicator
@Default(.enableExtensionLiveActivities) var enableExtensionLiveActivities
@Default(.showStandardMediaControls) var showStandardMediaControls
+ @Default(.externalDisplayStyle) var externalDisplayStyle
+ @Default(.hideNonNotchUntilHover) var hideNonNotchUntilHover
// Dynamic sizing based on view type and graph count with smooth transitions
var dynamicNotchSize: CGSize {
@@ -231,8 +233,52 @@ struct ContentView: View {
return NotchShape(topCornerRadius: topRadius, bottomCornerRadius: bottomRadius)
}
+ /// Whether the current screen should render as a Dynamic Island pill
+ /// rather than the standard notch shape. Always false on physical notch screens.
+ private var isDynamicIslandMode: Bool {
+ shouldUseDynamicIslandMode(for: vm.screen ?? coordinator.selectedScreen)
+ }
+
+ /// Whether the current screen lacks a physical notch.
+ private var isNonNotchScreen: Bool {
+ let screenName = vm.screen ?? coordinator.selectedScreen
+ guard let screen = NSScreen.screens.first(where: { $0.localizedName == screenName }) else {
+ return true
+ }
+ return screen.safeAreaInsets.top <= 0
+ }
+
+ /// Whether the notch/island should hide off-screen when closed on a non-notch display.
+ private var shouldHideUntilHover: Bool {
+ hideNonNotchUntilHover && isNonNotchScreen && vm.notchState == .closed
+ }
+
+ /// Pill shape for Dynamic Island mode with animated corner radius transitions.
+ private var currentPillShape: DynamicIslandPillShape {
+ let radius: CGFloat
+ if vm.notchState == .open {
+ radius = enableMinimalisticUI
+ ? minimalisticCornerRadiusInsets.opened.top
+ : dynamicIslandPillCornerRadiusInsets.opened
+ } else {
+ // Use half the closed height for a true capsule shape
+ radius = max(vm.closedNotchSize.height / 2, dynamicIslandPillCornerRadiusInsets.closed.standard)
+ }
+ return DynamicIslandPillShape(cornerRadius: radius)
+ }
+
+ /// Resolves the clip/content shape per-screen: pill on non-notch screens
+ /// when dynamic island mode is active, standard notch shape otherwise.
+ private var resolvedClipShape: AnyShape {
+ if isDynamicIslandMode {
+ return AnyShape(currentPillShape)
+ }
+ return AnyShape(currentNotchShape)
+ }
+
var body: some View {
let interactionsEnabled = !lockScreenManager.isLocked
+ let isIslandMode = isDynamicIslandMode
let notchHorizontalPadding: CGFloat = {
guard vm.notchState == .open else {
return activeCornerRadiusInsets.closed.bottom
@@ -249,6 +295,8 @@ struct ContentView: View {
return vm.effectiveClosedNotchHeight == 0 ? zeroHeightHoverPadding : 0
}()
let notchBottomPadding = currentShadowPadding + hoverAreaPadding
+ // Extra top padding to detach pill from screen edge in Dynamic Island mode
+ let pillTopOffset: CGFloat = isIslandMode ? dynamicIslandTopOffset : 0
ZStack(alignment: .top) {
let mainLayout = NotchLayout()
@@ -256,7 +304,7 @@ struct ContentView: View {
.padding(.horizontal, notchHorizontalPadding)
.padding([.horizontal, .bottom], vm.notchState == .open ? 12 : 0)
.background(.black)
- .clipShape(currentNotchShape)
+ .clipShape(resolvedClipShape)
.compositingGroup()
.shadow(
color: ((vm.notchState == .open || isHovering) && Defaults[.enableShadow])
@@ -264,7 +312,16 @@ struct ContentView: View {
: .clear,
radius: Defaults[.cornerRadiusScaling] ? 10 : 5
)
+ // Extra horizontal inset for Dynamic Island mode so the shadow
+ // is not clipped by the outer frame constraint
+ .padding(.horizontal, isIslandMode ? dynamicIslandShadowInset : 0)
+ .padding(.top, pillTopOffset)
.padding(.bottom, notchBottomPadding)
+ // Hide off-screen when non-notch hide-until-hover is active
+ .offset(y: shouldHideUntilHover && !isHovering
+ ? -(vm.closedNotchSize.height + pillTopOffset + currentShadowPadding + 10)
+ : 0
+ )
mainLayout
.conditionalModifier(!useModernCloseAnimation) { view in
@@ -288,7 +345,7 @@ struct ContentView: View {
}
.conditionalModifier(interactionsEnabled) { view in
view
- .contentShape(currentNotchShape)
+ .contentShape(resolvedClipShape)
.onHover { hovering in
handleHover(hovering)
}
@@ -410,8 +467,8 @@ struct ContentView: View {
}
.frame(
- maxWidth: dynamicNotchSize.width,
- maxHeight: dynamicNotchSize.height + currentShadowPadding,
+ maxWidth: dynamicNotchSize.width + (isDynamicIslandMode ? dynamicIslandShadowInset * 2 : 0),
+ maxHeight: dynamicNotchSize.height + currentShadowPadding + (isDynamicIslandMode ? dynamicIslandTopOffset : 0),
alignment: .top
)
.frame(maxHeight: .infinity, alignment: .top)
diff --git a/DynamicIsland/DynamicIslandApp.swift b/DynamicIsland/DynamicIslandApp.swift
index ff5c2c4..bee93b2 100644
--- a/DynamicIsland/DynamicIslandApp.swift
+++ b/DynamicIsland/DynamicIslandApp.swift
@@ -224,7 +224,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
-> NSWindow
{
// Use the current required size instead of always using openNotchSize
- let requiredSize = calculateRequiredNotchSize()
+ let baseSize = calculateRequiredNotchSize()
+ let requiredSize = adjustedSizeForScreen(baseSize, screen: screen)
let window = DynamicIslandWindow(
contentRect: NSRect(
@@ -317,10 +318,25 @@ class AppDelegate: NSObject, NSApplicationDelegate {
isStatsTabActive: coordinator.currentView == .stats,
secondRowProgress: coordinator.statsSecondRowExpansion
)
- return addShadowPadding(
+ var result = addShadowPadding(
to: adjustedContentSize,
isMinimalistic: Defaults[.enableMinimalisticUI]
)
+
+ return result
+ }
+
+ /// Adjusts a base notch size for a specific screen by adding Dynamic Island
+ /// shadow insets and top-offset only when the screen lacks a physical notch
+ /// and the user has chosen the Dynamic Island style.
+ private func adjustedSizeForScreen(_ baseSize: CGSize, screen: NSScreen) -> CGSize {
+ guard shouldUseDynamicIslandMode(for: screen.localizedName) else {
+ return baseSize
+ }
+ var adjusted = baseSize
+ adjusted.width += dynamicIslandShadowInset * 2
+ adjusted.height += dynamicIslandTopOffset
+ return adjusted
}
func ensureWindowSize(_ size: CGSize, animated: Bool, force: Bool = false) {
@@ -332,15 +348,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if Defaults[.showOnAllDisplays] {
for (screen, window) in windows {
- if force || window.frame.size != size {
- resizeWindow(window, on: screen, to: size, animated: animated)
+ let screenSize = adjustedSizeForScreen(size, screen: screen)
+ if force || window.frame.size != screenSize {
+ resizeWindow(window, on: screen, to: screenSize, animated: animated)
}
}
} else if let window {
let screen = window.screen ?? NSScreen.screens.first { $0.frame.intersects(window.frame) } ?? NSScreen.main ?? NSScreen.screens.first
guard let screen else { return }
- if force || window.frame.size != size {
- resizeWindow(window, on: screen, to: size, animated: animated)
+ let screenSize = adjustedSizeForScreen(size, screen: screen)
+ if force || window.frame.size != screenSize {
+ resizeWindow(window, on: screen, to: screenSize, animated: animated)
}
}
}
diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings
index b3c3e64..734637b 100644
--- a/DynamicIsland/Localizable.xcstrings
+++ b/DynamicIsland/Localizable.xcstrings
@@ -1591,6 +1591,10 @@
}
}
},
+ "±10 Seconds" : {
+ "comment" : "Display name for the \"±10 Seconds\" music skip behavior.",
+ "isCommentAutoGenerated" : true
+ },
"0" : {
"comment" : "A label displayed in a circular gauge to indicate the minimum battery level.",
"isCommentAutoGenerated" : true,
@@ -1907,6 +1911,10 @@
}
}
},
+ "Accent" : {
+ "comment" : "Name of the Caps Lock Indicator Tint Mode option that uses the accent color.",
+ "isCommentAutoGenerated" : true
+ },
"Accent color" : {
"localizations" : {
"ar" : {
@@ -2252,6 +2260,10 @@
}
}
},
+ "Adaptive gradient" : {
+ "comment" : "Name of the timer icon color mode that uses an adaptive gradient.",
+ "isCommentAutoGenerated" : true
+ },
"Add" : {
"localizations" : {
"ar" : {
@@ -3841,6 +3853,10 @@
}
}
},
+ "Apple Music" : {
+ "comment" : "Names for the different media player types available in the app.",
+ "isCommentAutoGenerated" : true
+ },
"Applies the notch-style parallax effect to the lock screen media widget album art." : {
"localizations" : {
"pt-BR" : {
@@ -4252,6 +4268,10 @@
}
}
},
+ "Bar" : {
+ "comment" : "Localized name for the \"Bar\" timer progress style.",
+ "isCommentAutoGenerated" : true
+ },
"Battery" : {
"localizations" : {
"ar" : {
@@ -6234,6 +6254,10 @@
}
}
},
+ "Classic notch shape that blends into the top screen edge" : {
+ "comment" : "Description of a notch shape that blends into the top screen edge.",
+ "isCommentAutoGenerated" : true
+ },
"Clear" : {
"extractionState" : "stale",
"localizations" : {
@@ -8491,6 +8515,10 @@
}
}
},
+ "Digital" : {
+ "comment" : "The name of the \"Digital\" style of reminder presentation.",
+ "isCommentAutoGenerated" : true
+ },
"Dimensions" : {
"localizations" : {
"pt-BR" : {
@@ -9256,6 +9284,10 @@
}
}
},
+ "Dynamic Island" : {
+ "comment" : "Description of the dynamic island style.",
+ "isCommentAutoGenerated" : true
+ },
"Dynamic Island Progress Bars" : {
"comment" : "A section header that lists the options for customizing the appearance of the progress bars displayed by Dynamic Island.",
"isCommentAutoGenerated" : true,
@@ -10814,6 +10846,10 @@
}
}
},
+ "External display style" : {
+ "comment" : "A picker that lets the user choose the style of the external display.",
+ "isCommentAutoGenerated" : true
+ },
"Favorites" : {
"comment" : "Localized name of the \"Favorites\" tab in the clipboard.",
"isCommentAutoGenerated" : true
@@ -11861,6 +11897,10 @@
}
}
},
+ "Green" : {
+ "comment" : "Display name for the \"Green\" Caps Lock Indicator Tint Mode.",
+ "isCommentAutoGenerated" : true
+ },
"Height:" : {
"extractionState" : "stale",
"localizations" : {
@@ -15527,7 +15567,6 @@
}
},
"Minutes" : {
- "extractionState" : "stale",
"localizations" : {
"fr" : {
"stringUnit" : {
@@ -17737,6 +17776,10 @@
}
}
},
+ "Now Playing" : {
+ "comment" : "Localized name for each media controller type.",
+ "isCommentAutoGenerated" : true
+ },
"Off" : {
"comment" : "Text displayed in a status label at the top of the screen indicating whether low power mode is on or off.",
"isCommentAutoGenerated" : true
@@ -18103,6 +18146,10 @@
}
}
},
+ "Panel" : {
+ "comment" : "Display name for the \"Panel\" clipboard display mode.",
+ "isCommentAutoGenerated" : true
+ },
"Panel mode shows clipboard in a floating window near the notch." : {
"comment" : "A description of the \"Panel\" clipboard display mode.",
"isCommentAutoGenerated" : true,
@@ -18232,6 +18279,10 @@
}
}
},
+ "Pill-shaped island with rounded corners, similar to iPhone's Dynamic Island" : {
+ "comment" : "Description of the Dynamic Island style for external displays.",
+ "isCommentAutoGenerated" : true
+ },
"Pin items from history to save them here" : {
"localizations" : {
"pt-BR" : {
@@ -19697,6 +19748,10 @@
}
}
},
+ "Ring" : {
+ "comment" : "Localized name for the \"Ring\" timer progress style.",
+ "isCommentAutoGenerated" : true
+ },
"Rotation:" : {
"extractionState" : "stale",
"localizations" : {
@@ -20517,6 +20572,10 @@
}
}
},
+ "Separate Tab" : {
+ "comment" : "Name of the clipboard display mode option that opens the clipboard in a separate tab.",
+ "isCommentAutoGenerated" : true
+ },
"Separate Tab mode integrates Copied Items and Notes into a single view. If both are enabled, Notes appear on the right and Clipboard on the left." : {
"comment" : "A description of the \"Separate Tab\" clipboard display mode.",
"isCommentAutoGenerated" : true,
@@ -21571,6 +21630,14 @@
}
}
},
+ "Shows screen assistant as a dropdown attached to the AI button" : {
+ "comment" : "Description of the \"popover\" display mode for the screen assistant.",
+ "isCommentAutoGenerated" : true
+ },
+ "Shows screen assistant in a floating panel near the notch" : {
+ "comment" : "Description of the \"Panel\" screen assistant display mode.",
+ "isCommentAutoGenerated" : true
+ },
"Shows the model's reasoning process before the final answer" : {
"localizations" : {
"fr" : {
@@ -21633,6 +21700,10 @@
}
}
},
+ "Skip forward or backward by ten seconds" : {
+ "comment" : "Description of the \"Skip forward or backward by ten seconds\" music skip behavior option.",
+ "isCommentAutoGenerated" : true
+ },
"Slider color" : {
"localizations" : {
"ar" : {
@@ -22200,6 +22271,10 @@
}
}
},
+ "Spotify" : {
+ "comment" : "Add more media player types here.",
+ "isCommentAutoGenerated" : true
+ },
"Square" : {
"localizations" : {
"ar" : {
@@ -22302,12 +22377,20 @@
},
"Standard" : {
+ },
+ "Standard Notch" : {
+ "comment" : "Text for the \"Standard Notch\" option in the external display style picker.",
+ "isCommentAutoGenerated" : true
},
"Standard notch media controls are hidden while this toggle is off." : {
},
"Standard notch media controls are hidden. Re-enable the toggle above to restore them." : {
+ },
+ "Standard previous/next track controls" : {
+ "comment" : "Description of a music skip behavior option that uses standard previous/next track controls.",
+ "isCommentAutoGenerated" : true
},
"Start" : {
"localizations" : {
@@ -23657,6 +23740,10 @@
},
"shouldTranslate" : false
},
+ "Track Skip" : {
+ "comment" : "Name of the music skip behavior option that skips to the next track.",
+ "isCommentAutoGenerated" : true
+ },
"Transform" : {
"extractionState" : "stale",
"localizations" : {
@@ -25243,6 +25330,10 @@
}
}
},
+ "When enabled, the notch slides up and hides on external (non-notch) displays until you hover over it." : {
+ "comment" : "A description of the feature that hides the notch on non-notch displays.",
+ "isCommentAutoGenerated" : true
+ },
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
"localizations" : {
"pt-BR" : {
@@ -25567,6 +25658,10 @@
"comment" : "A footer note explaining the app's funding.",
"isCommentAutoGenerated" : true
},
+ "Youtube Music" : {
+ "comment" : "Name for the \"Youtube Music\" media controller type in settings.",
+ "isCommentAutoGenerated" : true
+ },
"YouTube Music requires this third-party app to be installed: " : {
"localizations" : {
"ar" : {
diff --git a/DynamicIsland/components/Notch/DynamicIslandPillShape.swift b/DynamicIsland/components/Notch/DynamicIslandPillShape.swift
new file mode 100644
index 0000000..18d8899
--- /dev/null
+++ b/DynamicIsland/components/Notch/DynamicIslandPillShape.swift
@@ -0,0 +1,50 @@
+/*
+ * Atoll (DynamicIsland)
+ * Copyright (C) 2024-2026 Atoll Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import SwiftUI
+
+/// A pill-shaped (capsule-like) clip shape used for the Dynamic Island mode
+/// on external and non-notched displays. Uses `.continuous` rounded corners
+/// for an Apple-style squircle appearance, inspired by DynamicNotchKit's
+/// floating style.
+struct DynamicIslandPillShape: Shape {
+ var cornerRadius: CGFloat
+
+ var animatableData: CGFloat {
+ get { cornerRadius }
+ set { cornerRadius = newValue }
+ }
+
+ func path(in rect: CGRect) -> Path {
+ let resolvedRadius = min(cornerRadius, min(rect.width, rect.height) / 2)
+ return Path(roundedRect: rect, cornerRadius: resolvedRadius, style: .continuous)
+ }
+}
+
+#Preview {
+ VStack(spacing: 20) {
+ DynamicIslandPillShape(cornerRadius: 16)
+ .fill(.black)
+ .frame(width: 185, height: 32)
+
+ DynamicIslandPillShape(cornerRadius: 24)
+ .fill(.black)
+ .frame(width: 640, height: 200)
+ }
+ .padding(20)
+}
diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift
index cb80cb8..4e81f35 100644
--- a/DynamicIsland/components/Settings/SettingsView.swift
+++ b/DynamicIsland/components/Settings/SettingsView.swift
@@ -606,6 +606,8 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .general, title: "Extend hover area", keywords: ["hover", "cursor"], highlightID: SettingsTab.general.highlightID(for: "Extend hover area")),
SettingsSearchEntry(tab: .general, title: "Enable haptics", keywords: ["haptic", "feedback"], highlightID: SettingsTab.general.highlightID(for: "Enable haptics")),
SettingsSearchEntry(tab: .general, title: "Open notch on hover", keywords: ["hover to open", "auto open"], highlightID: SettingsTab.general.highlightID(for: "Open notch on hover")),
+ SettingsSearchEntry(tab: .general, title: "External display style", keywords: ["dynamic island", "pill", "external display", "non-notch", "floating", "capsule"], highlightID: SettingsTab.general.highlightID(for: "External display style")),
+ SettingsSearchEntry(tab: .general, title: "Hide until hovered", keywords: ["hide", "hover", "external", "non-notch", "auto hide", "slide"], highlightID: SettingsTab.general.highlightID(for: "Hide until hovered")),
SettingsSearchEntry(tab: .general, title: "Notch display height", keywords: ["display height", "menu bar size"], highlightID: SettingsTab.general.highlightID(for: "Notch display height")),
// Live Activities
@@ -905,6 +907,8 @@ struct GeneralSettings: View {
@Default(.musicGestureBehavior) var musicGestureBehavior
@Default(.reverseSwipeGestures) var reverseSwipeGestures
@Default(.reverseScrollGestures) var reverseScrollGestures
+ @Default(.externalDisplayStyle) var externalDisplayStyle
+ @Default(.hideNonNotchUntilHover) var hideNonNotchUntilHover
private func highlightID(_ title: String) -> String {
SettingsTab.general.highlightID(for: title)
@@ -1118,6 +1122,24 @@ struct GeneralSettings: View {
NotificationCenter.default.post(name: Notification.Name.notchHeightChanged, object: nil)
}
}
+ Picker("External display style", selection: $externalDisplayStyle) {
+ ForEach(ExternalDisplayStyle.allCases) { style in
+ Text(style.localizedName)
+ .tag(style)
+ }
+ }
+ .onChange(of: externalDisplayStyle) {
+ NotificationCenter.default.post(name: Notification.Name.notchHeightChanged, object: nil)
+ }
+ .settingsHighlight(id: highlightID("External display style"))
+ Text(externalDisplayStyle.description)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ Defaults.Toggle("Hide until hovered on non-notch displays", key: .hideNonNotchUntilHover)
+ .settingsHighlight(id: highlightID("Hide until hovered"))
+ Text("When enabled, the notch slides up and hides on external (non-notch) displays until you hover over it.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
} header: {
Text("Notch behavior")
}
diff --git a/DynamicIsland/enums/generic.swift b/DynamicIsland/enums/generic.swift
index f9a9b8d..7b7eb52 100644
--- a/DynamicIsland/enums/generic.swift
+++ b/DynamicIsland/enums/generic.swift
@@ -29,6 +29,36 @@ public enum Style {
case floating
}
+/// Controls how Atoll renders on external and non-notched displays.
+/// - `notch`: Standard notch shape (concave top corners blending into the screen edge).
+/// - `dynamicIsland`: Pill-shaped island with continuously rounded corners,
+/// inspired by DynamicNotchKit's floating style. Only applies to screens
+/// that do NOT have a physical notch.
+enum ExternalDisplayStyle: String, CaseIterable, Defaults.Serializable, Identifiable {
+ case notch = "Standard Notch"
+ case dynamicIsland = "Dynamic Island"
+
+ var id: String { rawValue }
+
+ var localizedName: String {
+ switch self {
+ case .notch:
+ return String(localized: "Standard Notch")
+ case .dynamicIsland:
+ return String(localized: "Dynamic Island")
+ }
+ }
+
+ var description: String {
+ switch self {
+ case .notch:
+ return String(localized: "Classic notch shape that blends into the top screen edge")
+ case .dynamicIsland:
+ return String(localized: "Pill-shaped island with rounded corners, similar to iPhone's Dynamic Island")
+ }
+ }
+}
+
public enum ContentType: Int, Codable, Hashable, Equatable {
case normal
case menu
diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift
index b470a79..5e38106 100644
--- a/DynamicIsland/models/Constants.swift
+++ b/DynamicIsland/models/Constants.swift
@@ -699,6 +699,11 @@ extension Defaults.Keys {
static let enableHaptics = Key("enableHaptics", default: true)
static let openNotchOnHover = Key("openNotchOnHover", default: true)
static let extendHoverArea = Key("extendHoverArea", default: false)
+ static let externalDisplayStyle = Key(
+ "externalDisplayStyle",
+ default: .notch
+ )
+ static let hideNonNotchUntilHover = Key("hideNonNotchUntilHover", default: false)
static let notchHeightMode = Key(
"notchHeightMode",
default: WindowHeightMode.matchRealNotchSize
diff --git a/DynamicIsland/sizing/matters.swift b/DynamicIsland/sizing/matters.swift
index 5987a3e..3757150 100644
--- a/DynamicIsland/sizing/matters.swift
+++ b/DynamicIsland/sizing/matters.swift
@@ -104,6 +104,51 @@ func addShadowPadding(to size: CGSize, isMinimalistic: Bool) -> CGSize {
CGSize(width: size.width, height: size.height + notchShadowPaddingValue(isMinimalistic: isMinimalistic))
}
+/// Determines whether a specific screen should render the Dynamic Island pill
+/// shape instead of the standard notch shape.
+///
+/// Returns `true` only when ALL of these conditions are met:
+/// 1. The user has selected `.dynamicIsland` in `externalDisplayStyle`
+/// 2. The screen does NOT have a physical notch (safeAreaInsets.top == 0)
+///
+/// Screens with a physical notch always use the standard notch shape.
+func shouldUseDynamicIslandMode(for screenName: String?) -> Bool {
+ guard Defaults[.externalDisplayStyle] == .dynamicIsland else {
+ return false
+ }
+
+ var selectedScreen: NSScreen? = NSScreen.main
+ if let screenName {
+ selectedScreen = NSScreen.screens.first(where: { $0.localizedName == screenName })
+ }
+
+ guard let screen = selectedScreen else {
+ // No screen found — fallback to standard notch
+ return false
+ }
+
+ // Physical notch screens always use standard notch shape
+ return screen.safeAreaInsets.top <= 0
+}
+
+/// Corner radius insets for the Dynamic Island pill shape.
+/// - closed: half the closed notch height for a true capsule look
+/// - opened: generous radius for smooth expanded pill
+let dynamicIslandPillCornerRadiusInsets: (opened: CGFloat, closed: (standard: CGFloat, minimalistic: CGFloat)) = (
+ opened: 24,
+ closed: (standard: 16, minimalistic: 16)
+)
+
+/// Vertical offset from the top screen edge for the Dynamic Island pill.
+/// Creates a visual gap so the pill floats below the menu bar, mimicking
+/// the iPhone's Dynamic Island detachment from the physical screen edge.
+let dynamicIslandTopOffset: CGFloat = 6
+
+/// Extra horizontal padding applied OUTSIDE the pill clip shape in Dynamic
+/// Island mode so the drop shadow has room to render without being clipped
+/// by the outer frame constraint.
+let dynamicIslandShadowInset: CGFloat = 14
+
enum MusicPlayerImageSizes {
static let cornerRadiusInset: (opened: CGFloat, closed: CGFloat) = (opened: 13.0, closed: 4.0)
static let size = (opened: CGSize(width: 90, height: 90), closed: CGSize(width: 20, height: 20))