diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..3eda0ea --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "env": { + "browser": true, + "es2021": true, + "jest": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "indent": ["error", 4], + "linebreak-style": ["error", "windows"], + "quotes": ["error", "single"], + "semi": ["error", "always"], + "no-unused-vars": ["warn"], + "no-console": ["warn", { "allow": ["warn", "error"] }], + "prefer-const": "error", + "arrow-body-style": ["error", "as-needed"], + "curly": "error", + "eqeqeq": "error", + "no-var": "error" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4b7f93e..b4d4167 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,99 @@ # Dependencies node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* package-lock.json yarn.lock -# Environment -.env -.env.local -.env.*.local +# Build output +dist/ +build/ +*.min.js +*.min.css -# IDE +# IDE and editor files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json .idea/ -.vscode/ *.swp *.swo .DS_Store Thumbs.db -# Build output -dist/ -build/ -out/ - -# Logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* +# Environment variables +.env +.env.local +.env.*.local # Testing coverage/ +.nyc_output/ +jest-results/ # Temporary files +*.log *.tmp *.temp -.cache/ \ No newline at end of file +.cache/ + +# Generated files +pages/* +!pages/.gitkeep + +# Image source files and working copies +*.psd +*.ai +*.sketch +*.fig +*.xd +*.xcf +*.raw +*.cr2 +*.nef +*.dng + +# Image optimization caches +.imagemin-cache/ +.responsive-images-cache/ + +# Generated image formats +images/**/*.webp +images/**/*-resized.* +images/**/*-thumbnail.* +images/**/*-optimized.* + +# Keep original images +!images/**/*.svg +!images/**/*.png +!images/**/*.jpg +!images/**/*.jpeg +!images/**/*.gif + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Debug logs +debug.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Rollup build cache +.rollup.cache/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..711a9f0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,29 @@ +{ + "recommendations": [ + // Essential extensions + "dbaeumer.vscode-eslint", // ESLint + "esbenp.prettier-vscode", // Prettier + "ritwickdey.LiveServer", // Live Server + "formulahendry.auto-rename-tag", // Auto Rename Tag + + // JavaScript/Web development + "christian-kohler.path-intellisense", // Path Intellisense + "formulahendry.auto-close-tag", // Auto Close Tag + "naumovs.color-highlight", // Color Highlight + "csstools.postcss", // PostCSS Language Support + + // Git integration + "eamodio.gitlens", // GitLens + "mhutchie.git-graph", // Git Graph + + // Productivity + "streetsidesoftware.code-spell-checker", // Code Spell Checker + "wayou.vscode-todo-highlight", // TODO Highlight + "yzhang.markdown-all-in-one", // Markdown All in One + + // Theme and icons (optional but recommended) + "pkief.material-icon-theme", // Material Icon Theme + "zhuangtongfa.material-theme" // One Dark Pro + ], + "unwantedRecommendations": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index b242572..25ee330 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,77 @@ { - "githubPullRequests.ignoredPullRequestBranches": [ - "main" - ] -} \ No newline at end of file + // Git settings + "githubPullRequests.ignoredPullRequestBranches": ["main"], + "git.enableSmartCommit": true, + "git.confirmSync": false, + "git.autofetch": true, + + // Editor settings + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "editor.tabSize": 4, + "editor.insertSpaces": true, + "editor.rulers": [100], + "editor.wordWrap": "on", + "editor.bracketPairColorization.enabled": true, + + // File settings + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.eol": "\n", + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/node_modules": true, + "**/dist": true, + "**/*.min.js": true, + "**/*.min.css": true + }, + "files.associations": { + "*.css": "css", + "*.js": "javascript", + "*.json": "jsonc", + "*.md": "markdown" + }, + + // Language specific settings + "javascript.updateImportsOnFileMove.enabled": "always", + "javascript.suggest.autoImports": true, + "javascript.format.enable": false, // Use Prettier instead + + "css.validate": true, + "css.lint.duplicateProperties": "warning", + "css.lint.zeroUnits": "warning", + + "html.format.wrapLineLength": 100, + "html.format.wrapAttributes": "auto", + + // Extension settings + "prettier.singleQuote": true, + "prettier.printWidth": 100, + "prettier.tabWidth": 4, + "prettier.trailingComma": "es5", + + "eslint.validate": [ + "javascript", + "javascriptreact" + ], + + // Workspace settings + "workbench.colorCustomizations": { + "statusBar.background": "#00ffff", + "statusBar.foreground": "#000000", + "statusBar.noFolderBackground": "#ff00de" + }, + + // Search settings + "search.exclude": { + "**/node_modules": true, + "**/dist": true, + "**/*.min.*": true, + "package-lock.json": true + } +} diff --git a/css/base/layout.css b/css/base/layout.css new file mode 100644 index 0000000..fa8d865 --- /dev/null +++ b/css/base/layout.css @@ -0,0 +1,72 @@ +/* Container */ +.container { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 0 1rem; +} + +/* Grid layouts */ +.grid { + display: grid; + gap: 2rem; +} + +.tools-grid { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); +} + +.features-grid { + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); +} + +/* Flexbox layouts */ +.flex { + display: flex; +} + +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +.flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +.flex-column { + display: flex; + flex-direction: column; +} + +/* Spacing utilities */ +.mt-1 { margin-top: 0.5rem; } +.mt-2 { margin-top: 1rem; } +.mt-3 { margin-top: 1.5rem; } +.mt-4 { margin-top: 2rem; } + +.mb-1 { margin-bottom: 0.5rem; } +.mb-2 { margin-bottom: 1rem; } +.mb-3 { margin-bottom: 1.5rem; } +.mb-4 { margin-bottom: 2rem; } + +.mx-auto { margin-left: auto; margin-right: auto; } + +/* Responsive utilities */ +@media (max-width: 768px) { + .container { + padding: 0 0.5rem; + } + + .tools-grid, + .features-grid { + grid-template-columns: 1fr; + } + + .flex-responsive { + flex-direction: column; + } +} \ No newline at end of file diff --git a/css/base/reset.css b/css/base/reset.css new file mode 100644 index 0000000..0bb5af6 --- /dev/null +++ b/css/base/reset.css @@ -0,0 +1,66 @@ +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Typography */ +body { + font-family: 'Roboto', sans-serif; + line-height: 1.6; +} + +h1, h2, h3, h4 { + font-family: 'Orbitron', sans-serif; + margin-bottom: 1rem; +} + +a { + text-decoration: none; + color: inherit; + transition: var(--transition); +} + +a:hover { + color: var(--primary-color); +} + +/* Lists */ +ul, ol { + list-style: none; +} + +/* Images */ +img { + max-width: 100%; + height: auto; + display: block; +} + +/* Form elements */ +button, +input, +select, +textarea { + font: inherit; + color: inherit; + border: none; + background: none; +} + +button { + cursor: pointer; +} + +/* Focus styles */ +:focus { + outline: 2px solid var(--primary-color); + outline-offset: 2px; +} + +/* Selection */ +::selection { + background-color: var(--primary-color); + color: var(--text-light); +} \ No newline at end of file diff --git a/css/components/alerts.css b/css/components/alerts.css new file mode 100644 index 0000000..bec3632 --- /dev/null +++ b/css/components/alerts.css @@ -0,0 +1,148 @@ +/** + * Alert Components + */ + +/* Base alert */ +.alert { + padding: var(--spacing-md); + border-radius: var(--radius-md); + margin-bottom: var(--spacing-md); + display: flex; + align-items: flex-start; + gap: var(--spacing-sm); +} + +/* Alert variations */ +.alert-info { + background-color: rgba(0, 255, 255, 0.1); + border-left: 4px solid var(--primary-color); + color: var(--primary-color); +} + +.alert-success { + background-color: rgba(0, 255, 0, 0.1); + border-left: 4px solid var(--success-color); + color: var(--success-color); +} + +.alert-warning { + background-color: rgba(255, 255, 0, 0.1); + border-left: 4px solid var(--warning-color); + color: var(--warning-color); +} + +.alert-error { + background-color: rgba(255, 0, 0, 0.1); + border-left: 4px solid var(--error-color); + color: var(--error-color); +} + +/* Alert with icon */ +.alert-icon { + font-size: 1.25rem; + line-height: 1; +} + +/* Alert content */ +.alert-content { + flex: 1; +} + +.alert-title { + font-weight: bold; + margin-bottom: var(--spacing-xs); +} + +.alert-message { + color: inherit; + opacity: 0.9; +} + +/* Dismissible alert */ +.alert-dismissible { + position: relative; + padding-right: calc(var(--spacing-xl) + var(--spacing-sm)); +} + +.alert-dismiss { + position: absolute; + top: var(--spacing-sm); + right: var(--spacing-sm); + padding: var(--spacing-xs); + background: none; + border: none; + color: inherit; + opacity: 0.5; + cursor: pointer; + transition: var(--transition); +} + +.alert-dismiss:hover { + opacity: 1; +} + +/* Alert animations */ +.alert-animate { + animation: alertSlideIn 0.3s ease-out; +} + +@keyframes alertSlideIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Alert sizes */ +.alert-sm { + padding: var(--spacing-sm); + font-size: 0.875rem; +} + +.alert-lg { + padding: var(--spacing-lg); + font-size: 1.125rem; +} + +/* Alert groups */ +.alert-group { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +/* Toast notifications */ +.toast { + position: fixed; + bottom: var(--spacing-md); + right: var(--spacing-md); + z-index: var(--z-toast); + max-width: 350px; + animation: toastSlideIn 0.3s ease-out; +} + +@keyframes toastSlideIn { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .alert { + padding: var(--spacing-sm); + } + + .toast { + max-width: calc(100% - var(--spacing-md) * 2); + } +} \ No newline at end of file diff --git a/css/components/cards.css b/css/components/cards.css new file mode 100644 index 0000000..baefa56 --- /dev/null +++ b/css/components/cards.css @@ -0,0 +1,145 @@ +/** + * Card Components + */ + +/* Base card */ +.card { + background: var(--card-bg); + border-radius: var(--radius-md); + padding: var(--spacing-md); + transition: var(--transition); +} + +/* Card variations */ +.card-hover:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-md); +} + +.card-interactive { + cursor: pointer; + transition: var(--transition); +} + +.card-interactive:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-lg); +} + +/* Card with border */ +.card-bordered { + border: 1px solid var(--text-muted); +} + +/* Card with gradient border */ +.card-gradient { + position: relative; + background: var(--card-bg); + padding: var(--spacing-md); + border-radius: var(--radius-md); +} + +.card-gradient::before { + content: ''; + position: absolute; + top: -2px; + right: -2px; + bottom: -2px; + left: -2px; + background: linear-gradient(45deg, var(--primary-color), var(--secondary-color)); + border-radius: calc(var(--radius-md) + 2px); + z-index: -1; + transition: var(--transition); + opacity: 0.5; +} + +.card-gradient:hover::before { + opacity: 1; +} + +/* Card layouts */ +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--spacing-md); +} + +.card-footer { + margin-top: var(--spacing-md); + padding-top: var(--spacing-md); + border-top: 1px solid var(--text-muted); +} + +/* Card content */ +.card-title { + font-size: 1.25rem; + font-weight: bold; + margin-bottom: var(--spacing-sm); +} + +.card-subtitle { + color: var(--text-muted); + font-size: 0.875rem; + margin-bottom: var(--spacing-md); +} + +.card-content { + margin-bottom: var(--spacing-md); +} + +/* Card with image */ +.card-image { + margin: calc(var(--spacing-md) * -1) calc(var(--spacing-md) * -1) var(--spacing-md); + overflow: hidden; + border-radius: var(--radius-md) var(--radius-md) 0 0; +} + +.card-image img { + width: 100%; + height: 200px; + object-fit: cover; + transition: var(--transition); +} + +.card-image:hover img { + transform: scale(1.05); +} + +/* Card groups */ +.card-group { + display: grid; + gap: var(--spacing-md); + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); +} + +/* Card sizes */ +.card-sm { + padding: var(--spacing-sm); +} + +.card-lg { + padding: var(--spacing-lg); +} + +/* Card themes */ +.card-primary { + background: var(--primary-color); + color: var(--text-light); +} + +.card-secondary { + background: var(--secondary-color); + color: var(--text-light); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .card-group { + grid-template-columns: 1fr; + } + + .card { + padding: var(--spacing-sm); + } +} \ No newline at end of file diff --git a/css/styles.css b/css/styles.css index 15b7289..2b14376 100644 --- a/css/styles.css +++ b/css/styles.css @@ -1,239 +1,92 @@ -:root { - --primary-color: #00ffff; - --secondary-color: #ff00de; - --bg-dark: #0c0c0c; - --bg-light: #ffffff; - --text-dark: #1a1a1a; - --text-light: #ffffff; - --card-bg: #1a1a1a; - --card-hover: #2a2a2a; - --transition: all 0.3s ease; -} - -/* Base Styles */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Roboto', sans-serif; - background-color: var(--bg-dark); - color: var(--text-light); - line-height: 1.6; -} - -h1, h2, h3, h4 { - font-family: 'Orbitron', sans-serif; - margin-bottom: 1rem; -} - -a { - color: var(--primary-color); - text-decoration: none; - transition: var(--transition); -} - -a:hover { - color: var(--secondary-color); -} - -/* Header & Navigation */ +/** + * Digital Services Hub Main Stylesheet + */ + +/* Base imports */ +@import 'base/reset.css'; +@import 'base/layout.css'; + +/* Theme configuration */ +@import 'themes/variables.css'; + +/* Utility styles */ +@import 'utils/utilities.css'; +@import 'utils/animations.css'; + +/* Component styles */ +@import 'components/buttons.css'; +@import 'components/inputs.css'; +@import 'components/progress.css'; +@import 'components/navigation.css'; +@import 'components/cards.css'; +@import 'components/alerts.css'; + +/* Tool-specific styles */ +@import 'components/text-to-speech.css'; +@import 'components/image-resizer.css'; +@import 'components/color-palette.css'; +@import 'components/ascii-art.css'; +@import 'components/qr-code.css'; +@import 'components/password-generator.css'; +@import 'components/url-shortener.css'; +@import 'components/social-share.css'; +@import 'components/about.css'; + +/* Page-specific styles */ .hero { - background: linear-gradient(135deg, var(--bg-dark), #1a1a1a); - padding: 2rem 0; - text-align: center; - position: relative; - overflow: hidden; -} - -.nav-container { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 2rem; - max-width: 1200px; - margin: 0 auto; -} - -.logo h1 { - font-size: 2rem; - margin: 0; - background: linear-gradient(45deg, var(--primary-color), var(--secondary-color)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; -} - -.theme-toggle button { - background: none; - border: none; - color: var(--text-light); - font-size: 1.5rem; - cursor: pointer; - padding: 0.5rem; - transition: var(--transition); -} - -.theme-toggle button:hover { - color: var(--primary-color); + min-height: 60vh; + background: linear-gradient(135deg, var(--bg-dark) 0%, var(--card-bg) 100%); + padding: var(--spacing-xl) 0; } .hero-content { - padding: 4rem 2rem; + text-align: center; max-width: 800px; margin: 0 auto; } .hero-title { font-size: 3rem; - margin-bottom: 1rem; - text-shadow: 0 0 10px rgba(0, 255, 255, 0.5); -} - -.hero-subtitle { - font-size: 1.5rem; - opacity: 0.9; -} - -/* Main Content */ -.container { - max-width: 1200px; - margin: 0 auto; - padding: 4rem 2rem; -} - -/* Tools Grid */ -.tools-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 2rem; - margin-bottom: 4rem; -} - -.tool-card { - background: var(--card-bg); - border-radius: 10px; - padding: 2rem; - text-align: center; - transition: var(--transition); - cursor: pointer; - position: relative; - overflow: hidden; -} - -.tool-card:hover { - transform: translateY(-5px); - background: var(--card-hover); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); -} - -.tool-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 4px; - background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); - opacity: 0; - transition: var(--transition); -} - -.tool-card:hover::before { - opacity: 1; -} - -.tool-icon { - font-size: 2.5rem; - margin-bottom: 1rem; color: var(--primary-color); + margin-bottom: var(--spacing-md); } -.tool-card h3 { +.hero-subtitle { font-size: 1.5rem; - margin-bottom: 1rem; -} - -.tool-card p { - margin-bottom: 1.5rem; - opacity: 0.9; -} - -.tool-features { - display: flex; - justify-content: center; - gap: 1rem; -} - -.tool-features span { - background: rgba(255, 255, 255, 0.1); - padding: 0.25rem 0.75rem; - border-radius: 15px; - font-size: 0.9rem; -} - -/* Features Section */ -.features { - text-align: center; - padding: 4rem 0; -} - -.features h2 { - margin-bottom: 3rem; -} - -.features-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 2rem; -} - -.feature { - padding: 2rem; -} - -.feature i { - font-size: 2.5rem; - color: var(--primary-color); - margin-bottom: 1rem; + color: var(--text-muted); + margin-bottom: var(--spacing-lg); } -/* Footer */ +/* Footer styles */ .footer { - background: var(--card-bg); - padding: 4rem 2rem 2rem; + background-color: var(--card-bg); + padding: var(--spacing-xl) 0; + margin-top: var(--spacing-xl); } .footer-content { - max-width: 1200px; - margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 2rem; - margin-bottom: 2rem; + gap: var(--spacing-lg); } .footer-section h4 { color: var(--primary-color); - margin-bottom: 1rem; -} - -.footer-section ul { - list-style: none; + margin-bottom: var(--spacing-md); } .footer-section ul li { - margin-bottom: 0.5rem; + margin-bottom: var(--spacing-sm); } .footer-bottom { text-align: center; - padding-top: 2rem; - border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-top: var(--spacing-lg); + border-top: 1px solid var(--text-muted); + margin-top: var(--spacing-xl); } -/* Responsive Design */ +/* Responsive styles */ @media (max-width: 768px) { .hero-title { font-size: 2rem; @@ -243,48 +96,8 @@ a:hover { font-size: 1.2rem; } - .container { - padding: 2rem 1rem; - } - - .tools-grid { - gap: 1rem; - } - .footer-content { grid-template-columns: 1fr; text-align: center; } } - -/* Animations */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.tool-card { - animation: fadeIn 0.5s ease-out forwards; -} - -.tool-card:nth-child(2) { - animation-delay: 0.1s; -} - -.tool-card:nth-child(3) { - animation-delay: 0.2s; -} - -.tool-card:nth-child(4) { - animation-delay: 0.3s; -} - -.tool-card:nth-child(5) { - animation-delay: 0.4s; -} diff --git a/css/themes/theme-variables.css b/css/themes/theme-variables.css deleted file mode 100644 index 6533158..0000000 --- a/css/themes/theme-variables.css +++ /dev/null @@ -1,26 +0,0 @@ -:root { - /* Dark theme (default) */ - --bg-color: #000; - --container-bg: rgba(16, 24, 39, 0.8); - --text-color: #fff; - --accent-color: #00fff2; - --input-bg: #1f2937; - --input-border: #374151; - --button-gradient-1: #00a3ff; - --button-gradient-2: #00fff2; - --shadow-color: #00fff2; - --error-color: #ff0000; -} - -[data-theme="light"] { - --bg-color: #f0f2f5; - --container-bg: rgba(255, 255, 255, 0.9); - --text-color: #1a1a1a; - --accent-color: #0066cc; - --input-bg: #ffffff; - --input-border: #cccccc; - --button-gradient-1: #0066cc; - --button-gradient-2: #0099ff; - --shadow-color: #0099ff; - --error-color: #cc0000; -} \ No newline at end of file diff --git a/css/themes/variables.css b/css/themes/variables.css new file mode 100644 index 0000000..57431c1 --- /dev/null +++ b/css/themes/variables.css @@ -0,0 +1,67 @@ +/** + * CSS Variables and Theming + */ + +:root { + /* Colors */ + --primary-color: #00ffff; + --secondary-color: #ff00de; + --success-color: #00ff00; + --warning-color: #ffff00; + --error-color: #ff0000; + + /* Background colors */ + --bg-dark: #0c0c0c; + --bg-light: #ffffff; + --card-bg: #1a1a1a; + --card-hover: #2a2a2a; + + /* Text colors */ + --text-dark: #1a1a1a; + --text-light: #ffffff; + --text-muted: #888888; + + /* Spacing */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + + /* Border radius */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 16px; + --radius-full: 9999px; + + /* Shadows */ + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + + /* Transitions */ + --transition-fast: 150ms ease; + --transition: 300ms ease; + --transition-slow: 500ms ease; + + /* Z-index layers */ + --z-negative: -1; + --z-normal: 1; + --z-tooltip: 10; + --z-fixed: 100; + --z-modal: 1000; +} + +/* Dark theme overrides */ +[data-theme="dark"] { + --bg-primary: var(--bg-dark); + --text-primary: var(--text-light); + --card-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); +} + +/* Light theme overrides */ +[data-theme="light"] { + --bg-primary: var(--bg-light); + --text-primary: var(--text-dark); + --card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} \ No newline at end of file diff --git a/css/utils/utilities.css b/css/utils/utilities.css new file mode 100644 index 0000000..72edba0 --- /dev/null +++ b/css/utils/utilities.css @@ -0,0 +1,137 @@ +/** + * Utility Classes + */ + +/* Display */ +.block { display: block; } +.inline { display: inline; } +.inline-block { display: inline-block; } +.hidden { display: none; } + +/* Visibility */ +.visible { visibility: visible; } +.invisible { visibility: hidden; } + +/* Position */ +.relative { position: relative; } +.absolute { position: absolute; } +.fixed { position: fixed; } +.sticky { position: sticky; } + +/* Text alignment */ +.text-left { text-align: left; } +.text-center { text-align: center; } +.text-right { text-align: right; } +.text-justify { text-align: justify; } + +/* Text styles */ +.text-bold { font-weight: bold; } +.text-normal { font-weight: normal; } +.text-italic { font-style: italic; } +.text-uppercase { text-transform: uppercase; } +.text-capitalize { text-transform: capitalize; } +.text-underline { text-decoration: underline; } +.text-nowrap { white-space: nowrap; } +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Text colors */ +.text-primary { color: var(--primary-color); } +.text-secondary { color: var(--secondary-color); } +.text-success { color: var(--success-color); } +.text-warning { color: var(--warning-color); } +.text-error { color: var(--error-color); } +.text-muted { color: var(--text-muted); } + +/* Background colors */ +.bg-primary { background-color: var(--primary-color); } +.bg-secondary { background-color: var(--secondary-color); } +.bg-success { background-color: var(--success-color); } +.bg-warning { background-color: var(--warning-color); } +.bg-error { background-color: var(--error-color); } +.bg-dark { background-color: var(--bg-dark); } +.bg-light { background-color: var(--bg-light); } + +/* Padding utilities */ +.p-0 { padding: 0; } +.p-1 { padding: var(--spacing-xs); } +.p-2 { padding: var(--spacing-sm); } +.p-3 { padding: var(--spacing-md); } +.p-4 { padding: var(--spacing-lg); } +.p-5 { padding: var(--spacing-xl); } + +.px-1 { padding-left: var(--spacing-xs); padding-right: var(--spacing-xs); } +.px-2 { padding-left: var(--spacing-sm); padding-right: var(--spacing-sm); } +.px-3 { padding-left: var(--spacing-md); padding-right: var(--spacing-md); } +.px-4 { padding-left: var(--spacing-lg); padding-right: var(--spacing-lg); } +.px-5 { padding-left: var(--spacing-xl); padding-right: var(--spacing-xl); } + +.py-1 { padding-top: var(--spacing-xs); padding-bottom: var(--spacing-xs); } +.py-2 { padding-top: var(--spacing-sm); padding-bottom: var(--spacing-sm); } +.py-3 { padding-top: var(--spacing-md); padding-bottom: var(--spacing-md); } +.py-4 { padding-top: var(--spacing-lg); padding-bottom: var(--spacing-lg); } +.py-5 { padding-top: var(--spacing-xl); padding-bottom: var(--spacing-xl); } + +/* Border radius */ +.rounded-none { border-radius: 0; } +.rounded-sm { border-radius: var(--radius-sm); } +.rounded-md { border-radius: var(--radius-md); } +.rounded-lg { border-radius: var(--radius-lg); } +.rounded-full { border-radius: var(--radius-full); } + +/* Shadows */ +.shadow-none { box-shadow: none; } +.shadow-sm { box-shadow: var(--shadow-sm); } +.shadow-md { box-shadow: var(--shadow-md); } +.shadow-lg { box-shadow: var(--shadow-lg); } + +/* Opacity */ +.opacity-0 { opacity: 0; } +.opacity-25 { opacity: 0.25; } +.opacity-50 { opacity: 0.5; } +.opacity-75 { opacity: 0.75; } +.opacity-100 { opacity: 1; } + +/* Cursor */ +.cursor-pointer { cursor: pointer; } +.cursor-not-allowed { cursor: not-allowed; } +.cursor-wait { cursor: wait; } +.cursor-text { cursor: text; } + +/* Object fit */ +.object-contain { object-fit: contain; } +.object-cover { object-fit: cover; } +.object-fill { object-fit: fill; } +.object-none { object-fit: none; } + +/* Z-index */ +.z-negative { z-index: var(--z-negative); } +.z-normal { z-index: var(--z-normal); } +.z-tooltip { z-index: var(--z-tooltip); } +.z-fixed { z-index: var(--z-fixed); } +.z-modal { z-index: var(--z-modal); } + +/* Overflow */ +.overflow-auto { overflow: auto; } +.overflow-hidden { overflow: hidden; } +.overflow-scroll { overflow: scroll; } +.overflow-x-auto { overflow-x: auto; } +.overflow-y-auto { overflow-y: auto; } + +/* Width and Height */ +.w-full { width: 100%; } +.w-screen { width: 100vw; } +.h-full { height: 100%; } +.h-screen { height: 100vh; } + +/* Responsive hide/show */ +@media (max-width: 768px) { + .hide-mobile { display: none; } +} + +@media (min-width: 769px) { + .hide-desktop { display: none; } +} \ No newline at end of file diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..c8eb93d --- /dev/null +++ b/docs/API.md @@ -0,0 +1,251 @@ +# API Documentation + +## Core APIs + +### ToolsManager + +The `ToolsManager` class provides methods for managing and interacting with tools. + +```typescript +interface Tool { + id: string; + name: string; + description: string; + icon: string; + features: string[]; + path: string; + category: string; + order: number; +} + +class ToolsManager { + static getAllTools(): Tool[]; + static getToolsByCategory(category: string): Tool[]; + static getToolById(id: string): Tool | undefined; + static getCategoriesWithInfo(): Record; + static getCategoryInfo(category: string): CategoryInfo | undefined; + static getStats(): ToolStats; + static isValidCategory(category: string): boolean; + static getToolsSortedByOrder(): Tool[]; + static searchTools(query: string): Tool[]; +} +``` + +### Storage Utilities + +The storage utilities provide a consistent interface for data persistence. + +```typescript +interface StorageOptions { + type?: 'local' | 'session'; +} + +function isStorageAvailable(type: string): boolean; +function getStorageItem(key: string, type?: 'local' | 'session'): T | null; +function setStorageItem(key: string, value: any, type?: 'local' | 'session'): boolean; +function removeStorageItem(key: string, type?: 'local' | 'session'): boolean; +function clearStorage(type?: 'local' | 'session'): boolean; +``` + +### DOM Utilities + +Utilities for DOM manipulation and browser interactions. + +```typescript +function sanitizeHTML(html: string): string; +function isInViewport(element: Element, offset?: number): boolean; +function copyToClipboard(text: string): Promise; +function isMobile(): boolean; +function getBrowserLanguage(): string; +``` + +### Format Utilities + +Utilities for data formatting and conversion. + +```typescript +interface DateFormatOptions extends Intl.DateTimeFormatOptions {} + +function formatDate(date: Date | string | number, options?: DateFormatOptions): string; +function formatRelativeTime(date: Date | string | number): string; +function formatFileSize(bytes: number): string; +function getFileExtension(filename: string): string; +function rgbToHex(r: number, g: number, b: number): string; +function hexToRgb(hex: string): { r: number; g: number; b: number } | null; +function isLightColor(color: string): boolean; +``` + +## Component APIs + +### Card Component + +The card component provides a flexible container for content. + +```typescript +interface CardProps { + variant?: 'default' | 'hover' | 'interactive' | 'gradient'; + size?: 'sm' | 'md' | 'lg'; + theme?: 'primary' | 'secondary'; + className?: string; +} + +// Usage example: +
+
...
+
...
+ +
+``` + +### Alert Component + +The alert component provides feedback messages. + +```typescript +interface AlertProps { + type?: 'info' | 'success' | 'warning' | 'error'; + size?: 'sm' | 'md' | 'lg'; + dismissible?: boolean; + animate?: boolean; +} + +// Usage example: +
+
...
+
+
...
+
...
+
+ +
+``` + +## Configuration + +### Theme Configuration + +Theme variables and customization options. + +```typescript +interface ThemeConfig { + colors: { + primary: string; + secondary: string; + success: string; + warning: string; + error: string; + }; + spacing: { + xs: string; + sm: string; + md: string; + lg: string; + xl: string; + }; + radius: { + sm: string; + md: string; + lg: string; + full: string; + }; +} + +// Usage example: +const theme: ThemeConfig = { + colors: { + primary: '#00ffff', + secondary: '#ff00de', + // ... + }, + // ... +}; +``` + +### Tool Configuration + +Configuration options for tools. + +```typescript +interface ToolConfig { + id: string; + name: string; + description: string; + icon: string; + features: string[]; + path: string; + category: keyof typeof TOOL_CATEGORIES; + order: number; +} + +// Usage example: +const toolConfig: ToolConfig = { + id: 'text-to-speech', + name: 'Text to Speech', + description: 'Convert text to natural-sounding speech', + icon: 'fa-volume-up', + features: ['Multiple voices', 'Download audio'], + path: './pages/text-to-speech.html', + category: TOOL_CATEGORIES.AUDIO, + order: 1 +}; +``` + +## Events + +### Custom Events + +The application uses custom events for communication. + +```typescript +interface ToolEvent extends CustomEvent { + detail: { + toolId: string; + action: 'load' | 'unload' | 'update'; + data?: any; + }; +} + +// Dispatch event +window.dispatchEvent(new CustomEvent('tool:load', { + detail: { + toolId: 'text-to-speech', + action: 'load' + } +})); + +// Listen for event +window.addEventListener('tool:load', (event: ToolEvent) => { + const { toolId, action } = event.detail; + // Handle event +}); +``` + +## Error Handling + +Standard error types and handling patterns. + +```typescript +interface ToolError extends Error { + code: string; + toolId?: string; + data?: any; +} + +class ValidationError extends Error { + constructor(message: string, public code: string) { + super(message); + this.name = 'ValidationError'; + } +} + +// Usage example: +try { + // Tool operation +} catch (error) { + if (error instanceof ValidationError) { + // Handle validation error + } else { + // Handle other errors + } +} +``` \ No newline at end of file diff --git a/docs/PROJECT-DESCRIPTION.md b/docs/PROJECT-DESCRIPTION.md deleted file mode 100644 index fca8c62..0000000 --- a/docs/PROJECT-DESCRIPTION.md +++ /dev/null @@ -1,308 +0,0 @@ -# Digital Services Hub - Project Description - -## 🎯 Vision - -Digital Services Hub aims to provide a comprehensive suite of free, web-based tools that simplify everyday digital tasks. Our focus is on creating a platform that is: - -- 🌟 User-friendly and intuitive -- πŸ”’ Secure and privacy-focused -- β™Ώ Accessible to everyone -- πŸš€ Fast and efficient -- πŸ’» Platform-independent -- 🌐 Globally available - -## πŸ› οΈ Core Tools - -### Text to Speech -Transform written text into natural-sounding speech with our advanced text-to-speech tool. Features include: -- Multiple voice options and languages -- Adjustable speed and pitch controls -- Real-time preview -- Audio file download -- History and favorites - -### Image Resizer -Efficiently resize and optimize images while maintaining quality. Key features: -- Aspect ratio preservation -- Multiple output formats -- Batch processing capability -- Quality control -- Drag and drop support - -### Color Palette Generator -Create beautiful color schemes for your design projects. Capabilities include: -- Color harmony generation -- Custom color picking -- Multiple format export (HEX, RGB, HSL) -- Palette saving and sharing -- Accessibility checking - -### ASCII Art Generator -Convert images into creative ASCII art with extensive customization: -- Multiple character sets -- Size adjustment -- Color preservation -- Export options -- Real-time preview - -### QR Code Generator -Create customized QR codes for various purposes: -- Multiple format support -- Custom colors and sizes -- Error correction levels -- Download options -- Logo integration - -### Password Generator -Generate secure passwords with advanced options: -- Customizable length and complexity -- Character type selection -- Strength indicator -- Password history -- Copy to clipboard - -### URL Shortener -Create concise, shareable links with tracking capabilities: -- Custom aliases -- Click analytics -- Expiry settings -- QR code generation -- Link management - -## 🎨 Design Philosophy - -Our design approach focuses on: - -### Simplicity -- Clean, intuitive interfaces -- Clear visual hierarchy -- Minimal learning curve -- Consistent design patterns -- Focused functionality - -### Accessibility -- WCAG 2.1 compliance -- Screen reader support -- Keyboard navigation -- High contrast modes -- Responsive design - -### Performance -- Fast load times -- Efficient processing -- Minimal dependencies -- Offline capability -- Resource optimization - -### Security -- Client-side processing -- Data privacy -- Secure connections -- Input validation -- Error handling - -## πŸ’‘ Technical Implementation - -### Architecture -- Modular design -- Event-driven -- Component-based -- Service-oriented -- Progressive enhancement - -### Technologies -- Vanilla JavaScript -- Modern CSS -- Semantic HTML -- Web APIs -- SVG Graphics - -### Development Practices -- Clean code -- Comprehensive testing -- Continuous integration -- Version control -- Code review - -## 🎯 Target Audience - -Our platform serves: - -### Developers -- Quick tools for common tasks -- API integration options -- Development utilities -- Code formatting -- Testing tools - -### Designers -- Color tools -- Image processing -- Asset generation -- Design utilities -- Export options - -### Content Creators -- Text processing -- Media conversion -- Asset management -- Format conversion -- Batch processing - -### General Users -- Simple interfaces -- Common utilities -- File conversion -- Quick tools -- Help guides - -## 🌟 Unique Selling Points - -1. **All-in-One Platform** - - Multiple tools in one place - - Consistent interface - - Integrated workflow - - Cross-tool functionality - - Unified experience - -2. **Privacy Focus** - - No data collection - - Client-side processing - - No account required - - Transparent operation - - Security first - -3. **Accessibility** - - Universal design - - Inclusive features - - Multiple languages - - Device support - - User preferences - -4. **Performance** - - Fast processing - - Quick loading - - Efficient operation - - Resource optimization - - Offline support - -## πŸ”„ Development Cycle - -### Planning -- User research -- Feature prioritization -- Technical planning -- Resource allocation -- Timeline development - -### Implementation -- Agile methodology -- Sprint planning -- Regular updates -- Quality assurance -- Performance optimization - -### Testing -- Unit testing -- Integration testing -- User testing -- Performance testing -- Accessibility testing - -### Deployment -- Continuous integration -- Automated deployment -- Version control -- Documentation -- Monitoring - -## πŸ“Š Success Metrics - -We measure success through: - -### User Engagement -- Daily active users -- Tool usage statistics -- User retention -- Session duration -- Feature adoption - -### Performance -- Load times -- Processing speed -- Error rates -- Uptime -- Resource usage - -### Accessibility -- WCAG compliance -- User feedback -- Screen reader tests -- Keyboard navigation -- Color contrast - -### Impact -- User testimonials -- Community growth -- Feature requests -- Bug reports -- Social sharing - -## 🀝 Community Involvement - -We encourage: - -### Contributions -- Code contributions -- Feature suggestions -- Bug reports -- Documentation -- Translations - -### Feedback -- User surveys -- Feature voting -- Issue tracking -- Discussion forums -- Social media - -### Support -- Documentation -- Tutorials -- Community forums -- Email support -- Social channels - -## πŸ”œ Future Direction - -Our roadmap includes: - -### Platform Growth -- Additional tools -- Enhanced features -- Mobile apps -- Browser extensions -- API access - -### Technology -- PWA implementation -- Performance optimization -- New technologies -- Enhanced security -- Better integration - -### Community -- User accounts -- Collaboration features -- Sharing capabilities -- Custom workspaces -- Team features - -### Global Reach -- More languages -- Cultural adaptation -- Regional features -- Local support -- Global community - -Join us in building the future of digital tools! \ No newline at end of file diff --git a/docs/PROJECT-OVERVIEW.md b/docs/PROJECT-OVERVIEW.md deleted file mode 100644 index 113ed57..0000000 --- a/docs/PROJECT-OVERVIEW.md +++ /dev/null @@ -1,344 +0,0 @@ -# Digital Services Hub - Technical Overview - -## Project Architecture - -### Core Components - -1. **Configuration and Tools Management** - ```javascript - // tools.js - export const TOOLS = [ - { - id: 'text-to-speech', - name: 'Text to Speech', - description: '...', - icon: 'fa-volume-up', - features: ['Multiple voices', 'Download audio'], - path: 'text-to-speech.html', - category: 'audio', - order: 1 - }, - // ... other tools - ]; - - export const CATEGORIES = { - audio: { name: 'Audio Tools', icon: 'fa-music' }, - image: { name: 'Image Tools', icon: 'fa-image' }, - // ... other categories - }; - ``` - -2. **Base Tool Class** - ```javascript - class BaseTool { - constructor() { - // Initialize theme - initializeTheme(); - - // Initialize tool - this.elements = this.initializeElements(); - this.state = this.initializeState(); - this.bindEvents(); - this.initialize(); - - // Set up error boundary - this.setupErrorBoundary(); - } - - // Abstract methods - initializeElements() { /* Must be implemented */ } - initializeState() { /* Must be implemented */ } - bindEvents() { /* Must be implemented */ } - initialize() { /* Must be implemented */ } - - // Error handling - setupErrorBoundary() { /* Error boundary setup */ } - handleError(error) { /* Error handling */ } - - // File handling - validateFile(file, options) { /* File validation */ } - downloadFile(blob, filename) { /* File download */ } - - // Event handling - addKeyboardShortcut(key, callback, options) { /* Keyboard shortcuts */ } - } - ``` - -3. **Utility Modules** - ```javascript - // constants.js - Centralized configuration - export const APP_CONFIG = { /* App settings */ }; - export const STORAGE_KEYS = { /* Storage keys */ }; - export const THEMES = { /* Theme settings */ }; - export const FILE_LIMITS = { /* File restrictions */ }; - export const UI_CONSTANTS = { /* UI settings */ }; - export const ERROR_MESSAGES = { /* Error messages */ }; - export const KEYBOARD_SHORTCUTS = { /* Keyboard shortcuts */ }; - export const ACCESSIBILITY = { /* ARIA labels & roles */ }; - - // helpers.js - Utility functions - const utils = { - sanitizeHTML(html) { /* XSS prevention */ }, - isValidEmail(email) { /* Email validation */ }, - generateUID() { /* Unique ID generation */ }, - formatDate(date) { /* Date formatting */ }, - formatRelativeTime(date) { /* Relative time */ }, - copyToClipboard(text) { /* Clipboard operations */ }, - // ... other utilities - }; - - // theme.js - Theme management - export function initializeTheme() { /* Theme initialization */ } - - // ui.js - UI components - export const notifications = { /* Notification system */ }; - export const modal = { /* Modal dialogs */ }; - export const loader = { /* Loading indicators */ }; - ``` - -### Directory Structure - -``` -digital-services-hub/ -β”œβ”€β”€ css/ -β”‚ β”œβ”€β”€ components/ # Tool-specific styles -β”‚ β”‚ β”œβ”€β”€ ascii-art.css -β”‚ β”‚ β”œβ”€β”€ color-palette.css -β”‚ β”‚ └── ... -β”‚ β”œβ”€β”€ themes/ # Theme definitions -β”‚ β”‚ └── theme-variables.css -β”‚ └── utils/ # Shared styles -β”‚ β”œβ”€β”€ animations.css -β”‚ └── layout.css -β”œβ”€β”€ js/ -β”‚ β”œβ”€β”€ config/ # Configuration -β”‚ β”‚ └── tools.js # Tool definitions -β”‚ β”œβ”€β”€ features/ # Tool implementations -β”‚ β”‚ β”œβ”€β”€ ascii-art.js -β”‚ β”‚ β”œβ”€β”€ base-tool.js -β”‚ β”‚ └── ... -β”‚ └── utils/ # Utility modules -β”‚ β”œβ”€β”€ constants.js -β”‚ β”œβ”€β”€ helpers.js -β”‚ β”œβ”€β”€ theme.js -β”‚ β”œβ”€β”€ ui.js -β”‚ └── template-generator.js -β”œβ”€β”€ pages/ # Tool pages -β”‚ β”œβ”€β”€ ascii-art.html -β”‚ β”œβ”€β”€ color-palette.html -β”‚ └── ... -β”œβ”€β”€ scripts/ # Build scripts -β”‚ β”œβ”€β”€ build.js # Page generation -β”‚ └── validate.js # Code validation -└── index.html -``` - -## Implementation Details - -### Automated Build System - -1. **Page Generation** - ```javascript - // template-generator.js - export function generateToolPage(toolId) { - // Generate tool page HTML - } - - export function generateToolCard(tool) { - // Generate tool card HTML - } - - // build.js - async function buildToolPages() { - // Generate all tool pages - for (const tool of TOOLS) { - const pageContent = generateToolPage(tool.id); - await fs.writeFile(path.join('pages', tool.path), pageContent); - } - } - ``` - -2. **Code Validation** - ```javascript - // validate.js - async function validateProject() { - // Validate tool configuration - validateToolConfig(); - - // Validate file structure - await validateFileStructure(); - - // Validate HTML files - await validateHtmlFiles(); - - // Validate JavaScript files - await validateJavaScriptFiles(); - - // Validate CSS files - await validateCssFiles(); - } - ``` - -### Feature Modules - -Each tool extends the BaseTool class and implements its specific functionality: - -```javascript -class ToolName extends BaseTool { - initializeElements() { - // Initialize DOM elements - return { - input: document.getElementById('input'), - output: document.getElementById('output'), - // ... other elements - }; - } - - initializeState() { - // Initialize tool state - return { - settings: utils.getStorageItem(STORAGE_KEYS.SETTINGS), - history: utils.getStorageItem(STORAGE_KEYS.HISTORY), - // ... other state - }; - } - - bindEvents() { - // Set up event listeners - this.addKeyboardShortcut('s', this.save, { ctrl: true }); - // ... other events - } - - initialize() { - // Additional initialization - this.loadSettings(); - this.setupUI(); - } -} -``` - -### Error Handling - -1. **Error Boundary** - ```javascript - setupErrorBoundary() { - window.addEventListener('error', (event) => { - if (this.isEventFromTool(event)) { - this.handleError(event.error); - event.preventDefault(); - } - }); - - window.addEventListener('unhandledrejection', (event) => { - if (this.isEventFromTool(event)) { - this.handleError(event.reason); - event.preventDefault(); - } - }); - } - ``` - -2. **Notifications** - ```javascript - showNotification(message, type = 'info', duration = 3000) { - notifications[type](message, duration); - } - ``` - -### Theme System - -```javascript -function initializeTheme() { - const currentTheme = localStorage.getItem(STORAGE_KEYS.THEME); - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); - - // Set initial theme - if (currentTheme === 'dark' || (!currentTheme && prefersDark.matches)) { - document.body.classList.add('dark-theme'); - } - - // Handle theme changes - prefersDark.addEventListener('change', (e) => { - if (!localStorage.getItem(STORAGE_KEYS.THEME)) { - document.body.classList.toggle('dark-theme', e.matches); - } - }); -} -``` - -## Technical Specifications - -### Browser Support -- Chrome 90+ -- Firefox 88+ -- Safari 14+ -- Edge 90+ - -### Performance Targets -- Initial load: < 1.5s -- Tool initialization: < 300ms -- Operation response: < 50ms -- Build time: < 5s - -### Security Measures -- Input sanitization using DOMPurify -- Content Security Policy headers -- CORS configuration -- XSS prevention through sanitizeHTML -- Error boundaries for crash prevention - -### Accessibility Features -- ARIA labels and roles -- Keyboard navigation with shortcuts -- Screen reader support -- High contrast theme support -- Focus management in modals -- Live regions for notifications - -## Development Guidelines - -### Code Style -```javascript -// Use TypeScript-style JSDoc comments -/** - * @param {string} input - Raw user input - * @returns {Promise} Sanitized input - * @throws {Error} If input is invalid - */ -async function processInput(input) { - // Implementation -} - -// Use early returns -function validateInput(input) { - if (!input) return false; - if (typeof input !== 'string') return false; - return true; -} - -// Use consistent error handling -try { - await processUserInput(input); -} catch (error) { - this.handleError(error); - this.showNotification('Failed to process input', 'error'); -} -``` - -### Testing Requirements -- Unit tests for utility functions -- Integration tests for tool modules -- E2E tests for critical paths -- Accessibility testing (WCAG 2.1) -- Performance testing (Lighthouse) -- Build validation tests - -### Documentation Standards -- JSDoc for all functions -- README for each tool -- API documentation -- Usage examples -- Changelog updates -- Code comments for complex logic - -This technical overview provides a comprehensive guide to the project's architecture and implementation details. For specific tool documentation, refer to the individual tool directories. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index f10b872..38a8234 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,141 +1,108 @@ # Digital Services Hub -A modern collection of free web-based tools for everyday digital tasks. Built with vanilla JavaScript and designed for simplicity, accessibility, and performance. +A comprehensive suite of free, web-based tools that simplify everyday digital tasks. -## πŸ› οΈ Available Tools - -### Text to Speech -Convert text to natural-sounding speech with multiple voice options, adjustable speed and pitch, and the ability to download audio files. - -### Image Resizer -Resize images while maintaining quality, with support for multiple output formats, aspect ratio preservation, and batch processing. - -### Color Palette Generator -Create beautiful color schemes with advanced harmony generation, custom color picking, and export options in multiple formats. +## 🎯 Vision -### ASCII Art Generator -Transform images into ASCII art with customizable character sets, size options, and color preservation capabilities. +Digital Services Hub provides a modern, accessible platform focused on: +- 🌟 User-friendly and intuitive interfaces +- πŸ”’ Privacy-first approach with client-side processing +- β™Ώ Accessibility and inclusive design +- πŸš€ Performance and efficiency +- πŸ’» Cross-platform compatibility +- 🌐 Global availability -### QR Code Generator -Generate customizable QR codes with options for size, error correction, colors, and downloadable formats. - -### Password Generator -Create strong, secure passwords with customizable length, character types, and a built-in strength meter. +## πŸ› οΈ Available Tools -### URL Shortener -Shorten long URLs with custom alias options, expiry settings, and click analytics. +### Audio Tools +- **Text to Speech**: Convert text to natural-sounding speech with multiple voices and languages +- **Audio Converter** (Coming Soon): Convert between various audio formats -## πŸš€ Features +### Image Tools +- **Image Resizer**: Resize and optimize images while maintaining quality +- **ASCII Art**: Convert images into creative ASCII art +- **Color Palette**: Generate beautiful color harmonies for designs -- 🎨 Modern, responsive design -- πŸŒ™ Dark/light theme support -- ⌨️ Keyboard shortcuts -- πŸ“± Mobile-friendly interface -- β™Ώ WCAG 2.1 compliant -- πŸ”’ Secure, client-side processing -- πŸ’Ύ Local storage for settings -- πŸ“Š Usage analytics -- πŸ”„ Auto-save functionality -- πŸ“‹ Copy to clipboard -- ⚑ Offline support -- 🌐 Multi-language support +### Utility Tools +- **QR Code Generator**: Create customizable QR codes +- **Password Generator**: Generate secure passwords with advanced options +- **URL Shortener**: Create short, trackable links -## πŸ”§ Installation +## πŸš€ Getting Started 1. Clone the repository: ```bash git clone https://github.com/TMHDigital/Digital_Services.HUB.git ``` -2. Navigate to the project directory: +2. Install dependencies: ```bash - cd Digital_Services.HUB + npm install ``` -3. Open `index.html` in your browser or serve with a local server: +3. Start the development server: ```bash - python -m http.server 8000 - # or - php -S localhost:8000 - # or - npx serve + npm run dev ``` -## πŸ’» Usage +## πŸ—οΈ Project Structure -1. Visit the homepage at `index.html` -2. Select a tool from the available options -3. Follow the tool-specific instructions -4. Use the settings panel to customize the tool -5. Download or copy the results as needed - -## βš™οΈ Configuration +``` +Digital_Services.HUB/ +β”œβ”€β”€ css/ # Stylesheets +β”‚ β”œβ”€β”€ base/ # Base styles and reset +β”‚ β”œβ”€β”€ components/ # Component styles +β”‚ β”œβ”€β”€ themes/ # Theme configuration +β”‚ └── utils/ # Utility styles +β”œβ”€β”€ js/ # JavaScript files +β”‚ β”œβ”€β”€ build/ # Build scripts +β”‚ β”œβ”€β”€ components/ # UI components +β”‚ β”œβ”€β”€ config/ # Configuration +β”‚ β”œβ”€β”€ features/ # Tool implementations +β”‚ └── utils/ # Utility functions +β”œβ”€β”€ pages/ # Tool pages +β”œβ”€β”€ images/ # Static images +└── docs/ # Documentation +``` -Tools can be configured through the settings panel or by modifying `js/config/tools.js`: +## πŸ› οΈ Development -```javascript -export const TOOLS = [ - { - id: 'text-to-speech', - name: 'Text to Speech', - features: ['Multiple voices', 'Download audio'], - // ... other settings - }, - // ... other tools -]; -``` +### Prerequisites +- Node.js 16+ +- npm or yarn +- Modern web browser -## 🀝 Contributing +### Commands +- `npm run dev`: Start development server +- `npm run build`: Build for production +- `npm run test`: Run tests +- `npm run lint`: Lint code +- `npm run validate`: Validate project structure +### Contributing 1. Fork the repository -2. Create a feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) +2. Create your feature branch (`git checkout -b feature/AmazingFeature`) +3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the branch (`git push origin feature/AmazingFeature`) 5. Open a Pull Request -## πŸ“ License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## πŸ“ž Support - -- Create an issue for bug reports or feature requests -- Join our community discussions -- Follow us on social media for updates - -## πŸ™ Acknowledgments - -- [DOMPurify](https://github.com/cure53/DOMPurify) for HTML sanitization -- [QRCode.js](https://github.com/davidshimjs/qrcodejs) for QR code generation -- [Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API) for text-to-speech -- [Color.js](https://colorjs.io/) for color manipulation -- [FileSaver.js](https://github.com/eligrey/FileSaver.js/) for file downloads +## πŸ“ Documentation -## πŸ”œ Roadmap +- [Technical Documentation](./TECHNICAL.md) +- [API Documentation](./API.md) +- [Contributing Guidelines](../CONTRIBUTING.md) +- [Code of Conduct](../CODE_OF_CONDUCT.md) -- [ ] Additional language support -- [ ] PWA implementation -- [ ] More customization options -- [ ] API integration -- [ ] User accounts -- [ ] Cloud storage -- [ ] Mobile apps -- [ ] Browser extensions +## πŸ—ΊοΈ Roadmap -## πŸ“Š Statistics +See our [Future Features](./FUTURE-FEATURES.md) document for planned improvements and upcoming tools. -- 7+ tools available -- 100% client-side processing -- 95+ Lighthouse score -- WCAG 2.1 AA compliant -- 50+ countries reached -- 1000+ daily users +## πŸ“„ License -## πŸ† Awards & Recognition +This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details. -- Featured on Product Hunt -- GitHub trending repository -- Web accessibility awards -- Developer community choice +## 🀝 Support -Stay connected with us for updates and new features! +- Report bugs: [Issue Tracker](https://github.com/TMHDigital/Digital_Services.HUB/issues) +- Request features: [Feature Requests](https://github.com/TMHDigital/Digital_Services.HUB/issues/new) +- Join discussions: [Discussions](https://github.com/TMHDigital/Digital_Services.HUB/discussions) diff --git a/docs/TECHNICAL.md b/docs/TECHNICAL.md new file mode 100644 index 0000000..1372f30 --- /dev/null +++ b/docs/TECHNICAL.md @@ -0,0 +1,228 @@ +# Technical Documentation + +## Architecture Overview + +### Core Components + +1. **Tools Manager** + - Handles tool registration and configuration + - Manages tool state and lifecycle + - Provides tool discovery and filtering + +2. **Theme System** + - Manages light/dark theme switching + - Handles CSS variable customization + - Provides theme persistence + +3. **Storage System** + - Manages local storage operations + - Handles data persistence + - Provides storage cleanup + +### Directory Structure + +``` +js/ +β”œβ”€β”€ build/ # Build and validation scripts +β”‚ β”œβ”€β”€ generate-pages.js +β”‚ └── validate.js +β”œβ”€β”€ components/ # Reusable UI components +β”‚ β”œβ”€β”€ modal.js +β”‚ └── tooltip.js +β”œβ”€β”€ config/ # Configuration files +β”‚ β”œβ”€β”€ app.js +β”‚ └── tools.js +β”œβ”€β”€ features/ # Tool implementations +β”‚ └── tools-manager.js +└── utils/ # Utility functions + β”œβ”€β”€ dom.js + β”œβ”€β”€ format.js + └── storage.js + +css/ +β”œβ”€β”€ base/ # Base styles +β”‚ β”œβ”€β”€ reset.css +β”‚ └── layout.css +β”œβ”€β”€ components/ # Component styles +β”‚ β”œβ”€β”€ alerts.css +β”‚ β”œβ”€β”€ buttons.css +β”‚ └── cards.css +β”œβ”€β”€ themes/ # Theme styles +β”‚ └── variables.css +└── utils/ # Utility styles + β”œβ”€β”€ animations.css + └── utilities.css + +images/ +β”œβ”€β”€ icons/ # Tool and UI icons +β”œβ”€β”€ logos/ # Project logos and branding +β”œβ”€β”€ tools/ # Tool-specific images +β”œβ”€β”€ previews/ # Tool preview images +└── misc/ # Miscellaneous images +``` + +### Asset Organization + +#### Image Guidelines + +1. **Format Standards** + - Icons: SVG preferred, PNG fallback (max 64x64) + - Logos: SVG and PNG (multiple sizes) + - Tool Images: WebP with PNG fallback + - Previews: WebP with JPEG fallback + +2. **Naming Conventions** + - All lowercase with hyphens + - Include dimensions for raster images + - Format: `name-WxH.ext` + - Example: `tool-preview-800x600.webp` + +3. **Optimization Requirements** + - SVGs should be optimized (SVGO) + - Raster images should be compressed + - WebP for modern browsers + - Fallback formats for compatibility + +### Tool Configuration + +Tools are configured in `js/config/tools.js`: + +```javascript +export const TOOLS = [ + { + id: 'tool-id', + name: 'Tool Name', + description: 'Tool description', + icon: 'fa-icon-name', + features: ['Feature 1', 'Feature 2'], + path: './pages/tool-page.html', + category: TOOL_CATEGORIES.CATEGORY, + order: 1 + } +]; +``` + +### Theme System + +Themes are managed through CSS variables and data attributes: + +```css +[data-theme="dark"] { + --bg-primary: var(--bg-dark); + --text-primary: var(--text-light); +} + +[data-theme="light"] { + --bg-primary: var(--bg-light); + --text-primary: var(--text-dark); +} +``` + +### Storage System + +Data persistence is handled through the Storage API: + +```javascript +export function setStorageItem(key, value, type = 'local') { + try { + const storage = type === 'local' ? localStorage : sessionStorage; + storage.setItem(key, JSON.stringify(value)); + return true; + } catch (error) { + console.error(`Error setting ${key}:`, error); + return false; + } +} +``` + +## Build System + +### Scripts + +- `build:bundle`: Bundles JavaScript using Rollup +- `build:pages`: Generates tool pages +- `validate`: Validates project structure +- `test`: Runs Jest tests +- `lint`: Runs ESLint + +### Page Generation + +Pages are generated using the template system: + +```javascript +async function generatePages() { + for (const tool of TOOLS) { + const pageContent = await generateToolPage(tool.id); + await fs.writeFile( + path.join('pages', tool.path), + pageContent, + 'utf-8' + ); + } +} +``` + +## Testing + +Tests are written using Jest: + +```javascript +describe('ToolsManager', () => { + test('getAllTools returns all tools', () => { + const tools = ToolsManager.getAllTools(); + expect(Array.isArray(tools)).toBe(true); + expect(tools.length).toBeGreaterThan(0); + }); +}); +``` + +## Performance Considerations + +1. **Code Splitting** + - Each tool is loaded independently + - Common utilities are shared + - CSS is modularized + +2. **Caching** + - Static assets are cached + - Tool configurations are cached + - User preferences are persisted + +3. **Optimization** + - Images are optimized + - CSS is minified + - JavaScript is bundled and minified + +## Security + +1. **Input Validation** + - All user input is sanitized + - File uploads are validated + - URLs are checked + +2. **Output Encoding** + - HTML is escaped + - JSON is validated + - Files are safely downloaded + +3. **Data Protection** + - No server storage + - Client-side processing + - Secure defaults + +## Accessibility + +1. **ARIA Support** + - Proper roles + - State management + - Focus management + +2. **Keyboard Navigation** + - Focus trapping + - Shortcut keys + - Tab order + +3. **Screen Readers** + - Alt text + - ARIA labels + - Semantic HTML \ No newline at end of file diff --git a/images/.gitkeep b/images/.gitkeep new file mode 100644 index 0000000..490deac --- /dev/null +++ b/images/.gitkeep @@ -0,0 +1,8 @@ +# This directory contains project images +# Organized in subdirectories: +# +# - icons/ : Tool and UI icons +# - logos/ : Project logos and branding +# - tools/ : Tool-specific images +# - previews/ : Tool preview images +# - misc/ : Miscellaneous images \ No newline at end of file diff --git a/js/common.js b/js/common.js index 64f73e5..1e82c07 100644 --- a/js/common.js +++ b/js/common.js @@ -1,11 +1,24 @@ import { generateToolList } from './utils/template-generator.js'; import { initializeTheme } from './utils/theme.js'; -// Initialize theme -initializeTheme(); +/** + * Initialize the application + * @returns {void} + */ +function initializeApp() { + try { + // Initialize theme + initializeTheme(); -// Generate tool listings if on index page -const toolsContainer = document.getElementById('tools-container'); -if (toolsContainer) { - toolsContainer.innerHTML = generateToolList(); + // Generate tool listings if on index page + const toolsContainer = document.getElementById('tools-container'); + if (toolsContainer) { + toolsContainer.innerHTML = generateToolList(); + } + } catch (error) { + console.error('Failed to initialize application:', error); + } } + +// Initialize when DOM is ready +document.addEventListener('DOMContentLoaded', initializeApp); diff --git a/js/config/app.js b/js/config/app.js new file mode 100644 index 0000000..688211d --- /dev/null +++ b/js/config/app.js @@ -0,0 +1,34 @@ +/** + * Application configuration + */ + +export const APP_CONFIG = { + NAME: 'Digital Services Hub', + VERSION: '1.0.0', + AUTHOR: 'TMH Digital', + GITHUB_URL: 'https://github.com/TMHDigital/Digital_Services.HUB' +}; + +/** + * Storage keys used throughout the application + * @readonly + * @enum {string} + */ +export const STORAGE_KEYS = { + THEME: 'theme', + RECENT_URLS: 'shortened_urls', + RECENT_PALETTES: 'saved_palettes', + RECENT_QR_CODES: 'saved_qr_codes', + PASSWORD_HISTORY: 'password_history' +}; + +/** + * Available themes + * @readonly + * @enum {string} + */ +export const THEMES = { + LIGHT: 'light', + DARK: 'dark', + SYSTEM: 'system' +}; \ No newline at end of file diff --git a/js/config/tools.js b/js/config/tools.js index d62f01d..b4f866c 100644 --- a/js/config/tools.js +++ b/js/config/tools.js @@ -1,3 +1,30 @@ +/** + * @typedef {Object} Tool + * @property {string} id - Unique identifier for the tool + * @property {string} name - Display name of the tool + * @property {string} description - Tool description + * @property {string} icon - FontAwesome icon class + * @property {string[]} features - List of key features + * @property {string} path - Path to the tool's page + * @property {string} category - Tool category + * @property {number} order - Display order + */ + +/** + * Tool categories + * @readonly + * @enum {string} + */ +export const TOOL_CATEGORIES = { + AUDIO: 'audio', + IMAGE: 'image', + TEXT: 'text', + UTILITY: 'utility', + DESIGN: 'design', + SECURITY: 'security' +}; + +/** @type {Tool[]} */ export const TOOLS = [ { id: 'text-to-speech', @@ -6,7 +33,7 @@ export const TOOLS = [ icon: 'fa-volume-up', features: ['Multiple voices', 'Download audio'], path: './pages/text-to-speech.html', - category: 'audio', + category: TOOL_CATEGORIES.AUDIO, order: 1 }, { @@ -16,7 +43,7 @@ export const TOOLS = [ icon: 'fa-image', features: ['Preserve ratio', 'Multiple formats'], path: './pages/image-resizer.html', - category: 'image', + category: TOOL_CATEGORIES.IMAGE, order: 2 }, { @@ -26,7 +53,7 @@ export const TOOLS = [ icon: 'fa-palette', features: ['Color harmony', 'Export options'], path: './pages/color-palette.html', - category: 'design', + category: TOOL_CATEGORIES.DESIGN, order: 3 }, { @@ -36,7 +63,7 @@ export const TOOLS = [ icon: 'fa-font', features: ['Custom styles', 'Export text'], path: './pages/ascii-art.html', - category: 'image', + category: TOOL_CATEGORIES.IMAGE, order: 4 }, { @@ -46,7 +73,7 @@ export const TOOLS = [ icon: 'fa-qrcode', features: ['Custom styles', 'Download PNG'], path: './pages/qr-code.html', - category: 'utility', + category: TOOL_CATEGORIES.UTILITY, order: 5 }, { @@ -56,7 +83,7 @@ export const TOOLS = [ icon: 'fa-key', features: ['Custom options', 'Strength meter'], path: './pages/password-generator.html', - category: 'security', + category: TOOL_CATEGORIES.SECURITY, order: 6 }, { @@ -66,39 +93,59 @@ export const TOOLS = [ icon: 'fa-link', features: ['Click analytics', 'Custom aliases'], path: './pages/url-shortener.html', - category: 'utility', + category: TOOL_CATEGORIES.UTILITY, order: 7 } ]; +/** + * @typedef {Object} CategoryInfo + * @property {string} name - Display name of the category + * @property {string} description - Category description + * @property {string} icon - FontAwesome icon class + */ + +/** @type {Record} */ export const CATEGORIES = { - audio: { + [TOOL_CATEGORIES.AUDIO]: { name: 'Audio Tools', description: 'Tools for audio processing and conversion', icon: 'fa-music' }, - image: { + [TOOL_CATEGORIES.IMAGE]: { name: 'Image Tools', description: 'Tools for image manipulation and conversion', icon: 'fa-image' }, - design: { + [TOOL_CATEGORIES.DESIGN]: { name: 'Design Tools', description: 'Tools for design and color management', icon: 'fa-palette' }, - utility: { + [TOOL_CATEGORIES.UTILITY]: { name: 'Utility Tools', description: 'General purpose utility tools', icon: 'fa-tools' }, - security: { + [TOOL_CATEGORIES.SECURITY]: { name: 'Security Tools', description: 'Tools for security and privacy', icon: 'fa-shield-alt' + }, + [TOOL_CATEGORIES.TEXT]: { + name: 'Text Tools', + description: 'Tools for text manipulation and processing', + icon: 'fa-font' } }; +/** + * @typedef {Object} ToolStats + * @property {number} totalTools - Total number of tools + * @property {Record} toolsByCategory - Number of tools in each category + */ + +/** @type {ToolStats} */ export const TOOL_STATS = { totalTools: TOOLS.length, toolsByCategory: Object.fromEntries( diff --git a/js/features/__tests__/tools-manager.test.js b/js/features/__tests__/tools-manager.test.js new file mode 100644 index 0000000..2df29a4 --- /dev/null +++ b/js/features/__tests__/tools-manager.test.js @@ -0,0 +1,53 @@ +import { ToolsManager } from '../tools-manager.js'; +import { TOOL_CATEGORIES } from '../../config/tools.js'; + +describe('ToolsManager', () => { + test('getAllTools returns all tools', () => { + const tools = ToolsManager.getAllTools(); + expect(Array.isArray(tools)).toBe(true); + expect(tools.length).toBeGreaterThan(0); + }); + + test('getToolsByCategory returns correct tools', () => { + const imageTools = ToolsManager.getToolsByCategory(TOOL_CATEGORIES.IMAGE); + expect(Array.isArray(imageTools)).toBe(true); + imageTools.forEach(tool => { + expect(tool.category).toBe(TOOL_CATEGORIES.IMAGE); + }); + }); + + test('getToolById returns correct tool', () => { + const tool = ToolsManager.getToolById('image-resizer'); + expect(tool).toBeDefined(); + expect(tool.id).toBe('image-resizer'); + }); + + test('getCategoryInfo returns correct info', () => { + const info = ToolsManager.getCategoryInfo(TOOL_CATEGORIES.IMAGE); + expect(info).toBeDefined(); + expect(info.name).toBe('Image Tools'); + }); + + test('isValidCategory validates categories correctly', () => { + expect(ToolsManager.isValidCategory(TOOL_CATEGORIES.IMAGE)).toBe(true); + expect(ToolsManager.isValidCategory('invalid-category')).toBe(false); + }); + + test('getToolsSortedByOrder returns sorted tools', () => { + const tools = ToolsManager.getToolsSortedByOrder(); + for (let i = 1; i < tools.length; i++) { + expect(tools[i].order).toBeGreaterThanOrEqual(tools[i-1].order); + } + }); + + test('searchTools finds tools by name and description', () => { + const results = ToolsManager.searchTools('image'); + expect(Array.isArray(results)).toBe(true); + expect(results.length).toBeGreaterThan(0); + results.forEach(tool => { + const matchesName = tool.name.toLowerCase().includes('image'); + const matchesDesc = tool.description.toLowerCase().includes('image'); + expect(matchesName || matchesDesc).toBe(true); + }); + }); +}); \ No newline at end of file diff --git a/js/features/base-tool.js b/js/features/base-tool.js index 569835e..b89130b 100644 --- a/js/features/base-tool.js +++ b/js/features/base-tool.js @@ -1,6 +1,8 @@ import { initializeTheme } from '../utils/theme.js'; import { notifications } from '../utils/ui.js'; import utils from '../utils/helpers.js'; +import { generateSocialShare } from '../utils/template-generator.js'; +import { initializeSocialShare } from '../utils/ui.js'; export class BaseTool { constructor() { @@ -19,6 +21,8 @@ export class BaseTool { // Set up error boundary this.setupErrorBoundary(); + + this.initializeSocialShare(); } /** @@ -199,4 +203,25 @@ export class BaseTool { }); } } + + initializeSocialShare() { + const mainContainer = document.querySelector('main.container'); + if (mainContainer) { + const shareSection = document.createElement('div'); + shareSection.id = 'share-section'; + shareSection.innerHTML = generateSocialShare( + window.location.href, + document.title + ); + mainContainer.appendChild(shareSection); + initializeSocialShare(); + } + } + + updateThemeIcon(theme) { + const themeIcon = document.querySelector('#theme-button i'); + if (themeIcon) { + themeIcon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; + } + } } \ No newline at end of file diff --git a/js/features/tools-manager.js b/js/features/tools-manager.js new file mode 100644 index 0000000..d25cdb0 --- /dev/null +++ b/js/features/tools-manager.js @@ -0,0 +1,87 @@ +import { TOOLS, TOOL_CATEGORIES, CATEGORIES, TOOL_STATS } from '../config/tools.js'; + +/** + * Tools Manager class to handle tool-related operations + */ +export class ToolsManager { + /** + * Get all available tools + * @returns {Tool[]} + */ + static getAllTools() { + return TOOLS; + } + + /** + * Get tools by category + * @param {string} category - Category to filter by + * @returns {Tool[]} + */ + static getToolsByCategory(category) { + return TOOLS.filter(tool => tool.category === category); + } + + /** + * Get tool by ID + * @param {string} id - Tool ID to find + * @returns {Tool|undefined} + */ + static getToolById(id) { + return TOOLS.find(tool => tool.id === id); + } + + /** + * Get all available categories with their metadata + * @returns {Record} + */ + static getCategoriesWithInfo() { + return CATEGORIES; + } + + /** + * Get category information + * @param {string} category - Category to get info for + * @returns {CategoryInfo|undefined} + */ + static getCategoryInfo(category) { + return CATEGORIES[category]; + } + + /** + * Get tool statistics + * @returns {ToolStats} + */ + static getStats() { + return TOOL_STATS; + } + + /** + * Validate if a category exists + * @param {string} category - Category to validate + * @returns {boolean} + */ + static isValidCategory(category) { + return Object.values(TOOL_CATEGORIES).includes(category); + } + + /** + * Get tools sorted by order + * @returns {Tool[]} + */ + static getToolsSortedByOrder() { + return [...TOOLS].sort((a, b) => a.order - b.order); + } + + /** + * Search tools by name or description + * @param {string} query - Search query + * @returns {Tool[]} + */ + static searchTools(query) { + const lowercaseQuery = query.toLowerCase(); + return TOOLS.filter(tool => + tool.name.toLowerCase().includes(lowercaseQuery) || + tool.description.toLowerCase().includes(lowercaseQuery) + ); + } +} \ No newline at end of file diff --git a/js/utils/constants.js b/js/utils/constants.js deleted file mode 100644 index 0733401..0000000 --- a/js/utils/constants.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Constants and configuration values for Digital Services Hub - */ - -export const APP_CONFIG = { - NAME: 'Digital Services Hub', - VERSION: '1.0.0', - AUTHOR: 'TMH Digital', - GITHUB_URL: 'https://github.com/TMHDigital/Digital_Services.HUB' -}; - -export const STORAGE_KEYS = { - THEME: 'theme', - RECENT_URLS: 'shortened_urls', - RECENT_PALETTES: 'saved_palettes', - RECENT_QR_CODES: 'saved_qr_codes', - PASSWORD_HISTORY: 'password_history' -}; - -export const THEMES = { - LIGHT: 'light', - DARK: 'dark', - SYSTEM: 'system' -}; - -export const FILE_LIMITS = { - MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB - MAX_IMAGE_DIMENSION: 4096, - SUPPORTED_IMAGE_TYPES: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'], - SUPPORTED_AUDIO_TYPES: ['audio/mp3', 'audio/wav', 'audio/mpeg'], - MAX_HISTORY_ITEMS: 10 -}; - -export const UI_CONSTANTS = { - NOTIFICATION_DURATION: 3000, - MOBILE_BREAKPOINT: 768, - ANIMATION_DURATION: 300, - MAX_NOTIFICATIONS: 3, - DEBOUNCE_DELAY: 250, - MODAL_Z_INDEX: 1000, - NOTIFICATION_Z_INDEX: 1100, - LOADER_Z_INDEX: 1200 -}; - -export const ERROR_MESSAGES = { - FILE_TOO_LARGE: 'File size exceeds the maximum limit of 10MB', - UNSUPPORTED_FILE_TYPE: 'File type is not supported', - INVALID_DIMENSIONS: 'Image dimensions exceed the maximum limit', - NETWORK_ERROR: 'Network error occurred. Please check your connection', - STORAGE_ERROR: 'Error accessing local storage', - GENERIC_ERROR: 'An unexpected error occurred. Please try again' -}; - -export const KEYBOARD_SHORTCUTS = { - THEME_TOGGLE: { - key: 't', - ctrl: true, - shift: true, - description: 'Toggle dark/light theme' - }, - CLOSE_MODAL: { - key: 'Escape', - description: 'Close modal or popup' - }, - COPY_TO_CLIPBOARD: { - key: 'c', - ctrl: true, - description: 'Copy to clipboard' - } -}; - -export const ACCESSIBILITY = { - ARIA_LABELS: { - THEME_TOGGLE: 'Toggle dark/light theme', - FILE_UPLOAD: 'Choose a file to upload', - CLOSE_MODAL: 'Close modal', - COPY_BUTTON: 'Copy to clipboard', - DOWNLOAD_BUTTON: 'Download file', - GENERATE_BUTTON: 'Generate', - NOTIFICATION: 'Notification message', - LOADING: 'Loading, please wait' - }, - ROLES: { - ALERT: 'alert', - DIALOG: 'dialog', - STATUS: 'status', - BUTTON: 'button', - TOOLBAR: 'toolbar', - TABLIST: 'tablist', - TAB: 'tab', - TABPANEL: 'tabpanel' - } -}; \ No newline at end of file diff --git a/js/utils/dom.js b/js/utils/dom.js new file mode 100644 index 0000000..7897816 --- /dev/null +++ b/js/utils/dom.js @@ -0,0 +1,70 @@ +/** + * DOM manipulation and browser utilities + */ + +/** + * Sanitize HTML string to prevent XSS + * @param {string} html - HTML string to sanitize + * @returns {string} Sanitized HTML + */ +export function sanitizeHTML(html) { + const div = document.createElement('div'); + div.textContent = html; + return div.innerHTML; +} + +/** + * Check if element is in viewport + * @param {Element} element - Element to check + * @param {number} offset - Offset from viewport edges + * @returns {boolean} Whether element is in viewport + */ +export function isInViewport(element, offset = 0) { + const rect = element.getBoundingClientRect(); + return ( + rect.top >= 0 - offset && + rect.left >= 0 - offset && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + offset && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + offset + ); +} + +/** + * Copy text to clipboard + * @param {string} text - Text to copy + * @returns {Promise} + */ +export async function copyToClipboard(text) { + try { + await navigator.clipboard.writeText(text); + } catch (err) { + // Fallback for older browsers + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + try { + document.execCommand('copy'); + } finally { + document.body.removeChild(textArea); + } + } +} + +/** + * Check if device is mobile + * @returns {boolean} Whether device is mobile + */ +export function isMobile() { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); +} + +/** + * Get browser language + * @returns {string} Browser language code + */ +export function getBrowserLanguage() { + return navigator.language || navigator.userLanguage; +} \ No newline at end of file diff --git a/js/utils/format.js b/js/utils/format.js new file mode 100644 index 0000000..2787a08 --- /dev/null +++ b/js/utils/format.js @@ -0,0 +1,104 @@ +/** + * Formatting utilities for various data types + */ + +/** + * Format date with options + * @param {Date|string|number} date - Date to format + * @param {Intl.DateTimeFormatOptions} options - Format options + * @returns {string} Formatted date + */ +export function formatDate(date, options = {}) { + const dateObj = new Date(date); + return new Intl.DateTimeFormat(undefined, options).format(dateObj); +} + +/** + * Format relative time (e.g., "2 hours ago") + * @param {Date|string|number} date - Date to format + * @returns {string} Relative time string + */ +export function formatRelativeTime(date) { + const now = new Date(); + const then = new Date(date); + const diff = now.getTime() - then.getTime(); + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 7) { + return formatDate(then); + } else if (days > 0) { + return `${days} day${days === 1 ? '' : 's'} ago`; + } else if (hours > 0) { + return `${hours} hour${hours === 1 ? '' : 's'} ago`; + } else if (minutes > 0) { + return `${minutes} minute${minutes === 1 ? '' : 's'} ago`; + } else { + return 'Just now'; + } +} + +/** + * Format file size in bytes to human readable format + * @param {number} bytes - Size in bytes + * @returns {string} Formatted size + */ +export function formatFileSize(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; +} + +/** + * Get file extension from filename + * @param {string} filename - Filename + * @returns {string} File extension + */ +export function getFileExtension(filename) { + return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2); +} + +/** + * Convert RGB to Hex color + * @param {number} r - Red (0-255) + * @param {number} g - Green (0-255) + * @param {number} b - Blue (0-255) + * @returns {string} Hex color + */ +export function rgbToHex(r, g, b) { + return '#' + [r, g, b].map(x => { + const hex = x.toString(16); + return hex.length === 1 ? '0' + hex : hex; + }).join(''); +} + +/** + * Convert Hex to RGB color + * @param {string} hex - Hex color + * @returns {{r: number, g: number, b: number}|null} RGB color or null if invalid + */ +export function hexToRgb(hex) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; +} + +/** + * Check if color is light + * @param {string} color - Hex color + * @returns {boolean} Whether color is light + */ +export function isLightColor(color) { + const rgb = hexToRgb(color); + if (!rgb) return false; + const { r, g, b } = rgb; + const brightness = (r * 299 + g * 587 + b * 114) / 1000; + return brightness > 128; +} \ No newline at end of file diff --git a/js/utils/helpers.js b/js/utils/helpers.js deleted file mode 100644 index a394698..0000000 --- a/js/utils/helpers.js +++ /dev/null @@ -1,350 +0,0 @@ -/** - * Utility functions for Digital Services Hub - */ - -const utils = { - /** - * Sanitize HTML string to prevent XSS - * @param {string} html - HTML string to sanitize - * @returns {string} Sanitized HTML - */ - sanitizeHTML(html) { - const div = document.createElement('div'); - div.textContent = html; - return div.innerHTML; - }, - - /** - * Validate email address - * @param {string} email - Email to validate - * @returns {boolean} Whether email is valid - */ - isValidEmail(email) { - const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return re.test(String(email).toLowerCase()); - }, - - /** - * Generate a unique ID - * @returns {string} Unique ID - */ - generateUID() { - return Date.now().toString(36) + Math.random().toString(36).substr(2); - }, - - /** - * Deep clone an object - * @param {Object} obj - Object to clone - * @returns {Object} Cloned object - */ - deepClone(obj) { - if (obj === null || typeof obj !== 'object') return obj; - if (obj instanceof Date) return new Date(obj); - if (obj instanceof Array) return obj.map(item => this.deepClone(item)); - if (obj instanceof Object) { - return Object.fromEntries( - Object.entries(obj).map(([key, value]) => [key, this.deepClone(value)]) - ); - } - throw new Error(`Unable to clone object of type ${typeof obj}`); - }, - - /** - * Check if running in mobile browser - * @returns {boolean} Whether browser is mobile - */ - isMobile() { - return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); - }, - - /** - * Get browser language - * @returns {string} Browser language code - */ - getBrowserLanguage() { - return navigator.language || navigator.userLanguage; - }, - - /** - * Format date to locale string - * @param {Date|string|number} date - Date to format - * @param {Object} options - Intl.DateTimeFormat options - * @returns {string} Formatted date - */ - formatDate(date, options = {}) { - const d = new Date(date); - return d.toLocaleDateString(this.getBrowserLanguage(), options); - }, - - /** - * Format relative time - * @param {Date|string|number} date - Date to format - * @returns {string} Relative time string - */ - formatRelativeTime(date) { - const rtf = new Intl.RelativeTimeFormat(this.getBrowserLanguage(), { numeric: 'auto' }); - const now = new Date(); - const diff = new Date(date).getTime() - now.getTime(); - const diffDays = Math.round(diff / (1000 * 60 * 60 * 24)); - const diffHours = Math.round(diff / (1000 * 60 * 60)); - const diffMinutes = Math.round(diff / (1000 * 60)); - - if (Math.abs(diffDays) >= 1) return rtf.format(diffDays, 'day'); - if (Math.abs(diffHours) >= 1) return rtf.format(diffHours, 'hour'); - return rtf.format(diffMinutes, 'minute'); - }, - - /** - * Check if storage is available - * @param {string} type - Storage type ('localStorage' or 'sessionStorage') - * @returns {boolean} Whether storage is available - */ - isStorageAvailable(type) { - try { - const storage = window[type]; - const x = '__storage_test__'; - storage.setItem(x, x); - storage.removeItem(x); - return true; - } catch (e) { - return false; - } - }, - - /** - * Safe storage getter - * @param {string} key - Storage key - * @param {string} type - Storage type ('local' or 'session') - * @returns {any} Stored value or null - */ - getStorageItem(key, type = 'local') { - try { - const storage = type === 'local' ? localStorage : sessionStorage; - const item = storage.getItem(key); - return item ? JSON.parse(item) : null; - } catch (error) { - console.error(`Error reading from ${type}Storage:`, error); - return null; - } - }, - - /** - * Safe storage setter - * @param {string} key - Storage key - * @param {any} value - Value to store - * @param {string} type - Storage type ('local' or 'session') - * @returns {boolean} Whether operation was successful - */ - setStorageItem(key, value, type = 'local') { - try { - const storage = type === 'local' ? localStorage : sessionStorage; - storage.setItem(key, JSON.stringify(value)); - return true; - } catch (error) { - console.error(`Error writing to ${type}Storage:`, error); - return false; - } - }, - - /** - * Load image as Promise - * @param {string} src - Image URL - * @returns {Promise} Loaded image - */ - loadImage(src) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.onload = () => resolve(img); - img.onerror = reject; - img.src = src; - }); - }, - - /** - * Check if element is in viewport - * @param {HTMLElement} element - Element to check - * @param {number} [offset=0] - Offset from viewport edges - * @returns {boolean} Whether element is in viewport - */ - isInViewport(element, offset = 0) { - const rect = element.getBoundingClientRect(); - return ( - rect.top >= 0 - offset && - rect.left >= 0 - offset && - rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + offset && - rect.right <= (window.innerWidth || document.documentElement.clientWidth) + offset - ); - }, - - /** - * Copy text to clipboard - * @param {string} text - Text to copy - * @returns {Promise} Whether copy was successful - */ - async copyToClipboard(text) { - if (navigator.clipboard) { - try { - await navigator.clipboard.writeText(text); - return true; - } catch (error) { - console.error('Failed to copy using Clipboard API:', error); - } - } - - // Fallback - try { - const textarea = document.createElement('textarea'); - textarea.value = text; - textarea.style.position = 'fixed'; - textarea.style.opacity = '0'; - document.body.appendChild(textarea); - textarea.select(); - document.execCommand('copy'); - document.body.removeChild(textarea); - return true; - } catch (error) { - console.error('Failed to copy using fallback:', error); - return false; - } - }, - - /** - * Detect file type from array buffer - * @param {ArrayBuffer} buffer - File data - * @returns {string|null} MIME type or null - */ - detectFileType(buffer) { - const arr = new Uint8Array(buffer).subarray(0, 4); - let header = ''; - for (let i = 0; i < arr.length; i++) { - header += arr[i].toString(16); - } - - switch (header) { - case '89504e47': return 'image/png'; - case '47494638': return 'image/gif'; - case 'ffd8ffe0': - case 'ffd8ffe1': - case 'ffd8ffe2': return 'image/jpeg'; - case '52494646': return 'image/webp'; - default: return null; - } - }, - - /** - * Debounce function - * @param {Function} func - Function to debounce - * @param {number} wait - Wait time in milliseconds - * @returns {Function} Debounced function - */ - debounce(func, wait) { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - }, - - /** - * Throttle function - * @param {Function} func - Function to throttle - * @param {number} limit - Limit in milliseconds - * @returns {Function} Throttled function - */ - throttle(func, limit) { - let inThrottle; - return function executedFunction(...args) { - if (!inThrottle) { - func(...args); - inThrottle = true; - setTimeout(() => inThrottle = false, limit); - } - }; - }, - - /** - * Format file size - * @param {number} bytes - Size in bytes - * @returns {string} Formatted size - */ - formatFileSize(bytes) { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; - }, - - /** - * Get file extension - * @param {string} filename - File name - * @returns {string} File extension - */ - getFileExtension(filename) { - return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2); - }, - - /** - * Generate random string - * @param {number} length - String length - * @param {string} [chars] - Characters to use - * @returns {string} Random string - */ - generateRandomString(length, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') { - let result = ''; - const charactersLength = chars.length; - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; - }, - - /** - * Check if color is light - * @param {string} color - Color in hex format - * @returns {boolean} Whether color is light - */ - isLightColor(color) { - const hex = color.replace('#', ''); - const r = parseInt(hex.substr(0, 2), 16); - const g = parseInt(hex.substr(2, 2), 16); - const b = parseInt(hex.substr(4, 2), 16); - const brightness = ((r * 299) + (g * 587) + (b * 114)) / 1000; - return brightness > 155; - }, - - /** - * Convert RGB to Hex - * @param {number} r - Red value - * @param {number} g - Green value - * @param {number} b - Blue value - * @returns {string} Hex color - */ - rgbToHex(r, g, b) { - return '#' + [r, g, b].map(x => { - const hex = x.toString(16); - return hex.length === 1 ? '0' + hex : hex; - }).join(''); - }, - - /** - * Convert Hex to RGB - * @param {string} hex - Hex color - * @returns {Object} RGB values - */ - hexToRgb(hex) { - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : null; - } -}; - -// Export for ES modules -export default utils; \ No newline at end of file diff --git a/js/utils/storage.js b/js/utils/storage.js new file mode 100644 index 0000000..19d3c88 --- /dev/null +++ b/js/utils/storage.js @@ -0,0 +1,88 @@ +/** + * Storage utilities for managing local and session storage + */ + +/** + * Check if storage type is available + * @param {string} type - Storage type ('localStorage' or 'sessionStorage') + * @returns {boolean} Whether storage is available + */ +export function isStorageAvailable(type) { + try { + const storage = window[type]; + const x = '__storage_test__'; + storage.setItem(x, x); + storage.removeItem(x); + return true; + } catch (e) { + return false; + } +} + +/** + * Get item from storage + * @param {string} key - Storage key + * @param {'local'|'session'} type - Storage type + * @returns {any} Stored value or null + */ +export function getStorageItem(key, type = 'local') { + try { + const storage = type === 'local' ? localStorage : sessionStorage; + const item = storage.getItem(key); + return item ? JSON.parse(item) : null; + } catch (error) { + console.error(`Error getting ${key} from ${type} storage:`, error); + return null; + } +} + +/** + * Set item in storage + * @param {string} key - Storage key + * @param {any} value - Value to store + * @param {'local'|'session'} type - Storage type + * @returns {boolean} Whether operation was successful + */ +export function setStorageItem(key, value, type = 'local') { + try { + const storage = type === 'local' ? localStorage : sessionStorage; + storage.setItem(key, JSON.stringify(value)); + return true; + } catch (error) { + console.error(`Error setting ${key} in ${type} storage:`, error); + return false; + } +} + +/** + * Remove item from storage + * @param {string} key - Storage key + * @param {'local'|'session'} type - Storage type + * @returns {boolean} Whether operation was successful + */ +export function removeStorageItem(key, type = 'local') { + try { + const storage = type === 'local' ? localStorage : sessionStorage; + storage.removeItem(key); + return true; + } catch (error) { + console.error(`Error removing ${key} from ${type} storage:`, error); + return false; + } +} + +/** + * Clear all items from storage + * @param {'local'|'session'} type - Storage type + * @returns {boolean} Whether operation was successful + */ +export function clearStorage(type = 'local') { + try { + const storage = type === 'local' ? localStorage : sessionStorage; + storage.clear(); + return true; + } catch (error) { + console.error(`Error clearing ${type} storage:`, error); + return false; + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..23bf255 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "digital-services-hub", + "version": "1.0.0", + "description": "A collection of digital tools and utilities", + "type": "module", + "scripts": { + "test": "jest", + "test:watch": "jest --watch", + "lint": "eslint js/", + "lint:fix": "eslint js/ --fix", + "build": "npm run build:bundle && npm run build:pages", + "build:bundle": "rollup -c", + "build:pages": "node js/build/generate-pages.js", + "validate": "node js/build/validate.js", + "dev": "rollup -c -w", + "start": "serve ." + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.5.1" + }, + "devDependencies": { + "@babel/core": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", + "babel-jest": "^29.7.0", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "rollup": "^4.9.1", + "serve": "^14.2.1" + }, + "jest": { + "transform": { + "^.+\\.js$": "babel-jest" + }, + "testEnvironment": "node", + "moduleNameMapper": { + "^@/(.*)$": "/js/$1" + } + } +} \ No newline at end of file diff --git a/pages/ascii-art.html b/pages/ascii-art.html index 04a8f29..2028486 100644 --- a/pages/ascii-art.html +++ b/pages/ascii-art.html @@ -7,6 +7,7 @@ + diff --git a/pages/color-palette.html b/pages/color-palette.html index f7b436b..a1ba988 100644 --- a/pages/color-palette.html +++ b/pages/color-palette.html @@ -7,6 +7,7 @@ + diff --git a/pages/image-resizer.html b/pages/image-resizer.html index 2b7e343..340acef 100644 --- a/pages/image-resizer.html +++ b/pages/image-resizer.html @@ -7,6 +7,7 @@ + diff --git a/pages/password-generator.html b/pages/password-generator.html index a43c228..47f23aa 100644 --- a/pages/password-generator.html +++ b/pages/password-generator.html @@ -7,6 +7,7 @@ + diff --git a/pages/qr-code.html b/pages/qr-code.html index be824d9..ce0fa95 100644 --- a/pages/qr-code.html +++ b/pages/qr-code.html @@ -7,6 +7,7 @@ + diff --git a/pages/text-to-speech.html b/pages/text-to-speech.html index 3909163..d5a0909 100644 --- a/pages/text-to-speech.html +++ b/pages/text-to-speech.html @@ -7,6 +7,7 @@ + @@ -23,7 +24,7 @@

Digital Services Hub

- +

Text-to-Speech Converter

Convert text to natural-sounding speech

@@ -184,7 +185,7 @@

Quick Templates

- + diff --git a/pages/url-shortener.html b/pages/url-shortener.html index 7047815..c7e5cc0 100644 --- a/pages/url-shortener.html +++ b/pages/url-shortener.html @@ -7,6 +7,7 @@ + diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..ba1b1a5 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,22 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; + +export default { + input: 'js/common.js', + output: { + file: 'dist/bundle.js', + format: 'es', + sourcemap: true + }, + plugins: [ + nodeResolve(), + terser({ + format: { + comments: false + } + }) + ], + watch: { + include: 'js/**' + } +}; \ No newline at end of file diff --git a/scripts/build.js b/scripts/build.js deleted file mode 100644 index 214c64c..0000000 --- a/scripts/build.js +++ /dev/null @@ -1,401 +0,0 @@ -import { TOOLS } from '../js/config/tools.js'; -import { generateToolPage } from '../js/utils/template-generator.js'; -import fs from 'fs/promises'; -import path from 'path'; - -async function buildToolPages() { - try { - // Ensure pages directory exists - await fs.mkdir('pages', { recursive: true }); - - // Generate each tool page - for (const tool of TOOLS) { - const pageContent = generateToolPage(tool.id); - await fs.writeFile( - path.join('pages', tool.path), - pageContent, - 'utf-8' - ); - console.log(`Generated ${tool.path}`); - } - - // Generate index page - await generateIndexPage(); - console.log('Generated index.html'); - - // Generate about page - await generateAboutPage(); - console.log('Generated about.html'); - - // Ensure all required CSS files exist - await ensureToolStyles(); - console.log('Verified tool styles'); - - // Ensure all required JS files exist - await ensureToolScripts(); - console.log('Verified tool scripts'); - - } catch (error) { - console.error('Build failed:', error); - process.exit(1); - } -} - -async function ensureToolStyles() { - const cssDir = path.join('css', 'components'); - await fs.mkdir(cssDir, { recursive: true }); - - for (const tool of TOOLS) { - const cssPath = path.join(cssDir, `${tool.id}.css`); - try { - await fs.access(cssPath); - } catch { - // Create empty CSS file if it doesn't exist - await fs.writeFile(cssPath, '/* Styles for ' + tool.name + ' */\n', 'utf-8'); - console.log(`Created empty CSS file for ${tool.id}`); - } - } -} - -async function ensureToolScripts() { - const jsDir = path.join('js', 'features'); - await fs.mkdir(jsDir, { recursive: true }); - - for (const tool of TOOLS) { - const jsPath = path.join(jsDir, `${tool.id}.js`); - try { - await fs.access(jsPath); - } catch { - // Create basic JS file if it doesn't exist - const basicScript = `import { BaseTool } from './base-tool.js'; - -class ${toPascalCase(tool.id)} extends BaseTool { - constructor() { - super(); - this.initializeElements(); - this.setupEventListeners(); - } - - initializeElements() { - // Initialize tool elements - } - - setupEventListeners() { - // Setup event listeners - } -} - -// Initialize the tool -const ${toCamelCase(tool.id)} = new ${toPascalCase(tool.id)}(); -`; - await fs.writeFile(jsPath, basicScript, 'utf-8'); - console.log(`Created basic JS file for ${tool.id}`); - } - } -} - -function toPascalCase(str) { - return str.split('-') - .map(word => word.charAt(0).toUpperCase() + word.slice(1)) - .join(''); -} - -function toCamelCase(str) { - const pascal = toPascalCase(str); - return pascal.charAt(0).toLowerCase() + pascal.slice(1); -} - -async function generateIndexPage() { - const indexContent = ` - - - - - Digital Services Hub - Web Tools for Digital Tasks - - - - - - -
- -
-

Welcome to Digital Services Hub

-

Free web-based tools for your digital tasks

-
-
- -
- -
- -
-

Why Choose Digital Services Hub?

-
-
- -

Fast & Efficient

-

All tools are optimized for speed and performance.

-
-
- -

Secure

-

Your data stays in your browser, no server uploads needed.

-
-
- -

Accessible

-

Built with accessibility in mind for all users.

-
-
- -

Responsive

-

Works seamlessly on desktop and mobile devices.

-
-
-
-
- -
- - -
- - - -`; - - await fs.writeFile('index.html', indexContent, 'utf-8'); -} - -async function generateAboutPage() { - const aboutContent = ` - - - - - About - Digital Services Hub - - - - - - - -
- -
-

About Digital Services Hub

-

Empowering users with modern web tools

-
-
- -
- -
-
-

Our Mission

-

Digital Services Hub is dedicated to providing free, accessible, and powerful web-based tools for everyday digital tasks. We believe that quality digital tools should be available to everyone, regardless of technical expertise or budget.

-
-
- ${TOOLS.length}+ - Tools -
-
- 100 - Free -
-
- 0 - Ads -
-
-
-
- - -
-

Our Tools

- -
-
- - -
-

Technologies

-
-
-

Frontend

-
    -
  • HTML5
  • -
  • CSS3
  • -
  • JavaScript (ES6+)
  • -
-
-
-

Libraries

-
    -
  • Web Speech API
  • -
  • Canvas API
  • -
  • File API
  • -
-
-
-

Tools

-
    -
  • Git
  • -
  • GitHub
  • -
  • VS Code
  • -
-
-
-
- - -
-

Key Features

-
-
-
- -
-

Fast & Efficient

-

All tools are optimized for speed and performance, ensuring quick results without delays.

-
- -
-
- -
-

Secure

-

Your data stays in your browser. No server uploads, no tracking, complete privacy.

-
- -
-
- -
-

Accessible

-

Built with accessibility in mind, following WCAG guidelines for all users.

-
- -
-
- -
-

Responsive

-

Works seamlessly across all devices and screen sizes.

-
- -
-
- -
-

Open Source

-

Fully open source and free to use, modify, and distribute.

-
- -
-
- -
-

Regular Updates

-

Continuously improved with new features and security updates.

-
-
-
- - -
-

Contribute

-

Digital Services Hub is an open-source project. We welcome contributions from developers of all skill levels.

- -
-
- -
- - -
- - - -`; - - await fs.writeFile(path.join('pages', 'about.html'), aboutContent, 'utf-8'); -} - -// Run the build process -buildToolPages().then(() => { - console.log('Build completed successfully!'); -}); \ No newline at end of file diff --git a/scripts/validate.js b/scripts/validate.js deleted file mode 100644 index abf9953..0000000 --- a/scripts/validate.js +++ /dev/null @@ -1,217 +0,0 @@ -import { TOOLS, CATEGORIES } from '../js/config/tools.js'; -import fs from 'fs/promises'; -import path from 'path'; - -async function validateProject() { - const errors = []; - const warnings = []; - - try { - // Validate tool configuration - validateToolConfig(errors, warnings); - - // Validate file structure - await validateFileStructure(errors, warnings); - - // Validate HTML files - await validateHtmlFiles(errors, warnings); - - // Validate JavaScript files - await validateJavaScriptFiles(errors, warnings); - - // Validate CSS files - await validateCssFiles(errors, warnings); - - // Report results - if (errors.length > 0) { - console.error('\nValidation Errors:'); - errors.forEach(error => console.error(`❌ ${error}`)); - } - - if (warnings.length > 0) { - console.warn('\nValidation Warnings:'); - warnings.forEach(warning => console.warn(`⚠️ ${warning}`)); - } - - if (errors.length === 0 && warnings.length === 0) { - console.log('βœ… Validation passed successfully!'); - return true; - } - - if (errors.length > 0) { - process.exit(1); - } - - return warnings.length === 0; - } catch (error) { - console.error('Validation failed:', error); - process.exit(1); - } -} - -function validateToolConfig(errors, warnings) { - // Check for duplicate IDs - const ids = TOOLS.map(tool => tool.id); - const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index); - if (duplicateIds.length > 0) { - errors.push(`Duplicate tool IDs found: ${duplicateIds.join(', ')}`); - } - - // Check for valid categories - TOOLS.forEach(tool => { - if (!CATEGORIES[tool.category]) { - errors.push(`Invalid category '${tool.category}' for tool '${tool.id}'`); - } - }); - - // Check for required fields - TOOLS.forEach(tool => { - ['id', 'name', 'description', 'icon', 'features', 'path', 'category', 'order'].forEach(field => { - if (!tool[field]) { - errors.push(`Missing required field '${field}' in tool '${tool.id}'`); - } - }); - }); - - // Check for unique order values - const orders = TOOLS.map(tool => tool.order); - const duplicateOrders = orders.filter((order, index) => orders.indexOf(order) !== index); - if (duplicateOrders.length > 0) { - errors.push(`Duplicate order values found: ${duplicateOrders.join(', ')}`); - } -} - -async function validateFileStructure(errors, warnings) { - const requiredDirs = [ - 'pages', - 'js', - 'js/features', - 'js/utils', - 'js/config', - 'css', - 'css/components', - 'css/utils', - 'css/themes' - ]; - - for (const dir of requiredDirs) { - try { - await fs.access(dir); - } catch { - errors.push(`Missing required directory: ${dir}`); - } - } - - // Check for required base files - const requiredFiles = [ - 'index.html', - 'js/common.js', - 'css/styles.css', - 'js/utils/helpers.js', - 'js/utils/validation.js', - 'js/utils/ui.js' - ]; - - for (const file of requiredFiles) { - try { - await fs.access(file); - } catch { - errors.push(`Missing required file: ${file}`); - } - } -} - -async function validateHtmlFiles(errors, warnings) { - // Check each tool's HTML file - for (const tool of TOOLS) { - const htmlPath = path.join('pages', tool.path); - try { - const content = await fs.readFile(htmlPath, 'utf-8'); - - // Check for required meta tags - if (!content.includes(' { - if (!content.includes(method)) { - errors.push(`Missing required method '${method}' in ${jsPath}`); - } - }); - - // Check for proper initialization - const className = tool.id.split('-') - .map(part => part.charAt(0).toUpperCase() + part.slice(1)) - .join(''); - if (!content.includes(`new ${className}()`)) { - errors.push(`Missing tool initialization in ${jsPath}`); - } - } catch (error) { - errors.push(`Failed to validate JS file ${jsPath}: ${error.message}`); - } - } -} - -async function validateCssFiles(errors, warnings) { - // Check each tool's CSS file - for (const tool of TOOLS) { - const cssPath = path.join('css', 'components', `${tool.id}.css`); - try { - const content = await fs.readFile(cssPath, 'utf-8'); - - // Check for responsive design - if (!content.includes('@media')) { - warnings.push(`No media queries found in ${cssPath}`); - } - - // Check for CSS variables usage - if (!content.includes('var(--')) { - warnings.push(`No CSS variables used in ${cssPath}`); - } - - // Check for proper naming convention - const mainClass = `.${tool.id}`; - if (!content.includes(mainClass)) { - warnings.push(`Missing main tool class '${mainClass}' in ${cssPath}`); - } - } catch (error) { - errors.push(`Failed to validate CSS file ${cssPath}: ${error.message}`); - } - } -} - -// Run validation -validateProject(); \ No newline at end of file