Skip to content
Open
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,26 @@ swiftlint

The CI pipeline runs `swiftlint` on every PR. Fix all errors before pushing.

**Refreshing the landing page screenshot**

`docs/assets/hero.png` is the shot at the top of the [website](https://periicles.github.io/Notchapp/). Until it exists the page falls back to a CSS mockup, so the site is never broken by a missing file.

Run NotchBar, open a calendar event so the panel shows the **in progress** state — title, times and progress bar, which is the whole point of the shot — then:

```sh
scripts/capture-hero.sh # counts down, then captures; hover the notch and hold
```

The crop region is read from the panel's real window, so it cannot drift from the app's geometry.

Direct capture needs Screen Recording permission for your terminal. Without it `screencapture` fails with *could not create image from rect* — take a full-screen shot instead (**⌘⇧5 → Options → Timer**, so the panel stays open while it fires) and crop that, which needs no permission:

```sh
scripts/capture-hero.sh --from ~/Desktop/Screenshot*.png
```

Check what you are about to publish: the shot includes the menu bar on either side of the notch, and the panel shows real event titles.

## Contributing

**Branch naming**
Expand Down
1 change: 1 addition & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ <h1 data-i18n="hero.title">are we done yet?</h1>
<p class="meta" data-i18n="meta.reqs">macOS 14+ · Apple Silicon · free &amp; open source</p>
</div>
<div class="hero-stage">
<img class="hero-shot" src="assets/hero.png" alt="" data-i18n-attr="alt:hero.shot" hidden>
<div class="desktop">
<div class="menubar"><span class="clock">10:27</span><span class="mi"></span></div>
<div class="notchwrap">
Expand Down
10 changes: 10 additions & 0 deletions docs/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
fr: "Ton notch répond à la question. La progression de ton cours, là où tu regardes déjà." },
"cta.download":{ en: "Download for Mac", fr: "Télécharger pour Mac" },
"meta.reqs": { en: "macOS 14+ · Apple Silicon · free & open source", fr: "macOS 14+ · Apple Silicon · gratuit & open source" },
"hero.shot": { en: "NotchBar expanded in the notch, showing the current event's progress",
fr: "NotchBar déployé dans le notch, montrant la progression de l'événement en cours" },
"mock.title": { en: "Organic Chemistry", fr: "Chimie organique" },
"mock.left": { en: "<b>23 min</b> left", fr: "<b>23 min</b> restantes" },
"feat.heading":{ en: "What it does", fr: "Ce que ça fait" },
Expand Down Expand Up @@ -90,5 +92,13 @@
b.addEventListener("click", function () { apply(b.getAttribute("data-lang")); });
});

// Swap the CSS mock for the real screenshot, but only once it has loaded:
// a missing asset then costs nothing instead of showing a broken image.
var shot = document.querySelector(".hero-shot");
if (shot) {
shot.addEventListener("load", function () { shot.hidden = false; });
if (shot.complete && shot.naturalWidth > 0) shot.hidden = false;
}

