feat(settings): add "Square Corners" appearance toggle (#1215)

* feat(settings): add "Square Corners" appearance toggle

A Display toggle in Settings → Appearance → Visual Options that overrides
the active theme to render cards and cover art with square (non-rounded)
corners, for a sharper, boxy look.

Covered surfaces: grid cards (album/playlist/artist/song/because) and
their covers, detail-header heros (album/playlist/tracks), the Now
Playing / Radio view, the fullscreen player, the cover lightbox, the
queue panel cover, and the mini player.

Persisted in themeStore and driven by an `html[data-square-corners]`
attribute set in App.tsx — applied in both the main and mini-player
webviews (the mini player rehydrates the theme store on the shared
`storage` event). Strings added for all 12 locales.

* docs(changelog): add Square Corners to 1.50.0 CHANGELOG and What's New
This commit is contained in:
Psychotoxical
2026-06-29 06:48:26 +02:00
committed by GitHub
parent 4c102d88a5
commit e1ff4385d6
19 changed files with 137 additions and 0 deletions
+11
View File
@@ -9,6 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
>
## [1.50.0]
## Added
### Square corners — sharp-edged cards and covers
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1215](https://github.com/Psychotoxical/psysonic/pull/1215)**
* New **Square Corners** toggle under **Settings → Appearance → Visual Options → Display** overrides the active theme to render cards and cover art with square, non-rounded corners. Covers album, playlist, artist and song cards, detail-page cover art, the Now Playing / Radio and fullscreen views, the cover lightbox, the queue cover, and the mini player. Off by default; buttons, inputs and dialogs keep the theme's corners.
## [1.49.0] - 2026-06-29
## Added
+9
View File
@@ -8,6 +8,15 @@ Within each section, order by **user impact** (most noticeable first) — not PR
`CHANGELOG.md` keeps strict PR order inside Added / Changed / Fixed.
## [1.50.0]
## Highlights
### Square corners — a sharper, boxier look
- New **Square Corners** toggle under **Settings → Appearance → Visual Options** strips the rounded corners off cards and cover art across the app — handy when a theme's rounding doesn't suit your album covers. Off by default; everything else stays the way your theme defines it.
## [1.49.0]
## Highlights
+9
View File
@@ -86,6 +86,15 @@ export default function App() {
document.documentElement.setAttribute('data-button-size', buttonSize);
}, [buttonSize]);
// Strip rounded corners off grid cards when the user opts in — single CSS
// hook (`html[data-square-corners]`) overriding whatever radius the theme set.
const squareCorners = useThemeStore(s => s.squareCorners);
useEffect(() => {
const root = document.documentElement;
if (squareCorners) root.setAttribute('data-square-corners', '');
else root.removeAttribute('data-square-corners');
}, [squareCorners]);
// Hide all inline track-preview buttons when the user opts out — single
// CSS hook (`html[data-track-previews="off"]`) instead of conditional
// rendering in every tracklist. Per-location toggles use additional
@@ -104,6 +104,13 @@ export function AppearanceTab() {
onChange={theme.setFloatingPlayerBar}
/>
<div className="settings-section-divider" />
<SettingsToggle
label={t('settings.squareCorners')}
desc={t('settings.squareCornersSub')}
checked={theme.squareCorners}
onChange={theme.setSquareCorners}
/>
<div className="settings-section-divider" />
<SettingsToggle
label={t('settings.showArtistImages')}
desc={t('settings.showArtistImagesDesc')}
+2
View File
@@ -698,6 +698,8 @@ export const settings = {
showBitrateSub: 'Audio-Bitrate in Track-Listen anzeigen',
floatingPlayerBar: 'Schwebende Player-Leiste',
floatingPlayerBarSub: 'Player-Leiste über dem Inhalt schweben lassen',
squareCorners: 'Eckige Ecken',
squareCornersSub: 'Abgerundete Ecken bei Karten entfernen kantige, eckige Optik',
uiScaleTitle: 'Interface-Skalierung',
uiScaleLabel: 'Zoom',
waveformCacheClearBtn: 'Waveform-Cache leeren',
+2
View File
@@ -765,6 +765,8 @@ export const settings = {
showBitrateSub: 'Display audio bitrate in track listings',
floatingPlayerBar: 'Floating Player Bar',
floatingPlayerBarSub: 'Keep the player bar floating above content',
squareCorners: 'Square Corners',
squareCornersSub: 'Remove rounded corners from cards for a sharp, square look',
uiScaleTitle: 'Interface Scale',
uiScaleLabel: 'Zoom',
};
+2
View File
@@ -697,6 +697,8 @@ export const settings = {
showBitrateSub: 'Mostrar bitrate de audio en las listas de pistas',
floatingPlayerBar: 'Barra del Reproductor Flotante',
floatingPlayerBarSub: 'Mantener la barra del reproductor flotando sobre el contenido',
squareCorners: 'Esquinas rectas',
squareCornersSub: 'Quitar las esquinas redondeadas de las tarjetas para un aspecto nítido y cuadrado',
uiScaleTitle: 'Escala de Interfaz',
uiScaleLabel: 'Zoom',
confirmDeleteServerLibrary: '¿También quieres eliminar el índice local de biblioteca de este servidor? Pulsa Cancelar para conservar la copia en caché para uso sin conexión.',
+2
View File
@@ -685,6 +685,8 @@ export const settings = {
showBitrateSub: 'Afficher le débit audio dans les listes de pistes',
floatingPlayerBar: 'Barre de Lecteur Flottante',
floatingPlayerBarSub: 'Garder la barre du lecteur flottante au-dessus du contenu',
squareCorners: 'Coins carrés',
squareCornersSub: 'Supprimer les coins arrondis des cartes pour un rendu net et anguleux',
uiScaleTitle: "Mise à l'échelle de l'interface",
uiScaleLabel: 'Zoom',
confirmDeleteServerLibrary: 'Supprimer aussi lindex local de bibliothèque de ce serveur ? Cliquez sur Annuler pour conserver la copie en cache pour un usage hors ligne.',
+2
View File
@@ -764,6 +764,8 @@ export const settings = {
showBitrateSub: 'A hang bitrátájának megjelenítése a számlistákban',
floatingPlayerBar: 'Lebegő lejátszósáv',
floatingPlayerBarSub: 'A lejátszósáv lebegve tartása a tartalom felett',
squareCorners: 'Szögletes sarkok',
squareCornersSub: 'A kártyák lekerekített sarkainak eltávolítása éles, szögletes megjelenésért',
uiScaleTitle: 'Felület méretezése',
uiScaleLabel: 'Nagyítás',
};
+2
View File
@@ -758,6 +758,8 @@ export const settings = {
showBitrateSub: 'トラック一覧に音声ビットレートを表示',
floatingPlayerBar: 'フローティングプレイヤーバー',
floatingPlayerBarSub: 'コンテンツの上にプレイヤーバーを浮かせて表示',
squareCorners: '角を四角に',
squareCornersSub: 'カードの角の丸みをなくして、シャープで角ばった見た目にする',
uiScaleTitle: 'インターフェース倍率',
uiScaleLabel: 'ズーム',
};
+2
View File
@@ -684,6 +684,8 @@ export const settings = {
showBitrateSub: 'Vis audio-bitrate i sporlister',
floatingPlayerBar: 'Flytende Spillerlinje',
floatingPlayerBarSub: 'Hold spillerlinjen flytende over innholdet',
squareCorners: 'Firkantede hjørner',
squareCornersSub: 'Fjern avrundede hjørner på kort for et skarpt, kantete utseende',
uiScaleTitle: 'Grensesnittskala',
uiScaleLabel: 'Zoom',
confirmDeleteServerLibrary: 'Vil du også slette denne serverens lokale bibliotekindeks? Klikk Avbryt for å beholde den bufrede kopien for offline bruk.',
+2
View File
@@ -685,6 +685,8 @@ export const settings = {
showBitrateSub: 'Toon audio bitrate in tracklijsten',
floatingPlayerBar: 'Zwevende Spelerbalk',
floatingPlayerBarSub: 'Houd de spelerbalk zwevend boven de inhoud',
squareCorners: 'Rechte hoeken',
squareCornersSub: 'Verwijder afgeronde hoeken van kaarten voor een strakke, hoekige look',
uiScaleTitle: 'Interface schaal',
uiScaleLabel: 'Zoom',
confirmDeleteServerLibrary: 'Ook de lokale bibliotheekindex van deze server verwijderen? Klik op Annuleren om de gecachte kopie voor offline gebruik te behouden.',
+2
View File
@@ -765,5 +765,7 @@ export const settings = {
showBitrateSub: 'Wyświetlaj bitrate audio na listach utworów',
floatingPlayerBar: 'Pasek odtwarzacza jako nakładka',
floatingPlayerBarSub: 'Wyświetlaj pasek odtwarzacza jako nakładkę nad zawartością',
squareCorners: 'Proste rogi',
squareCornersSub: 'Usuń zaokrąglone rogi kart, aby uzyskać ostry, kanciasty wygląd',
uiScaleTitle: 'Skala interfejsu',
uiScaleLabel: 'Przybliżenie',};
+2
View File
@@ -700,6 +700,8 @@ export const settings = {
showBitrateSub: 'Arată bitrate-ul audio în listările de piese',
floatingPlayerBar: 'Bară Player plutitoare',
floatingPlayerBarSub: 'Păstrează bara playerului plutind deasupra conținutului',
squareCorners: 'Colțuri drepte',
squareCornersSub: 'Elimină colțurile rotunjite ale cardurilor pentru un aspect clar, unghiular',
uiScaleTitle: 'Scara interfaței',
uiScaleLabel: 'Zoom',
confirmDeleteServerLibrary: 'Ștergi și indexul local al bibliotecii pentru acest server? Apasă Anulează pentru a păstra copia din cache pentru utilizare offline.',
+2
View File
@@ -785,6 +785,8 @@ export const settings = {
showBitrateSub: 'Отображать битрейт аудио в списках треков',
floatingPlayerBar: 'Плавающая панель плеера',
floatingPlayerBarSub: 'Держать панель плеера плавающей над содержимым',
squareCorners: 'Прямые углы',
squareCornersSub: 'Убрать скруглённые углы у карточек для чёткого, прямоугольного вида',
uiScaleTitle: 'Масштаб интерфейса',
uiScaleLabel: 'Масштаб',
confirmDeleteServerLibrary: 'Также удалить локальный индекс библиотеки этого сервера? Нажмите «Отмена», чтобы сохранить кэш-копию для офлайн-использования.',
+2
View File
@@ -684,6 +684,8 @@ export const settings = {
showBitrateSub: '在曲目列表中显示音频比特率',
floatingPlayerBar: '浮动播放栏',
floatingPlayerBarSub: '保持播放栏悬浮在内容上方',
squareCorners: '直角边角',
squareCornersSub: '移除卡片的圆角,呈现利落的方正外观',
uiScaleTitle: '界面缩放',
uiScaleLabel: '缩放',
confirmDeleteServerLibrary: '是否同时删除该服务器的本地库索引?点击“取消”可保留缓存副本以供离线使用。',
+6
View File
@@ -83,6 +83,10 @@ interface ThemeState {
setExpandReplayGain: (v: boolean) => void;
floatingPlayerBar: boolean;
setFloatingPlayerBar: (v: boolean) => void;
/** Strip rounded corners off all grid cards (and their covers), overriding the
* active theme. Drives `html[data-square-corners]` in App.tsx. */
squareCorners: boolean;
setSquareCorners: (v: boolean) => void;
/** Master toggle for external artist artwork (fanart.tv). Off by default (§20). */
externalArtworkEnabled: boolean;
setExternalArtworkEnabled: (v: boolean) => void;
@@ -149,6 +153,8 @@ export const useThemeStore = create<ThemeState>()(
setExpandReplayGain: (v) => set({ expandReplayGain: v }),
floatingPlayerBar: false,
setFloatingPlayerBar: (v) => set({ floatingPlayerBar: v }),
squareCorners: false,
setSquareCorners: (v) => set({ squareCorners: v }),
externalArtworkEnabled: false,
setExternalArtworkEnabled: (v) => set({ externalArtworkEnabled: v }),
externalArtworkByok: '',
+1
View File
@@ -96,3 +96,4 @@
@import './back-to-top.css';
@import './error-boundary.css';
@import './backdrop-source-list.css';
@import './square-corners.css';
+70
View File
@@ -0,0 +1,70 @@
/* Square Corners override
*
* Toggled by `html[data-square-corners]` (themeStore `squareCorners`, set in
* App.tsx). A global, theme-independent override that strips the rounded
* corners off the grid cards (album / playlist / artist / song / "because")
* and their cover artwork, for users who prefer a sharp, boxy look.
*
* Scope is deliberately limited to cards + their covers buttons, inputs,
* dialogs and the player bar keep whatever radius the active theme defines.
* `!important` is required because some themes set card radii with their own
* `!important` (see e.g. `.artist-card-avatar-initial`).
*
* The fallback artist initials avatar (`.artist-card-avatar-initial`) stays a
* circle on purpose it is an avatar placeholder, not a card corner. */
/* Grid cards + their covers. */
html[data-square-corners] :is(
.card,
.album-card,
.artist-card,
.song-card,
.because-card,
.album-card-cover,
.because-card-cover-wrap,
.artist-card-avatar,
.album-card-cover img,
.artist-card-avatar img
) {
border-radius: 0 !important;
}
/* Large cover art in the album / playlist / tracks detail headers (heros). */
html[data-square-corners] :is(
.album-detail-cover,
.album-detail-cover-btn,
.hero-cover,
.playlist-hero-cover,
.tracks-hero-cover,
.tracks-hero-cover img
) {
border-radius: 0 !important;
}
/* Now Playing / Radio full view cover art. */
html[data-square-corners] :is(
.np-dash-hero-cover,
.np-cover
) {
border-radius: 0 !important;
}
/* Fullscreen player, the cover lightbox, and the queue panel's current cover. */
html[data-square-corners] :is(
.fsp-cover,
.cover-lightbox-img,
.queue-current-cover
) {
border-radius: 0 !important;
}
/* Mini player cover. The mini player is a separate webview, but the
* `data-square-corners` attribute is applied in both window kinds (the App.tsx
* attribute effects run in each webview, and the mini player rehydrates the
* theme store on the shared-localStorage `storage` event). */
html[data-square-corners] :is(
.mp-cover,
.mp-album-cover
) {
border-radius: 0 !important;
}