@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: '<the-chords-circle-of-fifths />'
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 { signal, Component } from '@angular/core';
2
+ import { input, computed, Component, signal } from '@angular/core';
3
3
  import { NgClass } from '@angular/common';
4
4
 
5
- const MAJOR_PROGRESSIONS = [
6
- { name: 'Anthemic', mood: '😄 Joyful', genre: 'Folk, Rock', numerals: ['I', 'IV', 'V', 'I'] },
7
- { name: 'Hopeful', mood: '✨ Hopeful', genre: 'Pop, Indie', numerals: ['I', 'V', 'vi', 'IV'] },
8
- { name: 'Melancholic', mood: '😢 Emotional', genre: 'Ballads, Pop', numerals: ['vi', 'IV', 'I', 'V'] },
9
- { name: 'Cinematic', mood: '🎬 Tense', genre: 'Film, Jazz', numerals: ['ii', 'V', 'vii°', 'I'] },
10
- ];
11
- const MINOR_PROGRESSIONS = [
12
- { name: 'Brooding', mood: '🌑 Dark', genre: 'Rock, Metal', numerals: ['i', 'VII', 'VI', 'VII'] },
13
- { name: 'Haunting', mood: '👻 Mysterious', genre: 'Film, Gothic', numerals: ['i', 'iv', 'VII', 'III'] },
14
- { name: 'Driving', mood: '⚡ Urgent', genre: 'Pop, EDM', numerals: ['i', 'VI', 'III', 'VII'] },
15
- { name: 'Wistful', mood: '🌙 Wistful', genre: 'Cinematic', numerals: ['i', 'v', 'VI', 'VII'] },
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
- dimChord(idx) {
157
- // The diminished chord root is the minor key two steps clockwise (idx+2)
158
- const minor = this.keys[(idx + 2) % 12].minor;
159
- return minor.replace(/m$/, '°');
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 = this.keys[idx];
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: type === 'major' ? 'Major' : 'Minor',
177
- fullName: type === 'major' ? `${key.major} Major` : `${key.minor} Minor`,
463
+ scale,
464
+ fullName: `${type === 'major' ? key.major : key.minor} ${scale}`,
178
465
  relativeKey: type === 'major'
179
- ? `Relative minor: ${key.minor}`
180
- : `Relative major: ${key.major}`,
466
+ ? `${copy.relativeMinor}: ${key.minor}`
467
+ : `${copy.relativeMajor}: ${key.major}`,
181
468
  };
182
469
  }
