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
10 changes: 6 additions & 4 deletions .impeccable/design.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": 2,
"generatedAt": "2026-09-07T21:00:00Z",
"generatedAt": "2026-09-09T23:55:00Z",
"title": "Design System: uNotch",
"extensions": {
"colorMeta": {
Expand Down Expand Up @@ -33,6 +33,8 @@
]
},
"components": [
{"name": "Brand Mark", "kind": "image", "description": "The mark is a **screen with a side notch**: a rounded rectangular screen outline\nwith a solid mint rail extending inward from its left edge. It describes the\nactual app placement, with no letterform or detached status dot.\nGeometry lives on a 1024 grid (see `logo/mark.svg`):\n\n- Screen: (176, 256), 672 × 512, corner radius 80, stroke 64.\n- Side notch: x=144–336, y=392–632; flush left edge and 56 px inner corners.\n Its left edge aligns with the outside of the screen stroke.\n- Tile variant (`logo/icon.svg`): charcoal rounded square (radius 205 on 928),\n paper screen, mint notch. This is the macOS app icon and the social avatar.\n- Favicon: the same mark on a full-bleed charcoal tile (radius 224 on 1024).\n- Mono variants (`logo/mark-mono-black.svg`, `logo/mark-mono-white.svg`) render\n the screen and notch in the same ink.\n- **Menu bar**: always a macOS *template* image (system tints it). Never draw\n the mint notch in colour in the menu bar.\n\nClear space: keep at least one stroke width (64/1024 of the mark height) free\naround the mark. Minimum size: 16 px. The screen and attached side notch must\nremain distinct at that size; never omit the notch.", "html": "<img src=\"brand/logo/icon.svg\" width=\"32\" height=\"32\" alt=\"uNotch\">", "css": ""},
{"name": "Social Preview", "kind": "image", "description": "The share card is a 1200 × 630 PNG, composed in `social/card.html` and rendered\nwith `scripts/generate-social.sh`. It uses the backdrop ground, charcoal type,\nthe existing tagline, the new icon, and a screen showing the actual site HUD\nwith illustrative usage values. Provider names, free/open-source status, and\nmacOS requirements come from the site. Keep primary content 64 px from the\ncard edges. Use a new versioned image filename whenever its pixels change,\nsince `/assets/` is cached immutably. Open Graph and Twitter metadata must\nreference the same absolute PNG URL, with dimensions and descriptive alt text.", "html": "<img src=\"assets/unotch-social-v2.png\" width=\"1200\" height=\"630\" alt=\"uNotch — Live AI usage, quietly tucked into the edge of your Mac.\">", "css": ""},
{
"name": "Primary Button",
"kind": "button",
Expand Down Expand Up @@ -84,7 +86,7 @@
],
"narrative": {
"northStar": "Tucked",
"overview": "uNotch is a quiet instrument. It lives at the edge of a Mac screen as a seven-point cue and only becomes a readout when the pointer asks. Everything in the system follows from that posture: dark glass that borrows the desktop behind it, ink that is paper at opacity rather than fixed grey, and one accent — the mint status point from the logo — that appears only where something is live or where the user acts. The system is native first: SF Rounded numerals, the system text face, NSVisualEffectView glass, and no loaded web fonts or third-party requests anywhere, on the app or on the site.\n\nThe system explicitly rejects the \"AI tool\" landing-page look: purple-to-blue gradients, neon glows, card grids of features, floating blobs, eyebrow caps labels over every heading, and marketing verbs. It also rejects dashboard density. uNotch shows a handful of numbers; each one gets room.",
"overview": "uNotch is a quiet instrument. It lives at the edge of a Mac screen as a seven-point cue and only becomes a readout when the pointer asks. Everything in the system follows from that posture: dark glass that borrows the desktop behind it, ink that is paper at opacity rather than fixed grey, and one accent — the mint side notch in the logo — that appears only where something is live or where the user acts. The system is native first: SF Rounded numerals, the system text face, NSVisualEffectView glass, and no loaded web fonts or third-party requests anywhere, on the app or on the site.\n\nThe system explicitly rejects the \"AI tool\" landing-page look: purple-to-blue gradients, neon glows, card grids of features, floating blobs, eyebrow caps labels over every heading, and marketing verbs. It also rejects dashboard density. uNotch shows a handful of numbers; each one gets room.",
"keyCharacteristics": [
"Dark glass surfaces tinted charcoal; depth from tint, never from stacked shadows",
"One accent (mint) rationed to live state and the single primary action",
Expand Down Expand Up @@ -116,10 +118,10 @@
"Don't put features in a grid of cards. Rows with hairlines.",
"Don't add eyebrow caps labels above headings.",
"Don't add a second shadow anywhere; the callout is the only surface that casts one.",
"Don't colour the mint point in the menu bar; the menu bar mark is a template image.",
"Don't colour the mint notch in the menu bar; the menu bar mark is a template image.",
"Don't use the words seamless, powerful, supercharge, empower, or effortless.",
"Don't load a web font. If the display face is unavailable, the system face is correct.",
"Don't let the mark shrink below 16 px or draw it without the status point."
"Don't let the mark shrink below 16 px or draw it without the attached side notch."
]
}
}
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ All notable changes to uNotch are recorded here. The format follows

