Plugin Directory

Changeset 3472405


Ignore:
Timestamp:
03/02/2026 07:27:20 AM (5 weeks ago)
Author:
closemarketing
Message:

Update to version 1.3.3 from GitHub

Location:
frontblocks
Files:
2 added
24 edited
1 copied

Legend:

Unmodified
Added
Removed
  • frontblocks/tags/1.3.3/assets/admin/settings.css

    r3462660 r3472405  
    521521  margin-left: -0.25rem;
    522522}
     523.frbl-settings-wrapper .tw-mb-0 {
     524  margin-bottom: 0px;
     525}
    523526.frbl-settings-wrapper .tw-mb-2 {
    524527  margin-bottom: 0.5rem;
     
    530533  margin-bottom: 2rem;
    531534}
    532 .frbl-settings-wrapper .tw-ml-2 {
    533   margin-left: 0.5rem;
    534 }
    535535.frbl-settings-wrapper .tw-ml-3 {
    536536  margin-left: 0.75rem;
    537537}
    538 .frbl-settings-wrapper .tw-ml-auto {
    539   margin-left: auto;
    540 }
    541538.frbl-settings-wrapper .tw-mr-2 {
    542539  margin-right: 0.5rem;
    543540}
    544 .frbl-settings-wrapper .tw-mt-1 {
    545   margin-top: 0.25rem;
     541.frbl-settings-wrapper .tw-mt-0 {
     542  margin-top: 0px;
    546543}
    547544.frbl-settings-wrapper .tw-mt-2 {
    548545  margin-top: 0.5rem;
    549546}
     547.frbl-settings-wrapper .tw-mt-4 {
     548  margin-top: 1rem;
     549}
     550.frbl-settings-wrapper .tw-mt-6 {
     551  margin-top: 1.5rem;
     552}
    550553.frbl-settings-wrapper .tw-mt-8 {
    551554  margin-top: 2rem;
     
    575578  max-width: 64rem;
    576579}
     580.frbl-settings-wrapper .tw-flex-1 {
     581  flex: 1 1 0%;
     582}
    577583.frbl-settings-wrapper .tw-flex-shrink-0 {
    578584  flex-shrink: 0;
    579585}
    580 .frbl-settings-wrapper .tw-flex-grow {
    581   flex-grow: 1;
    582 }
    583 .frbl-settings-wrapper .tw-items-start {
    584   align-items: flex-start;
    585 }
    586586.frbl-settings-wrapper .tw-items-center {
    587587  align-items: center;
     
    592592.frbl-settings-wrapper .tw-gap-2 {
    593593  gap: 0.5rem;
    594 }
    595 .frbl-settings-wrapper .tw-gap-3 {
    596   gap: 0.75rem;
    597594}
    598595.frbl-settings-wrapper :is(.tw-space-x-2 > :not([hidden]) ~ :not([hidden])) {
     
    601598  margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
    602599}
    603 .frbl-settings-wrapper :is(.tw-space-y-4 > :not([hidden]) ~ :not([hidden])) {
    604   --tw-space-y-reverse: 0;
    605   margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
    606   margin-bottom: calc(1rem * var(--tw-space-y-reverse));
    607 }
    608600.frbl-settings-wrapper :is(.tw-space-y-6 > :not([hidden]) ~ :not([hidden])) {
    609601  --tw-space-y-reverse: 0;
     
    611603  margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
    612604}
     605.frbl-settings-wrapper .tw-overflow-auto {
     606  overflow: auto;
     607}
    613608.frbl-settings-wrapper .tw-overflow-hidden {
    614609  overflow: hidden;
     
    647642  border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
    648643}
    649 .frbl-settings-wrapper .tw-border-green-300 {
    650   --tw-border-opacity: 1;
    651   border-color: rgb(134 239 172 / var(--tw-border-opacity, 1));
    652 }
    653644.frbl-settings-wrapper .tw-border-red-200 {
    654645  --tw-border-opacity: 1;
    655646  border-color: rgb(254 202 202 / var(--tw-border-opacity, 1));
    656647}
    657 .frbl-settings-wrapper .tw-border-red-300 {
    658   --tw-border-opacity: 1;
    659   border-color: rgb(252 165 165 / var(--tw-border-opacity, 1));
    660 }
    661648.frbl-settings-wrapper .tw-border-transparent {
    662649  border-color: transparent;
    663650}
    664 .frbl-settings-wrapper .tw-border-yellow-300 {
     651.frbl-settings-wrapper .tw-border-yellow-200 {
    665652  --tw-border-opacity: 1;
    666   border-color: rgb(253 224 71 / var(--tw-border-opacity, 1));
     653  border-color: rgb(254 240 138 / var(--tw-border-opacity, 1));
    667654}
    668655.frbl-settings-wrapper .tw-border-yellow-400 {
     
    678665  background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
    679666}
    680 .frbl-settings-wrapper .tw-bg-green-100 {
    681   --tw-bg-opacity: 1;
    682   background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1));
    683 }
    684667.frbl-settings-wrapper .tw-bg-primary-100 {
    685668  --tw-bg-opacity: 1;
     
    690673  background-color: rgb(104 125 249 / var(--tw-bg-opacity, 1));
    691674}
    692 .frbl-settings-wrapper .tw-bg-red-100 {
    693   --tw-bg-opacity: 1;
    694   background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
    695 }
    696675.frbl-settings-wrapper .tw-bg-red-50 {
    697676  --tw-bg-opacity: 1;
     
    701680  --tw-bg-opacity: 1;
    702681  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
    703 }
    704 .frbl-settings-wrapper .tw-bg-yellow-100 {
    705   --tw-bg-opacity: 1;
    706   background-color: rgb(254 249 195 / var(--tw-bg-opacity, 1));
    707682}
    708683.frbl-settings-wrapper .tw-bg-yellow-50 {
     
    721696  --tw-gradient-to: #fff var(--tw-gradient-to-position);
    722697}
    723 .frbl-settings-wrapper .tw-p-3 {
    724   padding: 0.75rem;
    725 }
    726698.frbl-settings-wrapper .tw-p-4 {
    727699  padding: 1rem;
     700}
     701.frbl-settings-wrapper .tw-p-6 {
     702  padding: 1.5rem;
    728703}
    729704.frbl-settings-wrapper .tw-px-3 {
     
    743718  padding-bottom: 0.25rem;
    744719}
     720.frbl-settings-wrapper .tw-py-2 {
     721  padding-top: 0.5rem;
     722  padding-bottom: 0.5rem;
     723}
    745724.frbl-settings-wrapper .tw-py-3 {
    746725  padding-top: 0.75rem;
     
    755734  padding-bottom: 2rem;
    756735}
    757 .frbl-settings-wrapper .tw-pr-4 {
    758   padding-right: 1rem;
    759 }
    760736.frbl-settings-wrapper .tw-pt-6 {
    761737  padding-top: 1.5rem;
     
    763739.frbl-settings-wrapper .tw-text-center {
    764740  text-align: center;
     741}
     742.frbl-settings-wrapper .tw-text-2xl {
     743  font-size: 1.5rem;
     744  line-height: 2rem;
    765745}
    766746.frbl-settings-wrapper .tw-text-3xl {
     
    772752  line-height: 1.5rem;
    773753}
     754.frbl-settings-wrapper .tw-text-lg {
     755  font-size: 1.125rem;
     756  line-height: 1.75rem;
     757}
    774758.frbl-settings-wrapper .tw-text-sm {
    775759  font-size: 0.875rem;
     
    809793  color: rgb(75 85 99 / var(--tw-text-opacity, 1));
    810794}
     795.frbl-settings-wrapper .tw-text-gray-700 {
     796  --tw-text-opacity: 1;
     797  color: rgb(55 65 81 / var(--tw-text-opacity, 1));
     798}
    811799.frbl-settings-wrapper .tw-text-gray-900 {
    812800  --tw-text-opacity: 1;
    813801  color: rgb(17 24 39 / var(--tw-text-opacity, 1));
    814802}
    815 .frbl-settings-wrapper .tw-text-green-800 {
    816   --tw-text-opacity: 1;
    817   color: rgb(22 101 52 / var(--tw-text-opacity, 1));
    818 }
    819803.frbl-settings-wrapper .tw-text-primary-500 {
    820804  --tw-text-opacity: 1;
     
    829813  color: rgb(185 28 28 / var(--tw-text-opacity, 1));
    830814}
    831 .frbl-settings-wrapper .tw-text-red-800 {
    832   --tw-text-opacity: 1;
    833   color: rgb(153 27 27 / var(--tw-text-opacity, 1));
    834 }
    835815.frbl-settings-wrapper .tw-text-white {
    836816  --tw-text-opacity: 1;
     
    844824  --tw-text-opacity: 1;
    845825  color: rgb(161 98 7 / var(--tw-text-opacity, 1));
    846 }
    847 .frbl-settings-wrapper .tw-text-yellow-800 {
    848   --tw-text-opacity: 1;
    849   color: rgb(133 77 14 / var(--tw-text-opacity, 1));
    850826}
    851827.frbl-settings-wrapper .tw-underline {
    852828  text-decoration-line: underline;
    853 }
    854 .frbl-settings-wrapper .tw-no-underline {
    855   text-decoration-line: none;
    856 }
    857 .frbl-settings-wrapper .tw-opacity-50 {
    858   opacity: 0.5;
    859829}
    860830.frbl-settings-wrapper .tw-shadow-sm {
     
    10521022  }
    10531023}
    1054 .frbl-settings-wrapper .hover\:tw-bg-primary-200:hover {
    1055   --tw-bg-opacity: 1;
    1056   background-color: rgb(205 208 251 / var(--tw-bg-opacity, 1));
    1057 }
    10581024.frbl-settings-wrapper .hover\:tw-bg-primary-600:hover {
    10591025  --tw-bg-opacity: 1;
     
    10631029  --tw-text-opacity: 1;
    10641030  color: rgb(85 101 237 / var(--tw-text-opacity, 1));
    1065 }
    1066 .frbl-settings-wrapper .hover\:tw-no-underline:hover {
    1067   text-decoration-line: none;
    10681031}
    10691032.frbl-settings-wrapper .focus\:tw-border-transparent:focus {
     
    11011064}
    11021065
    1103 /* Custom styles for license fields */
    1104 .frbl-settings-wrapper .tw-text-base:not(button) {
    1105     font-size: 1rem;
    1106     line-height: 1.5rem;
    1107     width: 100%;
    1108 }
    1109 
    1110 .frbl-settings-wrapper .tw-flex {
    1111     display: flex;
    1112     column-gap: 20px;
    1113 }
    1114 
    1115 /* Submit button width */
    1116 .frbl-settings-wrapper .tw-w-1\/2 {
    1117     width: 50%;
    1118 }
    1119 
    1120 /* Features Grid Layout */
    1121 .frbl-section-wrapper {
    1122     margin-bottom: 3rem;
    1123 }
    1124 
    1125 .frbl-features-grid {
    1126     display: grid;
    1127     grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    1128     gap: 1.25rem;
    1129 }
    1130 
    1131 /* Feature Card Styles */
    1132 .frbl-feature-card {
    1133     position: relative;
    1134     background: #ffffff;
    1135     border: 1px solid #e5e7eb;
    1136     border-radius: 12px;
    1137     padding: 1.5rem;
    1138     transition: all 0.2s ease;
    1139     overflow: hidden;
    1140     display: flex;
    1141     flex-direction: column;
    1142     min-height: 80px;
    1143 }
    1144 
    1145 .frbl-feature-card:hover {
    1146     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    1147     transform: translateY(-2px);
    1148     border-color: #687df9;
    1149 }
    1150 
    1151 /* PRO Badge */
    1152 .frbl-pro-badge {
    1153     position: absolute;
    1154     top: 0;
    1155     left: 0;
    1156     background: linear-gradient(135deg, #ec4899 0%, #f97316 100%);
    1157     color: #ffffff;
    1158     font-size: 0.625rem;
    1159     font-weight: 700;
    1160     letter-spacing: 0.05em;
    1161     padding: 0.25rem 0.75rem;
    1162     border-radius: 0 0 12px 0;
    1163     box-shadow: 0 2px 4px rgba(236, 72, 153, 0.3);
    1164     z-index: 10;
    1165 }
    1166 
    1167 /* Feature Card Content */
    1168 .frbl-feature-content {
    1169     display: flex;
    1170     align-items: center;
    1171     justify-content: space-between;
    1172     gap: 1rem;
    1173     min-height: 3rem;
    1174 }
    1175 
    1176 /* Feature Icon */
    1177 .frbl-feature-icon {
    1178     flex-shrink: 0;
    1179     width: 2.5rem;
    1180     height: 2.5rem;
    1181     display: flex;
    1182     align-items: center;
    1183     justify-content: center;
    1184     background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
    1185     border-radius: 10px;
    1186     color: #687df9;
    1187     transition: all 0.2s ease;
    1188 }
    1189 
    1190 .frbl-feature-card:hover .frbl-feature-icon {
    1191     background: linear-gradient(135deg, #e0e3fc 0%, #d0d4fb 100%);
    1192     transform: scale(1.05);
    1193 }
    1194 
    1195 .frbl-feature-icon svg {
    1196     width: 1.5rem;
    1197     height: 1.5rem;
    1198     stroke-width: 2;
    1199 }
    1200 
    1201 /* Feature Info */
    1202 .frbl-feature-info {
    1203     flex: 1;
    1204     min-width: 0;
    1205     display: flex;
    1206     align-items: center;
    1207 }
    1208 
    1209 .frbl-feature-title {
    1210     font-size: 0.9375rem;
    1211     font-weight: 600;
    1212     color: #1f2937;
    1213     margin: 0;
    1214     line-height: 1.5;
    1215 }
    1216 
    1217 .frbl-feature-description {
    1218     font-size: 0.8125rem;
    1219     color: #6b7280;
    1220     margin: 0.25rem 0 0 0;
    1221     line-height: 1.4;
    1222 }
    1223 
    1224 /* Active Feature Cards */
    1225 .frbl-feature-card.frbl-feature-active {
    1226     background: linear-gradient(135deg, #ffffff 0%, #f0fdf4 100%);
    1227     border-color: #d1fae5;
    1228 }
    1229 
    1230 .frbl-feature-card.frbl-feature-active:hover {
    1231     border-color: #10b981;
    1232 }
    1233 
    1234 .frbl-feature-card.frbl-feature-active .frbl-feature-icon {
    1235     background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
    1236     color: #10b981;
    1237 }
    1238 
    1239 .frbl-feature-card.frbl-feature-active:hover .frbl-feature-icon {
    1240     background: linear-gradient(135deg, #a7f3d0 0%, #6ee7b7 100%);
    1241 }
    1242 
    1243 /* Active cards need vertical layout for description */
    1244 .frbl-feature-card.frbl-feature-active .frbl-feature-info {
    1245     flex-direction: column;
    1246     align-items: flex-start;
    1247 }
    1248 
    1249 /* Active cards don't have toggle, so don't use space-between */
    1250 .frbl-feature-card.frbl-feature-active .frbl-feature-content {
    1251     justify-content: flex-start;
    1252 }
    1253 
    1254 /* Feature Toggle */
    1255 .frbl-feature-toggle {
    1256     flex-shrink: 0;
    1257     display: flex;
    1258     align-items: center;
    1259 }
    1260 
    1261 /* PRO Card Styles */
    1262 .frbl-feature-card.frbl-feature-pro {
    1263     background: linear-gradient(135deg, #ffffff 0%, #fef3f2 100%);
    1264     border-color: #fee2e2;
    1265 }
    1266 
    1267 .frbl-feature-card.frbl-feature-pro:hover {
    1268     border-color: #ec4899;
    1269 }
    1270 
    1271 .frbl-feature-card.frbl-feature-pro .frbl-feature-icon {
    1272     background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
    1273     color: #ec4899;
    1274 }
    1275 
    1276 .frbl-feature-card.frbl-feature-pro:hover .frbl-feature-icon {
    1277     background: linear-gradient(135deg, #fecaca 0%, #fca5a5 100%);
    1278 }
    1279 
    1280 /* Disabled state for PRO features without license */
    1281 .frbl-feature-card.frbl-feature-pro .frbl-toggle:has(input:disabled) {
    1282     opacity: 0.6;
    1283 }
    1284 
    1285 /* Responsive adjustments */
    1286 @media (max-width: 768px) {
    1287     .frbl-features-grid {
    1288         grid-template-columns: 1fr;
    1289     }
    1290    
    1291     .frbl-feature-card {
    1292         padding: 1.25rem;
    1293     }
    1294 }
    1295 
    1296 @media (min-width: 768px) and (max-width: 1024px) {
    1297     .frbl-features-grid {
    1298         grid-template-columns: repeat(2, 1fr);
    1299     }
    1300 }
    1301 
    1302 
    1303 /* =============================================
    1304    License Management Styles (Complete & Independent)
    1305    Based on FormsCRM but standalone for FrontBlocks
    1306    ============================================= */
    1307 
    1308 /* License Wrapper - Grid Layout */
    1309 .formscrm-license-wrapper {
    1310     display: grid;
    1311     grid-template-columns: 1fr 320px;
    1312     gap: 24px;
    1313     max-width: 1200px;
    1314     margin: 20px 0;
    1315 }
    1316 
    1317 @media (max-width: 900px) {
    1318     .formscrm-license-wrapper {
    1319         grid-template-columns: 1fr;
    1320     }
    1321 }
    1322 
    1323 /* Main Card */
    1324 .formscrm-card {
    1325     background: #fff;
    1326     border: 1px solid #e5e7eb;
    1327     border-radius: 12px;
    1328     padding: 32px;
    1329     box-shadow: 0 1px 3px rgba(0,0,0,0.05);
    1330 }
    1331 
    1332 .formscrm-card-header {
    1333     margin-bottom: 24px;
    1334     padding-bottom: 20px;
    1335     border-bottom: 1px solid #e5e7eb;
    1336 }
    1337 
    1338 .formscrm-card-header h2 {
    1339     margin: 0 0 8px 0;
    1340     font-size: 1.5rem;
    1341     font-weight: 600;
    1342     color: #1f2937;
    1343 }
    1344 
    1345 .formscrm-card-header p {
    1346     margin: 0;
    1347     color: #6b7280;
    1348     font-size: 0.875rem;
    1349 }
    1350 
    1351 /* Form Elements */
    1352 .formscrm-form-group {
    1353     margin-bottom: 24px;
    1354 }
    1355 
    1356 .formscrm-label {
    1357     display: block;
    1358     font-weight: 600;
    1359     color: #374151;
    1360     margin-bottom: 8px;
    1361     font-size: 0.875rem;
    1362 }
    1363 
    1364 .formscrm-input-group {
    1365     display: flex;
    1366     gap: 12px;
    1367     align-items: center;
    1368 }
    1369 
    1370 .formscrm-input {
    1371     flex: 1;
    1372     padding: 12px 16px;
    1373     border: 1px solid #d1d5db;
    1374     border-radius: 8px;
    1375     font-size: 1rem;
    1376     transition: all 0.2s;
    1377     background: #fff;
    1378 }
    1379 
    1380 .formscrm-input:focus {
    1381     outline: none;
    1382     border-color: #8b5cf6;
    1383     box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
    1384 }
    1385 
    1386 .formscrm-input[readonly] {
    1387     background: #f9fafb;
    1388     color: #6b7280;
    1389     cursor: not-allowed;
    1390 }
    1391 
    1392 /* Deactivate Label */
    1393 .formscrm-deactivate-label {
    1394     display: flex;
    1395     align-items: center;
    1396     gap: 8px;
    1397     padding: 12px 16px;
    1398     background: #fef2f2;
    1399     border: 1px solid #fecaca;
    1400     border-radius: 8px;
    1401     cursor: pointer;
    1402     transition: background 0.2s;
    1403     white-space: nowrap;
    1404 }
    1405 
    1406 .formscrm-deactivate-label:hover {
    1407     background: #fee2e2;
    1408 }
    1409 
    1410 .formscrm-deactivate-label input {
    1411     margin: 0;
    1412 }
    1413 
    1414 .formscrm-deactivate-label span {
    1415     font-size: 0.875rem;
    1416     font-weight: 600;
    1417     color: #dc2626;
    1418 }
    1419 
    1420 /* Help Text */
    1421 .formscrm-help-text {
    1422     margin: 8px 0 0 0;
    1423     font-size: 0.75rem;
    1424     color: #9ca3af;
    1425 }
    1426 
    1427 /* Status Box */
    1428 .formscrm-status-box {
    1429     display: flex;
    1430     align-items: center;
    1431     gap: 12px;
    1432     padding: 14px 18px;
    1433     border-radius: 8px;
    1434     border: 2px solid;
    1435 }
    1436 
    1437 .formscrm-status-active {
    1438     background: #dcfce7;
    1439     border-color: #86efac;
    1440     color: #166534;
    1441 }
    1442 
    1443 .formscrm-status-expired {
    1444     background: #fee2e2;
    1445     border-color: #fca5a5;
    1446     color: #991b1b;
    1447 }
    1448 
    1449 .formscrm-status-inactive {
    1450     background: #fef9c3;
    1451     border-color: #fde047;
    1452     color: #854d0e;
    1453 }
    1454 
    1455 .formscrm-status-icon {
    1456     display: flex;
    1457     flex-shrink: 0;
    1458 }
    1459 
    1460 .formscrm-icon,
    1461 .fcod-icon {
    1462     width: 22px;
    1463     height: 22px;
    1464 }
    1465 
    1466 .formscrm-status-text {
    1467     font-weight: 700;
    1468     font-size: 1rem;
    1469 }
    1470 
    1471 /* Notices */
    1472 .formscrm-notice {
    1473     padding: 14px 18px;
    1474     border-radius: 8px;
    1475     margin-bottom: 20px;
    1476 }
    1477 
    1478 .formscrm-notice p {
    1479     margin: 0;
    1480     font-size: 0.875rem;
    1481     line-height: 1.5;
    1482 }
    1483 
    1484 .formscrm-notice a {
    1485     font-weight: 600;
    1486     text-decoration: underline;
    1487 }
    1488 
    1489 .formscrm-notice-info {
    1490     background: #f0f9ff;
    1491     border: 1px solid #bfdbfe;
    1492     color: #1e40af;
    1493 }
    1494 
    1495 .formscrm-notice-info a {
    1496     color: #1d4ed8;
    1497 }
    1498 
    1499 .formscrm-notice-error {
    1500     background: #fef2f2;
    1501     border: 1px solid #fecaca;
    1502     color: #991b1b;
    1503 }
    1504 
    1505 .formscrm-notice-error a {
    1506     color: #dc2626;
    1507 }
    1508 
    1509 /* Form Actions */
    1510 .formscrm-form-actions {
    1511     margin-top: 24px;
    1512     padding-top: 24px;
    1513     border-top: 1px solid #e5e7eb;
    1514 }
    1515 
    1516 /* Buttons - FrontBlocks Purple Style */
    1517 .formscrm-button {
    1518     display: inline-flex;
    1519     align-items: center;
    1520     padding: 12px 24px;
    1521     border-radius: 8px;
    1522     font-size: 1rem;
    1523     font-weight: 600;
    1524     cursor: pointer;
    1525     transition: all 0.2s;
    1526     border: none;
    1527 }
    1528 
    1529 .formscrm-button-primary {
    1530     background: #8b5cf6;
    1531     color: #fff;
    1532 }
    1533 
    1534 .formscrm-button-primary:hover {
    1535     background: #7c3aed;
    1536     box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
    1537 }
    1538 
    1539 /* Info Card (Sidebar) */
    1540 .formscrm-info-card {
    1541     background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
    1542     border: 1px solid #e2e8f0;
    1543     border-radius: 12px;
    1544     padding: 24px;
    1545     height: fit-content;
    1546 }
    1547 
    1548 .formscrm-info-card h3 {
    1549     margin: 0 0 12px 0;
    1550     font-size: 1.125rem;
    1551     font-weight: 700;
    1552     color: #1e293b;
    1553 }
    1554 
    1555 .formscrm-info-card p {
    1556     margin: 0 0 16px 0;
    1557     font-size: 0.875rem;
    1558     color: #64748b;
    1559     line-height: 1.5;
    1560 }
    1561 
    1562 /* Benefits List */
    1563 .formscrm-benefits-list {
    1564     margin: 0;
    1565     padding: 0;
    1566     list-style: none;
    1567 }
    1568 
    1569 .formscrm-benefits-list li {
    1570     position: relative;
    1571     padding-left: 28px;
    1572     margin-bottom: 10px;
    1573     font-size: 0.875rem;
    1574     color: #475569;
    1575 }
    1576 
    1577 .formscrm-benefits-list li::before {
    1578     content: "✓";
    1579     position: absolute;
    1580     left: 0;
    1581     top: 0;
    1582     color: #8b5cf6;
    1583     font-weight: 700;
    1584     font-size: 1.125rem;
    1585 }
  • frontblocks/tags/1.3.3/assets/carousel/frontblocks-advanced-option.js

    r3462660 r3472405  
    11"use strict";
    22
     3function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
     4function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
     5function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
    36// Add custom controls to the Advanced panel of GenerateBlocks Grid block
    47var addFilter = wp.hooks.addFilter;
    5 var Fragment = wp.element.Fragment;
     8var _wp$element = wp.element,
     9  Fragment = _wp$element.Fragment,
     10  useEffect = _wp$element.useEffect,
     11  useRef = _wp$element.useRef;
    612var _wp$blockEditor = wp.blockEditor,
    713  InspectorControls = _wp$blockEditor.InspectorControls,
     
    1319  ToggleControl = _wp$components.ToggleControl;
    1420var __ = wp.i18n.__;
     21
     22/**
     23 * Returns the document that renders block content.
     24 * WordPress 6.x uses an <iframe> for the editor canvas.
     25 */
     26function getEditorDocument() {
     27  var iframe = document.querySelector('iframe[name="editor-canvas"]');
     28  return iframe && iframe.contentDocument && iframe.contentDocument.body ? iframe.contentDocument : document;
     29}
     30var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-/;
     31
     32/**
     33 * Walk an element's subtree to find the container whose DIRECT children
     34 * are actual blocks (UUID data-block).  Stops at depth 4.
     35 */
     36function findSlidesParent(el, depth) {
     37  if (depth === 0) return null;
     38  if (Array.from(el.children).some(function (c) {
     39    return UUID_RE.test(c.dataset.block || '');
     40  })) {
     41    return el;
     42  }
     43  var _iterator = _createForOfIteratorHelper(el.children),
     44    _step;
     45  try {
     46    for (_iterator.s(); !(_step = _iterator.n()).done;) {
     47      var child = _step.value;
     48      var found = findSlidesParent(child, depth - 1);
     49      if (found) return found;
     50    }
     51  } catch (err) {
     52    _iterator.e(err);
     53  } finally {
     54    _iterator.f();
     55  }
     56  return null;
     57}
     58
     59/**
     60 * Find the grid DOM element for a given block.
     61 *
     62 * For generateblocks/element: [data-block][data-type] IS the gb-element.
     63 *   Do NOT search inside – that returns a child item.
     64 * For generateblocks/grid:  look for .gb-grid-wrapper inside.
     65 * For core/group: walk the subtree to find the element whose children
     66 *   are the real blocks, because WP nests them differently across versions.
     67 */
     68function findGridEl(blockEl, name) {
     69  if (name === 'generateblocks/element') {
     70    return blockEl;
     71  }
     72  if (name === 'generateblocks/grid') {
     73    if (blockEl.classList.contains('gb-grid-wrapper')) return blockEl;
     74    var grid = blockEl.querySelector('.gb-grid-wrapper');
     75    return grid || null;
     76  }
     77  if (name === 'core/group') {
     78    // Walk up to 4 levels deep to find where the child blocks live.
     79    return findSlidesParent(blockEl, 4) || blockEl;
     80  }
     81  return null;
     82}
    1583function addCustomCarouselPanel(BlockEdit) {
    1684  return function (props) {
    17     // Support grid blocks, element blocks with grid display, and core/group blocks
    1885    if (props.name !== 'generateblocks/grid' && props.name !== 'generateblocks/element' && props.name !== 'core/group') {
    1986      return /*#__PURE__*/React.createElement(BlockEdit, props);
    2087    }
    21 
    22     // For element blocks, only show carousel options if it has grid display
    2388    if (props.name === 'generateblocks/element') {
    2489      var styles = props.attributes.styles || {};
     
    2792      }
    2893    }
    29 
    30     // For core/group blocks, only show carousel options if it has grid layout
    3194    if (props.name === 'core/group') {
    3295      var layout = props.attributes.layout || {};
     
    48111      _props$attributes$frb6 = _props$attributes.frblAutoplay,
    49112      frblAutoplay = _props$attributes$frb6 === void 0 ? '' : _props$attributes$frb6,
    50       _props$attributes$frb7 = _props$attributes.frblButtons,
    51       frblButtons = _props$attributes$frb7 === void 0 ? 'arrows' : _props$attributes$frb7,
    52       _props$attributes$frb8 = _props$attributes.frblRewind,
    53       frblRewind = _props$attributes$frb8 === void 0 ? true : _props$attributes$frb8,
     113      _props$attributes$frb7 = _props$attributes.frblGap,
     114      frblGap = _props$attributes$frb7 === void 0 ? '20' : _props$attributes$frb7,
     115      _props$attributes$frb8 = _props$attributes.frblButtons,
     116      frblButtons = _props$attributes$frb8 === void 0 ? 'arrows' : _props$attributes$frb8,
     117      _props$attributes$frb9 = _props$attributes.frblRewind,
     118      frblRewind = _props$attributes$frb9 === void 0 ? true : _props$attributes$frb9,
    54119      frblButtonColor = _props$attributes.frblButtonColor,
    55120      frblButtonBgColor = _props$attributes.frblButtonBgColor,
    56       _props$attributes$frb9 = _props$attributes.frblButtonsPosition,
    57       frblButtonsPosition = _props$attributes$frb9 === void 0 ? 'side' : _props$attributes$frb9,
    58       _props$attributes$frb0 = _props$attributes.frblDisableOnDesktop,
    59       frblDisableOnDesktop = _props$attributes$frb0 === void 0 ? false : _props$attributes$frb0;
     121      _props$attributes$frb0 = _props$attributes.frblButtonsPosition,
     122      frblButtonsPosition = _props$attributes$frb0 === void 0 ? 'side' : _props$attributes$frb0,
     123      _props$attributes$frb1 = _props$attributes.frblDisableOnDesktop,
     124      frblDisableOnDesktop = _props$attributes$frb1 === void 0 ? false : _props$attributes$frb1;
     125
     126    // ── Editor carousel preview ──────────────────────────────────────────
     127    var stateRef = useRef(null);
     128    useEffect(function () {
     129      var mounted = true;
     130      var timer = null;
     131      function cleanup() {
     132        if (!stateRef.current) return;
     133        var _stateRef$current = stateRef.current,
     134          gridEl = _stateRef$current.gridEl,
     135          slides = _stateRef$current.slides,
     136          spacer = _stateRef$current.spacer,
     137          prevBtn = _stateRef$current.prevBtn,
     138          nextBtn = _stateRef$current.nextBtn,
     139          resizeObs = _stateRef$current.resizeObs,
     140          scrollFn = _stateRef$current.scrollFn;
     141
     142        // Disconnect observer and scroll listener.
     143        if (resizeObs) resizeObs.disconnect();
     144        var editorDoc = getEditorDocument();
     145        var editorScrollEl = editorDoc.documentElement || editorDoc.body;
     146        if (scrollFn) editorScrollEl.removeEventListener('scroll', scrollFn, true);
     147
     148        // Remove arrows from outer window body (completely outside React).
     149        [prevBtn, nextBtn].forEach(function (btn) {
     150          if (btn && btn.parentNode) btn.parentNode.removeChild(btn);
     151        });
     152
     153        // Remove right spacer element.
     154        if (spacer && spacer.parentNode) spacer.parentNode.removeChild(spacer);
     155
     156        // Restore gridEl styles.
     157        try {
     158          gridEl.classList.remove('frbl-carousel-noscrollbar');
     159          ['display', 'flex-wrap', 'gap', 'overflow-x', 'scroll-behavior', 'padding-left', 'padding-right'].forEach(function (p) {
     160            return gridEl.style.removeProperty(p);
     161          });
     162          slides.forEach(function (slide) {
     163            ['flex-shrink', 'width', 'min-width', 'margin-left', 'margin-right', 'grid-column'].forEach(function (p) {
     164              return slide.style.removeProperty(p);
     165            });
     166          });
     167        } catch (e) {}
     168        stateRef.current = null;
     169      }
     170      function init() {
     171        if (!mounted) return;
     172        cleanup();
     173        var editorDoc = getEditorDocument();
     174
     175        // GB 2.x wraps blocks in a root element that shares the same data-block UUID.
     176        // Use data-type to select the correct inner element; fall back for native blocks.
     177        var blockEl = editorDoc.querySelector("[data-block=\"".concat(props.clientId, "\"][data-type=\"").concat(props.name, "\"]"));
     178        if (!blockEl) {
     179          blockEl = editorDoc.querySelector("[data-block=\"".concat(props.clientId, "\"]"));
     180        }
     181        if (!blockEl) return;
     182        var gridEl = findGridEl(blockEl, props.name);
     183        if (!gridEl) return;
     184
     185        // Slides = direct children that are real blocks (UUID data-block).
     186        var slides = Array.from(gridEl.children).filter(function (el) {
     187          return UUID_RE.test(el.dataset.block || '');
     188        });
     189        if (slides.length === 0) return;
     190        var perView = Math.max(1, parseInt(frblItemsToView) || 4);
     191        var gap = Math.max(0, parseInt(frblGap) || 20);
     192        var btnColor = frblButtonColor || '#fff';
     193        var btnBg = frblButtonBgColor || 'rgba(0,0,0,0.45)';
     194
     195        // Measure content-box width before any style changes.
     196        var editorWin = editorDoc.defaultView || window;
     197        var ARROW_W = 40; // px reserved on each side for arrow buttons.
     198        var cs = editorWin.getComputedStyle(gridEl);
     199        var innerW = gridEl.getBoundingClientRect().width - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0);
     200        var totalWidth = Math.round(innerW) || 600;
     201        // Content area after reserving space for both arrow buttons.
     202        var contentW = totalWidth - ARROW_W * 2;
     203        var slideWidth = Math.round((contentW - gap * (perView - 1)) / perView);
     204        var step = slideWidth + gap;
     205
     206        // ── Apply scroll-based layout directly to gridEl ─────────────
     207        // No DOM restructuring → no React reconciliation conflicts.
     208        gridEl.classList.add('frbl-carousel-noscrollbar');
     209        gridEl.style.display = 'flex';
     210        gridEl.style.flexWrap = 'nowrap';
     211        gridEl.style.gap = gap + 'px';
     212        gridEl.style.overflowX = 'scroll';
     213        gridEl.style.scrollBehavior = 'smooth';
     214        // paddingLeft creates left space for the prev arrow.
     215        // paddingRight is NOT used because Chrome ignores it in scroll extent;
     216        // a spacer flex child is appended instead to guarantee right-side space.
     217        gridEl.style.paddingLeft = ARROW_W + 'px';
     218        gridEl.style.paddingRight = '0';
     219        slides.forEach(function (slide) {
     220          slide.style.flexShrink = '0';
     221          slide.style.width = slideWidth + 'px';
     222          slide.style.minWidth = slideWidth + 'px';
     223          slide.style.marginLeft = '0';
     224          slide.style.marginRight = '0';
     225          slide.style.gridColumn = 'unset';
     226        });
     227
     228        // Right-side spacer: Chrome does not include padding-right in horizontal
     229        // scroll extent, so we use a real flex child to guarantee right space.
     230        var appender = gridEl.querySelector('.block-list-appender');
     231        var spacer = editorDoc.createElement('span');
     232        spacer.setAttribute('data-frbl-spacer', '1');
     233        spacer.style.cssText = "display:block;flex-shrink:0;width:".concat(ARROW_W, "px;min-width:").concat(ARROW_W, "px;");
     234        gridEl.insertBefore(spacer, appender || null);
     235
     236        // ── Navigation (scrollLeft, infinite loop) ───────────────────
     237        var idx = 0;
     238        function scrollTo(n) {
     239          var total = slides.length;
     240          if (n >= total) {
     241            // Forward past last: instant snap to 0 then continue.
     242            gridEl.style.scrollBehavior = 'auto';
     243            gridEl.scrollLeft = 0;
     244            void gridEl.offsetWidth;
     245            gridEl.style.scrollBehavior = 'smooth';
     246            idx = 0;
     247            return;
     248          }
     249          if (n < 0) {
     250            // Back past first: instant snap to last.
     251            gridEl.style.scrollBehavior = 'auto';
     252            gridEl.scrollLeft = Math.round((total - 1) * step);
     253            void gridEl.offsetWidth;
     254            gridEl.style.scrollBehavior = 'smooth';
     255            idx = total - 1;
     256            return;
     257          }
     258          idx = n;
     259          gridEl.scrollLeft = Math.round(idx * step);
     260        }
     261
     262        // ── Arrows in OUTER document body ────────────────────────────
     263        // Placing them outside the iframe means React never touches them.
     264        var iframe = document.querySelector('iframe[name="editor-canvas"]');
     265        function updateArrowPos() {
     266          if (!stateRef.current) return;
     267          var ifrRect = iframe ? iframe.getBoundingClientRect() : {
     268            top: 0,
     269            left: 0
     270          };
     271          var rect = gridEl.getBoundingClientRect(); // coords in iframe viewport
     272          var midY = Math.round(ifrRect.top + rect.top + rect.height / 2 - 16);
     273          prevBtn.style.top = midY + 'px';
     274          prevBtn.style.left = Math.round(ifrRect.left + rect.left + 8) + 'px';
     275          nextBtn.style.top = midY + 'px';
     276          nextBtn.style.right = Math.round(window.innerWidth - (ifrRect.left + rect.right) + 8) + 'px';
     277        }
     278        function makeArrow(dir) {
     279          var btn = document.createElement('button');
     280          btn.type = 'button';
     281          btn.className = "frbl-editor-arrow frbl-editor-arrow-".concat(dir);
     282          btn.setAttribute('aria-label', dir === 'prev' ? 'Previous slide' : 'Next slide');
     283          btn.style.cssText = "position:fixed;z-index:99999;background-color:".concat(btnBg, ";");
     284          var d = dir === 'prev' ? 'M6 1L1 6L6 11' : 'M1 11L6 6L1 1';
     285          btn.innerHTML = "<svg width=\"7\" height=\"12\" viewBox=\"0 0 7 12\" fill=\"none\"><path d=\"".concat(d, "\" stroke=\"").concat(btnColor, "\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>");
     286          btn.addEventListener('click', function (e) {
     287            e.stopPropagation();
     288            scrollTo(dir === 'prev' ? idx - 1 : idx + 1);
     289          });
     290          document.body.appendChild(btn);
     291          return btn;
     292        }
     293        var prevBtn = makeArrow('prev');
     294        var nextBtn = makeArrow('next');
     295        updateArrowPos();
     296
     297        // Keep arrows positioned correctly when the editor scrolls or resizes.
     298        var resizeObs = new ResizeObserver(updateArrowPos);
     299        resizeObs.observe(gridEl);
     300        var editorScrollEl = editorDoc.documentElement || editorDoc.body;
     301        var scrollFn = updateArrowPos;
     302        editorScrollEl.addEventListener('scroll', scrollFn, {
     303          passive: true,
     304          capture: true
     305        });
     306        stateRef.current = {
     307          gridEl: gridEl,
     308          slides: slides,
     309          spacer: spacer,
     310          prevBtn: prevBtn,
     311          nextBtn: nextBtn,
     312          resizeObs: resizeObs,
     313          scrollFn: scrollFn
     314        };
     315      }
     316      if (frblGridOption !== 'none') {
     317        timer = setTimeout(init, 300);
     318      } else {
     319        cleanup();
     320      }
     321      return function () {
     322        mounted = false;
     323        if (timer) clearTimeout(timer);
     324        cleanup();
     325      };
     326    }, [frblGridOption, frblItemsToView, frblGap, frblButtonColor, frblButtonBgColor, props.clientId]);
     327    // ── Inspector panel ──────────────────────────────────────────────────
     328
    60329    return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(BlockEdit, props), /*#__PURE__*/React.createElement(InspectorControls, null, /*#__PURE__*/React.createElement(PanelBody, {
    61330      title: __('Carousel Settings', 'frontblocks'),
     
    75344      }],
    76345      onChange: function onChange(value) {
    77         props.setAttributes({
     346        return props.setAttributes({
    78347          frblGridOption: value
    79348        });
     
    124393        });
    125394      }
    126     }), frblGridOption === 'slider' && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ToggleControl, {
     395    }), /*#__PURE__*/React.createElement(TextControl, {
     396      label: __('Gap (px)', 'frontblocks'),
     397      value: frblGap,
     398      onChange: function onChange(value) {
     399        return props.setAttributes({
     400          frblGap: value
     401        });
     402      },
     403      help: __('Space between slides in pixels. Leave empty for 20.', 'frontblocks')
     404    }), frblGridOption === 'slider' && /*#__PURE__*/React.createElement(ToggleControl, {
    127405      label: __('Rewind', 'frontblocks'),
    128406      checked: frblRewind,
     
    132410        });
    133411      }
    134     })), /*#__PURE__*/React.createElement(SelectControl, {
     412    }), /*#__PURE__*/React.createElement(SelectControl, {
    135413      label: __('Buttons', 'frontblocks'),
    136414      value: frblButtons,
     
    150428        });
    151429      }
    152     }), frblButtons === 'arrows' && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SelectControl, {
     430    }), frblButtons === 'arrows' && /*#__PURE__*/React.createElement(SelectControl, {
    153431      label: __('Buttons Position', 'frontblocks'),
    154432      value: frblButtonsPosition,
     
    165443        });
    166444      }
    167     })), /*#__PURE__*/React.createElement(PanelColorSettings, {
     445    }), /*#__PURE__*/React.createElement(PanelColorSettings, {
    168446      title: __('Button Colors', 'frontblocks'),
    169447      colorSettings: [{
  • frontblocks/tags/1.3.3/assets/carousel/frontblocks-carousel.css

    r3462660 r3472405  
    11/**
    2 * ## Glide Carousel style
    3 * --------------------------- */
    4 
    5 .glide {
     2 * Frontblocks Carousel – scoped under .frontblocks so other Glide instances are not affected.
     3 */
     4
     5.frontblocks.glide {
    66  position: relative;
    77  width: 100%;
     8  max-width: 100%;
    89  box-sizing: border-box;
    9 }
    10 .glide * {
     10  overflow: visible;
     11  margin: 0;
     12  padding: 0;
     13}
     14
     15.frontblocks.glide * {
    1116  box-sizing: inherit;
    1217}
    13 .glide__track {
     18
     19.frontblocks .glide__track {
    1420  overflow: hidden;
    15 }
    16 .glide__slides {
     21  width: 100%;
     22  position: relative;
     23  left: 0;
     24  margin: 0;
     25  padding: 0;
     26}
     27
     28.frontblocks .glide__slides {
    1729  position: relative;
    1830  width: 100%;
     
    2840  flex-wrap: nowrap;
    2941  will-change: transform;
    30   transition: transform 400ms cubic-bezier(0.165, 0.84, 0.44, 1);
    31 }
    32 .glide__slides--dragging {
    33   user-select: none;
    34 }
    35 .glide__slide {
     42}
     43
     44.frontblocks .glide__slides--dragging {
     45  user-select: none;
     46}
     47
     48.frontblocks .glide__slide {
    3649  width: 100%;
    3750  height: 100%;
     
    4154  -webkit-touch-callout: none;
    4255  -webkit-tap-highlight-color: transparent;
    43 }
    44 .glide__slide a {
     56  min-width: 0;
     57  overflow: hidden;
     58}
     59
     60/* Prevent inner content (cards, AWB, etc.) from overflowing the slide and being cut off. */
     61.frontblocks .glide__slide > * {
     62  max-width: 100%;
     63  box-sizing: border-box;
     64}
     65
     66.frontblocks .glide__slide a {
    4567  user-select: none;
    4668  -webkit-user-drag: none;
     
    4870  -ms-user-select: none;
    4971}
    50 .glide__arrows {
     72
     73.frontblocks .glide__arrows {
    5174  -webkit-touch-callout: none;
    5275  user-select: none;
    5376}
    54 .glide__bullets {
     77
     78.frontblocks .glide__bullets {
    5579  -webkit-touch-callout: none;
    5680  user-select: none;
    5781}
    58 .glide--rtl {
     82
     83.frontblocks.glide--rtl {
    5984  direction: rtl;
    6085}
    6186
    62 /*# sourceMappingURL=glide.core.css.map */
    63 
    64 .glide__arrow {
     87.frontblocks .glide__arrow {
    6588  position: absolute;
    6689  display: block;
     
    79102  line-height: 1;
    80103}
    81 .glide__arrow:focus {
     104
     105.frontblocks .glide__arrow:focus {
    82106  outline: none;
    83107}
    84 .glide__arrow:hover {
     108
     109.frontblocks .glide__arrow:hover {
    85110  border-color: white;
    86111}
    87 .glide__arrow--left {
    88   left: -1em;
    89 }
    90 .glide__arrow--right {
    91   right: -1em;
    92 }
    93 .glide__arrow--disabled {
     112
     113.frontblocks .glide__arrow--left {
     114  left: 1em;
     115}
     116
     117.frontblocks .glide__arrow--right {
     118  right: 1em;
     119}
     120
     121.frontblocks .glide__arrow--disabled {
    94122  opacity: 0.33;
    95123}
    96 .glide__bullets {
    97   position: absolute;
    98   z-index: 2;
    99   bottom: -1em;
    100   left: 50%;
    101   display: inline-flex;
     124
     125.frontblocks .glide__bullets {
     126  display: flex;
     127  justify-content: center;
    102128  list-style: none;
    103   transform: translateX(-50%);
    104   gap: 5px;
    105 }
    106 .glide__bullet {
    107   background-color: rgba(255, 255, 255, 0.5);
    108   width: 13px;
    109   height: 13px;
     129  gap: 2px;
     130  margin: 0.75em 0 0;
     131  padding: 0;
     132}
     133
     134.frontblocks .glide__bullet {
     135  box-sizing: content-box;
     136  width: 10px;
     137  height: 10px;
    110138  padding: 0;
    111139  border-radius: 50%;
    112   border: 2px solid transparent;
    113   transition: all 300ms ease-in-out;
     140  border: none;
     141  background-color: var(--frbl-bullet-bg, rgba(0, 0, 0, 0.25));
    114142  cursor: pointer;
    115   line-height: 0;
    116   margin: 0;
    117 }
    118 .glide__bullet:focus {
    119   outline: none;
    120 }
    121 .glide__bullet:hover, .glide__bullet:focus {
    122   border: 2px solid white;
    123   background-color: rgba(255, 255, 255, 0.5);
    124 }
    125 .glide__bullet--active {
    126   background-color: white;
    127 }
    128 .glide--swipeable {
     143  transition: background-color 300ms ease-in-out, transform 300ms ease-in-out;
     144  margin: 0 2px;
     145  flex-shrink: 0;
     146}
     147
     148.frontblocks .glide__bullet:focus {
     149  outline: 2px solid var(--frbl-bullet-color, rgba(0, 0, 0, 0.8));
     150  outline-offset: 2px;
     151}
     152
     153.frontblocks .glide__bullet:hover {
     154  background-color: var(--frbl-bullet-color, rgba(0, 0, 0, 0.8));
     155  transform: scale(1.2);
     156}
     157
     158.frontblocks .glide__bullet--active {
     159  background-color: var(--frbl-bullet-color, rgba(0, 0, 0, 0.8));
     160  transform: scale(1.2);
     161}
     162
     163.frontblocks.glide--swipeable {
    129164  cursor: grab;
    130165  cursor: -moz-grab;
    131166  cursor: -webkit-grab;
    132167}
    133 .glide--dragging {
     168
     169.frontblocks.glide--dragging {
    134170  cursor: grabbing;
    135171  cursor: -moz-grabbing;
    136172  cursor: -webkit-grabbing;
    137173}
    138 .glide__arrows--bottom .glide__arrow {
    139     top: calc(100% + 25px);
    140     left: 0;
    141 }
    142 .glide__arrows--bottom .glide__arrow--right {
    143     left: 50px;
    144     right: unset;
    145 }
    146 /* Arrows position top/side - spans full container width */
    147 .glide__arrows--top {
    148     position: absolute;
    149     top: 50%;
    150     left: 0;
    151     right: 0;
    152     width: 100%;
    153     z-index: 10;
    154     pointer-events: none;
    155     transform: translateY(-50%);
    156 }
    157 .glide__arrows--top .glide__arrow {
    158     pointer-events: all;
    159 }
    160 .glide__arrows--top .glide__arrow--left {
    161     left: 2em;
    162 }
    163 .glide__arrows--top .glide__arrow--right {
    164     right: 2em;
    165 }
    166 /* Responsive */
     174
     175.frontblocks .glide__arrows--bottom .glide__arrow {
     176  top: calc(100% + 25px);
     177  left: 0;
     178}
     179
     180.frontblocks .glide__arrows--bottom .glide__arrow--right {
     181  left: 50px;
     182  right: unset;
     183}
     184
     185.frontblocks .glide__arrows--top {
     186  position: absolute;
     187  top: 50%;
     188  left: 0;
     189  right: 0;
     190  width: 100%;
     191  z-index: 10;
     192  pointer-events: none;
     193  transform: translateY(-50%);
     194}
     195
     196.frontblocks .glide__arrows--top .glide__arrow {
     197  pointer-events: all;
     198}
     199
     200.frontblocks .glide__arrows--top .glide__arrow--left {
     201  left: 2em;
     202}
     203
     204.frontblocks .glide__arrows--top .glide__arrow--right {
     205  right: 2em;
     206}
     207
    167208@media only screen and (max-width: 768px) {
    168     .glide__arrow--left {
    169         left: 0;
    170     }
    171     .glide__arrow--right {
    172         right: 0;
    173     }
    174 }
    175 
    176 /*# sourceMappingURL=glide.theme.css.map */
     209  .frontblocks .glide__arrow--left {
     210    left: 0.5em;
     211  }
     212  .frontblocks .glide__arrow--right {
     213    right: 0.5em;
     214  }
     215}
    177216
    178217/**
    179  * Override native Gutenberg Grid styles when carousel is active
     218 * Override native Gutenberg Grid styles when carousel is active (scoped to frontblocks carousel).
    180219 */
    181220.wp-block-group.frontblocks-carousel,
    182221.wp-block-group.frontblocks-carousel.is-layout-grid {
    183     display: block;
    184     grid-template-columns: none;
    185     gap: 0;
     222  display: block;
     223  grid-template-columns: none;
     224  gap: 0;
     225}
     226
     227.wp-block-group.frontblocks-carousel .frontblocks.glide,
     228.wp-block-group.frontblocks-carousel .frontblocks .glide__track {
     229  width: 100%;
     230  max-width: 100%;
     231  margin-left: 0;
     232  margin-right: 0;
     233  padding-left: 0;
     234  padding-right: 0;
    186235}
    187236
    188237.wp-block-group.frontblocks-carousel > * {
    189     width: 100%;
    190 }
    191 
    192 /* Ensure inner container doesn't interfere with carousel */
     238  width: 100%;
     239}
     240
    193241.wp-block-group.frontblocks-carousel > .wp-block-group__inner-container {
    194     display: block;
    195     grid-template-columns: none;
    196     gap: 0;
    197     width: 100%;
    198 }
    199 
    200 /* Override grid styles for direct children in carousel mode */
    201 .glide__slides.wp-block-group,
    202 .glide__slides.wp-block-group.is-layout-grid {
    203     display: flex;
    204     grid-template-columns: none;
    205     gap: 0;
    206     column-gap: 0;
    207     row-gap: 0;
    208 }
    209 
    210 /* Ensure slides have proper width */
    211 .glide__slides > .glide__slide {
    212     min-width: 0;
    213     flex-shrink: 0;
    214 }
    215 
    216 /* Force full width for single slide view */
    217 .frontblocks-carousel[data-view="1"] .glide__slide {
    218     margin-left: 0;
    219     margin-right: 0;
    220 }
    221 
    222 /* Remove gaps when showing one slide */
    223 .frontblocks-carousel[data-view="1"].glide__slides {
    224     gap: 0;
    225 }
    226 
    227 /* Responsive single slide view */
    228 @media only screen and (max-width: 768px) {
    229     .frontblocks-carousel[data-mobile-view="1"] .glide__slide {
    230         width: 100%;
    231         flex: 0 0 100%;
    232         max-width: 100%;
    233         margin-left: 0;
    234         margin-right: 0;
    235     }
    236 }
    237 
    238 @media only screen and (min-width: 769px) and (max-width: 1024px) {
    239     .frontblocks-carousel[data-tablet-view="1"] .glide__slide {
    240         width: 100%;
    241         flex: 0 0 100%;
    242         max-width: 100%;
    243         margin-left: 0;
    244         margin-right: 0;
    245     }
    246 }
    247 
    248 @media only screen and (min-width: 1025px) and (max-width: 1440px) {
    249     .frontblocks-carousel[data-laptop-view="1"] .glide__slide {
    250         width: 100%;
    251         flex: 0 0 100%;
    252         max-width: 100%;
    253         margin-left: 0;
    254         margin-right: 0;
    255     }
    256 }
    257 
    258 /* Handle alignfull content within carousel slides */
    259 .glide__slide > .wp-block-cover.alignfull,
    260 .glide__slide > .alignfull {
    261     width: 100%;
    262     max-width: none;
    263     margin-left: 0;
    264     margin-right: 0;
    265 }
    266 
    267 /* Ensure cover blocks maintain minimum height */
    268 .glide__slide .wp-block-cover {
    269     min-height: 430px;
    270     display: flex;
    271     align-items: center;
    272     justify-content: center;
    273 }
    274 
    275 /* Prevent carousel from overflowing viewport */
    276 .glide {
    277     position: relative;
    278     overflow: visible;
    279     min-height: 430px;
    280 }
    281 
    282 .glide__track {
    283     overflow: hidden;
    284     width: 100%;
    285     position: relative;
    286     z-index: 1;
    287 }
    288 
    289 .glide__slides {
    290     position: relative;
    291     z-index: 1;
    292     height: auto;
    293     min-height: 430px;
    294 }
    295 
    296 .glide__slide {
    297     position: relative;
    298     z-index: 1;
    299     height: auto;
    300     opacity: 1;
    301     visibility: visible;
    302 }
     242  display: block;
     243  grid-template-columns: none;
     244  gap: 0;
     245  width: 100%;
     246}
     247
     248.frontblocks .glide__slides.wp-block-group,
     249.frontblocks .glide__slides.wp-block-group.is-layout-grid {
     250  display: flex;
     251  grid-template-columns: none;
     252  gap: 0;
     253  column-gap: 0;
     254  row-gap: 0;
     255}
     256
     257.frontblocks .glide__slides > .glide__slide {
     258  min-width: 0;
     259  flex-shrink: 0;
     260}
     261
     262/**
     263 * GenerateBlocks (and any other plugin) may inject column-gap via generated
     264 * utility classes on the carousel slides container.  Glide manages its own
     265 * spacing through margin-right on each slide, so any external column-gap
     266 * breaks the width calculation and must be zeroed out.
     267 */
     268.frontblocks-carousel,
     269.frontblocks .glide__slides {
     270  column-gap: 0 !important;
     271  gap: 0 !important;
     272}
  • frontblocks/tags/1.3.3/assets/carousel/frontblocks-carousel.js

    r3462660 r3472405  
    2929            parentwrap.replaceChild(wrapperParent, wrapper);
    3030            wrapperParent.appendChild(wrapper);
    31             wrapperParent.classList.add('glide');
     31            wrapperParent.classList.add('frontblocks', 'glide');
    3232
    3333            // Options
     
    3838            const carouselTabletView = item.getAttribute('data-tablet-view') ? parseInt(item.getAttribute('data-tablet-view')) : 2;
    3939            const carouselMobileView = item.getAttribute('data-mobile-view') ? parseInt(item.getAttribute('data-mobile-view')) : 1;
    40             const autoplayValue = item.getAttribute('data-autoplay');
    41             const carouselAutoplay = autoplayValue && autoplayValue !== '' ? parseInt(autoplayValue) : 0;
     40            const autoplayAttr = item.getAttribute('data-autoplay');
     41            const carouselAutoplay = (autoplayAttr !== '' && autoplayAttr !== null && autoplayAttr !== undefined) ? parseInt(autoplayAttr, 10) : 0;
     42            const carouselGap = item.getAttribute('data-gap') ? parseInt(item.getAttribute('data-gap'), 10) : 20;
    4243            const carouselRewind = item.getAttribute('data-rewind') ? item.getAttribute('data-rewind') : false;
    4344            const carouselbuttonsColor = item.getAttribute('data-buttons-color') ? item.getAttribute('data-buttons-color') : 'black';
     
    6465                bullets.classList.add('glide__bullets');
    6566                bullets.setAttribute('data-glide-el', 'controls[nav]');
     67                bullets.setAttribute('role', 'group');
     68                bullets.setAttribute('aria-label', 'Slide navigation');
    6669
    6770                for (let i = 0; i < item.children.length; i++) {
     
    7073                    bullet.setAttribute('data-glide-dir', '=' + i);
    7174                    bullet.setAttribute('aria-label', 'Go to slide ' + (i + 1));
    72                     bullet.style.backgroundColor = carouselbuttonsBackgroundColor;
    7375                    bullets.appendChild(bullet);
    7476                }
     
    7678                wrapperParent.appendChild(bullets);
    7779
    78                 // Add custom CSS for active bullet color
    79                 const style = document.createElement('style');
    80                 style.textContent = `
    81                     .glide__bullet.glide__bullet--active {
    82                         background-color: ${carouselbuttonsColor} !important;
    83                     }
    84                 `;
    85                 document.head.appendChild(style);
     80                // Set bullet colors via CSS custom properties on the wrapper.
     81                // This avoids specificity conflicts with the stylesheet.
     82                if (carouselbuttonsColor) {
     83                    wrapperParent.style.setProperty('--frbl-bullet-color', carouselbuttonsColor);
     84                }
     85                if (carouselbuttonsBackgroundColor && carouselbuttonsBackgroundColor !== 'transparent') {
     86                    wrapperParent.style.setProperty('--frbl-bullet-bg', carouselbuttonsBackgroundColor);
     87                }
    8688            }
    8789
     
    110112                wrapperParent.appendChild(arrows);
    111113            }
    112             // Calculate gap based on perView - use 0 gap when showing 1 slide
    113             const calculateGap = (view) => view === 1 ? 0 : 20;
    114            
    115114            const glideFrontBlocks = new Glide(wrapperParent, {
    116115                type: carouselType,
    117116                perView: carouselView,
    118117                startAt: 0,
    119                 autoplay: carouselAutoplay === 0 ? false : carouselAutoplay,
    120                 gap: calculateGap(carouselView),
     118                autoplay: carouselAutoplay > 0 ? carouselAutoplay : false,
     119                gap: isNaN(carouselGap) ? 20 : carouselGap,
    121120                rewind: carouselRewind,
    122121                breakpoints: {
    123122                    768: {
    124                         perView: carouselMobileView,
    125                         gap: calculateGap(carouselMobileView)
     123                        perView: carouselMobileView
    126124                    },
    127125                    1024: {
    128                         perView: carouselTabletView,
    129                         gap: calculateGap(carouselTabletView)
     126                        perView: carouselTabletView
    130127                    },
    131128                    1440: {
    132                         perView: carouselLaptopView,
    133                         gap: calculateGap(carouselLaptopView)
     129                        perView: carouselLaptopView
    134130                    }
    135131                }
  • frontblocks/tags/1.3.3/assets/gravityforms-inline/frontblocks-gf-inline.css

    r3402582 r3472405  
    174174}
    175175
     176/* -------------------------------------------------------------------------
     177   GenerateBlocks Accordion + Gravity Form: remove blank space when closed
     178   When a Gravity Form is inside a GB accordion and the accordion is above
     179   the footer, the closed content can still reserve space. Force it to none.
     180   ------------------------------------------------------------------------- */
     181.gb-accordion__item:not(.gb-accordion__item-open):not([data-accordion-is-open="true"]) > .gb-accordion__content {
     182    display: none;
     183    overflow: hidden;
     184    max-height: 0;
     185    min-height: 0 ;
     186    margin: 0 ;
     187    padding: 0 ;
     188    visibility: hidden;
     189}
     190
     191/* Prevent Gravity Form wrapper from forcing height when inside closed accordion. */
     192.gb-accordion__item:not(.gb-accordion__item-open):not([data-accordion-is-open="true"]) > .gb-accordion__content .gform_wrapper {
     193    min-height: 0;
     194    height: 0;
     195    overflow: hidden;
     196    visibility: hidden;
     197}
     198
  • frontblocks/tags/1.3.3/assets/shape-animations/frontblocks-shape-animation-option.js

    r3409365 r3472405  
    1919    PanelBody = _wp$components.PanelBody,
    2020    ToggleControl = _wp$components.ToggleControl,
     21    TextareaControl = _wp$components.TextareaControl,
    2122    Button = _wp$components.Button,
    22     Notice = _wp$components.Notice,
    23     FormFileUpload = _wp$components.FormFileUpload;
     23    Notice = _wp$components.Notice;
    2424
    2525  /**
     
    4646        jsonPreview = _useState4[0],
    4747        setJsonPreview = _useState4[1];
    48       var _useState5 = useState(''),
    49         _useState6 = _slicedToArray(_useState5, 2),
    50         fileName = _useState6[0],
    51         setFileName = _useState6[1];
    52       var _useState7 = useState(0),
    53         _useState8 = _slicedToArray(_useState7, 2),
    54         fileInputKey = _useState8[0],
    55         setFileInputKey = _useState8[1];
    5648
    5749      // Detect if JSON is Lottie format.
     
    10193      };
    10294
    103       // Handle file upload.
    104       var handleFileUpload = function handleFileUpload(event) {
    105         var file = event.target.files[0];
    106         if (!file) {
    107           return;
    108         }
    109        
    110         // Check if it's a JSON file.
    111         if (!file.name.endsWith('.json')) {
    112           setJsonError(__('Please select a JSON file', 'frontblocks'));
    113           setFileInputKey(function(prev) { return prev + 1; });
    114           return;
    115         }
    116        
    117         setFileName(file.name);
    118        
    119         // Read file content.
    120         var reader = new FileReader();
    121         reader.onload = function(e) {
    122           var content = e.target.result;
    123           setAttributes({ frblCustomSvgAnimationJson: content });
    124           validateJson(content);
    125           setFileInputKey(function(prev) { return prev + 1; });
    126         };
    127         reader.onerror = function() {
    128           setJsonError(__('Error reading file', 'frontblocks'));
    129           setFileInputKey(function(prev) { return prev + 1; });
    130         };
    131         reader.readAsText(file);
     95      // Handle JSON change.
     96      var handleJsonChange = function handleJsonChange(value) {
     97        setAttributes({
     98          frblCustomSvgAnimationJson: value
     99        });
     100        if (value.trim()) {
     101          validateJson(value);
     102        } else {
     103          setJsonError('');
     104          setJsonPreview(null);
     105        }
    132106      };
    133      
    134       // Clear imported JSON.
    135       var handleClear = function handleClear() {
    136         setAttributes({ frblCustomSvgAnimationJson: '' });
    137         setJsonError('');
    138         setJsonPreview(null);
    139         setFileName('');
    140         setFileInputKey(function(prev) { return prev + 1; });
    141       };
    142      
     107
    143108      // Example JSON template.
    144109      var exampleJson = JSON.stringify({
     
    153118        }
    154119      }, null, 2);
    155      
    156       // Download example JSON.
    157       var handleDownloadExample = function handleDownloadExample() {
    158         var blob = new Blob([exampleJson], { type: 'application/json' });
    159         var url = URL.createObjectURL(blob);
    160         var a = document.createElement('a');
    161         a.href = url;
    162         a.download = 'example-animation.json';
    163         document.body.appendChild(a);
    164         a.click();
    165         document.body.removeChild(a);
    166         URL.revokeObjectURL(url);
    167       };
    168      
    169120      return wp.element.createElement(Fragment, {}, wp.element.createElement(BlockEdit, props), wp.element.createElement(InspectorControls, {}, wp.element.createElement(PanelBody, {
    170121        title: __('FrontBlocks Custom SVG Animation', 'frontblocks'),
     
    182133        style: {
    183134          fontSize: '12px',
    184           marginBottom: '12px',
     135          marginBottom: '8px',
    185136          color: '#757575'
    186137        }
    187       }, __('Import a JSON file with your animation configuration:', 'frontblocks')), wp.element.createElement(FormFileUpload, {
    188         key: fileInputKey,
    189         accept: '.json',
    190         onChange: handleFileUpload,
    191         render: function(ref) {
    192           return wp.element.createElement(Button, {
    193             isSecondary: true,
    194             onClick: ref.openFileDialog,
    195             style: { marginBottom: '8px', width: '100%' }
    196           }, fileName ? __('Change JSON file', 'frontblocks') : __('Import JSON file', 'frontblocks'));
    197         }
    198       }), fileName && wp.element.createElement('div', {
    199         style: {
    200           display: 'flex',
    201           alignItems: 'center',
    202           justifyContent: 'space-between',
    203           marginBottom: '12px',
    204           padding: '8px',
    205           background: '#f6f7f7',
    206           borderRadius: '4px',
    207           fontSize: '12px'
    208         }
    209       }, wp.element.createElement('span', {}, '📄 ' + fileName), wp.element.createElement(Button, {
    210         isSmall: true,
    211         isDestructive: true,
    212         onClick: handleClear
    213       }, __('Clear', 'frontblocks'))), jsonError && wp.element.createElement(Notice, {
     138      }, __('Paste your JSON configuration below:', 'frontblocks')), wp.element.createElement(TextareaControl, {
     139        label: __('JSON Configuration', 'frontblocks'),
     140        value: frblCustomSvgAnimationJson,
     141        onChange: handleJsonChange,
     142        rows: 10,
     143        help: __('JSON with svg and animation properties', 'frontblocks')
     144      }), jsonError && wp.element.createElement(Notice, {
    214145        status: 'error',
    215146        isDismissible: false
     
    228159          marginBottom: '8px'
    229160        }
    230       }, __('📋 Download example JSON', 'frontblocks')), wp.element.createElement('pre', {
     161      }, __('📋 Show example JSON', 'frontblocks')), wp.element.createElement('pre', {
    231162        style: {
    232163          background: '#f6f7f7',
     
    240171        isSecondary: true,
    241172        isSmall: true,
    242         onClick: handleDownloadExample,
    243         style: {
    244           marginTop: '8px',
    245           marginRight: '8px'
    246         }
    247       }, __('Download example', 'frontblocks')), wp.element.createElement(Button, {
    248         isSecondary: true,
    249         isSmall: true,
    250173        onClick: function onClick() {
    251174          setAttributes({
    252175            frblCustomSvgAnimationJson: exampleJson
    253176          });
    254           setFileName('example-animation.json');
    255177          validateJson(exampleJson);
    256           setFileInputKey(function(prev) { return prev + 1; });
    257178        },
    258179        style: {
  • frontblocks/tags/1.3.3/assets/stacked-images/frontblocks-stacked-images-frontend.js

    r3462660 r3472405  
    7878                    entries.forEach(entry => {
    7979                        if (entry.isIntersecting) {
     80                            container.classList.add('frbl-animated');
    8081                            animateImages(images, duration, delay);
    8182                            observer.unobserve(entry.target);
     
    8485                },
    8586                {
    86                     threshold: 0.2,
    87                     rootMargin: '0px',
     87                    threshold: 0.05,
     88                    rootMargin: '50px 0px 50px 0px',
    8889                }
    8990            );
    9091
    9192            observer.observe(container);
     93
     94            // Fallback: if observer never fires (e.g. layout/height issue on mobile), animate after a short delay.
     95            setTimeout(() => {
     96                if (!container.classList.contains('frbl-animated')) {
     97                    container.classList.add('frbl-animated');
     98                    animateImages(images, duration, delay);
     99                }
     100            }, 800);
    92101        });
    93102    }
  • frontblocks/tags/1.3.3/assets/stacked-images/frontblocks-stacked-images.css

    r3462660 r3472405  
    33 */
    44
    5 /* Wrapper */
     5/* Wrapper – centered, compact size on all viewports */
    66.frbl-stacked-images-wrapper {
    77    position: relative;
    88    width: 100%;
     9    max-width: 520px;
     10    max-height: 65vh;
     11    margin-left: auto;
     12    margin-right: auto;
    913    overflow: visible;
    1014    display: block;
    1115    background: transparent;
    12     padding: 50px;
    13     margin: -50px;
     16    padding: 40px;
     17    box-sizing: border-box;
    1418}
    1519
    16 /* Container */
     20/* Container – center content horizontally and vertically */
    1721.frbl-stacked-images-container {
    1822    position: relative;
     
    2327    justify-content: center;
    2428    overflow: visible;
     29    margin: 0 auto;
    2530}
    2631
    27 /* Individual image */
     32/* Individual image – flex center so img is always centered */
    2833.frbl-stacked-image {
    2934    position: absolute;
    3035    top: 0;
    3136    left: 0;
     37    right: 0;
     38    bottom: 0;
    3239    width: 100%;
    3340    height: 100%;
     
    3946    visibility: hidden;
    4047    overflow: visible;
     48    margin: auto;
    4149}
    4250
     
    4755
    4856.frbl-stacked-image img {
    49     max-width: 100%;
    50     max-height: 100%;
     57    max-width: 85%;
     58    max-height: 85%;
    5159    width: auto;
    5260    height: auto;
    5361    object-fit: contain;
     62    object-position: center;
    5463    display: block;
     64    margin: auto;
    5565    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    5666}
     
    99109}
    100110
    101 /* Responsive */
     111/* Tablet / ventana media: aquí suele verse enorme, lo limitamos mucho */
     112@media (min-width: 769px) and (max-width: 1200px) {
     113    .frbl-stacked-images-wrapper {
     114        max-width: 420px;
     115        max-height: 55vh;
     116        padding: 24px;
     117    }
     118    .frbl-stacked-image img {
     119        max-width: 82%;
     120        max-height: 82%;
     121    }
     122}
     123
     124/* Mobile */
    102125@media (max-width: 768px) {
    103126    .frbl-stacked-images-wrapper {
    104         height: auto !important;
    105         min-height: 300px;
     127        min-height: 260px;
     128        max-height: 60vh;
     129        max-width: 88%;
     130        margin-left: auto;
     131        margin-right: auto;
     132        padding: 20px;
     133    }
     134    .frbl-stacked-images-container {
     135        min-height: 260px;
     136        margin: 0 auto;
     137    }
     138    .frbl-stacked-image img {
     139        max-width: 85%;
     140        max-height: 55vh;
     141        object-position: center;
     142        margin: auto;
    106143    }
    107144}
  • frontblocks/tags/1.3.3/frontblocks.php

    r3462660 r3472405  
    11<?php
    22/**
    3  * Plugin Name: FrontBlocks for GeneratePress
     3 * Plugin Name: FrontBlocks for Gutenberg/GeneratePress
    44 * Plugin URI:  https://wordpress.org/plugins/frontblocks/
    5  * Description: Blocks and helpers that extends GeneratePress blocks.
    6  * Version:     1.3.2
     5 * Description: Blocks and helpers that extends Gutenberg and GeneratePress blocks.
     6 * Version:     1.3.3
    77 * Author:      Closemarketing
    88 * Author URI:  https://close.marketing
     
    2727defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
    2828
    29 define( 'FRBL_VERSION', '1.3.2' );
     29define( 'FRBL_VERSION', '1.3.3' );
    3030define( 'FRBL_PLUGIN', __FILE__ );
    3131define( 'FRBL_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
  • frontblocks/tags/1.3.3/includes/Frontend/Carousel.php

    r3409365 r3472405  
    3434    private function init_hooks() {
    3535        add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
     36        add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_canvas_assets' ) );
    3637        add_filter( 'render_block_generateblocks/grid', array( $this, 'add_custom_attributes_to_grid_block' ), 10, 2 );
    3738        add_filter( 'render_block_generateblocks/element', array( $this, 'add_custom_attributes_to_element_block' ), 10, 2 );
    3839        add_filter( 'render_block_core/group', array( $this, 'add_custom_attributes_to_core_group_block' ), 10, 2 );
    3940        add_action( 'init', array( $this, 'register_custom_attributes' ), 5 );
     41    }
     42
     43    /**
     44     * Enqueue carousel CSS in the editor canvas (iframe).
     45     *
     46     * @return void
     47     */
     48    public function enqueue_block_canvas_assets() {
     49        if ( ! is_admin() ) {
     50            return;
     51        }
     52        wp_enqueue_style(
     53            'frontblocks-carousel-editor',
     54            FRBL_PLUGIN_URL . 'assets/carousel/frontblocks-carousel-editor.css',
     55            array(),
     56            FRBL_VERSION
     57        );
    4058    }
    4159
     
    7694        $responsive_to_view = isset( $attrs['frblResponsiveToView'] ) ? (int) $attrs['frblResponsiveToView'] : 1;
    7795        $autoplay           = isset( $attrs['frblAutoplay'] ) ? ( (int) $attrs['frblAutoplay'] * 1000 ) : '';
     96        $gap                = isset( $attrs['frblGap'] ) && '' !== $attrs['frblGap'] ? (int) $attrs['frblGap'] : 20;
    7897        $rewind             = isset( $attrs['frblRewind'] ) ? (bool) $attrs['frblRewind'] : true;
    7998        $buttons            = isset( $attrs['frblButtons'] ) ? sanitize_text_field( $attrs['frblButtons'] ) : 'arrows';
     
    100119                    ' data-mobile-view="' . esc_attr( $responsive_to_view ) . '"' .
    101120                    ' data-autoplay="' . esc_attr( $autoplay ) . '"' .
     121                    ' data-gap="' . esc_attr( $gap ) . '"' .
    102122                    ' data-buttons="' . esc_attr( $buttons ) . '"' .
    103123                    ' data-buttons-color="' . esc_attr( $button_color ) . '"' .
     
    140160        $responsive_to_view = isset( $attrs['frblResponsiveToView'] ) ? (int) $attrs['frblResponsiveToView'] : 1;
    141161        $autoplay           = isset( $attrs['frblAutoplay'] ) ? ( (int) $attrs['frblAutoplay'] * 1000 ) : '';
     162        $gap                = isset( $attrs['frblGap'] ) && '' !== $attrs['frblGap'] ? (int) $attrs['frblGap'] : 20;
    142163        $rewind             = isset( $attrs['frblRewind'] ) ? (bool) $attrs['frblRewind'] : true;
    143164        $buttons            = isset( $attrs['frblButtons'] ) ? sanitize_text_field( $attrs['frblButtons'] ) : 'arrows';
     
    164185                    ' data-mobile-view="' . esc_attr( $responsive_to_view ) . '"' .
    165186                    ' data-autoplay="' . esc_attr( $autoplay ) . '"' .
     187                    ' data-gap="' . esc_attr( $gap ) . '"' .
    166188                    ' data-buttons="' . esc_attr( $buttons ) . '"' .
    167189                    ' data-buttons-color="' . esc_attr( $button_color ) . '"' .
     
    204226        $responsive_to_view = isset( $attrs['frblResponsiveToView'] ) ? (int) $attrs['frblResponsiveToView'] : 1;
    205227        $autoplay           = isset( $attrs['frblAutoplay'] ) ? ( (int) $attrs['frblAutoplay'] * 1000 ) : '';
     228        $gap                = isset( $attrs['frblGap'] ) && '' !== $attrs['frblGap'] ? (int) $attrs['frblGap'] : 20;
    206229        $rewind             = isset( $attrs['frblRewind'] ) ? (bool) $attrs['frblRewind'] : true;
    207230        $buttons            = isset( $attrs['frblButtons'] ) ? sanitize_text_field( $attrs['frblButtons'] ) : 'arrows';
     
    228251                    ' data-mobile-view="' . esc_attr( $responsive_to_view ) . '"' .
    229252                    ' data-autoplay="' . esc_attr( $autoplay ) . '"' .
     253                    ' data-gap="' . esc_attr( $gap ) . '"' .
    230254                    ' data-buttons="' . esc_attr( $buttons ) . '"' .
    231255                    ' data-buttons-color="' . esc_attr( $button_color ) . '"' .
     
    299323            'type'    => 'string',
    300324            'default' => '',
     325        );
     326        $block_args['attributes']['frblGap']              = array(
     327            'type'    => 'string',
     328            'default' => '20',
    301329        );
    302330        $block_args['attributes']['frblRewind']           = array(
     
    371399                            default: ''
    372400                        },
     401                        frblGap: {
     402                            type: 'string',
     403                            default: '20'
     404                        },
    373405                        frblButtons: {
    374406                            type: 'string',
  • frontblocks/tags/1.3.3/readme.txt

    r3462660 r3472405  
    1 === FrontBlocks for Gutenberg and GeneratePress ===
     1=== FrontBlocks for Gutenberg/GeneratePress ===
    22Contributors: davidperez, sacrajaimez, alexbreagarcia, matiasquero, amulero, mit2sumit, alexcm13
    33Tags: carrusel, slider, lightweight, generatepress, gutenberg
    44Donate link: https://close.marketing/go/donate/
    55Requires at least: 5.0
    6 Tested up to: 6.9
    7 Stable tag: 1.3.2
    8 Version: 1.3.2
     6Tested up to: 7.0
     7Stable tag: 1.3.3
     8Version: 1.3.3
    99License: GPLv2 or later
    1010License URI: http://www.gnu.org/licenses/gpl-2.0.html
    1111
    12 This plugin extends the functionality of GeneratePress by adding a carousel, slider, animations, sticky columns, edge alignment for containers and the ability to insert posts.
     12Plugin extending Gutenberg and GeneratePress with carousel, slider, animations, sticky columns, edge alignment and post insertion capabilities.
    1313
    1414== Description ==
     
    153153
    154154== Changelog ==
     155
     156== 1.3.3 ==
     157*   Fixed: Carousel bullets display and behavior.
     158*   Fixed: Carousel editor styling and functionality.
     159*   Fixed: Carousel in native (core) blocks.
     160*   Fixed: Carousel JavaScript and CSS issues.
     161*   Fixed: Stacked images block display.
     162*   Fixed: Accordion in Gravity Forms inline layout.
     163*   Improved: Carousel styles - updated classes and removed unnecessary declarations.
     164*   Improved: Settings page and carousel advanced options.
     165*   Improved: Shape animations option component.
     166*   Improved: PHPStan compliance and code quality.
    155167
    156168== 1.3.2 ==
  • frontblocks/tags/1.3.3/vendor/composer/installed.php

    r3462660 r3472405  
    22    'root' => array(
    33        'name' => 'close/frontblocks',
    4         'pretty_version' => '1.3.2',
    5         'version' => '1.3.2.0',
    6         'reference' => '95247c40e9617d1fb79fb93f1daf791fb7e918db',
     4        'pretty_version' => '1.3.3',
     5        'version' => '1.3.3.0',
     6        'reference' => '7fb5a8fa3abcff0754a417193682455d7af5dc2d',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1212    'versions' => array(
    1313        'close/frontblocks' => array(
    14             'pretty_version' => '1.3.2',
    15             'version' => '1.3.2.0',
    16             'reference' => '95247c40e9617d1fb79fb93f1daf791fb7e918db',
     14            'pretty_version' => '1.3.3',
     15            'version' => '1.3.3.0',
     16            'reference' => '7fb5a8fa3abcff0754a417193682455d7af5dc2d',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
  • frontblocks/trunk/assets/admin/settings.css

    r3462660 r3472405  
    521521  margin-left: -0.25rem;
    522522}
     523.frbl-settings-wrapper .tw-mb-0 {
     524  margin-bottom: 0px;
     525}
    523526.frbl-settings-wrapper .tw-mb-2 {
    524527  margin-bottom: 0.5rem;
     
    530533  margin-bottom: 2rem;
    531534}
    532 .frbl-settings-wrapper .tw-ml-2 {
    533   margin-left: 0.5rem;
    534 }
    535535.frbl-settings-wrapper .tw-ml-3 {
    536536  margin-left: 0.75rem;
    537537}
    538 .frbl-settings-wrapper .tw-ml-auto {
    539   margin-left: auto;
    540 }
    541538.frbl-settings-wrapper .tw-mr-2 {
    542539  margin-right: 0.5rem;
    543540}
    544 .frbl-settings-wrapper .tw-mt-1 {
    545   margin-top: 0.25rem;
     541.frbl-settings-wrapper .tw-mt-0 {
     542  margin-top: 0px;
    546543}
    547544.frbl-settings-wrapper .tw-mt-2 {
    548545  margin-top: 0.5rem;
    549546}
     547.frbl-settings-wrapper .tw-mt-4 {
     548  margin-top: 1rem;
     549}
     550.frbl-settings-wrapper .tw-mt-6 {
     551  margin-top: 1.5rem;
     552}
    550553.frbl-settings-wrapper .tw-mt-8 {
    551554  margin-top: 2rem;
     
    575578  max-width: 64rem;
    576579}
     580.frbl-settings-wrapper .tw-flex-1 {
     581  flex: 1 1 0%;
     582}
    577583.frbl-settings-wrapper .tw-flex-shrink-0 {
    578584  flex-shrink: 0;
    579585}
    580 .frbl-settings-wrapper .tw-flex-grow {
    581   flex-grow: 1;
    582 }
    583 .frbl-settings-wrapper .tw-items-start {
    584   align-items: flex-start;
    585 }
    586586.frbl-settings-wrapper .tw-items-center {
    587587  align-items: center;
     
    592592.frbl-settings-wrapper .tw-gap-2 {
    593593  gap: 0.5rem;
    594 }
    595 .frbl-settings-wrapper .tw-gap-3 {
    596   gap: 0.75rem;
    597594}
    598595.frbl-settings-wrapper :is(.tw-space-x-2 > :not([hidden]) ~ :not([hidden])) {
     
    601598  margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
    602599}
    603 .frbl-settings-wrapper :is(.tw-space-y-4 > :not([hidden]) ~ :not([hidden])) {
    604   --tw-space-y-reverse: 0;
    605   margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
    606   margin-bottom: calc(1rem * var(--tw-space-y-reverse));
    607 }
    608600.frbl-settings-wrapper :is(.tw-space-y-6 > :not([hidden]) ~ :not([hidden])) {
    609601  --tw-space-y-reverse: 0;
     
    611603  margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
    612604}
     605.frbl-settings-wrapper .tw-overflow-auto {
     606  overflow: auto;
     607}
    613608.frbl-settings-wrapper .tw-overflow-hidden {
    614609  overflow: hidden;
     
    647642  border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
    648643}
    649 .frbl-settings-wrapper .tw-border-green-300 {
    650   --tw-border-opacity: 1;
    651   border-color: rgb(134 239 172 / var(--tw-border-opacity, 1));
    652 }
    653644.frbl-settings-wrapper .tw-border-red-200 {
    654645  --tw-border-opacity: 1;
    655646  border-color: rgb(254 202 202 / var(--tw-border-opacity, 1));
    656647}
    657 .frbl-settings-wrapper .tw-border-red-300 {
    658   --tw-border-opacity: 1;
    659   border-color: rgb(252 165 165 / var(--tw-border-opacity, 1));
    660 }
    661648.frbl-settings-wrapper .tw-border-transparent {
    662649  border-color: transparent;
    663650}
    664 .frbl-settings-wrapper .tw-border-yellow-300 {
     651.frbl-settings-wrapper .tw-border-yellow-200 {
    665652  --tw-border-opacity: 1;
    666   border-color: rgb(253 224 71 / var(--tw-border-opacity, 1));
     653  border-color: rgb(254 240 138 / var(--tw-border-opacity, 1));
    667654}
    668655.frbl-settings-wrapper .tw-border-yellow-400 {
     
    678665  background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
    679666}
    680 .frbl-settings-wrapper .tw-bg-green-100 {
    681   --tw-bg-opacity: 1;
    682   background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1));
    683 }
    684667.frbl-settings-wrapper .tw-bg-primary-100 {
    685668  --tw-bg-opacity: 1;
     
    690673  background-color: rgb(104 125 249 / var(--tw-bg-opacity, 1));
    691674}
    692 .frbl-settings-wrapper .tw-bg-red-100 {
    693   --tw-bg-opacity: 1;
    694   background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
    695 }
    696675.frbl-settings-wrapper .tw-bg-red-50 {
    697676  --tw-bg-opacity: 1;
     
    701680  --tw-bg-opacity: 1;
    702681  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
    703 }
    704 .frbl-settings-wrapper .tw-bg-yellow-100 {
    705   --tw-bg-opacity: 1;
    706   background-color: rgb(254 249 195 / var(--tw-bg-opacity, 1));
    707682}
    708683.frbl-settings-wrapper .tw-bg-yellow-50 {
     
    721696  --tw-gradient-to: #fff var(--tw-gradient-to-position);
    722697}
    723 .frbl-settings-wrapper .tw-p-3 {
    724   padding: 0.75rem;
    725 }
    726698.frbl-settings-wrapper .tw-p-4 {
    727699  padding: 1rem;
     700}
     701.frbl-settings-wrapper .tw-p-6 {
     702  padding: 1.5rem;
    728703}
    729704.frbl-settings-wrapper .tw-px-3 {
     
    743718  padding-bottom: 0.25rem;
    744719}
     720.frbl-settings-wrapper .tw-py-2 {
     721  padding-top: 0.5rem;
     722  padding-bottom: 0.5rem;
     723}
    745724.frbl-settings-wrapper .tw-py-3 {
    746725  padding-top: 0.75rem;
     
    755734  padding-bottom: 2rem;
    756735}
    757 .frbl-settings-wrapper .tw-pr-4 {
    758   padding-right: 1rem;
    759 }
    760736.frbl-settings-wrapper .tw-pt-6 {
    761737  padding-top: 1.5rem;
     
    763739.frbl-settings-wrapper .tw-text-center {
    764740  text-align: center;
     741}
     742.frbl-settings-wrapper .tw-text-2xl {
     743  font-size: 1.5rem;
     744  line-height: 2rem;
    765745}
    766746.frbl-settings-wrapper .tw-text-3xl {
     
    772752  line-height: 1.5rem;
    773753}
     754.frbl-settings-wrapper .tw-text-lg {
     755  font-size: 1.125rem;
     756  line-height: 1.75rem;
     757}
    774758.frbl-settings-wrapper .tw-text-sm {
    775759  font-size: 0.875rem;
     
    809793  color: rgb(75 85 99 / var(--tw-text-opacity, 1));
    810794}
     795.frbl-settings-wrapper .tw-text-gray-700 {
     796  --tw-text-opacity: 1;
     797  color: rgb(55 65 81 / var(--tw-text-opacity, 1));
     798}
    811799.frbl-settings-wrapper .tw-text-gray-900 {
    812800  --tw-text-opacity: 1;
    813801  color: rgb(17 24 39 / var(--tw-text-opacity, 1));
    814802}
    815 .frbl-settings-wrapper .tw-text-green-800 {
    816   --tw-text-opacity: 1;
    817   color: rgb(22 101 52 / var(--tw-text-opacity, 1));
    818 }
    819803.frbl-settings-wrapper .tw-text-primary-500 {
    820804  --tw-text-opacity: 1;
     
    829813  color: rgb(185 28 28 / var(--tw-text-opacity, 1));
    830814}
    831 .frbl-settings-wrapper .tw-text-red-800 {
    832   --tw-text-opacity: 1;
    833   color: rgb(153 27 27 / var(--tw-text-opacity, 1));
    834 }
    835815.frbl-settings-wrapper .tw-text-white {
    836816  --tw-text-opacity: 1;
     
    844824  --tw-text-opacity: 1;
    845825  color: rgb(161 98 7 / var(--tw-text-opacity, 1));
    846 }
    847 .frbl-settings-wrapper .tw-text-yellow-800 {
    848   --tw-text-opacity: 1;
    849   color: rgb(133 77 14 / var(--tw-text-opacity, 1));
    850826}
    851827.frbl-settings-wrapper .tw-underline {
    852828  text-decoration-line: underline;
    853 }
    854 .frbl-settings-wrapper .tw-no-underline {
    855   text-decoration-line: none;
    856 }
    857 .frbl-settings-wrapper .tw-opacity-50 {
    858   opacity: 0.5;
    859829}
    860830.frbl-settings-wrapper .tw-shadow-sm {
     
    10521022  }
    10531023}
    1054 .frbl-settings-wrapper .hover\:tw-bg-primary-200:hover {
    1055   --tw-bg-opacity: 1;
    1056   background-color: rgb(205 208 251 / var(--tw-bg-opacity, 1));
    1057 }
    10581024.frbl-settings-wrapper .hover\:tw-bg-primary-600:hover {
    10591025  --tw-bg-opacity: 1;
     
    10631029  --tw-text-opacity: 1;
    10641030  color: rgb(85 101 237 / var(--tw-text-opacity, 1));
    1065 }
    1066 .frbl-settings-wrapper .hover\:tw-no-underline:hover {
    1067   text-decoration-line: none;
    10681031}
    10691032.frbl-settings-wrapper .focus\:tw-border-transparent:focus {
     
    11011064}
    11021065
    1103 /* Custom styles for license fields */
    1104 .frbl-settings-wrapper .tw-text-base:not(button) {
    1105     font-size: 1rem;
    1106     line-height: 1.5rem;
    1107     width: 100%;
    1108 }
    1109 
    1110 .frbl-settings-wrapper .tw-flex {
    1111     display: flex;
    1112     column-gap: 20px;
    1113 }
    1114 
    1115 /* Submit button width */
    1116 .frbl-settings-wrapper .tw-w-1\/2 {
    1117     width: 50%;
    1118 }
    1119 
    1120 /* Features Grid Layout */
    1121 .frbl-section-wrapper {
    1122     margin-bottom: 3rem;
    1123 }
    1124 
    1125 .frbl-features-grid {
    1126     display: grid;
    1127     grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    1128     gap: 1.25rem;
    1129 }
    1130 
    1131 /* Feature Card Styles */
    1132 .frbl-feature-card {
    1133     position: relative;
    1134     background: #ffffff;
    1135     border: 1px solid #e5e7eb;
    1136     border-radius: 12px;
    1137     padding: 1.5rem;
    1138     transition: all 0.2s ease;
    1139     overflow: hidden;
    1140     display: flex;
    1141     flex-direction: column;
    1142     min-height: 80px;
    1143 }
    1144 
    1145 .frbl-feature-card:hover {
    1146     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    1147     transform: translateY(-2px);
    1148     border-color: #687df9;
    1149 }
    1150 
    1151 /* PRO Badge */
    1152 .frbl-pro-badge {
    1153     position: absolute;
    1154     top: 0;
    1155     left: 0;
    1156     background: linear-gradient(135deg, #ec4899 0%, #f97316 100%);
    1157     color: #ffffff;
    1158     font-size: 0.625rem;
    1159     font-weight: 700;
    1160     letter-spacing: 0.05em;
    1161     padding: 0.25rem 0.75rem;
    1162     border-radius: 0 0 12px 0;
    1163     box-shadow: 0 2px 4px rgba(236, 72, 153, 0.3);
    1164     z-index: 10;
    1165 }
    1166 
    1167 /* Feature Card Content */
    1168 .frbl-feature-content {
    1169     display: flex;
    1170     align-items: center;
    1171     justify-content: space-between;
    1172     gap: 1rem;
    1173     min-height: 3rem;
    1174 }
    1175 
    1176 /* Feature Icon */
    1177 .frbl-feature-icon {
    1178     flex-shrink: 0;
    1179     width: 2.5rem;
    1180     height: 2.5rem;
    1181     display: flex;
    1182     align-items: center;
    1183     justify-content: center;
    1184     background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
    1185     border-radius: 10px;
    1186     color: #687df9;
    1187     transition: all 0.2s ease;
    1188 }
    1189 
    1190 .frbl-feature-card:hover .frbl-feature-icon {
    1191     background: linear-gradient(135deg, #e0e3fc 0%, #d0d4fb 100%);
    1192     transform: scale(1.05);
    1193 }
    1194 
    1195 .frbl-feature-icon svg {
    1196     width: 1.5rem;
    1197     height: 1.5rem;
    1198     stroke-width: 2;
    1199 }
    1200 
    1201 /* Feature Info */
    1202 .frbl-feature-info {
    1203     flex: 1;
    1204     min-width: 0;
    1205     display: flex;
    1206     align-items: center;
    1207 }
    1208 
    1209 .frbl-feature-title {
    1210     font-size: 0.9375rem;
    1211     font-weight: 600;
    1212     color: #1f2937;
    1213     margin: 0;
    1214     line-height: 1.5;
    1215 }
    1216 
    1217 .frbl-feature-description {
    1218     font-size: 0.8125rem;
    1219     color: #6b7280;
    1220     margin: 0.25rem 0 0 0;
    1221     line-height: 1.4;
    1222 }
    1223 
    1224 /* Active Feature Cards */
    1225 .frbl-feature-card.frbl-feature-active {
    1226     background: linear-gradient(135deg, #ffffff 0%, #f0fdf4 100%);
    1227     border-color: #d1fae5;
    1228 }
    1229 
    1230 .frbl-feature-card.frbl-feature-active:hover {
    1231     border-color: #10b981;
    1232 }
    1233 
    1234 .frbl-feature-card.frbl-feature-active .frbl-feature-icon {
    1235     background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
    1236     color: #10b981;
    1237 }
    1238 
    1239 .frbl-feature-card.frbl-feature-active:hover .frbl-feature-icon {
    1240     background: linear-gradient(135deg, #a7f3d0 0%, #6ee7b7 100%);
    1241 }
    1242 
    1243 /* Active cards need vertical layout for description */
    1244 .frbl-feature-card.frbl-feature-active .frbl-feature-info {
    1245     flex-direction: column;
    1246     align-items: flex-start;
    1247 }
    1248 
    1249 /* Active cards don't have toggle, so don't use space-between */
    1250 .frbl-feature-card.frbl-feature-active .frbl-feature-content {
    1251     justify-content: flex-start;
    1252 }
    1253 
    1254 /* Feature Toggle */
    1255 .frbl-feature-toggle {
    1256     flex-shrink: 0;
    1257     display: flex;
    1258     align-items: center;
    1259 }
    1260 
    1261 /* PRO Card Styles */
    1262 .frbl-feature-card.frbl-feature-pro {
    1263     background: linear-gradient(135deg, #ffffff 0%, #fef3f2 100%);
    1264     border-color: #fee2e2;
    1265 }
    1266 
    1267 .frbl-feature-card.frbl-feature-pro:hover {
    1268     border-color: #ec4899;
    1269 }
    1270 
    1271 .frbl-feature-card.frbl-feature-pro .frbl-feature-icon {
    1272     background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
    1273     color: #ec4899;
    1274 }
    1275 
    1276 .frbl-feature-card.frbl-feature-pro:hover .frbl-feature-icon {
    1277     background: linear-gradient(135deg, #fecaca 0%, #fca5a5 100%);
    1278 }
    1279 
    1280 /* Disabled state for PRO features without license */
    1281 .frbl-feature-card.frbl-feature-pro .frbl-toggle:has(input:disabled) {
    1282     opacity: 0.6;
    1283 }
    1284 
    1285 /* Responsive adjustments */
    1286 @media (max-width: 768px) {
    1287     .frbl-features-grid {
    1288         grid-template-columns: 1fr;
    1289     }
    1290    
    1291     .frbl-feature-card {
    1292         padding: 1.25rem;
    1293     }
    1294 }
    1295 
    1296 @media (min-width: 768px) and (max-width: 1024px) {
    1297     .frbl-features-grid {
    1298         grid-template-columns: repeat(2, 1fr);
    1299     }
    1300 }
    1301 
    1302 
    1303 /* =============================================
    1304    License Management Styles (Complete & Independent)
    1305    Based on FormsCRM but standalone for FrontBlocks
    1306    ============================================= */
    1307 
    1308 /* License Wrapper - Grid Layout */
    1309 .formscrm-license-wrapper {
    1310     display: grid;
    1311     grid-template-columns: 1fr 320px;
    1312     gap: 24px;
    1313     max-width: 1200px;
    1314     margin: 20px 0;
    1315 }
    1316 
    1317 @media (max-width: 900px) {
    1318     .formscrm-license-wrapper {
    1319         grid-template-columns: 1fr;
    1320     }
    1321 }
    1322 
    1323 /* Main Card */
    1324 .formscrm-card {
    1325     background: #fff;
    1326     border: 1px solid #e5e7eb;
    1327     border-radius: 12px;
    1328     padding: 32px;
    1329     box-shadow: 0 1px 3px rgba(0,0,0,0.05);
    1330 }
    1331 
    1332 .formscrm-card-header {
    1333     margin-bottom: 24px;
    1334     padding-bottom: 20px;
    1335     border-bottom: 1px solid #e5e7eb;
    1336 }
    1337 
    1338 .formscrm-card-header h2 {
    1339     margin: 0 0 8px 0;
    1340     font-size: 1.5rem;
    1341     font-weight: 600;
    1342     color: #1f2937;
    1343 }
    1344 
    1345 .formscrm-card-header p {
    1346     margin: 0;
    1347     color: #6b7280;
    1348     font-size: 0.875rem;
    1349 }
    1350 
    1351 /* Form Elements */
    1352 .formscrm-form-group {
    1353     margin-bottom: 24px;
    1354 }
    1355 
    1356 .formscrm-label {
    1357     display: block;
    1358     font-weight: 600;
    1359     color: #374151;
    1360     margin-bottom: 8px;
    1361     font-size: 0.875rem;
    1362 }
    1363 
    1364 .formscrm-input-group {
    1365     display: flex;
    1366     gap: 12px;
    1367     align-items: center;
    1368 }
    1369 
    1370 .formscrm-input {
    1371     flex: 1;
    1372     padding: 12px 16px;
    1373     border: 1px solid #d1d5db;
    1374     border-radius: 8px;
    1375     font-size: 1rem;
    1376     transition: all 0.2s;
    1377     background: #fff;
    1378 }
    1379 
    1380 .formscrm-input:focus {
    1381     outline: none;
    1382     border-color: #8b5cf6;
    1383     box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
    1384 }
    1385 
    1386 .formscrm-input[readonly] {
    1387     background: #f9fafb;
    1388     color: #6b7280;
    1389     cursor: not-allowed;
    1390 }
    1391 
    1392 /* Deactivate Label */
    1393 .formscrm-deactivate-label {
    1394     display: flex;
    1395     align-items: center;
    1396     gap: 8px;
    1397     padding: 12px 16px;
    1398     background: #fef2f2;
    1399     border: 1px solid #fecaca;
    1400     border-radius: 8px;
    1401     cursor: pointer;
    1402     transition: background 0.2s;
    1403     white-space: nowrap;
    1404 }
    1405 
    1406 .formscrm-deactivate-label:hover {
    1407     background: #fee2e2;
    1408 }
    1409 
    1410 .formscrm-deactivate-label input {
    1411     margin: 0;
    1412 }
    1413 
    1414 .formscrm-deactivate-label span {
    1415     font-size: 0.875rem;
    1416     font-weight: 600;
    1417     color: #dc2626;
    1418 }
    1419 
    1420 /* Help Text */
    1421 .formscrm-help-text {
    1422     margin: 8px 0 0 0;
    1423     font-size: 0.75rem;
    1424     color: #9ca3af;
    1425 }
    1426 
    1427 /* Status Box */
    1428 .formscrm-status-box {
    1429     display: flex;
    1430     align-items: center;
    1431     gap: 12px;
    1432     padding: 14px 18px;
    1433     border-radius: 8px;
    1434     border: 2px solid;
    1435 }
    1436 
    1437 .formscrm-status-active {
    1438     background: #dcfce7;
    1439     border-color: #86efac;
    1440     color: #166534;
    1441 }
    1442 
    1443 .formscrm-status-expired {
    1444     background: #fee2e2;
    1445     border-color: #fca5a5;
    1446     color: #991b1b;
    1447 }
    1448 
    1449 .formscrm-status-inactive {
    1450     background: #fef9c3;
    1451     border-color: #fde047;
    1452     color: #854d0e;
    1453 }
    1454 
    1455 .formscrm-status-icon {
    1456     display: flex;
    1457     flex-shrink: 0;
    1458 }
    1459 
    1460 .formscrm-icon,
    1461 .fcod-icon {
    1462     width: 22px;
    1463     height: 22px;
    1464 }
    1465 
    1466 .formscrm-status-text {
    1467     font-weight: 700;
    1468     font-size: 1rem;
    1469 }
    1470 
    1471 /* Notices */
    1472 .formscrm-notice {
    1473     padding: 14px 18px;
    1474     border-radius: 8px;
    1475     margin-bottom: 20px;
    1476 }
    1477 
    1478 .formscrm-notice p {
    1479     margin: 0;
    1480     font-size: 0.875rem;
    1481     line-height: 1.5;
    1482 }
    1483 
    1484 .formscrm-notice a {
    1485     font-weight: 600;
    1486     text-decoration: underline;
    1487 }
    1488 
    1489 .formscrm-notice-info {
    1490     background: #f0f9ff;
    1491     border: 1px solid #bfdbfe;
    1492     color: #1e40af;
    1493 }
    1494 
    1495 .formscrm-notice-info a {
    1496     color: #1d4ed8;
    1497 }
    1498 
    1499 .formscrm-notice-error {
    1500     background: #fef2f2;
    1501     border: 1px solid #fecaca;
    1502     color: #991b1b;
    1503 }
    1504 
    1505 .formscrm-notice-error a {
    1506     color: #dc2626;
    1507 }
    1508 
    1509 /* Form Actions */
    1510 .formscrm-form-actions {
    1511     margin-top: 24px;
    1512     padding-top: 24px;
    1513     border-top: 1px solid #e5e7eb;
    1514 }
    1515 
    1516 /* Buttons - FrontBlocks Purple Style */
    1517 .formscrm-button {
    1518     display: inline-flex;
    1519     align-items: center;
    1520     padding: 12px 24px;
    1521     border-radius: 8px;
    1522     font-size: 1rem;
    1523     font-weight: 600;
    1524     cursor: pointer;
    1525     transition: all 0.2s;
    1526     border: none;
    1527 }
    1528 
    1529 .formscrm-button-primary {
    1530     background: #8b5cf6;
    1531     color: #fff;
    1532 }
    1533 
    1534 .formscrm-button-primary:hover {
    1535     background: #7c3aed;
    1536     box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
    1537 }
    1538 
    1539 /* Info Card (Sidebar) */
    1540 .formscrm-info-card {
    1541     background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
    1542     border: 1px solid #e2e8f0;
    1543     border-radius: 12px;
    1544     padding: 24px;
    1545     height: fit-content;
    1546 }
    1547 
    1548 .formscrm-info-card h3 {
    1549     margin: 0 0 12px 0;
    1550     font-size: 1.125rem;
    1551     font-weight: 700;
    1552     color: #1e293b;
    1553 }
    1554 
    1555 .formscrm-info-card p {
    1556     margin: 0 0 16px 0;
    1557     font-size: 0.875rem;
    1558     color: #64748b;
    1559     line-height: 1.5;
    1560 }
    1561 
    1562 /* Benefits List */
    1563 .formscrm-benefits-list {
    1564     margin: 0;
    1565     padding: 0;
    1566     list-style: none;
    1567 }
    1568 
    1569 .formscrm-benefits-list li {
    1570     position: relative;
    1571     padding-left: 28px;
    1572     margin-bottom: 10px;
    1573     font-size: 0.875rem;
    1574     color: #475569;
    1575 }
    1576 
    1577 .formscrm-benefits-list li::before {
    1578     content: "✓";
    1579     position: absolute;
    1580     left: 0;
    1581     top: 0;
    1582     color: #8b5cf6;
    1583     font-weight: 700;
    1584     font-size: 1.125rem;
    1585 }
  • frontblocks/trunk/assets/carousel/frontblocks-advanced-option.js

    r3462660 r3472405  
    11"use strict";
    22
     3function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
     4function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
     5function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
    36// Add custom controls to the Advanced panel of GenerateBlocks Grid block
    47var addFilter = wp.hooks.addFilter;
    5 var Fragment = wp.element.Fragment;
     8var _wp$element = wp.element,
     9  Fragment = _wp$element.Fragment,
     10  useEffect = _wp$element.useEffect,
     11  useRef = _wp$element.useRef;
    612var _wp$blockEditor = wp.blockEditor,
    713  InspectorControls = _wp$blockEditor.InspectorControls,
     
    1319  ToggleControl = _wp$components.ToggleControl;
    1420var __ = wp.i18n.__;
     21
     22/**
     23 * Returns the document that renders block content.
     24 * WordPress 6.x uses an <iframe> for the editor canvas.
     25 */
     26function getEditorDocument() {
     27  var iframe = document.querySelector('iframe[name="editor-canvas"]');
     28  return iframe && iframe.contentDocument && iframe.contentDocument.body ? iframe.contentDocument : document;
     29}
     30var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-/;
     31
     32/**
     33 * Walk an element's subtree to find the container whose DIRECT children
     34 * are actual blocks (UUID data-block).  Stops at depth 4.
     35 */
     36function findSlidesParent(el, depth) {
     37  if (depth === 0) return null;
     38  if (Array.from(el.children).some(function (c) {
     39    return UUID_RE.test(c.dataset.block || '');
     40  })) {
     41    return el;
     42  }
     43  var _iterator = _createForOfIteratorHelper(el.children),
     44    _step;
     45  try {
     46    for (_iterator.s(); !(_step = _iterator.n()).done;) {
     47      var child = _step.value;
     48      var found = findSlidesParent(child, depth - 1);
     49      if (found) return found;
     50    }
     51  } catch (err) {
     52    _iterator.e(err);
     53  } finally {
     54    _iterator.f();
     55  }
     56  return null;
     57}
     58
     59/**
     60 * Find the grid DOM element for a given block.
     61 *
     62 * For generateblocks/element: [data-block][data-type] IS the gb-element.
     63 *   Do NOT search inside – that returns a child item.
     64 * For generateblocks/grid:  look for .gb-grid-wrapper inside.
     65 * For core/group: walk the subtree to find the element whose children
     66 *   are the real blocks, because WP nests them differently across versions.
     67 */
     68function findGridEl(blockEl, name) {
     69  if (name === 'generateblocks/element') {
     70    return blockEl;
     71  }
     72  if (name === 'generateblocks/grid') {
     73    if (blockEl.classList.contains('gb-grid-wrapper')) return blockEl;
     74    var grid = blockEl.querySelector('.gb-grid-wrapper');
     75    return grid || null;
     76  }
     77  if (name === 'core/group') {
     78    // Walk up to 4 levels deep to find where the child blocks live.
     79    return findSlidesParent(blockEl, 4) || blockEl;
     80  }
     81  return null;
     82}
    1583function addCustomCarouselPanel(BlockEdit) {
    1684  return function (props) {
    17     // Support grid blocks, element blocks with grid display, and core/group blocks
    1885    if (props.name !== 'generateblocks/grid' && props.name !== 'generateblocks/element' && props.name !== 'core/group') {
    1986      return /*#__PURE__*/React.createElement(BlockEdit, props);
    2087    }
    21 
    22     // For element blocks, only show carousel options if it has grid display
    2388    if (props.name === 'generateblocks/element') {
    2489      var styles = props.attributes.styles || {};
     
    2792      }
    2893    }
    29 
    30     // For core/group blocks, only show carousel options if it has grid layout
    3194    if (props.name === 'core/group') {
    3295      var layout = props.attributes.layout || {};
     
    48111      _props$attributes$frb6 = _props$attributes.frblAutoplay,
    49112      frblAutoplay = _props$attributes$frb6 === void 0 ? '' : _props$attributes$frb6,
    50       _props$attributes$frb7 = _props$attributes.frblButtons,
    51       frblButtons = _props$attributes$frb7 === void 0 ? 'arrows' : _props$attributes$frb7,
    52       _props$attributes$frb8 = _props$attributes.frblRewind,
    53       frblRewind = _props$attributes$frb8 === void 0 ? true : _props$attributes$frb8,
     113      _props$attributes$frb7 = _props$attributes.frblGap,
     114      frblGap = _props$attributes$frb7 === void 0 ? '20' : _props$attributes$frb7,
     115      _props$attributes$frb8 = _props$attributes.frblButtons,
     116      frblButtons = _props$attributes$frb8 === void 0 ? 'arrows' : _props$attributes$frb8,
     117      _props$attributes$frb9 = _props$attributes.frblRewind,
     118      frblRewind = _props$attributes$frb9 === void 0 ? true : _props$attributes$frb9,
    54119      frblButtonColor = _props$attributes.frblButtonColor,
    55120      frblButtonBgColor = _props$attributes.frblButtonBgColor,
    56       _props$attributes$frb9 = _props$attributes.frblButtonsPosition,
    57       frblButtonsPosition = _props$attributes$frb9 === void 0 ? 'side' : _props$attributes$frb9,
    58       _props$attributes$frb0 = _props$attributes.frblDisableOnDesktop,
    59       frblDisableOnDesktop = _props$attributes$frb0 === void 0 ? false : _props$attributes$frb0;
     121      _props$attributes$frb0 = _props$attributes.frblButtonsPosition,
     122      frblButtonsPosition = _props$attributes$frb0 === void 0 ? 'side' : _props$attributes$frb0,
     123      _props$attributes$frb1 = _props$attributes.frblDisableOnDesktop,
     124      frblDisableOnDesktop = _props$attributes$frb1 === void 0 ? false : _props$attributes$frb1;
     125
     126    // ── Editor carousel preview ──────────────────────────────────────────
     127    var stateRef = useRef(null);
     128    useEffect(function () {
     129      var mounted = true;
     130      var timer = null;
     131      function cleanup() {
     132        if (!stateRef.current) return;
     133        var _stateRef$current = stateRef.current,
     134          gridEl = _stateRef$current.gridEl,
     135          slides = _stateRef$current.slides,
     136          spacer = _stateRef$current.spacer,
     137          prevBtn = _stateRef$current.prevBtn,
     138          nextBtn = _stateRef$current.nextBtn,
     139          resizeObs = _stateRef$current.resizeObs,
     140          scrollFn = _stateRef$current.scrollFn;
     141
     142        // Disconnect observer and scroll listener.
     143        if (resizeObs) resizeObs.disconnect();
     144        var editorDoc = getEditorDocument();
     145        var editorScrollEl = editorDoc.documentElement || editorDoc.body;
     146        if (scrollFn) editorScrollEl.removeEventListener('scroll', scrollFn, true);
     147
     148        // Remove arrows from outer window body (completely outside React).
     149        [prevBtn, nextBtn].forEach(function (btn) {
     150          if (btn && btn.parentNode) btn.parentNode.removeChild(btn);
     151        });
     152
     153        // Remove right spacer element.
     154        if (spacer && spacer.parentNode) spacer.parentNode.removeChild(spacer);
     155
     156        // Restore gridEl styles.
     157        try {
     158          gridEl.classList.remove('frbl-carousel-noscrollbar');
     159          ['display', 'flex-wrap', 'gap', 'overflow-x', 'scroll-behavior', 'padding-left', 'padding-right'].forEach(function (p) {
     160            return gridEl.style.removeProperty(p);
     161          });
     162          slides.forEach(function (slide) {
     163            ['flex-shrink', 'width', 'min-width', 'margin-left', 'margin-right', 'grid-column'].forEach(function (p) {
     164              return slide.style.removeProperty(p);
     165            });
     166          });
     167        } catch (e) {}
     168        stateRef.current = null;
     169      }
     170      function init() {
     171        if (!mounted) return;
     172        cleanup();
     173        var editorDoc = getEditorDocument();
     174
     175        // GB 2.x wraps blocks in a root element that shares the same data-block UUID.
     176        // Use data-type to select the correct inner element; fall back for native blocks.
     177        var blockEl = editorDoc.querySelector("[data-block=\"".concat(props.clientId, "\"][data-type=\"").concat(props.name, "\"]"));
     178        if (!blockEl) {
     179          blockEl = editorDoc.querySelector("[data-block=\"".concat(props.clientId, "\"]"));
     180        }
     181        if (!blockEl) return;
     182        var gridEl = findGridEl(blockEl, props.name);
     183        if (!gridEl) return;
     184
     185        // Slides = direct children that are real blocks (UUID data-block).
     186        var slides = Array.from(gridEl.children).filter(function (el) {
     187          return UUID_RE.test(el.dataset.block || '');
     188        });
     189        if (slides.length === 0) return;
     190        var perView = Math.max(1, parseInt(frblItemsToView) || 4);
     191        var gap = Math.max(0, parseInt(frblGap) || 20);
     192        var btnColor = frblButtonColor || '#fff';
     193        var btnBg = frblButtonBgColor || 'rgba(0,0,0,0.45)';
     194
     195        // Measure content-box width before any style changes.
     196        var editorWin = editorDoc.defaultView || window;
     197        var ARROW_W = 40; // px reserved on each side for arrow buttons.
     198        var cs = editorWin.getComputedStyle(gridEl);
     199        var innerW = gridEl.getBoundingClientRect().width - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0);
     200        var totalWidth = Math.round(innerW) || 600;
     201        // Content area after reserving space for both arrow buttons.
     202        var contentW = totalWidth - ARROW_W * 2;
     203        var slideWidth = Math.round((contentW - gap * (perView - 1)) / perView);
     204        var step = slideWidth + gap;
     205
     206        // ── Apply scroll-based layout directly to gridEl ─────────────
     207        // No DOM restructuring → no React reconciliation conflicts.
     208        gridEl.classList.add('frbl-carousel-noscrollbar');
     209        gridEl.style.display = 'flex';
     210        gridEl.style.flexWrap = 'nowrap';
     211        gridEl.style.gap = gap + 'px';
     212        gridEl.style.overflowX = 'scroll';
     213        gridEl.style.scrollBehavior = 'smooth';
     214        // paddingLeft creates left space for the prev arrow.
     215        // paddingRight is NOT used because Chrome ignores it in scroll extent;
     216        // a spacer flex child is appended instead to guarantee right-side space.
     217        gridEl.style.paddingLeft = ARROW_W + 'px';
     218        gridEl.style.paddingRight = '0';
     219        slides.forEach(function (slide) {
     220          slide.style.flexShrink = '0';
     221          slide.style.width = slideWidth + 'px';
     222          slide.style.minWidth = slideWidth + 'px';
     223          slide.style.marginLeft = '0';
     224          slide.style.marginRight = '0';
     225          slide.style.gridColumn = 'unset';
     226        });
     227
     228        // Right-side spacer: Chrome does not include padding-right in horizontal
     229        // scroll extent, so we use a real flex child to guarantee right space.
     230        var appender = gridEl.querySelector('.block-list-appender');
     231        var spacer = editorDoc.createElement('span');
     232        spacer.setAttribute('data-frbl-spacer', '1');
     233        spacer.style.cssText = "display:block;flex-shrink:0;width:".concat(ARROW_W, "px;min-width:").concat(ARROW_W, "px;");
     234        gridEl.insertBefore(spacer, appender || null);
     235
     236        // ── Navigation (scrollLeft, infinite loop) ───────────────────
     237        var idx = 0;
     238        function scrollTo(n) {
     239          var total = slides.length;
     240          if (n >= total) {
     241            // Forward past last: instant snap to 0 then continue.
     242            gridEl.style.scrollBehavior = 'auto';
     243            gridEl.scrollLeft = 0;
     244            void gridEl.offsetWidth;
     245            gridEl.style.scrollBehavior = 'smooth';
     246            idx = 0;
     247            return;
     248          }
     249          if (n < 0) {
     250            // Back past first: instant snap to last.
     251            gridEl.style.scrollBehavior = 'auto';
     252            gridEl.scrollLeft = Math.round((total - 1) * step);
     253            void gridEl.offsetWidth;
     254            gridEl.style.scrollBehavior = 'smooth';
     255            idx = total - 1;
     256            return;
     257          }
     258          idx = n;
     259          gridEl.scrollLeft = Math.round(idx * step);
     260        }
     261
     262        // ── Arrows in OUTER document body ────────────────────────────
     263        // Placing them outside the iframe means React never touches them.
     264        var iframe = document.querySelector('iframe[name="editor-canvas"]');
     265        function updateArrowPos() {
     266          if (!stateRef.current) return;
     267          var ifrRect = iframe ? iframe.getBoundingClientRect() : {
     268            top: 0,
     269            left: 0
     270          };
     271          var rect = gridEl.getBoundingClientRect(); // coords in iframe viewport
     272          var midY = Math.round(ifrRect.top + rect.top + rect.height / 2 - 16);
     273          prevBtn.style.top = midY + 'px';
     274          prevBtn.style.left = Math.round(ifrRect.left + rect.left + 8) + 'px';
     275          nextBtn.style.top = midY + 'px';
     276          nextBtn.style.right = Math.round(window.innerWidth - (ifrRect.left + rect.right) + 8) + 'px';
     277        }
     278        function makeArrow(dir) {
     279          var btn = document.createElement('button');
     280          btn.type = 'button';
     281          btn.className = "frbl-editor-arrow frbl-editor-arrow-".concat(dir);
     282          btn.setAttribute('aria-label', dir === 'prev' ? 'Previous slide' : 'Next slide');
     283          btn.style.cssText = "position:fixed;z-index:99999;background-color:".concat(btnBg, ";");
     284          var d = dir === 'prev' ? 'M6 1L1 6L6 11' : 'M1 11L6 6L1 1';
     285          btn.innerHTML = "<svg width=\"7\" height=\"12\" viewBox=\"0 0 7 12\" fill=\"none\"><path d=\"".concat(d, "\" stroke=\"").concat(btnColor, "\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>");
     286          btn.addEventListener('click', function (e) {
     287            e.stopPropagation();
     288            scrollTo(dir === 'prev' ? idx - 1 : idx + 1);
     289          });
     290          document.body.appendChild(btn);
     291          return btn;
     292        }
     293        var prevBtn = makeArrow('prev');
     294        var nextBtn = makeArrow('next');
     295        updateArrowPos();
     296
     297        // Keep arrows positioned correctly when the editor scrolls or resizes.
     298        var resizeObs = new ResizeObserver(updateArrowPos);
     299        resizeObs.observe(gridEl);
     300        var editorScrollEl = editorDoc.documentElement || editorDoc.body;
     301        var scrollFn = updateArrowPos;
     302        editorScrollEl.addEventListener('scroll', scrollFn, {
     303          passive: true,
     304          capture: true
     305        });
     306        stateRef.current = {
     307          gridEl: gridEl,
     308          slides: slides,
     309          spacer: spacer,
     310          prevBtn: prevBtn,
     311          nextBtn: nextBtn,
     312          resizeObs: resizeObs,
     313          scrollFn: scrollFn
     314        };
     315      }
     316      if (frblGridOption !== 'none') {
     317        timer = setTimeout(init, 300);
     318      } else {
     319        cleanup();
     320      }
     321      return function () {
     322        mounted = false;
     323        if (timer) clearTimeout(timer);
     324        cleanup();
     325      };
     326    }, [frblGridOption, frblItemsToView, frblGap, frblButtonColor, frblButtonBgColor, props.clientId]);
     327    // ── Inspector panel ──────────────────────────────────────────────────
     328
    60329    return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(BlockEdit, props), /*#__PURE__*/React.createElement(InspectorControls, null, /*#__PURE__*/React.createElement(PanelBody, {
    61330      title: __('Carousel Settings', 'frontblocks'),
     
    75344      }],
    76345      onChange: function onChange(value) {
    77         props.setAttributes({
     346        return props.setAttributes({
    78347          frblGridOption: value
    79348        });
     
    124393        });
    125394      }
    126     }), frblGridOption === 'slider' && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ToggleControl, {
     395    }), /*#__PURE__*/React.createElement(TextControl, {
     396      label: __('Gap (px)', 'frontblocks'),
     397      value: frblGap,
     398      onChange: function onChange(value) {
     399        return props.setAttributes({
     400          frblGap: value
     401        });
     402      },
     403      help: __('Space between slides in pixels. Leave empty for 20.', 'frontblocks')
     404    }), frblGridOption === 'slider' && /*#__PURE__*/React.createElement(ToggleControl, {
    127405      label: __('Rewind', 'frontblocks'),
    128406      checked: frblRewind,
     
    132410        });
    133411      }
    134     })), /*#__PURE__*/React.createElement(SelectControl, {
     412    }), /*#__PURE__*/React.createElement(SelectControl, {
    135413      label: __('Buttons', 'frontblocks'),
    136414      value: frblButtons,
     
    150428        });
    151429      }
    152     }), frblButtons === 'arrows' && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SelectControl, {
     430    }), frblButtons === 'arrows' && /*#__PURE__*/React.createElement(SelectControl, {
    153431      label: __('Buttons Position', 'frontblocks'),
    154432      value: frblButtonsPosition,
     
    165443        });
    166444      }
    167     })), /*#__PURE__*/React.createElement(PanelColorSettings, {
     445    }), /*#__PURE__*/React.createElement(PanelColorSettings, {
    168446      title: __('Button Colors', 'frontblocks'),
    169447      colorSettings: [{
  • frontblocks/trunk/assets/carousel/frontblocks-carousel.css

    r3462660 r3472405  
    11/**
    2 * ## Glide Carousel style
    3 * --------------------------- */
    4 
    5 .glide {
     2 * Frontblocks Carousel – scoped under .frontblocks so other Glide instances are not affected.
     3 */
     4
     5.frontblocks.glide {
    66  position: relative;
    77  width: 100%;
     8  max-width: 100%;
    89  box-sizing: border-box;
    9 }
    10 .glide * {
     10  overflow: visible;
     11  margin: 0;
     12  padding: 0;
     13}
     14
     15.frontblocks.glide * {
    1116  box-sizing: inherit;
    1217}
    13 .glide__track {
     18
     19.frontblocks .glide__track {
    1420  overflow: hidden;
    15 }
    16 .glide__slides {
     21  width: 100%;
     22  position: relative;
     23  left: 0;
     24  margin: 0;
     25  padding: 0;
     26}
     27
     28.frontblocks .glide__slides {
    1729  position: relative;
    1830  width: 100%;
     
    2840  flex-wrap: nowrap;
    2941  will-change: transform;
    30   transition: transform 400ms cubic-bezier(0.165, 0.84, 0.44, 1);
    31 }
    32 .glide__slides--dragging {
    33   user-select: none;
    34 }
    35 .glide__slide {
     42}
     43
     44.frontblocks .glide__slides--dragging {
     45  user-select: none;
     46}
     47
     48.frontblocks .glide__slide {
    3649  width: 100%;
    3750  height: 100%;
     
    4154  -webkit-touch-callout: none;
    4255  -webkit-tap-highlight-color: transparent;
    43 }
    44 .glide__slide a {
     56  min-width: 0;
     57  overflow: hidden;
     58}
     59
     60/* Prevent inner content (cards, AWB, etc.) from overflowing the slide and being cut off. */
     61.frontblocks .glide__slide > * {
     62  max-width: 100%;
     63  box-sizing: border-box;
     64}
     65
     66.frontblocks .glide__slide a {
    4567  user-select: none;
    4668  -webkit-user-drag: none;
     
    4870  -ms-user-select: none;
    4971}
    50 .glide__arrows {
     72
     73.frontblocks .glide__arrows {
    5174  -webkit-touch-callout: none;
    5275  user-select: none;
    5376}
    54 .glide__bullets {
     77
     78.frontblocks .glide__bullets {
    5579  -webkit-touch-callout: none;
    5680  user-select: none;
    5781}
    58 .glide--rtl {
     82
     83.frontblocks.glide--rtl {
    5984  direction: rtl;
    6085}
    6186
    62 /*# sourceMappingURL=glide.core.css.map */
    63 
    64 .glide__arrow {
     87.frontblocks .glide__arrow {
    6588  position: absolute;
    6689  display: block;
     
    79102  line-height: 1;
    80103}
    81 .glide__arrow:focus {
     104
     105.frontblocks .glide__arrow:focus {
    82106  outline: none;
    83107}
    84 .glide__arrow:hover {
     108
     109.frontblocks .glide__arrow:hover {
    85110  border-color: white;
    86111}
    87 .glide__arrow--left {
    88   left: -1em;
    89 }
    90 .glide__arrow--right {
    91   right: -1em;
    92 }
    93 .glide__arrow--disabled {
     112
     113.frontblocks .glide__arrow--left {
     114  left: 1em;
     115}
     116
     117.frontblocks .glide__arrow--right {
     118  right: 1em;
     119}
     120
     121.frontblocks .glide__arrow--disabled {
    94122  opacity: 0.33;
    95123}
    96 .glide__bullets {
    97   position: absolute;
    98   z-index: 2;
    99   bottom: -1em;
    100   left: 50%;
    101   display: inline-flex;
     124
     125.frontblocks .glide__bullets {
     126  display: flex;
     127  justify-content: center;
    102128  list-style: none;
    103   transform: translateX(-50%);
    104   gap: 5px;
    105 }
    106 .glide__bullet {
    107   background-color: rgba(255, 255, 255, 0.5);
    108   width: 13px;
    109   height: 13px;
     129  gap: 2px;
     130  margin: 0.75em 0 0;
     131  padding: 0;
     132}
     133
     134.frontblocks .glide__bullet {
     135  box-sizing: content-box;
     136  width: 10px;
     137  height: 10px;
    110138  padding: 0;
    111139  border-radius: 50%;
    112   border: 2px solid transparent;
    113   transition: all 300ms ease-in-out;
     140  border: none;
     141  background-color: var(--frbl-bullet-bg, rgba(0, 0, 0, 0.25));
    114142  cursor: pointer;
    115   line-height: 0;
    116   margin: 0;
    117 }
    118 .glide__bullet:focus {
    119   outline: none;
    120 }
    121 .glide__bullet:hover, .glide__bullet:focus {
    122   border: 2px solid white;
    123   background-color: rgba(255, 255, 255, 0.5);
    124 }
    125 .glide__bullet--active {
    126   background-color: white;
    127 }
    128 .glide--swipeable {
     143  transition: background-color 300ms ease-in-out, transform 300ms ease-in-out;
     144  margin: 0 2px;
     145  flex-shrink: 0;
     146}
     147
     148.frontblocks .glide__bullet:focus {
     149  outline: 2px solid var(--frbl-bullet-color, rgba(0, 0, 0, 0.8));
     150  outline-offset: 2px;
     151}
     152
     153.frontblocks .glide__bullet:hover {
     154  background-color: var(--frbl-bullet-color, rgba(0, 0, 0, 0.8));
     155  transform: scale(1.2);
     156}
     157
     158.frontblocks .glide__bullet--active {
     159  background-color: var(--frbl-bullet-color, rgba(0, 0, 0, 0.8));
     160  transform: scale(1.2);
     161}
     162
     163.frontblocks.glide--swipeable {
    129164  cursor: grab;
    130165  cursor: -moz-grab;
    131166  cursor: -webkit-grab;
    132167}
    133 .glide--dragging {
     168
     169.frontblocks.glide--dragging {
    134170  cursor: grabbing;
    135171  cursor: -moz-grabbing;
    136172  cursor: -webkit-grabbing;
    137173}
    138 .glide__arrows--bottom .glide__arrow {
    139     top: calc(100% + 25px);
    140     left: 0;
    141 }
    142 .glide__arrows--bottom .glide__arrow--right {
    143     left: 50px;
    144     right: unset;
    145 }
    146 /* Arrows position top/side - spans full container width */
    147 .glide__arrows--top {
    148     position: absolute;
    149     top: 50%;
    150     left: 0;
    151     right: 0;
    152     width: 100%;
    153     z-index: 10;
    154     pointer-events: none;
    155     transform: translateY(-50%);
    156 }
    157 .glide__arrows--top .glide__arrow {
    158     pointer-events: all;
    159 }
    160 .glide__arrows--top .glide__arrow--left {
    161     left: 2em;
    162 }
    163 .glide__arrows--top .glide__arrow--right {
    164     right: 2em;
    165 }
    166 /* Responsive */
     174
     175.frontblocks .glide__arrows--bottom .glide__arrow {
     176  top: calc(100% + 25px);
     177  left: 0;
     178}
     179
     180.frontblocks .glide__arrows--bottom .glide__arrow--right {
     181  left: 50px;
     182  right: unset;
     183}
     184
     185.frontblocks .glide__arrows--top {
     186  position: absolute;
     187  top: 50%;
     188  left: 0;
     189  right: 0;
     190  width: 100%;
     191  z-index: 10;
     192  pointer-events: none;
     193  transform: translateY(-50%);
     194}
     195
     196.frontblocks .glide__arrows--top .glide__arrow {
     197  pointer-events: all;
     198}
     199
     200.frontblocks .glide__arrows--top .glide__arrow--left {
     201  left: 2em;
     202}
     203
     204.frontblocks .glide__arrows--top .glide__arrow--right {
     205  right: 2em;
     206}
     207
    167208@media only screen and (max-width: 768px) {
    168     .glide__arrow--left {
    169         left: 0;
    170     }
    171     .glide__arrow--right {
    172         right: 0;
    173     }
    174 }
    175 
    176 /*# sourceMappingURL=glide.theme.css.map */
     209  .frontblocks .glide__arrow--left {
     210    left: 0.5em;
     211  }
     212  .frontblocks .glide__arrow--right {
     213    right: 0.5em;
     214  }
     215}
    177216
    178217/**
    179  * Override native Gutenberg Grid styles when carousel is active
     218 * Override native Gutenberg Grid styles when carousel is active (scoped to frontblocks carousel).
    180219 */
    181220.wp-block-group.frontblocks-carousel,
    182221.wp-block-group.frontblocks-carousel.is-layout-grid {
    183     display: block;
    184     grid-template-columns: none;
    185     gap: 0;
     222  display: block;
     223  grid-template-columns: none;
     224  gap: 0;
     225}
     226
     227.wp-block-group.frontblocks-carousel .frontblocks.glide,
     228.wp-block-group.frontblocks-carousel .frontblocks .glide__track {
     229  width: 100%;
     230  max-width: 100%;
     231  margin-left: 0;
     232  margin-right: 0;
     233  padding-left: 0;
     234  padding-right: 0;
    186235}
    187236
    188237.wp-block-group.frontblocks-carousel > * {
    189     width: 100%;
    190 }
    191 
    192 /* Ensure inner container doesn't interfere with carousel */
     238  width: 100%;
     239}
     240
    193241.wp-block-group.frontblocks-carousel > .wp-block-group__inner-container {
    194     display: block;
    195     grid-template-columns: none;
    196     gap: 0;
    197     width: 100%;
    198 }
    199 
    200 /* Override grid styles for direct children in carousel mode */
    201 .glide__slides.wp-block-group,
    202 .glide__slides.wp-block-group.is-layout-grid {
    203     display: flex;
    204     grid-template-columns: none;
    205     gap: 0;
    206     column-gap: 0;
    207     row-gap: 0;
    208 }
    209 
    210 /* Ensure slides have proper width */
    211 .glide__slides > .glide__slide {
    212     min-width: 0;
    213     flex-shrink: 0;
    214 }
    215 
    216 /* Force full width for single slide view */
    217 .frontblocks-carousel[data-view="1"] .glide__slide {
    218     margin-left: 0;
    219     margin-right: 0;
    220 }
    221 
    222 /* Remove gaps when showing one slide */
    223 .frontblocks-carousel[data-view="1"].glide__slides {
    224     gap: 0;
    225 }
    226 
    227 /* Responsive single slide view */
    228 @media only screen and (max-width: 768px) {
    229     .frontblocks-carousel[data-mobile-view="1"] .glide__slide {
    230         width: 100%;
    231         flex: 0 0 100%;
    232         max-width: 100%;
    233         margin-left: 0;
    234         margin-right: 0;
    235     }
    236 }
    237 
    238 @media only screen and (min-width: 769px) and (max-width: 1024px) {
    239     .frontblocks-carousel[data-tablet-view="1"] .glide__slide {
    240         width: 100%;
    241         flex: 0 0 100%;
    242         max-width: 100%;
    243         margin-left: 0;
    244         margin-right: 0;
    245     }
    246 }
    247 
    248 @media only screen and (min-width: 1025px) and (max-width: 1440px) {
    249     .frontblocks-carousel[data-laptop-view="1"] .glide__slide {
    250         width: 100%;
    251         flex: 0 0 100%;
    252         max-width: 100%;
    253         margin-left: 0;
    254         margin-right: 0;
    255     }
    256 }
    257 
    258 /* Handle alignfull content within carousel slides */
    259 .glide__slide > .wp-block-cover.alignfull,
    260 .glide__slide > .alignfull {
    261     width: 100%;
    262     max-width: none;
    263     margin-left: 0;
    264     margin-right: 0;
    265 }
    266 
    267 /* Ensure cover blocks maintain minimum height */
    268 .glide__slide .wp-block-cover {
    269     min-height: 430px;
    270     display: flex;
    271     align-items: center;
    272     justify-content: center;
    273 }
    274 
    275 /* Prevent carousel from overflowing viewport */
    276 .glide {
    277     position: relative;
    278     overflow: visible;
    279     min-height: 430px;
    280 }
    281 
    282 .glide__track {
    283     overflow: hidden;
    284     width: 100%;
    285     position: relative;
    286     z-index: 1;
    287 }
    288 
    289 .glide__slides {
    290     position: relative;
    291     z-index: 1;
    292     height: auto;
    293     min-height: 430px;
    294 }
    295 
    296 .glide__slide {
    297     position: relative;
    298     z-index: 1;
    299     height: auto;
    300     opacity: 1;
    301     visibility: visible;
    302 }
     242  display: block;
     243  grid-template-columns: none;
     244  gap: 0;
     245  width: 100%;
     246}
     247
     248.frontblocks .glide__slides.wp-block-group,
     249.frontblocks .glide__slides.wp-block-group.is-layout-grid {
     250  display: flex;
     251  grid-template-columns: none;
     252  gap: 0;
     253  column-gap: 0;
     254  row-gap: 0;
     255}
     256
     257.frontblocks .glide__slides > .glide__slide {
     258  min-width: 0;
     259  flex-shrink: 0;
     260}
     261
     262/**
     263 * GenerateBlocks (and any other plugin) may inject column-gap via generated
     264 * utility classes on the carousel slides container.  Glide manages its own
     265 * spacing through margin-right on each slide, so any external column-gap
     266 * breaks the width calculation and must be zeroed out.
     267 */
     268.frontblocks-carousel,
     269.frontblocks .glide__slides {
     270  column-gap: 0 !important;
     271  gap: 0 !important;
     272}
  • frontblocks/trunk/assets/carousel/frontblocks-carousel.js

    r3462660 r3472405  
    2929            parentwrap.replaceChild(wrapperParent, wrapper);
    3030            wrapperParent.appendChild(wrapper);
    31             wrapperParent.classList.add('glide');
     31            wrapperParent.classList.add('frontblocks', 'glide');
    3232
    3333            // Options
     
    3838            const carouselTabletView = item.getAttribute('data-tablet-view') ? parseInt(item.getAttribute('data-tablet-view')) : 2;
    3939            const carouselMobileView = item.getAttribute('data-mobile-view') ? parseInt(item.getAttribute('data-mobile-view')) : 1;
    40             const autoplayValue = item.getAttribute('data-autoplay');
    41             const carouselAutoplay = autoplayValue && autoplayValue !== '' ? parseInt(autoplayValue) : 0;
     40            const autoplayAttr = item.getAttribute('data-autoplay');
     41            const carouselAutoplay = (autoplayAttr !== '' && autoplayAttr !== null && autoplayAttr !== undefined) ? parseInt(autoplayAttr, 10) : 0;
     42            const carouselGap = item.getAttribute('data-gap') ? parseInt(item.getAttribute('data-gap'), 10) : 20;
    4243            const carouselRewind = item.getAttribute('data-rewind') ? item.getAttribute('data-rewind') : false;
    4344            const carouselbuttonsColor = item.getAttribute('data-buttons-color') ? item.getAttribute('data-buttons-color') : 'black';
     
    6465                bullets.classList.add('glide__bullets');
    6566                bullets.setAttribute('data-glide-el', 'controls[nav]');
     67                bullets.setAttribute('role', 'group');
     68                bullets.setAttribute('aria-label', 'Slide navigation');
    6669
    6770                for (let i = 0; i < item.children.length; i++) {
     
    7073                    bullet.setAttribute('data-glide-dir', '=' + i);
    7174                    bullet.setAttribute('aria-label', 'Go to slide ' + (i + 1));
    72                     bullet.style.backgroundColor = carouselbuttonsBackgroundColor;
    7375                    bullets.appendChild(bullet);
    7476                }
     
    7678                wrapperParent.appendChild(bullets);
    7779
    78                 // Add custom CSS for active bullet color
    79                 const style = document.createElement('style');
    80                 style.textContent = `
    81                     .glide__bullet.glide__bullet--active {
    82                         background-color: ${carouselbuttonsColor} !important;
    83                     }
    84                 `;
    85                 document.head.appendChild(style);
     80                // Set bullet colors via CSS custom properties on the wrapper.
     81                // This avoids specificity conflicts with the stylesheet.
     82                if (carouselbuttonsColor) {
     83                    wrapperParent.style.setProperty('--frbl-bullet-color', carouselbuttonsColor);
     84                }
     85                if (carouselbuttonsBackgroundColor && carouselbuttonsBackgroundColor !== 'transparent') {
     86                    wrapperParent.style.setProperty('--frbl-bullet-bg', carouselbuttonsBackgroundColor);
     87                }
    8688            }
    8789
     
    110112                wrapperParent.appendChild(arrows);
    111113            }
    112             // Calculate gap based on perView - use 0 gap when showing 1 slide
    113             const calculateGap = (view) => view === 1 ? 0 : 20;
    114            
    115114            const glideFrontBlocks = new Glide(wrapperParent, {
    116115                type: carouselType,
    117116                perView: carouselView,
    118117                startAt: 0,
    119                 autoplay: carouselAutoplay === 0 ? false : carouselAutoplay,
    120                 gap: calculateGap(carouselView),
     118                autoplay: carouselAutoplay > 0 ? carouselAutoplay : false,
     119                gap: isNaN(carouselGap) ? 20 : carouselGap,
    121120                rewind: carouselRewind,
    122121                breakpoints: {
    123122                    768: {
    124                         perView: carouselMobileView,
    125                         gap: calculateGap(carouselMobileView)
     123                        perView: carouselMobileView
    126124                    },
    127125                    1024: {
    128                         perView: carouselTabletView,
    129                         gap: calculateGap(carouselTabletView)
     126                        perView: carouselTabletView
    130127                    },
    131128                    1440: {
    132                         perView: carouselLaptopView,
    133                         gap: calculateGap(carouselLaptopView)
     129                        perView: carouselLaptopView
    134130                    }
    135131                }
  • frontblocks/trunk/assets/gravityforms-inline/frontblocks-gf-inline.css

    r3402582 r3472405  
    174174}
    175175
     176/* -------------------------------------------------------------------------
     177   GenerateBlocks Accordion + Gravity Form: remove blank space when closed
     178   When a Gravity Form is inside a GB accordion and the accordion is above
     179   the footer, the closed content can still reserve space. Force it to none.
     180   ------------------------------------------------------------------------- */
     181.gb-accordion__item:not(.gb-accordion__item-open):not([data-accordion-is-open="true"]) > .gb-accordion__content {
     182    display: none;
     183    overflow: hidden;
     184    max-height: 0;
     185    min-height: 0 ;
     186    margin: 0 ;
     187    padding: 0 ;
     188    visibility: hidden;
     189}
     190
     191/* Prevent Gravity Form wrapper from forcing height when inside closed accordion. */
     192.gb-accordion__item:not(.gb-accordion__item-open):not([data-accordion-is-open="true"]) > .gb-accordion__content .gform_wrapper {
     193    min-height: 0;
     194    height: 0;
     195    overflow: hidden;
     196    visibility: hidden;
     197}
     198
  • frontblocks/trunk/assets/shape-animations/frontblocks-shape-animation-option.js

    r3409365 r3472405  
    1919    PanelBody = _wp$components.PanelBody,
    2020    ToggleControl = _wp$components.ToggleControl,
     21    TextareaControl = _wp$components.TextareaControl,
    2122    Button = _wp$components.Button,
    22     Notice = _wp$components.Notice,
    23     FormFileUpload = _wp$components.FormFileUpload;
     23    Notice = _wp$components.Notice;
    2424
    2525  /**
     
    4646        jsonPreview = _useState4[0],
    4747        setJsonPreview = _useState4[1];
    48       var _useState5 = useState(''),
    49         _useState6 = _slicedToArray(_useState5, 2),
    50         fileName = _useState6[0],
    51         setFileName = _useState6[1];
    52       var _useState7 = useState(0),
    53         _useState8 = _slicedToArray(_useState7, 2),
    54         fileInputKey = _useState8[0],
    55         setFileInputKey = _useState8[1];
    5648
    5749      // Detect if JSON is Lottie format.
     
    10193      };
    10294
    103       // Handle file upload.
    104       var handleFileUpload = function handleFileUpload(event) {
    105         var file = event.target.files[0];
    106         if (!file) {
    107           return;
    108         }
    109        
    110         // Check if it's a JSON file.
    111         if (!file.name.endsWith('.json')) {
    112           setJsonError(__('Please select a JSON file', 'frontblocks'));
    113           setFileInputKey(function(prev) { return prev + 1; });
    114           return;
    115         }
    116        
    117         setFileName(file.name);
    118        
    119         // Read file content.
    120         var reader = new FileReader();
    121         reader.onload = function(e) {
    122           var content = e.target.result;
    123           setAttributes({ frblCustomSvgAnimationJson: content });
    124           validateJson(content);
    125           setFileInputKey(function(prev) { return prev + 1; });
    126         };
    127         reader.onerror = function() {
    128           setJsonError(__('Error reading file', 'frontblocks'));
    129           setFileInputKey(function(prev) { return prev + 1; });
    130         };
    131         reader.readAsText(file);
     95      // Handle JSON change.
     96      var handleJsonChange = function handleJsonChange(value) {
     97        setAttributes({
     98          frblCustomSvgAnimationJson: value
     99        });
     100        if (value.trim()) {
     101          validateJson(value);
     102        } else {
     103          setJsonError('');
     104          setJsonPreview(null);
     105        }
    132106      };
    133      
    134       // Clear imported JSON.
    135       var handleClear = function handleClear() {
    136         setAttributes({ frblCustomSvgAnimationJson: '' });
    137         setJsonError('');
    138         setJsonPreview(null);
    139         setFileName('');
    140         setFileInputKey(function(prev) { return prev + 1; });
    141       };
    142      
     107
    143108      // Example JSON template.
    144109      var exampleJson = JSON.stringify({
     
    153118        }
    154119      }, null, 2);
    155      
    156       // Download example JSON.
    157       var handleDownloadExample = function handleDownloadExample() {
    158         var blob = new Blob([exampleJson], { type: 'application/json' });
    159         var url = URL.createObjectURL(blob);
    160         var a = document.createElement('a');
    161         a.href = url;
    162         a.download = 'example-animation.json';
    163         document.body.appendChild(a);
    164         a.click();
    165         document.body.removeChild(a);
    166         URL.revokeObjectURL(url);
    167       };
    168      
    169120      return wp.element.createElement(Fragment, {}, wp.element.createElement(BlockEdit, props), wp.element.createElement(InspectorControls, {}, wp.element.createElement(PanelBody, {
    170121        title: __('FrontBlocks Custom SVG Animation', 'frontblocks'),
     
    182133        style: {
    183134          fontSize: '12px',
    184           marginBottom: '12px',
     135          marginBottom: '8px',
    185136          color: '#757575'
    186137        }
    187       }, __('Import a JSON file with your animation configuration:', 'frontblocks')), wp.element.createElement(FormFileUpload, {
    188         key: fileInputKey,
    189         accept: '.json',
    190         onChange: handleFileUpload,
    191         render: function(ref) {
    192           return wp.element.createElement(Button, {
    193             isSecondary: true,
    194             onClick: ref.openFileDialog,
    195             style: { marginBottom: '8px', width: '100%' }
    196           }, fileName ? __('Change JSON file', 'frontblocks') : __('Import JSON file', 'frontblocks'));
    197         }
    198       }), fileName && wp.element.createElement('div', {
    199         style: {
    200           display: 'flex',
    201           alignItems: 'center',
    202           justifyContent: 'space-between',
    203           marginBottom: '12px',
    204           padding: '8px',
    205           background: '#f6f7f7',
    206           borderRadius: '4px',
    207           fontSize: '12px'
    208         }
    209       }, wp.element.createElement('span', {}, '📄 ' + fileName), wp.element.createElement(Button, {
    210         isSmall: true,
    211         isDestructive: true,
    212         onClick: handleClear
    213       }, __('Clear', 'frontblocks'))), jsonError && wp.element.createElement(Notice, {
     138      }, __('Paste your JSON configuration below:', 'frontblocks')), wp.element.createElement(TextareaControl, {
     139        label: __('JSON Configuration', 'frontblocks'),
     140        value: frblCustomSvgAnimationJson,
     141        onChange: handleJsonChange,
     142        rows: 10,
     143        help: __('JSON with svg and animation properties', 'frontblocks')
     144      }), jsonError && wp.element.createElement(Notice, {
    214145        status: 'error',
    215146        isDismissible: false
     
    228159          marginBottom: '8px'
    229160        }
    230       }, __('📋 Download example JSON', 'frontblocks')), wp.element.createElement('pre', {
     161      }, __('📋 Show example JSON', 'frontblocks')), wp.element.createElement('pre', {
    231162        style: {
    232163          background: '#f6f7f7',
     
    240171        isSecondary: true,
    241172        isSmall: true,
    242         onClick: handleDownloadExample,
    243         style: {
    244           marginTop: '8px',
    245           marginRight: '8px'
    246         }
    247       }, __('Download example', 'frontblocks')), wp.element.createElement(Button, {
    248         isSecondary: true,
    249         isSmall: true,
    250173        onClick: function onClick() {
    251174          setAttributes({
    252175            frblCustomSvgAnimationJson: exampleJson
    253176          });
    254           setFileName('example-animation.json');
    255177          validateJson(exampleJson);
    256           setFileInputKey(function(prev) { return prev + 1; });
    257178        },
    258179        style: {
  • frontblocks/trunk/assets/stacked-images/frontblocks-stacked-images-frontend.js

    r3462660 r3472405  
    7878                    entries.forEach(entry => {
    7979                        if (entry.isIntersecting) {
     80                            container.classList.add('frbl-animated');
    8081                            animateImages(images, duration, delay);
    8182                            observer.unobserve(entry.target);
     
    8485                },
    8586                {
    86                     threshold: 0.2,
    87                     rootMargin: '0px',
     87                    threshold: 0.05,
     88                    rootMargin: '50px 0px 50px 0px',
    8889                }
    8990            );
    9091
    9192            observer.observe(container);
     93
     94            // Fallback: if observer never fires (e.g. layout/height issue on mobile), animate after a short delay.
     95            setTimeout(() => {
     96                if (!container.classList.contains('frbl-animated')) {
     97                    container.classList.add('frbl-animated');
     98                    animateImages(images, duration, delay);
     99                }
     100            }, 800);
    92101        });
    93102    }
  • frontblocks/trunk/assets/stacked-images/frontblocks-stacked-images.css

    r3462660 r3472405  
    33 */
    44
    5 /* Wrapper */
     5/* Wrapper – centered, compact size on all viewports */
    66.frbl-stacked-images-wrapper {
    77    position: relative;
    88    width: 100%;
     9    max-width: 520px;
     10    max-height: 65vh;
     11    margin-left: auto;
     12    margin-right: auto;
    913    overflow: visible;
    1014    display: block;
    1115    background: transparent;
    12     padding: 50px;
    13     margin: -50px;
     16    padding: 40px;
     17    box-sizing: border-box;
    1418}
    1519
    16 /* Container */
     20/* Container – center content horizontally and vertically */
    1721.frbl-stacked-images-container {
    1822    position: relative;
     
    2327    justify-content: center;
    2428    overflow: visible;
     29    margin: 0 auto;
    2530}
    2631
    27 /* Individual image */
     32/* Individual image – flex center so img is always centered */
    2833.frbl-stacked-image {
    2934    position: absolute;
    3035    top: 0;
    3136    left: 0;
     37    right: 0;
     38    bottom: 0;
    3239    width: 100%;
    3340    height: 100%;
     
    3946    visibility: hidden;
    4047    overflow: visible;
     48    margin: auto;
    4149}
    4250
     
    4755
    4856.frbl-stacked-image img {
    49     max-width: 100%;
    50     max-height: 100%;
     57    max-width: 85%;
     58    max-height: 85%;
    5159    width: auto;
    5260    height: auto;
    5361    object-fit: contain;
     62    object-position: center;
    5463    display: block;
     64    margin: auto;
    5565    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    5666}
     
    99109}
    100110
    101 /* Responsive */
     111/* Tablet / ventana media: aquí suele verse enorme, lo limitamos mucho */
     112@media (min-width: 769px) and (max-width: 1200px) {
     113    .frbl-stacked-images-wrapper {
     114        max-width: 420px;
     115        max-height: 55vh;
     116        padding: 24px;
     117    }
     118    .frbl-stacked-image img {
     119        max-width: 82%;
     120        max-height: 82%;
     121    }
     122}
     123
     124/* Mobile */
    102125@media (max-width: 768px) {
    103126    .frbl-stacked-images-wrapper {
    104         height: auto !important;
    105         min-height: 300px;
     127        min-height: 260px;
     128        max-height: 60vh;
     129        max-width: 88%;
     130        margin-left: auto;
     131        margin-right: auto;
     132        padding: 20px;
     133    }
     134    .frbl-stacked-images-container {
     135        min-height: 260px;
     136        margin: 0 auto;
     137    }
     138    .frbl-stacked-image img {
     139        max-width: 85%;
     140        max-height: 55vh;
     141        object-position: center;
     142        margin: auto;
    106143    }
    107144}
  • frontblocks/trunk/frontblocks.php

    r3462660 r3472405  
    11<?php
    22/**
    3  * Plugin Name: FrontBlocks for GeneratePress
     3 * Plugin Name: FrontBlocks for Gutenberg/GeneratePress
    44 * Plugin URI:  https://wordpress.org/plugins/frontblocks/
    5  * Description: Blocks and helpers that extends GeneratePress blocks.
    6  * Version:     1.3.2
     5 * Description: Blocks and helpers that extends Gutenberg and GeneratePress blocks.
     6 * Version:     1.3.3
    77 * Author:      Closemarketing
    88 * Author URI:  https://close.marketing
     
    2727defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
    2828
    29 define( 'FRBL_VERSION', '1.3.2' );
     29define( 'FRBL_VERSION', '1.3.3' );
    3030define( 'FRBL_PLUGIN', __FILE__ );
    3131define( 'FRBL_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
  • frontblocks/trunk/includes/Frontend/Carousel.php

    r3409365 r3472405  
    3434    private function init_hooks() {
    3535        add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
     36        add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_canvas_assets' ) );
    3637        add_filter( 'render_block_generateblocks/grid', array( $this, 'add_custom_attributes_to_grid_block' ), 10, 2 );
    3738        add_filter( 'render_block_generateblocks/element', array( $this, 'add_custom_attributes_to_element_block' ), 10, 2 );
    3839        add_filter( 'render_block_core/group', array( $this, 'add_custom_attributes_to_core_group_block' ), 10, 2 );
    3940        add_action( 'init', array( $this, 'register_custom_attributes' ), 5 );
     41    }
     42
     43    /**
     44     * Enqueue carousel CSS in the editor canvas (iframe).
     45     *
     46     * @return void
     47     */
     48    public function enqueue_block_canvas_assets() {
     49        if ( ! is_admin() ) {
     50            return;
     51        }
     52        wp_enqueue_style(
     53            'frontblocks-carousel-editor',
     54            FRBL_PLUGIN_URL . 'assets/carousel/frontblocks-carousel-editor.css',
     55            array(),
     56            FRBL_VERSION
     57        );
    4058    }
    4159
     
    7694        $responsive_to_view = isset( $attrs['frblResponsiveToView'] ) ? (int) $attrs['frblResponsiveToView'] : 1;
    7795        $autoplay           = isset( $attrs['frblAutoplay'] ) ? ( (int) $attrs['frblAutoplay'] * 1000 ) : '';
     96        $gap                = isset( $attrs['frblGap'] ) && '' !== $attrs['frblGap'] ? (int) $attrs['frblGap'] : 20;
    7897        $rewind             = isset( $attrs['frblRewind'] ) ? (bool) $attrs['frblRewind'] : true;
    7998        $buttons            = isset( $attrs['frblButtons'] ) ? sanitize_text_field( $attrs['frblButtons'] ) : 'arrows';
     
    100119                    ' data-mobile-view="' . esc_attr( $responsive_to_view ) . '"' .
    101120                    ' data-autoplay="' . esc_attr( $autoplay ) . '"' .
     121                    ' data-gap="' . esc_attr( $gap ) . '"' .
    102122                    ' data-buttons="' . esc_attr( $buttons ) . '"' .
    103123                    ' data-buttons-color="' . esc_attr( $button_color ) . '"' .
     
    140160        $responsive_to_view = isset( $attrs['frblResponsiveToView'] ) ? (int) $attrs['frblResponsiveToView'] : 1;
    141161        $autoplay           = isset( $attrs['frblAutoplay'] ) ? ( (int) $attrs['frblAutoplay'] * 1000 ) : '';
     162        $gap                = isset( $attrs['frblGap'] ) && '' !== $attrs['frblGap'] ? (int) $attrs['frblGap'] : 20;
    142163        $rewind             = isset( $attrs['frblRewind'] ) ? (bool) $attrs['frblRewind'] : true;
    143164        $buttons            = isset( $attrs['frblButtons'] ) ? sanitize_text_field( $attrs['frblButtons'] ) : 'arrows';
     
    164185                    ' data-mobile-view="' . esc_attr( $responsive_to_view ) . '"' .
    165186                    ' data-autoplay="' . esc_attr( $autoplay ) . '"' .
     187                    ' data-gap="' . esc_attr( $gap ) . '"' .
    166188                    ' data-buttons="' . esc_attr( $buttons ) . '"' .
    167189                    ' data-buttons-color="' . esc_attr( $button_color ) . '"' .
     
    204226        $responsive_to_view = isset( $attrs['frblResponsiveToView'] ) ? (int) $attrs['frblResponsiveToView'] : 1;
    205227        $autoplay           = isset( $attrs['frblAutoplay'] ) ? ( (int) $attrs['frblAutoplay'] * 1000 ) : '';
     228        $gap                = isset( $attrs['frblGap'] ) && '' !== $attrs['frblGap'] ? (int) $attrs['frblGap'] : 20;
    206229        $rewind             = isset( $attrs['frblRewind'] ) ? (bool) $attrs['frblRewind'] : true;
    207230        $buttons            = isset( $attrs['frblButtons'] ) ? sanitize_text_field( $attrs['frblButtons'] ) : 'arrows';
     
    228251                    ' data-mobile-view="' . esc_attr( $responsive_to_view ) . '"' .
    229252                    ' data-autoplay="' . esc_attr( $autoplay ) . '"' .
     253                    ' data-gap="' . esc_attr( $gap ) . '"' .
    230254                    ' data-buttons="' . esc_attr( $buttons ) . '"' .
    231255                    ' data-buttons-color="' . esc_attr( $button_color ) . '"' .
     
    299323            'type'    => 'string',
    300324            'default' => '',
     325        );
     326        $block_args['attributes']['frblGap']              = array(
     327            'type'    => 'string',
     328            'default' => '20',
    301329        );
    302330        $block_args['attributes']['frblRewind']           = array(
     
    371399                            default: ''
    372400                        },
     401                        frblGap: {
     402                            type: 'string',
     403                            default: '20'
     404                        },
    373405                        frblButtons: {
    374406                            type: 'string',
  • frontblocks/trunk/readme.txt

    r3462660 r3472405  
    1 === FrontBlocks for Gutenberg and GeneratePress ===
     1=== FrontBlocks for Gutenberg/GeneratePress ===
    22Contributors: davidperez, sacrajaimez, alexbreagarcia, matiasquero, amulero, mit2sumit, alexcm13
    33Tags: carrusel, slider, lightweight, generatepress, gutenberg
    44Donate link: https://close.marketing/go/donate/
    55Requires at least: 5.0
    6 Tested up to: 6.9
    7 Stable tag: 1.3.2
    8 Version: 1.3.2
     6Tested up to: 7.0
     7Stable tag: 1.3.3
     8Version: 1.3.3
    99License: GPLv2 or later
    1010License URI: http://www.gnu.org/licenses/gpl-2.0.html
    1111
    12 This plugin extends the functionality of GeneratePress by adding a carousel, slider, animations, sticky columns, edge alignment for containers and the ability to insert posts.
     12Plugin extending Gutenberg and GeneratePress with carousel, slider, animations, sticky columns, edge alignment and post insertion capabilities.
    1313
    1414== Description ==
     
    153153
    154154== Changelog ==
     155
     156== 1.3.3 ==
     157*   Fixed: Carousel bullets display and behavior.
     158*   Fixed: Carousel editor styling and functionality.
     159*   Fixed: Carousel in native (core) blocks.
     160*   Fixed: Carousel JavaScript and CSS issues.
     161*   Fixed: Stacked images block display.
     162*   Fixed: Accordion in Gravity Forms inline layout.
     163*   Improved: Carousel styles - updated classes and removed unnecessary declarations.
     164*   Improved: Settings page and carousel advanced options.
     165*   Improved: Shape animations option component.
     166*   Improved: PHPStan compliance and code quality.
    155167
    156168== 1.3.2 ==
  • frontblocks/trunk/vendor/composer/installed.php

    r3462660 r3472405  
    22    'root' => array(
    33        'name' => 'close/frontblocks',
    4         'pretty_version' => '1.3.2',
    5         'version' => '1.3.2.0',
    6         'reference' => '95247c40e9617d1fb79fb93f1daf791fb7e918db',
     4        'pretty_version' => '1.3.3',
     5        'version' => '1.3.3.0',
     6        'reference' => '7fb5a8fa3abcff0754a417193682455d7af5dc2d',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1212    'versions' => array(
    1313        'close/frontblocks' => array(
    14             'pretty_version' => '1.3.2',
    15             'version' => '1.3.2.0',
    16             'reference' => '95247c40e9617d1fb79fb93f1daf791fb7e918db',
     14            'pretty_version' => '1.3.3',
     15            'version' => '1.3.3.0',
     16            'reference' => '7fb5a8fa3abcff0754a417193682455d7af5dc2d',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.