create-portfoliable 1.0.3

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 (62) hide show
  1. package/bin/create-portfoliable.mjs +425 -0
  2. package/cli/portfoliable.mjs +836 -0
  3. package/index.html +18 -0
  4. package/package.json +102 -0
  5. package/public/favicon.ico +0 -0
  6. package/public/favicon.png +0 -0
  7. package/scripts/check-updates.mjs +31 -0
  8. package/scripts/ensure-valence-index-css.mjs +245 -0
  9. package/scripts/generate-password-hash.mjs +25 -0
  10. package/scripts/release-orchestrator.mjs +363 -0
  11. package/scripts/scaffold-case.mjs +460 -0
  12. package/scripts/scaffold-consumer.mjs +87 -0
  13. package/scripts/smoke-homeview.mjs +133 -0
  14. package/scripts/smoke-initializer.mjs +129 -0
  15. package/scripts/smoke-packed.mjs +184 -0
  16. package/scripts/switch-valence-source.mjs +162 -0
  17. package/scripts/sync-template-shared.mjs +136 -0
  18. package/scripts/terminal-ui.mjs +274 -0
  19. package/scripts/verify-integration.mjs +72 -0
  20. package/src/App.js +10015 -0
  21. package/src/header-visibility.contract.js +31 -0
  22. package/src/header-visibility.contract.test.js +41 -0
  23. package/src/main.js +12 -0
  24. package/src/parser/markdown.js +855 -0
  25. package/src/style.css +88 -0
  26. package/templates/README.md +242 -0
  27. package/templates/configs/i18n/i18n.config.js +230 -0
  28. package/templates/configs/i18n/i18n.labels.js +188 -0
  29. package/templates/configs/portfoliable.a11y.config.js +262 -0
  30. package/templates/configs/portfoliable.design.config.js +651 -0
  31. package/templates/gitignore +9 -0
  32. package/templates/index.html +20 -0
  33. package/templates/public/api/.htaccess +4 -0
  34. package/templates/public/api/README.md +27 -0
  35. package/templates/public/api/password.config.example.json +16 -0
  36. package/templates/public/api/password.config.json +16 -0
  37. package/templates/public/api/unlock-case.php +56 -0
  38. package/templates/public/favicon.ico +0 -0
  39. package/templates/public/favicon.png +0 -0
  40. package/templates/public/portfoliable-logo.svg +3 -0
  41. package/templates/scripts/add-language.mjs +253 -0
  42. package/templates/scripts/convert-video-to-mp4.mjs +207 -0
  43. package/templates/scripts/convert-wav-to-mp3.mjs +184 -0
  44. package/templates/scripts/delete-case.mjs +320 -0
  45. package/templates/scripts/delete-language.mjs +195 -0
  46. package/templates/scripts/generate-password-hash.mjs +98 -0
  47. package/templates/scripts/i18n-config-utils.mjs +123 -0
  48. package/templates/scripts/scaffold-case.mjs +444 -0
  49. package/templates/scripts/sync-locales.mjs +1048 -0
  50. package/templates/scripts/validate-content.mjs +257 -0
  51. package/templates/scripts/validate-protection.mjs +187 -0
  52. package/templates/src/cases/index.js +188 -0
  53. package/templates/src/content/about/ABOUTME.md +202 -0
  54. package/templates/src/content/cases/template-case/case.md +479 -0
  55. package/templates/src/content/cases/template-case/cover.png +0 -0
  56. package/templates/src/content/cases/template-case/he-cover.png +0 -0
  57. package/templates/src/content/cases/template-case/template-audio.mp3 +0 -0
  58. package/templates/src/content/cases/template-case/template-video.mp4 +0 -0
  59. package/templates/src/content/thumbnail-options.generated.json +1044 -0
  60. package/templates/src/main.js +23 -0
  61. package/templates/src/parser/markdown.js +805 -0
  62. package/vite.config.js +111 -0