183
- get chordTable() {
184
- const idx = this.selectedIndex();
470
+ deriveSections(numerals, lookup) {
185
471
  const type = this.selectedType();
186
- if (idx === null || type === null)
187
- return [];
188
- const prev = (idx - 1 + 12) % 12;
189
- const next = (idx + 1) % 12;
190
- if (type === 'major') {
191
- return [
192
- { numeral: 'I', chord: this.keys[idx].major, chordType: 'major', role: 'Tonic', state: 'tonic' },
193
- { numeral: 'ii', chord: this.keys[prev].minor, chordType: 'minor', role: 'Supertonic', state: 'scale-minor' },
194
- { numeral: 'iii', chord: this.keys[next].minor, chordType: 'minor', role: 'Mediant', state: 'scale-minor' },
195
- { numeral: 'IV', chord: this.keys[prev].major, chordType: 'major', role: 'Subdominant', state: 'scale-major' },
196
- { numeral: 'V', chord: this.keys[next].major, chordType: 'major', role: 'Dominant', state: 'scale-major' },
197
- { numeral: 'vi', chord: this.keys[idx].minor, chordType: 'minor', role: 'Submediant', state: 'scale-minor' },
198
- { numeral: 'vii°', chord: this.dimChord(idx), chordType: 'diminished', role: 'Leading Tone', state: 'scale-diminished' },
199
- ];
200
- }
201
- else {
202
- return [
203
- { numeral: 'i', chord: this.keys[idx].minor, chordType: 'minor', role: 'Tonic', state: 'tonic' },
204
- { numeral: 'iv', chord: this.keys[prev].minor, chordType: 'minor', role: 'Subdominant', state: 'scale-minor' },
205
- { numeral: 'v', chord: this.keys[next].minor, chordType: 'minor', role: 'Dominant', state: 'scale-minor' },
206
- { numeral: 'III', chord: this.keys[idx].major, chordType: 'major', role: 'Mediant', state: 'scale-major' },
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 defs = this.selectedType() === 'major' ? MAJOR_PROGRESSIONS : MINOR_PROGRESSIONS;
219
- return defs.map(d => ({ ...d, chords: d.numerals.map(n => lookup.get(n) ?? n) }));
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":";;;;AA4CA,MAAM,YAAY,GAAuD;AACvE,IAAA,KAAK,EAAE;AACL,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE;YACrC,IAAI,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE;YAC1C,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE;YAC7C,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC;AAChC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,eAAe,EAAE;YAC5C,IAAI,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,iBAAiB,EAAE;YAChD,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE;YAC7C,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;AACjC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,aAAa,EAAE;YAC9C,IAAI,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,EAAE;YAChD,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,EAAE;YACjD,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC;AACjC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,EAAE;YAC3C,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE;YACxC,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE;YAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC;AACnC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE;YACtC,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE;YACxC,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,EAAE;YACjD,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC;AACjC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE;YACtC,IAAI,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE;YAC1C,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,EAAE;YACjD,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC;AACnC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE;YACpC,IAAI,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,EAAE;YAChD,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,EAAE;YACjD,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC;AACjC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,WAAW,EAAE;YAC5C,IAAI,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,cAAc,EAAE;YAClD,KAAK,EAAE,EAAE,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE,yBAAyB,EAAE;YAC/D,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC;AAClC,SAAA;AACF,KAAA;AACD,IAAA,KAAK,EAAE;AACL,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE;YACvC,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,WAAW,EAAE;YACxC,KAAK,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,aAAa,EAAE;YAC/C,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC;AACpC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,aAAa,EAAE;YAC3C,IAAI,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,eAAe,EAAE;YAClD,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,EAAE;YACjD,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC;AACpC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,WAAW,EAAE;YACxC,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,WAAW,EAAE;YACzC,KAAK,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE;YACzC,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC;AACpC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,YAAY,EAAE;YACzC,IAAI,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,eAAe,EAAE;YAC/C,KAAK,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,EAAE;YAC5C,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC;AAClC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE;YACxC,IAAI,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,eAAe,EAAE;YAClD,KAAK,EAAE,EAAE,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE,mBAAmB,EAAE;YACzD,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC;AACnC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE;YACrC,IAAI,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,EAAE;YAC3C,KAAK,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,gBAAgB,EAAE;YACvD,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC;AAChC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE;YACjC,IAAI,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,EAAE;YAC3C,KAAK,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,kBAAkB,EAAE;YACzD,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC;AACpC,SAAA;AACD,QAAA;YACE,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE;YACtC,IAAI,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,cAAc,EAAE;YAC/C,KAAK,EAAE,EAAE,EAAE,EAAE,oBAAoB,EAAE,EAAE,EAAE,oBAAoB,EAAE;YAC7D,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC;AACnC,SAAA;AACF,KAAA;CACF;AAED,MAAM,IAAI,GAAG;AACX,IAAA,EAAE,EAAE;AACF,QAAA,KAAK,EAAE,kBAAkB;AACzB,QAAA,QAAQ,EAAE,+DAA+D;AACzE,QAAA,SAAS,EAAE,kBAAkB;AAC7B,QAAA,QAAQ,EAAE,aAAa;AACvB,QAAA,SAAS,EAAE,mBAAmB;AAC9B,QAAA,QAAQ,EAAE,qBAAqB;AAC/B,QAAA,WAAW,EAAE,eAAe;AAC5B,QAAA,WAAW,EAAE,sBAAsB;AACnC,QAAA,WAAW,EAAE,4BAA4B;AACzC,QAAA,gBAAgB,EAAE,yBAAyB;AAC3C,QAAA,cAAc,EAAE,iBAAiB;AACjC,QAAA,kBAAkB,EAAE,wBAAwB;AAC5C,QAAA,oBAAoB,EAAE,SAAS;AAC/B,QAAA,MAAM,EAAE,QAAQ;AAChB,QAAA,MAAM,EAAE,QAAQ;AAChB,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,IAAI,EAAE,MAAM;AACZ,QAAA,MAAM,EAAE,QAAQ;AAChB,QAAA,QAAQ,EAAE,UAAU;AACpB,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,UAAU,EAAE,YAAY;AACxB,QAAA,aAAa,EAAE,gBAAgB;AAC/B,QAAA,aAAa,EAAE,gBAAgB;AAC/B,QAAA,KAAK,EAAE;AACL,YAAA,KAAK,EAAE,OAAO;AACd,YAAA,UAAU,EAAE,YAAY;AACxB,YAAA,OAAO,EAAE,SAAS;AAClB,YAAA,WAAW,EAAE,aAAa;AAC1B,YAAA,QAAQ,EAAE,UAAU;AACpB,YAAA,UAAU,EAAE,YAAY;AACxB,YAAA,WAAW,EAAE,cAAc;AAC3B,YAAA,QAAQ,EAAE,UAAU;AACrB,SAAA;AACF,KAAA;AACD,IAAA,EAAE,EAAE;AACF,QAAA,KAAK,EAAE,oBAAoB;AAC3B,QAAA,QAAQ,EAAE,8EAA8E;AACxF,QAAA,SAAS,EAAE,oBAAoB;AAC/B,QAAA,QAAQ,EAAE,2BAA2B;AACrC,QAAA,SAAS,EAAE,sBAAsB;AACjC,QAAA,QAAQ,EAAE,+BAA+B;AACzC,QAAA,WAAW,EAAE,gBAAgB;AAC7B,QAAA,WAAW,EAAE,yBAAyB;AACtC,QAAA,WAAW,EAAE,+BAA+B;AAC5C,QAAA,gBAAgB,EAAE,yBAAyB;AAC3C,QAAA,cAAc,EAAE,oBAAoB;AACpC,QAAA,kBAAkB,EAAE,yBAAyB;AAC7C,QAAA,oBAAoB,EAAE,gBAAgB;AACtC,QAAA,MAAM,EAAE,MAAM;AACd,QAAA,MAAM,EAAE,QAAQ;AAChB,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,IAAI,EAAE,QAAQ;AACd,QAAA,MAAM,EAAE,UAAU;AAClB,QAAA,QAAQ,EAAE,UAAU;AACpB,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,KAAK,EAAE,OAAO;AACd,QAAA,UAAU,EAAE,YAAY;AACxB,QAAA,aAAa,EAAE,gBAAgB;AAC/B,QAAA,aAAa,EAAE,gBAAgB;AAC/B,QAAA,KAAK,EAAE;AACL,YAAA,KAAK,EAAE,QAAQ;AACf,YAAA,UAAU,EAAE,aAAa;AACzB,YAAA,OAAO,EAAE,UAAU;AACnB,YAAA,WAAW,EAAE,cAAc;AAC3B,YAAA,QAAQ,EAAE,WAAW;AACrB,YAAA,UAAU,EAAE,aAAa;AACzB,YAAA,WAAW,EAAE,UAAU;AACvB,YAAA,QAAQ,EAAE,WAAW;AACtB,SAAA;AACF,KAAA;CACO;AAEV,MAAM,IAAI,GAAgB;AACxB,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC1D,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC1D,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC1D,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC3D,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC3D,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC3D,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC5D,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC5D,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC3D,IAAA,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC3D,IAAA,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;AAC5D,IAAA,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;CAC5D;AAED,SAAS,QAAQ,CAAC,GAAW,EAAA;;AAE3B,IAAA,OAAO,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;AACtD;AAEA,SAAS,cAAc,CAAC,GAAW,EAAE,IAAuB,EAAE,IAA6B,EAAA;IACzF,MAAM,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE;IAChC,MAAM,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE;AAC3B,IAAA,IAAI,IAAI,KAAK,OAAO,EAAE;QACpB,OAAO;AACL,YAAA,EAAE,OAAO,EAAE,GAAG,EAAK,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAG,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAQ,KAAK,EAAE,OAAO,EAAE;AACtH,YAAA,EAAE,OAAO,EAAE,IAAI,EAAI,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAG,KAAK,EAAE,aAAa,EAAE;AAC5H,YAAA,EAAE,OAAO,EAAE,KAAK,EAAG,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAM,KAAK,EAAE,aAAa,EAAE;AAC5H,YAAA,EAAE,OAAO,EAAE,IAAI,EAAI,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,aAAa,EAAE;AAC5H,YAAA,EAAE,OAAO,EAAE,GAAG,EAAK,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAK,KAAK,EAAE,aAAa,EAAE;AAC5H,YAAA,EAAE,OAAO,EAAE,IAAI,EAAI,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAG,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAG,KAAK,EAAE,aAAa,EAAE;AAC5H,YAAA,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAK,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,kBAAkB,EAAE;SAClI;IACH;SAAO;QACL,OAAO;AACL,YAAA,EAAE,OAAO,EAAE,GAAG,EAAI,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAG,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAQ,KAAK,EAAE,OAAO,EAAE;AACrH,YAAA,EAAE,OAAO,EAAE,IAAI,EAAG,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,aAAa,EAAE;AAC3H,YAAA,EAAE,OAAO,EAAE,GAAG,EAAI,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAK,KAAK,EAAE,aAAa,EAAE;AAC3H,YAAA,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAG,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAM,KAAK,EAAE,aAAa,EAAE;AAC3H,YAAA,EAAE,OAAO,EAAE,IAAI,EAAG,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAG,KAAK,EAAE,aAAa,EAAE;AAC3H,YAAA,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAO,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAK,KAAK,EAAE,aAAa,EAAE;AAC3H,YAAA,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAK,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAG,KAAK,EAAE,kBAAkB,EAAE;SACjI;IACH;AACF;MASa,qBAAqB,CAAA;IACvB,MAAM,GAAG,KAAK,CAAW,EAAE;+EAAC;IAC5B,QAAQ,GAAG,KAAK,CAAW,IAAI;iFAAC;AAChC,IAAA,IAAI,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;6EAAC;AAE5C,IAAA,KAAK,GAAG,QAAQ,CAAC,MAAK;AAC7B,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE;QAC3B,IAAI,CAAC,KAAK,CAAC,MAAM;AAAE,YAAA,OAAO,IAAI;AAC9B,QAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC;QAC/B,IAAI,IAAI,GAAmE,IAAI;AAC/E,QAAA,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,EAAE,GAAG,EAAE,EAAE;YACjC,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,OAAO,CAAU,EAAE;;AAE9C,gBAAA,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;AAC5F,gBAAA,IAAI,CAAC,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK;oBAAE,IAAI,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE;YAC9D;QACF;AACA,QAAA,OAAO,IAAI,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI;IAC7C,CAAC;8EAAC;AAEO,IAAA,IAAI,GAAG,QAAQ,CAAC,MAAK;AAC5B,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE;AACtB,QAAA,IAAI,CAAC,CAAC;AAAE,YAAA,OAAO,EAAE;AACjB,QAAA,OAAO,cAAc,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACnD,CAAC;6EAAC;AAEO,IAAA,IAAI,GAAG,QAAQ,CAAC,MAAK;AAC5B,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE;AACtB,QAAA,IAAI,CAAC,CAAC;AAAE,YAAA,OAAO,IAAI;AACnB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;QACxB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QACvB,OAAO;AACL,YAAA,QAAQ,EAAE,CAAA,EAAG,CAAC,CAAC,IAAI,KAAK,OAAO,GAAG,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAA,CAAA,EAAI,CAAC,CAAC,IAAI,KAAK,OAAO,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA,CAAE;AACzG,YAAA,WAAW,EAAE,CAAC,CAAC,IAAI,KAAK;kBACpB,GAAG,IAAI,CAAC,aAAa,CAAA,EAAA,EAAK,GAAG,CAAC,KAAK,CAAA;kBACnC,GAAG,IAAI,CAAC,aAAa,CAAA,EAAA,EAAK,GAAG,CAAC,KAAK,CAAA,CAAE;SAC1C;IACH,CAAC;6EAAC;uGArCS,qBAAqB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAArB,qBAAqB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECrRlC,ksBAkBA,EAAA,MAAA,EAAA,CAAA,kvFAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,ED+PY,OAAO,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAIN,qBAAqB,EAAA,UAAA,EAAA,CAAA;kBAPjC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,eAAe,EAAA,UAAA,EACb,IAAI,EAAA,OAAA,EACP,CAAC,OAAO,CAAC,EAAA,QAAA,EAAA,ksBAAA,EAAA,MAAA,EAAA,CAAA,kvFAAA,CAAA,EAAA;;MAoDP,uBAAuB,CAAA;IACzB,QAAQ,GAAG,KAAK,CAAW,IAAI;iFAAC;AAChC,IAAA,IAAI,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;6EAAC;IAC5C,EAAE,GAAG,GAAG;IACR,EAAE,GAAG,GAAG;IAER,OAAO,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE;IACpC,KAAK,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE;IAClC,KAAK,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE;IAElC,IAAI,GAAG,IAAI;IAEpB,aAAa,GAAG,MAAM,CAAgB,IAAI;sFAAC;IAC3C,YAAY,GAAG,MAAM,CAA2B,IAAI;qFAAC;IAC5C,qBAAqB,GAAG,MAAM,CAAiC,IAAI;8FAAC;AACpE,IAAA,aAAa,GAAG,MAAM,CAAc,IAAI,GAAG,EAAE;sFAAC;AAEvD,IAAA,IAAY,KAAK,GAAA;AACf,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE;QAChC,IAAI,GAAG,KAAK,IAAI;AAAE,YAAA,OAAO,IAAI;QAC7B,OAAO;YACL,IAAI,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE;AACzB,YAAA,IAAI,EAAE,GAAG;AACT,YAAA,IAAI,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE;SACrB;IACH;IAEA,SAAS,CAAC,KAAa,EAAE,IAAuB,EAAA;AAC9C,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;AAClE,YAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;AAC5B,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;QAC7B;aAAO;AACL,YAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC;AAC7B,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;QAC7B;AACA,QAAA,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC;QACpC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;IACnC;IAEA,qBAAqB,GAAA;AACnB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;AAChC,QAAA,IAAI,CAAC,IAAI;YAAE;;AAEX,QAAA,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACnG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;IACnC;AAEA,IAAA,UAAU,CAAC,IAAY,EAAA;QACrB,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,KAAI;AAChC,YAAA,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC;YACzB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;AACnD,YAAA,OAAO,IAAI;AACb,QAAA,CAAC,CAAC;IACJ;AAEA,IAAA,UAAU,CAAC,IAAyF,EAAA;AAClG,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI;cAC3C,CAAC,CAAA,EAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA,EAAA,EAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAA,EAAG,CAAC,CAAC,KAAK,CAAA,EAAA,EAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI;cAChI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;AAC1B,QAAA,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC;IACrC;AAEA,IAAA,aAAa,CAAC,KAAa,EAAA;AACzB,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK;AACpB,QAAA,IAAI,CAAC,CAAC;AAAE,YAAA,OAAO,EAAE;AACjB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;AAChC,QAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;YAAE,OAAO,IAAI,KAAK,OAAO,GAAG,OAAO,GAAG,aAAa;QACvE,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,YAAA,OAAO,aAAa;AAC9D,QAAA,OAAO,EAAE;IACX;AAEA,IAAA,aAAa,CAAC,KAAa,EAAA;AACzB,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK;AACpB,QAAA,IAAI,CAAC,CAAC;AAAE,YAAA,OAAO,EAAE;AACjB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;QAChC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE;AAChC,QAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;YAAE,OAAO,IAAI,KAAK,OAAO,GAAG,OAAO,GAAG,aAAa;QACvE,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,YAAA,OAAO,aAAa;QAC9D,IAAI,KAAK,KAAK,MAAM;AAAE,YAAA,OAAO,kBAAkB;AAC/C,QAAA,OAAO,EAAE;IACX;AAEA,IAAA,YAAY,CAAC,KAAa,EAAA;AACxB,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK;AACpB,QAAA,IAAI,CAAC,CAAC;AAAE,YAAA,OAAO,EAAE;AACjB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;AAChC,QAAA,IAAI,IAAI,KAAK,OAAO,EAAE;AACpB,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,GAAG;AAChC,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,IAAI;AACjC,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,GAAG;QAClC;aAAO;AACL,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,KAAK;AAClC,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,IAAI;AACjC,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,KAAK;QACpC;AACA,QAAA,OAAO,EAAE;IACX;AAEA,IAAA,YAAY,CAAC,KAAa,EAAA;AACxB,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK;AACpB,QAAA,IAAI,CAAC,CAAC;AAAE,YAAA,OAAO,EAAE;AACjB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;QAChC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE;AAChC,QAAA,IAAI,IAAI,KAAK,OAAO,EAAE;AACpB,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,IAAI;AACjC,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,IAAI;AACjC,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,KAAK;YAClC,IAAI,KAAK,KAAK,MAAM;AAAE,gBAAA,OAAO,MAAM;QACrC;aAAO;AACL,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,GAAG;AAChC,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,IAAI;AACjC,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAAE,gBAAA,OAAO,GAAG;YAChC,IAAI,KAAK,KAAK,MAAM;AAAE,gBAAA,OAAO,KAAK;QACpC;AACA,QAAA,OAAO,EAAE;IACX;IAEA,OAAO,CAAC,MAAc,EAAE,MAAc,EAAE,KAAa,EAAE,GAAG,GAAG,GAAG,EAAA;AAC9D,QAAA,MAAM,GAAG,GAAG,KAAK,GAAG,EAAE,GAAG,EAAE;AAC3B,QAAA,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC;AAC5C,QAAA,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,GAAG,KAAK,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,EAChB,EAAE,GAAG,IAAI,CAAC,EAAE;QACd,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAClC,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAChC,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAClC,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAChC,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAClC,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAChC,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAClC,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAChC,QAAA,MAAM,CAAC,GAAG,CAAC,CAAS,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QACrC,OAAO,CAAA,CAAA,EAAI,CAAC,CAAC,EAAE,CAAC,CAAA,CAAA,EAAI,CAAC,CAAC,EAAE,CAAC,CAAA,EAAA,EAAK,MAAM,CAAA,CAAA,EAAI,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,CAAA,CAAA,EAAI,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAA,CAAA,EAAI,CAAC,CAAC,EAAE,CAAC,CAAA,EAAA,EAAK,MAAM,CAAA,CAAA,EAAI,MAAM,CAAA,OAAA,EAAU,CAAC,CAAC,EAAE,CAAC,CAAA,CAAA,EAAI,CAAC,CAAC,EAAE,CAAC,CAAA,CAAA,CAAG;IAC3I;IAEA,OAAO,CAAC,IAAY,EAAE,KAAa,EAAA;AACjC,QAAA,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC;QAC7C,OAAO;AACL,YAAA,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/B,YAAA,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;SAChC;IACH;AAGA,IAAA,IAAI,UAAU,GAAA;AACZ,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE;AAChC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;AAChC,QAAA,IAAI,GAAG,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;AAAE,YAAA,OAAO,EAAE;QAC5C,OAAO,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC/C;AAEA,IAAA,IAAI,UAAU,GAAA;AACZ,QAAA,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC;IAC5C;AAEA,IAAA,iBAAiB,CAAC,GAAc,EAAA;AAC9B,QAAA,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;AAAE,YAAA,OAAO,CAAA,EAAG,GAAG,CAAC,MAAM,GAAG;AAC3C,QAAA,IAAI,GAAG,CAAC,KAAK,GAAG,CAAC;AAAE,YAAA,OAAO,CAAA,EAAG,GAAG,CAAC,KAAK,GAAG;AACzC,QAAA,OAAO,GAAG;IACZ;AAEA,IAAA,IAAI,YAAY,GAAA;AACd,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE;AAChC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;AAChC,QAAA,IAAI,GAAG,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;AAAE,YAAA,OAAO,IAAI;AAC9C,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;AACrB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;AACxB,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK;QACxD,OAAO;AACL,YAAA,IAAI,EAAE,IAAI,KAAK,OAAO,GAAG,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK;YAC9C,KAAK;AACL,YAAA,QAAQ,EAAE,CAAA,EAAG,IAAI,KAAK,OAAO,GAAG,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE;YAChE,WAAW,EACT,IAAI,KAAK;kBACL,GAAG,IAAI,CAAC,aAAa,CAAA,EAAA,EAAK,GAAG,CAAC,KAAK,CAAA;kBACnC,GAAG,IAAI,CAAC,aAAa,CAAA,EAAA,EAAK,GAAG,CAAC,KAAK,CAAA,CAAE;SAC5C;IACH;IAEQ,cAAc,CAAC,QAAkB,EAAE,MAA2B,EAAA;AACpE,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAG;AACjC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;AACxB,QAAA,MAAM,OAAO,GAAG,CAAC,EAAY,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;;AAG/F,QAAA,MAAM,WAAW,GAAG,IAAI,KAAK,OAAO,GAAG,IAAI,GAAG,IAAI;QAClD,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC;AAC/C,QAAA,MAAM,MAAM,GAAG,SAAS,GAAG;cACvB,OAAO,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACzE,cAAE,OAAO,CAAC,IAAI,KAAK,OAAO,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;;AAGjF,QAAA,MAAM,WAAW,GAAG,IAAI,KAAK,OAAO,GAAG,IAAI,GAAG,KAAK;QACnD,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC;AAC/C,QAAA,MAAM,MAAM,GAAG,SAAS,IAAI;cACxB,OAAO,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACzE,cAAE,OAAO,CAAC,IAAI,KAAK,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;;AAGpF,QAAA,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,KAAK,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAEzF,OAAO;YACL,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE;YACjC,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE;YACjC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,KAAK,EAAE;SAChC;IACH;AAEA,IAAA,IAAI,YAAY,GAAA;AACd,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU;QAC7B,IAAI,CAAC,KAAK,CAAC,MAAM;AAAE,YAAA,OAAO,EAAE;QAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAC9D,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;AAChC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,KAAK,OAAO,GAAG,OAAO,GAAG,OAAO;AAChE,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,qBAAqB,EAAE,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,MAAM;AAC/B,YAAA,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;AAC/B,YAAA,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;AAC/B,YAAA,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;YACjC,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC;YAC5E,QAAQ,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;AAC3D,SAAA,CAAC,CAAC;IACL;uGA/NW,uBAAuB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAvB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,uBAAuB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,6BAAA,EAAA,MAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,WAAA,EAAA,YAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EErUpC,o7OAoLA,EAAA,MAAA,EAAA,CAAA,qpQAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EF4IY,OAAO,oFA3CN,qBAAqB,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,UAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAgDrB,uBAAuB,EAAA,UAAA,EAAA,CAAA;kBARnC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,6BAA6B,EAAA,UAAA,EAC3B,IAAI,EAAA,OAAA,EACP,CAAC,OAAO,EAAE,qBAAqB,CAAC,EAAA,IAAA,EAGnC,EAAE,aAAa,EAAE,YAAY,EAAE,EAAA,QAAA,EAAA,o7OAAA,EAAA,MAAA,EAAA,CAAA,qpQAAA,CAAA,EAAA;;;AGnUvC;;AAEG;;ACFH;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gblp/circle-of-fifths",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -10,18 +10,195 @@ interface CircleKey {
10
10
  interface ChordRow {
11
11
  numeral: string;
12
12
  chord: string;
13
- chordType: 'major' | 'minor' | 'diminished';
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
- private dimChord;
242
+ get chordTable(): ChordRow[];
243
+ get chordNames(): string[];
54
244
  getAccidentalText(key: CircleKey): string;
55
245
  get selectedInfo(): {
56
246
  name: string;
57
- scale: string;
247
+ scale: "Major" | "Minor" | "Mayor" | "Menor";
58
248
  fullName: string;
59
249
  relativeKey: string;
60
250
  } | null;
61
- get chordTable(): ChordRow[];
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 };