diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab393cf..f6ef6b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
## April 2026
+- Team logo download pipeline now produces 128×128 transparent squares directly (via a new `Scripts/square_logo.swift` helper called from `download_team_logos.sh`), so notification attachments can ship the bundled PNG as-is and the banner thumbnail renders crisply without runtime compositing
+- Game state tag (PRE / LIVE / CRIT / OVER / FINAL / OFF) now surfaced on each daily game row and on live series rows in the ROUND block
+- Playoff series rows in the ROUND block are always clickable (open the NHL series page) and mark completed series as "Final"
- Goal notifications now include the scorer's sweater number, name, and strength tag (PPG / SHG / EN) when available (derived from `/gamecenter/{id}/play-by-play`)
- Regular-season game rows now show the league-wide game number (derived from the NHL gameId)
- New PLAYOFFS ROUND section lists every active series with series score, next game number, and upcoming tip-off time
diff --git a/IceGlass/Assets.xcassets/TeamLogo_ANA.imageset/ANA_light.svg b/IceGlass/Assets.xcassets/TeamLogo_ANA.imageset/ANA_light.svg
deleted file mode 100644
index d1bde81..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_ANA.imageset/ANA_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_ANA.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_ANA.imageset/Contents.json
deleted file mode 100644
index 5d509a5..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_ANA.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "ANA_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_BOS.imageset/BOS_light.svg b/IceGlass/Assets.xcassets/TeamLogo_BOS.imageset/BOS_light.svg
deleted file mode 100644
index 2349ee4..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_BOS.imageset/BOS_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_BOS.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_BOS.imageset/Contents.json
deleted file mode 100644
index ccc6609..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_BOS.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "BOS_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_BUF.imageset/BUF_light.svg b/IceGlass/Assets.xcassets/TeamLogo_BUF.imageset/BUF_light.svg
deleted file mode 100644
index f9988e8..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_BUF.imageset/BUF_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_BUF.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_BUF.imageset/Contents.json
deleted file mode 100644
index 75dc599..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_BUF.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "BUF_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_CAR.imageset/CAR_light.svg b/IceGlass/Assets.xcassets/TeamLogo_CAR.imageset/CAR_light.svg
deleted file mode 100644
index 6eff6de..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_CAR.imageset/CAR_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_CAR.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_CAR.imageset/Contents.json
deleted file mode 100644
index ff42297..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_CAR.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "CAR_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_CBJ.imageset/CBJ_light.svg b/IceGlass/Assets.xcassets/TeamLogo_CBJ.imageset/CBJ_light.svg
deleted file mode 100644
index 1cc24ec..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_CBJ.imageset/CBJ_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_CBJ.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_CBJ.imageset/Contents.json
deleted file mode 100644
index 93c5ba5..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_CBJ.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "CBJ_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_CGY.imageset/CGY_light.svg b/IceGlass/Assets.xcassets/TeamLogo_CGY.imageset/CGY_light.svg
deleted file mode 100644
index f497b98..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_CGY.imageset/CGY_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_CGY.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_CGY.imageset/Contents.json
deleted file mode 100644
index d3c11a8..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_CGY.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "CGY_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_CHI.imageset/CHI_light.svg b/IceGlass/Assets.xcassets/TeamLogo_CHI.imageset/CHI_light.svg
deleted file mode 100644
index a4013ae..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_CHI.imageset/CHI_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_CHI.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_CHI.imageset/Contents.json
deleted file mode 100644
index 8f77e4a..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_CHI.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "CHI_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_COL.imageset/COL_light.svg b/IceGlass/Assets.xcassets/TeamLogo_COL.imageset/COL_light.svg
deleted file mode 100644
index 601d211..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_COL.imageset/COL_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_COL.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_COL.imageset/Contents.json
deleted file mode 100644
index c3e0e00..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_COL.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "COL_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_DAL.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_DAL.imageset/Contents.json
deleted file mode 100644
index 4a373db..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_DAL.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "DAL_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_DAL.imageset/DAL_light.svg b/IceGlass/Assets.xcassets/TeamLogo_DAL.imageset/DAL_light.svg
deleted file mode 100644
index 5ee18ca..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_DAL.imageset/DAL_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_DET.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_DET.imageset/Contents.json
deleted file mode 100644
index 1f187c9..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_DET.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "DET_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_DET.imageset/DET_light.svg b/IceGlass/Assets.xcassets/TeamLogo_DET.imageset/DET_light.svg
deleted file mode 100644
index 426d963..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_DET.imageset/DET_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_EDM.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_EDM.imageset/Contents.json
deleted file mode 100644
index 576e167..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_EDM.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "EDM_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_EDM.imageset/EDM_light.svg b/IceGlass/Assets.xcassets/TeamLogo_EDM.imageset/EDM_light.svg
deleted file mode 100644
index edf6dc4..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_EDM.imageset/EDM_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_FLA.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_FLA.imageset/Contents.json
deleted file mode 100644
index 88990d9..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_FLA.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "FLA_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_FLA.imageset/FLA_light.svg b/IceGlass/Assets.xcassets/TeamLogo_FLA.imageset/FLA_light.svg
deleted file mode 100644
index bb482d9..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_FLA.imageset/FLA_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_LAK.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_LAK.imageset/Contents.json
deleted file mode 100644
index f22051a..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_LAK.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "LAK_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_LAK.imageset/LAK_light.svg b/IceGlass/Assets.xcassets/TeamLogo_LAK.imageset/LAK_light.svg
deleted file mode 100644
index 8782b42..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_LAK.imageset/LAK_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_MIN.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_MIN.imageset/Contents.json
deleted file mode 100644
index 48fdf26..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_MIN.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "MIN_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_MIN.imageset/MIN_light.svg b/IceGlass/Assets.xcassets/TeamLogo_MIN.imageset/MIN_light.svg
deleted file mode 100644
index fc0e9ce..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_MIN.imageset/MIN_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_MTL.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_MTL.imageset/Contents.json
deleted file mode 100644
index 71c5b06..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_MTL.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "MTL_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_MTL.imageset/MTL_light.svg b/IceGlass/Assets.xcassets/TeamLogo_MTL.imageset/MTL_light.svg
deleted file mode 100644
index cab0075..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_MTL.imageset/MTL_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_NJD.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_NJD.imageset/Contents.json
deleted file mode 100644
index 407b5fa..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_NJD.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "NJD_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_NJD.imageset/NJD_light.svg b/IceGlass/Assets.xcassets/TeamLogo_NJD.imageset/NJD_light.svg
deleted file mode 100644
index ad19cea..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_NJD.imageset/NJD_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_NSH.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_NSH.imageset/Contents.json
deleted file mode 100644
index 5d65a7d..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_NSH.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "NSH_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_NSH.imageset/NSH_light.svg b/IceGlass/Assets.xcassets/TeamLogo_NSH.imageset/NSH_light.svg
deleted file mode 100644
index a813051..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_NSH.imageset/NSH_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_NYI.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_NYI.imageset/Contents.json
deleted file mode 100644
index 93cbdd7..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_NYI.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "NYI_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_NYI.imageset/NYI_light.svg b/IceGlass/Assets.xcassets/TeamLogo_NYI.imageset/NYI_light.svg
deleted file mode 100644
index 7d2caf5..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_NYI.imageset/NYI_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_NYR.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_NYR.imageset/Contents.json
deleted file mode 100644
index b0c0bc6..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_NYR.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "NYR_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_NYR.imageset/NYR_light.svg b/IceGlass/Assets.xcassets/TeamLogo_NYR.imageset/NYR_light.svg
deleted file mode 100644
index 9794b7c..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_NYR.imageset/NYR_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_OTT.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_OTT.imageset/Contents.json
deleted file mode 100644
index fb2e442..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_OTT.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "OTT_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_OTT.imageset/OTT_light.svg b/IceGlass/Assets.xcassets/TeamLogo_OTT.imageset/OTT_light.svg
deleted file mode 100644
index b657670..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_OTT.imageset/OTT_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_PHI.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_PHI.imageset/Contents.json
deleted file mode 100644
index 1a0a8ce..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_PHI.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "PHI_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_PHI.imageset/PHI_light.svg b/IceGlass/Assets.xcassets/TeamLogo_PHI.imageset/PHI_light.svg
deleted file mode 100644
index 5e71709..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_PHI.imageset/PHI_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_PIT.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_PIT.imageset/Contents.json
deleted file mode 100644
index 74ccaa4..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_PIT.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "PIT_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_PIT.imageset/PIT_light.svg b/IceGlass/Assets.xcassets/TeamLogo_PIT.imageset/PIT_light.svg
deleted file mode 100644
index a73a223..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_PIT.imageset/PIT_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_SEA.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_SEA.imageset/Contents.json
deleted file mode 100644
index c390bb5..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_SEA.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "SEA_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_SEA.imageset/SEA_light.svg b/IceGlass/Assets.xcassets/TeamLogo_SEA.imageset/SEA_light.svg
deleted file mode 100644
index 9927ddf..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_SEA.imageset/SEA_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_SJS.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_SJS.imageset/Contents.json
deleted file mode 100644
index 49320b6..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_SJS.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "SJS_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_SJS.imageset/SJS_light.svg b/IceGlass/Assets.xcassets/TeamLogo_SJS.imageset/SJS_light.svg
deleted file mode 100644
index 335ca3d..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_SJS.imageset/SJS_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_STL.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_STL.imageset/Contents.json
deleted file mode 100644
index 4fe3ab3..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_STL.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "STL_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_STL.imageset/STL_light.svg b/IceGlass/Assets.xcassets/TeamLogo_STL.imageset/STL_light.svg
deleted file mode 100644
index 5a05aeb..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_STL.imageset/STL_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_TBL.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_TBL.imageset/Contents.json
deleted file mode 100644
index b0b28a3..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_TBL.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "TBL_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_TBL.imageset/TBL_light.svg b/IceGlass/Assets.xcassets/TeamLogo_TBL.imageset/TBL_light.svg
deleted file mode 100644
index 09ab6bb..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_TBL.imageset/TBL_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_TOR.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_TOR.imageset/Contents.json
deleted file mode 100644
index 1bb7d64..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_TOR.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "TOR_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_TOR.imageset/TOR_light.svg b/IceGlass/Assets.xcassets/TeamLogo_TOR.imageset/TOR_light.svg
deleted file mode 100644
index 8e16ff5..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_TOR.imageset/TOR_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_UTA.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_UTA.imageset/Contents.json
deleted file mode 100644
index c8c64cf..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_UTA.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "UTA_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_UTA.imageset/UTA_light.svg b/IceGlass/Assets.xcassets/TeamLogo_UTA.imageset/UTA_light.svg
deleted file mode 100644
index 6292849..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_UTA.imageset/UTA_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_VAN.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_VAN.imageset/Contents.json
deleted file mode 100644
index ae40818..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_VAN.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "VAN_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_VAN.imageset/VAN_light.svg b/IceGlass/Assets.xcassets/TeamLogo_VAN.imageset/VAN_light.svg
deleted file mode 100644
index 17d3d82..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_VAN.imageset/VAN_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_VGK.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_VGK.imageset/Contents.json
deleted file mode 100644
index 3414e40..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_VGK.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "VGK_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_VGK.imageset/VGK_light.svg b/IceGlass/Assets.xcassets/TeamLogo_VGK.imageset/VGK_light.svg
deleted file mode 100644
index c857e40..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_VGK.imageset/VGK_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_WPG.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_WPG.imageset/Contents.json
deleted file mode 100644
index cf1bfa9..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_WPG.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "WPG_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_WPG.imageset/WPG_light.svg b/IceGlass/Assets.xcassets/TeamLogo_WPG.imageset/WPG_light.svg
deleted file mode 100644
index da0f542..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_WPG.imageset/WPG_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Assets.xcassets/TeamLogo_WSH.imageset/Contents.json b/IceGlass/Assets.xcassets/TeamLogo_WSH.imageset/Contents.json
deleted file mode 100644
index 126fc7d..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_WSH.imageset/Contents.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "images" : [
- {
- "filename" : "WSH_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
diff --git a/IceGlass/Assets.xcassets/TeamLogo_WSH.imageset/WSH_light.svg b/IceGlass/Assets.xcassets/TeamLogo_WSH.imageset/WSH_light.svg
deleted file mode 100644
index 275bbd4..0000000
--- a/IceGlass/Assets.xcassets/TeamLogo_WSH.imageset/WSH_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/IceGlass/Managers/MenuManager.swift b/IceGlass/Managers/MenuManager.swift
index c379555..50c905b 100644
--- a/IceGlass/Managers/MenuManager.swift
+++ b/IceGlass/Managers/MenuManager.swift
@@ -220,6 +220,9 @@ class MenuManager: @unchecked Sendable {
devSubmenu.addItem(
NSMenuItem(title: "Test Goal Scored Notification", action: #selector(triggerTestGoalScored), keyEquivalent: "").withTarget(self)
)
+ devSubmenu.addItem(
+ NSMenuItem(title: "Thumbnail Size Test", action: #selector(triggerThumbnailSizeTest), keyEquivalent: "").withTarget(self)
+ )
devMenuItem.submenu = devSubmenu
menu.addItem(devMenuItem)
#endif
@@ -304,6 +307,10 @@ class MenuManager: @unchecked Sendable {
)
}
}
+
+ @objc private func triggerThumbnailSizeTest() {
+ NotificationManager.shared.sendThumbnailSizeTest()
+ }
#endif
@objc private func showAbout() {
@@ -365,6 +372,10 @@ class MenuManager: @unchecked Sendable {
}
private static func nextGameLabel(for game: Scoreboard.Game) -> String {
+ let state = game.parsedGameState
+ if state.isLive {
+ return state.shortTag
+ }
let dayLabel: String
switch game.gameDate {
case Date.todayET: dayLabel = "Today"
@@ -381,6 +392,7 @@ class MenuManager: @unchecked Sendable {
}
}
let time = game.startTimeET.trimmingCharacters(in: .whitespaces)
- return "\(dayLabel) \(time)"
+ let base = "\(dayLabel) \(time)"
+ return state == .pre ? "\(base) (PRE)" : base
}
}
diff --git a/IceGlass/Managers/NotificationManager.swift b/IceGlass/Managers/NotificationManager.swift
index 722c4b1..b515a28 100644
--- a/IceGlass/Managers/NotificationManager.swift
+++ b/IceGlass/Managers/NotificationManager.swift
@@ -5,7 +5,9 @@
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
//
+import CoreGraphics
import Foundation
+import ImageIO
import UniformTypeIdentifiers
import UserNotifications
@@ -143,24 +145,29 @@ class NotificationManager: @unchecked Sendable {
// MARK: - Team Logo Attachment
- /// Creates a UNNotificationAttachment from the bundled team logo PNG
+ /// Creates a UNNotificationAttachment from the bundled team logo PNG.
+ /// PNGs live on the filesystem (TeamLogos/ folder) rather than the asset
+ /// catalog because UNNotificationAttachment takes a file URL — asset
+ /// catalog images aren't accessible as standalone files.
+ ///
+ /// PNGs are pre-rendered at 48×48 (the measured native thumbnail size)
+ /// so macOS doesn't downsample them during display.
private func teamLogoAttachment(for teamAbbrev: String) -> UNNotificationAttachment? {
- // Look for PNG in the TeamLogos bundle directory
- guard let logoURL = Bundle.main.url(forResource: teamAbbrev, withExtension: "png", subdirectory: "TeamLogos") else {
+ guard let logoURL = Bundle.main.url(
+ forResource: teamAbbrev, withExtension: "png", subdirectory: "TeamLogos"
+ ) else {
logger.debug("No logo found for \(teamAbbrev)")
return nil
}
- // UNNotificationAttachment needs its own copy in a temp directory
let tempDir = FileManager.default.temporaryDirectory
.appendingPathComponent("team-logos", isDirectory: true)
try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
-
let tempFile = tempDir.appendingPathComponent("\(teamAbbrev)-\(UUID().uuidString).png")
do {
try FileManager.default.copyItem(at: logoURL, to: tempFile)
- let attachment = try UNNotificationAttachment(
+ return try UNNotificationAttachment(
identifier: "team-logo-\(teamAbbrev)",
url: tempFile,
options: [
@@ -168,7 +175,6 @@ class NotificationManager: @unchecked Sendable {
UNNotificationAttachmentOptionsThumbnailHiddenKey: false
]
)
- return attachment
} catch {
logger.error("Failed to create logo attachment for \(teamAbbrev): \(error.localizedDescription)")
return nil
@@ -180,4 +186,103 @@ class NotificationManager: @unchecked Sendable {
gameStartsSent.removeAll()
scoreChangesSent.removeAll()
}
+
+#if DEBUG
+ // MARK: - Thumbnail Size Test
+
+ /// Fires one notification per size in the ladder with a distinctive test
+ /// image generated at that exact pixel size. The crispest thumbnail
+ /// reveals the notification system's native display size.
+ func sendThumbnailSizeTest() {
+ let sizes = [16, 24, 32, 40, 48, 56, 64, 80, 96, 128]
+ for size in sizes {
+ guard let attachment = testAttachment(size: size) else {
+ logger.warning("Failed to create test attachment for size \(size)")
+ continue
+ }
+
+ let content = UNMutableNotificationContent()
+ content.title = "Thumb Test"
+ content.body = "\(size)px"
+ content.sound = nil
+ content.interruptionLevel = .active
+ content.attachments = [attachment]
+
+ let request = UNNotificationRequest(
+ identifier: "thumb-test-\(size)-\(UUID().uuidString)",
+ content: content,
+ trigger: UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
+ )
+ UNUserNotificationCenter.current().add(request) { [weak self] error in
+ if let error = error {
+ self?.logger.error("Thumb test \(size)px failed: \(error.localizedDescription)")
+ }
+ }
+ }
+ }
+
+ /// Builds a UNNotificationAttachment with a generated NxN PNG.
+ /// Pattern: solid white background, 1-source-pixel black border, a
+ /// small centered 4x4 black pixel grid (so we can see pixel-level
+ /// behaviour). Unique filename + attachment identifier so macOS
+ /// treats each size as a distinct attachment (no cache reuse).
+ private func testAttachment(size: Int) -> UNNotificationAttachment? {
+ let tempDir = FileManager.default.temporaryDirectory
+ .appendingPathComponent("thumb-test", isDirectory: true)
+ try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
+ let tempFile = tempDir.appendingPathComponent("thumb-\(size)-\(UUID().uuidString).png")
+
+ guard generateTestPNG(size: size, at: tempFile) else { return nil }
+
+ do {
+ return try UNNotificationAttachment(
+ identifier: "thumb-test-attachment-\(size)-\(UUID().uuidString)",
+ url: tempFile,
+ options: [
+ UNNotificationAttachmentOptionsTypeHintKey: UTType.png.identifier,
+ UNNotificationAttachmentOptionsThumbnailHiddenKey: false
+ ]
+ )
+ } catch {
+ logger.error("Failed to create thumb test attachment (\(size)): \(error.localizedDescription)")
+ return nil
+ }
+ }
+
+ private func generateTestPNG(size: Int, at url: URL) -> Bool {
+ let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB()
+ guard let ctx = CGContext(
+ data: nil, width: size, height: size,
+ bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace,
+ bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
+ ) else { return false }
+
+ // White fill
+ ctx.setFillColor(red: 1, green: 1, blue: 1, alpha: 1)
+ ctx.fill(CGRect(x: 0, y: 0, width: size, height: size))
+
+ // 1-pixel black border (no antialiasing for pixel accuracy)
+ ctx.setShouldAntialias(false)
+ ctx.setFillColor(red: 0, green: 0, blue: 0, alpha: 1)
+ // Top row
+ ctx.fill(CGRect(x: 0, y: size - 1, width: size, height: 1))
+ // Bottom row
+ ctx.fill(CGRect(x: 0, y: 0, width: size, height: 1))
+ // Left column
+ ctx.fill(CGRect(x: 0, y: 0, width: 1, height: size))
+ // Right column
+ ctx.fill(CGRect(x: size - 1, y: 0, width: 1, height: size))
+
+ // Centered 4x4 black square at exact source pixel positions
+ let cx = size / 2
+ let cy = size / 2
+ ctx.fill(CGRect(x: cx - 2, y: cy - 2, width: 4, height: 4))
+
+ guard let image = ctx.makeImage(),
+ let dest = CGImageDestinationCreateWithURL(url as CFURL, UTType.png.identifier as CFString, 1, nil)
+ else { return false }
+ CGImageDestinationAddImage(dest, image, nil)
+ return CGImageDestinationFinalize(dest)
+ }
+#endif
}
diff --git a/IceGlass/Models/GameState.swift b/IceGlass/Models/GameState.swift
index 665b88b..1669e19 100644
--- a/IceGlass/Models/GameState.swift
+++ b/IceGlass/Models/GameState.swift
@@ -28,6 +28,20 @@ enum GameState: String, Codable {
self == .future || self == .pre
}
+ /// Short tag for display in menu rows. Empty for future games — the start
+ /// time already implies that state.
+ var shortTag: String {
+ switch self {
+ case .future: return ""
+ case .pre: return "PRE"
+ case .live: return "LIVE"
+ case .crit: return "CRIT"
+ case .over: return "OVER"
+ case .final_: return "FINAL"
+ case .official: return "OFF"
+ }
+ }
+
var pollingInterval: PollingInterval {
switch self {
case .future:
diff --git a/IceGlass/Models/ScoreboardModel.swift b/IceGlass/Models/ScoreboardModel.swift
index 16fd62f..f290599 100644
--- a/IceGlass/Models/ScoreboardModel.swift
+++ b/IceGlass/Models/ScoreboardModel.swift
@@ -82,19 +82,21 @@ struct Scoreboard: Codable {
}
/// Formatted menu title:
- /// "NYR @ WAS 0: 2 9:30 PM" (finished/live — padded score + time)
- /// "DAL @ TOR 7:30 PM" (future — no score gap)
+ /// "NYR @ WAS 0: 2 9:30 PM FINAL" (finished/live — padded score + time + state tag)
+ /// "DAL @ TOR 7:30 PM" (future — no score gap, no tag)
var menuTitle: String {
let state = parsedGameState
let matchup = "\(awayTeam.abbrev) @ \(homeTeam.abbrev)"
+ let tag = state.shortTag
+ let tagSuffix = tag.isEmpty ? "" : " \(tag)"
if state.isFuture {
- return "\(matchup) \(startTimeET)"
+ return "\(matchup) \(startTimeET)\(tagSuffix)"
}
let aScore = String(format: "%2d", awayTeam.score ?? 0)
let hScore = String(format: "%-2d", homeTeam.score ?? 0)
- return "\(matchup) \(aScore):\(hScore) \(startTimeET)"
+ return "\(matchup) \(aScore):\(hScore) \(startTimeET)\(tagSuffix)"
}
/// Sequential game number encoded in the last 4 digits of `id`.
diff --git a/IceGlass/Resources/TeamLogos/ANA.png b/IceGlass/Resources/TeamLogos/ANA.png
new file mode 100644
index 0000000..baf343b
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/ANA.png differ
diff --git a/IceGlass/Resources/TeamLogos/BOS.png b/IceGlass/Resources/TeamLogos/BOS.png
new file mode 100644
index 0000000..6cd70bb
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/BOS.png differ
diff --git a/IceGlass/Resources/TeamLogos/BUF.png b/IceGlass/Resources/TeamLogos/BUF.png
new file mode 100644
index 0000000..7ba901c
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/BUF.png differ
diff --git a/IceGlass/Resources/TeamLogos/CAR.png b/IceGlass/Resources/TeamLogos/CAR.png
new file mode 100644
index 0000000..79bb723
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/CAR.png differ
diff --git a/IceGlass/Resources/TeamLogos/CBJ.png b/IceGlass/Resources/TeamLogos/CBJ.png
new file mode 100644
index 0000000..1de9248
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/CBJ.png differ
diff --git a/IceGlass/Resources/TeamLogos/CGY.png b/IceGlass/Resources/TeamLogos/CGY.png
new file mode 100644
index 0000000..e50fdd5
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/CGY.png differ
diff --git a/IceGlass/Resources/TeamLogos/CHI.png b/IceGlass/Resources/TeamLogos/CHI.png
new file mode 100644
index 0000000..93c4b61
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/CHI.png differ
diff --git a/IceGlass/Resources/TeamLogos/COL.png b/IceGlass/Resources/TeamLogos/COL.png
new file mode 100644
index 0000000..5ea8d4b
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/COL.png differ
diff --git a/IceGlass/Resources/TeamLogos/DAL.png b/IceGlass/Resources/TeamLogos/DAL.png
new file mode 100644
index 0000000..16d03f4
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/DAL.png differ
diff --git a/IceGlass/Resources/TeamLogos/DET.png b/IceGlass/Resources/TeamLogos/DET.png
new file mode 100644
index 0000000..59c2c95
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/DET.png differ
diff --git a/IceGlass/Resources/TeamLogos/EDM.png b/IceGlass/Resources/TeamLogos/EDM.png
new file mode 100644
index 0000000..42a114d
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/EDM.png differ
diff --git a/IceGlass/Resources/TeamLogos/FLA.png b/IceGlass/Resources/TeamLogos/FLA.png
new file mode 100644
index 0000000..32d8f5c
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/FLA.png differ
diff --git a/IceGlass/Resources/TeamLogos/LAK.png b/IceGlass/Resources/TeamLogos/LAK.png
new file mode 100644
index 0000000..7975064
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/LAK.png differ
diff --git a/IceGlass/Resources/TeamLogos/MIN.png b/IceGlass/Resources/TeamLogos/MIN.png
new file mode 100644
index 0000000..90dcf2e
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/MIN.png differ
diff --git a/IceGlass/Resources/TeamLogos/MTL.png b/IceGlass/Resources/TeamLogos/MTL.png
new file mode 100644
index 0000000..dfb5008
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/MTL.png differ
diff --git a/IceGlass/Resources/TeamLogos/NJD.png b/IceGlass/Resources/TeamLogos/NJD.png
new file mode 100644
index 0000000..0ba1f81
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/NJD.png differ
diff --git a/IceGlass/Resources/TeamLogos/NSH.png b/IceGlass/Resources/TeamLogos/NSH.png
new file mode 100644
index 0000000..f5dc7e0
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/NSH.png differ
diff --git a/IceGlass/Resources/TeamLogos/NYI.png b/IceGlass/Resources/TeamLogos/NYI.png
new file mode 100644
index 0000000..639eed4
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/NYI.png differ
diff --git a/IceGlass/Resources/TeamLogos/NYR.png b/IceGlass/Resources/TeamLogos/NYR.png
new file mode 100644
index 0000000..48ac48c
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/NYR.png differ
diff --git a/IceGlass/Resources/TeamLogos/OTT.png b/IceGlass/Resources/TeamLogos/OTT.png
new file mode 100644
index 0000000..045f6e7
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/OTT.png differ
diff --git a/IceGlass/Resources/TeamLogos/PHI.png b/IceGlass/Resources/TeamLogos/PHI.png
new file mode 100644
index 0000000..e2353d1
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/PHI.png differ
diff --git a/IceGlass/Resources/TeamLogos/PIT.png b/IceGlass/Resources/TeamLogos/PIT.png
new file mode 100644
index 0000000..177cd10
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/PIT.png differ
diff --git a/IceGlass/Resources/TeamLogos/SEA.png b/IceGlass/Resources/TeamLogos/SEA.png
new file mode 100644
index 0000000..12272ec
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/SEA.png differ
diff --git a/IceGlass/Resources/TeamLogos/SJS.png b/IceGlass/Resources/TeamLogos/SJS.png
new file mode 100644
index 0000000..f7619de
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/SJS.png differ
diff --git a/IceGlass/Resources/TeamLogos/STL.png b/IceGlass/Resources/TeamLogos/STL.png
new file mode 100644
index 0000000..ceab5b0
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/STL.png differ
diff --git a/IceGlass/Resources/TeamLogos/TBL.png b/IceGlass/Resources/TeamLogos/TBL.png
new file mode 100644
index 0000000..257f431
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/TBL.png differ
diff --git a/IceGlass/Resources/TeamLogos/TOR.png b/IceGlass/Resources/TeamLogos/TOR.png
new file mode 100644
index 0000000..fdcac4b
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/TOR.png differ
diff --git a/IceGlass/Resources/TeamLogos/UTA.png b/IceGlass/Resources/TeamLogos/UTA.png
new file mode 100644
index 0000000..8c88a85
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/UTA.png differ
diff --git a/IceGlass/Resources/TeamLogos/VAN.png b/IceGlass/Resources/TeamLogos/VAN.png
new file mode 100644
index 0000000..25652a5
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/VAN.png differ
diff --git a/IceGlass/Resources/TeamLogos/VGK.png b/IceGlass/Resources/TeamLogos/VGK.png
new file mode 100644
index 0000000..8b8fa37
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/VGK.png differ
diff --git a/IceGlass/Resources/TeamLogos/WPG.png b/IceGlass/Resources/TeamLogos/WPG.png
new file mode 100644
index 0000000..3671991
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/WPG.png differ
diff --git a/IceGlass/Resources/TeamLogos/WSH.png b/IceGlass/Resources/TeamLogos/WSH.png
new file mode 100644
index 0000000..e996994
Binary files /dev/null and b/IceGlass/Resources/TeamLogos/WSH.png differ
diff --git a/IceGlass/Services/MainService.swift b/IceGlass/Services/MainService.swift
index 56857bc..1e073a6 100644
--- a/IceGlass/Services/MainService.swift
+++ b/IceGlass/Services/MainService.swift
@@ -155,10 +155,19 @@ class MainService: @unchecked Sendable {
self.allGamesByDate = filtered
self.updateSnapshots(from: filtered)
+ let wasFirstFetch = self.isFirstFetch
if self.isFirstFetch {
self.isFirstFetch = false
}
+#if DEBUG
+ // Fire a test game-start notification on startup so the dev
+ // loop doesn't require clicking through the menu each time.
+ if wasFirstFetch, let game = filtered.flatMap(\.games).first {
+ self.notificationManager.notifyGameStarted(game, bypassDedup: true)
+ }
+#endif
+
self.logger.info("Scoreboard updated: \(filtered.map { "\($0.date): \($0.games.count) games" }.joined(separator: ", "))")
let interval = self.bestPollingInterval
diff --git a/IceGlass/TeamLogos/ANA.png b/IceGlass/TeamLogos/ANA.png
deleted file mode 100644
index ab955db..0000000
Binary files a/IceGlass/TeamLogos/ANA.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/BOS.png b/IceGlass/TeamLogos/BOS.png
deleted file mode 100644
index 9715e22..0000000
Binary files a/IceGlass/TeamLogos/BOS.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/BUF.png b/IceGlass/TeamLogos/BUF.png
deleted file mode 100644
index 5556b3d..0000000
Binary files a/IceGlass/TeamLogos/BUF.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/CAR.png b/IceGlass/TeamLogos/CAR.png
deleted file mode 100644
index 138458a..0000000
Binary files a/IceGlass/TeamLogos/CAR.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/CBJ.png b/IceGlass/TeamLogos/CBJ.png
deleted file mode 100644
index 8fb2c21..0000000
Binary files a/IceGlass/TeamLogos/CBJ.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/CGY.png b/IceGlass/TeamLogos/CGY.png
deleted file mode 100644
index 0dd211a..0000000
Binary files a/IceGlass/TeamLogos/CGY.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/CHI.png b/IceGlass/TeamLogos/CHI.png
deleted file mode 100644
index 6725fa4..0000000
Binary files a/IceGlass/TeamLogos/CHI.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/COL.png b/IceGlass/TeamLogos/COL.png
deleted file mode 100644
index 533f251..0000000
Binary files a/IceGlass/TeamLogos/COL.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/DAL.png b/IceGlass/TeamLogos/DAL.png
deleted file mode 100644
index 89e46b8..0000000
Binary files a/IceGlass/TeamLogos/DAL.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/DET.png b/IceGlass/TeamLogos/DET.png
deleted file mode 100644
index c1a0a55..0000000
Binary files a/IceGlass/TeamLogos/DET.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/EDM.png b/IceGlass/TeamLogos/EDM.png
deleted file mode 100644
index 39585ff..0000000
Binary files a/IceGlass/TeamLogos/EDM.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/FLA.png b/IceGlass/TeamLogos/FLA.png
deleted file mode 100644
index 4ed6408..0000000
Binary files a/IceGlass/TeamLogos/FLA.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/LAK.png b/IceGlass/TeamLogos/LAK.png
deleted file mode 100644
index 335aaa6..0000000
Binary files a/IceGlass/TeamLogos/LAK.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/MIN.png b/IceGlass/TeamLogos/MIN.png
deleted file mode 100644
index 717839d..0000000
Binary files a/IceGlass/TeamLogos/MIN.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/MTL.png b/IceGlass/TeamLogos/MTL.png
deleted file mode 100644
index c732ad0..0000000
Binary files a/IceGlass/TeamLogos/MTL.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/NJD.png b/IceGlass/TeamLogos/NJD.png
deleted file mode 100644
index f2564dc..0000000
Binary files a/IceGlass/TeamLogos/NJD.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/NSH.png b/IceGlass/TeamLogos/NSH.png
deleted file mode 100644
index 07ce369..0000000
Binary files a/IceGlass/TeamLogos/NSH.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/NYI.png b/IceGlass/TeamLogos/NYI.png
deleted file mode 100644
index 27ad502..0000000
Binary files a/IceGlass/TeamLogos/NYI.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/NYR.png b/IceGlass/TeamLogos/NYR.png
deleted file mode 100644
index 94f06af..0000000
Binary files a/IceGlass/TeamLogos/NYR.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/OTT.png b/IceGlass/TeamLogos/OTT.png
deleted file mode 100644
index d4b02c8..0000000
Binary files a/IceGlass/TeamLogos/OTT.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/PHI.png b/IceGlass/TeamLogos/PHI.png
deleted file mode 100644
index a7fd3c7..0000000
Binary files a/IceGlass/TeamLogos/PHI.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/PIT.png b/IceGlass/TeamLogos/PIT.png
deleted file mode 100644
index 1681dd2..0000000
Binary files a/IceGlass/TeamLogos/PIT.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/SEA.png b/IceGlass/TeamLogos/SEA.png
deleted file mode 100644
index 4aa278b..0000000
Binary files a/IceGlass/TeamLogos/SEA.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/SJS.png b/IceGlass/TeamLogos/SJS.png
deleted file mode 100644
index 7de6420..0000000
Binary files a/IceGlass/TeamLogos/SJS.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/STL.png b/IceGlass/TeamLogos/STL.png
deleted file mode 100644
index f1b6724..0000000
Binary files a/IceGlass/TeamLogos/STL.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/TBL.png b/IceGlass/TeamLogos/TBL.png
deleted file mode 100644
index caef44c..0000000
Binary files a/IceGlass/TeamLogos/TBL.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/TOR.png b/IceGlass/TeamLogos/TOR.png
deleted file mode 100644
index f4fd73f..0000000
Binary files a/IceGlass/TeamLogos/TOR.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/UTA.png b/IceGlass/TeamLogos/UTA.png
deleted file mode 100644
index 79eccb0..0000000
Binary files a/IceGlass/TeamLogos/UTA.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/VAN.png b/IceGlass/TeamLogos/VAN.png
deleted file mode 100644
index 952a6f6..0000000
Binary files a/IceGlass/TeamLogos/VAN.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/VGK.png b/IceGlass/TeamLogos/VGK.png
deleted file mode 100644
index 678edcc..0000000
Binary files a/IceGlass/TeamLogos/VGK.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/WPG.png b/IceGlass/TeamLogos/WPG.png
deleted file mode 100644
index 137e92d..0000000
Binary files a/IceGlass/TeamLogos/WPG.png and /dev/null differ
diff --git a/IceGlass/TeamLogos/WSH.png b/IceGlass/TeamLogos/WSH.png
deleted file mode 100644
index 51a4cba..0000000
Binary files a/IceGlass/TeamLogos/WSH.png and /dev/null differ
diff --git a/Scripts/download_team_logos.sh b/Scripts/download_team_logos.sh
index dfa56a3..d7fa157 100755
--- a/Scripts/download_team_logos.sh
+++ b/Scripts/download_team_logos.sh
@@ -1,21 +1,32 @@
#!/bin/bash
#
# download_team_logos.sh
-# Downloads NHL team logos (SVG) from the NHL assets CDN.
+# Downloads NHL team logos from the NHL assets CDN.
#
# Usage: ./Scripts/download_team_logos.sh [season]
# season: e.g. 20252026 (defaults to current season)
#
-# SVGs are saved to Assets.xcassets imagesets for UI use.
-# PNGs (80x80) are saved to TeamLogos/ for notification attachments.
+# Downloads SVGs to /tmp, rasterizes to PNGs sized for the macOS notification
+# thumbnail (72×72 square, aspect-preserved with transparent padding). Output
+# goes to IceGlass/Resources/TeamLogos/ — used as notification attachments via
+# Bundle.main.url(forResource:withExtension:subdirectory:"TeamLogos").
+#
+# Native notification thumbnail size was measured empirically at ~48 physical
+# pixels. 72px is a small 1.5x over-sample that stays crisp without any
+# visible downsample aliasing (tested at 48/72/96/128 — 48–72 are sharp).
+#
+# Pipeline: rsvg renders the SVG at a generous intermediate resolution;
+# square_logo.swift then trims the transparent margins (which NHL brand SVGs
+# pad the viewBox with) so the logo fills the final square, then scales
+# aspect-preserved into the 72×72 target.
#
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
-ASSETS_DIR="$PROJECT_DIR/IceGlass/Assets.xcassets"
-LOGOS_DIR="$PROJECT_DIR/IceGlass/TeamLogos"
+LOGOS_DIR="$PROJECT_DIR/IceGlass/Resources/TeamLogos"
+TMP_DIR="$(mktemp -d)"
# Determine season
if [ -n "$1" ]; then
@@ -30,7 +41,10 @@ else
fi
fi
-echo "Downloading NHL team logos for season $SEASON"
+TARGET_SIZE=72
+INTERMEDIATE_WIDTH=1024 # high-res intermediate so the trim+downscale has detail
+
+echo "Downloading NHL team logos for season $SEASON (target size ${TARGET_SIZE}×${TARGET_SIZE})"
TEAMS=(
ANA BOS BUF CAR CBJ CGY CHI COL DAL DET
@@ -39,57 +53,41 @@ TEAMS=(
WPG WSH
)
-# Create logos directory for notification attachments
mkdir -p "$LOGOS_DIR"
for TEAM in "${TEAMS[@]}"; do
- IMAGESET_DIR="$ASSETS_DIR/TeamLogo_${TEAM}.imageset"
- mkdir -p "$IMAGESET_DIR"
-
SVG_URL="https://assets.nhle.com/logos/nhl/svg/${TEAM}_light.svg"
- SVG_FILE="$IMAGESET_DIR/${TEAM}_light.svg"
+ SVG_FILE="$TMP_DIR/${TEAM}_light.svg"
+ INTERMEDIATE="$TMP_DIR/${TEAM}_native.png"
LOGO_PNG="$LOGOS_DIR/${TEAM}.png"
echo " Downloading $TEAM..."
curl -s -o "$SVG_FILE" "${SVG_URL}?season=${SEASON}"
- # Convert SVG to PNG (200px wide, aspect-ratio preserved) for notification attachments
+ # Rasterize SVG at high-res intermediate width (aspect-preserved, transparent)
if command -v rsvg-convert &>/dev/null; then
- rsvg-convert -w 200 -h 200 --keep-aspect-ratio --background-color=transparent "$SVG_FILE" -o "$LOGO_PNG" 2>/dev/null
+ rsvg-convert -w "$INTERMEDIATE_WIDTH" --keep-aspect-ratio --background-color=transparent "$SVG_FILE" -o "$INTERMEDIATE" 2>/dev/null
else
python3 -c "
try:
import cairosvg
- cairosvg.svg2png(url='$SVG_FILE', write_to='$LOGO_PNG', output_width=200)
+ cairosvg.svg2png(url='$SVG_FILE', write_to='$INTERMEDIATE', output_width=$INTERMEDIATE_WIDTH)
except ImportError:
pass
" 2>/dev/null || true
fi
- # Write Contents.json for the imageset (SVG only, no PNG)
- cat > "$IMAGESET_DIR/Contents.json" << EOJSON
-{
- "images" : [
- {
- "filename" : "${TEAM}_light.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- },
- "properties" : {
- "preserves-vector-representation" : true,
- "template-rendering-intent" : "original"
- }
-}
-EOJSON
+ if [ ! -f "$INTERMEDIATE" ]; then
+ echo " warning: failed to rasterize $TEAM svg"
+ continue
+ fi
+
+ # Composite onto a target-size square transparent canvas (aspect-preserved, centered)
+ swift "$SCRIPT_DIR/square_logo.swift" "$INTERMEDIATE" "$LOGO_PNG" "$TARGET_SIZE"
done
+rm -rf "$TMP_DIR"
+
echo ""
-echo "Done! Downloaded ${#TEAMS[@]} team logos to:"
-echo " Asset catalogs: $ASSETS_DIR/TeamLogo_*.imageset/ (SVG)"
-echo " Notifications: $LOGOS_DIR/ (PNG 80x80)"
-echo ""
+echo "Done! Downloaded ${#TEAMS[@]} team logos to $LOGOS_DIR (${TARGET_SIZE}×${TARGET_SIZE})"
echo "Season: $SEASON"
diff --git a/Scripts/square_logo.swift b/Scripts/square_logo.swift
new file mode 100644
index 0000000..8005cf1
--- /dev/null
+++ b/Scripts/square_logo.swift
@@ -0,0 +1,124 @@
+#!/usr/bin/env swift
+//
+// square_logo.swift
+// IceGlass
+//
+// Usage: square_logo.swift
+//
+// Loads an RGBA PNG, trims transparent margins (alpha bounding box),
+// fits the trimmed content into a target-side × target-side transparent
+// sRGB square canvas (aspect-preserved, centered), and writes PNG.
+//
+// The trim step matters because NHL SVG logos often ship with significant
+// transparent padding inside the viewBox — without trimming, the visible
+// logo only occupies a fraction of the thumbnail.
+//
+
+import CoreGraphics
+import Foundation
+import ImageIO
+import UniformTypeIdentifiers
+
+guard CommandLine.arguments.count == 4,
+ let targetSize = Int(CommandLine.arguments[3]),
+ targetSize > 0
+else {
+ FileHandle.standardError.write(
+ Data("Usage: square_logo.swift \n".utf8)
+ )
+ exit(2)
+}
+
+let inputURL = URL(fileURLWithPath: CommandLine.arguments[1])
+let outputURL = URL(fileURLWithPath: CommandLine.arguments[2])
+
+guard let source = CGImageSourceCreateWithURL(inputURL as CFURL, nil),
+ let srcImage = CGImageSourceCreateImageAtIndex(source, 0, nil) else {
+ FileHandle.standardError.write(Data("failed to load \(inputURL.path)\n".utf8))
+ exit(1)
+}
+
+// MARK: - Find alpha bounding box
+
+/// Read the source image into a premultiplied RGBA8 buffer so we can scan
+/// pixels directly, then find the tightest rectangle enclosing all alpha
+/// values above a small threshold.
+func alphaBoundingBox(_ image: CGImage, alphaThreshold: UInt8 = 8) -> CGRect? {
+ let w = image.width
+ let h = image.height
+ let bytesPerPixel = 4
+ let bytesPerRow = w * bytesPerPixel
+ var buffer = [UInt8](repeating: 0, count: h * bytesPerRow)
+
+ let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB()
+ guard let ctx = CGContext(
+ data: &buffer, width: w, height: h,
+ bitsPerComponent: 8, bytesPerRow: bytesPerRow,
+ space: colorSpace,
+ bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
+ ) else { return nil }
+ ctx.draw(image, in: CGRect(x: 0, y: 0, width: w, height: h))
+
+ var minX = w, minY = h, maxX = -1, maxY = -1
+ for y in 0.. alphaThreshold {
+ if x < minX { minX = x }
+ if x > maxX { maxX = x }
+ if y < minY { minY = y }
+ if y > maxY { maxY = y }
+ }
+ }
+ }
+ guard maxX >= minX, maxY >= minY else { return nil }
+ return CGRect(x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1)
+}
+
+let bbox = alphaBoundingBox(srcImage) ?? CGRect(x: 0, y: 0, width: srcImage.width, height: srcImage.height)
+
+// MARK: - Crop to bbox, then fit into target square
+
+guard let cropped = srcImage.cropping(to: bbox) else {
+ FileHandle.standardError.write(Data("crop failed\n".utf8))
+ exit(1)
+}
+
+let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB()
+guard let ctx = CGContext(
+ data: nil,
+ width: targetSize,
+ height: targetSize,
+ bitsPerComponent: 8,
+ bytesPerRow: 0,
+ space: colorSpace,
+ bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
+) else {
+ FileHandle.standardError.write(Data("CGContext create failed\n".utf8))
+ exit(1)
+}
+ctx.interpolationQuality = .high
+ctx.clear(CGRect(x: 0, y: 0, width: targetSize, height: targetSize))
+
+let cw = CGFloat(cropped.width)
+let ch = CGFloat(cropped.height)
+let S = CGFloat(targetSize)
+let scale = min(S / cw, S / ch)
+let dw = cw * scale
+let dh = ch * scale
+let drawRect = CGRect(x: (S - dw) / 2, y: (S - dh) / 2, width: dw, height: dh)
+ctx.draw(cropped, in: drawRect)
+
+guard let final = ctx.makeImage(),
+ let dest = CGImageDestinationCreateWithURL(
+ outputURL as CFURL, UTType.png.identifier as CFString, 1, nil
+ )
+else {
+ FileHandle.standardError.write(Data("image/dest create failed\n".utf8))
+ exit(1)
+}
+CGImageDestinationAddImage(dest, final, nil)
+guard CGImageDestinationFinalize(dest) else {
+ FileHandle.standardError.write(Data("finalize failed\n".utf8))
+ exit(1)
+}
diff --git a/project.yml b/project.yml
index 7bab8b0..19ee3be 100644
--- a/project.yml
+++ b/project.yml
@@ -16,8 +16,10 @@ targets:
type: application
platform: macOS
sources:
- - IceGlass
- - path: IceGlass/TeamLogos
+ - path: IceGlass
+ excludes:
+ - Resources/TeamLogos
+ - path: IceGlass/Resources/TeamLogos
type: folder
buildPhase: resources
settings: