@gblp/chord-finder 0.3.2 → 0.3.4

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
@@ -1,41 +1,41 @@
1
- # @gblp/chord-finder
2
-
3
- Standalone Angular 22 component for finding guitar chords and rendering SVG chord diagrams.
4
-
5
- [View the package on npm](https://www.npmjs.com/package/@gblp/chord-finder)
6
-
7
- ```bash
8
- npm install @gblp/chord-finder
9
- ```
10
-
11
- ```ts
12
- import { Component } from "@angular/core";
13
- import { ChordFinderComponent } from "@gblp/chord-finder";
14
-
15
- @Component({
16
- imports: [ChordFinderComponent],
17
- template: `<the-chords-chord-finder [language]="'es'" />`,
18
- })
19
- export class App {}
20
- ```
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
-
41
- Build the package with `npm run build:lib`. The publishable Angular package is written to `dist/chord-finder`.
1
+ # @gblp/chord-finder
2
+
3
+ Standalone Angular 22 component for finding guitar chords and rendering SVG chord diagrams.
4
+
5
+ [View the package on npm](https://www.npmjs.com/package/@gblp/chord-finder)
6
+
7
+ ```bash
8
+ npm install @gblp/chord-finder
9
+ ```
10
+
11
+ ```ts
12
+ import { Component } from "@angular/core";
13
+ import { ChordFinderComponent } from "@gblp/chord-finder";
14
+
15
+ @Component({
16
+ imports: [ChordFinderComponent],
17
+ template: `<the-chords-chord-finder [language]="'es'" />`,
18
+ })
19
+ export class App {}
20
+ ```
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
+
41
+ Build the package with `npm run build:lib`. The publishable Angular package is written to `dist/chord-finder`.
@@ -110,15 +110,26 @@ const ERROR_COPY = {
110
110
  invalid: 'Invalid chord name. Try C, F#, C#m, Bb, Am7, or Dsus4.',
111
111
  missingRoot: 'Chord root not found in chords-db.',
112
112
  missingType: (suffix) => `The type "${suffix}" is not available for this chord.`,
113
+ tooManySections: (max) => `Too many sections — maximum allowed is ${max}.`,
114
+ tooManyChords: (section, max) => `Section "${section}" exceeds the maximum of ${max} chords.`,
115
+ emptySectionName: 'Section name cannot be empty.',
116
+ emptySection: (name) => `Section "${name}" has no chords.`,
117
+ noValidSections: 'No valid sections found. Expected format: "name: chord1, chord2; name2: chord3".',
113
118
  },
114
119
  es: {
115
120
  invalid: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.',
116
121
  missingRoot: 'Raíz no encontrada en chords-db.',
117
122
  missingType: (suffix) => `El tipo "${suffix}" no existe para este acorde en la base actual.`,
123
+ tooManySections: (max) => `Demasiadas secciones — el máximo permitido es ${max}.`,
124
+ tooManyChords: (section, max) => `La sección "${section}" supera el máximo de ${max} acordes.`,
125
+ emptySectionName: 'El nombre de la sección no puede estar vacío.',
126
+ emptySection: (name) => `La sección "${name}" no tiene acordes.`,
127
+ noValidSections: 'No se encontraron secciones válidas. Formato esperado: "nombre: acorde1, acorde2; nombre2: acorde3".',
118
128
  },
119
129
  };
120
130
  class ChordService {
121
- maxBatchSize = 5;
131
+ MAX_SECTIONS = 6;
132
+ MAX_CHORDS_PER_SECTION = 6;
122
133
  guitarDb = guitarDbJson;
123
134
  dbRootMap = {
124
135
  C: 'C',
@@ -168,15 +179,59 @@ class ChordService {
168
179
  4: 'sus4',
169
180
  };
170
181
  search(input, language = 'en') {
171
- const tokens = input
182
+ return input
172
183
  .split(',')
173
184
  .map((token) => token.trim())
185
+ .filter(Boolean)
186
+ .map((token, index) => this.searchSingle(token, index, language));
187
+ }
188
+ isSectionFormat(input) {
189
+ return input.includes(':');
190
+ }
191
+ searchSections(input, language = 'en') {
192
+ const copy = ERROR_COPY[language];
193
+ const rawSections = input
194
+ .split(';')
195
+ .map((s) => s.trim())
174
196
  .filter(Boolean);
175
- const limitedTokens = tokens.slice(0, this.maxBatchSize);
176
- return {
177
- results: limitedTokens.map((token, index) => this.searchSingle(token, index, language)),
178
- wasLimited: tokens.length > this.maxBatchSize,
179
- };
197
+ if (!rawSections.length) {
198
+ return { sections: [], error: copy.noValidSections };
199
+ }
200
+ if (rawSections.length > this.MAX_SECTIONS) {
201
+ return { sections: [], error: copy.tooManySections(this.MAX_SECTIONS) };
202
+ }
203
+ const sections = [];
204
+ for (const [si, raw] of rawSections.entries()) {
205
+ const colonIndex = raw.indexOf(':');
206
+ if (colonIndex === -1) {
207
+ return { sections: [], error: copy.noValidSections };
208
+ }
209
+ const name = raw.slice(0, colonIndex).trim();
210
+ const chordsStr = raw.slice(colonIndex + 1).trim();
211
+ if (!name) {
212
+ return { sections: [], error: copy.emptySectionName };
213
+ }
214
+ const tokens = chordsStr
215
+ .split(',')
216
+ .map((t) => t.trim())
217
+ .filter(Boolean);
218
+ if (!tokens.length) {
219
+ return { sections: [], error: copy.emptySection(name) };
220
+ }
221
+ if (tokens.length > this.MAX_CHORDS_PER_SECTION) {
222
+ return {
223
+ sections: [],
224
+ error: copy.tooManyChords(name, this.MAX_CHORDS_PER_SECTION),
225
+ };
226
+ }
227
+ sections.push({
228
+ name,
229
+ results: tokens.map((token, ci) =>
230
+ // ponytail: offset by si*10 to keep IDs unique across sections (max 6 chords × 6 sections = 36 < 60)
231
+ this.searchSingle(token, si * 10 + ci, language)),
232
+ });
233
+ }
234
+ return { sections, error: null };
180
235
  }
181
236
  suffixes() {
182
237
  return this.guitarDb.suffixes;
@@ -290,7 +345,10 @@ const COPY = {
290
345
  descriptionEnd: 'separated by commas. Sharps and flats are supported:',
291
346
  inputLabel: 'Chords',
292
347
  exportPng: 'Export PNG',
293
- limited: 'Only the first 5 chords are rendered.',
348
+ bgColor: 'Background',
349
+ lineColor: 'Diagram color',
350
+ transparent: 'Transparent',
351
+ download: 'Download',
294
352
  availableTypes: 'Available chord types:',
295
353
  resultsLabel: 'Chord results',
296
354
  chord: 'Chord',
@@ -309,7 +367,10 @@ const COPY = {
309
367
  descriptionEnd: 'separados por coma. Soporta sostenidos y bemoles:',
310
368
  inputLabel: 'Acordes',
311
369
  exportPng: 'Exportar PNG',
312
- limited: 'Solo se renderizan los primeros 5 acordes.',
370
+ bgColor: 'Fondo',
371
+ lineColor: 'Color del diagrama',
372
+ transparent: 'Transparente',
373
+ download: 'Descargar',
313
374
  availableTypes: 'Tipos de acorde disponibles:',
314
375
  resultsLabel: 'Resultados de acordes',
315
376
  chord: 'Acorde',
@@ -330,20 +391,60 @@ class ChordFinderComponent {
330
391
  query = 'C';
331
392
  results = signal([], /* @ts-ignore */
332
393
  ...(ngDevMode ? [{ debugName: "results" }] : /* istanbul ignore next */ []));
333
- wasLimited = signal(false, /* @ts-ignore */
334
- ...(ngDevMode ? [{ debugName: "wasLimited" }] : /* istanbul ignore next */ []));
394
+ sections = signal([], /* @ts-ignore */
395
+ ...(ngDevMode ? [{ debugName: "sections" }] : /* istanbul ignore next */ []));
396
+ inputMode = signal('plain', /* @ts-ignore */
397
+ ...(ngDevMode ? [{ debugName: "inputMode" }] : /* istanbul ignore next */ []));
398
+ inputError = signal(null, /* @ts-ignore */
399
+ ...(ngDevMode ? [{ debugName: "inputError" }] : /* istanbul ignore next */ []));
335
400
  selectedPositionIndex = {};
336
401
  supportedSuffixes = computed(() => this.chordService.suffixes().slice(0, 18).join(', '), /* @ts-ignore */
337
402
  ...(ngDevMode ? [{ debugName: "supportedSuffixes" }] : /* istanbul ignore next */ []));
338
- resultsRow = viewChild.required('resultsRow');
403
+ hasResults = computed(() => this.inputMode() === 'sections'
404
+ ? this.sections().length > 0
405
+ : this.results().length > 0, /* @ts-ignore */
406
+ ...(ngDevMode ? [{ debugName: "hasResults" }] : /* istanbul ignore next */ []));
407
+ resultsRow = viewChild('resultsRow', /* @ts-ignore */
408
+ ...(ngDevMode ? [{ debugName: "resultsRow" }] : /* istanbul ignore next */ []));
409
+ sectionsContainer = viewChild('sectionsContainer', /* @ts-ignore */
410
+ ...(ngDevMode ? [{ debugName: "sectionsContainer" }] : /* istanbul ignore next */ []));
411
+ exportPanelOpen = signal(false, /* @ts-ignore */
412
+ ...(ngDevMode ? [{ debugName: "exportPanelOpen" }] : /* istanbul ignore next */ []));
413
+ exportBgColor = '#ffffff';
414
+ exportLineColor = '#000000';
415
+ exportTransparent = false;
339
416
  constructor(chordService) {
340
417
  this.chordService = chordService;
341
418
  effect(() => this.runSearch(this.language()));
342
419
  }
343
420
  async exportPng() {
344
- const svgs = Array.from(this.resultsRow().nativeElement.querySelectorAll('svg.chord-svg'));
345
- if (!svgs.length)
346
- return;
421
+ if (this.inputMode() === 'sections') {
422
+ const container = this.sectionsContainer()?.nativeElement;
423
+ if (!container)
424
+ return;
425
+ for (const [i, section] of this.sections().entries()) {
426
+ const sectionEl = container.querySelector(`[data-section="${i}"]`);
427
+ if (!sectionEl)
428
+ continue;
429
+ const svgs = Array.from(sectionEl.querySelectorAll('svg.chord-svg'));
430
+ if (!svgs.length)
431
+ continue;
432
+ const filename = section.name
433
+ .trim()
434
+ .toLowerCase()
435
+ .replace(/\s+/g, '-')
436
+ .replace(/[^a-z0-9-]/g, '') || 'section';
437
+ await this.renderPng(svgs, filename);
438
+ }
439
+ }
440
+ else {
441
+ const svgs = Array.from(this.resultsRow()?.nativeElement.querySelectorAll('svg.chord-svg') ?? []);
442
+ if (!svgs.length)
443
+ return;
444
+ await this.renderPng(svgs, 'chords');
445
+ }
446
+ }
447
+ async renderPng(svgs, filename) {
347
448
  const padding = 32;
348
449
  const gap = 24;
349
450
  const width = 240;
@@ -357,19 +458,25 @@ class ChordFinderComponent {
357
458
  if (!ctx)
358
459
  return;
359
460
  ctx.scale(scale, scale);
360
- ctx.fillStyle = '#ffffff';
361
- ctx.fillRect(0, 0, canvas.width, canvas.height);
461
+ if (!this.exportTransparent) {
462
+ ctx.fillStyle = this.exportBgColor;
463
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
464
+ }
465
+ const bg = this.exportTransparent ? 'transparent' : this.exportBgColor;
466
+ const fg = this.exportLineColor;
467
+ // ponytail: text inside filled dots/barres inverts bg; transparent defaults to white
468
+ const fgInverse = this.exportTransparent ? '#ffffff' : this.exportBgColor;
362
469
  for (const [index, svg] of svgs.entries()) {
363
470
  const clone = svg.cloneNode(true);
364
- clone.insertAdjacentHTML('afterbegin', `<style>
365
- .chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}
366
- .card-bg{fill:#fff}.title{font-size:42px;font-weight:400;fill:#000}
367
- .grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square}
368
- .grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}
369
- .barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}
370
- .markers text{fill:#000;font-size:30px;font-weight:400}
371
- .fret-number{fill:#000;font-size:42px;font-weight:400}
372
- .string-labels text{fill:#000;font-size:18px;font-weight:400}
471
+ clone.insertAdjacentHTML('afterbegin', `<style>
472
+ .chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}
473
+ .card-bg{fill:${bg}}.title{font-size:42px;font-weight:400;fill:${fg}}
474
+ .grid line{stroke:${fg};stroke-width:2.6;stroke-linecap:square}
475
+ .grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:${fg}}
476
+ .barres text,.dots text{fill:${fgInverse};font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}
477
+ .markers text{fill:${fg};font-size:30px;font-weight:400}
478
+ .fret-number{fill:${fg};font-size:42px;font-weight:400}
479
+ .string-labels text{fill:${fg};font-size:18px;font-weight:400}
373
480
  </style>`);
374
481
  const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], {
375
482
  type: 'image/svg+xml',
@@ -381,17 +488,36 @@ class ChordFinderComponent {
381
488
  URL.revokeObjectURL(url);
382
489
  }
383
490
  const link = document.createElement('a');
384
- link.download = 'chords.png';
491
+ link.download = `${filename}.png`;
385
492
  link.href = canvas.toDataURL('image/png');
386
493
  link.click();
387
494
  }
388
495
  runSearch(language = this.language()) {
389
- const { results, wasLimited } = this.chordService.search(this.query, language);
390
- this.results.set(results);
391
- this.wasLimited.set(wasLimited);
392
- for (const result of results) {
393
- if (this.selectedPositionIndex[result.id] === undefined) {
394
- this.selectedPositionIndex[result.id] = 0;
496
+ const query = this.query.trim();
497
+ if (this.chordService.isSectionFormat(query)) {
498
+ this.inputMode.set('sections');
499
+ const { sections, error } = this.chordService.searchSections(query, language);
500
+ this.inputError.set(error);
501
+ this.sections.set(sections);
502
+ this.results.set([]);
503
+ for (const section of sections) {
504
+ for (const result of section.results) {
505
+ if (this.selectedPositionIndex[result.id] === undefined) {
506
+ this.selectedPositionIndex[result.id] = 0;
507
+ }
508
+ }
509
+ }
510
+ }
511
+ else {
512
+ this.inputMode.set('plain');
513
+ this.inputError.set(null);
514
+ const results = this.chordService.search(query, language);
515
+ this.results.set(results);
516
+ this.sections.set([]);
517
+ for (const result of results) {
518
+ if (this.selectedPositionIndex[result.id] === undefined) {
519
+ this.selectedPositionIndex[result.id] = 0;
520
+ }
395
521
  }
396
522
  }
397
523
  }
@@ -411,12 +537,12 @@ class ChordFinderComponent {
411
537
  return result.id;
412
538
  }
413
539
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordFinderComponent, deps: [{ token: ChordService }], target: i0.ɵɵFactoryTarget.Component });
414
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordFinderComponent, isStandalone: true, selector: "the-chords-chord-finder", inputs: { language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.lang": "language()" } }, viewQueries: [{ propertyName: "resultsRow", first: true, predicate: ["resultsRow"], descendants: true, isSignal: true }], ngImport: i0, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\n <h1>{{ text().title }}</h1>\n\n <p class=\"description\">\n {{ text().descriptionStart }}\n <strong>{{ text().descriptionLimit }}</strong>\n {{ text().descriptionEnd }}\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\n <input\n id=\"chordInput\"\n type=\"text\"\n [(ngModel)]=\"query\"\n (input)=\"runSearch()\"\n placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\"\n />\n <button\n type=\"button\"\n (click)=\"exportPng()\"\n [disabled]=\"!results().length\"\n >\n {{ text().exportPng }}\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">{{ text().limited }}</p>\n }\n\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\n </section>\n\n <section\n #resultsRow\n class=\"results-row\"\n [attr.aria-label]=\"text().resultsLabel\"\n >\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">{{ text().chord }}</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n {{ text().position }}\n <select\n [ngModel]=\"selectedPositionIndex[result.id]\"\n (ngModelChange)=\"selectPosition(result.id, $event)\"\n >\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\n </option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram\n [title]=\"result.displayName\"\n [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\"\n />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n {{ text().openSource }}\n <a\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n elparaquecosadeque/chord-generator </a\n >. {{ text().contributions }}\n </p>\n <p>{{ text().assistance }}</p>\n </footer>\n</main>\n", styles: [":host{--_chords-background: var(--chords-background, #020004);--_chords-background-alt: var(--chords-background-alt, #050012);--_chords-surface: var(--chords-surface, #000000);--_chords-surface-elevated: var( --chords-surface-elevated, rgba(0, 0, 0, .8) );--_chords-text: var(--chords-text, #f8f8ff);--_chords-muted: var(--chords-muted, #dedbff);--_chords-primary: var(--chords-primary, #00fff0);--_chords-secondary: var(--chords-secondary, #ff2bd6);--_chords-highlight: var(--chords-highlight, #ffee00);--_chords-danger: var(--chords-danger, #ff315a);--_chords-border: var(--chords-border, rgba(0, 255, 240, .76));--_chords-on-primary: var(--chords-on-primary, #000000);box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:var(--_chords-text);background:radial-gradient(circle at 10% 12%,color-mix(in srgb,var(--_chords-primary) 25%,transparent),transparent 28%),radial-gradient(circle at 92% 4%,color-mix(in srgb,var(--_chords-secondary) 25%,transparent),transparent 24%),radial-gradient(circle at 50% 110%,color-mix(in srgb,var(--_chords-highlight) 16%,transparent),transparent 30%),linear-gradient(135deg,var(--_chords-background) 0%,var(--_chords-background-alt) 48%,var(--_chords-surface) 100%)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 30px color-mix(in srgb,var(--_chords-primary) 32%,transparent),inset 0 0 26px color-mix(in srgb,var(--_chords-secondary) 13%,transparent)}.eyebrow{margin:0 0 14px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px color-mix(in srgb,var(--_chords-primary) 80%,transparent),0 0 32px color-mix(in srgb,var(--_chords-secondary) 50%,transparent)}.description,.hint{max-width:760px;margin:22px 0 0;color:var(--_chords-muted);font-size:18px;line-height:1.55}.description strong{color:var(--_chords-highlight)}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input{width:min(760px,100%);padding:18px 20px;color:var(--_chords-text);background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px color-mix(in srgb,var(--_chords-secondary) 58%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-primary) 12%,transparent)}.search-row input:focus{border-color:var(--_chords-primary);box-shadow:0 0 24px color-mix(in srgb,var(--_chords-primary) 78%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 20%,transparent)}.search-row button{width:fit-content;padding:12px 18px;color:var(--_chords-on-primary);background:var(--_chords-primary);border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:var(--_chords-highlight);font-weight:900}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-primary) 25%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 12%,transparent)}.result-card.has-error{border-color:var(--_chords-danger);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-danger) 35%,transparent)}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:var(--_chords-text);font-size:30px}.db-name{margin-top:6px;color:var(--_chords-primary);font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:var(--_chords-text);background:var(--_chords-background-alt);border:1px solid var(--_chords-secondary);border-radius:12px;font-weight:800;outline:none}select:focus{border-color:var(--_chords-primary)}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:var(--_chords-danger);font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid color-mix(in srgb,var(--_chords-primary) 42%,transparent);color:var(--_chords-muted);font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:var(--_chords-primary);font-weight:900}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ChordDiagram, selector: "app-chord-diagram", inputs: ["title", "position", "positionIndex"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
540
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: ChordFinderComponent, isStandalone: true, selector: "the-chords-chord-finder", inputs: { language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.lang": "language()" } }, viewQueries: [{ propertyName: "resultsRow", first: true, predicate: ["resultsRow"], descendants: true, isSignal: true }, { propertyName: "sectionsContainer", first: true, predicate: ["sectionsContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<main class=\"page-shell\">\r\n <section class=\"hero-card\">\r\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\r\n <h1>{{ text().title }}</h1>\r\n\r\n <p class=\"description\">\r\n {{ text().descriptionStart }}\r\n <strong>{{ text().descriptionLimit }}</strong>\r\n {{ text().descriptionEnd }}\r\n <strong>C, F#, C#m, Bb</strong>.\r\n </p>\r\n\r\n <div class=\"search-row\">\r\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\r\n <input\r\n id=\"chordInput\"\r\n type=\"text\"\r\n [(ngModel)]=\"query\"\r\n (input)=\"runSearch()\"\r\n placeholder=\"C, F#, C#m, Bb\"\r\n autocomplete=\"off\"\r\n />\r\n <div class=\"export-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"export-btn\"\r\n (click)=\"exportPanelOpen.set(!exportPanelOpen())\"\r\n [disabled]=\"!hasResults()\"\r\n [class.open]=\"exportPanelOpen()\"\r\n >\r\n {{ text().exportPng }}\r\n <span class=\"chevron\" aria-hidden=\"true\">\u25BE</span>\r\n </button>\r\n <div class=\"export-panel\" [class.open]=\"exportPanelOpen()\">\r\n <div class=\"export-panel-inner\">\r\n <div class=\"export-options\">\r\n <label class=\"color-opt\">\r\n <span>{{ text().bgColor }}</span>\r\n <div class=\"color-row\">\r\n <input\r\n type=\"color\"\r\n [(ngModel)]=\"exportBgColor\"\r\n [disabled]=\"exportTransparent\"\r\n />\r\n <label class=\"check-label\">\r\n <input type=\"checkbox\" [(ngModel)]=\"exportTransparent\" />\r\n {{ text().transparent }}\r\n </label>\r\n </div>\r\n </label>\r\n <label class=\"color-opt\">\r\n <span>{{ text().lineColor }}</span>\r\n <input type=\"color\" [(ngModel)]=\"exportLineColor\" />\r\n </label>\r\n <button\r\n type=\"button\"\r\n class=\"download-btn\"\r\n (click)=\"exportPng()\"\r\n [disabled]=\"!hasResults()\"\r\n >\r\n {{ text().download }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (inputError()) {\r\n <p class=\"input-error\">{{ inputError() }}</p>\r\n }\r\n\r\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\r\n </section>\r\n\r\n @if (inputMode() === 'plain') {\r\n <section\r\n #resultsRow\r\n class=\"results-row\"\r\n [attr.aria-label]=\"text().resultsLabel\"\r\n >\r\n @for (result of results(); track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </section>\r\n } @else if (inputMode() === 'sections') {\r\n <div #sectionsContainer class=\"sections-container\">\r\n @for (section of sections(); let si = $index; track si) {\r\n <section\r\n class=\"section-block\"\r\n [attr.data-section]=\"si\"\r\n [attr.aria-label]=\"section.name\"\r\n >\r\n <h2 class=\"section-name\">{{ section.name }}</h2>\r\n <div class=\"results-row\">\r\n @for (result of section.results; track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">\r\n {{ text().normalizedAs }} {{ result.dbName }}\r\n </p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n\r\n <footer class=\"app-footer\">\r\n <p>\r\n {{ text().openSource }}\r\n <a\r\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\r\n target=\"_blank\"\r\n rel=\"noreferrer\"\r\n >\r\n elparaquecosadeque/chord-generator </a\r\n >. {{ text().contributions }}\r\n </p>\r\n <p>{{ text().assistance }}</p>\r\n </footer>\r\n</main>\r\n", styles: [":host{--_chords-background: var(--chords-background, #020004);--_chords-background-alt: var(--chords-background-alt, #050012);--_chords-surface: var(--chords-surface, #000000);--_chords-surface-elevated: var( --chords-surface-elevated, rgba(0, 0, 0, .8) );--_chords-text: var(--chords-text, #f8f8ff);--_chords-muted: var(--chords-muted, #dedbff);--_chords-primary: var(--chords-primary, #00fff0);--_chords-secondary: var(--chords-secondary, #ff2bd6);--_chords-highlight: var(--chords-highlight, #ffee00);--_chords-danger: var(--chords-danger, #ff315a);--_chords-border: var(--chords-border, rgba(0, 255, 240, .76));--_chords-on-primary: var(--chords-on-primary, #000000);box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:var(--_chords-text);background:radial-gradient(circle at 10% 12%,color-mix(in srgb,var(--_chords-primary) 25%,transparent),transparent 28%),radial-gradient(circle at 92% 4%,color-mix(in srgb,var(--_chords-secondary) 25%,transparent),transparent 24%),radial-gradient(circle at 50% 110%,color-mix(in srgb,var(--_chords-highlight) 16%,transparent),transparent 30%),linear-gradient(135deg,var(--_chords-background) 0%,var(--_chords-background-alt) 48%,var(--_chords-surface) 100%)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 30px color-mix(in srgb,var(--_chords-primary) 32%,transparent),inset 0 0 26px color-mix(in srgb,var(--_chords-secondary) 13%,transparent)}.eyebrow{margin:0 0 14px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px color-mix(in srgb,var(--_chords-primary) 80%,transparent),0 0 32px color-mix(in srgb,var(--_chords-secondary) 50%,transparent)}.description,.hint{max-width:760px;margin:22px 0 0;color:var(--_chords-muted);font-size:18px;line-height:1.55}.description strong{color:var(--_chords-highlight)}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input:not([type=color]):not([type=checkbox]){width:min(760px,100%);padding:18px 20px;color:var(--_chords-text);background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px color-mix(in srgb,var(--_chords-secondary) 58%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-primary) 12%,transparent)}.search-row input:not([type=color]):not([type=checkbox]):focus{border-color:var(--_chords-primary);box-shadow:0 0 24px color-mix(in srgb,var(--_chords-primary) 78%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 20%,transparent)}.search-row button{width:fit-content;padding:12px 18px;color:var(--_chords-on-primary);background:var(--_chords-primary);border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:var(--_chords-highlight);font-weight:900}.input-error{margin:12px 0 0;color:var(--_chords-danger);font-size:14px;font-weight:700}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-primary) 25%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 12%,transparent)}.result-card.has-error{border-color:var(--_chords-danger);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-danger) 35%,transparent)}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:var(--_chords-text);font-size:30px}.db-name{margin-top:6px;color:var(--_chords-primary);font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:var(--_chords-text);background:var(--_chords-background-alt);border:1px solid var(--_chords-secondary);border-radius:12px;font-weight:800;outline:none}select:focus{border-color:var(--_chords-primary)}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:var(--_chords-danger);font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid color-mix(in srgb,var(--_chords-primary) 42%,transparent);color:var(--_chords-muted);font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:var(--_chords-primary);font-weight:900}.export-wrap{display:flex;flex-direction:column;align-items:flex-start}.export-btn{display:flex;align-items:center;gap:8px}.export-btn .chevron{display:inline-block;transition:transform .28s ease}.export-btn.open .chevron{transform:rotate(180deg)}.export-panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .28s ease}.export-panel.open{grid-template-rows:1fr}.export-panel-inner{overflow:hidden}.export-options{display:flex;flex-wrap:wrap;align-items:flex-end;gap:16px;padding:14px 2px 4px}.color-opt{display:flex;flex-direction:column;gap:6px;color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase;cursor:default}.color-row{display:flex;align-items:center;gap:10px}.check-label{display:flex;align-items:center;gap:6px;color:var(--_chords-muted);font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;cursor:pointer}.check-label input[type=checkbox]{accent-color:var(--_chords-primary);width:16px;height:16px;cursor:pointer}input[type=color]{width:44px;height:36px;padding:2px;background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:8px;cursor:pointer}input[type=color]:disabled{opacity:.35;cursor:not-allowed}.search-row .download-btn{background:var(--_chords-secondary)}.sections-container{display:flex;flex-direction:column;gap:40px;margin-top:34px}.section-block{display:flex;flex-direction:column;gap:0}.section-name{margin:0 0 16px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.2em;text-transform:uppercase;border-bottom:1px solid color-mix(in srgb,var(--_chords-primary) 35%,transparent);padding-bottom:10px}.section-block .results-row{margin-top:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ChordDiagram, selector: "app-chord-diagram", inputs: ["title", "position", "positionIndex"] }], changeDetection: i0.ChangeDetectionStrategy.Eager });
415
541
  }
416
542
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: ChordFinderComponent, decorators: [{
417
543
  type: Component,
418
- args: [{ selector: 'the-chords-chord-finder', standalone: true, imports: [FormsModule, ChordDiagram], changeDetection: ChangeDetectionStrategy.Eager, host: { '[attr.lang]': 'language()' }, template: "<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\n <h1>{{ text().title }}</h1>\n\n <p class=\"description\">\n {{ text().descriptionStart }}\n <strong>{{ text().descriptionLimit }}</strong>\n {{ text().descriptionEnd }}\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\n <input\n id=\"chordInput\"\n type=\"text\"\n [(ngModel)]=\"query\"\n (input)=\"runSearch()\"\n placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\"\n />\n <button\n type=\"button\"\n (click)=\"exportPng()\"\n [disabled]=\"!results().length\"\n >\n {{ text().exportPng }}\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">{{ text().limited }}</p>\n }\n\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\n </section>\n\n <section\n #resultsRow\n class=\"results-row\"\n [attr.aria-label]=\"text().resultsLabel\"\n >\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">{{ text().chord }}</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n {{ text().position }}\n <select\n [ngModel]=\"selectedPositionIndex[result.id]\"\n (ngModelChange)=\"selectPosition(result.id, $event)\"\n >\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\n </option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram\n [title]=\"result.displayName\"\n [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\"\n />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n {{ text().openSource }}\n <a\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n elparaquecosadeque/chord-generator </a\n >. {{ text().contributions }}\n </p>\n <p>{{ text().assistance }}</p>\n </footer>\n</main>\n", styles: [":host{--_chords-background: var(--chords-background, #020004);--_chords-background-alt: var(--chords-background-alt, #050012);--_chords-surface: var(--chords-surface, #000000);--_chords-surface-elevated: var( --chords-surface-elevated, rgba(0, 0, 0, .8) );--_chords-text: var(--chords-text, #f8f8ff);--_chords-muted: var(--chords-muted, #dedbff);--_chords-primary: var(--chords-primary, #00fff0);--_chords-secondary: var(--chords-secondary, #ff2bd6);--_chords-highlight: var(--chords-highlight, #ffee00);--_chords-danger: var(--chords-danger, #ff315a);--_chords-border: var(--chords-border, rgba(0, 255, 240, .76));--_chords-on-primary: var(--chords-on-primary, #000000);box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:var(--_chords-text);background:radial-gradient(circle at 10% 12%,color-mix(in srgb,var(--_chords-primary) 25%,transparent),transparent 28%),radial-gradient(circle at 92% 4%,color-mix(in srgb,var(--_chords-secondary) 25%,transparent),transparent 24%),radial-gradient(circle at 50% 110%,color-mix(in srgb,var(--_chords-highlight) 16%,transparent),transparent 30%),linear-gradient(135deg,var(--_chords-background) 0%,var(--_chords-background-alt) 48%,var(--_chords-surface) 100%)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 30px color-mix(in srgb,var(--_chords-primary) 32%,transparent),inset 0 0 26px color-mix(in srgb,var(--_chords-secondary) 13%,transparent)}.eyebrow{margin:0 0 14px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px color-mix(in srgb,var(--_chords-primary) 80%,transparent),0 0 32px color-mix(in srgb,var(--_chords-secondary) 50%,transparent)}.description,.hint{max-width:760px;margin:22px 0 0;color:var(--_chords-muted);font-size:18px;line-height:1.55}.description strong{color:var(--_chords-highlight)}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input{width:min(760px,100%);padding:18px 20px;color:var(--_chords-text);background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px color-mix(in srgb,var(--_chords-secondary) 58%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-primary) 12%,transparent)}.search-row input:focus{border-color:var(--_chords-primary);box-shadow:0 0 24px color-mix(in srgb,var(--_chords-primary) 78%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 20%,transparent)}.search-row button{width:fit-content;padding:12px 18px;color:var(--_chords-on-primary);background:var(--_chords-primary);border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:var(--_chords-highlight);font-weight:900}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-primary) 25%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 12%,transparent)}.result-card.has-error{border-color:var(--_chords-danger);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-danger) 35%,transparent)}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:var(--_chords-text);font-size:30px}.db-name{margin-top:6px;color:var(--_chords-primary);font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:var(--_chords-text);background:var(--_chords-background-alt);border:1px solid var(--_chords-secondary);border-radius:12px;font-weight:800;outline:none}select:focus{border-color:var(--_chords-primary)}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:var(--_chords-danger);font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid color-mix(in srgb,var(--_chords-primary) 42%,transparent);color:var(--_chords-muted);font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:var(--_chords-primary);font-weight:900}\n"] }]
419
- }], ctorParameters: () => [{ type: ChordService }], propDecorators: { language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], resultsRow: [{ type: i0.ViewChild, args: ['resultsRow', { isSignal: true }] }] } });
544
+ args: [{ selector: 'the-chords-chord-finder', standalone: true, imports: [FormsModule, ChordDiagram], changeDetection: ChangeDetectionStrategy.Eager, host: { '[attr.lang]': 'language()' }, template: "<main class=\"page-shell\">\r\n <section class=\"hero-card\">\r\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\r\n <h1>{{ text().title }}</h1>\r\n\r\n <p class=\"description\">\r\n {{ text().descriptionStart }}\r\n <strong>{{ text().descriptionLimit }}</strong>\r\n {{ text().descriptionEnd }}\r\n <strong>C, F#, C#m, Bb</strong>.\r\n </p>\r\n\r\n <div class=\"search-row\">\r\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\r\n <input\r\n id=\"chordInput\"\r\n type=\"text\"\r\n [(ngModel)]=\"query\"\r\n (input)=\"runSearch()\"\r\n placeholder=\"C, F#, C#m, Bb\"\r\n autocomplete=\"off\"\r\n />\r\n <div class=\"export-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"export-btn\"\r\n (click)=\"exportPanelOpen.set(!exportPanelOpen())\"\r\n [disabled]=\"!hasResults()\"\r\n [class.open]=\"exportPanelOpen()\"\r\n >\r\n {{ text().exportPng }}\r\n <span class=\"chevron\" aria-hidden=\"true\">\u25BE</span>\r\n </button>\r\n <div class=\"export-panel\" [class.open]=\"exportPanelOpen()\">\r\n <div class=\"export-panel-inner\">\r\n <div class=\"export-options\">\r\n <label class=\"color-opt\">\r\n <span>{{ text().bgColor }}</span>\r\n <div class=\"color-row\">\r\n <input\r\n type=\"color\"\r\n [(ngModel)]=\"exportBgColor\"\r\n [disabled]=\"exportTransparent\"\r\n />\r\n <label class=\"check-label\">\r\n <input type=\"checkbox\" [(ngModel)]=\"exportTransparent\" />\r\n {{ text().transparent }}\r\n </label>\r\n </div>\r\n </label>\r\n <label class=\"color-opt\">\r\n <span>{{ text().lineColor }}</span>\r\n <input type=\"color\" [(ngModel)]=\"exportLineColor\" />\r\n </label>\r\n <button\r\n type=\"button\"\r\n class=\"download-btn\"\r\n (click)=\"exportPng()\"\r\n [disabled]=\"!hasResults()\"\r\n >\r\n {{ text().download }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (inputError()) {\r\n <p class=\"input-error\">{{ inputError() }}</p>\r\n }\r\n\r\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\r\n </section>\r\n\r\n @if (inputMode() === 'plain') {\r\n <section\r\n #resultsRow\r\n class=\"results-row\"\r\n [attr.aria-label]=\"text().resultsLabel\"\r\n >\r\n @for (result of results(); track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </section>\r\n } @else if (inputMode() === 'sections') {\r\n <div #sectionsContainer class=\"sections-container\">\r\n @for (section of sections(); let si = $index; track si) {\r\n <section\r\n class=\"section-block\"\r\n [attr.data-section]=\"si\"\r\n [attr.aria-label]=\"section.name\"\r\n >\r\n <h2 class=\"section-name\">{{ section.name }}</h2>\r\n <div class=\"results-row\">\r\n @for (result of section.results; track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">\r\n {{ text().normalizedAs }} {{ result.dbName }}\r\n </p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n\r\n <footer class=\"app-footer\">\r\n <p>\r\n {{ text().openSource }}\r\n <a\r\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\r\n target=\"_blank\"\r\n rel=\"noreferrer\"\r\n >\r\n elparaquecosadeque/chord-generator </a\r\n >. {{ text().contributions }}\r\n </p>\r\n <p>{{ text().assistance }}</p>\r\n </footer>\r\n</main>\r\n", styles: [":host{--_chords-background: var(--chords-background, #020004);--_chords-background-alt: var(--chords-background-alt, #050012);--_chords-surface: var(--chords-surface, #000000);--_chords-surface-elevated: var( --chords-surface-elevated, rgba(0, 0, 0, .8) );--_chords-text: var(--chords-text, #f8f8ff);--_chords-muted: var(--chords-muted, #dedbff);--_chords-primary: var(--chords-primary, #00fff0);--_chords-secondary: var(--chords-secondary, #ff2bd6);--_chords-highlight: var(--chords-highlight, #ffee00);--_chords-danger: var(--chords-danger, #ff315a);--_chords-border: var(--chords-border, rgba(0, 255, 240, .76));--_chords-on-primary: var(--chords-on-primary, #000000);box-sizing:border-box;display:block;min-height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}:host *,:host *:before,:host *:after{box-sizing:inherit}.page-shell{min-height:100%;padding:clamp(24px,5vw,56px);color:var(--_chords-text);background:radial-gradient(circle at 10% 12%,color-mix(in srgb,var(--_chords-primary) 25%,transparent),transparent 28%),radial-gradient(circle at 92% 4%,color-mix(in srgb,var(--_chords-secondary) 25%,transparent),transparent 24%),radial-gradient(circle at 50% 110%,color-mix(in srgb,var(--_chords-highlight) 16%,transparent),transparent 30%),linear-gradient(135deg,var(--_chords-background) 0%,var(--_chords-background-alt) 48%,var(--_chords-surface) 100%)}.hero-card{max-width:1020px;padding:clamp(24px,4vw,38px);border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 30px color-mix(in srgb,var(--_chords-primary) 32%,transparent),inset 0 0 26px color-mix(in srgb,var(--_chords-secondary) 13%,transparent)}.eyebrow{margin:0 0 14px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.22em;text-transform:uppercase}h1{margin:0;max-width:820px;font-size:clamp(44px,8vw,92px);line-height:.88;letter-spacing:-.06em;text-shadow:0 0 18px color-mix(in srgb,var(--_chords-primary) 80%,transparent),0 0 32px color-mix(in srgb,var(--_chords-secondary) 50%,transparent)}.description,.hint{max-width:760px;margin:22px 0 0;color:var(--_chords-muted);font-size:18px;line-height:1.55}.description strong{color:var(--_chords-highlight)}.search-row{display:grid;gap:10px;margin-top:28px}.search-row label,.select-label,.result-label{color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase}.search-row input:not([type=color]):not([type=checkbox]){width:min(760px,100%);padding:18px 20px;color:var(--_chords-text);background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:18px;font-size:clamp(22px,4vw,32px);font-weight:900;outline:none;box-shadow:0 0 18px color-mix(in srgb,var(--_chords-secondary) 58%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-primary) 12%,transparent)}.search-row input:not([type=color]):not([type=checkbox]):focus{border-color:var(--_chords-primary);box-shadow:0 0 24px color-mix(in srgb,var(--_chords-primary) 78%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 20%,transparent)}.search-row button{width:fit-content;padding:12px 18px;color:var(--_chords-on-primary);background:var(--_chords-primary);border:0;border-radius:12px;font-weight:900;cursor:pointer}.search-row button:disabled{opacity:.45;cursor:not-allowed}.warning{margin:16px 0 0;color:var(--_chords-highlight);font-weight:900}.input-error{margin:12px 0 0;color:var(--_chords-danger);font-size:14px;font-weight:700}.results-row{display:flex;flex-wrap:wrap;gap:32px;margin-top:34px;align-items:flex-start}.result-card{width:292px;padding:18px;border:1px solid var(--_chords-border);border-radius:30px;background:var(--_chords-surface-elevated);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-primary) 25%,transparent),inset 0 0 18px color-mix(in srgb,var(--_chords-secondary) 12%,transparent)}.result-card.has-error{border-color:var(--_chords-danger);box-shadow:0 0 22px color-mix(in srgb,var(--_chords-danger) 35%,transparent)}.result-header{display:grid;gap:12px;margin-bottom:16px}.result-label,.db-name{margin:0}.result-header h2{margin:4px 0 0;color:var(--_chords-text);font-size:30px}.db-name{margin-top:6px;color:var(--_chords-primary);font-size:12px;font-weight:800}.select-label{display:grid;gap:8px}select{width:100%;padding:11px 12px;color:var(--_chords-text);background:var(--_chords-background-alt);border:1px solid var(--_chords-secondary);border-radius:12px;font-weight:800;outline:none}select:focus{border-color:var(--_chords-primary)}.svg-wrap{display:grid;place-items:center;padding:0;background:transparent;border-radius:28px}.error-text{color:var(--_chords-danger);font-weight:800;line-height:1.45}.app-footer{max-width:1020px;margin-top:40px;padding-top:20px;border-top:1px solid color-mix(in srgb,var(--_chords-primary) 42%,transparent);color:var(--_chords-muted);font-size:14px;line-height:1.6}.app-footer p{margin:0}.app-footer p+p{margin-top:6px}.app-footer a{color:var(--_chords-primary);font-weight:900}.export-wrap{display:flex;flex-direction:column;align-items:flex-start}.export-btn{display:flex;align-items:center;gap:8px}.export-btn .chevron{display:inline-block;transition:transform .28s ease}.export-btn.open .chevron{transform:rotate(180deg)}.export-panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .28s ease}.export-panel.open{grid-template-rows:1fr}.export-panel-inner{overflow:hidden}.export-options{display:flex;flex-wrap:wrap;align-items:flex-end;gap:16px;padding:14px 2px 4px}.color-opt{display:flex;flex-direction:column;gap:6px;color:var(--_chords-secondary);font-size:12px;font-weight:900;letter-spacing:.14em;text-transform:uppercase;cursor:default}.color-row{display:flex;align-items:center;gap:10px}.check-label{display:flex;align-items:center;gap:6px;color:var(--_chords-muted);font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;cursor:pointer}.check-label input[type=checkbox]{accent-color:var(--_chords-primary);width:16px;height:16px;cursor:pointer}input[type=color]{width:44px;height:36px;padding:2px;background:var(--_chords-surface);border:2px solid var(--_chords-secondary);border-radius:8px;cursor:pointer}input[type=color]:disabled{opacity:.35;cursor:not-allowed}.search-row .download-btn{background:var(--_chords-secondary)}.sections-container{display:flex;flex-direction:column;gap:40px;margin-top:34px}.section-block{display:flex;flex-direction:column;gap:0}.section-name{margin:0 0 16px;color:var(--_chords-primary);font-size:13px;font-weight:900;letter-spacing:.2em;text-transform:uppercase;border-bottom:1px solid color-mix(in srgb,var(--_chords-primary) 35%,transparent);padding-bottom:10px}.section-block .results-row{margin-top:0}\n"] }]
545
+ }], ctorParameters: () => [{ type: ChordService }], propDecorators: { language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], resultsRow: [{ type: i0.ViewChild, args: ['resultsRow', { isSignal: true }] }], sectionsContainer: [{ type: i0.ViewChild, args: ['sectionsContainer', { isSignal: true }] }] } });
420
546
 
421
547
  /*
422
548
  * Public API Surface of chord-finder
@@ -1 +1 @@
1
- {"version":3,"file":"gblp-chord-finder.mjs","sources":["../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.ts","../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.html","../../../projects/chord-finder/src/lib/services/chord.service.ts","../../../projects/chord-finder/src/lib/chord-finder.ts","../../../projects/chord-finder/src/lib/chord-finder.html","../../../projects/chord-finder/src/public-api.ts","../../../projects/chord-finder/src/gblp-chord-finder.ts"],"sourcesContent":["import { Component, computed, input, ChangeDetectionStrategy } from '@angular/core';\r\nimport { ChordsDbPosition } from '../../models/chord.model';\r\n\r\ninterface BarreSpan {\r\n fret: number;\r\n x: number;\r\n y: number;\r\n width: number;\r\n labelX: number;\r\n labelY: number;\r\n finger: number | undefined;\r\n}\r\n\r\ninterface DotMarker {\r\n fret: number;\r\n stringIndex: number;\r\n finger?: number;\r\n}\r\n\r\n@Component({\r\n selector: 'app-chord-diagram',\r\n imports: [],\r\n templateUrl: './chord-diagram.html',\r\n changeDetection: ChangeDetectionStrategy.Eager,\r\n styleUrl: './chord-diagram.scss'\r\n})\r\nexport class ChordDiagram {\r\n title = input.required<string>();\r\n position = input.required<ChordsDbPosition>();\r\n positionIndex = input<number>(0);\r\n\r\n readonly width = 240;\r\n readonly height = 330;\r\n readonly left = 54;\r\n readonly top = 112;\r\n readonly stringGap = 28;\r\n readonly fretGap = 34;\r\n readonly dotRadius = 13;\r\n readonly stringLabels = ['E', 'A', 'D', 'G', 'B', 'E'];\r\n\r\n fretCount = computed(() => Math.max(5, ...this.position().frets.filter((fret) => fret > 0)));\r\n\r\n fretLines = computed(() => Array.from({ length: this.fretCount() + 1 }, (_, index) => index));\r\n\r\n stringLines = computed(() => Array.from({ length: 6 }, (_, index) => index));\r\n\r\n visibleDots = computed<DotMarker[]>(() => {\r\n const barres = new Set(this.position().barres ?? []);\r\n const fingers = this.position().fingers ?? [];\r\n\r\n return this.position().frets\r\n .map((fret, stringIndex) => ({\r\n fret,\r\n stringIndex,\r\n finger: fingers[stringIndex] > 0 ? fingers[stringIndex] : undefined\r\n }))\r\n .filter(({ fret }) => fret > 0)\r\n .filter(({ fret }) => !barres.has(fret));\r\n });\r\n\r\n barreSpans = computed<BarreSpan[]>(() => {\r\n const barres = this.position().barres ?? [];\r\n const fingers = this.position().fingers ?? [];\r\n\r\n return barres\r\n .map((barreFret) => {\r\n const playableStrings = this.position().frets\r\n .map((fret, index) => ({ fret, index }))\r\n .filter(({ fret }) => fret >= barreFret);\r\n\r\n if (playableStrings.length < 2) return null;\r\n\r\n const firstString = playableStrings[0].index;\r\n const lastString = playableStrings[playableStrings.length - 1].index;\r\n const x = this.stringX(firstString) - this.dotRadius;\r\n const y = this.dotY(barreFret) - this.dotRadius;\r\n const width = this.stringX(lastString) - this.stringX(firstString) + this.dotRadius * 2;\r\n const labelX = x + width / 2;\r\n const labelY = y + this.dotRadius;\r\n const finger = fingers[firstString] > 0 ? fingers[firstString] : undefined;\r\n\r\n return {\r\n fret: barreFret,\r\n x,\r\n y,\r\n width,\r\n labelX,\r\n labelY,\r\n finger\r\n } satisfies BarreSpan;\r\n })\r\n .filter((span): span is BarreSpan => span !== null);\r\n });\r\n\r\n get viewBox(): string {\r\n return `0 0 ${this.width} ${this.height}`;\r\n }\r\n\r\n baseFret(): number {\r\n return this.position().baseFret ?? 1;\r\n }\r\n\r\n stringX(index: number): number {\r\n return this.left + index * this.stringGap;\r\n }\r\n\r\n fretY(index: number): number {\r\n return this.top + index * this.fretGap;\r\n }\r\n\r\n dotY(fret: number): number {\r\n return this.top + (fret - 0.5) * this.fretGap;\r\n }\r\n\r\n markerFor(fret: number): string {\r\n if (fret === -1) return '×';\r\n if (fret === 0) return '○';\r\n return '';\r\n }\r\n\r\n fretLabel(): string {\r\n return String(this.baseFret());\r\n }\r\n\r\n positionCode(): string {\r\n return this.position().frets.map((fret) => (fret === -1 ? 'x' : String(fret))).join('');\r\n }\r\n}\r\n","<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\r\n <title>{{ title() }} - posición {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\r\n\r\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\r\n\r\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\r\n\r\n <g class=\"markers\">\r\n @for (fret of position().frets; track $index) {\r\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\r\n }\r\n </g>\r\n\r\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\r\n\r\n <g class=\"grid\">\r\n @for (line of fretLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(0)\"\r\n [attr.x2]=\"stringX(5)\"\r\n [attr.y1]=\"fretY(line)\"\r\n [attr.y2]=\"fretY(line)\"\r\n [class.nut]=\"line === 0 && baseFret() === 1\"\r\n />\r\n }\r\n\r\n @for (line of stringLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(line)\"\r\n [attr.x2]=\"stringX(line)\"\r\n [attr.y1]=\"fretY(0)\"\r\n [attr.y2]=\"fretY(fretCount())\"\r\n />\r\n }\r\n </g>\r\n\r\n <g class=\"barres\">\r\n @for (barre of barreSpans(); track barre.fret) {\r\n <rect\r\n [attr.x]=\"barre.x\"\r\n [attr.y]=\"barre.y\"\r\n [attr.width]=\"barre.width\"\r\n [attr.height]=\"dotRadius * 2\"\r\n rx=\"13\"\r\n />\r\n @if (barre.finger) {\r\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"dots\">\r\n @for (dot of visibleDots(); track dot.stringIndex) {\r\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\r\n @if (dot.finger) {\r\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"string-labels\">\r\n @for (label of stringLabels; track label + $index) {\r\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\r\n }\r\n </g>\r\n</svg>\r\n","import { Injectable } from '@angular/core';\nimport guitarDbJson from '@tombatossals/chords-db/lib/guitar.json';\nimport {\n ChordSearchResult,\n ChordsDbInstrument,\n Language,\n ParsedChord,\n} from '../models/chord.model';\n\nconst ERROR_COPY = {\n en: {\n invalid: 'Invalid chord name. Try C, F#, C#m, Bb, Am7, or Dsus4.',\n missingRoot: 'Chord root not found in chords-db.',\n missingType: (suffix: string) =>\n `The type \"${suffix}\" is not available for this chord.`,\n },\n es: {\n invalid: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.',\n missingRoot: 'Raíz no encontrada en chords-db.',\n missingType: (suffix: string) =>\n `El tipo \"${suffix}\" no existe para este acorde en la base actual.`,\n },\n} satisfies Record<\n Language,\n {\n invalid: string;\n missingRoot: string;\n missingType: (suffix: string) => string;\n }\n>;\n\n@Injectable({ providedIn: 'root' })\nexport class ChordService {\n readonly maxBatchSize = 5;\n\n private readonly guitarDb = guitarDbJson as ChordsDbInstrument;\n\n private readonly dbRootMap: Record<string, string> = {\n C: 'C',\n 'C#': 'Csharp',\n Db: 'Csharp',\n D: 'D',\n 'D#': 'Eb',\n Eb: 'Eb',\n E: 'E',\n Fb: 'E',\n 'E#': 'F',\n F: 'F',\n 'F#': 'Fsharp',\n Gb: 'Fsharp',\n G: 'G',\n 'G#': 'Ab',\n Ab: 'Ab',\n A: 'A',\n 'A#': 'Bb',\n Bb: 'Bb',\n B: 'B',\n Cb: 'B',\n 'B#': 'C',\n };\n\n private readonly suffixAliases: Record<string, string> = {\n '': 'major',\n M: 'major',\n maj: 'major',\n major: 'major',\n m: 'minor',\n min: 'minor',\n '-': 'minor',\n minor: 'minor',\n Δ: 'maj7',\n maj7: 'maj7',\n M7: 'maj7',\n m7: 'm7',\n min7: 'm7',\n dim: 'dim',\n diminished: 'dim',\n aug: 'aug',\n augmented: 'aug',\n sus: 'sus4',\n sus2: 'sus2',\n sus4: 'sus4',\n add9: 'add9',\n 4: 'sus4',\n };\n\n search(\n input: string,\n language: Language = 'en',\n ): { results: ChordSearchResult[]; wasLimited: boolean } {\n const tokens = input\n .split(',')\n .map((token) => token.trim())\n .filter(Boolean);\n\n const limitedTokens = tokens.slice(0, this.maxBatchSize);\n\n return {\n results: limitedTokens.map((token, index) =>\n this.searchSingle(token, index, language),\n ),\n wasLimited: tokens.length > this.maxBatchSize,\n };\n }\n\n suffixes(): string[] {\n return this.guitarDb.suffixes;\n }\n\n private searchSingle(\n token: string,\n index: number,\n language: Language,\n ): ChordSearchResult {\n const parsed = this.parseChordName(token);\n const copy = ERROR_COPY[language];\n\n if (!parsed) {\n return {\n id: `${index}-${token}`,\n raw: token,\n displayName: token,\n positions: [],\n error: copy.invalid,\n };\n }\n const chordFamily = this.guitarDb.chords[parsed.dbRoot];\n\n if (!chordFamily) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n positions: [],\n error: copy.missingRoot,\n };\n }\n\n const chord = chordFamily.find((item) => item.suffix === parsed.suffix);\n\n if (!chord) {\n return {\n id: `${index}-${parsed.displayName}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${parsed.dbRoot} ${parsed.suffix}`,\n positions: [],\n error: copy.missingType(parsed.suffix),\n };\n }\n\n return {\n id: `${index}-${parsed.dbRoot}-${parsed.suffix}`,\n raw: token,\n displayName: parsed.displayName,\n dbName: `${chord.key} ${chord.suffix}`,\n chord,\n positions: chord.positions,\n };\n }\n\n private parseChordName(raw: string): ParsedChord | null {\n const cleaned = raw\n .replaceAll('♯', '#')\n .replaceAll('♭', 'b')\n .replace(/\\s+/g, '');\n\n const match = cleaned.match(/^([A-Ga-g])([#b]?)(.*)$/);\n if (!match) return null;\n\n const letter = match[1].toUpperCase();\n const accidental = match[2] ?? '';\n const suffixRaw = match[3] ?? '';\n const root = `${letter}${accidental}`;\n const dbRoot = this.dbRootMap[root];\n\n if (!dbRoot) return null;\n\n const suffix = this.normalizeSuffix(suffixRaw);\n if (!suffix) return null;\n\n return {\n raw,\n root,\n dbRoot,\n displayName: `${root}${this.displaySuffix(suffix)}`,\n suffix,\n };\n }\n\n private normalizeSuffix(rawSuffix: string): string | null {\n const trimmed = rawSuffix.trim();\n\n if (this.suffixAliases[trimmed] !== undefined) {\n return this.suffixAliases[trimmed];\n }\n\n const lower = trimmed.toLowerCase();\n if (this.suffixAliases[lower] !== undefined) {\n return this.suffixAliases[lower];\n }\n\n if (this.guitarDb.suffixes.includes(trimmed)) {\n return trimmed;\n }\n\n if (this.guitarDb.suffixes.includes(lower)) {\n return lower;\n }\n\n return null;\n }\n\n private displaySuffix(suffix: string): string {\n if (suffix === 'major') return '';\n if (suffix === 'minor') return 'm';\n return suffix;\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n computed,\n effect,\n input,\n signal,\n viewChild,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { ChordDiagram } from './components/chord-diagram/chord-diagram';\nimport {\n ChordSearchResult,\n ChordsDbPosition,\n Language,\n} from './models/chord.model';\nimport { ChordService } from './services/chord.service';\n\nconst COPY = {\n en: {\n eyebrow: 'Angular · SVG · chords-db',\n title: 'Chord Finder',\n descriptionStart: 'Enter up to',\n descriptionLimit: '5 chords',\n descriptionEnd: 'separated by commas. Sharps and flats are supported:',\n inputLabel: 'Chords',\n exportPng: 'Export PNG',\n limited: 'Only the first 5 chords are rendered.',\n availableTypes: 'Available chord types:',\n resultsLabel: 'Chord results',\n chord: 'Chord',\n normalizedAs: 'Normalized as',\n position: 'Position',\n of: 'of',\n openSource: 'Open source project:',\n contributions: 'Contributions are welcome.',\n assistance:\n 'Built with AI assistance under supervision from the repository owner.',\n },\n es: {\n eyebrow: 'Angular · SVG · chords-db',\n title: 'Buscador de acordes',\n descriptionStart: 'Escribe hasta',\n descriptionLimit: '5 acordes',\n descriptionEnd: 'separados por coma. Soporta sostenidos y bemoles:',\n inputLabel: 'Acordes',\n exportPng: 'Exportar PNG',\n limited: 'Solo se renderizan los primeros 5 acordes.',\n availableTypes: 'Tipos de acorde disponibles:',\n resultsLabel: 'Resultados de acordes',\n chord: 'Acorde',\n normalizedAs: 'Normalizado como',\n position: 'Posición',\n of: 'de',\n openSource: 'Proyecto de código abierto:',\n contributions: 'Las contribuciones son bienvenidas.',\n assistance:\n 'Creado con asistencia de IA bajo la supervisión del propietario del repositorio.',\n },\n} as const;\n\n@Component({\n selector: 'the-chords-chord-finder',\n standalone: true,\n imports: [FormsModule, ChordDiagram],\n templateUrl: './chord-finder.html',\n changeDetection: ChangeDetectionStrategy.Eager,\n styleUrl: './chord-finder.scss',\n host: { '[attr.lang]': 'language()' },\n})\nexport class ChordFinderComponent {\n readonly language = input<Language>('en');\n readonly text = computed(() => COPY[this.language()]);\n query = 'C';\n results = signal<ChordSearchResult[]>([]);\n wasLimited = signal(false);\n selectedPositionIndex: Record<string, number> = {};\n supportedSuffixes = computed(() =>\n this.chordService.suffixes().slice(0, 18).join(', '),\n );\n\n resultsRow = viewChild.required<ElementRef<HTMLElement>>('resultsRow');\n\n constructor(private readonly chordService: ChordService) {\n effect(() => this.runSearch(this.language()));\n }\n\n async exportPng(): Promise<void> {\n const svgs = Array.from(\n this.resultsRow().nativeElement.querySelectorAll<SVGSVGElement>(\n 'svg.chord-svg',\n ),\n );\n if (!svgs.length) return;\n\n const padding = 32;\n const gap = 24;\n const width = 240;\n const height = 330;\n const scale = 2;\n\n const canvas = document.createElement('canvas');\n canvas.width =\n (padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;\n canvas.height = (padding * 2 + height) * scale;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.scale(scale, scale);\n ctx.fillStyle = '#ffffff';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n for (const [index, svg] of svgs.entries()) {\n const clone = svg.cloneNode(true) as SVGSVGElement;\n\n clone.insertAdjacentHTML(\n 'afterbegin',\n `<style>\n .chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}\n .card-bg{fill:#fff}.title{font-size:42px;font-weight:400;fill:#000}\n .grid line{stroke:#000;stroke-width:2.6;stroke-linecap:square}\n .grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:#000}\n .barres text,.dots text{fill:#fff;font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}\n .markers text{fill:#000;font-size:30px;font-weight:400}\n .fret-number{fill:#000;font-size:42px;font-weight:400}\n .string-labels text{fill:#000;font-size:18px;font-weight:400}\n </style>`,\n );\n\n const url = URL.createObjectURL(\n new Blob([new XMLSerializer().serializeToString(clone)], {\n type: 'image/svg+xml',\n }),\n );\n const image = new Image();\n image.src = url;\n await image.decode();\n\n ctx.drawImage(\n image,\n padding + index * (width + gap),\n padding,\n width,\n height,\n );\n URL.revokeObjectURL(url);\n }\n\n const link = document.createElement('a');\n link.download = 'chords.png';\n link.href = canvas.toDataURL('image/png');\n link.click();\n }\n\n runSearch(language: Language = this.language()): void {\n const { results, wasLimited } = this.chordService.search(\n this.query,\n language,\n );\n this.results.set(results);\n this.wasLimited.set(wasLimited);\n\n for (const result of results) {\n if (this.selectedPositionIndex[result.id] === undefined) {\n this.selectedPositionIndex[result.id] = 0;\n }\n }\n }\n\n selectPosition(resultId: string, value: string | number): void {\n this.selectedPositionIndex[resultId] = Number(value);\n }\n\n selectedIndex(resultId: string): number {\n return this.selectedPositionIndex[resultId] ?? 0;\n }\n\n selectedPosition(result: ChordSearchResult): ChordsDbPosition | null {\n if (!result.positions.length) return null;\n return (\n result.positions[this.selectedPositionIndex[result.id] ?? 0] ??\n result.positions[0]\n );\n }\n\n trackByResultId(_: number, result: ChordSearchResult): string {\n return result.id;\n }\n}\n","<main class=\"page-shell\">\n <section class=\"hero-card\">\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\n <h1>{{ text().title }}</h1>\n\n <p class=\"description\">\n {{ text().descriptionStart }}\n <strong>{{ text().descriptionLimit }}</strong>\n {{ text().descriptionEnd }}\n <strong>C, F#, C#m, Bb</strong>.\n </p>\n\n <div class=\"search-row\">\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\n <input\n id=\"chordInput\"\n type=\"text\"\n [(ngModel)]=\"query\"\n (input)=\"runSearch()\"\n placeholder=\"C, F#, C#m, Bb\"\n autocomplete=\"off\"\n />\n <button\n type=\"button\"\n (click)=\"exportPng()\"\n [disabled]=\"!results().length\"\n >\n {{ text().exportPng }}\n </button>\n </div>\n\n @if (wasLimited()) {\n <p class=\"warning\">{{ text().limited }}</p>\n }\n\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\n </section>\n\n <section\n #resultsRow\n class=\"results-row\"\n [attr.aria-label]=\"text().resultsLabel\"\n >\n @for (result of results(); track trackByResultId($index, result)) {\n <article class=\"result-card\" [class.has-error]=\"result.error\">\n <header class=\"result-header\">\n <div>\n <p class=\"result-label\">{{ text().chord }}</p>\n <h2>{{ result.displayName }}</h2>\n @if (result.dbName && result.raw !== result.displayName) {\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\n }\n </div>\n\n @if (result.positions.length > 1) {\n <label class=\"select-label\">\n {{ text().position }}\n <select\n [ngModel]=\"selectedPositionIndex[result.id]\"\n (ngModelChange)=\"selectPosition(result.id, $event)\"\n >\n @for (position of result.positions; track $index) {\n <option [value]=\"$index\">\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\n </option>\n }\n </select>\n </label>\n }\n </header>\n\n @if (result.error) {\n <p class=\"error-text\">{{ result.error }}</p>\n } @else if (selectedPosition(result); as position) {\n <div class=\"svg-wrap\">\n <app-chord-diagram\n [title]=\"result.displayName\"\n [position]=\"position\"\n [positionIndex]=\"selectedIndex(result.id)\"\n />\n </div>\n }\n </article>\n }\n </section>\n\n <footer class=\"app-footer\">\n <p>\n {{ text().openSource }}\n <a\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n elparaquecosadeque/chord-generator </a\n >. {{ text().contributions }}\n </p>\n <p>{{ text().assistance }}</p>\n </footer>\n</main>\n","/*\r\n * Public API Surface of chord-finder\r\n */\r\n\r\nexport * from './lib/chord-finder';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.ChordService"],"mappings":"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"}
1
+ {"version":3,"file":"gblp-chord-finder.mjs","sources":["../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.ts","../../../projects/chord-finder/src/lib/components/chord-diagram/chord-diagram.html","../../../projects/chord-finder/src/lib/services/chord.service.ts","../../../projects/chord-finder/src/lib/chord-finder.ts","../../../projects/chord-finder/src/lib/chord-finder.html","../../../projects/chord-finder/src/public-api.ts","../../../projects/chord-finder/src/gblp-chord-finder.ts"],"sourcesContent":["import { Component, computed, input, ChangeDetectionStrategy } from '@angular/core';\r\nimport { ChordsDbPosition } from '../../models/chord.model';\r\n\r\ninterface BarreSpan {\r\n fret: number;\r\n x: number;\r\n y: number;\r\n width: number;\r\n labelX: number;\r\n labelY: number;\r\n finger: number | undefined;\r\n}\r\n\r\ninterface DotMarker {\r\n fret: number;\r\n stringIndex: number;\r\n finger?: number;\r\n}\r\n\r\n@Component({\r\n selector: 'app-chord-diagram',\r\n imports: [],\r\n templateUrl: './chord-diagram.html',\r\n changeDetection: ChangeDetectionStrategy.Eager,\r\n styleUrl: './chord-diagram.scss'\r\n})\r\nexport class ChordDiagram {\r\n title = input.required<string>();\r\n position = input.required<ChordsDbPosition>();\r\n positionIndex = input<number>(0);\r\n\r\n readonly width = 240;\r\n readonly height = 330;\r\n readonly left = 54;\r\n readonly top = 112;\r\n readonly stringGap = 28;\r\n readonly fretGap = 34;\r\n readonly dotRadius = 13;\r\n readonly stringLabels = ['E', 'A', 'D', 'G', 'B', 'E'];\r\n\r\n fretCount = computed(() => Math.max(5, ...this.position().frets.filter((fret) => fret > 0)));\r\n\r\n fretLines = computed(() => Array.from({ length: this.fretCount() + 1 }, (_, index) => index));\r\n\r\n stringLines = computed(() => Array.from({ length: 6 }, (_, index) => index));\r\n\r\n visibleDots = computed<DotMarker[]>(() => {\r\n const barres = new Set(this.position().barres ?? []);\r\n const fingers = this.position().fingers ?? [];\r\n\r\n return this.position().frets\r\n .map((fret, stringIndex) => ({\r\n fret,\r\n stringIndex,\r\n finger: fingers[stringIndex] > 0 ? fingers[stringIndex] : undefined\r\n }))\r\n .filter(({ fret }) => fret > 0)\r\n .filter(({ fret }) => !barres.has(fret));\r\n });\r\n\r\n barreSpans = computed<BarreSpan[]>(() => {\r\n const barres = this.position().barres ?? [];\r\n const fingers = this.position().fingers ?? [];\r\n\r\n return barres\r\n .map((barreFret) => {\r\n const playableStrings = this.position().frets\r\n .map((fret, index) => ({ fret, index }))\r\n .filter(({ fret }) => fret >= barreFret);\r\n\r\n if (playableStrings.length < 2) return null;\r\n\r\n const firstString = playableStrings[0].index;\r\n const lastString = playableStrings[playableStrings.length - 1].index;\r\n const x = this.stringX(firstString) - this.dotRadius;\r\n const y = this.dotY(barreFret) - this.dotRadius;\r\n const width = this.stringX(lastString) - this.stringX(firstString) + this.dotRadius * 2;\r\n const labelX = x + width / 2;\r\n const labelY = y + this.dotRadius;\r\n const finger = fingers[firstString] > 0 ? fingers[firstString] : undefined;\r\n\r\n return {\r\n fret: barreFret,\r\n x,\r\n y,\r\n width,\r\n labelX,\r\n labelY,\r\n finger\r\n } satisfies BarreSpan;\r\n })\r\n .filter((span): span is BarreSpan => span !== null);\r\n });\r\n\r\n get viewBox(): string {\r\n return `0 0 ${this.width} ${this.height}`;\r\n }\r\n\r\n baseFret(): number {\r\n return this.position().baseFret ?? 1;\r\n }\r\n\r\n stringX(index: number): number {\r\n return this.left + index * this.stringGap;\r\n }\r\n\r\n fretY(index: number): number {\r\n return this.top + index * this.fretGap;\r\n }\r\n\r\n dotY(fret: number): number {\r\n return this.top + (fret - 0.5) * this.fretGap;\r\n }\r\n\r\n markerFor(fret: number): string {\r\n if (fret === -1) return '×';\r\n if (fret === 0) return '○';\r\n return '';\r\n }\r\n\r\n fretLabel(): string {\r\n return String(this.baseFret());\r\n }\r\n\r\n positionCode(): string {\r\n return this.position().frets.map((fret) => (fret === -1 ? 'x' : String(fret))).join('');\r\n }\r\n}\r\n","<svg class=\"chord-svg\" [attr.viewBox]=\"viewBox\" role=\"img\" [attr.aria-label]=\"title() + ' guitar chord diagram'\">\r\n <title>{{ title() }} - posición {{ positionIndex() + 1 }} - {{ positionCode() }}</title>\r\n\r\n <rect class=\"card-bg\" x=\"0\" y=\"0\" [attr.width]=\"width\" [attr.height]=\"height\" rx=\"24\" fill=\"#ffffff\" />\r\n\r\n <text x=\"120\" y=\"50\" text-anchor=\"middle\" class=\"title\">{{ title() }}</text>\r\n\r\n <g class=\"markers\">\r\n @for (fret of position().frets; track $index) {\r\n <text [attr.x]=\"stringX($index)\" y=\"92\" text-anchor=\"middle\">{{ markerFor(fret) }}</text>\r\n }\r\n </g>\r\n\r\n <text x=\"26\" [attr.y]=\"fretY(0) + 28\" text-anchor=\"middle\" class=\"fret-number\">{{ fretLabel() }}</text>\r\n\r\n <g class=\"grid\">\r\n @for (line of fretLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(0)\"\r\n [attr.x2]=\"stringX(5)\"\r\n [attr.y1]=\"fretY(line)\"\r\n [attr.y2]=\"fretY(line)\"\r\n [class.nut]=\"line === 0 && baseFret() === 1\"\r\n />\r\n }\r\n\r\n @for (line of stringLines(); track line) {\r\n <line\r\n [attr.x1]=\"stringX(line)\"\r\n [attr.x2]=\"stringX(line)\"\r\n [attr.y1]=\"fretY(0)\"\r\n [attr.y2]=\"fretY(fretCount())\"\r\n />\r\n }\r\n </g>\r\n\r\n <g class=\"barres\">\r\n @for (barre of barreSpans(); track barre.fret) {\r\n <rect\r\n [attr.x]=\"barre.x\"\r\n [attr.y]=\"barre.y\"\r\n [attr.width]=\"barre.width\"\r\n [attr.height]=\"dotRadius * 2\"\r\n rx=\"13\"\r\n />\r\n @if (barre.finger) {\r\n <text [attr.x]=\"barre.labelX\" [attr.y]=\"barre.labelY\" text-anchor=\"middle\">{{ barre.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"dots\">\r\n @for (dot of visibleDots(); track dot.stringIndex) {\r\n <circle [attr.cx]=\"stringX(dot.stringIndex)\" [attr.cy]=\"dotY(dot.fret)\" [attr.r]=\"dotRadius\" />\r\n @if (dot.finger) {\r\n <text [attr.x]=\"stringX(dot.stringIndex)\" [attr.y]=\"dotY(dot.fret)\" text-anchor=\"middle\">{{ dot.finger }}</text>\r\n }\r\n }\r\n </g>\r\n\r\n <g class=\"string-labels\">\r\n @for (label of stringLabels; track label + $index) {\r\n <text [attr.x]=\"stringX($index)\" [attr.y]=\"fretY(fretCount()) + 32\" text-anchor=\"middle\">{{ label }}</text>\r\n }\r\n </g>\r\n</svg>\r\n","import { Injectable } from '@angular/core';\r\nimport guitarDbJson from '@tombatossals/chords-db/lib/guitar.json';\r\nimport {\r\n ChordSearchResult,\r\n ChordSection,\r\n ChordsDbInstrument,\r\n Language,\r\n ParsedChord,\r\n} from '../models/chord.model';\r\n\r\nconst ERROR_COPY = {\r\n en: {\r\n invalid: 'Invalid chord name. Try C, F#, C#m, Bb, Am7, or Dsus4.',\r\n missingRoot: 'Chord root not found in chords-db.',\r\n missingType: (suffix: string) =>\r\n `The type \"${suffix}\" is not available for this chord.`,\r\n tooManySections: (max: number) =>\r\n `Too many sections — maximum allowed is ${max}.`,\r\n tooManyChords: (section: string, max: number) =>\r\n `Section \"${section}\" exceeds the maximum of ${max} chords.`,\r\n emptySectionName: 'Section name cannot be empty.',\r\n emptySection: (name: string) => `Section \"${name}\" has no chords.`,\r\n noValidSections:\r\n 'No valid sections found. Expected format: \"name: chord1, chord2; name2: chord3\".',\r\n },\r\n es: {\r\n invalid: 'Nombre inválido. Prueba C, F#, C#m, Bb, Am7 o Dsus4.',\r\n missingRoot: 'Raíz no encontrada en chords-db.',\r\n missingType: (suffix: string) =>\r\n `El tipo \"${suffix}\" no existe para este acorde en la base actual.`,\r\n tooManySections: (max: number) =>\r\n `Demasiadas secciones — el máximo permitido es ${max}.`,\r\n tooManyChords: (section: string, max: number) =>\r\n `La sección \"${section}\" supera el máximo de ${max} acordes.`,\r\n emptySectionName: 'El nombre de la sección no puede estar vacío.',\r\n emptySection: (name: string) =>\r\n `La sección \"${name}\" no tiene acordes.`,\r\n noValidSections:\r\n 'No se encontraron secciones válidas. Formato esperado: \"nombre: acorde1, acorde2; nombre2: acorde3\".',\r\n },\r\n} satisfies Record<\r\n Language,\r\n {\r\n invalid: string;\r\n missingRoot: string;\r\n missingType: (suffix: string) => string;\r\n tooManySections: (max: number) => string;\r\n tooManyChords: (section: string, max: number) => string;\r\n emptySectionName: string;\r\n emptySection: (name: string) => string;\r\n noValidSections: string;\r\n }\r\n>;\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class ChordService {\r\n private readonly MAX_SECTIONS = 6;\r\n private readonly MAX_CHORDS_PER_SECTION = 6;\r\n\r\n private readonly guitarDb = guitarDbJson as ChordsDbInstrument;\r\n\r\n private readonly dbRootMap: Record<string, string> = {\r\n C: 'C',\r\n 'C#': 'Csharp',\r\n Db: 'Csharp',\r\n D: 'D',\r\n 'D#': 'Eb',\r\n Eb: 'Eb',\r\n E: 'E',\r\n Fb: 'E',\r\n 'E#': 'F',\r\n F: 'F',\r\n 'F#': 'Fsharp',\r\n Gb: 'Fsharp',\r\n G: 'G',\r\n 'G#': 'Ab',\r\n Ab: 'Ab',\r\n A: 'A',\r\n 'A#': 'Bb',\r\n Bb: 'Bb',\r\n B: 'B',\r\n Cb: 'B',\r\n 'B#': 'C',\r\n };\r\n\r\n private readonly suffixAliases: Record<string, string> = {\r\n '': 'major',\r\n M: 'major',\r\n maj: 'major',\r\n major: 'major',\r\n m: 'minor',\r\n min: 'minor',\r\n '-': 'minor',\r\n minor: 'minor',\r\n Δ: 'maj7',\r\n maj7: 'maj7',\r\n M7: 'maj7',\r\n m7: 'm7',\r\n min7: 'm7',\r\n dim: 'dim',\r\n diminished: 'dim',\r\n aug: 'aug',\r\n augmented: 'aug',\r\n sus: 'sus4',\r\n sus2: 'sus2',\r\n sus4: 'sus4',\r\n add9: 'add9',\r\n 4: 'sus4',\r\n };\r\n\r\n search(\r\n input: string,\r\n language: Language = 'en',\r\n ): ChordSearchResult[] {\r\n return input\r\n .split(',')\r\n .map((token) => token.trim())\r\n .filter(Boolean)\r\n .map((token, index) => this.searchSingle(token, index, language));\r\n }\r\n\r\n isSectionFormat(input: string): boolean {\r\n return input.includes(':');\r\n }\r\n\r\n searchSections(\r\n input: string,\r\n language: Language = 'en',\r\n ): { sections: ChordSection[]; error: string | null } {\r\n const copy = ERROR_COPY[language];\r\n\r\n const rawSections = input\r\n .split(';')\r\n .map((s) => s.trim())\r\n .filter(Boolean);\r\n\r\n if (!rawSections.length) {\r\n return { sections: [], error: copy.noValidSections };\r\n }\r\n\r\n if (rawSections.length > this.MAX_SECTIONS) {\r\n return { sections: [], error: copy.tooManySections(this.MAX_SECTIONS) };\r\n }\r\n\r\n const sections: ChordSection[] = [];\r\n\r\n for (const [si, raw] of rawSections.entries()) {\r\n const colonIndex = raw.indexOf(':');\r\n if (colonIndex === -1) {\r\n return { sections: [], error: copy.noValidSections };\r\n }\r\n\r\n const name = raw.slice(0, colonIndex).trim();\r\n const chordsStr = raw.slice(colonIndex + 1).trim();\r\n\r\n if (!name) {\r\n return { sections: [], error: copy.emptySectionName };\r\n }\r\n\r\n const tokens = chordsStr\r\n .split(',')\r\n .map((t) => t.trim())\r\n .filter(Boolean);\r\n\r\n if (!tokens.length) {\r\n return { sections: [], error: copy.emptySection(name) };\r\n }\r\n\r\n if (tokens.length > this.MAX_CHORDS_PER_SECTION) {\r\n return {\r\n sections: [],\r\n error: copy.tooManyChords(name, this.MAX_CHORDS_PER_SECTION),\r\n };\r\n }\r\n\r\n sections.push({\r\n name,\r\n results: tokens.map((token, ci) =>\r\n // ponytail: offset by si*10 to keep IDs unique across sections (max 6 chords × 6 sections = 36 < 60)\r\n this.searchSingle(token, si * 10 + ci, language),\r\n ),\r\n });\r\n }\r\n\r\n return { sections, error: null };\r\n }\r\n\r\n suffixes(): string[] {\r\n return this.guitarDb.suffixes;\r\n }\r\n\r\n private searchSingle(\r\n token: string,\r\n index: number,\r\n language: Language,\r\n ): ChordSearchResult {\r\n const parsed = this.parseChordName(token);\r\n const copy = ERROR_COPY[language];\r\n\r\n if (!parsed) {\r\n return {\r\n id: `${index}-${token}`,\r\n raw: token,\r\n displayName: token,\r\n positions: [],\r\n error: copy.invalid,\r\n };\r\n }\r\n const chordFamily = this.guitarDb.chords[parsed.dbRoot];\r\n\r\n if (!chordFamily) {\r\n return {\r\n id: `${index}-${parsed.displayName}`,\r\n raw: token,\r\n displayName: parsed.displayName,\r\n positions: [],\r\n error: copy.missingRoot,\r\n };\r\n }\r\n\r\n const chord = chordFamily.find((item) => item.suffix === parsed.suffix);\r\n\r\n if (!chord) {\r\n return {\r\n id: `${index}-${parsed.displayName}`,\r\n raw: token,\r\n displayName: parsed.displayName,\r\n dbName: `${parsed.dbRoot} ${parsed.suffix}`,\r\n positions: [],\r\n error: copy.missingType(parsed.suffix),\r\n };\r\n }\r\n\r\n return {\r\n id: `${index}-${parsed.dbRoot}-${parsed.suffix}`,\r\n raw: token,\r\n displayName: parsed.displayName,\r\n dbName: `${chord.key} ${chord.suffix}`,\r\n chord,\r\n positions: chord.positions,\r\n };\r\n }\r\n\r\n private parseChordName(raw: string): ParsedChord | null {\r\n const cleaned = raw\r\n .replaceAll('♯', '#')\r\n .replaceAll('♭', 'b')\r\n .replace(/\\s+/g, '');\r\n\r\n const match = cleaned.match(/^([A-Ga-g])([#b]?)(.*)$/);\r\n if (!match) return null;\r\n\r\n const letter = match[1].toUpperCase();\r\n const accidental = match[2] ?? '';\r\n const suffixRaw = match[3] ?? '';\r\n const root = `${letter}${accidental}`;\r\n const dbRoot = this.dbRootMap[root];\r\n\r\n if (!dbRoot) return null;\r\n\r\n const suffix = this.normalizeSuffix(suffixRaw);\r\n if (!suffix) return null;\r\n\r\n return {\r\n raw,\r\n root,\r\n dbRoot,\r\n displayName: `${root}${this.displaySuffix(suffix)}`,\r\n suffix,\r\n };\r\n }\r\n\r\n private normalizeSuffix(rawSuffix: string): string | null {\r\n const trimmed = rawSuffix.trim();\r\n\r\n if (this.suffixAliases[trimmed] !== undefined) {\r\n return this.suffixAliases[trimmed];\r\n }\r\n\r\n const lower = trimmed.toLowerCase();\r\n if (this.suffixAliases[lower] !== undefined) {\r\n return this.suffixAliases[lower];\r\n }\r\n\r\n if (this.guitarDb.suffixes.includes(trimmed)) {\r\n return trimmed;\r\n }\r\n\r\n if (this.guitarDb.suffixes.includes(lower)) {\r\n return lower;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n private displaySuffix(suffix: string): string {\r\n if (suffix === 'major') return '';\r\n if (suffix === 'minor') return 'm';\r\n return suffix;\r\n }\r\n}\r\n","import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n computed,\r\n effect,\r\n input,\r\n signal,\r\n viewChild,\r\n} from '@angular/core';\r\nimport { FormsModule } from '@angular/forms';\r\nimport { ChordDiagram } from './components/chord-diagram/chord-diagram';\r\nimport {\r\n ChordSearchResult,\r\n ChordSection,\r\n ChordsDbPosition,\r\n Language,\r\n} from './models/chord.model';\r\nimport { ChordService } from './services/chord.service';\r\n\r\nconst COPY = {\r\n en: {\r\n eyebrow: 'Angular · SVG · chords-db',\r\n title: 'Chord Finder',\r\n descriptionStart: 'Enter up to',\r\n descriptionLimit: '5 chords',\r\n descriptionEnd: 'separated by commas. Sharps and flats are supported:',\r\n inputLabel: 'Chords',\r\n exportPng: 'Export PNG',\r\n bgColor: 'Background',\r\n lineColor: 'Diagram color',\r\n transparent: 'Transparent',\r\n download: 'Download',\r\n availableTypes: 'Available chord types:',\r\n resultsLabel: 'Chord results',\r\n chord: 'Chord',\r\n normalizedAs: 'Normalized as',\r\n position: 'Position',\r\n of: 'of',\r\n openSource: 'Open source project:',\r\n contributions: 'Contributions are welcome.',\r\n assistance:\r\n 'Built with AI assistance under supervision from the repository owner.',\r\n },\r\n es: {\r\n eyebrow: 'Angular · SVG · chords-db',\r\n title: 'Buscador de acordes',\r\n descriptionStart: 'Escribe hasta',\r\n descriptionLimit: '5 acordes',\r\n descriptionEnd: 'separados por coma. Soporta sostenidos y bemoles:',\r\n inputLabel: 'Acordes',\r\n exportPng: 'Exportar PNG',\r\n bgColor: 'Fondo',\r\n lineColor: 'Color del diagrama',\r\n transparent: 'Transparente',\r\n download: 'Descargar',\r\n availableTypes: 'Tipos de acorde disponibles:',\r\n resultsLabel: 'Resultados de acordes',\r\n chord: 'Acorde',\r\n normalizedAs: 'Normalizado como',\r\n position: 'Posición',\r\n of: 'de',\r\n openSource: 'Proyecto de código abierto:',\r\n contributions: 'Las contribuciones son bienvenidas.',\r\n assistance:\r\n 'Creado con asistencia de IA bajo la supervisión del propietario del repositorio.',\r\n },\r\n} as const;\r\n\r\n@Component({\r\n selector: 'the-chords-chord-finder',\r\n standalone: true,\r\n imports: [FormsModule, ChordDiagram],\r\n templateUrl: './chord-finder.html',\r\n changeDetection: ChangeDetectionStrategy.Eager,\r\n styleUrl: './chord-finder.scss',\r\n host: { '[attr.lang]': 'language()' },\r\n})\r\nexport class ChordFinderComponent {\r\n readonly language = input<Language>('en');\r\n readonly text = computed(() => COPY[this.language()]);\r\n query = 'C';\r\n results = signal<ChordSearchResult[]>([]);\r\n sections = signal<ChordSection[]>([]);\r\n inputMode = signal<'plain' | 'sections'>('plain');\r\n inputError = signal<string | null>(null);\r\n selectedPositionIndex: Record<string, number> = {};\r\n supportedSuffixes = computed(() =>\r\n this.chordService.suffixes().slice(0, 18).join(', '),\r\n );\r\n readonly hasResults = computed(() =>\r\n this.inputMode() === 'sections'\r\n ? this.sections().length > 0\r\n : this.results().length > 0,\r\n );\r\n\r\n resultsRow = viewChild<ElementRef<HTMLElement>>('resultsRow');\r\n sectionsContainer = viewChild<ElementRef<HTMLElement>>('sectionsContainer');\r\n\r\n exportPanelOpen = signal(false);\r\n exportBgColor = '#ffffff';\r\n exportLineColor = '#000000';\r\n exportTransparent = false;\r\n\r\n constructor(private readonly chordService: ChordService) {\r\n effect(() => this.runSearch(this.language()));\r\n }\r\n\r\n async exportPng(): Promise<void> {\r\n if (this.inputMode() === 'sections') {\r\n const container = this.sectionsContainer()?.nativeElement;\r\n if (!container) return;\r\n for (const [i, section] of this.sections().entries()) {\r\n const sectionEl = container.querySelector<HTMLElement>(\r\n `[data-section=\"${i}\"]`,\r\n );\r\n if (!sectionEl) continue;\r\n const svgs = Array.from(\r\n sectionEl.querySelectorAll<SVGSVGElement>('svg.chord-svg'),\r\n );\r\n if (!svgs.length) continue;\r\n const filename =\r\n section.name\r\n .trim()\r\n .toLowerCase()\r\n .replace(/\\s+/g, '-')\r\n .replace(/[^a-z0-9-]/g, '') || 'section';\r\n await this.renderPng(svgs, filename);\r\n }\r\n } else {\r\n const svgs = Array.from(\r\n this.resultsRow()?.nativeElement.querySelectorAll<SVGSVGElement>(\r\n 'svg.chord-svg',\r\n ) ?? [],\r\n );\r\n if (!svgs.length) return;\r\n await this.renderPng(svgs, 'chords');\r\n }\r\n }\r\n\r\n private async renderPng(\r\n svgs: SVGSVGElement[],\r\n filename: string,\r\n ): Promise<void> {\r\n const padding = 32;\r\n const gap = 24;\r\n const width = 240;\r\n const height = 330;\r\n const scale = 2;\r\n\r\n const canvas = document.createElement('canvas');\r\n canvas.width =\r\n (padding * 2 + svgs.length * width + (svgs.length - 1) * gap) * scale;\r\n canvas.height = (padding * 2 + height) * scale;\r\n\r\n const ctx = canvas.getContext('2d');\r\n if (!ctx) return;\r\n\r\n ctx.scale(scale, scale);\r\n\r\n if (!this.exportTransparent) {\r\n ctx.fillStyle = this.exportBgColor;\r\n ctx.fillRect(0, 0, canvas.width, canvas.height);\r\n }\r\n\r\n const bg = this.exportTransparent ? 'transparent' : this.exportBgColor;\r\n const fg = this.exportLineColor;\r\n // ponytail: text inside filled dots/barres inverts bg; transparent defaults to white\r\n const fgInverse = this.exportTransparent ? '#ffffff' : this.exportBgColor;\r\n\r\n for (const [index, svg] of svgs.entries()) {\r\n const clone = svg.cloneNode(true) as SVGSVGElement;\r\n\r\n clone.insertAdjacentHTML(\r\n 'afterbegin',\r\n `<style>\r\n .chord-svg{font-family:Roboto,Arial,sans-serif;font-weight:400}\r\n .card-bg{fill:${bg}}.title{font-size:42px;font-weight:400;fill:${fg}}\r\n .grid line{stroke:${fg};stroke-width:2.6;stroke-linecap:square}\r\n .grid line.nut{stroke-width:7}.barres rect,.dots circle{fill:${fg}}\r\n .barres text,.dots text{fill:${fgInverse};font-size:16px;font-weight:400;dominant-baseline:central;alignment-baseline:middle}\r\n .markers text{fill:${fg};font-size:30px;font-weight:400}\r\n .fret-number{fill:${fg};font-size:42px;font-weight:400}\r\n .string-labels text{fill:${fg};font-size:18px;font-weight:400}\r\n </style>`,\r\n );\r\n\r\n const url = URL.createObjectURL(\r\n new Blob([new XMLSerializer().serializeToString(clone)], {\r\n type: 'image/svg+xml',\r\n }),\r\n );\r\n const image = new Image();\r\n image.src = url;\r\n await image.decode();\r\n\r\n ctx.drawImage(\r\n image,\r\n padding + index * (width + gap),\r\n padding,\r\n width,\r\n height,\r\n );\r\n URL.revokeObjectURL(url);\r\n }\r\n\r\n const link = document.createElement('a');\r\n link.download = `${filename}.png`;\r\n link.href = canvas.toDataURL('image/png');\r\n link.click();\r\n }\r\n\r\n runSearch(language: Language = this.language()): void {\r\n const query = this.query.trim();\r\n\r\n if (this.chordService.isSectionFormat(query)) {\r\n this.inputMode.set('sections');\r\n const { sections, error } = this.chordService.searchSections(\r\n query,\r\n language,\r\n );\r\n this.inputError.set(error);\r\n this.sections.set(sections);\r\n this.results.set([]);\r\n for (const section of sections) {\r\n for (const result of section.results) {\r\n if (this.selectedPositionIndex[result.id] === undefined) {\r\n this.selectedPositionIndex[result.id] = 0;\r\n }\r\n }\r\n }\r\n } else {\r\n this.inputMode.set('plain');\r\n this.inputError.set(null);\r\n const results = this.chordService.search(query, language);\r\n this.results.set(results);\r\n this.sections.set([]);\r\n for (const result of results) {\r\n if (this.selectedPositionIndex[result.id] === undefined) {\r\n this.selectedPositionIndex[result.id] = 0;\r\n }\r\n }\r\n }\r\n }\r\n\r\n selectPosition(resultId: string, value: string | number): void {\r\n this.selectedPositionIndex[resultId] = Number(value);\r\n }\r\n\r\n selectedIndex(resultId: string): number {\r\n return this.selectedPositionIndex[resultId] ?? 0;\r\n }\r\n\r\n selectedPosition(result: ChordSearchResult): ChordsDbPosition | null {\r\n if (!result.positions.length) return null;\r\n return (\r\n result.positions[this.selectedPositionIndex[result.id] ?? 0] ??\r\n result.positions[0]\r\n );\r\n }\r\n\r\n trackByResultId(_: number, result: ChordSearchResult): string {\r\n return result.id;\r\n }\r\n}\r\n","<main class=\"page-shell\">\r\n <section class=\"hero-card\">\r\n <p class=\"eyebrow\">{{ text().eyebrow }}</p>\r\n <h1>{{ text().title }}</h1>\r\n\r\n <p class=\"description\">\r\n {{ text().descriptionStart }}\r\n <strong>{{ text().descriptionLimit }}</strong>\r\n {{ text().descriptionEnd }}\r\n <strong>C, F#, C#m, Bb</strong>.\r\n </p>\r\n\r\n <div class=\"search-row\">\r\n <label for=\"chordInput\">{{ text().inputLabel }}</label>\r\n <input\r\n id=\"chordInput\"\r\n type=\"text\"\r\n [(ngModel)]=\"query\"\r\n (input)=\"runSearch()\"\r\n placeholder=\"C, F#, C#m, Bb\"\r\n autocomplete=\"off\"\r\n />\r\n <div class=\"export-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"export-btn\"\r\n (click)=\"exportPanelOpen.set(!exportPanelOpen())\"\r\n [disabled]=\"!hasResults()\"\r\n [class.open]=\"exportPanelOpen()\"\r\n >\r\n {{ text().exportPng }}\r\n <span class=\"chevron\" aria-hidden=\"true\">▾</span>\r\n </button>\r\n <div class=\"export-panel\" [class.open]=\"exportPanelOpen()\">\r\n <div class=\"export-panel-inner\">\r\n <div class=\"export-options\">\r\n <label class=\"color-opt\">\r\n <span>{{ text().bgColor }}</span>\r\n <div class=\"color-row\">\r\n <input\r\n type=\"color\"\r\n [(ngModel)]=\"exportBgColor\"\r\n [disabled]=\"exportTransparent\"\r\n />\r\n <label class=\"check-label\">\r\n <input type=\"checkbox\" [(ngModel)]=\"exportTransparent\" />\r\n {{ text().transparent }}\r\n </label>\r\n </div>\r\n </label>\r\n <label class=\"color-opt\">\r\n <span>{{ text().lineColor }}</span>\r\n <input type=\"color\" [(ngModel)]=\"exportLineColor\" />\r\n </label>\r\n <button\r\n type=\"button\"\r\n class=\"download-btn\"\r\n (click)=\"exportPng()\"\r\n [disabled]=\"!hasResults()\"\r\n >\r\n {{ text().download }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (inputError()) {\r\n <p class=\"input-error\">{{ inputError() }}</p>\r\n }\r\n\r\n <p class=\"hint\">{{ text().availableTypes }} {{ supportedSuffixes() }}...</p>\r\n </section>\r\n\r\n @if (inputMode() === 'plain') {\r\n <section\r\n #resultsRow\r\n class=\"results-row\"\r\n [attr.aria-label]=\"text().resultsLabel\"\r\n >\r\n @for (result of results(); track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">{{ text().normalizedAs }} {{ result.dbName }}</p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </section>\r\n } @else if (inputMode() === 'sections') {\r\n <div #sectionsContainer class=\"sections-container\">\r\n @for (section of sections(); let si = $index; track si) {\r\n <section\r\n class=\"section-block\"\r\n [attr.data-section]=\"si\"\r\n [attr.aria-label]=\"section.name\"\r\n >\r\n <h2 class=\"section-name\">{{ section.name }}</h2>\r\n <div class=\"results-row\">\r\n @for (result of section.results; track trackByResultId($index, result)) {\r\n <article class=\"result-card\" [class.has-error]=\"result.error\">\r\n <header class=\"result-header\">\r\n <div>\r\n <p class=\"result-label\">{{ text().chord }}</p>\r\n <h2>{{ result.displayName }}</h2>\r\n @if (result.dbName && result.raw !== result.displayName) {\r\n <p class=\"db-name\">\r\n {{ text().normalizedAs }} {{ result.dbName }}\r\n </p>\r\n }\r\n </div>\r\n @if (result.positions.length > 1) {\r\n <label class=\"select-label\">\r\n {{ text().position }}\r\n <select\r\n [ngModel]=\"selectedPositionIndex[result.id]\"\r\n (ngModelChange)=\"selectPosition(result.id, $event)\"\r\n >\r\n @for (position of result.positions; track $index) {\r\n <option [value]=\"$index\">\r\n {{ $index + 1 }} {{ text().of }} {{ result.positions.length }}\r\n </option>\r\n }\r\n </select>\r\n </label>\r\n }\r\n </header>\r\n @if (result.error) {\r\n <p class=\"error-text\">{{ result.error }}</p>\r\n } @else if (selectedPosition(result); as position) {\r\n <div class=\"svg-wrap\">\r\n <app-chord-diagram\r\n [title]=\"result.displayName\"\r\n [position]=\"position\"\r\n [positionIndex]=\"selectedIndex(result.id)\"\r\n />\r\n </div>\r\n }\r\n </article>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n }\r\n\r\n <footer class=\"app-footer\">\r\n <p>\r\n {{ text().openSource }}\r\n <a\r\n href=\"https://github.com/elparaquecosadeque/chord-generator\"\r\n target=\"_blank\"\r\n rel=\"noreferrer\"\r\n >\r\n elparaquecosadeque/chord-generator </a\r\n >. {{ text().contributions }}\r\n </p>\r\n <p>{{ text().assistance }}</p>\r\n </footer>\r\n</main>\r\n","/*\r\n * Public API Surface of chord-finder\r\n */\r\n\r\nexport * from './lib/chord-finder';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.ChordService"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gblp/chord-finder",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -24,15 +24,22 @@ interface ChordSearchResult {
24
24
  positions: ChordsDbPosition[];
25
25
  error?: string;
26
26
  }
27
+ interface ChordSection {
28
+ name: string;
29
+ results: ChordSearchResult[];
30
+ }
27
31
 
28
32
  declare class ChordService {
29
- readonly maxBatchSize = 5;
33
+ private readonly MAX_SECTIONS;
34
+ private readonly MAX_CHORDS_PER_SECTION;
30
35
  private readonly guitarDb;
31
36
  private readonly dbRootMap;
32
37
  private readonly suffixAliases;
33
- search(input: string, language?: Language): {
34
- results: ChordSearchResult[];
35
- wasLimited: boolean;
38
+ search(input: string, language?: Language): ChordSearchResult[];
39
+ isSectionFormat(input: string): boolean;
40
+ searchSections(input: string, language?: Language): {
41
+ sections: ChordSection[];
42
+ error: string | null;
36
43
  };
37
44
  suffixes(): string[];
38
45
  private searchSingle;
@@ -54,7 +61,10 @@ declare class ChordFinderComponent {
54
61
  readonly descriptionEnd: "separated by commas. Sharps and flats are supported:";
55
62
  readonly inputLabel: "Chords";
56
63
  readonly exportPng: "Export PNG";
57
- readonly limited: "Only the first 5 chords are rendered.";
64
+ readonly bgColor: "Background";
65
+ readonly lineColor: "Diagram color";
66
+ readonly transparent: "Transparent";
67
+ readonly download: "Download";
58
68
  readonly availableTypes: "Available chord types:";
59
69
  readonly resultsLabel: "Chord results";
60
70
  readonly chord: "Chord";
@@ -72,7 +82,10 @@ declare class ChordFinderComponent {
72
82
  readonly descriptionEnd: "separados por coma. Soporta sostenidos y bemoles:";
73
83
  readonly inputLabel: "Acordes";
74
84
  readonly exportPng: "Exportar PNG";
75
- readonly limited: "Solo se renderizan los primeros 5 acordes.";
85
+ readonly bgColor: "Fondo";
86
+ readonly lineColor: "Color del diagrama";
87
+ readonly transparent: "Transparente";
88
+ readonly download: "Descargar";
76
89
  readonly availableTypes: "Tipos de acorde disponibles:";
77
90
  readonly resultsLabel: "Resultados de acordes";
78
91
  readonly chord: "Acorde";
@@ -85,12 +98,21 @@ declare class ChordFinderComponent {
85
98
  }>;
86
99
  query: string;
87
100
  results: _angular_core.WritableSignal<ChordSearchResult[]>;
88
- wasLimited: _angular_core.WritableSignal<boolean>;
101
+ sections: _angular_core.WritableSignal<ChordSection[]>;
102
+ inputMode: _angular_core.WritableSignal<"plain" | "sections">;
103
+ inputError: _angular_core.WritableSignal<string | null>;
89
104
  selectedPositionIndex: Record<string, number>;
90
105
  supportedSuffixes: _angular_core.Signal<string>;
91
- resultsRow: _angular_core.Signal<ElementRef<HTMLElement>>;
106
+ readonly hasResults: _angular_core.Signal<boolean>;
107
+ resultsRow: _angular_core.Signal<ElementRef<HTMLElement> | undefined>;
108
+ sectionsContainer: _angular_core.Signal<ElementRef<HTMLElement> | undefined>;
109
+ exportPanelOpen: _angular_core.WritableSignal<boolean>;
110
+ exportBgColor: string;
111
+ exportLineColor: string;
112
+ exportTransparent: boolean;
92
113
  constructor(chordService: ChordService);
93
114
  exportPng(): Promise<void>;
115
+ private renderPng;
94
116
  runSearch(language?: Language): void;
95
117
  selectPosition(resultId: string, value: string | number): void;
96
118
  selectedIndex(resultId: string): number;