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: