diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0dc554e..d901f1b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,11 @@ "Bash(dotnet run:*)", "Bash(curl:*)", "Bash(pkill:*)", - "Bash(true)" + "Bash(true)", + "Bash(node:*)", + "Bash(grep:*)", + "Bash(mv:*)", + "Bash(sed:*)" ], "deny": [] } diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 36ee0d3..fef9c3f 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -68,11 +68,11 @@
-
+
- @@ -87,38 +87,38 @@
-
-
- + + +
+ +
+
+
+ Preview +
+
+ + Gerado em 0s + +
+
+
+
+
+ +

@Localizer["YourQRCodeWillAppear"]

+ + @Localizer["UltraFastGenerationGuaranteed"] + +
+
+ + +
+
+ + + @if (User.Identity.IsAuthenticated && await AdService.ShouldShowAds(userId)) + { +
+
+
+ QR Rapido Premium +
+
+
+
+
@Localizer["ThreeTimesFaster"]
+
+
    +
  • @Localizer["NoAdsForever"]
  • +
  • @Localizer["UnlimitedQRCodes"]
  • +
  • @Localizer["PriorityGeneration"]
  • +
  • @Localizer["DynamicQRCodes"]
  • +
  • @Localizer["RealTimeAnalytics"]
  • +
  • @Localizer["DeveloperAPI"]
  • +
+
+ + @Localizer["AcceleratePrice"] + + @Localizer["CancelAnytime"] +
+
+
+ } + + +
+
+
+ @Localizer["TipsFasterQR"] +
+
+
+
    +
  • @Localizer["ShortURLsFaster"]
  • +
  • @Localizer["LessTextMoreSpeed"]
  • +
  • @Localizer["SolidColorsOptimize"]
  • +
  • @Localizer["SmallerSizesAccelerate"]
  • +
+
+
+ + + @await Html.PartialAsync("_AdSpace", new { position = "sidebar" }) +
+
+
+ + +
+
+
+

@Localizer["WhyQRRapidoFaster"]

+

@Localizer["ComparisonOtherGenerators"]

+
+ +
+
+
+
+
QR Rapido
+
1.2s
+

@Localizer["OptimizedForSpeed"]

+ +
+
+
+
+
+
+
@Localizer["CompetitorA"]
+
3.5s
+

@Localizer["TraditionalGenerator"]

+
+
+
+
+
+
+
@Localizer["CompetitorB"]
+
4.8s
+

@Localizer["HeavyInterface"]

+
+
+
+
+
+
+
@Localizer["CompetitorC"]
+
6.2s
+

@Localizer["ManyAds"]

+
+
+
+
+
+
+ + + + + +
+ + + + + + +@await Html.PartialAsync("_AdSpace", new { position = "footer" }) diff --git a/wwwroot/css/qrrapido-theme.css b/wwwroot/css/qrrapido-theme.css index f8af186..8e53c09 100644 --- a/wwwroot/css/qrrapido-theme.css +++ b/wwwroot/css/qrrapido-theme.css @@ -376,6 +376,334 @@ footer a:hover { border-image: linear-gradient(135deg, var(--qr-primary) 0%, var(--qr-accent) 100%) 1; } +/* ================================= + FLUXO ASCENDENTE - ESTADOS VISUAIS PROGRESSIVOS + ================================= */ + +/* Estados desabilitados - cor e opacidade reduzida */ +.qr-flow-disabled { + opacity: 0.6 !important; + pointer-events: none !important; + transition: all 0.3s ease !important; +} + +.qr-flow-disabled .form-control, +.qr-flow-disabled .form-select { + background-color: #f5f5f5 !important; + border-color: #e0e0e0 !important; + color: #999999 !important; + cursor: not-allowed !important; +} + +.qr-flow-disabled .form-label { + color: #999999 !important; +} + +.qr-flow-disabled .btn-group .btn { + background-color: #f5f5f5 !important; + border-color: #e0e0e0 !important; + color: #999999 !important; + cursor: not-allowed !important; +} + +/* Estados habilitados - transição suave */ +.qr-flow-enabled { + opacity: 1 !important; + pointer-events: auto !important; + transition: all 0.3s ease !important; +} + +.qr-flow-enabled .form-control, +.qr-flow-enabled .form-select { + background-color: #ffffff !important; + border-color: #ced4da !important; + color: #495057 !important; + cursor: auto !important; +} + +.qr-flow-enabled .form-label { + color: #212529 !important; +} + +.qr-flow-enabled .btn-group .btn { + background-color: #ffffff !important; + border-color: #6c757d !important; + color: #6c757d !important; + cursor: pointer !important; +} + +/* Estados para o tema escuro */ +html[data-theme="dark"] .qr-flow-disabled .form-control, +html[data-theme="dark"] .qr-flow-disabled .form-select { + background-color: #3a3a3a !important; + border-color: #555555 !important; + color: #777777 !important; +} + +html[data-theme="dark"] .qr-flow-disabled .form-label { + color: #777777 !important; +} + +html[data-theme="dark"] .qr-flow-disabled .btn-group .btn { + background-color: #3a3a3a !important; + border-color: #555555 !important; + color: #777777 !important; +} + +html[data-theme="dark"] .qr-flow-enabled .form-control, +html[data-theme="dark"] .qr-flow-enabled .form-select { + background-color: #4a5568 !important; + border-color: #718096 !important; + color: #e2e8f0 !important; +} + +html[data-theme="dark"] .qr-flow-enabled .form-label { + color: #e2e8f0 !important; +} + +html[data-theme="dark"] .qr-flow-enabled .btn-group .btn { + background-color: #4a5568 !important; + border-color: #718096 !important; + color: #e2e8f0 !important; +} + +/* Animação de ascensão */ +@keyframes ascend { + from { + opacity: 0.6; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.qr-flow-ascending { + animation: ascend 0.4s ease-out forwards !important; +} + +/* Botão de geração - estado oculto inicial */ +#generate-button-container { + transition: all 0.4s ease !important; + transform: translateY(20px); + opacity: 0; +} + +#generate-button-container.show { + display: block !important; + transform: translateY(0); + opacity: 1; + animation: ascend 0.4s ease-out forwards; +} + +/* ================================= + DESTAQUE ELEGANTE PARA CAMPO INICIAL + ================================= */ + +/* Animação pulsante elegante */ +@keyframes pulseHighlight { + 0% { + box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); + border-color: #3B82F6; + } + 70% { + box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); + border-color: #3B82F6; + } + 100% { + box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); + border-color: #3B82F6; + } +} + +/* Classe para destacar campo inicial */ +.qr-field-highlight { + border: 2px solid #3B82F6 !important; + box-shadow: 0 0 5px rgba(59, 130, 246, 0.3) !important; + transition: all 0.3s ease !important; +} + +/* Remover destaque suavemente */ +.qr-field-highlight.removing { + animation: none !important; + border-color: #ced4da !important; + box-shadow: none !important; +} + +/* Ícone de seta animado */ +@keyframes chevronBounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateY(0); + } + 40% { + transform: translateY(-3px); + } + 60% { + transform: translateY(-1px); + } +} + +.chevron-animated { + animation: chevronBounce 2s infinite; + color: #3B82F6; + margin-left: 8px; + font-size: 0.875rem; +} + +/* Tooltip para campos bloqueados */ +.blocked-field-tooltip { + position: relative; +} + +.blocked-field-tooltip::after { + content: "Selecione primeiro o Tipo de QR Code"; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + background: rgba(33, 37, 41, 0.9); + color: white; + padding: 8px 12px; + border-radius: 6px; + font-size: 0.75rem; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + z-index: 1000; + margin-bottom: 5px; +} + +.blocked-field-tooltip::before { + content: ""; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border: 5px solid transparent; + border-top-color: rgba(33, 37, 41, 0.9); + margin-bottom: -5px; + opacity: 0; + transition: opacity 0.3s ease; +} + +.blocked-field-tooltip:hover::after, +.blocked-field-tooltip:hover::before { + opacity: 1; +} + +/* Botão de ajuda flutuante */ +.help-button-floating { + position: fixed; + top: 20px; + right: 20px; + z-index: 1050; + width: 50px; + height: 50px; + border-radius: 50%; + background: linear-gradient(135deg, #3B82F6, #6366F1); + border: none; + color: white; + font-size: 1.25rem; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.help-button-floating:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(59, 130, 246, 0.6); + background: linear-gradient(135deg, #2563EB, #4F46E5); +} + +.help-button-floating:active { + transform: translateY(0); +} + +/* Seta apontando para o select */ +.pointer-arrow { + position: absolute; + width: 0; + height: 0; + border-left: 15px solid transparent; + border-right: 15px solid transparent; + border-bottom: 20px solid #3B82F6; + top: -25px; + left: 50%; + transform: translateX(-50%); + animation: arrowBounce 1.5s ease-in-out infinite; +} + +@keyframes arrowBounce { + 0%, 100% { + transform: translateX(-50%) translateY(0); + } + 50% { + transform: translateX(-50%) translateY(-5px); + } +} + +/* Toasts customizados */ +.toast-onboarding { + background: linear-gradient(135deg, #3B82F6, #6366F1); + color: white; + border: none; + box-shadow: 0 8px 25px rgba(59, 130, 246, 0.3); + border-radius: 12px; + overflow: hidden; +} + +.toast-onboarding .toast-header { + background: rgba(255, 255, 255, 0.1); + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + color: white; +} + +.toast-onboarding .toast-body { + font-weight: 500; + line-height: 1.5; +} + +.toast-onboarding .btn-close { + filter: invert(1); + opacity: 0.8; +} + +.toast-onboarding .btn-close:hover { + opacity: 1; +} + +/* Container de toasts personalizado */ +.toast-container-onboarding { + position: fixed; + bottom: 20px; + right: 20px; + z-index: 1055; + max-width: 350px; +} + +/* Tema escuro - ajustes para destaque */ +html[data-theme="dark"] .qr-field-highlight { + border-color: #60A5FA !important; + box-shadow: 0 0 5px rgba(96, 165, 250, 0.3) !important; +} + +html[data-theme="dark"] .chevron-animated { + color: #60A5FA; +} + +html[data-theme="dark"] .blocked-field-tooltip::after { + background: rgba(248, 250, 252, 0.95); + color: #1f2937; +} + +html[data-theme="dark"] .blocked-field-tooltip::before { + border-top-color: rgba(248, 250, 252, 0.95); +} + /* Print Styles */ @media print { .ad-container, diff --git a/wwwroot/js/qr-speed-generator.js b/wwwroot/js/qr-speed-generator.js index ed32cef..b08a496 100644 --- a/wwwroot/js/qr-speed-generator.js +++ b/wwwroot/js/qr-speed-generator.js @@ -4,6 +4,9 @@ class QRRapidoGenerator { this.startTime = 0; this.currentQR = null; this.timerInterval = null; + this.selectedType = null; + this.selectedStyle = null; + this.contentValid = false; this.languageStrings = { 'pt-BR': { tagline: 'Gere QR codes em segundos!', @@ -45,6 +48,7 @@ class QRRapidoGenerator { this.checkAdFreeStatus(); this.updateLanguage(); this.updateStatsCounters(); + this.initializeProgressiveFlow(); } initializeEvents() { @@ -54,10 +58,21 @@ class QRRapidoGenerator { form.addEventListener('submit', this.generateQRWithTimer.bind(this)); } - // Quick style selection + // Quick style selection with flow control document.querySelectorAll('input[name="quick-style"]').forEach(radio => { - radio.addEventListener('change', this.applyQuickStyle.bind(this)); + radio.addEventListener('change', (e) => { + this.handleStyleSelection(e.target.value); + this.applyQuickStyle(e); + }); }); + + // Content field monitoring + const contentField = document.getElementById('qr-content'); + if (contentField) { + contentField.addEventListener('input', (e) => { + this.handleContentChange(e.target.value); + }); + } // Logo upload feedback const logoUpload = document.getElementById('logo-upload'); @@ -71,10 +86,13 @@ class QRRapidoGenerator { cornerStyle.addEventListener('change', this.handleCornerStyleChange.bind(this)); } - // QR type change with hints + // QR type change with hints and flow control const qrType = document.getElementById('qr-type'); if (qrType) { - qrType.addEventListener('change', this.updateContentHints.bind(this)); + qrType.addEventListener('change', (e) => { + this.handleTypeSelection(e.target.value); + this.updateContentHints(); + }); } // Language selector @@ -98,6 +116,7 @@ class QRRapidoGenerator { if (saveBtn) { saveBtn.addEventListener('click', this.saveToHistory.bind(this)); } + } setupDownloadButtons() { @@ -373,6 +392,12 @@ class QRRapidoGenerator { return false; } + // Check if flow is properly completed + if (!this.selectedType || !this.selectedStyle || !this.contentValid) { + this.showError('Complete todas as etapas antes de gerar o QR Code'); + return false; + } + // Special validation for VCard if (qrType === 'vcard') { try { @@ -652,7 +677,11 @@ class QRRapidoGenerator { // Show/hide VCard interface based on type if (type === 'vcard') { - if (vcardInterface) vcardInterface.style.display = 'block'; + if (vcardInterface) { + vcardInterface.style.display = 'block'; + // Add VCard input monitoring for progressive flow + this.setupVCardMonitoring(); + } if (contentTextarea) { contentTextarea.style.display = 'none'; contentTextarea.removeAttribute('required'); @@ -689,6 +718,30 @@ class QRRapidoGenerator { const langHints = hints[this.currentLang] || hints['pt-BR']; hintsElement.textContent = langHints[type] || 'Digite o conteúdo apropriado para o tipo selecionado'; } + + setupVCardMonitoring() { + // Monitor VCard required fields for progressive flow + const requiredFields = ['vcard-name', 'vcard-mobile', 'vcard-email']; + + requiredFields.forEach(fieldId => { + const field = document.getElementById(fieldId); + if (field) { + field.addEventListener('input', () => { + // Use a small delay to allow for validation + setTimeout(() => { + const isValid = this.validateContent(''); // VCard validation is internal + this.contentValid = isValid; + + if (isValid && this.selectedType && this.selectedStyle) { + this.showGenerateButton(); + } else { + this.hideGenerateButton(); + } + }, 100); + }); + } + }); + } changeLanguage(e) { e.preventDefault(); @@ -1085,6 +1138,281 @@ class QRRapidoGenerator { }, type === 'success' ? 3000 : 5000); } + initializeProgressiveFlow() { + // Reset all states to initial disabled state + this.selectedType = null; + this.selectedStyle = null; + this.contentValid = false; + + // Ensure proper initial state + this.disableStyleSelection(); + this.disableContentFields(); + this.disableAdvancedOptions(); + this.hideGenerateButton(); + } + + handleTypeSelection(type) { + this.selectedType = type; + + if (type) { + // Remove highlight when type is selected + this.removeInitialHighlight(); + + // Enable style selection + this.enableStyleSelection(); + // Reset subsequent selections + this.selectedStyle = null; + this.contentValid = false; + this.disableContentFields(); + this.hideGenerateButton(); + + } else { + // Disable everything if no type selected + this.disableStyleSelection(); + this.disableContentFields(); + this.disableAdvancedOptions(); + this.hideGenerateButton(); + } + } + + handleStyleSelection(style) { + this.selectedStyle = style; + + if (style && this.selectedType) { + // Enable content fields + this.enableContentFields(); + + // Open advanced panel if 'advanced' style is selected (future enhancement) + if (style === 'advanced') { + this.openAdvancedCustomization(); + } + + // Reset content validation + this.contentValid = false; + this.hideGenerateButton(); + + } + } + + handleContentChange(content) { + const isValid = this.validateContent(content); + this.contentValid = isValid; + + if (isValid && this.selectedType && this.selectedStyle) { + this.showGenerateButton(); + + } else { + this.hideGenerateButton(); + } + } + + validateContent(content) { + if (!content) return false; + + const trimmedContent = content.trim(); + + // VCard has its own validation + if (this.selectedType === 'vcard') { + try { + if (window.vcardGenerator) { + const errors = window.vcardGenerator.validateVCardData(); + return errors.length === 0; + } + } catch (error) { + return false; + } + return false; + } + + // General validation: at least 3 characters + return trimmedContent.length >= 3; + } + + enableStyleSelection() { + const styleContainer = document.getElementById('style-selector'); + const styleInputs = document.querySelectorAll('input[name="quick-style"]'); + const styleLabel = styleContainer?.parentElement.querySelector('.form-label'); + + if (styleContainer) { + styleContainer.classList.remove('qr-flow-disabled'); + styleContainer.classList.add('qr-flow-enabled', 'qr-flow-ascending'); + } + + if (styleLabel) { + styleLabel.classList.remove('qr-flow-disabled'); + styleLabel.classList.add('qr-flow-enabled'); + } + + styleInputs.forEach(input => { + input.disabled = false; + }); + } + + disableStyleSelection() { + const styleContainer = document.getElementById('style-selector'); + const styleInputs = document.querySelectorAll('input[name="quick-style"]'); + const styleLabel = styleContainer?.parentElement.querySelector('.form-label'); + + if (styleContainer) { + styleContainer.classList.remove('qr-flow-enabled', 'qr-flow-ascending'); + styleContainer.classList.add('qr-flow-disabled'); + } + + if (styleLabel) { + styleLabel.classList.remove('qr-flow-enabled'); + styleLabel.classList.add('qr-flow-disabled'); + } + + styleInputs.forEach(input => { + input.disabled = true; + input.checked = false; + }); + + // Reset to default selection + const defaultStyle = document.getElementById('style-classic'); + if (defaultStyle) { + defaultStyle.checked = true; + } + } + + enableContentFields() { + const contentTextarea = document.getElementById('qr-content'); + const contentLabel = contentTextarea?.parentElement.querySelector('.form-label'); + const vcardInterface = document.getElementById('vcard-interface'); + + if (contentTextarea) { + contentTextarea.classList.remove('qr-flow-disabled'); + contentTextarea.classList.add('qr-flow-enabled', 'qr-flow-ascending'); + contentTextarea.disabled = false; + } + + if (contentLabel) { + contentLabel.classList.remove('qr-flow-disabled'); + contentLabel.classList.add('qr-flow-enabled'); + } + + if (vcardInterface && this.selectedType === 'vcard') { + vcardInterface.classList.remove('qr-flow-disabled'); + vcardInterface.classList.add('qr-flow-enabled', 'qr-flow-ascending'); + + // Enable VCard form fields + const vcardInputs = vcardInterface.querySelectorAll('input, textarea, select'); + vcardInputs.forEach(input => { + input.disabled = false; + }); + } + + // Enable advanced customization + this.enableAdvancedOptions(); + } + + disableContentFields() { + const contentTextarea = document.getElementById('qr-content'); + const contentLabel = contentTextarea?.parentElement.querySelector('.form-label'); + const vcardInterface = document.getElementById('vcard-interface'); + + if (contentTextarea) { + contentTextarea.classList.remove('qr-flow-enabled', 'qr-flow-ascending'); + contentTextarea.classList.add('qr-flow-disabled'); + contentTextarea.disabled = true; + contentTextarea.value = ''; + } + + if (contentLabel) { + contentLabel.classList.remove('qr-flow-enabled'); + contentLabel.classList.add('qr-flow-disabled'); + } + + if (vcardInterface) { + vcardInterface.classList.remove('qr-flow-enabled', 'qr-flow-ascending'); + vcardInterface.classList.add('qr-flow-disabled'); + + // Disable VCard form fields + const vcardInputs = vcardInterface.querySelectorAll('input, textarea, select'); + vcardInputs.forEach(input => { + input.disabled = true; + input.value = ''; + input.checked = false; + }); + } + + this.disableAdvancedOptions(); + } + + enableAdvancedOptions() { + const advancedAccordion = document.getElementById('customization-accordion'); + + if (advancedAccordion) { + advancedAccordion.classList.remove('qr-flow-disabled'); + advancedAccordion.classList.add('qr-flow-enabled', 'qr-flow-ascending'); + + // Enable form controls inside accordion + const advancedInputs = advancedAccordion.querySelectorAll('input, select'); + advancedInputs.forEach(input => { + input.disabled = false; + }); + } + } + + disableAdvancedOptions() { + const advancedAccordion = document.getElementById('customization-accordion'); + + if (advancedAccordion) { + advancedAccordion.classList.remove('qr-flow-enabled', 'qr-flow-ascending'); + advancedAccordion.classList.add('qr-flow-disabled'); + + // Disable form controls inside accordion + const advancedInputs = advancedAccordion.querySelectorAll('input, select'); + advancedInputs.forEach(input => { + input.disabled = true; + }); + } + } + + showGenerateButton() { + const buttonContainer = document.getElementById('generate-button-container'); + const generateBtn = document.getElementById('generate-btn'); + + if (buttonContainer) { + buttonContainer.style.display = 'block'; + buttonContainer.classList.add('show'); + } + + if (generateBtn) { + generateBtn.disabled = false; + } + } + + hideGenerateButton() { + const buttonContainer = document.getElementById('generate-button-container'); + const generateBtn = document.getElementById('generate-btn'); + + if (buttonContainer) { + buttonContainer.classList.remove('show'); + setTimeout(() => { + if (!buttonContainer.classList.contains('show')) { + buttonContainer.style.display = 'none'; + } + }, 400); + } + + if (generateBtn) { + generateBtn.disabled = true; + } + } + + openAdvancedCustomization() { + const advancedPanel = document.getElementById('customization-panel'); + const accordionButton = document.querySelector('[data-bs-target="#customization-panel"]'); + + if (advancedPanel && accordionButton) { + // Use Bootstrap's collapse to open the panel + const collapse = new bootstrap.Collapse(advancedPanel, { + show: true + }); + } + } + setupRealTimePreview() { const contentField = document.getElementById('qr-content'); const typeField = document.getElementById('qr-type'); @@ -1105,6 +1433,14 @@ class QRRapidoGenerator { typeField.addEventListener('change', updatePreview); } } + + // Remove destaque inicial quando tipo for selecionado + removeInitialHighlight() { + const typeField = document.getElementById('qr-type'); + if (typeField && typeField.classList.contains('qr-field-highlight')) { + typeField.classList.remove('qr-field-highlight'); + } + } } // Initialize when DOM loads