@jjlmoya/utils-performing-arts 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.github/workflows/npm-publish.yml +40 -0
  2. package/.gitignore +6 -0
  3. package/.stylelintrc.json +98 -0
  4. package/astro.config.mjs +19 -0
  5. package/eslint.config.js +201 -0
  6. package/package.json +79 -0
  7. package/prompts/create_tool.md +98 -0
  8. package/prompts/i18n/de.md +16 -0
  9. package/prompts/i18n/en.md +16 -0
  10. package/prompts/i18n/es.md +16 -0
  11. package/prompts/i18n/fr.md +16 -0
  12. package/prompts/i18n/id.md +16 -0
  13. package/prompts/i18n/it.md +16 -0
  14. package/prompts/i18n/ja.md +16 -0
  15. package/prompts/i18n/ko.md +16 -0
  16. package/prompts/i18n/nl.md +16 -0
  17. package/prompts/i18n/pl.md +16 -0
  18. package/prompts/i18n/pt.md +16 -0
  19. package/prompts/i18n/ru.md +16 -0
  20. package/prompts/i18n/sv.md +16 -0
  21. package/prompts/i18n/tr.md +16 -0
  22. package/prompts/i18n/zh.md +16 -0
  23. package/prompts/seo.md +58 -0
  24. package/prompts/translations/french.md +33 -0
  25. package/scripts/postinstall.mjs +27 -0
  26. package/src/category/PerformingArtsCategorySEO.astro +9 -0
  27. package/src/category/i18n/de.ts +27 -0
  28. package/src/category/i18n/en.ts +27 -0
  29. package/src/category/i18n/es.ts +27 -0
  30. package/src/category/i18n/fr.ts +27 -0
  31. package/src/category/i18n/id.ts +27 -0
  32. package/src/category/i18n/it.ts +27 -0
  33. package/src/category/i18n/ja.ts +27 -0
  34. package/src/category/i18n/ko.ts +27 -0
  35. package/src/category/i18n/nl.ts +27 -0
  36. package/src/category/i18n/pl.ts +27 -0
  37. package/src/category/i18n/pt.ts +27 -0
  38. package/src/category/i18n/ru.ts +27 -0
  39. package/src/category/i18n/sv.ts +27 -0
  40. package/src/category/i18n/tr.ts +27 -0
  41. package/src/category/i18n/zh.ts +27 -0
  42. package/src/category/index.ts +10 -0
  43. package/src/components/PreviewNavSidebar.astro +116 -0
  44. package/src/components/PreviewToolbar.astro +143 -0
  45. package/src/data.ts +10 -0
  46. package/src/entries.ts +3 -0
  47. package/src/env.d.ts +5 -0
  48. package/src/index.ts +20 -0
  49. package/src/layouts/PreviewLayout.astro +118 -0
  50. package/src/pages/[locale]/[slug].astro +165 -0
  51. package/src/pages/[locale].astro +251 -0
  52. package/src/pages/index.astro +4 -0
  53. package/src/tests/bibliography_wellformed_export.test.ts +46 -0
  54. package/src/tests/category_seo_quality.test.ts +74 -0
  55. package/src/tests/diacritics_density.test.ts +118 -0
  56. package/src/tests/faq_count.test.ts +18 -0
  57. package/src/tests/i18n_coverage.test.ts +34 -0
  58. package/src/tests/inverted_punctuation.test.ts +84 -0
  59. package/src/tests/locale_completeness.test.ts +23 -0
  60. package/src/tests/mocks/astro_mock.js +2 -0
  61. package/src/tests/no_em_dash.test.ts +47 -0
  62. package/src/tests/no_en_dash.test.ts +70 -0
  63. package/src/tests/no_h1_in_components.test.ts +48 -0
  64. package/src/tests/pagespeed_best_practices.test.ts +198 -0
  65. package/src/tests/qa-test-helpers.ts +32 -0
  66. package/src/tests/qa_bibliography_links.test.ts +43 -0
  67. package/src/tests/qa_claim_evidence.test.ts +69 -0
  68. package/src/tests/qa_logic_reference_coverage.test.ts +46 -0
  69. package/src/tests/qa_runtime_i18n.test.ts +100 -0
  70. package/src/tests/schemas_fulfillment.test.ts +23 -0
  71. package/src/tests/script_density.test.ts +94 -0
  72. package/src/tests/seo_length.test.ts +22 -0
  73. package/src/tests/seo_parity.test.ts +60 -0
  74. package/src/tests/seo_translation_completeness.test.ts +69 -0
  75. package/src/tests/seo_wellformed_export.test.ts +65 -0
  76. package/src/tests/shared-test-helpers.ts +56 -0
  77. package/src/tests/slug_language_code_format.test.ts +23 -0
  78. package/src/tests/slug_uniqueness.test.ts +81 -0
  79. package/src/tests/spanish_leakage.test.ts +175 -0
  80. package/src/tests/title_quality.test.ts +55 -0
  81. package/src/tests/tool_exports.test.ts +34 -0
  82. package/src/tests/tool_validation.test.ts +16 -0
  83. package/src/tests/translation_copy.test.ts +115 -0
  84. package/src/tool/rehearsal-call-sheet-planner/bibliography.astro +6 -0
  85. package/src/tool/rehearsal-call-sheet-planner/bibliography.ts +12 -0
  86. package/src/tool/rehearsal-call-sheet-planner/component.astro +74 -0
  87. package/src/tool/rehearsal-call-sheet-planner/controller.ts +98 -0
  88. package/src/tool/rehearsal-call-sheet-planner/dom-views.ts +86 -0
  89. package/src/tool/rehearsal-call-sheet-planner/entry.ts +27 -0
  90. package/src/tool/rehearsal-call-sheet-planner/evaluator.ts +27 -0
  91. package/src/tool/rehearsal-call-sheet-planner/i18n/de.ts +60 -0
  92. package/src/tool/rehearsal-call-sheet-planner/i18n/en.ts +158 -0
  93. package/src/tool/rehearsal-call-sheet-planner/i18n/es.ts +60 -0
  94. package/src/tool/rehearsal-call-sheet-planner/i18n/fr.ts +60 -0
  95. package/src/tool/rehearsal-call-sheet-planner/i18n/id.ts +47 -0
  96. package/src/tool/rehearsal-call-sheet-planner/i18n/it.ts +47 -0
  97. package/src/tool/rehearsal-call-sheet-planner/i18n/ja.ts +47 -0
  98. package/src/tool/rehearsal-call-sheet-planner/i18n/ko.ts +47 -0
  99. package/src/tool/rehearsal-call-sheet-planner/i18n/nl.ts +47 -0
  100. package/src/tool/rehearsal-call-sheet-planner/i18n/pl.ts +47 -0
  101. package/src/tool/rehearsal-call-sheet-planner/i18n/pt.ts +47 -0
  102. package/src/tool/rehearsal-call-sheet-planner/i18n/ru.ts +48 -0
  103. package/src/tool/rehearsal-call-sheet-planner/i18n/sv.ts +48 -0
  104. package/src/tool/rehearsal-call-sheet-planner/i18n/tr.ts +48 -0
  105. package/src/tool/rehearsal-call-sheet-planner/i18n/zh.ts +48 -0
  106. package/src/tool/rehearsal-call-sheet-planner/index.ts +11 -0
  107. package/src/tool/rehearsal-call-sheet-planner/logic.test.ts +68 -0
  108. package/src/tool/rehearsal-call-sheet-planner/logic.ts +229 -0
  109. package/src/tool/rehearsal-call-sheet-planner/rehearsal-call-sheet-planner.css +593 -0
  110. package/src/tool/rehearsal-call-sheet-planner/seo.astro +16 -0
  111. package/src/tool/rehearsal-call-sheet-planner/storage.ts +33 -0
  112. package/src/tool/rehearsal-call-sheet-planner/types.ts +84 -0
  113. package/src/tool/rehearsal-call-sheet-planner/ui.ts +70 -0
  114. package/src/tools.ts +8 -0
  115. package/src/types.ts +68 -0
  116. package/tsconfig.json +15 -0
  117. package/vitest.config.ts +20 -0