apply(resolveLang());
})();
7 changes: 7 additions & 0 deletions docs/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ header.site,footer.site{max-width:var(--max);margin:0 auto;padding:22px 24px;
.hero h1{font-size:clamp(44px,8vw,92px);font-weight:850;letter-spacing:-.045em;line-height:.9;margin:0 0 20px}
.hero .sub{color:var(--muted);font-size:17px;max-width:42ch;margin:0 0 24px}
.hero-stage{display:flex;align-items:center}
/* The real screenshot, revealed by main.js only once it actually loads. Until
docs/assets/hero.png exists — or with JS off — it stays hidden and the CSS
mock below is what the page shows. No broken image, ever. */
.hero-shot{width:100%;height:auto;border-radius:14px;
box-shadow:0 30px 60px -34px rgba(0,0,0,.6)}
.hero-shot:not([hidden]){display:block}
.hero-shot:not([hidden]) + .desktop{display:none}

.desktop{position:relative;width:100%;border-radius:14px 14px 10px 10px;overflow:hidden;
background:radial-gradient(120% 90% at 20% -10%,#1a2340 0,rgba(26,35,64,0) 55%),
Expand Down
107 changes: 107 additions & 0 deletions scripts/capture-hero.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
# Captures the hero screenshot used on the landing page (docs/assets/hero.png).
#
# Usage:
# capture-hero.sh capture directly, after a countdown
# capture-hero.sh --from <shot.png> crop an existing full-screen screenshot
# capture-hero.sh --delay 8 seconds before the direct capture fires
#
# NotchBar must be running: the crop region is read from the panel's real window
# rather than hardcoded, so it cannot drift from the app's geometry.
#
# The direct mode needs Screen Recording permission for your terminal (System
# Settings > Privacy & Security > Screen Recording). Without it, screencapture
# fails with "could not create image from rect" — use --from instead: take a
# full-screen shot with Cmd-Shift-5 (Options > Timer, so the panel stays open),
# then pass the file. That path needs no permission at all.
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT="$ROOT/docs/assets/hero.png"
DELAY=6
FROM=""

while [ $# -gt 0 ]; do
case "$1" in
--from) FROM="${2:-}"; shift 2 ;;
--delay) DELAY="${2:-}"; shift 2 ;;
*) echo "error: unknown argument '$1'" >&2; exit 1 ;;
esac
done

# Margin around the panel, in points: enough menu bar on either side for the
# notch to read as a notch, and a little desktop underneath.
SIDE_MARGIN=150
BOTTOM_MARGIN=40

read -r X Y W H SCREEN_W SCALE <<EOF
$(swift - "$SIDE_MARGIN" "$BOTTOM_MARGIN" <<'SWIFT'
import AppKit
import CoreGraphics

let sideMargin = CGFloat(Double(CommandLine.arguments[1])!)
let bottomMargin = CGFloat(Double(CommandLine.arguments[2])!)

let screen = NSScreen.screens.first(where: {
let key = NSDeviceDescriptionKey("NSScreenNumber")
guard let id = $0.deviceDescription[key] as? CGDirectDisplayID else { return false }
return CGDisplayIsBuiltin(id) != 0
}) ?? NSScreen.main!

// The panel is the widest window NotchBar owns; the other is the hover sensor.
guard let list = CGWindowListCopyWindowInfo([.optionAll], kCGNullWindowID) as? [[String: Any]] else {
FileHandle.standardError.write(Data("error: cannot list windows\n".utf8))
exit(1)
}
let panels = list
.filter { ($0[kCGWindowOwnerName as String] as? String) == "NotchBar" }
.compactMap { $0[kCGWindowBounds as String] as? [String: CGFloat] }
.sorted { ($0["Width"] ?? 0) > ($1["Width"] ?? 0) }

guard let panel = panels.first, let pw = panel["Width"], let ph = panel["Height"] else {
FileHandle.standardError.write(Data("error: NotchBar is not running\n".utf8))
exit(1)
}

// Window bounds are already top-left origin, and the panel bleeds 2pt above the
// screen, so clamp the top edge to 0.
let x = max((panel["X"] ?? 0) - sideMargin, 0)
let y = max(panel["Y"] ?? 0, 0)
let width = min(pw + sideMargin * 2, screen.frame.width - x)
let height = ph + bottomMargin

print("\(Int(x)) \(Int(y)) \(Int(width)) \(Int(height)) \(Int(screen.frame.width)) \(Int(screen.backingScaleFactor))")
SWIFT
)
EOF

echo "==> Region: ${W}x${H} pt at (${X},${Y}) — screen ${SCREEN_W}pt @${SCALE}x"
mkdir -p "$(dirname "$OUT")"

if [ -n "$FROM" ]; then
[ -f "$FROM" ] || { echo "error: no such file: $FROM" >&2; exit 1; }

SHOT_W=$(sips -g pixelWidth "$FROM" | awk '/pixelWidth/{print $2}')
# A full-screen shot spans the screen, so its own width gives the true
# pixel-per-point ratio — more reliable than assuming the backing scale.
RATIO=$(awk -v a="$SHOT_W" -v b="$SCREEN_W" 'BEGIN{printf "%.6f", a/b}')
echo "==> Cropping $FROM (${SHOT_W}px wide, ${RATIO} px/pt)"

px() { awk -v v="$1" -v r="$RATIO" 'BEGIN{printf "%d", v*r+0.5}'; }
sips -c "$(px "$H")" "$(px "$W")" --cropOffset "$(px "$Y")" "$(px "$X")" \
"$FROM" --out "$OUT" >/dev/null
else
echo "==> Capturing in ${DELAY}s — hover the notch now and keep the cursor on it"
screencapture -T "$DELAY" -x -R "$X,$Y,$W,$H" "$OUT" || {
echo "error: screencapture failed. Grant Screen Recording to your terminal," >&2
echo " or take a full-screen shot with Cmd-Shift-5 and pass --from." >&2
exit 1
}
fi

OUT_W=$(sips -g pixelWidth "$OUT" | awk '/pixelWidth/{print $2}')
OUT_H=$(sips -g pixelHeight "$OUT" | awk '/pixelHeight/{print $2}')
echo "==> Done: $OUT (${OUT_W}x${OUT_H}px)"
[ "$OUT_W" -lt 1000 ] && echo "warning: under 1000px wide — it will look soft on a retina display" >&2
echo " Commit it, and the landing page picks it up automatically."
exit 0