Implemented a copy-to-clipboard button for QR codes, including Blazor logic for feedback and disabled states. Injected IJSRuntime for JS interop and added supporting JS functions for image copying and fallback download. Reduced QR code image size for better compatibility. Added responsive and accessible CSS for the new button. Cleaned up timers and feedback state on component disposal.
695 lines
12 KiB
CSS
695 lines
12 KiB
CSS
/* QRPay Component Styles */
|
|
|
|
.qrpay-container {
|
|
width: 100%;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
/* Form Card Styles */
|
|
.qrpay-card {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 24px;
|
|
padding: 40px;
|
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
|
width: 100%;
|
|
max-width: 500px;
|
|
animation: slideUp 0.6s ease-out;
|
|
}
|
|
|
|
/* Header Styles */
|
|
.qrpay-header {
|
|
text-align: center;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.qrpay-logo {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.qrpay-logo img {
|
|
width: 80px;
|
|
height: 80px;
|
|
filter: drop-shadow(0 4px 12px rgba(45, 90, 39, 0.3));
|
|
}
|
|
|
|
.qrpay-title {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
background: linear-gradient(135deg, #2d5a27, #4a7c59);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
margin: 0 0 8px 0;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.qrpay-subtitle {
|
|
color: #6b7280;
|
|
font-size: 1.1rem;
|
|
margin: 0;
|
|
font-weight: 400;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* Form Styles */
|
|
.qrpay-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
}
|
|
|
|
.form-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.form-label {
|
|
font-weight: 600;
|
|
color: #374151;
|
|
font-size: 0.95rem;
|
|
margin: 0;
|
|
}
|
|
|
|
.form-control {
|
|
padding: 16px 20px;
|
|
border: 2px solid #e5e7eb;
|
|
border-radius: 12px;
|
|
font-size: 1rem;
|
|
transition: all 0.3s ease;
|
|
background: #ffffff;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.form-control:focus {
|
|
outline: none;
|
|
border-color: #4a7c59;
|
|
box-shadow: 0 0 0 4px rgba(74, 124, 89, 0.1);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.form-control:invalid {
|
|
border-color: #ef4444;
|
|
}
|
|
|
|
.validation-message {
|
|
color: #ef4444;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
margin-top: 4px;
|
|
display: block;
|
|
animation: fadeIn 0.3s ease;
|
|
}
|
|
|
|
/* Unified Button Base Styles */
|
|
.btn-base {
|
|
border: none;
|
|
padding: 18px 32px;
|
|
border-radius: 12px;
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
width: 100%;
|
|
min-height: 56px;
|
|
text-decoration: none;
|
|
outline: none;
|
|
}
|
|
|
|
/* Primary Green Button (Generate) */
|
|
.btn-generate {
|
|
background: linear-gradient(135deg, #2d5a27, #4a7c59);
|
|
color: white;
|
|
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.3);
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.btn-generate:hover:not(:disabled) {
|
|
background: linear-gradient(135deg, #1f3d1a, #3d6b4a);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 24px rgba(45, 90, 39, 0.4);
|
|
}
|
|
|
|
.btn-generate:active:not(:disabled) {
|
|
transform: translateY(0);
|
|
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.3);
|
|
}
|
|
|
|
.btn-generate:disabled {
|
|
opacity: 0.7;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
background: linear-gradient(135deg, #2d5a27, #4a7c59);
|
|
}
|
|
|
|
/* Secondary Green Button (Back) */
|
|
.btn-back {
|
|
background: rgba(74, 124, 89, 0.1);
|
|
color: #2d5a27;
|
|
border: 2px solid #4a7c59;
|
|
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.1);
|
|
}
|
|
|
|
.btn-back:hover {
|
|
background: rgba(74, 124, 89, 0.15);
|
|
border-color: #2d5a27;
|
|
color: #1f3d1a;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 24px rgba(45, 90, 39, 0.2);
|
|
}
|
|
|
|
.btn-back:active {
|
|
transform: translateY(0);
|
|
box-shadow: 0 4px 12px rgba(45, 90, 39, 0.1);
|
|
background: rgba(74, 124, 89, 0.2);
|
|
}
|
|
|
|
/* Copy Button Styles */
|
|
.btn-copy {
|
|
background: rgba(16, 185, 129, 0.1);
|
|
color: #065f46;
|
|
border: 2px solid #10b981;
|
|
padding: 12px 20px;
|
|
border-radius: 10px;
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
margin-top: 16px;
|
|
min-height: 48px;
|
|
width: auto;
|
|
max-width: 200px;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
}
|
|
|
|
.btn-copy:hover:not(:disabled) {
|
|
background: rgba(16, 185, 129, 0.15);
|
|
border-color: #059669;
|
|
color: #047857;
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
|
|
}
|
|
|
|
.btn-copy:active:not(:disabled) {
|
|
transform: translateY(0);
|
|
box-shadow: 0 2px 6px rgba(16, 185, 129, 0.15);
|
|
}
|
|
|
|
.btn-copy:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.btn-copy.copying {
|
|
background: rgba(16, 185, 129, 0.05);
|
|
border-color: #6ee7b7;
|
|
color: #059669;
|
|
}
|
|
|
|
.copy-spinner {
|
|
width: 16px;
|
|
height: 16px;
|
|
border: 2px solid rgba(6, 95, 70, 0.3);
|
|
border-radius: 50%;
|
|
border-top-color: #065f46;
|
|
animation: spin 1s ease-in-out infinite;
|
|
}
|
|
|
|
.spinner {
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 50%;
|
|
border-top-color: white;
|
|
animation: spin 1s ease-in-out infinite;
|
|
}
|
|
|
|
/* QR Code Display Styles */
|
|
.qrcode-container {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 24px;
|
|
padding: 40px;
|
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
|
width: 100%;
|
|
max-width: 500px;
|
|
text-align: center;
|
|
animation: slideUp 0.6s ease-out;
|
|
}
|
|
|
|
.qrcode-header {
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.qrcode-header h2 {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: #2d5a27;
|
|
margin: 0 0 8px 0;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.qrcode-header p {
|
|
color: #6b7280;
|
|
font-size: 1.1rem;
|
|
margin: 0;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.qrcode-display {
|
|
background: #ffffff;
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
margin-bottom: 32px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.qrcode-display img {
|
|
width: 280px;
|
|
height: 280px;
|
|
border-radius: 8px;
|
|
max-width: 100%;
|
|
height: auto;
|
|
aspect-ratio: 1;
|
|
display: block;
|
|
margin: 0 auto 16px auto;
|
|
}
|
|
|
|
/* Payment Details */
|
|
.payment-details {
|
|
background: #f8fafc;
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 0;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.detail-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.detail-label {
|
|
font-weight: 600;
|
|
color: #374151;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.detail-value {
|
|
font-weight: 500;
|
|
color: #2d5a27;
|
|
text-align: right;
|
|
word-break: break-word;
|
|
max-width: 60%;
|
|
}
|
|
|
|
/* Animations */
|
|
@keyframes slideUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(30px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
/* Enhanced Responsive Design */
|
|
|
|
/* Large tablets and small desktops */
|
|
@media (max-width: 768px) {
|
|
.qrpay-container {
|
|
padding: 16px;
|
|
align-items: flex-start;
|
|
padding-top: 32px;
|
|
}
|
|
|
|
.qrpay-card,
|
|
.qrcode-container {
|
|
padding: 32px;
|
|
border-radius: 20px;
|
|
}
|
|
|
|
.qrpay-title {
|
|
font-size: 2.25rem;
|
|
}
|
|
|
|
.qrcode-display img {
|
|
width: 260px;
|
|
height: 260px;
|
|
}
|
|
|
|
.form-control {
|
|
font-size: 16px; /* Prevents zoom on iOS */
|
|
}
|
|
|
|
.btn-copy {
|
|
font-size: 0.9rem;
|
|
padding: 10px 16px;
|
|
max-width: 180px;
|
|
}
|
|
}
|
|
|
|
/* Portrait tablets and large phones */
|
|
@media (max-width: 640px) {
|
|
.qrpay-container {
|
|
padding: 12px;
|
|
min-height: auto;
|
|
padding-top: 20px;
|
|
padding-bottom: 20px;
|
|
}
|
|
|
|
.qrpay-card,
|
|
.qrcode-container {
|
|
padding: 24px;
|
|
border-radius: 16px;
|
|
margin: 0;
|
|
}
|
|
|
|
.qrpay-header {
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.qrpay-logo img {
|
|
width: 70px;
|
|
height: 70px;
|
|
}
|
|
|
|
.qrpay-title {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.qrpay-subtitle {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.qrcode-header h2 {
|
|
font-size: 1.75rem;
|
|
}
|
|
|
|
.qrcode-header p {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.qrcode-display {
|
|
padding: 20px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.qrcode-display img {
|
|
width: 240px;
|
|
height: 240px;
|
|
}
|
|
|
|
.btn-copy {
|
|
font-size: 0.85rem;
|
|
padding: 10px 14px;
|
|
max-width: 160px;
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.payment-details {
|
|
padding: 20px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.detail-row {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 4px;
|
|
padding: 16px 0;
|
|
}
|
|
|
|
.detail-label {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.detail-value {
|
|
max-width: 100%;
|
|
text-align: left;
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.form-group {
|
|
gap: 6px;
|
|
}
|
|
|
|
.form-label {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.btn-generate,
|
|
.btn-back {
|
|
padding: 16px 24px;
|
|
font-size: 1rem;
|
|
}
|
|
}
|
|
|
|
/* Mobile phones in portrait */
|
|
@media (max-width: 480px) {
|
|
.qrpay-container {
|
|
padding: 8px;
|
|
padding-top: 16px;
|
|
padding-bottom: 16px;
|
|
}
|
|
|
|
.qrpay-card,
|
|
.qrcode-container {
|
|
padding: 20px;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.qrpay-header {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.qrpay-logo {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.qrpay-logo img {
|
|
width: 60px;
|
|
height: 60px;
|
|
}
|
|
|
|
.qrpay-title {
|
|
font-size: 1.75rem;
|
|
}
|
|
|
|
.qrpay-subtitle {
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.qrcode-header {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.qrcode-header h2 {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.qrcode-header p {
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.qrcode-display {
|
|
padding: 16px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.qrcode-display img {
|
|
width: 200px;
|
|
height: 200px;
|
|
}
|
|
|
|
.btn-copy {
|
|
font-size: 0.8rem;
|
|
padding: 8px 12px;
|
|
max-width: 140px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.payment-details {
|
|
padding: 16px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.detail-row {
|
|
padding: 12px 0;
|
|
}
|
|
|
|
.qrpay-form {
|
|
gap: 20px;
|
|
}
|
|
|
|
.form-control {
|
|
padding: 14px 16px;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.btn-generate,
|
|
.btn-back {
|
|
padding: 14px 20px;
|
|
min-height: 52px;
|
|
}
|
|
}
|
|
|
|
/* Very small phones */
|
|
@media (max-width: 360px) {
|
|
.qrpay-container {
|
|
padding: 4px;
|
|
padding-top: 12px;
|
|
padding-bottom: 12px;
|
|
}
|
|
|
|
.qrpay-card,
|
|
.qrcode-container {
|
|
padding: 16px;
|
|
}
|
|
|
|
.qrpay-title {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.qrcode-display img {
|
|
width: 180px;
|
|
height: 180px;
|
|
}
|
|
|
|
.btn-copy {
|
|
font-size: 0.75rem;
|
|
padding: 8px 10px;
|
|
max-width: 120px;
|
|
}
|
|
|
|
.form-control {
|
|
padding: 12px 14px;
|
|
}
|
|
}
|
|
|
|
/* Landscape orientation adjustments */
|
|
@media (max-height: 600px) and (orientation: landscape) {
|
|
.qrpay-container {
|
|
align-items: flex-start;
|
|
padding-top: 16px;
|
|
padding-bottom: 16px;
|
|
}
|
|
|
|
.qrpay-header {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.qrpay-logo img {
|
|
width: 50px;
|
|
height: 50px;
|
|
}
|
|
|
|
.qrpay-title {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.qrcode-header {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.qrcode-display {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.qrcode-display img {
|
|
width: 160px;
|
|
height: 160px;
|
|
}
|
|
|
|
.btn-copy {
|
|
font-size: 0.8rem;
|
|
padding: 6px 10px;
|
|
max-width: 110px;
|
|
}
|
|
|
|
.payment-details {
|
|
margin-bottom: 20px;
|
|
}
|
|
}
|
|
|
|
/* Touch improvements for mobile */
|
|
@media (max-width: 768px) {
|
|
.btn-generate:hover,
|
|
.btn-back:hover,
|
|
.btn-copy:hover {
|
|
transform: none; /* Disable hover transforms on mobile */
|
|
}
|
|
|
|
.form-control:focus {
|
|
transform: none; /* Disable focus transforms on mobile for better UX */
|
|
}
|
|
|
|
/* Larger touch targets */
|
|
.btn-generate,
|
|
.btn-back,
|
|
.btn-copy {
|
|
min-height: 44px; /* WCAG recommended minimum touch target */
|
|
}
|
|
}
|
|
|
|
/* Accessibility improvements */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.qrpay-card,
|
|
.qrcode-container {
|
|
animation: none;
|
|
}
|
|
|
|
.validation-message {
|
|
animation: none;
|
|
}
|
|
|
|
.spinner,
|
|
.copy-spinner {
|
|
animation: none;
|
|
}
|
|
|
|
* {
|
|
transition: none !important;
|
|
}
|
|
} |