Make WordPress Themes

Changeset 356478


Ignore:
Timestamp:
11/01/2026 09:38:18 AM (less than one hour ago)
Author:
themedropbox
Message:

New version of Flowix - 1.1.2

Location:
flowix/1.1.2
Files:
13 added
3 deleted
36 edited
1 copied

Legend:

Unmodified
Added
Removed
  • flowix/1.1.2/404.php

    r319286 r356478  
    66get_header(); ?>
    77<div class="container">
    8         <div class="error-404" style="padding:100px 20px;text-align:center;">
     8        <div class="error-404">
    99                <div class="error-404-number" aria-hidden="true">404</div>
    1010                <span class="eyebrow"><?php esc_html_e( 'Page Not Found', 'flowix' ); ?></span>
    11                 <h1 style="margin-bottom:16px;"><?php esc_html_e( "This page wandered off.", 'flowix' ); ?></h1>
    12                 <p class="lead" style="max-width:440px;margin:0 auto 36px;">
     11                <h1><?php esc_html_e( "This page wandered off.", 'flowix' ); ?></h1>
     12                <p class="lead">
    1313                        <?php esc_html_e( "The page you're looking for doesn't exist. Maybe it moved, or the link was a typo.", 'flowix' ); ?>
    1414                </p>
    15                 <div style="display:flex;gap:14px;justify-content:center;flex-wrap:wrap;">
     15                <div class="error-404-actions">
    1616                        <a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="btn btn--primary btn--lg">
    1717                                <?php esc_html_e( 'Go Home', 'flowix' ); ?>
  • flowix/1.1.2/archive.php

    r353748 r356478  
    77
    88get_header(); ?>
    9 <div class="container fx-blog" style="padding-top:48px;padding-bottom:64px;">
    10         <header class="fx-blog-header" style="margin-bottom:36px;padding-bottom:24px;border-bottom:1px solid var(--border-light);">
     9<div class="container fx-blog">
     10        <header class="fx-blog-header fx-page-header">
    1111                <?php the_archive_title( '<h1>', '</h1>' ); ?>
    12                 <?php the_archive_description( '<div class="lead" style="margin-top:8px;">', '</div>' ); ?>
     12                <?php the_archive_description( '<div class="lead fx-page-sub">', '</div>' ); ?>
    1313        </header>
    1414        <?php if ( have_posts() ) : ?>
  • flowix/1.1.2/assets/css/builder-controls.css

    r353588 r356478  
    8686.fx-gal-thumbs span { aspect-ratio: 1; background: #f0f0f1 center / cover; border-radius: 3px; }
    8787.fx-gallery-field { flex-direction: column; align-items: stretch; }
     88.fx-cz-notice { display: block; padding: 8px 12px; margin: 0 0 12px; }
     89.fx-cz-notice .button { margin-top: 8px; }
  • flowix/1.1.2/assets/css/header-builder.css

    r353588 r356478  
    44.fx-header .fx-hrow-inner {
    55        display: grid !important;
    6         grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) !important;
     6        /* Sides never squeeze below their content (logo, icons); a centred menu wraps instead. */
     7        grid-template-columns: minmax(min-content, 1fr) minmax(0, auto) minmax(min-content, 1fr) !important;
    78        align-items: center;
    89        gap: var(--space-lg, 24px);
    … …  
    2829/* Items */
    2930.fx-hitem--menu { display: flex; justify-content: center; }
     31/* A long menu wraps onto a second line instead of running under the logo or icons. */
     32.fx-hzone .fx-hitem--menu { min-width: 0; }
     33.fx-hzone .fx-hitem--menu > ul { flex-wrap: wrap; row-gap: 2px; }
     34.fx-hzone--center .fx-hitem--menu > ul { justify-content: center; }
     35.fx-header .fx-desktop .fx-hitem--logo { flex-shrink: 0; white-space: nowrap; }
     36.fx-hitem--secondary-menu a { white-space: nowrap; }
    3037.fx-hspacer { flex: 1 1 auto; }
    3138.fx-hdivider { width: 1px; height: 22px; background: currentColor; opacity: 0.25; margin: 0 6px; }
    … …  
    99106   ================================================================ */
    100107.fx-footer .fx-fmain { grid-template-columns: var(--fx-fgrid, 2fr 1fr 1fr 1fr); }
     108/* Column layout presets (Footer Builder → Column layout). */
     109.fx-footer .fx-fmain.fx-fgrid--1fr { --fx-fgrid: 1fr; }
     110.fx-footer .fx-fmain.fx-fgrid--1fr-1fr { --fx-fgrid: 1fr 1fr; }
     111.fx-footer .fx-fmain.fx-fgrid--2fr-1fr { --fx-fgrid: 2fr 1fr; }
     112.fx-footer .fx-fmain.fx-fgrid--1fr-2fr { --fx-fgrid: 1fr 2fr; }
     113.fx-footer .fx-fmain.fx-fgrid--1fr-1fr-1fr { --fx-fgrid: 1fr 1fr 1fr; }
     114.fx-footer .fx-fmain.fx-fgrid--2fr-1fr-1fr { --fx-fgrid: 2fr 1fr 1fr; }
     115.fx-footer .fx-fmain.fx-fgrid--1fr-1fr-2fr { --fx-fgrid: 1fr 1fr 2fr; }
     116.fx-footer .fx-fmain.fx-fgrid--1fr-2fr-1fr { --fx-fgrid: 1fr 2fr 1fr; }
     117.fx-footer .fx-fmain.fx-fgrid--1fr-1fr-1fr-1fr { --fx-fgrid: 1fr 1fr 1fr 1fr; }
     118.fx-footer .fx-fmain.fx-fgrid--1p4fr-1fr-1fr-1fr { --fx-fgrid: 1.4fr 1fr 1fr 1fr; }
     119.fx-footer .fx-fmain.fx-fgrid--2fr-1fr-1fr-1fr { --fx-fgrid: 2fr 1fr 1fr 1fr; }
     120.fx-footer .fx-fmain.fx-fgrid--1fr-1fr-1fr-2fr { --fx-fgrid: 1fr 1fr 1fr 2fr; }
     121.fx-footer .fx-fmain.fx-fgrid--1fr-1fr-1fr-1fr-1fr { --fx-fgrid: 1fr 1fr 1fr 1fr 1fr; }
     122.fx-footer .fx-fmain.fx-fgrid--2fr-1fr-1fr-1fr-1fr { --fx-fgrid: 2fr 1fr 1fr 1fr 1fr; }
     123.fx-footer .fx-fmain.fx-fgrid--1fr-1fr-1fr-1fr-1fr-1fr { --fx-fgrid: 1fr 1fr 1fr 1fr 1fr 1fr; }
    101124.fx-footer .fx-fmain > div:last-child { display: block; } /* old theme rule hid the last column on tablets */
    102125.fx-fcol { min-width: 0; display: flex; flex-direction: column; gap: 22px; }
    … …  
    109132.fx-fcontact svg { flex: none; margin-top: 3px; opacity: 0.7; }
    110133.fx-fcontact a { color: inherit; }
    111 .fx-fnewsletter { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
    112 .fx-fnewsletter input[type="email"] { flex: 1 1 160px; min-width: 0; padding: 10px 12px; border-radius: var(--radius-sm, 4px); border: 1px solid color-mix(in srgb, var(--ft-tx, #faf7f2) 25%, transparent); background: color-mix(in srgb, var(--ft-tx, #faf7f2) 6%, transparent); color: inherit; }
    113134.fx-fbacktotop { display: inline-flex; align-items: center; gap: 6px; font-size: 0.75rem; color: inherit; text-decoration: none; opacity: 0.8; }
    114135.fx-fbacktotop:hover { opacity: 1; }
  • flowix/1.1.2/assets/css/home-sections.css

    r353588 r356478  
    66.fx-sec-empty { padding: 24px; border: 2px dashed var(--border-light, #e0d6c6); border-radius: 8px; text-align: center; color: var(--walnut, #6b4e33); }
    77
    8 /* Image banner */
    9 .fx-sec--banner {
    10         display: flex;
    11         align-items: center;
    12         background: var(--espresso, #2d1a0e) var(--fx-banner-img, none) center / cover no-repeat;
    13         color: var(--cream, #faf7f2);
    14         isolation: isolate;
    15 }
    16 .fx-sec--banner::before { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.2)); z-index: -1; }
    17 .fx-banner--s { min-height: 280px; }
    18 .fx-banner--m { min-height: 440px; }
    19 .fx-banner--l { min-height: 620px; }
    20 .fx-banner--full { min-height: 100vh; }
    21 .fx-banner-inner { width: 100%; display: flex; }
    22 .fx-banner--center .fx-banner-inner { justify-content: center; text-align: center; }
    23 .fx-banner--right .fx-banner-inner { justify-content: flex-end; }
    24 .fx-banner-content { max-width: 620px; padding: 48px 0; }
    25 .fx-banner-title { color: inherit; font-size: clamp(2rem, 5vw, 3.6rem); margin: 8px 0 14px; }
    26 .fx-banner-text { font-size: 1.1rem; opacity: 0.9; margin-bottom: 24px; }
    27 .fx-banner-eyebrow { color: var(--gold-light, #d4b57a); }
    288
    29 /* Image + text */
    30 .fx-ti-inner { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(28px, 5vw, 72px); align-items: center; }
    31 .fx-ti--right .fx-ti-media { order: 2; }
    32 .fx-ti-img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius-md, 8px); display: block; }
    33 .fx-ti-placeholder { background: var(--linen, #f0e8dc); }
    34 .fx-ti-content .section-heading { margin-bottom: 18px; text-align: left; }
    35 .fx-ti-text { margin-bottom: 24px; line-height: 1.75; }
    36 
    37 /* Feature icons */
    38 .fx-features { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 28px; }
    39 .fx-feature { text-align: center; padding: 28px 20px; border-radius: var(--radius-md, 8px); background: #fff; border: 1px solid var(--border-light, #e8e0d3); }
    40 .fx-feature-icon { display: inline-flex; width: 60px; height: 60px; align-items: center; justify-content: center; border-radius: 50%; background: var(--linen, #f0e8dc); color: var(--gold, #9b7b3a); margin-bottom: 14px; }
    41 .fx-feature-title { font-size: 1.15rem; margin-bottom: 6px; }
    42 .fx-feature-text { font-size: 0.92rem; opacity: 0.8; margin: 0; }
    43 
    44 /* Call to action & countdown */
    45 .fx-sec--cta, .fx-sec--countdown { background: var(--linen, #f0e8dc); }
    46 .fx-cta-inner { text-align: center; max-width: 760px; }
    47 .fx-cta-text { font-size: 1.05rem; margin-bottom: 8px; }
    48 .fx-countdown { display: flex; justify-content: center; gap: clamp(8px, 2vw, 18px); margin-top: 22px; flex-wrap: wrap; }
    49 .fx-cd-unit { min-width: 84px; padding: 16px 10px; background: var(--espresso, #2d1a0e); color: var(--cream, #faf7f2); border-radius: var(--radius-md, 8px); display: flex; flex-direction: column; align-items: center; }
    50 .fx-cd-num { font-family: var(--font-display); font-size: clamp(1.8rem, 4vw, 2.6rem); line-height: 1; font-variant-numeric: tabular-nums; }
    51 .fx-cd-label { font-family: var(--font-ui); font-size: 0.62rem; letter-spacing: 0.16em; text-transform: uppercase; margin-top: 6px; opacity: 0.75; }
    52 .fx-countdown.is-over { font-size: 1.2rem; font-weight: 600; }
    53 
    54 /* Ticker */
    55 .fx-sec--ticker { overflow: hidden; background: var(--espresso, #2d1a0e); color: var(--cream, #faf7f2); padding: 14px 0; }
    56 .fx-ticker-track { display: flex; width: max-content; animation: fx-ticker var(--fx-ticker-dur, 30s) linear infinite; }
    57 .fx-ticker--right .fx-ticker-track { animation-direction: reverse; }
    58 .fx-sec--ticker:hover .fx-ticker-track { animation-play-state: paused; }
    59 .fx-ticker-run { display: flex; align-items: center; flex: none; }
    60 .fx-ticker-item { font-family: var(--font-ui); font-size: 0.8rem; letter-spacing: 0.14em; text-transform: uppercase; white-space: nowrap; padding: 0 22px; }
    61 .fx-ticker-sep { color: var(--gold-light, #d4b57a); }
    62 @keyframes fx-ticker { from { transform: translateX(0); } to { transform: translateX(-50%); } }
    63 @media (prefers-reduced-motion: reduce) { .fx-ticker-track { animation: none; } }
    64 
    65 /* Video */
    66 .fx-video { position: relative; border-radius: var(--radius-md, 8px); overflow: hidden; background: #000; }
    67 .fx-video video, .fx-video iframe { display: block; width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0; }
    68 .fx-video-caption { text-align: center; margin-top: 12px; font-size: 0.9rem; opacity: 0.8; }
    69 
    70 @media (max-width: 782px) {
    71         .fx-ti-inner { grid-template-columns: 1fr; }
    72         .fx-ti--right .fx-ti-media { order: 0; }
    73 }
    74 .fx-video .fx-sec-empty { color: #fff; border-color: rgba(255, 255, 255, 0.4); margin: 0; }
     9/* Spacer section heights (Homepage Sections → Spacer), in steps of 10px. */
     10.fx-spacer--0 { height: 0px; }
     11.fx-spacer--10 { height: 10px; }
     12.fx-spacer--20 { height: 20px; }
     13.fx-spacer--30 { height: 30px; }
     14.fx-spacer--40 { height: 40px; }
     15.fx-spacer--50 { height: 50px; }
     16.fx-spacer--60 { height: 60px; }
     17.fx-spacer--70 { height: 70px; }
     18.fx-spacer--80 { height: 80px; }
     19.fx-spacer--90 { height: 90px; }
     20.fx-spacer--100 { height: 100px; }
     21.fx-spacer--110 { height: 110px; }
     22.fx-spacer--120 { height: 120px; }
     23.fx-spacer--130 { height: 130px; }
     24.fx-spacer--140 { height: 140px; }
     25.fx-spacer--150 { height: 150px; }
     26.fx-spacer--160 { height: 160px; }
     27.fx-spacer--170 { height: 170px; }
     28.fx-spacer--180 { height: 180px; }
     29.fx-spacer--190 { height: 190px; }
     30.fx-spacer--200 { height: 200px; }
     31.fx-spacer--210 { height: 210px; }
     32.fx-spacer--220 { height: 220px; }
     33.fx-spacer--230 { height: 230px; }
     34.fx-spacer--240 { height: 240px; }
     35.fx-spacer--250 { height: 250px; }
     36.fx-spacer--260 { height: 260px; }
     37.fx-spacer--270 { height: 270px; }
     38.fx-spacer--280 { height: 280px; }
     39.fx-spacer--290 { height: 290px; }
     40.fx-spacer--300 { height: 300px; }
     41.fx-spacer--310 { height: 310px; }
     42.fx-spacer--320 { height: 320px; }
     43.fx-spacer--330 { height: 330px; }
     44.fx-spacer--340 { height: 340px; }
     45.fx-spacer--350 { height: 350px; }
     46.fx-spacer--360 { height: 360px; }
     47.fx-spacer--370 { height: 370px; }
     48.fx-spacer--380 { height: 380px; }
     49.fx-spacer--390 { height: 390px; }
     50.fx-spacer--400 { height: 400px; }
     51.fx-spacer--410 { height: 410px; }
     52.fx-spacer--420 { height: 420px; }
     53.fx-spacer--430 { height: 430px; }
     54.fx-spacer--440 { height: 440px; }
     55.fx-spacer--450 { height: 450px; }
     56.fx-spacer--460 { height: 460px; }
     57.fx-spacer--470 { height: 470px; }
     58.fx-spacer--480 { height: 480px; }
     59.fx-spacer--490 { height: 490px; }
     60.fx-spacer--500 { height: 500px; }
     61.fx-spacer--510 { height: 510px; }
     62.fx-spacer--520 { height: 520px; }
     63.fx-spacer--530 { height: 530px; }
     64.fx-spacer--540 { height: 540px; }
     65.fx-spacer--550 { height: 550px; }
     66.fx-spacer--560 { height: 560px; }
     67.fx-spacer--570 { height: 570px; }
     68.fx-spacer--580 { height: 580px; }
     69.fx-spacer--590 { height: 590px; }
     70.fx-spacer--600 { height: 600px; }
  • flowix/1.1.2/assets/css/shop-features.css

    r353588 r356478  
    1111.woocommerce div.product > .fx-badges { top: 16px; left: 16px; }
    1212
    13 /* ── Card buttons (wishlist, quick view) ── */
    14 .fx-card-btns { position: absolute; top: 12px; right: 12px; display: flex; flex-direction: column; gap: 8px; z-index: 4; }
    15 .fx-card-btn {
    16         width: 38px; height: 38px; border-radius: 50%; border: 0; padding: 0; cursor: pointer;
    17         display: grid; place-items: center;
    18         background: var(--bg-surface, #fff); color: var(--tx-strong, #1c0f06);
    19         box-shadow: 0 4px 14px var(--shadow-warm, rgba(0,0,0,.12));
    20         opacity: 0; transform: translateX(8px);
    21         transition: opacity .25s, transform .25s, background .2s, color .2s;
    22 }
    23 .fx-card-btn:nth-child(2) { transition-delay: .05s; }
    24 .product-card:hover .fx-card-btn, li.product:hover .fx-card-btn, .fx-card-btn:focus-visible, .fx-wl-btn.is-on { opacity: 1; transform: none; }
    25 @media (hover: none) { .fx-card-btn { opacity: 1; transform: none; } }
    26 .fx-card-btn:hover { background: var(--tx-strong, #1c0f06); color: var(--bg-page, #fff); }
    27 .fx-card-btn svg { pointer-events: none; }
    28 .fx-wl-btn.is-on { color: var(--terracotta, #c0392b); }
    29 .fx-wl-btn.is-on svg { fill: currentColor; }
    30 .fx-wl-btn.is-on:hover { background: var(--bg-surface, #fff); color: var(--terracotta, #c0392b); }
    31 .fx-pop svg { animation: fx-pop .45s cubic-bezier(.3,1.6,.5,1); }
    32 @keyframes fx-pop { 0% { transform: scale(1); } 40% { transform: scale(1.45); } 100% { transform: scale(1); } }
    33 .fx-card-btn.is-loading { animation: fx-pulse 0.8s infinite alternate; }
    34 @keyframes fx-pulse { to { opacity: .45; } }
    35 
    36 /* Colour dots on cards */
    37 .fx-card-dots { display: flex; gap: 5px; margin-top: 4px; flex-wrap: wrap; }
    38 li.product .fx-card-dots { justify-content: inherit; padding: 0 0 6px; }
    39 .fx-dot { width: 12px; height: 12px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(0,0,0,.15); }
    40 
    41 /* ── Quick view ── */
    42 html.fx-qv-lock { overflow: hidden; }
    43 .fx-qv-modal { position: fixed; inset: 0; z-index: 100000; display: grid; place-items: center; padding: 16px; }
    44 .fx-qv-backdrop { position: absolute; inset: 0; background: rgba(15,10,5,.55); backdrop-filter: blur(3px); opacity: 0; transition: opacity .25s; }
    45 .fx-qv-dialog {
    46         position: relative; width: min(960px, 100%); max-height: calc(100vh - 32px); overflow: auto;
    47         background: var(--bg-page, #fff); color: var(--tx-strong, #1c0f06); border-radius: var(--radius-lg, 8px);
    48         box-shadow: 0 30px 80px rgba(0,0,0,.35);
    49         opacity: 0; transform: translateY(20px) scale(.97); transition: opacity .25s, transform .25s;
    50 }
    51 .fx-qv-modal.is-open .fx-qv-backdrop, .fx-qv-modal.is-open .fx-qv-dialog { opacity: 1; transform: none; }
    52 .fx-qv-close { position: absolute; top: 10px; right: 12px; z-index: 5; width: 40px; height: 40px; border: 0; border-radius: 50%; background: var(--bg-surface, #fff); color: inherit; font-size: 26px; line-height: 1; cursor: pointer; box-shadow: 0 2px 10px rgba(0,0,0,.12); }
    53 .fx-qv { display: grid; grid-template-columns: 1fr 1fr; }
    54 .fx-qv-media { position: relative; background: var(--bg-tint2, var(--bg-tint, #f6f1ea)); }
    55 .fx-qv-main img { display: block; width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; }
    56 .fx-qv-thumbs { display: flex; gap: 6px; padding: 8px; overflow-x: auto; }
    57 .fx-qv-thumb { flex: 0 0 56px; padding: 0; border: 2px solid transparent; border-radius: 4px; overflow: hidden; cursor: pointer; background: none; }
    58 .fx-qv-thumb.is-on, .fx-qv-thumb:hover { border-color: var(--tx-strong, #1c0f06); }
    59 .fx-qv-thumb img { display: block; width: 100%; height: 56px; object-fit: cover; }
    60 .fx-qv-summary { padding: 36px 32px 28px; display: flex; flex-direction: column; gap: 10px; }
    61 .fx-qv-title { margin: 0 30px 0 0; }
    62 .fx-qv-summary .price { font-size: 1.3rem; }
    63 .fx-qv-summary form.cart { margin: 8px 0 0; }
    64 .fx-qv-more { margin-top: auto; font-weight: 600; font-size: .9rem; }
    65 .fx-qv-dialog .fx-qv.product div.summary { width: auto !important; float: none !important; margin: 0 !important; }
    66 .fx-qv .fx-qv-summary .woocommerce-product-details__short-description, .fx-qv-summary p { max-width: none; }
    67 .fx-qv table.variations, .fx-qv table.variations tbody, .fx-qv table.variations tr, .fx-qv table.variations th, .fx-qv table.variations td { display: block; width: auto; padding: 0; text-align: left; }
    68 .fx-qv table.variations tr { margin-bottom: 14px; }
    69 .fx-qv table.variations th { margin-bottom: 8px; }
    70 .fx-qv form.cart .woocommerce-variation-add-to-cart, .fx-qv form.cart:not(.variations_form) { display: flex; flex-wrap: wrap; gap: 8px; align-items: stretch; }
    71 .fx-qv form.cart .fx-buy-now { margin-left: 0; }
    72 @media (max-width: 700px) { .fx-qv { grid-template-columns: 1fr; } .fx-qv-summary { padding: 22px 18px; } }
    73 
    74 /* ── Swatches ── */
    75 .fx-swatch-wrap select { position: absolute !important; width: 1px !important; height: 1px !important; opacity: 0 !important; pointer-events: none; }
    76 .fx-swatch-wrap { position: relative; }
    77 .fx-swatches { display: flex; flex-wrap: wrap; gap: 8px; }
    78 .fx-swatch {
    79         --fx-sw: transparent;
    80         min-width: 36px; height: 36px; padding: 0; border: 1px solid var(--bd-light, #ddd); border-radius: 50%;
    81         background: var(--fx-sw); color: var(--tx-strong, #1c0f06); cursor: pointer; overflow: hidden;
    82         display: inline-grid; place-items: center; font: 600 .8rem/1 var(--font-ui, inherit);
    83         box-shadow: 0 0 0 2px var(--bg-page, #fff) inset; transition: box-shadow .15s, transform .15s, border-color .15s;
    84 }
    85 .fx-swatches--rounded .fx-swatch { border-radius: 8px; }
    86 .fx-swatches--square .fx-swatch { border-radius: 0; }
    87 .fx-swatch.is-label { padding: 0 14px; border-radius: 999px; background: var(--bg-surface, #fff); box-shadow: none; }
    88 .fx-swatches--rounded .fx-swatch.is-label { border-radius: 8px; }
    89 .fx-swatches--square .fx-swatch.is-label { border-radius: 0; }
    90 .fx-swatch.is-image img { width: 100%; height: 100%; object-fit: cover; }
    91 .fx-swatch.is-image { width: 44px; height: 44px; }
    92 .fx-swatch:hover { transform: translateY(-1px); border-color: var(--tx-strong, #1c0f06); }
    93 .fx-swatch.is-selected { border-color: var(--tx-strong, #1c0f06); box-shadow: 0 0 0 2px var(--bg-page, #fff) inset, 0 0 0 2px var(--tx-strong, #1c0f06); }
    94 .fx-swatch.is-label.is-selected { background: var(--tx-strong, #1c0f06); color: var(--bg-page, #fff); box-shadow: none; }
    95 .fx-swatch.is-disabled { opacity: .3; cursor: not-allowed; position: relative; }
    96 .fx-swatch.is-disabled::after { content: ""; position: absolute; left: 50%; top: -4px; bottom: -4px; width: 1px; background: currentColor; transform: rotate(45deg); }
    97 .fx-swatch-chosen { display: block; margin-top: 6px; font-size: .85rem; color: var(--tx-muted, #6b5a4c); min-height: 1.2em; }
    98 .woocommerce div.product form.cart .variations td, .woocommerce div.product form.cart .variations th { vertical-align: top; padding-bottom: 14px; }
    99 
    100 /* ── Sale countdown & urgency ── */
    101 .fx-sale-cd { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 14px; margin: 4px 0 16px; padding: 12px 14px; border-radius: var(--radius-md, 4px); background: color-mix(in srgb, var(--terracotta, #c0392b) 9%, transparent); color: var(--terracotta, #c0392b); font-weight: 600; }
    102 .fx-sale-cd-label { display: inline-flex; align-items: center; gap: 6px; }
    103 .fx-sale-cd .fx-countdown--inline { display: flex; gap: 6px; }
    104 .fx-sale-cd .fx-cd-unit { display: inline-flex; align-items: baseline; gap: 1px; padding: 4px 7px; border-radius: 4px; background: var(--bg-page, #fff); color: var(--tx-strong, #1c0f06); font-variant-numeric: tabular-nums; }
    105 .fx-sale-cd .fx-cd-num { font-size: 1rem; font-weight: 700; }
    106 .fx-sale-cd .fx-cd-label { font-size: .7rem; opacity: .7; }
    107 .fx-urgency { margin: 0 0 16px; }
    108 .fx-urgency p { display: flex; align-items: center; gap: 6px; margin: 0 0 6px; font-weight: 600; color: var(--terracotta, #c0392b); }
    109 .fx-urgency-bar { display: block; height: 6px; border-radius: 6px; background: var(--bd-light, #eee); overflow: hidden; }
    110 .fx-urgency-bar span { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--terracotta, #c0392b), var(--amber, #d89a3c)); }
    111 
    112 /* ── Buy now ── */
    113 form.cart .fx-buy-now, .woocommerce div.product form.cart .fx-buy-now {
    114         margin-left: 8px; padding: 15px 30px; flex: 1;
    115         background: var(--fx-accent, var(--gold, #b8893a)); border: 1px solid var(--fx-accent, var(--gold, #b8893a)); color: #fff !important;
    116         font: 700 .75rem/1 var(--font-ui, inherit); letter-spacing: .2em; text-transform: uppercase; cursor: pointer;
    117 }
    118 form.cart .fx-buy-now:hover { filter: brightness(1.08); }
    119 .woocommerce-variation-add-to-cart-disabled .fx-buy-now { opacity: .5; pointer-events: none; }
    120 li.product .fx-card-dots { padding: 0 18px 6px; }
    121 form.cart.variations_form .fx-buy-now, form.cart .fx-buy-now { vertical-align: middle; }
    122 @media (max-width: 480px) { form.cart .fx-buy-now { width: 100%; margin: 10px 0 0 !important; } }
    12313
    12414/* ── Delivery & trust ── */
    … …  
    17565@media (max-width: 600px) { .fx-toast { right: 16px; bottom: 16px; } }
    17666
    177 /* ── Load more ── */
    178 .fx-loadmore { display: flex; justify-content: center; margin: 32px 0; }
    179 .fx-new-item { opacity: 0; transform: translateY(12px); }
    180 ul.products > *, .products-grid > * { transition: opacity .4s, transform .4s; }
    18167
    182 /* ── Product lists (wishlist, recently viewed) ── */
    183 .fx-plist.is-loading .fx-plist-grid { min-height: 200px; opacity: .5; }
    184 .fx-plist-empty { text-align: center; padding: 40px 16px; color: var(--tx-muted, #6b5a4c); }
    185 .fx-form-error { margin-bottom: 12px; }
    186 
    187 /* Options table above the price/add-to-cart row (WooCommerce puts them side by side, which squeezes swatches). */
    188 .woocommerce div.product form.variations_form, .fx-qv form.variations_form { display: block; }
    189 .woocommerce div.product form.variations_form table.variations { width: 100%; margin-bottom: 8px; }
    190 .woocommerce div.product form.variations_form .woocommerce-variation-add-to-cart { display: flex; flex-wrap: wrap; gap: 8px; align-items: stretch; }
    191 .woocommerce div.product form.variations_form .woocommerce-variation-add-to-cart .fx-buy-now { margin-left: 0; }
    192 .woocommerce div.product form.variations_form .woocommerce-variation-description p { margin: 0 0 12px; }
    193 .fx-qv-media { align-self: start; }
    194 .fx-qv .variations th, .fx-qv .variations td, .fx-qv .variations label { padding: 0 !important; margin: 0 !important; line-height: 1.4; }
    195 .fx-qv table.variations th { margin-bottom: 8px !important; }
    196 .fx-qv table.variations { margin: 6px 0 14px !important; }
    197 
    198 /* Phones: quantity + Add to cart on one row, Buy now full width underneath. */
    199 @media (max-width: 600px) {
    200         .woocommerce div.product form.cart:not(.variations_form), .woocommerce div.product form.cart .woocommerce-variation-add-to-cart { display: flex; flex-wrap: wrap; gap: 8px; }
    201         .woocommerce div.product form.cart .single_add_to_cart_button { flex: 1 1 auto; padding-left: 16px !important; padding-right: 16px !important; white-space: nowrap; }
    202         .woocommerce div.product form.cart .fx-buy-now { flex: 1 1 100%; margin: 0 !important; }
    203 }
     68/* Free-delivery bar fill, in steps of 5%. */
     69.fx-fsb-fill--0 { width: 0%; }
     70.fx-fsb-fill--5 { width: 5%; }
     71.fx-fsb-fill--10 { width: 10%; }
     72.fx-fsb-fill--15 { width: 15%; }
     73.fx-fsb-fill--20 { width: 20%; }
     74.fx-fsb-fill--25 { width: 25%; }
     75.fx-fsb-fill--30 { width: 30%; }
     76.fx-fsb-fill--35 { width: 35%; }
     77.fx-fsb-fill--40 { width: 40%; }
     78.fx-fsb-fill--45 { width: 45%; }
     79.fx-fsb-fill--50 { width: 50%; }
     80.fx-fsb-fill--55 { width: 55%; }
     81.fx-fsb-fill--60 { width: 60%; }
     82.fx-fsb-fill--65 { width: 65%; }
     83.fx-fsb-fill--70 { width: 70%; }
     84.fx-fsb-fill--75 { width: 75%; }
     85.fx-fsb-fill--80 { width: 80%; }
     86.fx-fsb-fill--85 { width: 85%; }
     87.fx-fsb-fill--90 { width: 90%; }
     88.fx-fsb-fill--95 { width: 95%; }
     89.fx-fsb-fill--100 { width: 100%; }
  • flowix/1.1.2/assets/js/builder-controls.js

    r353748 r356478  
    2424                        return fallback;
    2525                }
     26        }
     27
     28        /** Redraw a builder when its setting is changed from elsewhere (undo, a look, import). */
     29        function follow( builder, load ) {
     30                builder.written = builder.control.setting.get();
     31                builder.control.setting.bind( function( v ) {
     32                        if ( v === builder.written ) {
     33                                return;
     34                        }
     35                        builder.written = v;
     36                        load( v );
     37                        builder.open = null;
     38                        builder.render();
     39                } );
    2640        }
    2741
    … …  
    3953                this.render();
    4054                var self = this;
     55                follow( this, function( v ) {
     56                        self.cfg = $.extend( true, {}, B.header.defaults, parse( v, {} ) );
     57                } );
    4158                if ( api.previewedDevice ) {
    4259                        api.previewedDevice.bind( function( d ) {
    … …  
    5168
    5269        HeaderBuilder.prototype.save = function() {
    53                 this.control.setting.set( JSON.stringify( this.cfg ) );
     70                this.written = JSON.stringify( this.cfg );
     71                this.control.setting.set( this.written );
    5472        };
    5573
    … …  
    262280                this.open = null;
    263281                this.render();
     282                var self = this;
     283                follow( this, function( v ) {
     284                        self.cfg = $.extend( true, {}, B.footer.defaults, parse( v, {} ) );
     285                } );
    264286        }
    265287
    … …  
    268290
    269291        FooterBuilder.prototype.save = function() {
    270                 this.control.setting.set( JSON.stringify( this.cfg ) );
     292                this.written = JSON.stringify( this.cfg );
     293                this.control.setting.set( this.written );
    271294        };
    272295
    … …  
    438461                categories: 'flowix_categories_section',
    439462                featured: 'flowix_featured',
    440                 testimonials: 'flowix_testimonials',
    441                 vendor: 'flowix_vendor_cta',
    442463                page_content: 'static_front_page',
    443464        };
    … …  
    461482                        self.renderNotice();
    462483                } );
     484                follow( this, function( v ) {
     485                        var l = parse( v, null );
     486                        self.list = Array.isArray( l ) ? l : $.extend( true, [], B.sections.initial );
     487                        self.list.forEach( function( sec ) {
     488                                if ( ! sec.d || Array.isArray( sec.d ) ) {
     489                                        sec.d = {};
     490                                }
     491                        } );
     492                } );
    463493        }
    464494
    … …  
    467497                clearTimeout( this.timer );
    468498                var run = function() {
    469                         self.control.setting.set( JSON.stringify( self.list ) );
     499                        self.written = JSON.stringify( self.list );
     500                        self.control.setting.set( self.written );
    470501                };
    471502                if ( later ) {
  • flowix/1.1.2/assets/js/customizer-controls.js

    r319286 r356478  
    1919                        'flowix_cats',
    2020                        'flowix_feat',
    21                         'flowix_testi',
    22                         'flowix_vendor',
    23                         'flowix_news',
    2421                        'flowix_footer_sec',
    2522                        'flowix_header',
  • flowix/1.1.2/assets/js/customizer-preview.js

    r319286 r356478  
    191191
    192192        /* ── FOOTER ───────────────────────────────────────────────────── */
    193         bind( 'flowix_footer_tagline', function( val ) {
    194                 document.querySelectorAll( '.footer-brand-desc' ).forEach( function(e){e.textContent=val;} );
    195         } );
    196193        bind( 'flowix_footer_text_color', function( val ) {
    197194                var footer = document.querySelector( '#colophon' );
    … …  
    205202        bindSectionBg( 'flowix_cats',       '#categories-section' );
    206203        bindSectionBg( 'flowix_feat',       '#featured-section' );
    207         bindSectionBg( 'flowix_testi',      '#testimonials-section' );
    208         bindSectionBg( 'flowix_vendor',     '.vendor-cta' );
    209         bindSectionBg( 'flowix_news',       '.newsletter-section' );
    210204        bindSectionBg( 'flowix_footer_sec', '#colophon' );
    211205
  • flowix/1.1.2/assets/js/element-styles-controls.js

    r353588 r356478  
    527527                                        $wrap.addClass( 'is-set' );
    528528                                } );
    529                                 var $transparent = el( 'button', { type: 'button', class: 'button button-small flowix-transparent', text: 'transparent' } ).on( 'click', function() {
     529                                var $transparent = el( 'button', { type: 'button', class: 'button button-small flowix-transparent', text: T.transparent } ).on( 'click', function() {
    530530                                        tok = null;
    531531                                        self.write( key, 'transparent', true );
    … …  
    811811                        var $mock = el( 'div', { class: 'flowix-look-mock' } ).css( { background: c.background, color: c.text, fontFamily: "'" + f.body + "', sans-serif" } );
    812812                        $mock.append( el( 'div', { class: 'flowix-look-bar' } ).css( { background: c.header || c.primary, borderBottom: '1px solid rgba(0,0,0,.08)' } ).append(
    813                                 el( 'span', { text: 'Shop' } ).css( { color: c.header_text || '#fff', fontFamily: "'" + f.display + "', serif" } ),
     813                                el( 'span', { text: T.lookShop } ).css( { color: c.header_text || '#fff', fontFamily: "'" + f.display + "', serif" } ),
    814814                                el( 'i' ).css( 'background', c.accent )
    815815                        ) );
    816816                        $mock.append( el( 'div', { class: 'flowix-look-hero' } ).css( 'background', c.hero || c.primary ).append(
    817                                 el( 'b', { text: 'Live better.' } ).css( { fontFamily: "'" + f.display + "', serif", color: c.hero_text || '#fff' } ),
    818                                 el( 'span', { text: 'Shop now' } ).css( { background: c.accent, color: c.background, borderRadius: Math.min( 30, r * 2 ) + 'px' } )
     817                                el( 'b', { text: T.lookTagline } ).css( { fontFamily: "'" + f.display + "', serif", color: c.hero_text || '#fff' } ),
     818                                el( 'span', { text: T.lookButton } ).css( { background: c.accent, color: c.background, borderRadius: Math.min( 30, r * 2 ) + 'px' } )
    819819                        ) );
    820820                        var $cards = el( 'div', { class: 'flowix-look-cards' } );
    … …  
    829829                        var $apply = el( 'button', { type: 'button', class: 'button button-primary', text: T.applyLook } ).on( 'click', function() {
    830830                                if ( window.confirm( T.confirmLook.replace( '%s', look.name ) ) ) {
     831                                        historyName( 'look', look.name );
    831832                                        applyLook( look, layoutToo );
    832833                                        $root.find( '.flowix-look' ).removeClass( 'is-applied' );
    … …  
    868869
    869870
     871        /** Names the next undo step (see flowix-history.js), e.g. "Look: Surf Club". */
     872        function historyName( kind, detail ) {
     873                if ( window.flowixHistory ) {
     874                        window.flowixHistory.name( kind, detail );
     875                }
     876        }
     877
    870878        function applyLook( look, layout ) {
    871879                api( 'flowix_brand' ).set( JSON.stringify( look.brand ) );
    … …  
    904912                var cur = parse( api( 'flowix_home_sections' ).get() );
    905913                if ( ! Array.isArray( cur ) || ! cur.length ) {
    906                         cur = [ 'hero', 'trust', 'categories', 'featured', 'testimonials', 'vendor' ].map( function( t ) {
    907                                 return { id: t, type: t, on: true, d: {} };
    908                         } );
     914                        cur = window.flowixBuilder && window.flowixBuilder.sections ? ( window.flowixBuilder.sections.initial || [] ) : [];
    909915                }
    910916                var defs = window.flowixBuilder && window.flowixBuilder.sections ? window.flowixBuilder.sections.defaults || {} : {};
    … …  
    946952                var b = $.extend( true, {}, D.brand.defaults, parse( control.setting.get() ) );
    947953                var timer;
     954                var written = control.setting.get();
    948955                var save = function( now ) {
    949956                        clearTimeout( timer );
    950957                        var run = function() {
    951                                 control.setting.set( JSON.stringify( b ) );
     958                                written = JSON.stringify( b );
     959                                control.setting.set( written );
    952960                        };
    953961                        if ( now ) {
    … …  
    10931101                };
    10941102                render();
     1103                // Changed from elsewhere (undo, a look, import): redraw with the new values.
     1104                control.setting.bind( function( v ) {
     1105                        if ( v !== written ) {
     1106                                clearTimeout( timer );
     1107                                written = v;
     1108                                b = $.extend( true, {}, D.brand.defaults, parse( v ) );
     1109                                render();
     1110                        }
     1111                } );
    10951112        }
    10961113
    … …  
    11531170                                }
    11541171                                var n = 0;
     1172                                historyName( 'import', file.name );
    11551173                                Object.keys( data.styles ).forEach( function( id ) {
    11561174                                        if ( api( id ) && ( id.indexOf( 'flowix_el_' ) === 0 || [ 'flowix_custom_elements', 'flowix_site_fx', 'flowix_brand', 'flowix_styles', 'flowix_header', 'flowix_footer', 'flowix_home_sections' ].indexOf( id ) !== -1 ) ) {
    … …  
    11691187                                return;
    11701188                        }
     1189                        historyName( 'resetAll' );
    11711190                        flowixSettingIds().forEach( function( id ) {
    11721191                                api( id ).set( id === 'flowix_custom_elements' ? '[]' : '' );
  • flowix/1.1.2/assets/js/element-styles-preview.js

    r353588 r356478  
    5555
    5656        function setStyleTag( key, css ) {
    57                 var $tag = $( '#flowix-el-' + key );
     57                // Element stylesheets are enqueued as "flowix-el-{key}" (WordPress prints them as #flowix-el-{key}-inline-css).
     58                var $tag = $( '#flowix-el-' + key + '-inline-css, #flowix-el-' + key ).first();
    5859                if ( ! $tag.length ) {
    59                         $tag = $( '<style id="flowix-el-' + key + '"></style>' ).appendTo( 'head' );
     60                        $tag = $( '<style id="flowix-el-' + key + '-inline-css"></style>' ).appendTo( 'head' );
    6061                }
    6162                $tag.text( css );
    … …  
    8384                                setStyleTag( key, r.css );
    8485                                if ( key === 'brand' ) {
    85                                         $( '#flowix-brand' ).text( r.css );
     86                                        $( '#flowix-brand-inline-css' ).text( r.css );
    8687                                        if ( r.dark ) {
    8788                                                document.documentElement.setAttribute( 'data-flowix-dark', r.dark.mode );
  • flowix/1.1.2/assets/js/main.js

    r353588 r356478  
    2525        function on( el, ev, fn ) { if ( el ) el.addEventListener( ev, fn ); }
    2626
     27        /* ── KEYBOARD: keep focus inside an open panel, give it back on close ── */
     28        var FOCUSABLE = 'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
     29        var trapPanel = null;
     30        var trapReturn = null;
     31        function focusables( panel ) {
     32                return Array.prototype.filter.call( panel.querySelectorAll( FOCUSABLE ), function ( el ) {
     33                        return el.offsetWidth || el.offsetHeight || el.getClientRects().length;
     34                } );
     35        }
     36        function trapOpen( panel, first ) {
     37                trapPanel = panel;
     38                trapReturn = document.activeElement;
     39                panel.removeAttribute( 'aria-hidden' );
     40                setTimeout( function () {
     41                        var list = focusables( panel );
     42                        var target = first || list[ 0 ] || panel;
     43                        if ( target === panel ) panel.setAttribute( 'tabindex', '-1' );
     44                        target.focus();
     45                }, 60 );
     46        }
     47        function trapClose( panel ) {
     48                panel.setAttribute( 'aria-hidden', 'true' );
     49                if ( trapPanel !== panel ) return;
     50                trapPanel = null;
     51                if ( trapReturn && trapReturn.focus && document.contains( trapReturn ) ) trapReturn.focus();
     52                trapReturn = null;
     53        }
     54        document.addEventListener( 'keydown', function ( e ) {
     55                if ( e.key !== 'Tab' || ! trapPanel ) return;
     56                var list = focusables( trapPanel );
     57                if ( ! list.length ) { e.preventDefault(); return; }
     58                var first = list[ 0 ];
     59                var last = list[ list.length - 1 ];
     60                if ( e.shiftKey && ( document.activeElement === first || ! trapPanel.contains( document.activeElement ) ) ) {
     61                        e.preventDefault();
     62                        last.focus();
     63                } else if ( ! e.shiftKey && ( document.activeElement === last || ! trapPanel.contains( document.activeElement ) ) ) {
     64                        e.preventDefault();
     65                        first.focus();
     66                }
     67        } );
     68        [ searchOverlay, miniCartDrawer, mobileDrawer ].forEach( function ( panel ) {
     69                if ( panel ) panel.setAttribute( 'aria-hidden', 'true' );
     70        } );
     71
    2772        /* ── SEARCH ── */
    2873        function openSearch() {
    … …  
    3176                if ( searchOpen ) searchOpen.setAttribute( 'aria-expanded', 'true' );
    3277                lockScroll();
    33                 setTimeout( function () {
    34                         var inp = searchOverlay.querySelector( 'input[type="search"]' );
    35                         if ( inp ) inp.focus();
    36                 }, 100 );
     78                trapOpen( searchOverlay, searchOverlay.querySelector( 'input[type="search"]' ) );
    3779        }
    3880        function closeSearch() {
    39                 if ( ! searchOverlay ) return;
     81                if ( ! searchOverlay || ! searchOverlay.classList.contains( 'open' ) ) return;
    4082                searchOverlay.classList.remove( 'open' );
    4183                if ( searchOpen ) searchOpen.setAttribute( 'aria-expanded', 'false' );
    4284                unlockScroll();
     85                trapClose( searchOverlay );
    4386        }
    4487        on( searchClose, 'click', closeSearch );
    … …  
    70113                if ( miniCartOpen ) miniCartOpen.setAttribute( 'aria-expanded', 'true' );
    71114                lockScroll();
     115                trapOpen( miniCartDrawer, miniCartClose );
    72116        }
    73117        function closeMiniCart() {
    74                 if ( ! miniCartDrawer ) return;
     118                if ( ! miniCartDrawer || ! miniCartDrawer.classList.contains( 'open' ) ) return;
    75119                miniCartDrawer.classList.remove( 'open' );
    76120                if ( miniCartOverlay ) miniCartOverlay.classList.remove( 'open' );
    77121                if ( miniCartOpen ) miniCartOpen.setAttribute( 'aria-expanded', 'false' );
    78122                unlockScroll();
     123                trapClose( miniCartDrawer );
    79124        }
    80125        window.flowixOpenMiniCart = function () { openMiniCart(); refreshMiniCart(); };
    … …  
    89134                if ( navHamburger ) { navHamburger.classList.add( 'active' ); navHamburger.setAttribute( 'aria-expanded', 'true' ); }
    90135                lockScroll();
     136                trapOpen( mobileDrawer, mobileClose );
    91137        }
    92138        function closeMobileNav() {
    93                 if ( ! mobileDrawer ) return;
     139                if ( ! mobileDrawer || ! mobileDrawer.classList.contains( 'open' ) ) return;
    94140                mobileDrawer.classList.remove( 'open' );
    95141                if ( mobileOverlay ) mobileOverlay.classList.remove( 'open' );
    96142                if ( navHamburger ) { navHamburger.classList.remove( 'active' ); navHamburger.setAttribute( 'aria-expanded', 'false' ); }
    97143                unlockScroll();
     144                trapClose( mobileDrawer );
    98145        }
    99146        on( mobileClose, 'click', closeMobileNav );
    … …  
    166213        }
    167214
    168         /* ── COUNTDOWNS (homepage "Sale Countdown" sections) ── */
    169         ( function () {
    170                 function pad( n ) { return ( n < 10 ? '0' : '' ) + n; }
    171                 function tick() {
    172                         document.querySelectorAll( '.fx-countdown[data-end]' ).forEach( function ( el ) {
    173                                 var left = parseInt( el.getAttribute( 'data-end' ), 10 ) - Date.now();
    174                                 if ( ! left || left <= 0 ) {
    175                                         if ( ! el.classList.contains( 'is-over' ) ) {
    176                                                 el.classList.add( 'is-over' );
    177                                                 el.textContent = el.getAttribute( 'data-after' ) || '';
    178                                         }
    179                                         return;
    180                                 }
    181                                 var s = Math.floor( left / 1000 );
    182                                 var v = { d: Math.floor( s / 86400 ), h: Math.floor( s % 86400 / 3600 ), m: Math.floor( s % 3600 / 60 ), s: s % 60 };
    183                                 el.querySelectorAll( '[data-u]' ).forEach( function ( n ) { n.textContent = pad( v[ n.getAttribute( 'data-u' ) ] ); } );
    184                         } );
    185                 }
    186                 tick();
    187                 setInterval( tick, 1000 );
    188         } )();
    189215
    190216        /* ── SMOOTH SCROLL ANCHORS ── */
    … …  
    206232                                e.preventDefault();
    207233                                window.scrollTo( { top: target.getBoundingClientRect().top + window.scrollY - 90, behavior: 'smooth' } );
     234                                // Move keyboard focus too (skip link, in-page links).
     235                                if ( ! target.matches( FOCUSABLE ) && ! target.hasAttribute( 'tabindex' ) ) target.setAttribute( 'tabindex', '-1' );
     236                                target.focus( { preventScroll: true } );
    208237                        }
    209238                } )();
    … …  
    256285                data.append( 'action',     'woocommerce_add_to_cart' );
    257286                data.append( 'product_id', productId );
    258                 data.append( 'quantity',   1 );
     287                var qtyInput = btn.parentNode && btn.parentNode.querySelector( '.sticky-atc-qty' );
     288                data.append( 'quantity',   Math.max( 1, parseInt( qtyInput ? qtyInput.value : 1, 10 ) || 1 ) );
    259289
    260290                fetch( flowixVars.ajaxUrl, { method: 'POST', body: data, credentials: 'same-origin' } )
    … …  
    280310        /* ── SCROLL-IN ANIMATIONS ── */
    281311        if ( 'IntersectionObserver' in window ) {
    282                 var animItems = document.querySelectorAll( '.product-card, .cat-tile, .testimonial-card, .trust-item' );
     312                var animItems = document.querySelectorAll( '.product-card, .cat-tile, .trust-item' );
    283313                if ( animItems.length ) {
    284314                        var styleEl = document.createElement( 'style' );
    … …  
    294324        }
    295325
    296         /* ── DROPDOWN NAV ACCESSIBILITY ── */
     326        /* ── DROPDOWN NAV ACCESSIBILITY ──
     327           Opens on hover and on keyboard focus; aria-expanded follows both. Escape closes it and
     328           returns focus to the parent link; the arrow-down key opens it from the parent link. */
    297329        document.querySelectorAll( '.primary-nav > li' ).forEach( function ( item ) {
    298330                var link    = item.querySelector( 'a' );
    299331                var submenu = item.querySelector( '.sub-menu' );
    300332                if ( ! submenu || ! link ) return;
     333                var hover = false;
     334                var escaped = false;
     335                function set( open ) {
     336                        item.classList.toggle( 'is-open', open );
     337                        link.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
     338                }
    301339                link.setAttribute( 'aria-haspopup', 'true' );
    302340                link.setAttribute( 'aria-expanded', 'false' );
    303                 on( item, 'mouseenter', function () { link.setAttribute( 'aria-expanded', 'true' ); } );
    304                 on( item, 'mouseleave', function () { link.setAttribute( 'aria-expanded', 'false' ); } );
     341                on( item, 'mouseenter', function () { hover = true; set( true ); } );
     342                on( item, 'mouseleave', function () { hover = false; if ( ! item.contains( document.activeElement ) ) set( false ); } );
     343                on( item, 'focusin', function () {
     344                        if ( escaped ) { escaped = false; return; }
     345                        set( true );
     346                } );
     347                on( item, 'focusout', function ( e ) {
     348                        if ( ! item.contains( e.relatedTarget ) && ! hover ) set( false );
     349                } );
     350                on( item, 'keydown', function ( e ) {
     351                        if ( e.key === 'Escape' && item.classList.contains( 'is-open' ) ) {
     352                                e.stopPropagation();
     353                                set( false );
     354                                escaped = document.activeElement !== link;
     355                                link.focus();
     356                        } else if ( e.key === 'ArrowDown' && document.activeElement === link ) {
     357                                var firstSub = submenu.querySelector( 'a' );
     358                                if ( firstSub ) { e.preventDefault(); set( true ); firstSub.focus(); }
     359                        }
     360                } );
    305361        } );
    306362
  • flowix/1.1.2/assets/js/shop-features.js

    r353588 r356478  
    11/**
    2  * Flowix — Shop Features: wishlist, quick view, swatches, added-to-cart, load more,
    3  * sale countdown, delivery cut-off, recently viewed, product tabs layout.
     2 * Flowix — Shop Features: front-end behaviour for the shop options in the Customizer.
    43 */
    54( function( $ ) {
    … …  
    1211        var T = S.i18n;
    1312
    14         function store( key, val ) {
    15                 try {
    16                         if ( val === undefined ) {
    17                                 return JSON.parse( window.localStorage.getItem( key ) || '[]' );
    18                         }
    19                         window.localStorage.setItem( key, JSON.stringify( val ) );
    20                 } catch ( e ) {}
    21                 return [];
    22         }
    23         function ids( list ) {
    24                 return ( Array.isArray( list ) ? list : [] ).map( Number ).filter( Boolean );
    25         }
    2613
    27 
    28         /* ── ADD TO CART WITHOUT LEAVING THE PAGE (product page + quick view) ── */
     14        /* ── ADD TO CART WITHOUT LEAVING THE PAGE ── */
    2915        $( document ).on( 'submit', 'form.cart', function( e ) {
    3016                var form = this;
    3117                var sub = e.originalEvent && e.originalEvent.submitter;
    32                 if ( ( sub && sub.name === 'flowix_buy_now' ) || form.classList.contains( 'grouped_form' ) || ! window.fetch || ! window.FormData ) {
     18                var box = '.product';
     19                if ( form.classList.contains( 'grouped_form' ) || ! window.fetch || ! window.FormData ) {
    3320                        return; // normal submit → checkout / WooCommerce handles it
    3421                }
    … …  
    6350                                        $btn.removeClass( 'added' );
    6451                                }, 2000 );
    65                                 var name = $( form ).closest( '.product, .fx-qv' ).find( '.product_title' ).first().text();
     52                                var name = $( form ).closest( box ).find( '.product_title' ).first().text();
    6653                                $( document.body ).trigger( 'wc_fragment_refresh' );
    6754                                document.body.dispatchEvent( new CustomEvent( 'added_to_cart' ) );
    68                                 afterAdd( name, $( form ).closest( '.product, .fx-qv' ).find( 'img' ).first().attr( 'src' ) );
     55                                afterAdd( name, $( form ).closest( box ).find( 'img' ).first().attr( 'src' ) );
    6956                        } )
    7057                        .catch( function() {
  • flowix/1.1.2/footer.php

    r353748 r356478  
    11</main><!-- #main-content -->
    22
    3 <!-- Newsletter section -->
    4 <?php if ( function_exists( 'flowix_newsletter_default' ) && get_theme_mod( 'flowix_show_newsletter', true ) ) : ?>
    5 <section class="newsletter-section" id="newsletter" aria-labelledby="newsletter-heading">
    6         <div class="container">
    7                 <span class="eyebrow"><?php esc_html_e( 'Stay in the loop', 'flowix' ); ?></span>
    8                 <h2 id="newsletter-heading" style="font-size:1.6rem;margin-bottom:6px;"><?php echo esc_html( get_theme_mod( 'flowix_newsletter_heading', flowix_newsletter_default( 'heading' ) ) ); ?></h2>
    9                 <p class="lead"><?php echo esc_html( get_theme_mod( 'flowix_newsletter_subtext', flowix_newsletter_default( 'subtext' ) ) ); ?></p>
    10                 <?php if ( isset( $_GET['flowix_subscribed'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only. ?>
    11                         <p class="fx-newsletter-thanks" role="status"><?php esc_html_e( "Thanks — you're on the list.", 'flowix' ); ?></p>
    12                 <?php endif; ?>
    13                 <form class="newsletter-form" method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
    14                         <input type="hidden" name="action" value="flowix_newsletter">
    15                         <?php wp_nonce_field( 'flowix_newsletter', 'newsletter_nonce' ); ?>
    16                         <label for="newsletter-email" class="screen-reader-text"><?php esc_html_e( 'Email address', 'flowix' ); ?></label>
    17                         <input type="email" id="newsletter-email" name="email" placeholder="<?php esc_attr_e( 'your@email.co.za', 'flowix' ); ?>" required>
    18                         <button type="submit" class="btn btn--gold"><?php esc_html_e( 'Subscribe', 'flowix' ); ?></button>
    19                 </form>
    20         </div>
    21 </section>
    22 <?php endif; ?>
     3<?php do_action( 'flowix_before_footer' ); ?>
    234
    245<?php flowix_render_footer(); ?>
    25 
    26 </div><!-- #page -->
    276
    287<?php wp_footer(); ?>
  • flowix/1.1.2/functions.php

    r353748 r356478  
    55 *
    66 * @package Flowix
    7  * @version 1.0.5
     7 * @version 1.1.2
    88 */
    99
    … …  
    1111
    1212// Version constant
    13 define( 'FLOWIX_VERSION', '1.1.0' );
     13define( 'FLOWIX_VERSION', '1.1.2' );
    1414define( 'FLOWIX_DIR',     get_template_directory() );
    1515define( 'FLOWIX_URI',     get_template_directory_uri() );
     16
     17/**
     18 * Field type for the builders' longer text (taglines, text items, address).
     19 */
     20function flowix_multiline_field() {
     21        return 'line';
     22}
     23
     24/**
     25 * Escape theme-built HTML on output: everything WordPress allows in post content, plus the
     26 * inline SVG icons, buttons and form fields the header, footer and shop parts use.
     27 *
     28 * @param string $html HTML built by the theme.
     29 * @return string
     30 */
     31function flowix_kses( $html ) {
     32        static $allowed = null;
     33        if ( null === $allowed ) {
     34                $allowed = wp_kses_allowed_html( 'post' );
     35                $common  = [ 'class' => true, 'id' => true, 'style' => true, 'title' => true, 'role' => true, 'tabindex' => true, 'hidden' => true,
     36                        'aria-label' => true, 'aria-hidden' => true, 'aria-expanded' => true, 'aria-controls' => true, 'aria-haspopup' => true,
     37                        'aria-current' => true, 'aria-live' => true, 'aria-labelledby' => true, 'aria-describedby' => true, 'data-*' => true ];
     38                $svg     = [ 'xmlns' => true, 'width' => true, 'height' => true, 'viewbox' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true,
     39                        'stroke-linecap' => true, 'stroke-linejoin' => true, 'focusable' => true, 'd' => true, 'cx' => true, 'cy' => true, 'r' => true, 'rx' => true,
     40                        'ry' => true, 'x' => true, 'y' => true, 'x1' => true, 'y1' => true, 'x2' => true, 'y2' => true, 'points' => true, 'transform' => true,
     41                        'opacity' => true, 'fill-rule' => true, 'clip-rule' => true ];
     42                foreach ( [ 'svg', 'path', 'circle', 'line', 'polyline', 'polygon', 'rect', 'ellipse', 'g' ] as $tag ) {
     43                        $allowed[ $tag ] = $common + $svg;
     44                }
     45                $allowed['button'] = $common + [ 'type' => true, 'name' => true, 'value' => true, 'disabled' => true ];
     46                $allowed['form']   = $common + [ 'action' => true, 'method' => true, 'role' => true ];
     47                $allowed['input']  = $common + [ 'type' => true, 'name' => true, 'value' => true, 'placeholder' => true, 'required' => true, 'checked' => true,
     48                        'min' => true, 'max' => true, 'step' => true, 'autocomplete' => true ];
     49                $allowed['label']  = $common + [ 'for' => true ];
     50                $allowed['select'] = $common + [ 'name' => true ];
     51                $allowed['option'] = [ 'value' => true, 'selected' => true ];
     52                foreach ( $allowed as $tag => $attrs ) {
     53                        if ( is_array( $attrs ) ) {
     54                                $allowed[ $tag ] += $common;
     55                        }
     56                }
     57        }
     58        return wp_kses( (string) $html, $allowed );
     59}
    1660
    1761/* ================================================================
    … …  
    132176add_action( 'wp_enqueue_scripts', 'flowix_enqueue_scripts' );
    133177
    134 /* ================================================================
    135    CRITICAL CSS — INLINE ABOVE THE FOLD
    136    Outputs minimal render-critical styles directly in <head> so the
    137    page paints immediately without waiting for the full stylesheet.
     178/**
     179 * Right-to-left sites (Arabic, Hebrew, Persian…): swap each Flowix stylesheet for its mirrored
     180 * "-rtl.css" twin. The twins are made by tools/build.py.
     181 */
     182function flowix_rtl_styles() {
     183        if ( ! is_rtl() ) {
     184                return;
     185        }
     186        foreach ( wp_styles()->registered as $handle => $style ) {
     187                if ( 0 !== strpos( $handle, 'flowix-' ) || ! is_string( $style->src ) || 0 !== strpos( $style->src, FLOWIX_URI ) ) {
     188                        continue;
     189                }
     190                $rel = ltrim( substr( strtok( $style->src, '?' ), strlen( FLOWIX_URI ) ), '/' );
     191                if ( '.css' === substr( $rel, -4 ) && file_exists( FLOWIX_DIR . '/' . substr( $rel, 0, -4 ) . '-rtl.css' ) ) {
     192                        wp_style_add_data( $handle, 'rtl', 'replace' );
     193                }
     194        }
     195}
     196add_action( 'wp_enqueue_scripts', 'flowix_rtl_styles', 999 );
     197add_action( 'customize_controls_enqueue_scripts', 'flowix_rtl_styles', 999 );
     198add_action( 'enqueue_block_editor_assets', 'flowix_rtl_styles', 999 );
     199add_action( 'admin_enqueue_scripts', 'flowix_rtl_styles', 999 );
     200
     201/* ================================================================
     202   CRITICAL CSS — the first stylesheet in <head>: page colours and the
     203   hidden skip link, so the page paints right before the full stylesheet.
    138204   ================================================================ */
    139205function flowix_critical_css() {
    140         $bc       = flowix_brand()['colors'];
    141         $accent   = $bc['accent'];
    142         $body_bg  = $bc['background'];
    143         $body_text= $bc['text'];
    144         echo '<style id="flowix-critical">*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}'
     206        $bc  = flowix_brand()['colors'];
     207        $css = '*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}'
    145208                . 'html{font-size:' . absint( get_theme_mod( 'flowix_font_size_base', 16 ) ) . 'px}'
    146                 . 'body{background:' . esc_attr( $body_bg ) . ';color:' . esc_attr( $body_text ) . ';font-family:system-ui,sans-serif;line-height:1.65}'
    147                 . '#masthead{background:' . esc_attr( $bc['header'] ) . ';position:relative;z-index:100}'
    148                 . '.site-title a{color:' . esc_attr( $bc['header_text'] ) . '}'
    149                 . '.hero{background:' . esc_attr( get_theme_mod( 'flowix_hero_bg_color' ) ?: $bc['hero'] ) . '}'
     209                . 'body{background:' . sanitize_hex_color( $bc['background'] ) . ';color:' . sanitize_hex_color( $bc['text'] ) . ';font-family:system-ui,sans-serif;line-height:1.65}'
     210                . '#masthead{background:' . sanitize_hex_color( $bc['header'] ) . ';position:relative;z-index:100}'
     211                . '.site-title a{color:' . sanitize_hex_color( $bc['header_text'] ) . '}'
     212                . '.hero{background:' . sanitize_hex_color( get_theme_mod( 'flowix_hero_bg_color' ) ?: $bc['hero'] ) . '}'
    150213                . '.skip-link{position:absolute;top:-9999px;left:-9999px}'
    151                 . '.skip-link:focus{top:0;left:0;z-index:9999;background:' . esc_attr( $accent ) . ';color:#fff;padding:8px 16px}'
    152                 . '</style>' . "\n";
    153 }
    154 add_action( 'wp_head', 'flowix_critical_css', 1 );
    155 
    156 /* ================================================================
    157    FONT DISPLAY SWAP — ensure fonts don't block rendering
    158    ================================================================ */
    159 function flowix_font_display_swap( $tag, $handle ) {
    160         if ( 'flowix-fonts' !== $handle ) return $tag;
    161         // Add font-display=swap to the Google Fonts URL if not already present
    162         if ( strpos( $tag, 'display=swap' ) === false ) {
    163                 $tag = str_replace( "rel='stylesheet'", "rel='stylesheet' media='print' onload=\"this.media='all'\"", $tag );
    164         }
    165         return $tag;
    166 }
    167 add_filter( 'style_loader_tag', 'flowix_font_display_swap', 10, 2 );
     214                . '.skip-link:focus{top:0;left:0;z-index:9999;background:' . sanitize_hex_color( $bc['accent'] ) . ';color:#fff;padding:8px 16px}';
     215        wp_register_style( 'flowix-critical', false, [], FLOWIX_VERSION );
     216        wp_enqueue_style( 'flowix-critical' );
     217        wp_add_inline_style( 'flowix-critical', $css );
     218}
     219add_action( 'wp_enqueue_scripts', 'flowix_critical_css', 1 );
     220
     221/**
     222 * Connect to Google Fonts early (the Brand Kit fonts come from there).
     223 *
     224 * @param array  $urls          URLs to print for resource hints.
     225 * @param string $relation_type The relation type the URLs are printed for.
     226 * @return array
     227 */
     228function flowix_resource_hints( $urls, $relation_type ) {
     229        if ( 'preconnect' === $relation_type && wp_style_is( 'flowix-fonts', 'queue' ) ) {
     230                $urls[] = 'https://fonts.googleapis.com';
     231                $urls[] = [
     232                        'href' => 'https://fonts.gstatic.com',
     233                        'crossorigin',
     234                ];
     235        }
     236        return $urls;
     237}
     238add_filter( 'wp_resource_hints', 'flowix_resource_hints', 10, 2 );
    168239
    169240/* ================================================================
    … …  
    255326        if ( ! class_exists( 'WooCommerce' ) || ! WC()->cart ) return $fragments;
    256327        $count = WC()->cart->get_cart_contents_count();
    257         $fragments['.cart-item-count'] = '<span class="cart-item-count">' . $count . '</span>';
    258         $fragments['.mini-cart-count'] = '<span class="mini-cart-count">' . $count . '</span>';
     328        $fragments['.cart-item-count'] = '<span class="cart-item-count">' . absint( $count ) . '</span>';
     329        $fragments['.mini-cart-count'] = '<span class="mini-cart-count">' . absint( $count ) . '</span>';
    259330        return $fragments;
    260331}
    … …  
    265336   ================================================================ */
    266337function flowix_loop_columns() {
    267         return get_theme_mod( 'flowix_shop_columns', 4 );
     338        return absint( get_theme_mod( 'flowix_shop_columns', 4 ) );
    268339}
    269340add_filter( 'loop_shop_columns', 'flowix_loop_columns' );
    … …  
    273344   ================================================================ */
    274345function flowix_products_per_page() {
    275         return get_theme_mod( 'flowix_products_per_page', 12 );
     346        return absint( get_theme_mod( 'flowix_products_per_page', 12 ) );
    276347}
    277348add_filter( 'loop_shop_per_page', 'flowix_products_per_page' );
    … …  
    386457        $image_id   = $product->get_image_id();
    387458        $image_url  = $image_id
    388                 ? wp_get_attachment_image_url( $image_id, 'flowix-product-card' )
    389                 : wc_placeholder_img_src( 'flowix-product-card' );
    390         $image_alt  = get_post_meta( $image_id, '_wp_attachment_image_alt', true ) ?: get_the_title( $product->get_id() );
     459                ? wp_get_attachment_image_url( $image_id, 'woocommerce_thumbnail' )
     460                : wc_placeholder_img_src( 'woocommerce_thumbnail' );
     461        $image_alt  = $image_id ? (string) get_post_meta( $image_id, '_wp_attachment_image_alt', true ) : '';
    391462        $price_html = $product->get_price_html();
    392463        $on_sale    = $product->is_on_sale();
    393         $is_new     = ( strtotime( $product->get_date_created() ) > strtotime( '-30 days' ) );
     464        $is_new     = $product->get_date_created() && $product->get_date_created()->getTimestamp() > time() - 30 * DAY_IN_SECONDS;
    394465        $cats       = get_the_terms( $product->get_id(), 'product_cat' );
    395466        $cat_name   = ( $cats && ! is_wp_error( $cats ) ) ? reset( $cats )->name : '';
    … …  
    398469        $atc_text = $product->is_type( 'simple' ) ? __( 'Add to Cart', 'flowix' ) : __( 'View Product', 'flowix' );
    399470        ?>
    400         <article class="product-card" itemscope itemtype="https://schema.org/Product">
     471        <article class="product-card">
    401472                <div class="product-card-media">
    402473                        <a href="<?php echo esc_url( get_permalink( $product->get_id() ) ); ?>" aria-label="<?php echo esc_attr( $product->get_name() ); ?>">
    … …  
    404475                                         alt="<?php echo esc_attr( $image_alt ); ?>"
    405476                                         width="480" height="480"
    406                                          loading="lazy"
    407                                          itemprop="image">
     477                                         loading="lazy">
    408478                        </a>
    409479                        <?php
    410480                        if ( function_exists( 'flowix_card_overlay' ) ) {
    411                                 echo flowix_card_overlay( $product ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built from escaped parts.
     481                                echo flowix_kses( flowix_card_overlay( $product ) );
    412482                        } else {
    413483                                ?>
    … …  
    425495                                        <?php echo esc_html( $atc_text ); ?>
    426496                                </a>
    427                                 <a href="<?php echo esc_url( get_permalink( $product->get_id() ) ); ?>" class="btn btn--sm" style="background:rgba(255,255,255,0.1);border-color:rgba(255,255,255,0.2);color:#fff">
     497                                <a href="<?php echo esc_url( get_permalink( $product->get_id() ) ); ?>" class="btn btn--sm btn--ghost-light">
    428498                                        <?php esc_html_e( 'Details', 'flowix' ); ?>
    429499                                </a>
    … …  
    432502                <div class="product-card-body">
    433503                        <?php if ( $cat_name ) : ?>
    434                                 <span class="product-card-category" itemprop="category"><?php echo esc_html( $cat_name ); ?></span>
     504                                <span class="product-card-category"><?php echo esc_html( $cat_name ); ?></span>
    435505                        <?php endif; ?>
    436                         <h3 class="product-card-title" itemprop="name">
    437                                 <a href="<?php echo esc_url( get_permalink( $product->get_id() ) ); ?>" itemprop="url">
     506                        <h3 class="product-card-title">
     507                                <a href="<?php echo esc_url( get_permalink( $product->get_id() ) ); ?>">
    438508                                        <?php echo esc_html( $product->get_name() ); ?>
    439509                                </a>
    440510                        </h3>
    441                         <div class="product-card-price" itemprop="offers" itemscope itemtype="https://schema.org/Offer">
     511                        <div class="product-card-price">
    442512                                <?php echo wp_kses_post( $price_html ); ?>
    443                                 <meta itemprop="priceCurrency" content="<?php echo esc_attr( get_woocommerce_currency() ); ?>">
    444513                        </div>
    445                         <?php echo function_exists( 'flowix_card_swatches' ) ? flowix_card_swatches( $product ) : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
     514                        <?php do_action( 'flowix_product_card_after_price', $product ); ?>
    446515                </div>
    447516        </article>
    … …  
    470539
    471540/* ================================================================
    472    PAGE TEMPLATES
    473    ================================================================ */
    474 function flowix_page_templates( $templates ) {
    475         $templates['templates/page-full-width.php']  = __( 'Full Width', 'flowix' );
    476         $templates['templates/page-no-header.php']   = __( 'Landing Page (No Header/Footer)', 'flowix' );
    477         return $templates;
    478 }
    479 add_filter( 'theme_page_templates', 'flowix_page_templates' );
     541   SKIP LINK — printed before the admin toolbar (which core adds to
     542   wp_body_open at priority 0), so it is always the first thing to tab to.
     543   ================================================================ */
     544function flowix_skip_link() {
     545        echo '<a class="skip-link" href="#main-content">' . esc_html__( 'Skip to content', 'flowix' ) . '</a>';
     546}
     547add_action( 'wp_body_open', 'flowix_skip_link', -1 );
    480548
    481549/* ================================================================
    482550   EXCERPT LENGTH & MORE
    483551   ================================================================ */
    484 add_filter( 'excerpt_length', fn() => 22 );
    485 add_filter( 'excerpt_more', fn() => '&hellip;' );
    486 
    487 /* ================================================================
    488    REMOVE WOOCOMMERCE DEFAULT STYLES (we provide our own)
    489    ================================================================ */
    490 function flowix_dequeue_woo_styles( $enqueue_styles ) {
    491         // Keep core WC styles, remove block-based ones we don't need
    492         // unset( $enqueue_styles['wc-block-style'] );
    493         return $enqueue_styles;
    494 }
    495 add_filter( 'woocommerce_enqueue_styles', 'flowix_dequeue_woo_styles' );
    496 
    497 require_once FLOWIX_DIR . '/inc/upsell.php';
     552add_filter( 'excerpt_length', fn( $length ) => is_admin() ? $length : 22 );
     553add_filter( 'excerpt_more', fn( $more ) => is_admin() ? $more : '&hellip;' );
     554
    498555
    499556/* ================================================================
    … …  
    509566require_once FLOWIX_DIR . '/inc/page-options.php';
    510567require_once FLOWIX_DIR . '/inc/patterns.php';
     568require_once FLOWIX_DIR . '/inc/editor-history.php';
    511569require_once FLOWIX_DIR . '/inc/header-builder.php';
    512570require_once FLOWIX_DIR . '/inc/footer-builder.php';
  • flowix/1.1.2/header.php

    r353588 r356478  
    55<meta name="viewport" content="width=device-width, initial-scale=1">
    66<meta name="theme-color" content="<?php echo esc_attr( flowix_brand()['colors']['header'] ); ?>">
    7 <?php if ( get_theme_mod( 'flowix_font_heading', '' ) || get_theme_mod( 'flowix_font_body', '' ) ) : ?>
    8 <link rel="preconnect" href="https://fonts.googleapis.com">
    9 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    10 <?php endif; ?>
    117<?php wp_head(); ?>
    128</head>
    139<body <?php body_class(); ?>>
    1410<?php wp_body_open(); ?>
    15 
    16 <a class="skip-link" href="#main-content"><?php esc_html_e( 'Skip to content', 'flowix' ); ?></a>
    1711
    1812<!-- Back to top -->
    … …  
    7872<nav class="mobile-nav-drawer" id="mobile-nav-drawer" aria-label="<?php esc_attr_e( 'Mobile Navigation', 'flowix' ); ?>">
    7973        <div class="mobile-nav-header">
    80                 <span class="site-title" style="color:var(--cream);font-size:1.2rem;">
     74                <span class="site-title">
    8175                        <?php bloginfo( 'name' ); ?>
    8276                </span>
    … …  
    9690        ?>
    9791        <?php if ( class_exists( 'WooCommerce' ) ) : ?>
    98                 <div style="padding:20px 24px;border-top:1px solid rgba(155,123,58,0.2);margin-top:auto;">
    99                         <a href="<?php echo esc_url( wc_get_cart_url() ); ?>" class="btn btn--gold" style="width:100%;text-align:center;display:block;">
     92                <div class="mobile-nav-footer">
     93                        <a href="<?php echo esc_url( wc_get_cart_url() ); ?>" class="btn btn--gold btn--block">
    10094                                <?php esc_html_e( 'View Cart', 'flowix' ); ?>
    10195                        </a>
    … …  
    108102<!-- Breadcrumbs (non-homepage) -->
    109103<?php if ( ! is_front_page() && ! is_home() ) : ?>
    110         <div class="breadcrumbs-bar" aria-label="<?php esc_attr_e( 'Breadcrumb', 'flowix' ); ?>">
     104        <div class="breadcrumbs-bar">
    111105                <div class="container">
    112106                        <?php
    … …  
    114108                                woocommerce_breadcrumb( [
    115109                                        'delimiter'   => '&nbsp;/&nbsp;',
    116                                         'wrap_before' => '<nav class="woocommerce-breadcrumb" aria-label="Breadcrumb">',
     110                                        'wrap_before' => '<nav class="woocommerce-breadcrumb" aria-label="' . esc_attr__( 'Breadcrumb', 'flowix' ) . '">',
    117111                                        'wrap_after'  => '</nav>',
    118112                                ] );
    119113                        } else {
    120                                 echo '<nav aria-label="Breadcrumb">';
    121                                 echo '<a href="' . esc_url( home_url() ) . '">' . esc_html__( 'Home', 'flowix' ) . '</a>';
    122                                 echo ' / <span>' . esc_html( get_the_title() ) . '</span>';
     114                                if ( is_search() ) {
     115                                        /* translators: %s: search query */
     116                                        $flowix_crumb = sprintf( __( 'Search results for "%s"', 'flowix' ), get_search_query( false ) );
     117                                } elseif ( is_404() ) {
     118                                        $flowix_crumb = __( 'Page not found', 'flowix' );
     119                                } elseif ( is_archive() ) {
     120                                        $flowix_crumb = wp_strip_all_tags( get_the_archive_title() );
     121                                } else {
     122                                        $flowix_crumb = get_the_title();
     123                                }
     124                                echo '<nav aria-label="' . esc_attr__( 'Breadcrumb', 'flowix' ) . '">';
     125                                echo '<a href="' . esc_url( home_url( '/' ) ) . '">' . esc_html__( 'Home', 'flowix' ) . '</a>';
     126                                echo ' / <span>' . esc_html( $flowix_crumb ) . '</span>';
    123127                                echo '</nav>';
    124128                        }
  • flowix/1.1.2/inc/brand-kit.php

    r353588 r356478  
    7272function flowix_brand() {
    7373        static $cache = [];
    74         $ctx = function_exists( 'flowix_context_look_key' ) ? flowix_context_look_key() : '';
     74        $ctx = '';
    7575        $ctx = $ctx && ! did_action( 'flowix_look_ready' ) ? '' : $ctx;
    7676        if ( ! isset( $cache[ $ctx ] ) || is_customize_preview() ) {
    … …  
    291291                '--btn-ink'      => $p['text'],
    292292                '--btn-ink-text' => $p['bg'],
    293                 // Older light background tokens still used by a few sections (countdown, call-to-action, newsletter…).
     293                // Older light background tokens still used by a few sections.
    294294                '--linen'        => $p['surface'],
    295295                '--parchment'    => $p['surface'],
    … …  
    334334        }
    335335        $float = ( 'toggle' === $mode && $dark['float'] ) ? 'true' : 'false';
    336         echo '<script id="flowix-dark-init">(function(){var m=' . wp_json_encode( $mode ) . ',d=document.documentElement,s=null;if(' . esc_js( $float ) . ')d.classList.add("fx-float-on");try{s=localStorage.getItem("flowix-theme")}catch(e){}var sys=window.matchMedia&&matchMedia("(prefers-color-scheme: dark)").matches;var on=m==="always"||(m==="auto"&&sys)||(m==="toggle"&&(s?s==="dark":sys));if(on)d.classList.add("fx-dark");d.setAttribute("data-flowix-dark",m);})();</script>' . "\n";
     336        wp_print_inline_script_tag(
     337                '(function(){var m=' . wp_json_encode( $mode ) . ',d=document.documentElement,s=null;if(' . $float . ')d.classList.add("fx-float-on");try{s=localStorage.getItem("flowix-theme")}catch(e){}var sys=window.matchMedia&&matchMedia("(prefers-color-scheme: dark)").matches;var on=m==="always"||(m==="auto"&&sys)||(m==="toggle"&&(s?s==="dark":sys));if(on)d.classList.add("fx-dark");d.setAttribute("data-flowix-dark",m);})();',
     338                [ 'id' => 'flowix-dark-init' ]
     339        );
    337340}
    338341add_action( 'wp_head', 'flowix_dark_head_script', 1 );
    … …  
    342345        $d = flowix_brand()['dark'];
    343346        if ( ( 'toggle' === $d['mode'] && $d['float'] ) || is_customize_preview() ) {
    344                 echo '<button type="button" class="fx-dark-toggle fx-dark-float" data-flowix-dark-toggle aria-label="' . esc_attr__( 'Switch between light and dark mode', 'flowix' ) . '">' . flowix_inline_icon( 'moon', 18 ) . flowix_inline_icon( 'sun', 18 ) . '</button>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     347                echo flowix_kses( '<button type="button" class="fx-dark-toggle fx-dark-float" data-flowix-dark-toggle aria-label="' . esc_attr__( 'Switch between light and dark mode', 'flowix' ) . '">' . flowix_inline_icon( 'moon', 18 ) . flowix_inline_icon( 'sun', 18 ) . '</button>' );
    345348        }
    346349} );
    … …  
    351354}
    352355
    353 /** Printed after the old global colour CSS (and before element styles) so the brand wins. */
    354 add_action( 'wp_head', function() {
    355         echo '<style id="flowix-brand">' . flowix_brand_css( flowix_brand() ) . "</style>\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built from sanitized values.
    356 }, 998 );
     356/** Added after the other theme stylesheets (and before the element styles) so the brand wins. */
     357function flowix_enqueue_brand_css() {
     358        wp_register_style( 'flowix-brand', false, [], FLOWIX_VERSION );
     359        wp_enqueue_style( 'flowix-brand' );
     360        wp_add_inline_style( 'flowix-brand', wp_strip_all_tags( flowix_brand_css( flowix_brand() ) ) );
     361}
     362add_action( 'wp_enqueue_scripts', 'flowix_enqueue_brand_css', 1000 );
    357363
    358364/* ================================================================
  • flowix/1.1.2/inc/customizer-output.php

    r353588 r356478  
    168168                [ 'flowix_cats',  '#categories-section',   'var(--bg-page, var(--cream))', 60, 60 ],
    169169                [ 'flowix_feat',  '#featured-section',     'var(--bg-warm, var(--cream-warm))', 60, 60 ],
    170                 [ 'flowix_testi', '#testimonials-section', 'var(--bg-page, var(--cream))', 60, 60 ],
    171                 [ 'flowix_vendor','.vendor-cta',           'var(--ink)', 80, 80 ],
    172                 [ 'flowix_news',  '.newsletter-section',   'var(--hr-bg)', 56, 56 ],
    173170                [ 'flowix_footer_sec', '#colophon',        'var(--ft-bg)', 60, 40 ],
    174171        ];
  • flowix/1.1.2/inc/customizer.php

    r353748 r356478  
    1111   HELPER: PER-SECTION BACKGROUND CONTROLS
    1212   ================================================================ */
     13/**
     14 * Select / radio settings: keep the value only if it is one of the control's choices.
     15 *
     16 * @param mixed                $value   Submitted value.
     17 * @param WP_Customize_Setting $setting The setting.
     18 */
     19function flowix_sanitize_choice( $value, $setting ) {
     20        $control = $setting->manager->get_control( $setting->id );
     21        $choices = $control ? (array) $control->choices : [];
     22        return array_key_exists( (string) $value, $choices ) || array_key_exists( (int) $value, $choices ) ? $value : $setting->default;
     23}
     24
    1325function flowix_register_bg_controls( $wp_customize, $section_id, $prefix, $default_color = '#FAF7F2', $default_pt = 60, $default_pb = 60 ) {
    1426
    … …  
    7183
    7284        $add_select = function( $id, $section, $label, $default, $choices, $transport = 'postMessage' ) use ( $wp_customize ) {
    73                 $wp_customize->add_setting( $id, [ 'default' => $default, 'sanitize_callback' => 'sanitize_text_field', 'transport' => $transport ] );
     85                $wp_customize->add_setting( $id, [ 'default' => $default, 'sanitize_callback' => 'flowix_sanitize_choice', 'transport' => $transport ] );
    7486                $wp_customize->add_control( $id, [ 'section' => $section, 'label' => $label, 'type' => 'select', 'choices' => $choices ] );
    7587        };
    … …  
    133145        $wp_customize->add_control( 'flowix_featured_count', [ 'section' => 'flowix_featured', 'label' => __( 'Number of Products', 'flowix' ), 'type' => 'number', 'input_attrs' => [ 'min' => 2, 'max' => 16, 'step' => 2 ] ] );
    134146
    135         /* ══════════════════════════════════════════════════════
    136            TESTIMONIALS
    137         ══════════════════════════════════════════════════════ */
    138         $wp_customize->add_section( 'flowix_testimonials', [ 'panel' => 'flowix_panel', 'title' => __( 'Testimonials', 'flowix' ), 'priority' => 80 ] );
    139         $add_text( 'flowix_testimonials_eyebrow', 'flowix_testimonials', __( 'Eyebrow Text', 'flowix' ), __( 'Happy Customers', 'flowix' ) );
    140         $add_text( 'flowix_testimonials_heading', 'flowix_testimonials', __( 'Heading', 'flowix' ),      __( 'What People Say', 'flowix' ) );
    141         $t = [ 1 => [ 'Incredible quality and such fast delivery!', 'Thandi M.', 'Johannesburg' ], 2 => [ 'Finally a SA marketplace that actually works.', 'Ruan B.', 'Cape Town' ], 3 => [ "I found products I could never find elsewhere.", 'Priya N.', 'Durban' ] ];
    142         for ( $i = 1; $i <= 3; $i++ ) {
    143                 /* translators: %d: review number */
    144                 $add_text( "flowix_t{$i}_quote", 'flowix_testimonials', sprintf( __( 'Review %d — Quote', 'flowix' ), $i ), $t[$i][0], 'textarea' );
    145                 /* translators: %d: review number */
    146                 $add_text( "flowix_t{$i}_name",  'flowix_testimonials', sprintf( __( 'Review %d — Name', 'flowix' ), $i ),  $t[$i][1] );
    147                 /* translators: %d: review number */
    148                 $add_text( "flowix_t{$i}_loc",   'flowix_testimonials', sprintf( __( 'Review %d — Location', 'flowix' ), $i ), $t[$i][2] );
    149         }
    150         flowix_register_bg_controls( $wp_customize, 'flowix_testimonials', 'flowix_testi', '', 60, 60 );
    151147
    152         /* ══════════════════════════════════════════════════════
    153            VENDOR CTA
    154         ══════════════════════════════════════════════════════ */
    155         $wp_customize->add_section( 'flowix_vendor_cta', [ 'panel' => 'flowix_panel', 'title' => __( 'Vendor CTA Section', 'flowix' ), 'priority' => 90 ] );
    156         $add_text( 'flowix_vendor_eyebrow',   'flowix_vendor_cta', __( 'Eyebrow Text', 'flowix' ),       __( 'Grow Your Business', 'flowix' ) );
    157         $add_text( 'flowix_vendor_heading',   'flowix_vendor_cta', __( 'Heading', 'flowix' ),            __( 'Sell on Flowix', 'flowix' ) );
    158         $add_text( 'flowix_vendor_paragraph', 'flowix_vendor_cta', __( 'Paragraph', 'flowix' ),          __( 'Join our growing marketplace. List your products and reach thousands of shoppers.', 'flowix' ), 'textarea' );
    159         $add_text( 'flowix_vendor_btn1_text', 'flowix_vendor_cta', __( 'Button 1 Label', 'flowix' ),     __( 'Start Selling', 'flowix' ) );
    160         $add_text( 'flowix_vendor_btn1_url',  'flowix_vendor_cta', __( 'Button 1 URL', 'flowix' ),       home_url( '/become-a-vendor' ), 'url', 'refresh' );
    161         $add_text( 'flowix_vendor_btn2_text', 'flowix_vendor_cta', __( 'Button 2 Label', 'flowix' ),     __( 'Learn More', 'flowix' ) );
    162         $add_text( 'flowix_vendor_btn2_url',  'flowix_vendor_cta', __( 'Button 2 URL', 'flowix' ),       home_url( '/about' ), 'url', 'refresh' );
    163         flowix_register_bg_controls( $wp_customize, 'flowix_vendor_cta', 'flowix_vendor', '', 80, 80 );
    164 
    165         /* ══════════════════════════════════════════════════════
    166            NEWSLETTER
    167         ══════════════════════════════════════════════════════ */
    168148
    169149        /* ══════════════════════════════════════════════════════
    … …  
    171151        ══════════════════════════════════════════════════════ */
    172152        $wp_customize->add_section( 'flowix_shop', [ 'active_callback' => 'flowix_has_shop', 'panel' => 'flowix_panel', 'title' => __( 'Shop Settings', 'flowix' ), 'priority' => 100 ] );
    173         $wp_customize->add_setting( 'flowix_shop_columns',      [ 'default' => 4,  'sanitize_callback' => 'absint', 'transport' => 'refresh' ] );
     153        $wp_customize->add_setting( 'flowix_shop_columns',      [ 'default' => 4,  'sanitize_callback' => 'flowix_sanitize_choice', 'transport' => 'refresh' ] );
    174154        $wp_customize->add_control( 'flowix_shop_columns',      [ 'section' => 'flowix_shop', 'label' => __( 'Product Columns', 'flowix' ), 'type' => 'select', 'choices' => [ 2=>'2', 3=>'3', 4=>'4', 5=>'5' ] ] );
    175155        $wp_customize->add_setting( 'flowix_products_per_page', [ 'default' => 12, 'sanitize_callback' => 'absint', 'transport' => 'refresh' ] );
    … …  
    181161        ══════════════════════════════════════════════════════ */
    182162        $wp_customize->add_section( 'flowix_footer', [ 'panel' => 'flowix_panel', 'title' => __( 'Footer', 'flowix' ), 'priority' => 110 ] );
    183         $add_text( 'flowix_footer_tagline',   'flowix_footer', __( 'Brand Tagline', 'flowix' ),  flowix_footer_tagline_default(), 'textarea' );
    184         $add_text( 'flowix_footer_copyright', 'flowix_footer', __( 'Copyright Text', 'flowix' ), gmdate( 'Y' ) . ' Flowix. All rights reserved.' );
     163        // The tagline and copyright line are set on their items in Flowix — Layout Builder → Footer Builder.
    185164        $add_text( 'flowix_social_facebook',  'flowix_footer', __( 'Facebook URL', 'flowix' ),    '', 'url', 'refresh' );
    186165        $add_text( 'flowix_social_instagram', 'flowix_footer', __( 'Instagram URL', 'flowix' ),   '', 'url', 'refresh' );
  • flowix/1.1.2/inc/design-gallery.php

    r353588 r356478  
    6666
    6767function flowix_look_default_home() {
    68         return [ 'hero', 'trust', 'categories', 'featured', 'testimonials', 'vendor' ];
     68        return wp_list_pluck( flowix_home_sections_default(), 'type' );
    6969}
    7070
    … …  
    101101                        ],
    102102                        'home'   => [ 'hero', 'b_stats', 'featured', 'categories', 'text_image', 'testimonials' ],
    103                         'footer' => [ 'main' => [ 'grid' => '1fr 1fr 1fr 1fr', 'cols' => [ [ 'brand' ], [ 'links_shop' ], [ 'links_info' ], [ 'newsletter' ] ] ], 'bottom' => [ 'l' => [ 'copyright' ], 'c' => [], 'r' => [ 'backtotop' ] ] ],
     103                        'footer' => [ 'main' => [ 'grid' => '1fr 1fr 1fr 1fr', 'cols' => [ [ 'brand' ], [ 'links_shop' ], [ 'links_info' ], [ flowix_footer_signup_item() ] ] ], 'bottom' => [ 'l' => [ 'copyright' ], 'c' => [], 'r' => [ 'backtotop' ] ] ],
    104104                        'elements' => flowix_look_base( 0, 0, [
    105105                                'hero'          => [ 'bg' => $tok( 'hero' ), 'tex' => 'graph', 'texc' => '#111111', 'texo' => 7, 'texs' => 40, 'bw' => [ 't' => 0, 'r' => 0, 'b' => 2, 'l' => 0, 'u' => 'px' ], 'bs' => 'solid', 'bc' => '#111111' ],
    … …  
    176176
    177177
    178 /* The gallery's looks for the Customizer scripts (Premium adds saved looks on top). */
     178/* The gallery's looks for the Customizer scripts. */
    179179add_filter( 'flowix_element_js_data', function( $data ) {
    180180        if ( ! isset( $data['looks'] ) ) {
  • flowix/1.1.2/inc/element-registry.php

    r353588 r356478  
    210210                                'feat_section' => [ __( 'Featured Section', 'flowix' ), '#featured-section', $b ],
    211211                                'feat_heading' => [ __( 'Featured Heading', 'flowix' ), '#featured-heading', $tb ],
    212                         ],
    213                 ],
    214                 'testimonials' => [
    215                         'title'    => __( 'Homepage — Testimonials', 'flowix' ),
    216                         'elements' => [
    217                                 'testi_section' => [ __( 'Testimonials Section', 'flowix' ), '#testimonials-section', $b ],
    218                                 'testi_grid'    => [ __( 'Testimonials Grid', 'flowix' ), '.testimonials-grid', $b ],
    219                                 'testi_card'    => [ __( 'Testimonial Card', 'flowix' ), '.testimonial-card', $b ],
    220                                 'testi_stars'   => [ __( 'Stars', 'flowix' ), '.testimonial-stars', $tb ],
    221                                 'testi_quote'   => [ __( 'Quote Text', 'flowix' ), '.testimonial-quote', $tb ],
    222                                 'testi_avatar'  => [ __( 'Avatar Circle', 'flowix' ), '.testimonial-avatar', $tb ],
    223                                 'testi_name'    => [ __( 'Customer Name', 'flowix' ), '.testimonial-name', $tb ],
    224                                 'testi_meta'    => [ __( 'Customer Location', 'flowix' ), '.testimonial-meta', $tb ],
    225                         ],
    226                 ],
    227                 'vendor' => [
    228                         'title'    => __( 'Homepage — Vendor Call-to-Action', 'flowix' ),
    229                         'elements' => [
    230                                 'vendor_section' => [ __( 'Vendor Section', 'flowix' ), '.vendor-cta', $b ],
    231                                 'vendor_inner'   => [ __( 'Vendor Content Box', 'flowix' ), '.vendor-cta-inner', $b ],
    232                                 'vendor_eyebrow' => [ __( 'Vendor Eyebrow', 'flowix' ), '.vendor-cta .eyebrow', $tb ],
    233                                 'vendor_heading' => [ __( 'Vendor Heading', 'flowix' ), '.vendor-cta h2', $tb ],
    234                                 'vendor_text'    => [ __( 'Vendor Paragraph', 'flowix' ), '.vendor-cta p', $tb ],
    235                                 'vendor_btn1'    => [ __( 'Vendor Button 1', 'flowix' ), '.vendor-cta-actions .btn:nth-child(1)', $tb ],
    236                                 'vendor_btn2'    => [ __( 'Vendor Button 2', 'flowix' ), '.vendor-cta-actions .btn:nth-child(2)', $tb ],
    237                         ],
    238                 ],
    239                 'newsletter' => [
    240                         'title'    => __( 'Newsletter Signup', 'flowix' ),
    241                         'elements' => [
    242                                 'news_section' => [ __( 'Newsletter Section', 'flowix' ), '.newsletter-section', $b ],
    243                                 'news_eyebrow' => [ __( 'Newsletter Eyebrow', 'flowix' ), '.newsletter-section .eyebrow', $tb ],
    244                                 'news_heading' => [ __( 'Newsletter Heading', 'flowix' ), '#newsletter-heading', $tb ],
    245                                 'news_text'    => [ __( 'Newsletter Text', 'flowix' ), '.newsletter-section .lead', $tb ],
    246                                 'news_form'    => [ __( 'Signup Form', 'flowix' ), '.newsletter-form', $b ],
    247                                 'news_input'   => [ __( 'Email Box', 'flowix' ), '.newsletter-form input[type="email"]', $tb ],
    248                                 'news_btn'     => [ __( 'Subscribe Button', 'flowix' ), '.newsletter-form .btn', $tb ],
    249212                        ],
    250213                ],
  • flowix/1.1.2/inc/element-styles.php

    r353588 r356478  
    231231        static $icons = null;
    232232        if ( null === $icons ) {
    233                 $data  = json_decode( (string) file_get_contents( FLOWIX_DIR . '/assets/icons/lucide.json' ), true ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local theme file.
     233                $data  = wp_json_file_decode( FLOWIX_DIR . '/assets/icons/lucide.json', [ 'associative' => true ] );
    234234                $icons = $data['icons'] ?? [];
    235235        }
    … …  
    278278/** System fonts that need no download. */
    279279function flowix_system_fonts() {
    280         return [
     280        $fonts = [
    281281                'System UI'       => 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
    282282                'Arial'           => 'Arial, Helvetica, sans-serif',
    … …  
    288288                'Times New Roman' => '"Times New Roman", Times, serif',
    289289                'Courier New'     => '"Courier New", Courier, monospace',
    290         ] + ( function_exists( 'flowix_custom_font_stacks' ) ? flowix_custom_font_stacks() : [] );
     290        ];
     291        return $fonts;
    291292}
    292293
    … …  
    724725
    725726/**
    726  * Print element CSS after all other theme styles so it always wins, plus the motion list.
    727  * In the Customizer preview each element gets its own <style> tag so live edits can replace it.
    728  */
    729 function flowix_print_element_css() {
     727 * The element CSS for this page view.
     728 * In the Customizer preview each element keeps its own CSS so live edits can replace it.
     729 */
     730function flowix_page_element_css_data() {
     731        static $memo = null;
     732        if ( null !== $memo ) {
     733                return $memo;
     734        }
    730735        if ( is_customize_preview() ) {
    731                 flowix_print_element_css_build();
    732                 return;
    733         }
    734         // Built output is cached until any design setting (or the page's own look) changes.
    735         $key    = 'flowix_css_' . flowix_css_fingerprint();
    736         $cached = get_transient( $key );
    737         if ( false === $cached ) {
    738                 ob_start();
    739                 flowix_print_element_css_build();
    740                 $cached = ob_get_clean();
    741                 set_transient( $key, $cached, WEEK_IN_SECONDS );
    742         }
    743         echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built from sanitized values.
    744 }
    745 
    746 /** Everything the element CSS depends on. */
    747 function flowix_css_fingerprint() {
    748         $mods = [];
    749         foreach ( (array) get_theme_mods() as $k => $v ) {
    750                 if ( 0 === strpos( (string) $k, 'flowix_' ) ) {
    751                         $mods[ $k ] = $v;
    752                 }
    753         }
    754         return md5( wp_json_encode( [
    755                 $mods,
    756                 function_exists( 'flowix_context_look_key' ) ? flowix_context_look_key() : '',
    757                 get_option( 'flowix_saved_styles' ),
    758                 get_bloginfo( 'name' ),
    759                 FLOWIX_VERSION,
    760                 filemtime( __FILE__ ),
    761                 filemtime( FLOWIX_DIR . '/inc/design-gallery.php' ),
    762         ] ) );
    763 }
    764 
    765 
    766 function flowix_print_element_css_build() {
    767         $preview = is_customize_preview();
    768         $css     = '';
    769         $motion  = [];
     736                $memo = flowix_element_css_data( true );
     737                return $memo;
     738        }
     739        $memo = flowix_element_css_data( false );
     740        return $memo;
     741}
     742
     743
     744/**
     745 * The element CSS (and motion / site effect settings) as plain data, so it can be cached and
     746 * then escaped as it is printed.
     747 *
     748 * @param bool $per_element Keep each element's CSS separate (Customizer preview).
     749 */
     750function flowix_element_css_data( $per_element ) {
     751        $data = [ 'css' => '', 'blocks' => [], 'motion' => [], 'site_css' => '', 'site_cfg' => [] ];
    770752        foreach ( flowix_styled_elements() as $id => $el ) {
    771753                $block = flowix_build_element_css( $el['sel'], $el['style'] );
    772                 if ( $preview ) {
    773                         echo '<style id="flowix-el-' . esc_attr( $id ) . '">' . $block . "</style>\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built from sanitized values.
     754                if ( $per_element ) {
     755                        $data['blocks'][ $id ] = $block;
    774756                } else {
    775                         $css .= $block;
    776                 }
    777         }
    778         if ( $css ) {
    779                 echo '<style id="flowix-element-styles">' . $css . "</style>\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built from sanitized values.
    780         }
    781 }
    782 add_action( 'wp_head', 'flowix_print_element_css', 999 );
     757                        $data['css'] .= $block;
     758                }
     759        }
     760        return $data;
     761}
     762
     763/**
     764 * Element CSS goes in last, after every other theme stylesheet (and the Brand Kit), so it always wins.
     765 * In the Customizer preview each element gets its own stylesheet handle ("flowix-el-{id}"), which the
     766 * preview script rewrites while you edit.
     767 */
     768function flowix_enqueue_element_css() {
     769        $data = flowix_page_element_css_data();
     770        foreach ( (array) ( $data['blocks'] ?? [] ) as $id => $block ) {
     771                $handle = 'flowix-el-' . sanitize_key( $id );
     772                wp_register_style( $handle, false, [ 'flowix-brand' ], FLOWIX_VERSION );
     773                wp_enqueue_style( $handle );
     774                wp_add_inline_style( $handle, wp_strip_all_tags( (string) $block ) );
     775        }
     776        if ( ! empty( $data['css'] ) ) {
     777                wp_register_style( 'flowix-element-styles', false, [ 'flowix-brand' ], FLOWIX_VERSION );
     778                wp_enqueue_style( 'flowix-element-styles' );
     779                wp_add_inline_style( 'flowix-element-styles', wp_strip_all_tags( (string) $data['css'] ) );
     780        }
     781}
     782add_action( 'wp_enqueue_scripts', 'flowix_enqueue_element_css', 1001 );
     783
    783784
    784785
    … …  
    797798                wp_send_json_error( null, 403 );
    798799        }
    799         $items = json_decode( wp_unslash( $_POST['items'] ?? '{}' ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- each value sanitized below.
     800        // JSON of element styles; every value is cleaned again by flowix_clean_style() / flowix_brand_clean() below.
     801        $items = json_decode( isset( $_POST['items'] ) ? sanitize_textarea_field( wp_unslash( $_POST['items'] ) ) : '{}', true );
    800802        $flat  = flowix_registry_flat();
    801803        $out   = [];
    … …  
    10091011                        'everyOdd'    => __( 'Every other one (1st, 3rd, 5th…)', 'flowix' ),
    10101012                        'everyEven'   => __( 'Every other one (2nd, 4th, 6th…)', 'flowix' ),
     1013                        'transparent' => __( 'transparent', 'flowix' ),
     1014                        // Sample words on the Design Gallery look thumbnails.
     1015                        'lookShop'    => __( 'Shop', 'flowix' ),
     1016                        'lookTagline' => __( 'Live better.', 'flowix' ),
     1017                        'lookButton'  => __( 'Shop now', 'flowix' ),
    10111018                ],
    10121019        ] );
  • flowix/1.1.2/inc/footer-builder.php

    r353748 r356478  
    1818        $title = [ 'title' => [ 'text', __( 'Heading', 'flowix' ) ] ];
    1919        return [
    20                 'brand'        => [ 'label' => __( 'Logo + Tagline', 'flowix' ), 'icon' => 'image', 'fields' => [ 'tag' => [ 'textarea', __( 'Tagline', 'flowix' ) ] ] ],
     20                'brand'        => [ 'label' => __( 'Logo + Tagline', 'flowix' ), 'icon' => 'image', 'fields' => [ 'tag' => [ flowix_multiline_field(), __( 'Tagline', 'flowix' ) ] ] ],
    2121                'social'       => [ 'label' => __( 'Social Icons', 'flowix' ), 'icon' => 'share-2', 'fields' => $title ],
    2222                'links_shop'   => [ 'label' => __( 'Shop Links (auto)', 'flowix' ), 'icon' => 'store', 'fields' => $title + [ 'n' => [ 'select', __( 'Categories to list', 'flowix' ), [ '3' => '3', '5' => '5', '8' => '8', '12' => '12' ] ] ] ],
    … …  
    3030                'widgets3'     => [ 'label' => __( 'Widget Area: Footer — Account', 'flowix' ), 'icon' => 'layout-grid' ],
    3131                'widgets4'     => [ 'label' => __( 'Widget Area: Footer — Info', 'flowix' ), 'icon' => 'layout-grid' ],
    32                 'contact'      => [ 'label' => __( 'Contact Details', 'flowix' ), 'icon' => 'map-pin', 'fields' => $title + [ 'addr' => [ 'textarea', __( 'Address', 'flowix' ) ], 'phone' => [ 'text', __( 'Phone', 'flowix' ) ], 'email' => [ 'text', __( 'Email', 'flowix' ) ], 'hours' => [ 'text', __( 'Opening hours', 'flowix' ) ] ] ],
     32                'contact'      => [ 'label' => __( 'Contact Details', 'flowix' ), 'icon' => 'map-pin', 'fields' => $title + [ 'addr' => [ flowix_multiline_field(), __( 'Address', 'flowix' ) ], 'phone' => [ 'text', __( 'Phone', 'flowix' ) ], 'email' => [ 'text', __( 'Email', 'flowix' ) ], 'hours' => [ 'text', __( 'Opening hours', 'flowix' ) ] ] ],
    3333                'payments'     => [ 'label' => __( 'Payment Badges', 'flowix' ), 'icon' => 'credit-card', 'fields' => [ 'list' => [ 'text', __( 'Badges (comma separated)', 'flowix' ) ] ] ],
    3434                'copyright'    => [ 'label' => __( 'Copyright', 'flowix' ), 'icon' => 'copyright', 'fields' => [ 'text' => [ 'text', __( 'Text — {year} and {site} are filled in for you', 'flowix' ) ] ] ],
    35                 'text1'        => [ 'label' => __( 'Text / HTML 1', 'flowix' ), 'icon' => 'type', 'fields' => $title + [ 'html' => [ 'textarea', __( 'Text (links and bold allowed)', 'flowix' ) ] ] ],
    36                 'text2'        => [ 'label' => __( 'Text / HTML 2', 'flowix' ), 'icon' => 'type', 'fields' => $title + [ 'html' => [ 'textarea', __( 'Text (links and bold allowed)', 'flowix' ) ] ] ],
     35                'text1'        => [ 'label' => __( 'Text / HTML 1', 'flowix' ), 'icon' => 'type', 'fields' => $title + [ 'html' => [ flowix_multiline_field(), __( 'Text (links and bold allowed)', 'flowix' ) ] ] ],
     36                'text2'        => [ 'label' => __( 'Text / HTML 2', 'flowix' ), 'icon' => 'type', 'fields' => $title + [ 'html' => [ flowix_multiline_field(), __( 'Text (links and bold allowed)', 'flowix' ) ] ] ],
    3737                'button'       => [ 'label' => __( 'Button', 'flowix' ), 'icon' => 'mouse-pointer-click', 'fields' => [ 't' => [ 'text', __( 'Label', 'flowix' ) ], 'u' => [ 'url', __( 'Link', 'flowix' ) ] ] ],
    3838                'backtotop'    => [ 'label' => __( 'Back to Top Link', 'flowix' ), 'icon' => 'arrow-up' ],
    … …  
    6161}
    6262
     63/** Class for a column layout preset: "1.4fr 1fr" → "fx-fgrid--1p4fr-1fr" (rules in header-builder.css). */
     64function flowix_footer_grid_class( $grid ) {
     65        return 'fx-fgrid--' . str_replace( [ '.', ' ' ], [ 'p', '-' ], $grid );
     66}
     67
    6368/** Footer tagline before the owner writes their own: shop wording with WooCommerce, else the site tagline. */
    6469function flowix_footer_tagline_default() {
    6570        return class_exists( 'WooCommerce' ) ? __( 'Your South African marketplace for quality goods, delivered to your door.', 'flowix' ) : get_bloginfo( 'description' );
     71}
     72
     73/** Footer item the design gallery looks put in their sign-up column. */
     74function flowix_footer_signup_item() {
     75        return 'contact';
    6676}
    6777
    … …  
    8191                        'links_account' => [ 'title' => __( 'My Account', 'flowix' ) ],
    8292                        'links_info'    => [ 'title' => __( 'Information', 'flowix' ) ],
    83                         'newsletter'    => [ 'title' => __( 'Stay in the loop', 'flowix' ), 'text' => $shop ? __( 'New arrivals and deals, straight to your inbox.', 'flowix' ) : __( 'New posts and news, straight to your inbox.', 'flowix' ) ],
    8493                        'contact'       => [ 'title' => __( 'Get in touch', 'flowix' ) ],
    8594                        'payments'      => [ 'list' => 'PayFast, Visa, Mastercard, EFT' ],
    … …  
    127136                        'grid'  => $grid,
    128137                        'cols'  => $cols,
    129                         'mcols' => in_array( (string) ( $m['mcols'] ?? '2' ), [ '1', '2' ], true ) ? (string) $m['mcols'] : '2',
     138                        'mcols' => '1' === (string) ( $m['mcols'] ?? '2' ) ? '1' : '2',
    130139                ],
    131140        ];
    … …  
    146155                                        break;
    147156                                case 'textarea':
     157                                case 'line':
    148158                                        $v = wp_kses( (string) $v, [ 'a' => [ 'href' => true, 'target' => true, 'rel' => true ], 'strong' => [], 'b' => [], 'em' => [], 'i' => [], 'br' => [], 'span' => [ 'class' => true ] ] );
    149159                                        break;
    … …  
    309319function flowix_render_footer() {
    310320        $cfg = flowix_footer_config();
    311         echo '<footer id="colophon" class="fx-footer" role="contentinfo" itemscope itemtype="https://schema.org/WPFooter">';
     321        echo '<footer id="colophon" class="fx-footer" role="contentinfo">';
    312322
    313323        if ( $cfg['main']['on'] ) {
    … …  
    320330                        $cols .= '<div class="footer-col fx-fcol fx-fcol--' . ( $i + 1 ) . '">' . $html . '</div>';
    321331                }
    322                 echo '<div class="footer-main fx-fmain fx-fmain--m' . esc_attr( $cfg['main']['mcols'] ) . '" style="--fx-fgrid: ' . esc_attr( $cfg['main']['grid'] ) . '">' . $cols . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- items escape their own output.
     332                echo flowix_kses( '<div class="footer-main fx-fmain fx-fmain--m' . esc_attr( $cfg['main']['mcols'] ) . ' ' . esc_attr( flowix_footer_grid_class( $cfg['main']['grid'] ) ) . '">' . $cols . '</div>' );
    323333        }
    324334
    … …  
    336346                }
    337347                if ( $any ) {
    338                         echo '<div class="footer-bottom fx-fbottom">' . $zones . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- items escape their own output.
     348                        echo flowix_kses( '<div class="footer-bottom fx-fbottom">' . $zones . '</div>' );
    339349                }
    340350        }
  • flowix/1.1.2/inc/header-builder.php

    r353748 r356478  
    3636                'button'         => [ 'label' => __( 'Button 1', 'flowix' ), 'icon' => 'mouse-pointer-click', 'fields' => [ 't' => [ 'text', __( 'Label', 'flowix' ) ], 'u' => [ 'url', __( 'Link', 'flowix' ) ], 's' => [ 'select', __( 'Style', 'flowix' ), [ 'gold' => __( 'Accent', 'flowix' ), 'primary' => __( 'Dark', 'flowix' ), 'ghost' => __( 'Outline', 'flowix' ) ] ], 'n' => [ 'check', __( 'Open in a new tab', 'flowix' ) ] ] ],
    3737                'button2'        => [ 'label' => __( 'Button 2', 'flowix' ), 'icon' => 'mouse-pointer-click', 'fields' => [ 't' => [ 'text', __( 'Label', 'flowix' ) ], 'u' => [ 'url', __( 'Link', 'flowix' ) ], 's' => [ 'select', __( 'Style', 'flowix' ), [ 'gold' => __( 'Accent', 'flowix' ), 'primary' => __( 'Dark', 'flowix' ), 'ghost' => __( 'Outline', 'flowix' ) ] ], 'n' => [ 'check', __( 'Open in a new tab', 'flowix' ) ] ] ],
    38                 'text'           => [ 'label' => __( 'Text / HTML 1', 'flowix' ), 'icon' => 'type', 'fields' => [ 'h' => [ 'textarea', __( 'Text (links and bold allowed)', 'flowix' ) ] ] ],
    39                 'text2'          => [ 'label' => __( 'Text / HTML 2', 'flowix' ), 'icon' => 'type', 'fields' => [ 'h' => [ 'textarea', __( 'Text (links and bold allowed)', 'flowix' ) ] ] ],
     38                'text'           => [ 'label' => __( 'Text / HTML 1', 'flowix' ), 'icon' => 'type', 'fields' => [ 'h' => [ flowix_multiline_field(), __( 'Text (links and bold allowed)', 'flowix' ) ] ] ],
     39                'text2'          => [ 'label' => __( 'Text / HTML 2', 'flowix' ), 'icon' => 'type', 'fields' => [ 'h' => [ flowix_multiline_field(), __( 'Text (links and bold allowed)', 'flowix' ) ] ] ],
    4040                'phone'          => [ 'label' => __( 'Phone Number', 'flowix' ), 'icon' => 'phone', 'fields' => [ 'v' => [ 'text', __( 'Phone number', 'flowix' ) ] ] ],
    4141                'email'          => [ 'label' => __( 'Email Address', 'flowix' ), 'icon' => 'mail', 'fields' => [ 'v' => [ 'text', __( 'Email address', 'flowix' ) ] ] ],
    … …  
    119119                                        break;
    120120                                case 'textarea':
     121                                case 'line':
    121122                                        $v = wp_kses( (string) $v, [ 'a' => [ 'href' => true, 'target' => true, 'rel' => true ], 'strong' => [], 'b' => [], 'em' => [], 'i' => [], 'br' => [], 'span' => [ 'class' => true ] ] );
    122123                                        break;
    … …  
    240241                        }
    241242                        $count = WC()->cart ? WC()->cart->get_cart_contents_count() : 0;
    242                         return '<button type="button" class="header-util-btn fx-hitem fx-hitem--cart" data-flowix-open="cart" aria-label="' . esc_attr__( 'Open cart', 'flowix' ) . '" aria-expanded="false" aria-controls="mini-cart-drawer" style="position:relative;">' . flowix_inline_icon( 'shopping-bag', 17 ) . '<span class="cart-item-count" aria-live="polite">' . esc_html( $count ) . '</span></button>';
     243                        return '<button type="button" class="header-util-btn fx-hitem fx-hitem--cart" data-flowix-open="cart" aria-label="' . esc_attr__( 'Open cart', 'flowix' ) . '" aria-expanded="false" aria-controls="mini-cart-drawer">' . flowix_inline_icon( 'shopping-bag', 17 ) . '<span class="cart-item-count" aria-live="polite">' . esc_html( $count ) . '</span></button>';
    243244
    244245                case 'cart_total':
    … …  
    334335        $echo = is_array( $args ) ? ( $args['echo'] ?? true ) : ( is_object( $args ) ? ( $args->echo ?? true ) : true );
    335336        if ( $echo ) {
    336                 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
     337                echo flowix_kses( $html );
    337338                return '';
    338339        }
    … …  
    377378                        }
    378379                        echo '<div class="fx-hrow fx-hrow--' . esc_attr( $row ) . ' fx-' . esc_attr( $dev ) . ' ' . esc_attr( $row_class[ $row ][0] ) . '">';
    379                         echo '<div class="fx-hrow-inner ' . esc_attr( $row_class[ $row ][1] ) . '">' . implode( '', $zones ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- items escape their own output.
     380                        echo flowix_kses( '<div class="fx-hrow-inner ' . esc_attr( $row_class[ $row ][1] ) . '">' . implode( '', $zones ) . '</div>' );
    380381                        echo '</div>';
    381382                }
    … …  
    451452                        'mobile'    => __( 'Mobile', 'flowix' ),
    452453                        'rows'      => [ 'top' => __( 'Top Bar', 'flowix' ), 'main' => __( 'Main Row', 'flowix' ), 'bottom' => __( 'Bottom Row', 'flowix' ) ],
    453                         'zones'     => [ 'l' => __( 'Left', 'flowix' ), 'c' => __( 'Centre', 'flowix' ), 'r' => __( 'Right', 'flowix' ) ],
     454                        'zones'     => is_rtl() ? [ 'l' => __( 'Right', 'flowix' ), 'c' => __( 'Centre', 'flowix' ), 'r' => __( 'Left', 'flowix' ) ] : [ 'l' => __( 'Left', 'flowix' ), 'c' => __( 'Centre', 'flowix' ), 'r' => __( 'Right', 'flowix' ) ], // 'l' is the start side: on the right for right-to-left sites.
    454455                        'available' => __( 'Available items — drag into a row', 'flowix' ),
    455456                        'dragHint'  => __( 'Drag items between the boxes. Click an item to change its settings.', 'flowix' ),
  • flowix/1.1.2/inc/home-sections.php

    r353748 r356478  
    1919   ================================================================ */
    2020
    21 /** Icons offered for feature boxes. */
    22 function flowix_feature_icons() {
    23         $names = [ 'truck', 'shield-check', 'credit-card', 'rotate-ccw', 'headphones', 'gift', 'heart', 'star', 'leaf', 'award', 'clock', 'map-pin', 'package', 'sparkles', 'thumbs-up', 'lock', 'percent', 'tag', 'zap', 'globe', 'smile', 'recycle', 'hand-heart', 'badge-check', 'store', 'phone', 'mail', 'coffee', 'sun', 'flower-2' ];
    24         return array_combine( $names, array_map( fn( $n ) => ucwords( str_replace( '-', ' ', $n ) ), $names ) );
    25 }
    2621
    2722/**
    … …  
    3732                'url'  => [ 'url', __( 'Button link', 'flowix' ) ],
    3833        ];
    39         $sources = [
    40                 'featured' => __( 'Featured products', 'flowix' ),
    41                 'sale'     => __( 'On sale', 'flowix' ),
    42                 'new'      => __( 'Newest', 'flowix' ),
    43                 'best'     => __( 'Best sellers', 'flowix' ),
    44                 'rated'    => __( 'Top rated', 'flowix' ),
    45                 'category' => __( 'From one category', 'flowix' ),
    46         ];
    47         $cats = [ '' => __( '— Choose —', 'flowix' ) ];
    48         if ( taxonomy_exists( 'product_cat' ) ) {
    49                 $terms = get_terms( [ 'taxonomy' => 'product_cat', 'hide_empty' => false ] );
    50                 if ( ! is_wp_error( $terms ) ) {
    51                         foreach ( $terms as $t ) {
    52                                 $cats[ $t->slug ] = $t->name;
    53                         }
    54                 }
    55         }
    5634        $post_cats = [ '' => __( 'All categories', 'flowix' ) ];
    5735        foreach ( get_categories( [ 'hide_empty' => false ] ) as $t ) {
    5836                $post_cats[ $t->slug ] = $t->name;
    5937        }
    60         $icons = flowix_feature_icons();
    61         $features = [ 'eyebrow' => [ 'text', __( 'Small label above', 'flowix' ) ], 'heading' => [ 'text', __( 'Heading', 'flowix' ) ] ];
    62         for ( $i = 1; $i <= 4; $i++ ) {
    63                 /* translators: %d: feature number */
    64                 $features[ "i{$i}" ] = [ 'icon', sprintf( __( 'Feature %d — icon', 'flowix' ), $i ), $icons ];
    65                 /* translators: %d: feature number */
    66                 $features[ "t{$i}" ] = [ 'text', sprintf( __( 'Feature %d — title', 'flowix' ), $i ) ];
    67                 /* translators: %d: feature number */
    68                 $features[ "x{$i}" ] = [ 'textarea', sprintf( __( 'Feature %d — text', 'flowix' ), $i ) ];
    69         }
    70 
    7138        return [
    7239                // Built-in sections (one each)
    … …  
    7643                'categories'   => [ 'label' => __( 'Categories', 'flowix' ), 'icon' => 'layout-grid', 'builtin' => true, 'woo' => true, 'note' => __( 'Edit its text under Flowix Theme → Categories Section.', 'flowix' ) ],
    7744                'featured'     => [ 'label' => __( 'Featured Products', 'flowix' ), 'icon' => 'star', 'builtin' => true, 'woo' => true, 'note' => __( 'Edit its text under Flowix Theme → Featured Products.', 'flowix' ) ],
    78                 'testimonials' => [ 'label' => __( 'Testimonials', 'flowix' ), 'icon' => 'quote', 'builtin' => true, 'note' => __( 'Edit the reviews under Flowix Theme → Testimonials.', 'flowix' ) ],
    79                 'vendor'       => [ 'label' => __( 'Vendor Call-to-Action', 'flowix' ), 'icon' => 'store', 'builtin' => true, 'note' => __( 'Edit its text under Flowix Theme → Vendor CTA Section.', 'flowix' ) ],
    80 
    8145                // Add as many as you like
    82                 'banner'       => [ 'label' => __( 'Image Banner', 'flowix' ), 'icon' => 'image', 'fields' => [
    83                         'image'   => [ 'image', __( 'Background image', 'flowix' ) ],
    84                         'eyebrow' => [ 'text', __( 'Small label above', 'flowix' ) ],
    85                         'heading' => [ 'text', __( 'Heading', 'flowix' ) ],
    86                         'text'    => [ 'textarea', __( 'Text', 'flowix' ) ],
    87                 ] + $btn + [
    88                         'height'  => [ 'select', __( 'Height', 'flowix' ), [ 'm' => __( 'Medium', 'flowix' ), 's' => __( 'Short', 'flowix' ), 'l' => __( 'Tall', 'flowix' ), 'full' => __( 'Full screen', 'flowix' ) ] ],
    89                         'align'   => [ 'select', __( 'Text position', 'flowix' ), [ 'center' => __( 'Centre', 'flowix' ), 'left' => __( 'Left', 'flowix' ), 'right' => __( 'Right', 'flowix' ) ] ],
    90                 ] ],
    9146                'posts'        => [ 'label' => __( 'Latest Posts', 'flowix' ), 'icon' => 'newspaper', 'fields' => [
    9247                        'eyebrow' => [ 'text', __( 'Small label above', 'flowix' ) ],
    … …  
    9550                        'count'   => [ 'number', __( 'How many', 'flowix' ), 1, 12 ],
    9651                ] + $btn ],
    97                 'text_image'   => [ 'label' => __( 'Image + Text', 'flowix' ), 'icon' => 'columns-2', 'fields' => [
    98                         'image'   => [ 'image', __( 'Image', 'flowix' ) ],
    99                         'side'    => [ 'select', __( 'Image side', 'flowix' ), [ 'left' => __( 'Left', 'flowix' ), 'right' => __( 'Right', 'flowix' ) ] ],
    100                         'eyebrow' => [ 'text', __( 'Small label above', 'flowix' ) ],
    101                         'heading' => [ 'text', __( 'Heading', 'flowix' ) ],
    102                         'text'    => [ 'textarea', __( 'Text', 'flowix' ) ],
    103                 ] + $btn ],
    104                 'features'     => [ 'label' => __( 'Feature Icons', 'flowix' ), 'icon' => 'sparkles', 'fields' => $features ],
    105                 'cta'          => [ 'label' => __( 'Call to Action', 'flowix' ), 'icon' => 'megaphone', 'fields' => [
    106                         'eyebrow' => [ 'text', __( 'Small label above', 'flowix' ) ],
    107                         'heading' => [ 'text', __( 'Heading', 'flowix' ) ],
    108                         'text'    => [ 'textarea', __( 'Text', 'flowix' ) ],
    109                 ] + $btn + [
    110                         'btn2'    => [ 'text', __( 'Second button label', 'flowix' ) ],
    111                         'url2'    => [ 'url', __( 'Second button link', 'flowix' ) ],
    112                 ] ],
    11352                'spacer'       => [ 'label' => __( 'Spacer', 'flowix' ), 'icon' => 'separator-horizontal', 'fields' => [
    114                         'h'       => [ 'number', __( 'Height (px)', 'flowix' ), 0, 600 ],
     53                        'h'       => [ 'number', __( 'Height (px, in steps of 10)', 'flowix' ), 0, 600 ],
    11554                ] ],
    11655        ];
    … …  
    12463function flowix_section_defaults_base( $type ) {
    12564        $d = [
    126                 'banner'     => [ 'eyebrow' => __( 'New season', 'flowix' ), 'heading' => __( 'Summer Collection', 'flowix' ), 'text' => __( 'Fresh styles, made for sunny days.', 'flowix' ), 'btn' => __( 'Shop the collection', 'flowix' ), 'url' => '/shop/', 'height' => 'm', 'align' => 'center' ],
    12765                'posts'      => [ 'eyebrow' => __( 'From the blog', 'flowix' ), 'heading' => __( 'Latest posts', 'flowix' ), 'cat' => '', 'count' => 3 ],
    128                 'text_image' => [ 'side' => 'left', 'eyebrow' => __( 'Our story', 'flowix' ), 'heading' => __( 'Made with care', 'flowix' ), 'text' => __( 'Tell your customers what makes you different — where your products come from and why you love them.', 'flowix' ), 'btn' => __( 'Learn more', 'flowix' ), 'url' => '/about/' ],
    129                 'features'   => [ 'heading' => __( 'Why shop with us', 'flowix' ), 'i1' => 'truck', 't1' => __( 'Fast delivery', 'flowix' ), 'x1' => __( 'Nationwide in 2–4 days.', 'flowix' ), 'i2' => 'shield-check', 't2' => __( 'Secure checkout', 'flowix' ), 'x2' => __( 'Your payment details stay safe.', 'flowix' ), 'i3' => 'rotate-ccw', 't3' => __( 'Easy returns', 'flowix' ), 'x3' => __( '30 days to change your mind.', 'flowix' ), 'i4' => 'headphones', 't4' => __( 'Real support', 'flowix' ), 'x4' => __( 'Talk to a person, not a bot.', 'flowix' ) ],
    130                 'cta'        => [ 'eyebrow' => __( 'Limited offer', 'flowix' ), 'heading' => __( 'Get 10% off your first order', 'flowix' ), 'text' => __( 'Sign up today and save on everything.', 'flowix' ), 'btn' => __( 'Claim my discount', 'flowix' ), 'url' => '/my-account/' ],
    13166                'spacer'     => [ 'h' => 60 ],
    13267        ];
    … …  
    14479                return $list;
    14580        }
    146         foreach ( [ 'hero', 'page_content', 'trust', 'categories', 'featured', 'testimonials', 'vendor' ] as $t ) {
     81        $builtin = [ 'hero', 'page_content', 'trust', 'categories', 'featured' ];
     82        foreach ( $builtin as $t ) {
    14783                $list[] = [ 'id' => $t, 'type' => $t, 'on' => true, 'd' => (object) [] ];
    14884        }
    … …  
    178114                }
    179115                $seen[ $type ] = true;
    180                 if ( ! empty( $t['block'] ) ) {
    181                         $out[] = [ 'id' => $id, 'type' => $type, 'on' => ! empty( $sec['on'] ), 'd' => (object) flowix_block_clean( $t['block'], (array) ( $sec['d'] ?? [] ) ) ];
    182                         continue;
    183                 }
    184116                $data          = [];
    185117                $raw           = (array) ( $sec['d'] ?? [] );
    … …  
    248180                if ( ! empty( $types[ $sec['type'] ]['builtin'] ) ) {
    249181                        get_template_part( 'template-parts/home/' . $sec['type'] );
    250                 } elseif ( ! empty( $types[ $sec['type'] ]['block'] ) ) {
    251                         echo '<section class="fx-sec fx-sec--block section fx-sec-' . esc_attr( $sec['id'] ) . '"><div class="container">' . flowix_render_block( $types[ $sec['type'] ]['block'], (array) $sec['d'], 'fxs-' . $sec['id'] ) . '</div></section>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- block output is escaped.
    252182                } else {
    253183                        flowix_render_section( $sec );
    … …  
    282212
    283213        switch ( $type ) {
    284                 case 'banner':
    285                         $img = ! empty( $d['image']['id'] ) ? wp_get_attachment_url( $d['image']['id'] ) : '';
    286                         echo '<section class="' . esc_attr( $cls . ' fx-banner--' . ( $d['height'] ?? 'm' ) . ' fx-banner--' . ( $d['align'] ?? 'center' ) ) . '"' . ( $img ? ' style="--fx-banner-img:url(\'' . esc_url( $img ) . '\')"' : '' ) . '>';
    287                         echo '<div class="container fx-banner-inner"><div class="fx-banner-content">';
    288                         if ( ! empty( $d['eyebrow'] ) ) {
    289                                 echo '<span class="eyebrow fx-banner-eyebrow">' . esc_html( $d['eyebrow'] ) . '</span>';
    290                         }
    291                         if ( ! empty( $d['heading'] ) ) {
    292                                 echo '<h2 class="fx-banner-title">' . esc_html( $d['heading'] ) . '</h2>';
    293                         }
    294                         if ( ! empty( $d['text'] ) ) {
    295                                 echo '<div class="fx-banner-text">' . wp_kses_post( wpautop( $d['text'] ) ) . '</div>';
    296                         }
    297                         echo flowix_section_button( $d['btn'] ?? '', $d['url'] ?? '' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    298                         echo '</div></div></section>';
    299                         break;
    300 
    301214                case 'posts':
    302215                        $q = new WP_Query( [
    … …  
    308221                        ] );
    309222                        echo '<section id="' . esc_attr( 'fx-' . $sec['id'] ) . '" class="' . esc_attr( $cls . ' section' ) . '"><div class="container">';
    310                         echo flowix_section_heading( $d ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     223                        echo flowix_kses( flowix_section_heading( $d ) );
    311224                        if ( $q->have_posts() ) {
    312225                                echo '<div class="products-grid fx-posts-grid">';
    … …  
    321234                        }
    322235                        if ( ! empty( $d['btn'] ) ) {
    323                                 echo '<div class="fx-sec-actions">' . flowix_section_button( $d['btn'], $d['url'] ?? '', 'btn btn--primary btn--lg' ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     236                                echo flowix_kses( '<div class="fx-sec-actions">' . flowix_section_button( $d['btn'], $d['url'] ?? '', 'btn btn--primary btn--lg' ) . '</div>' );
    324237                        }
    325238                        echo '</div></section>';
    326239                        break;
    327240
    328                 case 'text_image':
    329                         $img = ! empty( $d['image']['id'] ) ? wp_get_attachment_image( $d['image']['id'], 'large', false, [ 'class' => 'fx-ti-img' ] ) : '<div class="fx-ti-img fx-ti-placeholder"></div>';
    330                         echo '<section class="' . esc_attr( $cls . ' section fx-ti--' . ( $d['side'] ?? 'left' ) ) . '"><div class="container fx-ti-inner">';
    331                         echo '<div class="fx-ti-media">' . $img . '</div><div class="fx-ti-content">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    332                         echo flowix_section_heading( $d, false ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    333                         if ( ! empty( $d['text'] ) ) {
    334                                 echo '<div class="fx-ti-text">' . wp_kses_post( wpautop( $d['text'] ) ) . '</div>';
    335                         }
    336                         echo flowix_section_button( $d['btn'] ?? '', $d['url'] ?? '' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    337                         echo '</div></div></section>';
    338                         break;
    339 
    340                 case 'features':
    341                         echo '<section class="' . esc_attr( $cls . ' section' ) . '"><div class="container">';
    342                         echo flowix_section_heading( $d ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    343                         echo '<div class="fx-features">';
    344                         for ( $i = 1; $i <= 4; $i++ ) {
    345                                 if ( empty( $d[ "t{$i}" ] ) && empty( $d[ "x{$i}" ] ) ) {
    346                                         continue;
    347                                 }
    348                                 echo '<div class="fx-feature">';
    349                                 if ( ! empty( $d[ "i{$i}" ] ) ) {
    350                                         echo '<span class="fx-feature-icon">' . flowix_inline_icon( $d[ "i{$i}" ], 28, 1.6 ) . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    351                                 }
    352                                 if ( ! empty( $d[ "t{$i}" ] ) ) {
    353                                         echo '<h3 class="fx-feature-title">' . esc_html( $d[ "t{$i}" ] ) . '</h3>';
    354                                 }
    355                                 if ( ! empty( $d[ "x{$i}" ] ) ) {
    356                                         echo '<p class="fx-feature-text">' . wp_kses_post( $d[ "x{$i}" ] ) . '</p>';
    357                                 }
    358                                 echo '</div>';
    359                         }
    360                         echo '</div></div></section>';
    361                         break;
    362 
    363                 case 'cta':
    364                         echo '<section class="' . esc_attr( $cls . ' section' ) . '"><div class="container fx-cta-inner">';
    365                         echo flowix_section_heading( $d ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    366                         if ( ! empty( $d['text'] ) ) {
    367                                 echo '<div class="fx-cta-text">' . wp_kses_post( wpautop( $d['text'] ) ) . '</div>';
    368                         }
    369                         echo '<div class="fx-sec-actions">' . flowix_section_button( $d['btn'] ?? '', $d['url'] ?? '' ) . flowix_section_button( $d['btn2'] ?? '', $d['url2'] ?? '', 'btn btn--primary btn--lg' ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    370                         echo '</div></section>';
    371                         break;
    372 
    373241                case 'spacer':
    374                         echo '<div class="' . esc_attr( $cls ) . '" style="height:' . (int) ( $d['h'] ?? 60 ) . 'px" aria-hidden="true"></div>';
     242                        $h = (int) round( max( 0, min( 600, (int) ( $d['h'] ?? 60 ) ) ) / 10 ) * 10;
     243                        echo '<div class="' . esc_attr( $cls . ' fx-spacer--' . $h ) . '" aria-hidden="true"></div>';
    375244                        break;
    376245        }
    … …  
    391260                        'sec_title'      => [ __( 'Added Section Headings', 'flowix' ), '.fx-sec-title', $tb ],
    392261                        'sec_btn'        => [ __( 'Added Section Buttons', 'flowix' ), '.fx-sec-btn', $tb ],
    393                         'banner'         => [ __( 'Image Banners', 'flowix' ), '.fx-sec--banner', $b ],
    394                         'banner_content' => [ __( 'Image Banner — Text Box', 'flowix' ), '.fx-banner-content', $b ],
    395                         'banner_title'   => [ __( 'Image Banner — Heading', 'flowix' ), '.fx-banner-title', $tb ],
    396                         'banner_text'    => [ __( 'Image Banner — Text', 'flowix' ), '.fx-banner-text', $tb ],
    397262                        'posts_sec'      => [ __( 'Latest Posts Sections', 'flowix' ), '.fx-sec--posts', $b ],
    398263                        'post_card'      => [ __( 'Post Cards', 'flowix' ), '.fx-post-card', $b ],
    399                         'ti_sec'         => [ __( 'Image + Text Sections', 'flowix' ), '.fx-sec--text_image', $b ],
    400                         'ti_img'         => [ __( 'Image + Text — Image', 'flowix' ), '.fx-ti-img', [ 'image' ] ],
    401                         'ti_text'        => [ __( 'Image + Text — Text', 'flowix' ), '.fx-ti-text', $tb ],
    402                         'features_sec'   => [ __( 'Feature Icon Sections', 'flowix' ), '.fx-sec--features', $b ],
    403                         'feature'        => [ __( 'Feature Box', 'flowix' ), '.fx-feature', $b ],
    404                         'feature_icon'   => [ __( 'Feature Icon', 'flowix' ), '.fx-feature-icon', [ 'icon', 'box' ] ],
    405                         'feature_title'  => [ __( 'Feature Title', 'flowix' ), '.fx-feature-title', $tb ],
    406                         'feature_text'   => [ __( 'Feature Text', 'flowix' ), '.fx-feature-text', $tb ],
    407                         'cta_sec'        => [ __( 'Call-to-Action Sections', 'flowix' ), '.fx-sec--cta', $b ],
    408                         'cta_text'       => [ __( 'Call-to-Action Text', 'flowix' ), '.fx-cta-text', $tb ],
    409264                ],
    410265        ];
    … …  
    429284                'type'            => 'hidden',
    430285                'priority'        => 1,
    431                 'description'     => '<span class="notice notice-warning inline" style="display:block;padding:8px 12px;margin:0 0 12px">' . esc_html__( 'Your home page is set to show your latest posts, so these sections are not shown. To use them, choose "A static page" as your homepage.', 'flowix' ) . '<br><button type="button" class="button button-primary" style="margin-top:8px" data-flowix-static-home>' . esc_html__( 'Choose a home page', 'flowix' ) . '</button></span>',
     286                'description'     => '<span class="notice notice-warning inline fx-cz-notice">' . esc_html__( 'Your home page is set to show your latest posts, so these sections are not shown. To use them, choose "A static page" as your homepage.', 'flowix' ) . '<br><button type="button" class="button button-primary" data-flowix-static-home>' . esc_html__( 'Choose a home page', 'flowix' ) . '</button></span>',
    432287                'active_callback' => function () {
    433288                        return 'page' !== get_option( 'show_on_front' );
    … …  
    455310        foreach ( flowix_section_types() as $t ) {
    456311                $icons[ $t['icon'] ] = flowix_icon_library()[ $t['icon'] ] ?? '';
    457         }
    458         foreach ( array_keys( flowix_feature_icons() ) as $n ) {
    459                 $icons[ $n ] = flowix_icon_library()[ $n ] ?? '';
    460312        }
    461313        $defaults = [];
  • flowix/1.1.2/inc/shop-features.php

    r353588 r356478  
    33 * Flowix — Shop Features (WooCommerce boosters, no plugins needed)
    44 *
    5  * Quick view, wishlist, colour/image/label swatches, badge designer, free-delivery bar,
    6  * sale countdown, low-stock urgency, delivery estimate, trust badges, Buy now,
    7  * added-to-cart behaviour, product page layout, load more / infinite scroll, recently viewed.
     5 * Shop display options: badges, free-delivery bar, trust badges, added-to-cart behaviour,
     6 * product page layout and more.
    87 *
    98 * Settings live in Customize → Flowix — Layout Builder → Shop Features (theme_mods flowix_shop_*).
    … …  
    7473                        'text'     => 'sanitize_text_field',
    7574                        'textarea' => 'sanitize_textarea_field',
    76                         'select'   => 'sanitize_key',
     75                        'select'   => 'flowix_sanitize_choice',
    7776                ][ $s[0] ];
    7877                $wp_customize->add_setting( $id, [ 'default' => $s[2], 'sanitize_callback' => $sanitize, 'transport' => 'refresh' ] );
    … …  
    166165add_action( 'woocommerce_before_single_product_summary', function() {
    167166        global $product;
    168         echo flowix_product_badges( $product ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     167        echo flowix_kses( flowix_product_badges( $product ) );
    169168}, 10 );
    170169
    171 /** Card extras on WooCommerce's shop-page cards: badges, quick view, wishlist, colour dots. */
     170/** Extras on WooCommerce's shop-page cards (badges and card buttons). */
    172171add_action( 'woocommerce_before_shop_loop_item_title', function() {
    173172        global $product;
    174         echo flowix_card_overlay( $product ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     173        echo flowix_kses( flowix_card_overlay( $product ) );
    175174}, 9 );
    176175
    … …  
    186185
    187186
    188 /* Header builder item: wishlist link with count. */
     187/* Header builder items added by the shop features. */
    189188add_filter( 'flowix_header_items_extra', function( $items ) {
    190189        $items['freeship'] = [ 'label' => __( 'Free-Delivery Progress', 'flowix' ), 'icon' => 'truck' ];
    … …  
    209208                        $icon  = count( $parts ) > 1 ? sanitize_key( $parts[0] ) : 'check';
    210209                        $text  = end( $parts );
    211                         echo '<li>' . flowix_inline_icon( isset( flowix_icon_library()[ $icon ] ) ? $icon : 'check', 18 ) . '<span>' . esc_html( $text ) . '</span></li>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     210                        echo flowix_kses( '<li>' . flowix_inline_icon( isset( flowix_icon_library()[ $icon ] ) ? $icon : 'check', 18 ) . '<span>' . esc_html( $text ) . '</span></li>' );
    212211                }
    213212                echo '</ul>';
    … …  
    272271        $pc    = min( 100, round( $total / $goal * 100 ) );
    273272        $msg   = $left > 0 ? str_replace( '{amount}', wp_strip_all_tags( wc_price( $left ) ), flowix_shop( 'fsb_msg' ) ) : flowix_shop( 'fsb_done' );
    274         return '<div class="fx-fsb' . ( $left <= 0 ? ' is-done' : '' ) . ( $compact ? ' fx-fsb--compact' : '' ) . '"><p class="fx-fsb-msg">' . esc_html( $msg ) . '</p><span class="fx-fsb-track"><span class="fx-fsb-fill" style="width:' . esc_attr( $pc ) . '%"></span></span></div>';
     273        return '<div class="fx-fsb' . ( $left <= 0 ? ' is-done' : '' ) . ( $compact ? ' fx-fsb--compact' : '' ) . '"><p class="fx-fsb-msg">' . esc_html( $msg ) . '</p><span class="fx-fsb-track"><span class="fx-fsb-fill fx-fsb-fill--' . absint( round( $pc / 5 ) * 5 ) . '"></span></span></div>';
    275274}
    276275
    277276add_action( 'woocommerce_before_cart_table', function() {
    278         echo flowix_free_shipping_bar(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     277        echo flowix_kses( flowix_free_shipping_bar() );
    279278} );
    280279add_action( 'woocommerce_before_mini_cart', function() {
    281         echo flowix_free_shipping_bar(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     280        echo flowix_kses( flowix_free_shipping_bar() );
    282281} );
    283282add_filter( 'woocommerce_add_to_cart_fragments', function( $f ) {
  • flowix/1.1.2/index.php

    r353748 r356478  
    77
    88get_header(); ?>
    9 <div class="container fx-blog" style="padding-top:48px;padding-bottom:64px;">
    10         <header class="fx-blog-header" style="margin-bottom:36px;padding-bottom:24px;border-bottom:1px solid var(--border-light);">
     9<div class="container fx-blog">
     10        <header class="fx-blog-header fx-page-header">
    1111                <h1>
    1212                        <?php
  • flowix/1.1.2/page.php

    r353588 r356478  
    88$flowix_fx_notitle = $flowix_fx_canvas || get_post_meta( get_the_ID(), '_flowix_hide_title', true );
    99?>
    10 <div class="container<?php echo $flowix_fx_canvas ? ' fx-canvas' : ''; ?>" style="<?php echo $flowix_fx_canvas ? 'max-width:none;padding:0;' : 'max-width:var(--content-width);padding-top:52px;padding-bottom:72px;'; ?>">
     10<div class="container <?php echo $flowix_fx_canvas ? 'fx-canvas' : 'fx-narrow'; ?>">
    1111        <?php while ( have_posts() ) : the_post(); ?>
    1212                <article <?php post_class(); ?>>
    1313                        <?php if ( get_the_title() && ! $flowix_fx_notitle ) : ?>
    14                                 <header style="margin-bottom:36px;padding-bottom:24px;border-bottom:1px solid var(--border-light);">
     14                                <header class="fx-page-header">
    1515                                        <h1><?php the_title(); ?></h1>
    1616                                </header>
    … …  
    2626                        </div>
    2727                        <?php if ( get_edit_post_link() && ! $flowix_fx_canvas ) : ?>
    28                                 <footer style="margin-top:32px;">
     28                                <footer class="fx-page-footer">
    2929                                        <?php edit_post_link( __( 'Edit this page', 'flowix' ), '<span class="edit-link">', '</span>' ); ?>
    3030                                </footer>
  • flowix/1.1.2/readme.txt

    r353748 r356478  
    11=== Flowix ===
    2 Contributors: djarnd
    3 Tags: e-commerce, custom-colors, custom-logo, custom-menu, editor-style, featured-images, footer-widgets, theme-options, threaded-comments, translation-ready, blog, wide-blocks
     2Contributors: goodiez
     3Tags: e-commerce, custom-colors, custom-logo, custom-menu, editor-style, featured-images, footer-widgets, theme-options, threaded-comments, translation-ready, rtl-language-support, blog, wide-blocks
    44Requires at least: 6.4
    55Tested up to: 7.1
    66Requires PHP: 8.0
    7 Stable tag: 1.1.1
     7Stable tag: 1.1.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2121* **Click to style** — switch it on in the preview and click anything to jump straight to its settings.
    2222* **Header builder** — drag the logo, menu, search, cart, account, buttons, contact details and social icons into a top bar, main row and bottom row, separately for desktop and mobile. Sticky, hide-on-scroll and shrink options.
    23 * **Footer builder** — choose a column layout and drop in menus, text, contact details, a newsletter form, payment icons and more.
    24 * **Home page builder** — reorder, hide and add sections: hero, trust bar, categories, featured products, testimonials, image banners, image + text, feature icons, call to action and spacers.
     23* **Footer builder** — choose a column layout and drop in menus, text, contact details, social icons, payment icons and more.
     24* **Home page builder** — reorder and hide the home page sections (hero, trust bar, page content, categories, featured products) and add latest-posts sections and spacers.
    2525* **One-click looks** — start from Blank Canvas, Original Artisan or Swiss Grid.
    2626* **Dark mode** — automatic, always on, or a switch your shoppers can use.
    2727* **Shop extras** — sale badges with the discount percentage, "New" and "Sold out" badges, a free-delivery progress bar in the cart, trust badges under Add to cart, product page layout options, and an "added to cart" message or slide-out cart.
    2828* **Page layout options** — hide the title or use a full-width canvas on any page.
     29* **Undo, redo & Before/After** — step back through your Customizer changes and compare with how your site looked before.
     30* **Right-to-left languages** — the layout mirrors for Arabic, Hebrew and other RTL languages.
    2931* **Export, import & reset** your design.
    30 
    31 Flowix Premium (sold separately at https://goodiez.co.za/flowix) adds more looks, backgrounds, motion and particle effects, content blocks and extra shop features. The free theme is complete on its own.
    3232
    3333== Installation ==
    … …  
    5252Yes. Everything is stored in the Customizer settings, not in the theme files.
    5353
     54= Does Flowix collect any data? =
     55
     56No. Flowix does not collect, store or send any personal data, and has no tracking.
     57
     58* Dark mode switch: when a visitor uses the switch, their choice (light or dark) is remembered in their own browser (localStorage key "flowix-theme"). It is never sent to your site or anyone else.
     59* Fonts: the fonts you choose in the Brand Kit are loaded from Google Fonts (fonts.googleapis.com and fonts.gstatic.com), which means visitors' browsers connect to Google to download them. See Google's privacy policy: https://policies.google.com/privacy
     60
    5461== Changelog ==
     62
     63= 1.1.2 =
     64* New: undo and redo in the Customizer, a history of your changes, and a Before/After view to compare your design with how it was.
     65* New: right-to-left language support (Arabic, Hebrew and others).
     66* Fix: the header menu no longer overlaps a centred logo.
     67* WordPress.org review fixes: version numbers in sync, translation template (languages/flowix.pot) included, removed unused page template entries.
     68* Accessibility: mobile menu button has a name and announces open/closed; keyboard focus stays inside the search, cart and menu panels while they are open and returns when they close (Escape closes them); dropdown menus open with the keyboard; the skip link shows below the admin toolbar.
     69* Long titles and links now wrap instead of overflowing; floated images and embeds no longer push the sidebar down.
     70* Template layout styles moved from the templates into the stylesheet.
     71* Removed the newsletter form and the testimonial, vendor and free-text home page sections (WordPress.org content-creation rules).
     72* Posts split over several pages now show their page links. Clearer keyboard focus on form fields.
     73* Theme styles and the dark mode script now load through WordPress's own functions; removed schema markup and leftover inline styles.
     74* The footer tagline and copyright line are now set only in the Footer Builder (the duplicate settings under Flowix Theme → Footer are gone).
    5575
    5676= 1.1.1 =
  • flowix/1.1.2/search.php

    r319286 r356478  
    55 */
    66get_header(); ?>
    7 <div class="container" style="padding-top:48px;padding-bottom:72px;">
    8         <header style="margin-bottom:36px;padding-bottom:24px;border-bottom:1px solid var(--border-light);">
     7<div class="container fx-list-wrap">
     8        <header class="fx-page-header">
    99                <span class="eyebrow"><?php esc_html_e( 'Search Results', 'flowix' ); ?></span>
    1010                <h1>
    … …  
    1313                                /* translators: %s: search query */
    1414                                esc_html__( 'Results for: "%s"', 'flowix' ),
    15                                 '<em>' . get_search_query() . '</em>'
     15                                '<em>' . esc_html( get_search_query( false ) ) . '</em>'
    1616                        );
    1717                        ?>
    1818                </h1>
    1919                <?php if ( have_posts() ) : ?>
    20                         <p class="lead" style="margin-top:8px;">
     20                        <p class="lead fx-page-sub">
    2121                                <?php
    2222                                printf(
    … …  
    3131
    3232        <?php if ( have_posts() ) : ?>
    33                 <div class="products-grid" style="grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:24px;">
     33                <div class="products-grid fx-search-grid">
    3434                        <?php while ( have_posts() ) : the_post(); ?>
    3535                                <article <?php post_class( 'product-card' ); ?>>
    3636                                        <?php if ( has_post_thumbnail() ) : ?>
    3737                                                <div class="product-card-media">
    38                                                         <a href="<?php the_permalink(); ?>"><?php the_post_thumbnail( 'flowix-product-card' ); ?></a>
     38                                                        <a href="<?php the_permalink(); ?>"><?php the_post_thumbnail( 'flowix-blog-thumb' ); ?></a>
    3939                                                </div>
    4040                                        <?php endif; ?>
    4141                                        <div class="product-card-body">
    42                                                 <h2 class="product-card-title" style="font-size:1.05rem;">
     42                                                <h2 class="product-card-title fx-search-title">
    4343                                                        <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
    4444                                                </h2>
    45                                                 <p style="font-size:0.88rem;color:var(--walnut);margin:0 0 14px;"><?php the_excerpt(); ?></p>
     45                                                <div class="fx-search-excerpt"><?php the_excerpt(); ?></div>
    4646                                                <a href="<?php the_permalink(); ?>" class="btn btn--sm"><?php esc_html_e( 'View', 'flowix' ); ?></a>
    4747                                        </div>
    … …  
    4949                        <?php endwhile; ?>
    5050                </div>
    51                 <div style="margin-top:40px;">
     51                <div class="fx-pagination-wrap">
    5252                        <?php the_posts_pagination(); ?>
    5353                </div>
    5454        <?php else : ?>
    55                 <div style="text-align:center;padding:60px 20px;">
    56                         <h2 style="font-size:1.4rem;margin-bottom:16px;"><?php esc_html_e( 'Nothing found.', 'flowix' ); ?></h2>
    57                         <p style="color:var(--walnut);margin-bottom:28px;">
     55                <div class="fx-nothing">
     56                        <h2><?php esc_html_e( 'Nothing found.', 'flowix' ); ?></h2>
     57                        <p>
    5858                                <?php esc_html_e( "We couldn't find anything matching your search. Try different keywords or browse our shop.", 'flowix' ); ?>
    5959                        </p>
  • flowix/1.1.2/single.php

    r353588 r356478  
    55 */
    66get_header(); ?>
    7 <div class="container" style="max-width:var(--content-width);padding-top:52px;padding-bottom:72px;">
     7<div class="container fx-narrow">
    88        <?php while ( have_posts() ) : the_post(); ?>
    9                 <article <?php post_class(); ?> itemscope itemtype="https://schema.org/BlogPosting">
    10                         <header style="margin-bottom:32px;">
    11                                 <div style="display:flex;align-items:center;gap:16px;margin-bottom:14px;flex-wrap:wrap;">
     9                <article <?php post_class(); ?>>
     10                        <header class="fx-post-header">
     11                                <div class="fx-post-meta">
    1212                                        <?php
    1313                                        $flowix_cats = get_the_category();
    1414                                        if ( $flowix_cats ) {
    15                                                 echo '<a href="' . esc_url( get_category_link( $flowix_cats[0]->term_id ) ) . '" class="eyebrow" style="margin:0;">' . esc_html( $flowix_cats[0]->name ) . '</a>';
     15                                                echo '<a href="' . esc_url( get_category_link( $flowix_cats[0]->term_id ) ) . '" class="eyebrow">' . esc_html( $flowix_cats[0]->name ) . '</a>';
    1616                                        }
    1717                                        ?>
    18                                         <time datetime="<?php echo esc_attr( get_the_date( 'c' ) ); ?>"
    19                                                   style="font-family:var(--font-ui);font-size:0.65rem;letter-spacing:0.14em;text-transform:uppercase;color:var(--walnut);">
     18                                        <time class="fx-post-date" datetime="<?php echo esc_attr( get_the_date( 'c' ) ); ?>">
    2019                                                <?php echo esc_html( get_the_date() ); ?>
    2120                                        </time>
    2221                                </div>
    23                                 <h1 itemprop="headline"><?php the_title(); ?></h1>
     22                                <h1><?php the_title(); ?></h1>
    2423                        </header>
    2524
    2625                        <?php if ( has_post_thumbnail() ) : ?>
    27                                 <div style="margin-bottom:36px;border-radius:var(--radius-sm);overflow:hidden;">
    28                                         <?php the_post_thumbnail( 'flowix-blog-thumb', [ 'itemprop' => 'image', 'style' => 'width:100%;' ] ); ?>
     26                                <div class="fx-post-thumb">
     27                                        <?php the_post_thumbnail( 'flowix-blog-thumb' ); ?>
    2928                                </div>
    3029                        <?php endif; ?>
    3130
    32                         <div class="entry-content" itemprop="articleBody">
    33                                 <?php the_content(); ?>
     31                        <div class="entry-content">
     32                                <?php
     33                                the_content();
     34                                wp_link_pages( [
     35                                        'before' => '<div class="page-links"><span>' . esc_html__( 'Pages:', 'flowix' ) . '</span>',
     36                                        'after'  => '</div>',
     37                                ] );
     38                                ?>
    3439                        </div>
    3540
    36                         <footer style="margin-top:40px;padding-top:24px;border-top:1px solid var(--border-light);">
    37                                 <?php the_tags( '<div style="margin-bottom:16px;">', ', ', '</div>' ); ?>
     41                        <footer class="fx-post-footer">
     42                                <?php the_tags( '<div class="fx-post-tags">', ', ', '</div>' ); ?>
    3843                                <?php
    3944                                the_post_navigation( [
    … …  
    4651        <?php endwhile; ?>
    4752        <?php if ( comments_open() || get_comments_number() ) : ?>
    48                 <div class="container" style="max-width:var(--content-width);padding-bottom:72px;">
     53                <div class="container fx-narrow fx-comments-wrap">
    4954                        <?php comments_template(); ?>
    5055                </div>
  • flowix/1.1.2/style.css

    r353748 r356478  
    55Author URI:        https://goodiez.co.za
    66Description:       A clean, fast WooCommerce theme you can restyle element by element from the Customizer, no code needed. Brand Kit for colours and fonts, per-element styling with "click to style", header, footer and home page builders, one-click looks, dark mode, and shop extras like discount badges, a free-delivery bar and trust badges.
    7 Version:           1.1.1
     7Version:           1.1.2
    88Requires at least: 6.4
    99Tested up to:      7.1
    … …  
    1212License URI:       http://www.gnu.org/licenses/gpl-2.0.html
    1313Text Domain:       flowix
    14 Tags:              e-commerce, custom-colors, custom-logo, custom-menu, editor-style, featured-images, footer-widgets, theme-options, threaded-comments, translation-ready, blog, wide-blocks
     14Tags:              e-commerce, custom-colors, custom-logo, custom-menu, editor-style, featured-images, footer-widgets, theme-options, threaded-comments, translation-ready, rtl-language-support, blog, wide-blocks
    1515
    1616Flowix WordPress Theme, Copyright 2026 Djarn Dhevron
    … …  
    134134}
    135135.skip-link:focus { top: 0; }
     136.admin-bar .skip-link:focus { top: 32px; }
     137@media (max-width: 782px) { .admin-bar .skip-link:focus { top: 46px; } }
    136138
    137139img { max-width: 100%; height: auto; display: block; }
    … …  
    433435  box-shadow: 0 0 0 3px rgba(155,123,58,0.12);
    434436}
     437/* Keyboard focus: a clear ring on fields (links and buttons keep the browser's own ring). */
     438input:focus-visible,
     439textarea:focus-visible,
     440select:focus-visible {
     441  outline: 2px solid var(--gold);
     442  outline-offset: 1px;
     443}
    435444
    436445input::placeholder,
    … …  
    641650}
    642651
    643 .primary-nav > li:hover .sub-menu {
     652.primary-nav > li:hover .sub-menu,
     653.primary-nav > li.is-open .sub-menu {
    644654  opacity: 1;
    645655  pointer-events: all;
    … …  
    21192129.woocommerce-info { border-top-color: var(--caramel) !important; }
    21202130
    2121 /* ╔══════════════════════════════════════════════
    2122    ║  TESTIMONIALS SECTION
    2123    ╚══════════════════════════════════════════════ */
    2124 .testimonials-grid {
    2125   display: grid;
    2126   grid-template-columns: repeat(3, 1fr);
    2127   gap: 24px;
    2128 }
    2129 
    2130 .testimonial-card {
    2131   background: var(--bg-surface, #fff);
    2132   border: 1px solid var(--bd-light, var(--border-light));
    2133   border-radius: var(--radius-sm);
    2134   padding: 32px;
    2135   position: relative;
    2136 }
    2137 
    2138 .testimonial-card::before {
    2139   content: '\201C';
    2140   font-family: var(--font-accent);
    2141   font-size: 5rem;
    2142   color: var(--gold-pale);
    2143   position: absolute;
    2144   top: 16px;
    2145   left: 24px;
    2146   line-height: 1;
    2147 }
    2148 
    2149 .testimonial-quote {
    2150   font-family: var(--font-accent);
    2151   font-size: 1rem;
    2152   font-style: italic;
    2153   color: var(--tx-muted, var(--walnut));
    2154   line-height: 1.7;
    2155   margin-bottom: 24px;
    2156   position: relative;
    2157   z-index: 1;
    2158   padding-top: 20px;
    2159 }
    2160 
    2161 .testimonial-author {
    2162   display: flex;
    2163   align-items: center;
    2164   gap: 12px;
    2165   border-top: 1px solid var(--bd-light, var(--border-light));
    2166   padding-top: 20px;
    2167 }
    2168 
    2169 .testimonial-avatar {
    2170   width: 44px;
    2171   height: 44px;
    2172   border-radius: 50%;
    2173   object-fit: cover;
    2174   background: var(--bg-tint2, var(--linen));
    2175 }
    2176 
    2177 .testimonial-name {
    2178   font-family: var(--font-ui);
    2179   font-size: 0.72rem;
    2180   font-weight: 700;
    2181   letter-spacing: 0.1em;
    2182   text-transform: uppercase;
    2183   color: var(--tx-mid, var(--mahogany));
    2184 }
    2185 
    2186 .testimonial-meta {
    2187   font-family: var(--font-body);
    2188   font-size: 0.8rem;
    2189   color: var(--border);
    2190 }
    2191 
    2192 .testimonial-stars {
    2193   color: var(--amber);
    2194   font-size: 0.7rem;
    2195   letter-spacing: 2px;
    2196   margin-bottom: 4px;
    2197 }
    2198 
    2199 /* ╔══════════════════════════════════════════════
    2200    ║  VENDOR / SELLER CTA SECTION
    2201    ╚══════════════════════════════════════════════ */
    2202 .vendor-cta {
    2203   background: var(--espresso);
    2204   padding: 72px 0;
    2205   position: relative;
    2206   overflow: hidden;
    2207 }
    2208 
    2209 .vendor-cta::before {
    2210   content: '';
    2211   position: absolute;
    2212   top: -50%;
    2213   right: -10%;
    2214   width: 500px;
    2215   height: 500px;
    2216   border-radius: 50%;
    2217   border: 1px solid rgba(155,123,58,0.1);
    2218   pointer-events: none;
    2219 }
    2220 
    2221 .vendor-cta::after {
    2222   content: '';
    2223   position: absolute;
    2224   bottom: -60%;
    2225   right: 5%;
    2226   width: 300px;
    2227   height: 300px;
    2228   border-radius: 50%;
    2229   border: 1px solid rgba(155,123,58,0.07);
    2230   pointer-events: none;
    2231 }
    2232 
    2233 .vendor-cta-inner {
    2234   position: relative;
    2235   z-index: 1;
    2236   display: grid;
    2237   grid-template-columns: 1fr auto;
    2238   gap: 48px;
    2239   align-items: center;
    2240 }
    2241 
    2242 .vendor-cta h2 {
    2243   font-family: var(--font-display);
    2244   font-size: clamp(1.6rem, 3vw, 2.4rem);
    2245   color: var(--cream);
    2246   margin-bottom: 12px;
    2247   font-weight: 400;
    2248 }
    2249 
    2250 .vendor-cta p {
    2251   color: rgba(237,228,212,0.7);
    2252   max-width: 480px;
    2253   font-size: 1rem;
    2254   margin-bottom: 0;
    2255 }
    2256 
    2257 .vendor-cta-actions {
    2258   display: flex;
    2259   gap: 14px;
    2260   flex-wrap: wrap;
    2261   justify-content: flex-end;
    2262   flex-shrink: 0;
    2263 }
    2264 
    2265 /* ╔══════════════════════════════════════════════
    2266    ║  NEWSLETTER SECTION
    2267    ╚══════════════════════════════════════════════ */
    2268 .newsletter-section {
    2269   background: var(--parchment);
    2270   border-top: 1px solid var(--border);
    2271   border-bottom: 1px solid var(--border);
    2272   padding: 48px 0;
    2273   text-align: center;
    2274 }
    2275 
    2276 .newsletter-form {
    2277   max-width: 480px;
    2278   margin: 24px auto 0;
    2279   display: flex;
    2280   gap: 0;
    2281 }
    2282 
    2283 .newsletter-form input[type="email"] {
    2284   border-radius: var(--radius-sm) 0 0 var(--radius-sm);
    2285   border-right: none;
    2286   background: #fff;
    2287 }
    2288 
    2289 .newsletter-form .btn {
    2290   border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    2291   white-space: nowrap;
    2292   padding: 13px 28px;
    2293   font-size: 0.68rem;
    2294   background: var(--hr-tx, var(--espresso));
    2295   color: var(--hr-bg, var(--cream)) !important;
    2296   border-color: var(--hr-tx, var(--espresso));
    2297   flex-shrink: 0;
    2298 }
    2299 .newsletter-section { color: color-mix(in srgb, var(--hr-tx) 75%, transparent); }
    2300 .newsletter-section :is(h2, .lead) { color: var(--hr-tx); }
    2301 .newsletter-section .lead { opacity: .75; }
    2302 .newsletter-section .eyebrow { color: var(--hr-acc); }
    23032131
    23042132/* ╔══════════════════════════════════════════════
    … …  
    26292457  .header-topbar { display: none; }
    26302458  .shop-layout { grid-template-columns: 1fr; }
    2631   .testimonials-grid { grid-template-columns: 1fr; }
    2632   .vendor-cta-inner { grid-template-columns: 1fr; }
    2633   .vendor-cta-actions { justify-content: flex-start; }
    26342459  .single-product-layout { grid-template-columns: 1fr; gap: 32px; }
    26352460}
    … …  
    26432468  .trust-bar-items { flex-direction: column; gap: 8px; align-items: flex-start; }
    26442469  .trust-item { border-right: none; padding: 6px 0; }
    2645   .newsletter-form { flex-direction: column; }
    2646   .newsletter-form input[type="email"] { border-radius: var(--radius-sm); border-right: 1.5px solid var(--border); }
    2647   .newsletter-form .btn { border-radius: var(--radius-sm); width: 100%; }
    26482470  .woocommerce-cart .cart-collaterals .cart_totals { float: none; width: 100%; }
    26492471  .woocommerce-MyAccount-navigation { width: 100%; }
    … …  
    27932615.fx-blog .page-numbers.current { background: var(--ink, #111); color: #fff; border-color: transparent; }
    27942616.fx-page-content .entry-content > *:first-child { margin-top: 0; }
     2617
     2618/* ╔══════════════════════════════════════════════
     2619   ║  TEMPLATE LAYOUT (pages, posts, search, 404)
     2620   ╚══════════════════════════════════════════════ */
     2621.fx-narrow { max-width: var(--content-width); padding-top: 52px; padding-bottom: 72px; }
     2622.fx-canvas { max-width: none; padding: 0; }
     2623.fx-comments-wrap { padding-top: 0; }
     2624.fx-list-wrap { padding-top: 48px; padding-bottom: 72px; }
     2625.fx-blog { padding-top: 48px; padding-bottom: 64px; }
     2626.fx-page-header { margin-bottom: 36px; padding-bottom: 24px; border-bottom: 1px solid var(--border-light); }
     2627.fx-page-sub { margin-top: 8px; }
     2628.fx-page-footer { margin-top: 32px; }
     2629.fx-post-header { margin-bottom: 32px; }
     2630.fx-post-meta { display: flex; align-items: center; gap: 16px; margin-bottom: 14px; flex-wrap: wrap; }
     2631.fx-post-meta .eyebrow { margin: 0; }
     2632.fx-post-date { font-family: var(--font-ui); font-size: 0.65rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--walnut); }
     2633.fx-post-thumb { margin-bottom: 36px; border-radius: var(--radius-sm); overflow: hidden; }
     2634.fx-post-thumb img { width: 100%; }
     2635.fx-post-footer { margin-top: 40px; padding-top: 24px; border-top: 1px solid var(--border-light); }
     2636.fx-post-tags { margin-bottom: 16px; }
     2637.fx-search-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 24px; }
     2638.fx-search-title { font-size: 1.05rem; }
     2639.fx-search-excerpt { font-size: 0.88rem; color: var(--walnut); margin: 0 0 14px; }
     2640.fx-search-excerpt p { margin: 0; }
     2641.fx-pagination-wrap { margin-top: 40px; }
     2642.fx-nothing { text-align: center; padding: 60px 20px; }
     2643.fx-nothing h2 { font-size: 1.4rem; margin-bottom: 16px; }
     2644.fx-nothing p { color: var(--walnut); margin-bottom: 28px; }
     2645.error-404 h1 { margin-bottom: 16px; }
     2646.error-404 .lead { max-width: 440px; margin: 0 auto 36px; }
     2647.error-404-actions { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
     2648.mobile-nav-header .site-title { color: var(--cream); font-size: 1.2rem; }
     2649.mobile-nav-footer { padding: 20px 24px; border-top: 1px solid rgba(155, 123, 58, 0.2); margin-top: auto; }
     2650.btn--block { display: block; width: 100%; text-align: center; }
     2651.btn--ghost-light { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.2); color: #fff; }
     2652.fx-section-more { text-align: center; margin-top: var(--space-xl); }
     2653
     2654/* ╔══════════════════════════════════════════════
     2655   ║  LONG WORDS, FLOATS AND KEYBOARD PANELS
     2656   ╚══════════════════════════════════════════════ */
     2657body { overflow-wrap: break-word; }
     2658h1, h2, h3, h4, h5, h6, .entry-title, .product_title, .widget-title { overflow-wrap: break-word; word-break: break-word; hyphens: auto; }
     2659.entry-content a, .comment-content a, .widget a { overflow-wrap: anywhere; }
     2660.entry-content::after, .comment-content::after, .widget::after, .clearfix::after { content: ""; display: table; clear: both; }
     2661.clear { clear: both; }
     2662.mini-cart-drawer:not(.open), .mobile-nav-drawer:not(.open), .search-overlay:not(.open) { visibility: hidden; transition-property: transform, opacity, visibility; transition-delay: 0s, 0s, var(--dur-lg, .35s); }
     2663.mini-cart-drawer.open, .mobile-nav-drawer.open, .search-overlay.open { visibility: visible; transition-delay: 0s; }
  • flowix/1.1.2/template-parts/home/featured.php

    r353588 r356478  
    2727                        wp_reset_postdata(); ?>
    2828                </div>
    29                 <div style="text-align:center;margin-top:var(--space-xl);">
     29                <div class="fx-section-more">
    3030                        <?php if ( class_exists( 'WooCommerce' ) ) : ?>
    3131                                <a href="<?php echo esc_url( get_permalink( wc_get_page_id( 'shop' ) ) ); ?>" class="btn btn--primary btn--lg">
  • flowix/1.1.2/template-parts/home/page_content.php

    r353748 r356478  
    1313        return;
    1414}
    15 $flowix_page = get_post( $flowix_front );
    16 if ( ! $flowix_page || '' === trim( (string) $flowix_page->post_content ) ) {
     15$flowix_query = new WP_Query( [ 'page_id' => $flowix_front, 'post_type' => 'page' ] );
     16if ( ! $flowix_query->have_posts() || '' === trim( (string) $flowix_query->posts[0]->post_content ) ) {
    1717        return;
    1818}
    19 $GLOBALS['post'] = $flowix_page; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
    20 setup_postdata( $flowix_page );
    2119?>
    2220<section class="section fx-page-content">
    2321        <div class="container entry-content">
    24                 <?php the_content(); ?>
     22                <?php
     23                while ( $flowix_query->have_posts() ) {
     24                        $flowix_query->the_post();
     25                        the_content();
     26                }
     27                wp_reset_postdata();
     28                ?>
    2529        </div>
    2630</section>
    27 <?php
    28 wp_reset_postdata();
  • flowix/1.1.2/template-parts/home/trust.php

    r353588 r356478  
    3232                        <div class="trust-item">
    3333                                <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
    34                                         <?php echo $flowix_svgs[ $flowix_i - 1 ]; // phpcs:ignore ?>
     34                                        <?php echo flowix_kses( $flowix_svgs[ $flowix_i - 1 ] ); ?>
    3535                                </svg>
    3636                                <?php echo esc_html( $flowix_text ); ?>
  • flowix/1.1.2/woocommerce.php

    r353588 r356478  
    1212$flowix_wrapper_class  = 'woocommerce-page-wrapper';
    1313if ( $flowix_has_sidebar ) $flowix_wrapper_class .= ' has-sidebar';
    14 if ( function_exists( 'flowix_cat_banner_on' ) && flowix_cat_banner_on() ) {
    15         echo flowix_cat_banner(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped while built.
    16 }
    1714?>
    1815
Note: See TracChangeset for help on using the changeset viewer.