@mtldev514/retro-portfolio-engine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +408 -0
- package/bin/cli.js +103 -0
- package/engine/admin/admin.css +720 -0
- package/engine/admin/admin.html +801 -0
- package/engine/admin/admin_api.py +230 -0
- package/engine/admin/scripts/backup.sh +116 -0
- package/engine/admin/scripts/config_loader.py +180 -0
- package/engine/admin/scripts/init.sh +141 -0
- package/engine/admin/scripts/manager.py +308 -0
- package/engine/admin/scripts/restore.sh +121 -0
- package/engine/admin/scripts/server.py +41 -0
- package/engine/admin/scripts/update.sh +321 -0
- package/engine/admin/scripts/validate_json.py +62 -0
- package/engine/fonts.css +37 -0
- package/engine/index.html +190 -0
- package/engine/js/config-loader.js +370 -0
- package/engine/js/config.js +173 -0
- package/engine/js/counter.js +17 -0
- package/engine/js/effects.js +97 -0
- package/engine/js/i18n.js +68 -0
- package/engine/js/init.js +107 -0
- package/engine/js/media.js +264 -0
- package/engine/js/render.js +282 -0
- package/engine/js/router.js +133 -0
- package/engine/js/sparkle.js +123 -0
- package/engine/js/themes.js +607 -0
- package/engine/style.css +2037 -0
- package/index.js +35 -0
- package/package.json +48 -0
- package/scripts/admin.js +67 -0
- package/scripts/build.js +142 -0
- package/scripts/init.js +237 -0
- package/scripts/post-install.js +16 -0
- package/scripts/serve.js +54 -0
- package/templates/user-portfolio/.github/workflows/deploy.yml +57 -0
- package/templates/user-portfolio/config/app.json +36 -0
- package/templates/user-portfolio/config/categories.json +241 -0
- package/templates/user-portfolio/config/languages.json +15 -0
- package/templates/user-portfolio/config/media-types.json +59 -0
- package/templates/user-portfolio/data/painting.json +3 -0
- package/templates/user-portfolio/data/projects.json +3 -0
- package/templates/user-portfolio/lang/en.json +114 -0
- package/templates/user-portfolio/lang/fr.json +114 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Retro Portfolio Update Script
|
|
4
|
+
# Safely updates the template while preserving user data
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo ""
|
|
9
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
10
|
+
echo " 🔄 Retro Portfolio - Update Script"
|
|
11
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
12
|
+
echo ""
|
|
13
|
+
|
|
14
|
+
# Colors for output
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
GREEN='\033[0;32m'
|
|
17
|
+
YELLOW='\033[1;33m'
|
|
18
|
+
BLUE='\033[0;34m'
|
|
19
|
+
NC='\033[0m' # No Color
|
|
20
|
+
|
|
21
|
+
# Detect setup type
|
|
22
|
+
SETUP_TYPE="unknown"
|
|
23
|
+
|
|
24
|
+
if [ -d "template/.git" ]; then
|
|
25
|
+
SETUP_TYPE="submodule"
|
|
26
|
+
elif [ -d ".git" ]; then
|
|
27
|
+
# Check if this is the template repo or a user's fork
|
|
28
|
+
REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "")
|
|
29
|
+
if [[ "$REMOTE_URL" == *"retro-portfolio"* ]] && [[ "$REMOTE_URL" != *"retro-portfolio-config"* ]]; then
|
|
30
|
+
SETUP_TYPE="direct"
|
|
31
|
+
else
|
|
32
|
+
SETUP_TYPE="fork"
|
|
33
|
+
fi
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
echo -e "${BLUE}📋 Setup type detected: $SETUP_TYPE${NC}"
|
|
37
|
+
echo ""
|
|
38
|
+
|
|
39
|
+
# Function to create backup
|
|
40
|
+
create_backup() {
|
|
41
|
+
echo -e "${YELLOW}📦 Creating backup...${NC}"
|
|
42
|
+
|
|
43
|
+
BACKUP_DIR=".backup-personal-$(date +%Y%m%d-%H%M%S)"
|
|
44
|
+
mkdir -p "$BACKUP_DIR"
|
|
45
|
+
|
|
46
|
+
# Backup user data
|
|
47
|
+
[ -d "config" ] && cp -r config "$BACKUP_DIR/" && echo " ✓ Backed up config/"
|
|
48
|
+
[ -d "data" ] && cp -r data "$BACKUP_DIR/" && echo " ✓ Backed up data/"
|
|
49
|
+
[ -d "lang" ] && cp -r lang "$BACKUP_DIR/" && echo " ✓ Backed up lang/"
|
|
50
|
+
[ -f ".env" ] && cp .env "$BACKUP_DIR/" && echo " ✓ Backed up .env"
|
|
51
|
+
[ -f "config-source.json" ] && cp config-source.json "$BACKUP_DIR/" && echo " ✓ Backed up config-source.json"
|
|
52
|
+
|
|
53
|
+
echo -e "${GREEN} ✓ Backup created: $BACKUP_DIR${NC}"
|
|
54
|
+
echo ""
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Function to verify data integrity
|
|
58
|
+
verify_data() {
|
|
59
|
+
echo -e "${YELLOW}🔍 Verifying data integrity...${NC}"
|
|
60
|
+
|
|
61
|
+
ERRORS=0
|
|
62
|
+
|
|
63
|
+
# Check essential directories exist
|
|
64
|
+
if [ ! -d "config" ] || [ ! -f "config/app.json" ]; then
|
|
65
|
+
echo -e "${RED} ✗ config/ missing or incomplete${NC}"
|
|
66
|
+
ERRORS=$((ERRORS + 1))
|
|
67
|
+
else
|
|
68
|
+
echo -e "${GREEN} ✓ config/ intact${NC}"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
if [ ! -d "data" ]; then
|
|
72
|
+
echo -e "${RED} ✗ data/ missing${NC}"
|
|
73
|
+
ERRORS=$((ERRORS + 1))
|
|
74
|
+
else
|
|
75
|
+
echo -e "${GREEN} ✓ data/ intact${NC}"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if [ ! -d "lang" ]; then
|
|
79
|
+
echo -e "${RED} ✗ lang/ missing${NC}"
|
|
80
|
+
ERRORS=$((ERRORS + 1))
|
|
81
|
+
else
|
|
82
|
+
echo -e "${GREEN} ✓ lang/ intact${NC}"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
if [ ! -f ".env" ]; then
|
|
86
|
+
echo -e "${YELLOW} ⚠️ .env missing (you may need to recreate it)${NC}"
|
|
87
|
+
else
|
|
88
|
+
echo -e "${GREEN} ✓ .env intact${NC}"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
echo ""
|
|
92
|
+
|
|
93
|
+
if [ $ERRORS -gt 0 ]; then
|
|
94
|
+
echo -e "${RED}❌ Data verification failed! Restore from backup.${NC}"
|
|
95
|
+
return 1
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
echo -e "${GREEN}✅ All data verified successfully!${NC}"
|
|
99
|
+
echo ""
|
|
100
|
+
return 0
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Function to update submodule setup
|
|
104
|
+
update_submodule() {
|
|
105
|
+
echo -e "${BLUE}📥 Updating template submodule...${NC}"
|
|
106
|
+
echo ""
|
|
107
|
+
|
|
108
|
+
cd template
|
|
109
|
+
|
|
110
|
+
# Check current version
|
|
111
|
+
CURRENT_COMMIT=$(git rev-parse --short HEAD)
|
|
112
|
+
echo "Current version: $CURRENT_COMMIT"
|
|
113
|
+
|
|
114
|
+
# Pull latest
|
|
115
|
+
git fetch origin
|
|
116
|
+
git pull origin main
|
|
117
|
+
|
|
118
|
+
# Check new version
|
|
119
|
+
NEW_COMMIT=$(git rev-parse --short HEAD)
|
|
120
|
+
echo "New version: $NEW_COMMIT"
|
|
121
|
+
|
|
122
|
+
cd ..
|
|
123
|
+
|
|
124
|
+
if [ "$CURRENT_COMMIT" != "$NEW_COMMIT" ]; then
|
|
125
|
+
echo ""
|
|
126
|
+
echo -e "${GREEN}✅ Template updated successfully!${NC}"
|
|
127
|
+
echo ""
|
|
128
|
+
echo -e "${YELLOW}Don't forget to commit the update:${NC}"
|
|
129
|
+
echo " git add template"
|
|
130
|
+
echo " git commit -m \"Update template to $NEW_COMMIT\""
|
|
131
|
+
else
|
|
132
|
+
echo ""
|
|
133
|
+
echo -e "${GREEN}✅ Already on latest version!${NC}"
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
echo ""
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Function to update direct/fork setup
|
|
140
|
+
update_direct() {
|
|
141
|
+
echo -e "${BLUE}📥 Updating repository...${NC}"
|
|
142
|
+
echo ""
|
|
143
|
+
|
|
144
|
+
# Check current version
|
|
145
|
+
CURRENT_COMMIT=$(git rev-parse --short HEAD)
|
|
146
|
+
echo "Current version: $CURRENT_COMMIT"
|
|
147
|
+
|
|
148
|
+
# Check for uncommitted changes in tracked files
|
|
149
|
+
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
|
150
|
+
echo -e "${YELLOW}⚠️ You have uncommitted changes in tracked files.${NC}"
|
|
151
|
+
echo ""
|
|
152
|
+
read -p "Stash changes before updating? (recommended) (Y/n): " -n 1 -r
|
|
153
|
+
echo
|
|
154
|
+
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
|
155
|
+
git stash push -m "Auto-stash before update $(date)"
|
|
156
|
+
STASHED=true
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# Pull latest changes
|
|
161
|
+
if [ "$SETUP_TYPE" == "fork" ]; then
|
|
162
|
+
# Check if upstream exists
|
|
163
|
+
if ! git remote get-url upstream > /dev/null 2>&1; then
|
|
164
|
+
echo -e "${YELLOW}Adding upstream remote...${NC}"
|
|
165
|
+
git remote add upstream https://github.com/yourusername/retro-portfolio.git
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
git fetch upstream
|
|
169
|
+
git merge upstream/main
|
|
170
|
+
else
|
|
171
|
+
git pull origin main
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Pop stash if we stashed
|
|
175
|
+
if [ "$STASHED" = true ]; then
|
|
176
|
+
echo ""
|
|
177
|
+
echo -e "${YELLOW}Restoring your changes...${NC}"
|
|
178
|
+
git stash pop || echo -e "${YELLOW}⚠️ Could not auto-restore stash. Run 'git stash pop' manually.${NC}"
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# Check new version
|
|
182
|
+
NEW_COMMIT=$(git rev-parse --short HEAD)
|
|
183
|
+
echo "New version: $NEW_COMMIT"
|
|
184
|
+
|
|
185
|
+
echo ""
|
|
186
|
+
if [ "$CURRENT_COMMIT" != "$NEW_COMMIT" ]; then
|
|
187
|
+
echo -e "${GREEN}✅ Repository updated successfully!${NC}"
|
|
188
|
+
else
|
|
189
|
+
echo -e "${GREEN}✅ Already on latest version!${NC}"
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
echo ""
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# Function to show what changed
|
|
196
|
+
show_changes() {
|
|
197
|
+
echo -e "${BLUE}📝 Recent changes:${NC}"
|
|
198
|
+
echo ""
|
|
199
|
+
|
|
200
|
+
if [ -f "CHANGELOG.md" ]; then
|
|
201
|
+
# Show last 20 lines of changelog
|
|
202
|
+
tail -n 20 CHANGELOG.md
|
|
203
|
+
else
|
|
204
|
+
# Show recent commits
|
|
205
|
+
git log --oneline --decorate -5
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
echo ""
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# Function to check for new example files
|
|
212
|
+
check_new_examples() {
|
|
213
|
+
echo -e "${BLUE}🆕 Checking for new features...${NC}"
|
|
214
|
+
echo ""
|
|
215
|
+
|
|
216
|
+
NEW_FEATURES=false
|
|
217
|
+
|
|
218
|
+
# Check for new config examples
|
|
219
|
+
if [ -d "config.example" ]; then
|
|
220
|
+
for example_file in config.example/*.example; do
|
|
221
|
+
if [ -f "$example_file" ]; then
|
|
222
|
+
base_name=$(basename "$example_file" .example)
|
|
223
|
+
user_file="config/$base_name"
|
|
224
|
+
|
|
225
|
+
# If user file exists, check for new fields
|
|
226
|
+
if [ -f "$user_file" ]; then
|
|
227
|
+
# Simple check: compare file sizes (crude but fast)
|
|
228
|
+
EXAMPLE_SIZE=$(wc -c < "$example_file")
|
|
229
|
+
USER_SIZE=$(wc -c < "$user_file")
|
|
230
|
+
|
|
231
|
+
if [ "$EXAMPLE_SIZE" -gt "$USER_SIZE" ]; then
|
|
232
|
+
echo -e "${YELLOW} ℹ️ New options may be available in: $base_name${NC}"
|
|
233
|
+
echo " Compare: diff config/$base_name config.example/$base_name.example"
|
|
234
|
+
NEW_FEATURES=true
|
|
235
|
+
fi
|
|
236
|
+
fi
|
|
237
|
+
fi
|
|
238
|
+
done
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
if [ "$NEW_FEATURES" = false ]; then
|
|
242
|
+
echo -e "${GREEN} ✓ No new configuration options detected${NC}"
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
echo ""
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# Main update flow
|
|
249
|
+
main() {
|
|
250
|
+
# Create backup first
|
|
251
|
+
create_backup
|
|
252
|
+
|
|
253
|
+
# Store current directory
|
|
254
|
+
ORIGINAL_DIR=$(pwd)
|
|
255
|
+
|
|
256
|
+
# Update based on setup type
|
|
257
|
+
case $SETUP_TYPE in
|
|
258
|
+
submodule)
|
|
259
|
+
update_submodule
|
|
260
|
+
;;
|
|
261
|
+
direct|fork)
|
|
262
|
+
update_direct
|
|
263
|
+
;;
|
|
264
|
+
*)
|
|
265
|
+
echo -e "${RED}❌ Could not detect setup type${NC}"
|
|
266
|
+
echo "Please update manually. See UPDATE.md for instructions."
|
|
267
|
+
exit 1
|
|
268
|
+
;;
|
|
269
|
+
esac
|
|
270
|
+
|
|
271
|
+
# Return to original directory
|
|
272
|
+
cd "$ORIGINAL_DIR"
|
|
273
|
+
|
|
274
|
+
# Verify data integrity
|
|
275
|
+
if ! verify_data; then
|
|
276
|
+
echo -e "${RED}❌ Update completed but data verification failed!${NC}"
|
|
277
|
+
echo -e "${YELLOW}Restore from backup:${NC}"
|
|
278
|
+
echo " cp -r $BACKUP_DIR/* ."
|
|
279
|
+
exit 1
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# Show what changed
|
|
283
|
+
show_changes
|
|
284
|
+
|
|
285
|
+
# Check for new examples
|
|
286
|
+
check_new_examples
|
|
287
|
+
|
|
288
|
+
# Final instructions
|
|
289
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
290
|
+
echo -e "${GREEN} ✅ Update Complete!${NC}"
|
|
291
|
+
echo "═══════════════════════════════════════════════════════════"
|
|
292
|
+
echo ""
|
|
293
|
+
echo "Next steps:"
|
|
294
|
+
echo ""
|
|
295
|
+
echo "1. Review changes:"
|
|
296
|
+
echo " cat CHANGELOG.md"
|
|
297
|
+
echo ""
|
|
298
|
+
echo "2. Test locally:"
|
|
299
|
+
echo " python3 -m http.server 8000"
|
|
300
|
+
echo " open http://localhost:8000"
|
|
301
|
+
echo ""
|
|
302
|
+
echo "3. Clear browser cache (Cmd+Shift+R or Ctrl+Shift+R)"
|
|
303
|
+
echo ""
|
|
304
|
+
echo "4. Deploy updated site:"
|
|
305
|
+
echo " git push"
|
|
306
|
+
echo ""
|
|
307
|
+
|
|
308
|
+
if [ "$SETUP_TYPE" == "submodule" ]; then
|
|
309
|
+
echo "5. Commit the template update:"
|
|
310
|
+
echo " git add template"
|
|
311
|
+
echo " git commit -m \"Update template\""
|
|
312
|
+
echo " git push"
|
|
313
|
+
echo ""
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
echo -e "${YELLOW}💡 Backup saved to: $BACKUP_DIR${NC}"
|
|
317
|
+
echo ""
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
# Run main
|
|
321
|
+
main
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import glob
|
|
5
|
+
|
|
6
|
+
def validate_json_files(directories):
|
|
7
|
+
"""
|
|
8
|
+
Recursively finds and validates all .json files in the given directories.
|
|
9
|
+
Returns True if all files are valid, False otherwise.
|
|
10
|
+
"""
|
|
11
|
+
has_errors = False
|
|
12
|
+
total_files = 0
|
|
13
|
+
valid_files = 0
|
|
14
|
+
|
|
15
|
+
print("🔍 Starting JSON validation...")
|
|
16
|
+
|
|
17
|
+
for directory in directories:
|
|
18
|
+
if not os.path.isdir(directory):
|
|
19
|
+
print(f"⚠️ Directory not found: {directory}")
|
|
20
|
+
continue
|
|
21
|
+
|
|
22
|
+
# Use glob to find all json files recursively
|
|
23
|
+
files = glob.glob(os.path.join(directory, "**/*.json"), recursive=True)
|
|
24
|
+
|
|
25
|
+
for file_path in files:
|
|
26
|
+
total_files += 1
|
|
27
|
+
try:
|
|
28
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
29
|
+
json.load(f)
|
|
30
|
+
valid_files += 1
|
|
31
|
+
# print(f"✅ Valid: {file_path}") # Optional: Uncomment for verbose output
|
|
32
|
+
except json.JSONDecodeError as e:
|
|
33
|
+
print(f"❌ Syntax Error in {file_path}:")
|
|
34
|
+
print(f" Line {e.lineno}, Column {e.colno}: {e.msg}")
|
|
35
|
+
has_errors = True
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"❌ Error reading {file_path}: {e}")
|
|
38
|
+
has_errors = True
|
|
39
|
+
|
|
40
|
+
print("\n📊 Validation Summary")
|
|
41
|
+
print(f" Total Files: {total_files}")
|
|
42
|
+
print(f" Valid Files: {valid_files}")
|
|
43
|
+
print(f" Invalid Files: {total_files - valid_files}")
|
|
44
|
+
|
|
45
|
+
return not has_errors
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
# Directories to validate
|
|
49
|
+
dirs_to_check = ["data", "config", "lang"]
|
|
50
|
+
|
|
51
|
+
# Adjust paths if running from root or scripts dir
|
|
52
|
+
if os.path.basename(os.getcwd()) == "scripts":
|
|
53
|
+
os.chdir("..")
|
|
54
|
+
|
|
55
|
+
success = validate_json_files(dirs_to_check)
|
|
56
|
+
|
|
57
|
+
if success:
|
|
58
|
+
print("\n✨ All JSON files are valid!")
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
else:
|
|
61
|
+
print("\n💥 Validation failed. Please fix the errors above.")
|
|
62
|
+
sys.exit(1)
|
package/engine/fonts.css
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* ═══ Typography — independent of theme/style ═══ */
|
|
2
|
+
|
|
3
|
+
/* Base */
|
|
4
|
+
body {
|
|
5
|
+
font-size: 14px;
|
|
6
|
+
line-height: 1.5;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* Gallery cards */
|
|
10
|
+
.gallery-item h3 {
|
|
11
|
+
font-size: 0.95em;
|
|
12
|
+
margin: 4px 5px 0;
|
|
13
|
+
line-height: 1.3;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.gallery-item .gallery-subtitle {
|
|
17
|
+
font-size: 0.85em;
|
|
18
|
+
margin: 1px 5px 0;
|
|
19
|
+
line-height: 1.3;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Dates — switch from monospace to primary font */
|
|
23
|
+
.item-date {
|
|
24
|
+
font-size: 0.75em;
|
|
25
|
+
font-family: var(--font-primary);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* UI controls — bump from below-legibility sizes */
|
|
29
|
+
.sort-btn { font-size: 11px; }
|
|
30
|
+
|
|
31
|
+
/* Winamp — minimal bumps to preserve skin authenticity */
|
|
32
|
+
.winamp-title { font-size: 10px; }
|
|
33
|
+
.winamp-pl-item { font-size: 11px; }
|
|
34
|
+
|
|
35
|
+
/* Terminal — sharper text */
|
|
36
|
+
.terminal-body { font-size: 12px; }
|
|
37
|
+
.terminal-line { text-shadow: 0 0 2px var(--term-glow, rgba(0,255,0,0.2)); }
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title data-i18n="header_title">alex a montreal</title>
|
|
8
|
+
<link rel="stylesheet" href="style.css">
|
|
9
|
+
<link rel="stylesheet" href="fonts.css">
|
|
10
|
+
|
|
11
|
+
<!-- Load configuration first (supports remote/local sources) -->
|
|
12
|
+
<script src="js/config-loader.js"></script>
|
|
13
|
+
<script src="js/i18n.js" defer></script>
|
|
14
|
+
<script src="js/themes.js" defer></script>
|
|
15
|
+
<script src="js/render.js" defer></script>
|
|
16
|
+
<script src="js/router.js" defer></script>
|
|
17
|
+
<script src="js/media.js" defer></script>
|
|
18
|
+
<script src="js/sparkle.js" defer></script>
|
|
19
|
+
<script src="js/effects.js" defer></script>
|
|
20
|
+
<!-- Initialize app after all modules loaded -->
|
|
21
|
+
<script src="js/init.js" defer></script>
|
|
22
|
+
</head>
|
|
23
|
+
|
|
24
|
+
<body>
|
|
25
|
+
<div id="rotate-overlay">
|
|
26
|
+
<div class="rotate-content">
|
|
27
|
+
<div class="rotate-icon">📺↩️</div>
|
|
28
|
+
<h2>ROTATE YOUR DEVICE!</h2>
|
|
29
|
+
<p>This website was designed for<br><b>DESKTOP COMPUTERS</b></p>
|
|
30
|
+
<p class="rotate-sub">Please turn your phone sideways<br>for the full retro experience!</p>
|
|
31
|
+
<div class="rotate-ascii">
|
|
32
|
+
┌─────────────────┐<br>
|
|
33
|
+
│ ╔══════════╗ │<br>
|
|
34
|
+
│ ║ TURN ║ │<br>
|
|
35
|
+
│ ║ ME! ║ │<br>
|
|
36
|
+
│ ╚══════════╝ │<br>
|
|
37
|
+
└─────────────────┘<br>
|
|
38
|
+
⟲
|
|
39
|
+
</div>
|
|
40
|
+
<p class="rotate-blink">*** LANDSCAPE MODE REQUIRED ***</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="container">
|
|
44
|
+
<header>
|
|
45
|
+
<div class="settings-switcher">
|
|
46
|
+
<button class="settings-btn">⚙</button>
|
|
47
|
+
<div class="settings-dropdown">
|
|
48
|
+
<div class="settings-section-label">Effects</div>
|
|
49
|
+
<div class="settings-option" onclick="togglePartyMode()">
|
|
50
|
+
<span id="party-mode-indicator">✨</span> Party Mode
|
|
51
|
+
</div>
|
|
52
|
+
<div class="settings-divider"></div>
|
|
53
|
+
<div class="settings-section-label">Theme</div>
|
|
54
|
+
<div class="settings-option" onclick="themes.changeTheme('jr16')"><span class="theme-icon">🌿</span>
|
|
55
|
+
JR-16</div>
|
|
56
|
+
<div class="settings-option" onclick="themes.changeTheme('beton')"><span
|
|
57
|
+
class="theme-icon">🌫️</span> Béton</div>
|
|
58
|
+
<div class="settings-option" onclick="themes.changeTheme('ciment')"><span
|
|
59
|
+
class="theme-icon">🪨</span> Ciment</div>
|
|
60
|
+
<div class="settings-option" onclick="themes.changeTheme('bubblegum')"><span
|
|
61
|
+
class="theme-icon">🍬</span> Bubble Gum</div>
|
|
62
|
+
<div class="settings-divider"></div>
|
|
63
|
+
<div class="settings-section-label">Language</div>
|
|
64
|
+
<div class="settings-option" onclick="i18n.changeLang('en')"><span class="lang-flag">🇬🇧🇨🇦</span>
|
|
65
|
+
English</div>
|
|
66
|
+
<div class="settings-option" onclick="i18n.changeLang('fr')"><span class="lang-flag">⚜️🇨🇦</span>
|
|
67
|
+
French</div>
|
|
68
|
+
<div class="settings-option" onclick="i18n.changeLang('mx')"><span class="lang-flag">🇲🇽</span>
|
|
69
|
+
Spanish</div>
|
|
70
|
+
<div class="settings-option" onclick="i18n.changeLang('ht')"><span class="lang-flag">🇭🇹</span>
|
|
71
|
+
Creole</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<h1 id="page-title" data-i18n="header_title">alex a montreal</h1>
|
|
75
|
+
</header>
|
|
76
|
+
|
|
77
|
+
<div class="marquee-container">
|
|
78
|
+
<marquee scrollamount="5" data-i18n="marquee"></marquee>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="content">
|
|
82
|
+
<aside class="sidebar">
|
|
83
|
+
<div class="winamp">
|
|
84
|
+
<div class="winamp-titlebar">
|
|
85
|
+
<span class="winamp-grip"></span>
|
|
86
|
+
<span class="winamp-title">Radyo</span>
|
|
87
|
+
<span class="winamp-grip"></span>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="winamp-display">
|
|
90
|
+
<div class="winamp-time-row">
|
|
91
|
+
<span class="winamp-time" id="winamp-time">00:00</span>
|
|
92
|
+
<span class="winamp-time-sep">/</span>
|
|
93
|
+
<span class="winamp-duration" id="winamp-duration">00:00</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="winamp-ticker">
|
|
96
|
+
<span class="radio-track-name" data-i18n="sidebar_radio_title">Your Radio</span> - Winamp 2.91
|
|
97
|
+
</div>
|
|
98
|
+
<div class="winamp-viz" id="winamp-viz"></div>
|
|
99
|
+
<div class="winamp-info">
|
|
100
|
+
<div class="winamp-bitrate"><span class="winamp-kbps">192</span>kbps</div>
|
|
101
|
+
<div class="winamp-freq"><span class="winamp-khz">44</span>kHz</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="winamp-transport">
|
|
105
|
+
<button class="winamp-btn radio-prev" title="Previous"><svg class="icon" viewBox="0 0 24 24">
|
|
106
|
+
<path d="M6 6h2v12H6zm3.5 6l8.5 6V6z" transform="scale(-1,1) translate(-24,0)" />
|
|
107
|
+
</svg></button>
|
|
108
|
+
<button class="winamp-btn radio-playpause" title="Play"><svg class="icon" viewBox="0 0 24 24">
|
|
109
|
+
<path d="M8 5v14l11-7z" />
|
|
110
|
+
</svg></button>
|
|
111
|
+
<button class="winamp-btn radio-next" title="Next"><svg class="icon" viewBox="0 0 24 24">
|
|
112
|
+
<path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z" />
|
|
113
|
+
</svg></button>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="winamp-volume-row">
|
|
116
|
+
<svg class="icon winamp-vol-icon" viewBox="0 0 24 24">
|
|
117
|
+
<path d="M3 9v6h4l5 5V4L7 9H3z" />
|
|
118
|
+
</svg>
|
|
119
|
+
<input type="range" class="winamp-volume radio-volume" value="80" min="0" max="100">
|
|
120
|
+
<svg class="icon winamp-vol-icon" viewBox="0 0 24 24">
|
|
121
|
+
<path
|
|
122
|
+
d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z" />
|
|
123
|
+
</svg>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="winamp-seek-row">
|
|
126
|
+
<input type="range" class="winamp-seek" id="winamp-seek" value="0" min="0" max="100">
|
|
127
|
+
</div>
|
|
128
|
+
<div class="winamp-playlist-titlebar">
|
|
129
|
+
<span class="winamp-grip-sm"></span>
|
|
130
|
+
<span class="winamp-pl-title">PLAYLIST</span>
|
|
131
|
+
<span class="winamp-grip-sm"></span>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="winamp-playlist" id="radio-tracklist">
|
|
134
|
+
<div class="winamp-pl-item winamp-pl-empty" data-i18n="sidebar_radio_loading">Loading tracks...
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</aside>
|
|
139
|
+
|
|
140
|
+
<main id="app">
|
|
141
|
+
<div id="filter-nav" class="filter-bar">
|
|
142
|
+
<button class="filter-btn active" data-filter="all" data-i18n="filter_all">All</button>
|
|
143
|
+
<button class="filter-btn" data-filter="painting" data-i18n="nav_painting">Painting</button>
|
|
144
|
+
<button class="filter-btn" data-filter="drawing" data-i18n="nav_drawing">Drawing</button>
|
|
145
|
+
<button class="filter-btn" data-filter="photography" data-i18n="nav_photo">Photography</button>
|
|
146
|
+
<button class="filter-btn" data-filter="sculpting" data-i18n="nav_sculpting">Sculpting</button>
|
|
147
|
+
<button class="filter-btn" data-filter="music" data-i18n="nav_music">Music</button>
|
|
148
|
+
<button class="filter-btn" data-filter="projects" data-i18n="nav_projects">Code</button>
|
|
149
|
+
<span class="sort-controls">
|
|
150
|
+
<button class="sort-btn active" data-sort="desc" title="Newest first">▼</button>
|
|
151
|
+
<button class="sort-btn" data-sort="asc" title="Oldest first">▲</button>
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
<!-- Gallery grid loaded by render.js -->
|
|
155
|
+
</main>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<footer class="footer-terminal">
|
|
159
|
+
<div class="terminal-window">
|
|
160
|
+
<div class="terminal-titlebar">
|
|
161
|
+
<div class="terminal-titlebar-btns">
|
|
162
|
+
<span class="terminal-tbtn terminal-tbtn-close"></span>
|
|
163
|
+
<span class="terminal-tbtn terminal-tbtn-min"></span>
|
|
164
|
+
<span class="terminal-tbtn terminal-tbtn-max"></span>
|
|
165
|
+
</div>
|
|
166
|
+
<span class="terminal-titlebar-text">user@host:~</span>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="terminal-body">
|
|
169
|
+
<div class="terminal-line"><span class="terminal-prompt">$</span> <span
|
|
170
|
+
data-i18n="footer_copy">© 2026 alex a montreal</span> — <a
|
|
171
|
+
href="https://github.com/mtldev514" target="_blank" title="GitHub"><svg class="icon"
|
|
172
|
+
viewBox="0 0 24 24">
|
|
173
|
+
<path
|
|
174
|
+
d="M12 .3a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.4-1.8-1.4-1.8-1.1-.8.1-.7.1-.7 1.2.1 1.9 1.3 1.9 1.3 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-5.9 0-1.3.5-2.4 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.3 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0012 .3z" />
|
|
175
|
+
</svg> github</a> · <a href="https://www.linkedin.com/in/alexcatus/?locale=fr_FR"
|
|
176
|
+
target="_blank" title="LinkedIn"><svg class="icon" viewBox="0 0 24 24">
|
|
177
|
+
<path
|
|
178
|
+
d="M20.5 2h-17A1.5 1.5 0 002 3.5v17A1.5 1.5 0 003.5 22h17a1.5 1.5 0 001.5-1.5v-17A1.5 1.5 0 0020.5 2zM8 19H5v-9h3zM6.5 8.25A1.75 1.75 0 118.3 6.5a1.78 1.78 0 01-1.8 1.75zM19 19h-3v-4.74c0-1.42-.6-1.93-1.38-1.93A1.74 1.74 0 0013 14.19V19h-3v-9h2.9v1.3a3.11 3.11 0 012.7-1.4c1.55 0 3.36.86 3.36 3.66z" />
|
|
179
|
+
</svg> linkedin</a> — <span class="terminal-dim"><span
|
|
180
|
+
data-i18n="footer_visitors">Visits:</span> <span
|
|
181
|
+
class="visitor-counter">......</span></span> <span class="terminal-cursor">█</span>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</footer>
|
|
186
|
+
</div>
|
|
187
|
+
<script src="js/counter.js"></script>
|
|
188
|
+
</body>
|
|
189
|
+
|
|
190
|
+
</html>
|