## [Unreleased]

### Changed
- The logo now depicts a screen with a notch tucked into its left edge, across
the app icon, menu bar, settings, website, and favicons.
- Link previews now show a designed share card with the tagline and usage HUD,
with complete Open Graph and Twitter image metadata.

## [1.2.0] — 2026-09-07

### Added
Expand Down
11 changes: 11 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ UNOTCH_SNAPSHOT=/tmp/hud.png UNOTCH_SETTINGS_SNAPSHOT=/tmp/settings.png swift te

The website is `site/index.html`; `scripts/build-site.sh` assembles it with the
brand tokens into `_site/` for a local preview (`python3 -m http.server -d _site`).
Logo geometry lives in `brand/logo/`. After changing it, update the matching
Swift paths and regenerate the web exports with `scripts/generate-favicons.sh`
(requires ImageMagick). The app icon is generated during DMG packaging.

The share card's editable layout is `brand/social/card.html`. It borrows the
website's HUD and styles when rendered with `scripts/generate-social.sh`
(requires Python 3, Playwright CLI with Chromium, and ImageMagick; render on macOS
for the system font). The PNG is committed, so deployment needs no renderer.
When changing it, choose a new filename in the generator and both image meta
tags in `site/index.html` because `/assets/` has immutable caching.

To test exactly what production serves, build the image from the repo root:

