From 31e2e682cb64e606f676ec83bb6e0f9e9d5f1e5b Mon Sep 17 00:00:00 2001 From: Thales <> Date: Sun, 4 Oct 2026 21:28:57 +0100 Subject: [PATCH] feat(recommend): Adrianna Claro on We Recommend, and partner cards with several links Adrianna Claro, a soul and contemporary R&B singer, songwriter and composer, joins Artists & Creators, with her photo and her music on Spotify and Apple Music. Killah Trakz gains Spotify and Apple Music beside Instagram, and NIHIL gains Spotify. A partner card could only hold one link, because the whole card was the link. A card can now carry several: it is then a plain card with one labelled pill per destination, since a link inside a link is invalid HTML and which one a click follows is up to the browser. A card with a single url stays a link as a whole, exactly as before. The README table lists the same links, and is now sorted by category, then name, the order the app already shows them in. Closes #761 --- README.md | 21 +++++----- static/css/daw.css | 13 ++++++ static/img/friends/adrianna-claro.webp | Bin 0 -> 5264 bytes static/js/catalog.js | 54 +++++++++++++++++++++---- static/js/i18n.js | 10 +++++ tests/e2e/we-recommend.spec.mjs | 53 ++++++++++++++++++++++++ 6 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 static/img/friends/adrianna-claro.webp create mode 100644 tests/e2e/we-recommend.spec.mjs diff --git a/README.md b/README.md index f9bb21dd..f69f0ccc 100644 --- a/README.md +++ b/README.md @@ -53,22 +53,23 @@ StemDeck is free and **does not accept any money, sponsorship, or funding** fro | Category | Name | What they do | Link | |---|---|---|---| -| Artists & Creators | Joao Gaspar | Producer, film scorer, touring/session musician | [@jay_glaspar](https://www.instagram.com/jay_glaspar) | -| Artists & Creators | Killah Trakz | Industry Secret Villain and Media Influencer | [@killahtrakz](https://www.instagram.com/killahtrakz/) | -| Artists & Creators | More Notes Less Talk | Gear-focused creative project with a raw, tape-recorded identity | [@morenoteslesstalk](https://www.youtube.com/@morenoteslesstalk) | +| Artists & Creators | Adrianna Claro | Soul and Contemporary R&B Singer, Songwriter and Composer | [Spotify](https://open.spotify.com/album/10fZ48iJhIe3lWcXSMUlFG) · [Apple Music](https://music.apple.com/us/album/happy/1592026727?i=1592026728) | | Artists & Creators | Analog4Lyfe | Analog gear specialist | [@analog4lyfe](https://www.instagram.com/analog4lyfe) | | Artists & Creators | Dead röses | Cork-based punk rock band | [@dead_rosesband](https://www.instagram.com/dead_rosesband) | -| Artists & Creators | NIHIL | Modern alternative metal from Argentina. Fueled by emotionally charged vocals, anger and emptiness | [@somosnihil](https://www.instagram.com/somosnihil/) | +| Artists & Creators | Joao Gaspar | Producer, film scorer, touring/session musician | [@jay_glaspar](https://www.instagram.com/jay_glaspar) | +| Artists & Creators | Killah Trakz | Industry Secret Villain and Media Influencer | [@killahtrakz](https://www.instagram.com/killahtrakz/) · [Spotify](https://open.spotify.com/artist/6nePYOqT4E7D4C48twPtKk) · [Apple Music](https://music.apple.com/us/artist/killah-trakz/371296633) | +| Artists & Creators | More Notes Less Talk | Gear-focused creative project with a raw, tape-recorded identity | [@morenoteslesstalk](https://www.youtube.com/@morenoteslesstalk) | +| Artists & Creators | NIHIL | Modern alternative metal from Argentina. Fueled by emotionally charged vocals, anger and emptiness | [@somosnihil](https://www.instagram.com/somosnihil/) · [Spotify](https://open.spotify.com/artist/1OeKplJxFNM6JHrWeo2SaV) | | Instrument Builders & Repair | Dlima Guitars | Custom guitars and basses | [@dlimaguitars](https://www.instagram.com/dlimaguitars) | -| Instrument Builders & Repair | Lisbon Guitar Works | Handmade guitars in Lisbon | [dlimaguitars.com](https://dlimaguitars.com) | | Instrument Builders & Repair | Kris Luthier | Instrument repair and restoration | [@krisluthier](https://www.instagram.com/krisluthier) | -| Music Gear | Empress Effects | Boutique effects pedals | [empresseffects.com](https://empresseffects.com) | -| Music Gear | Thomann | Large music-equipment retailer | [@thomann.music](https://www.instagram.com/thomann.music) | +| Instrument Builders & Repair | Lisbon Guitar Works | Handmade guitars in Lisbon | [dlimaguitars.com](https://dlimaguitars.com) | +| Media & Community | Not Another Audio Podcast | Audio, music and tech industry podcast by working professionals | [Libsyn](https://directory.libsyn.com/shows/view/id/20f5a1e3-6fea-4d41-8cdf-451ce6fc6cda) | +| Media & Community | r/bass | Bass-player community | [r/Bass](https://www.reddit.com/r/Bass) | +| Media & Community | slashCAM | Camera, video, and post-production media | [@slashcam.de](https://www.instagram.com/slashcam.de) | | Music & Karaoke Technology | Beltr | Local, subscription-free karaoke software | [beltr.app](https://beltr.app/) | | Music & Karaoke Technology | Seratone | TV-based karaoke system | [seratone.audio](https://seratone.audio/) | -| Media & Community | slashCAM | Camera, video, and post-production media | [@slashcam.de](https://www.instagram.com/slashcam.de) | -| Media & Community | r/bass | Bass-player community | [r/Bass](https://www.reddit.com/r/Bass) | -| Media & Community | Not Another Audio Podcast | Audio, music and tech industry podcast by working professionals | [Libsyn](https://directory.libsyn.com/shows/view/id/20f5a1e3-6fea-4d41-8cdf-451ce6fc6cda) | +| Music Gear | Empress Effects | Boutique effects pedals | [empresseffects.com](https://empresseffects.com) | +| Music Gear | Thomann | Large music-equipment retailer | [@thomann.music](https://www.instagram.com/thomann.music) | | Writers & Storytellers | Alexandre Borges | Portuguese writer, screenwriter, and cultural commentator | [Books & author profile](https://www.instagram.com/alexgram_b/) | diff --git a/static/css/daw.css b/static/css/daw.css index d015e4a0..5e139735 100644 --- a/static/css/daw.css +++ b/static/css/daw.css @@ -4564,6 +4564,19 @@ input, textarea { font-family: inherit; } } .lib-friend:hover .lib-friend-ig, .lib-friend:hover .lib-friend-link { opacity: 1; } +/* A card with several links: the card itself is not a link, each pill is. */ +.lib-friend-multi:hover { background: var(--panel); border-color: var(--border); color: var(--fg-2); } +.lib-friend-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; margin-top: auto; padding-top: 3px; } +.lib-friend-pill { + display: inline-flex; align-items: center; gap: 4px; + padding: 2px 7px; border-radius: 999px; + border: 1px solid var(--border-strong); background: var(--panel-2); + color: var(--fg-2); font-size: 9.5px; font-weight: 600; text-decoration: none; + transition: color var(--t-fast), border-color var(--t-fast); +} +.lib-friend-pill:hover { color: var(--fg); border-color: var(--accent); } +.lib-friend-pill .lib-friend-link, +.lib-friend-pill .lib-friend-ig { width: 10px; height: 10px; margin-top: 0; padding-top: 0; opacity: 1; } .lib-friend-name { width: 100%; font-size: 11px; diff --git a/static/img/friends/adrianna-claro.webp b/static/img/friends/adrianna-claro.webp new file mode 100644 index 0000000000000000000000000000000000000000..38f1e7f6a6a3fb62da6c6c472952eebd4384be7e GIT binary patch literal 5264 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 W`TzfT=M+?a*;RsM7C 0; + const a = multi ? document.createElement("div") : friendLink(f.url); + a.className = multi ? "lib-friend lib-friend-multi" : "lib-friend"; a.title = f.name; // A monogram badge (first initial) keeps the card on-brand when an entry has @@ -3575,7 +3602,20 @@ function friendCard(f) { a.appendChild(role); } - a.appendChild(friendGlyph(f.url)); + if (!multi) { + a.appendChild(friendGlyph(f.url)); + return a; + } + const row = document.createElement("span"); + row.className = "lib-friend-links"; + for (const link of f.links) { + const l = friendLink(link.url); + l.className = "lib-friend-pill"; + l.title = `${f.name}: ${link.label}`; + l.append(friendGlyph(link.url), link.label); + row.appendChild(l); + } + a.appendChild(row); return a; } diff --git a/static/js/i18n.js b/static/js/i18n.js index d6e9f29b..4bf2534f 100644 --- a/static/js/i18n.js +++ b/static/js/i18n.js @@ -733,6 +733,7 @@ const en = { "friends.role.deadRoses": "Cork-based punk rock band", "friends.role.killahTrakz": "Industry Secret Villain and Media Influencer", "friends.role.nihil": "Modern alternative metal from Argentina. Fueled by emotionally charged vocals, anger and emptiness", + "friends.role.adriannaClaro": "Soul and Contemporary R&B Singer, Songwriter and Composer", "friends.role.empressEffects": "Boutique effects pedals", "friends.role.thomann": "Large music-equipment retailer", "friends.role.beltr": "Local, subscription-free karaoke software", @@ -1621,6 +1622,7 @@ const pl = { "friends.role.deadRoses": "Zespół punkrockowy z Corku", "friends.role.killahTrakz": "Sekretny złoczyńca branży i influencer medialny", "friends.role.nihil": "Nowoczesny metal alternatywny z Argentyny. Napędzany emocjonalnym wokalem, gniewem i pustką", + "friends.role.adriannaClaro": "Wokalistka soul i współczesnego R&B, autorka piosenek i kompozytorka", "friends.role.empressEffects": "Butikowe efekty gitarowe", "friends.role.thomann": "Duży sklep ze sprzętem muzycznym", "friends.role.beltr": "Lokalne karaoke bez abonamentu", @@ -2500,6 +2502,7 @@ const ja = { "friends.role.deadRoses": "コーク拠点のパンクロックバンド", "friends.role.killahTrakz": "業界の隠れた悪役でメディアインフルエンサー", "friends.role.nihil": "アルゼンチン発のモダンなオルタナティヴメタル。感情的なボーカル、怒り、そして虚無が原動力", + "friends.role.adriannaClaro": "ソウルとコンテンポラリーR&Bのシンガー、ソングライター、作曲家", "friends.role.empressEffects": "ブティック系エフェクターペダル", "friends.role.thomann": "大手の楽器機材販売店", "friends.role.beltr": "サブスク不要、ローカルで動くカラオケソフト", @@ -3355,6 +3358,7 @@ const zhHans = { "friends.role.deadRoses": "来自科克的朋克摇滚乐队", "friends.role.killahTrakz": "行业隐秘反派与媒体影响者", "friends.role.nihil": "来自阿根廷的现代另类金属。以饱含情感的人声、愤怒与虚无为驱动", + "friends.role.adriannaClaro": "灵魂乐与当代 R&B 歌手、词曲作者和作曲家", "friends.role.empressEffects": "精品效果器", "friends.role.thomann": "大型乐器器材零售商", "friends.role.beltr": "本地运行、无需订阅的卡拉OK软件", @@ -4211,6 +4215,7 @@ const de = { "friends.role.deadRoses": "Punkrock-Band aus Cork", "friends.role.killahTrakz": "Heimlicher Bösewicht der Branche und Medien-Influencer", "friends.role.nihil": "Moderner Alternative Metal aus Argentinien. Angetrieben von emotionsgeladenem Gesang, Wut und Leere", + "friends.role.adriannaClaro": "Soul- und Contemporary-R&B-Sängerin, Songwriterin und Komponistin", "friends.role.empressEffects": "Boutique-Effektpedale", "friends.role.thomann": "Großer Händler für Musik-Equipment", "friends.role.beltr": "Lokale Karaoke-Software ohne Abo", @@ -5077,6 +5082,7 @@ const pt = { "friends.role.deadRoses": "Banda de punk rock de Cork", "friends.role.killahTrakz": "Vilão secreto da indústria e influenciador digital", "friends.role.nihil": "Metal alternativo moderno da Argentina. Movido por vocais carregados de emoção, raiva e vazio", + "friends.role.adriannaClaro": "Cantora, compositora e autora de soul e R&B contemporâneo", "friends.role.empressEffects": "Pedais de efeito boutique", "friends.role.thomann": "Grande varejista de equipamentos musicais", "friends.role.beltr": "Software de karaokê local, sem assinatura", @@ -5944,6 +5950,7 @@ const id = { "friends.role.deadRoses": "Band punk rock asal Cork", "friends.role.killahTrakz": "Penjahat Rahasia Industri dan Influencer Media", "friends.role.nihil": "Metal alternatif modern dari Argentina. Digerakkan oleh vokal penuh emosi, amarah, dan kehampaan", + "friends.role.adriannaClaro": "Penyanyi, penulis lagu, dan komposer soul dan R&B kontemporer", "friends.role.empressEffects": "Pedal efek butik", "friends.role.thomann": "Peritel besar perangkat musik", "friends.role.beltr": "Perangkat lunak karaoke lokal tanpa langganan", @@ -6800,6 +6807,7 @@ const fr = { "friends.role.deadRoses": "Groupe de punk rock basé à Cork", "friends.role.killahTrakz": "Méchant secret de l'industrie et influenceur média", "friends.role.nihil": "Metal alternatif moderne d'Argentine. Porté par un chant chargé d'émotion, la colère et le vide", + "friends.role.adriannaClaro": "Chanteuse, autrice et compositrice de soul et de R&B contemporain", "friends.role.empressEffects": "Pédales d'effets boutique", "friends.role.thomann": "Grand détaillant de matériel de musique", "friends.role.beltr": "Logiciel de karaoké local, sans abonnement", @@ -7840,6 +7848,7 @@ const es = { "friends.role.deadRoses": "Banda de punk rock de Cork", "friends.role.killahTrakz": "Villano secreto de la industria e influencer de medios", "friends.role.nihil": "Metal alternativo moderno de Argentina. Impulsado por voces cargadas de emoción, rabia y vacío", + "friends.role.adriannaClaro": "Cantante, compositora y autora de soul y R&B contemporáneo", "friends.role.empressEffects": "Pedales de efectos boutique", "friends.role.thomann": "Gran tienda de equipo musical", "friends.role.beltr": "Software de karaoke local y sin suscripción", @@ -8728,6 +8737,7 @@ const ko = { "friends.role.deadRoses": "코크를 기반으로 활동하는 펑크 록 밴드", "friends.role.killahTrakz": "업계의 숨은 악당이자 미디어 인플루언서", "friends.role.nihil": "아르헨티나에서 온 모던 얼터너티브 메탈. 감정적인 보컬과 분노, 공허가 원동력", + "friends.role.adriannaClaro": "소울과 컨템퍼러리 R&B 가수, 송라이터, 작곡가", "friends.role.empressEffects": "부티크 이펙터 페달", "friends.role.thomann": "대형 음악 장비 판매점", "friends.role.beltr": "구독이 필요 없는 로컬 노래방 소프트웨어", diff --git a/tests/e2e/we-recommend.spec.mjs b/tests/e2e/we-recommend.spec.mjs new file mode 100644 index 00000000..9a157960 --- /dev/null +++ b/tests/e2e/we-recommend.spec.mjs @@ -0,0 +1,53 @@ +// We Recommend: a partner card with one link is a link as a whole; a card with +// several (Adrianna Claro: Spotify and Apple Music) is a plain card with one +// labelled link per destination, since a link cannot hold other links. + +import { test, expect } from "@playwright/test"; + +import { seedLibrary, stubUpdateCheck } from "./helpers.mjs"; + +async function openWeRecommend(page) { + await seedLibrary(page); + await stubUpdateCheck(page); + await page.goto("/", { waitUntil: "domcontentloaded" }); + await page.locator("#friendsBtn").click(); +} + +test.describe("We Recommend", () => { + test("a card with two links offers both, each opening outside the app", async ({ page }) => { + await openWeRecommend(page); + const card = page.locator(".lib-friend").filter({ hasText: "Adrianna Claro" }); + await expect(card).toHaveCount(1); + await expect(card).toBeVisible(); + // Not a link itself, so the two below are the only things to click. + expect(await card.evaluate((el) => el.tagName)).toBe("DIV"); + // Her photo loads, rather than falling back to the monogram. + await expect(card.locator("img.lib-friend-avatar")).toHaveAttribute("src", "/img/friends/adrianna-claro.webp"); + await expect.poll(() => card.locator("img.lib-friend-avatar").evaluate((img) => img.naturalWidth)).toBeGreaterThan(0); + + const spotify = card.locator(".lib-friend-pill", { hasText: "Spotify" }); + const apple = card.locator(".lib-friend-pill", { hasText: "Apple Music" }); + await expect(spotify).toHaveAttribute("href", "https://open.spotify.com/album/10fZ48iJhIe3lWcXSMUlFG"); + await expect(apple).toHaveAttribute("href", "https://music.apple.com/us/album/happy/1592026727?i=1592026728"); + for (const link of [spotify, apple]) { + await expect(link).toHaveAttribute("target", "_blank"); + await expect(link).toHaveAttribute("rel", "noopener noreferrer"); + } + }); + + test("a card with one link is still a link as a whole", async ({ page }) => { + await openWeRecommend(page); + const card = page.locator(".lib-friend").filter({ hasText: "Analog4Lyfe" }); + expect(await card.evaluate((el) => el.tagName)).toBe("A"); + await expect(card).toHaveAttribute("href", "https://www.instagram.com/analog4lyfe"); + await expect(card.locator(".lib-friend-pill")).toHaveCount(0); + }); + + test("Killah Trakz and NIHIL offer Instagram and Spotify, and Killah Trakz Apple Music too", async ({ page }) => { + await openWeRecommend(page); + const pills = (name) => page.locator(".lib-friend").filter({ hasText: name }).locator(".lib-friend-pill"); + await expect(pills("Killah Trakz")).toHaveText(["Instagram", "Spotify", "Apple Music"]); + await expect(pills("NIHIL")).toHaveText(["Instagram", "Spotify"]); + await expect(pills("NIHIL").filter({ hasText: "Spotify" })).toHaveAttribute("href", "https://open.spotify.com/artist/1OeKplJxFNM6JHrWeo2SaV"); + }); +});