@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.
- package/.github/workflows/npm-publish.yml +40 -0
- package/.gitignore +6 -0
- package/.stylelintrc.json +98 -0
- package/astro.config.mjs +19 -0
- package/eslint.config.js +201 -0
- package/package.json +79 -0
- package/prompts/create_tool.md +98 -0
- package/prompts/i18n/de.md +16 -0
- package/prompts/i18n/en.md +16 -0
- package/prompts/i18n/es.md +16 -0
- package/prompts/i18n/fr.md +16 -0
- package/prompts/i18n/id.md +16 -0
- package/prompts/i18n/it.md +16 -0
- package/prompts/i18n/ja.md +16 -0
- package/prompts/i18n/ko.md +16 -0
- package/prompts/i18n/nl.md +16 -0
- package/prompts/i18n/pl.md +16 -0
- package/prompts/i18n/pt.md +16 -0
- package/prompts/i18n/ru.md +16 -0
- package/prompts/i18n/sv.md +16 -0
- package/prompts/i18n/tr.md +16 -0
- package/prompts/i18n/zh.md +16 -0
- package/prompts/seo.md +58 -0
- package/prompts/translations/french.md +33 -0
- package/scripts/postinstall.mjs +27 -0
- package/src/category/PerformingArtsCategorySEO.astro +9 -0
- package/src/category/i18n/de.ts +27 -0
- package/src/category/i18n/en.ts +27 -0
- package/src/category/i18n/es.ts +27 -0
- package/src/category/i18n/fr.ts +27 -0
- package/src/category/i18n/id.ts +27 -0
- package/src/category/i18n/it.ts +27 -0
- package/src/category/i18n/ja.ts +27 -0
- package/src/category/i18n/ko.ts +27 -0
- package/src/category/i18n/nl.ts +27 -0
- package/src/category/i18n/pl.ts +27 -0
- package/src/category/i18n/pt.ts +27 -0
- package/src/category/i18n/ru.ts +27 -0
- package/src/category/i18n/sv.ts +27 -0
- package/src/category/i18n/tr.ts +27 -0
- package/src/category/i18n/zh.ts +27 -0
- package/src/category/index.ts +10 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +10 -0
- package/src/entries.ts +3 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +20 -0
- package/src/layouts/PreviewLayout.astro +118 -0
- package/src/pages/[locale]/[slug].astro +165 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/bibliography_wellformed_export.test.ts +46 -0
- package/src/tests/category_seo_quality.test.ts +74 -0
- package/src/tests/diacritics_density.test.ts +118 -0
- package/src/tests/faq_count.test.ts +18 -0
- package/src/tests/i18n_coverage.test.ts +34 -0
- package/src/tests/inverted_punctuation.test.ts +84 -0
- package/src/tests/locale_completeness.test.ts +23 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_em_dash.test.ts +47 -0
- package/src/tests/no_en_dash.test.ts +70 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/pagespeed_best_practices.test.ts +198 -0
- package/src/tests/qa-test-helpers.ts +32 -0
- package/src/tests/qa_bibliography_links.test.ts +43 -0
- package/src/tests/qa_claim_evidence.test.ts +69 -0
- package/src/tests/qa_logic_reference_coverage.test.ts +46 -0
- package/src/tests/qa_runtime_i18n.test.ts +100 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/script_density.test.ts +94 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/seo_parity.test.ts +60 -0
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/seo_wellformed_export.test.ts +65 -0
- package/src/tests/shared-test-helpers.ts +56 -0
- package/src/tests/slug_language_code_format.test.ts +23 -0
- package/src/tests/slug_uniqueness.test.ts +81 -0
- package/src/tests/spanish_leakage.test.ts +175 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_exports.test.ts +34 -0
- package/src/tests/tool_validation.test.ts +16 -0
- package/src/tests/translation_copy.test.ts +115 -0
- package/src/tool/rehearsal-call-sheet-planner/bibliography.astro +6 -0
- package/src/tool/rehearsal-call-sheet-planner/bibliography.ts +12 -0
- package/src/tool/rehearsal-call-sheet-planner/component.astro +74 -0
- package/src/tool/rehearsal-call-sheet-planner/controller.ts +98 -0
- package/src/tool/rehearsal-call-sheet-planner/dom-views.ts +86 -0
- package/src/tool/rehearsal-call-sheet-planner/entry.ts +27 -0
- package/src/tool/rehearsal-call-sheet-planner/evaluator.ts +27 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/de.ts +60 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/en.ts +158 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/es.ts +60 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/fr.ts +60 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/id.ts +47 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/it.ts +47 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/ja.ts +47 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/ko.ts +47 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/nl.ts +47 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/pl.ts +47 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/pt.ts +47 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/ru.ts +48 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/sv.ts +48 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/tr.ts +48 -0
- package/src/tool/rehearsal-call-sheet-planner/i18n/zh.ts +48 -0
- package/src/tool/rehearsal-call-sheet-planner/index.ts +11 -0
- package/src/tool/rehearsal-call-sheet-planner/logic.test.ts +68 -0
- package/src/tool/rehearsal-call-sheet-planner/logic.ts +229 -0
- package/src/tool/rehearsal-call-sheet-planner/rehearsal-call-sheet-planner.css +593 -0
- package/src/tool/rehearsal-call-sheet-planner/seo.astro +16 -0
- package/src/tool/rehearsal-call-sheet-planner/storage.ts +33 -0
- package/src/tool/rehearsal-call-sheet-planner/types.ts +84 -0
- package/src/tool/rehearsal-call-sheet-planner/ui.ts +70 -0
- package/src/tools.ts +8 -0
- package/src/types.ts +68 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
const slug = 'performing-arts';
|
|
4
|
+
const title = 'Инструменты репетиций для исполнительского искусства';
|
|
5
|
+
const description = 'Приватные браузерные инструменты для планирования репетиций, листов вызова, конфликтов доступности и рабочего дня постановки.';
|
|
6
|
+
|
|
7
|
+
export const content: CategoryLocaleContent = {
|
|
8
|
+
slug,
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
seo: [
|
|
12
|
+
{ type: 'title', text: 'Build a Rehearsal Day from Real Constraints', level: 2 },
|
|
13
|
+
{ type: 'paragraph', html: 'A rehearsal plan has to hold several truths at once: scenes need time, people need to be available, rooms need to be usable, and the day still needs breathing room. This planner reads a simple scene list and lays it onto a working day so a director or stage manager can see the shape of the call before sending it out.' },
|
|
14
|
+
{ type: 'title', text: 'Make Conflicts Visible Before the Call Goes Out', level: 2 },
|
|
15
|
+
{ type: 'paragraph', html: 'Each scene is checked against the availability windows you enter for its participants and room. The result separates clean sessions from conflicts and scenes that no longer fit the day. It is a planning aid, not a replacement for a production calendar or local working time rules.' },
|
|
16
|
+
{ type: 'title', text: 'Keep Production Notes in the Same Place', level: 2 },
|
|
17
|
+
{ type: 'paragraph', html: 'The call sheet keeps scene notes, participant calls, room calls, and the day overview together. Export the plan as JSON for your own records or print a clean call sheet for the room. Nothing leaves the browser, and the last draft is saved locally on the device.' },
|
|
18
|
+
{ type: 'title', text: 'Privacy and Practical Limits', level: 2 },
|
|
19
|
+
{ type: 'paragraph', html: 'This tool does not send names, schedules, or production notes to a server. It does not manage contracts, confirm legal compliance, or solve overlapping multi-room optimization. Treat the result as a transparent first draft and confirm the final call with the people and rules that govern your production.' },
|
|
20
|
+
{ type: 'stats', items: [
|
|
21
|
+
{ label: 'Tool', value: '1', icon: 'mdi:theater' },
|
|
22
|
+
{ label: 'Rooms', value: 'Any', icon: 'mdi:door-open' },
|
|
23
|
+
{ label: 'Format', value: 'Offline', icon: 'mdi:cloud-off-outline' },
|
|
24
|
+
{ label: 'Privacy', value: 'Local', icon: 'mdi:shield-check' },
|
|
25
|
+
] },
|
|
26
|
+
],
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
const slug = 'performing-arts';
|
|
4
|
+
const title = 'Repetitionsverktyg för scenkonst';
|
|
5
|
+
const description = 'Privata webbläsarverktyg för repetitionsplanering, kallelser, tillgänglighetskonflikter och en överskådlig produktionsdag.';
|
|
6
|
+
|
|
7
|
+
export const content: CategoryLocaleContent = {
|
|
8
|
+
slug,
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
seo: [
|
|
12
|
+
{ type: 'title', text: 'Build a Rehearsal Day from Real Constraints', level: 2 },
|
|
13
|
+
{ type: 'paragraph', html: 'A rehearsal plan has to hold several truths at once: scenes need time, people need to be available, rooms need to be usable, and the day still needs breathing room. This planner reads a simple scene list and lays it onto a working day so a director or stage manager can see the shape of the call before sending it out.' },
|
|
14
|
+
{ type: 'title', text: 'Make Conflicts Visible Before the Call Goes Out', level: 2 },
|
|
15
|
+
{ type: 'paragraph', html: 'Each scene is checked against the availability windows you enter for its participants and room. The result separates clean sessions from conflicts and scenes that no longer fit the day. It is a planning aid, not a replacement for a production calendar or local working time rules.' },
|
|
16
|
+
{ type: 'title', text: 'Keep Production Notes in the Same Place', level: 2 },
|
|
17
|
+
{ type: 'paragraph', html: 'The call sheet keeps scene notes, participant calls, room calls, and the day overview together. Export the plan as JSON for your own records or print a clean call sheet for the room. Nothing leaves the browser, and the last draft is saved locally on the device.' },
|
|
18
|
+
{ type: 'title', text: 'Privacy and Practical Limits', level: 2 },
|
|
19
|
+
{ type: 'paragraph', html: 'This tool does not send names, schedules, or production notes to a server. It does not manage contracts, confirm legal compliance, or solve overlapping multi-room optimization. Treat the result as a transparent first draft and confirm the final call with the people and rules that govern your production.' },
|
|
20
|
+
{ type: 'stats', items: [
|
|
21
|
+
{ label: 'Tool', value: '1', icon: 'mdi:theater' },
|
|
22
|
+
{ label: 'Rooms', value: 'Any', icon: 'mdi:door-open' },
|
|
23
|
+
{ label: 'Format', value: 'Offline', icon: 'mdi:cloud-off-outline' },
|
|
24
|
+
{ label: 'Privacy', value: 'Local', icon: 'mdi:shield-check' },
|
|
25
|
+
] },
|
|
26
|
+
],
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
const slug = 'performing-arts';
|
|
4
|
+
const title = 'Sahne sanatları prova araçları';
|
|
5
|
+
const description = 'Provaları planlamak, çağrı listeleri oluşturmak, uygunluk çatışmalarını görmek ve prodüksiyon gününü düzenlemek için özel araçlar.';
|
|
6
|
+
|
|
7
|
+
export const content: CategoryLocaleContent = {
|
|
8
|
+
slug,
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
seo: [
|
|
12
|
+
{ type: 'title', text: 'Build a Rehearsal Day from Real Constraints', level: 2 },
|
|
13
|
+
{ type: 'paragraph', html: 'A rehearsal plan has to hold several truths at once: scenes need time, people need to be available, rooms need to be usable, and the day still needs breathing room. This planner reads a simple scene list and lays it onto a working day so a director or stage manager can see the shape of the call before sending it out.' },
|
|
14
|
+
{ type: 'title', text: 'Make Conflicts Visible Before the Call Goes Out', level: 2 },
|
|
15
|
+
{ type: 'paragraph', html: 'Each scene is checked against the availability windows you enter for its participants and room. The result separates clean sessions from conflicts and scenes that no longer fit the day. It is a planning aid, not a replacement for a production calendar or local working time rules.' },
|
|
16
|
+
{ type: 'title', text: 'Keep Production Notes in the Same Place', level: 2 },
|
|
17
|
+
{ type: 'paragraph', html: 'The call sheet keeps scene notes, participant calls, room calls, and the day overview together. Export the plan as JSON for your own records or print a clean call sheet for the room. Nothing leaves the browser, and the last draft is saved locally on the device.' },
|
|
18
|
+
{ type: 'title', text: 'Privacy and Practical Limits', level: 2 },
|
|
19
|
+
{ type: 'paragraph', html: 'This tool does not send names, schedules, or production notes to a server. It does not manage contracts, confirm legal compliance, or solve overlapping multi-room optimization. Treat the result as a transparent first draft and confirm the final call with the people and rules that govern your production.' },
|
|
20
|
+
{ type: 'stats', items: [
|
|
21
|
+
{ label: 'Tool', value: '1', icon: 'mdi:theater' },
|
|
22
|
+
{ label: 'Rooms', value: 'Any', icon: 'mdi:door-open' },
|
|
23
|
+
{ label: 'Format', value: 'Offline', icon: 'mdi:cloud-off-outline' },
|
|
24
|
+
{ label: 'Privacy', value: 'Local', icon: 'mdi:shield-check' },
|
|
25
|
+
] },
|
|
26
|
+
],
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
const slug = 'performing-arts';
|
|
4
|
+
const title = '表演艺术排练工具';
|
|
5
|
+
const description = '用于规划排练、制作通告单、发现人员时间冲突并整理制作日的浏览器隐私工具。';
|
|
6
|
+
|
|
7
|
+
export const content: CategoryLocaleContent = {
|
|
8
|
+
slug,
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
seo: [
|
|
12
|
+
{ type: 'title', text: 'Build a Rehearsal Day from Real Constraints', level: 2 },
|
|
13
|
+
{ type: 'paragraph', html: 'A rehearsal plan has to hold several truths at once: scenes need time, people need to be available, rooms need to be usable, and the day still needs breathing room. This planner reads a simple scene list and lays it onto a working day so a director or stage manager can see the shape of the call before sending it out.' },
|
|
14
|
+
{ type: 'title', text: 'Make Conflicts Visible Before the Call Goes Out', level: 2 },
|
|
15
|
+
{ type: 'paragraph', html: 'Each scene is checked against the availability windows you enter for its participants and room. The result separates clean sessions from conflicts and scenes that no longer fit the day. It is a planning aid, not a replacement for a production calendar or local working time rules.' },
|
|
16
|
+
{ type: 'title', text: 'Keep Production Notes in the Same Place', level: 2 },
|
|
17
|
+
{ type: 'paragraph', html: 'The call sheet keeps scene notes, participant calls, room calls, and the day overview together. Export the plan as JSON for your own records or print a clean call sheet for the room. Nothing leaves the browser, and the last draft is saved locally on the device.' },
|
|
18
|
+
{ type: 'title', text: 'Privacy and Practical Limits', level: 2 },
|
|
19
|
+
{ type: 'paragraph', html: 'This tool does not send names, schedules, or production notes to a server. It does not manage contracts, confirm legal compliance, or solve overlapping multi-room optimization. Treat the result as a transparent first draft and confirm the final call with the people and rules that govern your production.' },
|
|
20
|
+
{ type: 'stats', items: [
|
|
21
|
+
{ label: 'Tool', value: '1', icon: 'mdi:theater' },
|
|
22
|
+
{ label: 'Rooms', value: 'Any', icon: 'mdi:door-open' },
|
|
23
|
+
{ label: 'Format', value: 'Offline', icon: 'mdi:cloud-off-outline' },
|
|
24
|
+
{ label: 'Privacy', value: 'Local', icon: 'mdi:shield-check' },
|
|
25
|
+
] },
|
|
26
|
+
],
|
|
27
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { rehearsalCallSheetPlanner } from '../tool/rehearsal-call-sheet-planner/entry';
|
|
2
|
+
import type { CategoryLocaleContent, KnownLocale } from '../types';
|
|
3
|
+
|
|
4
|
+
export const performingArtsCategory = {
|
|
5
|
+
icon: 'mdi:dice-multiple-outline',
|
|
6
|
+
tools: [rehearsalCallSheetPlanner],
|
|
7
|
+
i18n: {
|
|
8
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
9
|
+
} as Partial<Record<KnownLocale, () => Promise<CategoryLocaleContent>>>,
|
|
10
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface NavItem {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
href: string;
|
|
6
|
+
isActive?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
categoryTitle: string;
|
|
11
|
+
tools?: NavItem[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { categoryTitle, tools = [] } = Astro.props;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<nav class="preview-nav-sidebar">
|
|
18
|
+
<div class="sidebar-header">
|
|
19
|
+
<h3>{categoryTitle}</h3>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<ul class="tools-list">
|
|
23
|
+
{tools.map((tool) => (
|
|
24
|
+
<li>
|
|
25
|
+
<a
|
|
26
|
+
href={tool.href}
|
|
27
|
+
class:list={['tool-link', { active: tool.isActive }]}
|
|
28
|
+
>
|
|
29
|
+
<span class="tool-title">{tool.title}</span>
|
|
30
|
+
</a>
|
|
31
|
+
</li>
|
|
32
|
+
))}
|
|
33
|
+
</ul>
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.preview-nav-sidebar {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
height: 100%;
|
|
41
|
+
background: var(--bg-surface, #0f172a);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.sidebar-header {
|
|
45
|
+
padding: 2rem 1.5rem 1.5rem;
|
|
46
|
+
border-bottom: 1px solid var(--border-color, #1e293b);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sidebar-header h3 {
|
|
50
|
+
margin: 0;
|
|
51
|
+
font-size: 0.75rem;
|
|
52
|
+
font-weight: 900;
|
|
53
|
+
text-transform: uppercase;
|
|
54
|
+
letter-spacing: 0.1em;
|
|
55
|
+
color: var(--text-muted, #94a3b8);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.tools-list {
|
|
59
|
+
list-style: none;
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding: 0.5rem 0;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: 0.25rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.tools-list li {
|
|
68
|
+
margin: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.tool-link {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
padding: 0.75rem 1.5rem;
|
|
75
|
+
color: var(--text-muted, #94a3b8);
|
|
76
|
+
text-decoration: none;
|
|
77
|
+
font-size: 0.9375rem;
|
|
78
|
+
font-weight: 500;
|
|
79
|
+
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
|
80
|
+
border-left: 3px solid transparent;
|
|
81
|
+
position: relative;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.tool-link:hover {
|
|
85
|
+
color: var(--text-base, #f1f5f9);
|
|
86
|
+
background: rgba(255, 255, 255, 0.08);
|
|
87
|
+
padding-left: 1.75rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.tool-link.active {
|
|
91
|
+
color: var(--accent, #f43f5e);
|
|
92
|
+
background: rgba(244, 63, 94, 0.15);
|
|
93
|
+
border-left-color: var(--accent, #f43f5e);
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.tool-link.active::before {
|
|
98
|
+
content: '';
|
|
99
|
+
position: absolute;
|
|
100
|
+
left: 0;
|
|
101
|
+
top: 50%;
|
|
102
|
+
transform: translateY(-50%);
|
|
103
|
+
width: 3px;
|
|
104
|
+
height: 24px;
|
|
105
|
+
background: var(--accent, #f43f5e);
|
|
106
|
+
border-radius: 0 2px 2px 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.tool-title {
|
|
110
|
+
display: block;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
text-overflow: ellipsis;
|
|
113
|
+
white-space: nowrap;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
116
|
+
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { KnownLocale } from '../types';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
currentLocale: KnownLocale;
|
|
6
|
+
localeUrls?: Partial<Record<KnownLocale, string>>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { currentLocale, localeUrls = {} } = Astro.props;
|
|
10
|
+
|
|
11
|
+
const otherLocales = Object.entries(localeUrls).filter(([l]) => l !== currentLocale) as [
|
|
12
|
+
KnownLocale,
|
|
13
|
+
string,
|
|
14
|
+
][];
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<nav class="preview-toolbar">
|
|
18
|
+
{
|
|
19
|
+
otherLocales.length > 0 && (
|
|
20
|
+
<div class="locale-group">
|
|
21
|
+
<span class="locale-current">{currentLocale.toUpperCase()}</span>
|
|
22
|
+
{otherLocales.map(([locale, url]) => (
|
|
23
|
+
<a href={url} class="btn-locale">
|
|
24
|
+
{locale.toUpperCase()}
|
|
25
|
+
</a>
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
<button id="theme-toggle" class="btn-theme" aria-label="Toggle theme" title="Toggle theme">
|
|
32
|
+
<span class="theme-icon"></span>
|
|
33
|
+
</button>
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
function initToolbar() {
|
|
38
|
+
const btn = document.getElementById('theme-toggle');
|
|
39
|
+
if (!btn) return;
|
|
40
|
+
|
|
41
|
+
const applyTheme = (theme: string) => {
|
|
42
|
+
document.documentElement.classList.remove('theme-light', 'theme-dark');
|
|
43
|
+
document.documentElement.classList.add(theme);
|
|
44
|
+
localStorage.setItem('theme', theme);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
btn.addEventListener('click', () => {
|
|
48
|
+
const next = document.documentElement.classList.contains('theme-dark')
|
|
49
|
+
? 'theme-light'
|
|
50
|
+
: 'theme-dark';
|
|
51
|
+
applyTheme(next);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
initToolbar();
|
|
56
|
+
document.addEventListener('astro:after-swap', initToolbar);
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<style>
|
|
60
|
+
.preview-toolbar {
|
|
61
|
+
position: fixed;
|
|
62
|
+
top: 1rem;
|
|
63
|
+
right: 1.5rem;
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 0.5rem;
|
|
67
|
+
z-index: 1000;
|
|
68
|
+
background: var(--bg-surface);
|
|
69
|
+
padding: 0.4rem;
|
|
70
|
+
border-radius: 1rem;
|
|
71
|
+
border: 1px solid var(--border-color);
|
|
72
|
+
backdrop-filter: blur(12px);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.locale-group {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 0.25rem;
|
|
79
|
+
padding-right: 0.5rem;
|
|
80
|
+
border-right: 1px solid var(--border-color);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.locale-current {
|
|
84
|
+
font-size: 0.7rem;
|
|
85
|
+
font-weight: 900;
|
|
86
|
+
color: var(--text-muted);
|
|
87
|
+
padding: 0.4rem 0.6rem;
|
|
88
|
+
letter-spacing: 0.08em;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.btn-locale {
|
|
92
|
+
background: transparent;
|
|
93
|
+
border: 1px solid var(--border-color);
|
|
94
|
+
color: var(--text-main);
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
padding: 0.4rem 0.7rem;
|
|
97
|
+
border-radius: 0.5rem;
|
|
98
|
+
font-weight: 700;
|
|
99
|
+
font-size: 0.7rem;
|
|
100
|
+
text-decoration: none;
|
|
101
|
+
letter-spacing: 0.08em;
|
|
102
|
+
transition: all 0.2s ease;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.btn-locale:hover {
|
|
106
|
+
border-color: var(--accent);
|
|
107
|
+
color: var(--accent);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.btn-theme {
|
|
111
|
+
background: transparent;
|
|
112
|
+
border: none;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
padding: 0.5rem;
|
|
115
|
+
border-radius: 0.6rem;
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
transition: background 0.2s ease;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.btn-theme:hover {
|
|
123
|
+
background: rgba(255, 255, 255, 0.08);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.theme-icon {
|
|
127
|
+
display: block;
|
|
128
|
+
width: 16px;
|
|
129
|
+
height: 16px;
|
|
130
|
+
border-radius: 50%;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
:global(.theme-light) .theme-icon {
|
|
134
|
+
background: #f59e0b;
|
|
135
|
+
box-shadow: 0 0 8px #fbbf24;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
:global(.theme-dark) .theme-icon {
|
|
139
|
+
background: #94a3b8;
|
|
140
|
+
box-shadow: inset -4px -2px 0 #1e293b;
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
143
|
+
|
package/src/data.ts
ADDED
package/src/entries.ts
ADDED
package/src/env.d.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { performingArtsCategory } from './category';
|
|
2
|
+
export const performingArtsCategorySEO = () => import('./category/PerformingArtsCategorySEO.astro').then((m) => m.default);
|
|
3
|
+
|
|
4
|
+
export { rehearsalCallSheetPlanner, REHEARSAL_CALL_SHEET_PLANNER_TOOL } from './tool/rehearsal-call-sheet-planner';
|
|
5
|
+
|
|
6
|
+
export type {
|
|
7
|
+
KnownLocale,
|
|
8
|
+
FAQItem,
|
|
9
|
+
BibliographyEntry,
|
|
10
|
+
HowToStep,
|
|
11
|
+
ToolLocaleContent,
|
|
12
|
+
CategoryLocaleContent,
|
|
13
|
+
LocaleLoader,
|
|
14
|
+
LocaleMap,
|
|
15
|
+
PerformingArtsToolEntry,
|
|
16
|
+
PerformingArtsCategoryEntry,
|
|
17
|
+
ToolDefinition,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
export { ALL_ENTRIES, ALL_TOOLS } from './tools';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
import "@jjlmoya/utils-shared/theme.css";
|
|
3
|
+
import PreviewToolbar from "../components/PreviewToolbar.astro";
|
|
4
|
+
import type { KnownLocale } from "../types";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
title: string;
|
|
8
|
+
currentLocale?: KnownLocale;
|
|
9
|
+
localeUrls?: Partial<Record<KnownLocale, string>>;
|
|
10
|
+
hasSidebar?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { title, currentLocale = "es", localeUrls = {}, hasSidebar = false } = Astro.props;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<!doctype html>
|
|
17
|
+
<html lang={currentLocale}>
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="UTF-8" />
|
|
20
|
+
<meta name="viewport" content="width=device-width" />
|
|
21
|
+
<title>{title} · preview</title>
|
|
22
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
23
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
24
|
+
<link
|
|
25
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap"
|
|
26
|
+
rel="stylesheet"
|
|
27
|
+
/>
|
|
28
|
+
|
|
29
|
+
<script is:inline>
|
|
30
|
+
(function () {
|
|
31
|
+
const saved = localStorage.getItem("theme") || "theme-dark";
|
|
32
|
+
document.documentElement.classList.add(saved);
|
|
33
|
+
})();
|
|
34
|
+
</script>
|
|
35
|
+
<slot name="head" />
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<PreviewToolbar currentLocale={currentLocale} localeUrls={localeUrls} />
|
|
39
|
+
<div class:list={["page-wrapper", { "with-sidebar": hasSidebar }]}>
|
|
40
|
+
{
|
|
41
|
+
hasSidebar && (
|
|
42
|
+
<aside class="sidebar-area">
|
|
43
|
+
<slot name="sidebar" />
|
|
44
|
+
</aside>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
<main>
|
|
48
|
+
<slot />
|
|
49
|
+
</main>
|
|
50
|
+
</div>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
53
|
+
|
|
54
|
+
<style is:global>
|
|
55
|
+
:root {
|
|
56
|
+
--accent: #f43f5e;
|
|
57
|
+
--primary-base: #9f1239;
|
|
58
|
+
--cyan: #06b6d4;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.theme-dark,
|
|
62
|
+
.theme-light {
|
|
63
|
+
--text-main: var(--text-base);
|
|
64
|
+
--border-color: var(--border-base);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
*,
|
|
68
|
+
*::before,
|
|
69
|
+
*::after {
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
body {
|
|
74
|
+
background-color: var(--bg-page);
|
|
75
|
+
color: var(--text-base);
|
|
76
|
+
font-family: Inter, sans-serif;
|
|
77
|
+
margin: 0;
|
|
78
|
+
min-height: 100vh;
|
|
79
|
+
transition:
|
|
80
|
+
background-color 0.3s ease,
|
|
81
|
+
color 0.3s ease;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main {
|
|
85
|
+
padding: 0 2rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.page-wrapper {
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.page-wrapper.with-sidebar {
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-columns: 240px 1fr;
|
|
96
|
+
min-height: 100vh;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.sidebar-area {
|
|
100
|
+
position: sticky;
|
|
101
|
+
top: 0;
|
|
102
|
+
height: 100vh;
|
|
103
|
+
overflow-y: auto;
|
|
104
|
+
border-right: 1px solid var(--border-color);
|
|
105
|
+
background: var(--bg-page);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@media (max-width: 768px) {
|
|
109
|
+
.page-wrapper.with-sidebar {
|
|
110
|
+
grid-template-columns: 1fr;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.sidebar-area {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
118
|
+
|