@gblp/circle-of-fifths 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -2,15 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
Standalone Angular 22 component for exploring keys, diatonic chords, and common progressions with an interactive circle of fifths.
|
|
4
4
|
|
|
5
|
+
[View the package on npm](https://www.npmjs.com/package/@gblp/circle-of-fifths)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @gblp/circle-of-fifths
|
|
9
|
+
```
|
|
10
|
+
|
|
5
11
|
```ts
|
|
6
12
|
import { Component } from '@angular/core';
|
|
7
13
|
import { CircleOfFifthsComponent } from '@gblp/circle-of-fifths';
|
|
8
14
|
|
|
9
15
|
@Component({
|
|
10
16
|
imports: [CircleOfFifthsComponent],
|
|
11
|
-
template:
|
|
17
|
+
template: `<the-chords-circle-of-fifths [language]="'es'" />`,
|
|
12
18
|
})
|
|
13
19
|
export class App {}
|
|
14
20
|
```
|
|
15
21
|
|
|
22
|
+
`language` accepts `en` or `es` and defaults to `en`.
|
|
23
|
+
|
|
24
|
+
Override the component theme from any ancestor with these inherited CSS variables:
|
|
25
|
+
|
|
26
|
+
```css
|
|
27
|
+
--chords-background;
|
|
28
|
+
--chords-background-alt;
|
|
29
|
+
--chords-surface;
|
|
30
|
+
--chords-surface-elevated;
|
|
31
|
+
--chords-text;
|
|
32
|
+
--chords-muted;
|
|
33
|
+
--chords-primary;
|
|
34
|
+
--chords-secondary;
|
|
35
|
+
--chords-highlight;
|
|
36
|
+
--chords-danger;
|
|
37
|
+
--chords-border;
|
|
38
|
+
--chords-on-primary;
|
|
39
|
+
```
|
|
40
|
+
|
|
16
41
|
Build the package with `npm run build:lib`. The publishable Angular package is written to `dist/circle-of-fifths-lib`.
|
|
@@ -1,43 +1,300 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
2
|
+
import { input, computed, Component, signal } from '@angular/core';
|
|
3
3
|
import { NgClass } from '@angular/common';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
const PROGRESSIONS = {
|
|
6
|
+
major: [
|
|
7
|
+
{
|
|
8
|
+
name: { en: 'Anthemic', es: 'Épica' },
|
|
9
|
+
mood: { en: '😄 Joyful', es: '😄 Alegre' },
|
|
10
|
+
genre: { en: 'Folk, Rock', es: 'Folk, rock' },
|
|
11
|
+
numerals: ['I', 'IV', 'V', 'I'],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: { en: 'Hopeful', es: 'Esperanzadora' },
|
|
15
|
+
mood: { en: '✨ Hopeful', es: '✨ Esperanzadora' },
|
|
16
|
+
genre: { en: 'Pop, Indie', es: 'Pop, indie' },
|
|
17
|
+
numerals: ['I', 'V', 'vi', 'IV'],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: { en: 'Melancholic', es: 'Melancólica' },
|
|
21
|
+
mood: { en: '😢 Emotional', es: '😢 Emocional' },
|
|
22
|
+
genre: { en: 'Ballads, Pop', es: 'Baladas, pop' },
|
|
23
|
+
numerals: ['vi', 'IV', 'I', 'V'],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: { en: 'Cinematic', es: 'Cinemática' },
|
|
27
|
+
mood: { en: '🎬 Tense', es: '🎬 Tensa' },
|
|
28
|
+
genre: { en: 'Film, Jazz', es: 'Cine, jazz' },
|
|
29
|
+
numerals: ['ii', 'V', 'vii°', 'I'],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: { en: 'Nostalgic', es: 'Retro' },
|
|
33
|
+
mood: { en: '🕺 Retro', es: '🕺 Retro' },
|
|
34
|
+
genre: { en: 'Doo-Wop, Pop', es: 'Doo-Wop, pop' },
|
|
35
|
+
numerals: ['I', 'vi', 'IV', 'V'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: { en: 'Dreamy', es: 'Soñadora' },
|
|
39
|
+
mood: { en: '🌸 Floaty', es: '🌸 Etérea' },
|
|
40
|
+
genre: { en: 'J-Pop, Anime', es: 'J-Pop, anime' },
|
|
41
|
+
numerals: ['IV', 'V', 'iii', 'vi'],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: { en: 'Gospel', es: 'Gospel' },
|
|
45
|
+
mood: { en: '🙏 Uplifting', es: '🙏 Elevadora' },
|
|
46
|
+
genre: { en: 'Gospel, Soul', es: 'Gospel, soul' },
|
|
47
|
+
numerals: ['I', 'IV', 'ii', 'V'],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: { en: 'Bittersweet', es: 'Agridulce' },
|
|
51
|
+
mood: { en: '🍂 Bittersweet', es: '🍂 Agridulce' },
|
|
52
|
+
genre: { en: 'Alt Rock, Indie', es: 'Rock alternativo, indie' },
|
|
53
|
+
numerals: ['I', 'iii', 'IV', 'V'],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
minor: [
|
|
57
|
+
{
|
|
58
|
+
name: { en: 'Brooding', es: 'Sombría' },
|
|
59
|
+
mood: { en: '🌑 Dark', es: '🌑 Oscura' },
|
|
60
|
+
genre: { en: 'Rock, Metal', es: 'Rock, metal' },
|
|
61
|
+
numerals: ['i', 'VII', 'VI', 'VII'],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: { en: 'Haunting', es: 'Inquietante' },
|
|
65
|
+
mood: { en: '👻 Mysterious', es: '👻 Misteriosa' },
|
|
66
|
+
genre: { en: 'Film, Gothic', es: 'Cine, gótico' },
|
|
67
|
+
numerals: ['i', 'iv', 'VII', 'III'],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: { en: 'Driving', es: 'Impulsora' },
|
|
71
|
+
mood: { en: '⚡ Urgent', es: '⚡ Urgente' },
|
|
72
|
+
genre: { en: 'Pop, EDM', es: 'Pop, EDM' },
|
|
73
|
+
numerals: ['i', 'VI', 'III', 'VII'],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: { en: 'Wistful', es: 'Nostálgica' },
|
|
77
|
+
mood: { en: '🌙 Wistful', es: '🌙 Nostálgica' },
|
|
78
|
+
genre: { en: 'Cinematic', es: 'Cinemática' },
|
|
79
|
+
numerals: ['i', 'v', 'VI', 'VII'],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: { en: 'Flamenco', es: 'Flamenco' },
|
|
83
|
+
mood: { en: '🌹 Passionate', es: '🌹 Apasionado' },
|
|
84
|
+
genre: { en: 'Flamenco, World', es: 'Flamenco, mundial' },
|
|
85
|
+
numerals: ['i', 'VII', 'VI', 'iv'],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: { en: 'Tragic', es: 'Trágica' },
|
|
89
|
+
mood: { en: '💔 Tragic', es: '💔 Trágica' },
|
|
90
|
+
genre: { en: 'Classical, Drama', es: 'Clásica, drama' },
|
|
91
|
+
numerals: ['i', 'iv', 'v', 'i'],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: { en: 'Epic', es: 'Épica' },
|
|
95
|
+
mood: { en: '⚔️ Heroic', es: '⚔️ Heroico' },
|
|
96
|
+
genre: { en: 'Epic, Orchestral', es: 'Épica, orquestal' },
|
|
97
|
+
numerals: ['i', 'III', 'VII', 'VI'],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: { en: 'Ethereal', es: 'Etérea' },
|
|
101
|
+
mood: { en: '🌊 Hypnotic', es: '🌊 Hipnótica' },
|
|
102
|
+
genre: { en: 'Ambient, Post-Rock', es: 'Ambient, post-rock' },
|
|
103
|
+
numerals: ['i', 'VI', 'iv', 'VII'],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
const COPY = {
|
|
108
|
+
en: {
|
|
109
|
+
title: 'Circle of Fifths',
|
|
110
|
+
subtitle: 'Click any major or minor key to highlight its diatonic chords',
|
|
111
|
+
ariaLabel: 'Circle of Fifths',
|
|
112
|
+
clickKey: 'Click a key',
|
|
113
|
+
seeChords: 'to see its chords',
|
|
114
|
+
deselect: '↩ click to deselect',
|
|
115
|
+
tonicLegend: 'Tonic (I / i)',
|
|
116
|
+
majorLegend: 'Major chords (IV, V)',
|
|
117
|
+
minorLegend: 'Minor chords (ii, iii, vi)',
|
|
118
|
+
diminishedLegend: 'Diminished (vii° / ii°)',
|
|
119
|
+
diatonicChords: 'Diatonic Chords',
|
|
120
|
+
commonProgressions: 'Common Progressions in',
|
|
121
|
+
generateProgressions: 'Shuffle',
|
|
122
|
+
chorus: 'Chorus',
|
|
123
|
+
bridge: 'Bridge',
|
|
124
|
+
outro: 'Outro',
|
|
125
|
+
verse: 'Verse',
|
|
126
|
+
copy: 'Copy',
|
|
127
|
+
expand: 'Expand',
|
|
128
|
+
collapse: 'Collapse',
|
|
129
|
+
major: 'Major',
|
|
130
|
+
minor: 'Minor',
|
|
131
|
+
diminished: 'diminished',
|
|
132
|
+
relativeMinor: 'Relative minor',
|
|
133
|
+
relativeMajor: 'Relative major',
|
|
134
|
+
roles: {
|
|
135
|
+
tonic: 'Tonic',
|
|
136
|
+
supertonic: 'Supertonic',
|
|
137
|
+
mediant: 'Mediant',
|
|
138
|
+
subdominant: 'Subdominant',
|
|
139
|
+
dominant: 'Dominant',
|
|
140
|
+
submediant: 'Submediant',
|
|
141
|
+
leadingTone: 'Leading Tone',
|
|
142
|
+
subtonic: 'Subtonic',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
es: {
|
|
146
|
+
title: 'Círculo de quintas',
|
|
147
|
+
subtitle: 'Haz clic en una tonalidad mayor o menor para resaltar sus acordes diatónicos',
|
|
148
|
+
ariaLabel: 'Círculo de quintas',
|
|
149
|
+
clickKey: 'Haz clic en una tonalidad',
|
|
150
|
+
seeChords: 'para ver sus acordes',
|
|
151
|
+
deselect: '↩ haz clic para deseleccionar',
|
|
152
|
+
tonicLegend: 'Tónica (I / i)',
|
|
153
|
+
majorLegend: 'Acordes mayores (IV, V)',
|
|
154
|
+
minorLegend: 'Acordes menores (ii, iii, vi)',
|
|
155
|
+
diminishedLegend: 'Disminuido (vii° / ii°)',
|
|
156
|
+
diatonicChords: 'Acordes diatónicos',
|
|
157
|
+
commonProgressions: 'Progresiones comunes en',
|
|
158
|
+
generateProgressions: 'Generar nuevos',
|
|
159
|
+
chorus: 'Coro',
|
|
160
|
+
bridge: 'Puente',
|
|
161
|
+
outro: 'Final',
|
|
162
|
+
verse: 'Estrofa',
|
|
163
|
+
copy: 'Copiar',
|
|
164
|
+
expand: 'Expandir',
|
|
165
|
+
collapse: 'Colapsar',
|
|
166
|
+
major: 'Mayor',
|
|
167
|
+
minor: 'Menor',
|
|
168
|
+
diminished: 'disminuido',
|
|
169
|
+
relativeMinor: 'Relativa menor',
|
|
170
|
+
relativeMajor: 'Relativa mayor',
|
|
171
|
+
roles: {
|
|
172
|
+
tonic: 'Tónica',
|
|
173
|
+
supertonic: 'Supertónica',
|
|
174
|
+
mediant: 'Mediante',
|
|
175
|
+
subdominant: 'Subdominante',
|
|
176
|
+
dominant: 'Dominante',
|
|
177
|
+
submediant: 'Submediante',
|
|
178
|
+
leadingTone: 'Sensible',
|
|
179
|
+
subtonic: 'Subtónica',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
const KEYS = [
|
|
184
|
+
{ index: 0, major: 'C', minor: 'Am', sharps: 0, flats: 0 },
|
|
185
|
+
{ index: 1, major: 'G', minor: 'Em', sharps: 1, flats: 0 },
|
|
186
|
+
{ index: 2, major: 'D', minor: 'Bm', sharps: 2, flats: 0 },
|
|
187
|
+
{ index: 3, major: 'A', minor: 'F♯m', sharps: 3, flats: 0 },
|
|
188
|
+
{ index: 4, major: 'E', minor: 'C♯m', sharps: 4, flats: 0 },
|
|
189
|
+
{ index: 5, major: 'B', minor: 'G♯m', sharps: 5, flats: 0 },
|
|
190
|
+
{ index: 6, major: 'F♯', minor: 'D♯m', sharps: 6, flats: 0 },
|
|
191
|
+
{ index: 7, major: 'D♭', minor: 'B♭m', sharps: 0, flats: 5 },
|
|
192
|
+
{ index: 8, major: 'A♭', minor: 'Fm', sharps: 0, flats: 4 },
|
|
193
|
+
{ index: 9, major: 'E♭', minor: 'Cm', sharps: 0, flats: 3 },
|
|
194
|
+
{ index: 10, major: 'B♭', minor: 'Gm', sharps: 0, flats: 2 },
|
|
195
|
+
{ index: 11, major: 'F', minor: 'Dm', sharps: 0, flats: 1 },
|
|
16
196
|
];
|
|
197
|
+
function dimChord(idx) {
|
|
198
|
+
// The diminished chord root is the minor key two steps clockwise (idx+2)
|
|
199
|
+
return KEYS[(idx + 2) % 12].minor.replace(/m$/, '°');
|
|
200
|
+
}
|
|
201
|
+
function buildChordRows(idx, type, copy) {
|
|
202
|
+
const prev = (idx - 1 + 12) % 12;
|
|
203
|
+
const next = (idx + 1) % 12;
|
|
204
|
+
if (type === 'major') {
|
|
205
|
+
return [
|
|
206
|
+
{ numeral: 'I', chord: KEYS[idx].major, chordType: copy.major, role: copy.roles.tonic, state: 'tonic' },
|
|
207
|
+
{ numeral: 'ii', chord: KEYS[prev].minor, chordType: copy.minor, role: copy.roles.supertonic, state: 'scale-minor' },
|
|
208
|
+
{ numeral: 'iii', chord: KEYS[next].minor, chordType: copy.minor, role: copy.roles.mediant, state: 'scale-minor' },
|
|
209
|
+
{ numeral: 'IV', chord: KEYS[prev].major, chordType: copy.major, role: copy.roles.subdominant, state: 'scale-major' },
|
|
210
|
+
{ numeral: 'V', chord: KEYS[next].major, chordType: copy.major, role: copy.roles.dominant, state: 'scale-major' },
|
|
211
|
+
{ numeral: 'vi', chord: KEYS[idx].minor, chordType: copy.minor, role: copy.roles.submediant, state: 'scale-minor' },
|
|
212
|
+
{ numeral: 'vii°', chord: dimChord(idx), chordType: copy.diminished, role: copy.roles.leadingTone, state: 'scale-diminished' },
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
return [
|
|
217
|
+
{ numeral: 'i', chord: KEYS[idx].minor, chordType: copy.minor, role: copy.roles.tonic, state: 'tonic' },
|
|
218
|
+
{ numeral: 'iv', chord: KEYS[prev].minor, chordType: copy.minor, role: copy.roles.subdominant, state: 'scale-minor' },
|
|
219
|
+
{ numeral: 'v', chord: KEYS[next].minor, chordType: copy.minor, role: copy.roles.dominant, state: 'scale-minor' },
|
|
220
|
+
{ numeral: 'III', chord: KEYS[idx].major, chordType: copy.major, role: copy.roles.mediant, state: 'scale-major' },
|
|
221
|
+
{ numeral: 'VI', chord: KEYS[prev].major, chordType: copy.major, role: copy.roles.submediant, state: 'scale-major' },
|
|
222
|
+
{ numeral: 'VII', chord: KEYS[next].major, chordType: copy.major, role: copy.roles.subtonic, state: 'scale-major' },
|
|
223
|
+
{ numeral: 'ii°', chord: dimChord(idx), chordType: copy.diminished, role: copy.roles.supertonic, state: 'scale-diminished' },
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
class ChordSectionComponent {
|
|
228
|
+
chords = input([], /* @ts-ignore */
|
|
229
|
+
...(ngDevMode ? [{ debugName: "chords" }] : /* istanbul ignore next */ []));
|
|
230
|
+
language = input('en', /* @ts-ignore */
|
|
231
|
+
...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
|
|
232
|
+
text = computed(() => COPY[this.language()], /* @ts-ignore */
|
|
233
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
234
|
+
match = computed(() => {
|
|
235
|
+
const input = this.chords();
|
|
236
|
+
if (!input.length)
|
|
237
|
+
return null;
|
|
238
|
+
const inputSet = new Set(input);
|
|
239
|
+
let best = null;
|
|
240
|
+
for (let idx = 0; idx < 12; idx++) {
|
|
241
|
+
for (const type of ['major', 'minor']) {
|
|
242
|
+
// chord names are language-independent; COPY.en is a dummy for label fields
|
|
243
|
+
const score = buildChordRows(idx, type, COPY.en).filter((r) => inputSet.has(r.chord)).length;
|
|
244
|
+
if (!best || score > best.score)
|
|
245
|
+
best = { idx, type, score };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return best && best.score > 0 ? best : null;
|
|
249
|
+
}, /* @ts-ignore */
|
|
250
|
+
...(ngDevMode ? [{ debugName: "match" }] : /* istanbul ignore next */ []));
|
|
251
|
+
rows = computed(() => {
|
|
252
|
+
const m = this.match();
|
|
253
|
+
if (!m)
|
|
254
|
+
return [];
|
|
255
|
+
return buildChordRows(m.idx, m.type, this.text());
|
|
256
|
+
}, /* @ts-ignore */
|
|
257
|
+
...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
258
|
+
info = computed(() => {
|
|
259
|
+
const m = this.match();
|
|
260
|
+
if (!m)
|
|
261
|
+
return null;
|
|
262
|
+
const copy = this.text();
|
|
263
|
+
const key = KEYS[m.idx];
|
|
264
|
+
return {
|
|
265
|
+
fullName: `${m.type === 'major' ? key.major : key.minor} ${m.type === 'major' ? copy.major : copy.minor}`,
|
|
266
|
+
relativeKey: m.type === 'major'
|
|
267
|
+
? `${copy.relativeMinor}: ${key.minor}`
|
|
268
|
+
: `${copy.relativeMajor}: ${key.major}`,
|
|
269
|
+
};
|
|
270
|
+
}, /* @ts-ignore */
|
|
271
|
+
...(ngDevMode ? [{ debugName: "info" }] : /* istanbul ignore next */ []));
|
|
272
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChordSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
273
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChordSectionComponent, isStandalone: true, selector: "chord-section", inputs: { chords: { classPropertyName: "chords", publicName: "chords", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (rows().length) {\r\n <section class=\"chord-section\">\r\n @if (info(); as info) {\r\n <h2>{{ info.fullName }} \u2014 {{ text().diatonicChords }}</h2>\r\n <p class=\"relative-label\">{{ info.relativeKey }}</p>\r\n }\r\n <div class=\"chord-grid\">\r\n @for (row of rows(); track row.numeral) {\r\n <div class=\"chord-card\" [ngClass]=\"row.state\">\r\n <div class=\"chord-numeral\">{{ row.numeral }}</div>\r\n <div class=\"chord-name\">{{ row.chord }}</div>\r\n <div class=\"chord-type\">{{ row.chordType }}</div>\r\n <div class=\"chord-role\">{{ row.role }}</div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n}\r\n", styles: [":host{--_chords-background: var(--chords-background, #f0f4f8);--_chords-surface: var(--chords-surface, #ffffff);--_chords-surface-elevated: var(--chords-surface-elevated, #f8fafc);--_chords-text: var(--chords-text, #1e293b);--_chords-muted: var(--chords-muted, #64748b);--_chords-primary: var(--chords-primary, #3b82f6);--_chords-secondary: var(--chords-secondary, #06b6d4);--_chords-highlight: var(--chords-highlight, #f59e0b);--_chords-danger: var(--chords-danger, #a855f7);--_chords-border: var(--chords-border, #e2e8f0);--_chords-on-primary: var(--chords-on-primary, #ffffff);display:block;font-family:Segoe UI,system-ui,sans-serif}.chord-section{margin-top:36px;text-align:center}.chord-section h2{margin:0 0 4px;font-size:1.25rem;font-weight:700;color:var(--_chords-text)}.chord-section .relative-label{margin:0 0 20px;font-size:.88rem;color:var(--_chords-muted)}.chord-grid{display:flex;flex-wrap:nowrap;gap:8px;justify-content:center;overflow-x:auto;padding-bottom:4px}.chord-card{border-radius:12px;padding:12px 14px;min-width:82px;flex-shrink:0;text-align:center;background:color-mix(in srgb,var(--_chords-text) 8%,var(--_chords-surface));border:2px solid transparent;transition:transform .15s ease,box-shadow .15s ease}.chord-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px color-mix(in srgb,var(--_chords-text) 10%,transparent)}.chord-card .chord-numeral{font-size:.85rem;font-weight:700;font-style:italic;color:var(--_chords-muted)}.chord-card .chord-name{font-size:1.4rem;font-weight:800;color:var(--_chords-text);margin:2px 0;letter-spacing:-.02em}.chord-card .chord-type{font-size:.65rem;text-transform:uppercase;letter-spacing:.06em;color:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background))}.chord-card .chord-role{font-size:.7rem;color:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background));margin-top:2px}.chord-card.tonic{background:color-mix(in srgb,var(--_chords-highlight) 10%,var(--_chords-surface));border-color:var(--_chords-highlight)}.chord-card.tonic .chord-numeral,.chord-card.tonic .chord-name{color:color-mix(in srgb,var(--_chords-highlight) 58%,var(--_chords-text))}.chord-card.scale-major{background:color-mix(in srgb,var(--_chords-primary) 9%,var(--_chords-surface));border-color:var(--_chords-primary)}.chord-card.scale-major .chord-numeral,.chord-card.scale-major .chord-name{color:color-mix(in srgb,var(--_chords-primary) 65%,var(--_chords-text))}.chord-card.scale-diminished{background:color-mix(in srgb,var(--_chords-danger) 9%,var(--_chords-surface));border-color:var(--_chords-danger)}.chord-card.scale-diminished .chord-numeral,.chord-card.scale-diminished .chord-name{color:color-mix(in srgb,var(--_chords-danger) 65%,var(--_chords-text))}@media(max-width:480px){.chord-card{min-width:75px;padding:10px 12px}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
274
|
+
}
|
|
275
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChordSectionComponent, decorators: [{
|
|
276
|
+
type: Component,
|
|
277
|
+
args: [{ selector: 'chord-section', standalone: true, imports: [NgClass], template: "@if (rows().length) {\r\n <section class=\"chord-section\">\r\n @if (info(); as info) {\r\n <h2>{{ info.fullName }} \u2014 {{ text().diatonicChords }}</h2>\r\n <p class=\"relative-label\">{{ info.relativeKey }}</p>\r\n }\r\n <div class=\"chord-grid\">\r\n @for (row of rows(); track row.numeral) {\r\n <div class=\"chord-card\" [ngClass]=\"row.state\">\r\n <div class=\"chord-numeral\">{{ row.numeral }}</div>\r\n <div class=\"chord-name\">{{ row.chord }}</div>\r\n <div class=\"chord-type\">{{ row.chordType }}</div>\r\n <div class=\"chord-role\">{{ row.role }}</div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n}\r\n", styles: [":host{--_chords-background: var(--chords-background, #f0f4f8);--_chords-surface: var(--chords-surface, #ffffff);--_chords-surface-elevated: var(--chords-surface-elevated, #f8fafc);--_chords-text: var(--chords-text, #1e293b);--_chords-muted: var(--chords-muted, #64748b);--_chords-primary: var(--chords-primary, #3b82f6);--_chords-secondary: var(--chords-secondary, #06b6d4);--_chords-highlight: var(--chords-highlight, #f59e0b);--_chords-danger: var(--chords-danger, #a855f7);--_chords-border: var(--chords-border, #e2e8f0);--_chords-on-primary: var(--chords-on-primary, #ffffff);display:block;font-family:Segoe UI,system-ui,sans-serif}.chord-section{margin-top:36px;text-align:center}.chord-section h2{margin:0 0 4px;font-size:1.25rem;font-weight:700;color:var(--_chords-text)}.chord-section .relative-label{margin:0 0 20px;font-size:.88rem;color:var(--_chords-muted)}.chord-grid{display:flex;flex-wrap:nowrap;gap:8px;justify-content:center;overflow-x:auto;padding-bottom:4px}.chord-card{border-radius:12px;padding:12px 14px;min-width:82px;flex-shrink:0;text-align:center;background:color-mix(in srgb,var(--_chords-text) 8%,var(--_chords-surface));border:2px solid transparent;transition:transform .15s ease,box-shadow .15s ease}.chord-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px color-mix(in srgb,var(--_chords-text) 10%,transparent)}.chord-card .chord-numeral{font-size:.85rem;font-weight:700;font-style:italic;color:var(--_chords-muted)}.chord-card .chord-name{font-size:1.4rem;font-weight:800;color:var(--_chords-text);margin:2px 0;letter-spacing:-.02em}.chord-card .chord-type{font-size:.65rem;text-transform:uppercase;letter-spacing:.06em;color:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background))}.chord-card .chord-role{font-size:.7rem;color:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background));margin-top:2px}.chord-card.tonic{background:color-mix(in srgb,var(--_chords-highlight) 10%,var(--_chords-surface));border-color:var(--_chords-highlight)}.chord-card.tonic .chord-numeral,.chord-card.tonic .chord-name{color:color-mix(in srgb,var(--_chords-highlight) 58%,var(--_chords-text))}.chord-card.scale-major{background:color-mix(in srgb,var(--_chords-primary) 9%,var(--_chords-surface));border-color:var(--_chords-primary)}.chord-card.scale-major .chord-numeral,.chord-card.scale-major .chord-name{color:color-mix(in srgb,var(--_chords-primary) 65%,var(--_chords-text))}.chord-card.scale-diminished{background:color-mix(in srgb,var(--_chords-danger) 9%,var(--_chords-surface));border-color:var(--_chords-danger)}.chord-card.scale-diminished .chord-numeral,.chord-card.scale-diminished .chord-name{color:color-mix(in srgb,var(--_chords-danger) 65%,var(--_chords-text))}@media(max-width:480px){.chord-card{min-width:75px;padding:10px 12px}}\n"] }]
|
|
278
|
+
}], propDecorators: { chords: [{ type: i0.Input, args: [{ isSignal: true, alias: "chords", required: false }] }], language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }] } });
|
|
17
279
|
class CircleOfFifthsComponent {
|
|
280
|
+
language = input('en', /* @ts-ignore */
|
|
281
|
+
...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
|
|
282
|
+
text = computed(() => COPY[this.language()], /* @ts-ignore */
|
|
283
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
18
284
|
CX = 300;
|
|
19
285
|
CY = 300;
|
|
20
286
|
KEY_SIG = { inner: 250, outer: 288 };
|
|
21
287
|
MAJOR = { inner: 172, outer: 250 };
|
|
22
288
|
MINOR = { inner: 102, outer: 172 };
|
|
23
|
-
keys =
|
|
24
|
-
{ index: 0, major: 'C', minor: 'Am', sharps: 0, flats: 0 },
|
|
25
|
-
{ index: 1, major: 'G', minor: 'Em', sharps: 1, flats: 0 },
|
|
26
|
-
{ index: 2, major: 'D', minor: 'Bm', sharps: 2, flats: 0 },
|
|
27
|
-
{ index: 3, major: 'A', minor: 'F♯m', sharps: 3, flats: 0 },
|
|
28
|
-
{ index: 4, major: 'E', minor: 'C♯m', sharps: 4, flats: 0 },
|
|
29
|
-
{ index: 5, major: 'B', minor: 'G♯m', sharps: 5, flats: 0 },
|
|
30
|
-
{ index: 6, major: 'F♯', minor: 'D♯m', sharps: 6, flats: 0 },
|
|
31
|
-
{ index: 7, major: 'D♭', minor: 'B♭m', sharps: 0, flats: 5 },
|
|
32
|
-
{ index: 8, major: 'A♭', minor: 'Fm', sharps: 0, flats: 4 },
|
|
33
|
-
{ index: 9, major: 'E♭', minor: 'Cm', sharps: 0, flats: 3 },
|
|
34
|
-
{ index: 10, major: 'B♭', minor: 'Gm', sharps: 0, flats: 2 },
|
|
35
|
-
{ index: 11, major: 'F', minor: 'Dm', sharps: 0, flats: 1 },
|
|
36
|
-
];
|
|
289
|
+
keys = KEYS;
|
|
37
290
|
selectedIndex = signal(null, /* @ts-ignore */
|
|
38
291
|
...(ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []));
|
|
39
292
|
selectedType = signal(null, /* @ts-ignore */
|
|
40
293
|
...(ngDevMode ? [{ debugName: "selectedType" }] : /* istanbul ignore next */ []));
|
|
294
|
+
activeProgressionDefs = signal(null, /* @ts-ignore */
|
|
295
|
+
...(ngDevMode ? [{ debugName: "activeProgressionDefs" }] : /* istanbul ignore next */ []));
|
|
296
|
+
expandedCards = signal(new Set(), /* @ts-ignore */
|
|
297
|
+
...(ngDevMode ? [{ debugName: "expandedCards" }] : /* istanbul ignore next */ []));
|
|
41
298
|
get slice() {
|
|
42
299
|
const idx = this.selectedIndex();
|
|
43
300
|
if (idx === null)
|
|
@@ -57,6 +314,29 @@ class CircleOfFifthsComponent {
|
|
|
57
314
|
this.selectedIndex.set(index);
|
|
58
315
|
this.selectedType.set(type);
|
|
59
316
|
}
|
|
317
|
+
this.activeProgressionDefs.set(null);
|
|
318
|
+
this.expandedCards.set(new Set());
|
|
319
|
+
}
|
|
320
|
+
randomizeProgressions() {
|
|
321
|
+
const type = this.selectedType();
|
|
322
|
+
if (!type)
|
|
323
|
+
return;
|
|
324
|
+
// ponytail: sort-shuffle is fine for UI randomness
|
|
325
|
+
this.activeProgressionDefs.set([...PROGRESSIONS[type]].sort(() => Math.random() - 0.5).slice(0, 4));
|
|
326
|
+
this.expandedCards.set(new Set());
|
|
327
|
+
}
|
|
328
|
+
toggleCard(name) {
|
|
329
|
+
this.expandedCards.update((set) => {
|
|
330
|
+
const next = new Set(set);
|
|
331
|
+
next.has(name) ? next.delete(name) : next.add(name);
|
|
332
|
+
return next;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
copyChords(prog) {
|
|
336
|
+
const text = this.expandedCards().has(prog.name)
|
|
337
|
+
? [`${this.text().verse}: ${prog.chords.join(', ')}`, ...prog.sections.map((s) => `${s.label}: ${s.chords.join(', ')}`)].join('; ')
|
|
338
|
+
: prog.chords.join(', ');
|
|
339
|
+
navigator.clipboard.writeText(text);
|
|
60
340
|
}
|
|
61
341
|
getMajorState(index) {
|
|
62
342
|
const s = this.slice;
|
|
@@ -153,10 +433,15 @@ class CircleOfFifthsComponent {
|
|
|
153
433
|
y: this.CY + midR * Math.sin(a),
|
|
154
434
|
};
|
|
155
435
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
436
|
+
get chordTable() {
|
|
437
|
+
const idx = this.selectedIndex();
|
|
438
|
+
const type = this.selectedType();
|
|
439
|
+
if (idx === null || type === null)
|
|
440
|
+
return [];
|
|
441
|
+
return buildChordRows(idx, type, this.text());
|
|
442
|
+
}
|
|
443
|
+
get chordNames() {
|
|
444
|
+
return this.chordTable.map((r) => r.chord);
|
|
160
445
|
}
|
|
161
446
|
getAccidentalText(key) {
|
|
162
447
|
if (key.sharps > 0)
|
|
@@ -170,61 +455,66 @@ class CircleOfFifthsComponent {
|
|
|
170
455
|
const type = this.selectedType();
|
|
171
456
|
if (idx === null || type === null)
|
|
172
457
|
return null;
|
|
173
|
-
const key =
|
|
458
|
+
const key = KEYS[idx];
|
|
459
|
+
const copy = this.text();
|
|
460
|
+
const scale = type === 'major' ? copy.major : copy.minor;
|
|
174
461
|
return {
|
|
175
462
|
name: type === 'major' ? key.major : key.minor,
|
|
176
|
-
scale
|
|
177
|
-
fullName: type === 'major' ?
|
|
463
|
+
scale,
|
|
464
|
+
fullName: `${type === 'major' ? key.major : key.minor} ${scale}`,
|
|
178
465
|
relativeKey: type === 'major'
|
|
179
|
-
?
|
|
180
|
-
:
|
|
466
|
+
? `${copy.relativeMinor}: ${key.minor}`
|
|
467
|
+
: `${copy.relativeMajor}: ${key.major}`,
|
|
181
468
|
};
|
|
182
469
|
}
|
|
183
|
-
|
|
184
|
-
const idx = this.selectedIndex();
|
|
470
|
+
deriveSections(numerals, lookup) {
|
|
185
471
|
const type = this.selectedType();
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
];
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
{ numeral: 'VI', chord: this.keys[prev].major, chordType: 'major', role: 'Submediant', state: 'scale-major' },
|
|
208
|
-
{ numeral: 'VII', chord: this.keys[next].major, chordType: 'major', role: 'Subtonic', state: 'scale-major' },
|
|
209
|
-
{ numeral: 'ii°', chord: this.dimChord(idx), chordType: 'diminished', role: 'Supertonic', state: 'scale-diminished' },
|
|
210
|
-
];
|
|
211
|
-
}
|
|
472
|
+
const copy = this.text();
|
|
473
|
+
const resolve = (ns) => ({ numerals: ns, chords: ns.map((n) => lookup.get(n) ?? n) });
|
|
474
|
+
// Chorus: rotate to lift chord (IV for major, VI for minor); else fixed fallback
|
|
475
|
+
const chorusPivot = type === 'major' ? 'IV' : 'VI';
|
|
476
|
+
const chorusIdx = numerals.indexOf(chorusPivot);
|
|
477
|
+
const chorus = chorusIdx > 0
|
|
478
|
+
? resolve([...numerals.slice(chorusIdx), ...numerals.slice(0, chorusIdx)])
|
|
479
|
+
: resolve(type === 'major' ? ['I', 'IV', 'V', 'I'] : ['i', 'VI', 'III', 'VII']);
|
|
480
|
+
// Bridge: rotate to contrast chord (vi for major, III for minor); else fixed fallback
|
|
481
|
+
const bridgePivot = type === 'major' ? 'vi' : 'III';
|
|
482
|
+
const bridgeIdx = numerals.indexOf(bridgePivot);
|
|
483
|
+
const bridge = bridgeIdx >= 0
|
|
484
|
+
? resolve([...numerals.slice(bridgeIdx), ...numerals.slice(0, bridgeIdx)])
|
|
485
|
+
: resolve(type === 'major' ? ['vi', 'IV', 'ii', 'V'] : ['III', 'VII', 'VI', 'iv']);
|
|
486
|
+
// Outro: fixed resolving pattern per mode
|
|
487
|
+
const outro = resolve(type === 'major' ? ['I', 'V', 'IV', 'I'] : ['i', 'VII', 'VI', 'i']);
|
|
488
|
+
return [
|
|
489
|
+
{ label: copy.chorus, ...chorus },
|
|
490
|
+
{ label: copy.bridge, ...bridge },
|
|
491
|
+
{ label: copy.outro, ...outro },
|
|
492
|
+
];
|
|
212
493
|
}
|
|
213
494
|
get progressions() {
|
|
214
495
|
const table = this.chordTable;
|
|
215
496
|
if (!table.length)
|
|
216
497
|
return [];
|
|
217
|
-
const lookup = new Map(table.map(r => [r.numeral, r.chord]));
|
|
218
|
-
const
|
|
219
|
-
|
|
498
|
+
const lookup = new Map(table.map((r) => [r.numeral, r.chord]));
|
|
499
|
+
const language = this.language();
|
|
500
|
+
const type = this.selectedType() === 'major' ? 'major' : 'minor';
|
|
501
|
+
const defs = this.activeProgressionDefs() ?? PROGRESSIONS[type].slice(0, 4);
|
|
502
|
+
return defs.map((definition) => ({
|
|
503
|
+
name: definition.name[language],
|
|
504
|
+
mood: definition.mood[language],
|
|
505
|
+
genre: definition.genre[language],
|
|
506
|
+
numerals: definition.numerals,
|
|
507
|
+
chords: definition.numerals.map((numeral) => lookup.get(numeral) ?? numeral),
|
|
508
|
+
sections: this.deriveSections(definition.numerals, lookup),
|
|
509
|
+
}));
|
|
220
510
|
}
|
|
221
511
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: CircleOfFifthsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
222
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: CircleOfFifthsComponent, isStandalone: true, selector: "the-chords-circle-of-fifths", ngImport: i0, template: "<div class=\"page\">\n <header class=\"page-header\">\r\n <h1>Circle of Fifths</h1>\r\n <p>Click any major or minor key to highlight its diatonic chords</p>\r\n </header>\r\n\r\n <div class=\"circle-wrapper\">\r\n <svg viewBox=\"0 0 600 600\" xmlns=\"http://www.w3.org/2000/svg\" aria-label=\"Circle of Fifths\">\r\n\r\n <!-- \u2500\u2500 Key Signature Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <path\r\n [attr.d]=\"arcPath(KEY_SIG.inner, KEY_SIG.outer, key.index)\"\r\n class=\"keysig-segment\" />\r\n <text\r\n [attr.x]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).y\"\r\n class=\"keysig-label\">\r\n {{ getAccidentalText(key) }}\r\n </text>\r\n }\r\n\r\n <!-- \u2500\u2500 Major Keys Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <g [ngClass]=\"['major-group', getMajorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'major')\">\r\n <path [attr.d]=\"arcPath(MAJOR.inner, MAJOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y + (getMajorRole(key.index) ? -9 : 0)\"\r\n class=\"major-label\">\r\n {{ key.major }}\r\n </text>\r\n\r\n @if (getMajorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y + 11\"\r\n class=\"role-label\">\r\n {{ getMajorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- \u2500\u2500 Minor Keys Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <g [ngClass]=\"['minor-group', getMinorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'minor')\">\r\n <path [attr.d]=\"arcPath(MINOR.inner, MINOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).y + (getMinorRole(key.index) ? -8 : 0)\"\r\n class=\"minor-label\">\r\n {{ key.minor }}\r\n </text>\r\n\r\n @if (getMinorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).y + 9\"\r\n class=\"role-label\">\r\n {{ getMinorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- \u2500\u2500 Outer border \u2500\u2500 -->\r\n <circle cx=\"300\" cy=\"300\" r=\"289\" fill=\"none\" stroke=\"#1e293b\" stroke-width=\"2.5\" />\r\n\r\n <!-- \u2500\u2500 Center Circle \u2500\u2500 -->\r\n <circle cx=\"300\" cy=\"300\" r=\"99\" class=\"center-circle\" />\r\n\r\n @if (selectedInfo) {\r\n <text x=\"300\" y=\"283\" class=\"center-name\">{{ selectedInfo.name }}</text>\r\n <text x=\"300\" y=\"304\" class=\"center-scale\">{{ selectedInfo.scale }}</text>\r\n <text x=\"300\" y=\"322\" class=\"center-relative\">{{ selectedInfo.relativeKey }}</text>\r\n <text x=\"300\" y=\"338\" class=\"center-deselect\">\u21A9 click to deselect</text>\r\n } @else {\r\n <text x=\"300\" y=\"293\" class=\"center-hint\">Click a key</text>\r\n <text x=\"300\" y=\"312\" class=\"center-hint-sm\">to see its chords</text>\r\n }\r\n\r\n </svg>\r\n </div>\r\n\r\n <!-- \u2500\u2500 Legend \u2500\u2500 -->\r\n <div class=\"legend\">\r\n <span class=\"legend-item tonic\">Tonic (I / i)</span>\r\n <span class=\"legend-item scale-major\">Major chords (IV, V)</span>\r\n <span class=\"legend-item scale-minor\">Minor chords (ii, iii, vi)</span>\r\n <span class=\"legend-item scale-diminished\">Diminished (vii\u00B0 / ii\u00B0)</span>\r\n </div>\r\n\r\n <!-- \u2500\u2500 Chord Table \u2500\u2500 -->\r\n @if (selectedInfo && chordTable.length) {\r\n <section class=\"chord-section\">\r\n <h2>{{ selectedInfo.fullName }} \u2014 Diatonic Chords</h2>\r\n <p class=\"relative-label\">{{ selectedInfo.relativeKey }}</p>\r\n <div class=\"chord-grid\">\r\n @for (row of chordTable; track row.numeral) {\r\n <div class=\"chord-card\" [ngClass]=\"row.state\">\r\n <div class=\"chord-numeral\">{{ row.numeral }}</div>\r\n <div class=\"chord-name\">{{ row.chord }}</div>\r\n <div class=\"chord-type\">{{ row.chordType }}</div>\r\n <div class=\"chord-role\">{{ row.role }}</div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n\r\n <!-- \u2500\u2500 Chord Progressions \u2500\u2500 -->\r\n @if (progressions.length) {\r\n <section class=\"progressions-section\">\r\n <h2>Common Progressions in {{ selectedInfo?.fullName }}</h2>\r\n <div class=\"progression-list\">\r\n @for (prog of progressions; track prog.name) {\r\n <div class=\"progression-card\">\r\n <div class=\"prog-header\">\r\n <span class=\"prog-mood\">{{ prog.mood }}</span>\r\n <span class=\"prog-name\">{{ prog.name }}</span>\r\n <span class=\"prog-genre\">{{ prog.genre }}</span>\r\n </div>\r\n <div class=\"prog-chords\">\r\n @for (chord of prog.chords; track $index; let last = $last) {\r\n <span class=\"prog-chord\">{{ chord }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">\u2192</span> }\r\n }\r\n </div>\r\n <div class=\"prog-numerals\">\r\n @for (num of prog.numerals; track $index; let last = $last) {\r\n <span>{{ num }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">\u2192</span> }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n", styles: [":host{box-sizing:border-box;display:block;min-height:100%;background:#f0f4f8;color:#1e293b}:host *,:host *:before,:host *:after{box-sizing:inherit}.page{max-width:720px;margin:0 auto;padding:24px 16px 48px;font-family:Segoe UI,system-ui,sans-serif}.page-header{text-align:center;margin-bottom:28px}.page-header h1{margin:0 0 6px;font-size:2rem;font-weight:800;color:#1e293b;letter-spacing:-.02em}.page-header p{margin:0;color:#64748b;font-size:.95rem}.circle-wrapper{max-width:580px;margin:0 auto}.circle-wrapper svg{display:block;width:100%;height:auto;overflow:visible}.keysig-segment{fill:#1e293b}.keysig-label{fill:#94a3b8;font-size:12px;font-weight:500;text-anchor:middle;dominant-baseline:central;pointer-events:none}.major-group{cursor:pointer}.major-group path{fill:#dde5f0;stroke:#f8fafc;stroke-width:2;transition:fill .18s ease}.major-group:hover path{fill:#c8d4e8}.major-group .major-label{fill:#334155;font-size:15px;font-weight:700;text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:fill .18s ease}.major-group.tonic path{fill:#f59e0b}.major-group.tonic:hover path{fill:#d97706}.major-group.tonic .major-label{fill:#fff}.major-group.scale-major path{fill:#3b82f6}.major-group.scale-major:hover path{fill:#2563eb}.major-group.scale-major .major-label{fill:#fff}.minor-group{cursor:pointer}.minor-group path{fill:#eaf0f8;stroke:#f8fafc;stroke-width:2;transition:fill .18s ease}.minor-group:hover path{fill:#d5e1ef}.minor-group .minor-label{fill:#475569;font-size:12px;font-weight:600;text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:fill .18s ease}.minor-group.tonic path{fill:#f59e0b}.minor-group.tonic:hover path{fill:#d97706}.minor-group.tonic .minor-label{fill:#fff}.minor-group.scale-minor path{fill:#06b6d4}.minor-group.scale-minor:hover path{fill:#0891b2}.minor-group.scale-minor .minor-label{fill:#fff}.minor-group.scale-diminished path{fill:#a855f7}.minor-group.scale-diminished:hover path{fill:#9333ea}.minor-group.scale-diminished .minor-label{fill:#fff}.role-label{font-size:10px;font-style:italic;font-weight:600;text-anchor:middle;dominant-baseline:central;pointer-events:none;fill:#ffffffd9}.center-circle{fill:#fff;stroke:#e2e8f0;stroke-width:2;filter:drop-shadow(0 2px 10px rgba(0,0,0,.08))}.center-name{font-size:30px;font-weight:800;text-anchor:middle;dominant-baseline:auto;fill:#1e293b;letter-spacing:-.02em}.center-scale{font-size:13px;font-weight:600;text-anchor:middle;dominant-baseline:auto;fill:#64748b;text-transform:uppercase;letter-spacing:.08em}.center-relative{font-size:9.5px;font-weight:500;text-anchor:middle;dominant-baseline:auto;fill:#94a3b8}.center-deselect{font-size:8px;text-anchor:middle;dominant-baseline:auto;fill:#cbd5e1}.center-hint{font-size:16px;font-weight:600;text-anchor:middle;dominant-baseline:auto;fill:#94a3b8}.center-hint-sm{font-size:11px;text-anchor:middle;dominant-baseline:auto;fill:#cbd5e1}.legend{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;margin:16px 0 0}.legend .legend-item{display:flex;align-items:center;gap:6px;font-size:.8rem;color:#64748b}.legend .legend-item:before{content:\"\";display:inline-block;width:14px;height:14px;border-radius:3px}.legend .legend-item.tonic:before{background:#f59e0b}.legend .legend-item.scale-major:before{background:#3b82f6}.legend .legend-item.scale-minor:before{background:#06b6d4}.legend .legend-item.scale-diminished:before{background:#a855f7}.chord-section{margin-top:36px;text-align:center}.chord-section h2{margin:0 0 4px;font-size:1.25rem;font-weight:700;color:#1e293b}.chord-section .relative-label{margin:0 0 20px;font-size:.88rem;color:#64748b}.chord-grid{display:flex;flex-wrap:nowrap;gap:8px;justify-content:center;overflow-x:auto;padding-bottom:4px}.chord-card{border-radius:12px;padding:12px 14px;min-width:82px;flex-shrink:0;text-align:center;background:#e8edf5;border:2px solid transparent;transition:transform .15s ease,box-shadow .15s ease}.chord-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001a}.chord-card .chord-numeral{font-size:.85rem;font-weight:700;font-style:italic;color:#64748b}.chord-card .chord-name{font-size:1.4rem;font-weight:800;color:#1e293b;margin:2px 0;letter-spacing:-.02em}.chord-card .chord-type{font-size:.65rem;text-transform:uppercase;letter-spacing:.06em;color:#94a3b8}.chord-card .chord-role{font-size:.7rem;color:#94a3b8;margin-top:2px}.chord-card.tonic{background:#fef9ec;border-color:#f59e0b}.chord-card.tonic .chord-numeral,.chord-card.tonic .chord-name{color:#92400e}.chord-card.scale-major{background:#eff6ff;border-color:#3b82f6}.chord-card.scale-major .chord-numeral,.chord-card.scale-major .chord-name{color:#1e40af}.chord-card.scale-diminished{background:#fdf4ff;border-color:#a855f7}.chord-card.scale-diminished .chord-numeral,.chord-card.scale-diminished .chord-name{color:#6b21a8}.progressions-section{margin-top:36px;text-align:center}.progressions-section h2{margin:0 0 20px;font-size:1.25rem;font-weight:700;color:#1e293b}.progression-list{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}@media(max-width:480px){.progression-list{grid-template-columns:1fr}}.progression-card{background:#fff;border:1.5px solid #e2e8f0;border-radius:14px;padding:16px 18px;text-align:left;transition:box-shadow .15s ease,transform .15s ease}.progression-card:hover{box-shadow:0 4px 16px #00000014;transform:translateY(-2px)}.prog-header{display:flex;align-items:baseline;gap:8px;margin-bottom:12px;flex-wrap:wrap}.prog-mood{font-size:.95rem;font-weight:700;color:#1e293b}.prog-name{font-size:.75rem;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.06em}.prog-genre{margin-left:auto;font-size:.7rem;color:#94a3b8;font-style:italic}.prog-chords{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:6px}.prog-chord{font-size:1.15rem;font-weight:800;color:#1e293b;letter-spacing:-.02em}.prog-numerals{display:flex;align-items:center;gap:6px;font-size:.75rem;font-style:italic;color:#94a3b8}.prog-arrow{color:#cbd5e1;font-size:.8rem}@media(max-width:480px){.page-header h1{font-size:1.5rem}.chord-card{min-width:75px;padding:10px 12px}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
512
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: CircleOfFifthsComponent, isStandalone: true, selector: "the-chords-circle-of-fifths", inputs: { language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.lang": "language()" } }, ngImport: i0, template: "<div class=\"page\">\r\n <header class=\"page-header\">\r\n <h1>{{ text().title }}</h1>\r\n <p>{{ text().subtitle }}</p>\r\n </header>\r\n\r\n <div class=\"circle-wrapper\">\r\n <svg\r\n viewBox=\"0 0 600 600\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n [attr.aria-label]=\"text().ariaLabel\"\r\n >\r\n <!-- \u2500\u2500 Key Signature Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <path [attr.d]=\"arcPath(KEY_SIG.inner, KEY_SIG.outer, key.index)\" class=\"keysig-segment\" />\r\n <text\r\n [attr.x]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).y\"\r\n class=\"keysig-label\"\r\n >\r\n {{ getAccidentalText(key) }}\r\n </text>\r\n }\r\n\r\n <!-- \u2500\u2500 Major Keys Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <g\r\n [ngClass]=\"['major-group', getMajorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'major')\"\r\n >\r\n <path [attr.d]=\"arcPath(MAJOR.inner, MAJOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"\r\n textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y +\r\n (getMajorRole(key.index) ? -9 : 0)\r\n \"\r\n class=\"major-label\"\r\n >\r\n {{ key.major }}\r\n </text>\r\n\r\n @if (getMajorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y + 11\"\r\n class=\"role-label\"\r\n >\r\n {{ getMajorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- \u2500\u2500 Minor Keys Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <g\r\n [ngClass]=\"['minor-group', getMinorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'minor')\"\r\n >\r\n <path [attr.d]=\"arcPath(MINOR.inner, MINOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"\r\n textPos((MINOR.inner + MINOR.outer) / 2, key.index).y +\r\n (getMinorRole(key.index) ? -8 : 0)\r\n \"\r\n class=\"minor-label\"\r\n >\r\n {{ key.minor }}\r\n </text>\r\n\r\n @if (getMinorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).y + 9\"\r\n class=\"role-label\"\r\n >\r\n {{ getMinorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- \u2500\u2500 Outer border \u2500\u2500 -->\r\n <circle cx=\"300\" cy=\"300\" r=\"289\" class=\"outer-border\" />\r\n\r\n <!-- \u2500\u2500 Center Circle \u2500\u2500 -->\r\n <circle cx=\"300\" cy=\"300\" r=\"99\" class=\"center-circle\" />\r\n\r\n @if (selectedInfo) {\r\n <text x=\"300\" y=\"283\" class=\"center-name\">{{ selectedInfo.name }}</text>\r\n <text x=\"300\" y=\"304\" class=\"center-scale\">{{ selectedInfo.scale }}</text>\r\n <text x=\"300\" y=\"322\" class=\"center-relative\">{{ selectedInfo.relativeKey }}</text>\r\n <text x=\"300\" y=\"338\" class=\"center-deselect\">{{ text().deselect }}</text>\r\n } @else {\r\n <text x=\"300\" y=\"293\" class=\"center-hint\">{{ text().clickKey }}</text>\r\n <text x=\"300\" y=\"312\" class=\"center-hint-sm\">{{ text().seeChords }}</text>\r\n }\r\n </svg>\r\n </div>\r\n\r\n <!-- \u2500\u2500 Legend \u2500\u2500 -->\r\n <div class=\"legend\">\r\n <span class=\"legend-item tonic\">{{ text().tonicLegend }}</span>\r\n <span class=\"legend-item scale-major\">{{ text().majorLegend }}</span>\r\n <span class=\"legend-item scale-minor\">{{ text().minorLegend }}</span>\r\n <span class=\"legend-item scale-diminished\">{{ text().diminishedLegend }}</span>\r\n </div>\r\n\r\n <chord-section [chords]=\"chordNames\" [language]=\"language()\"></chord-section>\r\n\r\n <!-- \u2500\u2500 Chord Progressions \u2500\u2500 -->\r\n @if (progressions.length) {\r\n <section class=\"progressions-section\">\r\n <div class=\"progressions-header\">\r\n <h2>{{ text().commonProgressions }} {{ selectedInfo?.fullName }}</h2>\r\n <button class=\"btn-randomize\" (click)=\"randomizeProgressions()\">{{ text().generateProgressions }}</button>\r\n </div>\r\n <div class=\"progression-list\">\r\n @for (prog of progressions; track prog.name) {\r\n <div class=\"progression-card\">\r\n <div class=\"prog-header\">\r\n <span class=\"prog-mood\">{{ prog.mood }}</span>\r\n <span class=\"prog-name\">{{ prog.name }}</span>\r\n <span class=\"prog-genre\">{{ prog.genre }}</span>\r\n </div>\r\n <div class=\"prog-chords\">\r\n @for (chord of prog.chords; track $index; let last = $last) {\r\n <span class=\"prog-chord\">{{ chord }}</span>\r\n @if (!last) {\r\n <span class=\"prog-arrow\">\u2192</span>\r\n }\r\n }\r\n </div>\r\n <div class=\"prog-numerals\">\r\n @for (num of prog.numerals; track $index; let last = $last) {\r\n <span>{{ num }}</span>\r\n @if (!last) {\r\n <span class=\"prog-arrow\">\u2192</span>\r\n }\r\n }\r\n </div>\r\n <div class=\"prog-expand-row\">\r\n <button class=\"btn-expand\" (click)=\"toggleCard(prog.name)\">\r\n {{ expandedCards().has(prog.name) ? text().collapse : text().expand }}\r\n </button>\r\n <button class=\"btn-expand\" (click)=\"copyChords(prog)\">\r\n {{ text().copy }}\r\n </button>\r\n </div>\r\n @if (expandedCards().has(prog.name)) {\r\n <div class=\"prog-sections\">\r\n @for (section of prog.sections; track section.label) {\r\n <div class=\"prog-section\">\r\n <div class=\"prog-section-label\">{{ section.label }}</div>\r\n <div class=\"prog-chords\">\r\n @for (chord of section.chords; track $index; let last = $last) {\r\n <span class=\"prog-chord\">{{ chord }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">\u2192</span> }\r\n }\r\n </div>\r\n <div class=\"prog-numerals\">\r\n @for (num of section.numerals; track $index; let last = $last) {\r\n <span>{{ num }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">\u2192</span> }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n", styles: [":host{--_chords-background: var(--chords-background, #f0f4f8);--_chords-background-alt: var(--chords-background-alt, #f8fafc);--_chords-surface: var(--chords-surface, #ffffff);--_chords-surface-elevated: var(--chords-surface-elevated, #f8fafc);--_chords-text: var(--chords-text, #1e293b);--_chords-muted: var(--chords-muted, #64748b);--_chords-primary: var(--chords-primary, #3b82f6);--_chords-secondary: var(--chords-secondary, #06b6d4);--_chords-highlight: var(--chords-highlight, #f59e0b);--_chords-danger: var(--chords-danger, #a855f7);--_chords-border: var(--chords-border, #e2e8f0);--_chords-on-primary: var(--chords-on-primary, #ffffff);box-sizing:border-box;display:block;min-height:100%;background:var(--_chords-background);color:var(--_chords-text)}:host *,:host *:before,:host *:after{box-sizing:inherit}.page{max-width:720px;margin:0 auto;padding:24px 16px 48px;font-family:Segoe UI,system-ui,sans-serif}.page-header{text-align:center;margin-bottom:28px}.page-header h1{margin:0 0 6px;font-size:2rem;font-weight:800;color:var(--_chords-text);letter-spacing:-.02em}.page-header p{margin:0;color:var(--_chords-muted);font-size:.95rem}.circle-wrapper{max-width:580px;margin:0 auto}.circle-wrapper svg{display:block;width:100%;height:auto;overflow:visible}.keysig-segment{fill:var(--_chords-text)}.keysig-label{fill:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background));font-size:12px;font-weight:500;text-anchor:middle;dominant-baseline:central;pointer-events:none}.major-group{cursor:pointer}.major-group path{fill:color-mix(in srgb,var(--_chords-primary) 14%,var(--_chords-surface));stroke:var(--_chords-surface);stroke-width:2;transition:fill .18s ease}.major-group:hover path{fill:color-mix(in srgb,var(--_chords-primary) 24%,var(--_chords-surface))}.major-group .major-label{fill:var(--_chords-text);font-size:15px;font-weight:700;text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:fill .18s ease}.major-group.tonic path{fill:var(--_chords-highlight)}.major-group.tonic:hover path{fill:color-mix(in srgb,var(--_chords-highlight) 84%,var(--_chords-text))}.major-group.tonic .major-label{fill:var(--_chords-on-primary)}.major-group.scale-major path{fill:var(--_chords-primary)}.major-group.scale-major:hover path{fill:color-mix(in srgb,var(--_chords-primary) 82%,var(--_chords-text))}.major-group.scale-major .major-label{fill:var(--_chords-on-primary)}.minor-group{cursor:pointer}.minor-group path{fill:color-mix(in srgb,var(--_chords-secondary) 10%,var(--_chords-surface));stroke:var(--_chords-surface);stroke-width:2;transition:fill .18s ease}.minor-group:hover path{fill:color-mix(in srgb,var(--_chords-secondary) 20%,var(--_chords-surface))}.minor-group .minor-label{fill:color-mix(in srgb,var(--_chords-text) 78%,var(--_chords-muted));font-size:12px;font-weight:600;text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:fill .18s ease}.minor-group.tonic path{fill:var(--_chords-highlight)}.minor-group.tonic:hover path{fill:color-mix(in srgb,var(--_chords-highlight) 84%,var(--_chords-text))}.minor-group.tonic .minor-label{fill:var(--_chords-on-primary)}.minor-group.scale-minor path{fill:var(--_chords-secondary)}.minor-group.scale-minor:hover path{fill:color-mix(in srgb,var(--_chords-secondary) 82%,var(--_chords-text))}.minor-group.scale-minor .minor-label{fill:var(--_chords-on-primary)}.minor-group.scale-diminished path{fill:var(--_chords-danger)}.minor-group.scale-diminished:hover path{fill:color-mix(in srgb,var(--_chords-danger) 82%,var(--_chords-text))}.minor-group.scale-diminished .minor-label{fill:var(--_chords-on-primary)}.role-label{font-size:10px;font-style:italic;font-weight:600;text-anchor:middle;dominant-baseline:central;pointer-events:none;fill:color-mix(in srgb,var(--_chords-on-primary) 85%,transparent)}.outer-border{fill:none;stroke:var(--_chords-text);stroke-width:2.5}.center-circle{fill:var(--_chords-surface);stroke:var(--_chords-border);stroke-width:2;filter:drop-shadow(0 2px 10px color-mix(in srgb,var(--_chords-text) 8%,transparent))}.center-name{font-size:30px;font-weight:800;text-anchor:middle;dominant-baseline:auto;fill:var(--_chords-text);letter-spacing:-.02em}.center-scale{font-size:13px;font-weight:600;text-anchor:middle;dominant-baseline:auto;fill:var(--_chords-muted);text-transform:uppercase;letter-spacing:.08em}.center-relative{font-size:9.5px;font-weight:500;text-anchor:middle;dominant-baseline:auto;fill:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background))}.center-deselect{font-size:8px;text-anchor:middle;dominant-baseline:auto;fill:color-mix(in srgb,var(--_chords-muted) 42%,var(--_chords-background))}.center-hint{font-size:16px;font-weight:600;text-anchor:middle;dominant-baseline:auto;fill:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background))}.center-hint-sm{font-size:11px;text-anchor:middle;dominant-baseline:auto;fill:color-mix(in srgb,var(--_chords-muted) 42%,var(--_chords-background))}.legend{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;margin:16px 0 0}.legend .legend-item{display:flex;align-items:center;gap:6px;font-size:.8rem;color:var(--_chords-muted)}.legend .legend-item:before{content:\"\";display:inline-block;width:14px;height:14px;border-radius:3px}.legend .legend-item.tonic:before{background:var(--_chords-highlight)}.legend .legend-item.scale-major:before{background:var(--_chords-primary)}.legend .legend-item.scale-minor:before{background:var(--_chords-secondary)}.legend .legend-item.scale-diminished:before{background:var(--_chords-danger)}.progressions-section{margin-top:36px;text-align:center}.progressions-header{display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:20px;flex-wrap:wrap}.progressions-header h2{margin:0;font-size:1.25rem;font-weight:700;color:var(--_chords-text)}.btn-randomize{padding:6px 14px;font-size:.8rem;font-weight:600;border:1.5px solid var(--_chords-primary);border-radius:20px;background:transparent;color:var(--_chords-primary);cursor:pointer;transition:background .15s,color .15s;white-space:nowrap}.btn-randomize:hover{background:var(--_chords-primary);color:var(--_chords-on-primary)}.progression-list{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}@media(max-width:480px){.progression-list{grid-template-columns:1fr}}.progression-card{background:var(--_chords-surface);border:1.5px solid var(--_chords-border);border-radius:14px;padding:16px 18px;text-align:left;transition:box-shadow .15s ease,transform .15s ease}.progression-card:hover{box-shadow:0 4px 16px color-mix(in srgb,var(--_chords-text) 8%,transparent);transform:translateY(-2px)}.prog-header{display:flex;align-items:baseline;gap:8px;margin-bottom:12px;flex-wrap:wrap}.prog-mood{font-size:.95rem;font-weight:700;color:var(--_chords-text)}.prog-name{font-size:.75rem;font-weight:600;color:var(--_chords-muted);text-transform:uppercase;letter-spacing:.06em}.prog-genre{margin-left:auto;font-size:.7rem;color:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background));font-style:italic}.prog-chords{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:6px}.prog-chord{font-size:1.15rem;font-weight:800;color:var(--_chords-text);letter-spacing:-.02em}.prog-numerals{display:flex;align-items:center;gap:6px;font-size:.75rem;font-style:italic;color:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background))}.prog-expand-row{display:flex;justify-content:flex-end;margin-top:10px}.btn-expand{padding:3px 10px;font-size:.72rem;font-weight:600;border:1px solid var(--_chords-border);border-radius:12px;background:transparent;color:var(--_chords-muted);cursor:pointer;transition:border-color .15s,color .15s}.btn-expand:hover{border-color:var(--_chords-primary);color:var(--_chords-primary)}.prog-sections{margin-top:12px;border-top:1px solid var(--_chords-border);padding-top:10px;display:flex;flex-direction:column;gap:10px}.prog-section{padding:8px 10px;border-radius:8px;background:color-mix(in srgb,var(--_chords-text) 4%,var(--_chords-surface))}.prog-section-label{font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--_chords-muted);margin-bottom:5px}.prog-arrow{color:color-mix(in srgb,var(--_chords-muted) 42%,var(--_chords-background));font-size:.8rem}@media(max-width:480px){.page-header h1{font-size:1.5rem}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ChordSectionComponent, selector: "chord-section", inputs: ["chords", "language"] }] });
|
|
223
513
|
}
|
|
224
514
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: CircleOfFifthsComponent, decorators: [{
|
|
225
515
|
type: Component,
|
|
226
|
-
args: [{ selector: 'the-chords-circle-of-fifths', standalone: true, imports: [NgClass], template: "<div class=\"page\">\n <header class=\"page-header\">\r\n <h1>Circle of Fifths</h1>\r\n <p>Click any major or minor key to highlight its diatonic chords</p>\r\n </header>\r\n\r\n <div class=\"circle-wrapper\">\r\n <svg viewBox=\"0 0 600 600\" xmlns=\"http://www.w3.org/2000/svg\" aria-label=\"Circle of Fifths\">\r\n\r\n <!-- \u2500\u2500 Key Signature Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <path\r\n [attr.d]=\"arcPath(KEY_SIG.inner, KEY_SIG.outer, key.index)\"\r\n class=\"keysig-segment\" />\r\n <text\r\n [attr.x]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).y\"\r\n class=\"keysig-label\">\r\n {{ getAccidentalText(key) }}\r\n </text>\r\n }\r\n\r\n <!-- \u2500\u2500 Major Keys Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <g [ngClass]=\"['major-group', getMajorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'major')\">\r\n <path [attr.d]=\"arcPath(MAJOR.inner, MAJOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y + (getMajorRole(key.index) ? -9 : 0)\"\r\n class=\"major-label\">\r\n {{ key.major }}\r\n </text>\r\n\r\n @if (getMajorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y + 11\"\r\n class=\"role-label\">\r\n {{ getMajorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- \u2500\u2500 Minor Keys Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <g [ngClass]=\"['minor-group', getMinorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'minor')\">\r\n <path [attr.d]=\"arcPath(MINOR.inner, MINOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).y + (getMinorRole(key.index) ? -8 : 0)\"\r\n class=\"minor-label\">\r\n {{ key.minor }}\r\n </text>\r\n\r\n @if (getMinorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).y + 9\"\r\n class=\"role-label\">\r\n {{ getMinorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- \u2500\u2500 Outer border \u2500\u2500 -->\r\n <circle cx=\"300\" cy=\"300\" r=\"289\" fill=\"none\" stroke=\"#1e293b\" stroke-width=\"2.5\" />\r\n\r\n <!-- \u2500\u2500 Center Circle \u2500\u2500 -->\r\n <circle cx=\"300\" cy=\"300\" r=\"99\" class=\"center-circle\" />\r\n\r\n @if (selectedInfo) {\r\n <text x=\"300\" y=\"283\" class=\"center-name\">{{ selectedInfo.name }}</text>\r\n <text x=\"300\" y=\"304\" class=\"center-scale\">{{ selectedInfo.scale }}</text>\r\n <text x=\"300\" y=\"322\" class=\"center-relative\">{{ selectedInfo.relativeKey }}</text>\r\n <text x=\"300\" y=\"338\" class=\"center-deselect\">\u21A9 click to deselect</text>\r\n } @else {\r\n <text x=\"300\" y=\"293\" class=\"center-hint\">Click a key</text>\r\n <text x=\"300\" y=\"312\" class=\"center-hint-sm\">to see its chords</text>\r\n }\r\n\r\n </svg>\r\n </div>\r\n\r\n <!-- \u2500\u2500 Legend \u2500\u2500 -->\r\n <div class=\"legend\">\r\n <span class=\"legend-item tonic\">Tonic (I / i)</span>\r\n <span class=\"legend-item scale-major\">Major chords (IV, V)</span>\r\n <span class=\"legend-item scale-minor\">Minor chords (ii, iii, vi)</span>\r\n <span class=\"legend-item scale-diminished\">Diminished (vii\u00B0 / ii\u00B0)</span>\r\n </div>\r\n\r\n <!-- \u2500\u2500 Chord Table \u2500\u2500 -->\r\n @if (selectedInfo && chordTable.length) {\r\n <section class=\"chord-section\">\r\n <h2>{{ selectedInfo.fullName }} \u2014 Diatonic Chords</h2>\r\n <p class=\"relative-label\">{{ selectedInfo.relativeKey }}</p>\r\n <div class=\"chord-grid\">\r\n @for (row of chordTable; track row.numeral) {\r\n <div class=\"chord-card\" [ngClass]=\"row.state\">\r\n <div class=\"chord-numeral\">{{ row.numeral }}</div>\r\n <div class=\"chord-name\">{{ row.chord }}</div>\r\n <div class=\"chord-type\">{{ row.chordType }}</div>\r\n <div class=\"chord-role\">{{ row.role }}</div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n\r\n <!-- \u2500\u2500 Chord Progressions \u2500\u2500 -->\r\n @if (progressions.length) {\r\n <section class=\"progressions-section\">\r\n <h2>Common Progressions in {{ selectedInfo?.fullName }}</h2>\r\n <div class=\"progression-list\">\r\n @for (prog of progressions; track prog.name) {\r\n <div class=\"progression-card\">\r\n <div class=\"prog-header\">\r\n <span class=\"prog-mood\">{{ prog.mood }}</span>\r\n <span class=\"prog-name\">{{ prog.name }}</span>\r\n <span class=\"prog-genre\">{{ prog.genre }}</span>\r\n </div>\r\n <div class=\"prog-chords\">\r\n @for (chord of prog.chords; track $index; let last = $last) {\r\n <span class=\"prog-chord\">{{ chord }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">\u2192</span> }\r\n }\r\n </div>\r\n <div class=\"prog-numerals\">\r\n @for (num of prog.numerals; track $index; let last = $last) {\r\n <span>{{ num }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">\u2192</span> }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n", styles: [":host{box-sizing:border-box;display:block;min-height:100%;background:#f0f4f8;color:#1e293b}:host *,:host *:before,:host *:after{box-sizing:inherit}.page{max-width:720px;margin:0 auto;padding:24px 16px 48px;font-family:Segoe UI,system-ui,sans-serif}.page-header{text-align:center;margin-bottom:28px}.page-header h1{margin:0 0 6px;font-size:2rem;font-weight:800;color:#1e293b;letter-spacing:-.02em}.page-header p{margin:0;color:#64748b;font-size:.95rem}.circle-wrapper{max-width:580px;margin:0 auto}.circle-wrapper svg{display:block;width:100%;height:auto;overflow:visible}.keysig-segment{fill:#1e293b}.keysig-label{fill:#94a3b8;font-size:12px;font-weight:500;text-anchor:middle;dominant-baseline:central;pointer-events:none}.major-group{cursor:pointer}.major-group path{fill:#dde5f0;stroke:#f8fafc;stroke-width:2;transition:fill .18s ease}.major-group:hover path{fill:#c8d4e8}.major-group .major-label{fill:#334155;font-size:15px;font-weight:700;text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:fill .18s ease}.major-group.tonic path{fill:#f59e0b}.major-group.tonic:hover path{fill:#d97706}.major-group.tonic .major-label{fill:#fff}.major-group.scale-major path{fill:#3b82f6}.major-group.scale-major:hover path{fill:#2563eb}.major-group.scale-major .major-label{fill:#fff}.minor-group{cursor:pointer}.minor-group path{fill:#eaf0f8;stroke:#f8fafc;stroke-width:2;transition:fill .18s ease}.minor-group:hover path{fill:#d5e1ef}.minor-group .minor-label{fill:#475569;font-size:12px;font-weight:600;text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:fill .18s ease}.minor-group.tonic path{fill:#f59e0b}.minor-group.tonic:hover path{fill:#d97706}.minor-group.tonic .minor-label{fill:#fff}.minor-group.scale-minor path{fill:#06b6d4}.minor-group.scale-minor:hover path{fill:#0891b2}.minor-group.scale-minor .minor-label{fill:#fff}.minor-group.scale-diminished path{fill:#a855f7}.minor-group.scale-diminished:hover path{fill:#9333ea}.minor-group.scale-diminished .minor-label{fill:#fff}.role-label{font-size:10px;font-style:italic;font-weight:600;text-anchor:middle;dominant-baseline:central;pointer-events:none;fill:#ffffffd9}.center-circle{fill:#fff;stroke:#e2e8f0;stroke-width:2;filter:drop-shadow(0 2px 10px rgba(0,0,0,.08))}.center-name{font-size:30px;font-weight:800;text-anchor:middle;dominant-baseline:auto;fill:#1e293b;letter-spacing:-.02em}.center-scale{font-size:13px;font-weight:600;text-anchor:middle;dominant-baseline:auto;fill:#64748b;text-transform:uppercase;letter-spacing:.08em}.center-relative{font-size:9.5px;font-weight:500;text-anchor:middle;dominant-baseline:auto;fill:#94a3b8}.center-deselect{font-size:8px;text-anchor:middle;dominant-baseline:auto;fill:#cbd5e1}.center-hint{font-size:16px;font-weight:600;text-anchor:middle;dominant-baseline:auto;fill:#94a3b8}.center-hint-sm{font-size:11px;text-anchor:middle;dominant-baseline:auto;fill:#cbd5e1}.legend{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;margin:16px 0 0}.legend .legend-item{display:flex;align-items:center;gap:6px;font-size:.8rem;color:#64748b}.legend .legend-item:before{content:\"\";display:inline-block;width:14px;height:14px;border-radius:3px}.legend .legend-item.tonic:before{background:#f59e0b}.legend .legend-item.scale-major:before{background:#3b82f6}.legend .legend-item.scale-minor:before{background:#06b6d4}.legend .legend-item.scale-diminished:before{background:#a855f7}.chord-section{margin-top:36px;text-align:center}.chord-section h2{margin:0 0 4px;font-size:1.25rem;font-weight:700;color:#1e293b}.chord-section .relative-label{margin:0 0 20px;font-size:.88rem;color:#64748b}.chord-grid{display:flex;flex-wrap:nowrap;gap:8px;justify-content:center;overflow-x:auto;padding-bottom:4px}.chord-card{border-radius:12px;padding:12px 14px;min-width:82px;flex-shrink:0;text-align:center;background:#e8edf5;border:2px solid transparent;transition:transform .15s ease,box-shadow .15s ease}.chord-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001a}.chord-card .chord-numeral{font-size:.85rem;font-weight:700;font-style:italic;color:#64748b}.chord-card .chord-name{font-size:1.4rem;font-weight:800;color:#1e293b;margin:2px 0;letter-spacing:-.02em}.chord-card .chord-type{font-size:.65rem;text-transform:uppercase;letter-spacing:.06em;color:#94a3b8}.chord-card .chord-role{font-size:.7rem;color:#94a3b8;margin-top:2px}.chord-card.tonic{background:#fef9ec;border-color:#f59e0b}.chord-card.tonic .chord-numeral,.chord-card.tonic .chord-name{color:#92400e}.chord-card.scale-major{background:#eff6ff;border-color:#3b82f6}.chord-card.scale-major .chord-numeral,.chord-card.scale-major .chord-name{color:#1e40af}.chord-card.scale-diminished{background:#fdf4ff;border-color:#a855f7}.chord-card.scale-diminished .chord-numeral,.chord-card.scale-diminished .chord-name{color:#6b21a8}.progressions-section{margin-top:36px;text-align:center}.progressions-section h2{margin:0 0 20px;font-size:1.25rem;font-weight:700;color:#1e293b}.progression-list{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}@media(max-width:480px){.progression-list{grid-template-columns:1fr}}.progression-card{background:#fff;border:1.5px solid #e2e8f0;border-radius:14px;padding:16px 18px;text-align:left;transition:box-shadow .15s ease,transform .15s ease}.progression-card:hover{box-shadow:0 4px 16px #00000014;transform:translateY(-2px)}.prog-header{display:flex;align-items:baseline;gap:8px;margin-bottom:12px;flex-wrap:wrap}.prog-mood{font-size:.95rem;font-weight:700;color:#1e293b}.prog-name{font-size:.75rem;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.06em}.prog-genre{margin-left:auto;font-size:.7rem;color:#94a3b8;font-style:italic}.prog-chords{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:6px}.prog-chord{font-size:1.15rem;font-weight:800;color:#1e293b;letter-spacing:-.02em}.prog-numerals{display:flex;align-items:center;gap:6px;font-size:.75rem;font-style:italic;color:#94a3b8}.prog-arrow{color:#cbd5e1;font-size:.8rem}@media(max-width:480px){.page-header h1{font-size:1.5rem}.chord-card{min-width:75px;padding:10px 12px}}\n"] }]
|
|
227
|
-
}] });
|
|
516
|
+
args: [{ selector: 'the-chords-circle-of-fifths', standalone: true, imports: [NgClass, ChordSectionComponent], host: { '[attr.lang]': 'language()' }, template: "<div class=\"page\">\r\n <header class=\"page-header\">\r\n <h1>{{ text().title }}</h1>\r\n <p>{{ text().subtitle }}</p>\r\n </header>\r\n\r\n <div class=\"circle-wrapper\">\r\n <svg\r\n viewBox=\"0 0 600 600\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n [attr.aria-label]=\"text().ariaLabel\"\r\n >\r\n <!-- \u2500\u2500 Key Signature Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <path [attr.d]=\"arcPath(KEY_SIG.inner, KEY_SIG.outer, key.index)\" class=\"keysig-segment\" />\r\n <text\r\n [attr.x]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).y\"\r\n class=\"keysig-label\"\r\n >\r\n {{ getAccidentalText(key) }}\r\n </text>\r\n }\r\n\r\n <!-- \u2500\u2500 Major Keys Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <g\r\n [ngClass]=\"['major-group', getMajorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'major')\"\r\n >\r\n <path [attr.d]=\"arcPath(MAJOR.inner, MAJOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"\r\n textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y +\r\n (getMajorRole(key.index) ? -9 : 0)\r\n \"\r\n class=\"major-label\"\r\n >\r\n {{ key.major }}\r\n </text>\r\n\r\n @if (getMajorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y + 11\"\r\n class=\"role-label\"\r\n >\r\n {{ getMajorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- \u2500\u2500 Minor Keys Ring \u2500\u2500 -->\r\n @for (key of keys; track key.index) {\r\n <g\r\n [ngClass]=\"['minor-group', getMinorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'minor')\"\r\n >\r\n <path [attr.d]=\"arcPath(MINOR.inner, MINOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"\r\n textPos((MINOR.inner + MINOR.outer) / 2, key.index).y +\r\n (getMinorRole(key.index) ? -8 : 0)\r\n \"\r\n class=\"minor-label\"\r\n >\r\n {{ key.minor }}\r\n </text>\r\n\r\n @if (getMinorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).y + 9\"\r\n class=\"role-label\"\r\n >\r\n {{ getMinorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- \u2500\u2500 Outer border \u2500\u2500 -->\r\n <circle cx=\"300\" cy=\"300\" r=\"289\" class=\"outer-border\" />\r\n\r\n <!-- \u2500\u2500 Center Circle \u2500\u2500 -->\r\n <circle cx=\"300\" cy=\"300\" r=\"99\" class=\"center-circle\" />\r\n\r\n @if (selectedInfo) {\r\n <text x=\"300\" y=\"283\" class=\"center-name\">{{ selectedInfo.name }}</text>\r\n <text x=\"300\" y=\"304\" class=\"center-scale\">{{ selectedInfo.scale }}</text>\r\n <text x=\"300\" y=\"322\" class=\"center-relative\">{{ selectedInfo.relativeKey }}</text>\r\n <text x=\"300\" y=\"338\" class=\"center-deselect\">{{ text().deselect }}</text>\r\n } @else {\r\n <text x=\"300\" y=\"293\" class=\"center-hint\">{{ text().clickKey }}</text>\r\n <text x=\"300\" y=\"312\" class=\"center-hint-sm\">{{ text().seeChords }}</text>\r\n }\r\n </svg>\r\n </div>\r\n\r\n <!-- \u2500\u2500 Legend \u2500\u2500 -->\r\n <div class=\"legend\">\r\n <span class=\"legend-item tonic\">{{ text().tonicLegend }}</span>\r\n <span class=\"legend-item scale-major\">{{ text().majorLegend }}</span>\r\n <span class=\"legend-item scale-minor\">{{ text().minorLegend }}</span>\r\n <span class=\"legend-item scale-diminished\">{{ text().diminishedLegend }}</span>\r\n </div>\r\n\r\n <chord-section [chords]=\"chordNames\" [language]=\"language()\"></chord-section>\r\n\r\n <!-- \u2500\u2500 Chord Progressions \u2500\u2500 -->\r\n @if (progressions.length) {\r\n <section class=\"progressions-section\">\r\n <div class=\"progressions-header\">\r\n <h2>{{ text().commonProgressions }} {{ selectedInfo?.fullName }}</h2>\r\n <button class=\"btn-randomize\" (click)=\"randomizeProgressions()\">{{ text().generateProgressions }}</button>\r\n </div>\r\n <div class=\"progression-list\">\r\n @for (prog of progressions; track prog.name) {\r\n <div class=\"progression-card\">\r\n <div class=\"prog-header\">\r\n <span class=\"prog-mood\">{{ prog.mood }}</span>\r\n <span class=\"prog-name\">{{ prog.name }}</span>\r\n <span class=\"prog-genre\">{{ prog.genre }}</span>\r\n </div>\r\n <div class=\"prog-chords\">\r\n @for (chord of prog.chords; track $index; let last = $last) {\r\n <span class=\"prog-chord\">{{ chord }}</span>\r\n @if (!last) {\r\n <span class=\"prog-arrow\">\u2192</span>\r\n }\r\n }\r\n </div>\r\n <div class=\"prog-numerals\">\r\n @for (num of prog.numerals; track $index; let last = $last) {\r\n <span>{{ num }}</span>\r\n @if (!last) {\r\n <span class=\"prog-arrow\">\u2192</span>\r\n }\r\n }\r\n </div>\r\n <div class=\"prog-expand-row\">\r\n <button class=\"btn-expand\" (click)=\"toggleCard(prog.name)\">\r\n {{ expandedCards().has(prog.name) ? text().collapse : text().expand }}\r\n </button>\r\n <button class=\"btn-expand\" (click)=\"copyChords(prog)\">\r\n {{ text().copy }}\r\n </button>\r\n </div>\r\n @if (expandedCards().has(prog.name)) {\r\n <div class=\"prog-sections\">\r\n @for (section of prog.sections; track section.label) {\r\n <div class=\"prog-section\">\r\n <div class=\"prog-section-label\">{{ section.label }}</div>\r\n <div class=\"prog-chords\">\r\n @for (chord of section.chords; track $index; let last = $last) {\r\n <span class=\"prog-chord\">{{ chord }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">\u2192</span> }\r\n }\r\n </div>\r\n <div class=\"prog-numerals\">\r\n @for (num of section.numerals; track $index; let last = $last) {\r\n <span>{{ num }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">\u2192</span> }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n", styles: [":host{--_chords-background: var(--chords-background, #f0f4f8);--_chords-background-alt: var(--chords-background-alt, #f8fafc);--_chords-surface: var(--chords-surface, #ffffff);--_chords-surface-elevated: var(--chords-surface-elevated, #f8fafc);--_chords-text: var(--chords-text, #1e293b);--_chords-muted: var(--chords-muted, #64748b);--_chords-primary: var(--chords-primary, #3b82f6);--_chords-secondary: var(--chords-secondary, #06b6d4);--_chords-highlight: var(--chords-highlight, #f59e0b);--_chords-danger: var(--chords-danger, #a855f7);--_chords-border: var(--chords-border, #e2e8f0);--_chords-on-primary: var(--chords-on-primary, #ffffff);box-sizing:border-box;display:block;min-height:100%;background:var(--_chords-background);color:var(--_chords-text)}:host *,:host *:before,:host *:after{box-sizing:inherit}.page{max-width:720px;margin:0 auto;padding:24px 16px 48px;font-family:Segoe UI,system-ui,sans-serif}.page-header{text-align:center;margin-bottom:28px}.page-header h1{margin:0 0 6px;font-size:2rem;font-weight:800;color:var(--_chords-text);letter-spacing:-.02em}.page-header p{margin:0;color:var(--_chords-muted);font-size:.95rem}.circle-wrapper{max-width:580px;margin:0 auto}.circle-wrapper svg{display:block;width:100%;height:auto;overflow:visible}.keysig-segment{fill:var(--_chords-text)}.keysig-label{fill:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background));font-size:12px;font-weight:500;text-anchor:middle;dominant-baseline:central;pointer-events:none}.major-group{cursor:pointer}.major-group path{fill:color-mix(in srgb,var(--_chords-primary) 14%,var(--_chords-surface));stroke:var(--_chords-surface);stroke-width:2;transition:fill .18s ease}.major-group:hover path{fill:color-mix(in srgb,var(--_chords-primary) 24%,var(--_chords-surface))}.major-group .major-label{fill:var(--_chords-text);font-size:15px;font-weight:700;text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:fill .18s ease}.major-group.tonic path{fill:var(--_chords-highlight)}.major-group.tonic:hover path{fill:color-mix(in srgb,var(--_chords-highlight) 84%,var(--_chords-text))}.major-group.tonic .major-label{fill:var(--_chords-on-primary)}.major-group.scale-major path{fill:var(--_chords-primary)}.major-group.scale-major:hover path{fill:color-mix(in srgb,var(--_chords-primary) 82%,var(--_chords-text))}.major-group.scale-major .major-label{fill:var(--_chords-on-primary)}.minor-group{cursor:pointer}.minor-group path{fill:color-mix(in srgb,var(--_chords-secondary) 10%,var(--_chords-surface));stroke:var(--_chords-surface);stroke-width:2;transition:fill .18s ease}.minor-group:hover path{fill:color-mix(in srgb,var(--_chords-secondary) 20%,var(--_chords-surface))}.minor-group .minor-label{fill:color-mix(in srgb,var(--_chords-text) 78%,var(--_chords-muted));font-size:12px;font-weight:600;text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:fill .18s ease}.minor-group.tonic path{fill:var(--_chords-highlight)}.minor-group.tonic:hover path{fill:color-mix(in srgb,var(--_chords-highlight) 84%,var(--_chords-text))}.minor-group.tonic .minor-label{fill:var(--_chords-on-primary)}.minor-group.scale-minor path{fill:var(--_chords-secondary)}.minor-group.scale-minor:hover path{fill:color-mix(in srgb,var(--_chords-secondary) 82%,var(--_chords-text))}.minor-group.scale-minor .minor-label{fill:var(--_chords-on-primary)}.minor-group.scale-diminished path{fill:var(--_chords-danger)}.minor-group.scale-diminished:hover path{fill:color-mix(in srgb,var(--_chords-danger) 82%,var(--_chords-text))}.minor-group.scale-diminished .minor-label{fill:var(--_chords-on-primary)}.role-label{font-size:10px;font-style:italic;font-weight:600;text-anchor:middle;dominant-baseline:central;pointer-events:none;fill:color-mix(in srgb,var(--_chords-on-primary) 85%,transparent)}.outer-border{fill:none;stroke:var(--_chords-text);stroke-width:2.5}.center-circle{fill:var(--_chords-surface);stroke:var(--_chords-border);stroke-width:2;filter:drop-shadow(0 2px 10px color-mix(in srgb,var(--_chords-text) 8%,transparent))}.center-name{font-size:30px;font-weight:800;text-anchor:middle;dominant-baseline:auto;fill:var(--_chords-text);letter-spacing:-.02em}.center-scale{font-size:13px;font-weight:600;text-anchor:middle;dominant-baseline:auto;fill:var(--_chords-muted);text-transform:uppercase;letter-spacing:.08em}.center-relative{font-size:9.5px;font-weight:500;text-anchor:middle;dominant-baseline:auto;fill:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background))}.center-deselect{font-size:8px;text-anchor:middle;dominant-baseline:auto;fill:color-mix(in srgb,var(--_chords-muted) 42%,var(--_chords-background))}.center-hint{font-size:16px;font-weight:600;text-anchor:middle;dominant-baseline:auto;fill:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background))}.center-hint-sm{font-size:11px;text-anchor:middle;dominant-baseline:auto;fill:color-mix(in srgb,var(--_chords-muted) 42%,var(--_chords-background))}.legend{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;margin:16px 0 0}.legend .legend-item{display:flex;align-items:center;gap:6px;font-size:.8rem;color:var(--_chords-muted)}.legend .legend-item:before{content:\"\";display:inline-block;width:14px;height:14px;border-radius:3px}.legend .legend-item.tonic:before{background:var(--_chords-highlight)}.legend .legend-item.scale-major:before{background:var(--_chords-primary)}.legend .legend-item.scale-minor:before{background:var(--_chords-secondary)}.legend .legend-item.scale-diminished:before{background:var(--_chords-danger)}.progressions-section{margin-top:36px;text-align:center}.progressions-header{display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:20px;flex-wrap:wrap}.progressions-header h2{margin:0;font-size:1.25rem;font-weight:700;color:var(--_chords-text)}.btn-randomize{padding:6px 14px;font-size:.8rem;font-weight:600;border:1.5px solid var(--_chords-primary);border-radius:20px;background:transparent;color:var(--_chords-primary);cursor:pointer;transition:background .15s,color .15s;white-space:nowrap}.btn-randomize:hover{background:var(--_chords-primary);color:var(--_chords-on-primary)}.progression-list{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}@media(max-width:480px){.progression-list{grid-template-columns:1fr}}.progression-card{background:var(--_chords-surface);border:1.5px solid var(--_chords-border);border-radius:14px;padding:16px 18px;text-align:left;transition:box-shadow .15s ease,transform .15s ease}.progression-card:hover{box-shadow:0 4px 16px color-mix(in srgb,var(--_chords-text) 8%,transparent);transform:translateY(-2px)}.prog-header{display:flex;align-items:baseline;gap:8px;margin-bottom:12px;flex-wrap:wrap}.prog-mood{font-size:.95rem;font-weight:700;color:var(--_chords-text)}.prog-name{font-size:.75rem;font-weight:600;color:var(--_chords-muted);text-transform:uppercase;letter-spacing:.06em}.prog-genre{margin-left:auto;font-size:.7rem;color:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background));font-style:italic}.prog-chords{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:6px}.prog-chord{font-size:1.15rem;font-weight:800;color:var(--_chords-text);letter-spacing:-.02em}.prog-numerals{display:flex;align-items:center;gap:6px;font-size:.75rem;font-style:italic;color:color-mix(in srgb,var(--_chords-muted) 72%,var(--_chords-background))}.prog-expand-row{display:flex;justify-content:flex-end;margin-top:10px}.btn-expand{padding:3px 10px;font-size:.72rem;font-weight:600;border:1px solid var(--_chords-border);border-radius:12px;background:transparent;color:var(--_chords-muted);cursor:pointer;transition:border-color .15s,color .15s}.btn-expand:hover{border-color:var(--_chords-primary);color:var(--_chords-primary)}.prog-sections{margin-top:12px;border-top:1px solid var(--_chords-border);padding-top:10px;display:flex;flex-direction:column;gap:10px}.prog-section{padding:8px 10px;border-radius:8px;background:color-mix(in srgb,var(--_chords-text) 4%,var(--_chords-surface))}.prog-section-label{font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--_chords-muted);margin-bottom:5px}.prog-arrow{color:color-mix(in srgb,var(--_chords-muted) 42%,var(--_chords-background));font-size:.8rem}@media(max-width:480px){.page-header h1{font-size:1.5rem}}\n"] }]
|
|
517
|
+
}], propDecorators: { language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }] } });
|
|
228
518
|
|
|
229
519
|
/*
|
|
230
520
|
* Public API Surface of circle-of-fifths-lib
|
|
@@ -234,5 +524,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
234
524
|
* Generated bundle index. Do not edit.
|
|
235
525
|
*/
|
|
236
526
|
|
|
237
|
-
export { CircleOfFifthsComponent };
|
|
527
|
+
export { ChordSectionComponent, CircleOfFifthsComponent };
|
|
238
528
|
//# sourceMappingURL=gblp-circle-of-fifths.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gblp-circle-of-fifths.mjs","sources":["../../../projects/circle-of-fifths-lib/src/lib/circle-of-fifths.ts","../../../projects/circle-of-fifths-lib/src/lib/circle-of-fifths.html","../../../projects/circle-of-fifths-lib/src/public-api.ts","../../../projects/circle-of-fifths-lib/src/gblp-circle-of-fifths.ts"],"sourcesContent":["import { Component, signal } from '@angular/core';\r\nimport { NgClass } from '@angular/common';\r\n\r\ninterface CircleKey {\r\n index: number;\r\n major: string;\r\n minor: string;\r\n sharps: number;\r\n flats: number;\r\n}\r\n\r\ninterface ChordRow {\r\n numeral: string;\r\n chord: string;\r\n chordType: 'major' | 'minor' | 'diminished';\r\n role: string;\r\n state: 'tonic' | 'scale-major' | 'scale-minor' | 'scale-diminished';\r\n}\r\n\r\ninterface Progression {\r\n name: string;\r\n mood: string;\r\n genre: string;\r\n numerals: string[];\r\n chords: string[];\r\n}\r\n\r\nconst MAJOR_PROGRESSIONS: Omit<Progression, 'chords'>[] = [\r\n { name: 'Anthemic', mood: '😄 Joyful', genre: 'Folk, Rock', numerals: ['I','IV','V','I'] },\r\n { name: 'Hopeful', mood: '✨ Hopeful', genre: 'Pop, Indie', numerals: ['I','V','vi','IV'] },\r\n { name: 'Melancholic', mood: '😢 Emotional', genre: 'Ballads, Pop', numerals: ['vi','IV','I','V'] },\r\n { name: 'Cinematic', mood: '🎬 Tense', genre: 'Film, Jazz', numerals: ['ii','V','vii°','I'] },\r\n];\r\n\r\nconst MINOR_PROGRESSIONS: Omit<Progression, 'chords'>[] = [\r\n { name: 'Brooding', mood: '🌑 Dark', genre: 'Rock, Metal', numerals: ['i','VII','VI','VII'] },\r\n { name: 'Haunting', mood: '👻 Mysterious', genre: 'Film, Gothic', numerals: ['i','iv','VII','III'] },\r\n { name: 'Driving', mood: '⚡ Urgent', genre: 'Pop, EDM', numerals: ['i','VI','III','VII'] },\r\n { name: 'Wistful', mood: '🌙 Wistful', genre: 'Cinematic', numerals: ['i','v','VI','VII'] },\r\n];\r\n\r\n@Component({\r\n selector: 'the-chords-circle-of-fifths',\n standalone: true,\r\n imports: [NgClass],\r\n templateUrl: './circle-of-fifths.html',\n styleUrl: './circle-of-fifths.scss',\n})\r\nexport class CircleOfFifthsComponent {\r\n readonly CX = 300;\r\n readonly CY = 300;\r\n\r\n readonly KEY_SIG = { inner: 250, outer: 288 };\r\n readonly MAJOR = { inner: 172, outer: 250 };\r\n readonly MINOR = { inner: 102, outer: 172 };\r\n\r\n readonly keys: CircleKey[] = [\r\n { index: 0, major: 'C', minor: 'Am', sharps: 0, flats: 0 },\r\n { index: 1, major: 'G', minor: 'Em', sharps: 1, flats: 0 },\r\n { index: 2, major: 'D', minor: 'Bm', sharps: 2, flats: 0 },\r\n { index: 3, major: 'A', minor: 'F♯m', sharps: 3, flats: 0 },\r\n { index: 4, major: 'E', minor: 'C♯m', sharps: 4, flats: 0 },\r\n { index: 5, major: 'B', minor: 'G♯m', sharps: 5, flats: 0 },\r\n { index: 6, major: 'F♯', minor: 'D♯m', sharps: 6, flats: 0 },\r\n { index: 7, major: 'D♭', minor: 'B♭m', sharps: 0, flats: 5 },\r\n { index: 8, major: 'A♭', minor: 'Fm', sharps: 0, flats: 4 },\r\n { index: 9, major: 'E♭', minor: 'Cm', sharps: 0, flats: 3 },\r\n { index: 10, major: 'B♭', minor: 'Gm', sharps: 0, flats: 2 },\r\n { index: 11, major: 'F', minor: 'Dm', sharps: 0, flats: 1 },\r\n ];\r\n\r\n selectedIndex = signal<number | null>(null);\r\n selectedType = signal<'major' | 'minor' | null>(null);\r\n\r\n private get slice() {\r\n const idx = this.selectedIndex();\r\n if (idx === null) return null;\r\n return {\r\n prev: (idx - 1 + 12) % 12,\r\n curr: idx,\r\n next: (idx + 1) % 12,\r\n };\r\n }\r\n\r\n selectKey(index: number, type: 'major' | 'minor'): void {\r\n if (this.selectedIndex() === index && this.selectedType() === type) {\r\n this.selectedIndex.set(null);\r\n this.selectedType.set(null);\r\n } else {\r\n this.selectedIndex.set(index);\r\n this.selectedType.set(type);\r\n }\r\n }\r\n\r\n getMajorState(index: number): string {\r\n const s = this.slice;\r\n if (!s) return '';\r\n const type = this.selectedType();\r\n if (index === s.curr) return type === 'major' ? 'tonic' : 'scale-major';\r\n if (index === s.prev || index === s.next) return 'scale-major';\r\n return '';\r\n }\r\n\r\n getMinorState(index: number): string {\r\n const s = this.slice;\r\n if (!s) return '';\r\n const type = this.selectedType();\r\n const dimIdx = (s.curr + 2) % 12;\r\n if (index === s.curr) return type === 'minor' ? 'tonic' : 'scale-minor';\r\n if (index === s.prev || index === s.next) return 'scale-minor';\r\n if (index === dimIdx) return 'scale-diminished';\r\n return '';\r\n }\r\n\r\n getMajorRole(index: number): string {\r\n const s = this.slice;\r\n if (!s) return '';\r\n const type = this.selectedType();\r\n if (type === 'major') {\r\n if (index === s.curr) return 'I';\r\n if (index === s.prev) return 'IV';\r\n if (index === s.next) return 'V';\r\n } else {\r\n if (index === s.curr) return 'III';\r\n if (index === s.prev) return 'VI';\r\n if (index === s.next) return 'VII';\r\n }\r\n return '';\r\n }\r\n\r\n getMinorRole(index: number): string {\r\n const s = this.slice;\r\n if (!s) return '';\r\n const type = this.selectedType();\r\n const dimIdx = (s.curr + 2) % 12;\r\n if (type === 'major') {\r\n if (index === s.curr) return 'vi';\r\n if (index === s.prev) return 'ii';\r\n if (index === s.next) return 'iii';\r\n if (index === dimIdx) return 'vii°';\r\n } else {\r\n if (index === s.curr) return 'i';\r\n if (index === s.prev) return 'iv';\r\n if (index === s.next) return 'v';\r\n if (index === dimIdx) return 'ii°';\r\n }\r\n return '';\r\n }\r\n\r\n arcPath(innerR: number, outerR: number, index: number, gap = 1.5): string {\r\n const mid = index * 30 - 90;\r\n const s = (mid - 15 + gap) * (Math.PI / 180);\r\n const e = (mid + 15 - gap) * (Math.PI / 180);\r\n const cx = this.CX, cy = this.CY;\r\n const x1 = cx + outerR * Math.cos(s), y1 = cy + outerR * Math.sin(s);\r\n const x2 = cx + outerR * Math.cos(e), y2 = cy + outerR * Math.sin(e);\r\n const x3 = cx + innerR * Math.cos(e), y3 = cy + innerR * Math.sin(e);\r\n const x4 = cx + innerR * Math.cos(s), y4 = cy + innerR * Math.sin(s);\r\n const f = (n: number) => n.toFixed(2);\r\n return `M${f(x1)} ${f(y1)} A${outerR} ${outerR} 0 0 1 ${f(x2)} ${f(y2)} L${f(x3)} ${f(y3)} A${innerR} ${innerR} 0 0 0 ${f(x4)} ${f(y4)}Z`;\r\n }\r\n\r\n textPos(midR: number, index: number): { x: number; y: number } {\r\n const a = (index * 30 - 90) * (Math.PI / 180);\r\n return {\r\n x: this.CX + midR * Math.cos(a),\r\n y: this.CY + midR * Math.sin(a),\r\n };\r\n }\r\n\r\n private dimChord(idx: number): string {\r\n // The diminished chord root is the minor key two steps clockwise (idx+2)\r\n const minor = this.keys[(idx + 2) % 12].minor;\r\n return minor.replace(/m$/, '°');\r\n }\r\n\r\n getAccidentalText(key: CircleKey): string {\r\n if (key.sharps > 0) return `${key.sharps}♯`;\r\n if (key.flats > 0) return `${key.flats}♭`;\r\n return '—';\r\n }\r\n\r\n get selectedInfo() {\r\n const idx = this.selectedIndex();\r\n const type = this.selectedType();\r\n if (idx === null || type === null) return null;\r\n const key = this.keys[idx];\r\n return {\r\n name: type === 'major' ? key.major : key.minor,\r\n scale: type === 'major' ? 'Major' : 'Minor',\r\n fullName: type === 'major' ? `${key.major} Major` : `${key.minor} Minor`,\r\n relativeKey: type === 'major'\r\n ? `Relative minor: ${key.minor}`\r\n : `Relative major: ${key.major}`,\r\n };\r\n }\r\n\r\n get chordTable(): ChordRow[] {\r\n const idx = this.selectedIndex();\r\n const type = this.selectedType();\r\n if (idx === null || type === null) return [];\r\n\r\n const prev = (idx - 1 + 12) % 12;\r\n const next = (idx + 1) % 12;\r\n\r\n if (type === 'major') {\r\n return [\r\n { numeral: 'I', chord: this.keys[idx].major, chordType: 'major', role: 'Tonic', state: 'tonic' },\r\n { numeral: 'ii', chord: this.keys[prev].minor, chordType: 'minor', role: 'Supertonic', state: 'scale-minor' },\r\n { numeral: 'iii', chord: this.keys[next].minor, chordType: 'minor', role: 'Mediant', state: 'scale-minor' },\r\n { numeral: 'IV', chord: this.keys[prev].major, chordType: 'major', role: 'Subdominant', state: 'scale-major' },\r\n { numeral: 'V', chord: this.keys[next].major, chordType: 'major', role: 'Dominant', state: 'scale-major' },\r\n { numeral: 'vi', chord: this.keys[idx].minor, chordType: 'minor', role: 'Submediant', state: 'scale-minor' },\r\n { numeral: 'vii°', chord: this.dimChord(idx), chordType: 'diminished', role: 'Leading Tone', state: 'scale-diminished' },\r\n ];\r\n } else {\r\n return [\r\n { numeral: 'i', chord: this.keys[idx].minor, chordType: 'minor', role: 'Tonic', state: 'tonic' },\r\n { numeral: 'iv', chord: this.keys[prev].minor, chordType: 'minor', role: 'Subdominant', state: 'scale-minor' },\r\n { numeral: 'v', chord: this.keys[next].minor, chordType: 'minor', role: 'Dominant', state: 'scale-minor' },\r\n { numeral: 'III', chord: this.keys[idx].major, chordType: 'major', role: 'Mediant', state: 'scale-major' },\r\n { numeral: 'VI', chord: this.keys[prev].major, chordType: 'major', role: 'Submediant', state: 'scale-major' },\r\n { numeral: 'VII', chord: this.keys[next].major, chordType: 'major', role: 'Subtonic', state: 'scale-major' },\r\n { numeral: 'ii°', chord: this.dimChord(idx), chordType: 'diminished', role: 'Supertonic', state: 'scale-diminished' },\r\n ];\r\n }\r\n }\r\n\r\n get progressions(): Progression[] {\r\n const table = this.chordTable;\r\n if (!table.length) return [];\r\n const lookup = new Map(table.map(r => [r.numeral, r.chord]));\r\n const defs = this.selectedType() === 'major' ? MAJOR_PROGRESSIONS : MINOR_PROGRESSIONS;\r\n return defs.map(d => ({ ...d, chords: d.numerals.map(n => lookup.get(n) ?? n) }));\r\n }\r\n}\r\n","<div class=\"page\">\n <header class=\"page-header\">\r\n <h1>Circle of Fifths</h1>\r\n <p>Click any major or minor key to highlight its diatonic chords</p>\r\n </header>\r\n\r\n <div class=\"circle-wrapper\">\r\n <svg viewBox=\"0 0 600 600\" xmlns=\"http://www.w3.org/2000/svg\" aria-label=\"Circle of Fifths\">\r\n\r\n <!-- ── Key Signature Ring ── -->\r\n @for (key of keys; track key.index) {\r\n <path\r\n [attr.d]=\"arcPath(KEY_SIG.inner, KEY_SIG.outer, key.index)\"\r\n class=\"keysig-segment\" />\r\n <text\r\n [attr.x]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).y\"\r\n class=\"keysig-label\">\r\n {{ getAccidentalText(key) }}\r\n </text>\r\n }\r\n\r\n <!-- ── Major Keys Ring ── -->\r\n @for (key of keys; track key.index) {\r\n <g [ngClass]=\"['major-group', getMajorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'major')\">\r\n <path [attr.d]=\"arcPath(MAJOR.inner, MAJOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y + (getMajorRole(key.index) ? -9 : 0)\"\r\n class=\"major-label\">\r\n {{ key.major }}\r\n </text>\r\n\r\n @if (getMajorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y + 11\"\r\n class=\"role-label\">\r\n {{ getMajorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- ── Minor Keys Ring ── -->\r\n @for (key of keys; track key.index) {\r\n <g [ngClass]=\"['minor-group', getMinorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'minor')\">\r\n <path [attr.d]=\"arcPath(MINOR.inner, MINOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).y + (getMinorRole(key.index) ? -8 : 0)\"\r\n class=\"minor-label\">\r\n {{ key.minor }}\r\n </text>\r\n\r\n @if (getMinorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).y + 9\"\r\n class=\"role-label\">\r\n {{ getMinorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- ── Outer border ── -->\r\n <circle cx=\"300\" cy=\"300\" r=\"289\" fill=\"none\" stroke=\"#1e293b\" stroke-width=\"2.5\" />\r\n\r\n <!-- ── Center Circle ── -->\r\n <circle cx=\"300\" cy=\"300\" r=\"99\" class=\"center-circle\" />\r\n\r\n @if (selectedInfo) {\r\n <text x=\"300\" y=\"283\" class=\"center-name\">{{ selectedInfo.name }}</text>\r\n <text x=\"300\" y=\"304\" class=\"center-scale\">{{ selectedInfo.scale }}</text>\r\n <text x=\"300\" y=\"322\" class=\"center-relative\">{{ selectedInfo.relativeKey }}</text>\r\n <text x=\"300\" y=\"338\" class=\"center-deselect\">↩ click to deselect</text>\r\n } @else {\r\n <text x=\"300\" y=\"293\" class=\"center-hint\">Click a key</text>\r\n <text x=\"300\" y=\"312\" class=\"center-hint-sm\">to see its chords</text>\r\n }\r\n\r\n </svg>\r\n </div>\r\n\r\n <!-- ── Legend ── -->\r\n <div class=\"legend\">\r\n <span class=\"legend-item tonic\">Tonic (I / i)</span>\r\n <span class=\"legend-item scale-major\">Major chords (IV, V)</span>\r\n <span class=\"legend-item scale-minor\">Minor chords (ii, iii, vi)</span>\r\n <span class=\"legend-item scale-diminished\">Diminished (vii° / ii°)</span>\r\n </div>\r\n\r\n <!-- ── Chord Table ── -->\r\n @if (selectedInfo && chordTable.length) {\r\n <section class=\"chord-section\">\r\n <h2>{{ selectedInfo.fullName }} — Diatonic Chords</h2>\r\n <p class=\"relative-label\">{{ selectedInfo.relativeKey }}</p>\r\n <div class=\"chord-grid\">\r\n @for (row of chordTable; track row.numeral) {\r\n <div class=\"chord-card\" [ngClass]=\"row.state\">\r\n <div class=\"chord-numeral\">{{ row.numeral }}</div>\r\n <div class=\"chord-name\">{{ row.chord }}</div>\r\n <div class=\"chord-type\">{{ row.chordType }}</div>\r\n <div class=\"chord-role\">{{ row.role }}</div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n\r\n <!-- ── Chord Progressions ── -->\r\n @if (progressions.length) {\r\n <section class=\"progressions-section\">\r\n <h2>Common Progressions in {{ selectedInfo?.fullName }}</h2>\r\n <div class=\"progression-list\">\r\n @for (prog of progressions; track prog.name) {\r\n <div class=\"progression-card\">\r\n <div class=\"prog-header\">\r\n <span class=\"prog-mood\">{{ prog.mood }}</span>\r\n <span class=\"prog-name\">{{ prog.name }}</span>\r\n <span class=\"prog-genre\">{{ prog.genre }}</span>\r\n </div>\r\n <div class=\"prog-chords\">\r\n @for (chord of prog.chords; track $index; let last = $last) {\r\n <span class=\"prog-chord\">{{ chord }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">→</span> }\r\n }\r\n </div>\r\n <div class=\"prog-numerals\">\r\n @for (num of prog.numerals; track $index; let last = $last) {\r\n <span>{{ num }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">→</span> }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n","/*\n * Public API Surface of circle-of-fifths-lib\n */\n\nexport * from './lib/circle-of-fifths';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gblp-circle-of-fifths.mjs","sources":["../../../projects/circle-of-fifths-lib/src/lib/circle-of-fifths.ts","../../../projects/circle-of-fifths-lib/src/lib/chord-section.component.html","../../../projects/circle-of-fifths-lib/src/lib/circle-of-fifths.html","../../../projects/circle-of-fifths-lib/src/public-api.ts","../../../projects/circle-of-fifths-lib/src/gblp-circle-of-fifths.ts"],"sourcesContent":["import { Component, computed, input, signal } from '@angular/core';\r\nimport { NgClass } from '@angular/common';\r\n\r\ninterface CircleKey {\r\n index: number;\r\n major: string;\r\n minor: string;\r\n sharps: number;\r\n flats: number;\r\n}\r\n\r\ninterface ChordRow {\r\n numeral: string;\r\n chord: string;\r\n chordType: string;\r\n role: string;\r\n state: 'tonic' | 'scale-major' | 'scale-minor' | 'scale-diminished';\r\n}\r\n\r\ninterface Section {\r\n label: string;\r\n numerals: string[];\r\n chords: string[];\r\n}\r\n\r\ninterface Progression {\r\n name: string;\r\n mood: string;\r\n genre: string;\r\n numerals: string[];\r\n chords: string[];\r\n sections: Section[];\r\n}\r\n\r\ntype Language = 'en' | 'es';\r\ntype LocalizedText = Record<Language, string>;\r\n\r\ninterface ProgressionDefinition {\r\n name: LocalizedText;\r\n mood: LocalizedText;\r\n genre: LocalizedText;\r\n numerals: string[];\r\n}\r\n\r\nconst PROGRESSIONS: Record<'major' | 'minor', ProgressionDefinition[]> = {\r\n major: [\r\n {\r\n name: { en: 'Anthemic', es: 'Épica' },\r\n mood: { en: '😄 Joyful', es: '😄 Alegre' },\r\n genre: { en: 'Folk, Rock', es: 'Folk, rock' },\r\n numerals: ['I', 'IV', 'V', 'I'],\r\n },\r\n {\r\n name: { en: 'Hopeful', es: 'Esperanzadora' },\r\n mood: { en: '✨ Hopeful', es: '✨ Esperanzadora' },\r\n genre: { en: 'Pop, Indie', es: 'Pop, indie' },\r\n numerals: ['I', 'V', 'vi', 'IV'],\r\n },\r\n {\r\n name: { en: 'Melancholic', es: 'Melancólica' },\r\n mood: { en: '😢 Emotional', es: '😢 Emocional' },\r\n genre: { en: 'Ballads, Pop', es: 'Baladas, pop' },\r\n numerals: ['vi', 'IV', 'I', 'V'],\r\n },\r\n {\r\n name: { en: 'Cinematic', es: 'Cinemática' },\r\n mood: { en: '🎬 Tense', es: '🎬 Tensa' },\r\n genre: { en: 'Film, Jazz', es: 'Cine, jazz' },\r\n numerals: ['ii', 'V', 'vii°', 'I'],\r\n },\r\n {\r\n name: { en: 'Nostalgic', es: 'Retro' },\r\n mood: { en: '🕺 Retro', es: '🕺 Retro' },\r\n genre: { en: 'Doo-Wop, Pop', es: 'Doo-Wop, pop' },\r\n numerals: ['I', 'vi', 'IV', 'V'],\r\n },\r\n {\r\n name: { en: 'Dreamy', es: 'Soñadora' },\r\n mood: { en: '🌸 Floaty', es: '🌸 Etérea' },\r\n genre: { en: 'J-Pop, Anime', es: 'J-Pop, anime' },\r\n numerals: ['IV', 'V', 'iii', 'vi'],\r\n },\r\n {\r\n name: { en: 'Gospel', es: 'Gospel' },\r\n mood: { en: '🙏 Uplifting', es: '🙏 Elevadora' },\r\n genre: { en: 'Gospel, Soul', es: 'Gospel, soul' },\r\n numerals: ['I', 'IV', 'ii', 'V'],\r\n },\r\n {\r\n name: { en: 'Bittersweet', es: 'Agridulce' },\r\n mood: { en: '🍂 Bittersweet', es: '🍂 Agridulce' },\r\n genre: { en: 'Alt Rock, Indie', es: 'Rock alternativo, indie' },\r\n numerals: ['I', 'iii', 'IV', 'V'],\r\n },\r\n ],\r\n minor: [\r\n {\r\n name: { en: 'Brooding', es: 'Sombría' },\r\n mood: { en: '🌑 Dark', es: '🌑 Oscura' },\r\n genre: { en: 'Rock, Metal', es: 'Rock, metal' },\r\n numerals: ['i', 'VII', 'VI', 'VII'],\r\n },\r\n {\r\n name: { en: 'Haunting', es: 'Inquietante' },\r\n mood: { en: '👻 Mysterious', es: '👻 Misteriosa' },\r\n genre: { en: 'Film, Gothic', es: 'Cine, gótico' },\r\n numerals: ['i', 'iv', 'VII', 'III'],\r\n },\r\n {\r\n name: { en: 'Driving', es: 'Impulsora' },\r\n mood: { en: '⚡ Urgent', es: '⚡ Urgente' },\r\n genre: { en: 'Pop, EDM', es: 'Pop, EDM' },\r\n numerals: ['i', 'VI', 'III', 'VII'],\r\n },\r\n {\r\n name: { en: 'Wistful', es: 'Nostálgica' },\r\n mood: { en: '🌙 Wistful', es: '🌙 Nostálgica' },\r\n genre: { en: 'Cinematic', es: 'Cinemática' },\r\n numerals: ['i', 'v', 'VI', 'VII'],\r\n },\r\n {\r\n name: { en: 'Flamenco', es: 'Flamenco' },\r\n mood: { en: '🌹 Passionate', es: '🌹 Apasionado' },\r\n genre: { en: 'Flamenco, World', es: 'Flamenco, mundial' },\r\n numerals: ['i', 'VII', 'VI', 'iv'],\r\n },\r\n {\r\n name: { en: 'Tragic', es: 'Trágica' },\r\n mood: { en: '💔 Tragic', es: '💔 Trágica' },\r\n genre: { en: 'Classical, Drama', es: 'Clásica, drama' },\r\n numerals: ['i', 'iv', 'v', 'i'],\r\n },\r\n {\r\n name: { en: 'Epic', es: 'Épica' },\r\n mood: { en: '⚔️ Heroic', es: '⚔️ Heroico' },\r\n genre: { en: 'Epic, Orchestral', es: 'Épica, orquestal' },\r\n numerals: ['i', 'III', 'VII', 'VI'],\r\n },\r\n {\r\n name: { en: 'Ethereal', es: 'Etérea' },\r\n mood: { en: '🌊 Hypnotic', es: '🌊 Hipnótica' },\r\n genre: { en: 'Ambient, Post-Rock', es: 'Ambient, post-rock' },\r\n numerals: ['i', 'VI', 'iv', 'VII'],\r\n },\r\n ],\r\n};\r\n\r\nconst COPY = {\r\n en: {\r\n title: 'Circle of Fifths',\r\n subtitle: 'Click any major or minor key to highlight its diatonic chords',\r\n ariaLabel: 'Circle of Fifths',\r\n clickKey: 'Click a key',\r\n seeChords: 'to see its chords',\r\n deselect: '↩ click to deselect',\r\n tonicLegend: 'Tonic (I / i)',\r\n majorLegend: 'Major chords (IV, V)',\r\n minorLegend: 'Minor chords (ii, iii, vi)',\r\n diminishedLegend: 'Diminished (vii° / ii°)',\r\n diatonicChords: 'Diatonic Chords',\r\n commonProgressions: 'Common Progressions in',\r\n generateProgressions: 'Shuffle',\r\n chorus: 'Chorus',\r\n bridge: 'Bridge',\r\n outro: 'Outro',\r\n verse: 'Verse',\r\n copy: 'Copy',\r\n expand: 'Expand',\r\n collapse: 'Collapse',\r\n major: 'Major',\r\n minor: 'Minor',\r\n diminished: 'diminished',\r\n relativeMinor: 'Relative minor',\r\n relativeMajor: 'Relative major',\r\n roles: {\r\n tonic: 'Tonic',\r\n supertonic: 'Supertonic',\r\n mediant: 'Mediant',\r\n subdominant: 'Subdominant',\r\n dominant: 'Dominant',\r\n submediant: 'Submediant',\r\n leadingTone: 'Leading Tone',\r\n subtonic: 'Subtonic',\r\n },\r\n },\r\n es: {\r\n title: 'Círculo de quintas',\r\n subtitle: 'Haz clic en una tonalidad mayor o menor para resaltar sus acordes diatónicos',\r\n ariaLabel: 'Círculo de quintas',\r\n clickKey: 'Haz clic en una tonalidad',\r\n seeChords: 'para ver sus acordes',\r\n deselect: '↩ haz clic para deseleccionar',\r\n tonicLegend: 'Tónica (I / i)',\r\n majorLegend: 'Acordes mayores (IV, V)',\r\n minorLegend: 'Acordes menores (ii, iii, vi)',\r\n diminishedLegend: 'Disminuido (vii° / ii°)',\r\n diatonicChords: 'Acordes diatónicos',\r\n commonProgressions: 'Progresiones comunes en',\r\n generateProgressions: 'Generar nuevos',\r\n chorus: 'Coro',\r\n bridge: 'Puente',\r\n outro: 'Final',\r\n verse: 'Estrofa',\r\n copy: 'Copiar',\r\n expand: 'Expandir',\r\n collapse: 'Colapsar',\r\n major: 'Mayor',\r\n minor: 'Menor',\r\n diminished: 'disminuido',\r\n relativeMinor: 'Relativa menor',\r\n relativeMajor: 'Relativa mayor',\r\n roles: {\r\n tonic: 'Tónica',\r\n supertonic: 'Supertónica',\r\n mediant: 'Mediante',\r\n subdominant: 'Subdominante',\r\n dominant: 'Dominante',\r\n submediant: 'Submediante',\r\n leadingTone: 'Sensible',\r\n subtonic: 'Subtónica',\r\n },\r\n },\r\n} as const;\r\n\r\nconst KEYS: CircleKey[] = [\r\n { index: 0, major: 'C', minor: 'Am', sharps: 0, flats: 0 },\r\n { index: 1, major: 'G', minor: 'Em', sharps: 1, flats: 0 },\r\n { index: 2, major: 'D', minor: 'Bm', sharps: 2, flats: 0 },\r\n { index: 3, major: 'A', minor: 'F♯m', sharps: 3, flats: 0 },\r\n { index: 4, major: 'E', minor: 'C♯m', sharps: 4, flats: 0 },\r\n { index: 5, major: 'B', minor: 'G♯m', sharps: 5, flats: 0 },\r\n { index: 6, major: 'F♯', minor: 'D♯m', sharps: 6, flats: 0 },\r\n { index: 7, major: 'D♭', minor: 'B♭m', sharps: 0, flats: 5 },\r\n { index: 8, major: 'A♭', minor: 'Fm', sharps: 0, flats: 4 },\r\n { index: 9, major: 'E♭', minor: 'Cm', sharps: 0, flats: 3 },\r\n { index: 10, major: 'B♭', minor: 'Gm', sharps: 0, flats: 2 },\r\n { index: 11, major: 'F', minor: 'Dm', sharps: 0, flats: 1 },\r\n];\r\n\r\nfunction dimChord(idx: number): string {\r\n // The diminished chord root is the minor key two steps clockwise (idx+2)\r\n return KEYS[(idx + 2) % 12].minor.replace(/m$/, '°');\r\n}\r\n\r\nfunction buildChordRows(idx: number, type: 'major' | 'minor', copy: (typeof COPY)[Language]): ChordRow[] {\r\n const prev = (idx - 1 + 12) % 12;\r\n const next = (idx + 1) % 12;\r\n if (type === 'major') {\r\n return [\r\n { numeral: 'I', chord: KEYS[idx].major, chordType: copy.major, role: copy.roles.tonic, state: 'tonic' },\r\n { numeral: 'ii', chord: KEYS[prev].minor, chordType: copy.minor, role: copy.roles.supertonic, state: 'scale-minor' },\r\n { numeral: 'iii', chord: KEYS[next].minor, chordType: copy.minor, role: copy.roles.mediant, state: 'scale-minor' },\r\n { numeral: 'IV', chord: KEYS[prev].major, chordType: copy.major, role: copy.roles.subdominant, state: 'scale-major' },\r\n { numeral: 'V', chord: KEYS[next].major, chordType: copy.major, role: copy.roles.dominant, state: 'scale-major' },\r\n { numeral: 'vi', chord: KEYS[idx].minor, chordType: copy.minor, role: copy.roles.submediant, state: 'scale-minor' },\r\n { numeral: 'vii°', chord: dimChord(idx), chordType: copy.diminished, role: copy.roles.leadingTone, state: 'scale-diminished' },\r\n ];\r\n } else {\r\n return [\r\n { numeral: 'i', chord: KEYS[idx].minor, chordType: copy.minor, role: copy.roles.tonic, state: 'tonic' },\r\n { numeral: 'iv', chord: KEYS[prev].minor, chordType: copy.minor, role: copy.roles.subdominant, state: 'scale-minor' },\r\n { numeral: 'v', chord: KEYS[next].minor, chordType: copy.minor, role: copy.roles.dominant, state: 'scale-minor' },\r\n { numeral: 'III', chord: KEYS[idx].major, chordType: copy.major, role: copy.roles.mediant, state: 'scale-major' },\r\n { numeral: 'VI', chord: KEYS[prev].major, chordType: copy.major, role: copy.roles.submediant, state: 'scale-major' },\r\n { numeral: 'VII', chord: KEYS[next].major, chordType: copy.major, role: copy.roles.subtonic, state: 'scale-major' },\r\n { numeral: 'ii°', chord: dimChord(idx), chordType: copy.diminished, role: copy.roles.supertonic, state: 'scale-diminished' },\r\n ];\r\n }\r\n}\r\n\r\n@Component({\r\n selector: 'chord-section',\r\n standalone: true,\r\n imports: [NgClass],\r\n templateUrl: './chord-section.component.html',\r\n styleUrl: './chord-section.component.scss',\r\n})\r\nexport class ChordSectionComponent {\r\n readonly chords = input<string[]>([]);\r\n readonly language = input<Language>('en');\r\n readonly text = computed(() => COPY[this.language()]);\r\n\r\n readonly match = computed(() => {\r\n const input = this.chords();\r\n if (!input.length) return null;\r\n const inputSet = new Set(input);\r\n let best: { idx: number; type: 'major' | 'minor'; score: number } | null = null;\r\n for (let idx = 0; idx < 12; idx++) {\r\n for (const type of ['major', 'minor'] as const) {\r\n // chord names are language-independent; COPY.en is a dummy for label fields\r\n const score = buildChordRows(idx, type, COPY.en).filter((r) => inputSet.has(r.chord)).length;\r\n if (!best || score > best.score) best = { idx, type, score };\r\n }\r\n }\r\n return best && best.score > 0 ? best : null;\r\n });\r\n\r\n readonly rows = computed(() => {\r\n const m = this.match();\r\n if (!m) return [];\r\n return buildChordRows(m.idx, m.type, this.text());\r\n });\r\n\r\n readonly info = computed(() => {\r\n const m = this.match();\r\n if (!m) return null;\r\n const copy = this.text();\r\n const key = KEYS[m.idx];\r\n return {\r\n fullName: `${m.type === 'major' ? key.major : key.minor} ${m.type === 'major' ? copy.major : copy.minor}`,\r\n relativeKey: m.type === 'major'\r\n ? `${copy.relativeMinor}: ${key.minor}`\r\n : `${copy.relativeMajor}: ${key.major}`,\r\n };\r\n });\r\n}\r\n\r\n@Component({\r\n selector: 'the-chords-circle-of-fifths',\r\n standalone: true,\r\n imports: [NgClass, ChordSectionComponent],\r\n templateUrl: './circle-of-fifths.html',\r\n styleUrl: './circle-of-fifths.scss',\r\n host: { '[attr.lang]': 'language()' },\r\n})\r\nexport class CircleOfFifthsComponent {\r\n readonly language = input<Language>('en');\r\n readonly text = computed(() => COPY[this.language()]);\r\n readonly CX = 300;\r\n readonly CY = 300;\r\n\r\n readonly KEY_SIG = { inner: 250, outer: 288 };\r\n readonly MAJOR = { inner: 172, outer: 250 };\r\n readonly MINOR = { inner: 102, outer: 172 };\r\n\r\n readonly keys = KEYS;\r\n\r\n selectedIndex = signal<number | null>(null);\r\n selectedType = signal<'major' | 'minor' | null>(null);\r\n readonly activeProgressionDefs = signal<ProgressionDefinition[] | null>(null);\r\n readonly expandedCards = signal<Set<string>>(new Set());\r\n\r\n private get slice() {\r\n const idx = this.selectedIndex();\r\n if (idx === null) return null;\r\n return {\r\n prev: (idx - 1 + 12) % 12,\r\n curr: idx,\r\n next: (idx + 1) % 12,\r\n };\r\n }\r\n\r\n selectKey(index: number, type: 'major' | 'minor'): void {\r\n if (this.selectedIndex() === index && this.selectedType() === type) {\r\n this.selectedIndex.set(null);\r\n this.selectedType.set(null);\r\n } else {\r\n this.selectedIndex.set(index);\r\n this.selectedType.set(type);\r\n }\r\n this.activeProgressionDefs.set(null);\r\n this.expandedCards.set(new Set());\r\n }\r\n\r\n randomizeProgressions(): void {\r\n const type = this.selectedType();\r\n if (!type) return;\r\n // ponytail: sort-shuffle is fine for UI randomness\r\n this.activeProgressionDefs.set([...PROGRESSIONS[type]].sort(() => Math.random() - 0.5).slice(0, 4));\r\n this.expandedCards.set(new Set());\r\n }\r\n\r\n toggleCard(name: string): void {\r\n this.expandedCards.update((set) => {\r\n const next = new Set(set);\r\n next.has(name) ? next.delete(name) : next.add(name);\r\n return next;\r\n });\r\n }\r\n\r\n copyChords(prog: { name: string; chords: string[]; sections: { label: string; chords: string[] }[] }): void {\r\n const text = this.expandedCards().has(prog.name)\r\n ? [`${this.text().verse}: ${prog.chords.join(', ')}`, ...prog.sections.map((s) => `${s.label}: ${s.chords.join(', ')}`)].join('; ')\r\n : prog.chords.join(', ');\r\n navigator.clipboard.writeText(text);\r\n }\r\n\r\n getMajorState(index: number): string {\r\n const s = this.slice;\r\n if (!s) return '';\r\n const type = this.selectedType();\r\n if (index === s.curr) return type === 'major' ? 'tonic' : 'scale-major';\r\n if (index === s.prev || index === s.next) return 'scale-major';\r\n return '';\r\n }\r\n\r\n getMinorState(index: number): string {\r\n const s = this.slice;\r\n if (!s) return '';\r\n const type = this.selectedType();\r\n const dimIdx = (s.curr + 2) % 12;\r\n if (index === s.curr) return type === 'minor' ? 'tonic' : 'scale-minor';\r\n if (index === s.prev || index === s.next) return 'scale-minor';\r\n if (index === dimIdx) return 'scale-diminished';\r\n return '';\r\n }\r\n\r\n getMajorRole(index: number): string {\r\n const s = this.slice;\r\n if (!s) return '';\r\n const type = this.selectedType();\r\n if (type === 'major') {\r\n if (index === s.curr) return 'I';\r\n if (index === s.prev) return 'IV';\r\n if (index === s.next) return 'V';\r\n } else {\r\n if (index === s.curr) return 'III';\r\n if (index === s.prev) return 'VI';\r\n if (index === s.next) return 'VII';\r\n }\r\n return '';\r\n }\r\n\r\n getMinorRole(index: number): string {\r\n const s = this.slice;\r\n if (!s) return '';\r\n const type = this.selectedType();\r\n const dimIdx = (s.curr + 2) % 12;\r\n if (type === 'major') {\r\n if (index === s.curr) return 'vi';\r\n if (index === s.prev) return 'ii';\r\n if (index === s.next) return 'iii';\r\n if (index === dimIdx) return 'vii°';\r\n } else {\r\n if (index === s.curr) return 'i';\r\n if (index === s.prev) return 'iv';\r\n if (index === s.next) return 'v';\r\n if (index === dimIdx) return 'ii°';\r\n }\r\n return '';\r\n }\r\n\r\n arcPath(innerR: number, outerR: number, index: number, gap = 1.5): string {\r\n const mid = index * 30 - 90;\r\n const s = (mid - 15 + gap) * (Math.PI / 180);\r\n const e = (mid + 15 - gap) * (Math.PI / 180);\r\n const cx = this.CX,\r\n cy = this.CY;\r\n const x1 = cx + outerR * Math.cos(s),\r\n y1 = cy + outerR * Math.sin(s);\r\n const x2 = cx + outerR * Math.cos(e),\r\n y2 = cy + outerR * Math.sin(e);\r\n const x3 = cx + innerR * Math.cos(e),\r\n y3 = cy + innerR * Math.sin(e);\r\n const x4 = cx + innerR * Math.cos(s),\r\n y4 = cy + innerR * Math.sin(s);\r\n const f = (n: number) => n.toFixed(2);\r\n return `M${f(x1)} ${f(y1)} A${outerR} ${outerR} 0 0 1 ${f(x2)} ${f(y2)} L${f(x3)} ${f(y3)} A${innerR} ${innerR} 0 0 0 ${f(x4)} ${f(y4)}Z`;\r\n }\r\n\r\n textPos(midR: number, index: number): { x: number; y: number } {\r\n const a = (index * 30 - 90) * (Math.PI / 180);\r\n return {\r\n x: this.CX + midR * Math.cos(a),\r\n y: this.CY + midR * Math.sin(a),\r\n };\r\n }\r\n\r\n\r\n get chordTable(): ChordRow[] {\r\n const idx = this.selectedIndex();\r\n const type = this.selectedType();\r\n if (idx === null || type === null) return [];\r\n return buildChordRows(idx, type, this.text());\r\n }\r\n\r\n get chordNames(): string[] {\r\n return this.chordTable.map((r) => r.chord);\r\n }\r\n\r\n getAccidentalText(key: CircleKey): string {\r\n if (key.sharps > 0) return `${key.sharps}♯`;\r\n if (key.flats > 0) return `${key.flats}♭`;\r\n return '—';\r\n }\r\n\r\n get selectedInfo() {\r\n const idx = this.selectedIndex();\r\n const type = this.selectedType();\r\n if (idx === null || type === null) return null;\r\n const key = KEYS[idx];\r\n const copy = this.text();\r\n const scale = type === 'major' ? copy.major : copy.minor;\r\n return {\r\n name: type === 'major' ? key.major : key.minor,\r\n scale,\r\n fullName: `${type === 'major' ? key.major : key.minor} ${scale}`,\r\n relativeKey:\r\n type === 'major'\r\n ? `${copy.relativeMinor}: ${key.minor}`\r\n : `${copy.relativeMajor}: ${key.major}`,\r\n };\r\n }\r\n\r\n private deriveSections(numerals: string[], lookup: Map<string, string>): Section[] {\r\n const type = this.selectedType()!;\r\n const copy = this.text();\r\n const resolve = (ns: string[]) => ({ numerals: ns, chords: ns.map((n) => lookup.get(n) ?? n) });\r\n\r\n // Chorus: rotate to lift chord (IV for major, VI for minor); else fixed fallback\r\n const chorusPivot = type === 'major' ? 'IV' : 'VI';\r\n const chorusIdx = numerals.indexOf(chorusPivot);\r\n const chorus = chorusIdx > 0\r\n ? resolve([...numerals.slice(chorusIdx), ...numerals.slice(0, chorusIdx)])\r\n : resolve(type === 'major' ? ['I', 'IV', 'V', 'I'] : ['i', 'VI', 'III', 'VII']);\r\n\r\n // Bridge: rotate to contrast chord (vi for major, III for minor); else fixed fallback\r\n const bridgePivot = type === 'major' ? 'vi' : 'III';\r\n const bridgeIdx = numerals.indexOf(bridgePivot);\r\n const bridge = bridgeIdx >= 0\r\n ? resolve([...numerals.slice(bridgeIdx), ...numerals.slice(0, bridgeIdx)])\r\n : resolve(type === 'major' ? ['vi', 'IV', 'ii', 'V'] : ['III', 'VII', 'VI', 'iv']);\r\n\r\n // Outro: fixed resolving pattern per mode\r\n const outro = resolve(type === 'major' ? ['I', 'V', 'IV', 'I'] : ['i', 'VII', 'VI', 'i']);\r\n\r\n return [\r\n { label: copy.chorus, ...chorus },\r\n { label: copy.bridge, ...bridge },\r\n { label: copy.outro, ...outro },\r\n ];\r\n }\r\n\r\n get progressions(): Progression[] {\r\n const table = this.chordTable;\r\n if (!table.length) return [];\r\n const lookup = new Map(table.map((r) => [r.numeral, r.chord]));\r\n const language = this.language();\r\n const type = this.selectedType() === 'major' ? 'major' : 'minor';\r\n const defs = this.activeProgressionDefs() ?? PROGRESSIONS[type].slice(0, 4);\r\n return defs.map((definition) => ({\r\n name: definition.name[language],\r\n mood: definition.mood[language],\r\n genre: definition.genre[language],\r\n numerals: definition.numerals,\r\n chords: definition.numerals.map((numeral) => lookup.get(numeral) ?? numeral),\r\n sections: this.deriveSections(definition.numerals, lookup),\r\n }));\r\n }\r\n}\r\n","@if (rows().length) {\r\n <section class=\"chord-section\">\r\n @if (info(); as info) {\r\n <h2>{{ info.fullName }} — {{ text().diatonicChords }}</h2>\r\n <p class=\"relative-label\">{{ info.relativeKey }}</p>\r\n }\r\n <div class=\"chord-grid\">\r\n @for (row of rows(); track row.numeral) {\r\n <div class=\"chord-card\" [ngClass]=\"row.state\">\r\n <div class=\"chord-numeral\">{{ row.numeral }}</div>\r\n <div class=\"chord-name\">{{ row.chord }}</div>\r\n <div class=\"chord-type\">{{ row.chordType }}</div>\r\n <div class=\"chord-role\">{{ row.role }}</div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n}\r\n","<div class=\"page\">\r\n <header class=\"page-header\">\r\n <h1>{{ text().title }}</h1>\r\n <p>{{ text().subtitle }}</p>\r\n </header>\r\n\r\n <div class=\"circle-wrapper\">\r\n <svg\r\n viewBox=\"0 0 600 600\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n [attr.aria-label]=\"text().ariaLabel\"\r\n >\r\n <!-- ── Key Signature Ring ── -->\r\n @for (key of keys; track key.index) {\r\n <path [attr.d]=\"arcPath(KEY_SIG.inner, KEY_SIG.outer, key.index)\" class=\"keysig-segment\" />\r\n <text\r\n [attr.x]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((KEY_SIG.inner + KEY_SIG.outer) / 2, key.index).y\"\r\n class=\"keysig-label\"\r\n >\r\n {{ getAccidentalText(key) }}\r\n </text>\r\n }\r\n\r\n <!-- ── Major Keys Ring ── -->\r\n @for (key of keys; track key.index) {\r\n <g\r\n [ngClass]=\"['major-group', getMajorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'major')\"\r\n >\r\n <path [attr.d]=\"arcPath(MAJOR.inner, MAJOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"\r\n textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y +\r\n (getMajorRole(key.index) ? -9 : 0)\r\n \"\r\n class=\"major-label\"\r\n >\r\n {{ key.major }}\r\n </text>\r\n\r\n @if (getMajorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MAJOR.inner + MAJOR.outer) / 2, key.index).y + 11\"\r\n class=\"role-label\"\r\n >\r\n {{ getMajorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- ── Minor Keys Ring ── -->\r\n @for (key of keys; track key.index) {\r\n <g\r\n [ngClass]=\"['minor-group', getMinorState(key.index)]\"\r\n (click)=\"selectKey(key.index, 'minor')\"\r\n >\r\n <path [attr.d]=\"arcPath(MINOR.inner, MINOR.outer, key.index)\" />\r\n\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"\r\n textPos((MINOR.inner + MINOR.outer) / 2, key.index).y +\r\n (getMinorRole(key.index) ? -8 : 0)\r\n \"\r\n class=\"minor-label\"\r\n >\r\n {{ key.minor }}\r\n </text>\r\n\r\n @if (getMinorRole(key.index)) {\r\n <text\r\n [attr.x]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).x\"\r\n [attr.y]=\"textPos((MINOR.inner + MINOR.outer) / 2, key.index).y + 9\"\r\n class=\"role-label\"\r\n >\r\n {{ getMinorRole(key.index) }}\r\n </text>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- ── Outer border ── -->\r\n <circle cx=\"300\" cy=\"300\" r=\"289\" class=\"outer-border\" />\r\n\r\n <!-- ── Center Circle ── -->\r\n <circle cx=\"300\" cy=\"300\" r=\"99\" class=\"center-circle\" />\r\n\r\n @if (selectedInfo) {\r\n <text x=\"300\" y=\"283\" class=\"center-name\">{{ selectedInfo.name }}</text>\r\n <text x=\"300\" y=\"304\" class=\"center-scale\">{{ selectedInfo.scale }}</text>\r\n <text x=\"300\" y=\"322\" class=\"center-relative\">{{ selectedInfo.relativeKey }}</text>\r\n <text x=\"300\" y=\"338\" class=\"center-deselect\">{{ text().deselect }}</text>\r\n } @else {\r\n <text x=\"300\" y=\"293\" class=\"center-hint\">{{ text().clickKey }}</text>\r\n <text x=\"300\" y=\"312\" class=\"center-hint-sm\">{{ text().seeChords }}</text>\r\n }\r\n </svg>\r\n </div>\r\n\r\n <!-- ── Legend ── -->\r\n <div class=\"legend\">\r\n <span class=\"legend-item tonic\">{{ text().tonicLegend }}</span>\r\n <span class=\"legend-item scale-major\">{{ text().majorLegend }}</span>\r\n <span class=\"legend-item scale-minor\">{{ text().minorLegend }}</span>\r\n <span class=\"legend-item scale-diminished\">{{ text().diminishedLegend }}</span>\r\n </div>\r\n\r\n <chord-section [chords]=\"chordNames\" [language]=\"language()\"></chord-section>\r\n\r\n <!-- ── Chord Progressions ── -->\r\n @if (progressions.length) {\r\n <section class=\"progressions-section\">\r\n <div class=\"progressions-header\">\r\n <h2>{{ text().commonProgressions }} {{ selectedInfo?.fullName }}</h2>\r\n <button class=\"btn-randomize\" (click)=\"randomizeProgressions()\">{{ text().generateProgressions }}</button>\r\n </div>\r\n <div class=\"progression-list\">\r\n @for (prog of progressions; track prog.name) {\r\n <div class=\"progression-card\">\r\n <div class=\"prog-header\">\r\n <span class=\"prog-mood\">{{ prog.mood }}</span>\r\n <span class=\"prog-name\">{{ prog.name }}</span>\r\n <span class=\"prog-genre\">{{ prog.genre }}</span>\r\n </div>\r\n <div class=\"prog-chords\">\r\n @for (chord of prog.chords; track $index; let last = $last) {\r\n <span class=\"prog-chord\">{{ chord }}</span>\r\n @if (!last) {\r\n <span class=\"prog-arrow\">→</span>\r\n }\r\n }\r\n </div>\r\n <div class=\"prog-numerals\">\r\n @for (num of prog.numerals; track $index; let last = $last) {\r\n <span>{{ num }}</span>\r\n @if (!last) {\r\n <span class=\"prog-arrow\">→</span>\r\n }\r\n }\r\n </div>\r\n <div class=\"prog-expand-row\">\r\n <button class=\"btn-expand\" (click)=\"toggleCard(prog.name)\">\r\n {{ expandedCards().has(prog.name) ? text().collapse : text().expand }}\r\n </button>\r\n <button class=\"btn-expand\" (click)=\"copyChords(prog)\">\r\n {{ text().copy }}\r\n </button>\r\n </div>\r\n @if (expandedCards().has(prog.name)) {\r\n <div class=\"prog-sections\">\r\n @for (section of prog.sections; track section.label) {\r\n <div class=\"prog-section\">\r\n <div class=\"prog-section-label\">{{ section.label }}</div>\r\n <div class=\"prog-chords\">\r\n @for (chord of section.chords; track $index; let last = $last) {\r\n <span class=\"prog-chord\">{{ chord }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">→</span> }\r\n }\r\n </div>\r\n <div class=\"prog-numerals\">\r\n @for (num of section.numerals; track $index; let last = $last) {\r\n <span>{{ num }}</span>\r\n @if (!last) { <span class=\"prog-arrow\">→</span> }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n }\r\n</div>\r\n","/*\n * Public API Surface of circle-of-fifths-lib\n */\n\nexport * from './lib/circle-of-fifths';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -10,18 +10,195 @@ interface CircleKey {
|
|
|
10
10
|
interface ChordRow {
|
|
11
11
|
numeral: string;
|
|
12
12
|
chord: string;
|
|
13
|
-
chordType:
|
|
13
|
+
chordType: string;
|
|
14
14
|
role: string;
|
|
15
15
|
state: 'tonic' | 'scale-major' | 'scale-minor' | 'scale-diminished';
|
|
16
16
|
}
|
|
17
|
+
interface Section {
|
|
18
|
+
label: string;
|
|
19
|
+
numerals: string[];
|
|
20
|
+
chords: string[];
|
|
21
|
+
}
|
|
17
22
|
interface Progression {
|
|
18
23
|
name: string;
|
|
19
24
|
mood: string;
|
|
20
25
|
genre: string;
|
|
21
26
|
numerals: string[];
|
|
22
27
|
chords: string[];
|
|
28
|
+
sections: Section[];
|
|
29
|
+
}
|
|
30
|
+
type Language = 'en' | 'es';
|
|
31
|
+
type LocalizedText = Record<Language, string>;
|
|
32
|
+
interface ProgressionDefinition {
|
|
33
|
+
name: LocalizedText;
|
|
34
|
+
mood: LocalizedText;
|
|
35
|
+
genre: LocalizedText;
|
|
36
|
+
numerals: string[];
|
|
37
|
+
}
|
|
38
|
+
declare class ChordSectionComponent {
|
|
39
|
+
readonly chords: _angular_core.InputSignal<string[]>;
|
|
40
|
+
readonly language: _angular_core.InputSignal<Language>;
|
|
41
|
+
readonly text: _angular_core.Signal<{
|
|
42
|
+
readonly title: "Circle of Fifths";
|
|
43
|
+
readonly subtitle: "Click any major or minor key to highlight its diatonic chords";
|
|
44
|
+
readonly ariaLabel: "Circle of Fifths";
|
|
45
|
+
readonly clickKey: "Click a key";
|
|
46
|
+
readonly seeChords: "to see its chords";
|
|
47
|
+
readonly deselect: "↩ click to deselect";
|
|
48
|
+
readonly tonicLegend: "Tonic (I / i)";
|
|
49
|
+
readonly majorLegend: "Major chords (IV, V)";
|
|
50
|
+
readonly minorLegend: "Minor chords (ii, iii, vi)";
|
|
51
|
+
readonly diminishedLegend: "Diminished (vii° / ii°)";
|
|
52
|
+
readonly diatonicChords: "Diatonic Chords";
|
|
53
|
+
readonly commonProgressions: "Common Progressions in";
|
|
54
|
+
readonly generateProgressions: "Shuffle";
|
|
55
|
+
readonly chorus: "Chorus";
|
|
56
|
+
readonly bridge: "Bridge";
|
|
57
|
+
readonly outro: "Outro";
|
|
58
|
+
readonly verse: "Verse";
|
|
59
|
+
readonly copy: "Copy";
|
|
60
|
+
readonly expand: "Expand";
|
|
61
|
+
readonly collapse: "Collapse";
|
|
62
|
+
readonly major: "Major";
|
|
63
|
+
readonly minor: "Minor";
|
|
64
|
+
readonly diminished: "diminished";
|
|
65
|
+
readonly relativeMinor: "Relative minor";
|
|
66
|
+
readonly relativeMajor: "Relative major";
|
|
67
|
+
readonly roles: {
|
|
68
|
+
readonly tonic: "Tonic";
|
|
69
|
+
readonly supertonic: "Supertonic";
|
|
70
|
+
readonly mediant: "Mediant";
|
|
71
|
+
readonly subdominant: "Subdominant";
|
|
72
|
+
readonly dominant: "Dominant";
|
|
73
|
+
readonly submediant: "Submediant";
|
|
74
|
+
readonly leadingTone: "Leading Tone";
|
|
75
|
+
readonly subtonic: "Subtonic";
|
|
76
|
+
};
|
|
77
|
+
} | {
|
|
78
|
+
readonly title: "Círculo de quintas";
|
|
79
|
+
readonly subtitle: "Haz clic en una tonalidad mayor o menor para resaltar sus acordes diatónicos";
|
|
80
|
+
readonly ariaLabel: "Círculo de quintas";
|
|
81
|
+
readonly clickKey: "Haz clic en una tonalidad";
|
|
82
|
+
readonly seeChords: "para ver sus acordes";
|
|
83
|
+
readonly deselect: "↩ haz clic para deseleccionar";
|
|
84
|
+
readonly tonicLegend: "Tónica (I / i)";
|
|
85
|
+
readonly majorLegend: "Acordes mayores (IV, V)";
|
|
86
|
+
readonly minorLegend: "Acordes menores (ii, iii, vi)";
|
|
87
|
+
readonly diminishedLegend: "Disminuido (vii° / ii°)";
|
|
88
|
+
readonly diatonicChords: "Acordes diatónicos";
|
|
89
|
+
readonly commonProgressions: "Progresiones comunes en";
|
|
90
|
+
readonly generateProgressions: "Generar nuevos";
|
|
91
|
+
readonly chorus: "Coro";
|
|
92
|
+
readonly bridge: "Puente";
|
|
93
|
+
readonly outro: "Final";
|
|
94
|
+
readonly verse: "Estrofa";
|
|
95
|
+
readonly copy: "Copiar";
|
|
96
|
+
readonly expand: "Expandir";
|
|
97
|
+
readonly collapse: "Colapsar";
|
|
98
|
+
readonly major: "Mayor";
|
|
99
|
+
readonly minor: "Menor";
|
|
100
|
+
readonly diminished: "disminuido";
|
|
101
|
+
readonly relativeMinor: "Relativa menor";
|
|
102
|
+
readonly relativeMajor: "Relativa mayor";
|
|
103
|
+
readonly roles: {
|
|
104
|
+
readonly tonic: "Tónica";
|
|
105
|
+
readonly supertonic: "Supertónica";
|
|
106
|
+
readonly mediant: "Mediante";
|
|
107
|
+
readonly subdominant: "Subdominante";
|
|
108
|
+
readonly dominant: "Dominante";
|
|
109
|
+
readonly submediant: "Submediante";
|
|
110
|
+
readonly leadingTone: "Sensible";
|
|
111
|
+
readonly subtonic: "Subtónica";
|
|
112
|
+
};
|
|
113
|
+
}>;
|
|
114
|
+
readonly match: _angular_core.Signal<{
|
|
115
|
+
idx: number;
|
|
116
|
+
type: "major" | "minor";
|
|
117
|
+
score: number;
|
|
118
|
+
} | null>;
|
|
119
|
+
readonly rows: _angular_core.Signal<ChordRow[]>;
|
|
120
|
+
readonly info: _angular_core.Signal<{
|
|
121
|
+
fullName: string;
|
|
122
|
+
relativeKey: string;
|
|
123
|
+
} | null>;
|
|
124
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ChordSectionComponent, never>;
|
|
125
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ChordSectionComponent, "chord-section", never, { "chords": { "alias": "chords"; "required": false; "isSignal": true; }; "language": { "alias": "language"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
23
126
|
}
|
|
24
127
|
declare class CircleOfFifthsComponent {
|
|
128
|
+
readonly language: _angular_core.InputSignal<Language>;
|
|
129
|
+
readonly text: _angular_core.Signal<{
|
|
130
|
+
readonly title: "Circle of Fifths";
|
|
131
|
+
readonly subtitle: "Click any major or minor key to highlight its diatonic chords";
|
|
132
|
+
readonly ariaLabel: "Circle of Fifths";
|
|
133
|
+
readonly clickKey: "Click a key";
|
|
134
|
+
readonly seeChords: "to see its chords";
|
|
135
|
+
readonly deselect: "↩ click to deselect";
|
|
136
|
+
readonly tonicLegend: "Tonic (I / i)";
|
|
137
|
+
readonly majorLegend: "Major chords (IV, V)";
|
|
138
|
+
readonly minorLegend: "Minor chords (ii, iii, vi)";
|
|
139
|
+
readonly diminishedLegend: "Diminished (vii° / ii°)";
|
|
140
|
+
readonly diatonicChords: "Diatonic Chords";
|
|
141
|
+
readonly commonProgressions: "Common Progressions in";
|
|
142
|
+
readonly generateProgressions: "Shuffle";
|
|
143
|
+
readonly chorus: "Chorus";
|
|
144
|
+
readonly bridge: "Bridge";
|
|
145
|
+
readonly outro: "Outro";
|
|
146
|
+
readonly verse: "Verse";
|
|
147
|
+
readonly copy: "Copy";
|
|
148
|
+
readonly expand: "Expand";
|
|
149
|
+
readonly collapse: "Collapse";
|
|
150
|
+
readonly major: "Major";
|
|
151
|
+
readonly minor: "Minor";
|
|
152
|
+
readonly diminished: "diminished";
|
|
153
|
+
readonly relativeMinor: "Relative minor";
|
|
154
|
+
readonly relativeMajor: "Relative major";
|
|
155
|
+
readonly roles: {
|
|
156
|
+
readonly tonic: "Tonic";
|
|
157
|
+
readonly supertonic: "Supertonic";
|
|
158
|
+
readonly mediant: "Mediant";
|
|
159
|
+
readonly subdominant: "Subdominant";
|
|
160
|
+
readonly dominant: "Dominant";
|
|
161
|
+
readonly submediant: "Submediant";
|
|
162
|
+
readonly leadingTone: "Leading Tone";
|
|
163
|
+
readonly subtonic: "Subtonic";
|
|
164
|
+
};
|
|
165
|
+
} | {
|
|
166
|
+
readonly title: "Círculo de quintas";
|
|
167
|
+
readonly subtitle: "Haz clic en una tonalidad mayor o menor para resaltar sus acordes diatónicos";
|
|
168
|
+
readonly ariaLabel: "Círculo de quintas";
|
|
169
|
+
readonly clickKey: "Haz clic en una tonalidad";
|
|
170
|
+
readonly seeChords: "para ver sus acordes";
|
|
171
|
+
readonly deselect: "↩ haz clic para deseleccionar";
|
|
172
|
+
readonly tonicLegend: "Tónica (I / i)";
|
|
173
|
+
readonly majorLegend: "Acordes mayores (IV, V)";
|
|
174
|
+
readonly minorLegend: "Acordes menores (ii, iii, vi)";
|
|
175
|
+
readonly diminishedLegend: "Disminuido (vii° / ii°)";
|
|
176
|
+
readonly diatonicChords: "Acordes diatónicos";
|
|
177
|
+
readonly commonProgressions: "Progresiones comunes en";
|
|
178
|
+
readonly generateProgressions: "Generar nuevos";
|
|
179
|
+
readonly chorus: "Coro";
|
|
180
|
+
readonly bridge: "Puente";
|
|
181
|
+
readonly outro: "Final";
|
|
182
|
+
readonly verse: "Estrofa";
|
|
183
|
+
readonly copy: "Copiar";
|
|
184
|
+
readonly expand: "Expandir";
|
|
185
|
+
readonly collapse: "Colapsar";
|
|
186
|
+
readonly major: "Mayor";
|
|
187
|
+
readonly minor: "Menor";
|
|
188
|
+
readonly diminished: "disminuido";
|
|
189
|
+
readonly relativeMinor: "Relativa menor";
|
|
190
|
+
readonly relativeMajor: "Relativa mayor";
|
|
191
|
+
readonly roles: {
|
|
192
|
+
readonly tonic: "Tónica";
|
|
193
|
+
readonly supertonic: "Supertónica";
|
|
194
|
+
readonly mediant: "Mediante";
|
|
195
|
+
readonly subdominant: "Subdominante";
|
|
196
|
+
readonly dominant: "Dominante";
|
|
197
|
+
readonly submediant: "Submediante";
|
|
198
|
+
readonly leadingTone: "Sensible";
|
|
199
|
+
readonly subtonic: "Subtónica";
|
|
200
|
+
};
|
|
201
|
+
}>;
|
|
25
202
|
readonly CX = 300;
|
|
26
203
|
readonly CY = 300;
|
|
27
204
|
readonly KEY_SIG: {
|
|
@@ -39,8 +216,20 @@ declare class CircleOfFifthsComponent {
|
|
|
39
216
|
readonly keys: CircleKey[];
|
|
40
217
|
selectedIndex: _angular_core.WritableSignal<number | null>;
|
|
41
218
|
selectedType: _angular_core.WritableSignal<"major" | "minor" | null>;
|
|
219
|
+
readonly activeProgressionDefs: _angular_core.WritableSignal<ProgressionDefinition[] | null>;
|
|
220
|
+
readonly expandedCards: _angular_core.WritableSignal<Set<string>>;
|
|
42
221
|
private get slice();
|
|
43
222
|
selectKey(index: number, type: 'major' | 'minor'): void;
|
|
223
|
+
randomizeProgressions(): void;
|
|
224
|
+
toggleCard(name: string): void;
|
|
225
|
+
copyChords(prog: {
|
|
226
|
+
name: string;
|
|
227
|
+
chords: string[];
|
|
228
|
+
sections: {
|
|
229
|
+
label: string;
|
|
230
|
+
chords: string[];
|
|
231
|
+
}[];
|
|
232
|
+
}): void;
|
|
44
233
|
getMajorState(index: number): string;
|
|
45
234
|
getMinorState(index: number): string;
|
|
46
235
|
getMajorRole(index: number): string;
|
|
@@ -50,18 +239,19 @@ declare class CircleOfFifthsComponent {
|
|
|
50
239
|
x: number;
|
|
51
240
|
y: number;
|
|
52
241
|
};
|
|
53
|
-
|
|
242
|
+
get chordTable(): ChordRow[];
|
|
243
|
+
get chordNames(): string[];
|
|
54
244
|
getAccidentalText(key: CircleKey): string;
|
|
55
245
|
get selectedInfo(): {
|
|
56
246
|
name: string;
|
|
57
|
-
scale:
|
|
247
|
+
scale: "Major" | "Minor" | "Mayor" | "Menor";
|
|
58
248
|
fullName: string;
|
|
59
249
|
relativeKey: string;
|
|
60
250
|
} | null;
|
|
61
|
-
|
|
251
|
+
private deriveSections;
|
|
62
252
|
get progressions(): Progression[];
|
|
63
253
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CircleOfFifthsComponent, never>;
|
|
64
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CircleOfFifthsComponent, "the-chords-circle-of-fifths", never, {}, {}, never, never, true, never>;
|
|
254
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CircleOfFifthsComponent, "the-chords-circle-of-fifths", never, { "language": { "alias": "language"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
65
255
|
}
|
|
66
256
|
|
|
67
|
-
export { CircleOfFifthsComponent };
|
|
257
|
+
export { ChordSectionComponent, CircleOfFifthsComponent };
|