Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/) |


Expand Down
39 changes: 38 additions & 1 deletion static/css/daw.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -5677,12 +5690,36 @@ input, textarea { font-family: inherit; }
.rail-guide[aria-pressed="true"] { color: var(--accent); background: var(--panel); }
/* Only the control being explained is marked, so the screen stays calm; the
pill at the bottom says help mode is on. */
.help-mode .help-target { cursor: help; }
/* With no song open, the studio turns several controls off for the pointer
(Export Mix, the song facts, the presence cards). Help mode still has to hear
the pointer over them to explain them, and it takes every click itself, so
nothing can be pressed by accident. */
.help-mode .help-target { cursor: help; pointer-events: auto !important; }
.help-ring {
position: fixed; z-index: 9998; pointer-events: none;
border: 2px solid var(--accent); border-radius: var(--radius-sm);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent);
}
/* The glow when Guide turns on: every explainable control at once, fading in
and out over --intro-ms (set from INTRO_MS in helpMode.js). With reduced
motion it simply shows, still, for the same time. */
.help-intro { position: fixed; inset: 0; z-index: 9997; pointer-events: none; }
.help-intro-ring {
position: absolute;
border: 2px solid var(--accent); border-radius: var(--radius-sm);
box-shadow: 0 0 10px 2px color-mix(in srgb, var(--accent) 35%, transparent);
opacity: 0;
animation: help-intro-glow var(--intro-ms, 1600ms) ease-in-out forwards;
}
@keyframes help-intro-glow {
0% { opacity: 0; }
25% { opacity: 1; }
65% { opacity: 1; }
100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.help-intro-ring { animation: none; opacity: 0.8; }
}
.help-pill {
position: fixed; z-index: 9999; left: 50%; bottom: 18px; transform: translateX(-50%);
max-width: calc(100vw - 32px);
Expand Down
Binary file added static/img/friends/adrianna-claro.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 47 additions & 7 deletions static/js/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,34 @@ const FRIEND_GROUPS = [
{
name: "Killah Trakz",
roleKey: "friends.role.killahTrakz",
url: "https://www.instagram.com/killahtrakz/",
links: [
{ label: "Instagram", url: "https://www.instagram.com/killahtrakz/" },
{ label: "Spotify", url: "https://open.spotify.com/artist/6nePYOqT4E7D4C48twPtKk" },
{ label: "Apple Music", url: "https://music.apple.com/us/artist/killah-trakz/371296633" },
],
logo: "/img/friends/killah-trakz.jpg",
avatar: true,
},
{
name: "NIHIL",
roleKey: "friends.role.nihil",
url: "https://www.instagram.com/somosnihil/",
links: [
{ label: "Instagram", url: "https://www.instagram.com/somosnihil/" },
{ label: "Spotify", url: "https://open.spotify.com/artist/1OeKplJxFNM6JHrWeo2SaV" },
],
logo: "/img/friends/nihil.jpg",
avatar: true,
},
{
name: "Adrianna Claro",
roleKey: "friends.role.adriannaClaro",
logo: "/img/friends/adrianna-claro.webp",
avatar: true,
links: [
{ label: "Spotify", url: "https://open.spotify.com/album/10fZ48iJhIe3lWcXSMUlFG" },
{ label: "Apple Music", url: "https://music.apple.com/us/album/happy/1592026727?i=1592026728" },
],
},
],
},
{
Expand Down Expand Up @@ -3523,13 +3540,23 @@ function friendGlyph(url) {
return svg;
}

// One partner card: image (or initial badge), name, role, link glyph.
function friendCard(f) {
/** A link that opens outside the app, as every partner link does. */
function friendLink(url) {
const a = document.createElement("a");
a.className = "lib-friend";
a.href = f.url;
a.href = url;
a.target = "_blank";
a.rel = "noopener noreferrer";
return a;
}

// One partner card: image (or initial badge), name, role, link glyph. A card
// with one `url` is a link as a whole. A card with several `links` cannot be:
// a link inside a link is invalid HTML, and which one a click follows is up to
// the browser. So it is a plain card with one labelled link per destination.
function friendCard(f) {
const multi = Array.isArray(f.links) && f.links.length > 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
Expand Down Expand Up @@ -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;
}

Expand Down
59 changes: 57 additions & 2 deletions static/js/helpMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { t } from "./i18n.js";
/** Lane controls name their instrument in the text ("Silences the Drums"). */
const laneName = (el) => el.closest(".mx-row, .lane-header")?.querySelector(".mx-name")?.textContent.trim() || "";
const chipName = (el) => el.textContent.trim();
/** The instrument a presence card measures, by its own name ("Drums"), not
* the card's heading ("Drum intensity"), so the sentence reads naturally. */
const presenceName = (el) => t(`stem.${el.dataset.stem}`);

// Order matters only where selectors overlap: the first match wins.
const HELP = [
Expand Down Expand Up @@ -51,7 +54,13 @@ const HELP = [
[".mx-btn.mute", "help.mute", laneName],
[".mx-btn.solo", "help.solo", laneName],
[".lane-dl", "help.download", laneName],
["#transport .daw-info-row", "help.songFacts", null, "help.songFactsTitle"],
['.daw-meta-card[data-meta="key"]', "help.meta.key"],
['.daw-meta-card[data-meta="bpm"]', "help.meta.bpm"],
['.daw-meta-card[data-meta="lufs"]', "help.meta.lufs"],
['.daw-meta-card[data-meta="duration"]', "help.meta.duration"],
['.daw-meta-card[data-meta="dr"]', "help.meta.dr"],
['.daw-meta-card[data-meta="stability"]', "help.meta.stability"],
[".stem-presence-panel .stem-card[data-stem]", "help.presence", presenceName],
["#ruler-time", "help.waves", null, "help.wavesTitle"],
[".waves-column", "help.waves", null, "help.wavesTitle"],
["#t-stop", "help.stop"],
Expand Down Expand Up @@ -108,6 +117,11 @@ let on = false;
let tipEl = null;
let pillEl = null;
let ringEl = null;
let introEl = null;
let introTimer = null;
// How long the glow on every explainable control lasts when Guide turns on.
// daw.css reads it through --intro-ms, so the fade and the cleanup agree.
const INTRO_MS = 1600;
let current = null;

export function isHelpModeOn() {
Expand Down Expand Up @@ -249,6 +263,40 @@ function markTargets() {
}
}

/**
* When Guide turns on, every control it can explain glows once and fades, so
* the user sees where to point without the screen staying busy. One ring per
* control, drawn the same way as the pointer's ring, so no panel clips them.
* Large areas (the waveforms, the song facts) are left out: a glowing box
* across half the screen says nothing about where to point.
*/
function playIntro() {
stopIntro();
const vw = window.innerWidth, vh = window.innerHeight;
const big = vw * vh * 0.04;
for (const el of document.querySelectorAll(".help-target")) {
const r = visibleRect(el);
if (r.width * r.height > big || r.bottom < 0 || r.top > vh || r.right < 0 || r.left > vw) continue;
const ring = document.createElement("div");
ring.className = "help-intro-ring";
ring.style.left = `${Math.round(r.left)}px`;
ring.style.top = `${Math.round(r.top)}px`;
ring.style.width = `${Math.round(r.width)}px`;
ring.style.height = `${Math.round(r.height)}px`;
introEl.appendChild(ring);
}
introEl.hidden = false;
introTimer = setTimeout(stopIntro, INTRO_MS);
}

function stopIntro() {
clearTimeout(introTimer);
introTimer = null;
if (!introEl) return;
introEl.hidden = true;
introEl.replaceChildren();
}

function onPointerOver(e) {
const entry = findEntry(e.target);
if (entry) show(entry);
Expand Down Expand Up @@ -302,6 +350,7 @@ export function setHelpMode(next) {
window.addEventListener("resize", hide);
document.addEventListener("scroll", hide, true);
markTargets();
playIntro();
} else {
document.removeEventListener("pointerover", onPointerOver, true);
for (const type of PRESS_EVENTS) document.removeEventListener(type, swallow, true);
Expand All @@ -310,6 +359,7 @@ export function setHelpMode(next) {
window.removeEventListener("resize", hide);
document.removeEventListener("scroll", hide, true);
hide();
stopIntro();
markTargets();
}
}
Expand All @@ -331,6 +381,11 @@ export function initHelpMode() {
ringEl.className = "help-ring";
ringEl.setAttribute("aria-hidden", "true");
ringEl.hidden = true;
document.body.append(ringEl, tipEl, pillEl);
introEl = document.createElement("div");
introEl.className = "help-intro";
introEl.setAttribute("aria-hidden", "true");
introEl.style.setProperty("--intro-ms", `${INTRO_MS}ms`);
introEl.hidden = true;
document.body.append(introEl, ringEl, tipEl, pillEl);
btn.addEventListener("click", () => setHelpMode(!on));
}
Loading