```sh
Expand Down
44 changes: 39 additions & 5 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ components:
---

<!--
GENERATED from brand/BRAND.md + brand/tokens.json (2026-09-07, backdrop surface added).
GENERATED from brand/BRAND.md + brand/tokens.json (2026-09-09, screen-and-notch mark and share card).
The brand folder is the canon and wins any conflict with this file.
REGENERATE this file when the canon changes — never hand-edit it.
-->
Expand All @@ -145,7 +145,7 @@ components:
uNotch is a quiet instrument. It lives at the edge of a Mac screen as a seven-point cue and
only becomes a readout when the pointer asks. Everything in the system follows from that
posture: dark glass that borrows the desktop behind it, ink that is paper at opacity rather
than fixed grey, and one accent — the mint status point from the logo — that appears only
than fixed grey, and one accent — the mint side notch in the logo — that appears only
where something is live or where the user acts. The system is native first: SF Rounded
numerals, the system text face, `NSVisualEffectView` glass, and no loaded web fonts or
third-party requests anywhere, on the app or on the site.
Expand Down Expand Up @@ -174,7 +174,7 @@ A charcoal ground, paper ink, and a single mint signal.

### Primary
- **Mint** (#3BE29B): the status point. The "remaining" fill of the selected provider ring
and bars, the point on the mark, and the one primary button on a page. 10.5:1 on charcoal.
and bars, the side notch on the mark, and the one primary button on a page. 10.5:1 on charcoal.

### Neutral
- **Charcoal** (#15191B): brand ground for the product — icon tile, the tint under glass — and
Expand Down Expand Up @@ -248,6 +248,40 @@ never thicker than 1 px.

## 5. Components

### Brand mark

The mark is a **screen with a side notch**: a rounded rectangular screen outline
with a solid mint rail extending inward from its left edge. It describes the
actual app placement, with no letterform or detached status dot.
Geometry lives on a 1024 grid (see `logo/mark.svg`):

- Screen: (176, 256), 672 × 512, corner radius 80, stroke 64.
- Side notch: x=144–336, y=392–632; flush left edge and 56 px inner corners.
Its left edge aligns with the outside of the screen stroke.
- Tile variant (`logo/icon.svg`): charcoal rounded square (radius 205 on 928),
paper screen, mint notch. This is the macOS app icon and the social avatar.
- Favicon: the same mark on a full-bleed charcoal tile (radius 224 on 1024).
- Mono variants (`logo/mark-mono-black.svg`, `logo/mark-mono-white.svg`) render
the screen and notch in the same ink.
- **Menu bar**: always a macOS *template* image (system tints it). Never draw
the mint notch in colour in the menu bar.

Clear space: keep at least one stroke width (64/1024 of the mark height) free
around the mark. Minimum size: 16 px. The screen and attached side notch must
remain distinct at that size; never omit the notch.

### Social preview

The share card is a 1200 × 630 PNG, composed in `social/card.html` and rendered
with `scripts/generate-social.sh`. It uses the backdrop ground, charcoal type,
the existing tagline, the new icon, and a screen showing the actual site HUD
with illustrative usage values. Provider names, free/open-source status, and
macOS requirements come from the site. Keep primary content 64 px from the
card edges. Use a new versioned image filename whenever its pixels change,
since `/assets/` is cached immutably. Open Graph and Twitter metadata must
reference the same absolute PNG URL, with dimensions and descriptive alt text.


### Buttons
- **Shape:** pill (999px) on the site; rounded control (11px) inside the HUD.
- **Primary (dark glass):** mint fill, charcoal text, 600 weight, 12px 20px padding, 44px
Expand Down Expand Up @@ -320,9 +354,9 @@ never thicker than 1 px.
- **Don't** put features in a grid of cards. Rows with hairlines.
- **Don't** add eyebrow caps labels above headings.
- **Don't** add a second shadow anywhere; the callout is the only surface that casts one.
- **Don't** colour the mint point in the menu bar; the menu bar mark is a template image.
- **Don't** colour the mint notch in the menu bar; the menu bar mark is a template image.
- **Don't** use the words seamless, powerful, supercharge, empower, or effortless.
- **Don't** load a web font. If the display face is unavailable, the system face is correct.
- **Don't** put ash or slate text over a strong backdrop layer; full charcoal only (The
Strong-Layer Rule). Don't fade the render with painted scrims — they seam against the ambient.
- **Don't** let the mark shrink below 16 px or draw it without the status point.
- **Don't** let the mark shrink below 16 px or draw it without the attached side notch.
48 changes: 28 additions & 20 deletions Sources/uNotch/Brand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,43 +48,51 @@ enum AppInfo {
}
}

/// The brand mark drawn in code so the menu bar, HUD, and icon share one geometry.
/// Coordinates follow the 1024 grid in `brand/logo/mark.svg`.
/// The screen and side notch use the 1024 grid in `brand/logo/mark.svg`.
/// MenuBarLogo also draws these paths so the template and HUD stay consistent.
struct BrandMarkShape: Shape {
func path(in rect: CGRect) -> Path {
let s = min(rect.width, rect.height) / 1024
let origin = CGPoint(x: rect.midX - 512 * s, y: rect.midY - 512 * s)
return Path(roundedRect: CGRect(
x: origin.x + 176 * s, y: origin.y + 256 * s,
width: 672 * s, height: 512 * s
), cornerRadius: 80 * s)
}
}

struct BrandNotchShape: Shape {
func path(in rect: CGRect) -> Path {
let s = min(rect.width, rect.height) / 1024
let ox = rect.midX - 512 * s
let oy = rect.midY - 512 * s
func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint { CGPoint(x: ox + x * s, y: oy + y * s) }

func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint {
CGPoint(x: ox + x * s, y: oy + y * s)
}
var path = Path()
path.move(to: p(310, 308))
path.addLine(to: p(310, 558))
path.addCurve(to: p(714, 558), control1: p(310, 760), control2: p(714, 760))
path.addLine(to: p(714, 308))
path.move(to: p(144, 392))
path.addLine(to: p(280, 392))
path.addCurve(to: p(336, 448), control1: p(311, 392), control2: p(336, 417))
path.addLine(to: p(336, 576))
path.addCurve(to: p(280, 632), control1: p(336, 607), control2: p(311, 632))
path.addLine(to: p(144, 632))
path.closeSubpath()
return path
}
}

struct BrandMark: View {
var ink: Color = Brand.paper
var point: Color = Brand.mint
var notch: Color = Brand.mint

var body: some View {
GeometryReader { proxy in
let side = min(proxy.size.width, proxy.size.height)
let s = side / 1024
// Optical floor: below ~48 pt the point and stroke thin out, so clamp them
// (BRAND.md: "if the point disappears at 16 px, the mark has been drawn wrong").
let stroke = max(84 * s, 1.6)
let radius = max(39 * s, 1.4)
let s = min(proxy.size.width, proxy.size.height) / 1024
ZStack {
BrandMarkShape()
.stroke(ink, style: StrokeStyle(lineWidth: stroke, lineCap: .round))
Circle()
.fill(point)
.frame(width: radius * 2, height: radius * 2)
.position(x: proxy.size.width / 2 + (714 - 512) * s, y: proxy.size.height / 2 + (256 - 512) * s)
.stroke(ink, lineWidth: max(64 * s, 1.25))
BrandNotchShape()
.fill(notch)
}
}
.accessibilityHidden(true)
Expand Down
27 changes: 9 additions & 18 deletions Sources/uNotch/UNotchApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,28 +154,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
}
}

/// Menu bar mark: the brand geometry as a template image so macOS tints it.
/// The status point is drawn in the same ink here by design (see brand/BRAND.md).
/// The shared screen-and-notch paths as a template image that macOS tints.
private enum MenuBarLogo {
static func makeImage() -> NSImage {
let side: CGFloat = 18
let image = NSImage(size: NSSize(width: side, height: side), flipped: true) { rect in
let s = side / 1024
func p(_ x: CGFloat, _ y: CGFloat) -> NSPoint { NSPoint(x: x * s, y: y * s) }

NSColor.black.setStroke()
NSColor.black.setFill()
let mark = NSBezierPath()
mark.lineWidth = max(1.8, 84 * s * 1.3)
mark.lineCapStyle = .round
mark.move(to: p(310, 308))
mark.line(to: p(310, 558))
mark.curve(to: p(714, 558), controlPoint1: p(310, 760), controlPoint2: p(714, 760))
mark.line(to: p(714, 308))
mark.stroke()

let r = max(1.1, 39 * s * 1.5)
NSBezierPath(ovalIn: NSRect(x: 714 * s - r, y: 256 * s - r, width: r * 2, height: r * 2)).fill()
guard let context = NSGraphicsContext.current?.cgContext else { return false }
context.setStrokeColor(NSColor.black.cgColor)
context.setFillColor(NSColor.black.cgColor)
context.setLineWidth(max(1.25, 64 * side / 1024))
context.addPath(BrandMarkShape().path(in: rect).cgPath)
context.strokePath()
context.addPath(BrandNotchShape().path(in: rect).cgPath)
context.fillPath()
return true
}
image.isTemplate = true
Expand Down
45 changes: 29 additions & 16 deletions brand/BRAND.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,36 @@ One word to keep in mind while designing: *tucked*.

## Mark

The mark is a **u-shaped notch** with a **status point** at its top-right terminal.
The mark is a **screen with a side notch**: a rounded rectangular screen outline
with a solid mint rail extending inward from its left edge. It describes the
actual app placement, with no letterform or detached status dot.
Geometry lives on a 1024 grid (see `logo/mark.svg`):

- Stroke 84, round caps; the arms rise from y=308 to the bowl at y=558.
- The status point is a 78 px circle centred at (714, 256) — it sits *above* the
right arm, like a light on a rail.
- Screen: (176, 256), 672 × 512, corner radius 80, stroke 64.
- Side notch: x=144–336, y=392–632; flush left edge and 56 px inner corners.
Its left edge aligns with the outside of the screen stroke.
- Tile variant (`logo/icon.svg`): charcoal rounded square (radius 205 on 928),
paper mark, mint point. This is the macOS app icon and the social avatar.
- Mono variants (`logo/mark-mono-black.svg`, `logo/mark-mono-white.svg`) drop the
colour and render the point in the same ink. Use them for print, favicons in
monochrome contexts, and the menu bar.
- **Menu bar**: the mark is always a macOS *template* image (system tints it).
Never draw the mint point in colour in the menu bar.

Clear space: keep at least one stroke width (84/1024 of the mark height) free
around the mark. Minimum size: 16 px (the 16 px mono render is the survival floor —
if the point disappears at 16 px, the mark has been drawn wrong).
paper screen, mint notch. This is the macOS app icon and the social avatar.
- Favicon: the same mark on a full-bleed charcoal tile (radius 224 on 1024).
- Mono variants (`logo/mark-mono-black.svg`, `logo/mark-mono-white.svg`) render
the screen and notch in the same ink.
- **Menu bar**: always a macOS *template* image (system tints it). Never draw
the mint notch in colour in the menu bar.

Clear space: keep at least one stroke width (64/1024 of the mark height) free
around the mark. Minimum size: 16 px. The screen and attached side notch must
remain distinct at that size; never omit the notch.

## Social preview

The share card is a 1200 × 630 PNG, composed in `social/card.html` and rendered
with `scripts/generate-social.sh`. It uses the backdrop ground, charcoal type,
the existing tagline, the new icon, and a screen showing the actual site HUD
with illustrative usage values. Provider names, free/open-source status, and
macOS requirements come from the site. Keep primary content 64 px from the
card edges. Use a new versioned image filename whenever its pixels change,
since `/assets/` is cached immutably. Open Graph and Twitter metadata must
reference the same absolute PNG URL, with dimensions and descriptive alt text.

## Colour

Expand All @@ -49,10 +62,10 @@ if the point disappears at 16 px, the mark has been drawn wrong).
| `ash` | `#2A3135` | Hairlines and dividers on dark. |
| `smoke` | `#8B9498` | Secondary text on dark (5.7:1 on charcoal). |
| `paper` | `#F4F7F6` | Primary text on dark; the mark itself. |
| `mint` | `#3BE29B` | **The status point.** The only accent. |
| `mint` | `#3BE29B` | **The live signal.** Side notch, status point, and remaining usage. |

**The Status Point Rule.** Mint is a signal, not a theme. It appears where there is
live state or the primary action: the point on the mark, the "remaining" fill of the
live state or the primary action: the side notch on the mark, the "remaining" fill of the
selected provider, the one primary action on a page (it may repeat — a download
button at the top and bottom — but it is always the *same* action). Never on
headings, borders, bullets, backgrounds, or decoration. If a screen has mint on two
Expand Down
Loading