@@ -0,0 +1,593 @@
1
+ :root {
2
+ --n-ink: #18232b;
3
+ --n-muted: #596872;
4
+ --n-page: #f6f2e9;
5
+ --n-surface: #fffdf8;
6
+ --n-surface-soft: #ece7da;
7
+ --n-line: #d6d0c3;
8
+ --n-coral: #d95d4f;
9
+ --n-coral-soft: #f5d8cf;
10
+ --n-teal: #2e7772;
11
+ --n-teal-soft: #d5e9e2;
12
+ --n-amber: #a86e2c;
13
+ --n-amber-soft: #f2e3c5;
14
+ --n-shadow: rgba(24, 35, 43, 0.1);
15
+ }
16
+
17
+ .theme-dark {
18
+ --n-ink: #f4f0e7;
19
+ --n-muted: #b8c0bd;
20
+ --n-page: #182126;
21
+ --n-surface: #222c30;
22
+ --n-surface-soft: #303b3d;
23
+ --n-line: #4c5959;
24
+ --n-coral: #ff8875;
25
+ --n-coral-soft: #573b37;
26
+ --n-teal: #7dc3b3;
27
+ --n-teal-soft: #294742;
28
+ --n-amber: #e2ae62;
29
+ --n-amber-soft: #55452c;
30
+ --n-shadow: rgba(0, 0, 0, 0.3);
31
+ }
32
+
33
+ .call-sheet-planner {
34
+ color: var(--n-ink);
35
+ background: var(--n-page);
36
+ border: 1px solid var(--n-line);
37
+ border-radius: 1.4rem;
38
+ padding: clamp(1rem, 3vw, 2rem);
39
+ box-shadow: 0 1rem 2rem var(--n-shadow);
40
+ }
41
+
42
+ .planner-actions,
43
+ .preset-group,
44
+ .action-group,
45
+ .block-heading,
46
+ .timeline-card-top,
47
+ .call-sheet-heading,
48
+ .plan-summary,
49
+ .summary-lists,
50
+ .field-grid {
51
+ display: flex;
52
+ align-items: center;
53
+ }
54
+
55
+ .planner-actions {
56
+ display: grid;
57
+ grid-template-columns: minmax(0, 1fr) auto;
58
+ align-items: start;
59
+ gap: 1rem;
60
+ padding-bottom: 1.2rem;
61
+ border-bottom: 1px solid var(--n-line);
62
+ }
63
+
64
+ .preset-group,
65
+ .action-group {
66
+ gap: 0.55rem;
67
+ flex-wrap: wrap;
68
+ }
69
+
70
+ .action-group {
71
+ justify-content: flex-end;
72
+ }
73
+
74
+ .preset-group > span,
75
+ .eyebrow,
76
+ .step-number {
77
+ color: var(--n-muted);
78
+ font-size: 0.72rem;
79
+ font-weight: 800;
80
+ letter-spacing: 0.08em;
81
+ text-transform: uppercase;
82
+ }
83
+
84
+ button {
85
+ border: 1px solid var(--n-line);
86
+ border-radius: 999px;
87
+ padding: 0.6rem 0.85rem;
88
+ color: var(--n-ink);
89
+ background: var(--n-surface);
90
+ font: inherit;
91
+ font-size: 0.82rem;
92
+ font-weight: 750;
93
+ cursor: pointer;
94
+ }
95
+
96
+ button:hover,
97
+ button:focus-visible {
98
+ border-color: var(--n-coral);
99
+ color: var(--n-coral);
100
+ }
101
+
102
+ button:focus-visible,
103
+ input:focus-visible,
104
+ textarea:focus-visible,
105
+ a:focus-visible {
106
+ outline: 3px solid var(--n-amber);
107
+ outline-offset: 3px;
108
+ }
109
+
110
+ .primary-button {
111
+ border-color: var(--n-coral);
112
+ color: var(--n-surface);
113
+ background: var(--n-coral);
114
+ }
115
+
116
+ .primary-button:hover,
117
+ .primary-button:focus-visible {
118
+ color: var(--n-surface);
119
+ background: var(--n-ink);
120
+ }
121
+
122
+ .planner-layout {
123
+ display: grid;
124
+ grid-template-columns: minmax(17rem, 0.8fr) minmax(0, 1.5fr);
125
+ gap: clamp(1rem, 3vw, 2rem);
126
+ padding-top: 1.5rem;
127
+ }
128
+
129
+ .planner-controls {
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 1rem;
133
+ }
134
+
135
+ .control-block {
136
+ display: flex;
137
+ flex-direction: column;
138
+ gap: 0.55rem;
139
+ padding: 1rem;
140
+ border: 1px solid var(--n-line);
141
+ border-radius: 1rem;
142
+ background: var(--n-surface);
143
+ }
144
+
145
+ .block-heading {
146
+ justify-content: space-between;
147
+ margin-bottom: 0.2rem;
148
+ color: var(--n-teal);
149
+ font-weight: 850;
150
+ }
151
+
152
+ .step-number {
153
+ color: var(--n-coral);
154
+ }
155
+
156
+ label {
157
+ color: var(--n-ink);
158
+ font-size: 0.86rem;
159
+ font-weight: 750;
160
+ }
161
+
162
+ input,
163
+ textarea {
164
+ width: 100%;
165
+ box-sizing: border-box;
166
+ border: 1px solid var(--n-line);
167
+ border-radius: 0.65rem;
168
+ padding: 0.7rem 0.75rem;
169
+ color: var(--n-ink);
170
+ background: var(--n-page);
171
+ font: inherit;
172
+ line-height: 1.45;
173
+ }
174
+
175
+ textarea {
176
+ resize: vertical;
177
+ min-height: 5rem;
178
+ }
179
+
180
+ .field-grid {
181
+ gap: 0.65rem;
182
+ }
183
+
184
+ .field-grid label {
185
+ flex: 1;
186
+ }
187
+
188
+ small {
189
+ color: var(--n-muted);
190
+ font-size: 0.76rem;
191
+ line-height: 1.45;
192
+ }
193
+
194
+ .reset-button {
195
+ align-self: flex-start;
196
+ color: var(--n-coral);
197
+ background: transparent;
198
+ }
199
+
200
+ .planner-result {
201
+ min-width: 0;
202
+ }
203
+
204
+ .plan-summary {
205
+ display: grid;
206
+ grid-template-columns: repeat(4, 1fr);
207
+ gap: 0.65rem;
208
+ margin-bottom: 1.2rem;
209
+ }
210
+
211
+ .plan-summary div {
212
+ display: flex;
213
+ flex-direction: column;
214
+ gap: 0.15rem;
215
+ padding: 0.8rem;
216
+ border-radius: 0.8rem;
217
+ background: var(--n-surface-soft);
218
+ }
219
+
220
+ .input-issues {
221
+ margin-bottom: 1.2rem;
222
+ padding: 1rem;
223
+ border: 1px solid var(--n-amber);
224
+ border-radius: 1rem;
225
+ background: var(--n-amber-soft);
226
+ }
227
+
228
+ .input-issues-heading {
229
+ display: flex;
230
+ align-items: baseline;
231
+ justify-content: space-between;
232
+ gap: 1rem;
233
+ }
234
+
235
+ .input-issues-heading h3 {
236
+ margin: 0;
237
+ color: var(--n-ink);
238
+ font-size: 1rem;
239
+ }
240
+
241
+ .input-issues-heading span,
242
+ .input-issues li span {
243
+ color: var(--n-muted);
244
+ font-size: 0.78rem;
245
+ }
246
+
247
+ .input-issues ul {
248
+ display: grid;
249
+ gap: 0.65rem;
250
+ margin: 0.8rem 0 0;
251
+ padding: 0;
252
+ list-style: none;
253
+ }
254
+
255
+ .input-issues li {
256
+ display: grid;
257
+ gap: 0.25rem;
258
+ padding-top: 0.65rem;
259
+ border-top: 1px solid var(--n-line);
260
+ }
261
+
262
+ .input-issues li strong {
263
+ color: var(--n-ink);
264
+ font-size: 0.8rem;
265
+ }
266
+
267
+ .input-issues code {
268
+ display: block;
269
+ overflow-wrap: anywhere;
270
+ color: var(--n-ink);
271
+ font: inherit;
272
+ font-size: 0.78rem;
273
+ }
274
+
275
+ .plan-summary strong {
276
+ color: var(--n-coral);
277
+ font-size: 1.45rem;
278
+ }
279
+
280
+ .plan-summary span {
281
+ color: var(--n-muted);
282
+ font-size: 0.72rem;
283
+ font-weight: 800;
284
+ text-transform: uppercase;
285
+ }
286
+
287
+ .plan-message {
288
+ margin: -0.65rem 0 1.2rem;
289
+ color: var(--n-muted);
290
+ font-size: 0.82rem;
291
+ line-height: 1.45;
292
+ }
293
+
294
+ .timeline {
295
+ margin-bottom: 1.2rem;
296
+ padding: 1rem;
297
+ border-radius: 1rem;
298
+ background: var(--n-surface);
299
+ }
300
+
301
+ .timeline-scale {
302
+ display: flex;
303
+ justify-content: space-between;
304
+ color: var(--n-muted);
305
+ font-size: 0.75rem;
306
+ font-weight: 800;
307
+ }
308
+
309
+ .timeline-track {
310
+ display: flex;
311
+ gap: 0.5rem;
312
+ min-height: 15rem;
313
+ margin-top: 0.7rem;
314
+ padding-top: 0.7rem;
315
+ border-top: 2px solid var(--n-line);
316
+ }
317
+
318
+ .timeline-card {
319
+ flex: 0 0 var(--scene-width);
320
+ align-self: stretch;
321
+ box-sizing: border-box;
322
+ min-width: 7rem;
323
+ padding: 0.75rem;
324
+ border-left: 4px solid var(--n-teal);
325
+ border-radius: 0.65rem;
326
+ background: var(--n-teal-soft);
327
+ overflow-wrap: anywhere;
328
+ }
329
+
330
+ .timeline-card.conflict {
331
+ border-color: var(--n-amber);
332
+ background: var(--n-amber-soft);
333
+ }
334
+
335
+ .timeline-card.unscheduled {
336
+ border-color: var(--n-coral);
337
+ background: var(--n-coral-soft);
338
+ }
339
+
340
+ .timeline-card-top {
341
+ display: grid;
342
+ gap: 0.35rem;
343
+ color: var(--n-muted);
344
+ font-size: 0.68rem;
345
+ font-weight: 800;
346
+ }
347
+
348
+ .timeline-card-top b {
349
+ color: var(--n-ink);
350
+ text-align: left;
351
+ white-space: nowrap;
352
+ }
353
+
354
+ .timeline-card h3 {
355
+ margin: 1.2rem 0 0.35rem;
356
+ color: var(--n-ink);
357
+ font-size: 1rem;
358
+ }
359
+
360
+ .timeline-card p {
361
+ margin: 0;
362
+ color: var(--n-muted);
363
+ font-size: 0.78rem;
364
+ }
365
+
366
+ .timeline-card ul {
367
+ margin: 1rem 0 0;
368
+ padding-left: 1rem;
369
+ color: var(--n-ink);
370
+ font-size: 0.75rem;
371
+ line-height: 1.45;
372
+ }
373
+
374
+ .empty-state {
375
+ padding: 2rem;
376
+ border: 1px dashed var(--n-line);
377
+ border-radius: 1rem;
378
+ color: var(--n-muted);
379
+ text-align: center;
380
+ }
381
+
382
+ .call-sheet {
383
+ margin-bottom: 1.2rem;
384
+ border: 1px solid var(--n-line);
385
+ border-radius: 1rem;
386
+ background: var(--n-surface);
387
+ overflow: hidden;
388
+ }
389
+
390
+ .call-sheet-heading {
391
+ justify-content: space-between;
392
+ align-items: flex-end;
393
+ gap: 1rem;
394
+ padding: 1rem;
395
+ border-bottom: 1px solid var(--n-line);
396
+ }
397
+
398
+ .call-sheet-heading h3 {
399
+ margin: 0.25rem 0 0;
400
+ font-size: 1.4rem;
401
+ }
402
+
403
+ .call-sheet-heading p {
404
+ margin: 0;
405
+ color: var(--n-muted);
406
+ font-size: 0.78rem;
407
+ text-align: right;
408
+ }
409
+
410
+ .table-wrap {
411
+ overflow-x: auto;
412
+ }
413
+
414
+ table {
415
+ width: 100%;
416
+ border-collapse: collapse;
417
+ font-size: 0.78rem;
418
+ }
419
+
420
+ th,
421
+ td {
422
+ padding: 0.75rem;
423
+ border-bottom: 1px solid var(--n-line);
424
+ text-align: left;
425
+ vertical-align: top;
426
+ }
427
+
428
+ th {
429
+ color: var(--n-muted);
430
+ font-size: 0.68rem;
431
+ text-transform: uppercase;
432
+ }
433
+
434
+ td small {
435
+ display: block;
436
+ margin-top: 0.25rem;
437
+ }
438
+
439
+ .status-pill {
440
+ display: inline-block;
441
+ border-radius: 999px;
442
+ padding: 0.25rem 0.45rem;
443
+ color: var(--n-teal);
444
+ background: var(--n-teal-soft);
445
+ font-size: 0.68rem;
446
+ font-weight: 850;
447
+ }
448
+
449
+ .status-pill.conflict {
450
+ color: var(--n-amber);
451
+ background: var(--n-amber-soft);
452
+ }
453
+
454
+ .status-pill.unscheduled {
455
+ color: var(--n-coral);
456
+ background: var(--n-coral-soft);
457
+ }
458
+
459
+ .summary-lists {
460
+ align-items: stretch;
461
+ gap: 1rem;
462
+ }
463
+
464
+ .summary-lists section {
465
+ flex: 1;
466
+ min-width: 0;
467
+ padding: 1rem;
468
+ border-radius: 1rem;
469
+ background: var(--n-surface-soft);
470
+ }
471
+
472
+ .summary-lists h3 {
473
+ margin: 0 0 0.75rem;
474
+ font-size: 0.95rem;
475
+ }
476
+
477
+ .summary-lists ul {
478
+ display: flex;
479
+ flex-direction: column;
480
+ gap: 0.55rem;
481
+ margin: 0;
482
+ padding: 0;
483
+ list-style: none;
484
+ }
485
+
486
+ .summary-lists li {
487
+ display: grid;
488
+ grid-template-columns: 1fr auto;
489
+ gap: 0.2rem 0.7rem;
490
+ color: var(--n-ink);
491
+ font-size: 0.8rem;
492
+ }
493
+
494
+ .summary-lists li span,
495
+ .summary-lists li small {
496
+ text-align: right;
497
+ }
498
+
499
+ .summary-lists li small {
500
+ grid-column: 1 / -1;
501
+ text-align: left;
502
+ }
503
+
504
+ .rules-note {
505
+ margin: 1.4rem 0 0;
506
+ color: var(--n-muted);
507
+ font-size: 0.78rem;
508
+ line-height: 1.55;
509
+ }
510
+
511
+ .rules-note a {
512
+ color: var(--n-teal);
513
+ font-weight: 800;
514
+ }
515
+
516
+ @media (max-width: 900px) {
517
+ .planner-layout {
518
+ grid-template-columns: 1fr;
519
+ }
520
+ }
521
+
522
+ @media (max-width: 620px) {
523
+ .planner-actions {
524
+ grid-template-columns: 1fr;
525
+ }
526
+
527
+ .action-group {
528
+ justify-content: flex-start;
529
+ }
530
+
531
+ .input-issues-heading {
532
+ display: block;
533
+ }
534
+
535
+ .input-issues-heading span {
536
+ display: block;
537
+ margin-top: 0.3rem;
538
+ }
539
+
540
+ .plan-summary {
541
+ grid-template-columns: repeat(2, 1fr);
542
+ }
543
+
544
+ .timeline-track {
545
+ display: grid;
546
+ min-height: 0;
547
+ }
548
+
549
+ .timeline-card {
550
+ min-height: 8rem;
551
+ width: 100%;
552
+ }
553
+
554
+ .call-sheet-heading,
555
+ .summary-lists {
556
+ display: block;
557
+ }
558
+
559
+ .call-sheet-heading p {
560
+ margin-top: 0.75rem;
561
+ text-align: left;
562
+ }
563
+
564
+ .summary-lists section + section {
565
+ margin-top: 1rem;
566
+ }
567
+ }
568
+
569
+ @media print {
570
+ .call-sheet-planner {
571
+ border: 0;
572
+ padding: 0;
573
+ box-shadow: none;
574
+ }
575
+
576
+ .planner-actions,
577
+ .planner-controls,
578
+ .rules-note,
579
+ .plan-summary,
580
+ .timeline,
581
+ .summary-lists {
582
+ display: none;
583
+ }
584
+
585
+ .planner-layout {
586
+ display: block;
587
+ padding-top: 0;
588
+ }
589
+
590
+ .timeline {
591
+ break-inside: avoid;
592
+ }
593
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { rehearsalCallSheetPlanner } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props as Props;
11
+ const loader = rehearsalCallSheetPlanner.i18n[locale] || rehearsalCallSheetPlanner.i18n.en;
12
+ const content = await loader?.();
13
+ if (!content) return null;
14
+ ---
15
+
16
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,33 @@
1
+ import type { PlannerSettings } from './types';
2
+
3
+ const STORAGE_KEY = 'jjlmoya-performing-arts-call-sheet-v1';
4
+
5
+ function isPlannerSettings(value: unknown): value is PlannerSettings {
6
+ if (!value || typeof value !== 'object') return false;
7
+ const candidate = value as Record<string, unknown>;
8
+ const stringKeys = ['showName', 'date', 'dayStart', 'dayEnd', 'scenesText', 'participantAvailabilityText', 'roomAvailabilityText', 'notes'];
9
+ return stringKeys.every((key) => typeof candidate[key] === 'string') && typeof candidate.breakMinutes === 'number';
10
+ }
11
+
12
+ export function readPlannerSettings(): PlannerSettings | null {
13
+ try {
14
+ const stored = localStorage.getItem(STORAGE_KEY);
15
+ if (!stored) return null;
16
+ const value: unknown = JSON.parse(stored);
17
+ return isPlannerSettings(value) ? value : null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export function writePlannerSettings(settings: PlannerSettings): void {
24
+ try {
25
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
26
+ } catch {}
27
+ }
28
+
29
+ export function clearPlannerSettings(): void {
30
+ try {
31
+ localStorage.removeItem(STORAGE_KEY);
32
+ } catch {}
33
+ }
@@ -0,0 +1,84 @@
1
+ export type SceneStatus = 'scheduled' | 'conflict' | 'unscheduled';
2
+
3
+ export type SceneIssueKey =
4
+ | 'dayOverflow'
5
+ | 'participantAvailability'
6
+ | 'roomAvailability'
7
+ | 'missingParticipantAvailability'
8
+ | 'missingRoomAvailability';
9
+
10
+ export interface PlannerSettings {
11
+ showName: string;
12
+ date: string;
13
+ dayStart: string;
14
+ dayEnd: string;
15
+ breakMinutes: number;
16
+ scenesText: string;
17
+ participantAvailabilityText: string;
18
+ roomAvailabilityText: string;
19
+ notes: string;
20
+ }
21
+
22
+ export interface SceneInput {
23
+ id: string;
24
+ name: string;
25
+ duration: number;
26
+ room: string;
27
+ participants: string[];
28
+ notes: string;
29
+ }
30
+
31
+ export interface SceneIssue {
32
+ key: SceneIssueKey;
33
+ detail: string;
34
+ }
35
+
36
+ export type InputIssueSection = 'scenes' | 'people' | 'rooms';
37
+
38
+ export interface InputIssue {
39
+ section: InputIssueSection;
40
+ line: number;
41
+ value: string;
42
+ detail: string;
43
+ }
44
+
45
+ export interface ScheduledScene extends SceneInput {
46
+ start: number;
47
+ end: number;
48
+ status: SceneStatus;
49
+ issues: SceneIssue[];
50
+ }
51
+
52
+ export interface AvailabilityWindow {
53
+ start: number;
54
+ end: number;
55
+ }
56
+
57
+ export type AvailabilityMap = Record<string, AvailabilityWindow[]>;
58
+
59
+ export interface PersonSummary {
60
+ name: string;
61
+ sceneCount: number;
62
+ firstCall: number;
63
+ lastCall: number;
64
+ }
65
+
66
+ export interface RoomSummary {
67
+ name: string;
68
+ sceneCount: number;
69
+ firstCall: number;
70
+ lastCall: number;
71
+ }
72
+
73
+ export interface CallSheetResult {
74
+ settings: PlannerSettings;
75
+ scenes: ScheduledScene[];
76
+ dayStart: number;
77
+ dayEnd: number;
78
+ scheduledMinutes: number;
79
+ conflictCount: number;
80
+ unscheduledCount: number;
81
+ inputIssues: InputIssue[];
82
+ people: PersonSummary[];
83
+ rooms: RoomSummary[];
84
+ }