@@ -0,0 +1,188 @@
1
+ export default {
2
+ "en": {
3
+ // SEO Meta
4
+ "meta_home_title": "Portfoliable",
5
+ "meta_home_description": "Your cases, your stories.",
6
+
7
+ // Home View
8
+ "h1_title": "Welcome to Portfoliable",
9
+ "footer_text": "Made with Portfoliable",
10
+ "nav_home": "Home",
11
+ "about_title": "About",
12
+ "view_about_aria_label": "About view",
13
+
14
+ // Case View and Actions
15
+ "view_case_aria_label": "Case view",
16
+ "protected_case": "Protected Case",
17
+ "enter_passcode": "Enter the passcode to unlock project details.",
18
+ "unlock": "Unlock",
19
+ "case_unlocked": "Case unlocked.",
20
+ "incorrect_passcode": "Incorrect passcode.",
21
+
22
+ // Player View
23
+ "player_video_title": "Case video",
24
+ "player_label_play": "Play",
25
+ "player_label_pause": "Pause",
26
+ "player_label_cc_on": "Captions on",
27
+ "player_label_cc_off": "Captions off",
28
+ "player_label_mute": "Mute",
29
+ "player_label_unmute": "Unmute",
30
+ "player_label_speed": "Playback speed",
31
+
32
+ // Audio Player
33
+ "player_audio_reader": "Audio reader",
34
+ "player_audio_play": "Play audio",
35
+ "player_audio_pause": "Pause audio",
36
+ "player_audio_mute": "Mute audio",
37
+ "player_audio_unmute": "Unmute audio",
38
+ "player_audio_speed": "Audio speed",
39
+ "player_audio_hide": "Hide player",
40
+ "player_audio_show": "Show player",
41
+ "player_audio_autoscroll_on": "Autoscroll on",
42
+ "player_audio_autoscroll_off": "Autoscroll off",
43
+ "player_audio_volume": "Volume",
44
+ "player_audio_volume_level": "Volume level",
45
+ "player_audio_position": "Playback position",
46
+
47
+ // Search and Navigator
48
+ "btn_return": "Return",
49
+ "search_cases_placeholder": "Search cases...",
50
+ "search_cases_tooltip_open": "Open search",
51
+ "search_cases_tooltip_close": "Close search",
52
+ "search_main_view_title": "Home",
53
+ "search_main_view_snippet": "Explore featured case studies.",
54
+ "search_case_studies_label": "Case Studies",
55
+ "case_nav_prev": "Previous case",
56
+ "case_nav_next": "Next case",
57
+ "case_nav_prev_locked": "Previous case is locked",
58
+ "case_nav_next_locked": "Next case is locked",
59
+
60
+ // Breadcrumb Menus
61
+ "breadcrumb_menu_case_header": "Case menu",
62
+ "breadcrumb_menu_video_header": "Video menu",
63
+
64
+ // Accessibility Modal
65
+ "popup_a11y_title": "Accessibility",
66
+ "a11y_cat_typography": "Typography",
67
+ "a11y_size": "Text size",
68
+ "a11y_dyslexia": "Dyslexia font",
69
+ "a11y_cat_visuals": "Visuals",
70
+ "a11y_dark": "Dark mode",
71
+ "a11y_contrast": "High contrast",
72
+ "a11y_motion": "No motion",
73
+ "a11y_tab": "TAB",
74
+
75
+ // Language Modal
76
+ "popup_lang_title": "Language",
77
+ "lang_cat": "",
78
+
79
+ // Sharing
80
+ "share_text": "Check out this case study.",
81
+
82
+ // Toast
83
+ "resume_reading": "Resume:",
84
+ "toast_label_close": "Close",
85
+ "toast_label_never_show": "Never show",
86
+
87
+ // Diagnostics and Errors
88
+ "mermaid_error": "Error rendering diagram.",
89
+
90
+ // Other
91
+ "gallery_pill_pitch": "Pitch",
92
+ "gallery_pill_repo": "Repository",
93
+ "gallery_pill_demo": "Demo",
94
+ },
95
+ "he": {
96
+ // SEO Meta
97
+ "meta_home_title": "פורטפוליאבל",
98
+ "meta_home_description": "הקייסים שלך, הסיפורים שלך.",
99
+
100
+ // Home View
101
+ "h1_title": "ברוכים הבאים ל-Portfoliable",
102
+ "footer_text": "נבנה עם Portfoliable",
103
+ "nav_home": "בית",
104
+ "about_title": "אודות",
105
+ "view_about_aria_label": "תצוגת אודות",
106
+
107
+ // Case View and Actions
108
+ "view_case_aria_label": "תצוגת קייס",
109
+ "protected_case": "קייס מוגן",
110
+ "enter_passcode": "הזינו קוד גישה כדי לפתוח את פרטי הפרויקט.",
111
+ "unlock": "פתיחה",
112
+ "case_unlocked": "הקייס נפתח.",
113
+ "incorrect_passcode": "קוד הגישה שגוי.",
114
+
115
+ // Player View
116
+ "player_video_title": "וידאו קייס",
117
+ "player_label_play": "ניגון",
118
+ "player_label_pause": "השהיה",
119
+ "player_label_cc_on": "כתוביות פועלות",
120
+ "player_label_cc_off": "כתוביות כבויות",
121
+ "player_label_mute": "השתקה",
122
+ "player_label_unmute": "ביטול השתקה",
123
+ "player_label_speed": "מהירות ניגון",
124
+
125
+ // Audio Player
126
+ "player_audio_reader": "קורא שמע",
127
+ "player_audio_play": "נגן שמע",
128
+ "player_audio_pause": "השהה שמע",
129
+ "player_audio_mute": "השתק שמע",
130
+ "player_audio_unmute": "בטל השתקת שמע",
131
+ "player_audio_speed": "מהירות שמע",
132
+ "player_audio_hide": "הסתר נגן",
133
+ "player_audio_show": "הצג נגן",
134
+ "player_audio_autoscroll_on": "גלילה אוטומטית פעילה",
135
+ "player_audio_autoscroll_off": "גלילה אוטומטית כבויה",
136
+ "player_audio_volume": "עוצמה",
137
+ "player_audio_volume_level": "רמת עוצמה",
138
+ "player_audio_position": "מיקום ניגון",
139
+
140
+ // Search and Navigator
141
+ "btn_return": "חזרה",
142
+ "search_cases_placeholder": "חיפוש קייסים...",
143
+ "search_cases_tooltip_open": "פתח חיפוש",
144
+ "search_cases_tooltip_close": "סגור חיפוש",
145
+ "search_main_view_title": "בית",
146
+ "search_main_view_snippet": "גלו קייסים נבחרים.",
147
+ "search_case_studies_label": "קייסים",
148
+ "case_nav_prev": "הקייס הקודם",
149
+ "case_nav_next": "הקייס הבא",
150
+ "case_nav_prev_locked": "הקייס הקודם נעול",
151
+ "case_nav_next_locked": "הקייס הבא נעול",
152
+
153
+ // Breadcrumb Menus
154
+ "breadcrumb_menu_case_header": "תפריט קייס",
155
+ "breadcrumb_menu_video_header": "תפריט וידאו",
156
+
157
+ // Accessibility Modal
158
+ "popup_a11y_title": "נגישות",
159
+ "a11y_cat_typography": "טיפוגרפיה",
160
+ "a11y_size": "גודל טקסט",
161
+ "a11y_dyslexia": "גופן דיסלקציה",
162
+ "a11y_cat_visuals": "ויזואליות",
163
+ "a11y_dark": "מצב כהה",
164
+ "a11y_contrast": "ניגודיות גבוהה",
165
+ "a11y_motion": "ללא תנועה",
166
+ "a11y_tab": "TAB",
167
+
168
+ // Language Modal
169
+ "popup_lang_title": "שפה",
170
+ "lang_cat": "",
171
+
172
+ // Sharing
173
+ "share_text": "בדקו את הקייס הזה.",
174
+
175
+ // Toast
176
+ "resume_reading": "המשך:",
177
+ "toast_label_close": "סגור",
178
+ "toast_label_never_show": "אל תציג שוב",
179
+
180
+ // Diagnostics and Errors
181
+ "mermaid_error": "שגיאה ברינדור דיאגרמה.",
182
+
183
+ // Other
184
+ "gallery_pill_pitch": "פיץ'",
185
+ "gallery_pill_repo": "מאגר",
186
+ "gallery_pill_demo": "דמו",
187
+ }
188
+ };
@@ -0,0 +1,262 @@
1
+ // File: configs/portfoliable.a11y.config.js
2
+ // Purpose: Define keyboard shortcuts and a11y-related shortcut actions.
3
+ // Author: Lio Schimanko
4
+
5
+ // MARK: MATCH ORDER
6
+ // Ensures keyboard event matching checks modifiers in a stable order.
7
+ const MODIFIER_ORDER = ['metaKey', 'ctrlKey', 'altKey', 'shiftKey'];
8
+
9
+ // MARK: SHORTCUT CATALOG
10
+ // Declares all keyboard shortcuts consumed by the runtime shell.
11
+ export const keyboardShortcuts = [
12
+ {
13
+ id: 'about',
14
+ action: 'toggleAboutView',
15
+ code: 'KeyI',
16
+ altKey: true,
17
+ labels: {
18
+ mac: '⌥ I',
19
+ windows: 'Alt I',
20
+ linux: 'Alt I'
21
+ }
22
+ },
23
+ {
24
+ id: 'language',
25
+ action: 'toggleLanguageModal',
26
+ code: 'KeyL',
27
+ altKey: true,
28
+ labels: {
29
+ mac: '⌥ L',
30
+ windows: 'Alt L',
31
+ linux: 'Alt L'
32
+ }
33
+ },
34
+ {
35
+ id: 'accessibility',
36
+ action: 'toggleA11yModal',
37
+ code: 'KeyA',
38
+ altKey: true,
39
+ labels: {
40
+ mac: '⌥ A',
41
+ windows: 'Alt A',
42
+ linux: 'Alt A'
43
+ }
44
+ },
45
+ {
46
+ id: 'text-size',
47
+ action: 'toggleA11yState',
48
+ stateKey: 'largeText',
49
+ code: 'KeyT',
50
+ altKey: true,
51
+ labels: {
52
+ mac: '⌥ T',
53
+ windows: 'Alt T',
54
+ linux: 'Alt T'
55
+ }
56
+ },
57
+ {
58
+ id: 'dark-mode',
59
+ action: 'toggleA11yState',
60
+ stateKey: 'darkMode',
61
+ code: 'KeyD',
62
+ altKey: true,
63
+ labels: {
64
+ mac: '⌥ D',
65
+ windows: 'Alt D',
66
+ linux: 'Alt D'
67
+ }
68
+ },
69
+ {
70
+ id: 'high-contrast',
71
+ action: 'toggleA11yState',
72
+ stateKey: 'highContrast',
73
+ code: 'KeyC',
74
+ altKey: true,
75
+ labels: {
76
+ mac: '⌥ C',
77
+ windows: 'Alt C',
78
+ linux: 'Alt C'
79
+ }
80
+ },
81
+ {
82
+ id: 'reduce-motion',
83
+ action: 'toggleA11yState',
84
+ stateKey: 'reduceMotion',
85
+ code: 'KeyM',
86
+ altKey: true,
87
+ labels: {
88
+ mac: '⌥ M',
89
+ windows: 'Alt M',
90
+ linux: 'Alt M'
91
+ }
92
+ },
93
+ {
94
+ id: 'tab-navigation',
95
+ action: 'toggleA11yState',
96
+ stateKey: 'tabNav',
97
+ code: 'KeyF',
98
+ altKey: true,
99
+ labels: {
100
+ mac: '⌥ F',
101
+ windows: 'Alt F',
102
+ linux: 'Alt F'
103
+ }
104
+ },
105
+ {
106
+ id: 'dyslexia-font',
107
+ action: 'toggleA11yState',
108
+ stateKey: 'dyslexiaFont',
109
+ code: 'KeyY',
110
+ altKey: true,
111
+ labels: {
112
+ mac: '⌥ Y',
113
+ windows: 'Alt Y',
114
+ linux: 'Alt Y'
115
+ }
116
+ },
117
+ {
118
+ id: 'case-search-open',
119
+ action: 'openCaseSearch',
120
+ code: 'KeyS',
121
+ ctrlKey: true,
122
+ labels: {
123
+ mac: '⌃ S',
124
+ windows: 'Ctrl S',
125
+ linux: 'Ctrl S'
126
+ }
127
+ },
128
+ {
129
+ id: 'case-search-close',
130
+ action: 'closeCaseSearch',
131
+ code: 'KeyX',
132
+ ctrlKey: true,
133
+ labels: {
134
+ mac: '⌃ X',
135
+ windows: 'Ctrl X',
136
+ linux: 'Ctrl X'
137
+ }
138
+ },
139
+ {
140
+ id: 'open-player',
141
+ action: 'openPlayer',
142
+ code: 'Enter',
143
+ labels: {
144
+ mac: 'Return',
145
+ windows: 'Enter',
146
+ linux: 'Enter'
147
+ }
148
+ },
149
+ {
150
+ id: 'navigate-back',
151
+ action: 'navigateBack',
152
+ code: 'Backspace',
153
+ labels: {
154
+ mac: 'Delete',
155
+ windows: 'Backspace',
156
+ linux: 'Backspace'
157
+ }
158
+ }
159
+ ];
160
+
161
+ // MARK: I18N SHORTCUT LABEL BINDINGS
162
+ // Maps translation keys to shortcut IDs used for runtime kbd markup.
163
+ export const shortcutLabelBindings = {
164
+ btn_return: { shortcutId: 'navigate-back' },
165
+ btn_about: { shortcutId: 'about' },
166
+ btn_lang: { shortcutId: 'language' },
167
+ btn_a11y: { shortcutId: 'accessibility' },
168
+ a11y_size: { shortcutId: 'text-size', kbdClass: 'a11y-kbd' },
169
+ a11y_dark: { shortcutId: 'dark-mode', kbdClass: 'a11y-kbd' },
170
+ a11y_contrast: { shortcutId: 'high-contrast', kbdClass: 'a11y-kbd' },
171
+ a11y_motion: { shortcutId: 'reduce-motion', kbdClass: 'a11y-kbd' },
172
+ a11y_tab: { shortcutId: 'tab-navigation', kbdClass: 'a11y-kbd' },
173
+ a11y_dyslexia: { shortcutId: 'dyslexia-font', kbdClass: 'a11y-kbd' }
174
+ };
175
+
176
+ // MARK: PLATFORM DETECTION
177
+ // Normalizes the host platform so shortcut labels can use mac/windows/linux variants.
178
+ function detectPlatform(platformHint) {
179
+ const raw = String(platformHint || globalThis?.navigator?.platform || '').toLowerCase();
180
+ if (raw.includes('mac')) return 'mac';
181
+ if (raw.includes('win')) return 'windows';
182
+ return 'linux';
183
+ }
184
+
185
+ // MARK: PUBLIC SHORTCUT LOOKUPS
186
+ // Resolves a shortcut object by its ID.
187
+ export function getShortcutById(shortcutId) {
188
+ return keyboardShortcuts.find((shortcut) => shortcut.id === shortcutId) || null;
189
+ }
190
+
191
+ // Returns a display-friendly shortcut string for the current platform.
192
+ export function getShortcutDisplay(shortcutId, platformHint) {
193
+ const shortcut = getShortcutById(shortcutId);
194
+ if (!shortcut) return '';
195
+
196
+ const platform = detectPlatform(platformHint);
197
+ return shortcut.labels?.[platform] || shortcut.labels?.linux || '';
198
+ }
199
+
200
+ // Returns contextual tooltip parts split into modifier label and key label.
201
+ export function getShortcutTooltipParts(shortcutId) {
202
+ const shortcut = getShortcutById(shortcutId);
203
+ if (!shortcut) {
204
+ return {
205
+ kbdLabel: '',
206
+ kbdKey: '',
207
+ showPlus: false
208
+ };
209
+ }
210
+
211
+ const platform = detectPlatform();
212
+
213
+ const KEY_LABEL_BY_CODE = {
214
+ Enter: {
215
+ mac: 'Return',
216
+ windows: 'Enter',
217
+ linux: 'Enter'
218
+ },
219
+ Backspace: {
220
+ mac: 'Delete',
221
+ windows: 'Backspace',
222
+ linux: 'Backspace'
223
+ }
224
+ };
225
+
226
+ const explicitKeyLabel = KEY_LABEL_BY_CODE[shortcut.code]?.[platform];
227
+
228
+ const keyLabel = String(explicitKeyLabel || shortcut.code || '')
229
+ .replace(/^Key/, '')
230
+ .replace(/^Digit/, '');
231
+
232
+ const modifierLabels = [];
233
+ if (shortcut.ctrlKey) modifierLabels.push(platform === 'mac' ? '⌃' : 'Ctrl');
234
+ if (shortcut.altKey) modifierLabels.push('Alt');
235
+ if (shortcut.shiftKey) modifierLabels.push('Shift');
236
+ if (shortcut.metaKey) modifierLabels.push('Cmd');
237
+
238
+ return {
239
+ kbdLabel: modifierLabels.join(' + '),
240
+ kbdKey: keyLabel,
241
+ showPlus: modifierLabels.length > 0
242
+ };
243
+ }
244
+
245
+ // MARK: EVENT MATCHING
246
+ // Returns true only when code and all modifier expectations match.
247
+ function doesShortcutMatchEvent(shortcut, event) {
248
+ if (!shortcut || !event) return false;
249
+ if (event.code !== shortcut.code) return false;
250
+
251
+ return MODIFIER_ORDER.every((modifierName) => {
252
+ const expected = Boolean(shortcut[modifierName]);
253
+ const actual = Boolean(event[modifierName]);
254
+ return expected === actual;
255
+ });
256
+ }
257
+
258
+ // Finds the first configured shortcut matching a KeyboardEvent.
259
+ export function matchShortcutEvent(event) {
260
+ if (!event) return null;
261
+ return keyboardShortcuts.find((shortcut) => doesShortcutMatchEvent(shortcut, event)) || null;
262
+ }