Plugin Directory

Changeset 3743505


Ignore:
Timestamp:
10/13/2026 10:00:51 AM (13 days ago)
Author:
shiponkarmakar
Message:

Release v1.1.0

Location:
optimazz
Files:
6 added
58 edited
1 copied

Legend:

Unmodified
Added
Removed
  • optimazz/tags/1.1.0/build/index-rtl.css

    r3723128 r3743505  
    1 .oz-shell{align-items:flex-start;background:#fff;border:1px solid #e7ecef;border-radius:16px;color:#0f2027;display:flex;font-size:14px;margin:10px 0 130px 20px;min-height:640px;overflow:hidden}.oz-shell *{box-sizing:border-box}.oz-shell .dashicons{font-family:dashicons}.oz-sidebar{align-self:flex-start;background:#fff;display:flex;flex:0 0 264px;flex-direction:column;padding:18px 14px}.oz-sidebar__brand{align-items:center;border-bottom:1px solid #e7ecef;display:flex;gap:12px;margin-bottom:10px;padding:10px 10px 18px}.oz-sidebar__brand strong{display:block;font-size:18px;font-weight:750;letter-spacing:-.01em}.oz-sidebar__brand span{color:#6b7a86;font-size:11px}.oz-sidebar__brand-fallback{align-items:center;display:flex;gap:12px}.oz-sidebar__foot{border-top:1px solid #e7ecef;color:#6b7a86;font-size:11px;margin-top:12px;padding:12px 12px 2px}.oz-logo{background:linear-gradient(-135deg,#1dd1a1,#10ac81);border-radius:12px;color:#fff;display:grid;font-size:26px;height:44px;place-items:center;width:44px}.oz-logo-img{display:block;height:30px;max-width:100%;width:auto}.oz-navitem{align-items:center;background:transparent;border:0;border-right:3px solid transparent;border-radius:10px 0 0 10px;color:#0f2027;cursor:pointer;display:flex;gap:12px;padding:11px 12px;text-align:right;transition:all .14s ease;width:100%}.oz-navitem>.oz-ico{color:#6b7a86;flex:0 0 20px;height:20px;transition:color .14s ease,transform .14s ease;width:20px}.oz-navitem:hover>.oz-ico{color:#0f2027}.oz-navitem.is-active>.oz-ico{color:#0c8f6b}.oz-navitem__text{flex:1;min-width:0}.oz-navitem__text strong{display:block;font-size:13.5px;font-weight:650;line-height:1.2}.oz-navitem__text small{color:#6b7a86;font-size:11px}.oz-navitem__dot{background:#1dd1a1;border-radius:50%;height:8px;width:8px}.oz-navitem:hover{background:#f4f7f8}.oz-navitem.is-active{background:rgba(29,209,161,.12);border-right-color:#1dd1a1}.oz-navitem.is-active .oz-navitem__text strong{color:#0c8f6b}.oz-content{align-self:stretch;background:#f4f7f8;border-right:1px solid #e7ecef;display:flex;flex:1;flex-direction:column;min-width:0}.oz-topbar{align-items:center;background:#fff;border-bottom:1px solid #e7ecef;display:flex;gap:14px;padding:20px 28px}.oz-topbar__icon{background:rgba(29,209,161,.14);border-radius:12px;color:#0c8f6b;display:grid;font-size:22px;height:44px;place-items:center;width:44px}.oz-topbar h1{font-size:20px;font-weight:700;margin:0;padding:0}.oz-topbar p{color:#6b7a86;font-size:12.5px;margin:0}.oz-main{display:flex;flex-direction:column;gap:16px;padding:24px 28px}.oz-welcome{align-items:center;background:linear-gradient(-125deg,rgba(29,209,161,.9),rgba(16,172,129,.92));border-radius:16px;color:#06382c;display:flex;gap:20px;justify-content:space-between;overflow:hidden;padding:22px 26px}.oz-welcome h2{color:#06382c;font-size:18px;font-weight:700;margin:0 0 6px}.oz-welcome p{color:#0c5b46;font-size:13px;margin:0;max-width:620px}.oz-welcome__icon{flex:0 0 auto;font-size:64px;height:64px;opacity:.5;width:64px}.oz-score{align-items:center;background:linear-gradient(-125deg,#1dd1a1,#10ac81 62%,#0c8f6b);border-radius:16px;box-shadow:0 16px 34px -18px rgba(12,143,107,.65);color:#06382c;display:flex;gap:30px;overflow:hidden;padding:28px 32px;position:relative}.oz-score:before{background:radial-gradient(circle,hsla(0,0%,100%,.3),transparent 62%);height:440px;left:-8%;top:-55%;width:440px}.oz-score:after,.oz-score:before{border-radius:50%;content:"";pointer-events:none;position:absolute}.oz-score:after{background:radial-gradient(circle,rgba(0,0,0,.08),transparent 60%);bottom:-70%;height:320px;right:8%;width:320px}.oz-score>*{position:relative;z-index:1}.oz-score__gauge{filter:drop-shadow(0 8px 16px rgba(3,40,30,.28));flex:0 0 auto;line-height:0;position:relative}.oz-score__grade{background:#fff;border-radius:50%;box-shadow:0 4px 10px rgba(3,40,30,.28);color:#0c8f6b;display:grid;font-size:14px;font-weight:800;height:30px;place-items:center;position:absolute;left:2px;top:8px;width:30px}.oz-score__body{flex:1;min-width:0}.oz-score__body h2{color:#06382c;font-size:21px;font-weight:750;letter-spacing:-.02em;margin:0 0 4px}.oz-score__body p{color:rgba(6,56,44,.82);font-size:13px;font-weight:500;margin:0 0 15px}.oz-score__chips{display:flex;flex-wrap:wrap;gap:8px}.oz-chip{align-items:center;background:hsla(0,0%,100%,.18);border:1px solid hsla(0,0%,100%,.28);border-radius:999px;color:rgba(6,56,44,.72);display:inline-flex;font-size:11.5px;font-weight:650;gap:6px;padding:5px 9px 5px 12px;transition:transform .15s ease,box-shadow .15s ease}.oz-chip .dashicons{font-size:15px;height:15px;opacity:.65;width:15px}.oz-chip.is-on{background:#fff;border-color:#fff;box-shadow:0 3px 8px -2px rgba(3,40,30,.22);color:#0c8f6b}.oz-chip.is-on .dashicons{color:#1dd1a1;opacity:1}.oz-chip.is-on:hover{box-shadow:0 6px 14px -4px rgba(3,40,30,.3);transform:translateY(-1px)}.oz-convert{align-items:center;background:#f4f7f8;border-radius:12px;display:flex;gap:16px;justify-content:space-between;margin-top:16px;padding:16px}.oz-convert__info{color:#6b7a86;display:flex;flex-direction:column;font-size:12.5px;gap:3px}.oz-convert__info strong{color:#0f2027;font-size:13px}.oz-progress{background:#dfe6e8;border-radius:999px;height:6px;margin-top:4px;overflow:hidden;width:200px}.oz-progress div{background:linear-gradient(-90deg,#1dd1a1,#10ac81);height:100%;transition:width .3s ease}.oz-kpis{display:grid;gap:14px;grid-template-columns:repeat(3,1fr)}.oz-kpi{background:#fff;border:1px solid #e7ecef;border-radius:14px;display:flex;flex-direction:column;padding:16px 18px}.oz-kpi>.dashicons{color:#10ac81;font-size:20px;margin-bottom:8px}.oz-kpi strong{font-size:24px;font-weight:750;min-height:28px}.oz-kpi span{color:#6b7a86;font-size:12px}.oz-quick{display:grid;gap:16px;grid-template-columns:1fr 1fr}.oz-quick__actions{display:flex;flex-wrap:wrap;gap:10px}.oz-cachelist{border-top:1px solid #e7ecef;margin:18px 0 0}.oz-cachelist>li{align-items:center;border-bottom:1px solid #f1f4f5;color:#6b7a86;display:flex;font-size:13px;gap:10px;padding:11px 2px}.oz-cachelist>li:last-child{border-bottom:0}.oz-cachelist>li .dashicons{color:#10ac81;font-size:17px}.oz-cachelist>li strong{color:#0f2027;font-weight:650;margin-right:auto}.oz-cachelist>li .oz-row-label{display:flex;flex-direction:column;line-height:1.3}.oz-cachelist>li .oz-row-label small{color:#6b7a86;font-size:11.5px;opacity:.85}.oz-status{margin:0}.oz-status li{align-items:center;border-bottom:1px solid #e7ecef;display:flex;font-size:13.5px;font-weight:600;gap:10px;padding:9px 6px}.oz-status li:last-child{border-bottom:0}.oz-status li>.oz-ico{color:#6b7a86;flex:0 0 18px;height:18px;width:18px}.oz-status__name{background:transparent;border:0;color:#0f2027;cursor:pointer;flex:1;font:inherit;font-weight:600;padding:4px 0;text-align:right}.oz-status__name:hover{color:#0c8f6b}.oz-status .components-toggle-control{margin-right:auto}.oz-card,.oz-panel{background:#fff;border:1px solid #e7ecef;border-radius:16px;overflow:visible}.oz-card .oz-card__head{border-radius:16px 16px 0 0}.oz-panel__head{padding:18px 22px 0}.oz-panel__head h2{font-size:16px;font-weight:650;margin:0}.oz-panel__head p{color:#6b7a86;font-size:12.5px;margin:4px 0 0}.oz-panel__body{padding:8px 22px 20px}.oz-panel__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-panel__body .components-base-control:last-child{border-bottom:0}.oz-card{transition:box-shadow .18s ease,border-color .18s ease}.oz-card.is-enabled{border-color:rgba(29,209,161,.5);box-shadow:0 0 0 1px rgba(29,209,161,.22)}.oz-card__head{align-items:flex-start;display:flex;gap:15px;padding:20px 22px}.oz-card__icon{background:#f4f7f8;border-radius:12px;color:#6b7a86;display:grid;flex:0 0 46px;font-size:22px;height:46px;place-items:center;width:46px}.oz-card__title{flex:1;min-width:0}.oz-card__title h2{align-items:center;display:flex;font-size:16px;font-weight:650;gap:10px;margin:0 0 3px}.oz-card__title p{color:#6b7a86;font-size:12.5px;margin:0}.oz-card__body{border-top:1px solid #e7ecef;padding:4px 22px 16px}.oz-card__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-card__body .components-base-control:last-child{border-bottom:0}.is-enabled .oz-card__icon{background:linear-gradient(-135deg,#1dd1a1,#10ac81);color:#fff}.oz-inline-actions{display:flex;gap:10px;padding-top:16px}.oz-dbgroup{border-top:1px solid #eef0f2;padding:18px 0}.oz-dbgroup:first-of-type{border-top:0;padding-top:4px}.oz-dbgroup__title{color:#0e1116;font-size:14px;font-weight:700;margin:0 0 4px}.oz-dbgroup__desc{color:#6b7280;font-size:13px;line-height:1.5;margin:0 0 14px}.oz-dbgroup .components-toggle-control{margin-bottom:14px}.oz-dbgroup .components-toggle-control:last-child{margin-bottom:0}.oz-dbwarn{align-items:flex-start;background:#fff5f5;border:1px solid #fcd9d9;border-radius:10px;color:#b42318;display:flex;font-size:13px;font-weight:600;gap:8px;line-height:1.5;margin:4px 0;padding:12px 14px}.oz-dbwarn .dashicons{color:#e05242;flex:0 0 auto}.oz-cachewarn{align-items:center;background:#fff8ec;border:1px solid #f4dcae;border-radius:12px;color:#7a4b00;display:flex;gap:13px;margin:0 0 18px;padding:14px 16px}.oz-cachewarn .dashicons{color:#e0900f;flex:0 0 auto;font-size:22px;height:22px;width:22px}.oz-cachewarn__text{flex:1 1 auto;min-width:0}.oz-cachewarn__text strong{color:#6b4200;display:block;font-size:14px;font-weight:700}.oz-cachewarn__text p{color:#8a6410;font-size:12.5px;line-height:1.5;margin:3px 0 0}.oz-cachewarn .oz-btn-primary{flex:0 0 auto}@media(max-width:640px){.oz-cachewarn{flex-wrap:wrap}.oz-cachewarn .oz-btn-primary{justify-content:center;width:100%}}.oz-subhead{color:#8a96a0;font-size:11px;font-weight:700;letter-spacing:.08em;margin:18px 0 2px;padding-top:14px;text-transform:uppercase}.oz-health,.oz-subhead{border-top:1px solid #e7ecf0}.oz-health{margin-top:18px;padding-top:16px}.oz-health__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.oz-health__head strong{font-size:14px}.oz-health__hint{color:#6b7280;font-size:12px;line-height:1.5;margin:4px 0 10px}.oz-health__list{display:flex;flex-direction:column;gap:6px;margin:0}.oz-health__row{align-items:center;display:flex;font-size:12.5px;gap:8px;line-height:1.4;margin:0}.oz-health__row .dashicons{flex:0 0 auto;font-size:17px;height:17px;width:17px}.oz-health__row.is-ok{color:#216e4e}.oz-health__row.is-ok .dashicons{color:#1dd1a1}.oz-health__row.is-warn{color:#8a5a00}.oz-health__row.is-warn .dashicons{color:#e0900f}.oz-health__row.is-bad{color:#b42318}.oz-health__row.is-bad .dashicons{color:#e05242}.oz-health__err{color:#b42318;font-size:12.5px}.oz-health__fix{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-top:14px}.oz-health__fix span{color:#6b7280;flex:1 1 160px;font-size:12px}.oz-health__conflict{display:flex;flex-direction:column;gap:8px;margin-top:12px}.oz-health__conflict-row{align-items:center;background:#fff5f5;border:1px solid #fcd9d9;border-radius:9px;display:flex;flex-wrap:wrap;gap:10px;padding:10px 12px}.oz-health__conflict-row .dashicons{color:#e05242;flex:0 0 auto}.oz-health__conflict-row span:not(.dashicons){color:#8a2f22;flex:1 1 200px;font-size:12.5px}.oz-health__conflict-row .oz-btn-danger.components-button{flex:0 0 auto;height:32px}.oz-health__manual{background:#fff8ec;border:1px solid #f4dcae;border-radius:9px;margin-top:12px;padding:10px 12px}.oz-health__manual p{color:#7a4b00;font-size:12px;margin:0 0 6px}.oz-health__manual code{background:#2b2f36;border-radius:6px;color:#e7f0ec;display:inline-block;font-size:12.5px;padding:6px 10px}.oz-btn-ghost.components-button{border:1px solid #d5dbe0;border-radius:8px;color:#384049;font-size:12.5px;height:30px}.oz-btn-ghost.components-button:hover:not(:disabled){border-color:#1dd1a1;color:#0b7a5c}.oz-pill{align-items:center;border-radius:999px;display:inline-flex;font-size:11px;font-weight:700;gap:5px;padding:3px 9px}.oz-pill i{background:currentColor;border-radius:50%;height:6px;width:6px}.oz-pill.is-on{background:rgba(29,209,161,.16);color:#0c8f6b}.oz-pill.is-off{background:#eef1f2;color:#6b7a86}.oz-shell .components-range-control__thumb,.oz-shell .components-range-control__track{background-color:#1dd1a1!important}.oz-fieldgroup{background:rgba(29,209,161,.035);border:1px solid #e7ecef;border-radius:12px;margin-top:18px;padding:16px 16px 4px}.oz-fieldgroup__title{color:#0e1116;font-size:13px;font-weight:700;margin:0 0 4px}.oz-fieldgroup__desc{color:#6b7280;font-size:12.5px;line-height:1.5;margin:0 0 14px}.oz-fieldgroup .components-base-control{margin-bottom:14px}.oz-shell .components-form-token-field__input-container{background:#fff;border:1px solid #e7ecef;border-radius:10px;min-height:44px;padding:5px 8px;transition:border-color .15s ease,box-shadow .15s ease}.oz-shell .components-form-token-field__input-container.is-active,.oz-shell .components-form-token-field__input-container:focus-within{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-shell .components-form-token-field__token{border-radius:8px;font-size:12.5px;margin:3px;overflow:hidden}.oz-shell .components-form-token-field__token-text{background:rgba(29,209,161,.14);border-radius:0 8px 8px 0;color:#0c8f6b;font-weight:650;line-height:1.7;padding:2px 11px 2px 4px}.oz-shell .components-form-token-field__remove-token{background:rgba(29,209,161,.14);border-radius:8px 0 0 8px;color:#0c8f6b;min-width:24px;padding:0 7px;transition:background .15s ease,color .15s ease}.oz-shell .components-form-token-field__remove-token svg{fill:currentColor}.oz-shell .components-form-token-field__remove-token:hover{background:#10ac81;color:#fff}.oz-shell .components-form-token-field__input{font-size:13.5px;margin:4px;min-height:28px}.oz-shell .components-input-control__backdrop{display:none!important}.oz-shell .components-input-control__input,.oz-shell .components-select-control__input,.oz-shell .components-text-control__input{background:#fff!important;border:1px solid #e7ecef!important;border-radius:10px!important;box-shadow:none!important;color:#0f2027;font-size:13.5px;min-height:42px;padding:0 14px}.oz-shell .components-input-control__input:hover,.oz-shell .components-select-control__input:hover,.oz-shell .components-text-control__input:hover{border-color:#bcc6cc!important}.oz-shell .components-input-control__input:focus,.oz-shell .components-select-control__input:focus,.oz-shell .components-text-control__input:focus{border-color:#1dd1a1!important;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-shell select.components-select-control__input{cursor:pointer}.oz-pillgroup .components-base-control__label{display:block;font-weight:600;margin-bottom:8px}.oz-pillgroup__row{background:#f0f2f4;border-radius:12px;display:inline-flex;flex-wrap:wrap;gap:6px;padding:4px}.oz-pillgroup .components-base-control__help{display:block;margin-top:8px}.oz-pillbtn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:9px;color:#4b5563;cursor:pointer;font-size:13px;font-weight:600;min-width:44px;padding:7px 16px;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.oz-pillbtn:hover{color:#0e1116}.oz-pillbtn.is-on{background:#fff;box-shadow:0 1px 3px rgba(4,56,44,.14);color:#0c8f6b}.oz-pillbtn:focus-visible{outline:2px solid #1dd1a1;outline-offset:1px}.oz-select{position:relative}.oz-select .components-base-control__label{display:block;margin-bottom:8px}.oz-select__btn{align-items:center;background:#fff;border:1px solid #e7ecef;border-radius:10px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;height:42px;justify-content:space-between;padding:0 14px;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.oz-select__btn .dashicons{color:#0c8f6b;transition:transform .18s ease}.oz-select__btn:hover{border-color:#bcc6cc}.oz-select__btn:focus,.oz-select__btn:focus-visible{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-select__btn[aria-expanded=true]{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-select__btn[aria-expanded=true] .dashicons{transform:rotate(-180deg)}.oz-select__menu{background:#fff;border:1px solid #e7ecef;border-radius:12px;box-shadow:0 14px 34px rgba(15,32,39,.18);right:0;list-style:none;margin:0;padding:6px;position:absolute;left:0;top:calc(100% + 6px);z-index:30}.oz-select__item{align-items:center;border-radius:8px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;justify-content:space-between;padding:9px 12px}.oz-select__item .dashicons{color:#10ac81;font-size:18px}.oz-select__item.is-active,.oz-select__item:hover{background:rgba(29,209,161,.12);color:#0c8f6b}.oz-select__item.is-selected{color:#0c8f6b;font-weight:650}.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{border-radius:10px;font-weight:650;height:38px;transition:transform .16s cubic-bezier(.2,.7,.3,1),box-shadow .18s ease,background .16s ease,border-color .16s ease,color .16s ease}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:transform .22s cubic-bezier(.2,.7,.3,1)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:translateY(-1px)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:scale(1.14)}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled){transform:translateY(0) scale(.975);transition-duration:.05s}.oz-shell .oz-btn-danger.components-button:focus-visible,.oz-shell .oz-btn-ghost.components-button:focus-visible,.oz-shell .oz-btn-primary.components-button:focus-visible{outline:2px solid #1dd1a1;outline-offset:2px}.oz-shell .oz-btn-danger.components-button:disabled,.oz-shell .oz-btn-ghost.components-button:disabled,.oz-shell .oz-btn-primary.components-button:disabled{opacity:.5;transform:none}.oz-shell .oz-btn-primary.components-button{background:linear-gradient(-135deg,#1dd1a1,#10ac81);border:0;box-shadow:0 1px 2px rgba(20,60,48,.12);color:#fff;padding:0 18px}.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){background:linear-gradient(-135deg,#10ac81,#0c8f6b);box-shadow:0 8px 20px -7px rgba(29,209,161,.65);color:#fff}.oz-shell .oz-btn-primary.components-button.is-busy{animation:oz-btn-sheen 1.4s linear infinite;background-size:200% 100%}.oz-shell .oz-btn-ghost.components-button{background:#fff;border:1px solid rgba(29,209,161,.5);box-shadow:none;color:#0c8f6b}.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled){background:rgba(29,209,161,.08);border-color:#1dd1a1;box-shadow:0 6px 16px -8px rgba(29,209,161,.5);color:#0c8f6b}.oz-shell .oz-btn-danger.components-button{background:#fff;border:1px solid rgba(214,54,56,.5);color:#d63638}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled){background:rgba(214,54,56,.08);border-color:#d63638;box-shadow:0 6px 16px -8px rgba(214,54,56,.5);color:#b32d2e}.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg{transform:scale(1.1) rotate(-90deg)}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg{transform:none}}@keyframes oz-btn-sheen{0%{background-image:linear-gradient(-135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:-100% 0}to{background-image:linear-gradient(-135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:300% 0}}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{transition:background .16s ease,color .16s ease,border-color .16s ease}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:none}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:none}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:none}.oz-shell .oz-btn-danger.components-button.is-busy,.oz-shell .oz-btn-ghost.components-button.is-busy,.oz-shell .oz-btn-primary.components-button.is-busy{animation:none}}.oz-shell .components-button.is-primary:focus:not(:disabled),.oz-shell .components-button:focus,.oz-shell .components-button:focus:not(:disabled){box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle,.oz-shell .components-form-toggle__input,.oz-shell .components-form-toggle__input:active,.oz-shell .components-form-toggle__input:focus,.oz-shell .components-form-toggle__input:focus-visible{border:none!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__track{background-color:#cdd6db!important;border:1px solid transparent!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle.is-checked .components-form-toggle__track{background-color:#1dd1a1!important}.oz-shell .components-form-toggle__input:focus+.components-form-toggle__track,.oz-shell .components-form-toggle__input:focus-visible+.components-form-toggle__track{box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__thumb{background-color:#fff!important;border:none!important;box-shadow:0 1px 2px rgba(15,32,39,.25)!important}.oz-savebar{align-items:center;background:#fff;border-top:1px solid #e7ecef;bottom:0;box-shadow:0 -6px 18px rgba(15,32,39,.06);display:flex;gap:16px;justify-content:flex-end;right:160px;padding:13px 32px;position:fixed;left:0;z-index:9}.oz-savebar__hint{color:#6b7a86;font-size:13px}.oz-savebar.is-dirty .oz-savebar__hint{color:#0c8f6b;font-weight:650}.folded .oz-savebar{right:36px}.oz-toast{bottom:72px;z-index:99}.oz-job,.oz-toast{position:fixed;left:24px}.oz-job{background:#fff;border:1px solid #e7ecef;border-radius:16px;bottom:84px;box-shadow:0 18px 50px rgba(15,32,39,.22);color:#0f2027;font-size:13px;max-width:calc(100vw - 48px);overflow:hidden;width:420px;z-index:100}.oz-job__head{align-items:center;display:flex;gap:12px;padding:16px 16px 12px}.oz-job__head .components-spinner{margin:0}.oz-job__icon-status{font-size:22px;height:22px;width:22px}.oz-job__icon-status--done{color:#10ac81}.oz-job__icon-status--stopped{color:#6b7a86}.oz-job__icon-status--error{color:#d63638}.oz-job__title{flex:1;min-width:0}.oz-job__title strong{display:block;font-size:15px;font-weight:700;line-height:1.2}.oz-job__title span{color:#6b7a86;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.oz-job__pct{color:#0f2027;font-size:13px;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__icon{background:transparent;border:0;border-radius:6px;color:#6b7a86;cursor:pointer;display:inline-flex;padding:4px}.oz-job__icon:hover{background:#f4f7f8;color:#0f2027}.oz-job__icon .dashicons{font-size:18px;height:18px;width:18px}.oz-job__body{padding:0 16px 16px}.oz-job__row{align-items:baseline;display:flex;gap:12px;justify-content:space-between;margin-bottom:8px}.oz-job__row span{line-height:1.4}.oz-job__row strong{flex:0 0 auto;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__bar{background:#e7ecef;border-radius:999px;height:7px;overflow:hidden}.oz-job__bar div{background:linear-gradient(-90deg,#1dd1a1,#10ac81);border-radius:999px;height:100%;transition:width .35s ease}.oz-job__bar.is-indeterminate div{animation:oz-indeterminate 1.1s ease-in-out infinite;border-radius:999px;width:40%!important}.oz-job.is-min .oz-job__bar{margin:2px 16px 16px}.oz-job.is-min .oz-job__head{padding-bottom:10px}.oz-job.is-done .oz-job__bar div{background:linear-gradient(-90deg,#10ac81,#0c8f6b)}.oz-job.is-stopped .oz-job__bar div{background:#c3ccd2}.oz-job.is-error .oz-job__bar div{background:linear-gradient(-90deg,#f16b6b,#d63638)}.oz-job__step{color:#6b7a86;font-size:12px;margin-top:8px}.oz-job__stop{align-items:center;background:transparent;border:0;color:#d63638;cursor:pointer;display:inline-flex;font-size:13px;font-weight:650;gap:6px;margin-top:14px;padding:4px 0}.oz-job__stop .dashicons{font-size:18px}.oz-job__stop:hover{color:#b32d2e}.oz-job__log{background:#11202b;border-radius:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.7;margin-top:14px;max-height:150px;overflow-y:auto;padding:10px 12px}.oz-logline{color:#cdd8df;display:flex;gap:8px}.oz-logline__tag{color:#5fd0ad;flex:0 0 auto;font-weight:700;min-width:42px}.oz-logline--warn .oz-logline__tag{color:#f0b429}.oz-logline--error .oz-logline__tag{color:#ff6b6b}.oz-logline--done .oz-logline__tag{color:#1dd1a1}.oz-logline__text{word-break:break-word}@media(max-width:1100px){.oz-kpis,.oz-quick{grid-template-columns:1fr}}@media(max-width:1024px){.oz-score{align-items:flex-start;flex-direction:column;text-align:right}.oz-convert{align-items:stretch;flex-direction:column;gap:12px}.oz-convert .oz-btn-ghost.components-button,.oz-convert .oz-btn-primary.components-button{justify-content:center;width:100%}}.oz-mobile-toggle,.oz-nav-overlay{display:none!important}@media(max-width:1024px){.oz-shell{flex-direction:column;margin:8px 8px 110px}.oz-content{border-right:0;min-width:0;width:100%}.oz-sidebar{border-bottom:1px solid #e7ecef;border-left:0;flex:none;max-height:none;overflow:visible;padding:14px 12px;position:static;width:100%}.oz-sidebar__brand{margin-bottom:10px;padding-bottom:12px}.oz-sidebar nav{display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}.oz-navitem{border-bottom:3px solid transparent;border-right:0;border-radius:10px;flex:0 0 auto;padding:8px 12px;width:auto}.oz-navitem.is-active{border-bottom-color:#1dd1a1;border-right-color:transparent}.oz-navitem .oz-navitem__text strong{white-space:nowrap}.oz-navitem .oz-navitem__dot,.oz-navitem small,.oz-sidebar__foot{display:none}.oz-topbar{padding:16px 18px}.oz-topbar h1{font-size:18px}.oz-main{padding:16px 18px}.oz-savebar{right:0;padding:12px 18px}.oz-card__body{padding-right:18px;padding-left:18px}.oz-job{right:8px;left:8px;width:auto}}@media(max-width:560px){.oz-tiles{grid-template-columns:1fr}.oz-hero,.oz-score{padding:18px}.oz-card__head{gap:12px;padding:16px}.oz-card__icon{flex-basis:40px;height:40px;width:40px}.oz-savebar__hint{display:none}}@keyframes oz-indeterminate{0%{transform:translateX(100%)}to{transform:translateX(-350%)}}.oz-muted{color:#6b7a86;font-size:13px}.oz-reqs{list-style:none;margin:0;padding:0}.oz-req{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:grid;font-size:13.5px;gap:6px 10px;grid-template-columns:20px 1fr auto;padding:10px 6px}.oz-req:last-child{border-bottom:0}.oz-req__dot{font-size:18px;height:18px;width:18px}.oz-req__label{font-weight:650}.oz-req__value{color:#6b7a86;font-weight:600;justify-self:end}.oz-req__note{color:#6b7a86;font-size:12px;font-weight:400;grid-column:2/-1;line-height:1.45;margin-top:2px}.oz-req--ok>.oz-req__dot{color:#0c8f6b}.oz-req--warn>.oz-req__dot{color:#e0900f}.oz-req--error>.oz-req__dot{color:#e05242}.oz-req--info>.oz-req__dot,.oz-req--info>.oz-req__value{color:#6b7a86}.oz-req--warn{background:rgba(224,144,15,.06)}.oz-req--error{background:rgba(224,82,66,.06)}.oz-notice{align-items:flex-start;background:rgba(224,82,66,.08);border:1px solid rgba(224,82,66,.28);border-radius:10px;color:#7a2018;display:flex;font-size:13px;gap:10px;line-height:1.5;margin:0 0 14px;padding:12px 14px}.oz-notice .dashicons{color:#e05242;flex:0 0 20px;margin-top:1px}.oz-notice--warn{background:rgba(224,144,15,.09);border-color:rgba(224,144,15,.3);color:#7a5406}.oz-notice--warn .dashicons{color:#e0900f}.oz-recs{list-style:none;margin:0;padding:0}.oz-rec{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:flex;gap:12px;padding:12px 8px}.oz-rec:last-child{border-bottom:0}.oz-rec__dot{flex:0 0 20px;font-size:20px;height:20px;margin-top:1px;width:20px}.oz-rec__text{flex:1;min-width:0}.oz-rec__text strong{display:block;font-size:13.5px;font-weight:650}.oz-rec__text small{color:#6b7a86;display:block;font-size:12px;line-height:1.45;margin-top:2px}.oz-rec__btn.components-button{flex:0 0 auto}.oz-rec--high>.oz-rec__dot{color:#e05242}.oz-rec--medium>.oz-rec__dot{color:#e0900f}.oz-rec--low>.oz-rec__dot{color:#6b7a86}.oz-rec--high{background:rgba(224,82,66,.05)}.oz-rec--medium{background:rgba(224,144,15,.05)}.oz-recs-clear{align-items:center;color:#6b7a86;display:flex;font-size:13px;gap:8px}.oz-recs-clear .dashicons{color:#0c8f6b}.oz-select--compact{border-bottom:0!important;flex:none;margin:0 auto 0 0!important;padding:0!important}.oz-select--compact .oz-select__btn{background:#f7faf9;border-radius:9px;font-size:13px;font-weight:650;gap:10px;height:30px;min-width:132px;padding:0 12px 0 10px;width:auto}.oz-select--compact .oz-select__btn:disabled{cursor:progress;opacity:.6}.oz-select--compact .oz-select__btn .dashicons{font-size:16px;height:16px;width:16px}.oz-select--compact .oz-select__menu{right:auto;min-width:190px;left:0}.oz-select--compact .oz-select__item{font-size:13px;padding:8px 10px}
     1.oz-shell{align-items:flex-start;background:#fff;border:1px solid #e7ecef;border-radius:16px;color:#0f2027;display:flex;font-size:14px;margin:10px 0 130px 20px;min-height:640px;overflow:hidden}.oz-shell *{box-sizing:border-box}.oz-shell .dashicons{font-family:dashicons}.oz-sidebar{align-self:flex-start;background:#fff;display:flex;flex:0 0 264px;flex-direction:column;padding:18px 14px}.oz-sidebar__brand{align-items:center;border-bottom:1px solid #e7ecef;display:flex;gap:12px;margin-bottom:10px;padding:10px 10px 18px}.oz-sidebar__brand strong{display:block;font-size:18px;font-weight:750;letter-spacing:-.01em}.oz-sidebar__brand span{color:#6b7a86;font-size:11px}.oz-sidebar__brand-fallback{align-items:center;display:flex;gap:12px}.oz-sidebar__foot{border-top:1px solid #e7ecef;color:#6b7a86;font-size:11px;margin-top:12px;padding:12px 12px 2px}.oz-logo{background:linear-gradient(-135deg,#1dd1a1,#10ac81);border-radius:12px;color:#fff;display:grid;font-size:26px;height:44px;place-items:center;width:44px}.oz-logo-img{display:block;height:30px;max-width:100%;width:auto}.oz-navitem{align-items:center;background:transparent;border:0;border-right:3px solid transparent;border-radius:10px 0 0 10px;color:#0f2027;cursor:pointer;display:flex;gap:12px;padding:11px 12px;text-align:right;transition:all .14s ease;width:100%}.oz-navitem>.oz-ico{color:#6b7a86;flex:0 0 20px;height:20px;transition:color .14s ease,transform .14s ease;width:20px}.oz-navitem:hover>.oz-ico{color:#0f2027}.oz-navitem.is-active>.oz-ico{color:#0c8f6b}.oz-navitem__text{flex:1;min-width:0}.oz-navitem__text strong{display:block;font-size:13.5px;font-weight:650;line-height:1.2}.oz-navitem__text small{color:#6b7a86;font-size:11px}.oz-navitem__dot{background:#1dd1a1;border-radius:50%;height:8px;width:8px}.oz-navitem:hover{background:#f4f7f8}.oz-navitem.is-active{background:rgba(29,209,161,.12);border-right-color:#1dd1a1}.oz-navitem.is-active .oz-navitem__text strong{color:#0c8f6b}.oz-content{align-self:stretch;background:#f4f7f8;border-right:1px solid #e7ecef;display:flex;flex:1;flex-direction:column;min-width:0}.oz-topbar{align-items:center;background:#fff;border-bottom:1px solid #e7ecef;display:flex;gap:14px;padding:20px 28px}.oz-topbar__icon{background:rgba(29,209,161,.14);border-radius:12px;color:#0c8f6b;display:grid;font-size:22px;height:44px;place-items:center;width:44px}.oz-topbar h1{font-size:20px;font-weight:700;margin:0;padding:0}.oz-topbar p{color:#6b7a86;font-size:12.5px;margin:0}.oz-main{display:flex;flex-direction:column;gap:16px;padding:24px 28px}.oz-welcome{align-items:center;background:linear-gradient(-125deg,rgba(29,209,161,.9),rgba(16,172,129,.92));border-radius:16px;color:#06382c;display:flex;gap:20px;justify-content:space-between;overflow:hidden;padding:22px 26px}.oz-welcome h2{color:#06382c;font-size:18px;font-weight:700;margin:0 0 6px}.oz-welcome p{color:#0c5b46;font-size:13px;margin:0;max-width:620px}.oz-welcome__icon{flex:0 0 auto;font-size:64px;height:64px;opacity:.5;width:64px}.oz-score{align-items:center;background:linear-gradient(-125deg,#1dd1a1,#10ac81 62%,#0c8f6b);border-radius:16px;box-shadow:0 16px 34px -18px rgba(12,143,107,.65);color:#06382c;display:flex;gap:30px;overflow:hidden;padding:28px 32px;position:relative}.oz-score:before{background:radial-gradient(circle,hsla(0,0%,100%,.3),transparent 62%);height:440px;left:-8%;top:-55%;width:440px}.oz-score:after,.oz-score:before{border-radius:50%;content:"";pointer-events:none;position:absolute}.oz-score:after{background:radial-gradient(circle,rgba(0,0,0,.08),transparent 60%);bottom:-70%;height:320px;right:8%;width:320px}.oz-score>*{position:relative;z-index:1}.oz-score__gauge{filter:drop-shadow(0 8px 16px rgba(3,40,30,.28));flex:0 0 auto;line-height:0;position:relative}.oz-score__grade{background:#fff;border-radius:50%;box-shadow:0 4px 10px rgba(3,40,30,.28);color:#0c8f6b;display:grid;font-size:14px;font-weight:800;height:30px;place-items:center;position:absolute;left:2px;top:8px;width:30px}.oz-score__body{flex:1;min-width:0}.oz-score__body h2{color:#06382c;font-size:21px;font-weight:750;letter-spacing:-.02em;margin:0 0 4px}.oz-score__body p{color:rgba(6,56,44,.82);font-size:13px;font-weight:500;margin:0 0 15px}.oz-score__chips{display:flex;flex-wrap:wrap;gap:8px}.oz-chip{align-items:center;background:hsla(0,0%,100%,.18);border:1px solid hsla(0,0%,100%,.28);border-radius:999px;color:rgba(6,56,44,.72);display:inline-flex;font-size:11.5px;font-weight:650;gap:6px;padding:5px 9px 5px 12px;transition:transform .15s ease,box-shadow .15s ease}.oz-chip .dashicons{font-size:15px;height:15px;opacity:.65;width:15px}.oz-chip.is-on{background:#fff;border-color:#fff;box-shadow:0 3px 8px -2px rgba(3,40,30,.22);color:#0c8f6b}.oz-chip.is-on .dashicons{color:#1dd1a1;opacity:1}.oz-chip.is-on:hover{box-shadow:0 6px 14px -4px rgba(3,40,30,.3);transform:translateY(-1px)}.oz-convert{align-items:center;background:#f4f7f8;border-radius:12px;display:flex;gap:16px;justify-content:space-between;margin-top:16px;padding:16px}.oz-convert__info{color:#6b7a86;display:flex;flex-direction:column;font-size:12.5px;gap:3px}.oz-convert__info strong{color:#0f2027;font-size:13px}.oz-progress{background:#dfe6e8;border-radius:999px;height:6px;margin-top:4px;overflow:hidden;width:200px}.oz-progress div{background:linear-gradient(-90deg,#1dd1a1,#10ac81);height:100%;transition:width .3s ease}.oz-kpis{display:grid;gap:14px;grid-template-columns:repeat(3,1fr)}.oz-kpis--single{grid-template-columns:1fr}.oz-kpi{background:#fff;border:1px solid #e7ecef;border-radius:14px;display:flex;flex-direction:column;padding:16px 18px}.oz-kpi>.dashicons{color:#10ac81;font-size:20px;margin-bottom:8px}.oz-kpi strong{font-size:24px;font-weight:750;min-height:28px}.oz-kpi span{color:#6b7a86;font-size:12px}.oz-quick{display:grid;gap:16px;grid-template-columns:1fr 1fr}.oz-quick__actions{display:flex;flex-wrap:wrap;gap:10px}.oz-cachelist{border-top:1px solid #e7ecef;margin:18px 0 0}.oz-cachelist>li{align-items:center;border-bottom:1px solid #f1f4f5;color:#6b7a86;display:flex;font-size:13px;gap:10px;padding:11px 2px}.oz-cachelist>li:last-child{border-bottom:0}.oz-cachelist>li .dashicons{color:#10ac81;font-size:17px}.oz-cachelist>li strong{color:#0f2027;font-weight:650;margin-right:auto}.oz-cachelist>li .oz-row-label{display:flex;flex-direction:column;line-height:1.3}.oz-cachelist>li .oz-row-label small{color:#6b7a86;font-size:11.5px;opacity:.85}.oz-status{margin:0}.oz-status li{align-items:center;border-bottom:1px solid #e7ecef;display:flex;font-size:13.5px;font-weight:600;gap:10px;padding:9px 6px}.oz-status li:last-child{border-bottom:0}.oz-status li>.oz-ico{color:#6b7a86;flex:0 0 18px;height:18px;width:18px}.oz-status__name{background:transparent;border:0;color:#0f2027;cursor:pointer;flex:1;font:inherit;font-weight:600;padding:4px 0;text-align:right}.oz-status__name:hover{color:#0c8f6b}.oz-status .components-toggle-control{margin-right:auto}.oz-card,.oz-panel{background:#fff;border:1px solid #e7ecef;border-radius:16px;overflow:visible}.oz-card .oz-card__head{border-radius:16px 16px 0 0}.oz-panel__head{padding:18px 22px 0}.oz-panel__head h2{font-size:16px;font-weight:650;margin:0}.oz-panel__head p{color:#6b7a86;font-size:12.5px;margin:4px 0 0}.oz-panel__body{padding:8px 22px 20px}.oz-panel__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-panel__body .components-base-control:last-child{border-bottom:0}.oz-card{transition:box-shadow .18s ease,border-color .18s ease}.oz-card.is-enabled{border-color:rgba(29,209,161,.5);box-shadow:0 0 0 1px rgba(29,209,161,.22)}.oz-card__head{align-items:flex-start;display:flex;gap:15px;padding:20px 22px}.oz-card__icon{background:#f4f7f8;border-radius:12px;color:#6b7a86;display:grid;flex:0 0 46px;font-size:22px;height:46px;place-items:center;width:46px}.oz-card__title{flex:1;min-width:0}.oz-card__title h2{align-items:center;display:flex;font-size:16px;font-weight:650;gap:10px;margin:0 0 3px}.oz-card__title p{color:#6b7a86;font-size:12.5px;margin:0}.oz-card__body{border-top:1px solid #e7ecef;padding:4px 22px 16px}.oz-card__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-card__body .components-base-control:last-child{border-bottom:0}.is-enabled .oz-card__icon{background:linear-gradient(-135deg,#1dd1a1,#10ac81);color:#fff}.oz-inline-actions{display:flex;gap:10px;padding-top:16px}.oz-dbgroup{border-top:1px solid #eef0f2;padding:18px 0}.oz-dbgroup:first-of-type{border-top:0;padding-top:4px}.oz-dbgroup__title{color:#0e1116;font-size:14px;font-weight:700;margin:0 0 4px}.oz-dbgroup__desc{color:#6b7280;font-size:13px;line-height:1.5;margin:0 0 14px}.oz-dbgroup .components-toggle-control{margin-bottom:14px}.oz-dbgroup .components-toggle-control:last-child{margin-bottom:0}.oz-dbwarn{align-items:flex-start;background:#fff5f5;border:1px solid #fcd9d9;border-radius:10px;color:#b42318;display:flex;font-size:13px;font-weight:600;gap:8px;line-height:1.5;margin:4px 0;padding:12px 14px}.oz-dbwarn .dashicons{color:#e05242;flex:0 0 auto}.oz-cachewarn{align-items:center;background:#fff8ec;border:1px solid #f4dcae;border-radius:12px;color:#7a4b00;display:flex;gap:13px;margin:0 0 18px;padding:14px 16px}.oz-cachewarn .dashicons{color:#e0900f;flex:0 0 auto;font-size:22px;height:22px;width:22px}.oz-cachewarn__text{flex:1 1 auto;min-width:0}.oz-cachewarn__text strong{color:#6b4200;display:block;font-size:14px;font-weight:700}.oz-cachewarn__text p{color:#8a6410;font-size:12.5px;line-height:1.5;margin:3px 0 0}.oz-cachewarn .oz-btn-primary{flex:0 0 auto}@media(max-width:640px){.oz-cachewarn{flex-wrap:wrap}.oz-cachewarn .oz-btn-primary{justify-content:center;width:100%}}.oz-subhead{color:#8a96a0;font-size:11px;font-weight:700;letter-spacing:.08em;margin:18px 0 2px;padding-top:14px;text-transform:uppercase}.oz-health,.oz-subhead{border-top:1px solid #e7ecf0}.oz-health{margin-top:18px;padding-top:16px}.oz-health__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.oz-health__head strong{font-size:14px}.oz-health__hint{color:#6b7280;font-size:12px;line-height:1.5;margin:4px 0 10px}.oz-health__list{display:flex;flex-direction:column;gap:6px;margin:0}.oz-health__row{align-items:center;display:flex;font-size:12.5px;gap:8px;line-height:1.4;margin:0}.oz-health__row .dashicons{flex:0 0 auto;font-size:17px;height:17px;width:17px}.oz-health__row.is-ok{color:#216e4e}.oz-health__row.is-ok .dashicons{color:#1dd1a1}.oz-health__row.is-warn{color:#8a5a00}.oz-health__row.is-warn .dashicons{color:#e0900f}.oz-health__row.is-bad{color:#b42318}.oz-health__row.is-bad .dashicons{color:#e05242}.oz-health__err{color:#b42318;font-size:12.5px}.oz-health__fix{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-top:14px}.oz-health__fix span{color:#6b7280;flex:1 1 160px;font-size:12px}.oz-health__conflict{display:flex;flex-direction:column;gap:8px;margin-top:12px}.oz-health__conflict-row{align-items:center;background:#fff5f5;border:1px solid #fcd9d9;border-radius:9px;display:flex;flex-wrap:wrap;gap:10px;padding:10px 12px}.oz-health__conflict-row .dashicons{color:#e05242;flex:0 0 auto}.oz-health__conflict-row span:not(.dashicons){color:#8a2f22;flex:1 1 200px;font-size:12.5px}.oz-health__conflict-row .oz-btn-danger.components-button{flex:0 0 auto;height:32px}.oz-health__manual{background:#fff8ec;border:1px solid #f4dcae;border-radius:9px;margin-top:12px;padding:10px 12px}.oz-health__manual p{color:#7a4b00;font-size:12px;margin:0 0 6px}.oz-health__manual code{background:#2b2f36;border-radius:6px;color:#e7f0ec;display:inline-block;font-size:12.5px;padding:6px 10px}.oz-btn-ghost.components-button{border:1px solid #d5dbe0;border-radius:8px;color:#384049;font-size:12.5px;height:30px}.oz-btn-ghost.components-button:hover:not(:disabled){border-color:#1dd1a1;color:#0b7a5c}.oz-pill{align-items:center;border-radius:999px;display:inline-flex;font-size:11px;font-weight:700;gap:5px;padding:3px 9px}.oz-pill i{background:currentColor;border-radius:50%;height:6px;width:6px}.oz-pill.is-on{background:rgba(29,209,161,.16);color:#0c8f6b}.oz-pill.is-off{background:#eef1f2;color:#6b7a86}.oz-shell .components-range-control__thumb,.oz-shell .components-range-control__track{background-color:#1dd1a1!important}.oz-fieldgroup{background:rgba(29,209,161,.035);border:1px solid #e7ecef;border-radius:12px;margin-top:18px;padding:16px 16px 4px}.oz-fieldgroup__title{color:#0e1116;font-size:13px;font-weight:700;margin:0 0 4px}.oz-fieldgroup__desc{color:#6b7280;font-size:12.5px;line-height:1.5;margin:0 0 14px}.oz-fieldgroup .components-base-control{margin-bottom:14px}.oz-shell .components-form-token-field__input-container{background:#fff;border:1px solid #e7ecef;border-radius:10px;min-height:44px;padding:5px 8px;transition:border-color .15s ease,box-shadow .15s ease}.oz-shell .components-form-token-field__input-container.is-active,.oz-shell .components-form-token-field__input-container:focus-within{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-shell .components-form-token-field__token{border-radius:8px;font-size:12.5px;margin:3px;overflow:hidden}.oz-shell .components-form-token-field__token-text{background:rgba(29,209,161,.14);border-radius:0 8px 8px 0;color:#0c8f6b;font-weight:650;line-height:1.7;padding:2px 11px 2px 4px}.oz-shell .components-form-token-field__remove-token{background:rgba(29,209,161,.14);border-radius:8px 0 0 8px;color:#0c8f6b;min-width:24px;padding:0 7px;transition:background .15s ease,color .15s ease}.oz-shell .components-form-token-field__remove-token svg{fill:currentColor}.oz-shell .components-form-token-field__remove-token:hover{background:#10ac81;color:#fff}.oz-shell .components-form-token-field__input{font-size:13.5px;margin:4px;min-height:28px}.oz-shell .components-input-control__backdrop{display:none!important}.oz-shell .components-input-control__input,.oz-shell .components-select-control__input,.oz-shell .components-text-control__input{background:#fff!important;border:1px solid #e7ecef!important;border-radius:10px!important;box-shadow:none!important;color:#0f2027;font-size:13.5px;min-height:42px;padding:0 14px}.oz-shell .components-input-control__input:hover,.oz-shell .components-select-control__input:hover,.oz-shell .components-text-control__input:hover{border-color:#bcc6cc!important}.oz-shell .components-input-control__input:focus,.oz-shell .components-select-control__input:focus,.oz-shell .components-text-control__input:focus{border-color:#1dd1a1!important;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-shell select.components-select-control__input{cursor:pointer}.oz-pillgroup .components-base-control__label{display:block;font-weight:600;margin-bottom:8px}.oz-pillgroup__row{background:#f0f2f4;border-radius:12px;display:inline-flex;flex-wrap:wrap;gap:6px;padding:4px}.oz-pillgroup .components-base-control__help{display:block;margin-top:8px}.oz-pillbtn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:9px;color:#4b5563;cursor:pointer;font-size:13px;font-weight:600;min-width:44px;padding:7px 16px;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.oz-pillbtn:hover{color:#0e1116}.oz-pillbtn.is-on{background:#fff;box-shadow:0 1px 3px rgba(4,56,44,.14);color:#0c8f6b}.oz-pillbtn:focus-visible{outline:2px solid #1dd1a1;outline-offset:1px}.oz-select{position:relative}.oz-select .components-base-control__label{display:block;margin-bottom:8px}.oz-select__btn{align-items:center;background:#fff;border:1px solid #e7ecef;border-radius:10px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;height:42px;justify-content:space-between;padding:0 14px;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.oz-select__btn .dashicons{color:#0c8f6b;transition:transform .18s ease}.oz-select__btn:hover{border-color:#bcc6cc}.oz-select__btn:focus,.oz-select__btn:focus-visible{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-select__btn[aria-expanded=true]{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-select__btn[aria-expanded=true] .dashicons{transform:rotate(-180deg)}.oz-select__menu{background:#fff;border:1px solid #e7ecef;border-radius:12px;box-shadow:0 14px 34px rgba(15,32,39,.18);right:0;list-style:none;margin:0;padding:6px;position:absolute;left:0;top:calc(100% + 6px);z-index:30}.oz-select__item{align-items:center;border-radius:8px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;justify-content:space-between;padding:9px 12px}.oz-select__item .dashicons{color:#10ac81;font-size:18px}.oz-select__item.is-active,.oz-select__item:hover{background:rgba(29,209,161,.12);color:#0c8f6b}.oz-select__item.is-selected{color:#0c8f6b;font-weight:650}.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{border-radius:10px;font-weight:650;height:38px;transition:transform .16s cubic-bezier(.2,.7,.3,1),box-shadow .18s ease,background .16s ease,border-color .16s ease,color .16s ease}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:transform .22s cubic-bezier(.2,.7,.3,1)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:translateY(-1px)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:scale(1.14)}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled){transform:translateY(0) scale(.975);transition-duration:.05s}.oz-shell .oz-btn-danger.components-button:focus-visible,.oz-shell .oz-btn-ghost.components-button:focus-visible,.oz-shell .oz-btn-primary.components-button:focus-visible{outline:2px solid #1dd1a1;outline-offset:2px}.oz-shell .oz-btn-danger.components-button:disabled,.oz-shell .oz-btn-ghost.components-button:disabled,.oz-shell .oz-btn-primary.components-button:disabled{opacity:.5;transform:none}.oz-shell .oz-btn-primary.components-button{background:linear-gradient(-135deg,#1dd1a1,#10ac81);border:0;box-shadow:0 1px 2px rgba(20,60,48,.12);color:#fff;padding:0 18px}.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){background:linear-gradient(-135deg,#10ac81,#0c8f6b);box-shadow:0 8px 20px -7px rgba(29,209,161,.65);color:#fff}.oz-shell .oz-btn-primary.components-button.is-busy{animation:oz-btn-sheen 1.4s linear infinite;background-size:200% 100%}.oz-shell .oz-btn-ghost.components-button{background:#fff;border:1px solid rgba(29,209,161,.5);box-shadow:none;color:#0c8f6b}.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled){background:rgba(29,209,161,.08);border-color:#1dd1a1;box-shadow:0 6px 16px -8px rgba(29,209,161,.5);color:#0c8f6b}.oz-shell .oz-btn-danger.components-button{background:#fff;border:1px solid rgba(214,54,56,.5);color:#d63638}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled){background:rgba(214,54,56,.08);border-color:#d63638;box-shadow:0 6px 16px -8px rgba(214,54,56,.5);color:#b32d2e}.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg{transform:scale(1.1) rotate(-90deg)}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg{transform:none}}@keyframes oz-btn-sheen{0%{background-image:linear-gradient(-135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:-100% 0}to{background-image:linear-gradient(-135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:300% 0}}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{transition:background .16s ease,color .16s ease,border-color .16s ease}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:none}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:none}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:none}.oz-shell .oz-btn-danger.components-button.is-busy,.oz-shell .oz-btn-ghost.components-button.is-busy,.oz-shell .oz-btn-primary.components-button.is-busy{animation:none}}.oz-shell .components-button.is-primary:focus:not(:disabled),.oz-shell .components-button:focus,.oz-shell .components-button:focus:not(:disabled){box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle,.oz-shell .components-form-toggle__input,.oz-shell .components-form-toggle__input:active,.oz-shell .components-form-toggle__input:focus,.oz-shell .components-form-toggle__input:focus-visible{border:none!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__track{background-color:#cdd6db!important;border:1px solid transparent!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle.is-checked .components-form-toggle__track{background-color:#1dd1a1!important}.oz-shell .components-form-toggle__input:focus+.components-form-toggle__track,.oz-shell .components-form-toggle__input:focus-visible+.components-form-toggle__track{box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__thumb{background-color:#fff!important;border:none!important;box-shadow:0 1px 2px rgba(15,32,39,.25)!important}.oz-savebar{align-items:center;background:#fff;border-top:1px solid #e7ecef;bottom:0;box-shadow:0 -6px 18px rgba(15,32,39,.06);display:flex;gap:16px;justify-content:flex-end;right:160px;padding:13px 32px;position:fixed;left:0;z-index:9}.oz-savebar__hint{color:#6b7a86;font-size:13px}.oz-savebar.is-dirty .oz-savebar__hint{color:#0c8f6b;font-weight:650}.folded .oz-savebar{right:36px}.oz-toast{bottom:72px;z-index:99}.oz-job,.oz-toast{position:fixed;left:24px}.oz-job{background:#fff;border:1px solid #e7ecef;border-radius:16px;bottom:84px;box-shadow:0 18px 50px rgba(15,32,39,.22);color:#0f2027;font-size:13px;max-width:calc(100vw - 48px);overflow:hidden;width:420px;z-index:100}.oz-job__head{align-items:center;display:flex;gap:12px;padding:16px 16px 12px}.oz-job__head .components-spinner{margin:0}.oz-job__icon-status{font-size:22px;height:22px;width:22px}.oz-job__icon-status--done{color:#10ac81}.oz-job__icon-status--stopped{color:#6b7a86}.oz-job__icon-status--error{color:#d63638}.oz-job__title{flex:1;min-width:0}.oz-job__title strong{display:block;font-size:15px;font-weight:700;line-height:1.2}.oz-job__title span{color:#6b7a86;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.oz-job__pct{color:#0f2027;font-size:13px;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__icon{background:transparent;border:0;border-radius:6px;color:#6b7a86;cursor:pointer;display:inline-flex;padding:4px}.oz-job__icon:hover{background:#f4f7f8;color:#0f2027}.oz-job__icon .dashicons{font-size:18px;height:18px;width:18px}.oz-job__body{padding:0 16px 16px}.oz-job__row{align-items:baseline;display:flex;gap:12px;justify-content:space-between;margin-bottom:8px}.oz-job__row span{line-height:1.4}.oz-job__row strong{flex:0 0 auto;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__bar{background:#e7ecef;border-radius:999px;height:7px;overflow:hidden}.oz-job__bar div{background:linear-gradient(-90deg,#1dd1a1,#10ac81);border-radius:999px;height:100%;transition:width .35s ease}.oz-job__bar.is-indeterminate div{animation:oz-indeterminate 1.1s ease-in-out infinite;border-radius:999px;width:40%!important}.oz-job.is-min .oz-job__bar{margin:2px 16px 16px}.oz-job.is-min .oz-job__head{padding-bottom:10px}.oz-job.is-done .oz-job__bar div{background:linear-gradient(-90deg,#10ac81,#0c8f6b)}.oz-job.is-stopped .oz-job__bar div{background:#c3ccd2}.oz-job.is-error .oz-job__bar div{background:linear-gradient(-90deg,#f16b6b,#d63638)}.oz-job__step{color:#6b7a86;font-size:12px;margin-top:8px}.oz-job__stop{align-items:center;background:transparent;border:0;color:#d63638;cursor:pointer;display:inline-flex;font-size:13px;font-weight:650;gap:6px;margin-top:14px;padding:4px 0}.oz-job__stop .dashicons{font-size:18px}.oz-job__stop:hover{color:#b32d2e}.oz-job__log{background:#11202b;border-radius:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.7;margin-top:14px;max-height:150px;overflow-y:auto;padding:10px 12px}.oz-logline{color:#cdd8df;display:flex;gap:8px}.oz-logline__tag{color:#5fd0ad;flex:0 0 auto;font-weight:700;min-width:42px}.oz-logline--warn .oz-logline__tag{color:#f0b429}.oz-logline--error .oz-logline__tag{color:#ff6b6b}.oz-logline--done .oz-logline__tag{color:#1dd1a1}.oz-logline__text{word-break:break-word}@media(max-width:1100px){.oz-kpis,.oz-quick{grid-template-columns:1fr}}@media(max-width:1024px){.oz-score{align-items:flex-start;flex-direction:column;text-align:right}.oz-convert{align-items:stretch;flex-direction:column;gap:12px}.oz-convert .oz-btn-ghost.components-button,.oz-convert .oz-btn-primary.components-button{justify-content:center;width:100%}}.oz-mobile-toggle,.oz-nav-overlay{display:none!important}@media(max-width:1024px){.oz-shell{flex-direction:column;margin:8px 8px 110px}.oz-content{border-right:0;min-width:0;width:100%}.oz-sidebar{border-bottom:1px solid #e7ecef;border-left:0;flex:none;max-height:none;overflow:visible;padding:14px 12px;position:static;width:100%}.oz-sidebar__brand{margin-bottom:10px;padding-bottom:12px}.oz-sidebar nav{display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}.oz-navitem{border-bottom:3px solid transparent;border-right:0;border-radius:10px;flex:0 0 auto;padding:8px 12px;width:auto}.oz-navitem.is-active{border-bottom-color:#1dd1a1;border-right-color:transparent}.oz-navitem .oz-navitem__text strong{white-space:nowrap}.oz-navitem .oz-navitem__dot,.oz-navitem small,.oz-sidebar__foot{display:none}.oz-topbar{padding:16px 18px}.oz-topbar h1{font-size:18px}.oz-main{padding:16px 18px}.oz-savebar{right:0;padding:12px 18px}.oz-card__body{padding-right:18px;padding-left:18px}.oz-job{right:8px;left:8px;width:auto}}@media(max-width:560px){.oz-tiles{grid-template-columns:1fr}.oz-hero,.oz-score{padding:18px}.oz-card__head{gap:12px;padding:16px}.oz-card__icon{flex-basis:40px;height:40px;width:40px}.oz-savebar__hint{display:none}}@keyframes oz-indeterminate{0%{transform:translateX(100%)}to{transform:translateX(-350%)}}.oz-muted{color:#6b7a86;font-size:13px}.oz-reqs{list-style:none;margin:0;padding:0}.oz-req{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:grid;font-size:13.5px;gap:6px 10px;grid-template-columns:20px 1fr auto;padding:10px 6px}.oz-req:last-child{border-bottom:0}.oz-req__dot{font-size:18px;height:18px;width:18px}.oz-req__label{font-weight:650}.oz-req__value{color:#6b7a86;font-weight:600;justify-self:end}.oz-req__note{color:#6b7a86;font-size:12px;font-weight:400;grid-column:2/-1;line-height:1.45;margin-top:2px}.oz-req--ok>.oz-req__dot{color:#0c8f6b}.oz-req--warn>.oz-req__dot{color:#e0900f}.oz-req--error>.oz-req__dot{color:#e05242}.oz-req--info>.oz-req__dot,.oz-req--info>.oz-req__value{color:#6b7a86}.oz-req--warn{background:rgba(224,144,15,.06)}.oz-req--error{background:rgba(224,82,66,.06)}.oz-notice{align-items:flex-start;background:rgba(224,82,66,.08);border:1px solid rgba(224,82,66,.28);border-radius:10px;color:#7a2018;display:flex;font-size:13px;gap:10px;line-height:1.5;margin:0 0 14px;padding:12px 14px}.oz-notice .dashicons{color:#e05242;flex:0 0 20px;margin-top:1px}.oz-notice--warn{background:rgba(224,144,15,.09);border-color:rgba(224,144,15,.3);color:#7a5406}.oz-notice--warn .dashicons{color:#e0900f}.oz-notice--ok{background:#eaf8f3;border-color:#b8e6d6;color:#0f5e48}.oz-notice--ok .dashicons{color:#0e9f7e}.oz-recs{list-style:none;margin:0;padding:0}.oz-rec{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:flex;gap:12px;padding:12px 8px}.oz-rec:last-child{border-bottom:0}.oz-rec__dot{flex:0 0 20px;font-size:20px;height:20px;margin-top:1px;width:20px}.oz-rec__text{flex:1;min-width:0}.oz-rec__text strong{display:block;font-size:13.5px;font-weight:650}.oz-rec__text small{color:#6b7a86;display:block;font-size:12px;line-height:1.45;margin-top:2px}.oz-rec__btn.components-button{flex:0 0 auto}.oz-rec--high>.oz-rec__dot{color:#e05242}.oz-rec--medium>.oz-rec__dot{color:#e0900f}.oz-rec--low>.oz-rec__dot{color:#6b7a86}.oz-rec--high{background:rgba(224,82,66,.05)}.oz-rec--medium{background:rgba(224,144,15,.05)}.oz-recs-clear{align-items:center;color:#6b7a86;display:flex;font-size:13px;gap:8px}.oz-recs-clear .dashicons{color:#0c8f6b}.oz-select--compact{border-bottom:0!important;flex:none;margin:0 auto 0 0!important;padding:0!important}.oz-select--compact .oz-select__btn{background:#f7faf9;border-radius:9px;font-size:13px;font-weight:650;gap:10px;height:30px;min-width:132px;padding:0 12px 0 10px;width:auto}.oz-select--compact .oz-select__btn:disabled{cursor:progress;opacity:.6}.oz-select--compact .oz-select__btn .dashicons{font-size:16px;height:16px;width:16px}.oz-select--compact .oz-select__menu{right:auto;min-width:190px;left:0}.oz-select--compact .oz-select__item{font-size:13px;padding:8px 10px}.oz-cachewarn--ok{background:#eaf8f3;border-color:#b8e6d6;color:#0f5e48}.oz-cachewarn__text p+p{margin-top:6px}.oz-cachewarn__link{color:inherit;display:inline-block;font-weight:600;margin-top:8px;text-decoration:underline}.oz-network__sites{align-items:start;margin-top:16px}.oz-network__sites .oz-panel__head h2,.oz-network__sites .oz-panel__head p{overflow-wrap:anywhere}.oz-cachelist__link{color:#1dd1a1;font-weight:600;margin-right:auto;text-decoration:none}.oz-cachelist__link:focus,.oz-cachelist__link:hover{text-decoration:underline}
  • optimazz/tags/1.1.0/build/index.asset.php

    r3723128 r3743505  
    1 <?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'e40d7f0648a583c6302d');
     1<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '5a501c583130cba224b3');
  • optimazz/tags/1.1.0/build/index.css

    r3723128 r3743505  
    1 .oz-shell{align-items:flex-start;background:#fff;border:1px solid #e7ecef;border-radius:16px;color:#0f2027;display:flex;font-size:14px;margin:10px 20px 130px 0;min-height:640px;overflow:hidden}.oz-shell *{box-sizing:border-box}.oz-shell .dashicons{font-family:dashicons}.oz-sidebar{align-self:flex-start;background:#fff;display:flex;flex:0 0 264px;flex-direction:column;padding:18px 14px}.oz-sidebar__brand{align-items:center;border-bottom:1px solid #e7ecef;display:flex;gap:12px;margin-bottom:10px;padding:10px 10px 18px}.oz-sidebar__brand strong{display:block;font-size:18px;font-weight:750;letter-spacing:-.01em}.oz-sidebar__brand span{color:#6b7a86;font-size:11px}.oz-sidebar__brand-fallback{align-items:center;display:flex;gap:12px}.oz-sidebar__foot{border-top:1px solid #e7ecef;color:#6b7a86;font-size:11px;margin-top:12px;padding:12px 12px 2px}.oz-logo{background:linear-gradient(135deg,#1dd1a1,#10ac81);border-radius:12px;color:#fff;display:grid;font-size:26px;height:44px;place-items:center;width:44px}.oz-logo-img{display:block;height:30px;max-width:100%;width:auto}.oz-navitem{align-items:center;background:transparent;border:0;border-left:3px solid transparent;border-radius:0 10px 10px 0;color:#0f2027;cursor:pointer;display:flex;gap:12px;padding:11px 12px;text-align:left;transition:all .14s ease;width:100%}.oz-navitem>.oz-ico{color:#6b7a86;flex:0 0 20px;height:20px;transition:color .14s ease,transform .14s ease;width:20px}.oz-navitem:hover>.oz-ico{color:#0f2027}.oz-navitem.is-active>.oz-ico{color:#0c8f6b}.oz-navitem__text{flex:1;min-width:0}.oz-navitem__text strong{display:block;font-size:13.5px;font-weight:650;line-height:1.2}.oz-navitem__text small{color:#6b7a86;font-size:11px}.oz-navitem__dot{background:#1dd1a1;border-radius:50%;height:8px;width:8px}.oz-navitem:hover{background:#f4f7f8}.oz-navitem.is-active{background:rgba(29,209,161,.12);border-left-color:#1dd1a1}.oz-navitem.is-active .oz-navitem__text strong{color:#0c8f6b}.oz-content{align-self:stretch;background:#f4f7f8;border-left:1px solid #e7ecef;display:flex;flex:1;flex-direction:column;min-width:0}.oz-topbar{align-items:center;background:#fff;border-bottom:1px solid #e7ecef;display:flex;gap:14px;padding:20px 28px}.oz-topbar__icon{background:rgba(29,209,161,.14);border-radius:12px;color:#0c8f6b;display:grid;font-size:22px;height:44px;place-items:center;width:44px}.oz-topbar h1{font-size:20px;font-weight:700;margin:0;padding:0}.oz-topbar p{color:#6b7a86;font-size:12.5px;margin:0}.oz-main{display:flex;flex-direction:column;gap:16px;padding:24px 28px}.oz-welcome{align-items:center;background:linear-gradient(125deg,rgba(29,209,161,.9),rgba(16,172,129,.92));border-radius:16px;color:#06382c;display:flex;gap:20px;justify-content:space-between;overflow:hidden;padding:22px 26px}.oz-welcome h2{color:#06382c;font-size:18px;font-weight:700;margin:0 0 6px}.oz-welcome p{color:#0c5b46;font-size:13px;margin:0;max-width:620px}.oz-welcome__icon{flex:0 0 auto;font-size:64px;height:64px;opacity:.5;width:64px}.oz-score{align-items:center;background:linear-gradient(125deg,#1dd1a1,#10ac81 62%,#0c8f6b);border-radius:16px;box-shadow:0 16px 34px -18px rgba(12,143,107,.65);color:#06382c;display:flex;gap:30px;overflow:hidden;padding:28px 32px;position:relative}.oz-score:before{background:radial-gradient(circle,hsla(0,0%,100%,.3),transparent 62%);height:440px;right:-8%;top:-55%;width:440px}.oz-score:after,.oz-score:before{border-radius:50%;content:"";pointer-events:none;position:absolute}.oz-score:after{background:radial-gradient(circle,rgba(0,0,0,.08),transparent 60%);bottom:-70%;height:320px;left:8%;width:320px}.oz-score>*{position:relative;z-index:1}.oz-score__gauge{filter:drop-shadow(0 8px 16px rgba(3,40,30,.28));flex:0 0 auto;line-height:0;position:relative}.oz-score__grade{background:#fff;border-radius:50%;box-shadow:0 4px 10px rgba(3,40,30,.28);color:#0c8f6b;display:grid;font-size:14px;font-weight:800;height:30px;place-items:center;position:absolute;right:2px;top:8px;width:30px}.oz-score__body{flex:1;min-width:0}.oz-score__body h2{color:#06382c;font-size:21px;font-weight:750;letter-spacing:-.02em;margin:0 0 4px}.oz-score__body p{color:rgba(6,56,44,.82);font-size:13px;font-weight:500;margin:0 0 15px}.oz-score__chips{display:flex;flex-wrap:wrap;gap:8px}.oz-chip{align-items:center;background:hsla(0,0%,100%,.18);border:1px solid hsla(0,0%,100%,.28);border-radius:999px;color:rgba(6,56,44,.72);display:inline-flex;font-size:11.5px;font-weight:650;gap:6px;padding:5px 12px 5px 9px;transition:transform .15s ease,box-shadow .15s ease}.oz-chip .dashicons{font-size:15px;height:15px;opacity:.65;width:15px}.oz-chip.is-on{background:#fff;border-color:#fff;box-shadow:0 3px 8px -2px rgba(3,40,30,.22);color:#0c8f6b}.oz-chip.is-on .dashicons{color:#1dd1a1;opacity:1}.oz-chip.is-on:hover{box-shadow:0 6px 14px -4px rgba(3,40,30,.3);transform:translateY(-1px)}.oz-convert{align-items:center;background:#f4f7f8;border-radius:12px;display:flex;gap:16px;justify-content:space-between;margin-top:16px;padding:16px}.oz-convert__info{color:#6b7a86;display:flex;flex-direction:column;font-size:12.5px;gap:3px}.oz-convert__info strong{color:#0f2027;font-size:13px}.oz-progress{background:#dfe6e8;border-radius:999px;height:6px;margin-top:4px;overflow:hidden;width:200px}.oz-progress div{background:linear-gradient(90deg,#1dd1a1,#10ac81);height:100%;transition:width .3s ease}.oz-kpis{display:grid;gap:14px;grid-template-columns:repeat(3,1fr)}.oz-kpi{background:#fff;border:1px solid #e7ecef;border-radius:14px;display:flex;flex-direction:column;padding:16px 18px}.oz-kpi>.dashicons{color:#10ac81;font-size:20px;margin-bottom:8px}.oz-kpi strong{font-size:24px;font-weight:750;min-height:28px}.oz-kpi span{color:#6b7a86;font-size:12px}.oz-quick{display:grid;gap:16px;grid-template-columns:1fr 1fr}.oz-quick__actions{display:flex;flex-wrap:wrap;gap:10px}.oz-cachelist{border-top:1px solid #e7ecef;margin:18px 0 0}.oz-cachelist>li{align-items:center;border-bottom:1px solid #f1f4f5;color:#6b7a86;display:flex;font-size:13px;gap:10px;padding:11px 2px}.oz-cachelist>li:last-child{border-bottom:0}.oz-cachelist>li .dashicons{color:#10ac81;font-size:17px}.oz-cachelist>li strong{color:#0f2027;font-weight:650;margin-left:auto}.oz-cachelist>li .oz-row-label{display:flex;flex-direction:column;line-height:1.3}.oz-cachelist>li .oz-row-label small{color:#6b7a86;font-size:11.5px;opacity:.85}.oz-status{margin:0}.oz-status li{align-items:center;border-bottom:1px solid #e7ecef;display:flex;font-size:13.5px;font-weight:600;gap:10px;padding:9px 6px}.oz-status li:last-child{border-bottom:0}.oz-status li>.oz-ico{color:#6b7a86;flex:0 0 18px;height:18px;width:18px}.oz-status__name{background:transparent;border:0;color:#0f2027;cursor:pointer;flex:1;font:inherit;font-weight:600;padding:4px 0;text-align:left}.oz-status__name:hover{color:#0c8f6b}.oz-status .components-toggle-control{margin-left:auto}.oz-card,.oz-panel{background:#fff;border:1px solid #e7ecef;border-radius:16px;overflow:visible}.oz-card .oz-card__head{border-radius:16px 16px 0 0}.oz-panel__head{padding:18px 22px 0}.oz-panel__head h2{font-size:16px;font-weight:650;margin:0}.oz-panel__head p{color:#6b7a86;font-size:12.5px;margin:4px 0 0}.oz-panel__body{padding:8px 22px 20px}.oz-panel__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-panel__body .components-base-control:last-child{border-bottom:0}.oz-card{transition:box-shadow .18s ease,border-color .18s ease}.oz-card.is-enabled{border-color:rgba(29,209,161,.5);box-shadow:0 0 0 1px rgba(29,209,161,.22)}.oz-card__head{align-items:flex-start;display:flex;gap:15px;padding:20px 22px}.oz-card__icon{background:#f4f7f8;border-radius:12px;color:#6b7a86;display:grid;flex:0 0 46px;font-size:22px;height:46px;place-items:center;width:46px}.oz-card__title{flex:1;min-width:0}.oz-card__title h2{align-items:center;display:flex;font-size:16px;font-weight:650;gap:10px;margin:0 0 3px}.oz-card__title p{color:#6b7a86;font-size:12.5px;margin:0}.oz-card__body{border-top:1px solid #e7ecef;padding:4px 22px 16px}.oz-card__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-card__body .components-base-control:last-child{border-bottom:0}.is-enabled .oz-card__icon{background:linear-gradient(135deg,#1dd1a1,#10ac81);color:#fff}.oz-inline-actions{display:flex;gap:10px;padding-top:16px}.oz-dbgroup{border-top:1px solid #eef0f2;padding:18px 0}.oz-dbgroup:first-of-type{border-top:0;padding-top:4px}.oz-dbgroup__title{color:#0e1116;font-size:14px;font-weight:700;margin:0 0 4px}.oz-dbgroup__desc{color:#6b7280;font-size:13px;line-height:1.5;margin:0 0 14px}.oz-dbgroup .components-toggle-control{margin-bottom:14px}.oz-dbgroup .components-toggle-control:last-child{margin-bottom:0}.oz-dbwarn{align-items:flex-start;background:#fff5f5;border:1px solid #fcd9d9;border-radius:10px;color:#b42318;display:flex;font-size:13px;font-weight:600;gap:8px;line-height:1.5;margin:4px 0;padding:12px 14px}.oz-dbwarn .dashicons{color:#e05242;flex:0 0 auto}.oz-cachewarn{align-items:center;background:#fff8ec;border:1px solid #f4dcae;border-radius:12px;color:#7a4b00;display:flex;gap:13px;margin:0 0 18px;padding:14px 16px}.oz-cachewarn .dashicons{color:#e0900f;flex:0 0 auto;font-size:22px;height:22px;width:22px}.oz-cachewarn__text{flex:1 1 auto;min-width:0}.oz-cachewarn__text strong{color:#6b4200;display:block;font-size:14px;font-weight:700}.oz-cachewarn__text p{color:#8a6410;font-size:12.5px;line-height:1.5;margin:3px 0 0}.oz-cachewarn .oz-btn-primary{flex:0 0 auto}@media(max-width:640px){.oz-cachewarn{flex-wrap:wrap}.oz-cachewarn .oz-btn-primary{justify-content:center;width:100%}}.oz-subhead{color:#8a96a0;font-size:11px;font-weight:700;letter-spacing:.08em;margin:18px 0 2px;padding-top:14px;text-transform:uppercase}.oz-health,.oz-subhead{border-top:1px solid #e7ecf0}.oz-health{margin-top:18px;padding-top:16px}.oz-health__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.oz-health__head strong{font-size:14px}.oz-health__hint{color:#6b7280;font-size:12px;line-height:1.5;margin:4px 0 10px}.oz-health__list{display:flex;flex-direction:column;gap:6px;margin:0}.oz-health__row{align-items:center;display:flex;font-size:12.5px;gap:8px;line-height:1.4;margin:0}.oz-health__row .dashicons{flex:0 0 auto;font-size:17px;height:17px;width:17px}.oz-health__row.is-ok{color:#216e4e}.oz-health__row.is-ok .dashicons{color:#1dd1a1}.oz-health__row.is-warn{color:#8a5a00}.oz-health__row.is-warn .dashicons{color:#e0900f}.oz-health__row.is-bad{color:#b42318}.oz-health__row.is-bad .dashicons{color:#e05242}.oz-health__err{color:#b42318;font-size:12.5px}.oz-health__fix{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-top:14px}.oz-health__fix span{color:#6b7280;flex:1 1 160px;font-size:12px}.oz-health__conflict{display:flex;flex-direction:column;gap:8px;margin-top:12px}.oz-health__conflict-row{align-items:center;background:#fff5f5;border:1px solid #fcd9d9;border-radius:9px;display:flex;flex-wrap:wrap;gap:10px;padding:10px 12px}.oz-health__conflict-row .dashicons{color:#e05242;flex:0 0 auto}.oz-health__conflict-row span:not(.dashicons){color:#8a2f22;flex:1 1 200px;font-size:12.5px}.oz-health__conflict-row .oz-btn-danger.components-button{flex:0 0 auto;height:32px}.oz-health__manual{background:#fff8ec;border:1px solid #f4dcae;border-radius:9px;margin-top:12px;padding:10px 12px}.oz-health__manual p{color:#7a4b00;font-size:12px;margin:0 0 6px}.oz-health__manual code{background:#2b2f36;border-radius:6px;color:#e7f0ec;display:inline-block;font-size:12.5px;padding:6px 10px}.oz-btn-ghost.components-button{border:1px solid #d5dbe0;border-radius:8px;color:#384049;font-size:12.5px;height:30px}.oz-btn-ghost.components-button:hover:not(:disabled){border-color:#1dd1a1;color:#0b7a5c}.oz-pill{align-items:center;border-radius:999px;display:inline-flex;font-size:11px;font-weight:700;gap:5px;padding:3px 9px}.oz-pill i{background:currentColor;border-radius:50%;height:6px;width:6px}.oz-pill.is-on{background:rgba(29,209,161,.16);color:#0c8f6b}.oz-pill.is-off{background:#eef1f2;color:#6b7a86}.oz-shell .components-range-control__thumb,.oz-shell .components-range-control__track{background-color:#1dd1a1!important}.oz-fieldgroup{background:rgba(29,209,161,.035);border:1px solid #e7ecef;border-radius:12px;margin-top:18px;padding:16px 16px 4px}.oz-fieldgroup__title{color:#0e1116;font-size:13px;font-weight:700;margin:0 0 4px}.oz-fieldgroup__desc{color:#6b7280;font-size:12.5px;line-height:1.5;margin:0 0 14px}.oz-fieldgroup .components-base-control{margin-bottom:14px}.oz-shell .components-form-token-field__input-container{background:#fff;border:1px solid #e7ecef;border-radius:10px;min-height:44px;padding:5px 8px;transition:border-color .15s ease,box-shadow .15s ease}.oz-shell .components-form-token-field__input-container.is-active,.oz-shell .components-form-token-field__input-container:focus-within{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-shell .components-form-token-field__token{border-radius:8px;font-size:12.5px;margin:3px;overflow:hidden}.oz-shell .components-form-token-field__token-text{background:rgba(29,209,161,.14);border-radius:8px 0 0 8px;color:#0c8f6b;font-weight:650;line-height:1.7;padding:2px 4px 2px 11px}.oz-shell .components-form-token-field__remove-token{background:rgba(29,209,161,.14);border-radius:0 8px 8px 0;color:#0c8f6b;min-width:24px;padding:0 7px;transition:background .15s ease,color .15s ease}.oz-shell .components-form-token-field__remove-token svg{fill:currentColor}.oz-shell .components-form-token-field__remove-token:hover{background:#10ac81;color:#fff}.oz-shell .components-form-token-field__input{font-size:13.5px;margin:4px;min-height:28px}.oz-shell .components-input-control__backdrop{display:none!important}.oz-shell .components-input-control__input,.oz-shell .components-select-control__input,.oz-shell .components-text-control__input{background:#fff!important;border:1px solid #e7ecef!important;border-radius:10px!important;box-shadow:none!important;color:#0f2027;font-size:13.5px;min-height:42px;padding:0 14px}.oz-shell .components-input-control__input:hover,.oz-shell .components-select-control__input:hover,.oz-shell .components-text-control__input:hover{border-color:#bcc6cc!important}.oz-shell .components-input-control__input:focus,.oz-shell .components-select-control__input:focus,.oz-shell .components-text-control__input:focus{border-color:#1dd1a1!important;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-shell select.components-select-control__input{cursor:pointer}.oz-pillgroup .components-base-control__label{display:block;font-weight:600;margin-bottom:8px}.oz-pillgroup__row{background:#f0f2f4;border-radius:12px;display:inline-flex;flex-wrap:wrap;gap:6px;padding:4px}.oz-pillgroup .components-base-control__help{display:block;margin-top:8px}.oz-pillbtn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:9px;color:#4b5563;cursor:pointer;font-size:13px;font-weight:600;min-width:44px;padding:7px 16px;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.oz-pillbtn:hover{color:#0e1116}.oz-pillbtn.is-on{background:#fff;box-shadow:0 1px 3px rgba(4,56,44,.14);color:#0c8f6b}.oz-pillbtn:focus-visible{outline:2px solid #1dd1a1;outline-offset:1px}.oz-select{position:relative}.oz-select .components-base-control__label{display:block;margin-bottom:8px}.oz-select__btn{align-items:center;background:#fff;border:1px solid #e7ecef;border-radius:10px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;height:42px;justify-content:space-between;padding:0 14px;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.oz-select__btn .dashicons{color:#0c8f6b;transition:transform .18s ease}.oz-select__btn:hover{border-color:#bcc6cc}.oz-select__btn:focus,.oz-select__btn:focus-visible{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-select__btn[aria-expanded=true]{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-select__btn[aria-expanded=true] .dashicons{transform:rotate(180deg)}.oz-select__menu{background:#fff;border:1px solid #e7ecef;border-radius:12px;box-shadow:0 14px 34px rgba(15,32,39,.18);left:0;list-style:none;margin:0;padding:6px;position:absolute;right:0;top:calc(100% + 6px);z-index:30}.oz-select__item{align-items:center;border-radius:8px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;justify-content:space-between;padding:9px 12px}.oz-select__item .dashicons{color:#10ac81;font-size:18px}.oz-select__item.is-active,.oz-select__item:hover{background:rgba(29,209,161,.12);color:#0c8f6b}.oz-select__item.is-selected{color:#0c8f6b;font-weight:650}.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{border-radius:10px;font-weight:650;height:38px;transition:transform .16s cubic-bezier(.2,.7,.3,1),box-shadow .18s ease,background .16s ease,border-color .16s ease,color .16s ease}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:transform .22s cubic-bezier(.2,.7,.3,1)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:translateY(-1px)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:scale(1.14)}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled){transform:translateY(0) scale(.975);transition-duration:.05s}.oz-shell .oz-btn-danger.components-button:focus-visible,.oz-shell .oz-btn-ghost.components-button:focus-visible,.oz-shell .oz-btn-primary.components-button:focus-visible{outline:2px solid #1dd1a1;outline-offset:2px}.oz-shell .oz-btn-danger.components-button:disabled,.oz-shell .oz-btn-ghost.components-button:disabled,.oz-shell .oz-btn-primary.components-button:disabled{opacity:.5;transform:none}.oz-shell .oz-btn-primary.components-button{background:linear-gradient(135deg,#1dd1a1,#10ac81);border:0;box-shadow:0 1px 2px rgba(20,60,48,.12);color:#fff;padding:0 18px}.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){background:linear-gradient(135deg,#10ac81,#0c8f6b);box-shadow:0 8px 20px -7px rgba(29,209,161,.65);color:#fff}.oz-shell .oz-btn-primary.components-button.is-busy{animation:oz-btn-sheen 1.4s linear infinite;background-size:200% 100%}.oz-shell .oz-btn-ghost.components-button{background:#fff;border:1px solid rgba(29,209,161,.5);box-shadow:none;color:#0c8f6b}.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled){background:rgba(29,209,161,.08);border-color:#1dd1a1;box-shadow:0 6px 16px -8px rgba(29,209,161,.5);color:#0c8f6b}.oz-shell .oz-btn-danger.components-button{background:#fff;border:1px solid rgba(214,54,56,.5);color:#d63638}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled){background:rgba(214,54,56,.08);border-color:#d63638;box-shadow:0 6px 16px -8px rgba(214,54,56,.5);color:#b32d2e}.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg{transform:scale(1.1) rotate(90deg)}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg{transform:none}}@keyframes oz-btn-sheen{0%{background-image:linear-gradient(135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:200% 0}to{background-image:linear-gradient(135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:-200% 0}}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{transition:background .16s ease,color .16s ease,border-color .16s ease}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:none}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:none}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:none}.oz-shell .oz-btn-danger.components-button.is-busy,.oz-shell .oz-btn-ghost.components-button.is-busy,.oz-shell .oz-btn-primary.components-button.is-busy{animation:none}}.oz-shell .components-button.is-primary:focus:not(:disabled),.oz-shell .components-button:focus,.oz-shell .components-button:focus:not(:disabled){box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle,.oz-shell .components-form-toggle__input,.oz-shell .components-form-toggle__input:active,.oz-shell .components-form-toggle__input:focus,.oz-shell .components-form-toggle__input:focus-visible{border:none!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__track{background-color:#cdd6db!important;border:1px solid transparent!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle.is-checked .components-form-toggle__track{background-color:#1dd1a1!important}.oz-shell .components-form-toggle__input:focus+.components-form-toggle__track,.oz-shell .components-form-toggle__input:focus-visible+.components-form-toggle__track{box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__thumb{background-color:#fff!important;border:none!important;box-shadow:0 1px 2px rgba(15,32,39,.25)!important}.oz-savebar{align-items:center;background:#fff;border-top:1px solid #e7ecef;bottom:0;box-shadow:0 -6px 18px rgba(15,32,39,.06);display:flex;gap:16px;justify-content:flex-end;left:160px;padding:13px 32px;position:fixed;right:0;z-index:9}.oz-savebar__hint{color:#6b7a86;font-size:13px}.oz-savebar.is-dirty .oz-savebar__hint{color:#0c8f6b;font-weight:650}.folded .oz-savebar{left:36px}.oz-toast{bottom:72px;z-index:99}.oz-job,.oz-toast{position:fixed;right:24px}.oz-job{background:#fff;border:1px solid #e7ecef;border-radius:16px;bottom:84px;box-shadow:0 18px 50px rgba(15,32,39,.22);color:#0f2027;font-size:13px;max-width:calc(100vw - 48px);overflow:hidden;width:420px;z-index:100}.oz-job__head{align-items:center;display:flex;gap:12px;padding:16px 16px 12px}.oz-job__head .components-spinner{margin:0}.oz-job__icon-status{font-size:22px;height:22px;width:22px}.oz-job__icon-status--done{color:#10ac81}.oz-job__icon-status--stopped{color:#6b7a86}.oz-job__icon-status--error{color:#d63638}.oz-job__title{flex:1;min-width:0}.oz-job__title strong{display:block;font-size:15px;font-weight:700;line-height:1.2}.oz-job__title span{color:#6b7a86;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.oz-job__pct{color:#0f2027;font-size:13px;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__icon{background:transparent;border:0;border-radius:6px;color:#6b7a86;cursor:pointer;display:inline-flex;padding:4px}.oz-job__icon:hover{background:#f4f7f8;color:#0f2027}.oz-job__icon .dashicons{font-size:18px;height:18px;width:18px}.oz-job__body{padding:0 16px 16px}.oz-job__row{align-items:baseline;display:flex;gap:12px;justify-content:space-between;margin-bottom:8px}.oz-job__row span{line-height:1.4}.oz-job__row strong{flex:0 0 auto;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__bar{background:#e7ecef;border-radius:999px;height:7px;overflow:hidden}.oz-job__bar div{background:linear-gradient(90deg,#1dd1a1,#10ac81);border-radius:999px;height:100%;transition:width .35s ease}.oz-job__bar.is-indeterminate div{animation:oz-indeterminate 1.1s ease-in-out infinite;border-radius:999px;width:40%!important}.oz-job.is-min .oz-job__bar{margin:2px 16px 16px}.oz-job.is-min .oz-job__head{padding-bottom:10px}.oz-job.is-done .oz-job__bar div{background:linear-gradient(90deg,#10ac81,#0c8f6b)}.oz-job.is-stopped .oz-job__bar div{background:#c3ccd2}.oz-job.is-error .oz-job__bar div{background:linear-gradient(90deg,#f16b6b,#d63638)}.oz-job__step{color:#6b7a86;font-size:12px;margin-top:8px}.oz-job__stop{align-items:center;background:transparent;border:0;color:#d63638;cursor:pointer;display:inline-flex;font-size:13px;font-weight:650;gap:6px;margin-top:14px;padding:4px 0}.oz-job__stop .dashicons{font-size:18px}.oz-job__stop:hover{color:#b32d2e}.oz-job__log{background:#11202b;border-radius:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.7;margin-top:14px;max-height:150px;overflow-y:auto;padding:10px 12px}.oz-logline{color:#cdd8df;display:flex;gap:8px}.oz-logline__tag{color:#5fd0ad;flex:0 0 auto;font-weight:700;min-width:42px}.oz-logline--warn .oz-logline__tag{color:#f0b429}.oz-logline--error .oz-logline__tag{color:#ff6b6b}.oz-logline--done .oz-logline__tag{color:#1dd1a1}.oz-logline__text{word-break:break-word}@media(max-width:1100px){.oz-kpis,.oz-quick{grid-template-columns:1fr}}@media(max-width:1024px){.oz-score{align-items:flex-start;flex-direction:column;text-align:left}.oz-convert{align-items:stretch;flex-direction:column;gap:12px}.oz-convert .oz-btn-ghost.components-button,.oz-convert .oz-btn-primary.components-button{justify-content:center;width:100%}}.oz-mobile-toggle,.oz-nav-overlay{display:none!important}@media(max-width:1024px){.oz-shell{flex-direction:column;margin:8px 8px 110px}.oz-content{border-left:0;min-width:0;width:100%}.oz-sidebar{border-bottom:1px solid #e7ecef;border-right:0;flex:none;max-height:none;overflow:visible;padding:14px 12px;position:static;width:100%}.oz-sidebar__brand{margin-bottom:10px;padding-bottom:12px}.oz-sidebar nav{display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}.oz-navitem{border-bottom:3px solid transparent;border-left:0;border-radius:10px;flex:0 0 auto;padding:8px 12px;width:auto}.oz-navitem.is-active{border-bottom-color:#1dd1a1;border-left-color:transparent}.oz-navitem .oz-navitem__text strong{white-space:nowrap}.oz-navitem .oz-navitem__dot,.oz-navitem small,.oz-sidebar__foot{display:none}.oz-topbar{padding:16px 18px}.oz-topbar h1{font-size:18px}.oz-main{padding:16px 18px}.oz-savebar{left:0;padding:12px 18px}.oz-card__body{padding-left:18px;padding-right:18px}.oz-job{left:8px;right:8px;width:auto}}@media(max-width:560px){.oz-tiles{grid-template-columns:1fr}.oz-hero,.oz-score{padding:18px}.oz-card__head{gap:12px;padding:16px}.oz-card__icon{flex-basis:40px;height:40px;width:40px}.oz-savebar__hint{display:none}}@keyframes oz-indeterminate{0%{transform:translateX(-100%)}to{transform:translateX(350%)}}.oz-muted{color:#6b7a86;font-size:13px}.oz-reqs{list-style:none;margin:0;padding:0}.oz-req{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:grid;font-size:13.5px;gap:6px 10px;grid-template-columns:20px 1fr auto;padding:10px 6px}.oz-req:last-child{border-bottom:0}.oz-req__dot{font-size:18px;height:18px;width:18px}.oz-req__label{font-weight:650}.oz-req__value{color:#6b7a86;font-weight:600;justify-self:end}.oz-req__note{color:#6b7a86;font-size:12px;font-weight:400;grid-column:2/-1;line-height:1.45;margin-top:2px}.oz-req--ok>.oz-req__dot{color:#0c8f6b}.oz-req--warn>.oz-req__dot{color:#e0900f}.oz-req--error>.oz-req__dot{color:#e05242}.oz-req--info>.oz-req__dot,.oz-req--info>.oz-req__value{color:#6b7a86}.oz-req--warn{background:rgba(224,144,15,.06)}.oz-req--error{background:rgba(224,82,66,.06)}.oz-notice{align-items:flex-start;background:rgba(224,82,66,.08);border:1px solid rgba(224,82,66,.28);border-radius:10px;color:#7a2018;display:flex;font-size:13px;gap:10px;line-height:1.5;margin:0 0 14px;padding:12px 14px}.oz-notice .dashicons{color:#e05242;flex:0 0 20px;margin-top:1px}.oz-notice--warn{background:rgba(224,144,15,.09);border-color:rgba(224,144,15,.3);color:#7a5406}.oz-notice--warn .dashicons{color:#e0900f}.oz-recs{list-style:none;margin:0;padding:0}.oz-rec{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:flex;gap:12px;padding:12px 8px}.oz-rec:last-child{border-bottom:0}.oz-rec__dot{flex:0 0 20px;font-size:20px;height:20px;margin-top:1px;width:20px}.oz-rec__text{flex:1;min-width:0}.oz-rec__text strong{display:block;font-size:13.5px;font-weight:650}.oz-rec__text small{color:#6b7a86;display:block;font-size:12px;line-height:1.45;margin-top:2px}.oz-rec__btn.components-button{flex:0 0 auto}.oz-rec--high>.oz-rec__dot{color:#e05242}.oz-rec--medium>.oz-rec__dot{color:#e0900f}.oz-rec--low>.oz-rec__dot{color:#6b7a86}.oz-rec--high{background:rgba(224,82,66,.05)}.oz-rec--medium{background:rgba(224,144,15,.05)}.oz-recs-clear{align-items:center;color:#6b7a86;display:flex;font-size:13px;gap:8px}.oz-recs-clear .dashicons{color:#0c8f6b}.oz-select--compact{border-bottom:0!important;flex:none;margin:0 0 0 auto!important;padding:0!important}.oz-select--compact .oz-select__btn{background:#f7faf9;border-radius:9px;font-size:13px;font-weight:650;gap:10px;height:30px;min-width:132px;padding:0 10px 0 12px;width:auto}.oz-select--compact .oz-select__btn:disabled{cursor:progress;opacity:.6}.oz-select--compact .oz-select__btn .dashicons{font-size:16px;height:16px;width:16px}.oz-select--compact .oz-select__menu{left:auto;min-width:190px;right:0}.oz-select--compact .oz-select__item{font-size:13px;padding:8px 10px}
     1.oz-shell{align-items:flex-start;background:#fff;border:1px solid #e7ecef;border-radius:16px;color:#0f2027;display:flex;font-size:14px;margin:10px 20px 130px 0;min-height:640px;overflow:hidden}.oz-shell *{box-sizing:border-box}.oz-shell .dashicons{font-family:dashicons}.oz-sidebar{align-self:flex-start;background:#fff;display:flex;flex:0 0 264px;flex-direction:column;padding:18px 14px}.oz-sidebar__brand{align-items:center;border-bottom:1px solid #e7ecef;display:flex;gap:12px;margin-bottom:10px;padding:10px 10px 18px}.oz-sidebar__brand strong{display:block;font-size:18px;font-weight:750;letter-spacing:-.01em}.oz-sidebar__brand span{color:#6b7a86;font-size:11px}.oz-sidebar__brand-fallback{align-items:center;display:flex;gap:12px}.oz-sidebar__foot{border-top:1px solid #e7ecef;color:#6b7a86;font-size:11px;margin-top:12px;padding:12px 12px 2px}.oz-logo{background:linear-gradient(135deg,#1dd1a1,#10ac81);border-radius:12px;color:#fff;display:grid;font-size:26px;height:44px;place-items:center;width:44px}.oz-logo-img{display:block;height:30px;max-width:100%;width:auto}.oz-navitem{align-items:center;background:transparent;border:0;border-left:3px solid transparent;border-radius:0 10px 10px 0;color:#0f2027;cursor:pointer;display:flex;gap:12px;padding:11px 12px;text-align:left;transition:all .14s ease;width:100%}.oz-navitem>.oz-ico{color:#6b7a86;flex:0 0 20px;height:20px;transition:color .14s ease,transform .14s ease;width:20px}.oz-navitem:hover>.oz-ico{color:#0f2027}.oz-navitem.is-active>.oz-ico{color:#0c8f6b}.oz-navitem__text{flex:1;min-width:0}.oz-navitem__text strong{display:block;font-size:13.5px;font-weight:650;line-height:1.2}.oz-navitem__text small{color:#6b7a86;font-size:11px}.oz-navitem__dot{background:#1dd1a1;border-radius:50%;height:8px;width:8px}.oz-navitem:hover{background:#f4f7f8}.oz-navitem.is-active{background:rgba(29,209,161,.12);border-left-color:#1dd1a1}.oz-navitem.is-active .oz-navitem__text strong{color:#0c8f6b}.oz-content{align-self:stretch;background:#f4f7f8;border-left:1px solid #e7ecef;display:flex;flex:1;flex-direction:column;min-width:0}.oz-topbar{align-items:center;background:#fff;border-bottom:1px solid #e7ecef;display:flex;gap:14px;padding:20px 28px}.oz-topbar__icon{background:rgba(29,209,161,.14);border-radius:12px;color:#0c8f6b;display:grid;font-size:22px;height:44px;place-items:center;width:44px}.oz-topbar h1{font-size:20px;font-weight:700;margin:0;padding:0}.oz-topbar p{color:#6b7a86;font-size:12.5px;margin:0}.oz-main{display:flex;flex-direction:column;gap:16px;padding:24px 28px}.oz-welcome{align-items:center;background:linear-gradient(125deg,rgba(29,209,161,.9),rgba(16,172,129,.92));border-radius:16px;color:#06382c;display:flex;gap:20px;justify-content:space-between;overflow:hidden;padding:22px 26px}.oz-welcome h2{color:#06382c;font-size:18px;font-weight:700;margin:0 0 6px}.oz-welcome p{color:#0c5b46;font-size:13px;margin:0;max-width:620px}.oz-welcome__icon{flex:0 0 auto;font-size:64px;height:64px;opacity:.5;width:64px}.oz-score{align-items:center;background:linear-gradient(125deg,#1dd1a1,#10ac81 62%,#0c8f6b);border-radius:16px;box-shadow:0 16px 34px -18px rgba(12,143,107,.65);color:#06382c;display:flex;gap:30px;overflow:hidden;padding:28px 32px;position:relative}.oz-score:before{background:radial-gradient(circle,hsla(0,0%,100%,.3),transparent 62%);height:440px;right:-8%;top:-55%;width:440px}.oz-score:after,.oz-score:before{border-radius:50%;content:"";pointer-events:none;position:absolute}.oz-score:after{background:radial-gradient(circle,rgba(0,0,0,.08),transparent 60%);bottom:-70%;height:320px;left:8%;width:320px}.oz-score>*{position:relative;z-index:1}.oz-score__gauge{filter:drop-shadow(0 8px 16px rgba(3,40,30,.28));flex:0 0 auto;line-height:0;position:relative}.oz-score__grade{background:#fff;border-radius:50%;box-shadow:0 4px 10px rgba(3,40,30,.28);color:#0c8f6b;display:grid;font-size:14px;font-weight:800;height:30px;place-items:center;position:absolute;right:2px;top:8px;width:30px}.oz-score__body{flex:1;min-width:0}.oz-score__body h2{color:#06382c;font-size:21px;font-weight:750;letter-spacing:-.02em;margin:0 0 4px}.oz-score__body p{color:rgba(6,56,44,.82);font-size:13px;font-weight:500;margin:0 0 15px}.oz-score__chips{display:flex;flex-wrap:wrap;gap:8px}.oz-chip{align-items:center;background:hsla(0,0%,100%,.18);border:1px solid hsla(0,0%,100%,.28);border-radius:999px;color:rgba(6,56,44,.72);display:inline-flex;font-size:11.5px;font-weight:650;gap:6px;padding:5px 12px 5px 9px;transition:transform .15s ease,box-shadow .15s ease}.oz-chip .dashicons{font-size:15px;height:15px;opacity:.65;width:15px}.oz-chip.is-on{background:#fff;border-color:#fff;box-shadow:0 3px 8px -2px rgba(3,40,30,.22);color:#0c8f6b}.oz-chip.is-on .dashicons{color:#1dd1a1;opacity:1}.oz-chip.is-on:hover{box-shadow:0 6px 14px -4px rgba(3,40,30,.3);transform:translateY(-1px)}.oz-convert{align-items:center;background:#f4f7f8;border-radius:12px;display:flex;gap:16px;justify-content:space-between;margin-top:16px;padding:16px}.oz-convert__info{color:#6b7a86;display:flex;flex-direction:column;font-size:12.5px;gap:3px}.oz-convert__info strong{color:#0f2027;font-size:13px}.oz-progress{background:#dfe6e8;border-radius:999px;height:6px;margin-top:4px;overflow:hidden;width:200px}.oz-progress div{background:linear-gradient(90deg,#1dd1a1,#10ac81);height:100%;transition:width .3s ease}.oz-kpis{display:grid;gap:14px;grid-template-columns:repeat(3,1fr)}.oz-kpis--single{grid-template-columns:1fr}.oz-kpi{background:#fff;border:1px solid #e7ecef;border-radius:14px;display:flex;flex-direction:column;padding:16px 18px}.oz-kpi>.dashicons{color:#10ac81;font-size:20px;margin-bottom:8px}.oz-kpi strong{font-size:24px;font-weight:750;min-height:28px}.oz-kpi span{color:#6b7a86;font-size:12px}.oz-quick{display:grid;gap:16px;grid-template-columns:1fr 1fr}.oz-quick__actions{display:flex;flex-wrap:wrap;gap:10px}.oz-cachelist{border-top:1px solid #e7ecef;margin:18px 0 0}.oz-cachelist>li{align-items:center;border-bottom:1px solid #f1f4f5;color:#6b7a86;display:flex;font-size:13px;gap:10px;padding:11px 2px}.oz-cachelist>li:last-child{border-bottom:0}.oz-cachelist>li .dashicons{color:#10ac81;font-size:17px}.oz-cachelist>li strong{color:#0f2027;font-weight:650;margin-left:auto}.oz-cachelist>li .oz-row-label{display:flex;flex-direction:column;line-height:1.3}.oz-cachelist>li .oz-row-label small{color:#6b7a86;font-size:11.5px;opacity:.85}.oz-status{margin:0}.oz-status li{align-items:center;border-bottom:1px solid #e7ecef;display:flex;font-size:13.5px;font-weight:600;gap:10px;padding:9px 6px}.oz-status li:last-child{border-bottom:0}.oz-status li>.oz-ico{color:#6b7a86;flex:0 0 18px;height:18px;width:18px}.oz-status__name{background:transparent;border:0;color:#0f2027;cursor:pointer;flex:1;font:inherit;font-weight:600;padding:4px 0;text-align:left}.oz-status__name:hover{color:#0c8f6b}.oz-status .components-toggle-control{margin-left:auto}.oz-card,.oz-panel{background:#fff;border:1px solid #e7ecef;border-radius:16px;overflow:visible}.oz-card .oz-card__head{border-radius:16px 16px 0 0}.oz-panel__head{padding:18px 22px 0}.oz-panel__head h2{font-size:16px;font-weight:650;margin:0}.oz-panel__head p{color:#6b7a86;font-size:12.5px;margin:4px 0 0}.oz-panel__body{padding:8px 22px 20px}.oz-panel__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-panel__body .components-base-control:last-child{border-bottom:0}.oz-card{transition:box-shadow .18s ease,border-color .18s ease}.oz-card.is-enabled{border-color:rgba(29,209,161,.5);box-shadow:0 0 0 1px rgba(29,209,161,.22)}.oz-card__head{align-items:flex-start;display:flex;gap:15px;padding:20px 22px}.oz-card__icon{background:#f4f7f8;border-radius:12px;color:#6b7a86;display:grid;flex:0 0 46px;font-size:22px;height:46px;place-items:center;width:46px}.oz-card__title{flex:1;min-width:0}.oz-card__title h2{align-items:center;display:flex;font-size:16px;font-weight:650;gap:10px;margin:0 0 3px}.oz-card__title p{color:#6b7a86;font-size:12.5px;margin:0}.oz-card__body{border-top:1px solid #e7ecef;padding:4px 22px 16px}.oz-card__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-card__body .components-base-control:last-child{border-bottom:0}.is-enabled .oz-card__icon{background:linear-gradient(135deg,#1dd1a1,#10ac81);color:#fff}.oz-inline-actions{display:flex;gap:10px;padding-top:16px}.oz-dbgroup{border-top:1px solid #eef0f2;padding:18px 0}.oz-dbgroup:first-of-type{border-top:0;padding-top:4px}.oz-dbgroup__title{color:#0e1116;font-size:14px;font-weight:700;margin:0 0 4px}.oz-dbgroup__desc{color:#6b7280;font-size:13px;line-height:1.5;margin:0 0 14px}.oz-dbgroup .components-toggle-control{margin-bottom:14px}.oz-dbgroup .components-toggle-control:last-child{margin-bottom:0}.oz-dbwarn{align-items:flex-start;background:#fff5f5;border:1px solid #fcd9d9;border-radius:10px;color:#b42318;display:flex;font-size:13px;font-weight:600;gap:8px;line-height:1.5;margin:4px 0;padding:12px 14px}.oz-dbwarn .dashicons{color:#e05242;flex:0 0 auto}.oz-cachewarn{align-items:center;background:#fff8ec;border:1px solid #f4dcae;border-radius:12px;color:#7a4b00;display:flex;gap:13px;margin:0 0 18px;padding:14px 16px}.oz-cachewarn .dashicons{color:#e0900f;flex:0 0 auto;font-size:22px;height:22px;width:22px}.oz-cachewarn__text{flex:1 1 auto;min-width:0}.oz-cachewarn__text strong{color:#6b4200;display:block;font-size:14px;font-weight:700}.oz-cachewarn__text p{color:#8a6410;font-size:12.5px;line-height:1.5;margin:3px 0 0}.oz-cachewarn .oz-btn-primary{flex:0 0 auto}@media(max-width:640px){.oz-cachewarn{flex-wrap:wrap}.oz-cachewarn .oz-btn-primary{justify-content:center;width:100%}}.oz-subhead{color:#8a96a0;font-size:11px;font-weight:700;letter-spacing:.08em;margin:18px 0 2px;padding-top:14px;text-transform:uppercase}.oz-health,.oz-subhead{border-top:1px solid #e7ecf0}.oz-health{margin-top:18px;padding-top:16px}.oz-health__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.oz-health__head strong{font-size:14px}.oz-health__hint{color:#6b7280;font-size:12px;line-height:1.5;margin:4px 0 10px}.oz-health__list{display:flex;flex-direction:column;gap:6px;margin:0}.oz-health__row{align-items:center;display:flex;font-size:12.5px;gap:8px;line-height:1.4;margin:0}.oz-health__row .dashicons{flex:0 0 auto;font-size:17px;height:17px;width:17px}.oz-health__row.is-ok{color:#216e4e}.oz-health__row.is-ok .dashicons{color:#1dd1a1}.oz-health__row.is-warn{color:#8a5a00}.oz-health__row.is-warn .dashicons{color:#e0900f}.oz-health__row.is-bad{color:#b42318}.oz-health__row.is-bad .dashicons{color:#e05242}.oz-health__err{color:#b42318;font-size:12.5px}.oz-health__fix{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-top:14px}.oz-health__fix span{color:#6b7280;flex:1 1 160px;font-size:12px}.oz-health__conflict{display:flex;flex-direction:column;gap:8px;margin-top:12px}.oz-health__conflict-row{align-items:center;background:#fff5f5;border:1px solid #fcd9d9;border-radius:9px;display:flex;flex-wrap:wrap;gap:10px;padding:10px 12px}.oz-health__conflict-row .dashicons{color:#e05242;flex:0 0 auto}.oz-health__conflict-row span:not(.dashicons){color:#8a2f22;flex:1 1 200px;font-size:12.5px}.oz-health__conflict-row .oz-btn-danger.components-button{flex:0 0 auto;height:32px}.oz-health__manual{background:#fff8ec;border:1px solid #f4dcae;border-radius:9px;margin-top:12px;padding:10px 12px}.oz-health__manual p{color:#7a4b00;font-size:12px;margin:0 0 6px}.oz-health__manual code{background:#2b2f36;border-radius:6px;color:#e7f0ec;display:inline-block;font-size:12.5px;padding:6px 10px}.oz-btn-ghost.components-button{border:1px solid #d5dbe0;border-radius:8px;color:#384049;font-size:12.5px;height:30px}.oz-btn-ghost.components-button:hover:not(:disabled){border-color:#1dd1a1;color:#0b7a5c}.oz-pill{align-items:center;border-radius:999px;display:inline-flex;font-size:11px;font-weight:700;gap:5px;padding:3px 9px}.oz-pill i{background:currentColor;border-radius:50%;height:6px;width:6px}.oz-pill.is-on{background:rgba(29,209,161,.16);color:#0c8f6b}.oz-pill.is-off{background:#eef1f2;color:#6b7a86}.oz-shell .components-range-control__thumb,.oz-shell .components-range-control__track{background-color:#1dd1a1!important}.oz-fieldgroup{background:rgba(29,209,161,.035);border:1px solid #e7ecef;border-radius:12px;margin-top:18px;padding:16px 16px 4px}.oz-fieldgroup__title{color:#0e1116;font-size:13px;font-weight:700;margin:0 0 4px}.oz-fieldgroup__desc{color:#6b7280;font-size:12.5px;line-height:1.5;margin:0 0 14px}.oz-fieldgroup .components-base-control{margin-bottom:14px}.oz-shell .components-form-token-field__input-container{background:#fff;border:1px solid #e7ecef;border-radius:10px;min-height:44px;padding:5px 8px;transition:border-color .15s ease,box-shadow .15s ease}.oz-shell .components-form-token-field__input-container.is-active,.oz-shell .components-form-token-field__input-container:focus-within{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-shell .components-form-token-field__token{border-radius:8px;font-size:12.5px;margin:3px;overflow:hidden}.oz-shell .components-form-token-field__token-text{background:rgba(29,209,161,.14);border-radius:8px 0 0 8px;color:#0c8f6b;font-weight:650;line-height:1.7;padding:2px 4px 2px 11px}.oz-shell .components-form-token-field__remove-token{background:rgba(29,209,161,.14);border-radius:0 8px 8px 0;color:#0c8f6b;min-width:24px;padding:0 7px;transition:background .15s ease,color .15s ease}.oz-shell .components-form-token-field__remove-token svg{fill:currentColor}.oz-shell .components-form-token-field__remove-token:hover{background:#10ac81;color:#fff}.oz-shell .components-form-token-field__input{font-size:13.5px;margin:4px;min-height:28px}.oz-shell .components-input-control__backdrop{display:none!important}.oz-shell .components-input-control__input,.oz-shell .components-select-control__input,.oz-shell .components-text-control__input{background:#fff!important;border:1px solid #e7ecef!important;border-radius:10px!important;box-shadow:none!important;color:#0f2027;font-size:13.5px;min-height:42px;padding:0 14px}.oz-shell .components-input-control__input:hover,.oz-shell .components-select-control__input:hover,.oz-shell .components-text-control__input:hover{border-color:#bcc6cc!important}.oz-shell .components-input-control__input:focus,.oz-shell .components-select-control__input:focus,.oz-shell .components-text-control__input:focus{border-color:#1dd1a1!important;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-shell select.components-select-control__input{cursor:pointer}.oz-pillgroup .components-base-control__label{display:block;font-weight:600;margin-bottom:8px}.oz-pillgroup__row{background:#f0f2f4;border-radius:12px;display:inline-flex;flex-wrap:wrap;gap:6px;padding:4px}.oz-pillgroup .components-base-control__help{display:block;margin-top:8px}.oz-pillbtn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:9px;color:#4b5563;cursor:pointer;font-size:13px;font-weight:600;min-width:44px;padding:7px 16px;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.oz-pillbtn:hover{color:#0e1116}.oz-pillbtn.is-on{background:#fff;box-shadow:0 1px 3px rgba(4,56,44,.14);color:#0c8f6b}.oz-pillbtn:focus-visible{outline:2px solid #1dd1a1;outline-offset:1px}.oz-select{position:relative}.oz-select .components-base-control__label{display:block;margin-bottom:8px}.oz-select__btn{align-items:center;background:#fff;border:1px solid #e7ecef;border-radius:10px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;height:42px;justify-content:space-between;padding:0 14px;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.oz-select__btn .dashicons{color:#0c8f6b;transition:transform .18s ease}.oz-select__btn:hover{border-color:#bcc6cc}.oz-select__btn:focus,.oz-select__btn:focus-visible{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-select__btn[aria-expanded=true]{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-select__btn[aria-expanded=true] .dashicons{transform:rotate(180deg)}.oz-select__menu{background:#fff;border:1px solid #e7ecef;border-radius:12px;box-shadow:0 14px 34px rgba(15,32,39,.18);left:0;list-style:none;margin:0;padding:6px;position:absolute;right:0;top:calc(100% + 6px);z-index:30}.oz-select__item{align-items:center;border-radius:8px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;justify-content:space-between;padding:9px 12px}.oz-select__item .dashicons{color:#10ac81;font-size:18px}.oz-select__item.is-active,.oz-select__item:hover{background:rgba(29,209,161,.12);color:#0c8f6b}.oz-select__item.is-selected{color:#0c8f6b;font-weight:650}.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{border-radius:10px;font-weight:650;height:38px;transition:transform .16s cubic-bezier(.2,.7,.3,1),box-shadow .18s ease,background .16s ease,border-color .16s ease,color .16s ease}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:transform .22s cubic-bezier(.2,.7,.3,1)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:translateY(-1px)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:scale(1.14)}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled){transform:translateY(0) scale(.975);transition-duration:.05s}.oz-shell .oz-btn-danger.components-button:focus-visible,.oz-shell .oz-btn-ghost.components-button:focus-visible,.oz-shell .oz-btn-primary.components-button:focus-visible{outline:2px solid #1dd1a1;outline-offset:2px}.oz-shell .oz-btn-danger.components-button:disabled,.oz-shell .oz-btn-ghost.components-button:disabled,.oz-shell .oz-btn-primary.components-button:disabled{opacity:.5;transform:none}.oz-shell .oz-btn-primary.components-button{background:linear-gradient(135deg,#1dd1a1,#10ac81);border:0;box-shadow:0 1px 2px rgba(20,60,48,.12);color:#fff;padding:0 18px}.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){background:linear-gradient(135deg,#10ac81,#0c8f6b);box-shadow:0 8px 20px -7px rgba(29,209,161,.65);color:#fff}.oz-shell .oz-btn-primary.components-button.is-busy{animation:oz-btn-sheen 1.4s linear infinite;background-size:200% 100%}.oz-shell .oz-btn-ghost.components-button{background:#fff;border:1px solid rgba(29,209,161,.5);box-shadow:none;color:#0c8f6b}.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled){background:rgba(29,209,161,.08);border-color:#1dd1a1;box-shadow:0 6px 16px -8px rgba(29,209,161,.5);color:#0c8f6b}.oz-shell .oz-btn-danger.components-button{background:#fff;border:1px solid rgba(214,54,56,.5);color:#d63638}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled){background:rgba(214,54,56,.08);border-color:#d63638;box-shadow:0 6px 16px -8px rgba(214,54,56,.5);color:#b32d2e}.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg{transform:scale(1.1) rotate(90deg)}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg{transform:none}}@keyframes oz-btn-sheen{0%{background-image:linear-gradient(135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:200% 0}to{background-image:linear-gradient(135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:-200% 0}}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{transition:background .16s ease,color .16s ease,border-color .16s ease}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:none}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:none}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:none}.oz-shell .oz-btn-danger.components-button.is-busy,.oz-shell .oz-btn-ghost.components-button.is-busy,.oz-shell .oz-btn-primary.components-button.is-busy{animation:none}}.oz-shell .components-button.is-primary:focus:not(:disabled),.oz-shell .components-button:focus,.oz-shell .components-button:focus:not(:disabled){box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle,.oz-shell .components-form-toggle__input,.oz-shell .components-form-toggle__input:active,.oz-shell .components-form-toggle__input:focus,.oz-shell .components-form-toggle__input:focus-visible{border:none!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__track{background-color:#cdd6db!important;border:1px solid transparent!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle.is-checked .components-form-toggle__track{background-color:#1dd1a1!important}.oz-shell .components-form-toggle__input:focus+.components-form-toggle__track,.oz-shell .components-form-toggle__input:focus-visible+.components-form-toggle__track{box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__thumb{background-color:#fff!important;border:none!important;box-shadow:0 1px 2px rgba(15,32,39,.25)!important}.oz-savebar{align-items:center;background:#fff;border-top:1px solid #e7ecef;bottom:0;box-shadow:0 -6px 18px rgba(15,32,39,.06);display:flex;gap:16px;justify-content:flex-end;left:160px;padding:13px 32px;position:fixed;right:0;z-index:9}.oz-savebar__hint{color:#6b7a86;font-size:13px}.oz-savebar.is-dirty .oz-savebar__hint{color:#0c8f6b;font-weight:650}.folded .oz-savebar{left:36px}.oz-toast{bottom:72px;z-index:99}.oz-job,.oz-toast{position:fixed;right:24px}.oz-job{background:#fff;border:1px solid #e7ecef;border-radius:16px;bottom:84px;box-shadow:0 18px 50px rgba(15,32,39,.22);color:#0f2027;font-size:13px;max-width:calc(100vw - 48px);overflow:hidden;width:420px;z-index:100}.oz-job__head{align-items:center;display:flex;gap:12px;padding:16px 16px 12px}.oz-job__head .components-spinner{margin:0}.oz-job__icon-status{font-size:22px;height:22px;width:22px}.oz-job__icon-status--done{color:#10ac81}.oz-job__icon-status--stopped{color:#6b7a86}.oz-job__icon-status--error{color:#d63638}.oz-job__title{flex:1;min-width:0}.oz-job__title strong{display:block;font-size:15px;font-weight:700;line-height:1.2}.oz-job__title span{color:#6b7a86;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.oz-job__pct{color:#0f2027;font-size:13px;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__icon{background:transparent;border:0;border-radius:6px;color:#6b7a86;cursor:pointer;display:inline-flex;padding:4px}.oz-job__icon:hover{background:#f4f7f8;color:#0f2027}.oz-job__icon .dashicons{font-size:18px;height:18px;width:18px}.oz-job__body{padding:0 16px 16px}.oz-job__row{align-items:baseline;display:flex;gap:12px;justify-content:space-between;margin-bottom:8px}.oz-job__row span{line-height:1.4}.oz-job__row strong{flex:0 0 auto;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__bar{background:#e7ecef;border-radius:999px;height:7px;overflow:hidden}.oz-job__bar div{background:linear-gradient(90deg,#1dd1a1,#10ac81);border-radius:999px;height:100%;transition:width .35s ease}.oz-job__bar.is-indeterminate div{animation:oz-indeterminate 1.1s ease-in-out infinite;border-radius:999px;width:40%!important}.oz-job.is-min .oz-job__bar{margin:2px 16px 16px}.oz-job.is-min .oz-job__head{padding-bottom:10px}.oz-job.is-done .oz-job__bar div{background:linear-gradient(90deg,#10ac81,#0c8f6b)}.oz-job.is-stopped .oz-job__bar div{background:#c3ccd2}.oz-job.is-error .oz-job__bar div{background:linear-gradient(90deg,#f16b6b,#d63638)}.oz-job__step{color:#6b7a86;font-size:12px;margin-top:8px}.oz-job__stop{align-items:center;background:transparent;border:0;color:#d63638;cursor:pointer;display:inline-flex;font-size:13px;font-weight:650;gap:6px;margin-top:14px;padding:4px 0}.oz-job__stop .dashicons{font-size:18px}.oz-job__stop:hover{color:#b32d2e}.oz-job__log{background:#11202b;border-radius:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.7;margin-top:14px;max-height:150px;overflow-y:auto;padding:10px 12px}.oz-logline{color:#cdd8df;display:flex;gap:8px}.oz-logline__tag{color:#5fd0ad;flex:0 0 auto;font-weight:700;min-width:42px}.oz-logline--warn .oz-logline__tag{color:#f0b429}.oz-logline--error .oz-logline__tag{color:#ff6b6b}.oz-logline--done .oz-logline__tag{color:#1dd1a1}.oz-logline__text{word-break:break-word}@media(max-width:1100px){.oz-kpis,.oz-quick{grid-template-columns:1fr}}@media(max-width:1024px){.oz-score{align-items:flex-start;flex-direction:column;text-align:left}.oz-convert{align-items:stretch;flex-direction:column;gap:12px}.oz-convert .oz-btn-ghost.components-button,.oz-convert .oz-btn-primary.components-button{justify-content:center;width:100%}}.oz-mobile-toggle,.oz-nav-overlay{display:none!important}@media(max-width:1024px){.oz-shell{flex-direction:column;margin:8px 8px 110px}.oz-content{border-left:0;min-width:0;width:100%}.oz-sidebar{border-bottom:1px solid #e7ecef;border-right:0;flex:none;max-height:none;overflow:visible;padding:14px 12px;position:static;width:100%}.oz-sidebar__brand{margin-bottom:10px;padding-bottom:12px}.oz-sidebar nav{display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}.oz-navitem{border-bottom:3px solid transparent;border-left:0;border-radius:10px;flex:0 0 auto;padding:8px 12px;width:auto}.oz-navitem.is-active{border-bottom-color:#1dd1a1;border-left-color:transparent}.oz-navitem .oz-navitem__text strong{white-space:nowrap}.oz-navitem .oz-navitem__dot,.oz-navitem small,.oz-sidebar__foot{display:none}.oz-topbar{padding:16px 18px}.oz-topbar h1{font-size:18px}.oz-main{padding:16px 18px}.oz-savebar{left:0;padding:12px 18px}.oz-card__body{padding-left:18px;padding-right:18px}.oz-job{left:8px;right:8px;width:auto}}@media(max-width:560px){.oz-tiles{grid-template-columns:1fr}.oz-hero,.oz-score{padding:18px}.oz-card__head{gap:12px;padding:16px}.oz-card__icon{flex-basis:40px;height:40px;width:40px}.oz-savebar__hint{display:none}}@keyframes oz-indeterminate{0%{transform:translateX(-100%)}to{transform:translateX(350%)}}.oz-muted{color:#6b7a86;font-size:13px}.oz-reqs{list-style:none;margin:0;padding:0}.oz-req{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:grid;font-size:13.5px;gap:6px 10px;grid-template-columns:20px 1fr auto;padding:10px 6px}.oz-req:last-child{border-bottom:0}.oz-req__dot{font-size:18px;height:18px;width:18px}.oz-req__label{font-weight:650}.oz-req__value{color:#6b7a86;font-weight:600;justify-self:end}.oz-req__note{color:#6b7a86;font-size:12px;font-weight:400;grid-column:2/-1;line-height:1.45;margin-top:2px}.oz-req--ok>.oz-req__dot{color:#0c8f6b}.oz-req--warn>.oz-req__dot{color:#e0900f}.oz-req--error>.oz-req__dot{color:#e05242}.oz-req--info>.oz-req__dot,.oz-req--info>.oz-req__value{color:#6b7a86}.oz-req--warn{background:rgba(224,144,15,.06)}.oz-req--error{background:rgba(224,82,66,.06)}.oz-notice{align-items:flex-start;background:rgba(224,82,66,.08);border:1px solid rgba(224,82,66,.28);border-radius:10px;color:#7a2018;display:flex;font-size:13px;gap:10px;line-height:1.5;margin:0 0 14px;padding:12px 14px}.oz-notice .dashicons{color:#e05242;flex:0 0 20px;margin-top:1px}.oz-notice--warn{background:rgba(224,144,15,.09);border-color:rgba(224,144,15,.3);color:#7a5406}.oz-notice--warn .dashicons{color:#e0900f}.oz-notice--ok{background:#eaf8f3;border-color:#b8e6d6;color:#0f5e48}.oz-notice--ok .dashicons{color:#0e9f7e}.oz-recs{list-style:none;margin:0;padding:0}.oz-rec{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:flex;gap:12px;padding:12px 8px}.oz-rec:last-child{border-bottom:0}.oz-rec__dot{flex:0 0 20px;font-size:20px;height:20px;margin-top:1px;width:20px}.oz-rec__text{flex:1;min-width:0}.oz-rec__text strong{display:block;font-size:13.5px;font-weight:650}.oz-rec__text small{color:#6b7a86;display:block;font-size:12px;line-height:1.45;margin-top:2px}.oz-rec__btn.components-button{flex:0 0 auto}.oz-rec--high>.oz-rec__dot{color:#e05242}.oz-rec--medium>.oz-rec__dot{color:#e0900f}.oz-rec--low>.oz-rec__dot{color:#6b7a86}.oz-rec--high{background:rgba(224,82,66,.05)}.oz-rec--medium{background:rgba(224,144,15,.05)}.oz-recs-clear{align-items:center;color:#6b7a86;display:flex;font-size:13px;gap:8px}.oz-recs-clear .dashicons{color:#0c8f6b}.oz-select--compact{border-bottom:0!important;flex:none;margin:0 0 0 auto!important;padding:0!important}.oz-select--compact .oz-select__btn{background:#f7faf9;border-radius:9px;font-size:13px;font-weight:650;gap:10px;height:30px;min-width:132px;padding:0 10px 0 12px;width:auto}.oz-select--compact .oz-select__btn:disabled{cursor:progress;opacity:.6}.oz-select--compact .oz-select__btn .dashicons{font-size:16px;height:16px;width:16px}.oz-select--compact .oz-select__menu{left:auto;min-width:190px;right:0}.oz-select--compact .oz-select__item{font-size:13px;padding:8px 10px}.oz-cachewarn--ok{background:#eaf8f3;border-color:#b8e6d6;color:#0f5e48}.oz-cachewarn__text p+p{margin-top:6px}.oz-cachewarn__link{color:inherit;display:inline-block;font-weight:600;margin-top:8px;text-decoration:underline}.oz-network__sites{align-items:start;margin-top:16px}.oz-network__sites .oz-panel__head h2,.oz-network__sites .oz-panel__head p{overflow-wrap:anywhere}.oz-cachelist__link{color:#1dd1a1;font-weight:600;margin-left:auto;text-decoration:none}.oz-cachelist__link:focus,.oz-cachelist__link:hover{text-decoration:underline}
  • optimazz/tags/1.1.0/build/index.js

    r3723128 r3743505  
    1 (()=>{"use strict";const e={n:a=>{const t=a&&a.__esModule?()=>a.default:()=>a;return e.d(t,{a:t}),t},d:(a,t)=>{for(var o in t)e.o(t,o)&&!e.o(a,o)&&Object.defineProperty(a,o,{enumerable:!0,get:t[o]})},o:(e,a)=>Object.hasOwn(e,a)},a=window.React,t=window.wp.element,o=window.wp.components,n=window.wp.apiFetch;var l=e.n(n);const i=window.wp.i18n,s=window.wp.hooks,{restUrl:r,nonce:c,settings:m}=window.optimazzData||{};l().use(l().createNonceMiddleware(c));const d=[{key:"dashboard",title:(0,i.__)("Dashboard","optimazz"),sub:(0,i.__)("Overview & quick actions","optimazz"),icon:"admin-home"},{key:"cache",title:(0,i.__)("Page Cache","optimazz"),sub:(0,i.__)("Static HTML caching","optimazz"),icon:"database",mod:"cache"},{key:"files",title:(0,i.__)("File Optimization","optimazz"),sub:(0,i.__)("Optimize CSS & JS","optimazz"),icon:"editor-code"},{key:"media",title:(0,i.__)("Media","optimazz"),sub:(0,i.__)("LazyLoad, dimensions, WebP","optimazz"),icon:"format-image",mod:"media"},{key:"fonts",title:(0,i.__)("Fonts","optimazz"),sub:(0,i.__)("Preload, swap, preconnect","optimazz"),icon:"editor-textcolor",mod:"fonts"},{key:"preload",title:(0,i.__)("Preload","optimazz"),sub:(0,i.__)("Generate cache files","optimazz"),icon:"update"},{key:"advanced",title:(0,i.__)("Advanced Rules","optimazz"),sub:(0,i.__)("Fine-tune cache rules","optimazz"),icon:"list-view"},{key:"database",title:(0,i.__)("Database","optimazz"),sub:(0,i.__)("Optimize, reduce bloat","optimazz"),icon:"database-view",mod:"database"},{key:"heartbeat",title:(0,i.__)("Heartbeat","optimazz"),sub:(0,i.__)("Control Heartbeat API","optimazz"),icon:"heart",mod:"heartbeat"},{key:"tweaks",title:(0,i.__)("Tweaks","optimazz"),sub:(0,i.__)("Remove WordPress bloat","optimazz"),icon:"admin-generic",mod:"tweaks"},{key:"cdn",title:(0,i.__)("CDN","optimazz"),sub:(0,i.__)("Rewrite asset URLs","optimazz"),icon:"cloud",mod:"cdn"},{key:"tools",title:(0,i.__)("Tools","optimazz"),sub:(0,i.__)("Import, export","optimazz"),icon:"admin-tools"}],p=(0,s.applyFilters)("optimazz.adminSections",[]),_=[...d,...p],z=["cache","js","css","media","fonts","database","heartbeat","tweaks","cdn"],h=[{title:(0,i.__)("Content leftovers","optimazz"),desc:(0,i.__)("Old post data that piles up over time. Removing it is permanent, so keep anything you still need.","optimazz"),items:[{key:"revisions",label:(0,i.__)("Post revisions","optimazz"),help:(0,i.__)("Old copies of posts saved automatically. Deleting them keeps only your current content.","optimazz")},{key:"auto_drafts",label:(0,i.__)("Auto drafts","optimazz"),help:(0,i.__)("Empty drafts WordPress creates while you write. Safe to remove.","optimazz")},{key:"trashed_posts",label:(0,i.__)("Trashed posts","optimazz"),help:(0,i.__)("Posts in the trash that have not been permanently deleted yet.","optimazz")}]},{title:(0,i.__)("Comment clutter","optimazz"),desc:(0,i.__)("Spam and trashed comments you are never going to approve. Clearing them keeps the comments table light.","optimazz"),items:[{key:"spam_comments",label:(0,i.__)("Spam comments","optimazz"),help:(0,i.__)("Comments marked as spam. Removing them frees up the comments table.","optimazz")},{key:"trashed_comments",label:(0,i.__)("Trashed comments","optimazz"),help:(0,i.__)("Comments in the trash awaiting permanent deletion.","optimazz")}]},{title:(0,i.__)("Temporary data","optimazz"),desc:(0,i.__)("Short-lived cached values. They are safe to clear and are rebuilt automatically whenever a plugin needs them.","optimazz"),items:[{key:"expired_transients",label:(0,i.__)("Expired transients","optimazz"),help:(0,i.__)("Cached values that have already passed their expiry date.","optimazz")}]},{title:(0,i.__)("Table maintenance","optimazz"),desc:(0,i.__)("Defragment your tables to reclaim wasted space and trim database overhead.","optimazz"),items:[{key:"optimize_tables",label:(0,i.__)("Optimize database tables","optimazz"),help:(0,i.__)("Runs OPTIMIZE on your tables to defragment them and free wasted space.","optimazz")}]}],u=[{label:(0,i.__)("Do not limit","optimazz"),value:"default"},{label:(0,i.__)("Reduce activity","optimazz"),value:"reduce"},{label:(0,i.__)("Disable","optimazz"),value:"disable"}];function g({on:e}){return(0,a.createElement)("span",{className:"oz-pill "+(e?"is-on":"is-off")},(0,a.createElement)("i",null),e?(0,i.__)("Active","optimazz"):(0,i.__)("Off","optimazz"))}function b({icon:e,title:t,description:n,enabled:l,onToggle:i,children:s}){return(0,a.createElement)("section",{className:"oz-card "+(l?"is-enabled":"")},(0,a.createElement)("header",{className:"oz-card__head"},(0,a.createElement)("span",{className:`oz-card__icon dashicons dashicons-${e}`}),(0,a.createElement)("div",{className:"oz-card__title"},(0,a.createElement)("h2",null,t," ",(0,a.createElement)(g,{on:l})),(0,a.createElement)("p",null,n)),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:l,onChange:i})),l&&(0,a.createElement)("div",{className:"oz-card__body"},s))}function f({job:e,onStop:n,onClose:l,onToggleMin:s}){const r=(0,t.useRef)(null);if((0,t.useEffect)(()=>{r.current&&(r.current.scrollTop=r.current.scrollHeight)},[e?.logs?.length]),!e)return null;const c=e.status||(e.running?"running":"done"),m={stopped:(0,i.__)("Cancelled","optimazz"),error:(0,i.__)("Failed","optimazz")},d="running"===c||"done"===c?e.title:`${e.title} — ${m[c]}`;return(0,a.createElement)("div",{className:`oz-job is-${c} ${e.minimized?"is-min":""}`},(0,a.createElement)("div",{className:"oz-job__head"},"running"===c?(0,a.createElement)(o.Spinner,null):(0,a.createElement)("span",{className:`oz-job__icon-status oz-job__icon-status--${c} dashicons dashicons-${{done:"yes-alt",stopped:"dismiss",error:"warning"}[c]||"yes-alt"}`}),(0,a.createElement)("div",{className:"oz-job__title"},(0,a.createElement)("strong",null,d),(0,a.createElement)("span",null,e.minimized?`${e.pct}%${e.step?` · ${e.step}`:""}`:e.subtitle)),(0,a.createElement)("button",{className:"oz-job__icon","aria-label":e.minimized?(0,i.__)("Expand","optimazz"):(0,i.__)("Minimize","optimazz"),onClick:s},(0,a.createElement)("span",{className:"dashicons dashicons-"+(e.minimized?"editor-expand":"minus")})),(0,a.createElement)("button",{className:"oz-job__icon","aria-label":(0,i.__)("Close","optimazz"),onClick:l},(0,a.createElement)("span",{className:"dashicons dashicons-no-alt"}))),e.minimized?(0,a.createElement)("div",{className:"oz-job__bar "+(e.indeterminate&&e.running?"is-indeterminate":"")},(0,a.createElement)("div",{style:{width:`${e.pct}%`}})):(0,a.createElement)("div",{className:"oz-job__body"},(0,a.createElement)("div",{className:"oz-job__row"},(0,a.createElement)("span",null,e.label),(0,a.createElement)("strong",null,e.pct,"%")),(0,a.createElement)("div",{className:"oz-job__bar "+(e.indeterminate&&e.running?"is-indeterminate":"")},(0,a.createElement)("div",{style:{width:`${e.pct}%`}})),e.step&&(0,a.createElement)("div",{className:"oz-job__step"},(0,i.__)("Step","optimazz")," ",e.step),e.running&&(0,a.createElement)("button",{className:"oz-job__stop",onClick:n},(0,a.createElement)("span",{className:"dashicons dashicons-controls-pause"}),(0,i.__)("Stop","optimazz")),(0,a.createElement)("div",{className:"oz-job__log",ref:r},e.logs.map((e,t)=>{const o=e.level||"INFO";return(0,a.createElement)("div",{key:t,className:`oz-logline oz-logline--${o.toLowerCase()}`},(0,a.createElement)("span",{className:"oz-logline__tag"},o),(0,a.createElement)("span",{className:"oz-logline__text"},e.text))}))))}let E=0;function v({label:e,value:o,options:n,onChange:l,compact:i=!1,disabled:s=!1}){const[r,c]=(0,t.useState)(!1),m=Math.max(0,n.findIndex(e=>e.value===o)),[d,p]=(0,t.useState)(m),_=(0,t.useRef)(null),z=(0,t.useRef)("oz-sel-"+ ++E).current;(0,t.useEffect)(()=>{const e=e=>{_.current&&!_.current.contains(e.target)&&c(!1)};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[]);const h=n.find(e=>e.value===o)||n[0],u=e=>{l(n[e].value),c(!1)};return(0,a.createElement)("div",{className:"components-base-control oz-select"+(i?" oz-select--compact":""),ref:_},(0,a.createElement)("span",{className:i?"screen-reader-text":"components-base-control__label",id:`${z}-label`},e),(0,a.createElement)("button",{type:"button",className:"oz-select__btn",disabled:s,role:"combobox","aria-haspopup":"listbox","aria-expanded":r,"aria-labelledby":`${z}-label`,"aria-activedescendant":r?`${z}-opt-${d}`:void 0,onClick:()=>c(e=>!e),onKeyDown:e=>{r?"Escape"===e.key||"Tab"===e.key?c(!1):"ArrowDown"===e.key?(e.preventDefault(),p(e=>Math.min(n.length-1,e+1))):"ArrowUp"===e.key?(e.preventDefault(),p(e=>Math.max(0,e-1))):"Home"===e.key?(e.preventDefault(),p(0)):"End"===e.key?(e.preventDefault(),p(n.length-1)):"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),u(d)):["ArrowDown","ArrowUp","Enter"," "].includes(e.key)&&(e.preventDefault(),p(m),c(!0))}},(0,a.createElement)("span",null,h?.label),(0,a.createElement)("span",{className:"dashicons dashicons-arrow-down-alt2"})),r&&(0,a.createElement)("ul",{className:"oz-select__menu",role:"listbox","aria-labelledby":`${z}-label`},n.map((e,t)=>(0,a.createElement)("li",{key:e.value,id:`${z}-opt-${t}`,role:"option","aria-selected":e.value===o,className:`oz-select__item ${e.value===o?"is-selected":""} ${t===d?"is-active":""}`,onMouseEnter:()=>p(t),onClick:()=>u(t)},(0,a.createElement)("span",null,e.label),e.value===o&&(0,a.createElement)("span",{className:"dashicons dashicons-yes"})))))}const y={dashboard:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M3 9.6 12 3l9 6.6"}),(0,a.createElement)("path",{d:"M5 9.1V20h5v-5h4v5h5V9.1"})),cache:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M12 3 2.5 8 12 13l9.5-5L12 3Z"}),(0,a.createElement)("path",{d:"M2.5 12 12 17l9.5-5"})),files:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"m9 8-4 4 4 4"}),(0,a.createElement)("path",{d:"m15 8 4 4-4 4"})),media:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("rect",{x:"3",y:"4",width:"18",height:"16",rx:"2"}),(0,a.createElement)("circle",{cx:"8.5",cy:"9.5",r:"1.5"}),(0,a.createElement)("path",{d:"m21 16-5-5-6 6"})),fonts:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M5 6V4h14v2"}),(0,a.createElement)("path",{d:"M12 4v16"}),(0,a.createElement)("path",{d:"M9 20h6"})),preload:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M21 12a9 9 0 1 1-2.6-6.4"}),(0,a.createElement)("path",{d:"M21 4v5h-5"})),advanced:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M4 8h9"}),(0,a.createElement)("circle",{cx:"16",cy:"8",r:"2.1"}),(0,a.createElement)("path",{d:"M19 8h1"}),(0,a.createElement)("path",{d:"M4 16h1"}),(0,a.createElement)("circle",{cx:"8",cy:"16",r:"2.1"}),(0,a.createElement)("path",{d:"M11 16h9"})),database:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("ellipse",{cx:"12",cy:"5",rx:"8",ry:"3"}),(0,a.createElement)("path",{d:"M4 5v14c0 1.6 3.6 3 8 3s8-1.4 8-3V5"}),(0,a.createElement)("path",{d:"M4 12c0 1.6 3.6 3 8 3s8-1.4 8-3"})),heartbeat:(0,a.createElement)("path",{d:"M3 12h4l2 6 4-14 2 8h6"}),tweaks:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"m12 4 1.5 4.5L18 10l-4.5 1.5L12 16l-1.5-4.5L6 10l4.5-1.5Z"}),(0,a.createElement)("path",{d:"M18.5 15v3"}),(0,a.createElement)("path",{d:"M17 16.5h3"})),cdn:(0,a.createElement)("path",{d:"M17 18H7a4 4 0 0 1-.3-8A6 6 0 0 1 18.3 9 3.5 3.5 0 0 1 17 18Z"}),tools:(0,a.createElement)("path",{d:"M14.5 5.5a4 4 0 0 0-5 5L4 16v4h4l5.5-5.5a4 4 0 0 0 5-5l-2.6 2.6-2-.4-.4-2 2.5-2.6Z"})};function w({name:e,paths:t}){return(0,a.createElement)("svg",{className:"oz-ico",viewBox:"0 0 24 24",width:"20",height:"20",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",focusable:"false"},t||y[e]||y.dashboard)}function k({ok:e,warn:t,label:o}){const n=e?"ok":t?"warn":"bad",l=e?"yes-alt":t?"warning":"dismiss";return(0,a.createElement)("li",{className:`oz-health__row is-${n}`},(0,a.createElement)("span",{className:`dashicons dashicons-${l}`}),(0,a.createElement)("span",null,o))}function C({label:e,help:t,value:o,options:n,onChange:l}){return(0,a.createElement)("div",{className:"components-base-control oz-pillgroup"},e&&(0,a.createElement)("span",{className:"components-base-control__label"},e),(0,a.createElement)("div",{className:"oz-pillgroup__row",role:"radiogroup","aria-label":e,onKeyDown:e=>{const a=n.findIndex(e=>e.value===o);"ArrowRight"===e.key||"ArrowDown"===e.key?(e.preventDefault(),l(n[Math.min(n.length-1,a+1)].value)):"ArrowLeft"!==e.key&&"ArrowUp"!==e.key||(e.preventDefault(),l(n[Math.max(0,a-1)].value))}},n.map(e=>{const t=e.value===o;return(0,a.createElement)("button",{key:String(e.value),type:"button",role:"radio","aria-checked":t,tabIndex:t?0:-1,className:"oz-pillbtn "+(t?"is-on":""),onClick:()=>l(e.value)},e.label)})),t&&(0,a.createElement)("span",{className:"components-base-control__help"},t))}function N({title:e,description:t,children:o}){return(0,a.createElement)("section",{className:"oz-panel"},(0,a.createElement)("header",{className:"oz-panel__head"},(0,a.createElement)("h2",null,e),t&&(0,a.createElement)("p",null,t)),(0,a.createElement)("div",{className:"oz-panel__body"},o))}const x=document.getElementById("optimazz-app");x&&(0,t.createRoot)(x).render((0,a.createElement)(function(){var e,n,s,c,g,E,y,x;const[S,M]=(0,t.useState)(m||{}),[T,$]=(0,t.useState)(m||{}),[B,R]=(0,t.useState)(null),[P,D]=(0,t.useState)(null),[O,F]=(0,t.useState)(!1),[H,L]=(0,t.useState)("dashboard"),[A,j]=(0,t.useState)(!1),[W,I]=(0,t.useState)(null),[q,U]=(0,t.useState)(!(!window.optimazzData||!window.optimazzData.hasBackup)),J=(0,t.useRef)(null);(0,t.useEffect)(()=>{const e=e=>{const a=e&&e.detail&&e.detail.key;a&&L(a)};return window.addEventListener("optimazz:navigate",e),()=>window.removeEventListener("optimazz:navigate",e)},[]);const G=JSON.stringify(S)!==JSON.stringify(T),V=(0,t.useCallback)((e,a,t)=>M(o=>({...o,[e]:{...o[e],[a]:t}})),[]),K=(e,a)=>V(e,a,!S[e]?.[a]),Y=(0,t.useCallback)(async()=>{try{R(await l()({url:`${r}/cache/stats`}))}catch(e){}},[]),[Z,Q]=(0,t.useState)(null),X=(0,t.useCallback)(async()=>{try{Q(await l()({url:`${r}/system?_=${Date.now()}`}))}catch(e){Q({error:!0})}},[]);(0,t.useEffect)(()=>{"tools"!==H||Z||X()},[H,Z,X]);const[ee,ae]=(0,t.useState)(null),te=(0,t.useCallback)(async()=>{try{ae(await l()({url:`${r}/recommendations?_=${Date.now()}`}))}catch(e){ae({error:!0})}},[]);(0,t.useEffect)(()=>{"dashboard"!==H||ee||te()},[H,ee,te]);const oe=(0,t.useCallback)(async()=>{F(!0);try{D(await l()({url:`${r}/cache/health`}))}catch(e){D({error:!0})}F(!1)},[]),ne=(0,t.useCallback)(async(e,a)=>{if(window.confirm(`${(0,i.__)("Deactivate","optimazz")} ${a}? ${(0,i.__)("Optimazz will keep handling caching.","optimazz")}`)){F(!0);try{const t=await l()({url:`${r}/cache/deactivate-conflict`,method:"POST",data:{plugin:e}});D(t),ie(`${a} ${(0,i.__)("deactivated — Optimazz is now the only cache.","optimazz")}`,"success")}catch(e){ie((0,i.__)("Could not deactivate the plugin.","optimazz"),"error")}F(!1)}},[]),le=(0,t.useCallback)(async()=>{if(window.confirm((0,i.__)("Make Optimazz the caching plugin? This replaces the current advanced-cache.php (from another caching plugin) and turns on WP_CACHE. Deactivate the other caching plugin afterwards to avoid conflicts.","optimazz"))){F(!0);try{const e=await l()({url:`${r}/cache/takeover`,method:"POST"});D(e),ie("hit"===e.live?(0,i.__)("Optimazz is now serving pages from cache.","optimazz"):e.dropin_ours?(0,i.__)("Drop-in installed. Preload the cache, then check again.","optimazz"):(0,i.__)("Could not fully take over — see the cache health details.","optimazz"),e.dropin_ours?"success":"error")}catch(e){ie((0,i.__)("Take-over failed. Check file permissions.","optimazz"),"error")}F(!1)}},[]);(0,t.useEffect)(()=>{Y()},[Y]);const ie=(e,a="success")=>{I({text:e,status:a}),window.clearTimeout(ie._t),ie._t=window.setTimeout(()=>I(null),3e3)};(0,t.useEffect)(()=>{try{const e=new URLSearchParams(window.location.search),a=e.get("optimazz_notice"),t={purged:(0,i.__)("Cache purged.","optimazz"),preloaded:(0,i.__)("Cache preload started in the background.","optimazz"),cleared:(0,i.__)("Cache cleared; preload started in the background.","optimazz")};if(a&&t[a]){ie(t[a]),e.delete("optimazz_notice");const o=e.toString();window.history.replaceState({},"",window.location.pathname+(o?`?${o}`:""))}window.optimazzData?.wpconfigUnwritable&&ie((0,i.__)("wp-config.php is not writable — add WP_CACHE manually to enable page caching.","optimazz"),"error")}catch(e){}},[]);const se=async e=>{j(!0);try{const a=await l()({url:`${r}/settings`,method:"POST",data:e});$(a.settings),M(a.settings),U(!!a.hasBackup),ae(null),ie((0,i.__)("Settings saved.","optimazz")),Y()}catch(e){ie(e.message||(0,i.__)("Save failed.","optimazz"),"error")}j(!1)},re=async()=>{await l()({url:`${r}/cache/purge`,method:"POST"}),ie((0,i.__)("Cache purged.","optimazz")),Y()},ce=()=>ve("preload"),[me,de]=(0,t.useState)(null),[pe,_e]=(0,t.useState)(null),[ze,he]=(0,t.useState)(null),ue=(0,t.useRef)(!1),ge=(0,t.useCallback)(async()=>{try{_e(await l()({url:`${r}/media/stats`}))}catch(e){}},[]);(0,t.useEffect)(()=>{"media"!==H||pe||ge()},[H,pe,ge]);const be={convert:{title:(0,i.__)("Converting images to WebP/AVIF","optimazz"),subtitle:(0,i.__)("Next-gen format generation","optimazz"),endpoint:`${r}/media/convert`,refresh:ge,summarize:e=>`${(0,i.__)("Generated","optimazz")} ${e.generated||0} ${(0,i.__)("files in this batch","optimazz")}${e.skipped?` · ${e.skipped} ${(0,i.__)("skipped","optimazz")}`:""}`},compress:{title:(0,i.__)("Compressing images","optimazz"),subtitle:(0,i.__)("Recompress, resize & strip metadata","optimazz"),endpoint:`${r}/media/optimize`,refresh:ge,summarize:e=>{const a=Math.max(0,(e.before||0)-(e.after||0)),t=e.skipped?` · ${e.skipped} ${(0,i.__)("skipped","optimazz")}`:"";return`${(0,i.__)("Processed","optimazz")} ${e.processed||0} — ${we(a)} ${(0,i.__)("saved","optimazz")}${t}`}},preload:{title:(0,i.__)("Preloading cache","optimazz"),subtitle:(0,i.__)("Warming pages","optimazz"),endpoint:`${r}/cache/preload`,refresh:Y,onBatch:Y,summarize:(e,a)=>`${(0,i.__)("Warmed","optimazz")} ${Math.min(e.next_offset||0,e.total||0)} / ${e.total||0} ${(0,i.__)("pages","optimazz")} · ${a.cached} ${(0,i.__)("cached","optimazz")}${a.missed?` · ${a.missed} ${(0,i.__)("not cached","optimazz")}`:""}`}},fe=e=>!e||e<1?"":e>=3600?`~${Math.round(e/3600)}h`:e>=60?`~${Math.round(e/60)}m`:`~${Math.round(e)}s`,Ee=()=>l()({url:`${r}/media/job`,method:"DELETE"}).catch(()=>{}),ve=async(e,{startOffset:a=0,reconnect:t=!1}={})=>{const o=be[e];if(!o||ze?.running)return;ue.current=!1;const n=t?(0,i.__)("Resuming…","optimazz"):`${o.title}…`;he({title:o.title,subtitle:o.subtitle,pct:0,label:n,step:"",logs:[{level:"INFO",text:n}],running:!0,status:"running",minimized:!1});let s=a,r=0,c=0;const m={cached:0,missed:0,diagnosis:""},d=Date.now();try{for(;;){var p;if(ue.current){await Ee(),he(e=>e&&{...e,running:!1,status:"stopped",logs:[...e.logs,{level:"WARN",text:(0,i.__)("Stopped by user.","optimazz")}]});break}const a=s;let t;try{t=await l()({url:o.endpoint,method:"POST",data:{offset:s}})}catch(e){if(c++,c>20)throw e;if(s=a+1,he(e=>e&&{...e,logs:[...e.logs.slice(-60),{level:"WARN",text:(0,i.__)("A batch failed (likely one slow/large image); skipped it and continued.","optimazz")}]}),r&&s>=r){he(e=>e&&{...e,running:!1,status:"done",pct:100,logs:[...e.logs,{level:"DONE",text:(0,i.__)("Completed (some items skipped).","optimazz")}]});break}await new Promise(e=>setTimeout(e,600));continue}if(c=0,s=null!==(p=t.next_offset)&&void 0!==p?p:s+(t.processed||0),r=t.total||r,!t.done&&s<=a){await Ee(),he(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:(0,i.__)("Job stalled — no progress.","optimazz")}]});break}m.cached+=t.cached||0,m.missed+=t.missed||0,m.diagnosis=t.diagnosis||m.diagnosis;const n=r?Math.min(100,Math.round(s/r*100)):100,_=o.summarize(t,m),z=(Date.now()-d)/1e3,h=s>0?s/Math.max(z,.001):0,u=h>0&&r&&s<r?(r-s)/h:0,g=u?` · ${fe(u)} ${(0,i.__)("left","optimazz")}`:"";if(he(e=>e&&{...e,pct:n,label:_,step:r?`${Math.min(s,r)} / ${r}${g}`:"",logs:[...e.logs.slice(-60),...t.breakdown?[{level:"INFO",text:`${(0,i.sprintf)(/* translators: %d: number of pages to warm. */ /* translators: %d: number of pages to warm. */
    2 (0,i.__)("Found %d pages to warm:","optimazz"),t.total||0)} ${t.breakdown}.`}]:[],{level:"INFO",text:_}]}),o.onBatch&&o.onBatch(),t.unsupported){await Ee(),he(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:t.message||(0,i.__)("Image processing is unavailable on this server (no GD or ImageMagick). See Tools → Server Requirements.","optimazz")}]});break}if(t.blocked){await Ee(),he(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:(0,i.__)('Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the "Optimazz-CacheWarmer" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter.',"optimazz")}]});break}if("preload"===e&&!t.done&&0===m.cached&&m.missed>=3){await Ee(),he(e=>e&&{...e,running:!1,status:"error",label:(0,i.__)("Pages are not being cached","optimazz"),logs:[...e.logs,{level:"ERROR",text:`${(0,i.__)("Stopped: pages are loading but none are being saved to the cache.","optimazz")} ${m.diagnosis}`}]});break}if(t.done){const e=[{level:"DONE",text:(0,i.__)("Completed.","optimazz")}];m.missed&&e.unshift({level:"WARN",text:`${(0,i.sprintf)(/* translators: %d: number of pages. */ /* translators: %d: number of pages. */
    3 (0,i._n)("%d page was not cached.","%d pages were not cached.",m.missed,"optimazz"),m.missed)} ${m.diagnosis}`}),he(a=>a&&{...a,running:!1,status:"done",pct:100,logs:[...a.logs,...e]});break}}}catch(e){he(a=>a&&{...a,running:!1,status:"error",logs:[...a.logs,{level:"ERROR",text:e.message||"Error"}]})}(o.refresh||ge)()};(0,t.useEffect)(()=>{let e=!1;return(async()=>{try{const a=await l()({url:`${r}/media/job`});!e&&a&&a.running&&be[a.type]&&ve(a.type,{startOffset:a.offset||0,reconnect:!0})}catch(e){}})(),()=>{e=!0}},[]);const ye=(0,t.useCallback)(async()=>{try{de(await l()({url:`${r}/database/counts`}))}catch(e){}},[]);(0,t.useEffect)(()=>{"database"!==H||me||ye()},[H,me,ye]);const we=e=>{if(!e)return"0 KB";const a=Math.floor(Math.log(e)/Math.log(1024));return`${(e/Math.pow(1024,a)).toFixed(a?1:0)} ${["B","KB","MB","GB"][a]}`},ke=z.filter(e=>S[e]?.enabled).length,Ce=S.cache||{},Ne=S.js||{},xe=S.css||{},Se=S.media||{},Me=S.fonts||{},Te=S.database||{},$e=S.heartbeat||{},Be=S.tweaks||{},Re=S.cdn||{},Pe=_.find(e=>e.key===H),De=[{on:!!Ce.enabled,w:30,label:(0,i.__)("Page caching","optimazz")},{on:!(!Ne.enabled||!Ne.delay),w:18,label:(0,i.__)("Delay JavaScript","optimazz")},{on:!(!Ne.enabled||!Ne.defer),w:8,label:(0,i.__)("Defer JavaScript","optimazz")},{on:!(!xe.enabled||!xe.minify),w:10,label:(0,i.__)("Minify CSS","optimazz")},{on:!(!Se.enabled||!Se.lazy_load),w:12,label:(0,i.__)("Lazy-load images","optimazz")},{on:!(!Se.enabled||!Se.webp&&!Se.webp_generate),w:8,label:(0,i.__)("Next-gen images","optimazz")},{on:!(!Me.enabled||!Me.display_swap),w:6,label:(0,i.__)("Font optimization","optimazz")},{on:!!Ce.preload,w:5,label:(0,i.__)("Cache preloading","optimazz")},{on:!!$e.enabled,w:3,label:(0,i.__)("Heartbeat control","optimazz")}],Oe=De.reduce((e,a)=>e+(a.on?a.w:0),0),Fe=De.filter(e=>!e.on).sort((e,a)=>a.w-e.w)[0],He=Oe>=90?"A":Oe>=75?"B":Oe>=55?"C":Oe>=35?"D":"E",Le=2*Math.PI*52,Ae=(Ne.enabled||xe.enabled||Se.enabled)&&!Ce.enabled,je=(0,a.createElement)(a.Fragment,null,Ae&&(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-info-outline"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Turn on Page Cache for the real speed gain","optimazz")),(0,a.createElement)("p",null,(0,i.__)("File and media optimizations are on, but Page Cache is off — so the server rebuilds every page on every request. Without caching this can make a site feel slower, not faster, especially on mobile. Enable Page Cache to serve ready-made HTML.","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-primary",onClick:()=>L("cache")},(0,i.__)("Enable Page Cache","optimazz"))),(0,a.createElement)("div",{className:"oz-score"},(0,a.createElement)("div",{className:"oz-score__gauge"},(0,a.createElement)("svg",{viewBox:"0 0 120 120",width:"132",height:"132"},(0,a.createElement)("circle",{cx:"60",cy:"60",r:"52",fill:"none",stroke:"rgba(255,255,255,.25)",strokeWidth:"10"}),(0,a.createElement)("circle",{cx:"60",cy:"60",r:"52",fill:"none",stroke:"#fff",strokeWidth:"10",strokeLinecap:"round",strokeDasharray:Le,strokeDashoffset:Le-Le*Oe/100,transform:"rotate(-90 60 60)",style:{transition:"stroke-dashoffset .6s ease"}}),(0,a.createElement)("text",{x:"60",y:"56",textAnchor:"middle",fontSize:"30",fontWeight:"800",fill:"#fff"},Oe),(0,a.createElement)("text",{x:"60",y:"78",textAnchor:"middle",fontSize:"12",fill:"rgba(255,255,255,.85)"},(0,i.__)("SCORE","optimazz"))),(0,a.createElement)("span",{className:"oz-score__grade"},He)),(0,a.createElement)("div",{className:"oz-score__body"},(0,a.createElement)("h2",null,(0,i.__)("Performance score","optimazz")),(0,a.createElement)("p",null,Fe?`${(0,i.__)("Biggest remaining win:","optimazz")} ${Fe.label} (+${Fe.w})`:(0,i.__)("Every optimization is enabled. Nicely done!","optimazz")),(0,a.createElement)("div",{className:"oz-score__chips"},De.map(e=>(0,a.createElement)("span",{key:e.label,className:"oz-chip "+(e.on?"is-on":"")},(0,a.createElement)("span",{className:"dashicons dashicons-"+(e.on?"yes-alt":"marker")}),e.label))))),(0,a.createElement)("div",{className:"oz-kpis"},(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,a.createElement)("strong",null,B?null!==(e=B.pages)&&void 0!==e?e:B.files:(0,a.createElement)(o.Spinner,null)),(0,a.createElement)("span",null,(0,i.__)("Cached pages","optimazz"))),(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-chart-area"}),(0,a.createElement)("strong",null,B?we(B.bytes):(0,a.createElement)(o.Spinner,null)),(0,a.createElement)("span",null,(0,i.__)("Cache size","optimazz"))),(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,a.createElement)("strong",null,ke,"/",z.length),(0,a.createElement)("span",null,(0,i.__)("Modules active","optimazz")))),ee&&!ee.error&&(ee.items||[]).length>0&&(0,a.createElement)(N,{title:(0,i.__)("Recommendations","optimazz"),description:(0,i.__)("What to fix next to speed up your site — one click each.","optimazz")},(0,a.createElement)("ul",{className:"oz-recs"},(ee.items||[]).map(e=>(0,a.createElement)("li",{key:e.id,className:`oz-rec oz-rec--${e.severity}`},(0,a.createElement)("span",{className:"oz-rec__dot dashicons "+("high"===e.severity?"dashicons-warning":"medium"===e.severity?"dashicons-info":"dashicons-marker")}),(0,a.createElement)("span",{className:"oz-rec__text"},(0,a.createElement)("strong",null,e.title),(0,a.createElement)("small",null,e.desc)),e.fix?(0,a.createElement)(o.Button,{className:"oz-btn-primary oz-rec__btn",isBusy:A,onClick:()=>(async e=>{if(!e||!e.module||!e.key)return;const a={...S,[e.module]:{...S[e.module],[e.key]:e.value}};await se(a),ae(null)})(e.fix)},(0,i.__)("Fix it","optimazz")):e.link?(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-rec__btn",onClick:()=>L(e.link)},(0,i.__)("Open","optimazz")):null)))),ee&&!ee.error&&0===(ee.items||[]).length&&(0,a.createElement)(N,{title:(0,i.__)("Recommendations","optimazz")},(0,a.createElement)("p",{className:"oz-recs-clear"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,i.__)("Nice — everything we recommend is already on. Your site is well optimized.","optimazz"))),(0,a.createElement)("div",{className:"oz-quick"},(0,a.createElement)(N,{title:(0,i.__)("Cache Files","optimazz"),description:(0,i.__)("Clear and preload all cache files.","optimazz")},(0,a.createElement)("div",{className:"oz-quick__actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",isBusy:!!ze?.running,onClick:async()=>{await re(),ce()}},(0,i.__)("Clear & Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",onClick:ce},(0,i.__)("Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-btn--x",icon:"dismiss",onClick:re},(0,i.__)("Purge cache","optimazz"))),(0,a.createElement)("ul",{className:"oz-cachelist"},(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-media-text"}),(0,i.__)("Cached pages","optimazz"),(0,a.createElement)("strong",null,B?null!==(n=B.pages)&&void 0!==n?n:B.files:"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,i.__)("Page cache size","optimazz"),(0,a.createElement)("strong",null,B?we(B.bytes):"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-admin-appearance"}),(0,i.__)("Optimized CSS","optimazz"),(0,a.createElement)("strong",null,B?`${B.css_files} (${we(B.css_bytes)})`:"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-clock"}),(0,i.__)("Cache lifespan","optimazz"),(0,a.createElement)(v,{compact:!0,disabled:A,label:(0,i.__)("Cache lifespan","optimazz"),value:Number(null!==(s=Ce.lifespan_hours)&&void 0!==s?s:0),options:(We=[{value:0,label:(0,i.__)("Never expire","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],Ie=Number(null!==(c=Ce.lifespan_hours)&&void 0!==c?c:0),qe=e=>(0,i.sprintf)(/* translators: %d: number of hours. */ /* translators: %d: number of hours. */
    4 (0,i._n)("%d hour","%d hours",e,"optimazz"),e),We.some(e=>e.value===Ie)?We:[...We,{value:Ie,label:qe(Ie)}].sort((e,a)=>e.value-a.value)),onChange:e=>se({...S,cache:{...S.cache,lifespan_hours:e}})})),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-update"}),(0,a.createElement)("span",{className:"oz-row-label"},(0,i.__)("Auto-preload","optimazz"),(0,a.createElement)("small",null,B?B.preload&&B.next_preload?B.next_preload_overdue?(0,i.__)("next run: due now","optimazz"):`${(0,i.__)("next run in","optimazz")} ${B.next_preload}`:(0,i.__)("off","optimazz"):"")),(0,a.createElement)(v,{compact:!0,disabled:A,label:(0,i.__)("Auto-preload frequency","optimazz"),value:Ce.preload?Ce.preload_interval||"twicedaily":"off",options:[{value:"off",label:(0,i.__)("Off","optimazz")},{value:"hourly",label:(0,i.__)("Every hour","optimazz")},{value:"twicedaily",label:(0,i.__)("Twice a day","optimazz")},{value:"daily",label:(0,i.__)("Once a day","optimazz")}],onChange:e=>{se({...S,cache:{...S.cache,preload:"off"!==e,preload_interval:"off"===e?Ce.preload_interval||"twicedaily":e}})}})),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-backup"}),(0,i.__)("Last auto-preload","optimazz"),(0,a.createElement)("strong",null,B?B.last_preload?(0,i.sprintf)(/* translators: 1: time ago, e.g. "2 hours", 2: number of pages */ /* translators: 1: time ago, e.g. "2 hours", 2: number of pages */
    5 (0,i.__)("%1$s ago · %2$d pages","optimazz"),B.last_preload.ago,B.last_preload.warmed)+(B.last_preload.blocked?` · ${(0,i.__)("blocked by firewall","optimazz")}`:""):(0,i.__)("not yet","optimazz"):"—")))),(0,a.createElement)(N,{title:(0,i.__)("Module Status","optimazz"),description:(0,i.__)("Enable or disable any module right here.","optimazz")},(0,a.createElement)("ul",{className:"oz-status"},d.filter(e=>e.mod).map(e=>(0,a.createElement)("li",{key:e.key},(0,a.createElement)(w,{name:e.key}),(0,a.createElement)("button",{className:"oz-status__name",onClick:()=>L(e.key)},e.title),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:!!S[e.mod]?.enabled,onChange:()=>K(e.mod,"enabled")}))),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-editor-code"}),(0,a.createElement)("button",{className:"oz-status__name",onClick:()=>L("files")},(0,i.__)("File Optimization","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:!(!Ne.enabled&&!xe.enabled),onChange:()=>{const e=!(Ne.enabled||xe.enabled);V("js","enabled",e),V("css","enabled",e)}}))))));var We,Ie,qe;const Ue=(0,a.createElement)(b,{icon:"database",title:(0,i.__)("Page Cache","optimazz"),description:(0,i.__)("Serve static HTML instead of running PHP on every request.","optimazz"),enabled:!!Ce.enabled,onToggle:()=>K("cache","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache mobile devices separately","optimazz"),checked:!!Ce.cache_mobile,onChange:()=>K("cache","cache_mobile")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache logged-in users","optimazz"),help:(0,i.__)("Leave off unless you know what you are doing.","optimazz"),checked:!!Ce.cache_logged_in,onChange:()=>K("cache","cache_logged_in")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Browser cache headers","optimazz"),help:(0,i.__)("Add Expires / Cache-Control rules so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only (written to .htaccess); ignored on Nginx.","optimazz"),checked:!!Ce.browser_cache,onChange:()=>K("cache","browser_cache")}),(0,a.createElement)(C,{label:(0,i.__)("Cache lifetime","optimazz"),help:(0,i.__)("How long a cached page is kept before it is refreshed. Pages are always purged immediately when their content changes.","optimazz"),value:null!==(g=Ce.lifespan_hours)&&void 0!==g?g:0,options:[{value:0,label:(0,i.__)("Never","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],onChange:e=>V("cache","lifespan_hours",e)}),(0,a.createElement)("div",{className:"oz-health"},(0,a.createElement)("div",{className:"oz-health__head"},(0,a.createElement)("strong",null,(0,i.__)("Cache health","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",isBusy:O,onClick:oe},(0,i.__)("Check now","optimazz"))),(0,a.createElement)("p",{className:"oz-health__hint"},(0,i.__)("Confirms caching is actually serving pages. If it is not, the plugin rebuilds every page on each request — which is slower, not faster.","optimazz")),P&&!P.error&&(0,a.createElement)("ul",{className:"oz-health__list"},(0,a.createElement)(k,{ok:"hit"===P.live,warn:"miss"===P.live,label:"hit"===P.live?(0,i.__)("Pages ARE being served from cache (HIT)","optimazz"):"miss"===P.live?(0,i.__)("Pages are NOT being served from cache (MISS) — see below","optimazz"):(0,i.__)("Live check unavailable","optimazz")}),(0,a.createElement)(k,{ok:P.dropin_installed&&P.dropin_ours,warn:P.conflict,label:P.conflict?P.conflict_name?`${(0,i.__)("advanced-cache.php belongs to","optimazz")} ${P.conflict_name} — ${(0,i.__)("take it over below","optimazz")}`:(0,i.__)("advanced-cache.php belongs to ANOTHER plugin — take it over below","optimazz"):P.dropin_ours?(0,i.__)("advanced-cache.php drop-in installed","optimazz"):(0,i.__)("advanced-cache.php drop-in is missing","optimazz")}),(0,a.createElement)(k,{ok:P.wp_cache,label:P.wp_cache?(0,i.__)("WP_CACHE is enabled in wp-config.php","optimazz"):(0,i.__)("WP_CACHE is not enabled in wp-config.php","optimazz")}),!P.wpconfig_writable&&(0,a.createElement)(k,{warn:!0,label:(0,i.__)("wp-config.php is not writable — add the WP_CACHE line manually (below)","optimazz")}),(0,a.createElement)(k,{ok:P.warmed>0,label:`${P.warmed} ${(0,i.__)("pages warmed in cache","optimazz")}`})),P&&!P.error&&(P.conflict||!P.wp_cache&&P.wpconfig_writable||!P.dropin_ours)&&P.enabled&&(0,a.createElement)("div",{className:"oz-health__fix"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:O,onClick:le},(0,i.__)("Take over caching","optimazz")),(0,a.createElement)("span",null,(0,i.__)("Make Optimazz the caching plugin — installs its drop-in and enables WP_CACHE.","optimazz"))),P&&!P.error&&Array.isArray(P.active_conflicts)&&P.active_conflicts.length>0&&(0,a.createElement)("div",{className:"oz-health__conflict"},P.active_conflicts.map(e=>(0,a.createElement)("div",{className:"oz-health__conflict-row",key:e.file},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,`${e.name} ${(0,i.__)("is still active and may reclaim caching. Deactivate it so Optimazz keeps serving the cache.","optimazz")}`),(0,a.createElement)(o.Button,{className:"oz-btn-danger",isBusy:O,onClick:()=>ne(e.file,e.name)},`${(0,i.__)("Deactivate","optimazz")} ${e.name}`)))),P&&!P.error&&!P.wp_cache&&!P.wpconfig_writable&&(0,a.createElement)("div",{className:"oz-health__manual"},(0,a.createElement)("p",null,(0,i.__)("wp-config.php is not writable. Add this line just after the opening <?php tag:","optimazz")),(0,a.createElement)("code",null,"define( 'WP_CACHE', true );")),P&&P.error&&(0,a.createElement)("p",{className:"oz-health__err"},(0,i.__)("Could not run the check. Try reloading the page.","optimazz")))),Je=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(b,{icon:"editor-code",title:(0,i.__)("JavaScript","optimazz"),description:(0,i.__)("Defer and delay scripts to cut Total Blocking Time.","optimazz"),enabled:!!Ne.enabled,onToggle:()=>K("js","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Minify JavaScript","optimazz"),help:(0,i.__)("Strip comments and whitespace from inline scripts. Line breaks are kept for safety; test your site after enabling.","optimazz"),checked:!!Ne.minify,onChange:()=>K("js","minify")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Defer JavaScript","optimazz"),help:(0,i.__)("Load scripts without blocking page render.","optimazz"),checked:!!Ne.defer,onChange:()=>K("js","defer")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Delay JS until interaction","optimazz"),help:(0,i.__)("Run scripts only after the first user interaction.","optimazz"),checked:!!Ne.delay,onChange:()=>K("js","delay")}),Ne.delay&&(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Delay all scripts","optimazz"),help:(0,i.__)("Delay every script until interaction. jQuery, your theme, and Elementor are always kept safe. Still, on sites where content is rendered by JavaScript this can delay the visible page — test before keeping it on.","optimazz"),checked:!!Ne.delay_all,onChange:()=>K("js","delay_all")}),Ne.delay&&Ne.delay_all&&(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)('Delaying all scripts can slow the first paint on JavaScript-rendered sites (Elementor, sliders). If your PageSpeed FCP/LCP get worse, switch to "Delay selected" and delay only tracking scripts.',"optimazz"))),Ne.delay&&!Ne.delay_all&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Delay these scripts","optimazz"),help:(0,i.__)("Only scripts matching these keywords are delayed. Match by filename, handle or URL — e.g. gtag, analytics, facebook.","optimazz"),placeholder:(0,i.__)("e.g. gtag, analytics","optimazz"),value:Ne.delay_list||[],onChange:e=>V("js","delay_list",e)}),(Ne.defer||Ne.delay)&&(0,a.createElement)("div",{className:"oz-fieldgroup"},(0,a.createElement)("h4",{className:"oz-fieldgroup__title"},(0,i.__)("Exclude specific scripts","optimazz")),(0,a.createElement)("p",{className:"oz-fieldgroup__desc"},(0,i.__)("If a script breaks, exclude it from just one optimization instead of both. Match by filename, script handle, or part of the URL.","optimazz")),Ne.defer&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never defer","optimazz"),help:(0,i.__)("Keep loading these render-blocking, but still allow delay. Use for scripts that must run during page load.","optimazz"),placeholder:(0,i.__)("e.g. jquery.min.js","optimazz"),value:Ne.no_defer||[],onChange:e=>V("js","no_defer",e)}),Ne.delay&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never delay","optimazz"),help:(0,i.__)("Run these before any interaction, but still allow defer. Use for sliders, above-the-fold, or consent scripts.","optimazz"),placeholder:(0,i.__)("e.g. slider, swiper","optimazz"),value:Ne.no_delay||[],onChange:e=>V("js","no_delay",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never optimize","optimazz"),help:(0,i.__)("Skip both defer and delay — leave these scripts exactly as they are.","optimazz"),placeholder:(0,i.__)("e.g. recaptcha","optimazz"),value:Ne.exclusions||[],onChange:e=>V("js","exclusions",e)}))),(0,a.createElement)(b,{icon:"admin-appearance",title:(0,i.__)("CSS","optimazz"),description:(0,i.__)("Minify, combine, and remove render-blocking stylesheets.","optimazz"),enabled:!!xe.enabled,onToggle:()=>K("css","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Minify CSS","optimazz"),checked:!!xe.minify,onChange:()=>K("css","minify")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Combine CSS files","optimazz"),help:(0,i.__)("Merge stylesheets to reduce requests.","optimazz"),checked:!!xe.combine,onChange:()=>K("css","combine")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load CSS asynchronously","optimazz"),help:(0,i.__)("Removes render-blocking CSS. Paste your above-the-fold (critical) CSS below, or the page may briefly flash unstyled.","optimazz"),checked:!!xe.async_load,onChange:()=>K("css","async_load")}),xe.async_load&&(0,a.createElement)(a.Fragment,null,!(xe.critical_css||"").trim()&&(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("No critical CSS provided. Async loading is on, so visitors may see a flash of unstyled content until you paste critical CSS below.","optimazz"))),(0,a.createElement)(o.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Critical CSS (above-the-fold)","optimazz"),help:(0,i.__)("Paste the above-the-fold CSS to inline. Generate it with a free tool such as a critical-CSS generator. Leave empty to disable inlining.","optimazz"),value:xe.critical_css||"",onChange:e=>V("css","critical_css",e),rows:6})),(0,a.createElement)("div",{className:"oz-subhead"},(0,i.__)("Remove unused CSS","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove block-editor CSS on the front end","optimazz"),help:(0,i.__)("Dequeues WordPress block-library and global styles. Turn on only if your front end does NOT render Gutenberg blocks (e.g. a page-builder or classic theme). Big byte saving when it applies.","optimazz"),checked:!!xe.remove_block_css,onChange:()=>K("css","remove_block_css")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load WooCommerce CSS only on shop pages","optimazz"),help:(0,i.__)("Dequeues WooCommerce styles on non-shop pages (kept on cart, checkout, account, product and shop). Turn off if you show Woo products or widgets on other pages.","optimazz"),checked:!!xe.remove_woo_css,onChange:()=>K("css","remove_woo_css")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load Easy Digital Downloads CSS only on EDD pages","optimazz"),help:(0,i.__)("Dequeues EDD styles on non-EDD pages (kept on checkout, purchase-confirmation, purchase history and download archives). Turn off if you show download or purchase links elsewhere.","optimazz"),checked:!!xe.remove_edd_css,onChange:()=>K("css","remove_edd_css")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Excluded CSS files (keywords)","optimazz"),value:xe.exclusions||[],onChange:e=>V("css","exclusions",e)}))),Ge=(0,a.createElement)(b,{icon:"format-image",title:(0,i.__)("Image Optimization","optimazz"),description:(0,i.__)("Lazy-load, prevent layout shift, serve next-gen formats, and lighten media.","optimazz"),enabled:!!Se.enabled,onToggle:()=>K("media","enabled")},pe&&!pe.can_process&&(0,a.createElement)("div",{className:"oz-notice oz-notice--error"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)('This server has no GD or ImageMagick extension, so image compression and WebP/AVIF cannot run — every image will be skipped. Ask your host to enable "php-gd" (or "php-imagick"). See Tools → Server Requirements.',"optimazz"))),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load images","optimazz"),help:(0,i.__)("Load off-screen images only when scrolled into view.","optimazz"),checked:!!Se.lazy_load,onChange:()=>K("media","lazy_load")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load iframes","optimazz"),checked:!!Se.lazy_iframes,onChange:()=>K("media","lazy_iframes")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load CSS background images","optimazz"),help:(0,i.__)("Defer inline background-image elements (test with page builders).","optimazz"),checked:!!Se.lazy_bg,onChange:()=>K("media","lazy_bg")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Replace YouTube embeds with a preview","optimazz"),help:(0,i.__)("Load the video iframe only when the visitor clicks play.","optimazz"),checked:!!Se.youtube_preview,onChange:()=>K("media","youtube_preview")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Add missing image dimensions","optimazz"),help:(0,i.__)("Set width/height to prevent layout shift (CLS).","optimazz"),checked:!!Se.add_dimensions,onChange:()=>K("media","add_dimensions")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Preload the LCP image","optimazz"),help:(0,i.__)("Preload the first/hero image with high priority.","optimazz"),checked:!!Se.preload_lcp,onChange:()=>K("media","preload_lcp")}),Se.lazy_load&&(0,a.createElement)(C,{label:(0,i.__)("Keep first images eager (LCP)","optimazz"),help:(0,i.__)("Do not lazy-load the first N images, to protect LCP.","optimazz"),value:null!==(E=Se.skip_first)&&void 0!==E?E:1,onChange:e=>V("media","skip_first",e),options:[0,1,2,3,4,5].map(e=>({label:String(e),value:e}))}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Serve WebP via <picture>","optimazz"),help:(0,i.__)("Deliver WebP to supporting browsers, original as fallback.","optimazz"),checked:!!Se.webp,onChange:()=>K("media","webp")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Generate WebP/AVIF on upload","optimazz"),help:pe&&!pe.can_webp?(0,i.__)("WebP generation is not available on this server.","optimazz"):(0,i.__)("Auto-create next-gen copies for newly uploaded images.","optimazz"),checked:!!Se.webp_generate,onChange:()=>K("media","webp_generate")}),pe?.can_avif&&(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Also generate/serve AVIF","optimazz"),help:(0,i.__)("Smaller than WebP; served first when supported.","optimazz"),checked:!!Se.avif,onChange:()=>K("media","avif")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Compress & resize on upload","optimazz"),help:(0,i.__)("Recompress, cap dimensions, and strip metadata from new uploads.","optimazz"),checked:!!Se.compress,onChange:()=>K("media","compress")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Convert opaque PNG to JPEG","optimazz"),help:(0,i.__)("Re-save non-transparent PNGs as much smaller JPEGs during bulk optimization.","optimazz"),checked:!!Se.png_to_jpg,onChange:()=>K("media","png_to_jpg")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Keep a backup of originals","optimazz"),help:(0,i.__)("Copy originals to uploads/optimazz-backup before optimizing.","optimazz"),checked:!!Se.backup_originals,onChange:()=>K("media","backup_originals")}),(0,a.createElement)(o.RangeControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Image quality","optimazz"),value:null!==(y=Se.quality)&&void 0!==y?y:82,onChange:e=>V("media","quality",e),min:40,max:100,help:(0,i.__)("Used for compression and WebP/AVIF generation.","optimazz")}),(0,a.createElement)(o.RangeControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Max image width (px)","optimazz"),value:null!==(x=Se.max_width)&&void 0!==x?x:2560,onChange:e=>V("media","max_width",e),min:800,max:4e3,step:80}),(0,a.createElement)("div",{className:"oz-convert"},(0,a.createElement)("div",{className:"oz-convert__info"},(0,a.createElement)("strong",null,(0,i.__)("WebP coverage","optimazz")),pe?(0,a.createElement)("span",null,pe.converted," / ",pe.images," ",(0,i.__)("images converted","optimazz")):(0,a.createElement)("span",null,(0,i.__)("Loading…","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"images-alt2",isBusy:ze?.running,disabled:pe&&!pe.can_webp||ze?.running,onClick:()=>ve("convert")},(0,i.__)("Convert to WebP","optimazz"))),(0,a.createElement)("div",{className:"oz-convert"},(0,a.createElement)("div",{className:"oz-convert__info"},(0,a.createElement)("strong",null,(0,i.__)("Bulk compression","optimazz")),pe?(0,a.createElement)("span",null,pe.optimized," ",(0,i.__)("optimized","optimazz")," · ",we(pe.saved)," ",(0,i.__)("saved","optimazz")):(0,a.createElement)("span",null,(0,i.__)("Loading…","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"filter",isBusy:ze?.running,disabled:ze?.running,onClick:()=>ve("compress")},(0,i.__)("Compress existing images","optimazz")))),Ve=(0,a.createElement)(N,{title:(0,i.__)("Preload Cache","optimazz"),description:(0,i.__)("Warm the cache automatically so visitors never hit a cold page.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Activate preloading","optimazz"),help:(0,i.__)("Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop.","optimazz"),checked:!!Ce.preload,onChange:()=>K("cache","preload")}),(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Automatic preload","optimazz"),help:(0,i.__)("How often to re-warm the whole site. Choose “Every hour” if your content changes often or you use a short cache lifespan.","optimazz"),value:Ce.preload_interval||"twicedaily",disabled:!Ce.preload,options:[{label:(0,i.__)("Every hour","optimazz"),value:"hourly"},{label:(0,i.__)("Twice a day","optimazz"),value:"twicedaily"},{label:(0,i.__)("Once a day","optimazz"),value:"daily"}],onChange:e=>V("cache","preload_interval",e)}),B&&B.wp_cron_disabled&&(0,a.createElement)("div",{className:"oz-notice oz-notice--warn",style:{marginTop:12}},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("WP-Cron is disabled on this site (DISABLE_WP_CRON). Scheduled preloads only run if a server cron job calls wp-cron.php — ask your host to set one up (e.g. every 5 minutes).","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"update",isBusy:!!ze?.running,onClick:ce},(0,i.__)("Preload now","optimazz")))),Ke=(0,a.createElement)(N,{title:(0,i.__)("Advanced Rules","optimazz"),description:(0,i.__)("Fine-tune which requests are cached.","optimazz")},(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache these URLs (contains)","optimazz"),value:Ce.excluded_uris||[],onChange:e=>V("cache","excluded_uris",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache when these cookies are set","optimazz"),value:Ce.excluded_cookies||[],onChange:e=>V("cache","excluded_cookies",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache for these user agents","optimazz"),value:Ce.excluded_user_agents||[],onChange:e=>V("cache","excluded_user_agents",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Ignore these query arguments","optimazz"),value:Ce.ignored_query_args||[],onChange:e=>V("cache","ignored_query_args",e)})),Ye=!(!S.tools||!S.tools.safe_mode),Ze=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(N,{title:(0,i.__)("Safe Mode","optimazz"),description:(0,i.__)("If a theme or page builder looks broken after optimizing, turn this on. It pauses the riskiest optimizations — combine CSS, async CSS, remove block CSS and JavaScript delay — while keeping caching, minify, defer and lazy-load. Your saved settings are not changed; they simply pause until you turn Safe Mode off.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Pause risky optimizations (Safe Mode)","optimazz"),checked:Ye,onChange:e=>{const a={...S,tools:{...S.tools||{},safe_mode:e}};M(a),se(a)}}),Ye&&(0,a.createElement)("div",{className:"oz-notice oz-notice--warn",style:{marginTop:12}},(0,a.createElement)("span",{className:"dashicons dashicons-shield"}),(0,a.createElement)("span",null,(0,i.__)("Safe Mode is ON — combine/async CSS, remove-block-CSS and JavaScript delay are paused site-wide. Turn it off to resume full optimization.","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions",style:{marginTop:14}},(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"undo",disabled:!q,onClick:async()=>{try{const e=await l()({url:`${r}/settings/revert`,method:"POST"});if("nothing"===e.message)return U(!1),void ie((0,i.__)("Nothing to undo.","optimazz"),"error");$(e.settings),M(e.settings),U(!!e.hasBackup),ae(null),ie((0,i.__)("Reverted to the previous settings.","optimazz")),Y()}catch(e){ie(e.message||(0,i.__)("Undo failed.","optimazz"),"error")}}},(0,i.__)("Undo last change","optimazz")),(0,a.createElement)("span",{className:"oz-muted",style:{fontSize:12}},q?(0,i.__)("Restores the settings from just before your last save, reset or Safe Mode toggle.","optimazz"):(0,i.__)("Nothing to undo yet — this becomes available after your next change.","optimazz")))),(0,a.createElement)(N,{title:(0,i.__)("Server Requirements","optimazz"),description:(0,i.__)("What your host supports. Warnings here explain why a feature (like WebP/AVIF) may be unavailable or slow.","optimazz")},!Z&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Checking your server…","optimazz")),Z&&Z.error&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Could not read the server report.","optimazz")),Z&&!Z.error&&(0,a.createElement)("ul",{className:"oz-reqs"},(Z.checks||[]).map((e,t)=>(0,a.createElement)("li",{key:t,className:`oz-req oz-req--${e.status}`},(0,a.createElement)("span",{className:"oz-req__dot dashicons "+("ok"===e.status?"dashicons-yes-alt":"warn"===e.status?"dashicons-warning":"info"===e.status?"dashicons-minus":"dashicons-dismiss")}),(0,a.createElement)("span",{className:"oz-req__label"},e.label),(0,a.createElement)("span",{className:"oz-req__value"},e.value),e.note&&(0,a.createElement)("span",{className:"oz-req__note"},e.note)))),(0,a.createElement)("div",{className:"oz-inline-actions",style:{marginTop:14}},(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",isBusy:!Z,onClick:()=>{Q(null),X()}},(0,i.__)("Re-check","optimazz")))),(0,a.createElement)(N,{title:(0,i.__)("Import / Export Settings","optimazz"),description:(0,i.__)("Back up your configuration or copy it to another site.","optimazz")},(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"download",onClick:()=>{const e=new window.Blob([JSON.stringify(S,null,2)],{type:"application/json"}),a=document.createElement("a");a.href=URL.createObjectURL(e),a.download="optimazz-settings.json",a.click(),URL.revokeObjectURL(a.href)}},(0,i.__)("Export settings","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"upload",onClick:()=>J.current?.click()},(0,i.__)("Import settings","optimazz")),(0,a.createElement)("input",{ref:J,type:"file",accept:"application/json",style:{display:"none"},onChange:e=>{const a=e.target.files?.[0];if(!a)return;const t=new window.FileReader;t.onload=()=>{try{const e=JSON.parse(t.result);se(e)}catch(e){ie((0,i.__)("Invalid settings file.","optimazz"),"error")}},t.readAsText(a),e.target.value=""}}))),(0,a.createElement)(N,{title:(0,i.__)("Reset","optimazz"),description:(0,i.__)("Restore every module to its default configuration. This cannot be undone.","optimazz")},(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-danger",icon:"image-rotate",onClick:async()=>{if(window.confirm((0,i.__)('Reset all Optimazz settings to defaults? You can undo this with "Undo last change".',"optimazz")))try{const e=await l()({url:`${r}/settings/reset`,method:"POST"});$(e.settings),M(e.settings),U(!!e.hasBackup),ie((0,i.__)("Settings reset to defaults.","optimazz")),Y()}catch(e){ie(e.message||(0,i.__)("Reset failed.","optimazz"),"error")}}},(0,i.__)("Reset all settings to defaults","optimazz"))))),Qe=(0,a.createElement)(b,{icon:"editor-textcolor",title:(0,i.__)("Fonts","optimazz"),description:(0,i.__)("Swap, preconnect, and preload fonts to remove render-blocking requests.","optimazz"),enabled:!!Me.enabled,onToggle:()=>K("fonts","enabled")},(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Font loading (font-display)","optimazz"),help:(0,i.__)("Optional = no layout shift (best for CLS): the web font is skipped on the first uncached load and used once cached. Swap = text stays visible but the web font swaps in and can shift layout (higher CLS). Block = brief invisible text, then the web font.","optimazz"),value:Me.font_display||(Me.display_swap,"swap"),options:[{label:(0,i.__)("Optional — no layout shift (recommended for CLS)","optimazz"),value:"optional"},{label:(0,i.__)("Swap — always show text (may shift layout)","optimazz"),value:"swap"},{label:(0,i.__)("Block — brief hidden text, then web font","optimazz"),value:"block"}],onChange:e=>V("fonts","font_display",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Combine Google Fonts into one request","optimazz"),help:(0,i.__)("Page builders often load each Google Font family separately. This merges them into a single request.","optimazz"),checked:!!Me.combine_google,onChange:e=>V("fonts","combine_google",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load only the font weights your pages use","optimazz"),help:(0,i.__)("Drops unused weights (e.g. 100, 200, 900) and italics your pages never use — often cutting font CSS by more than half. Weights 400 and 700 are always kept. Turn off if some text looks thinner or bolder than intended.","optimazz"),checked:!!Me.trim_weights,disabled:!Me.combine_google,onChange:e=>V("fonts","trim_weights",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Preload these font files (URLs)","optimazz"),value:Me.preload||[],onChange:e=>V("fonts","preload",e)})),Xe=e=>{var a;if(!me||void 0===me[e.key])return e.help;const t=null!==(a=me[e.key])&&void 0!==a?a:0;
    6 /* translators: %d: number of items found in the database. */return`${(0,i.sprintf)((0,i._n)("%d item in your database.","%d items in your database.",t,"optimazz"),t)} ${e.help}`},ea=(0,a.createElement)(N,{title:(0,i.__)("Database Optimization","optimazz"),description:(0,i.__)("Reduce bloat and reclaim space. Once a cleanup runs it cannot be undone, so back up your database first.","optimazz")},h.map(e=>(0,a.createElement)("div",{key:e.title,className:"oz-dbgroup"},(0,a.createElement)("h3",{className:"oz-dbgroup__title"},e.title),(0,a.createElement)("p",{className:"oz-dbgroup__desc"},e.desc),e.items.map(e=>{var t;return(0,a.createElement)(o.ToggleControl,{key:e.key,__nextHasNoMarginBottom:!0,label:me?`${e.label} (${null!==(t=me[e.key])&&void 0!==t?t:0})`:e.label,help:Xe(e),checked:!!Te[e.key],onChange:()=>K("database",e.key)})}))),(0,a.createElement)("div",{className:"oz-dbgroup"},(0,a.createElement)("h3",{className:"oz-dbgroup__title"},(0,i.__)("Set it and forget it","optimazz")),(0,a.createElement)("p",{className:"oz-dbgroup__desc"},(0,i.__)("Let Optimazz repeat the cleanups you picked above on a schedule, so your database stays lean without you lifting a finger.","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Schedule automatic cleanup","optimazz"),help:(0,i.__)("Uses WP-Cron to repeat the cleanups you enabled above.","optimazz"),checked:!!Te.enabled,onChange:()=>K("database","enabled")}),Te.enabled&&(0,a.createElement)(C,{label:(0,i.__)("Cleanup frequency","optimazz"),value:Te.schedule||"off",onChange:e=>V("database","schedule",e),options:[{label:(0,i.__)("Off","optimazz"),value:"off"},{label:(0,i.__)("Daily","optimazz"),value:"daily"},{label:(0,i.__)("Weekly","optimazz"),value:"weekly"},{label:(0,i.__)("Monthly","optimazz"),value:"monthly"}]})),(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("Back up your database before you run a cleanup — a database optimization cannot be undone.","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",isBusy:!!ze?.running,onClick:async()=>{if(ze?.running)return;const e=(0,i.__)("Cleaning and optimizing database…","optimazz");he({title:(0,i.__)("Optimizing database","optimazz"),subtitle:(0,i.__)("Cleanup & table optimization","optimazz"),pct:100,indeterminate:!0,label:e,step:"",logs:[{level:"INFO",text:e}],running:!0,status:"running",minimized:!1});try{const e=await l()({url:`${r}/database/optimize`,method:"POST",data:S.database||{}});de(e.counts);const a=Object.values(e.removed||{}).reduce((e,a)=>e+a,0),t=`${(0,i.__)("Cleaned","optimazz")} ${a} ${(0,i.__)("rows,","optimazz")} ${e.tables} ${(0,i.__)("tables optimized.","optimazz")}`;he(e=>e&&{...e,running:!1,status:"done",indeterminate:!1,pct:100,label:t,logs:[...e.logs,{level:"INFO",text:t},{level:"DONE",text:(0,i.__)("Completed.","optimazz")}]}),ie(t)}catch(e){he(a=>a&&{...a,running:!1,status:"error",indeterminate:!1,logs:[...a.logs,{level:"ERROR",text:e.message||"Error"}]}),ie(e.message||(0,i.__)("Optimize failed.","optimazz"),"error")}}},(0,i.__)("Optimize now","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",onClick:ye},(0,i.__)("Refresh counts","optimazz")))),aa={dashboard:je,cache:Ue,files:Je,media:Ge,fonts:Qe,preload:Ve,advanced:Ke,database:ea,heartbeat:(0,a.createElement)(N,{title:(0,i.__)("Heartbeat Control","optimazz"),description:(0,i.__)("Reduce or disable the WordPress Heartbeat API to lower server load.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Enable Heartbeat control","optimazz"),checked:!!$e.enabled,onChange:()=>K("heartbeat","enabled")}),$e.enabled&&(0,a.createElement)(a.Fragment,null,(0,a.createElement)(C,{label:(0,i.__)("Behavior in admin dashboard","optimazz"),value:$e.backend||"default",options:u,onChange:e=>V("heartbeat","backend",e)}),(0,a.createElement)(C,{label:(0,i.__)("Behavior in post editor","optimazz"),value:$e.editor||"default",options:u,onChange:e=>V("heartbeat","editor",e)}),(0,a.createElement)(C,{label:(0,i.__)("Behavior on front-end","optimazz"),value:$e.frontend||"default",options:u,onChange:e=>V("heartbeat","frontend",e)}))),tweaks:(0,a.createElement)(b,{icon:"admin-generic",title:(0,i.__)("Tweaks","optimazz"),description:(0,i.__)("Strip unnecessary WordPress front-end weight.","optimazz"),enabled:!!Be.enabled,onToggle:()=>K("tweaks","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable emojis","optimazz"),help:(0,i.__)("Remove the emoji detection script and styles.","optimazz"),checked:!!Be.disable_emojis,onChange:()=>K("tweaks","disable_emojis")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable embeds (oEmbed)","optimazz"),checked:!!Be.disable_embeds,onChange:()=>K("tweaks","disable_embeds")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable dashicons on front-end","optimazz"),help:(0,i.__)("Only for anonymous visitors.","optimazz"),checked:!!Be.disable_dashicons,onChange:()=>K("tweaks","disable_dashicons")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove query strings from assets","optimazz"),help:(0,i.__)("Strip ?ver= for better proxy caching.","optimazz"),checked:!!Be.remove_query_strings,onChange:()=>K("tweaks","remove_query_strings")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable XML-RPC","optimazz"),checked:!!Be.disable_xmlrpc,onChange:()=>K("tweaks","disable_xmlrpc")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable self-pingbacks","optimazz"),checked:!!Be.disable_pingback,onChange:()=>K("tweaks","disable_pingback")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove head meta (generator, RSD, shortlink)","optimazz"),checked:!!Be.remove_meta,onChange:()=>K("tweaks","remove_meta")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("DNS-prefetch domains","optimazz"),value:Be.dns_prefetch||[],onChange:e=>V("tweaks","dns_prefetch",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Preconnect origins","optimazz"),value:Be.preconnect||[],onChange:e=>V("tweaks","preconnect",e)})),cdn:(0,a.createElement)(b,{icon:"cloud",title:(0,i.__)("Content Delivery Network","optimazz"),description:(0,i.__)("Rewrite static asset URLs to your CDN hostname.","optimazz"),enabled:!!Re.enabled,onToggle:()=>K("cdn","enabled")},(0,a.createElement)(o.TextControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("CDN URL (CNAME)","optimazz"),placeholder:"https://cdn.example.com",value:Re.url||"",onChange:e=>V("cdn","url",e),help:(0,i.__)("CSS, JS, image, and font URLs will be served from here.","optimazz")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Exclude paths containing","optimazz"),value:Re.exclusions||[],onChange:e=>V("cdn","exclusions",e)})),tools:Ze};return p.forEach(e=>{aa[e.key]="function"==typeof e.render?e.render():e.render}),(0,a.createElement)("div",{className:"oz-shell"},(0,a.createElement)("aside",{className:"oz-sidebar"},(0,a.createElement)("div",{className:"oz-sidebar__brand"},window.optimazzData?.logoUrl?(0,a.createElement)("img",{className:"oz-logo-img",src:window.optimazzData.logoUrl,alt:"Optimazz"}):(0,a.createElement)("div",{className:"oz-sidebar__brand-fallback"},(0,a.createElement)("span",{className:"oz-logo dashicons dashicons-performance"}),(0,a.createElement)("strong",null,"Optimazz"))),(0,a.createElement)("nav",null,_.map(e=>(0,a.createElement)("button",{key:e.key,className:"oz-navitem "+(H===e.key?"is-active":""),onClick:()=>L(e.key)},(0,a.createElement)(w,{name:e.key,paths:e.iconSvg}),(0,a.createElement)("span",{className:"oz-navitem__text"},(0,a.createElement)("strong",null,e.title),(0,a.createElement)("small",null,e.sub)),e.mod&&S[e.mod]?.enabled&&(0,a.createElement)("i",{className:"oz-navitem__dot"})))),(0,a.createElement)("div",{className:"oz-sidebar__foot"},"v",window.optimazzData?.version)),(0,a.createElement)("div",{className:"oz-content"},(0,a.createElement)("header",{className:"oz-topbar"},(0,a.createElement)("span",{className:`oz-topbar__icon dashicons dashicons-${Pe.icon}`}),(0,a.createElement)("div",null,(0,a.createElement)("h1",null,Pe.title),(0,a.createElement)("p",null,Pe.sub))),(0,a.createElement)("main",{className:"oz-main"},aa[H])),(0,a.createElement)("div",{className:"oz-savebar "+(G?"is-dirty":"")},(0,a.createElement)("span",{className:"oz-savebar__hint"},G?(0,i.__)("You have unsaved changes","optimazz"):(0,i.__)("All changes saved","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:A,disabled:!G,onClick:()=>se(S)},(0,i.__)("Save changes","optimazz"))),W&&(0,a.createElement)("div",{className:"oz-toast"},(0,a.createElement)(o.Snackbar,{status:W.status,onRemove:()=>I(null)},W.text)),(0,a.createElement)(f,{job:ze,onStop:()=>{ue.current=!0},onClose:()=>{ue.current=!0,he(null)},onToggleMin:()=>he(e=>e&&{...e,minimized:!e.minimized})}))},null))})();
     1(()=>{"use strict";const e={n:a=>{const t=a&&a.__esModule?()=>a.default:()=>a;return e.d(t,{a:t}),t},d:(a,t)=>{for(var o in t)e.o(t,o)&&!e.o(a,o)&&Object.defineProperty(a,o,{enumerable:!0,get:t[o]})},o:(e,a)=>Object.hasOwn(e,a)},a=window.React,t=window.wp.element,o=window.wp.components,n=window.wp.apiFetch;var l=e.n(n);const i=window.wp.i18n,s=window.wp.hooks,{restUrl:r,nonce:c,settings:m}=window.optimazzData||{};l().use(l().createNonceMiddleware(c));const d=[{key:"dashboard",title:(0,i.__)("Dashboard","optimazz"),sub:(0,i.__)("Overview & quick actions","optimazz"),icon:"admin-home"},{key:"cache",title:(0,i.__)("Page Cache","optimazz"),sub:(0,i.__)("Static HTML caching","optimazz"),icon:"database",mod:"cache"},{key:"files",title:(0,i.__)("File Optimization","optimazz"),sub:(0,i.__)("Optimize CSS & JS","optimazz"),icon:"editor-code"},{key:"media",title:(0,i.__)("Media","optimazz"),sub:(0,i.__)("LazyLoad, dimensions, WebP","optimazz"),icon:"format-image",mod:"media"},{key:"fonts",title:(0,i.__)("Fonts","optimazz"),sub:(0,i.__)("Preload, swap, preconnect","optimazz"),icon:"editor-textcolor",mod:"fonts"},{key:"preload",title:(0,i.__)("Preload","optimazz"),sub:(0,i.__)("Generate cache files","optimazz"),icon:"update"},{key:"advanced",title:(0,i.__)("Advanced Rules","optimazz"),sub:(0,i.__)("Fine-tune cache rules","optimazz"),icon:"list-view"},{key:"database",title:(0,i.__)("Database","optimazz"),sub:(0,i.__)("Optimize, reduce bloat","optimazz"),icon:"database-view",mod:"database"},{key:"heartbeat",title:(0,i.__)("Heartbeat","optimazz"),sub:(0,i.__)("Control Heartbeat API","optimazz"),icon:"heart",mod:"heartbeat"},{key:"tweaks",title:(0,i.__)("Tweaks","optimazz"),sub:(0,i.__)("Remove WordPress bloat","optimazz"),icon:"admin-generic",mod:"tweaks"},{key:"cdn",title:(0,i.__)("CDN","optimazz"),sub:(0,i.__)("Rewrite asset URLs","optimazz"),icon:"cloud",mod:"cdn"},{key:"tools",title:(0,i.__)("Tools","optimazz"),sub:(0,i.__)("Import, export","optimazz"),icon:"admin-tools"}],p=(0,s.applyFilters)("optimazz.adminSections",[]),_=[...d,...p],z=e=>![!1,"","0",0,void 0,null].includes(e),h=![!1,"","0",0].includes(window.optimazzData?.serverFeatures),u=window.optimazzData?.networkCache||null,g=!h&&!!u&&z(u.enabled),b=h||g,f=z(window.optimazzData?.isSuperAdmin),E=window.optimazzData?.networkAdminUrl||"",v=e=>{if(!e)return"0 KB";const a=Math.floor(Math.log(e)/Math.log(1024));return`${(e/Math.pow(1024,a)).toFixed(a?1:0)} ${["B","KB","MB","GB"][a]}`},y=e=>({hourly:(0,i.__)("every hour","optimazz"),twicedaily:(0,i.__)("twice a day","optimazz"),daily:(0,i.__)("once a day","optimazz")}[e]||(0,i.__)("twice a day","optimazz"));function w(){return f&&E?(0,a.createElement)("a",{className:"oz-cachewarn__link",href:E},(0,i.__)("Open Network Admin → Optimazz","optimazz")):null}function k(){return(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-info-outline"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Page caching is off for this network","optimazz")),(0,a.createElement)("p",null,f?(0,i.__)("Turn it on for every site in Network Admin → Optimazz. Each site gets its own cache, and keeps its own exclusions here under Advanced Rules.","optimazz"):(0,i.__)("Your network administrator can turn it on for every site in Network Admin → Optimazz. All other optimizations — CSS, JavaScript, images, fonts and more — work normally on this site.","optimazz")),(0,a.createElement)(w,null)))}function C(){const e=u||{};return(0,a.createElement)("div",{className:"oz-cachewarn oz-cachewarn--ok",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Page caching is on — managed for the whole network","optimazz")),(0,a.createElement)("p",null,(0,i.sprintf)(/* translators: 1: yes/no, 2: cache lifetime, 3: preload frequency. */ /* translators: 1: yes/no, 2: cache lifetime, 3: preload frequency. */
     2(0,i.__)("Separate mobile cache: %1$s · Cache lifetime: %2$s · Automatic preload: %3$s.","optimazz"),z(e.cache_mobile)?(0,i.__)("yes","optimazz"):(0,i.__)("no","optimazz"),(t=e.lifespan_hours,null!==(o={0:(0,i.__)("never expires","optimazz"),12:(0,i.__)("12 hours","optimazz"),24:(0,i.__)("1 day","optimazz"),168:(0,i.__)("1 week","optimazz"),720:(0,i.__)("1 month","optimazz")}[Number(t)])&&void 0!==o?o:(0,i.sprintf)((0,i._n)("%d hour","%d hours",Number(t),"optimazz"),Number(t))),z(e.preload)?y(e.preload_interval):(0,i.__)("off","optimazz"))),(0,a.createElement)("p",null,(0,i.__)("This site has its own cache. Purge or preload it from the Dashboard or the Preload screen, and set its own exclusions under Advanced Rules.","optimazz")),(0,a.createElement)(w,null)));var t,o}const N=["cache","js","css","media","fonts","database","heartbeat","tweaks","cdn"],x=[{title:(0,i.__)("Content leftovers","optimazz"),desc:(0,i.__)("Old post data that piles up over time. Removing it is permanent, so keep anything you still need.","optimazz"),items:[{key:"revisions",label:(0,i.__)("Post revisions","optimazz"),help:(0,i.__)("Old copies of posts saved automatically. Deleting them keeps only your current content.","optimazz")},{key:"auto_drafts",label:(0,i.__)("Auto drafts","optimazz"),help:(0,i.__)("Empty drafts WordPress creates while you write. Safe to remove.","optimazz")},{key:"trashed_posts",label:(0,i.__)("Trashed posts","optimazz"),help:(0,i.__)("Posts in the trash that have not been permanently deleted yet.","optimazz")}]},{title:(0,i.__)("Comment clutter","optimazz"),desc:(0,i.__)("Spam and trashed comments you are never going to approve. Clearing them keeps the comments table light.","optimazz"),items:[{key:"spam_comments",label:(0,i.__)("Spam comments","optimazz"),help:(0,i.__)("Comments marked as spam. Removing them frees up the comments table.","optimazz")},{key:"trashed_comments",label:(0,i.__)("Trashed comments","optimazz"),help:(0,i.__)("Comments in the trash awaiting permanent deletion.","optimazz")}]},{title:(0,i.__)("Temporary data","optimazz"),desc:(0,i.__)("Short-lived cached values. They are safe to clear and are rebuilt automatically whenever a plugin needs them.","optimazz"),items:[{key:"expired_transients",label:(0,i.__)("Expired transients","optimazz"),help:(0,i.__)("Cached values that have already passed their expiry date.","optimazz")}]},{title:(0,i.__)("Table maintenance","optimazz"),desc:(0,i.__)("Defragment your tables to reclaim wasted space and trim database overhead.","optimazz"),items:[{key:"optimize_tables",label:(0,i.__)("Optimize database tables","optimazz"),help:(0,i.__)("Runs OPTIMIZE on your tables to defragment them and free wasted space.","optimazz")}]}],S=[{label:(0,i.__)("Do not limit","optimazz"),value:"default"},{label:(0,i.__)("Reduce activity","optimazz"),value:"reduce"},{label:(0,i.__)("Disable","optimazz"),value:"disable"}];function $({on:e}){return(0,a.createElement)("span",{className:"oz-pill "+(e?"is-on":"is-off")},(0,a.createElement)("i",null),e?(0,i.__)("Active","optimazz"):(0,i.__)("Off","optimazz"))}function T({icon:e,title:t,description:n,enabled:l,onToggle:i,children:s}){return(0,a.createElement)("section",{className:"oz-card "+(l?"is-enabled":"")},(0,a.createElement)("header",{className:"oz-card__head"},(0,a.createElement)("span",{className:`oz-card__icon dashicons dashicons-${e}`}),(0,a.createElement)("div",{className:"oz-card__title"},(0,a.createElement)("h2",null,t," ",(0,a.createElement)($,{on:l})),(0,a.createElement)("p",null,n)),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:l,onChange:i})),l&&(0,a.createElement)("div",{className:"oz-card__body"},s))}function M({job:e,onStop:n,onClose:l,onToggleMin:s}){const r=(0,t.useRef)(null);if((0,t.useEffect)(()=>{r.current&&(r.current.scrollTop=r.current.scrollHeight)},[e?.logs?.length]),!e)return null;const c=e.status||(e.running?"running":"done"),m={stopped:(0,i.__)("Cancelled","optimazz"),error:(0,i.__)("Failed","optimazz")},d="running"===c||"done"===c?e.title:`${e.title} — ${m[c]}`;return(0,a.createElement)("div",{className:`oz-job is-${c} ${e.minimized?"is-min":""}`},(0,a.createElement)("div",{className:"oz-job__head"},"running"===c?(0,a.createElement)(o.Spinner,null):(0,a.createElement)("span",{className:`oz-job__icon-status oz-job__icon-status--${c} dashicons dashicons-${{done:"yes-alt",stopped:"dismiss",error:"warning"}[c]||"yes-alt"}`}),(0,a.createElement)("div",{className:"oz-job__title"},(0,a.createElement)("strong",null,d),(0,a.createElement)("span",null,e.minimized?`${e.pct}%${e.step?` · ${e.step}`:""}`:e.subtitle)),(0,a.createElement)("button",{className:"oz-job__icon","aria-label":e.minimized?(0,i.__)("Expand","optimazz"):(0,i.__)("Minimize","optimazz"),onClick:s},(0,a.createElement)("span",{className:"dashicons dashicons-"+(e.minimized?"editor-expand":"minus")})),(0,a.createElement)("button",{className:"oz-job__icon","aria-label":(0,i.__)("Close","optimazz"),onClick:l},(0,a.createElement)("span",{className:"dashicons dashicons-no-alt"}))),e.minimized?(0,a.createElement)("div",{className:"oz-job__bar "+(e.indeterminate&&e.running?"is-indeterminate":"")},(0,a.createElement)("div",{style:{width:`${e.pct}%`}})):(0,a.createElement)("div",{className:"oz-job__body"},(0,a.createElement)("div",{className:"oz-job__row"},(0,a.createElement)("span",null,e.label),(0,a.createElement)("strong",null,e.pct,"%")),(0,a.createElement)("div",{className:"oz-job__bar "+(e.indeterminate&&e.running?"is-indeterminate":"")},(0,a.createElement)("div",{style:{width:`${e.pct}%`}})),e.step&&(0,a.createElement)("div",{className:"oz-job__step"},(0,i.__)("Step","optimazz")," ",e.step),e.running&&(0,a.createElement)("button",{className:"oz-job__stop",onClick:n},(0,a.createElement)("span",{className:"dashicons dashicons-controls-pause"}),(0,i.__)("Stop","optimazz")),(0,a.createElement)("div",{className:"oz-job__log",ref:r},e.logs.map((e,t)=>{const o=e.level||"INFO";return(0,a.createElement)("div",{key:t,className:`oz-logline oz-logline--${o.toLowerCase()}`},(0,a.createElement)("span",{className:"oz-logline__tag"},o),(0,a.createElement)("span",{className:"oz-logline__text"},e.text))}))))}let B=0;function P({label:e,value:o,options:n,onChange:l,compact:i=!1,disabled:s=!1}){const[r,c]=(0,t.useState)(!1),m=Math.max(0,n.findIndex(e=>e.value===o)),[d,p]=(0,t.useState)(m),_=(0,t.useRef)(null),z=(0,t.useRef)("oz-sel-"+ ++B).current;(0,t.useEffect)(()=>{const e=e=>{_.current&&!_.current.contains(e.target)&&c(!1)};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[]);const h=n.find(e=>e.value===o)||n[0],u=e=>{l(n[e].value),c(!1)};return(0,a.createElement)("div",{className:"components-base-control oz-select"+(i?" oz-select--compact":""),ref:_},(0,a.createElement)("span",{className:i?"screen-reader-text":"components-base-control__label",id:`${z}-label`},e),(0,a.createElement)("button",{type:"button",className:"oz-select__btn",disabled:s,role:"combobox","aria-haspopup":"listbox","aria-expanded":r,"aria-labelledby":`${z}-label`,"aria-activedescendant":r?`${z}-opt-${d}`:void 0,onClick:()=>c(e=>!e),onKeyDown:e=>{r?"Escape"===e.key||"Tab"===e.key?c(!1):"ArrowDown"===e.key?(e.preventDefault(),p(e=>Math.min(n.length-1,e+1))):"ArrowUp"===e.key?(e.preventDefault(),p(e=>Math.max(0,e-1))):"Home"===e.key?(e.preventDefault(),p(0)):"End"===e.key?(e.preventDefault(),p(n.length-1)):"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),u(d)):["ArrowDown","ArrowUp","Enter"," "].includes(e.key)&&(e.preventDefault(),p(m),c(!0))}},(0,a.createElement)("span",null,h?.label),(0,a.createElement)("span",{className:"dashicons dashicons-arrow-down-alt2"})),r&&(0,a.createElement)("ul",{className:"oz-select__menu",role:"listbox","aria-labelledby":`${z}-label`},n.map((e,t)=>(0,a.createElement)("li",{key:e.value,id:`${z}-opt-${t}`,role:"option","aria-selected":e.value===o,className:`oz-select__item ${e.value===o?"is-selected":""} ${t===d?"is-active":""}`,onMouseEnter:()=>p(t),onClick:()=>u(t)},(0,a.createElement)("span",null,e.label),e.value===o&&(0,a.createElement)("span",{className:"dashicons dashicons-yes"})))))}const O={dashboard:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M3 9.6 12 3l9 6.6"}),(0,a.createElement)("path",{d:"M5 9.1V20h5v-5h4v5h5V9.1"})),cache:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M12 3 2.5 8 12 13l9.5-5L12 3Z"}),(0,a.createElement)("path",{d:"M2.5 12 12 17l9.5-5"})),files:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"m9 8-4 4 4 4"}),(0,a.createElement)("path",{d:"m15 8 4 4-4 4"})),media:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("rect",{x:"3",y:"4",width:"18",height:"16",rx:"2"}),(0,a.createElement)("circle",{cx:"8.5",cy:"9.5",r:"1.5"}),(0,a.createElement)("path",{d:"m21 16-5-5-6 6"})),fonts:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M5 6V4h14v2"}),(0,a.createElement)("path",{d:"M12 4v16"}),(0,a.createElement)("path",{d:"M9 20h6"})),preload:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M21 12a9 9 0 1 1-2.6-6.4"}),(0,a.createElement)("path",{d:"M21 4v5h-5"})),advanced:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M4 8h9"}),(0,a.createElement)("circle",{cx:"16",cy:"8",r:"2.1"}),(0,a.createElement)("path",{d:"M19 8h1"}),(0,a.createElement)("path",{d:"M4 16h1"}),(0,a.createElement)("circle",{cx:"8",cy:"16",r:"2.1"}),(0,a.createElement)("path",{d:"M11 16h9"})),database:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("ellipse",{cx:"12",cy:"5",rx:"8",ry:"3"}),(0,a.createElement)("path",{d:"M4 5v14c0 1.6 3.6 3 8 3s8-1.4 8-3V5"}),(0,a.createElement)("path",{d:"M4 12c0 1.6 3.6 3 8 3s8-1.4 8-3"})),heartbeat:(0,a.createElement)("path",{d:"M3 12h4l2 6 4-14 2 8h6"}),tweaks:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"m12 4 1.5 4.5L18 10l-4.5 1.5L12 16l-1.5-4.5L6 10l4.5-1.5Z"}),(0,a.createElement)("path",{d:"M18.5 15v3"}),(0,a.createElement)("path",{d:"M17 16.5h3"})),cdn:(0,a.createElement)("path",{d:"M17 18H7a4 4 0 0 1-.3-8A6 6 0 0 1 18.3 9 3.5 3.5 0 0 1 17 18Z"}),tools:(0,a.createElement)("path",{d:"M14.5 5.5a4 4 0 0 0-5 5L4 16v4h4l5.5-5.5a4 4 0 0 0 5-5l-2.6 2.6-2-.4-.4-2 2.5-2.6Z"})};function R({name:e,paths:t}){return(0,a.createElement)("svg",{className:"oz-ico",viewBox:"0 0 24 24",width:"20",height:"20",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",focusable:"false"},t||O[e]||O.dashboard)}function D({ok:e,warn:t,label:o}){const n=e?"ok":t?"warn":"bad",l=e?"yes-alt":t?"warning":"dismiss";return(0,a.createElement)("li",{className:`oz-health__row is-${n}`},(0,a.createElement)("span",{className:`dashicons dashicons-${l}`}),(0,a.createElement)("span",null,o))}function F({label:e,help:t,value:o,options:n,onChange:l}){return(0,a.createElement)("div",{className:"components-base-control oz-pillgroup"},e&&(0,a.createElement)("span",{className:"components-base-control__label"},e),(0,a.createElement)("div",{className:"oz-pillgroup__row",role:"radiogroup","aria-label":e,onKeyDown:e=>{const a=n.findIndex(e=>e.value===o);"ArrowRight"===e.key||"ArrowDown"===e.key?(e.preventDefault(),l(n[Math.min(n.length-1,a+1)].value)):"ArrowLeft"!==e.key&&"ArrowUp"!==e.key||(e.preventDefault(),l(n[Math.max(0,a-1)].value))}},n.map(e=>{const t=e.value===o;return(0,a.createElement)("button",{key:String(e.value),type:"button",role:"radio","aria-checked":t,tabIndex:t?0:-1,className:"oz-pillbtn "+(t?"is-on":""),onClick:()=>l(e.value)},e.label)})),t&&(0,a.createElement)("span",{className:"components-base-control__help"},t))}function H({title:e,description:t,children:o}){return(0,a.createElement)("section",{className:"oz-panel"},(0,a.createElement)("header",{className:"oz-panel__head"},(0,a.createElement)("h2",null,e),t&&(0,a.createElement)("p",null,t)),(0,a.createElement)("div",{className:"oz-panel__body"},o))}function A({className:e="",items:t,nav:n,onNav:l,foot:s,topIcon:r,title:c,sub:m,dirty:d,saving:p,onSave:_,toast:z,onToastRemove:h,children:u,after:g}){return(0,a.createElement)("div",{className:`oz-shell ${e}`},(0,a.createElement)("aside",{className:"oz-sidebar"},(0,a.createElement)("div",{className:"oz-sidebar__brand"},window.optimazzData?.logoUrl?(0,a.createElement)("img",{className:"oz-logo-img",src:window.optimazzData.logoUrl,alt:"Optimazz"}):(0,a.createElement)("div",{className:"oz-sidebar__brand-fallback"},(0,a.createElement)("span",{className:"oz-logo dashicons dashicons-performance"}),(0,a.createElement)("strong",null,"Optimazz"))),(0,a.createElement)("nav",null,t.map(e=>(0,a.createElement)("button",{key:e.key,className:"oz-navitem "+(n===e.key?"is-active":""),onClick:()=>l(e.key)},(0,a.createElement)(R,{name:e.svg||e.key,paths:e.iconSvg}),(0,a.createElement)("span",{className:"oz-navitem__text"},(0,a.createElement)("strong",null,e.title),(0,a.createElement)("small",null,e.sub)),e.dot&&(0,a.createElement)("i",{className:"oz-navitem__dot"})))),(0,a.createElement)("div",{className:"oz-sidebar__foot"},s)),(0,a.createElement)("div",{className:"oz-content"},(0,a.createElement)("header",{className:"oz-topbar"},(0,a.createElement)("span",{className:`oz-topbar__icon dashicons dashicons-${r}`}),(0,a.createElement)("div",null,(0,a.createElement)("h1",null,c),(0,a.createElement)("p",null,m))),(0,a.createElement)("main",{className:"oz-main"},u)),(0,a.createElement)("div",{className:"oz-savebar "+(d?"is-dirty":"")},(0,a.createElement)("span",{className:"oz-savebar__hint"},d?(0,i.__)("You have unsaved changes","optimazz"):(0,i.__)("All changes saved","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:p,disabled:!d||p,onClick:_},(0,i.__)("Save changes","optimazz"))),z&&(0,a.createElement)("div",{className:"oz-toast"},(0,a.createElement)(o.Snackbar,{status:z.status,onRemove:h},z.text)),g)}const L=document.getElementById("optimazz-app");L&&(0,t.createRoot)(L).render((0,a.createElement)(function(){var e,n,s,c,f,E,w,$;const[B,O]=(0,t.useState)(m||{}),[L,W]=(0,t.useState)(m||{}),[j,I]=(0,t.useState)(null),[q,U]=(0,t.useState)(null),[J,G]=(0,t.useState)(!1),[V,K]=(0,t.useState)("dashboard"),[Y,Z]=(0,t.useState)(!1),[Q,X]=(0,t.useState)(null),[ee,ae]=(0,t.useState)(!(!window.optimazzData||!window.optimazzData.hasBackup)),te=(0,t.useRef)(null);(0,t.useEffect)(()=>{const e=e=>{const a=e&&e.detail&&e.detail.key;a&&K(a)};return window.addEventListener("optimazz:navigate",e),()=>window.removeEventListener("optimazz:navigate",e)},[]);const oe=JSON.stringify(B)!==JSON.stringify(L),ne=(0,t.useCallback)((e,a,t)=>O(o=>({...o,[e]:{...o[e],[a]:t}})),[]),le=(e,a)=>ne(e,a,!B[e]?.[a]),ie=(0,t.useCallback)(async()=>{try{I(await l()({url:`${r}/cache/stats`}))}catch(e){}},[]),[se,re]=(0,t.useState)(null),ce=(0,t.useCallback)(async()=>{try{re(await l()({url:`${r}/system?_=${Date.now()}`}))}catch(e){re({error:!0})}},[]);(0,t.useEffect)(()=>{"tools"!==V||se||ce()},[V,se,ce]);const[me,de]=(0,t.useState)(null),pe=(0,t.useCallback)(async()=>{try{de(await l()({url:`${r}/recommendations?_=${Date.now()}`}))}catch(e){de({error:!0})}},[]);(0,t.useEffect)(()=>{"dashboard"!==V||me||pe()},[V,me,pe]);const _e=(0,t.useCallback)(async()=>{G(!0);try{U(await l()({url:`${r}/cache/health`}))}catch(e){U({error:!0})}G(!1)},[]),ze=(0,t.useCallback)(async(e,a)=>{if(window.confirm(`${(0,i.__)("Deactivate","optimazz")} ${a}? ${(0,i.__)("Optimazz will keep handling caching.","optimazz")}`)){G(!0);try{const t=await l()({url:`${r}/cache/deactivate-conflict`,method:"POST",data:{plugin:e}});U(t),ue(`${a} ${(0,i.__)("deactivated — Optimazz is now the only cache.","optimazz")}`,"success")}catch(e){ue((0,i.__)("Could not deactivate the plugin.","optimazz"),"error")}G(!1)}},[]),he=(0,t.useCallback)(async()=>{if(window.confirm((0,i.__)("Make Optimazz the caching plugin? This replaces the current advanced-cache.php (from another caching plugin) and turns on WP_CACHE. Deactivate the other caching plugin afterwards to avoid conflicts.","optimazz"))){G(!0);try{const e=await l()({url:`${r}/cache/takeover`,method:"POST"});U(e),ue("hit"===e.live?(0,i.__)("Optimazz is now serving pages from cache.","optimazz"):e.dropin_ours?(0,i.__)("Drop-in installed. Preload the cache, then check again.","optimazz"):(0,i.__)("Could not fully take over — see the cache health details.","optimazz"),e.dropin_ours?"success":"error")}catch(e){ue((0,i.__)("Take-over failed. Check file permissions.","optimazz"),"error")}G(!1)}},[]);(0,t.useEffect)(()=>{ie()},[ie]);const ue=(e,a="success")=>{X({text:e,status:a}),window.clearTimeout(ue._t),ue._t=window.setTimeout(()=>X(null),3e3)};(0,t.useEffect)(()=>{try{const e=new URLSearchParams(window.location.search),a=e.get("optimazz_notice"),t={purged:(0,i.__)("Cache purged.","optimazz"),preloaded:(0,i.__)("Cache preload started in the background.","optimazz"),cleared:(0,i.__)("Cache cleared; preload started in the background.","optimazz")};if(a&&t[a]){ue(t[a]),e.delete("optimazz_notice");const o=e.toString();window.history.replaceState({},"",window.location.pathname+(o?`?${o}`:""))}window.optimazzData?.wpconfigUnwritable&&ue((0,i.__)("wp-config.php is not writable — add WP_CACHE manually to enable page caching.","optimazz"),"error")}catch(e){}},[]);const ge=async e=>{Z(!0);try{const a=await l()({url:`${r}/settings`,method:"POST",data:e});W(a.settings),O(a.settings),ae(!!a.hasBackup),de(null),ue((0,i.__)("Settings saved.","optimazz")),ie()}catch(e){ue(e.message||(0,i.__)("Save failed.","optimazz"),"error")}Z(!1)},be=async()=>{await l()({url:`${r}/cache/purge`,method:"POST"}),ue((0,i.__)("Cache purged.","optimazz")),ie()},fe=()=>Me("preload"),[Ee,ve]=(0,t.useState)(null),[ye,we]=(0,t.useState)(null),[ke,Ce]=(0,t.useState)(null),Ne=(0,t.useRef)(!1),xe=(0,t.useCallback)(async()=>{try{we(await l()({url:`${r}/media/stats`}))}catch(e){}},[]);(0,t.useEffect)(()=>{"media"!==V||ye||xe()},[V,ye,xe]);const Se={convert:{title:(0,i.__)("Converting images to WebP/AVIF","optimazz"),subtitle:(0,i.__)("Next-gen format generation","optimazz"),endpoint:`${r}/media/convert`,refresh:xe,summarize:e=>`${(0,i.__)("Generated","optimazz")} ${e.generated||0} ${(0,i.__)("files in this batch","optimazz")}${e.skipped?` · ${e.skipped} ${(0,i.__)("skipped","optimazz")}`:""}`},compress:{title:(0,i.__)("Compressing images","optimazz"),subtitle:(0,i.__)("Recompress, resize & strip metadata","optimazz"),endpoint:`${r}/media/optimize`,refresh:xe,summarize:e=>{const a=Math.max(0,(e.before||0)-(e.after||0)),t=e.skipped?` · ${e.skipped} ${(0,i.__)("skipped","optimazz")}`:"";return`${(0,i.__)("Processed","optimazz")} ${e.processed||0} — ${Pe(a)} ${(0,i.__)("saved","optimazz")}${t}`}},preload:{title:(0,i.__)("Preloading cache","optimazz"),subtitle:(0,i.__)("Warming pages","optimazz"),endpoint:`${r}/cache/preload`,refresh:ie,onBatch:ie,summarize:(e,a)=>`${(0,i.__)("Warmed","optimazz")} ${Math.min(e.next_offset||0,e.total||0)} / ${e.total||0} ${(0,i.__)("pages","optimazz")} · ${a.cached} ${(0,i.__)("cached","optimazz")}${a.missed?` · ${a.missed} ${(0,i.__)("not cached","optimazz")}`:""}`}},$e=e=>!e||e<1?"":e>=3600?`~${Math.round(e/3600)}h`:e>=60?`~${Math.round(e/60)}m`:`~${Math.round(e)}s`,Te=()=>l()({url:`${r}/media/job`,method:"DELETE"}).catch(()=>{}),Me=async(e,{startOffset:a=0,reconnect:t=!1}={})=>{const o=Se[e];if(!o||ke?.running)return;Ne.current=!1;const n=t?(0,i.__)("Resuming…","optimazz"):`${o.title}…`;Ce({title:o.title,subtitle:o.subtitle,pct:0,label:n,step:"",logs:[{level:"INFO",text:n}],running:!0,status:"running",minimized:!1});let s=a,r=0,c=0;const m={cached:0,missed:0,diagnosis:""},d=Date.now();try{for(;;){var p;if(Ne.current){await Te(),Ce(e=>e&&{...e,running:!1,status:"stopped",logs:[...e.logs,{level:"WARN",text:(0,i.__)("Stopped by user.","optimazz")}]});break}const a=s;let t;try{t=await l()({url:o.endpoint,method:"POST",data:{offset:s}})}catch(e){if(c++,c>20)throw e;if(s=a+1,Ce(e=>e&&{...e,logs:[...e.logs.slice(-60),{level:"WARN",text:(0,i.__)("A batch failed (likely one slow/large image); skipped it and continued.","optimazz")}]}),r&&s>=r){Ce(e=>e&&{...e,running:!1,status:"done",pct:100,logs:[...e.logs,{level:"DONE",text:(0,i.__)("Completed (some items skipped).","optimazz")}]});break}await new Promise(e=>setTimeout(e,600));continue}if(c=0,s=null!==(p=t.next_offset)&&void 0!==p?p:s+(t.processed||0),r=t.total||r,!t.done&&s<=a){await Te(),Ce(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:(0,i.__)("Job stalled — no progress.","optimazz")}]});break}m.cached+=t.cached||0,m.missed+=t.missed||0,m.diagnosis=t.diagnosis||m.diagnosis;const n=r?Math.min(100,Math.round(s/r*100)):100,_=o.summarize(t,m),z=(Date.now()-d)/1e3,h=s>0?s/Math.max(z,.001):0,u=h>0&&r&&s<r?(r-s)/h:0,g=u?` · ${$e(u)} ${(0,i.__)("left","optimazz")}`:"";if(Ce(e=>e&&{...e,pct:n,label:_,step:r?`${Math.min(s,r)} / ${r}${g}`:"",logs:[...e.logs.slice(-60),...t.breakdown?[{level:"INFO",text:`${(0,i.sprintf)(/* translators: %d: number of pages to warm. */ /* translators: %d: number of pages to warm. */
     3(0,i.__)("Found %d pages to warm:","optimazz"),t.total||0)} ${t.breakdown}.`}]:[],{level:"INFO",text:_}]}),o.onBatch&&o.onBatch(),t.unsupported){await Te(),Ce(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:t.message||(0,i.__)("Image processing is unavailable on this server (no GD or ImageMagick). See Tools → Server Requirements.","optimazz")}]});break}if(t.blocked){await Te(),Ce(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:(0,i.__)('Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the "Optimazz-CacheWarmer" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter.',"optimazz")}]});break}if("preload"===e&&!t.done&&0===m.cached&&m.missed>=3){await Te(),Ce(e=>e&&{...e,running:!1,status:"error",label:(0,i.__)("Pages are not being cached","optimazz"),logs:[...e.logs,{level:"ERROR",text:`${(0,i.__)("Stopped: pages are loading but none are being saved to the cache.","optimazz")} ${m.diagnosis}`}]});break}if(t.done){const e=[{level:"DONE",text:(0,i.__)("Completed.","optimazz")}];m.missed&&e.unshift({level:"WARN",text:`${(0,i.sprintf)(/* translators: %d: number of pages. */ /* translators: %d: number of pages. */
     4(0,i._n)("%d page was not cached.","%d pages were not cached.",m.missed,"optimazz"),m.missed)} ${m.diagnosis}`}),Ce(a=>a&&{...a,running:!1,status:"done",pct:100,logs:[...a.logs,...e]});break}}}catch(e){Ce(a=>a&&{...a,running:!1,status:"error",logs:[...a.logs,{level:"ERROR",text:e.message||"Error"}]})}(o.refresh||xe)()};(0,t.useEffect)(()=>{let e=!1;return(async()=>{try{const a=await l()({url:`${r}/media/job`});!e&&a&&a.running&&Se[a.type]&&Me(a.type,{startOffset:a.offset||0,reconnect:!0})}catch(e){}})(),()=>{e=!0}},[]);const Be=(0,t.useCallback)(async()=>{try{ve(await l()({url:`${r}/database/counts`}))}catch(e){}},[]);(0,t.useEffect)(()=>{"database"!==V||Ee||Be()},[V,Ee,Be]);const Pe=v,Oe=h?N:N.filter(e=>"cache"!==e),Re=Oe.filter(e=>B[e]?.enabled).length,De=B.cache||{},Fe=B.js||{},He=B.css||{},Ae=B.media||{},Le=B.fonts||{},We=B.database||{},je=B.heartbeat||{},Ie=B.tweaks||{},qe=B.cdn||{},Ue=_.find(e=>e.key===V),Je=[{on:!!De.enabled,w:30,label:(0,i.__)("Page caching","optimazz"),server:!0},{on:!(!Fe.enabled||!Fe.delay),w:18,label:(0,i.__)("Delay JavaScript","optimazz")},{on:!(!Fe.enabled||!Fe.defer),w:8,label:(0,i.__)("Defer JavaScript","optimazz")},{on:!(!He.enabled||!He.minify),w:10,label:(0,i.__)("Minify CSS","optimazz")},{on:!(!Ae.enabled||!Ae.lazy_load),w:12,label:(0,i.__)("Lazy-load images","optimazz")},{on:!(!Ae.enabled||!Ae.webp&&!Ae.webp_generate),w:8,label:(0,i.__)("Next-gen images","optimazz")},{on:!(!Le.enabled||!Le.display_swap),w:6,label:(0,i.__)("Font optimization","optimazz")},{on:!!De.preload,w:5,label:(0,i.__)("Cache preloading","optimazz"),server:!0},{on:!!je.enabled,w:3,label:(0,i.__)("Heartbeat control","optimazz")}],Ge=b?Je:Je.filter(e=>!0!==e.server),Ve=Ge.reduce((e,a)=>e+a.w,0)||1,Ke=Math.round(Ge.reduce((e,a)=>e+(a.on?a.w:0),0)/Ve*100),Ye=Ge.filter(e=>!e.on).sort((e,a)=>a.w-e.w)[0],Ze=Ke>=90?"A":Ke>=75?"B":Ke>=55?"C":Ke>=35?"D":"E",Qe=2*Math.PI*52,Xe=h&&(Fe.enabled||He.enabled||Ae.enabled)&&!De.enabled,ea=(0,a.createElement)(a.Fragment,null,Xe&&(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-info-outline"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Turn on Page Cache for the real speed gain","optimazz")),(0,a.createElement)("p",null,(0,i.__)("File and media optimizations are on, but Page Cache is off — so the server rebuilds every page on every request. Without caching this can make a site feel slower, not faster, especially on mobile. Enable Page Cache to serve ready-made HTML.","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-primary",onClick:()=>K("cache")},(0,i.__)("Enable Page Cache","optimazz"))),(0,a.createElement)("div",{className:"oz-score"},(0,a.createElement)("div",{className:"oz-score__gauge"},(0,a.createElement)("svg",{viewBox:"0 0 120 120",width:"132",height:"132"},(0,a.createElement)("circle",{cx:"60",cy:"60",r:"52",fill:"none",stroke:"rgba(255,255,255,.25)",strokeWidth:"10"}),(0,a.createElement)("circle",{cx:"60",cy:"60",r:"52",fill:"none",stroke:"#fff",strokeWidth:"10",strokeLinecap:"round",strokeDasharray:Qe,strokeDashoffset:Qe-Qe*Ke/100,transform:"rotate(-90 60 60)",style:{transition:"stroke-dashoffset .6s ease"}}),(0,a.createElement)("text",{x:"60",y:"56",textAnchor:"middle",fontSize:"30",fontWeight:"800",fill:"#fff"},Ke),(0,a.createElement)("text",{x:"60",y:"78",textAnchor:"middle",fontSize:"12",fill:"rgba(255,255,255,.85)"},(0,i.__)("SCORE","optimazz"))),(0,a.createElement)("span",{className:"oz-score__grade"},Ze)),(0,a.createElement)("div",{className:"oz-score__body"},(0,a.createElement)("h2",null,(0,i.__)("Performance score","optimazz")),(0,a.createElement)("p",null,Ye?`${(0,i.__)("Biggest remaining win:","optimazz")} ${Ye.label} (+${Ye.w})`:(0,i.__)("Every optimization is enabled. Nicely done!","optimazz")),(0,a.createElement)("div",{className:"oz-score__chips"},Ge.map(e=>(0,a.createElement)("span",{key:e.label,className:"oz-chip "+(e.on?"is-on":"")},(0,a.createElement)("span",{className:"dashicons dashicons-"+(e.on?"yes-alt":"marker")}),e.label))))),(0,a.createElement)("div",{className:"oz-kpis"+(b?"":" oz-kpis--single")},b&&(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,a.createElement)("strong",null,j?null!==(e=j.pages)&&void 0!==e?e:j.files:(0,a.createElement)(o.Spinner,null)),(0,a.createElement)("span",null,(0,i.__)("Cached pages","optimazz"))),b&&(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-chart-area"}),(0,a.createElement)("strong",null,j?Pe(j.bytes):(0,a.createElement)(o.Spinner,null)),(0,a.createElement)("span",null,(0,i.__)("Cache size","optimazz"))),(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,a.createElement)("strong",null,Re,"/",Oe.length),(0,a.createElement)("span",null,(0,i.__)("Modules active","optimazz")))),me&&!me.error&&(me.items||[]).length>0&&(0,a.createElement)(H,{title:(0,i.__)("Recommendations","optimazz"),description:(0,i.__)("What to fix next to speed up your site — one click each.","optimazz")},(0,a.createElement)("ul",{className:"oz-recs"},(me.items||[]).map(e=>(0,a.createElement)("li",{key:e.id,className:`oz-rec oz-rec--${e.severity}`},(0,a.createElement)("span",{className:"oz-rec__dot dashicons "+("high"===e.severity?"dashicons-warning":"medium"===e.severity?"dashicons-info":"dashicons-marker")}),(0,a.createElement)("span",{className:"oz-rec__text"},(0,a.createElement)("strong",null,e.title),(0,a.createElement)("small",null,e.desc)),e.fix?(0,a.createElement)(o.Button,{className:"oz-btn-primary oz-rec__btn",isBusy:Y,onClick:()=>(async e=>{if(!e||!e.module||!e.key)return;const a={...B,[e.module]:{...B[e.module],[e.key]:e.value}};await ge(a),de(null)})(e.fix)},(0,i.__)("Fix it","optimazz")):e.link?(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-rec__btn",onClick:()=>K(e.link)},(0,i.__)("Open","optimazz")):null)))),me&&!me.error&&0===(me.items||[]).length&&(0,a.createElement)(H,{title:(0,i.__)("Recommendations","optimazz")},(0,a.createElement)("p",{className:"oz-recs-clear"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,i.__)("Nice — everything we recommend is already on. Your site is well optimized.","optimazz"))),(0,a.createElement)("div",{className:"oz-quick"},b&&(0,a.createElement)(H,{title:(0,i.__)("Cache Files","optimazz"),description:(0,i.__)("Clear and preload all cache files.","optimazz")},(0,a.createElement)("div",{className:"oz-quick__actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",isBusy:!!ke?.running,onClick:async()=>{await be(),fe()}},(0,i.__)("Clear & Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",onClick:fe},(0,i.__)("Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-btn--x",icon:"dismiss",onClick:be},(0,i.__)("Purge cache","optimazz"))),(0,a.createElement)("ul",{className:"oz-cachelist"},(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-media-text"}),(0,i.__)("Cached pages","optimazz"),(0,a.createElement)("strong",null,j?null!==(n=j.pages)&&void 0!==n?n:j.files:"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,i.__)("Page cache size","optimazz"),(0,a.createElement)("strong",null,j?Pe(j.bytes):"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-admin-appearance"}),(0,i.__)("Optimized CSS","optimazz"),(0,a.createElement)("strong",null,j?`${j.css_files} (${Pe(j.css_bytes)})`:"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-clock"}),(0,i.__)("Cache lifespan","optimazz"),(0,a.createElement)(P,{compact:!0,disabled:Y,label:(0,i.__)("Cache lifespan","optimazz"),value:Number(null!==(s=De.lifespan_hours)&&void 0!==s?s:0),options:(aa=[{value:0,label:(0,i.__)("Never expire","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],ta=Number(null!==(c=De.lifespan_hours)&&void 0!==c?c:0),oa=e=>(0,i.sprintf)(/* translators: %d: number of hours. */ /* translators: %d: number of hours. */
     5(0,i._n)("%d hour","%d hours",e,"optimazz"),e),aa.some(e=>e.value===ta)?aa:[...aa,{value:ta,label:oa(ta)}].sort((e,a)=>e.value-a.value)),onChange:e=>ge({...B,cache:{...B.cache,lifespan_hours:e}})})),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-update"}),(0,a.createElement)("span",{className:"oz-row-label"},(0,i.__)("Auto-preload","optimazz"),(0,a.createElement)("small",null,j?j.preload&&j.next_preload?j.next_preload_overdue?(0,i.__)("next run: due now","optimazz"):`${(0,i.__)("next run in","optimazz")} ${j.next_preload}`:(0,i.__)("off","optimazz"):"")),(0,a.createElement)(P,{compact:!0,disabled:Y,label:(0,i.__)("Auto-preload frequency","optimazz"),value:De.preload?De.preload_interval||"twicedaily":"off",options:[{value:"off",label:(0,i.__)("Off","optimazz")},{value:"hourly",label:(0,i.__)("Every hour","optimazz")},{value:"twicedaily",label:(0,i.__)("Twice a day","optimazz")},{value:"daily",label:(0,i.__)("Once a day","optimazz")}],onChange:e=>{ge({...B,cache:{...B.cache,preload:"off"!==e,preload_interval:"off"===e?De.preload_interval||"twicedaily":e}})}})),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-backup"}),(0,i.__)("Last auto-preload","optimazz"),(0,a.createElement)("strong",null,j?j.last_preload?(0,i.sprintf)(/* translators: 1: time ago, e.g. "2 hours", 2: number of pages */ /* translators: 1: time ago, e.g. "2 hours", 2: number of pages */
     6(0,i.__)("%1$s ago · %2$d pages","optimazz"),j.last_preload.ago,j.last_preload.warmed)+(j.last_preload.blocked?` · ${(0,i.__)("blocked by firewall","optimazz")}`:""):(0,i.__)("not yet","optimazz"):"—")))),(0,a.createElement)(H,{title:(0,i.__)("Module Status","optimazz"),description:(0,i.__)("Enable or disable any module right here.","optimazz")},(0,a.createElement)("ul",{className:"oz-status"},d.filter(e=>e.mod&&(h||"cache"!==e.mod)).map(e=>(0,a.createElement)("li",{key:e.key},(0,a.createElement)(R,{name:e.key}),(0,a.createElement)("button",{className:"oz-status__name",onClick:()=>K(e.key)},e.title),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:!!B[e.mod]?.enabled,onChange:()=>le(e.mod,"enabled")}))),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-editor-code"}),(0,a.createElement)("button",{className:"oz-status__name",onClick:()=>K("files")},(0,i.__)("File Optimization","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:!(!Fe.enabled&&!He.enabled),onChange:()=>{const e=!(Fe.enabled||He.enabled);ne("js","enabled",e),ne("css","enabled",e)}}))))));var aa,ta,oa;const na=h?(0,a.createElement)(T,{icon:"database",title:(0,i.__)("Page Cache","optimazz"),description:(0,i.__)("Serve static HTML instead of running PHP on every request.","optimazz"),enabled:!!De.enabled,onToggle:()=>le("cache","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache mobile devices separately","optimazz"),checked:!!De.cache_mobile,onChange:()=>le("cache","cache_mobile")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache logged-in users","optimazz"),help:(0,i.__)("Leave off unless you know what you are doing.","optimazz"),checked:!!De.cache_logged_in,onChange:()=>le("cache","cache_logged_in")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Browser cache headers","optimazz"),help:(0,i.__)("Add Expires / Cache-Control rules so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only (written to .htaccess); ignored on Nginx.","optimazz"),checked:!!De.browser_cache,onChange:()=>le("cache","browser_cache")}),(0,a.createElement)(F,{label:(0,i.__)("Cache lifetime","optimazz"),help:(0,i.__)("How long a cached page is kept before it is refreshed. Pages are always purged immediately when their content changes.","optimazz"),value:null!==(f=De.lifespan_hours)&&void 0!==f?f:0,options:[{value:0,label:(0,i.__)("Never","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],onChange:e=>ne("cache","lifespan_hours",e)}),(0,a.createElement)("div",{className:"oz-health"},(0,a.createElement)("div",{className:"oz-health__head"},(0,a.createElement)("strong",null,(0,i.__)("Cache health","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",isBusy:J,onClick:_e},(0,i.__)("Check now","optimazz"))),(0,a.createElement)("p",{className:"oz-health__hint"},(0,i.__)("Confirms caching is actually serving pages. If it is not, the plugin rebuilds every page on each request — which is slower, not faster.","optimazz")),q&&!q.error&&(0,a.createElement)("ul",{className:"oz-health__list"},(0,a.createElement)(D,{ok:"hit"===q.live,warn:"miss"===q.live,label:"hit"===q.live?(0,i.__)("Pages ARE being served from cache (HIT)","optimazz"):"miss"===q.live?(0,i.__)("Pages are NOT being served from cache (MISS) — see below","optimazz"):(0,i.__)("Live check unavailable","optimazz")}),(0,a.createElement)(D,{ok:q.dropin_installed&&q.dropin_ours,warn:q.conflict,label:q.conflict?q.conflict_name?`${(0,i.__)("advanced-cache.php belongs to","optimazz")} ${q.conflict_name} — ${(0,i.__)("take it over below","optimazz")}`:(0,i.__)("advanced-cache.php belongs to ANOTHER plugin — take it over below","optimazz"):q.dropin_ours?(0,i.__)("advanced-cache.php drop-in installed","optimazz"):(0,i.__)("advanced-cache.php drop-in is missing","optimazz")}),(0,a.createElement)(D,{ok:q.wp_cache,label:q.wp_cache?(0,i.__)("WP_CACHE is enabled in wp-config.php","optimazz"):(0,i.__)("WP_CACHE is not enabled in wp-config.php","optimazz")}),!q.wpconfig_writable&&(0,a.createElement)(D,{warn:!0,label:(0,i.__)("wp-config.php is not writable — add the WP_CACHE line manually (below)","optimazz")}),(0,a.createElement)(D,{ok:q.warmed>0,label:`${q.warmed} ${(0,i.__)("pages warmed in cache","optimazz")}`})),q&&!q.error&&(q.conflict||!q.wp_cache&&q.wpconfig_writable||!q.dropin_ours)&&q.enabled&&(0,a.createElement)("div",{className:"oz-health__fix"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:J,onClick:he},(0,i.__)("Take over caching","optimazz")),(0,a.createElement)("span",null,(0,i.__)("Make Optimazz the caching plugin — installs its drop-in and enables WP_CACHE.","optimazz"))),q&&!q.error&&Array.isArray(q.active_conflicts)&&q.active_conflicts.length>0&&(0,a.createElement)("div",{className:"oz-health__conflict"},q.active_conflicts.map(e=>(0,a.createElement)("div",{className:"oz-health__conflict-row",key:e.file},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,`${e.name} ${(0,i.__)("is still active and may reclaim caching. Deactivate it so Optimazz keeps serving the cache.","optimazz")}`),(0,a.createElement)(o.Button,{className:"oz-btn-danger",isBusy:J,onClick:()=>ze(e.file,e.name)},`${(0,i.__)("Deactivate","optimazz")} ${e.name}`)))),q&&!q.error&&!q.wp_cache&&!q.wpconfig_writable&&(0,a.createElement)("div",{className:"oz-health__manual"},(0,a.createElement)("p",null,(0,i.__)("wp-config.php is not writable. Add this line just after the opening <?php tag:","optimazz")),(0,a.createElement)("code",null,"define( 'WP_CACHE', true );")),q&&q.error&&(0,a.createElement)("p",{className:"oz-health__err"},(0,i.__)("Could not run the check. Try reloading the page.","optimazz")))):g?(0,a.createElement)(C,null):(0,a.createElement)(k,null),la=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(T,{icon:"editor-code",title:(0,i.__)("JavaScript","optimazz"),description:(0,i.__)("Defer and delay scripts to cut Total Blocking Time.","optimazz"),enabled:!!Fe.enabled,onToggle:()=>le("js","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Minify JavaScript","optimazz"),help:(0,i.__)("Strip comments and whitespace from inline scripts. Line breaks are kept for safety; test your site after enabling.","optimazz"),checked:!!Fe.minify,onChange:()=>le("js","minify")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Defer JavaScript","optimazz"),help:(0,i.__)("Load scripts without blocking page render.","optimazz"),checked:!!Fe.defer,onChange:()=>le("js","defer")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Delay JS until interaction","optimazz"),help:(0,i.__)("Run scripts only after the first user interaction.","optimazz"),checked:!!Fe.delay,onChange:()=>le("js","delay")}),Fe.delay&&(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Delay all scripts","optimazz"),help:(0,i.__)("Delay every script until interaction. jQuery, your theme, and Elementor are always kept safe. Still, on sites where content is rendered by JavaScript this can delay the visible page — test before keeping it on.","optimazz"),checked:!!Fe.delay_all,onChange:()=>le("js","delay_all")}),Fe.delay&&Fe.delay_all&&(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)('Delaying all scripts can slow the first paint on JavaScript-rendered sites (Elementor, sliders). If your PageSpeed FCP/LCP get worse, switch to "Delay selected" and delay only tracking scripts.',"optimazz"))),Fe.delay&&!Fe.delay_all&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Delay these scripts","optimazz"),help:(0,i.__)("Only scripts matching these keywords are delayed. Match by filename, handle or URL — e.g. gtag, analytics, facebook.","optimazz"),placeholder:(0,i.__)("e.g. gtag, analytics","optimazz"),value:Fe.delay_list||[],onChange:e=>ne("js","delay_list",e)}),(Fe.defer||Fe.delay)&&(0,a.createElement)("div",{className:"oz-fieldgroup"},(0,a.createElement)("h4",{className:"oz-fieldgroup__title"},(0,i.__)("Exclude specific scripts","optimazz")),(0,a.createElement)("p",{className:"oz-fieldgroup__desc"},(0,i.__)("If a script breaks, exclude it from just one optimization instead of both. Match by filename, script handle, or part of the URL.","optimazz")),Fe.defer&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never defer","optimazz"),help:(0,i.__)("Keep loading these render-blocking, but still allow delay. Use for scripts that must run during page load.","optimazz"),placeholder:(0,i.__)("e.g. jquery.min.js","optimazz"),value:Fe.no_defer||[],onChange:e=>ne("js","no_defer",e)}),Fe.delay&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never delay","optimazz"),help:(0,i.__)("Run these before any interaction, but still allow defer. Use for sliders, above-the-fold, or consent scripts.","optimazz"),placeholder:(0,i.__)("e.g. slider, swiper","optimazz"),value:Fe.no_delay||[],onChange:e=>ne("js","no_delay",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never optimize","optimazz"),help:(0,i.__)("Skip both defer and delay — leave these scripts exactly as they are.","optimazz"),placeholder:(0,i.__)("e.g. recaptcha","optimazz"),value:Fe.exclusions||[],onChange:e=>ne("js","exclusions",e)}))),(0,a.createElement)(T,{icon:"admin-appearance",title:(0,i.__)("CSS","optimazz"),description:(0,i.__)("Minify, combine, and remove render-blocking stylesheets.","optimazz"),enabled:!!He.enabled,onToggle:()=>le("css","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Minify CSS","optimazz"),checked:!!He.minify,onChange:()=>le("css","minify")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Combine CSS files","optimazz"),help:(0,i.__)("Merge stylesheets to reduce requests.","optimazz"),checked:!!He.combine,onChange:()=>le("css","combine")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load CSS asynchronously","optimazz"),help:(0,i.__)("Removes render-blocking CSS. Paste your above-the-fold (critical) CSS below, or the page may briefly flash unstyled.","optimazz"),checked:!!He.async_load,onChange:()=>le("css","async_load")}),He.async_load&&(0,a.createElement)(a.Fragment,null,!(He.critical_css||"").trim()&&(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("No critical CSS provided. Async loading is on, so visitors may see a flash of unstyled content until you paste critical CSS below.","optimazz"))),(0,a.createElement)(o.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Critical CSS (above-the-fold)","optimazz"),help:(0,i.__)("Paste the above-the-fold CSS to inline. Generate it with a free tool such as a critical-CSS generator. Leave empty to disable inlining.","optimazz"),value:He.critical_css||"",onChange:e=>ne("css","critical_css",e),rows:6})),(0,a.createElement)("div",{className:"oz-subhead"},(0,i.__)("Remove unused CSS","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove block-editor CSS on the front end","optimazz"),help:(0,i.__)("Dequeues WordPress block-library and global styles. Turn on only if your front end does NOT render Gutenberg blocks (e.g. a page-builder or classic theme). Big byte saving when it applies.","optimazz"),checked:!!He.remove_block_css,onChange:()=>le("css","remove_block_css")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load WooCommerce CSS only on shop pages","optimazz"),help:(0,i.__)("Dequeues WooCommerce styles on non-shop pages (kept on cart, checkout, account, product and shop). Turn off if you show Woo products or widgets on other pages.","optimazz"),checked:!!He.remove_woo_css,onChange:()=>le("css","remove_woo_css")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load Easy Digital Downloads CSS only on EDD pages","optimazz"),help:(0,i.__)("Dequeues EDD styles on non-EDD pages (kept on checkout, purchase-confirmation, purchase history and download archives). Turn off if you show download or purchase links elsewhere.","optimazz"),checked:!!He.remove_edd_css,onChange:()=>le("css","remove_edd_css")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Excluded CSS files (keywords)","optimazz"),value:He.exclusions||[],onChange:e=>ne("css","exclusions",e)}))),ia=(0,a.createElement)(T,{icon:"format-image",title:(0,i.__)("Image Optimization","optimazz"),description:(0,i.__)("Lazy-load, prevent layout shift, serve next-gen formats, and lighten media.","optimazz"),enabled:!!Ae.enabled,onToggle:()=>le("media","enabled")},ye&&!ye.can_process&&(0,a.createElement)("div",{className:"oz-notice oz-notice--error"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)('This server has no GD or ImageMagick extension, so image compression and WebP/AVIF cannot run — every image will be skipped. Ask your host to enable "php-gd" (or "php-imagick"). See Tools → Server Requirements.',"optimazz"))),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load images","optimazz"),help:(0,i.__)("Load off-screen images only when scrolled into view.","optimazz"),checked:!!Ae.lazy_load,onChange:()=>le("media","lazy_load")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load iframes","optimazz"),checked:!!Ae.lazy_iframes,onChange:()=>le("media","lazy_iframes")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load CSS background images","optimazz"),help:(0,i.__)("Defer inline background-image elements (test with page builders).","optimazz"),checked:!!Ae.lazy_bg,onChange:()=>le("media","lazy_bg")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Replace YouTube embeds with a preview","optimazz"),help:(0,i.__)("Load the video iframe only when the visitor clicks play.","optimazz"),checked:!!Ae.youtube_preview,onChange:()=>le("media","youtube_preview")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Add missing image dimensions","optimazz"),help:(0,i.__)("Set width/height to prevent layout shift (CLS).","optimazz"),checked:!!Ae.add_dimensions,onChange:()=>le("media","add_dimensions")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Preload the LCP image","optimazz"),help:(0,i.__)("Preload the first/hero image with high priority.","optimazz"),checked:!!Ae.preload_lcp,onChange:()=>le("media","preload_lcp")}),Ae.lazy_load&&(0,a.createElement)(F,{label:(0,i.__)("Keep first images eager (LCP)","optimazz"),help:(0,i.__)("Do not lazy-load the first N images, to protect LCP.","optimazz"),value:null!==(E=Ae.skip_first)&&void 0!==E?E:1,onChange:e=>ne("media","skip_first",e),options:[0,1,2,3,4,5].map(e=>({label:String(e),value:e}))}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Serve WebP via <picture>","optimazz"),help:(0,i.__)("Deliver WebP to supporting browsers, original as fallback.","optimazz"),checked:!!Ae.webp,onChange:()=>le("media","webp")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Generate WebP/AVIF on upload","optimazz"),help:ye&&!ye.can_webp?(0,i.__)("WebP generation is not available on this server.","optimazz"):(0,i.__)("Auto-create next-gen copies for newly uploaded images.","optimazz"),checked:!!Ae.webp_generate,onChange:()=>le("media","webp_generate")}),ye?.can_avif&&(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Also generate/serve AVIF","optimazz"),help:(0,i.__)("Smaller than WebP; served first when supported.","optimazz"),checked:!!Ae.avif,onChange:()=>le("media","avif")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Compress & resize on upload","optimazz"),help:(0,i.__)("Recompress, cap dimensions, and strip metadata from new uploads.","optimazz"),checked:!!Ae.compress,onChange:()=>le("media","compress")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Convert opaque PNG to JPEG","optimazz"),help:(0,i.__)("Re-save non-transparent PNGs as much smaller JPEGs during bulk optimization.","optimazz"),checked:!!Ae.png_to_jpg,onChange:()=>le("media","png_to_jpg")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Keep a backup of originals","optimazz"),help:(0,i.__)("Copy originals to uploads/optimazz-backup before optimizing.","optimazz"),checked:!!Ae.backup_originals,onChange:()=>le("media","backup_originals")}),(0,a.createElement)(o.RangeControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Image quality","optimazz"),value:null!==(w=Ae.quality)&&void 0!==w?w:82,onChange:e=>ne("media","quality",e),min:40,max:100,help:(0,i.__)("Used for compression and WebP/AVIF generation.","optimazz")}),(0,a.createElement)(o.RangeControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Max image width (px)","optimazz"),value:null!==($=Ae.max_width)&&void 0!==$?$:2560,onChange:e=>ne("media","max_width",e),min:800,max:4e3,step:80}),(0,a.createElement)("div",{className:"oz-convert"},(0,a.createElement)("div",{className:"oz-convert__info"},(0,a.createElement)("strong",null,(0,i.__)("WebP coverage","optimazz")),ye?(0,a.createElement)("span",null,ye.converted," / ",ye.images," ",(0,i.__)("images converted","optimazz")):(0,a.createElement)("span",null,(0,i.__)("Loading…","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"images-alt2",isBusy:ke?.running,disabled:ye&&!ye.can_webp||ke?.running,onClick:()=>Me("convert")},(0,i.__)("Convert to WebP","optimazz"))),(0,a.createElement)("div",{className:"oz-convert"},(0,a.createElement)("div",{className:"oz-convert__info"},(0,a.createElement)("strong",null,(0,i.__)("Bulk compression","optimazz")),ye?(0,a.createElement)("span",null,ye.optimized," ",(0,i.__)("optimized","optimazz")," · ",Pe(ye.saved)," ",(0,i.__)("saved","optimazz")):(0,a.createElement)("span",null,(0,i.__)("Loading…","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"filter",isBusy:ke?.running,disabled:ke?.running,onClick:()=>Me("compress")},(0,i.__)("Compress existing images","optimazz")))),sa=b?(0,a.createElement)(H,{title:(0,i.__)("Preload Cache","optimazz"),description:(0,i.__)("Warm the cache automatically so visitors never hit a cold page.","optimazz")},h?(0,a.createElement)(a.Fragment,null,(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Activate preloading","optimazz"),help:(0,i.__)("Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop.","optimazz"),checked:!!De.preload,onChange:()=>le("cache","preload")}),(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Automatic preload","optimazz"),help:(0,i.__)("How often to re-warm the whole site. Choose “Every hour” if your content changes often or you use a short cache lifespan.","optimazz"),value:De.preload_interval||"twicedaily",disabled:!De.preload,options:[{label:(0,i.__)("Every hour","optimazz"),value:"hourly"},{label:(0,i.__)("Twice a day","optimazz"),value:"twicedaily"},{label:(0,i.__)("Once a day","optimazz"),value:"daily"}],onChange:e=>ne("cache","preload_interval",e)})):(0,a.createElement)("p",{className:"oz-muted"},z(u?.preload)?(0,i.sprintf)(/* translators: %s: preload frequency, e.g. "twice a day". */ /* translators: %s: preload frequency, e.g. "twice a day". */
     7(0,i.__)("Automatic preload runs %s for this site (set for the whole network in Network Admin → Optimazz).","optimazz"),y(u?.preload_interval)):(0,i.__)("Automatic preload is off for the network. You can still preload this site now.","optimazz")),j&&j.wp_cron_disabled&&(0,a.createElement)("div",{className:"oz-notice oz-notice--warn",style:{marginTop:12}},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("WP-Cron is disabled on this site (DISABLE_WP_CRON). Scheduled preloads only run if a server cron job calls wp-cron.php — ask your host to set one up (e.g. every 5 minutes).","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"update",isBusy:!!ke?.running,onClick:fe},(0,i.__)("Preload now","optimazz")))):(0,a.createElement)(k,null),ra=b?(0,a.createElement)(H,{title:(0,i.__)("Advanced Rules","optimazz"),description:(0,i.__)("Fine-tune which requests are cached.","optimazz")},(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache these URLs (contains)","optimazz"),value:De.excluded_uris||[],onChange:e=>ne("cache","excluded_uris",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache when these cookies are set","optimazz"),value:De.excluded_cookies||[],onChange:e=>ne("cache","excluded_cookies",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache for these user agents","optimazz"),value:De.excluded_user_agents||[],onChange:e=>ne("cache","excluded_user_agents",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Ignore these query arguments","optimazz"),value:De.ignored_query_args||[],onChange:e=>ne("cache","ignored_query_args",e)})):(0,a.createElement)(k,null),ca=!(!B.tools||!B.tools.safe_mode),ma=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(H,{title:(0,i.__)("Safe Mode","optimazz"),description:(0,i.__)("If a theme or page builder looks broken after optimizing, turn this on. It pauses the riskiest optimizations — combine CSS, async CSS, remove block CSS and JavaScript delay — while keeping caching, minify, defer and lazy-load. Your saved settings are not changed; they simply pause until you turn Safe Mode off.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Pause risky optimizations (Safe Mode)","optimazz"),checked:ca,onChange:e=>{const a={...B,tools:{...B.tools||{},safe_mode:e}};O(a),ge(a)}}),ca&&(0,a.createElement)("div",{className:"oz-notice oz-notice--warn",style:{marginTop:12}},(0,a.createElement)("span",{className:"dashicons dashicons-shield"}),(0,a.createElement)("span",null,(0,i.__)("Safe Mode is ON — combine/async CSS, remove-block-CSS and JavaScript delay are paused site-wide. Turn it off to resume full optimization.","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions",style:{marginTop:14}},(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"undo",disabled:!ee,onClick:async()=>{try{const e=await l()({url:`${r}/settings/revert`,method:"POST"});if("nothing"===e.message)return ae(!1),void ue((0,i.__)("Nothing to undo.","optimazz"),"error");W(e.settings),O(e.settings),ae(!!e.hasBackup),de(null),ue((0,i.__)("Reverted to the previous settings.","optimazz")),ie()}catch(e){ue(e.message||(0,i.__)("Undo failed.","optimazz"),"error")}}},(0,i.__)("Undo last change","optimazz")),(0,a.createElement)("span",{className:"oz-muted",style:{fontSize:12}},ee?(0,i.__)("Restores the settings from just before your last save, reset or Safe Mode toggle.","optimazz"):(0,i.__)("Nothing to undo yet — this becomes available after your next change.","optimazz")))),(0,a.createElement)(H,{title:(0,i.__)("Server Requirements","optimazz"),description:(0,i.__)("What your host supports. Warnings here explain why a feature (like WebP/AVIF) may be unavailable or slow.","optimazz")},!se&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Checking your server…","optimazz")),se&&se.error&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Could not read the server report.","optimazz")),se&&!se.error&&(0,a.createElement)("ul",{className:"oz-reqs"},(se.checks||[]).map((e,t)=>(0,a.createElement)("li",{key:t,className:`oz-req oz-req--${e.status}`},(0,a.createElement)("span",{className:"oz-req__dot dashicons "+("ok"===e.status?"dashicons-yes-alt":"warn"===e.status?"dashicons-warning":"info"===e.status?"dashicons-minus":"dashicons-dismiss")}),(0,a.createElement)("span",{className:"oz-req__label"},e.label),(0,a.createElement)("span",{className:"oz-req__value"},e.value),e.note&&(0,a.createElement)("span",{className:"oz-req__note"},e.note)))),(0,a.createElement)("div",{className:"oz-inline-actions",style:{marginTop:14}},(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",isBusy:!se,onClick:()=>{re(null),ce()}},(0,i.__)("Re-check","optimazz")))),(0,a.createElement)(H,{title:(0,i.__)("Import / Export Settings","optimazz"),description:(0,i.__)("Back up your configuration or copy it to another site.","optimazz")},(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"download",onClick:()=>{const e=new window.Blob([JSON.stringify(B,null,2)],{type:"application/json"}),a=document.createElement("a");a.href=URL.createObjectURL(e),a.download="optimazz-settings.json",a.click(),URL.revokeObjectURL(a.href)}},(0,i.__)("Export settings","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"upload",onClick:()=>te.current?.click()},(0,i.__)("Import settings","optimazz")),(0,a.createElement)("input",{ref:te,type:"file",accept:"application/json",style:{display:"none"},onChange:e=>{const a=e.target.files?.[0];if(!a)return;const t=new window.FileReader;t.onload=()=>{try{const e=JSON.parse(t.result);ge(e)}catch(e){ue((0,i.__)("Invalid settings file.","optimazz"),"error")}},t.readAsText(a),e.target.value=""}}))),(0,a.createElement)(H,{title:(0,i.__)("Reset","optimazz"),description:(0,i.__)("Restore every module to its default configuration. This cannot be undone.","optimazz")},(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-danger",icon:"image-rotate",onClick:async()=>{if(window.confirm((0,i.__)('Reset all Optimazz settings to defaults? You can undo this with "Undo last change".',"optimazz")))try{const e=await l()({url:`${r}/settings/reset`,method:"POST"});W(e.settings),O(e.settings),ae(!!e.hasBackup),ue((0,i.__)("Settings reset to defaults.","optimazz")),ie()}catch(e){ue(e.message||(0,i.__)("Reset failed.","optimazz"),"error")}}},(0,i.__)("Reset all settings to defaults","optimazz"))))),da=(0,a.createElement)(T,{icon:"editor-textcolor",title:(0,i.__)("Fonts","optimazz"),description:(0,i.__)("Swap, preconnect, and preload fonts to remove render-blocking requests.","optimazz"),enabled:!!Le.enabled,onToggle:()=>le("fonts","enabled")},(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Font loading (font-display)","optimazz"),help:(0,i.__)("Optional = no layout shift (best for CLS): the web font is skipped on the first uncached load and used once cached. Swap = text stays visible but the web font swaps in and can shift layout (higher CLS). Block = brief invisible text, then the web font.","optimazz"),value:Le.font_display||(Le.display_swap,"swap"),options:[{label:(0,i.__)("Optional — no layout shift (recommended for CLS)","optimazz"),value:"optional"},{label:(0,i.__)("Swap — always show text (may shift layout)","optimazz"),value:"swap"},{label:(0,i.__)("Block — brief hidden text, then web font","optimazz"),value:"block"}],onChange:e=>ne("fonts","font_display",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Combine Google Fonts into one request","optimazz"),help:(0,i.__)("Page builders often load each Google Font family separately. This merges them into a single request.","optimazz"),checked:!!Le.combine_google,onChange:e=>ne("fonts","combine_google",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load only the font weights your pages use","optimazz"),help:(0,i.__)("Drops unused weights (e.g. 100, 200, 900) and italics your pages never use — often cutting font CSS by more than half. Weights 400 and 700 are always kept. Turn off if some text looks thinner or bolder than intended.","optimazz"),checked:!!Le.trim_weights,disabled:!Le.combine_google,onChange:e=>ne("fonts","trim_weights",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load Google Fonts without blocking the page","optimazz"),help:(0,i.__)("The page appears right away in a fallback font and switches to your Google Font as soon as it arrives, instead of waiting for Google first. A big win for the first paint on mobile. Applies when font display is Swap or Fallback; with Optional the stylesheet keeps loading first so text never shifts.","optimazz"),checked:!1!==Le.async_google,disabled:!Le.combine_google,onChange:e=>ne("fonts","async_google",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Preload these font files (URLs)","optimazz"),value:Le.preload||[],onChange:e=>ne("fonts","preload",e)})),pa=e=>{var a;if(!Ee||void 0===Ee[e.key])return e.help;const t=null!==(a=Ee[e.key])&&void 0!==a?a:0;
     8/* translators: %d: number of items found in the database. */return`${(0,i.sprintf)((0,i._n)("%d item in your database.","%d items in your database.",t,"optimazz"),t)} ${e.help}`},_a=(0,a.createElement)(H,{title:(0,i.__)("Database Optimization","optimazz"),description:(0,i.__)("Reduce bloat and reclaim space. Once a cleanup runs it cannot be undone, so back up your database first.","optimazz")},x.map(e=>(0,a.createElement)("div",{key:e.title,className:"oz-dbgroup"},(0,a.createElement)("h3",{className:"oz-dbgroup__title"},e.title),(0,a.createElement)("p",{className:"oz-dbgroup__desc"},e.desc),e.items.map(e=>{var t;return(0,a.createElement)(o.ToggleControl,{key:e.key,__nextHasNoMarginBottom:!0,label:Ee?`${e.label} (${null!==(t=Ee[e.key])&&void 0!==t?t:0})`:e.label,help:pa(e),checked:!!We[e.key],onChange:()=>le("database",e.key)})}))),(0,a.createElement)("div",{className:"oz-dbgroup"},(0,a.createElement)("h3",{className:"oz-dbgroup__title"},(0,i.__)("Set it and forget it","optimazz")),(0,a.createElement)("p",{className:"oz-dbgroup__desc"},(0,i.__)("Let Optimazz repeat the cleanups you picked above on a schedule, so your database stays lean without you lifting a finger.","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Schedule automatic cleanup","optimazz"),help:(0,i.__)("Uses WP-Cron to repeat the cleanups you enabled above.","optimazz"),checked:!!We.enabled,onChange:()=>le("database","enabled")}),We.enabled&&(0,a.createElement)(F,{label:(0,i.__)("Cleanup frequency","optimazz"),value:We.schedule||"off",onChange:e=>ne("database","schedule",e),options:[{label:(0,i.__)("Off","optimazz"),value:"off"},{label:(0,i.__)("Daily","optimazz"),value:"daily"},{label:(0,i.__)("Weekly","optimazz"),value:"weekly"},{label:(0,i.__)("Monthly","optimazz"),value:"monthly"}]})),(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("Back up your database before you run a cleanup — a database optimization cannot be undone.","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",isBusy:!!ke?.running,onClick:async()=>{if(ke?.running)return;const e=(0,i.__)("Cleaning and optimizing database…","optimazz");Ce({title:(0,i.__)("Optimizing database","optimazz"),subtitle:(0,i.__)("Cleanup & table optimization","optimazz"),pct:100,indeterminate:!0,label:e,step:"",logs:[{level:"INFO",text:e}],running:!0,status:"running",minimized:!1});try{const e=await l()({url:`${r}/database/optimize`,method:"POST",data:B.database||{}});ve(e.counts);const a=Object.values(e.removed||{}).reduce((e,a)=>e+a,0),t=`${(0,i.__)("Cleaned","optimazz")} ${a} ${(0,i.__)("rows,","optimazz")} ${e.tables} ${(0,i.__)("tables optimized.","optimazz")}`;Ce(e=>e&&{...e,running:!1,status:"done",indeterminate:!1,pct:100,label:t,logs:[...e.logs,{level:"INFO",text:t},{level:"DONE",text:(0,i.__)("Completed.","optimazz")}]}),ue(t)}catch(e){Ce(a=>a&&{...a,running:!1,status:"error",indeterminate:!1,logs:[...a.logs,{level:"ERROR",text:e.message||"Error"}]}),ue(e.message||(0,i.__)("Optimize failed.","optimazz"),"error")}}},(0,i.__)("Optimize now","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",onClick:Be},(0,i.__)("Refresh counts","optimazz")))),za={dashboard:ea,cache:na,files:la,media:ia,fonts:da,preload:sa,advanced:ra,database:_a,heartbeat:(0,a.createElement)(H,{title:(0,i.__)("Heartbeat Control","optimazz"),description:(0,i.__)("Reduce or disable the WordPress Heartbeat API to lower server load.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Enable Heartbeat control","optimazz"),checked:!!je.enabled,onChange:()=>le("heartbeat","enabled")}),je.enabled&&(0,a.createElement)(a.Fragment,null,(0,a.createElement)(F,{label:(0,i.__)("Behavior in admin dashboard","optimazz"),value:je.backend||"default",options:S,onChange:e=>ne("heartbeat","backend",e)}),(0,a.createElement)(F,{label:(0,i.__)("Behavior in post editor","optimazz"),value:je.editor||"default",options:S,onChange:e=>ne("heartbeat","editor",e)}),(0,a.createElement)(F,{label:(0,i.__)("Behavior on front-end","optimazz"),value:je.frontend||"default",options:S,onChange:e=>ne("heartbeat","frontend",e)}))),tweaks:(0,a.createElement)(T,{icon:"admin-generic",title:(0,i.__)("Tweaks","optimazz"),description:(0,i.__)("Strip unnecessary WordPress front-end weight.","optimazz"),enabled:!!Ie.enabled,onToggle:()=>le("tweaks","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable emojis","optimazz"),help:(0,i.__)("Remove the emoji detection script and styles.","optimazz"),checked:!!Ie.disable_emojis,onChange:()=>le("tweaks","disable_emojis")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable embeds (oEmbed)","optimazz"),checked:!!Ie.disable_embeds,onChange:()=>le("tweaks","disable_embeds")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable dashicons on front-end","optimazz"),help:(0,i.__)("Only for anonymous visitors.","optimazz"),checked:!!Ie.disable_dashicons,onChange:()=>le("tweaks","disable_dashicons")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove query strings from assets","optimazz"),help:(0,i.__)("Strip ?ver= for better proxy caching.","optimazz"),checked:!!Ie.remove_query_strings,onChange:()=>le("tweaks","remove_query_strings")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable XML-RPC","optimazz"),checked:!!Ie.disable_xmlrpc,onChange:()=>le("tweaks","disable_xmlrpc")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable self-pingbacks","optimazz"),checked:!!Ie.disable_pingback,onChange:()=>le("tweaks","disable_pingback")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove head meta (generator, RSD, shortlink)","optimazz"),checked:!!Ie.remove_meta,onChange:()=>le("tweaks","remove_meta")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("DNS-prefetch domains","optimazz"),value:Ie.dns_prefetch||[],onChange:e=>ne("tweaks","dns_prefetch",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Preconnect origins","optimazz"),value:Ie.preconnect||[],onChange:e=>ne("tweaks","preconnect",e)})),cdn:(0,a.createElement)(T,{icon:"cloud",title:(0,i.__)("Content Delivery Network","optimazz"),description:(0,i.__)("Rewrite static asset URLs to your CDN hostname.","optimazz"),enabled:!!qe.enabled,onToggle:()=>le("cdn","enabled")},(0,a.createElement)(o.TextControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("CDN URL (CNAME)","optimazz"),placeholder:"https://cdn.example.com",value:qe.url||"",onChange:e=>ne("cdn","url",e),help:(0,i.__)("CSS, JS, image, and font URLs will be served from here.","optimazz")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Exclude paths containing","optimazz"),value:qe.exclusions||[],onChange:e=>ne("cdn","exclusions",e)})),tools:ma};return p.forEach(e=>{za[e.key]="function"==typeof e.render?e.render():e.render}),(0,a.createElement)(A,{items:_.map(e=>({...e,dot:!(!e.mod||!B[e.mod]?.enabled)})),nav:V,onNav:K,foot:`v${window.optimazzData?.version}`,topIcon:Ue.icon,title:Ue.title,sub:Ue.sub,dirty:oe,saving:Y,onSave:()=>ge(B),toast:Q,onToastRemove:()=>X(null),after:(0,a.createElement)(M,{job:ke,onStop:()=>{Ne.current=!0},onClose:()=>{Ne.current=!0,Ce(null)},onToggleMin:()=>Ce(e=>e&&{...e,minimized:!e.minimized})})},za[V])},null));const W={enabled:!1,cache_mobile:!0,cache_logged_in:!1,lifespan_hours:0,preload:!0,preload_interval:"twicedaily",browser_cache:!1},j=document.getElementById("optimazz-network-app");j&&(0,t.createRoot)(j).render((0,a.createElement)(function(){const[e,n]=(0,t.useState)(null),[s,c]=(0,t.useState)(W),[m,d]=(0,t.useState)(!1),[p,_]=(0,t.useState)(!1),[z,h]=(0,t.useState)(""),[u,g]=(0,t.useState)(null),[b,f]=(0,t.useState)("cache"),[E,y]=(0,t.useState)(null),w=(0,t.useRef)(!1),k=async e=>{if(E?.running)return;w.current=!1;const a=(0,i.__)("Preloading cache…","optimazz");y({title:(0,i.__)("Preloading cache","optimazz"),subtitle:e.name||e.url,pct:0,label:a,step:"",logs:[{level:"INFO",text:a}],running:!0,status:"running",minimized:!1});const t={cached:0,missed:0,diagnosis:""},o=Date.now();let s=0,c=0;const m=(e,a,t,o={})=>y(n=>n&&{...n,running:!1,status:e,...o,logs:[...n.logs,{level:a,text:t}]});try{for(;;){var d;if(w.current){m("stopped","WARN",(0,i.__)("Stopped by user.","optimazz"));break}const a=await l()({url:`${r}/network/preload-batch`,method:"POST",data:{site_id:e.id,offset:s}}),n=s;s=null!==(d=a.next_offset)&&void 0!==d?d:s+(a.processed||0),c=a.total||c,t.cached+=a.cached||0,t.missed+=a.missed||0,t.diagnosis=a.diagnosis||t.diagnosis;const p=c?Math.min(100,Math.round(s/c*100)):100,_=`${(0,i.__)("Warmed","optimazz")} ${Math.min(s,c)} / ${c} ${(0,i.__)("pages","optimazz")} · ${t.cached} ${(0,i.__)("cached","optimazz")}${t.missed?` · ${t.missed} ${(0,i.__)("not cached","optimazz")}`:""}`,z=s/Math.max((Date.now()-o)/1e3,.001),h=z>0&&c&&s<c?(c-s)/z:0,u=h>=60?` · ~${Math.round(h/60)}m ${(0,i.__)("left","optimazz")}`:h>=1?` · ~${Math.round(h)}s ${(0,i.__)("left","optimazz")}`:"";if(y(e=>e&&{...e,pct:p,label:_,step:c?`${Math.min(s,c)} / ${c}${u}`:"",logs:[...e.logs.slice(-60),...a.breakdown?[{level:"INFO",text:`${(0,i.sprintf)(/* translators: %d: number of pages to warm. */ /* translators: %d: number of pages to warm. */
     9(0,i.__)("Found %d pages to warm:","optimazz"),a.total||0)} ${a.breakdown}.`}]:[],{level:"INFO",text:_}]}),a.blocked){m("error","ERROR",(0,i.__)('Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the "Optimazz-CacheWarmer" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter.',"optimazz"));break}if(!a.done&&0===t.cached&&t.missed>=3){m("error","ERROR",`${(0,i.__)("Stopped: pages are loading but none are being saved to the cache.","optimazz")} ${t.diagnosis}`,{label:(0,i.__)("Pages are not being cached","optimazz")});break}if(a.done||s<=n){t.missed&&y(e=>e&&{...e,logs:[...e.logs,{level:"WARN",text:`${(0,i.sprintf)(/* translators: %d: number of pages. */ /* translators: %d: number of pages. */
     10(0,i._n)("%d page was not cached.","%d pages were not cached.",t.missed,"optimazz"),t.missed)} ${t.diagnosis}`}]}),m("done","DONE",(0,i.__)("Completed.","optimazz"),{pct:100});break}}}catch(e){m("error","ERROR",e?.message||(0,i.__)("Something went wrong.","optimazz"))}l()({url:`${r}/network`}).then(e=>{n(e)}).catch(()=>{})},C=e=>{n(e),c({...W,...e.settings||{}}),d(!1)},N=e=>g({status:"error",text:e?.message||(0,i.__)("Something went wrong.","optimazz")}),x=async(e,a,t,o)=>{h(t);try{const t=await l()({url:`${r}${e}`,method:"POST",data:a});return n(t),m||c({...W,...t.settings||{}}),o&&g({status:"success",text:o}),h(""),!0}catch(e){N(e)}return h(""),!1};(0,t.useEffect)(()=>{l()({url:`${r}/network`}).then(C).catch(N)},[]);const S=(e,a)=>{c(t=>({...t,[e]:a})),d(!0)};if(!e)return(0,a.createElement)("div",{className:"oz-shell oz-shell--network-app"},(0,a.createElement)("div",{className:"oz-main"},(0,a.createElement)(o.Spinner,null)));const $=e.health||{},B=e.sites||[],P=B.filter(e=>e.active),O=!!e.settings?.enabled,R=[{key:"cache",svg:"cache",icon:"database",title:(0,i.__)("Page Cache","optimazz"),sub:(0,i.__)("Caching for every site","optimazz"),dot:O},{key:"sites",svg:"dashboard",icon:"admin-multisite",title:(0,i.__)("Sites","optimazz"),sub:(0,i.__)("Cache per site","optimazz")}],L=R.find(e=>e.key===b)||R[0],j=(0,a.createElement)(a.Fragment,null,!e.network_active&&(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-info-outline"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Optimazz is not network-activated","optimazz")),(0,a.createElement)("p",null,(0,i.__)("Only the sites where Optimazz is activated are cached. Network-activate it on Network Admin → Plugins to cache every site.","optimazz")))),(0,a.createElement)(T,{icon:"database",title:(0,i.__)("Page Cache","optimazz"),description:(0,i.__)("Serve ready-made HTML for every site instead of running PHP on each request. Each site keeps its own cache and its own exclusions.","optimazz"),enabled:!!s.enabled,onToggle:()=>S("enabled",!s.enabled)},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache mobile devices separately","optimazz"),checked:!!s.cache_mobile,onChange:()=>S("cache_mobile",!s.cache_mobile)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache logged-in users","optimazz"),help:(0,i.__)("Leave off unless you know what you are doing.","optimazz"),checked:!!s.cache_logged_in,onChange:()=>S("cache_logged_in",!s.cache_logged_in)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Browser cache headers","optimazz"),help:(0,i.__)("Add Expires / Cache-Control rules to the shared .htaccess so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only.","optimazz"),checked:!!s.browser_cache,onChange:()=>S("browser_cache",!s.browser_cache)}),(0,a.createElement)(F,{label:(0,i.__)("Cache lifetime","optimazz"),help:(0,i.__)("How long a cached page is kept. Pages are always purged as soon as their content changes.","optimazz"),value:Number(s.lifespan_hours)||0,options:[{value:0,label:(0,i.__)("Never","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],onChange:e=>S("lifespan_hours",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Preload every site automatically","optimazz"),help:(0,i.__)("Each site warms its own pages on a schedule and right after its cache is cleared.","optimazz"),checked:!!s.preload,onChange:()=>S("preload",!s.preload)}),(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Automatic preload","optimazz"),value:s.preload_interval||"twicedaily",disabled:!s.preload,options:[{label:(0,i.__)("Every hour","optimazz"),value:"hourly"},{label:(0,i.__)("Twice a day","optimazz"),value:"twicedaily"},{label:(0,i.__)("Once a day","optimazz"),value:"daily"}],onChange:e=>S("preload_interval",e)})),(O||$.conflict)&&(0,a.createElement)(H,{title:(0,i.__)("Cache health","optimazz")},$.conflict?(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.sprintf)(/* translators: %s: name of another caching plugin. */ /* translators: %s: name of another caching plugin. */
     11(0,i.__)("%s controls page caching (advanced-cache.php)","optimazz"),$.conflict_name||(0,i.__)("Another plugin","optimazz"))),(0,a.createElement)("p",null,(0,i.__)("Optimazz cannot cache while another plugin owns the cache file. Deactivate that plugin, or let Optimazz take over.","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:"takeover"===z,onClick:()=>x("/network/takeover",{},"takeover",(0,i.__)("Optimazz now handles page caching.","optimazz"))},(0,i.__)("Use Optimazz for caching","optimazz")))):(0,a.createElement)(a.Fragment,null,(0,a.createElement)(D,{ok:$.dropin_ours,label:(0,i.__)("Cache engine installed (advanced-cache.php)","optimazz")}),(0,a.createElement)(D,{ok:$.wp_cache,label:(0,i.__)("WP_CACHE is on in wp-config.php","optimazz")}),!$.wpconfig_writable&&!$.wp_cache&&(0,a.createElement)(D,{ok:!1,label:(0,i.__)("wp-config.php is not writable — add define( 'WP_CACHE', true ); yourself","optimazz")}),!$.content_writable&&(0,a.createElement)(D,{ok:!1,label:(0,i.__)("The wp-content folder is not writable","optimazz")})))),I=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(H,{title:(0,i.__)("All sites","optimazz"),description:(0,i.sprintf)(/* translators: 1: sites with Optimazz, 2: all sites. */ /* translators: 1: sites with Optimazz, 2: all sites. */
     12(0,i.__)("Optimazz runs on %1$d of %2$d sites. Each site has its own cache.","optimazz"),P.length,B.length)},!O&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Page caching is off for the network — turn it on under Page Cache to purge or preload sites.","optimazz")),(0,a.createElement)("div",{className:"oz-quick__actions"},(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-btn--x",icon:"dismiss",disabled:!O,isBusy:"purge-all"===z,onClick:()=>x("/network/purge",{},"purge-all",(0,i.__)("Every site’s cache was purged.","optimazz"))},(0,i.__)("Purge all sites","optimazz")))),(0,a.createElement)("div",{className:"oz-quick oz-network__sites"},B.map(e=>(0,a.createElement)(H,{key:e.id,title:e.name||e.url,description:e.url},e.active?(0,a.createElement)(a.Fragment,null,(0,a.createElement)("div",{className:"oz-quick__actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",disabled:!O||!!E?.running,isBusy:`clear-${e.id}`===z,onClick:()=>(async e=>{await x("/network/purge",{site_id:e.id},`clear-${e.id}`)&&k(e)})(e)},(0,i.__)("Clear & Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",disabled:!O||!!E?.running,onClick:()=>k(e)},(0,i.__)("Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-btn--x",icon:"dismiss",disabled:!O,isBusy:`purge-${e.id}`===z,onClick:()=>x("/network/purge",{site_id:e.id},`purge-${e.id}`,(0,i.__)("Site cache purged.","optimazz"))},(0,i.__)("Purge cache","optimazz"))),(0,a.createElement)("ul",{className:"oz-cachelist"},(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-media-text"}),(0,i.__)("Cached pages","optimazz"),(0,a.createElement)("strong",null,e.pages)),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,i.__)("Page cache size","optimazz"),(0,a.createElement)("strong",null,v(e.bytes))),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-admin-generic"}),(0,i.__)("Optimazz settings","optimazz"),(0,a.createElement)("a",{className:"oz-cachelist__link",href:e.admin_url},(0,i.__)("Open","optimazz")," →")))):(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Optimazz is not active on this site, so its pages are not cached.","optimazz"))))));return(0,a.createElement)(A,{className:"oz-shell--network-app",items:R,nav:b,onNav:f,foot:`${(0,i.__)("Network","optimazz")} · v${window.optimazzData?.version}`,topIcon:L.icon,title:L.title,sub:L.sub,dirty:m,saving:p,onSave:async()=>{_(!0);try{C(await l()({url:`${r}/network/settings`,method:"POST",data:{settings:s}})),g({status:"success",text:(0,i.__)("Network settings saved.","optimazz")})}catch(e){N(e)}_(!1)},toast:u,onToastRemove:()=>g(null),after:(0,a.createElement)(M,{job:E,onStop:()=>{w.current=!0},onClose:()=>{w.current=!0,y(null)},onToggleMin:()=>y(e=>e&&{...e,minimized:!e.minimized})})},"sites"===b?I:j)},null))})();
  • optimazz/tags/1.1.0/changelog.txt

    r3723776 r3743505  
    11Optimazz – Website Optimization and Speedup — full changelog
     2
     3= 1.1.0 =
     4* Fixed: Google Fonts never loaded on sites whose theme or plugin requests them in the old weight format through the new Google Fonts address (for example "Open Sans:400,700" or "w400,700"). Google rejects those requests, so the font was missing while the broken stylesheet still held back the first paint on mobile by most of a second. Optimazz now repairs them into a valid request.
     5* New: Google Fonts load without blocking the page (on by default, Fonts settings). With font display Swap or Fallback the page appears right away in a fallback font and switches when the web font arrives, instead of waiting for Google first. With Optional (zero layout shift) the stylesheet still loads first, so text never shifts.
     6* Improved: sites using Jetpack's Site Accelerator load WordPress core stylesheets from c0.wp.com, which Optimazz used to skip as external, so they stayed render-blocking. When the version matches your WordPress, they are now minified, combined and loaded like local files.
     7* Improved: more chat widgets are delayed until the visitor interacts — OpenWidget, Zendesk, Drift, Freshchat / Freshworks, JivoChat, Smartsupp and Small Chat — cutting mobile Total Blocking Time.
     8* New: full multisite support. Network-activate Optimazz and manage page caching for the whole network in Network Admin → Optimazz: switch it on once, choose mobile and logged-in caching, cache lifetime, browser-cache rules and automatic preload, and see every site with its cached pages and its own Purge and Preload buttons. Each site gets its own cache — on subdirectory and subdomain networks — keeps its own exclusions (Advanced Rules) and all its other Optimazz settings, and can purge and preload its own pages.
     9* Improved: on multisite only the network administrator controls the files every site shares (wp-config.php, advanced-cache.php, .htaccess); a site's own settings never change them, so one site can't switch caching on or off for the others.
     10* Fixed: before this version, on multisite one site's settings controlled caching for every site, only one site of a subdomain network was actually cached, any site administrator could rewrite wp-config.php and .htaccess, and deactivating Optimazz on one site removed the cache for the whole network.
     11* Fixed: deleting Optimazz from a multisite network only cleaned up the main site; settings, scheduled tasks, image data and generated CSS of the other sites were left behind. Uninstall now cleans every site.
     12* Fixed: on sites using plain permalinks (addresses like ?page_id=5) the "Cached pages" count showed 1 no matter how many pages were cached.
     13* Improved: the preload summary now names the home page and counts each address once, so its parts add up to the total.
     14* Improved: changing a cache rule (for example a new exclusion) now also clears pages that were cached under the old rules.
     15* Improved: the Changelog section of this readme now lists recent versions only; the complete history is in changelog.txt.
    216
    317= 1.0.24 =
  • optimazz/tags/1.1.0/includes/Admin/Admin.php

    r3715895 r3743505  
    1818
    1919        /**
     20         * Network Admin page slug (multisite).
     21         */
     22        const NETWORK_SLUG = 'optimazz-network';
     23
     24        /**
    2025         * @var Settings
    2126         */
    … …  
    4045                add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
    4146                add_action( 'admin_notices', array( $this, 'render_notices' ) );
     47                add_action( 'network_admin_menu', array( $this, 'add_network_menu' ) );
    4248                // Hide third-party admin notices on the Optimazz screen to keep it clean.
    4349                add_action( 'in_admin_header', array( $this, 'suppress_foreign_notices' ), 1000 );
    … …  
    120126                }
    121127                $screen = get_current_screen();
    122                 return $screen && 'toplevel_page_' . self::MENU_SLUG === $screen->id;
     128                return $screen && in_array( $screen->id, array( 'toplevel_page_' . self::MENU_SLUG, 'toplevel_page_' . self::NETWORK_SLUG . '-network' ), true );
    123129        }
    124130
    … …  
    276282
    277283        /**
     284         * Network Admin → Optimazz (super admins): network page caching and sites.
     285         *
     286         * @return void
     287         */
     288        public function add_network_menu() {
     289                add_menu_page(
     290                        __( 'Optimazz', 'optimazz' ),
     291                        __( 'Optimazz', 'optimazz' ),
     292                        'manage_network_options',
     293                        self::NETWORK_SLUG,
     294                        array( $this, 'render_network' ),
     295                        'dashicons-performance',
     296                        81
     297                );
     298        }
     299
     300        /**
     301         * Render the Network Admin mount node.
     302         *
     303         * @return void
     304         */
     305        public function render_network() {
     306                echo '<div class="wrap"><div id="optimazz-network-app"></div></div>';
     307        }
     308
     309        /**
    278310         * Enqueue the built React app on our page only.
    279311         *
    … …  
    282314         */
    283315        public function enqueue( $hook ) {
    284                 if ( 'toplevel_page_' . self::MENU_SLUG !== $hook ) {
     316                $network = 'toplevel_page_' . self::NETWORK_SLUG === $hook;
     317                if ( 'toplevel_page_' . self::MENU_SLUG !== $hook && ! $network ) {
    285318                        return;
    286319                }
    … …  
    356389                                'wpconfigUnwritable' => (bool) get_transient( 'optimazz_wpconfig_unwritable' ),
    357390                                'hasBackup'          => $this->settings->has_backup(),
     391                                'serverFeatures'     => \Optimazz\Core\Settings::server_features_allowed(),
     392                                'multisite'          => is_multisite(),
     393                                // 'network' screen = Network Admin → Optimazz; 'site' = a site's own screen.
     394                                'context'            => $network ? 'network' : 'site',
     395                                'networkCache'       => is_multisite() ? \Optimazz\Core\Network::settings() : null,
     396                                'networkAdminUrl'    => is_multisite() ? network_admin_url( 'admin.php?page=' . self::NETWORK_SLUG ) : '',
     397                                'isSuperAdmin'       => is_super_admin(),
    358398                        )
    359399                );
  • optimazz/tags/1.1.0/includes/Admin/AdminBar.php

    r3617648 r3743505  
    5050
    5151                $items = array(
    52                         'optimazz-settings'      => array( __( 'Settings', 'optimazz' ), $settings ),
    53                         'optimazz-clear-preload' => array( __( 'Clear & Preload Cache', 'optimazz' ), $this->action_url( 'optimazz_clear_preload' ) ),
    54                         'optimazz-preload'       => array( __( 'Preload Cache', 'optimazz' ), $this->action_url( 'optimazz_preload' ) ),
    55                         'optimazz-purge'         => array( __( 'Purge Cache', 'optimazz' ), $this->action_url( 'optimazz_purge' ) ),
     52                        'optimazz-settings' => array( __( 'Settings', 'optimazz' ), $settings ),
    5653                );
     54                // Cache actions only where this site has a page cache (on multisite: when
     55                // the network admin has switched network caching on).
     56                if ( ! is_multisite() || \Optimazz\Core\Network::cache_enabled() ) {
     57                        $items['optimazz-clear-preload'] = array( __( 'Clear & Preload Cache', 'optimazz' ), $this->action_url( 'optimazz_clear_preload' ) );
     58                        $items['optimazz-preload']       = array( __( 'Preload Cache', 'optimazz' ), $this->action_url( 'optimazz_preload' ) );
     59                        $items['optimazz-purge']         = array( __( 'Purge Cache', 'optimazz' ), $this->action_url( 'optimazz_purge' ) );
     60                }
    5761                foreach ( $items as $id => $item ) {
    5862                        $bar->add_node(
    … …  
    9599         */
    96100        private function guard( $action ) {
    97                 if ( ! current_user_can( 'manage_options' ) || ! check_admin_referer( $action ) ) {
     101                if ( ( is_multisite() && ! \Optimazz\Core\Network::cache_enabled() ) || ! current_user_can( 'manage_options' ) || ! check_admin_referer( $action ) ) {
    98102                        wp_die( esc_html__( 'Not allowed.', 'optimazz' ) );
    99103                }
  • optimazz/tags/1.1.0/includes/Admin/CacheConflictNotice.php

    r3723128 r3743505  
    5959         */
    6060        public function render() {
    61                 if ( ! current_user_can( 'manage_options' ) ) {
     61                if ( is_multisite() || ! current_user_can( 'manage_options' ) ) {
    6262                        return;
    6363                }
    … …  
    121121         */
    122122        private function guard( $action ) {
    123                 if ( ! current_user_can( 'manage_options' ) || ! check_admin_referer( $action ) ) {
     123                // Multisite: never take over the shared drop-in or (network-)deactivate
     124                // another plugin from a site — the same rule as the REST endpoints.
     125                if ( ! \Optimazz\Core\Settings::server_features_allowed() || ! current_user_can( 'manage_options' ) || ! check_admin_referer( $action ) ) {
    124126                        wp_die( esc_html__( 'Not allowed.', 'optimazz' ) );
    125127                }
  • optimazz/tags/1.1.0/includes/Admin/RestController.php

    r3723128 r3743505  
    278278                // modules emit into the page, so stale cached HTML must be cleared for the
    279279                // change to take effect immediately (the whole point of Safe Mode).
     280                // 'cache' is included so a new exclusion (or lifetime, mobile split …)
     281                // also drops pages that were cached under the old rules.
    280282                $purged = false;
    281                 foreach ( array( 'css', 'js', 'media', 'fonts', 'cdn', 'tweaks', 'tools' ) as $mod ) {
     283                foreach ( array( 'cache', 'css', 'js', 'media', 'fonts', 'cdn', 'tweaks', 'tools' ) as $mod ) {
    282284                        $old = isset( $before[ $mod ] ) ? $before[ $mod ] : array();
    283285                        $new = isset( $saved[ $mod ] ) ? $saved[ $mod ] : array();
     286                        if ( 'cache' === $mod ) {
     287                                // Preload schedule and browser-cache headers don't change the
     288                                // cached HTML — changing them must not throw the cache away.
     289                                foreach ( array( 'preload', 'preload_interval', 'browser_cache' ) as $key ) {
     290                                        unset( $old[ $key ], $new[ $key ] );
     291                                }
     292                        }
    284293                        if ( wp_json_encode( $old ) !== wp_json_encode( $new ) ) {
    285294                                $purged = true;
    … …  
    373382         */
    374383        public function purge_cache() {
     384                if ( is_multisite() && ! \Optimazz\Core\Network::cache_enabled() ) {
     385                        return new WP_REST_Response( array( 'message' => __( 'Page caching is not available on multisite networks.', 'optimazz' ) ), 400 );
     386                }
    375387                Manager::storage()->purge_all();
    376388                return new WP_REST_Response( array( 'message' => 'purged' ), 200 );
    … …  
    573585         */
    574586        public function cache_takeover( WP_REST_Request $request ) {
     587                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     588                        return new WP_REST_Response( array( 'message' => __( 'Page caching is not available on multisite networks.', 'optimazz' ) ), 400 );
     589                }
    575590                // Make sure the page-cache module is on, then force our drop-in in.
    576591                $cache = $this->settings->all();
    … …  
    596611         */
    597612        public function cache_deactivate_conflict( WP_REST_Request $request ) {
     613                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     614                        return new WP_REST_Response( array( 'message' => __( 'Page caching is not available on multisite networks.', 'optimazz' ) ), 400 );
     615                }
    598616                $plugin  = (string) $request->get_param( 'plugin' );
    599617                if ( '' === $plugin || '' === Dropin::known_cache_plugin_name( $plugin ) ) {
  • optimazz/tags/1.1.0/includes/Core/Lifecycle.php

    r3723128 r3743505  
    1919         * actually enables page caching (handled by the Cache module).
    2020         *
     21         * @param bool $network_wide Whether this is a network-wide activation.
    2122         * @return void
    2223         */
    23         public static function activate() {
     24        public static function activate( $network_wide = false ) {
     25                // Multisite: add this site (or every site) to the cache site map. The
     26                // activated_plugin listener isn't registered yet in the activation
     27                // request, so queue the sync here.
     28                if ( is_multisite() ) {
     29                        Network::queue_sync();
     30                }
     31                // Network activation: every site starts from the defaults the first time
     32                // its settings are read, and the shared cache files follow the network
     33                // settings (Network::on_plugin_change() re-syncs the site map).
     34
    2435                if ( false === get_option( Settings::OPTION_KEY, false ) ) {
    2536                        add_option( Settings::OPTION_KEY, Settings::defaults() );
    … …  
    6879                update_option( self::VERSION_KEY, OPTIMAZZ_VERSION, false );
    6980
     81                // Multisite: the shared files follow the network settings. Sync them once
     82                // per network per version (this also removes a drop-in that a pre-1.1.0
     83                // site installed, unless the network turned caching on); then bring this
     84                // site's own rules and schedule up to date and rebuild its cache.
     85                if ( is_multisite() ) {
     86                        if ( OPTIMAZZ_VERSION !== get_site_option( Network::VERSION_KEY ) ) {
     87                                Network::sync();
     88                                \Optimazz_Cache_Engine::remove_mixed_case_hosts(); // Orphans of pre-1.1.0 mixed-case Host folders.
     89                        }
     90                        if ( class_exists( '\Optimazz\Modules\Cache\Manager' ) ) {
     91                                \Optimazz\Modules\Cache\Manager::sync( $settings->for_module( 'cache' ) );
     92                                \Optimazz\Modules\Cache\Manager::purge_and_rewarm();
     93                        }
     94                        if ( Network::cache_enabled() && class_exists( '\Optimazz\Core\Guard' ) ) {
     95                                Guard::schedule(); // Same post-update self-check as on a single site.
     96                        }
     97                        return;
     98                }
     99
    70100                // Only touch caching when the user actually has it enabled.
    71101                $cache = $settings->for_module( 'cache' );
    … …  
    91121         * so a disabled plugin never serves stale cached pages.
    92122         *
     123         * @param bool $network_wide Whether this is a network-wide deactivation.
    93124         * @return void
    94125         */
    95         public static function deactivate() {
    96                 if ( class_exists( '\Optimazz\Modules\Cache\Dropin' ) ) {
    97                         Dropin::uninstall();
     126        public static function deactivate( $network_wide = false ) {
     127                // Multisite, one site: the shared drop-in still serves the other sites.
     128                // Clear this site's cache and jobs; Network::on_plugin_change() drops it
     129                // from the site map so the engine stops caching it.
     130                if ( is_multisite() && ! $network_wide ) {
     131                        if ( class_exists( '\Optimazz\Modules\Cache\Manager' ) ) {
     132                                \Optimazz\Modules\Cache\Manager::storage()->purge_all();
     133                        }
     134                        self::clear_events();
     135                        return;
    98136                }
    99                 if ( class_exists( '\Optimazz\Modules\Cache\BrowserCache' ) ) {
    100                         \Optimazz\Modules\Cache\BrowserCache::apply( false );
     137                Network::with_shared_writes(
     138                        static function () {
     139                                if ( class_exists( '\Optimazz\Modules\Cache\Dropin' ) ) {
     140                                        Dropin::uninstall();
     141                                }
     142                                if ( class_exists( '\Optimazz\Modules\Cache\BrowserCache' ) ) {
     143                                        \Optimazz\Modules\Cache\BrowserCache::apply( false );
     144                                }
     145                        }
     146                );
     147                // Scheduled events live per site. A network deactivation runs once, so
     148                // clear every site.
     149                if ( is_multisite() && $network_wide ) {
     150                        foreach ( get_sites( array( 'fields' => 'ids', 'number' => 0 ) ) as $site_id ) {
     151                                switch_to_blog( $site_id );
     152                                self::clear_events();
     153                                restore_current_blog();
     154                        }
     155                } else {
     156                        self::clear_events();
    101157                }
    102                 wp_clear_scheduled_hook( 'optimazz_preload_cache' );
    103                 wp_clear_scheduled_hook( 'optimazz_preload_cache_once' );
    104                 wp_clear_scheduled_hook( 'optimazz_purge_expired' );
    105                 wp_clear_scheduled_hook( 'optimazz_db_cleanup' );
    106                 wp_clear_scheduled_hook( 'optimazz_health_check' );
     158        }
     159
     160        /**
     161         * Remove this site's Optimazz scheduled events.
     162         *
     163         * @return void
     164         */
     165        private static function clear_events() {
     166                foreach ( array( 'optimazz_preload_cache', 'optimazz_preload_cache_once', 'optimazz_purge_expired', 'optimazz_db_cleanup', 'optimazz_health_check' ) as $hook ) {
     167                        wp_clear_scheduled_hook( $hook );
     168                }
    107169        }
    108170}
  • optimazz/tags/1.1.0/includes/Core/Paths.php

    r3617648 r3743505  
    2525        public static function url_to_path( $url ) {
    2626                $host = wp_parse_url( $url, PHP_URL_HOST );
     27                if ( 'c0.wp.com' === $host ) {
     28                        return self::jetpack_core_path( $url );
     29                }
    2730                if ( $host && $host !== wp_parse_url( home_url(), PHP_URL_HOST ) ) {
    2831                        return '';
    … …  
    4851                return ABSPATH . ltrim( $path, '/' );
    4952        }
     53
     54        /**
     55         * Jetpack's Site Accelerator serves WordPress core files from
     56         * c0.wp.com/c/<version>/wp-includes/… . When that version is the one
     57         * installed here, the file is identical to our own copy, so it can be
     58         * minified, combined and loaded without blocking like any local file —
     59         * otherwise these core stylesheets stay render-blocking on every page.
     60         *
     61         * @param string $url c0.wp.com URL.
     62         * @return string Local path, or '' when it is not a matching core file.
     63         */
     64        private static function jetpack_core_path( $url ) {
     65                $path = (string) wp_parse_url( strtok( (string) $url, '?' ), PHP_URL_PATH );
     66                if ( false !== strpos( $path, '..' ) || ! preg_match( '#^/c/([^/]+)/((?:wp-includes|wp-admin)/.+)$#', $path, $m ) ) {
     67                        return '';
     68                }
     69                if ( $m[1] !== get_bloginfo( 'version' ) ) {
     70                        return ''; // A different WordPress version: the files may differ.
     71                }
     72                return ABSPATH . $m[2];
     73        }
    5074}
  • optimazz/tags/1.1.0/includes/Core/Plugin.php

    r3723128 r3743505  
    1111use Optimazz\Admin\AdminBar;
    1212use Optimazz\Admin\CacheConflictNotice;
     13use Optimazz\Admin\NetworkRestController;
    1314use Optimazz\Admin\RestController;
    1415use Optimazz\Modules\Cache\CacheModule;
    … …  
    9596                // Mode if a post-update front page renders unstyled.
    9697                Guard::register();
     98                Network::register(); // Multisite: keep the site map in step with the network.
    9799
    98100                // One-time reconciliation after a version change (update). Clears cached
    … …  
    109111
    110112                ( new RestController( $this->settings, $this->loader ) )->register();
     113                ( new NetworkRestController() )->register(); // Multisite: Network Admin → Optimazz.
    111114
    112115                // Translations are auto-loaded by WordPress for .org-hosted plugins
  • optimazz/tags/1.1.0/includes/Core/Recommendations.php

    r3700289 r3743505  
    5656                }
    5757
    58                 // ---- Page cache (biggest single win) ----
    59                 if ( ! $on( $cache, 'enabled' ) ) {
     58                // ---- Page cache (biggest single win; not available on multisite) ----
     59                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     60                        // Nothing to recommend — see Settings::server_features_allowed().
     61                } elseif ( ! $on( $cache, 'enabled' ) ) {
    6062                        $recs[] = self::rec( 'page_cache', 'high',
    6163                                __( 'Enable Page Cache', 'optimazz' ),
    … …  
    6365                                array( 'cache', 'enabled', true )
    6466                        );
    65                 } elseif ( ! $on( $cache, 'preload' ) ) {
     67                } elseif ( ! $on( $cache, 'preload' ) && ! is_multisite() ) {
    6668                        $recs[] = self::rec( 'preload', 'medium',
    6769                                __( 'Turn on cache preloading', 'optimazz' ),
  • optimazz/tags/1.1.0/includes/Core/Settings.php

    r3723128 r3743505  
    114114                                'combine_google' => true, // Merge all Google Fonts requests into one.
    115115                                'trim_weights'   => true, // Request only the font weights the page uses.
     116                                'async_google'   => true, // Load the Google Fonts stylesheet without blocking rendering.
    116117                                'preload'      => array(),
    117118                        ),
    … …  
    153154                if ( null === $this->cache ) {
    154155                        $stored      = get_option( self::OPTION_KEY, array() );
    155                         $this->cache = $this->merge_deep( self::defaults(), is_array( $stored ) ? $stored : array() );
     156                        $this->cache = self::enforce_platform( $this->merge_deep( self::defaults(), is_array( $stored ) ? $stored : array() ) );
    156157                }
    157158                return $this->cache;
     159        }
     160
     161        /**
     162         * Whether the current code may write SHARED server files.
     163         *
     164         * Page cache (wp-config.php WP_CACHE, wp-content/advanced-cache.php and the
     165         * cache folder) and browser-cache rules (.htaccess) are one-per-install. On
     166         * a single site its own settings control them. On multisite only a network
     167         * action (Network::sync() and friends, run for the super admin) may write
     168         * them — never a site's own settings save, so one site can't change caching
     169         * for the others and a site admin can't rewrite wp-config.php / .htaccess.
     170         *
     171         * @return bool
     172         */
     173        public static function server_features_allowed() {
     174                return ! is_multisite() || Network::is_writing();
     175        }
     176
     177        /**
     178         * On multisite the page-cache switches come from the network settings
     179         * (Network Admin → Optimazz); each site keeps its own exclusions.
     180         *
     181         * @param array $settings Settings.
     182         * @return array
     183         */
     184        private static function enforce_platform( array $settings ) {
     185                if ( is_multisite() && isset( $settings['cache'] ) && is_array( $settings['cache'] ) ) {
     186                        $settings['cache'] = array_merge( $settings['cache'], Network::settings() );
     187                }
     188                return $settings;
    158189        }
    159190
    … …  
    214245                $defaults = self::defaults();
    215246                update_option( self::OPTION_KEY, $defaults );
    216                 $this->cache = $defaults;
    217                 return $defaults;
     247                $this->cache = self::enforce_platform( $defaults ); // On multisite: network cache switches stay.
     248                return $this->cache;
    218249        }
    219250
    … …  
    285316                        }
    286317                }
    287                 return $clean;
     318                return self::enforce_platform( $clean );
    288319        }
    289320
  • optimazz/tags/1.1.0/includes/Modules/Cache/BrowserCache.php

    r3617648 r3743505  
    2525         */
    2626        public static function apply( $enabled ) {
     27                if ( $enabled && ! \Optimazz\Core\Settings::server_features_allowed() ) {
     28                        $enabled = false; // One .htaccess serves the whole network — never written from a subsite.
     29                }
    2730                $htaccess = ABSPATH . '.htaccess';
    2831
  • optimazz/tags/1.1.0/includes/Modules/Cache/Dropin.php

    r3723128 r3743505  
    5353         */
    5454        public static function install() {
     55                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     56                        return false; // advanced-cache.php is shared by every site of a network.
     57                }
    5558                $fs = self::filesystem();
    5659                if ( ! $fs || ! $fs->is_readable( self::template() ) ) {
    … …  
    6568
    6669                $ok = $fs->put_contents( self::DROPIN_PATH, $contents );
     70                self::forget_compiled( self::DROPIN_PATH );
    6771                if ( $ok ) {
    6872                        self::set_wp_cache( true );
    … …  
    322326
    323327        /**
     328         * Make PHP's opcode cache drop its compiled copy of a file we rewrote
     329         * (wp-config.php, advanced-cache.php), so the change applies on the very
     330         * next request instead of after the next timestamp check.
     331         *
     332         * @param string $file Absolute path.
     333         * @return void
     334         */
     335        private static function forget_compiled( $file ) {
     336                if ( function_exists( 'opcache_invalidate' ) ) {
     337                        @opcache_invalidate( $file, true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- may be restricted by opcache.restrict_api.
     338                }
     339        }
     340
     341        /**
    324342         * Forcefully make Optimazz the caching drop-in — overwrite whatever
    325343         * advanced-cache.php is present and enable WP_CACHE. Used by the admin
    … …  
    371389         */
    372390        private static function set_wp_cache( $enabled ) {
     391                // Multisite: wp-config.php is shared by the whole network, so a subsite
     392                // never edits it — not even to turn WP_CACHE off. Removing our drop-in is
     393                // enough to stop caching (WordPress only loads advanced-cache.php if the
     394                // file exists).
     395                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     396                        return false;
     397                }
    373398                $config = ABSPATH . 'wp-config.php';
    374399                if ( ! is_writable( $config ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions
    … …  
    405430                // core caching plugins — we toggle that single line here via
    406431                // WP_Filesystem. Nothing else in the file is touched.
    407                 return $fs->put_contents( $config, $contents );
     432                $ok = $fs->put_contents( $config, $contents );
     433                self::forget_compiled( $config ); // WP_CACHE must take effect on the next request.
     434                return $ok;
    408435        }
    409436}
  • optimazz/tags/1.1.0/includes/Modules/Cache/Manager.php

    r3723128 r3743505  
    5454                }
    5555
     56                // Multisite: the drop-in, WP_CACHE, the engine config and .htaccess are
     57                // shared by the whole network and only Network::sync() (a network admin
     58                // action) writes them. A site writes just its own rules file and keeps
     59                // its own preload schedule in step with the network settings.
     60                if ( is_multisite() && ! \Optimazz\Core\Network::is_writing() ) {
     61                        \Optimazz\Core\Network::write_current_site_rules();
     62                        $on = ! empty( $cache_settings['enabled'] );
     63                        Preloader::schedule( $on && ! empty( $cache_settings['preload'] ), isset( $cache_settings['preload_interval'] ) ? (string) $cache_settings['preload_interval'] : 'twicedaily' );
     64                        if ( ! $on ) {
     65                                wp_clear_scheduled_hook( Preloader::HOOK . '_once' );
     66                                wp_clear_scheduled_hook( 'optimazz_purge_expired' );
     67                        }
     68                        Preloader::sync_due();
     69                        return;
     70                }
     71
    5672                // The engine config is a trimmed, disk-readable copy of the settings.
    5773                \Optimazz_Cache_Engine::write_config(
    … …  
    107123         */
    108124        public static function purge_and_rewarm() {
    109                 self::storage()->purge_all();
     125                if ( is_multisite() && ! \Optimazz\Core\Network::cache_enabled() ) {
     126                        return; // Page caching is off for this network — nothing to rebuild.
     127                }
     128                self::storage()->purge_all(); // On multisite: this site's pages only.
    110129
    111130                $cache = ( new \Optimazz\Core\Settings() )->for_module( 'cache' );
    … …  
    128147                }
    129148
     149                if ( is_multisite() ) {
     150                        // A new site has no rules file yet; write its own.
     151                        if ( ! is_readable( \Optimazz_Cache_Engine::site_rules_file( get_current_blog_id() ) ) ) {
     152                                \Optimazz\Core\Network::write_current_site_rules();
     153                        }
     154                        // Network caching is on but the shared drop-in or engine config is
     155                        // missing/stale (plugin folder moved, a host tool emptied the cache
     156                        // folder): heal it for the whole network — at most every 10 minutes,
     157                        // so a drop-in that can't be installed (another plugin owns it, the
     158                        // folder isn't writable) never makes every request run a full sync.
     159                        if ( ! empty( $cache_settings['enabled'] )
     160                                && null === Dropin::conflict_owner() // Another plugin's drop-in: nothing to heal (Network screen offers take-over).
     161                                && ( ! Dropin::is_current() || ! is_readable( \Optimazz_Cache_Engine::config_file() ) )
     162                                && ! get_site_transient( 'optimazz_heal' ) ) {
     163                                set_site_transient( 'optimazz_heal', 1, 10 * MINUTE_IN_SECONDS );
     164                                \Optimazz\Core\Network::queue_sync();
     165                        }
     166                        return;
     167                }
     168
    130169                if ( ! is_readable( \Optimazz_Cache_Engine::config_file() ) ) {
    131170                        self::sync( $cache_settings );
  • optimazz/tags/1.1.0/includes/Modules/Cache/Preloader.php

    r3723128 r3743505  
    113113                }
    114114                $parts = array();
     115                if ( ! empty( $counts['__home'] ) ) {
     116                        $parts[] = __( 'the home page', 'optimazz' );
     117                }
    115118                foreach ( $counts as $group => $n ) {
    116                         if ( ! $n || '__terms' === $group ) {
     119                        if ( ! $n || '__terms' === $group || '__home' === $group ) {
    117120                                continue;
    118121                        }
    … …  
    140143                $max    = max( 1, (int) apply_filters( 'optimazz_preload_max', 300 ) );
    141144                $urls   = array( home_url( '/' ) );
    142                 $groups = array(); // Source of each URL, for breakdown().
     145                $groups = array( '__home' => array( home_url( '/' ) ) ); // Source of each URL, for breakdown().
    143146
    144147                // A static front page and/or posts page first, when configured.
    … …  
    214217
    215218                // How many of the FINAL list came from each source (for breakdown()).
     219                // Each URL counts once, under the first source that listed it (the home
     220                // page may also be the static front Page).
     221                $owner = array();
     222                foreach ( $groups as $group => $links ) {
     223                        foreach ( $links as $link ) {
     224                                if ( ! isset( $owner[ $link ] ) ) {
     225                                        $owner[ $link ] = $group;
     226                                }
     227                        }
     228                }
    216229                $counts = array();
    217                 foreach ( $groups as $group => $links ) {
    218                         $counts[ $group ] = count( array_intersect( array_unique( $links ), $urls ) );
     230                foreach ( $urls as $url ) {
     231                        if ( isset( $owner[ $url ] ) ) {
     232                                $counts[ $owner[ $url ] ] = ( isset( $counts[ $owner[ $url ] ] ) ? $counts[ $owner[ $url ] ] : 0 ) + 1;
     233                        }
    219234                }
    220235                set_transient( self::URLS_CACHE . '_breakdown', $counts, HOUR_IN_SECONDS );
    … …  
    275290                        require_once OPTIMAZZ_DIR . 'includes/Modules/Cache/engine.php';
    276291                }
    277                 $config = \Optimazz_Cache_Engine::load_config();
     292                $config = self::engine_config();
    278293                // A new manual run (offset 0) rebuilds the list; later steps reuse it.
    279294                $urls  = self::collect_all_urls( 0 === (int) $offset );
    … …  
    341356
    342357        /**
     358         * Engine config for this site: on a network, the network settings merged
     359         * with this site's own rules (ignored query args decide the cache key).
     360         *
     361         * @return array
     362         */
     363        private static function engine_config() {
     364                $home = (array) wp_parse_url( home_url( '/' ) );
     365                return \Optimazz_Cache_Engine::config_for( \Optimazz_Cache_Engine::url_host( $home ), isset( $home['path'] ) ? $home['path'] : '/' );
     366        }
     367
     368        /**
    343369         * Device copies to warm: desktop, plus mobile when a separate mobile cache is on.
    344370         *
    … …  
    383409                        require_once OPTIMAZZ_DIR . 'includes/Modules/Cache/engine.php';
    384410                }
    385                 $config = \Optimazz_Cache_Engine::load_config();
     411                $config = self::engine_config();
    386412                if ( empty( $config['enabled'] ) ) {
    387413                        delete_option( self::CURSOR );
    … …  
    472498                $parts  = wp_parse_url( $url );
    473499                $scheme = isset( $parts['scheme'] ) ? $parts['scheme'] : 'https';
    474                 $host   = isset( $parts['host'] ) ? $parts['host'] : '';
     500                $host   = \Optimazz_Cache_Engine::url_host( (array) $parts );
    475501                $uri    = ( isset( $parts['path'] ) ? $parts['path'] : '/' ) . ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' );
    476502                $file   = \Optimazz_Cache_Engine::path_for( $scheme, $host, $uri, $device, $config );
    … …  
    848874                        require_once OPTIMAZZ_DIR . 'includes/Modules/Cache/engine.php';
    849875                }
    850                 \Optimazz_Cache_Engine::write_cron_due( self::next_due(), site_url( 'wp-cron.php' ) );
     876                \Optimazz_Cache_Engine::write_cron_due( self::next_due(), site_url( 'wp-cron.php' ), is_multisite() ? get_current_blog_id() : 0 );
    851877        }
    852878
  • optimazz/tags/1.1.0/includes/Modules/Cache/Storage/DiskStorage.php

    r3723128 r3743505  
    2929
    3030        public function purge_all() {
     31                if ( is_multisite() ) {
     32                        \Optimazz_Cache_Engine::purge_site_dir( self::site_dir() ); // This site only.
     33                        return;
     34                }
    3135                \Optimazz_Cache_Engine::purge_all();
     36        }
     37
     38        /**
     39         * Cache folder of the current network site.
     40         *
     41         * @return string
     42         */
     43        private static function site_dir() {
     44                $site = get_site();
     45                return \Optimazz_Cache_Engine::site_dir( $site ? $site->domain : (string) wp_parse_url( home_url(), PHP_URL_HOST ), $site ? $site->path : '/' );
    3246        }
    3347
    … …  
    3953        public function stats() {
    4054                $root = \Optimazz_Cache_Engine::cache_root();
     55                $skip = array();
     56                if ( is_multisite() ) {
     57                        // Count this site's folder only, not other sites nested inside it.
     58                        $root = self::site_dir();
     59                        $skip = array_values( array_diff( \Optimazz_Cache_Engine::site_roots(), array( $root ) ) );
     60                }
    4161                if ( ! is_dir( $root ) ) {
    4262                        return array(
    … …  
    4969                $files = 0;
    5070                $bytes = 0;
    51                 // A page (one URL) is stored as several variants in its own folder —
    52                 // desktop/mobile, http/https, query strings. "Cached pages" counts the
    53                 // pages, so it matches what the preloader reports; 'files' is kept for
    54                 // the raw variant count.
     71                // A page (one URL) is stored as several variants — desktop/mobile,
     72                // http/https, logged-in buckets. "Cached pages" counts the pages, so it
     73                // matches what the preloader reports; 'files' is kept for the raw
     74                // variant count.
    5575                $dirs  = array();
    5676                $it    = new \RecursiveIteratorIterator(
    57                         new \RecursiveDirectoryIterator( $root, \FilesystemIterator::SKIP_DOTS )
     77                        new \RecursiveCallbackFilterIterator(
     78                                new \RecursiveDirectoryIterator( $root, \FilesystemIterator::SKIP_DOTS ),
     79                                static function ( $file ) use ( $skip ) {
     80                                        // Skip other network sites' folders (empty on a single site).
     81                                        return ! ( $file->isDir() && in_array( rtrim( $file->getPathname(), '/' ) . '/', $skip, true ) );
     82                                }
     83                        )
    5884                );
    5985                foreach ( $it as $file ) {
    … …  
    6692                                ++$files;
    6793                                $bytes += (int) $file->getSize();
    68                                 $dirs[ $file->getPath() ] = true;
     94                                // File name: {scheme}-{device}-{user}[-{query}].html. One page = its
     95                                // folder. At the site root the query part counts too, because plain
     96                                // permalinks (?page_id=5) keep every page there; elsewhere query
     97                                // variants (e.g. /shop/?orderby=price) are the same page.
     98                                $parts   = explode( '-', $file->getBasename( '.html' ), 4 );
     99                                $at_root = false === strpos( trim( substr( $file->getPath(), strlen( $root ) ), '/' ), '/' );
     100                                $dirs[ $file->getPath() . '|' . ( $at_root && isset( $parts[3] ) ? $parts[3] : '' ) ] = true;
    69101                        }
    70102                }
  • optimazz/tags/1.1.0/includes/Modules/Cache/engine.php

    r3723128 r3743505  
    3838                        'site_host'            => '',
    3939                        'lifespan_hours'       => 0,
     40                        // Multisite: map of "host/path/" => site ID for every site Optimazz
     41                        // caches, and the site the current request belongs to.
     42                        'multisite'            => false,
     43                        'sites'                => array(),
     44                        'site_id'              => 0,
    4045                );
    4146        }
    … …  
    7782
    7883        /**
     84         * Config for a request to $host + $uri. On a network this resolves the
     85         * site (by host and the longest matching path) and merges that site's own
     86         * rules — exclusions and ignored query args — over the network settings.
     87         * A request for a host/path that no cached site owns gets enabled=false.
     88         *
     89         * @param string $host Host header.
     90         * @param string $uri  Request URI.
     91         * @return array
     92         */
     93        public static function config_for( $host, $uri ) {
     94                $config = self::load_config();
     95                if ( empty( $config['multisite'] ) ) {
     96                        return $config;
     97                }
     98                $id = self::resolve_site( $config, $host, $uri );
     99                if ( ! $id ) {
     100                        $config['enabled'] = false;
     101                        return $config;
     102                }
     103                $config['site_id'] = $id;
     104                $file = self::site_rules_file( $id );
     105                if ( is_readable( $file ) ) {
     106                        $rules = include $file;
     107                        if ( is_array( $rules ) ) {
     108                                foreach ( array( 'ignored_query_args', 'excluded_uris', 'excluded_cookies', 'excluded_user_agents' ) as $key ) {
     109                                        if ( isset( $rules[ $key ] ) && is_array( $rules[ $key ] ) ) {
     110                                                $config[ $key ] = $rules[ $key ];
     111                                        }
     112                                }
     113                        }
     114                }
     115                return $config;
     116        }
     117
     118        /**
     119         * Which network site a host + URI belongs to: the site whose "host/path/"
     120         * key is the longest prefix of the request (so /shop/… goes to the /shop/
     121         * site, everything else on that host to the main site).
     122         *
     123         * @param array  $config Config with a 'sites' map.
     124         * @param string $host   Host header.
     125         * @param string $uri    Request URI.
     126         * @return int Site ID, or 0 when no cached site matches.
     127         */
     128        public static function resolve_site( array $config, $host, $uri ) {
     129                // Host as sent (a port is kept, matching how the cache folder is named
     130                // and how WordPress stores a site's domain).
     131                $host = strtolower( (string) $host );
     132                $path = (string) strtok( (string) $uri, '?' );
     133                $path = '/' . ltrim( $path, '/' );
     134                $key  = $host . rtrim( $path, '/' ) . '/';
     135                $best = 0;
     136                $len  = 0;
     137                foreach ( (array) $config['sites'] as $prefix => $id ) {
     138                        $prefix = (string) $prefix;
     139                        if ( strlen( $prefix ) > $len && 0 === strpos( $key, $prefix ) ) {
     140                                $best = (int) $id;
     141                                $len  = strlen( $prefix );
     142                        }
     143                }
     144                return $best;
     145        }
     146
     147        /**
     148         * Host of a parsed URL as a browser sends it in the Host header — with the
     149         * port when there is one. Cache folders and the network site map are keyed
     150         * on that, so URL-based lookups (purge, preload) must use it too.
     151         *
     152         * @param array $parts parse_url() result.
     153         * @return string
     154         */
     155        public static function url_host( array $parts ) {
     156                $host = isset( $parts['host'] ) ? strtolower( (string) $parts['host'] ) : '';
     157                return ( '' !== $host && ! empty( $parts['port'] ) ) ? $host . ':' . (int) $parts['port'] : $host;
     158        }
     159
     160        /**
     161         * File holding one network site's own cache rules.
     162         *
     163         * @param int $id Site ID.
     164         * @return string
     165         */
     166        public static function site_rules_file( $id ) {
     167                return self::cache_root() . 'sites/' . (int) $id . '.php';
     168        }
     169
     170        /**
     171         * Write one network site's own cache rules (only changes when they differ).
     172         *
     173         * @param int   $id    Site ID.
     174         * @param array $rules Rules: ignored_query_args, excluded_uris, excluded_cookies, excluded_user_agents.
     175         * @return bool
     176         */
     177        public static function write_site_rules( $id, array $rules ) {
     178                $file = self::site_rules_file( $id );
     179                // var_export serialises to a fast include file, not for debugging.
     180                $code = "<?php\n// Generated by Optimazz. Do not edit.\nreturn " . var_export( $rules, true ) . ";\n"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
     181                if ( is_readable( $file ) && (string) @file_get_contents( $file ) === $code ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions
     182                        return true;
     183                }
     184                self::mkdir( dirname( $file ) );
     185                self::protect_dir( self::cache_root() );
     186                return self::atomic_write( $file, $code );
     187        }
     188
     189        /**
    79190         * Persist config to disk as a returnable PHP array (fast to include).
    80191         *
    … …  
    87198                // var_export is used to serialize the config to a fast include file, not for debugging.
    88199                $code = "<?php\n// Generated by Optimazz. Do not edit.\nreturn " . var_export( $config, true ) . ";\n"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
     200                if ( is_readable( self::config_file() ) && (string) @file_get_contents( self::config_file() ) === $code ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions
     201                        return true; // Unchanged: keep the compiled copy (no opcode-cache churn).
     202                }
    89203                return self::atomic_write( self::config_file(), $code );
    90204        }
    … …  
    144258         */
    145259        public static function serve() {
    146                 $config = self::load_config();
     260                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- pre-WP drop-in; only used to pick a site and build a sanitized cache key.
     261                $config = self::config_for( isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '', isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/' );
    147262
    148263                if ( empty( $config['enabled'] ) ) {
    … …  
    167282                                header( 'Content-Type: text/html; charset=UTF-8' );
    168283                                readfile( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions
    169                                 self::maybe_kick_cron();
     284                                self::maybe_kick_cron( (int) $config['site_id'] );
    170285                                exit;
    171286                        }
    … …  
    181296         * A .php file, so the cache directory's access rules keep it private.
    182297         *
     298         * @param int $site_id Network site ID (0 on a single site).
    183299         * @return string
    184300         */
    185         private static function cron_due_file() {
    186                 return self::cache_root() . 'cron-due.php';
     301        private static function cron_due_file( $site_id = 0 ) {
     302                return self::cache_root() . ( $site_id ? 'cron-due-' . (int) $site_id . '.php' : 'cron-due.php' );
    187303        }
    188304
    … …  
    193309         * @param int    $timestamp Unix time of the next run (0 = none pending).
    194310         * @param string $cron_url  Absolute URL of wp-cron.php.
    195          * @return void
    196          */
    197         public static function write_cron_due( $timestamp, $cron_url ) {
     311         * @param int    $site_id   Network site ID (0 on a single site).
     312         * @return void
     313         */
     314        public static function write_cron_due( $timestamp, $cron_url, $site_id = 0 ) {
    198315                $data = array(
    199316                        'due' => (int) $timestamp,
    200317                        'url' => (string) $cron_url,
    201318                );
    202                 $file = self::cron_due_file();
     319                $file = self::cron_due_file( $site_id );
    203320                if ( is_readable( $file ) ) {
    204321                        $current = include $file;
    … …  
    224341         * after the page has been handed to the visitor where the server allows.
    225342         *
    226          * @return void
    227          */
    228         private static function maybe_kick_cron() {
    229                 $file = self::cron_due_file();
     343         * @param int $site_id Network site ID of the request (0 on a single site).
     344         * @return void
     345         */
     346        private static function maybe_kick_cron( $site_id = 0 ) {
     347                $file = self::cron_due_file( $site_id );
    230348                if ( ! is_readable( $file ) ) {
    231349                        return;
    … …  
    235353                        return;
    236354                }
    237                 $lock = self::cache_root() . 'cron.lock';
     355                $lock = self::cache_root() . ( $site_id ? 'cron-' . (int) $site_id . '.lock' : 'cron.lock' );
    238356                if ( file_exists( $lock ) && ( time() - (int) @filemtime( $lock ) ) < 60 ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors
    239357                        return;
    … …  
    464582        public static function path_for_request( array $config ) {
    465583                // phpcs:disable WordPress.Security.ValidatedSanitizedInput -- pre-WP drop-in; values feed a sanitized cache key only.
    466                 $host   = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : 'localhost';
     584                $host   = isset( $_SERVER['HTTP_HOST'] ) ? strtolower( $_SERVER['HTTP_HOST'] ) : 'localhost'; // Hosts are case-insensitive; one folder per host.
    467585                $uri    = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/';
    468586                $scheme = ( ! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] ) ? 'https' : 'http';
    … …  
    588706                }
    589707                $parts    = function_exists( 'wp_parse_url' ) ? wp_parse_url( $url ) : parse_url( $url ); // phpcs:ignore WordPress.WP.AlternativeFunctions
    590                 $host     = isset( $parts['host'] ) ? $parts['host'] : '';
     708                $host     = self::url_host( $parts );
    591709                $path     = isset( $parts['path'] ) ? $parts['path'] : '/';
    592710                $path     = str_replace( array( '..', "\0" ), '', $path );
    … …  
    595713                $dir      = self::cache_root() . self::sanitize_segment( $host ) . rtrim( $path, '/' ) . '/';
    596714
    597                 // The homepage's variants live directly in the host dir alongside every
    598                 // other page's SUBdirectory. Recursively deleting the host dir would wipe
    599                 // the whole site cache — so for the homepage, remove only its own .html
    600                 // variant files and leave the per-page subdirectories intact.
    601                 if ( $dir === $host_dir ) {
    602                         foreach ( (array) glob( $host_dir . '*.html' ) as $file ) {
     715                // A site's front page has its variants directly in the site's root
     716                // folder, alongside every other page's SUBdirectory. Recursively deleting
     717                // that folder would wipe the whole site — so for a front page (the host
     718                // root, or a network site's own root like /shop/) remove only its own
     719                // .html variant files and leave the per-page subdirectories intact.
     720                if ( $dir === $host_dir || in_array( $dir, self::site_roots(), true ) ) {
     721                        foreach ( (array) glob( $dir . '*.html' ) as $file ) {
    603722                                wp_delete_file( $file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
    604723                        }
    … …  
    628747                //    HTML while keeping the hashed assets keeps both sides consistent.
    629748                //    (Stale assets are cleared deliberately when CSS/JS settings change.)
    630                 $keep = array( '.', '..', 'config.php', 'index.html', '.htaccess', 'css', 'js', 'cron-due.php', 'cron.lock' );
     749                $keep = array( '.', '..', 'config.php', 'index.html', '.htaccess', 'css', 'js', 'cron-due.php', 'cron.lock', 'sites' );
    631750                foreach ( (array) scandir( $root ) as $item ) {
    632                         if ( in_array( $item, $keep, true ) ) {
     751                        if ( in_array( $item, $keep, true ) || preg_match( '/^cron(?:-due)?-\d+\.(?:php|lock)$/', (string) $item ) ) {
    633752                                continue;
    634753                        }
    … …  
    642761        }
    643762
     763        /**
     764         * Remove cache folders named after a mixed-case Host header. Since 1.1.0
     765         * hosts are lowercased, so such folders are never served or purged again.
     766         *
     767         * @return void
     768         */
     769        public static function remove_mixed_case_hosts() {
     770                $root = self::cache_root();
     771                if ( ! is_dir( $root ) ) {
     772                        return;
     773                }
     774                foreach ( (array) scandir( $root ) as $item ) {
     775                        if ( '.' !== $item && '..' !== $item && is_dir( $root . $item ) && strtolower( $item ) !== $item ) {
     776                                self::rrmdir( $root . $item . '/' );
     777                        }
     778                }
     779        }
     780
     781        /**
     782         * Cache folders of every network site's root ("<root>/<host>/<path>/").
     783         *
     784         * @return string[]
     785         */
     786        public static function site_roots() {
     787                $config = self::load_config();
     788                $roots  = array();
     789                foreach ( array_keys( (array) $config['sites'] ) as $key ) {
     790                        $parts   = explode( '/', (string) $key, 2 );
     791                        $path    = isset( $parts[1] ) ? trim( $parts[1], '/' ) : '';
     792                        $roots[] = self::cache_root() . self::sanitize_segment( $parts[0] ) . ( '' !== $path ? '/' . $path : '' ) . '/';
     793                }
     794                return $roots;
     795        }
     796
     797        /**
     798         * Cache folder of a site (host + path), e.g. "<root>/example.com/shop/".
     799         *
     800         * @param string $host Site host.
     801         * @param string $path Site path ('/' or '/shop/').
     802         * @return string
     803         */
     804        public static function site_dir( $host, $path ) {
     805                $path = trim( str_replace( array( '..', "\0" ), '', (string) $path ), '/' );
     806                $path = preg_replace( '#[^a-zA-Z0-9/_\-.]#', '-', $path );
     807                return self::cache_root() . self::sanitize_segment( strtolower( (string) $host ) ) . ( '' !== $path ? '/' . $path : '' ) . '/';
     808        }
     809
     810        /**
     811         * Purge one network site's pages without touching sites nested inside its
     812         * folder (the main site "/" contains the folder of a subdirectory site).
     813         *
     814         * @param string $dir Site cache folder (from site_dir()).
     815         * @return void
     816         */
     817        public static function purge_site_dir( $dir ) {
     818                $dir = rtrim( (string) $dir, '/' ) . '/';
     819                if ( 0 !== strpos( $dir, self::cache_root() ) || ! is_dir( $dir ) ) {
     820                        return;
     821                }
     822                $others = array_values( array_diff( self::site_roots(), array( $dir ) ) );
     823                self::purge_tree( $dir, $others );
     824        }
     825
     826        /**
     827         * Delete cached pages under $dir, skipping (and descending towards) the
     828         * folders of other sites.
     829         *
     830         * @param string   $dir    Folder with trailing slash.
     831         * @param string[] $others Other sites' root folders.
     832         * @return void
     833         */
     834        private static function purge_tree( $dir, array $others ) {
     835                foreach ( (array) scandir( $dir ) as $item ) {
     836                        if ( '.' === $item || '..' === $item ) {
     837                                continue;
     838                        }
     839                        $path = $dir . $item;
     840                        if ( ! is_dir( $path ) ) {
     841                                if ( '.html' === substr( $item, -5 ) && 'index.html' !== $item ) {
     842                                        wp_delete_file( $path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
     843                                }
     844                                continue;
     845                        }
     846                        $sub = $path . '/';
     847                        if ( in_array( $sub, $others, true ) ) {
     848                                continue; // Another site's folder.
     849                        }
     850                        $contains_other = false;
     851                        foreach ( $others as $other ) {
     852                                if ( 0 === strpos( $other, $sub ) ) {
     853                                        $contains_other = true;
     854                                        break;
     855                                }
     856                        }
     857                        if ( $contains_other ) {
     858                                self::purge_tree( $sub, $others );
     859                        } else {
     860                                self::rrmdir( $sub );
     861                        }
     862                }
     863        }
     864
    644865        /* --------------------------------------------------------------------- *
    645866         *  Filesystem helpers
    … …  
    684905                        return false;
    685906                }
     907                self::forget_compiled( $file );
    686908                return true;
     909        }
     910
     911        /**
     912         * Make PHP's opcode cache drop its compiled copy of a file we just
     913         * rewrote. Config and rules files are PHP includes: without this, a server
     914         * that only re-checks file times every few seconds (or never, with
     915         * opcache.validate_timestamps=0) keeps running the OLD settings — caching
     916         * stays off after being switched on, or a new exclusion is ignored.
     917         *
     918         * @param string $file Absolute path.
     919         * @return void
     920         */
     921        public static function forget_compiled( $file ) {
     922                if ( function_exists( 'opcache_invalidate' ) ) {
     923                        @opcache_invalidate( $file, true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- may be restricted by opcache.restrict_api.
     924                }
    687925        }
    688926
  • optimazz/tags/1.1.0/includes/Modules/Fonts/FontsModule.php

    r3715895 r3743505  
    2121
    2222class FontsModule extends AbstractModule {
     23
     24        /**
     25         * Whether parse_css2() repaired an invalid (CSS1-style) family request on
     26         * the current page, so the rebuilt URL must be output even when nothing
     27         * else changes.
     28         *
     29         * @var bool
     30         */
     31        private static $repaired = false;
    2332
    2433        public function id() {
    … …  
    157166                        return $html;
    158167                }
     168                self::$repaired = false;
    159169
    160170                $links    = array(); // Tags we will merge (in document order).
    … …  
    173183                        }
    174184                        $query  = (string) wp_parse_url( html_entity_decode( $m[2], ENT_QUOTES ), PHP_URL_QUERY ); // decode first: "&#038;" contains a "#" that would truncate the query.
     185                        $was    = self::$repaired;
    175186                        $parsed = 'css2' === strtolower( $m[3] ) ? self::parse_css2( $query ) : self::parse_css1( $query );
    176187                        if ( null === $parsed ) {
     188                                self::$repaired = $was; // This link is left as-is, so nothing was repaired.
    177189                                continue; // unsupported form — keep as-is.
    178190                        }
    … …  
    197209                }
    198210                $trim = (bool) $this->option( 'trim_weights', true );
    199                 // One link and nothing to trim — nothing to gain.
    200                 if ( 1 === count( $links ) && ! $trim ) {
     211                $disp = '' !== $display ? $display : $orig_display;
     212                // Non-blocking load only with swap / fallback. Those swap the font in
     213                // anyway; 'optional' is chosen for zero layout shift, and loading its
     214                // stylesheet late would make a cached font swap in after first paint.
     215                $async = in_array( $disp, array( 'swap', 'fallback' ), true ) && $this->option( 'async_google', true );
     216                // One link, nothing to trim, nothing repaired and no async loading —
     217                // rebuilding the URL would gain nothing (and would drop its other
     218                // attributes and parameters).
     219                if ( 1 === count( $links ) && ! $trim && ! $async && ! self::$repaired ) {
    201220                        return $html;
    202221                }
    … …  
    208227                        $params[] = 'family=' . str_replace( '%20', '+', rawurlencode( $name ) ) . self::css2_axis( $spec, $used );
    209228                }
    210                 $disp = '' !== $display ? $display : $orig_display;
    211229                if ( '' !== $disp ) {
    212230                        $params[] = 'display=' . $disp;
    213231                }
    214                 $url = 'https://fonts.googleapis.com/css2?' . implode( '&', $params );
    215                 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- rewriting existing output-buffer markup, not enqueueing.
    216                 $new = '<link rel="stylesheet" id="optimazz-google-fonts" href="' . esc_url( $url ) . '" media="all">';
     232                $url  = esc_url( 'https://fonts.googleapis.com/css2?' . implode( '&', $params ) );
     233                // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- rewriting existing output-buffer markup, not enqueueing.
     234                if ( $async ) {
     235                        // With swap/fallback the text renders in a fallback font first
     236                        // anyway, so the font stylesheet need not block the page: on
     237                        // mobile it otherwise holds back the first paint by the full round
     238                        // trip to Google. Preload it, apply it when loaded, and keep a
     239                        // <noscript> copy for visitors without JavaScript.
     240                        $new = '<link rel="preload" as="style" href="' . $url . '">'
     241                                . '<link rel="stylesheet" id="optimazz-google-fonts" href="' . $url . '" media="print" onload="this.media=\'all\'">'
     242                                . '<noscript><link rel="stylesheet" href="' . $url . '"></noscript>';
     243                } else {
     244                        $new = '<link rel="stylesheet" id="optimazz-google-fonts" href="' . $url . '" media="all">';
     245                }
     246                // phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
    217247
    218248                // Swap the first link for the merged one; remove the rest (each once).
    … …  
    252282                                        continue;
    253283                                }
    254                                 $spec = array( 'normal' => array(), 'italic' => array(), 'raw' => null );
    255                                 $vars = isset( $parts[1] ) ? explode( ',', $parts[1] ) : array( '400' );
    256                                 foreach ( $vars as $v ) {
    257                                         $v = strtolower( trim( $v ) );
    258                                         if ( '' === $v ) {
    259                                                 continue;
    260                                         }
    261                                         $v    = strtr( $v, array( 'regular' => '400', 'bold' => '700' ) );
    262                                         $ital = (bool) preg_match( '/(italic|i)$/', $v );
    263                                         $w    = (int) preg_replace( '/\D/', '', $v );
    264                                         $w    = $w ? $w : 400; // bare "italic".
    265                                         if ( $w < 100 || $w > 900 ) {
    266                                                 continue;
    267                                         }
    268                                         $spec[ $ital ? 'italic' : 'normal' ][ $w ] = 1;
    269                                 }
    270                                 if ( empty( $spec['normal'] ) && empty( $spec['italic'] ) ) {
    271                                         $spec['normal'][400] = 1;
    272                                 }
    273                                 $out[ $name ] = $spec;
     284                                $out[ $name ] = self::parse_variants( isset( $parts[1] ) ? $parts[1] : '400' );
    274285                        }
    275286                }
    276287                return $out ? $out : null;
     288        }
     289
     290        /**
     291         * Parse a CSS1-style variant list ("400,700i,bold,italic") into a spec.
     292         *
     293         * @param string $list Comma-separated variants.
     294         * @return array{normal:array,italic:array,raw:null}
     295         */
     296        private static function parse_variants( $list ) {
     297                $spec = array( 'normal' => array(), 'italic' => array(), 'raw' => null );
     298                foreach ( explode( ',', $list ) as $v ) {
     299                        $v = strtolower( trim( $v ) );
     300                        if ( '' === $v ) {
     301                                continue;
     302                        }
     303                        $v    = strtr( $v, array( 'regular' => '400', 'bold' => '700' ) );
     304                        $ital = (bool) preg_match( '/(italic|i)$/', $v );
     305                        $w    = (int) preg_replace( '/\D/', '', $v );
     306                        $w    = $w ? $w : 400; // bare "italic".
     307                        if ( $w < 100 || $w > 900 ) {
     308                                continue;
     309                        }
     310                        $spec[ $ital ? 'italic' : 'normal' ][ $w ] = 1;
     311                }
     312                if ( empty( $spec['normal'] ) && empty( $spec['italic'] ) ) {
     313                        $spec['normal'][400] = 1;
     314                }
     315                return $spec;
    277316        }
    278317
    … …  
    314353                                        $spec['normal'][ (int) $w ] = 1;
    315354                                }
     355                        } elseif ( preg_match( '/^(?:w?\d{3}i?|regular|bold|italic|bolditalic|\d{3}italic)(?:,(?:w?\d{3}i?|regular|bold|italic|bolditalic|\d{3}italic))*$/i', trim( $parts[1] ) ) ) {
     356                                // Old CSS1-style weights ("400,700", or the "w400,700" some themes
     357                                // print) sent to the CSS2 API. Google rejects these with HTTP 400, so
     358                                // the font never loads while the broken stylesheet still blocks
     359                                // rendering. Read them the CSS1 way so a valid request is built.
     360                                $spec           = self::parse_variants( str_ireplace( 'w', '', $parts[1] ) );
     361                                self::$repaired = true;
    316362                        } else {
    317363                                $spec['raw'] = $parts[1]; // variable ranges / other axes — keep verbatim.
  • optimazz/tags/1.1.0/includes/Modules/Js/JsModule.php

    r3692106 r3743505  
    239239                        'tawk.to', 'crisp.chat', 'widget.intercom.io', 'js.intercomcdn.com',
    240240                        'code.tidio.co', 'cdn.livechatinc.com', 'embed.tawk.to', 'messenger/customerchat',
     241                        'cdn.openwidget.com', 'static.zdassets.com', 'js.driftt.com', 'wchat.freshchat.com',
     242                        'widget.freshworks.com', 'code.jivosite.com', 'smartsuppchat.com', 'embed.small.chat',
    241243                        // Marketing / CRM.
    242244                        'js.hs-scripts.com', 'js.hsforms.net', 'cdn.onesignal.com',
  • optimazz/tags/1.1.0/languages/optimazz.pot

    r3723128 r3743505  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Optimazz – Website Optimization and Speedup 1.0.24\n"
     5"Project-Id-Version: Optimazz – Website Optimization and Speedup 1.1.0\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/optimazz\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-01T12:01:49+00:00\n"
     12"POT-Creation-Date: 2026-10-11T19:05:58+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    4040msgstr ""
    4141
    42 #: includes/Admin/Admin.php:149
    43 #: includes/Admin/Admin.php:150
     42#: includes/Admin/Admin.php:155
     43#: includes/Admin/Admin.php:156
     44#: includes/Admin/Admin.php:290
     45#: includes/Admin/Admin.php:291
    4446msgid "Optimazz"
    4547msgstr ""
    4648
    47 #: includes/Admin/Admin.php:166
     49#: includes/Admin/Admin.php:172
    4850#: src/index.js:36
    4951msgid "Dashboard"
    5052msgstr ""
    5153
    52 #: includes/Admin/Admin.php:167
     54#: includes/Admin/Admin.php:173
    5355#: includes/Modules/Cache/CacheModule.php:24
    5456#: src/index.js:37
    55 #: src/index.js:1171
     57#: src/index.js:1317
     58#: src/index.js:2075
     59#: src/index.js:2091
    5660msgid "Page Cache"
    5761msgstr ""
    5862
    59 #: includes/Admin/Admin.php:168
     63#: includes/Admin/Admin.php:174
    6064#: src/index.js:38
    61 #: src/index.js:1150
     65#: src/index.js:1296
    6266msgid "File Optimization"
    6367msgstr ""
    6468
    65 #: includes/Admin/Admin.php:169
     69#: includes/Admin/Admin.php:175
    6670#: src/index.js:39
    6771msgid "Media"
    6872msgstr ""
    6973
    70 #: includes/Admin/Admin.php:170
     74#: includes/Admin/Admin.php:176
    7175#: includes/Modules/Database/DatabaseModule.php:26
    7276#: src/index.js:43
    … …  
    7478msgstr ""
    7579
    76 #: includes/Admin/Admin.php:171
    77 #: includes/Admin/Admin.php:229
    78 #: includes/Admin/Admin.php:233
     80#: includes/Admin/Admin.php:177
     81#: includes/Admin/Admin.php:235
     82#: includes/Admin/Admin.php:239
    7983#: src/index.js:47
    8084msgid "Tools"
    8185msgstr ""
    8286
    83 #: includes/Admin/Admin.php:193
     87#: includes/Admin/Admin.php:199
    8488#: includes/Admin/AdminBar.php:52
     89#: src/index.js:2198
    8590msgid "Settings"
    8691msgstr ""
    8792
    88 #: includes/Admin/Admin.php:224
     93#: includes/Admin/Admin.php:230
    8994msgid "Optimazz Safe Mode is on."
    9095msgstr ""
    9196
    92 #: includes/Admin/Admin.php:225
     97#: includes/Admin/Admin.php:231
    9398msgid "Some pages were loading without styling — their optimized CSS could not be found, usually because a server or CDN cache was serving old pages. Optimazz paused advanced CSS/JS optimization to keep your site working."
    9499msgstr ""
    95100
    96 #: includes/Admin/Admin.php:227
     101#: includes/Admin/Admin.php:233
    97102msgid "It broke again shortly after full optimization was restored, so Safe Mode will stay on until you switch it off in"
    98103msgstr ""
    99104
    100 #: includes/Admin/Admin.php:228
     105#: includes/Admin/Admin.php:234
    101106msgid "It re-checks automatically and restores full optimization as soon as pages load correctly — clearing your server/CDN cache speeds that up. You can also manage it in"
    102107msgstr ""
    103108
    104 #: includes/Admin/Admin.php:232
     109#: includes/Admin/Admin.php:238
    105110msgid "Your pages are loading correctly again — you can switch Safe Mode off in"
    106111msgstr ""
    107112
    108 #: includes/Admin/Admin.php:236
     113#: includes/Admin/Admin.php:242
    109114msgid "Pages are loading correctly again, so Safe Mode was switched off automatically and full optimization is back on."
    110115msgstr ""
    111116
    112 #: includes/Admin/Admin.php:250
     117#: includes/Admin/Admin.php:256
    113118msgid "wp-config.php is not writable, so page caching could not be enabled. Add"
    114119msgstr ""
    115120
    116 #: includes/Admin/Admin.php:252
     121#: includes/Admin/Admin.php:258
    117122msgid "manually, or make wp-config.php writable."
    118123msgstr ""
    119124
    120 #: includes/Admin/Admin.php:259
     125#: includes/Admin/Admin.php:265
    121126msgid "Optimazz cache purged."
    122127msgstr ""
    123128
    124 #: includes/Admin/Admin.php:260
     129#: includes/Admin/Admin.php:266
    125130msgid "Optimazz cache preload started in the background."
    126131msgstr ""
    127132
    128 #: includes/Admin/Admin.php:261
     133#: includes/Admin/Admin.php:267
    129134msgid "Optimazz cache cleared; preload started in the background."
    130135msgstr ""
    131136
    132 #: includes/Admin/AdminBar.php:53
     137#: includes/Admin/AdminBar.php:57
    133138msgid "Clear & Preload Cache"
    134139msgstr ""
    135140
    136 #: includes/Admin/AdminBar.php:54
    137 #: src/index.js:1451
     141#: includes/Admin/AdminBar.php:58
     142#: src/index.js:1597
    138143msgid "Preload Cache"
    139144msgstr ""
    140145
    141 #: includes/Admin/AdminBar.php:55
     146#: includes/Admin/AdminBar.php:59
    142147msgid "Purge Cache"
    143148msgstr ""
    144149
    145 #: includes/Admin/AdminBar.php:73
     150#: includes/Admin/AdminBar.php:77
    146151msgid "Documentation"
    147152msgstr ""
    148153
    149 #: includes/Admin/AdminBar.php:98
    150 #: includes/Admin/CacheConflictNotice.php:124
    151 #: includes/Admin/CacheConflictNotice.php:154
     154#: includes/Admin/AdminBar.php:102
     155#: includes/Admin/CacheConflictNotice.php:126
     156#: includes/Admin/CacheConflictNotice.php:156
    152157msgid "Not allowed."
    153158msgstr ""
    … …  
    162167
    163168#: includes/Admin/CacheConflictNotice.php:88
    164 #: includes/Modules/Cache/Preloader.php:714
     169#: includes/Modules/Cache/Preloader.php:740
     170#: src/index.js:2136
    165171msgid "Another plugin"
    166172msgstr ""
    … …  
    180186#: includes/Admin/CacheConflictNotice.php:108
    181187#: includes/Admin/CacheConflictNotice.php:110
     188#: src/index.js:2140
    182189msgid "Use Optimazz for caching"
    183190msgstr ""
    … …  
    187194msgstr ""
    188195
    189 #: includes/Admin/RestController.php:467
     196#: includes/Admin/NetworkRestController.php:115
     197#: includes/Admin/NetworkRestController.php:134
     198msgid "Page caching is off for this site."
     199msgstr ""
     200
     201#: includes/Admin/NetworkRestController.php:153
     202msgid "Optimazz is not active on any site yet. Network-activate it (or activate it on a site) first."
     203msgstr ""
     204
     205#: includes/Admin/RestController.php:385
     206#: includes/Admin/RestController.php:588
     207#: includes/Admin/RestController.php:614
     208msgid "Page caching is not available on multisite networks."
     209msgstr ""
     210
     211#: includes/Admin/RestController.php:479
    190212msgid "PHP version"
    191213msgstr ""
    192214
    193 #: includes/Admin/RestController.php:470
     215#: includes/Admin/RestController.php:482
    194216msgid "Optimazz requires PHP 7.4 or newer."
    195217msgstr ""
    196218
    197 #: includes/Admin/RestController.php:473
     219#: includes/Admin/RestController.php:485
    198220msgid "WordPress version"
    199221msgstr ""
    200222
    201 #: includes/Admin/RestController.php:479
     223#: includes/Admin/RestController.php:491
    202224msgid "GD (image library)"
    203225msgstr ""
    204226
    205 #: includes/Admin/RestController.php:480
    206 #: includes/Admin/RestController.php:489
    207 #: includes/Admin/RestController.php:525
     227#: includes/Admin/RestController.php:492
     228#: includes/Admin/RestController.php:501
     229#: includes/Admin/RestController.php:537
    208230msgid "Enabled"
    209231msgstr ""
    210232
    211 #: includes/Admin/RestController.php:480
    212 #: includes/Admin/RestController.php:489
     233#: includes/Admin/RestController.php:492
     234#: includes/Admin/RestController.php:501
    213235msgid "Not installed (optional)"
    214236msgstr ""
    215237
    216 #: includes/Admin/RestController.php:484
     238#: includes/Admin/RestController.php:496
    217239msgid "GD is enabled but without WebP support — ask your host to enable WebP in GD."
    218240msgstr ""
    219241
    220 #: includes/Admin/RestController.php:485
     242#: includes/Admin/RestController.php:497
    221243msgid "The PHP GD extension is not enabled. GD is the most common engine for image compression and WebP. Ask your host to enable the \"php-gd\" extension."
    222244msgstr ""
    223245
    224 #: includes/Admin/RestController.php:488
     246#: includes/Admin/RestController.php:500
    225247msgid "ImageMagick (Imagick)"
    226248msgstr ""
    227249
    228 #: includes/Admin/RestController.php:489
     250#: includes/Admin/RestController.php:501
    229251msgid "Not enabled"
    230252msgstr ""
    231253
    232 #: includes/Admin/RestController.php:496
     254#: includes/Admin/RestController.php:508
    233255msgid "Optional — GD is already handling images. Imagick can add AVIF and higher-quality output if your host enables the \"php-imagick\" extension."
    234256msgstr ""
    235257
    236 #: includes/Admin/RestController.php:497
     258#: includes/Admin/RestController.php:509
    237259msgid "The PHP Imagick extension is not enabled either. With neither GD nor Imagick, image compression and WebP/AVIF cannot run. Ask your host to enable \"php-gd\" or \"php-imagick\"."
    238260msgstr ""
    239261
    240 #: includes/Admin/RestController.php:500
     262#: includes/Admin/RestController.php:512
    241263msgid "WebP support"
    242264msgstr ""
    243265
    244 #: includes/Admin/RestController.php:501
    245 #: includes/Admin/RestController.php:507
     266#: includes/Admin/RestController.php:513
     267#: includes/Admin/RestController.php:519
    246268msgid "Available"
    247269msgstr ""
    248270
    249 #: includes/Admin/RestController.php:501
    250 #: includes/Admin/RestController.php:507
     271#: includes/Admin/RestController.php:513
     272#: includes/Admin/RestController.php:519
    251273msgid "Not available"
    252274msgstr ""
    253275
    254 #: includes/Admin/RestController.php:503
     276#: includes/Admin/RestController.php:515
    255277msgid "This server cannot generate WebP images. Update GD/Imagick or ask your host to enable WebP."
    256278msgstr ""
    257279
    258 #: includes/Admin/RestController.php:506
     280#: includes/Admin/RestController.php:518
    259281msgid "AVIF support"
    260282msgstr ""
    261283
    262 #: includes/Admin/RestController.php:509
     284#: includes/Admin/RestController.php:521
    263285msgid "This server cannot generate AVIF images (needs PHP 8.1+ GD with AVIF, or ImageMagick with the AVIF delegate). WebP still works."
    264286msgstr ""
    265287
    266 #: includes/Admin/RestController.php:512
     288#: includes/Admin/RestController.php:524
    267289msgid "PHP memory limit"
    268290msgstr ""
    269291
    270 #: includes/Admin/RestController.php:515
     292#: includes/Admin/RestController.php:527
    271293msgid "Image optimization works best with 256 MB or more. On lower limits Optimazz processes fewer images per request to stay safe."
    272294msgstr ""
    273295
    274 #: includes/Admin/RestController.php:518
     296#: includes/Admin/RestController.php:530
    275297msgid "Max execution time"
    276298msgstr ""
    277299
    278 #: includes/Admin/RestController.php:519
     300#: includes/Admin/RestController.php:531
    279301msgid "Unlimited"
    280302msgstr ""
    281303
    282 #: includes/Admin/RestController.php:521
     304#: includes/Admin/RestController.php:533
    283305msgid "Under 30 seconds can interrupt bulk AVIF conversion of large images."
    284306msgstr ""
    285307
    286 #: includes/Admin/RestController.php:524
     308#: includes/Admin/RestController.php:536
    287309msgid "WP-Cron"
    288310msgstr ""
    289311
    290 #: includes/Admin/RestController.php:525
     312#: includes/Admin/RestController.php:537
    291313msgid "Disabled"
    292314msgstr ""
    293315
    294 #: includes/Admin/RestController.php:527
     316#: includes/Admin/RestController.php:539
    295317msgid "WP-Cron is disabled, so scheduled cache preloading relies on a real system cron job being configured."
    296318msgstr ""
    297319
    298 #: includes/Admin/RestController.php:541
     320#: includes/Admin/RestController.php:553
    299321msgid "GD and Imagick are"
    300322msgstr ""
    301323
    302 #: includes/Admin/RestController.php:542
     324#: includes/Admin/RestController.php:554
    303325msgid "GD is"
    304326msgstr ""
    305327
    306 #: includes/Admin/RestController.php:542
     328#: includes/Admin/RestController.php:554
    307329msgid "Imagick is"
    308330msgstr ""
    309331
    310 #: includes/Admin/RestController.php:544
     332#: includes/Admin/RestController.php:556
    311333msgid "PHP that runs this site"
    312334msgstr ""
    313335
    314336#. translators: 1: which engine is missing, 2: php.ini path, 3: PHP version
    315 #: includes/Admin/RestController.php:550
     337#: includes/Admin/RestController.php:562
    316338#, php-format
    317339msgid "This is the exact PHP handling your site — it loads its settings from %2$s. If you already enabled an image library in your hosting panel but %1$s still shown as not enabled, you almost certainly enabled it for a different PHP version or handler. It must be enabled for THIS PHP (%3$s). After enabling, restart PHP (or PHP-FPM) and click Re-check."
    … …  
    319341
    320342#. translators: 1: which engine is missing, 2: PHP version
    321 #: includes/Admin/RestController.php:557
     343#: includes/Admin/RestController.php:569
    322344#, php-format
    323345msgid "This is the exact PHP handling your site. If you already enabled an image library in your hosting panel but %1$s still shown as not enabled, you enabled it for a different PHP version. It must be enabled for THIS PHP (%2$s). After enabling, restart PHP and click Re-check."
    324346msgstr ""
    325347
    326 #: includes/Admin/RestController.php:707
     348#: includes/Admin/RestController.php:725
    327349msgid "Image processing is unavailable: this server has no GD or ImageMagick extension. Ask your host to enable \"php-gd\" (or \"php-imagick\"), then try again. See Tools → Server Requirements."
    328350msgstr ""
    … …  
    336358msgstr ""
    337359
    338 #: includes/Core/Recommendations.php:61
    339 #: src/index.js:971
     360#: includes/Core/Recommendations.php:63
     361#: src/index.js:1117
    340362msgid "Enable Page Cache"
    341363msgstr ""
    342364
    343 #: includes/Core/Recommendations.php:62
     365#: includes/Core/Recommendations.php:64
    344366msgid "Serve static HTML instead of running PHP on every request — the single biggest speed win."
    345367msgstr ""
    346368
    347 #: includes/Core/Recommendations.php:67
     369#: includes/Core/Recommendations.php:69
    348370msgid "Turn on cache preloading"
    349371msgstr ""
    350372
    351 #: includes/Core/Recommendations.php:68
     373#: includes/Core/Recommendations.php:70
    352374msgid "Warm the cache automatically so visitors never hit a cold, uncached page."
    353375msgstr ""
    354376
    355 #: includes/Core/Recommendations.php:76
     377#: includes/Core/Recommendations.php:78
    356378msgid "Enable JavaScript optimization"
    357379msgstr ""
    358380
    359 #: includes/Core/Recommendations.php:77
     381#: includes/Core/Recommendations.php:79
    360382msgid "Defer and delay scripts to cut Total Blocking Time — the heaviest-weighted mobile metric."
    361383msgstr ""
    362384
    363 #: includes/Core/Recommendations.php:84
     385#: includes/Core/Recommendations.php:86
    364386msgid "Delay JavaScript until interaction"
    365387msgstr ""
    366388
    367 #: includes/Core/Recommendations.php:85
     389#: includes/Core/Recommendations.php:87
    368390msgid "Delay non-critical scripts (analytics, chat, pixels) until the first interaction to reduce blocking time."
    369391msgstr ""
    370392
    371 #: includes/Core/Recommendations.php:93
     393#: includes/Core/Recommendations.php:95
    372394msgid "Enable CSS optimization"
    373395msgstr ""
    374396
    375 #: includes/Core/Recommendations.php:94
     397#: includes/Core/Recommendations.php:96
    376398msgid "Minify CSS to remove render-blocking bytes and speed up First Contentful Paint."
    377399msgstr ""
    378400
    379 #: includes/Core/Recommendations.php:102
     401#: includes/Core/Recommendations.php:104
    380402msgid "Enable Image optimization"
    381403msgstr ""
    382404
    383 #: includes/Core/Recommendations.php:103
     405#: includes/Core/Recommendations.php:105
    384406msgid "Lazy-load images, reserve their space (to prevent layout shift) and serve next-gen formats."
    385407msgstr ""
    386408
    387 #: includes/Core/Recommendations.php:109
     409#: includes/Core/Recommendations.php:111
    388410msgid "Reserve image space (fix layout shift)"
    389411msgstr ""
    390412
    391 #: includes/Core/Recommendations.php:110
     413#: includes/Core/Recommendations.php:112
    392414msgid "Add missing image dimensions and aspect-ratio so images do not push content down as they load (lowers CLS)."
    393415msgstr ""
    394416
    395 #: includes/Core/Recommendations.php:116
    396 #: src/index.js:948
    397 #: src/index.js:1369
     417#: includes/Core/Recommendations.php:118
     418#: src/index.js:1092
     419#: src/index.js:1515
    398420msgid "Lazy-load images"
    399421msgstr ""
    400422
    401 #: includes/Core/Recommendations.php:117
     423#: includes/Core/Recommendations.php:119
    402424msgid "Load off-screen images only when scrolled into view to speed up the initial load."
    403425msgstr ""
    404426
    405 #: includes/Core/Recommendations.php:124
     427#: includes/Core/Recommendations.php:126
    406428msgid "Serve WebP images"
    407429msgstr ""
    408430
    409 #: includes/Core/Recommendations.php:125
     431#: includes/Core/Recommendations.php:127
    410432msgid "Generate and serve WebP copies — typically 25–35% smaller than JPEG/PNG at the same quality."
    411433msgstr ""
    412434
    413 #: includes/Core/Recommendations.php:136
     435#: includes/Core/Recommendations.php:138
    414436msgid "Set font loading to \"Optional\" (reduce CLS)"
    415437msgstr ""
    416438
    417 #: includes/Core/Recommendations.php:137
     439#: includes/Core/Recommendations.php:139
    418440msgid "The \"swap\" mode lets web fonts reflow the page as they load, raising Cumulative Layout Shift. \"Optional\" removes that shift."
    419441msgstr ""
    420442
    421 #: includes/Core/Recommendations.php:143
     443#: includes/Core/Recommendations.php:145
    422444msgid "Enable Font optimization"
    423445msgstr ""
    424446
    425 #: includes/Core/Recommendations.php:144
     447#: includes/Core/Recommendations.php:146
    426448msgid "Preconnect to font hosts and control font-display to reduce render-blocking and layout shift."
    427449msgstr ""
    428450
    429 #: includes/Core/Recommendations.php:152
     451#: includes/Core/Recommendations.php:154
    430452msgid "Enable Database cleanup"
    431453msgstr ""
    432454
    433 #: includes/Core/Recommendations.php:153
     455#: includes/Core/Recommendations.php:155
    434456msgid "Remove revisions, auto-drafts, spam and expired transients, and optimize tables to keep the database lean."
    435457msgstr ""
    436458
    437 #: includes/Core/Recommendations.php:161
     459#: includes/Core/Recommendations.php:163
    438460msgid "Enable an image library on your server"
    439461msgstr ""
    440462
    441 #: includes/Core/Recommendations.php:162
     463#: includes/Core/Recommendations.php:164
    442464msgid "Your server has neither GD nor ImageMagick, so image compression and WebP/AVIF cannot run. Ask your host to enable the \"php-gd\" (or \"php-imagick\") extension. See Tools → Server Requirements."
    443465msgstr ""
    444466
     467#: includes/Modules/Cache/Preloader.php:116
     468msgid "the home page"
     469msgstr ""
     470
    445471#. translators: %d: number of category / tag archive pages.
    446 #: includes/Modules/Cache/Preloader.php:124
     472#: includes/Modules/Cache/Preloader.php:127
    447473#, php-format
    448474msgid "%d category or tag page"
    … …  
    451477msgstr[1] ""
    452478
    453 #: includes/Modules/Cache/Preloader.php:681
     479#: includes/Modules/Cache/Preloader.php:707
    454480msgid "your host's server cache"
    455481msgstr ""
    456482
    457 #: includes/Modules/Cache/Preloader.php:685
     483#: includes/Modules/Cache/Preloader.php:711
    458484msgid "a server or CDN cache"
    459485msgstr ""
    460486
    461 #: includes/Modules/Cache/Preloader.php:704
     487#: includes/Modules/Cache/Preloader.php:730
    462488msgid "The site could not load its own pages (the loopback request failed or timed out). Some hosts block a server from requesting its own website — ask your host to allow it."
    463489msgstr ""
    464490
    465491#. translators: %s: name of another caching plugin.
    466 #: includes/Modules/Cache/Preloader.php:713
     492#: includes/Modules/Cache/Preloader.php:739
    467493#, php-format
    468494msgid "%s controls page caching (advanced-cache.php), so Optimazz cannot save pages. Deactivate it, or use \"Take over caching\" in Cache health."
    469495msgstr ""
    470496
    471 #: includes/Modules/Cache/Preloader.php:718
     497#: includes/Modules/Cache/Preloader.php:744
    472498msgid "The cache file advanced-cache.php is missing. Turn Page Cache off and on again to reinstall it, and make sure the wp-content folder is writable."
    473499msgstr ""
    474500
    475 #: includes/Modules/Cache/Preloader.php:721
     501#: includes/Modules/Cache/Preloader.php:747
    476502msgid "WP_CACHE is not enabled in wp-config.php, so WordPress never loads the cache. Make wp-config.php writable and save the Cache settings, or add define( 'WP_CACHE', true ); yourself."
    477503msgstr ""
    478504
    479505#. translators: %s: name of a server or CDN cache, e.g. "Cloudflare".
    480 #: includes/Modules/Cache/Preloader.php:726
     506#: includes/Modules/Cache/Preloader.php:752
    481507#, php-format
    482508msgid "%s answered from its own cache before WordPress ran, so Optimazz never saw these requests. Purge that cache, or turn it off for HTML pages if you want Optimazz to cache them."
    483509msgstr ""
    484510
    485 #: includes/Modules/Cache/Preloader.php:730
     511#: includes/Modules/Cache/Preloader.php:756
    486512msgid "The Optimazz cache engine did not run for these requests. Usually a server-level cache or proxy (for example LiteSpeed, Varnish or a host cache) answers before WordPress loads. Check Cache health, or ask your host."
    487513msgstr ""
    488514
    489515#. translators: %s: HTTP status code, e.g. 404.
    490 #: includes/Modules/Cache/Preloader.php:737
     516#: includes/Modules/Cache/Preloader.php:763
    491517#, php-format
    492518msgid "Pages answered with HTTP %s instead of 200, and only normal pages are cached. Check that these URLs open correctly."
    493519msgstr ""
    494520
    495 #: includes/Modules/Cache/Preloader.php:742
     521#: includes/Modules/Cache/Preloader.php:768
    496522msgid "Page Cache is switched off."
    497523msgstr ""
    498524
    499 #: includes/Modules/Cache/Preloader.php:743
     525#: includes/Modules/Cache/Preloader.php:769
    500526msgid "Another plugin or the theme marks these pages \"do not cache\" (DONOTCACHEPAGE), so they are skipped. Membership, security or form plugins often do this."
    501527msgstr ""
    502528
    503 #: includes/Modules/Cache/Preloader.php:744
     529#: includes/Modules/Cache/Preloader.php:770
    504530msgid "The web address used to reach the site does not match the WordPress Site Address (for example www vs non-www). Check Settings → General."
    505531msgstr ""
    506532
    507 #: includes/Modules/Cache/Preloader.php:745
     533#: includes/Modules/Cache/Preloader.php:771
    508534msgid "Cache files could not be written. Make the wp-content/cache folder writable (permissions 755)."
    509535msgstr ""
    510536
    511 #: includes/Modules/Cache/Preloader.php:746
     537#: includes/Modules/Cache/Preloader.php:772
    512538msgid "These pages match your \"Never cache these URLs\" setting."
    513539msgstr ""
    514540
    515 #: includes/Modules/Cache/Preloader.php:747
     541#: includes/Modules/Cache/Preloader.php:773
    516542msgid "A cookie on the request matches your \"Never cache when these cookies are set\" setting."
    517543msgstr ""
    518544
    519 #: includes/Modules/Cache/Preloader.php:748
     545#: includes/Modules/Cache/Preloader.php:774
    520546msgid "The preloader's user-agent matches your \"Never cache for these user agents\" setting."
    521547msgstr ""
    522548
    523 #: includes/Modules/Cache/Preloader.php:749
     549#: includes/Modules/Cache/Preloader.php:775
    524550msgid "The pages came back empty or almost empty, so there was nothing to cache."
    525551msgstr ""
    526552
    527 #: includes/Modules/Cache/Preloader.php:750
     553#: includes/Modules/Cache/Preloader.php:776
    528554msgid "WordPress treated the request as logged in, so it was not cached."
    529555msgstr ""
    530556
    531 #: includes/Modules/Cache/Preloader.php:751
     557#: includes/Modules/Cache/Preloader.php:777
    532558msgid "These URLs show a \"not found\" (404) page, which is never cached."
    533559msgstr ""
    534560
    535561#. translators: %s: internal reason code.
    536 #: includes/Modules/Cache/Preloader.php:757
     562#: includes/Modules/Cache/Preloader.php:783
    537563#, php-format
    538564msgid "The cache skipped these pages (reason: %s)."
    … …  
    552578msgstr ""
    553579
    554 #: includes/Modules/Fonts/FontsModule.php:29
     580#: includes/Modules/Fonts/FontsModule.php:38
    555581#: src/index.js:40
    556 #: src/index.js:1570
     582#: src/index.js:1726
    557583msgid "Fonts"
    558584msgstr ""
    … …  
    568594
    569595#: includes/Modules/Media/MediaModule.php:75
    570 #: src/index.js:1359
     596#: src/index.js:1505
    571597msgid "Image Optimization"
    572598msgstr ""
    … …  
    578604#: includes/Modules/Tweaks/TweaksModule.php:25
    579605#: src/index.js:45
    580 #: src/index.js:1679
     606#: src/index.js:1841
    581607msgid "Tweaks"
    582608msgstr ""
    … …  
    603629
    604630#: src/index.js:41
    605 #: src/index.js:1055
     631#: src/index.js:1201
     632#: src/index.js:2191
    606633msgid "Preload"
    607634msgstr ""
    … …  
    612639
    613640#: src/index.js:42
    614 #: src/index.js:1482
     641#: src/index.js:1638
    615642msgid "Advanced Rules"
    616643msgstr ""
    … …  
    640667msgstr ""
    641668
    642 #: src/index.js:64
     669#: src/index.js:81
     670msgid "every hour"
     671msgstr ""
     672
     673#: src/index.js:82
     674#: src/index.js:84
     675msgid "twice a day"
     676msgstr ""
     677
     678#: src/index.js:83
     679msgid "once a day"
     680msgstr ""
     681
     682#: src/index.js:88
     683msgid "never expires"
     684msgstr ""
     685
     686#: src/index.js:89
     687#: src/index.js:1231
     688#: src/index.js:1333
     689#: src/index.js:2107
     690msgid "12 hours"
     691msgstr ""
     692
     693#: src/index.js:90
     694#: src/index.js:1232
     695#: src/index.js:1334
     696#: src/index.js:2108
     697msgid "1 day"
     698msgstr ""
     699
     700#: src/index.js:91
     701#: src/index.js:1233
     702#: src/index.js:1335
     703#: src/index.js:2109
     704msgid "1 week"
     705msgstr ""
     706
     707#: src/index.js:92
     708#: src/index.js:1234
     709#: src/index.js:1336
     710#: src/index.js:2110
     711msgid "1 month"
     712msgstr ""
     713
     714#. translators: %d: number of hours.
     715#: src/index.js:95
     716#: src/index.js:1237
     717#, js-format
     718msgid "%d hour"
     719msgid_plural "%d hours"
     720msgstr[0] ""
     721msgstr[1] ""
     722
     723#: src/index.js:102
     724msgid "Open Network Admin → Optimazz"
     725msgstr ""
     726
     727#: src/index.js:111
     728msgid "Page caching is off for this network"
     729msgstr ""
     730
     731#: src/index.js:113
     732msgid "Turn it on for every site in Network Admin → Optimazz. Each site gets its own cache, and keeps its own exclusions here under Advanced Rules."
     733msgstr ""
     734
     735#: src/index.js:114
     736msgid "Your network administrator can turn it on for every site in Network Admin → Optimazz. All other optimizations — CSS, JavaScript, images, fonts and more — work normally on this site."
     737msgstr ""
     738
     739#: src/index.js:128
     740msgid "Page caching is on — managed for the whole network"
     741msgstr ""
     742
     743#. translators: 1: yes/no, 2: cache lifetime, 3: preload frequency.
     744#: src/index.js:131
     745#, js-format
     746msgid "Separate mobile cache: %1$s · Cache lifetime: %2$s · Automatic preload: %3$s."
     747msgstr ""
     748
     749#: src/index.js:132
     750msgid "yes"
     751msgstr ""
     752
     753#: src/index.js:132
     754msgid "no"
     755msgstr ""
     756
     757#: src/index.js:134
     758#: src/index.js:1247
     759msgid "off"
     760msgstr ""
     761
     762#: src/index.js:136
     763msgid "This site has its own cache. Purge or preload it from the Dashboard or the Preload screen, and set its own exclusions under Advanced Rules."
     764msgstr ""
     765
     766#: src/index.js:146
    643767msgid "Content leftovers"
    644768msgstr ""
    645769
    646 #: src/index.js:65
     770#: src/index.js:147
    647771msgid "Old post data that piles up over time. Removing it is permanent, so keep anything you still need."
    648772msgstr ""
    649773
    650 #: src/index.js:67
     774#: src/index.js:149
    651775msgid "Post revisions"
    652776msgstr ""
    653777
    654 #: src/index.js:67
     778#: src/index.js:149
    655779msgid "Old copies of posts saved automatically. Deleting them keeps only your current content."
    656780msgstr ""
    657781
    658 #: src/index.js:68
     782#: src/index.js:150
    659783msgid "Auto drafts"
    660784msgstr ""
    661785
    662 #: src/index.js:68
     786#: src/index.js:150
    663787msgid "Empty drafts WordPress creates while you write. Safe to remove."
    664788msgstr ""
    665789
    666 #: src/index.js:69
     790#: src/index.js:151
    667791msgid "Trashed posts"
    668792msgstr ""
    669793
    670 #: src/index.js:69
     794#: src/index.js:151
    671795msgid "Posts in the trash that have not been permanently deleted yet."
    672796msgstr ""
    673797
    674 #: src/index.js:73
     798#: src/index.js:155
    675799msgid "Comment clutter"
    676800msgstr ""
    677801
    678 #: src/index.js:74
     802#: src/index.js:156
    679803msgid "Spam and trashed comments you are never going to approve. Clearing them keeps the comments table light."
    680804msgstr ""
    681805
    682 #: src/index.js:76
     806#: src/index.js:158
    683807msgid "Spam comments"
    684808msgstr ""
    685809
    686 #: src/index.js:76
     810#: src/index.js:158
    687811msgid "Comments marked as spam. Removing them frees up the comments table."
    688812msgstr ""
    689813
    690 #: src/index.js:77
     814#: src/index.js:159
    691815msgid "Trashed comments"
    692816msgstr ""
    693817
    694 #: src/index.js:77
     818#: src/index.js:159
    695819msgid "Comments in the trash awaiting permanent deletion."
    696820msgstr ""
    697821
    698 #: src/index.js:81
     822#: src/index.js:163
    699823msgid "Temporary data"
    700824msgstr ""
    701825
    702 #: src/index.js:82
     826#: src/index.js:164
    703827msgid "Short-lived cached values. They are safe to clear and are rebuilt automatically whenever a plugin needs them."
    704828msgstr ""
    705829
    706 #: src/index.js:84
     830#: src/index.js:166
    707831msgid "Expired transients"
    708832msgstr ""
    709833
    710 #: src/index.js:84
     834#: src/index.js:166
    711835msgid "Cached values that have already passed their expiry date."
    712836msgstr ""
    713837
    714 #: src/index.js:88
     838#: src/index.js:170
    715839msgid "Table maintenance"
    716840msgstr ""
    717841
    718 #: src/index.js:89
     842#: src/index.js:171
    719843msgid "Defragment your tables to reclaim wasted space and trim database overhead."
    720844msgstr ""
    721845
    722 #: src/index.js:91
     846#: src/index.js:173
    723847msgid "Optimize database tables"
    724848msgstr ""
    725849
    726 #: src/index.js:91
     850#: src/index.js:173
    727851msgid "Runs OPTIMIZE on your tables to defragment them and free wasted space."
    728852msgstr ""
    729853
    730 #: src/index.js:97
     854#: src/index.js:179
    731855msgid "Do not limit"
    732856msgstr ""
    733857
    734 #: src/index.js:98
     858#: src/index.js:180
    735859msgid "Reduce activity"
    736860msgstr ""
    737861
    738 #: src/index.js:99
     862#: src/index.js:181
    739863msgid "Disable"
    740864msgstr ""
    741865
    742 #: src/index.js:108
     866#: src/index.js:190
    743867msgid "Active"
    744868msgstr ""
    745869
    746 #: src/index.js:108
    747 #: src/index.js:1110
    748 #: src/index.js:1636
     870#: src/index.js:190
     871#: src/index.js:1256
     872#: src/index.js:1798
    749873msgid "Off"
    750874msgstr ""
    751875
    752 #: src/index.js:150
     876#: src/index.js:232
    753877msgid "Cancelled"
    754878msgstr ""
    755879
    756 #: src/index.js:151
     880#: src/index.js:233
    757881msgid "Failed"
    758882msgstr ""
    759883
    760 #: src/index.js:167
     884#: src/index.js:249
    761885msgid "Expand"
    762886msgstr ""
    763887
    764 #: src/index.js:167
     888#: src/index.js:249
    765889msgid "Minimize"
    766890msgstr ""
    767891
    768 #: src/index.js:170
     892#: src/index.js:252
    769893msgid "Close"
    770894msgstr ""
    771895
    772 #: src/index.js:184
     896#: src/index.js:266
    773897msgid "Step"
    774898msgstr ""
    775899
    776 #: src/index.js:189
     900#: src/index.js:271
    777901msgid "Stop"
    778902msgstr ""
    779903
    780 #: src/index.js:492
    781 #: src/index.js:1239
     904#: src/index.js:538
     905msgid "You have unsaved changes"
     906msgstr ""
     907
     908#: src/index.js:538
     909msgid "All changes saved"
     910msgstr ""
     911
     912#: src/index.js:541
     913msgid "Save changes"
     914msgstr ""
     915
     916#: src/index.js:639
     917#: src/index.js:1385
    782918msgid "Deactivate"
    783919msgstr ""
    784920
    785 #: src/index.js:492
     921#: src/index.js:639
    786922msgid "Optimazz will keep handling caching."
    787923msgstr ""
    788924
    789 #: src/index.js:499
     925#: src/index.js:646
    790926msgid "deactivated — Optimazz is now the only cache."
    791927msgstr ""
    792928
    793 #: src/index.js:501
     929#: src/index.js:648
    794930msgid "Could not deactivate the plugin."
    795931msgstr ""
    796932
    797 #: src/index.js:508
     933#: src/index.js:655
    798934msgid "Make Optimazz the caching plugin? This replaces the current advanced-cache.php (from another caching plugin) and turns on WP_CACHE. Deactivate the other caching plugin afterwards to avoid conflicts."
    799935msgstr ""
    800936
    801 #: src/index.js:515
     937#: src/index.js:662
    802938msgid "Optimazz is now serving pages from cache."
    803939msgstr ""
    804940
    805 #: src/index.js:516
     941#: src/index.js:663
    806942msgid "Drop-in installed. Preload the cache, then check again."
    807943msgstr ""
    808944
    809 #: src/index.js:517
     945#: src/index.js:664
    810946msgid "Could not fully take over — see the cache health details."
    811947msgstr ""
    812948
    813 #: src/index.js:520
     949#: src/index.js:667
    814950msgid "Take-over failed. Check file permissions."
    815951msgstr ""
    816952
    817 #: src/index.js:542
    818 #: src/index.js:588
     953#: src/index.js:689
     954#: src/index.js:735
    819955msgid "Cache purged."
    820956msgstr ""
    821957
    822 #: src/index.js:543
     958#: src/index.js:690
    823959msgid "Cache preload started in the background."
    824960msgstr ""
    825961
    826 #: src/index.js:544
     962#: src/index.js:691
    827963msgid "Cache cleared; preload started in the background."
    828964msgstr ""
    829965
    830 #: src/index.js:553
     966#: src/index.js:700
    831967msgid "wp-config.php is not writable — add WP_CACHE manually to enable page caching."
    832968msgstr ""
    833969
    834 #: src/index.js:567
     970#: src/index.js:714
    835971msgid "Settings saved."
    836972msgstr ""
    837973
    838 #: src/index.js:570
     974#: src/index.js:717
    839975msgid "Save failed."
    840976msgstr ""
    841977
    842 #: src/index.js:620
     978#: src/index.js:767
    843979msgid "Converting images to WebP/AVIF"
    844980msgstr ""
    845981
    846 #: src/index.js:621
     982#: src/index.js:768
    847983msgid "Next-gen format generation"
    848984msgstr ""
    849985
    850 #: src/index.js:624
     986#: src/index.js:771
    851987msgid "Generated"
    852988msgstr ""
    853989
    854 #: src/index.js:624
     990#: src/index.js:771
    855991msgid "files in this batch"
    856992msgstr ""
    857993
    858 #: src/index.js:624
    859 #: src/index.js:633
     994#: src/index.js:771
     995#: src/index.js:780
    860996msgid "skipped"
    861997msgstr ""
    862998
    863 #: src/index.js:627
     999#: src/index.js:774
    8641000msgid "Compressing images"
    8651001msgstr ""
    8661002
    867 #: src/index.js:628
     1003#: src/index.js:775
    8681004msgid "Recompress, resize & strip metadata"
    8691005msgstr ""
    8701006
    871 #: src/index.js:634
     1007#: src/index.js:781
    8721008msgid "Processed"
    8731009msgstr ""
    8741010
    875 #: src/index.js:634
    876 #: src/index.js:1440
     1011#: src/index.js:781
     1012#: src/index.js:1586
    8771013msgid "saved"
    8781014msgstr ""
    8791015
    880 #: src/index.js:638
     1016#: src/index.js:785
     1017#: src/index.js:1953
    8811018msgid "Preloading cache"
    8821019msgstr ""
    8831020
    884 #: src/index.js:639
     1021#: src/index.js:786
    8851022msgid "Warming pages"
    8861023msgstr ""
    8871024
    888 #: src/index.js:643
     1025#: src/index.js:790
     1026#: src/index.js:1974
    8891027msgid "Warmed"
    8901028msgstr ""
    8911029
    892 #: src/index.js:643
     1030#: src/index.js:790
     1031#: src/index.js:1974
    8931032msgid "pages"
    8941033msgstr ""
    8951034
    896 #: src/index.js:643
     1035#: src/index.js:790
     1036#: src/index.js:1974
    8971037msgid "cached"
    8981038msgstr ""
    8991039
    900 #: src/index.js:643
     1040#: src/index.js:790
     1041#: src/index.js:1974
    9011042msgid "not cached"
    9021043msgstr ""
    9031044
    904 #: src/index.js:664
     1045#: src/index.js:811
    9051046msgid "Resuming…"
    9061047msgstr ""
    9071048
    908 #: src/index.js:678
     1049#: src/index.js:825
     1050#: src/index.js:1963
    9091051msgid "Stopped by user."
    9101052msgstr ""
    9111053
    912 #: src/index.js:697
     1054#: src/index.js:844
    9131055msgid "A batch failed (likely one slow/large image); skipped it and continued."
    9141056msgstr ""
    9151057
    916 #: src/index.js:699
     1058#: src/index.js:846
    9171059msgid "Completed (some items skipped)."
    9181060msgstr ""
    9191061
    920 #: src/index.js:711
     1062#: src/index.js:858
    9211063msgid "Job stalled — no progress."
    9221064msgstr ""
    9231065
    924 #: src/index.js:722
     1066#: src/index.js:869
     1067#: src/index.js:1977
    9251068msgid "left"
    9261069msgstr ""
    9271070
    9281071#. translators: %d: number of pages to warm.
    929 #: src/index.js:734
     1072#: src/index.js:881
     1073#: src/index.js:1987
    9301074#, js-format
    9311075msgid "Found %d pages to warm:"
    9321076msgstr ""
    9331077
    934 #: src/index.js:744
     1078#: src/index.js:891
    9351079msgid "Image processing is unavailable on this server (no GD or ImageMagick). See Tools → Server Requirements."
    9361080msgstr ""
    9371081
    938 #: src/index.js:749
     1082#: src/index.js:896
     1083#: src/index.js:1993
    9391084msgid "Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the \"Optimazz-CacheWarmer\" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter."
    9401085msgstr ""
    9411086
    942 #: src/index.js:756
     1087#: src/index.js:903
     1088#: src/index.js:1997
    9431089msgid "Pages are not being cached"
    9441090msgstr ""
    9451091
    946 #: src/index.js:756
     1092#: src/index.js:903
     1093#: src/index.js:1997
    9471094msgid "Stopped: pages are loading but none are being saved to the cache."
    9481095msgstr ""
    9491096
    950 #: src/index.js:760
    951 #: src/index.js:841
     1097#: src/index.js:907
     1098#: src/index.js:988
     1099#: src/index.js:2007
    9521100msgid "Completed."
    9531101msgstr ""
    9541102
    9551103#. translators: %d: number of pages.
    956 #: src/index.js:764
     1104#: src/index.js:911
     1105#: src/index.js:2004
    9571106#, js-format
    9581107msgid "%d page was not cached."
    … …  
    9611110msgstr[1] ""
    9621111
    963 #: src/index.js:823
     1112#: src/index.js:970
    9641113msgid "Cleaning and optimizing database…"
    9651114msgstr ""
    9661115
    967 #: src/index.js:827
     1116#: src/index.js:974
    9681117msgid "Optimizing database"
    9691118msgstr ""
    9701119
    971 #: src/index.js:828
     1120#: src/index.js:975
    9721121msgid "Cleanup & table optimization"
    9731122msgstr ""
    9741123
    975 #: src/index.js:840
     1124#: src/index.js:987
    9761125msgid "Cleaned"
    9771126msgstr ""
    9781127
    979 #: src/index.js:840
     1128#: src/index.js:987
    9801129msgid "rows,"
    9811130msgstr ""
    9821131
    983 #: src/index.js:840
     1132#: src/index.js:987
    9841133msgid "tables optimized."
    9851134msgstr ""
    9861135
    987 #: src/index.js:845
     1136#: src/index.js:992
    9881137msgid "Optimize failed."
    9891138msgstr ""
    9901139
    991 #: src/index.js:851
     1140#: src/index.js:998
    9921141msgid "Reset all Optimazz settings to defaults? You can undo this with \"Undo last change\"."
    9931142msgstr ""
    9941143
    995 #: src/index.js:859
     1144#: src/index.js:1006
    9961145msgid "Settings reset to defaults."
    9971146msgstr ""
    9981147
    999 #: src/index.js:862
     1148#: src/index.js:1009
    10001149msgid "Reset failed."
    10011150msgstr ""
    10021151
    1003 #: src/index.js:872
     1152#: src/index.js:1019
    10041153msgid "Nothing to undo."
    10051154msgstr ""
    10061155
    1007 #: src/index.js:879
     1156#: src/index.js:1026
    10081157msgid "Reverted to the previous settings."
    10091158msgstr ""
    10101159
    1011 #: src/index.js:882
     1160#: src/index.js:1029
    10121161msgid "Undo failed."
    10131162msgstr ""
    10141163
    1015 #: src/index.js:914
     1164#: src/index.js:1061
    10161165msgid "Invalid settings file."
    10171166msgstr ""
    10181167
    1019 #: src/index.js:944
     1168#: src/index.js:1088
    10201169msgid "Page caching"
    10211170msgstr ""
    10221171
    1023 #: src/index.js:945
     1172#: src/index.js:1089
    10241173msgid "Delay JavaScript"
    10251174msgstr ""
    10261175
    1027 #: src/index.js:946
    1028 #: src/index.js:1266
     1176#: src/index.js:1090
     1177#: src/index.js:1412
    10291178msgid "Defer JavaScript"
    10301179msgstr ""
    10311180
    1032 #: src/index.js:947
    1033 #: src/index.js:1319
     1181#: src/index.js:1091
     1182#: src/index.js:1465
    10341183msgid "Minify CSS"
    10351184msgstr ""
    10361185
    1037 #: src/index.js:949
     1186#: src/index.js:1093
    10381187msgid "Next-gen images"
    10391188msgstr ""
    10401189
    1041 #: src/index.js:950
     1190#: src/index.js:1094
    10421191msgid "Font optimization"
    10431192msgstr ""
    10441193
    1045 #: src/index.js:951
     1194#: src/index.js:1095
    10461195msgid "Cache preloading"
    10471196msgstr ""
    10481197
    1049 #: src/index.js:952
     1198#: src/index.js:1096
    10501199msgid "Heartbeat control"
    10511200msgstr ""
    10521201
    1053 #: src/index.js:967
     1202#: src/index.js:1113
    10541203msgid "Turn on Page Cache for the real speed gain"
    10551204msgstr ""
    10561205
    1057 #: src/index.js:968
     1206#: src/index.js:1114
    10581207msgid "File and media optimizations are on, but Page Cache is off — so the server rebuilds every page on every request. Without caching this can make a site feel slower, not faster, especially on mobile. Enable Page Cache to serve ready-made HTML."
    10591208msgstr ""
    10601209
    1061 #: src/index.js:987
     1210#: src/index.js:1133
    10621211msgid "SCORE"
    10631212msgstr ""
    10641213
    1065 #: src/index.js:992
     1214#: src/index.js:1138
    10661215msgid "Performance score"
    10671216msgstr ""
    10681217
    1069 #: src/index.js:995
     1218#: src/index.js:1141
    10701219msgid "Biggest remaining win:"
    10711220msgstr ""
    10721221
    1073 #: src/index.js:996
     1222#: src/index.js:1142
    10741223msgid "Every optimization is enabled. Nicely done!"
    10751224msgstr ""
    10761225
    1077 #: src/index.js:1013
    1078 #: src/index.js:1065
     1226#: src/index.js:1159
     1227#: src/index.js:1211
     1228#: src/index.js:2204
    10791229msgid "Cached pages"
    10801230msgstr ""
    10811231
    1082 #: src/index.js:1018
     1232#: src/index.js:1164
    10831233msgid "Cache size"
    10841234msgstr ""
    10851235
    1086 #: src/index.js:1023
     1236#: src/index.js:1169
    10871237msgid "Modules active"
    10881238msgstr ""
    10891239
    1090 #: src/index.js:1028
    1091 #: src/index.js:1043
     1240#: src/index.js:1174
     1241#: src/index.js:1189
    10921242msgid "Recommendations"
    10931243msgstr ""
    10941244
    1095 #: src/index.js:1028
     1245#: src/index.js:1174
    10961246msgid "What to fix next to speed up your site — one click each."
    10971247msgstr ""
    10981248
    1099 #: src/index.js:1035
     1249#: src/index.js:1181
    11001250msgid "Fix it"
    11011251msgstr ""
    11021252
    1103 #: src/index.js:1036
     1253#: src/index.js:1182
    11041254msgid "Open"
    11051255msgstr ""
    11061256
    1107 #: src/index.js:1044
     1257#: src/index.js:1190
    11081258msgid "Nice — everything we recommend is already on. Your site is well optimized."
    11091259msgstr ""
    11101260
    1111 #: src/index.js:1049
     1261#: src/index.js:1195
    11121262msgid "Cache Files"
    11131263msgstr ""
    11141264
    1115 #: src/index.js:1049
     1265#: src/index.js:1195
    11161266msgid "Clear and preload all cache files."
    11171267msgstr ""
    11181268
    1119 #: src/index.js:1052
     1269#: src/index.js:1198
     1270#: src/index.js:2187
    11201271msgid "Clear & Preload"
    11211272msgstr ""
    11221273
    1123 #: src/index.js:1058
     1274#: src/index.js:1204
     1275#: src/index.js:2195
    11241276msgid "Purge cache"
    11251277msgstr ""
    11261278
    1127 #: src/index.js:1070
     1279#: src/index.js:1216
     1280#: src/index.js:2209
    11281281msgid "Page cache size"
    11291282msgstr ""
    11301283
    1131 #: src/index.js:1075
     1284#: src/index.js:1221
    11321285msgid "Optimized CSS"
    11331286msgstr ""
    11341287
    1135 #: src/index.js:1080
    1136 #: src/index.js:1081
     1288#: src/index.js:1226
     1289#: src/index.js:1227
    11371290msgid "Cache lifespan"
    11381291msgstr ""
    11391292
    1140 #: src/index.js:1084
     1293#: src/index.js:1230
    11411294msgid "Never expire"
    11421295msgstr ""
    11431296
    1144 #: src/index.js:1085
    1145 #: src/index.js:1187
    1146 msgid "12 hours"
    1147 msgstr ""
    1148 
    1149 #: src/index.js:1086
    1150 #: src/index.js:1188
    1151 msgid "1 day"
    1152 msgstr ""
    1153 
    1154 #: src/index.js:1087
    1155 #: src/index.js:1189
    1156 msgid "1 week"
    1157 msgstr ""
    1158 
    1159 #: src/index.js:1088
    1160 #: src/index.js:1190
    1161 msgid "1 month"
    1162 msgstr ""
    1163 
    1164 #. translators: %d: number of hours.
    1165 #: src/index.js:1091
    1166 #, js-format
    1167 msgid "%d hour"
    1168 msgid_plural "%d hours"
    1169 msgstr[0] ""
    1170 msgstr[1] ""
    1171 
    1172 #: src/index.js:1098
     1297#: src/index.js:1244
    11731298msgid "Auto-preload"
    11741299msgstr ""
    11751300
    1176 #: src/index.js:1101
    1177 msgid "off"
    1178 msgstr ""
    1179 
    1180 #: src/index.js:1103
     1301#: src/index.js:1249
    11811302msgid "next run: due now"
    11821303msgstr ""
    11831304
    1184 #: src/index.js:1104
     1305#: src/index.js:1250
    11851306msgid "next run in"
    11861307msgstr ""
    11871308
    1188 #: src/index.js:1107
     1309#: src/index.js:1253
    11891310msgid "Auto-preload frequency"
    11901311msgstr ""
    11911312
    1192 #: src/index.js:1111
    1193 #: src/index.js:1462
     1313#: src/index.js:1257
     1314#: src/index.js:1609
     1315#: src/index.js:2121
    11941316msgid "Every hour"
    11951317msgstr ""
    11961318
    1197 #: src/index.js:1112
    1198 #: src/index.js:1463
     1319#: src/index.js:1258
     1320#: src/index.js:1610
     1321#: src/index.js:2122
    11991322msgid "Twice a day"
    12001323msgstr ""
    12011324
    1202 #: src/index.js:1113
    1203 #: src/index.js:1464
     1325#: src/index.js:1259
     1326#: src/index.js:1611
     1327#: src/index.js:2123
    12041328msgid "Once a day"
    12051329msgstr ""
    12061330
    1207 #: src/index.js:1121
     1331#: src/index.js:1267
    12081332msgid "Last auto-preload"
    12091333msgstr ""
    12101334
    1211 #: src/index.js:1124
     1335#: src/index.js:1270
    12121336msgid "not yet"
    12131337msgstr ""
    12141338
    12151339#. translators: 1: time ago, e.g. "2 hours", 2: number of pages
    1216 #: src/index.js:1127
     1340#: src/index.js:1273
    12171341#, js-format
    12181342msgid "%1$s ago · %2$d pages"
    12191343msgstr ""
    12201344
    1221 #: src/index.js:1130
     1345#: src/index.js:1276
    12221346msgid "blocked by firewall"
    12231347msgstr ""
    12241348
    1225 #: src/index.js:1135
     1349#: src/index.js:1281
    12261350msgid "Module Status"
    12271351msgstr ""
    12281352
    1229 #: src/index.js:1135
     1353#: src/index.js:1281
    12301354msgid "Enable or disable any module right here."
    12311355msgstr ""
    12321356
    1233 #: src/index.js:1172
     1357#: src/index.js:1318
    12341358msgid "Serve static HTML instead of running PHP on every request."
    12351359msgstr ""
    12361360
    1237 #: src/index.js:1174
     1361#: src/index.js:1320
     1362#: src/index.js:2094
    12381363msgid "Cache mobile devices separately"
    12391364msgstr ""
    12401365
    1241 #: src/index.js:1176
     1366#: src/index.js:1322
     1367#: src/index.js:2096
    12421368msgid "Cache logged-in users"
    12431369msgstr ""
    12441370
    1245 #: src/index.js:1177
     1371#: src/index.js:1323
     1372#: src/index.js:2097
    12461373msgid "Leave off unless you know what you are doing."
    12471374msgstr ""
    12481375
    1249 #: src/index.js:1179
     1376#: src/index.js:1325
     1377#: src/index.js:2099
    12501378msgid "Browser cache headers"
    12511379msgstr ""
    12521380
    1253 #: src/index.js:1180
     1381#: src/index.js:1326
    12541382msgid "Add Expires / Cache-Control rules so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only (written to .htaccess); ignored on Nginx."
    12551383msgstr ""
    12561384
    1257 #: src/index.js:1182
     1385#: src/index.js:1328
     1386#: src/index.js:2102
    12581387msgid "Cache lifetime"
    12591388msgstr ""
    12601389
    1261 #: src/index.js:1183
     1390#: src/index.js:1329
    12621391msgid "How long a cached page is kept before it is refreshed. Pages are always purged immediately when their content changes."
    12631392msgstr ""
    12641393
    1265 #: src/index.js:1186
     1394#: src/index.js:1332
     1395#: src/index.js:2106
    12661396msgid "Never"
    12671397msgstr ""
    12681398
    1269 #: src/index.js:1196
     1399#: src/index.js:1342
     1400#: src/index.js:2128
    12701401msgid "Cache health"
    12711402msgstr ""
    12721403
    1273 #: src/index.js:1198
     1404#: src/index.js:1344
    12741405msgid "Check now"
    12751406msgstr ""
    12761407
    1277 #: src/index.js:1201
     1408#: src/index.js:1347
    12781409msgid "Confirms caching is actually serving pages. If it is not, the plugin rebuilds every page on each request — which is slower, not faster."
    12791410msgstr ""
    12801411
    1281 #: src/index.js:1205
     1412#: src/index.js:1351
    12821413msgid "Pages ARE being served from cache (HIT)"
    12831414msgstr ""
    12841415
    1285 #: src/index.js:1206
     1416#: src/index.js:1352
    12861417msgid "Pages are NOT being served from cache (MISS) — see below"
    12871418msgstr ""
    12881419
    1289 #: src/index.js:1207
     1420#: src/index.js:1353
    12901421msgid "Live check unavailable"
    12911422msgstr ""
    12921423
    1293 #: src/index.js:1211
     1424#: src/index.js:1357
    12941425msgid "advanced-cache.php belongs to"
    12951426msgstr ""
    12961427
    1297 #: src/index.js:1211
     1428#: src/index.js:1357
    12981429msgid "take it over below"
    12991430msgstr ""
    13001431
    1301 #: src/index.js:1212
     1432#: src/index.js:1358
    13021433msgid "advanced-cache.php belongs to ANOTHER plugin — take it over below"
    13031434msgstr ""
    13041435
    1305 #: src/index.js:1213
     1436#: src/index.js:1359
    13061437msgid "advanced-cache.php drop-in installed"
    13071438msgstr ""
    13081439
    1309 #: src/index.js:1214
     1440#: src/index.js:1360
    13101441msgid "advanced-cache.php drop-in is missing"
    13111442msgstr ""
    13121443
    1313 #: src/index.js:1216
     1444#: src/index.js:1362
    13141445msgid "WP_CACHE is enabled in wp-config.php"
    13151446msgstr ""
    13161447
    1317 #: src/index.js:1216
     1448#: src/index.js:1362
    13181449msgid "WP_CACHE is not enabled in wp-config.php"
    13191450msgstr ""
    13201451
    1321 #: src/index.js:1218
     1452#: src/index.js:1364
    13221453msgid "wp-config.php is not writable — add the WP_CACHE line manually (below)"
    13231454msgstr ""
    13241455
    1325 #: src/index.js:1221
     1456#: src/index.js:1367
    13261457msgid "pages warmed in cache"
    13271458msgstr ""
    13281459
    1329 #: src/index.js:1227
     1460#: src/index.js:1373
    13301461msgid "Take over caching"
    13311462msgstr ""
    13321463
    1333 #: src/index.js:1229
     1464#: src/index.js:1375
    13341465msgid "Make Optimazz the caching plugin — installs its drop-in and enables WP_CACHE."
    13351466msgstr ""
    13361467
    1337 #: src/index.js:1237
     1468#: src/index.js:1383
    13381469msgid "is still active and may reclaim caching. Deactivate it so Optimazz keeps serving the cache."
    13391470msgstr ""
    13401471
    1341 #: src/index.js:1247
     1472#: src/index.js:1393
    13421473msgid "wp-config.php is not writable. Add this line just after the opening <?php tag:"
    13431474msgstr ""
    13441475
    1345 #: src/index.js:1252
     1476#: src/index.js:1398
    13461477msgid "Could not run the check. Try reloading the page."
    13471478msgstr ""
    13481479
    1349 #: src/index.js:1260
     1480#: src/index.js:1406
    13501481msgid "JavaScript"
    13511482msgstr ""
    13521483
    1353 #: src/index.js:1261
     1484#: src/index.js:1407
    13541485msgid "Defer and delay scripts to cut Total Blocking Time."
    13551486msgstr ""
    13561487
    1357 #: src/index.js:1263
     1488#: src/index.js:1409
    13581489msgid "Minify JavaScript"
    13591490msgstr ""
    13601491
    1361 #: src/index.js:1264
     1492#: src/index.js:1410
    13621493msgid "Strip comments and whitespace from inline scripts. Line breaks are kept for safety; test your site after enabling."
    13631494msgstr ""
    13641495
    1365 #: src/index.js:1267
     1496#: src/index.js:1413
    13661497msgid "Load scripts without blocking page render."
    13671498msgstr ""
    13681499
    1369 #: src/index.js:1269
     1500#: src/index.js:1415
    13701501msgid "Delay JS until interaction"
    13711502msgstr ""
    13721503
    1373 #: src/index.js:1270
     1504#: src/index.js:1416
    13741505msgid "Run scripts only after the first user interaction."
    13751506msgstr ""
    13761507
    1377 #: src/index.js:1273
     1508#: src/index.js:1419
    13781509msgid "Delay all scripts"
    13791510msgstr ""
    13801511
    1381 #: src/index.js:1274
     1512#: src/index.js:1420
    13821513msgid "Delay every script until interaction. jQuery, your theme, and Elementor are always kept safe. Still, on sites where content is rendered by JavaScript this can delay the visible page — test before keeping it on."
    13831514msgstr ""
    13841515
    1385 #: src/index.js:1280
     1516#: src/index.js:1426
    13861517msgid "Delaying all scripts can slow the first paint on JavaScript-rendered sites (Elementor, sliders). If your PageSpeed FCP/LCP get worse, switch to \"Delay selected\" and delay only tracking scripts."
    13871518msgstr ""
    13881519
    1389 #: src/index.js:1284
     1520#: src/index.js:1430
    13901521msgid "Delay these scripts"
    13911522msgstr ""
    13921523
    1393 #: src/index.js:1285
     1524#: src/index.js:1431
    13941525msgid "Only scripts matching these keywords are delayed. Match by filename, handle or URL — e.g. gtag, analytics, facebook."
    13951526msgstr ""
    13961527
    1397 #: src/index.js:1286
     1528#: src/index.js:1432
    13981529msgid "e.g. gtag, analytics"
    13991530msgstr ""
    14001531
    1401 #: src/index.js:1292
     1532#: src/index.js:1438
    14021533msgid "Exclude specific scripts"
    14031534msgstr ""
    14041535
    1405 #: src/index.js:1294
     1536#: src/index.js:1440
    14061537msgid "If a script breaks, exclude it from just one optimization instead of both. Match by filename, script handle, or part of the URL."
    14071538msgstr ""
    14081539
    1409 #: src/index.js:1298
     1540#: src/index.js:1444
    14101541msgid "Never defer"
    14111542msgstr ""
    14121543
    1413 #: src/index.js:1299
     1544#: src/index.js:1445
    14141545msgid "Keep loading these render-blocking, but still allow delay. Use for scripts that must run during page load."
    14151546msgstr ""
    14161547
    1417 #: src/index.js:1300
     1548#: src/index.js:1446
    14181549msgid "e.g. jquery.min.js"
    14191550msgstr ""
    14201551
    1421 #: src/index.js:1304
     1552#: src/index.js:1450
    14221553msgid "Never delay"
    14231554msgstr ""
    14241555
    1425 #: src/index.js:1305
     1556#: src/index.js:1451
    14261557msgid "Run these before any interaction, but still allow defer. Use for sliders, above-the-fold, or consent scripts."
    14271558msgstr ""
    14281559
    1429 #: src/index.js:1306
     1560#: src/index.js:1452
    14301561msgid "e.g. slider, swiper"
    14311562msgstr ""
    14321563
    1433 #: src/index.js:1309
     1564#: src/index.js:1455
    14341565msgid "Never optimize"
    14351566msgstr ""
    14361567
    1437 #: src/index.js:1310
     1568#: src/index.js:1456
    14381569msgid "Skip both defer and delay — leave these scripts exactly as they are."
    14391570msgstr ""
    14401571
    1441 #: src/index.js:1311
     1572#: src/index.js:1457
    14421573msgid "e.g. recaptcha"
    14431574msgstr ""
    14441575
    1445 #: src/index.js:1316
     1576#: src/index.js:1462
    14461577msgid "CSS"
    14471578msgstr ""
    14481579
    1449 #: src/index.js:1317
     1580#: src/index.js:1463
    14501581msgid "Minify, combine, and remove render-blocking stylesheets."
    14511582msgstr ""
    14521583
    1453 #: src/index.js:1321
     1584#: src/index.js:1467
    14541585msgid "Combine CSS files"
    14551586msgstr ""
    14561587
    1457 #: src/index.js:1322
     1588#: src/index.js:1468
    14581589msgid "Merge stylesheets to reduce requests."
    14591590msgstr ""
    14601591
    1461 #: src/index.js:1324
     1592#: src/index.js:1470
    14621593msgid "Load CSS asynchronously"
    14631594msgstr ""
    14641595
    1465 #: src/index.js:1325
     1596#: src/index.js:1471
    14661597msgid "Removes render-blocking CSS. Paste your above-the-fold (critical) CSS below, or the page may briefly flash unstyled."
    14671598msgstr ""
    14681599
    1469 #: src/index.js:1332
     1600#: src/index.js:1478
    14701601msgid "No critical CSS provided. Async loading is on, so visitors may see a flash of unstyled content until you paste critical CSS below."
    14711602msgstr ""
    14721603
    1473 #: src/index.js:1336
     1604#: src/index.js:1482
    14741605msgid "Critical CSS (above-the-fold)"
    14751606msgstr ""
    14761607
    1477 #: src/index.js:1337
     1608#: src/index.js:1483
    14781609msgid "Paste the above-the-fold CSS to inline. Generate it with a free tool such as a critical-CSS generator. Leave empty to disable inlining."
    14791610msgstr ""
    14801611
    1481 #: src/index.js:1342
     1612#: src/index.js:1488
    14821613msgid "Remove unused CSS"
    14831614msgstr ""
    14841615
    1485 #: src/index.js:1343
     1616#: src/index.js:1489
    14861617msgid "Remove block-editor CSS on the front end"
    14871618msgstr ""
    14881619
    1489 #: src/index.js:1344
     1620#: src/index.js:1490
    14901621msgid "Dequeues WordPress block-library and global styles. Turn on only if your front end does NOT render Gutenberg blocks (e.g. a page-builder or classic theme). Big byte saving when it applies."
    14911622msgstr ""
    14921623
    1493 #: src/index.js:1346
     1624#: src/index.js:1492
    14941625msgid "Load WooCommerce CSS only on shop pages"
    14951626msgstr ""
    14961627
    1497 #: src/index.js:1347
     1628#: src/index.js:1493
    14981629msgid "Dequeues WooCommerce styles on non-shop pages (kept on cart, checkout, account, product and shop). Turn off if you show Woo products or widgets on other pages."
    14991630msgstr ""
    15001631
    1501 #: src/index.js:1349
     1632#: src/index.js:1495
    15021633msgid "Load Easy Digital Downloads CSS only on EDD pages"
    15031634msgstr ""
    15041635
    1505 #: src/index.js:1350
     1636#: src/index.js:1496
    15061637msgid "Dequeues EDD styles on non-EDD pages (kept on checkout, purchase-confirmation, purchase history and download archives). Turn off if you show download or purchase links elsewhere."
    15071638msgstr ""
    15081639
    1509 #: src/index.js:1352
     1640#: src/index.js:1498
    15101641msgid "Excluded CSS files (keywords)"
    15111642msgstr ""
    15121643
    1513 #: src/index.js:1360
     1644#: src/index.js:1506
    15141645msgid "Lazy-load, prevent layout shift, serve next-gen formats, and lighten media."
    15151646msgstr ""
    15161647
    1517 #: src/index.js:1366
     1648#: src/index.js:1512
    15181649msgid "This server has no GD or ImageMagick extension, so image compression and WebP/AVIF cannot run — every image will be skipped. Ask your host to enable \"php-gd\" (or \"php-imagick\"). See Tools → Server Requirements."
    15191650msgstr ""
    15201651
    1521 #: src/index.js:1370
     1652#: src/index.js:1516
    15221653msgid "Load off-screen images only when scrolled into view."
    15231654msgstr ""
    15241655
    1525 #: src/index.js:1372
     1656#: src/index.js:1518
    15261657msgid "Lazy-load iframes"
    15271658msgstr ""
    15281659
    1529 #: src/index.js:1374
     1660#: src/index.js:1520
    15301661msgid "Lazy-load CSS background images"
    15311662msgstr ""
    15321663
    1533 #: src/index.js:1375
     1664#: src/index.js:1521
    15341665msgid "Defer inline background-image elements (test with page builders)."
    15351666msgstr ""
    15361667
    1537 #: src/index.js:1377
     1668#: src/index.js:1523
    15381669msgid "Replace YouTube embeds with a preview"
    15391670msgstr ""
    15401671
    1541 #: src/index.js:1378
     1672#: src/index.js:1524
    15421673msgid "Load the video iframe only when the visitor clicks play."
    15431674msgstr ""
    15441675
    1545 #: src/index.js:1380
     1676#: src/index.js:1526
    15461677msgid "Add missing image dimensions"
    15471678msgstr ""
    15481679
    1549 #: src/index.js:1381
     1680#: src/index.js:1527
    15501681msgid "Set width/height to prevent layout shift (CLS)."
    15511682msgstr ""
    15521683
    1553 #: src/index.js:1383
     1684#: src/index.js:1529
    15541685msgid "Preload the LCP image"
    15551686msgstr ""
    15561687
    1557 #: src/index.js:1384
     1688#: src/index.js:1530
    15581689msgid "Preload the first/hero image with high priority."
    15591690msgstr ""
    15601691
    1561 #: src/index.js:1387
     1692#: src/index.js:1533
    15621693msgid "Keep first images eager (LCP)"
    15631694msgstr ""
    15641695
    1565 #: src/index.js:1388
     1696#: src/index.js:1534
    15661697msgid "Do not lazy-load the first N images, to protect LCP."
    15671698msgstr ""
    15681699
    1569 #: src/index.js:1393
     1700#: src/index.js:1539
    15701701msgid "Serve WebP via <picture>"
    15711702msgstr ""
    15721703
    1573 #: src/index.js:1394
     1704#: src/index.js:1540
    15741705msgid "Deliver WebP to supporting browsers, original as fallback."
    15751706msgstr ""
    15761707
    1577 #: src/index.js:1396
     1708#: src/index.js:1542
    15781709msgid "Generate WebP/AVIF on upload"
    15791710msgstr ""
    15801711
    1581 #: src/index.js:1398
     1712#: src/index.js:1544
    15821713msgid "WebP generation is not available on this server."
    15831714msgstr ""
    15841715
    1585 #: src/index.js:1399
     1716#: src/index.js:1545
    15861717msgid "Auto-create next-gen copies for newly uploaded images."
    15871718msgstr ""
    15881719
    1589 #: src/index.js:1402
     1720#: src/index.js:1548
    15901721msgid "Also generate/serve AVIF"
    15911722msgstr ""
    15921723
    1593 #: src/index.js:1403
     1724#: src/index.js:1549
    15941725msgid "Smaller than WebP; served first when supported."
    15951726msgstr ""
    15961727
    1597 #: src/index.js:1407
     1728#: src/index.js:1553
    15981729msgid "Compress & resize on upload"
    15991730msgstr ""
    16001731
    1601 #: src/index.js:1408
     1732#: src/index.js:1554
    16021733msgid "Recompress, cap dimensions, and strip metadata from new uploads."
    16031734msgstr ""
    16041735
    1605 #: src/index.js:1410
     1736#: src/index.js:1556
    16061737msgid "Convert opaque PNG to JPEG"
    16071738msgstr ""
    16081739
    1609 #: src/index.js:1411
     1740#: src/index.js:1557
    16101741msgid "Re-save non-transparent PNGs as much smaller JPEGs during bulk optimization."
    16111742msgstr ""
    16121743
    1613 #: src/index.js:1413
     1744#: src/index.js:1559
    16141745msgid "Keep a backup of originals"
    16151746msgstr ""
    16161747
    1617 #: src/index.js:1414
     1748#: src/index.js:1560
    16181749msgid "Copy originals to uploads/optimazz-backup before optimizing."
    16191750msgstr ""
    16201751
    1621 #: src/index.js:1416
     1752#: src/index.js:1562
    16221753msgid "Image quality"
    16231754msgstr ""
    16241755
    1625 #: src/index.js:1418
     1756#: src/index.js:1564
    16261757msgid "Used for compression and WebP/AVIF generation."
    16271758msgstr ""
    16281759
    1629 #: src/index.js:1419
     1760#: src/index.js:1565
    16301761msgid "Max image width (px)"
    16311762msgstr ""
    16321763
    1633 #: src/index.js:1424
     1764#: src/index.js:1570
    16341765msgid "WebP coverage"
    16351766msgstr ""
    16361767
    1637 #: src/index.js:1426
     1768#: src/index.js:1572
    16381769msgid "images converted"
    16391770msgstr ""
    16401771
    1641 #: src/index.js:1427
    1642 #: src/index.js:1441
     1772#: src/index.js:1573
     1773#: src/index.js:1587
    16431774msgid "Loading…"
    16441775msgstr ""
    16451776
    1646 #: src/index.js:1432
     1777#: src/index.js:1578
    16471778msgid "Convert to WebP"
    16481779msgstr ""
    16491780
    1650 #: src/index.js:1438
     1781#: src/index.js:1584
    16511782msgid "Bulk compression"
    16521783msgstr ""
    16531784
    1654 #: src/index.js:1440
     1785#: src/index.js:1586
    16551786msgid "optimized"
    16561787msgstr ""
    16571788
    1658 #: src/index.js:1444
     1789#: src/index.js:1590
    16591790msgid "Compress existing images"
    16601791msgstr ""
    16611792
    1662 #: src/index.js:1452
     1793#: src/index.js:1598
    16631794msgid "Warm the cache automatically so visitors never hit a cold page."
    16641795msgstr ""
    16651796
    1666 #: src/index.js:1453
     1797#: src/index.js:1600
    16671798msgid "Activate preloading"
    16681799msgstr ""
    16691800
    1670 #: src/index.js:1454
     1801#: src/index.js:1601
    16711802msgid "Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop."
    16721803msgstr ""
    16731804
    1674 #: src/index.js:1457
     1805#: src/index.js:1604
     1806#: src/index.js:2117
    16751807msgid "Automatic preload"
    16761808msgstr ""
    16771809
    1678 #: src/index.js:1458
     1810#: src/index.js:1605
    16791811msgid "How often to re-warm the whole site. Choose “Every hour” if your content changes often or you use a short cache lifespan."
    16801812msgstr ""
    16811813
    1682 #: src/index.js:1470
     1814#. translators: %s: preload frequency, e.g. "twice a day".
     1815#: src/index.js:1618
     1816#, js-format
     1817msgid "Automatic preload runs %s for this site (set for the whole network in Network Admin → Optimazz)."
     1818msgstr ""
     1819
     1820#: src/index.js:1621
     1821msgid "Automatic preload is off for the network. You can still preload this site now."
     1822msgstr ""
     1823
     1824#: src/index.js:1626
    16831825msgid "WP-Cron is disabled on this site (DISABLE_WP_CRON). Scheduled preloads only run if a server cron job calls wp-cron.php — ask your host to set one up (e.g. every 5 minutes)."
    16841826msgstr ""
    16851827
    1686 #: src/index.js:1475
     1828#: src/index.js:1631
    16871829msgid "Preload now"
    16881830msgstr ""
    16891831
    1690 #: src/index.js:1483
     1832#: src/index.js:1639
    16911833msgid "Fine-tune which requests are cached."
    16921834msgstr ""
    16931835
    1694 #: src/index.js:1484
     1836#: src/index.js:1640
    16951837msgid "Never cache these URLs (contains)"
    16961838msgstr ""
    16971839
    1698 #: src/index.js:1486
     1840#: src/index.js:1642
    16991841msgid "Never cache when these cookies are set"
    17001842msgstr ""
    17011843
    1702 #: src/index.js:1488
     1844#: src/index.js:1644
    17031845msgid "Never cache for these user agents"
    17041846msgstr ""
    17051847
    1706 #: src/index.js:1490
     1848#: src/index.js:1646
    17071849msgid "Ignore these query arguments"
    17081850msgstr ""
    17091851
    1710 #: src/index.js:1498
     1852#: src/index.js:1654
    17111853msgid "Safe Mode"
    17121854msgstr ""
    17131855
    1714 #: src/index.js:1499
     1856#: src/index.js:1655
    17151857msgid "If a theme or page builder looks broken after optimizing, turn this on. It pauses the riskiest optimizations — combine CSS, async CSS, remove block CSS and JavaScript delay — while keeping caching, minify, defer and lazy-load. Your saved settings are not changed; they simply pause until you turn Safe Mode off."
    17161858msgstr ""
    17171859
    1718 #: src/index.js:1502
     1860#: src/index.js:1658
    17191861msgid "Pause risky optimizations (Safe Mode)"
    17201862msgstr ""
    17211863
    1722 #: src/index.js:1509
     1864#: src/index.js:1665
    17231865msgid "Safe Mode is ON — combine/async CSS, remove-block-CSS and JavaScript delay are paused site-wide. Turn it off to resume full optimization."
    17241866msgstr ""
    17251867
    1726 #: src/index.js:1514
     1868#: src/index.js:1670
    17271869msgid "Undo last change"
    17281870msgstr ""
    17291871
    1730 #: src/index.js:1518
     1872#: src/index.js:1674
    17311873msgid "Restores the settings from just before your last save, reset or Safe Mode toggle."
    17321874msgstr ""
    17331875
    1734 #: src/index.js:1519
     1876#: src/index.js:1675
    17351877msgid "Nothing to undo yet — this becomes available after your next change."
    17361878msgstr ""
    17371879
    1738 #: src/index.js:1523
     1880#: src/index.js:1679
    17391881msgid "Server Requirements"
    17401882msgstr ""
    17411883
    1742 #: src/index.js:1524
     1884#: src/index.js:1680
    17431885msgid "What your host supports. Warnings here explain why a feature (like WebP/AVIF) may be unavailable or slow."
    17441886msgstr ""
    17451887
    1746 #: src/index.js:1525
     1888#: src/index.js:1681
    17471889msgid "Checking your server…"
    17481890msgstr ""
    17491891
    1750 #: src/index.js:1526
     1892#: src/index.js:1682
    17511893msgid "Could not read the server report."
    17521894msgstr ""
    17531895
    1754 #: src/index.js:1542
     1896#: src/index.js:1698
    17551897msgid "Re-check"
    17561898msgstr ""
    17571899
    1758 #: src/index.js:1546
     1900#: src/index.js:1702
    17591901msgid "Import / Export Settings"
    17601902msgstr ""
    17611903
    1762 #: src/index.js:1547
     1904#: src/index.js:1703
    17631905msgid "Back up your configuration or copy it to another site."
    17641906msgstr ""
    17651907
    1766 #: src/index.js:1550
     1908#: src/index.js:1706
    17671909msgid "Export settings"
    17681910msgstr ""
    17691911
    1770 #: src/index.js:1553
     1912#: src/index.js:1709
    17711913msgid "Import settings"
    17721914msgstr ""
    17731915
    1774 #: src/index.js:1558
     1916#: src/index.js:1714
    17751917msgid "Reset"
    17761918msgstr ""
    17771919
    1778 #: src/index.js:1559
     1920#: src/index.js:1715
    17791921msgid "Restore every module to its default configuration. This cannot be undone."
    17801922msgstr ""
    17811923
    1782 #: src/index.js:1562
     1924#: src/index.js:1718
    17831925msgid "Reset all settings to defaults"
    17841926msgstr ""
    17851927
    1786 #: src/index.js:1571
     1928#: src/index.js:1727
    17871929msgid "Swap, preconnect, and preload fonts to remove render-blocking requests."
    17881930msgstr ""
    17891931
    1790 #: src/index.js:1574
     1932#: src/index.js:1730
    17911933msgid "Font loading (font-display)"
    17921934msgstr ""
    17931935
    1794 #: src/index.js:1575
     1936#: src/index.js:1731
    17951937msgid "Optional = no layout shift (best for CLS): the web font is skipped on the first uncached load and used once cached. Swap = text stays visible but the web font swaps in and can shift layout (higher CLS). Block = brief invisible text, then the web font."
    17961938msgstr ""
    17971939
    1798 #: src/index.js:1578
     1940#: src/index.js:1734
    17991941msgid "Optional — no layout shift (recommended for CLS)"
    18001942msgstr ""
    18011943
    1802 #: src/index.js:1579
     1944#: src/index.js:1735
    18031945msgid "Swap — always show text (may shift layout)"
    18041946msgstr ""
    18051947
    1806 #: src/index.js:1580
     1948#: src/index.js:1736
    18071949msgid "Block — brief hidden text, then web font"
    18081950msgstr ""
    18091951
    1810 #: src/index.js:1584
     1952#: src/index.js:1740
    18111953msgid "Combine Google Fonts into one request"
    18121954msgstr ""
    18131955
    1814 #: src/index.js:1585
     1956#: src/index.js:1741
    18151957msgid "Page builders often load each Google Font family separately. This merges them into a single request."
    18161958msgstr ""
    18171959
    1818 #: src/index.js:1589
     1960#: src/index.js:1745
    18191961msgid "Load only the font weights your pages use"
    18201962msgstr ""
    18211963
    1822 #: src/index.js:1590
     1964#: src/index.js:1746
    18231965msgid "Drops unused weights (e.g. 100, 200, 900) and italics your pages never use — often cutting font CSS by more than half. Weights 400 and 700 are always kept. Turn off if some text looks thinner or bolder than intended."
    18241966msgstr ""
    18251967
    1826 #: src/index.js:1594
     1968#: src/index.js:1751
     1969msgid "Load Google Fonts without blocking the page"
     1970msgstr ""
     1971
     1972#: src/index.js:1752
     1973msgid "The page appears right away in a fallback font and switches to your Google Font as soon as it arrives, instead of waiting for Google first. A big win for the first paint on mobile. Applies when font display is Swap or Fallback; with Optional the stylesheet keeps loading first so text never shifts."
     1974msgstr ""
     1975
     1976#: src/index.js:1756
    18271977msgid "Preload these font files (URLs)"
    18281978msgstr ""
    18291979
    18301980#. translators: %d: number of items found in the database.
    1831 #: src/index.js:1605
     1981#: src/index.js:1767
    18321982#, js-format
    18331983msgid "%d item in your database."
    … …  
    18361986msgstr[1] ""
    18371987
    1838 #: src/index.js:1610
     1988#: src/index.js:1772
    18391989msgid "Database Optimization"
    18401990msgstr ""
    18411991
    1842 #: src/index.js:1611
     1992#: src/index.js:1773
    18431993msgid "Reduce bloat and reclaim space. Once a cleanup runs it cannot be undone, so back up your database first."
    18441994msgstr ""
    18451995
    1846 #: src/index.js:1627
     1996#: src/index.js:1789
    18471997msgid "Set it and forget it"
    18481998msgstr ""
    18491999
    1850 #: src/index.js:1628
     2000#: src/index.js:1790
    18512001msgid "Let Optimazz repeat the cleanups you picked above on a schedule, so your database stays lean without you lifting a finger."
    18522002msgstr ""
    18532003
    1854 #: src/index.js:1629
     2004#: src/index.js:1791
    18552005msgid "Schedule automatic cleanup"
    18562006msgstr ""
    18572007
    1858 #: src/index.js:1630
     2008#: src/index.js:1792
    18592009msgid "Uses WP-Cron to repeat the cleanups you enabled above."
    18602010msgstr ""
    18612011
    1862 #: src/index.js:1633
     2012#: src/index.js:1795
    18632013msgid "Cleanup frequency"
    18642014msgstr ""
    18652015
    1866 #: src/index.js:1637
     2016#: src/index.js:1799
    18672017msgid "Daily"
    18682018msgstr ""
    18692019
    1870 #: src/index.js:1638
     2020#: src/index.js:1800
    18712021msgid "Weekly"
    18722022msgstr ""
    18732023
    1874 #: src/index.js:1639
     2024#: src/index.js:1801
    18752025msgid "Monthly"
    18762026msgstr ""
    18772027
    1878 #: src/index.js:1646
     2028#: src/index.js:1808
    18792029msgid "Back up your database before you run a cleanup — a database optimization cannot be undone."
    18802030msgstr ""
    18812031
    1882 #: src/index.js:1651
     2032#: src/index.js:1813
    18832033msgid "Optimize now"
    18842034msgstr ""
    18852035
    1886 #: src/index.js:1654
     2036#: src/index.js:1816
    18872037msgid "Refresh counts"
    18882038msgstr ""
    18892039
    1890 #: src/index.js:1661
     2040#: src/index.js:1823
    18912041msgid "Heartbeat Control"
    18922042msgstr ""
    18932043
    1894 #: src/index.js:1662
     2044#: src/index.js:1824
    18952045msgid "Reduce or disable the WordPress Heartbeat API to lower server load."
    18962046msgstr ""
    18972047
    1898 #: src/index.js:1663
     2048#: src/index.js:1825
    18992049msgid "Enable Heartbeat control"
    19002050msgstr ""
    19012051
    1902 #: src/index.js:1667
     2052#: src/index.js:1829
    19032053msgid "Behavior in admin dashboard"
    19042054msgstr ""
    19052055
    1906 #: src/index.js:1669
     2056#: src/index.js:1831
    19072057msgid "Behavior in post editor"
    19082058msgstr ""
    19092059
    1910 #: src/index.js:1671
     2060#: src/index.js:1833
    19112061msgid "Behavior on front-end"
    19122062msgstr ""
    19132063
    1914 #: src/index.js:1680
     2064#: src/index.js:1842
    19152065msgid "Strip unnecessary WordPress front-end weight."
    19162066msgstr ""
    19172067
    1918 #: src/index.js:1682
     2068#: src/index.js:1844
    19192069msgid "Disable emojis"
    19202070msgstr ""
    19212071
    1922 #: src/index.js:1683
     2072#: src/index.js:1845
    19232073msgid "Remove the emoji detection script and styles."
    19242074msgstr ""
    19252075
    1926 #: src/index.js:1685
     2076#: src/index.js:1847
    19272077msgid "Disable embeds (oEmbed)"
    19282078msgstr ""
    19292079
    1930 #: src/index.js:1687
     2080#: src/index.js:1849
    19312081msgid "Disable dashicons on front-end"
    19322082msgstr ""
    19332083
    1934 #: src/index.js:1688
     2084#: src/index.js:1850
    19352085msgid "Only for anonymous visitors."
    19362086msgstr ""
    19372087
    1938 #: src/index.js:1690
     2088#: src/index.js:1852
    19392089msgid "Remove query strings from assets"
    19402090msgstr ""
    19412091
    1942 #: src/index.js:1691
     2092#: src/index.js:1853
    19432093msgid "Strip ?ver= for better proxy caching."
    19442094msgstr ""
    19452095
    1946 #: src/index.js:1693
     2096#: src/index.js:1855
    19472097msgid "Disable XML-RPC"
    19482098msgstr ""
    19492099
    1950 #: src/index.js:1695
     2100#: src/index.js:1857
    19512101msgid "Disable self-pingbacks"
    19522102msgstr ""
    19532103
    1954 #: src/index.js:1697
     2104#: src/index.js:1859
    19552105msgid "Remove head meta (generator, RSD, shortlink)"
    19562106msgstr ""
    19572107
    1958 #: src/index.js:1699
     2108#: src/index.js:1861
    19592109msgid "DNS-prefetch domains"
    19602110msgstr ""
    19612111
    1962 #: src/index.js:1701
     2112#: src/index.js:1863
    19632113msgid "Preconnect origins"
    19642114msgstr ""
    19652115
    1966 #: src/index.js:1707
     2116#: src/index.js:1869
    19672117msgid "Content Delivery Network"
    19682118msgstr ""
    19692119
    1970 #: src/index.js:1708
     2120#: src/index.js:1870
    19712121msgid "Rewrite static asset URLs to your CDN hostname."
    19722122msgstr ""
    19732123
    1974 #: src/index.js:1710
     2124#: src/index.js:1872
    19752125msgid "CDN URL (CNAME)"
    19762126msgstr ""
    19772127
    1978 #: src/index.js:1713
     2128#: src/index.js:1875
    19792129msgid "CSS, JS, image, and font URLs will be served from here."
    19802130msgstr ""
    19812131
    1982 #: src/index.js:1714
     2132#: src/index.js:1876
    19832133msgid "Exclude paths containing"
    19842134msgstr ""
    19852135
    1986 #: src/index.js:1781
    1987 msgid "You have unsaved changes"
    1988 msgstr ""
    1989 
    1990 #: src/index.js:1781
    1991 msgid "All changes saved"
    1992 msgstr ""
    1993 
    1994 #: src/index.js:1784
    1995 msgid "Save changes"
    1996 msgstr ""
     2136#: src/index.js:1952
     2137msgid "Preloading cache…"
     2138msgstr ""
     2139
     2140#: src/index.js:2012
     2141#: src/index.js:2025
     2142msgid "Something went wrong."
     2143msgstr ""
     2144
     2145#: src/index.js:2059
     2146msgid "Network settings saved."
     2147msgstr ""
     2148
     2149#: src/index.js:2075
     2150msgid "Caching for every site"
     2151msgstr ""
     2152
     2153#: src/index.js:2076
     2154msgid "Sites"
     2155msgstr ""
     2156
     2157#: src/index.js:2076
     2158msgid "Cache per site"
     2159msgstr ""
     2160
     2161#: src/index.js:2086
     2162msgid "Optimazz is not network-activated"
     2163msgstr ""
     2164
     2165#: src/index.js:2087
     2166msgid "Only the sites where Optimazz is activated are cached. Network-activate it on Network Admin → Plugins to cache every site."
     2167msgstr ""
     2168
     2169#: src/index.js:2092
     2170msgid "Serve ready-made HTML for every site instead of running PHP on each request. Each site keeps its own cache and its own exclusions."
     2171msgstr ""
     2172
     2173#: src/index.js:2100
     2174msgid "Add Expires / Cache-Control rules to the shared .htaccess so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only."
     2175msgstr ""
     2176
     2177#: src/index.js:2103
     2178msgid "How long a cached page is kept. Pages are always purged as soon as their content changes."
     2179msgstr ""
     2180
     2181#: src/index.js:2113
     2182msgid "Preload every site automatically"
     2183msgstr ""
     2184
     2185#: src/index.js:2114
     2186msgid "Each site warms its own pages on a schedule and right after its cache is cleared."
     2187msgstr ""
     2188
     2189#. translators: %s: name of another caching plugin.
     2190#: src/index.js:2135
     2191#, js-format
     2192msgid "%s controls page caching (advanced-cache.php)"
     2193msgstr ""
     2194
     2195#: src/index.js:2138
     2196msgid "Optimazz cannot cache while another plugin owns the cache file. Deactivate that plugin, or let Optimazz take over."
     2197msgstr ""
     2198
     2199#: src/index.js:2139
     2200msgid "Optimazz now handles page caching."
     2201msgstr ""
     2202
     2203#: src/index.js:2146
     2204msgid "Cache engine installed (advanced-cache.php)"
     2205msgstr ""
     2206
     2207#: src/index.js:2147
     2208msgid "WP_CACHE is on in wp-config.php"
     2209msgstr ""
     2210
     2211#: src/index.js:2148
     2212msgid "wp-config.php is not writable — add define( 'WP_CACHE', true ); yourself"
     2213msgstr ""
     2214
     2215#: src/index.js:2149
     2216msgid "The wp-content folder is not writable"
     2217msgstr ""
     2218
     2219#: src/index.js:2167
     2220msgid "All sites"
     2221msgstr ""
     2222
     2223#. translators: 1: sites with Optimazz, 2: all sites.
     2224#: src/index.js:2170
     2225#, js-format
     2226msgid "Optimazz runs on %1$d of %2$d sites. Each site has its own cache."
     2227msgstr ""
     2228
     2229#: src/index.js:2172
     2230msgid "Page caching is off for the network — turn it on under Page Cache to purge or preload sites."
     2231msgstr ""
     2232
     2233#: src/index.js:2175
     2234msgid "Every site’s cache was purged."
     2235msgstr ""
     2236
     2237#: src/index.js:2176
     2238msgid "Purge all sites"
     2239msgstr ""
     2240
     2241#: src/index.js:2194
     2242msgid "Site cache purged."
     2243msgstr ""
     2244
     2245#: src/index.js:2215
     2246msgid "Optimazz is not active on this site, so its pages are not cached."
     2247msgstr ""
     2248
     2249#: src/index.js:2225
     2250msgid "Network"
     2251msgstr ""
  • optimazz/tags/1.1.0/optimazz.php

    r3723128 r3743505  
    44 * Plugin URI:        https://wordpress.org/plugins/optimazz/
    55 * Description:        All-in-one WordPress performance optimization — page caching, JS delay, and CSS optimization.
    6  * Version:           1.0.24
     6 * Version:           1.1.0
    77 * Requires at least: 6.0
    88 * Requires PHP:      7.4
    … …  
    2626}
    2727
    28 define( 'OPTIMAZZ_VERSION', '1.0.24' );
     28define( 'OPTIMAZZ_VERSION', '1.1.0' );
    2929define( 'OPTIMAZZ_FILE', __FILE__ );
    3030define( 'OPTIMAZZ_DIR', plugin_dir_path( __FILE__ ) );
  • optimazz/tags/1.1.0/optimazz.pot

    r3723128 r3743505  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Optimazz – Website Optimization and Speedup 1.0.24\n"
     5"Project-Id-Version: Optimazz – Website Optimization and Speedup 1.1.0\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/optimazz\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-01T12:01:49+00:00\n"
     12"POT-Creation-Date: 2026-10-11T19:05:58+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    4040msgstr ""
    4141
    42 #: includes/Admin/Admin.php:149
    43 #: includes/Admin/Admin.php:150
     42#: includes/Admin/Admin.php:155
     43#: includes/Admin/Admin.php:156
     44#: includes/Admin/Admin.php:290
     45#: includes/Admin/Admin.php:291
    4446msgid "Optimazz"
    4547msgstr ""
    4648
    47 #: includes/Admin/Admin.php:166
     49#: includes/Admin/Admin.php:172
    4850#: src/index.js:36
    4951msgid "Dashboard"
    5052msgstr ""
    5153
    52 #: includes/Admin/Admin.php:167
     54#: includes/Admin/Admin.php:173
    5355#: includes/Modules/Cache/CacheModule.php:24
    5456#: src/index.js:37
    55 #: src/index.js:1171
     57#: src/index.js:1317
     58#: src/index.js:2075
     59#: src/index.js:2091
    5660msgid "Page Cache"
    5761msgstr ""
    5862
    59 #: includes/Admin/Admin.php:168
     63#: includes/Admin/Admin.php:174
    6064#: src/index.js:38
    61 #: src/index.js:1150
     65#: src/index.js:1296
    6266msgid "File Optimization"
    6367msgstr ""
    6468
    65 #: includes/Admin/Admin.php:169
     69#: includes/Admin/Admin.php:175
    6670#: src/index.js:39
    6771msgid "Media"
    6872msgstr ""
    6973
    70 #: includes/Admin/Admin.php:170
     74#: includes/Admin/Admin.php:176
    7175#: includes/Modules/Database/DatabaseModule.php:26
    7276#: src/index.js:43
    … …  
    7478msgstr ""
    7579
    76 #: includes/Admin/Admin.php:171
    77 #: includes/Admin/Admin.php:229
    78 #: includes/Admin/Admin.php:233
     80#: includes/Admin/Admin.php:177
     81#: includes/Admin/Admin.php:235
     82#: includes/Admin/Admin.php:239
    7983#: src/index.js:47
    8084msgid "Tools"
    8185msgstr ""
    8286
    83 #: includes/Admin/Admin.php:193
     87#: includes/Admin/Admin.php:199
    8488#: includes/Admin/AdminBar.php:52
     89#: src/index.js:2198
    8590msgid "Settings"
    8691msgstr ""
    8792
    88 #: includes/Admin/Admin.php:224
     93#: includes/Admin/Admin.php:230
    8994msgid "Optimazz Safe Mode is on."
    9095msgstr ""
    9196
    92 #: includes/Admin/Admin.php:225
     97#: includes/Admin/Admin.php:231
    9398msgid "Some pages were loading without styling — their optimized CSS could not be found, usually because a server or CDN cache was serving old pages. Optimazz paused advanced CSS/JS optimization to keep your site working."
    9499msgstr ""
    95100
    96 #: includes/Admin/Admin.php:227
     101#: includes/Admin/Admin.php:233
    97102msgid "It broke again shortly after full optimization was restored, so Safe Mode will stay on until you switch it off in"
    98103msgstr ""
    99104
    100 #: includes/Admin/Admin.php:228
     105#: includes/Admin/Admin.php:234
    101106msgid "It re-checks automatically and restores full optimization as soon as pages load correctly — clearing your server/CDN cache speeds that up. You can also manage it in"
    102107msgstr ""
    103108
    104 #: includes/Admin/Admin.php:232
     109#: includes/Admin/Admin.php:238
    105110msgid "Your pages are loading correctly again — you can switch Safe Mode off in"
    106111msgstr ""
    107112
    108 #: includes/Admin/Admin.php:236
     113#: includes/Admin/Admin.php:242
    109114msgid "Pages are loading correctly again, so Safe Mode was switched off automatically and full optimization is back on."
    110115msgstr ""
    111116
    112 #: includes/Admin/Admin.php:250
     117#: includes/Admin/Admin.php:256
    113118msgid "wp-config.php is not writable, so page caching could not be enabled. Add"
    114119msgstr ""
    115120
    116 #: includes/Admin/Admin.php:252
     121#: includes/Admin/Admin.php:258
    117122msgid "manually, or make wp-config.php writable."
    118123msgstr ""
    119124
    120 #: includes/Admin/Admin.php:259
     125#: includes/Admin/Admin.php:265
    121126msgid "Optimazz cache purged."
    122127msgstr ""
    123128
    124 #: includes/Admin/Admin.php:260
     129#: includes/Admin/Admin.php:266
    125130msgid "Optimazz cache preload started in the background."
    126131msgstr ""
    127132
    128 #: includes/Admin/Admin.php:261
     133#: includes/Admin/Admin.php:267
    129134msgid "Optimazz cache cleared; preload started in the background."
    130135msgstr ""
    131136
    132 #: includes/Admin/AdminBar.php:53
     137#: includes/Admin/AdminBar.php:57
    133138msgid "Clear & Preload Cache"
    134139msgstr ""
    135140
    136 #: includes/Admin/AdminBar.php:54
    137 #: src/index.js:1451
     141#: includes/Admin/AdminBar.php:58
     142#: src/index.js:1597
    138143msgid "Preload Cache"
    139144msgstr ""
    140145
    141 #: includes/Admin/AdminBar.php:55
     146#: includes/Admin/AdminBar.php:59
    142147msgid "Purge Cache"
    143148msgstr ""
    144149
    145 #: includes/Admin/AdminBar.php:73
     150#: includes/Admin/AdminBar.php:77
    146151msgid "Documentation"
    147152msgstr ""
    148153
    149 #: includes/Admin/AdminBar.php:98
    150 #: includes/Admin/CacheConflictNotice.php:124
    151 #: includes/Admin/CacheConflictNotice.php:154
     154#: includes/Admin/AdminBar.php:102
     155#: includes/Admin/CacheConflictNotice.php:126
     156#: includes/Admin/CacheConflictNotice.php:156
    152157msgid "Not allowed."
    153158msgstr ""
    … …  
    162167
    163168#: includes/Admin/CacheConflictNotice.php:88
    164 #: includes/Modules/Cache/Preloader.php:714
     169#: includes/Modules/Cache/Preloader.php:740
     170#: src/index.js:2136
    165171msgid "Another plugin"
    166172msgstr ""
    … …  
    180186#: includes/Admin/CacheConflictNotice.php:108
    181187#: includes/Admin/CacheConflictNotice.php:110
     188#: src/index.js:2140
    182189msgid "Use Optimazz for caching"
    183190msgstr ""
    … …  
    187194msgstr ""
    188195
    189 #: includes/Admin/RestController.php:467
     196#: includes/Admin/NetworkRestController.php:115
     197#: includes/Admin/NetworkRestController.php:134
     198msgid "Page caching is off for this site."
     199msgstr ""
     200
     201#: includes/Admin/NetworkRestController.php:153
     202msgid "Optimazz is not active on any site yet. Network-activate it (or activate it on a site) first."
     203msgstr ""
     204
     205#: includes/Admin/RestController.php:385
     206#: includes/Admin/RestController.php:588
     207#: includes/Admin/RestController.php:614
     208msgid "Page caching is not available on multisite networks."
     209msgstr ""
     210
     211#: includes/Admin/RestController.php:479
    190212msgid "PHP version"
    191213msgstr ""
    192214
    193 #: includes/Admin/RestController.php:470
     215#: includes/Admin/RestController.php:482
    194216msgid "Optimazz requires PHP 7.4 or newer."
    195217msgstr ""
    196218
    197 #: includes/Admin/RestController.php:473
     219#: includes/Admin/RestController.php:485
    198220msgid "WordPress version"
    199221msgstr ""
    200222
    201 #: includes/Admin/RestController.php:479
     223#: includes/Admin/RestController.php:491
    202224msgid "GD (image library)"
    203225msgstr ""
    204226
    205 #: includes/Admin/RestController.php:480
    206 #: includes/Admin/RestController.php:489
    207 #: includes/Admin/RestController.php:525
     227#: includes/Admin/RestController.php:492
     228#: includes/Admin/RestController.php:501
     229#: includes/Admin/RestController.php:537
    208230msgid "Enabled"
    209231msgstr ""
    210232
    211 #: includes/Admin/RestController.php:480
    212 #: includes/Admin/RestController.php:489
     233#: includes/Admin/RestController.php:492
     234#: includes/Admin/RestController.php:501
    213235msgid "Not installed (optional)"
    214236msgstr ""
    215237
    216 #: includes/Admin/RestController.php:484
     238#: includes/Admin/RestController.php:496
    217239msgid "GD is enabled but without WebP support — ask your host to enable WebP in GD."
    218240msgstr ""
    219241
    220 #: includes/Admin/RestController.php:485
     242#: includes/Admin/RestController.php:497
    221243msgid "The PHP GD extension is not enabled. GD is the most common engine for image compression and WebP. Ask your host to enable the \"php-gd\" extension."
    222244msgstr ""
    223245
    224 #: includes/Admin/RestController.php:488
     246#: includes/Admin/RestController.php:500
    225247msgid "ImageMagick (Imagick)"
    226248msgstr ""
    227249
    228 #: includes/Admin/RestController.php:489
     250#: includes/Admin/RestController.php:501
    229251msgid "Not enabled"
    230252msgstr ""
    231253
    232 #: includes/Admin/RestController.php:496
     254#: includes/Admin/RestController.php:508
    233255msgid "Optional — GD is already handling images. Imagick can add AVIF and higher-quality output if your host enables the \"php-imagick\" extension."
    234256msgstr ""
    235257
    236 #: includes/Admin/RestController.php:497
     258#: includes/Admin/RestController.php:509
    237259msgid "The PHP Imagick extension is not enabled either. With neither GD nor Imagick, image compression and WebP/AVIF cannot run. Ask your host to enable \"php-gd\" or \"php-imagick\"."
    238260msgstr ""
    239261
    240 #: includes/Admin/RestController.php:500
     262#: includes/Admin/RestController.php:512
    241263msgid "WebP support"
    242264msgstr ""
    243265
    244 #: includes/Admin/RestController.php:501
    245 #: includes/Admin/RestController.php:507
     266#: includes/Admin/RestController.php:513
     267#: includes/Admin/RestController.php:519
    246268msgid "Available"
    247269msgstr ""
    248270
    249 #: includes/Admin/RestController.php:501
    250 #: includes/Admin/RestController.php:507
     271#: includes/Admin/RestController.php:513
     272#: includes/Admin/RestController.php:519
    251273msgid "Not available"
    252274msgstr ""
    253275
    254 #: includes/Admin/RestController.php:503
     276#: includes/Admin/RestController.php:515
    255277msgid "This server cannot generate WebP images. Update GD/Imagick or ask your host to enable WebP."
    256278msgstr ""
    257279
    258 #: includes/Admin/RestController.php:506
     280#: includes/Admin/RestController.php:518
    259281msgid "AVIF support"
    260282msgstr ""
    261283
    262 #: includes/Admin/RestController.php:509
     284#: includes/Admin/RestController.php:521
    263285msgid "This server cannot generate AVIF images (needs PHP 8.1+ GD with AVIF, or ImageMagick with the AVIF delegate). WebP still works."
    264286msgstr ""
    265287
    266 #: includes/Admin/RestController.php:512
     288#: includes/Admin/RestController.php:524
    267289msgid "PHP memory limit"
    268290msgstr ""
    269291
    270 #: includes/Admin/RestController.php:515
     292#: includes/Admin/RestController.php:527
    271293msgid "Image optimization works best with 256 MB or more. On lower limits Optimazz processes fewer images per request to stay safe."
    272294msgstr ""
    273295
    274 #: includes/Admin/RestController.php:518
     296#: includes/Admin/RestController.php:530
    275297msgid "Max execution time"
    276298msgstr ""
    277299
    278 #: includes/Admin/RestController.php:519
     300#: includes/Admin/RestController.php:531
    279301msgid "Unlimited"
    280302msgstr ""
    281303
    282 #: includes/Admin/RestController.php:521
     304#: includes/Admin/RestController.php:533
    283305msgid "Under 30 seconds can interrupt bulk AVIF conversion of large images."
    284306msgstr ""
    285307
    286 #: includes/Admin/RestController.php:524
     308#: includes/Admin/RestController.php:536
    287309msgid "WP-Cron"
    288310msgstr ""
    289311
    290 #: includes/Admin/RestController.php:525
     312#: includes/Admin/RestController.php:537
    291313msgid "Disabled"
    292314msgstr ""
    293315
    294 #: includes/Admin/RestController.php:527
     316#: includes/Admin/RestController.php:539
    295317msgid "WP-Cron is disabled, so scheduled cache preloading relies on a real system cron job being configured."
    296318msgstr ""
    297319
    298 #: includes/Admin/RestController.php:541
     320#: includes/Admin/RestController.php:553
    299321msgid "GD and Imagick are"
    300322msgstr ""
    301323
    302 #: includes/Admin/RestController.php:542
     324#: includes/Admin/RestController.php:554
    303325msgid "GD is"
    304326msgstr ""
    305327
    306 #: includes/Admin/RestController.php:542
     328#: includes/Admin/RestController.php:554
    307329msgid "Imagick is"
    308330msgstr ""
    309331
    310 #: includes/Admin/RestController.php:544
     332#: includes/Admin/RestController.php:556
    311333msgid "PHP that runs this site"
    312334msgstr ""
    313335
    314336#. translators: 1: which engine is missing, 2: php.ini path, 3: PHP version
    315 #: includes/Admin/RestController.php:550
     337#: includes/Admin/RestController.php:562
    316338#, php-format
    317339msgid "This is the exact PHP handling your site — it loads its settings from %2$s. If you already enabled an image library in your hosting panel but %1$s still shown as not enabled, you almost certainly enabled it for a different PHP version or handler. It must be enabled for THIS PHP (%3$s). After enabling, restart PHP (or PHP-FPM) and click Re-check."
    … …  
    319341
    320342#. translators: 1: which engine is missing, 2: PHP version
    321 #: includes/Admin/RestController.php:557
     343#: includes/Admin/RestController.php:569
    322344#, php-format
    323345msgid "This is the exact PHP handling your site. If you already enabled an image library in your hosting panel but %1$s still shown as not enabled, you enabled it for a different PHP version. It must be enabled for THIS PHP (%2$s). After enabling, restart PHP and click Re-check."
    324346msgstr ""
    325347
    326 #: includes/Admin/RestController.php:707
     348#: includes/Admin/RestController.php:725
    327349msgid "Image processing is unavailable: this server has no GD or ImageMagick extension. Ask your host to enable \"php-gd\" (or \"php-imagick\"), then try again. See Tools → Server Requirements."
    328350msgstr ""
    … …  
    336358msgstr ""
    337359
    338 #: includes/Core/Recommendations.php:61
    339 #: src/index.js:971
     360#: includes/Core/Recommendations.php:63
     361#: src/index.js:1117
    340362msgid "Enable Page Cache"
    341363msgstr ""
    342364
    343 #: includes/Core/Recommendations.php:62
     365#: includes/Core/Recommendations.php:64
    344366msgid "Serve static HTML instead of running PHP on every request — the single biggest speed win."
    345367msgstr ""
    346368
    347 #: includes/Core/Recommendations.php:67
     369#: includes/Core/Recommendations.php:69
    348370msgid "Turn on cache preloading"
    349371msgstr ""
    350372
    351 #: includes/Core/Recommendations.php:68
     373#: includes/Core/Recommendations.php:70
    352374msgid "Warm the cache automatically so visitors never hit a cold, uncached page."
    353375msgstr ""
    354376
    355 #: includes/Core/Recommendations.php:76
     377#: includes/Core/Recommendations.php:78
    356378msgid "Enable JavaScript optimization"
    357379msgstr ""
    358380
    359 #: includes/Core/Recommendations.php:77
     381#: includes/Core/Recommendations.php:79
    360382msgid "Defer and delay scripts to cut Total Blocking Time — the heaviest-weighted mobile metric."
    361383msgstr ""
    362384
    363 #: includes/Core/Recommendations.php:84
     385#: includes/Core/Recommendations.php:86
    364386msgid "Delay JavaScript until interaction"
    365387msgstr ""
    366388
    367 #: includes/Core/Recommendations.php:85
     389#: includes/Core/Recommendations.php:87
    368390msgid "Delay non-critical scripts (analytics, chat, pixels) until the first interaction to reduce blocking time."
    369391msgstr ""
    370392
    371 #: includes/Core/Recommendations.php:93
     393#: includes/Core/Recommendations.php:95
    372394msgid "Enable CSS optimization"
    373395msgstr ""
    374396
    375 #: includes/Core/Recommendations.php:94
     397#: includes/Core/Recommendations.php:96
    376398msgid "Minify CSS to remove render-blocking bytes and speed up First Contentful Paint."
    377399msgstr ""
    378400
    379 #: includes/Core/Recommendations.php:102
     401#: includes/Core/Recommendations.php:104
    380402msgid "Enable Image optimization"
    381403msgstr ""
    382404
    383 #: includes/Core/Recommendations.php:103
     405#: includes/Core/Recommendations.php:105
    384406msgid "Lazy-load images, reserve their space (to prevent layout shift) and serve next-gen formats."
    385407msgstr ""
    386408
    387 #: includes/Core/Recommendations.php:109
     409#: includes/Core/Recommendations.php:111
    388410msgid "Reserve image space (fix layout shift)"
    389411msgstr ""
    390412
    391 #: includes/Core/Recommendations.php:110
     413#: includes/Core/Recommendations.php:112
    392414msgid "Add missing image dimensions and aspect-ratio so images do not push content down as they load (lowers CLS)."
    393415msgstr ""
    394416
    395 #: includes/Core/Recommendations.php:116
    396 #: src/index.js:948
    397 #: src/index.js:1369
     417#: includes/Core/Recommendations.php:118
     418#: src/index.js:1092
     419#: src/index.js:1515
    398420msgid "Lazy-load images"
    399421msgstr ""
    400422
    401 #: includes/Core/Recommendations.php:117
     423#: includes/Core/Recommendations.php:119
    402424msgid "Load off-screen images only when scrolled into view to speed up the initial load."
    403425msgstr ""
    404426
    405 #: includes/Core/Recommendations.php:124
     427#: includes/Core/Recommendations.php:126
    406428msgid "Serve WebP images"
    407429msgstr ""
    408430
    409 #: includes/Core/Recommendations.php:125
     431#: includes/Core/Recommendations.php:127
    410432msgid "Generate and serve WebP copies — typically 25–35% smaller than JPEG/PNG at the same quality."
    411433msgstr ""
    412434
    413 #: includes/Core/Recommendations.php:136
     435#: includes/Core/Recommendations.php:138
    414436msgid "Set font loading to \"Optional\" (reduce CLS)"
    415437msgstr ""
    416438
    417 #: includes/Core/Recommendations.php:137
     439#: includes/Core/Recommendations.php:139
    418440msgid "The \"swap\" mode lets web fonts reflow the page as they load, raising Cumulative Layout Shift. \"Optional\" removes that shift."
    419441msgstr ""
    420442
    421 #: includes/Core/Recommendations.php:143
     443#: includes/Core/Recommendations.php:145
    422444msgid "Enable Font optimization"
    423445msgstr ""
    424446
    425 #: includes/Core/Recommendations.php:144
     447#: includes/Core/Recommendations.php:146
    426448msgid "Preconnect to font hosts and control font-display to reduce render-blocking and layout shift."
    427449msgstr ""
    428450
    429 #: includes/Core/Recommendations.php:152
     451#: includes/Core/Recommendations.php:154
    430452msgid "Enable Database cleanup"
    431453msgstr ""
    432454
    433 #: includes/Core/Recommendations.php:153
     455#: includes/Core/Recommendations.php:155
    434456msgid "Remove revisions, auto-drafts, spam and expired transients, and optimize tables to keep the database lean."
    435457msgstr ""
    436458
    437 #: includes/Core/Recommendations.php:161
     459#: includes/Core/Recommendations.php:163
    438460msgid "Enable an image library on your server"
    439461msgstr ""
    440462
    441 #: includes/Core/Recommendations.php:162
     463#: includes/Core/Recommendations.php:164
    442464msgid "Your server has neither GD nor ImageMagick, so image compression and WebP/AVIF cannot run. Ask your host to enable the \"php-gd\" (or \"php-imagick\") extension. See Tools → Server Requirements."
    443465msgstr ""
    444466
     467#: includes/Modules/Cache/Preloader.php:116
     468msgid "the home page"
     469msgstr ""
     470
    445471#. translators: %d: number of category / tag archive pages.
    446 #: includes/Modules/Cache/Preloader.php:124
     472#: includes/Modules/Cache/Preloader.php:127
    447473#, php-format
    448474msgid "%d category or tag page"
    … …  
    451477msgstr[1] ""
    452478
    453 #: includes/Modules/Cache/Preloader.php:681
     479#: includes/Modules/Cache/Preloader.php:707
    454480msgid "your host's server cache"
    455481msgstr ""
    456482
    457 #: includes/Modules/Cache/Preloader.php:685
     483#: includes/Modules/Cache/Preloader.php:711
    458484msgid "a server or CDN cache"
    459485msgstr ""
    460486
    461 #: includes/Modules/Cache/Preloader.php:704
     487#: includes/Modules/Cache/Preloader.php:730
    462488msgid "The site could not load its own pages (the loopback request failed or timed out). Some hosts block a server from requesting its own website — ask your host to allow it."
    463489msgstr ""
    464490
    465491#. translators: %s: name of another caching plugin.
    466 #: includes/Modules/Cache/Preloader.php:713
     492#: includes/Modules/Cache/Preloader.php:739
    467493#, php-format
    468494msgid "%s controls page caching (advanced-cache.php), so Optimazz cannot save pages. Deactivate it, or use \"Take over caching\" in Cache health."
    469495msgstr ""
    470496
    471 #: includes/Modules/Cache/Preloader.php:718
     497#: includes/Modules/Cache/Preloader.php:744
    472498msgid "The cache file advanced-cache.php is missing. Turn Page Cache off and on again to reinstall it, and make sure the wp-content folder is writable."
    473499msgstr ""
    474500
    475 #: includes/Modules/Cache/Preloader.php:721
     501#: includes/Modules/Cache/Preloader.php:747
    476502msgid "WP_CACHE is not enabled in wp-config.php, so WordPress never loads the cache. Make wp-config.php writable and save the Cache settings, or add define( 'WP_CACHE', true ); yourself."
    477503msgstr ""
    478504
    479505#. translators: %s: name of a server or CDN cache, e.g. "Cloudflare".
    480 #: includes/Modules/Cache/Preloader.php:726
     506#: includes/Modules/Cache/Preloader.php:752
    481507#, php-format
    482508msgid "%s answered from its own cache before WordPress ran, so Optimazz never saw these requests. Purge that cache, or turn it off for HTML pages if you want Optimazz to cache them."
    483509msgstr ""
    484510
    485 #: includes/Modules/Cache/Preloader.php:730
     511#: includes/Modules/Cache/Preloader.php:756
    486512msgid "The Optimazz cache engine did not run for these requests. Usually a server-level cache or proxy (for example LiteSpeed, Varnish or a host cache) answers before WordPress loads. Check Cache health, or ask your host."
    487513msgstr ""
    488514
    489515#. translators: %s: HTTP status code, e.g. 404.
    490 #: includes/Modules/Cache/Preloader.php:737
     516#: includes/Modules/Cache/Preloader.php:763
    491517#, php-format
    492518msgid "Pages answered with HTTP %s instead of 200, and only normal pages are cached. Check that these URLs open correctly."
    493519msgstr ""
    494520
    495 #: includes/Modules/Cache/Preloader.php:742
     521#: includes/Modules/Cache/Preloader.php:768
    496522msgid "Page Cache is switched off."
    497523msgstr ""
    498524
    499 #: includes/Modules/Cache/Preloader.php:743
     525#: includes/Modules/Cache/Preloader.php:769
    500526msgid "Another plugin or the theme marks these pages \"do not cache\" (DONOTCACHEPAGE), so they are skipped. Membership, security or form plugins often do this."
    501527msgstr ""
    502528
    503 #: includes/Modules/Cache/Preloader.php:744
     529#: includes/Modules/Cache/Preloader.php:770
    504530msgid "The web address used to reach the site does not match the WordPress Site Address (for example www vs non-www). Check Settings → General."
    505531msgstr ""
    506532
    507 #: includes/Modules/Cache/Preloader.php:745
     533#: includes/Modules/Cache/Preloader.php:771
    508534msgid "Cache files could not be written. Make the wp-content/cache folder writable (permissions 755)."
    509535msgstr ""
    510536
    511 #: includes/Modules/Cache/Preloader.php:746
     537#: includes/Modules/Cache/Preloader.php:772
    512538msgid "These pages match your \"Never cache these URLs\" setting."
    513539msgstr ""
    514540
    515 #: includes/Modules/Cache/Preloader.php:747
     541#: includes/Modules/Cache/Preloader.php:773
    516542msgid "A cookie on the request matches your \"Never cache when these cookies are set\" setting."
    517543msgstr ""
    518544
    519 #: includes/Modules/Cache/Preloader.php:748
     545#: includes/Modules/Cache/Preloader.php:774
    520546msgid "The preloader's user-agent matches your \"Never cache for these user agents\" setting."
    521547msgstr ""
    522548
    523 #: includes/Modules/Cache/Preloader.php:749
     549#: includes/Modules/Cache/Preloader.php:775
    524550msgid "The pages came back empty or almost empty, so there was nothing to cache."
    525551msgstr ""
    526552
    527 #: includes/Modules/Cache/Preloader.php:750
     553#: includes/Modules/Cache/Preloader.php:776
    528554msgid "WordPress treated the request as logged in, so it was not cached."
    529555msgstr ""
    530556
    531 #: includes/Modules/Cache/Preloader.php:751
     557#: includes/Modules/Cache/Preloader.php:777
    532558msgid "These URLs show a \"not found\" (404) page, which is never cached."
    533559msgstr ""
    534560
    535561#. translators: %s: internal reason code.
    536 #: includes/Modules/Cache/Preloader.php:757
     562#: includes/Modules/Cache/Preloader.php:783
    537563#, php-format
    538564msgid "The cache skipped these pages (reason: %s)."
    … …  
    552578msgstr ""
    553579
    554 #: includes/Modules/Fonts/FontsModule.php:29
     580#: includes/Modules/Fonts/FontsModule.php:38
    555581#: src/index.js:40
    556 #: src/index.js:1570
     582#: src/index.js:1726
    557583msgid "Fonts"
    558584msgstr ""
    … …  
    568594
    569595#: includes/Modules/Media/MediaModule.php:75
    570 #: src/index.js:1359
     596#: src/index.js:1505
    571597msgid "Image Optimization"
    572598msgstr ""
    … …  
    578604#: includes/Modules/Tweaks/TweaksModule.php:25
    579605#: src/index.js:45
    580 #: src/index.js:1679
     606#: src/index.js:1841
    581607msgid "Tweaks"
    582608msgstr ""
    … …  
    603629
    604630#: src/index.js:41
    605 #: src/index.js:1055
     631#: src/index.js:1201
     632#: src/index.js:2191
    606633msgid "Preload"
    607634msgstr ""
    … …  
    612639
    613640#: src/index.js:42
    614 #: src/index.js:1482
     641#: src/index.js:1638
    615642msgid "Advanced Rules"
    616643msgstr ""
    … …  
    640667msgstr ""
    641668
    642 #: src/index.js:64
     669#: src/index.js:81
     670msgid "every hour"
     671msgstr ""
     672
     673#: src/index.js:82
     674#: src/index.js:84
     675msgid "twice a day"
     676msgstr ""
     677
     678#: src/index.js:83
     679msgid "once a day"
     680msgstr ""
     681
     682#: src/index.js:88
     683msgid "never expires"
     684msgstr ""
     685
     686#: src/index.js:89
     687#: src/index.js:1231
     688#: src/index.js:1333
     689#: src/index.js:2107
     690msgid "12 hours"
     691msgstr ""
     692
     693#: src/index.js:90
     694#: src/index.js:1232
     695#: src/index.js:1334
     696#: src/index.js:2108
     697msgid "1 day"
     698msgstr ""
     699
     700#: src/index.js:91
     701#: src/index.js:1233
     702#: src/index.js:1335
     703#: src/index.js:2109
     704msgid "1 week"
     705msgstr ""
     706
     707#: src/index.js:92
     708#: src/index.js:1234
     709#: src/index.js:1336
     710#: src/index.js:2110
     711msgid "1 month"
     712msgstr ""
     713
     714#. translators: %d: number of hours.
     715#: src/index.js:95
     716#: src/index.js:1237
     717#, js-format
     718msgid "%d hour"
     719msgid_plural "%d hours"
     720msgstr[0] ""
     721msgstr[1] ""
     722
     723#: src/index.js:102
     724msgid "Open Network Admin → Optimazz"
     725msgstr ""
     726
     727#: src/index.js:111
     728msgid "Page caching is off for this network"
     729msgstr ""
     730
     731#: src/index.js:113
     732msgid "Turn it on for every site in Network Admin → Optimazz. Each site gets its own cache, and keeps its own exclusions here under Advanced Rules."
     733msgstr ""
     734
     735#: src/index.js:114
     736msgid "Your network administrator can turn it on for every site in Network Admin → Optimazz. All other optimizations — CSS, JavaScript, images, fonts and more — work normally on this site."
     737msgstr ""
     738
     739#: src/index.js:128
     740msgid "Page caching is on — managed for the whole network"
     741msgstr ""
     742
     743#. translators: 1: yes/no, 2: cache lifetime, 3: preload frequency.
     744#: src/index.js:131
     745#, js-format
     746msgid "Separate mobile cache: %1$s · Cache lifetime: %2$s · Automatic preload: %3$s."
     747msgstr ""
     748
     749#: src/index.js:132
     750msgid "yes"
     751msgstr ""
     752
     753#: src/index.js:132
     754msgid "no"
     755msgstr ""
     756
     757#: src/index.js:134
     758#: src/index.js:1247
     759msgid "off"
     760msgstr ""
     761
     762#: src/index.js:136
     763msgid "This site has its own cache. Purge or preload it from the Dashboard or the Preload screen, and set its own exclusions under Advanced Rules."
     764msgstr ""
     765
     766#: src/index.js:146
    643767msgid "Content leftovers"
    644768msgstr ""
    645769
    646 #: src/index.js:65
     770#: src/index.js:147
    647771msgid "Old post data that piles up over time. Removing it is permanent, so keep anything you still need."
    648772msgstr ""
    649773
    650 #: src/index.js:67
     774#: src/index.js:149
    651775msgid "Post revisions"
    652776msgstr ""
    653777
    654 #: src/index.js:67
     778#: src/index.js:149
    655779msgid "Old copies of posts saved automatically. Deleting them keeps only your current content."
    656780msgstr ""
    657781
    658 #: src/index.js:68
     782#: src/index.js:150
    659783msgid "Auto drafts"
    660784msgstr ""
    661785
    662 #: src/index.js:68
     786#: src/index.js:150
    663787msgid "Empty drafts WordPress creates while you write. Safe to remove."
    664788msgstr ""
    665789
    666 #: src/index.js:69
     790#: src/index.js:151
    667791msgid "Trashed posts"
    668792msgstr ""
    669793
    670 #: src/index.js:69
     794#: src/index.js:151
    671795msgid "Posts in the trash that have not been permanently deleted yet."
    672796msgstr ""
    673797
    674 #: src/index.js:73
     798#: src/index.js:155
    675799msgid "Comment clutter"
    676800msgstr ""
    677801
    678 #: src/index.js:74
     802#: src/index.js:156
    679803msgid "Spam and trashed comments you are never going to approve. Clearing them keeps the comments table light."
    680804msgstr ""
    681805
    682 #: src/index.js:76
     806#: src/index.js:158
    683807msgid "Spam comments"
    684808msgstr ""
    685809
    686 #: src/index.js:76
     810#: src/index.js:158
    687811msgid "Comments marked as spam. Removing them frees up the comments table."
    688812msgstr ""
    689813
    690 #: src/index.js:77
     814#: src/index.js:159
    691815msgid "Trashed comments"
    692816msgstr ""
    693817
    694 #: src/index.js:77
     818#: src/index.js:159
    695819msgid "Comments in the trash awaiting permanent deletion."
    696820msgstr ""
    697821
    698 #: src/index.js:81
     822#: src/index.js:163
    699823msgid "Temporary data"
    700824msgstr ""
    701825
    702 #: src/index.js:82
     826#: src/index.js:164
    703827msgid "Short-lived cached values. They are safe to clear and are rebuilt automatically whenever a plugin needs them."
    704828msgstr ""
    705829
    706 #: src/index.js:84
     830#: src/index.js:166
    707831msgid "Expired transients"
    708832msgstr ""
    709833
    710 #: src/index.js:84
     834#: src/index.js:166
    711835msgid "Cached values that have already passed their expiry date."
    712836msgstr ""
    713837
    714 #: src/index.js:88
     838#: src/index.js:170
    715839msgid "Table maintenance"
    716840msgstr ""
    717841
    718 #: src/index.js:89
     842#: src/index.js:171
    719843msgid "Defragment your tables to reclaim wasted space and trim database overhead."
    720844msgstr ""
    721845
    722 #: src/index.js:91
     846#: src/index.js:173
    723847msgid "Optimize database tables"
    724848msgstr ""
    725849
    726 #: src/index.js:91
     850#: src/index.js:173
    727851msgid "Runs OPTIMIZE on your tables to defragment them and free wasted space."
    728852msgstr ""
    729853
    730 #: src/index.js:97
     854#: src/index.js:179
    731855msgid "Do not limit"
    732856msgstr ""
    733857
    734 #: src/index.js:98
     858#: src/index.js:180
    735859msgid "Reduce activity"
    736860msgstr ""
    737861
    738 #: src/index.js:99
     862#: src/index.js:181
    739863msgid "Disable"
    740864msgstr ""
    741865
    742 #: src/index.js:108
     866#: src/index.js:190
    743867msgid "Active"
    744868msgstr ""
    745869
    746 #: src/index.js:108
    747 #: src/index.js:1110
    748 #: src/index.js:1636
     870#: src/index.js:190
     871#: src/index.js:1256
     872#: src/index.js:1798
    749873msgid "Off"
    750874msgstr ""
    751875
    752 #: src/index.js:150
     876#: src/index.js:232
    753877msgid "Cancelled"
    754878msgstr ""
    755879
    756 #: src/index.js:151
     880#: src/index.js:233
    757881msgid "Failed"
    758882msgstr ""
    759883
    760 #: src/index.js:167
     884#: src/index.js:249
    761885msgid "Expand"
    762886msgstr ""
    763887
    764 #: src/index.js:167
     888#: src/index.js:249
    765889msgid "Minimize"
    766890msgstr ""
    767891
    768 #: src/index.js:170
     892#: src/index.js:252
    769893msgid "Close"
    770894msgstr ""
    771895
    772 #: src/index.js:184
     896#: src/index.js:266
    773897msgid "Step"
    774898msgstr ""
    775899
    776 #: src/index.js:189
     900#: src/index.js:271
    777901msgid "Stop"
    778902msgstr ""
    779903
    780 #: src/index.js:492
    781 #: src/index.js:1239
     904#: src/index.js:538
     905msgid "You have unsaved changes"
     906msgstr ""
     907
     908#: src/index.js:538
     909msgid "All changes saved"
     910msgstr ""
     911
     912#: src/index.js:541
     913msgid "Save changes"
     914msgstr ""
     915
     916#: src/index.js:639
     917#: src/index.js:1385
    782918msgid "Deactivate"
    783919msgstr ""
    784920
    785 #: src/index.js:492
     921#: src/index.js:639
    786922msgid "Optimazz will keep handling caching."
    787923msgstr ""
    788924
    789 #: src/index.js:499
     925#: src/index.js:646
    790926msgid "deactivated — Optimazz is now the only cache."
    791927msgstr ""
    792928
    793 #: src/index.js:501
     929#: src/index.js:648
    794930msgid "Could not deactivate the plugin."
    795931msgstr ""
    796932
    797 #: src/index.js:508
     933#: src/index.js:655
    798934msgid "Make Optimazz the caching plugin? This replaces the current advanced-cache.php (from another caching plugin) and turns on WP_CACHE. Deactivate the other caching plugin afterwards to avoid conflicts."
    799935msgstr ""
    800936
    801 #: src/index.js:515
     937#: src/index.js:662
    802938msgid "Optimazz is now serving pages from cache."
    803939msgstr ""
    804940
    805 #: src/index.js:516
     941#: src/index.js:663
    806942msgid "Drop-in installed. Preload the cache, then check again."
    807943msgstr ""
    808944
    809 #: src/index.js:517
     945#: src/index.js:664
    810946msgid "Could not fully take over — see the cache health details."
    811947msgstr ""
    812948
    813 #: src/index.js:520
     949#: src/index.js:667
    814950msgid "Take-over failed. Check file permissions."
    815951msgstr ""
    816952
    817 #: src/index.js:542
    818 #: src/index.js:588
     953#: src/index.js:689
     954#: src/index.js:735
    819955msgid "Cache purged."
    820956msgstr ""
    821957
    822 #: src/index.js:543
     958#: src/index.js:690
    823959msgid "Cache preload started in the background."
    824960msgstr ""
    825961
    826 #: src/index.js:544
     962#: src/index.js:691
    827963msgid "Cache cleared; preload started in the background."
    828964msgstr ""
    829965
    830 #: src/index.js:553
     966#: src/index.js:700
    831967msgid "wp-config.php is not writable — add WP_CACHE manually to enable page caching."
    832968msgstr ""
    833969
    834 #: src/index.js:567
     970#: src/index.js:714
    835971msgid "Settings saved."
    836972msgstr ""
    837973
    838 #: src/index.js:570
     974#: src/index.js:717
    839975msgid "Save failed."
    840976msgstr ""
    841977
    842 #: src/index.js:620
     978#: src/index.js:767
    843979msgid "Converting images to WebP/AVIF"
    844980msgstr ""
    845981
    846 #: src/index.js:621
     982#: src/index.js:768
    847983msgid "Next-gen format generation"
    848984msgstr ""
    849985
    850 #: src/index.js:624
     986#: src/index.js:771
    851987msgid "Generated"
    852988msgstr ""
    853989
    854 #: src/index.js:624
     990#: src/index.js:771
    855991msgid "files in this batch"
    856992msgstr ""
    857993
    858 #: src/index.js:624
    859 #: src/index.js:633
     994#: src/index.js:771
     995#: src/index.js:780
    860996msgid "skipped"
    861997msgstr ""
    862998
    863 #: src/index.js:627
     999#: src/index.js:774
    8641000msgid "Compressing images"
    8651001msgstr ""
    8661002
    867 #: src/index.js:628
     1003#: src/index.js:775
    8681004msgid "Recompress, resize & strip metadata"
    8691005msgstr ""
    8701006
    871 #: src/index.js:634
     1007#: src/index.js:781
    8721008msgid "Processed"
    8731009msgstr ""
    8741010
    875 #: src/index.js:634
    876 #: src/index.js:1440
     1011#: src/index.js:781
     1012#: src/index.js:1586
    8771013msgid "saved"
    8781014msgstr ""
    8791015
    880 #: src/index.js:638
     1016#: src/index.js:785
     1017#: src/index.js:1953
    8811018msgid "Preloading cache"
    8821019msgstr ""
    8831020
    884 #: src/index.js:639
     1021#: src/index.js:786
    8851022msgid "Warming pages"
    8861023msgstr ""
    8871024
    888 #: src/index.js:643
     1025#: src/index.js:790
     1026#: src/index.js:1974
    8891027msgid "Warmed"
    8901028msgstr ""
    8911029
    892 #: src/index.js:643
     1030#: src/index.js:790
     1031#: src/index.js:1974
    8931032msgid "pages"
    8941033msgstr ""
    8951034
    896 #: src/index.js:643
     1035#: src/index.js:790
     1036#: src/index.js:1974
    8971037msgid "cached"
    8981038msgstr ""
    8991039
    900 #: src/index.js:643
     1040#: src/index.js:790
     1041#: src/index.js:1974
    9011042msgid "not cached"
    9021043msgstr ""
    9031044
    904 #: src/index.js:664
     1045#: src/index.js:811
    9051046msgid "Resuming…"
    9061047msgstr ""
    9071048
    908 #: src/index.js:678
     1049#: src/index.js:825
     1050#: src/index.js:1963
    9091051msgid "Stopped by user."
    9101052msgstr ""
    9111053
    912 #: src/index.js:697
     1054#: src/index.js:844
    9131055msgid "A batch failed (likely one slow/large image); skipped it and continued."
    9141056msgstr ""
    9151057
    916 #: src/index.js:699
     1058#: src/index.js:846
    9171059msgid "Completed (some items skipped)."
    9181060msgstr ""
    9191061
    920 #: src/index.js:711
     1062#: src/index.js:858
    9211063msgid "Job stalled — no progress."
    9221064msgstr ""
    9231065
    924 #: src/index.js:722
     1066#: src/index.js:869
     1067#: src/index.js:1977
    9251068msgid "left"
    9261069msgstr ""
    9271070
    9281071#. translators: %d: number of pages to warm.
    929 #: src/index.js:734
     1072#: src/index.js:881
     1073#: src/index.js:1987
    9301074#, js-format
    9311075msgid "Found %d pages to warm:"
    9321076msgstr ""
    9331077
    934 #: src/index.js:744
     1078#: src/index.js:891
    9351079msgid "Image processing is unavailable on this server (no GD or ImageMagick). See Tools → Server Requirements."
    9361080msgstr ""
    9371081
    938 #: src/index.js:749
     1082#: src/index.js:896
     1083#: src/index.js:1993
    9391084msgid "Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the \"Optimazz-CacheWarmer\" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter."
    9401085msgstr ""
    9411086
    942 #: src/index.js:756
     1087#: src/index.js:903
     1088#: src/index.js:1997
    9431089msgid "Pages are not being cached"
    9441090msgstr ""
    9451091
    946 #: src/index.js:756
     1092#: src/index.js:903
     1093#: src/index.js:1997
    9471094msgid "Stopped: pages are loading but none are being saved to the cache."
    9481095msgstr ""
    9491096
    950 #: src/index.js:760
    951 #: src/index.js:841
     1097#: src/index.js:907
     1098#: src/index.js:988
     1099#: src/index.js:2007
    9521100msgid "Completed."
    9531101msgstr ""
    9541102
    9551103#. translators: %d: number of pages.
    956 #: src/index.js:764
     1104#: src/index.js:911
     1105#: src/index.js:2004
    9571106#, js-format
    9581107msgid "%d page was not cached."
    … …  
    9611110msgstr[1] ""
    9621111
    963 #: src/index.js:823
     1112#: src/index.js:970
    9641113msgid "Cleaning and optimizing database…"
    9651114msgstr ""
    9661115
    967 #: src/index.js:827
     1116#: src/index.js:974
    9681117msgid "Optimizing database"
    9691118msgstr ""
    9701119
    971 #: src/index.js:828
     1120#: src/index.js:975
    9721121msgid "Cleanup & table optimization"
    9731122msgstr ""
    9741123
    975 #: src/index.js:840
     1124#: src/index.js:987
    9761125msgid "Cleaned"
    9771126msgstr ""
    9781127
    979 #: src/index.js:840
     1128#: src/index.js:987
    9801129msgid "rows,"
    9811130msgstr ""
    9821131
    983 #: src/index.js:840
     1132#: src/index.js:987
    9841133msgid "tables optimized."
    9851134msgstr ""
    9861135
    987 #: src/index.js:845
     1136#: src/index.js:992
    9881137msgid "Optimize failed."
    9891138msgstr ""
    9901139
    991 #: src/index.js:851
     1140#: src/index.js:998
    9921141msgid "Reset all Optimazz settings to defaults? You can undo this with \"Undo last change\"."
    9931142msgstr ""
    9941143
    995 #: src/index.js:859
     1144#: src/index.js:1006
    9961145msgid "Settings reset to defaults."
    9971146msgstr ""
    9981147
    999 #: src/index.js:862
     1148#: src/index.js:1009
    10001149msgid "Reset failed."
    10011150msgstr ""
    10021151
    1003 #: src/index.js:872
     1152#: src/index.js:1019
    10041153msgid "Nothing to undo."
    10051154msgstr ""
    10061155
    1007 #: src/index.js:879
     1156#: src/index.js:1026
    10081157msgid "Reverted to the previous settings."
    10091158msgstr ""
    10101159
    1011 #: src/index.js:882
     1160#: src/index.js:1029
    10121161msgid "Undo failed."
    10131162msgstr ""
    10141163
    1015 #: src/index.js:914
     1164#: src/index.js:1061
    10161165msgid "Invalid settings file."
    10171166msgstr ""
    10181167
    1019 #: src/index.js:944
     1168#: src/index.js:1088
    10201169msgid "Page caching"
    10211170msgstr ""
    10221171
    1023 #: src/index.js:945
     1172#: src/index.js:1089
    10241173msgid "Delay JavaScript"
    10251174msgstr ""
    10261175
    1027 #: src/index.js:946
    1028 #: src/index.js:1266
     1176#: src/index.js:1090
     1177#: src/index.js:1412
    10291178msgid "Defer JavaScript"
    10301179msgstr ""
    10311180
    1032 #: src/index.js:947
    1033 #: src/index.js:1319
     1181#: src/index.js:1091
     1182#: src/index.js:1465
    10341183msgid "Minify CSS"
    10351184msgstr ""
    10361185
    1037 #: src/index.js:949
     1186#: src/index.js:1093
    10381187msgid "Next-gen images"
    10391188msgstr ""
    10401189
    1041 #: src/index.js:950
     1190#: src/index.js:1094
    10421191msgid "Font optimization"
    10431192msgstr ""
    10441193
    1045 #: src/index.js:951
     1194#: src/index.js:1095
    10461195msgid "Cache preloading"
    10471196msgstr ""
    10481197
    1049 #: src/index.js:952
     1198#: src/index.js:1096
    10501199msgid "Heartbeat control"
    10511200msgstr ""
    10521201
    1053 #: src/index.js:967
     1202#: src/index.js:1113
    10541203msgid "Turn on Page Cache for the real speed gain"
    10551204msgstr ""
    10561205
    1057 #: src/index.js:968
     1206#: src/index.js:1114
    10581207msgid "File and media optimizations are on, but Page Cache is off — so the server rebuilds every page on every request. Without caching this can make a site feel slower, not faster, especially on mobile. Enable Page Cache to serve ready-made HTML."
    10591208msgstr ""
    10601209
    1061 #: src/index.js:987
     1210#: src/index.js:1133
    10621211msgid "SCORE"
    10631212msgstr ""
    10641213
    1065 #: src/index.js:992
     1214#: src/index.js:1138
    10661215msgid "Performance score"
    10671216msgstr ""
    10681217
    1069 #: src/index.js:995
     1218#: src/index.js:1141
    10701219msgid "Biggest remaining win:"
    10711220msgstr ""
    10721221
    1073 #: src/index.js:996
     1222#: src/index.js:1142
    10741223msgid "Every optimization is enabled. Nicely done!"
    10751224msgstr ""
    10761225
    1077 #: src/index.js:1013
    1078 #: src/index.js:1065
     1226#: src/index.js:1159
     1227#: src/index.js:1211
     1228#: src/index.js:2204
    10791229msgid "Cached pages"
    10801230msgstr ""
    10811231
    1082 #: src/index.js:1018
     1232#: src/index.js:1164
    10831233msgid "Cache size"
    10841234msgstr ""
    10851235
    1086 #: src/index.js:1023
     1236#: src/index.js:1169
    10871237msgid "Modules active"
    10881238msgstr ""
    10891239
    1090 #: src/index.js:1028
    1091 #: src/index.js:1043
     1240#: src/index.js:1174
     1241#: src/index.js:1189
    10921242msgid "Recommendations"
    10931243msgstr ""
    10941244
    1095 #: src/index.js:1028
     1245#: src/index.js:1174
    10961246msgid "What to fix next to speed up your site — one click each."
    10971247msgstr ""
    10981248
    1099 #: src/index.js:1035
     1249#: src/index.js:1181
    11001250msgid "Fix it"
    11011251msgstr ""
    11021252
    1103 #: src/index.js:1036
     1253#: src/index.js:1182
    11041254msgid "Open"
    11051255msgstr ""
    11061256
    1107 #: src/index.js:1044
     1257#: src/index.js:1190
    11081258msgid "Nice — everything we recommend is already on. Your site is well optimized."
    11091259msgstr ""
    11101260
    1111 #: src/index.js:1049
     1261#: src/index.js:1195
    11121262msgid "Cache Files"
    11131263msgstr ""
    11141264
    1115 #: src/index.js:1049
     1265#: src/index.js:1195
    11161266msgid "Clear and preload all cache files."
    11171267msgstr ""
    11181268
    1119 #: src/index.js:1052
     1269#: src/index.js:1198
     1270#: src/index.js:2187
    11201271msgid "Clear & Preload"
    11211272msgstr ""
    11221273
    1123 #: src/index.js:1058
     1274#: src/index.js:1204
     1275#: src/index.js:2195
    11241276msgid "Purge cache"
    11251277msgstr ""
    11261278
    1127 #: src/index.js:1070
     1279#: src/index.js:1216
     1280#: src/index.js:2209
    11281281msgid "Page cache size"
    11291282msgstr ""
    11301283
    1131 #: src/index.js:1075
     1284#: src/index.js:1221
    11321285msgid "Optimized CSS"
    11331286msgstr ""
    11341287
    1135 #: src/index.js:1080
    1136 #: src/index.js:1081
     1288#: src/index.js:1226
     1289#: src/index.js:1227
    11371290msgid "Cache lifespan"
    11381291msgstr ""
    11391292
    1140 #: src/index.js:1084
     1293#: src/index.js:1230
    11411294msgid "Never expire"
    11421295msgstr ""
    11431296
    1144 #: src/index.js:1085
    1145 #: src/index.js:1187
    1146 msgid "12 hours"
    1147 msgstr ""
    1148 
    1149 #: src/index.js:1086
    1150 #: src/index.js:1188
    1151 msgid "1 day"
    1152 msgstr ""
    1153 
    1154 #: src/index.js:1087
    1155 #: src/index.js:1189
    1156 msgid "1 week"
    1157 msgstr ""
    1158 
    1159 #: src/index.js:1088
    1160 #: src/index.js:1190
    1161 msgid "1 month"
    1162 msgstr ""
    1163 
    1164 #. translators: %d: number of hours.
    1165 #: src/index.js:1091
    1166 #, js-format
    1167 msgid "%d hour"
    1168 msgid_plural "%d hours"
    1169 msgstr[0] ""
    1170 msgstr[1] ""
    1171 
    1172 #: src/index.js:1098
     1297#: src/index.js:1244
    11731298msgid "Auto-preload"
    11741299msgstr ""
    11751300
    1176 #: src/index.js:1101
    1177 msgid "off"
    1178 msgstr ""
    1179 
    1180 #: src/index.js:1103
     1301#: src/index.js:1249
    11811302msgid "next run: due now"
    11821303msgstr ""
    11831304
    1184 #: src/index.js:1104
     1305#: src/index.js:1250
    11851306msgid "next run in"
    11861307msgstr ""
    11871308
    1188 #: src/index.js:1107
     1309#: src/index.js:1253
    11891310msgid "Auto-preload frequency"
    11901311msgstr ""
    11911312
    1192 #: src/index.js:1111
    1193 #: src/index.js:1462
     1313#: src/index.js:1257
     1314#: src/index.js:1609
     1315#: src/index.js:2121
    11941316msgid "Every hour"
    11951317msgstr ""
    11961318
    1197 #: src/index.js:1112
    1198 #: src/index.js:1463
     1319#: src/index.js:1258
     1320#: src/index.js:1610
     1321#: src/index.js:2122
    11991322msgid "Twice a day"
    12001323msgstr ""
    12011324
    1202 #: src/index.js:1113
    1203 #: src/index.js:1464
     1325#: src/index.js:1259
     1326#: src/index.js:1611
     1327#: src/index.js:2123
    12041328msgid "Once a day"
    12051329msgstr ""
    12061330
    1207 #: src/index.js:1121
     1331#: src/index.js:1267
    12081332msgid "Last auto-preload"
    12091333msgstr ""
    12101334
    1211 #: src/index.js:1124
     1335#: src/index.js:1270
    12121336msgid "not yet"
    12131337msgstr ""
    12141338
    12151339#. translators: 1: time ago, e.g. "2 hours", 2: number of pages
    1216 #: src/index.js:1127
     1340#: src/index.js:1273
    12171341#, js-format
    12181342msgid "%1$s ago · %2$d pages"
    12191343msgstr ""
    12201344
    1221 #: src/index.js:1130
     1345#: src/index.js:1276
    12221346msgid "blocked by firewall"
    12231347msgstr ""
    12241348
    1225 #: src/index.js:1135
     1349#: src/index.js:1281
    12261350msgid "Module Status"
    12271351msgstr ""
    12281352
    1229 #: src/index.js:1135
     1353#: src/index.js:1281
    12301354msgid "Enable or disable any module right here."
    12311355msgstr ""
    12321356
    1233 #: src/index.js:1172
     1357#: src/index.js:1318
    12341358msgid "Serve static HTML instead of running PHP on every request."
    12351359msgstr ""
    12361360
    1237 #: src/index.js:1174
     1361#: src/index.js:1320
     1362#: src/index.js:2094
    12381363msgid "Cache mobile devices separately"
    12391364msgstr ""
    12401365
    1241 #: src/index.js:1176
     1366#: src/index.js:1322
     1367#: src/index.js:2096
    12421368msgid "Cache logged-in users"
    12431369msgstr ""
    12441370
    1245 #: src/index.js:1177
     1371#: src/index.js:1323
     1372#: src/index.js:2097
    12461373msgid "Leave off unless you know what you are doing."
    12471374msgstr ""
    12481375
    1249 #: src/index.js:1179
     1376#: src/index.js:1325
     1377#: src/index.js:2099
    12501378msgid "Browser cache headers"
    12511379msgstr ""
    12521380
    1253 #: src/index.js:1180
     1381#: src/index.js:1326
    12541382msgid "Add Expires / Cache-Control rules so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only (written to .htaccess); ignored on Nginx."
    12551383msgstr ""
    12561384
    1257 #: src/index.js:1182
     1385#: src/index.js:1328
     1386#: src/index.js:2102
    12581387msgid "Cache lifetime"
    12591388msgstr ""
    12601389
    1261 #: src/index.js:1183
     1390#: src/index.js:1329
    12621391msgid "How long a cached page is kept before it is refreshed. Pages are always purged immediately when their content changes."
    12631392msgstr ""
    12641393
    1265 #: src/index.js:1186
     1394#: src/index.js:1332
     1395#: src/index.js:2106
    12661396msgid "Never"
    12671397msgstr ""
    12681398
    1269 #: src/index.js:1196
     1399#: src/index.js:1342
     1400#: src/index.js:2128
    12701401msgid "Cache health"
    12711402msgstr ""
    12721403
    1273 #: src/index.js:1198
     1404#: src/index.js:1344
    12741405msgid "Check now"
    12751406msgstr ""
    12761407
    1277 #: src/index.js:1201
     1408#: src/index.js:1347
    12781409msgid "Confirms caching is actually serving pages. If it is not, the plugin rebuilds every page on each request — which is slower, not faster."
    12791410msgstr ""
    12801411
    1281 #: src/index.js:1205
     1412#: src/index.js:1351
    12821413msgid "Pages ARE being served from cache (HIT)"
    12831414msgstr ""
    12841415
    1285 #: src/index.js:1206
     1416#: src/index.js:1352
    12861417msgid "Pages are NOT being served from cache (MISS) — see below"
    12871418msgstr ""
    12881419
    1289 #: src/index.js:1207
     1420#: src/index.js:1353
    12901421msgid "Live check unavailable"
    12911422msgstr ""
    12921423
    1293 #: src/index.js:1211
     1424#: src/index.js:1357
    12941425msgid "advanced-cache.php belongs to"
    12951426msgstr ""
    12961427
    1297 #: src/index.js:1211
     1428#: src/index.js:1357
    12981429msgid "take it over below"
    12991430msgstr ""
    13001431
    1301 #: src/index.js:1212
     1432#: src/index.js:1358
    13021433msgid "advanced-cache.php belongs to ANOTHER plugin — take it over below"
    13031434msgstr ""
    13041435
    1305 #: src/index.js:1213
     1436#: src/index.js:1359
    13061437msgid "advanced-cache.php drop-in installed"
    13071438msgstr ""
    13081439
    1309 #: src/index.js:1214
     1440#: src/index.js:1360
    13101441msgid "advanced-cache.php drop-in is missing"
    13111442msgstr ""
    13121443
    1313 #: src/index.js:1216
     1444#: src/index.js:1362
    13141445msgid "WP_CACHE is enabled in wp-config.php"
    13151446msgstr ""
    13161447
    1317 #: src/index.js:1216
     1448#: src/index.js:1362
    13181449msgid "WP_CACHE is not enabled in wp-config.php"
    13191450msgstr ""
    13201451
    1321 #: src/index.js:1218
     1452#: src/index.js:1364
    13221453msgid "wp-config.php is not writable — add the WP_CACHE line manually (below)"
    13231454msgstr ""
    13241455
    1325 #: src/index.js:1221
     1456#: src/index.js:1367
    13261457msgid "pages warmed in cache"
    13271458msgstr ""
    13281459
    1329 #: src/index.js:1227
     1460#: src/index.js:1373
    13301461msgid "Take over caching"
    13311462msgstr ""
    13321463
    1333 #: src/index.js:1229
     1464#: src/index.js:1375
    13341465msgid "Make Optimazz the caching plugin — installs its drop-in and enables WP_CACHE."
    13351466msgstr ""
    13361467
    1337 #: src/index.js:1237
     1468#: src/index.js:1383
    13381469msgid "is still active and may reclaim caching. Deactivate it so Optimazz keeps serving the cache."
    13391470msgstr ""
    13401471
    1341 #: src/index.js:1247
     1472#: src/index.js:1393
    13421473msgid "wp-config.php is not writable. Add this line just after the opening <?php tag:"
    13431474msgstr ""
    13441475
    1345 #: src/index.js:1252
     1476#: src/index.js:1398
    13461477msgid "Could not run the check. Try reloading the page."
    13471478msgstr ""
    13481479
    1349 #: src/index.js:1260
     1480#: src/index.js:1406
    13501481msgid "JavaScript"
    13511482msgstr ""
    13521483
    1353 #: src/index.js:1261
     1484#: src/index.js:1407
    13541485msgid "Defer and delay scripts to cut Total Blocking Time."
    13551486msgstr ""
    13561487
    1357 #: src/index.js:1263
     1488#: src/index.js:1409
    13581489msgid "Minify JavaScript"
    13591490msgstr ""
    13601491
    1361 #: src/index.js:1264
     1492#: src/index.js:1410
    13621493msgid "Strip comments and whitespace from inline scripts. Line breaks are kept for safety; test your site after enabling."
    13631494msgstr ""
    13641495
    1365 #: src/index.js:1267
     1496#: src/index.js:1413
    13661497msgid "Load scripts without blocking page render."
    13671498msgstr ""
    13681499
    1369 #: src/index.js:1269
     1500#: src/index.js:1415
    13701501msgid "Delay JS until interaction"
    13711502msgstr ""
    13721503
    1373 #: src/index.js:1270
     1504#: src/index.js:1416
    13741505msgid "Run scripts only after the first user interaction."
    13751506msgstr ""
    13761507
    1377 #: src/index.js:1273
     1508#: src/index.js:1419
    13781509msgid "Delay all scripts"
    13791510msgstr ""
    13801511
    1381 #: src/index.js:1274
     1512#: src/index.js:1420
    13821513msgid "Delay every script until interaction. jQuery, your theme, and Elementor are always kept safe. Still, on sites where content is rendered by JavaScript this can delay the visible page — test before keeping it on."
    13831514msgstr ""
    13841515
    1385 #: src/index.js:1280
     1516#: src/index.js:1426
    13861517msgid "Delaying all scripts can slow the first paint on JavaScript-rendered sites (Elementor, sliders). If your PageSpeed FCP/LCP get worse, switch to \"Delay selected\" and delay only tracking scripts."
    13871518msgstr ""
    13881519
    1389 #: src/index.js:1284
     1520#: src/index.js:1430
    13901521msgid "Delay these scripts"
    13911522msgstr ""
    13921523
    1393 #: src/index.js:1285
     1524#: src/index.js:1431
    13941525msgid "Only scripts matching these keywords are delayed. Match by filename, handle or URL — e.g. gtag, analytics, facebook."
    13951526msgstr ""
    13961527
    1397 #: src/index.js:1286
     1528#: src/index.js:1432
    13981529msgid "e.g. gtag, analytics"
    13991530msgstr ""
    14001531
    1401 #: src/index.js:1292
     1532#: src/index.js:1438
    14021533msgid "Exclude specific scripts"
    14031534msgstr ""
    14041535
    1405 #: src/index.js:1294
     1536#: src/index.js:1440
    14061537msgid "If a script breaks, exclude it from just one optimization instead of both. Match by filename, script handle, or part of the URL."
    14071538msgstr ""
    14081539
    1409 #: src/index.js:1298
     1540#: src/index.js:1444
    14101541msgid "Never defer"
    14111542msgstr ""
    14121543
    1413 #: src/index.js:1299
     1544#: src/index.js:1445
    14141545msgid "Keep loading these render-blocking, but still allow delay. Use for scripts that must run during page load."
    14151546msgstr ""
    14161547
    1417 #: src/index.js:1300
     1548#: src/index.js:1446
    14181549msgid "e.g. jquery.min.js"
    14191550msgstr ""
    14201551
    1421 #: src/index.js:1304
     1552#: src/index.js:1450
    14221553msgid "Never delay"
    14231554msgstr ""
    14241555
    1425 #: src/index.js:1305
     1556#: src/index.js:1451
    14261557msgid "Run these before any interaction, but still allow defer. Use for sliders, above-the-fold, or consent scripts."
    14271558msgstr ""
    14281559
    1429 #: src/index.js:1306
     1560#: src/index.js:1452
    14301561msgid "e.g. slider, swiper"
    14311562msgstr ""
    14321563
    1433 #: src/index.js:1309
     1564#: src/index.js:1455
    14341565msgid "Never optimize"
    14351566msgstr ""
    14361567
    1437 #: src/index.js:1310
     1568#: src/index.js:1456
    14381569msgid "Skip both defer and delay — leave these scripts exactly as they are."
    14391570msgstr ""
    14401571
    1441 #: src/index.js:1311
     1572#: src/index.js:1457
    14421573msgid "e.g. recaptcha"
    14431574msgstr ""
    14441575
    1445 #: src/index.js:1316
     1576#: src/index.js:1462
    14461577msgid "CSS"
    14471578msgstr ""
    14481579
    1449 #: src/index.js:1317
     1580#: src/index.js:1463
    14501581msgid "Minify, combine, and remove render-blocking stylesheets."
    14511582msgstr ""
    14521583
    1453 #: src/index.js:1321
     1584#: src/index.js:1467
    14541585msgid "Combine CSS files"
    14551586msgstr ""
    14561587
    1457 #: src/index.js:1322
     1588#: src/index.js:1468
    14581589msgid "Merge stylesheets to reduce requests."
    14591590msgstr ""
    14601591
    1461 #: src/index.js:1324
     1592#: src/index.js:1470
    14621593msgid "Load CSS asynchronously"
    14631594msgstr ""
    14641595
    1465 #: src/index.js:1325
     1596#: src/index.js:1471
    14661597msgid "Removes render-blocking CSS. Paste your above-the-fold (critical) CSS below, or the page may briefly flash unstyled."
    14671598msgstr ""
    14681599
    1469 #: src/index.js:1332
     1600#: src/index.js:1478
    14701601msgid "No critical CSS provided. Async loading is on, so visitors may see a flash of unstyled content until you paste critical CSS below."
    14711602msgstr ""
    14721603
    1473 #: src/index.js:1336
     1604#: src/index.js:1482
    14741605msgid "Critical CSS (above-the-fold)"
    14751606msgstr ""
    14761607
    1477 #: src/index.js:1337
     1608#: src/index.js:1483
    14781609msgid "Paste the above-the-fold CSS to inline. Generate it with a free tool such as a critical-CSS generator. Leave empty to disable inlining."
    14791610msgstr ""
    14801611
    1481 #: src/index.js:1342
     1612#: src/index.js:1488
    14821613msgid "Remove unused CSS"
    14831614msgstr ""
    14841615
    1485 #: src/index.js:1343
     1616#: src/index.js:1489
    14861617msgid "Remove block-editor CSS on the front end"
    14871618msgstr ""
    14881619
    1489 #: src/index.js:1344
     1620#: src/index.js:1490
    14901621msgid "Dequeues WordPress block-library and global styles. Turn on only if your front end does NOT render Gutenberg blocks (e.g. a page-builder or classic theme). Big byte saving when it applies."
    14911622msgstr ""
    14921623
    1493 #: src/index.js:1346
     1624#: src/index.js:1492
    14941625msgid "Load WooCommerce CSS only on shop pages"
    14951626msgstr ""
    14961627
    1497 #: src/index.js:1347
     1628#: src/index.js:1493
    14981629msgid "Dequeues WooCommerce styles on non-shop pages (kept on cart, checkout, account, product and shop). Turn off if you show Woo products or widgets on other pages."
    14991630msgstr ""
    15001631
    1501 #: src/index.js:1349
     1632#: src/index.js:1495
    15021633msgid "Load Easy Digital Downloads CSS only on EDD pages"
    15031634msgstr ""
    15041635
    1505 #: src/index.js:1350
     1636#: src/index.js:1496
    15061637msgid "Dequeues EDD styles on non-EDD pages (kept on checkout, purchase-confirmation, purchase history and download archives). Turn off if you show download or purchase links elsewhere."
    15071638msgstr ""
    15081639
    1509 #: src/index.js:1352
     1640#: src/index.js:1498
    15101641msgid "Excluded CSS files (keywords)"
    15111642msgstr ""
    15121643
    1513 #: src/index.js:1360
     1644#: src/index.js:1506
    15141645msgid "Lazy-load, prevent layout shift, serve next-gen formats, and lighten media."
    15151646msgstr ""
    15161647
    1517 #: src/index.js:1366
     1648#: src/index.js:1512
    15181649msgid "This server has no GD or ImageMagick extension, so image compression and WebP/AVIF cannot run — every image will be skipped. Ask your host to enable \"php-gd\" (or \"php-imagick\"). See Tools → Server Requirements."
    15191650msgstr ""
    15201651
    1521 #: src/index.js:1370
     1652#: src/index.js:1516
    15221653msgid "Load off-screen images only when scrolled into view."
    15231654msgstr ""
    15241655
    1525 #: src/index.js:1372
     1656#: src/index.js:1518
    15261657msgid "Lazy-load iframes"
    15271658msgstr ""
    15281659
    1529 #: src/index.js:1374
     1660#: src/index.js:1520
    15301661msgid "Lazy-load CSS background images"
    15311662msgstr ""
    15321663
    1533 #: src/index.js:1375
     1664#: src/index.js:1521
    15341665msgid "Defer inline background-image elements (test with page builders)."
    15351666msgstr ""
    15361667
    1537 #: src/index.js:1377
     1668#: src/index.js:1523
    15381669msgid "Replace YouTube embeds with a preview"
    15391670msgstr ""
    15401671
    1541 #: src/index.js:1378
     1672#: src/index.js:1524
    15421673msgid "Load the video iframe only when the visitor clicks play."
    15431674msgstr ""
    15441675
    1545 #: src/index.js:1380
     1676#: src/index.js:1526
    15461677msgid "Add missing image dimensions"
    15471678msgstr ""
    15481679
    1549 #: src/index.js:1381
     1680#: src/index.js:1527
    15501681msgid "Set width/height to prevent layout shift (CLS)."
    15511682msgstr ""
    15521683
    1553 #: src/index.js:1383
     1684#: src/index.js:1529
    15541685msgid "Preload the LCP image"
    15551686msgstr ""
    15561687
    1557 #: src/index.js:1384
     1688#: src/index.js:1530
    15581689msgid "Preload the first/hero image with high priority."
    15591690msgstr ""
    15601691
    1561 #: src/index.js:1387
     1692#: src/index.js:1533
    15621693msgid "Keep first images eager (LCP)"
    15631694msgstr ""
    15641695
    1565 #: src/index.js:1388
     1696#: src/index.js:1534
    15661697msgid "Do not lazy-load the first N images, to protect LCP."
    15671698msgstr ""
    15681699
    1569 #: src/index.js:1393
     1700#: src/index.js:1539
    15701701msgid "Serve WebP via <picture>"
    15711702msgstr ""
    15721703
    1573 #: src/index.js:1394
     1704#: src/index.js:1540
    15741705msgid "Deliver WebP to supporting browsers, original as fallback."
    15751706msgstr ""
    15761707
    1577 #: src/index.js:1396
     1708#: src/index.js:1542
    15781709msgid "Generate WebP/AVIF on upload"
    15791710msgstr ""
    15801711
    1581 #: src/index.js:1398
     1712#: src/index.js:1544
    15821713msgid "WebP generation is not available on this server."
    15831714msgstr ""
    15841715
    1585 #: src/index.js:1399
     1716#: src/index.js:1545
    15861717msgid "Auto-create next-gen copies for newly uploaded images."
    15871718msgstr ""
    15881719
    1589 #: src/index.js:1402
     1720#: src/index.js:1548
    15901721msgid "Also generate/serve AVIF"
    15911722msgstr ""
    15921723
    1593 #: src/index.js:1403
     1724#: src/index.js:1549
    15941725msgid "Smaller than WebP; served first when supported."
    15951726msgstr ""
    15961727
    1597 #: src/index.js:1407
     1728#: src/index.js:1553
    15981729msgid "Compress & resize on upload"
    15991730msgstr ""
    16001731
    1601 #: src/index.js:1408
     1732#: src/index.js:1554
    16021733msgid "Recompress, cap dimensions, and strip metadata from new uploads."
    16031734msgstr ""
    16041735
    1605 #: src/index.js:1410
     1736#: src/index.js:1556
    16061737msgid "Convert opaque PNG to JPEG"
    16071738msgstr ""
    16081739
    1609 #: src/index.js:1411
     1740#: src/index.js:1557
    16101741msgid "Re-save non-transparent PNGs as much smaller JPEGs during bulk optimization."
    16111742msgstr ""
    16121743
    1613 #: src/index.js:1413
     1744#: src/index.js:1559
    16141745msgid "Keep a backup of originals"
    16151746msgstr ""
    16161747
    1617 #: src/index.js:1414
     1748#: src/index.js:1560
    16181749msgid "Copy originals to uploads/optimazz-backup before optimizing."
    16191750msgstr ""
    16201751
    1621 #: src/index.js:1416
     1752#: src/index.js:1562
    16221753msgid "Image quality"
    16231754msgstr ""
    16241755
    1625 #: src/index.js:1418
     1756#: src/index.js:1564
    16261757msgid "Used for compression and WebP/AVIF generation."
    16271758msgstr ""
    16281759
    1629 #: src/index.js:1419
     1760#: src/index.js:1565
    16301761msgid "Max image width (px)"
    16311762msgstr ""
    16321763
    1633 #: src/index.js:1424
     1764#: src/index.js:1570
    16341765msgid "WebP coverage"
    16351766msgstr ""
    16361767
    1637 #: src/index.js:1426
     1768#: src/index.js:1572
    16381769msgid "images converted"
    16391770msgstr ""
    16401771
    1641 #: src/index.js:1427
    1642 #: src/index.js:1441
     1772#: src/index.js:1573
     1773#: src/index.js:1587
    16431774msgid "Loading…"
    16441775msgstr ""
    16451776
    1646 #: src/index.js:1432
     1777#: src/index.js:1578
    16471778msgid "Convert to WebP"
    16481779msgstr ""
    16491780
    1650 #: src/index.js:1438
     1781#: src/index.js:1584
    16511782msgid "Bulk compression"
    16521783msgstr ""
    16531784
    1654 #: src/index.js:1440
     1785#: src/index.js:1586
    16551786msgid "optimized"
    16561787msgstr ""
    16571788
    1658 #: src/index.js:1444
     1789#: src/index.js:1590
    16591790msgid "Compress existing images"
    16601791msgstr ""
    16611792
    1662 #: src/index.js:1452
     1793#: src/index.js:1598
    16631794msgid "Warm the cache automatically so visitors never hit a cold page."
    16641795msgstr ""
    16651796
    1666 #: src/index.js:1453
     1797#: src/index.js:1600
    16671798msgid "Activate preloading"
    16681799msgstr ""
    16691800
    1670 #: src/index.js:1454
     1801#: src/index.js:1601
    16711802msgid "Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop."
    16721803msgstr ""
    16731804
    1674 #: src/index.js:1457
     1805#: src/index.js:1604
     1806#: src/index.js:2117
    16751807msgid "Automatic preload"
    16761808msgstr ""
    16771809
    1678 #: src/index.js:1458
     1810#: src/index.js:1605
    16791811msgid "How often to re-warm the whole site. Choose “Every hour” if your content changes often or you use a short cache lifespan."
    16801812msgstr ""
    16811813
    1682 #: src/index.js:1470
     1814#. translators: %s: preload frequency, e.g. "twice a day".
     1815#: src/index.js:1618
     1816#, js-format
     1817msgid "Automatic preload runs %s for this site (set for the whole network in Network Admin → Optimazz)."
     1818msgstr ""
     1819
     1820#: src/index.js:1621
     1821msgid "Automatic preload is off for the network. You can still preload this site now."
     1822msgstr ""
     1823
     1824#: src/index.js:1626
    16831825msgid "WP-Cron is disabled on this site (DISABLE_WP_CRON). Scheduled preloads only run if a server cron job calls wp-cron.php — ask your host to set one up (e.g. every 5 minutes)."
    16841826msgstr ""
    16851827
    1686 #: src/index.js:1475
     1828#: src/index.js:1631
    16871829msgid "Preload now"
    16881830msgstr ""
    16891831
    1690 #: src/index.js:1483
     1832#: src/index.js:1639
    16911833msgid "Fine-tune which requests are cached."
    16921834msgstr ""
    16931835
    1694 #: src/index.js:1484
     1836#: src/index.js:1640
    16951837msgid "Never cache these URLs (contains)"
    16961838msgstr ""
    16971839
    1698 #: src/index.js:1486
     1840#: src/index.js:1642
    16991841msgid "Never cache when these cookies are set"
    17001842msgstr ""
    17011843
    1702 #: src/index.js:1488
     1844#: src/index.js:1644
    17031845msgid "Never cache for these user agents"
    17041846msgstr ""
    17051847
    1706 #: src/index.js:1490
     1848#: src/index.js:1646
    17071849msgid "Ignore these query arguments"
    17081850msgstr ""
    17091851
    1710 #: src/index.js:1498
     1852#: src/index.js:1654
    17111853msgid "Safe Mode"
    17121854msgstr ""
    17131855
    1714 #: src/index.js:1499
     1856#: src/index.js:1655
    17151857msgid "If a theme or page builder looks broken after optimizing, turn this on. It pauses the riskiest optimizations — combine CSS, async CSS, remove block CSS and JavaScript delay — while keeping caching, minify, defer and lazy-load. Your saved settings are not changed; they simply pause until you turn Safe Mode off."
    17161858msgstr ""
    17171859
    1718 #: src/index.js:1502
     1860#: src/index.js:1658
    17191861msgid "Pause risky optimizations (Safe Mode)"
    17201862msgstr ""
    17211863
    1722 #: src/index.js:1509
     1864#: src/index.js:1665
    17231865msgid "Safe Mode is ON — combine/async CSS, remove-block-CSS and JavaScript delay are paused site-wide. Turn it off to resume full optimization."
    17241866msgstr ""
    17251867
    1726 #: src/index.js:1514
     1868#: src/index.js:1670
    17271869msgid "Undo last change"
    17281870msgstr ""
    17291871
    1730 #: src/index.js:1518
     1872#: src/index.js:1674
    17311873msgid "Restores the settings from just before your last save, reset or Safe Mode toggle."
    17321874msgstr ""
    17331875
    1734 #: src/index.js:1519
     1876#: src/index.js:1675
    17351877msgid "Nothing to undo yet — this becomes available after your next change."
    17361878msgstr ""
    17371879
    1738 #: src/index.js:1523
     1880#: src/index.js:1679
    17391881msgid "Server Requirements"
    17401882msgstr ""
    17411883
    1742 #: src/index.js:1524
     1884#: src/index.js:1680
    17431885msgid "What your host supports. Warnings here explain why a feature (like WebP/AVIF) may be unavailable or slow."
    17441886msgstr ""
    17451887
    1746 #: src/index.js:1525
     1888#: src/index.js:1681
    17471889msgid "Checking your server…"
    17481890msgstr ""
    17491891
    1750 #: src/index.js:1526
     1892#: src/index.js:1682
    17511893msgid "Could not read the server report."
    17521894msgstr ""
    17531895
    1754 #: src/index.js:1542
     1896#: src/index.js:1698
    17551897msgid "Re-check"
    17561898msgstr ""
    17571899
    1758 #: src/index.js:1546
     1900#: src/index.js:1702
    17591901msgid "Import / Export Settings"
    17601902msgstr ""
    17611903
    1762 #: src/index.js:1547
     1904#: src/index.js:1703
    17631905msgid "Back up your configuration or copy it to another site."
    17641906msgstr ""
    17651907
    1766 #: src/index.js:1550
     1908#: src/index.js:1706
    17671909msgid "Export settings"
    17681910msgstr ""
    17691911
    1770 #: src/index.js:1553
     1912#: src/index.js:1709
    17711913msgid "Import settings"
    17721914msgstr ""
    17731915
    1774 #: src/index.js:1558
     1916#: src/index.js:1714
    17751917msgid "Reset"
    17761918msgstr ""
    17771919
    1778 #: src/index.js:1559
     1920#: src/index.js:1715
    17791921msgid "Restore every module to its default configuration. This cannot be undone."
    17801922msgstr ""
    17811923
    1782 #: src/index.js:1562
     1924#: src/index.js:1718
    17831925msgid "Reset all settings to defaults"
    17841926msgstr ""
    17851927
    1786 #: src/index.js:1571
     1928#: src/index.js:1727
    17871929msgid "Swap, preconnect, and preload fonts to remove render-blocking requests."
    17881930msgstr ""
    17891931
    1790 #: src/index.js:1574
     1932#: src/index.js:1730
    17911933msgid "Font loading (font-display)"
    17921934msgstr ""
    17931935
    1794 #: src/index.js:1575
     1936#: src/index.js:1731
    17951937msgid "Optional = no layout shift (best for CLS): the web font is skipped on the first uncached load and used once cached. Swap = text stays visible but the web font swaps in and can shift layout (higher CLS). Block = brief invisible text, then the web font."
    17961938msgstr ""
    17971939
    1798 #: src/index.js:1578
     1940#: src/index.js:1734
    17991941msgid "Optional — no layout shift (recommended for CLS)"
    18001942msgstr ""
    18011943
    1802 #: src/index.js:1579
     1944#: src/index.js:1735
    18031945msgid "Swap — always show text (may shift layout)"
    18041946msgstr ""
    18051947
    1806 #: src/index.js:1580
     1948#: src/index.js:1736
    18071949msgid "Block — brief hidden text, then web font"
    18081950msgstr ""
    18091951
    1810 #: src/index.js:1584
     1952#: src/index.js:1740
    18111953msgid "Combine Google Fonts into one request"
    18121954msgstr ""
    18131955
    1814 #: src/index.js:1585
     1956#: src/index.js:1741
    18151957msgid "Page builders often load each Google Font family separately. This merges them into a single request."
    18161958msgstr ""
    18171959
    1818 #: src/index.js:1589
     1960#: src/index.js:1745
    18191961msgid "Load only the font weights your pages use"
    18201962msgstr ""
    18211963
    1822 #: src/index.js:1590
     1964#: src/index.js:1746
    18231965msgid "Drops unused weights (e.g. 100, 200, 900) and italics your pages never use — often cutting font CSS by more than half. Weights 400 and 700 are always kept. Turn off if some text looks thinner or bolder than intended."
    18241966msgstr ""
    18251967
    1826 #: src/index.js:1594
     1968#: src/index.js:1751
     1969msgid "Load Google Fonts without blocking the page"
     1970msgstr ""
     1971
     1972#: src/index.js:1752
     1973msgid "The page appears right away in a fallback font and switches to your Google Font as soon as it arrives, instead of waiting for Google first. A big win for the first paint on mobile. Applies when font display is Swap or Fallback; with Optional the stylesheet keeps loading first so text never shifts."
     1974msgstr ""
     1975
     1976#: src/index.js:1756
    18271977msgid "Preload these font files (URLs)"
    18281978msgstr ""
    18291979
    18301980#. translators: %d: number of items found in the database.
    1831 #: src/index.js:1605
     1981#: src/index.js:1767
    18321982#, js-format
    18331983msgid "%d item in your database."
    … …  
    18361986msgstr[1] ""
    18371987
    1838 #: src/index.js:1610
     1988#: src/index.js:1772
    18391989msgid "Database Optimization"
    18401990msgstr ""
    18411991
    1842 #: src/index.js:1611
     1992#: src/index.js:1773
    18431993msgid "Reduce bloat and reclaim space. Once a cleanup runs it cannot be undone, so back up your database first."
    18441994msgstr ""
    18451995
    1846 #: src/index.js:1627
     1996#: src/index.js:1789
    18471997msgid "Set it and forget it"
    18481998msgstr ""
    18491999
    1850 #: src/index.js:1628
     2000#: src/index.js:1790
    18512001msgid "Let Optimazz repeat the cleanups you picked above on a schedule, so your database stays lean without you lifting a finger."
    18522002msgstr ""
    18532003
    1854 #: src/index.js:1629
     2004#: src/index.js:1791
    18552005msgid "Schedule automatic cleanup"
    18562006msgstr ""
    18572007
    1858 #: src/index.js:1630
     2008#: src/index.js:1792
    18592009msgid "Uses WP-Cron to repeat the cleanups you enabled above."
    18602010msgstr ""
    18612011
    1862 #: src/index.js:1633
     2012#: src/index.js:1795
    18632013msgid "Cleanup frequency"
    18642014msgstr ""
    18652015
    1866 #: src/index.js:1637
     2016#: src/index.js:1799
    18672017msgid "Daily"
    18682018msgstr ""
    18692019
    1870 #: src/index.js:1638
     2020#: src/index.js:1800
    18712021msgid "Weekly"
    18722022msgstr ""
    18732023
    1874 #: src/index.js:1639
     2024#: src/index.js:1801
    18752025msgid "Monthly"
    18762026msgstr ""
    18772027
    1878 #: src/index.js:1646
     2028#: src/index.js:1808
    18792029msgid "Back up your database before you run a cleanup — a database optimization cannot be undone."
    18802030msgstr ""
    18812031
    1882 #: src/index.js:1651
     2032#: src/index.js:1813
    18832033msgid "Optimize now"
    18842034msgstr ""
    18852035
    1886 #: src/index.js:1654
     2036#: src/index.js:1816
    18872037msgid "Refresh counts"
    18882038msgstr ""
    18892039
    1890 #: src/index.js:1661
     2040#: src/index.js:1823
    18912041msgid "Heartbeat Control"
    18922042msgstr ""
    18932043
    1894 #: src/index.js:1662
     2044#: src/index.js:1824
    18952045msgid "Reduce or disable the WordPress Heartbeat API to lower server load."
    18962046msgstr ""
    18972047
    1898 #: src/index.js:1663
     2048#: src/index.js:1825
    18992049msgid "Enable Heartbeat control"
    19002050msgstr ""
    19012051
    1902 #: src/index.js:1667
     2052#: src/index.js:1829
    19032053msgid "Behavior in admin dashboard"
    19042054msgstr ""
    19052055
    1906 #: src/index.js:1669
     2056#: src/index.js:1831
    19072057msgid "Behavior in post editor"
    19082058msgstr ""
    19092059
    1910 #: src/index.js:1671
     2060#: src/index.js:1833
    19112061msgid "Behavior on front-end"
    19122062msgstr ""
    19132063
    1914 #: src/index.js:1680
     2064#: src/index.js:1842
    19152065msgid "Strip unnecessary WordPress front-end weight."
    19162066msgstr ""
    19172067
    1918 #: src/index.js:1682
     2068#: src/index.js:1844
    19192069msgid "Disable emojis"
    19202070msgstr ""
    19212071
    1922 #: src/index.js:1683
     2072#: src/index.js:1845
    19232073msgid "Remove the emoji detection script and styles."
    19242074msgstr ""
    19252075
    1926 #: src/index.js:1685
     2076#: src/index.js:1847
    19272077msgid "Disable embeds (oEmbed)"
    19282078msgstr ""
    19292079
    1930 #: src/index.js:1687
     2080#: src/index.js:1849
    19312081msgid "Disable dashicons on front-end"
    19322082msgstr ""
    19332083
    1934 #: src/index.js:1688
     2084#: src/index.js:1850
    19352085msgid "Only for anonymous visitors."
    19362086msgstr ""
    19372087
    1938 #: src/index.js:1690
     2088#: src/index.js:1852
    19392089msgid "Remove query strings from assets"
    19402090msgstr ""
    19412091
    1942 #: src/index.js:1691
     2092#: src/index.js:1853
    19432093msgid "Strip ?ver= for better proxy caching."
    19442094msgstr ""
    19452095
    1946 #: src/index.js:1693
     2096#: src/index.js:1855
    19472097msgid "Disable XML-RPC"
    19482098msgstr ""
    19492099
    1950 #: src/index.js:1695
     2100#: src/index.js:1857
    19512101msgid "Disable self-pingbacks"
    19522102msgstr ""
    19532103
    1954 #: src/index.js:1697
     2104#: src/index.js:1859
    19552105msgid "Remove head meta (generator, RSD, shortlink)"
    19562106msgstr ""
    19572107
    1958 #: src/index.js:1699
     2108#: src/index.js:1861
    19592109msgid "DNS-prefetch domains"
    19602110msgstr ""
    19612111
    1962 #: src/index.js:1701
     2112#: src/index.js:1863
    19632113msgid "Preconnect origins"
    19642114msgstr ""
    19652115
    1966 #: src/index.js:1707
     2116#: src/index.js:1869
    19672117msgid "Content Delivery Network"
    19682118msgstr ""
    19692119
    1970 #: src/index.js:1708
     2120#: src/index.js:1870
    19712121msgid "Rewrite static asset URLs to your CDN hostname."
    19722122msgstr ""
    19732123
    1974 #: src/index.js:1710
     2124#: src/index.js:1872
    19752125msgid "CDN URL (CNAME)"
    19762126msgstr ""
    19772127
    1978 #: src/index.js:1713
     2128#: src/index.js:1875
    19792129msgid "CSS, JS, image, and font URLs will be served from here."
    19802130msgstr ""
    19812131
    1982 #: src/index.js:1714
     2132#: src/index.js:1876
    19832133msgid "Exclude paths containing"
    19842134msgstr ""
    19852135
    1986 #: src/index.js:1781
    1987 msgid "You have unsaved changes"
    1988 msgstr ""
    1989 
    1990 #: src/index.js:1781
    1991 msgid "All changes saved"
    1992 msgstr ""
    1993 
    1994 #: src/index.js:1784
    1995 msgid "Save changes"
    1996 msgstr ""
     2136#: src/index.js:1952
     2137msgid "Preloading cache…"
     2138msgstr ""
     2139
     2140#: src/index.js:2012
     2141#: src/index.js:2025
     2142msgid "Something went wrong."
     2143msgstr ""
     2144
     2145#: src/index.js:2059
     2146msgid "Network settings saved."
     2147msgstr ""
     2148
     2149#: src/index.js:2075
     2150msgid "Caching for every site"
     2151msgstr ""
     2152
     2153#: src/index.js:2076
     2154msgid "Sites"
     2155msgstr ""
     2156
     2157#: src/index.js:2076
     2158msgid "Cache per site"
     2159msgstr ""
     2160
     2161#: src/index.js:2086
     2162msgid "Optimazz is not network-activated"
     2163msgstr ""
     2164
     2165#: src/index.js:2087
     2166msgid "Only the sites where Optimazz is activated are cached. Network-activate it on Network Admin → Plugins to cache every site."
     2167msgstr ""
     2168
     2169#: src/index.js:2092
     2170msgid "Serve ready-made HTML for every site instead of running PHP on each request. Each site keeps its own cache and its own exclusions."
     2171msgstr ""
     2172
     2173#: src/index.js:2100
     2174msgid "Add Expires / Cache-Control rules to the shared .htaccess so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only."
     2175msgstr ""
     2176
     2177#: src/index.js:2103
     2178msgid "How long a cached page is kept. Pages are always purged as soon as their content changes."
     2179msgstr ""
     2180
     2181#: src/index.js:2113
     2182msgid "Preload every site automatically"
     2183msgstr ""
     2184
     2185#: src/index.js:2114
     2186msgid "Each site warms its own pages on a schedule and right after its cache is cleared."
     2187msgstr ""
     2188
     2189#. translators: %s: name of another caching plugin.
     2190#: src/index.js:2135
     2191#, js-format
     2192msgid "%s controls page caching (advanced-cache.php)"
     2193msgstr ""
     2194
     2195#: src/index.js:2138
     2196msgid "Optimazz cannot cache while another plugin owns the cache file. Deactivate that plugin, or let Optimazz take over."
     2197msgstr ""
     2198
     2199#: src/index.js:2139
     2200msgid "Optimazz now handles page caching."
     2201msgstr ""
     2202
     2203#: src/index.js:2146
     2204msgid "Cache engine installed (advanced-cache.php)"
     2205msgstr ""
     2206
     2207#: src/index.js:2147
     2208msgid "WP_CACHE is on in wp-config.php"
     2209msgstr ""
     2210
     2211#: src/index.js:2148
     2212msgid "wp-config.php is not writable — add define( 'WP_CACHE', true ); yourself"
     2213msgstr ""
     2214
     2215#: src/index.js:2149
     2216msgid "The wp-content folder is not writable"
     2217msgstr ""
     2218
     2219#: src/index.js:2167
     2220msgid "All sites"
     2221msgstr ""
     2222
     2223#. translators: 1: sites with Optimazz, 2: all sites.
     2224#: src/index.js:2170
     2225#, js-format
     2226msgid "Optimazz runs on %1$d of %2$d sites. Each site has its own cache."
     2227msgstr ""
     2228
     2229#: src/index.js:2172
     2230msgid "Page caching is off for the network — turn it on under Page Cache to purge or preload sites."
     2231msgstr ""
     2232
     2233#: src/index.js:2175
     2234msgid "Every site’s cache was purged."
     2235msgstr ""
     2236
     2237#: src/index.js:2176
     2238msgid "Purge all sites"
     2239msgstr ""
     2240
     2241#: src/index.js:2194
     2242msgid "Site cache purged."
     2243msgstr ""
     2244
     2245#: src/index.js:2215
     2246msgid "Optimazz is not active on this site, so its pages are not cached."
     2247msgstr ""
     2248
     2249#: src/index.js:2225
     2250msgid "Network"
     2251msgstr ""
  • optimazz/tags/1.1.0/readme.txt

    r3723776 r3743505  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.0.24
     7Stable tag: 1.1.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    4949
    5050== Frequently Asked Questions ==
     51
     52= Does Optimazz work on WordPress multisite? =
     53
     54Yes. Network-activate it, then switch page caching on for every site in Network Admin → Optimazz (super admins only). Each site gets its own cache and keeps its own settings and cache exclusions. You can also activate Optimazz on individual sites only; then only those sites are cached.
    5155
    5256= Does Optimazz work alongside another caching plugin? =
    … …  
    100104
    101105== Changelog ==
     106
     107= 1.1.0 =
     108* Fixed: Google Fonts never loaded on sites whose theme or plugin requests them in the old weight format through the new Google Fonts address (for example "Open Sans:400,700" or "w400,700"). Google rejects those requests, so the font was missing while the broken stylesheet still held back the first paint on mobile by most of a second. Optimazz now repairs them into a valid request.
     109* New: Google Fonts load without blocking the page (on by default, Fonts settings). With font display Swap or Fallback the page appears right away in a fallback font and switches when the web font arrives, instead of waiting for Google first. With Optional (zero layout shift) the stylesheet still loads first, so text never shifts.
     110* Improved: sites using Jetpack's Site Accelerator load WordPress core stylesheets from c0.wp.com, which Optimazz used to skip as external, so they stayed render-blocking. When the version matches your WordPress, they are now minified, combined and loaded like local files.
     111* Improved: more chat widgets are delayed until the visitor interacts — OpenWidget, Zendesk, Drift, Freshchat / Freshworks, JivoChat, Smartsupp and Small Chat — cutting mobile Total Blocking Time.
     112* New: full multisite support. Network-activate Optimazz and manage page caching for the whole network in Network Admin → Optimazz: switch it on once, choose mobile and logged-in caching, cache lifetime, browser-cache rules and automatic preload, and see every site with its cached pages and its own Purge and Preload buttons. Each site gets its own cache — on subdirectory and subdomain networks — keeps its own exclusions (Advanced Rules) and all its other Optimazz settings, and can purge and preload its own pages.
     113* Improved: on multisite only the network administrator controls the files every site shares (wp-config.php, advanced-cache.php, .htaccess); a site's own settings never change them, so one site can't switch caching on or off for the others.
     114* Fixed: before this version, on multisite one site's settings controlled caching for every site, only one site of a subdomain network was actually cached, any site administrator could rewrite wp-config.php and .htaccess, and deactivating Optimazz on one site removed the cache for the whole network.
     115* Fixed: deleting Optimazz from a multisite network only cleaned up the main site; settings, scheduled tasks, image data and generated CSS of the other sites were left behind. Uninstall now cleans every site.
     116* Fixed: on sites using plain permalinks (addresses like ?page_id=5) the "Cached pages" count showed 1 no matter how many pages were cached.
     117* Improved: the preload summary now names the home page and counts each address once, so its parts add up to the total.
     118* Improved: changing a cache rule (for example a new exclusion) now also clears pages that were cached under the old rules.
     119* Improved: the Changelog section of this readme now lists recent versions only; the complete history is in changelog.txt.
    102120
    103121= 1.0.24 =
    … …  
    153171== Upgrade Notice ==
    154172
     173= 1.1.0 =
     174Multisite network support: manage page caching for every site from Network Admin → Optimazz, with a separate cache per site. Also a faster mobile first paint (Google Fonts) and many fixes. Recommended.
     175
    155176= 1.0.24 =
    156177Scheduled cache preloading now runs on time even when every visitor gets a cached page, works behind 6G/7G firewalls, and the cache rebuilds itself after you change settings. Also warns you when another caching plugin is blocking the Optimazz page cache. Recommended.
  • optimazz/tags/1.1.0/src/index.js

    r3723128 r3743505  
    5858const NAV_ALL = [ ...NAV, ...PRO_SECTIONS ];
    5959
     60// wp_localize_script() sends top-level scalars as strings: false arrives as ""
     61// and true as "1", so test for every "off" form.
     62const isOn = ( v ) => ! [ false, '', '0', 0, undefined, null ].includes( v );
     63// Single site: this site controls page caching itself. On multisite a super
     64// admin switches it on for the whole network (Network Admin → Optimazz) and
     65// each site keeps its own cache, exclusions, purge and preload.
     66const SERVER_FEATURES = ! [ false, '', '0', 0 ].includes( window.optimazzData?.serverFeatures );
     67const NETWORK_CACHE = window.optimazzData?.networkCache || null;
     68const NETWORK_CACHE_ON = ! SERVER_FEATURES && !! NETWORK_CACHE && isOn( NETWORK_CACHE.enabled );
     69const CACHE_AVAILABLE = SERVER_FEATURES || NETWORK_CACHE_ON;
     70const IS_SUPER_ADMIN = isOn( window.optimazzData?.isSuperAdmin );
     71const NETWORK_URL = window.optimazzData?.networkAdminUrl || '';
     72
     73const formatBytes = ( b ) => {
     74        if ( ! b ) return '0 KB';
     75        const u = [ 'B', 'KB', 'MB', 'GB' ];
     76        const i = Math.floor( Math.log( b ) / Math.log( 1024 ) );
     77        return `${ ( b / Math.pow( 1024, i ) ).toFixed( i ? 1 : 0 ) } ${ u[ i ] }`;
     78};
     79
     80const intervalLabel = ( v ) => ( {
     81        hourly: __( 'every hour', 'optimazz' ),
     82        twicedaily: __( 'twice a day', 'optimazz' ),
     83        daily: __( 'once a day', 'optimazz' ),
     84}[ v ] || __( 'twice a day', 'optimazz' ) );
     85
     86const lifespanLabel = ( h ) => {
     87        const map = {
     88                0: __( 'never expires', 'optimazz' ),
     89                12: __( '12 hours', 'optimazz' ),
     90                24: __( '1 day', 'optimazz' ),
     91                168: __( '1 week', 'optimazz' ),
     92                720: __( '1 month', 'optimazz' ),
     93        };
     94        /* translators: %d: number of hours. */
     95        return map[ Number( h ) ] ?? sprintf( _n( '%d hour', '%d hours', Number( h ), 'optimazz' ), Number( h ) );
     96};
     97
     98function NetworkLink() {
     99        if ( ! IS_SUPER_ADMIN || ! NETWORK_URL ) {
     100                return null;
     101        }
     102        return <a className="oz-cachewarn__link" href={ NETWORK_URL }>{ __( 'Open Network Admin → Optimazz', 'optimazz' ) }</a>;
     103}
     104
     105// Multisite, network caching off.
     106function MultisiteCacheNotice() {
     107        return (
     108                <div className="oz-cachewarn" role="status">
     109                        <span className="dashicons dashicons-info-outline" />
     110                        <div className="oz-cachewarn__text">
     111                                <strong>{ __( 'Page caching is off for this network', 'optimazz' ) }</strong>
     112                                <p>{ IS_SUPER_ADMIN
     113                                        ? __( 'Turn it on for every site in Network Admin → Optimazz. Each site gets its own cache, and keeps its own exclusions here under Advanced Rules.', 'optimazz' )
     114                                        : __( 'Your network administrator can turn it on for every site in Network Admin → Optimazz. All other optimizations — CSS, JavaScript, images, fonts and more — work normally on this site.', 'optimazz' ) }</p>
     115                                <NetworkLink />
     116                        </div>
     117                </div>
     118        );
     119}
     120
     121// Multisite, network caching on: this site's Page Cache screen.
     122function NetworkCacheOn() {
     123        const n = NETWORK_CACHE || {};
     124        return (
     125                <div className="oz-cachewarn oz-cachewarn--ok" role="status">
     126                        <span className="dashicons dashicons-yes-alt" />
     127                        <div className="oz-cachewarn__text">
     128                                <strong>{ __( 'Page caching is on — managed for the whole network', 'optimazz' ) }</strong>
     129                                <p>{ sprintf(
     130                                        /* translators: 1: yes/no, 2: cache lifetime, 3: preload frequency. */
     131                                        __( 'Separate mobile cache: %1$s · Cache lifetime: %2$s · Automatic preload: %3$s.', 'optimazz' ),
     132                                        isOn( n.cache_mobile ) ? __( 'yes', 'optimazz' ) : __( 'no', 'optimazz' ),
     133                                        lifespanLabel( n.lifespan_hours ),
     134                                        isOn( n.preload ) ? intervalLabel( n.preload_interval ) : __( 'off', 'optimazz' )
     135                                ) }</p>
     136                                <p>{ __( 'This site has its own cache. Purge or preload it from the Dashboard or the Preload screen, and set its own exclusions under Advanced Rules.', 'optimazz' ) }</p>
     137                                <NetworkLink />
     138                        </div>
     139                </div>
     140        );
     141}
    60142const MODULES = [ 'cache', 'js', 'css', 'media', 'fonts', 'database', 'heartbeat', 'tweaks', 'cdn' ];
    61143
    … …  
    407489
    408490/* ------------------------------------------------------------------ */
     491
     492/**
     493 * The Optimazz admin layout shared by the site screen and Network Admin:
     494 * brand + sidebar navigation, page header, content, save bar and toast.
     495 */
     496function AppShell( { className = '', items, nav, onNav, foot, topIcon, title, sub, dirty, saving, onSave, toast, onToastRemove, children, after } ) {
     497        return (
     498                <div className={ `oz-shell ${ className }` }>
     499                        <aside className="oz-sidebar">
     500                                <div className="oz-sidebar__brand">
     501                                        { window.optimazzData?.logoUrl ? (
     502                                                <img className="oz-logo-img" src={ window.optimazzData.logoUrl } alt="Optimazz" />
     503                                        ) : (
     504                                                <div className="oz-sidebar__brand-fallback">
     505                                                        <span className="oz-logo dashicons dashicons-performance" />
     506                                                        <strong>Optimazz</strong>
     507                                                </div>
     508                                        ) }
     509                                </div>
     510                                <nav>
     511                                        { items.map( ( n ) => (
     512                                                <button key={ n.key } className={ `oz-navitem ${ nav === n.key ? 'is-active' : '' }` } onClick={ () => onNav( n.key ) }>
     513                                                        <Icon name={ n.svg || n.key } paths={ n.iconSvg } />
     514                                                        <span className="oz-navitem__text">
     515                                                                <strong>{ n.title }</strong>
     516                                                                <small>{ n.sub }</small>
     517                                                        </span>
     518                                                        { n.dot && <i className="oz-navitem__dot" /> }
     519                                                </button>
     520                                        ) ) }
     521                                </nav>
     522                                <div className="oz-sidebar__foot">{ foot }</div>
     523                        </aside>
     524
     525                        <div className="oz-content">
     526                                <header className="oz-topbar">
     527                                        <span className={ `oz-topbar__icon dashicons dashicons-${ topIcon }` } />
     528                                        <div>
     529                                                <h1>{ title }</h1>
     530                                                <p>{ sub }</p>
     531                                        </div>
     532                                </header>
     533                                <main className="oz-main">{ children }</main>
     534                        </div>
     535
     536                        <div className={ `oz-savebar ${ dirty ? 'is-dirty' : '' }` }>
     537                                <span className="oz-savebar__hint">
     538                                        { dirty ? __( 'You have unsaved changes', 'optimazz' ) : __( 'All changes saved', 'optimazz' ) }
     539                                </span>
     540                                <Button className="oz-btn-primary" isBusy={ saving } disabled={ ! dirty || saving } onClick={ onSave }>
     541                                        { __( 'Save changes', 'optimazz' ) }
     542                                </Button>
     543                        </div>
     544
     545                        { toast && (
     546                                <div className="oz-toast">
     547                                        <Snackbar status={ toast.status } onRemove={ onToastRemove }>
     548                                                { toast.text }
     549                                        </Snackbar>
     550                                </div>
     551                        ) }
     552                        { after }
     553                </div>
     554        );
     555}
    409556
    410557function App() {
    … …  
    9191066        };
    9201067
    921         const fmtBytes = ( b ) => {
    922                 if ( ! b ) return '0 KB';
    923                 const u = [ 'B', 'KB', 'MB', 'GB' ];
    924                 const i = Math.floor( Math.log( b ) / Math.log( 1024 ) );
    925                 return `${ ( b / Math.pow( 1024, i ) ).toFixed( i ? 1 : 0 ) } ${ u[ i ] }`;
    926         };
    927 
    928         const enabledCount = MODULES.filter( ( k ) => settings[ k ]?.enabled ).length;
     1068        const fmtBytes = formatBytes;
     1069
     1070        // On multisite the page cache is unavailable, so it is not counted.
     1071        const usableModules = SERVER_FEATURES ? MODULES : MODULES.filter( ( k ) => k !== 'cache' );
     1072        const enabledCount = usableModules.filter( ( k ) => settings[ k ]?.enabled ).length;
    9291073        const c = settings.cache || {};
    9301074        const j = settings.js || {};
    … …  
    9421086        // Live performance score — weighted by which optimizations are enabled.
    9431087        const scoreParts = [
    944                 { on: !! c.enabled, w: 30, label: __( 'Page caching', 'optimazz' ) },
     1088                { on: !! c.enabled, w: 30, label: __( 'Page caching', 'optimazz' ), server: true },
    9451089                { on: !! ( j.enabled && j.delay ), w: 18, label: __( 'Delay JavaScript', 'optimazz' ) },
    9461090                { on: !! ( j.enabled && j.defer ), w: 8, label: __( 'Defer JavaScript', 'optimazz' ) },
    … …  
    9491093                { on: !! ( m.enabled && ( m.webp || m.webp_generate ) ), w: 8, label: __( 'Next-gen images', 'optimazz' ) },
    9501094                { on: !! ( f.enabled && f.display_swap ), w: 6, label: __( 'Font optimization', 'optimazz' ) },
    951                 { on: !! c.preload, w: 5, label: __( 'Cache preloading', 'optimazz' ) },
     1095                { on: !! c.preload, w: 5, label: __( 'Cache preloading', 'optimazz' ), server: true },
    9521096                { on: !! h.enabled, w: 3, label: __( 'Heartbeat control', 'optimazz' ) },
    9531097        ];
    954         const score = scoreParts.reduce( ( a, p ) => a + ( p.on ? p.w : 0 ), 0 );
    955         const nextWin = scoreParts.filter( ( p ) => ! p.on ).sort( ( a, b ) => b.w - a.w )[ 0 ];
     1098        const usableParts = CACHE_AVAILABLE ? scoreParts : scoreParts.filter( ( p ) => p.server !== true );
     1099        const maxScore = usableParts.reduce( ( a, p ) => a + p.w, 0 ) || 1;
     1100        const score = Math.round( ( usableParts.reduce( ( a, p ) => a + ( p.on ? p.w : 0 ), 0 ) / maxScore ) * 100 );
     1101        const nextWin = usableParts.filter( ( p ) => ! p.on ).sort( ( a, b ) => b.w - a.w )[ 0 ];
    9561102        const grade = score >= 90 ? 'A' : score >= 75 ? 'B' : score >= 55 ? 'C' : score >= 35 ? 'D' : 'E';
    9571103        const ring = 2 * Math.PI * 52;
    9581104
    959         const optimizingWithoutCache = ( j.enabled || s.enabled || m.enabled ) && ! c.enabled;
     1105        const optimizingWithoutCache = SERVER_FEATURES && ( j.enabled || s.enabled || m.enabled ) && ! c.enabled;
    9601106
    9611107        const dashboard = (
    … …  
    9971143                                        </p>
    9981144                                        <div className="oz-score__chips">
    999                                                 { scoreParts.map( ( p ) => (
     1145                                                { usableParts.map( ( p ) => (
    10001146                                                        <span key={ p.label } className={ `oz-chip ${ p.on ? 'is-on' : '' }` }>
    10011147                                                                <span className={ `dashicons dashicons-${ p.on ? 'yes-alt' : 'marker' }` } />
    … …  
    10071153                        </div>
    10081154
    1009                         <div className="oz-kpis">
    1010                                 <div className="oz-kpi">
     1155                        <div className={ `oz-kpis${ CACHE_AVAILABLE ? '' : ' oz-kpis--single' }` }>
     1156                                { CACHE_AVAILABLE && <div className="oz-kpi">
    10111157                                        <span className="dashicons dashicons-database" />
    10121158                                        <strong>{ stats ? ( stats.pages ?? stats.files ) : <Spinner /> }</strong>
    10131159                                        <span>{ __( 'Cached pages', 'optimazz' ) }</span>
    1014                                 </div>
    1015                                 <div className="oz-kpi">
     1160                                </div> }
     1161                                { CACHE_AVAILABLE && <div className="oz-kpi">
    10161162                                        <span className="dashicons dashicons-chart-area" />
    10171163                                        <strong>{ stats ? fmtBytes( stats.bytes ) : <Spinner /> }</strong>
    10181164                                        <span>{ __( 'Cache size', 'optimazz' ) }</span>
    1019                                 </div>
     1165                                </div> }
    10201166                                <div className="oz-kpi">
    10211167                                        <span className="dashicons dashicons-yes-alt" />
    1022                                         <strong>{ enabledCount }/{ MODULES.length }</strong>
     1168                                        <strong>{ enabledCount }/{ usableModules.length }</strong>
    10231169                                        <span>{ __( 'Modules active', 'optimazz' ) }</span>
    10241170                                </div>
    … …  
    10471193
    10481194                        <div className="oz-quick">
    1049                                 <Panel title={ __( 'Cache Files', 'optimazz' ) } description={ __( 'Clear and preload all cache files.', 'optimazz' ) }>
     1195                                { CACHE_AVAILABLE && <Panel title={ __( 'Cache Files', 'optimazz' ) } description={ __( 'Clear and preload all cache files.', 'optimazz' ) }>
    10501196                                        <div className="oz-quick__actions">
    10511197                                                <Button className="oz-btn-primary" icon="trash" isBusy={ !! job?.running } onClick={ async () => { await purge(); preload(); } }>
    … …  
    11321278                                                </li>
    11331279                                        </ul>
    1134                                 </Panel>
     1280                                </Panel> }
    11351281                                <Panel title={ __( 'Module Status', 'optimazz' ) } description={ __( 'Enable or disable any module right here.', 'optimazz' ) }>
    11361282                                        <ul className="oz-status">
    1137                                                 { NAV.filter( ( n ) => n.mod ).map( ( n ) => (
     1283                                                { NAV.filter( ( n ) => n.mod && ( SERVER_FEATURES || n.mod !== 'cache' ) ).map( ( n ) => (
    11381284                                                        <li key={ n.key }>
    11391285                                                                <Icon name={ n.key } />
    … …  
    11681314        );
    11691315
    1170         const cacheSection = (
     1316        const cacheSection = ! SERVER_FEATURES ? ( NETWORK_CACHE_ON ? <NetworkCacheOn /> : <MultisiteCacheNotice /> ) : (
    11711317                <Card icon="database" title={ __( 'Page Cache', 'optimazz' ) }
    11721318                        description={ __( 'Serve static HTML instead of running PHP on every request.', 'optimazz' ) }
    … …  
    14481594        );
    14491595
    1450         const preloadSection = (
     1596        const preloadSection = ! CACHE_AVAILABLE ? <MultisiteCacheNotice /> : (
    14511597                <Panel title={ __( 'Preload Cache', 'optimazz' ) }
    14521598                        description={ __( 'Warm the cache automatically so visitors never hit a cold page.', 'optimazz' ) }>
     1599                        { SERVER_FEATURES ? ( <>
    14531600                        <ToggleControl __nextHasNoMarginBottom label={ __( 'Activate preloading', 'optimazz' ) }
    14541601                                help={ __( 'Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop.', 'optimazz' ) }
    … …  
    14651612                                ] }
    14661613                                onChange={ ( v ) => set( 'cache', 'preload_interval', v ) } />
     1614                        </> ) : (
     1615                                <p className="oz-muted">{ isOn( NETWORK_CACHE?.preload )
     1616                                        ? sprintf(
     1617                                                /* translators: %s: preload frequency, e.g. "twice a day". */
     1618                                                __( 'Automatic preload runs %s for this site (set for the whole network in Network Admin → Optimazz).', 'optimazz' ),
     1619                                                intervalLabel( NETWORK_CACHE?.preload_interval )
     1620                                        )
     1621                                        : __( 'Automatic preload is off for the network. You can still preload this site now.', 'optimazz' ) }</p>
     1622                        ) }
    14671623                        { stats && stats.wp_cron_disabled && (
    14681624                                <div className="oz-notice oz-notice--warn" style={ { marginTop: 12 } }>
    … …  
    14791635        );
    14801636
    1481         const advancedSection = (
     1637        const advancedSection = ! CACHE_AVAILABLE ? <MultisiteCacheNotice /> : (
    14821638                <Panel title={ __( 'Advanced Rules', 'optimazz' ) }
    14831639                        description={ __( 'Fine-tune which requests are cached.', 'optimazz' ) }>
    … …  
    15921748                                disabled={ ! f.combine_google }
    15931749                                onChange={ ( v ) => set( 'fonts', 'trim_weights', v ) } />
     1750                        <ToggleControl __nextHasNoMarginBottom
     1751                                label={ __( 'Load Google Fonts without blocking the page', 'optimazz' ) }
     1752                                help={ __( 'The page appears right away in a fallback font and switches to your Google Font as soon as it arrives, instead of waiting for Google first. A big win for the first paint on mobile. Applies when font display is Swap or Fallback; with Optional the stylesheet keeps loading first so text never shifts.', 'optimazz' ) }
     1753                                checked={ f.async_google !== false }
     1754                                disabled={ ! f.combine_google }
     1755                                onChange={ ( v ) => set( 'fonts', 'async_google', v ) } />
    15941756                        <FormTokenField label={ __( 'Preload these font files (URLs)', 'optimazz' ) }
    15951757                                value={ f.preload || [] } onChange={ ( v ) => set( 'fonts', 'preload', v ) } />
    … …  
    17391901
    17401902        return (
    1741                 <div className="oz-shell">
    1742                         <aside className="oz-sidebar">
    1743                                 <div className="oz-sidebar__brand">
    1744                                         { window.optimazzData?.logoUrl ? (
    1745                                                 <img className="oz-logo-img" src={ window.optimazzData.logoUrl } alt="Optimazz" />
    1746                                         ) : (
    1747                                                 <div className="oz-sidebar__brand-fallback">
    1748                                                         <span className="oz-logo dashicons dashicons-performance" />
    1749                                                         <strong>Optimazz</strong>
    1750                                                 </div>
    1751                                         ) }
    1752                                 </div>
    1753                                 <nav>
    1754                                         { NAV_ALL.map( ( n ) => (
    1755                                                 <button key={ n.key } className={ `oz-navitem ${ nav === n.key ? 'is-active' : '' }` } onClick={ () => setNav( n.key ) }>
    1756                                                         <Icon name={ n.key } paths={ n.iconSvg } />
    1757                                                         <span className="oz-navitem__text">
    1758                                                                 <strong>{ n.title }</strong>
    1759                                                                 <small>{ n.sub }</small>
    1760                                                         </span>
    1761                                                         { n.mod && settings[ n.mod ]?.enabled && <i className="oz-navitem__dot" /> }
    1762                                                 </button>
    1763                                         ) ) }
    1764                                 </nav>
    1765                                 <div className="oz-sidebar__foot">v{ window.optimazzData?.version }</div>
    1766                         </aside>
    1767 
    1768                         <div className="oz-content">
    1769                                 <header className="oz-topbar">
    1770                                         <span className={ `oz-topbar__icon dashicons dashicons-${ active.icon }` } />
    1771                                         <div>
    1772                                                 <h1>{ active.title }</h1>
    1773                                                 <p>{ active.sub }</p>
    1774                                         </div>
    1775                                 </header>
    1776                                 <main className="oz-main">{ sections[ nav ] }</main>
    1777                         </div>
    1778 
    1779                         <div className={ `oz-savebar ${ dirty ? 'is-dirty' : '' }` }>
    1780                                 <span className="oz-savebar__hint">
    1781                                         { dirty ? __( 'You have unsaved changes', 'optimazz' ) : __( 'All changes saved', 'optimazz' ) }
    1782                                 </span>
    1783                                 <Button className="oz-btn-primary" isBusy={ saving } disabled={ ! dirty } onClick={ save }>
    1784                                         { __( 'Save changes', 'optimazz' ) }
    1785                                 </Button>
    1786                         </div>
    1787 
    1788                         { toast && (
    1789                                 <div className="oz-toast">
    1790                                         <Snackbar status={ toast.status } onRemove={ () => setToast( null ) }>
    1791                                                 { toast.text }
    1792                                         </Snackbar>
    1793                                 </div>
    1794                         ) }
    1795 
    1796                         <JobPanel
    1797                                 job={ job }
    1798                                 onStop={ stopJob }
    1799                                 onClose={ closeJob }
    1800                                 onToggleMin={ () => setJob( ( j ) => j && { ...j, minimized: ! j.minimized } ) }
    1801                         />
    1802                 </div>
     1903                <AppShell
     1904                        items={ NAV_ALL.map( ( n ) => ( { ...n, dot: !! ( n.mod && settings[ n.mod ]?.enabled ) } ) ) }
     1905                        nav={ nav } onNav={ setNav }
     1906                        foot={ `v${ window.optimazzData?.version }` }
     1907                        topIcon={ active.icon } title={ active.title } sub={ active.sub }
     1908                        dirty={ dirty } saving={ saving } onSave={ save }
     1909                        toast={ toast } onToastRemove={ () => setToast( null ) }
     1910                        after={
     1911                                <JobPanel
     1912                                        job={ job }
     1913                                        onStop={ stopJob }
     1914                                        onClose={ closeJob }
     1915                                        onToggleMin={ () => setJob( ( j ) => j && { ...j, minimized: ! j.minimized } ) }
     1916                                />
     1917                        }
     1918                >
     1919                        { sections[ nav ] }
     1920                </AppShell>
    18031921        );
    18041922}
    … …  
    18081926        createRoot( mount ).render( <App /> );
    18091927}
     1928
     1929/* ------------------------------------------------------------------ *
     1930 *  Network Admin → Optimazz (multisite, super admins)
     1931 * ------------------------------------------------------------------ */
     1932
     1933const NET_DEFAULTS = { enabled: false, cache_mobile: true, cache_logged_in: false, lifespan_hours: 0, preload: true, preload_interval: 'twicedaily', browser_cache: false };
     1934
     1935function NetworkApp() {
     1936        const [ data, setData ] = useState( null );
     1937        const [ draft, setDraft ] = useState( NET_DEFAULTS );
     1938        const [ dirty, setDirty ] = useState( false );
     1939        const [ saving, setSaving ] = useState( false );
     1940        const [ busy, setBusy ] = useState( '' );
     1941        const [ notice, setNotice ] = useState( null );
     1942        const [ nav, setNav ] = useState( 'cache' );
     1943        const [ job, setJob ] = useState( null );
     1944        const stopRef = useRef( false );
     1945
     1946        // Live preload of one site, in the same progress panel as the site screen.
     1947        const runPreload = async ( site ) => {
     1948                if ( job?.running ) {
     1949                        return;
     1950                }
     1951                stopRef.current = false;
     1952                const opening = __( 'Preloading cache…', 'optimazz' );
     1953                setJob( { title: __( 'Preloading cache', 'optimazz' ), subtitle: site.name || site.url, pct: 0, label: opening, step: '', logs: [ { level: 'INFO', text: opening } ], running: true, status: 'running', minimized: false } );
     1954                const acc = { cached: 0, missed: 0, diagnosis: '' };
     1955                const started = Date.now();
     1956                let offset = 0;
     1957                let total = 0;
     1958                const end = ( status, level, text, extra = {} ) => setJob( ( j ) => j && { ...j, running: false, status, ...extra, logs: [ ...j.logs, { level, text } ] } );
     1959                try {
     1960                        // eslint-disable-next-line no-constant-condition
     1961                        while ( true ) {
     1962                                if ( stopRef.current ) {
     1963                                        end( 'stopped', 'WARN', __( 'Stopped by user.', 'optimazz' ) );
     1964                                        break;
     1965                                }
     1966                                const res = await apiFetch( { url: `${ restUrl }/network/preload-batch`, method: 'POST', data: { site_id: site.id, offset } } );
     1967                                const prev = offset;
     1968                                offset = res.next_offset ?? offset + ( res.processed || 0 );
     1969                                total = res.total || total;
     1970                                acc.cached += res.cached || 0;
     1971                                acc.missed += res.missed || 0;
     1972                                acc.diagnosis = res.diagnosis || acc.diagnosis;
     1973                                const pct = total ? Math.min( 100, Math.round( ( offset / total ) * 100 ) ) : 100;
     1974                                const line = `${ __( 'Warmed', 'optimazz' ) } ${ Math.min( offset, total ) } / ${ total } ${ __( 'pages', 'optimazz' ) } · ${ acc.cached } ${ __( 'cached', 'optimazz' ) }${ acc.missed ? ` · ${ acc.missed } ${ __( 'not cached', 'optimazz' ) }` : '' }`;
     1975                                const rate = offset / Math.max( ( Date.now() - started ) / 1000, 0.001 );
     1976                                const left = rate > 0 && total && offset < total ? ( total - offset ) / rate : 0;
     1977                                const eta = left >= 60 ? ` · ~${ Math.round( left / 60 ) }m ${ __( 'left', 'optimazz' ) }` : ( left >= 1 ? ` · ~${ Math.round( left ) }s ${ __( 'left', 'optimazz' ) }` : '' );
     1978                                setJob( ( j ) => j && {
     1979                                        ...j,
     1980                                        pct,
     1981                                        label: line,
     1982                                        step: total ? `${ Math.min( offset, total ) } / ${ total }${ eta }` : '',
     1983                                        logs: [
     1984                                                ...j.logs.slice( -60 ),
     1985                                                ...( res.breakdown ? [ { level: 'INFO', text: `${ sprintf(
     1986                                                        /* translators: %d: number of pages to warm. */
     1987                                                        __( 'Found %d pages to warm:', 'optimazz' ), res.total || 0
     1988                                                ) } ${ res.breakdown }.` } ] : [] ),
     1989                                                { level: 'INFO', text: line },
     1990                                        ],
     1991                                } );
     1992                                if ( res.blocked ) {
     1993                                        end( 'error', 'ERROR', __( 'Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the "Optimazz-CacheWarmer" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter.', 'optimazz' ) );
     1994                                        break;
     1995                                }
     1996                                if ( ! res.done && acc.cached === 0 && acc.missed >= 3 ) {
     1997                                        end( 'error', 'ERROR', `${ __( 'Stopped: pages are loading but none are being saved to the cache.', 'optimazz' ) } ${ acc.diagnosis }`, { label: __( 'Pages are not being cached', 'optimazz' ) } );
     1998                                        break;
     1999                                }
     2000                                if ( res.done || offset <= prev ) {
     2001                                        if ( acc.missed ) {
     2002                                                setJob( ( j ) => j && { ...j, logs: [ ...j.logs, { level: 'WARN', text: `${ sprintf(
     2003                                                        /* translators: %d: number of pages. */
     2004                                                        _n( '%d page was not cached.', '%d pages were not cached.', acc.missed, 'optimazz' ), acc.missed
     2005                                                ) } ${ acc.diagnosis }` } ] } );
     2006                                        }
     2007                                        end( 'done', 'DONE', __( 'Completed.', 'optimazz' ), { pct: 100 } );
     2008                                        break;
     2009                                }
     2010                        }
     2011                } catch ( e ) {
     2012                        end( 'error', 'ERROR', e?.message || __( 'Something went wrong.', 'optimazz' ) );
     2013                }
     2014                // Refresh the cached-page counts on the cards.
     2015                apiFetch( { url: `${ restUrl }/network` } ).then( ( res ) => {
     2016                        setData( res );
     2017                } ).catch( () => {} );
     2018        };
     2019
     2020        const apply = ( res ) => {
     2021                setData( res );
     2022                setDraft( { ...NET_DEFAULTS, ...( res.settings || {} ) } );
     2023                setDirty( false );
     2024        };
     2025        const fail = ( e ) => setNotice( { status: 'error', text: e?.message || __( 'Something went wrong.', 'optimazz' ) } );
     2026        const call = async ( path, body, key, okText ) => {
     2027                setBusy( key );
     2028                try {
     2029                        const res = await apiFetch( { url: `${ restUrl }${ path }`, method: 'POST', data: body } );
     2030                        // Keep any unsaved Page Cache edits when acting on sites.
     2031                        setData( res );
     2032                        if ( ! dirty ) {
     2033                                setDraft( { ...NET_DEFAULTS, ...( res.settings || {} ) } );
     2034                        }
     2035                        if ( okText ) {
     2036                                setNotice( { status: 'success', text: okText } );
     2037                        }
     2038                        setBusy( '' );
     2039                        return true;
     2040                } catch ( e ) {
     2041                        fail( e );
     2042                }
     2043                setBusy( '' );
     2044                return false;
     2045        };
     2046
     2047        useEffect( () => {
     2048                apiFetch( { url: `${ restUrl }/network` } ).then( apply ).catch( fail );
     2049        }, [] );
     2050
     2051        const set = ( key, value ) => {
     2052                setDraft( ( d ) => ( { ...d, [ key ]: value } ) );
     2053                setDirty( true );
     2054        };
     2055        const save = async () => {
     2056                setSaving( true );
     2057                try {
     2058                        apply( await apiFetch( { url: `${ restUrl }/network/settings`, method: 'POST', data: { settings: draft } } ) );
     2059                        setNotice( { status: 'success', text: __( 'Network settings saved.', 'optimazz' ) } );
     2060                } catch ( e ) {
     2061                        fail( e );
     2062                }
     2063                setSaving( false );
     2064        };
     2065
     2066        if ( ! data ) {
     2067                return <div className="oz-shell oz-shell--network-app"><div className="oz-main"><Spinner /></div></div>;
     2068        }
     2069        const h = data.health || {};
     2070        const sites = data.sites || [];
     2071        const cached = sites.filter( ( s ) => s.active );
     2072        const on = !! data.settings?.enabled;
     2073
     2074        const NET_NAV = [
     2075                { key: 'cache', svg: 'cache', icon: 'database', title: __( 'Page Cache', 'optimazz' ), sub: __( 'Caching for every site', 'optimazz' ), dot: on },
     2076                { key: 'sites', svg: 'dashboard', icon: 'admin-multisite', title: __( 'Sites', 'optimazz' ), sub: __( 'Cache per site', 'optimazz' ) },
     2077        ];
     2078        const active = NET_NAV.find( ( n ) => n.key === nav ) || NET_NAV[ 0 ];
     2079
     2080        const cacheSection = (
     2081                <>
     2082                        { ! data.network_active && (
     2083                                <div className="oz-cachewarn" role="status">
     2084                                        <span className="dashicons dashicons-info-outline" />
     2085                                        <div className="oz-cachewarn__text">
     2086                                                <strong>{ __( 'Optimazz is not network-activated', 'optimazz' ) }</strong>
     2087                                                <p>{ __( 'Only the sites where Optimazz is activated are cached. Network-activate it on Network Admin → Plugins to cache every site.', 'optimazz' ) }</p>
     2088                                        </div>
     2089                                </div>
     2090                        ) }
     2091                        <Card icon="database" title={ __( 'Page Cache', 'optimazz' ) }
     2092                                description={ __( 'Serve ready-made HTML for every site instead of running PHP on each request. Each site keeps its own cache and its own exclusions.', 'optimazz' ) }
     2093                                enabled={ !! draft.enabled } onToggle={ () => set( 'enabled', ! draft.enabled ) }>
     2094                                <ToggleControl __nextHasNoMarginBottom label={ __( 'Cache mobile devices separately', 'optimazz' ) }
     2095                                        checked={ !! draft.cache_mobile } onChange={ () => set( 'cache_mobile', ! draft.cache_mobile ) } />
     2096                                <ToggleControl __nextHasNoMarginBottom label={ __( 'Cache logged-in users', 'optimazz' ) }
     2097                                        help={ __( 'Leave off unless you know what you are doing.', 'optimazz' ) }
     2098                                        checked={ !! draft.cache_logged_in } onChange={ () => set( 'cache_logged_in', ! draft.cache_logged_in ) } />
     2099                                <ToggleControl __nextHasNoMarginBottom label={ __( 'Browser cache headers', 'optimazz' ) }
     2100                                        help={ __( 'Add Expires / Cache-Control rules to the shared .htaccess so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only.', 'optimazz' ) }
     2101                                        checked={ !! draft.browser_cache } onChange={ () => set( 'browser_cache', ! draft.browser_cache ) } />
     2102                                <PillGroup label={ __( 'Cache lifetime', 'optimazz' ) }
     2103                                        help={ __( 'How long a cached page is kept. Pages are always purged as soon as their content changes.', 'optimazz' ) }
     2104                                        value={ Number( draft.lifespan_hours ) || 0 }
     2105                                        options={ [
     2106                                                { value: 0, label: __( 'Never', 'optimazz' ) },
     2107                                                { value: 12, label: __( '12 hours', 'optimazz' ) },
     2108                                                { value: 24, label: __( '1 day', 'optimazz' ) },
     2109                                                { value: 168, label: __( '1 week', 'optimazz' ) },
     2110                                                { value: 720, label: __( '1 month', 'optimazz' ) },
     2111                                        ] }
     2112                                        onChange={ ( v ) => set( 'lifespan_hours', v ) } />
     2113                                <ToggleControl __nextHasNoMarginBottom label={ __( 'Preload every site automatically', 'optimazz' ) }
     2114                                        help={ __( 'Each site warms its own pages on a schedule and right after its cache is cleared.', 'optimazz' ) }
     2115                                        checked={ !! draft.preload } onChange={ () => set( 'preload', ! draft.preload ) } />
     2116                                <SelectControl __nextHasNoMarginBottom __next40pxDefaultSize
     2117                                        label={ __( 'Automatic preload', 'optimazz' ) }
     2118                                        value={ draft.preload_interval || 'twicedaily' }
     2119                                        disabled={ ! draft.preload }
     2120                                        options={ [
     2121                                                { label: __( 'Every hour', 'optimazz' ), value: 'hourly' },
     2122                                                { label: __( 'Twice a day', 'optimazz' ), value: 'twicedaily' },
     2123                                                { label: __( 'Once a day', 'optimazz' ), value: 'daily' },
     2124                                        ] }
     2125                                        onChange={ ( v ) => set( 'preload_interval', v ) } />
     2126                        </Card>
     2127                        { ( on || h.conflict ) && (
     2128                                <Panel title={ __( 'Cache health', 'optimazz' ) }>
     2129                                        { h.conflict ? (
     2130                                                <div className="oz-cachewarn" role="status">
     2131                                                        <span className="dashicons dashicons-warning" />
     2132                                                        <div className="oz-cachewarn__text">
     2133                                                                <strong>{ sprintf(
     2134                                                                        /* translators: %s: name of another caching plugin. */
     2135                                                                        __( '%s controls page caching (advanced-cache.php)', 'optimazz' ),
     2136                                                                        h.conflict_name || __( 'Another plugin', 'optimazz' )
     2137                                                                ) }</strong>
     2138                                                                <p>{ __( 'Optimazz cannot cache while another plugin owns the cache file. Deactivate that plugin, or let Optimazz take over.', 'optimazz' ) }</p>
     2139                                                                <Button className="oz-btn-primary" isBusy={ 'takeover' === busy } onClick={ () => call( '/network/takeover', {}, 'takeover', __( 'Optimazz now handles page caching.', 'optimazz' ) ) }>
     2140                                                                        { __( 'Use Optimazz for caching', 'optimazz' ) }
     2141                                                                </Button>
     2142                                                        </div>
     2143                                                </div>
     2144                                        ) : (
     2145                                                <>
     2146                                                        <HealthRow ok={ h.dropin_ours } label={ __( 'Cache engine installed (advanced-cache.php)', 'optimazz' ) } />
     2147                                                        <HealthRow ok={ h.wp_cache } label={ __( 'WP_CACHE is on in wp-config.php', 'optimazz' ) } />
     2148                                                        { ! h.wpconfig_writable && ! h.wp_cache && <HealthRow ok={ false } label={ __( 'wp-config.php is not writable — add define( \'WP_CACHE\', true ); yourself', 'optimazz' ) } /> }
     2149                                                        { ! h.content_writable && <HealthRow ok={ false } label={ __( 'The wp-content folder is not writable', 'optimazz' ) } /> }
     2150                                                </>
     2151                                        ) }
     2152                                </Panel>
     2153                        ) }
     2154                </>
     2155        );
     2156
     2157        // Clear this site's cache, then warm it live in the progress panel.
     2158        const clearAndPreload = async ( site ) => {
     2159                const ok = await call( '/network/purge', { site_id: site.id }, `clear-${ site.id }` );
     2160                if ( ok ) {
     2161                        runPreload( site );
     2162                }
     2163        };
     2164
     2165        const sitesSection = (
     2166                <>
     2167                        <Panel title={ __( 'All sites', 'optimazz' ) }
     2168                                description={ sprintf(
     2169                                        /* translators: 1: sites with Optimazz, 2: all sites. */
     2170                                        __( 'Optimazz runs on %1$d of %2$d sites. Each site has its own cache.', 'optimazz' ), cached.length, sites.length
     2171                                ) }>
     2172                                { ! on && <p className="oz-muted">{ __( 'Page caching is off for the network — turn it on under Page Cache to purge or preload sites.', 'optimazz' ) }</p> }
     2173                                <div className="oz-quick__actions">
     2174                                        <Button className="oz-btn-ghost oz-btn--x" icon="dismiss" disabled={ ! on } isBusy={ 'purge-all' === busy }
     2175                                                onClick={ () => call( '/network/purge', {}, 'purge-all', __( 'Every site’s cache was purged.', 'optimazz' ) ) }>
     2176                                                { __( 'Purge all sites', 'optimazz' ) }
     2177                                        </Button>
     2178                                </div>
     2179                        </Panel>
     2180                        <div className="oz-quick oz-network__sites">
     2181                                { sites.map( ( s ) => (
     2182                                        <Panel key={ s.id } title={ s.name || s.url } description={ s.url }>
     2183                                                { s.active ? (
     2184                                                        <>
     2185                                                                <div className="oz-quick__actions">
     2186                                                                        <Button className="oz-btn-primary" icon="trash" disabled={ ! on || !! job?.running } isBusy={ `clear-${ s.id }` === busy } onClick={ () => clearAndPreload( s ) }>
     2187                                                                                { __( 'Clear & Preload', 'optimazz' ) }
     2188                                                                        </Button>
     2189                                                                        <Button className="oz-btn-ghost" icon="update" disabled={ ! on || !! job?.running }
     2190                                                                                onClick={ () => runPreload( s ) }>
     2191                                                                                { __( 'Preload', 'optimazz' ) }
     2192                                                                        </Button>
     2193                                                                        <Button className="oz-btn-ghost oz-btn--x" icon="dismiss" disabled={ ! on } isBusy={ `purge-${ s.id }` === busy }
     2194                                                                                onClick={ () => call( '/network/purge', { site_id: s.id }, `purge-${ s.id }`, __( 'Site cache purged.', 'optimazz' ) ) }>
     2195                                                                                { __( 'Purge cache', 'optimazz' ) }
     2196                                                                        </Button>
     2197                                                                        <Button className="oz-btn-ghost" icon="admin-generic" href={ s.admin_url }>
     2198                                                                                { __( 'Settings', 'optimazz' ) }
     2199                                                                        </Button>
     2200                                                                </div>
     2201                                                                <ul className="oz-cachelist">
     2202                                                                        <li>
     2203                                                                                <span className="dashicons dashicons-media-text" />
     2204                                                                                { __( 'Cached pages', 'optimazz' ) }
     2205                                                                                <strong>{ s.pages }</strong>
     2206                                                                        </li>
     2207                                                                        <li>
     2208                                                                                <span className="dashicons dashicons-database" />
     2209                                                                                { __( 'Page cache size', 'optimazz' ) }
     2210                                                                                <strong>{ formatBytes( s.bytes ) }</strong>
     2211                                                                        </li>
     2212                                                                </ul>
     2213                                                        </>
     2214                                                ) : (
     2215                                                        <p className="oz-muted">{ __( 'Optimazz is not active on this site, so its pages are not cached.', 'optimazz' ) }</p>
     2216                                                ) }
     2217                                        </Panel>
     2218                                ) ) }
     2219                        </div>
     2220                </>
     2221        );
     2222        return (
     2223                <AppShell className="oz-shell--network-app"
     2224                        items={ NET_NAV } nav={ nav } onNav={ setNav }
     2225                        foot={ `${ __( 'Network', 'optimazz' ) } · v${ window.optimazzData?.version }` }
     2226                        topIcon={ active.icon } title={ active.title } sub={ active.sub }
     2227                        dirty={ dirty } saving={ saving } onSave={ save }
     2228                        toast={ notice } onToastRemove={ () => setNotice( null ) }
     2229                        after={
     2230                                <JobPanel
     2231                                        job={ job }
     2232                                        onStop={ () => {
     2233                                                stopRef.current = true;
     2234                                        } }
     2235                                        onClose={ () => {
     2236                                                stopRef.current = true;
     2237                                                setJob( null );
     2238                                        } }
     2239                                        onToggleMin={ () => setJob( ( j ) => j && { ...j, minimized: ! j.minimized } ) }
     2240                                />
     2241                        }
     2242                >
     2243                        { 'sites' === nav ? sitesSection : cacheSection }
     2244                </AppShell>
     2245        );
     2246}
     2247
     2248const networkMount = document.getElementById( 'optimazz-network-app' );
     2249if ( networkMount ) {
     2250        createRoot( networkMount ).render( <NetworkApp /> );
     2251}
  • optimazz/tags/1.1.0/src/index.scss

    r3723128 r3743505  
    402402        grid-template-columns: repeat(3, 1fr);
    403403        gap: 14px;
     404
     405        // Multisite: only "Modules active" remains, so it spans the row.
     406        &--single {
     407                grid-template-columns: 1fr;
     408        }
    404409}
    405410
    … …  
    18021807                .dashicons { color: #e0900f; }
    18031808        }
     1809        &--ok {
     1810                background: #eaf8f3;
     1811                border-color: #b8e6d6;
     1812                color: #0f5e48;
     1813                .dashicons { color: #0e9f7e; }
     1814        }
    18041815}
    18051816
    … …  
    18821893        }
    18831894}
     1895
     1896/* ------------------------------------------------------------------ *
     1897 *  Multisite: network-managed cache states + Network Admin screen
     1898 * ------------------------------------------------------------------ */
     1899.oz-cachewarn--ok {
     1900        background: #eaf8f3;
     1901        border-color: #b8e6d6;
     1902        color: #0f5e48;
     1903}
     1904
     1905.oz-cachewarn__text p + p {
     1906        margin-top: 6px;
     1907}
     1908
     1909.oz-cachewarn__link {
     1910        display: inline-block;
     1911        margin-top: 8px;
     1912        font-weight: 600;
     1913        color: inherit;
     1914        text-decoration: underline;
     1915}
     1916
     1917// Network → Sites: one Cache Files-style card per site, two per row.
     1918.oz-network__sites {
     1919        margin-top: 16px;
     1920        align-items: start;
     1921
     1922        .oz-panel__head h2,
     1923        .oz-panel__head p {
     1924                overflow-wrap: anywhere;
     1925        }
     1926}
  • optimazz/tags/1.1.0/uninstall.php

    r3723128 r3743505  
    1818// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
    1919
    20 global $wpdb;
     20// Per-site data (options, transients, image meta, scheduled events). On a
     21// multisite network uninstall runs once, so it is repeated for every site.
     22$optimazz_cleanup_site = static function () {
     23        global $wpdb;
    2124
    22 // 1. Settings option (and the single-level undo backup).
    23 delete_option( 'optimazz_settings' );
    24 delete_option( 'optimazz_settings_backup' );
    25 delete_option( 'optimazz_version' ); // Post-update reconciliation marker.
    26 delete_option( 'optimazz_guard' );   // Update safety guard state.
    27 delete_option( 'optimazz_last_preload' ); // Last auto-preload run (dashboard).
    28 delete_option( 'optimazz_preload_cursor' ); // Position of a half-finished automatic preload.
    29 delete_transient( 'optimazz_preload_urls' ); // Cached preload URL list.
    30 delete_transient( 'optimazz_preload_urls_breakdown' ); // Counts behind the preload list.
    31 delete_transient( 'optimazz_preload_lock' ); // Running-chunk lock.
    32 delete_metadata( 'user', 0, 'optimazz_conflict_dismissed', '', true ); // Dismissed cache-conflict notice.
    33 wp_clear_scheduled_hook( 'optimazz_health_check' );
     25        // 1. Settings option (and the single-level undo backup).
     26        delete_option( 'optimazz_settings' );
     27        delete_option( 'optimazz_settings_backup' );
     28        delete_option( 'optimazz_version' ); // Post-update reconciliation marker.
     29        delete_option( 'optimazz_guard' );   // Update safety guard state.
     30        delete_option( 'optimazz_last_preload' ); // Last auto-preload run (dashboard).
     31        delete_option( 'optimazz_preload_cursor' ); // Position of a half-finished automatic preload.
     32        delete_transient( 'optimazz_preload_urls' ); // Cached preload URL list.
     33        delete_transient( 'optimazz_preload_urls_breakdown' ); // Counts behind the preload list.
     34        delete_transient( 'optimazz_preload_lock' ); // Running-chunk lock.
     35        wp_clear_scheduled_hook( 'optimazz_health_check' );
    3436
    35 // 2. Per-image optimization meta.
    36 $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_optimazz_image' ) ); // phpcs:ignore WordPress.DB.SlowDBQuery
     37        // 2. Per-image optimization meta.
     38        $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_optimazz_image' ) ); // phpcs:ignore WordPress.DB.SlowDBQuery
    3739
    38 // 3. Scheduled events.
    39 foreach ( array( 'optimazz_preload_cache', 'optimazz_preload_cache_once', 'optimazz_purge_expired', 'optimazz_db_cleanup' ) as $hook ) {
    40         wp_clear_scheduled_hook( $hook );
    41 }
     40        // 3. Scheduled events.
     41        foreach ( array( 'optimazz_preload_cache', 'optimazz_preload_cache_once', 'optimazz_purge_expired', 'optimazz_db_cleanup' ) as $hook ) {
     42                wp_clear_scheduled_hook( $hook );
     43        }
    4244
    43 // 4. Transients (incl. the per-stylesheet font-weight cache).
    44 delete_transient( 'optimazz_wpconfig_unwritable' );
    45 $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-off cleanup of our own transients on uninstall.
    46         $wpdb->prepare(
    47                 "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
    48                 $wpdb->esc_like( '_transient_optimazz_fw_' ) . '%',
    49                 $wpdb->esc_like( '_transient_timeout_optimazz_fw_' ) . '%'
    50         )
    51 );
     45        // 4. Transients (incl. the per-stylesheet font-weight cache).
     46        delete_transient( 'optimazz_wpconfig_unwritable' );
     47        $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-off cleanup of our own transients on uninstall.
     48                $wpdb->prepare(
     49                        "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
     50                        $wpdb->esc_like( '_transient_optimazz_fw_' ) . '%',
     51                        $wpdb->esc_like( '_transient_timeout_optimazz_fw_' ) . '%'
     52                )
     53        );
     54};
     55
     56delete_metadata( 'user', 0, 'optimazz_conflict_dismissed', '', true ); // Dismissed cache-conflict notice (users are network-wide).
     57delete_site_option( 'optimazz_network' ); // Network page-cache settings (multisite).
     58delete_site_option( 'optimazz_network_version' );
     59delete_site_transient( 'optimazz_heal' ); // Self-heal throttle (multisite).
    5260
    5361// 5. advanced-cache.php drop-in (only if it is ours).
    … …  
    111119$optimazz_rrmdir( WP_CONTENT_DIR . '/cache/optimazz' );
    112120
    113 $uploads = wp_get_upload_dir();
    114 if ( ! empty( $uploads['basedir'] ) ) {
    115         $optimazz_rrmdir( trailingslashit( $uploads['basedir'] ) . 'optimazz-backup' );
    116         // Generated CSS now lives under uploads/optimazz (moved out of cache/).
    117         $optimazz_rrmdir( trailingslashit( $uploads['basedir'] ) . 'optimazz' );
     121// Generated CSS (uploads/optimazz) and original-image backups
     122// (uploads/optimazz-backup) live in each site's own uploads folder.
     123$optimazz_clean_uploads = static function () use ( $optimazz_rrmdir ) {
     124        $uploads = wp_get_upload_dir();
     125        if ( ! empty( $uploads['basedir'] ) ) {
     126                $optimazz_rrmdir( trailingslashit( $uploads['basedir'] ) . 'optimazz-backup' );
     127                $optimazz_rrmdir( trailingslashit( $uploads['basedir'] ) . 'optimazz' );
     128        }
     129};
     130// 8. Per-site cleanup — every site of a network in one pass (uninstall runs once).
     131if ( is_multisite() ) {
     132        foreach ( get_sites( array( 'fields' => 'ids', 'number' => 0 ) ) as $optimazz_site_id ) {
     133                switch_to_blog( $optimazz_site_id );
     134                $optimazz_cleanup_site();
     135                $optimazz_clean_uploads();
     136                restore_current_blog();
     137        }
     138} else {
     139        $optimazz_cleanup_site();
     140        $optimazz_clean_uploads();
    118141}
  • optimazz/trunk/build/index-rtl.css

    r3723128 r3743505  
    1 .oz-shell{align-items:flex-start;background:#fff;border:1px solid #e7ecef;border-radius:16px;color:#0f2027;display:flex;font-size:14px;margin:10px 0 130px 20px;min-height:640px;overflow:hidden}.oz-shell *{box-sizing:border-box}.oz-shell .dashicons{font-family:dashicons}.oz-sidebar{align-self:flex-start;background:#fff;display:flex;flex:0 0 264px;flex-direction:column;padding:18px 14px}.oz-sidebar__brand{align-items:center;border-bottom:1px solid #e7ecef;display:flex;gap:12px;margin-bottom:10px;padding:10px 10px 18px}.oz-sidebar__brand strong{display:block;font-size:18px;font-weight:750;letter-spacing:-.01em}.oz-sidebar__brand span{color:#6b7a86;font-size:11px}.oz-sidebar__brand-fallback{align-items:center;display:flex;gap:12px}.oz-sidebar__foot{border-top:1px solid #e7ecef;color:#6b7a86;font-size:11px;margin-top:12px;padding:12px 12px 2px}.oz-logo{background:linear-gradient(-135deg,#1dd1a1,#10ac81);border-radius:12px;color:#fff;display:grid;font-size:26px;height:44px;place-items:center;width:44px}.oz-logo-img{display:block;height:30px;max-width:100%;width:auto}.oz-navitem{align-items:center;background:transparent;border:0;border-right:3px solid transparent;border-radius:10px 0 0 10px;color:#0f2027;cursor:pointer;display:flex;gap:12px;padding:11px 12px;text-align:right;transition:all .14s ease;width:100%}.oz-navitem>.oz-ico{color:#6b7a86;flex:0 0 20px;height:20px;transition:color .14s ease,transform .14s ease;width:20px}.oz-navitem:hover>.oz-ico{color:#0f2027}.oz-navitem.is-active>.oz-ico{color:#0c8f6b}.oz-navitem__text{flex:1;min-width:0}.oz-navitem__text strong{display:block;font-size:13.5px;font-weight:650;line-height:1.2}.oz-navitem__text small{color:#6b7a86;font-size:11px}.oz-navitem__dot{background:#1dd1a1;border-radius:50%;height:8px;width:8px}.oz-navitem:hover{background:#f4f7f8}.oz-navitem.is-active{background:rgba(29,209,161,.12);border-right-color:#1dd1a1}.oz-navitem.is-active .oz-navitem__text strong{color:#0c8f6b}.oz-content{align-self:stretch;background:#f4f7f8;border-right:1px solid #e7ecef;display:flex;flex:1;flex-direction:column;min-width:0}.oz-topbar{align-items:center;background:#fff;border-bottom:1px solid #e7ecef;display:flex;gap:14px;padding:20px 28px}.oz-topbar__icon{background:rgba(29,209,161,.14);border-radius:12px;color:#0c8f6b;display:grid;font-size:22px;height:44px;place-items:center;width:44px}.oz-topbar h1{font-size:20px;font-weight:700;margin:0;padding:0}.oz-topbar p{color:#6b7a86;font-size:12.5px;margin:0}.oz-main{display:flex;flex-direction:column;gap:16px;padding:24px 28px}.oz-welcome{align-items:center;background:linear-gradient(-125deg,rgba(29,209,161,.9),rgba(16,172,129,.92));border-radius:16px;color:#06382c;display:flex;gap:20px;justify-content:space-between;overflow:hidden;padding:22px 26px}.oz-welcome h2{color:#06382c;font-size:18px;font-weight:700;margin:0 0 6px}.oz-welcome p{color:#0c5b46;font-size:13px;margin:0;max-width:620px}.oz-welcome__icon{flex:0 0 auto;font-size:64px;height:64px;opacity:.5;width:64px}.oz-score{align-items:center;background:linear-gradient(-125deg,#1dd1a1,#10ac81 62%,#0c8f6b);border-radius:16px;box-shadow:0 16px 34px -18px rgba(12,143,107,.65);color:#06382c;display:flex;gap:30px;overflow:hidden;padding:28px 32px;position:relative}.oz-score:before{background:radial-gradient(circle,hsla(0,0%,100%,.3),transparent 62%);height:440px;left:-8%;top:-55%;width:440px}.oz-score:after,.oz-score:before{border-radius:50%;content:"";pointer-events:none;position:absolute}.oz-score:after{background:radial-gradient(circle,rgba(0,0,0,.08),transparent 60%);bottom:-70%;height:320px;right:8%;width:320px}.oz-score>*{position:relative;z-index:1}.oz-score__gauge{filter:drop-shadow(0 8px 16px rgba(3,40,30,.28));flex:0 0 auto;line-height:0;position:relative}.oz-score__grade{background:#fff;border-radius:50%;box-shadow:0 4px 10px rgba(3,40,30,.28);color:#0c8f6b;display:grid;font-size:14px;font-weight:800;height:30px;place-items:center;position:absolute;left:2px;top:8px;width:30px}.oz-score__body{flex:1;min-width:0}.oz-score__body h2{color:#06382c;font-size:21px;font-weight:750;letter-spacing:-.02em;margin:0 0 4px}.oz-score__body p{color:rgba(6,56,44,.82);font-size:13px;font-weight:500;margin:0 0 15px}.oz-score__chips{display:flex;flex-wrap:wrap;gap:8px}.oz-chip{align-items:center;background:hsla(0,0%,100%,.18);border:1px solid hsla(0,0%,100%,.28);border-radius:999px;color:rgba(6,56,44,.72);display:inline-flex;font-size:11.5px;font-weight:650;gap:6px;padding:5px 9px 5px 12px;transition:transform .15s ease,box-shadow .15s ease}.oz-chip .dashicons{font-size:15px;height:15px;opacity:.65;width:15px}.oz-chip.is-on{background:#fff;border-color:#fff;box-shadow:0 3px 8px -2px rgba(3,40,30,.22);color:#0c8f6b}.oz-chip.is-on .dashicons{color:#1dd1a1;opacity:1}.oz-chip.is-on:hover{box-shadow:0 6px 14px -4px rgba(3,40,30,.3);transform:translateY(-1px)}.oz-convert{align-items:center;background:#f4f7f8;border-radius:12px;display:flex;gap:16px;justify-content:space-between;margin-top:16px;padding:16px}.oz-convert__info{color:#6b7a86;display:flex;flex-direction:column;font-size:12.5px;gap:3px}.oz-convert__info strong{color:#0f2027;font-size:13px}.oz-progress{background:#dfe6e8;border-radius:999px;height:6px;margin-top:4px;overflow:hidden;width:200px}.oz-progress div{background:linear-gradient(-90deg,#1dd1a1,#10ac81);height:100%;transition:width .3s ease}.oz-kpis{display:grid;gap:14px;grid-template-columns:repeat(3,1fr)}.oz-kpi{background:#fff;border:1px solid #e7ecef;border-radius:14px;display:flex;flex-direction:column;padding:16px 18px}.oz-kpi>.dashicons{color:#10ac81;font-size:20px;margin-bottom:8px}.oz-kpi strong{font-size:24px;font-weight:750;min-height:28px}.oz-kpi span{color:#6b7a86;font-size:12px}.oz-quick{display:grid;gap:16px;grid-template-columns:1fr 1fr}.oz-quick__actions{display:flex;flex-wrap:wrap;gap:10px}.oz-cachelist{border-top:1px solid #e7ecef;margin:18px 0 0}.oz-cachelist>li{align-items:center;border-bottom:1px solid #f1f4f5;color:#6b7a86;display:flex;font-size:13px;gap:10px;padding:11px 2px}.oz-cachelist>li:last-child{border-bottom:0}.oz-cachelist>li .dashicons{color:#10ac81;font-size:17px}.oz-cachelist>li strong{color:#0f2027;font-weight:650;margin-right:auto}.oz-cachelist>li .oz-row-label{display:flex;flex-direction:column;line-height:1.3}.oz-cachelist>li .oz-row-label small{color:#6b7a86;font-size:11.5px;opacity:.85}.oz-status{margin:0}.oz-status li{align-items:center;border-bottom:1px solid #e7ecef;display:flex;font-size:13.5px;font-weight:600;gap:10px;padding:9px 6px}.oz-status li:last-child{border-bottom:0}.oz-status li>.oz-ico{color:#6b7a86;flex:0 0 18px;height:18px;width:18px}.oz-status__name{background:transparent;border:0;color:#0f2027;cursor:pointer;flex:1;font:inherit;font-weight:600;padding:4px 0;text-align:right}.oz-status__name:hover{color:#0c8f6b}.oz-status .components-toggle-control{margin-right:auto}.oz-card,.oz-panel{background:#fff;border:1px solid #e7ecef;border-radius:16px;overflow:visible}.oz-card .oz-card__head{border-radius:16px 16px 0 0}.oz-panel__head{padding:18px 22px 0}.oz-panel__head h2{font-size:16px;font-weight:650;margin:0}.oz-panel__head p{color:#6b7a86;font-size:12.5px;margin:4px 0 0}.oz-panel__body{padding:8px 22px 20px}.oz-panel__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-panel__body .components-base-control:last-child{border-bottom:0}.oz-card{transition:box-shadow .18s ease,border-color .18s ease}.oz-card.is-enabled{border-color:rgba(29,209,161,.5);box-shadow:0 0 0 1px rgba(29,209,161,.22)}.oz-card__head{align-items:flex-start;display:flex;gap:15px;padding:20px 22px}.oz-card__icon{background:#f4f7f8;border-radius:12px;color:#6b7a86;display:grid;flex:0 0 46px;font-size:22px;height:46px;place-items:center;width:46px}.oz-card__title{flex:1;min-width:0}.oz-card__title h2{align-items:center;display:flex;font-size:16px;font-weight:650;gap:10px;margin:0 0 3px}.oz-card__title p{color:#6b7a86;font-size:12.5px;margin:0}.oz-card__body{border-top:1px solid #e7ecef;padding:4px 22px 16px}.oz-card__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-card__body .components-base-control:last-child{border-bottom:0}.is-enabled .oz-card__icon{background:linear-gradient(-135deg,#1dd1a1,#10ac81);color:#fff}.oz-inline-actions{display:flex;gap:10px;padding-top:16px}.oz-dbgroup{border-top:1px solid #eef0f2;padding:18px 0}.oz-dbgroup:first-of-type{border-top:0;padding-top:4px}.oz-dbgroup__title{color:#0e1116;font-size:14px;font-weight:700;margin:0 0 4px}.oz-dbgroup__desc{color:#6b7280;font-size:13px;line-height:1.5;margin:0 0 14px}.oz-dbgroup .components-toggle-control{margin-bottom:14px}.oz-dbgroup .components-toggle-control:last-child{margin-bottom:0}.oz-dbwarn{align-items:flex-start;background:#fff5f5;border:1px solid #fcd9d9;border-radius:10px;color:#b42318;display:flex;font-size:13px;font-weight:600;gap:8px;line-height:1.5;margin:4px 0;padding:12px 14px}.oz-dbwarn .dashicons{color:#e05242;flex:0 0 auto}.oz-cachewarn{align-items:center;background:#fff8ec;border:1px solid #f4dcae;border-radius:12px;color:#7a4b00;display:flex;gap:13px;margin:0 0 18px;padding:14px 16px}.oz-cachewarn .dashicons{color:#e0900f;flex:0 0 auto;font-size:22px;height:22px;width:22px}.oz-cachewarn__text{flex:1 1 auto;min-width:0}.oz-cachewarn__text strong{color:#6b4200;display:block;font-size:14px;font-weight:700}.oz-cachewarn__text p{color:#8a6410;font-size:12.5px;line-height:1.5;margin:3px 0 0}.oz-cachewarn .oz-btn-primary{flex:0 0 auto}@media(max-width:640px){.oz-cachewarn{flex-wrap:wrap}.oz-cachewarn .oz-btn-primary{justify-content:center;width:100%}}.oz-subhead{color:#8a96a0;font-size:11px;font-weight:700;letter-spacing:.08em;margin:18px 0 2px;padding-top:14px;text-transform:uppercase}.oz-health,.oz-subhead{border-top:1px solid #e7ecf0}.oz-health{margin-top:18px;padding-top:16px}.oz-health__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.oz-health__head strong{font-size:14px}.oz-health__hint{color:#6b7280;font-size:12px;line-height:1.5;margin:4px 0 10px}.oz-health__list{display:flex;flex-direction:column;gap:6px;margin:0}.oz-health__row{align-items:center;display:flex;font-size:12.5px;gap:8px;line-height:1.4;margin:0}.oz-health__row .dashicons{flex:0 0 auto;font-size:17px;height:17px;width:17px}.oz-health__row.is-ok{color:#216e4e}.oz-health__row.is-ok .dashicons{color:#1dd1a1}.oz-health__row.is-warn{color:#8a5a00}.oz-health__row.is-warn .dashicons{color:#e0900f}.oz-health__row.is-bad{color:#b42318}.oz-health__row.is-bad .dashicons{color:#e05242}.oz-health__err{color:#b42318;font-size:12.5px}.oz-health__fix{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-top:14px}.oz-health__fix span{color:#6b7280;flex:1 1 160px;font-size:12px}.oz-health__conflict{display:flex;flex-direction:column;gap:8px;margin-top:12px}.oz-health__conflict-row{align-items:center;background:#fff5f5;border:1px solid #fcd9d9;border-radius:9px;display:flex;flex-wrap:wrap;gap:10px;padding:10px 12px}.oz-health__conflict-row .dashicons{color:#e05242;flex:0 0 auto}.oz-health__conflict-row span:not(.dashicons){color:#8a2f22;flex:1 1 200px;font-size:12.5px}.oz-health__conflict-row .oz-btn-danger.components-button{flex:0 0 auto;height:32px}.oz-health__manual{background:#fff8ec;border:1px solid #f4dcae;border-radius:9px;margin-top:12px;padding:10px 12px}.oz-health__manual p{color:#7a4b00;font-size:12px;margin:0 0 6px}.oz-health__manual code{background:#2b2f36;border-radius:6px;color:#e7f0ec;display:inline-block;font-size:12.5px;padding:6px 10px}.oz-btn-ghost.components-button{border:1px solid #d5dbe0;border-radius:8px;color:#384049;font-size:12.5px;height:30px}.oz-btn-ghost.components-button:hover:not(:disabled){border-color:#1dd1a1;color:#0b7a5c}.oz-pill{align-items:center;border-radius:999px;display:inline-flex;font-size:11px;font-weight:700;gap:5px;padding:3px 9px}.oz-pill i{background:currentColor;border-radius:50%;height:6px;width:6px}.oz-pill.is-on{background:rgba(29,209,161,.16);color:#0c8f6b}.oz-pill.is-off{background:#eef1f2;color:#6b7a86}.oz-shell .components-range-control__thumb,.oz-shell .components-range-control__track{background-color:#1dd1a1!important}.oz-fieldgroup{background:rgba(29,209,161,.035);border:1px solid #e7ecef;border-radius:12px;margin-top:18px;padding:16px 16px 4px}.oz-fieldgroup__title{color:#0e1116;font-size:13px;font-weight:700;margin:0 0 4px}.oz-fieldgroup__desc{color:#6b7280;font-size:12.5px;line-height:1.5;margin:0 0 14px}.oz-fieldgroup .components-base-control{margin-bottom:14px}.oz-shell .components-form-token-field__input-container{background:#fff;border:1px solid #e7ecef;border-radius:10px;min-height:44px;padding:5px 8px;transition:border-color .15s ease,box-shadow .15s ease}.oz-shell .components-form-token-field__input-container.is-active,.oz-shell .components-form-token-field__input-container:focus-within{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-shell .components-form-token-field__token{border-radius:8px;font-size:12.5px;margin:3px;overflow:hidden}.oz-shell .components-form-token-field__token-text{background:rgba(29,209,161,.14);border-radius:0 8px 8px 0;color:#0c8f6b;font-weight:650;line-height:1.7;padding:2px 11px 2px 4px}.oz-shell .components-form-token-field__remove-token{background:rgba(29,209,161,.14);border-radius:8px 0 0 8px;color:#0c8f6b;min-width:24px;padding:0 7px;transition:background .15s ease,color .15s ease}.oz-shell .components-form-token-field__remove-token svg{fill:currentColor}.oz-shell .components-form-token-field__remove-token:hover{background:#10ac81;color:#fff}.oz-shell .components-form-token-field__input{font-size:13.5px;margin:4px;min-height:28px}.oz-shell .components-input-control__backdrop{display:none!important}.oz-shell .components-input-control__input,.oz-shell .components-select-control__input,.oz-shell .components-text-control__input{background:#fff!important;border:1px solid #e7ecef!important;border-radius:10px!important;box-shadow:none!important;color:#0f2027;font-size:13.5px;min-height:42px;padding:0 14px}.oz-shell .components-input-control__input:hover,.oz-shell .components-select-control__input:hover,.oz-shell .components-text-control__input:hover{border-color:#bcc6cc!important}.oz-shell .components-input-control__input:focus,.oz-shell .components-select-control__input:focus,.oz-shell .components-text-control__input:focus{border-color:#1dd1a1!important;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-shell select.components-select-control__input{cursor:pointer}.oz-pillgroup .components-base-control__label{display:block;font-weight:600;margin-bottom:8px}.oz-pillgroup__row{background:#f0f2f4;border-radius:12px;display:inline-flex;flex-wrap:wrap;gap:6px;padding:4px}.oz-pillgroup .components-base-control__help{display:block;margin-top:8px}.oz-pillbtn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:9px;color:#4b5563;cursor:pointer;font-size:13px;font-weight:600;min-width:44px;padding:7px 16px;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.oz-pillbtn:hover{color:#0e1116}.oz-pillbtn.is-on{background:#fff;box-shadow:0 1px 3px rgba(4,56,44,.14);color:#0c8f6b}.oz-pillbtn:focus-visible{outline:2px solid #1dd1a1;outline-offset:1px}.oz-select{position:relative}.oz-select .components-base-control__label{display:block;margin-bottom:8px}.oz-select__btn{align-items:center;background:#fff;border:1px solid #e7ecef;border-radius:10px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;height:42px;justify-content:space-between;padding:0 14px;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.oz-select__btn .dashicons{color:#0c8f6b;transition:transform .18s ease}.oz-select__btn:hover{border-color:#bcc6cc}.oz-select__btn:focus,.oz-select__btn:focus-visible{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-select__btn[aria-expanded=true]{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-select__btn[aria-expanded=true] .dashicons{transform:rotate(-180deg)}.oz-select__menu{background:#fff;border:1px solid #e7ecef;border-radius:12px;box-shadow:0 14px 34px rgba(15,32,39,.18);right:0;list-style:none;margin:0;padding:6px;position:absolute;left:0;top:calc(100% + 6px);z-index:30}.oz-select__item{align-items:center;border-radius:8px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;justify-content:space-between;padding:9px 12px}.oz-select__item .dashicons{color:#10ac81;font-size:18px}.oz-select__item.is-active,.oz-select__item:hover{background:rgba(29,209,161,.12);color:#0c8f6b}.oz-select__item.is-selected{color:#0c8f6b;font-weight:650}.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{border-radius:10px;font-weight:650;height:38px;transition:transform .16s cubic-bezier(.2,.7,.3,1),box-shadow .18s ease,background .16s ease,border-color .16s ease,color .16s ease}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:transform .22s cubic-bezier(.2,.7,.3,1)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:translateY(-1px)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:scale(1.14)}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled){transform:translateY(0) scale(.975);transition-duration:.05s}.oz-shell .oz-btn-danger.components-button:focus-visible,.oz-shell .oz-btn-ghost.components-button:focus-visible,.oz-shell .oz-btn-primary.components-button:focus-visible{outline:2px solid #1dd1a1;outline-offset:2px}.oz-shell .oz-btn-danger.components-button:disabled,.oz-shell .oz-btn-ghost.components-button:disabled,.oz-shell .oz-btn-primary.components-button:disabled{opacity:.5;transform:none}.oz-shell .oz-btn-primary.components-button{background:linear-gradient(-135deg,#1dd1a1,#10ac81);border:0;box-shadow:0 1px 2px rgba(20,60,48,.12);color:#fff;padding:0 18px}.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){background:linear-gradient(-135deg,#10ac81,#0c8f6b);box-shadow:0 8px 20px -7px rgba(29,209,161,.65);color:#fff}.oz-shell .oz-btn-primary.components-button.is-busy{animation:oz-btn-sheen 1.4s linear infinite;background-size:200% 100%}.oz-shell .oz-btn-ghost.components-button{background:#fff;border:1px solid rgba(29,209,161,.5);box-shadow:none;color:#0c8f6b}.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled){background:rgba(29,209,161,.08);border-color:#1dd1a1;box-shadow:0 6px 16px -8px rgba(29,209,161,.5);color:#0c8f6b}.oz-shell .oz-btn-danger.components-button{background:#fff;border:1px solid rgba(214,54,56,.5);color:#d63638}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled){background:rgba(214,54,56,.08);border-color:#d63638;box-shadow:0 6px 16px -8px rgba(214,54,56,.5);color:#b32d2e}.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg{transform:scale(1.1) rotate(-90deg)}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg{transform:none}}@keyframes oz-btn-sheen{0%{background-image:linear-gradient(-135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:-100% 0}to{background-image:linear-gradient(-135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:300% 0}}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{transition:background .16s ease,color .16s ease,border-color .16s ease}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:none}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:none}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:none}.oz-shell .oz-btn-danger.components-button.is-busy,.oz-shell .oz-btn-ghost.components-button.is-busy,.oz-shell .oz-btn-primary.components-button.is-busy{animation:none}}.oz-shell .components-button.is-primary:focus:not(:disabled),.oz-shell .components-button:focus,.oz-shell .components-button:focus:not(:disabled){box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle,.oz-shell .components-form-toggle__input,.oz-shell .components-form-toggle__input:active,.oz-shell .components-form-toggle__input:focus,.oz-shell .components-form-toggle__input:focus-visible{border:none!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__track{background-color:#cdd6db!important;border:1px solid transparent!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle.is-checked .components-form-toggle__track{background-color:#1dd1a1!important}.oz-shell .components-form-toggle__input:focus+.components-form-toggle__track,.oz-shell .components-form-toggle__input:focus-visible+.components-form-toggle__track{box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__thumb{background-color:#fff!important;border:none!important;box-shadow:0 1px 2px rgba(15,32,39,.25)!important}.oz-savebar{align-items:center;background:#fff;border-top:1px solid #e7ecef;bottom:0;box-shadow:0 -6px 18px rgba(15,32,39,.06);display:flex;gap:16px;justify-content:flex-end;right:160px;padding:13px 32px;position:fixed;left:0;z-index:9}.oz-savebar__hint{color:#6b7a86;font-size:13px}.oz-savebar.is-dirty .oz-savebar__hint{color:#0c8f6b;font-weight:650}.folded .oz-savebar{right:36px}.oz-toast{bottom:72px;z-index:99}.oz-job,.oz-toast{position:fixed;left:24px}.oz-job{background:#fff;border:1px solid #e7ecef;border-radius:16px;bottom:84px;box-shadow:0 18px 50px rgba(15,32,39,.22);color:#0f2027;font-size:13px;max-width:calc(100vw - 48px);overflow:hidden;width:420px;z-index:100}.oz-job__head{align-items:center;display:flex;gap:12px;padding:16px 16px 12px}.oz-job__head .components-spinner{margin:0}.oz-job__icon-status{font-size:22px;height:22px;width:22px}.oz-job__icon-status--done{color:#10ac81}.oz-job__icon-status--stopped{color:#6b7a86}.oz-job__icon-status--error{color:#d63638}.oz-job__title{flex:1;min-width:0}.oz-job__title strong{display:block;font-size:15px;font-weight:700;line-height:1.2}.oz-job__title span{color:#6b7a86;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.oz-job__pct{color:#0f2027;font-size:13px;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__icon{background:transparent;border:0;border-radius:6px;color:#6b7a86;cursor:pointer;display:inline-flex;padding:4px}.oz-job__icon:hover{background:#f4f7f8;color:#0f2027}.oz-job__icon .dashicons{font-size:18px;height:18px;width:18px}.oz-job__body{padding:0 16px 16px}.oz-job__row{align-items:baseline;display:flex;gap:12px;justify-content:space-between;margin-bottom:8px}.oz-job__row span{line-height:1.4}.oz-job__row strong{flex:0 0 auto;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__bar{background:#e7ecef;border-radius:999px;height:7px;overflow:hidden}.oz-job__bar div{background:linear-gradient(-90deg,#1dd1a1,#10ac81);border-radius:999px;height:100%;transition:width .35s ease}.oz-job__bar.is-indeterminate div{animation:oz-indeterminate 1.1s ease-in-out infinite;border-radius:999px;width:40%!important}.oz-job.is-min .oz-job__bar{margin:2px 16px 16px}.oz-job.is-min .oz-job__head{padding-bottom:10px}.oz-job.is-done .oz-job__bar div{background:linear-gradient(-90deg,#10ac81,#0c8f6b)}.oz-job.is-stopped .oz-job__bar div{background:#c3ccd2}.oz-job.is-error .oz-job__bar div{background:linear-gradient(-90deg,#f16b6b,#d63638)}.oz-job__step{color:#6b7a86;font-size:12px;margin-top:8px}.oz-job__stop{align-items:center;background:transparent;border:0;color:#d63638;cursor:pointer;display:inline-flex;font-size:13px;font-weight:650;gap:6px;margin-top:14px;padding:4px 0}.oz-job__stop .dashicons{font-size:18px}.oz-job__stop:hover{color:#b32d2e}.oz-job__log{background:#11202b;border-radius:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.7;margin-top:14px;max-height:150px;overflow-y:auto;padding:10px 12px}.oz-logline{color:#cdd8df;display:flex;gap:8px}.oz-logline__tag{color:#5fd0ad;flex:0 0 auto;font-weight:700;min-width:42px}.oz-logline--warn .oz-logline__tag{color:#f0b429}.oz-logline--error .oz-logline__tag{color:#ff6b6b}.oz-logline--done .oz-logline__tag{color:#1dd1a1}.oz-logline__text{word-break:break-word}@media(max-width:1100px){.oz-kpis,.oz-quick{grid-template-columns:1fr}}@media(max-width:1024px){.oz-score{align-items:flex-start;flex-direction:column;text-align:right}.oz-convert{align-items:stretch;flex-direction:column;gap:12px}.oz-convert .oz-btn-ghost.components-button,.oz-convert .oz-btn-primary.components-button{justify-content:center;width:100%}}.oz-mobile-toggle,.oz-nav-overlay{display:none!important}@media(max-width:1024px){.oz-shell{flex-direction:column;margin:8px 8px 110px}.oz-content{border-right:0;min-width:0;width:100%}.oz-sidebar{border-bottom:1px solid #e7ecef;border-left:0;flex:none;max-height:none;overflow:visible;padding:14px 12px;position:static;width:100%}.oz-sidebar__brand{margin-bottom:10px;padding-bottom:12px}.oz-sidebar nav{display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}.oz-navitem{border-bottom:3px solid transparent;border-right:0;border-radius:10px;flex:0 0 auto;padding:8px 12px;width:auto}.oz-navitem.is-active{border-bottom-color:#1dd1a1;border-right-color:transparent}.oz-navitem .oz-navitem__text strong{white-space:nowrap}.oz-navitem .oz-navitem__dot,.oz-navitem small,.oz-sidebar__foot{display:none}.oz-topbar{padding:16px 18px}.oz-topbar h1{font-size:18px}.oz-main{padding:16px 18px}.oz-savebar{right:0;padding:12px 18px}.oz-card__body{padding-right:18px;padding-left:18px}.oz-job{right:8px;left:8px;width:auto}}@media(max-width:560px){.oz-tiles{grid-template-columns:1fr}.oz-hero,.oz-score{padding:18px}.oz-card__head{gap:12px;padding:16px}.oz-card__icon{flex-basis:40px;height:40px;width:40px}.oz-savebar__hint{display:none}}@keyframes oz-indeterminate{0%{transform:translateX(100%)}to{transform:translateX(-350%)}}.oz-muted{color:#6b7a86;font-size:13px}.oz-reqs{list-style:none;margin:0;padding:0}.oz-req{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:grid;font-size:13.5px;gap:6px 10px;grid-template-columns:20px 1fr auto;padding:10px 6px}.oz-req:last-child{border-bottom:0}.oz-req__dot{font-size:18px;height:18px;width:18px}.oz-req__label{font-weight:650}.oz-req__value{color:#6b7a86;font-weight:600;justify-self:end}.oz-req__note{color:#6b7a86;font-size:12px;font-weight:400;grid-column:2/-1;line-height:1.45;margin-top:2px}.oz-req--ok>.oz-req__dot{color:#0c8f6b}.oz-req--warn>.oz-req__dot{color:#e0900f}.oz-req--error>.oz-req__dot{color:#e05242}.oz-req--info>.oz-req__dot,.oz-req--info>.oz-req__value{color:#6b7a86}.oz-req--warn{background:rgba(224,144,15,.06)}.oz-req--error{background:rgba(224,82,66,.06)}.oz-notice{align-items:flex-start;background:rgba(224,82,66,.08);border:1px solid rgba(224,82,66,.28);border-radius:10px;color:#7a2018;display:flex;font-size:13px;gap:10px;line-height:1.5;margin:0 0 14px;padding:12px 14px}.oz-notice .dashicons{color:#e05242;flex:0 0 20px;margin-top:1px}.oz-notice--warn{background:rgba(224,144,15,.09);border-color:rgba(224,144,15,.3);color:#7a5406}.oz-notice--warn .dashicons{color:#e0900f}.oz-recs{list-style:none;margin:0;padding:0}.oz-rec{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:flex;gap:12px;padding:12px 8px}.oz-rec:last-child{border-bottom:0}.oz-rec__dot{flex:0 0 20px;font-size:20px;height:20px;margin-top:1px;width:20px}.oz-rec__text{flex:1;min-width:0}.oz-rec__text strong{display:block;font-size:13.5px;font-weight:650}.oz-rec__text small{color:#6b7a86;display:block;font-size:12px;line-height:1.45;margin-top:2px}.oz-rec__btn.components-button{flex:0 0 auto}.oz-rec--high>.oz-rec__dot{color:#e05242}.oz-rec--medium>.oz-rec__dot{color:#e0900f}.oz-rec--low>.oz-rec__dot{color:#6b7a86}.oz-rec--high{background:rgba(224,82,66,.05)}.oz-rec--medium{background:rgba(224,144,15,.05)}.oz-recs-clear{align-items:center;color:#6b7a86;display:flex;font-size:13px;gap:8px}.oz-recs-clear .dashicons{color:#0c8f6b}.oz-select--compact{border-bottom:0!important;flex:none;margin:0 auto 0 0!important;padding:0!important}.oz-select--compact .oz-select__btn{background:#f7faf9;border-radius:9px;font-size:13px;font-weight:650;gap:10px;height:30px;min-width:132px;padding:0 12px 0 10px;width:auto}.oz-select--compact .oz-select__btn:disabled{cursor:progress;opacity:.6}.oz-select--compact .oz-select__btn .dashicons{font-size:16px;height:16px;width:16px}.oz-select--compact .oz-select__menu{right:auto;min-width:190px;left:0}.oz-select--compact .oz-select__item{font-size:13px;padding:8px 10px}
     1.oz-shell{align-items:flex-start;background:#fff;border:1px solid #e7ecef;border-radius:16px;color:#0f2027;display:flex;font-size:14px;margin:10px 0 130px 20px;min-height:640px;overflow:hidden}.oz-shell *{box-sizing:border-box}.oz-shell .dashicons{font-family:dashicons}.oz-sidebar{align-self:flex-start;background:#fff;display:flex;flex:0 0 264px;flex-direction:column;padding:18px 14px}.oz-sidebar__brand{align-items:center;border-bottom:1px solid #e7ecef;display:flex;gap:12px;margin-bottom:10px;padding:10px 10px 18px}.oz-sidebar__brand strong{display:block;font-size:18px;font-weight:750;letter-spacing:-.01em}.oz-sidebar__brand span{color:#6b7a86;font-size:11px}.oz-sidebar__brand-fallback{align-items:center;display:flex;gap:12px}.oz-sidebar__foot{border-top:1px solid #e7ecef;color:#6b7a86;font-size:11px;margin-top:12px;padding:12px 12px 2px}.oz-logo{background:linear-gradient(-135deg,#1dd1a1,#10ac81);border-radius:12px;color:#fff;display:grid;font-size:26px;height:44px;place-items:center;width:44px}.oz-logo-img{display:block;height:30px;max-width:100%;width:auto}.oz-navitem{align-items:center;background:transparent;border:0;border-right:3px solid transparent;border-radius:10px 0 0 10px;color:#0f2027;cursor:pointer;display:flex;gap:12px;padding:11px 12px;text-align:right;transition:all .14s ease;width:100%}.oz-navitem>.oz-ico{color:#6b7a86;flex:0 0 20px;height:20px;transition:color .14s ease,transform .14s ease;width:20px}.oz-navitem:hover>.oz-ico{color:#0f2027}.oz-navitem.is-active>.oz-ico{color:#0c8f6b}.oz-navitem__text{flex:1;min-width:0}.oz-navitem__text strong{display:block;font-size:13.5px;font-weight:650;line-height:1.2}.oz-navitem__text small{color:#6b7a86;font-size:11px}.oz-navitem__dot{background:#1dd1a1;border-radius:50%;height:8px;width:8px}.oz-navitem:hover{background:#f4f7f8}.oz-navitem.is-active{background:rgba(29,209,161,.12);border-right-color:#1dd1a1}.oz-navitem.is-active .oz-navitem__text strong{color:#0c8f6b}.oz-content{align-self:stretch;background:#f4f7f8;border-right:1px solid #e7ecef;display:flex;flex:1;flex-direction:column;min-width:0}.oz-topbar{align-items:center;background:#fff;border-bottom:1px solid #e7ecef;display:flex;gap:14px;padding:20px 28px}.oz-topbar__icon{background:rgba(29,209,161,.14);border-radius:12px;color:#0c8f6b;display:grid;font-size:22px;height:44px;place-items:center;width:44px}.oz-topbar h1{font-size:20px;font-weight:700;margin:0;padding:0}.oz-topbar p{color:#6b7a86;font-size:12.5px;margin:0}.oz-main{display:flex;flex-direction:column;gap:16px;padding:24px 28px}.oz-welcome{align-items:center;background:linear-gradient(-125deg,rgba(29,209,161,.9),rgba(16,172,129,.92));border-radius:16px;color:#06382c;display:flex;gap:20px;justify-content:space-between;overflow:hidden;padding:22px 26px}.oz-welcome h2{color:#06382c;font-size:18px;font-weight:700;margin:0 0 6px}.oz-welcome p{color:#0c5b46;font-size:13px;margin:0;max-width:620px}.oz-welcome__icon{flex:0 0 auto;font-size:64px;height:64px;opacity:.5;width:64px}.oz-score{align-items:center;background:linear-gradient(-125deg,#1dd1a1,#10ac81 62%,#0c8f6b);border-radius:16px;box-shadow:0 16px 34px -18px rgba(12,143,107,.65);color:#06382c;display:flex;gap:30px;overflow:hidden;padding:28px 32px;position:relative}.oz-score:before{background:radial-gradient(circle,hsla(0,0%,100%,.3),transparent 62%);height:440px;left:-8%;top:-55%;width:440px}.oz-score:after,.oz-score:before{border-radius:50%;content:"";pointer-events:none;position:absolute}.oz-score:after{background:radial-gradient(circle,rgba(0,0,0,.08),transparent 60%);bottom:-70%;height:320px;right:8%;width:320px}.oz-score>*{position:relative;z-index:1}.oz-score__gauge{filter:drop-shadow(0 8px 16px rgba(3,40,30,.28));flex:0 0 auto;line-height:0;position:relative}.oz-score__grade{background:#fff;border-radius:50%;box-shadow:0 4px 10px rgba(3,40,30,.28);color:#0c8f6b;display:grid;font-size:14px;font-weight:800;height:30px;place-items:center;position:absolute;left:2px;top:8px;width:30px}.oz-score__body{flex:1;min-width:0}.oz-score__body h2{color:#06382c;font-size:21px;font-weight:750;letter-spacing:-.02em;margin:0 0 4px}.oz-score__body p{color:rgba(6,56,44,.82);font-size:13px;font-weight:500;margin:0 0 15px}.oz-score__chips{display:flex;flex-wrap:wrap;gap:8px}.oz-chip{align-items:center;background:hsla(0,0%,100%,.18);border:1px solid hsla(0,0%,100%,.28);border-radius:999px;color:rgba(6,56,44,.72);display:inline-flex;font-size:11.5px;font-weight:650;gap:6px;padding:5px 9px 5px 12px;transition:transform .15s ease,box-shadow .15s ease}.oz-chip .dashicons{font-size:15px;height:15px;opacity:.65;width:15px}.oz-chip.is-on{background:#fff;border-color:#fff;box-shadow:0 3px 8px -2px rgba(3,40,30,.22);color:#0c8f6b}.oz-chip.is-on .dashicons{color:#1dd1a1;opacity:1}.oz-chip.is-on:hover{box-shadow:0 6px 14px -4px rgba(3,40,30,.3);transform:translateY(-1px)}.oz-convert{align-items:center;background:#f4f7f8;border-radius:12px;display:flex;gap:16px;justify-content:space-between;margin-top:16px;padding:16px}.oz-convert__info{color:#6b7a86;display:flex;flex-direction:column;font-size:12.5px;gap:3px}.oz-convert__info strong{color:#0f2027;font-size:13px}.oz-progress{background:#dfe6e8;border-radius:999px;height:6px;margin-top:4px;overflow:hidden;width:200px}.oz-progress div{background:linear-gradient(-90deg,#1dd1a1,#10ac81);height:100%;transition:width .3s ease}.oz-kpis{display:grid;gap:14px;grid-template-columns:repeat(3,1fr)}.oz-kpis--single{grid-template-columns:1fr}.oz-kpi{background:#fff;border:1px solid #e7ecef;border-radius:14px;display:flex;flex-direction:column;padding:16px 18px}.oz-kpi>.dashicons{color:#10ac81;font-size:20px;margin-bottom:8px}.oz-kpi strong{font-size:24px;font-weight:750;min-height:28px}.oz-kpi span{color:#6b7a86;font-size:12px}.oz-quick{display:grid;gap:16px;grid-template-columns:1fr 1fr}.oz-quick__actions{display:flex;flex-wrap:wrap;gap:10px}.oz-cachelist{border-top:1px solid #e7ecef;margin:18px 0 0}.oz-cachelist>li{align-items:center;border-bottom:1px solid #f1f4f5;color:#6b7a86;display:flex;font-size:13px;gap:10px;padding:11px 2px}.oz-cachelist>li:last-child{border-bottom:0}.oz-cachelist>li .dashicons{color:#10ac81;font-size:17px}.oz-cachelist>li strong{color:#0f2027;font-weight:650;margin-right:auto}.oz-cachelist>li .oz-row-label{display:flex;flex-direction:column;line-height:1.3}.oz-cachelist>li .oz-row-label small{color:#6b7a86;font-size:11.5px;opacity:.85}.oz-status{margin:0}.oz-status li{align-items:center;border-bottom:1px solid #e7ecef;display:flex;font-size:13.5px;font-weight:600;gap:10px;padding:9px 6px}.oz-status li:last-child{border-bottom:0}.oz-status li>.oz-ico{color:#6b7a86;flex:0 0 18px;height:18px;width:18px}.oz-status__name{background:transparent;border:0;color:#0f2027;cursor:pointer;flex:1;font:inherit;font-weight:600;padding:4px 0;text-align:right}.oz-status__name:hover{color:#0c8f6b}.oz-status .components-toggle-control{margin-right:auto}.oz-card,.oz-panel{background:#fff;border:1px solid #e7ecef;border-radius:16px;overflow:visible}.oz-card .oz-card__head{border-radius:16px 16px 0 0}.oz-panel__head{padding:18px 22px 0}.oz-panel__head h2{font-size:16px;font-weight:650;margin:0}.oz-panel__head p{color:#6b7a86;font-size:12.5px;margin:4px 0 0}.oz-panel__body{padding:8px 22px 20px}.oz-panel__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-panel__body .components-base-control:last-child{border-bottom:0}.oz-card{transition:box-shadow .18s ease,border-color .18s ease}.oz-card.is-enabled{border-color:rgba(29,209,161,.5);box-shadow:0 0 0 1px rgba(29,209,161,.22)}.oz-card__head{align-items:flex-start;display:flex;gap:15px;padding:20px 22px}.oz-card__icon{background:#f4f7f8;border-radius:12px;color:#6b7a86;display:grid;flex:0 0 46px;font-size:22px;height:46px;place-items:center;width:46px}.oz-card__title{flex:1;min-width:0}.oz-card__title h2{align-items:center;display:flex;font-size:16px;font-weight:650;gap:10px;margin:0 0 3px}.oz-card__title p{color:#6b7a86;font-size:12.5px;margin:0}.oz-card__body{border-top:1px solid #e7ecef;padding:4px 22px 16px}.oz-card__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-card__body .components-base-control:last-child{border-bottom:0}.is-enabled .oz-card__icon{background:linear-gradient(-135deg,#1dd1a1,#10ac81);color:#fff}.oz-inline-actions{display:flex;gap:10px;padding-top:16px}.oz-dbgroup{border-top:1px solid #eef0f2;padding:18px 0}.oz-dbgroup:first-of-type{border-top:0;padding-top:4px}.oz-dbgroup__title{color:#0e1116;font-size:14px;font-weight:700;margin:0 0 4px}.oz-dbgroup__desc{color:#6b7280;font-size:13px;line-height:1.5;margin:0 0 14px}.oz-dbgroup .components-toggle-control{margin-bottom:14px}.oz-dbgroup .components-toggle-control:last-child{margin-bottom:0}.oz-dbwarn{align-items:flex-start;background:#fff5f5;border:1px solid #fcd9d9;border-radius:10px;color:#b42318;display:flex;font-size:13px;font-weight:600;gap:8px;line-height:1.5;margin:4px 0;padding:12px 14px}.oz-dbwarn .dashicons{color:#e05242;flex:0 0 auto}.oz-cachewarn{align-items:center;background:#fff8ec;border:1px solid #f4dcae;border-radius:12px;color:#7a4b00;display:flex;gap:13px;margin:0 0 18px;padding:14px 16px}.oz-cachewarn .dashicons{color:#e0900f;flex:0 0 auto;font-size:22px;height:22px;width:22px}.oz-cachewarn__text{flex:1 1 auto;min-width:0}.oz-cachewarn__text strong{color:#6b4200;display:block;font-size:14px;font-weight:700}.oz-cachewarn__text p{color:#8a6410;font-size:12.5px;line-height:1.5;margin:3px 0 0}.oz-cachewarn .oz-btn-primary{flex:0 0 auto}@media(max-width:640px){.oz-cachewarn{flex-wrap:wrap}.oz-cachewarn .oz-btn-primary{justify-content:center;width:100%}}.oz-subhead{color:#8a96a0;font-size:11px;font-weight:700;letter-spacing:.08em;margin:18px 0 2px;padding-top:14px;text-transform:uppercase}.oz-health,.oz-subhead{border-top:1px solid #e7ecf0}.oz-health{margin-top:18px;padding-top:16px}.oz-health__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.oz-health__head strong{font-size:14px}.oz-health__hint{color:#6b7280;font-size:12px;line-height:1.5;margin:4px 0 10px}.oz-health__list{display:flex;flex-direction:column;gap:6px;margin:0}.oz-health__row{align-items:center;display:flex;font-size:12.5px;gap:8px;line-height:1.4;margin:0}.oz-health__row .dashicons{flex:0 0 auto;font-size:17px;height:17px;width:17px}.oz-health__row.is-ok{color:#216e4e}.oz-health__row.is-ok .dashicons{color:#1dd1a1}.oz-health__row.is-warn{color:#8a5a00}.oz-health__row.is-warn .dashicons{color:#e0900f}.oz-health__row.is-bad{color:#b42318}.oz-health__row.is-bad .dashicons{color:#e05242}.oz-health__err{color:#b42318;font-size:12.5px}.oz-health__fix{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-top:14px}.oz-health__fix span{color:#6b7280;flex:1 1 160px;font-size:12px}.oz-health__conflict{display:flex;flex-direction:column;gap:8px;margin-top:12px}.oz-health__conflict-row{align-items:center;background:#fff5f5;border:1px solid #fcd9d9;border-radius:9px;display:flex;flex-wrap:wrap;gap:10px;padding:10px 12px}.oz-health__conflict-row .dashicons{color:#e05242;flex:0 0 auto}.oz-health__conflict-row span:not(.dashicons){color:#8a2f22;flex:1 1 200px;font-size:12.5px}.oz-health__conflict-row .oz-btn-danger.components-button{flex:0 0 auto;height:32px}.oz-health__manual{background:#fff8ec;border:1px solid #f4dcae;border-radius:9px;margin-top:12px;padding:10px 12px}.oz-health__manual p{color:#7a4b00;font-size:12px;margin:0 0 6px}.oz-health__manual code{background:#2b2f36;border-radius:6px;color:#e7f0ec;display:inline-block;font-size:12.5px;padding:6px 10px}.oz-btn-ghost.components-button{border:1px solid #d5dbe0;border-radius:8px;color:#384049;font-size:12.5px;height:30px}.oz-btn-ghost.components-button:hover:not(:disabled){border-color:#1dd1a1;color:#0b7a5c}.oz-pill{align-items:center;border-radius:999px;display:inline-flex;font-size:11px;font-weight:700;gap:5px;padding:3px 9px}.oz-pill i{background:currentColor;border-radius:50%;height:6px;width:6px}.oz-pill.is-on{background:rgba(29,209,161,.16);color:#0c8f6b}.oz-pill.is-off{background:#eef1f2;color:#6b7a86}.oz-shell .components-range-control__thumb,.oz-shell .components-range-control__track{background-color:#1dd1a1!important}.oz-fieldgroup{background:rgba(29,209,161,.035);border:1px solid #e7ecef;border-radius:12px;margin-top:18px;padding:16px 16px 4px}.oz-fieldgroup__title{color:#0e1116;font-size:13px;font-weight:700;margin:0 0 4px}.oz-fieldgroup__desc{color:#6b7280;font-size:12.5px;line-height:1.5;margin:0 0 14px}.oz-fieldgroup .components-base-control{margin-bottom:14px}.oz-shell .components-form-token-field__input-container{background:#fff;border:1px solid #e7ecef;border-radius:10px;min-height:44px;padding:5px 8px;transition:border-color .15s ease,box-shadow .15s ease}.oz-shell .components-form-token-field__input-container.is-active,.oz-shell .components-form-token-field__input-container:focus-within{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-shell .components-form-token-field__token{border-radius:8px;font-size:12.5px;margin:3px;overflow:hidden}.oz-shell .components-form-token-field__token-text{background:rgba(29,209,161,.14);border-radius:0 8px 8px 0;color:#0c8f6b;font-weight:650;line-height:1.7;padding:2px 11px 2px 4px}.oz-shell .components-form-token-field__remove-token{background:rgba(29,209,161,.14);border-radius:8px 0 0 8px;color:#0c8f6b;min-width:24px;padding:0 7px;transition:background .15s ease,color .15s ease}.oz-shell .components-form-token-field__remove-token svg{fill:currentColor}.oz-shell .components-form-token-field__remove-token:hover{background:#10ac81;color:#fff}.oz-shell .components-form-token-field__input{font-size:13.5px;margin:4px;min-height:28px}.oz-shell .components-input-control__backdrop{display:none!important}.oz-shell .components-input-control__input,.oz-shell .components-select-control__input,.oz-shell .components-text-control__input{background:#fff!important;border:1px solid #e7ecef!important;border-radius:10px!important;box-shadow:none!important;color:#0f2027;font-size:13.5px;min-height:42px;padding:0 14px}.oz-shell .components-input-control__input:hover,.oz-shell .components-select-control__input:hover,.oz-shell .components-text-control__input:hover{border-color:#bcc6cc!important}.oz-shell .components-input-control__input:focus,.oz-shell .components-select-control__input:focus,.oz-shell .components-text-control__input:focus{border-color:#1dd1a1!important;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-shell select.components-select-control__input{cursor:pointer}.oz-pillgroup .components-base-control__label{display:block;font-weight:600;margin-bottom:8px}.oz-pillgroup__row{background:#f0f2f4;border-radius:12px;display:inline-flex;flex-wrap:wrap;gap:6px;padding:4px}.oz-pillgroup .components-base-control__help{display:block;margin-top:8px}.oz-pillbtn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:9px;color:#4b5563;cursor:pointer;font-size:13px;font-weight:600;min-width:44px;padding:7px 16px;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.oz-pillbtn:hover{color:#0e1116}.oz-pillbtn.is-on{background:#fff;box-shadow:0 1px 3px rgba(4,56,44,.14);color:#0c8f6b}.oz-pillbtn:focus-visible{outline:2px solid #1dd1a1;outline-offset:1px}.oz-select{position:relative}.oz-select .components-base-control__label{display:block;margin-bottom:8px}.oz-select__btn{align-items:center;background:#fff;border:1px solid #e7ecef;border-radius:10px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;height:42px;justify-content:space-between;padding:0 14px;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.oz-select__btn .dashicons{color:#0c8f6b;transition:transform .18s ease}.oz-select__btn:hover{border-color:#bcc6cc}.oz-select__btn:focus,.oz-select__btn:focus-visible{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-select__btn[aria-expanded=true]{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-select__btn[aria-expanded=true] .dashicons{transform:rotate(-180deg)}.oz-select__menu{background:#fff;border:1px solid #e7ecef;border-radius:12px;box-shadow:0 14px 34px rgba(15,32,39,.18);right:0;list-style:none;margin:0;padding:6px;position:absolute;left:0;top:calc(100% + 6px);z-index:30}.oz-select__item{align-items:center;border-radius:8px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;justify-content:space-between;padding:9px 12px}.oz-select__item .dashicons{color:#10ac81;font-size:18px}.oz-select__item.is-active,.oz-select__item:hover{background:rgba(29,209,161,.12);color:#0c8f6b}.oz-select__item.is-selected{color:#0c8f6b;font-weight:650}.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{border-radius:10px;font-weight:650;height:38px;transition:transform .16s cubic-bezier(.2,.7,.3,1),box-shadow .18s ease,background .16s ease,border-color .16s ease,color .16s ease}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:transform .22s cubic-bezier(.2,.7,.3,1)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:translateY(-1px)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:scale(1.14)}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled){transform:translateY(0) scale(.975);transition-duration:.05s}.oz-shell .oz-btn-danger.components-button:focus-visible,.oz-shell .oz-btn-ghost.components-button:focus-visible,.oz-shell .oz-btn-primary.components-button:focus-visible{outline:2px solid #1dd1a1;outline-offset:2px}.oz-shell .oz-btn-danger.components-button:disabled,.oz-shell .oz-btn-ghost.components-button:disabled,.oz-shell .oz-btn-primary.components-button:disabled{opacity:.5;transform:none}.oz-shell .oz-btn-primary.components-button{background:linear-gradient(-135deg,#1dd1a1,#10ac81);border:0;box-shadow:0 1px 2px rgba(20,60,48,.12);color:#fff;padding:0 18px}.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){background:linear-gradient(-135deg,#10ac81,#0c8f6b);box-shadow:0 8px 20px -7px rgba(29,209,161,.65);color:#fff}.oz-shell .oz-btn-primary.components-button.is-busy{animation:oz-btn-sheen 1.4s linear infinite;background-size:200% 100%}.oz-shell .oz-btn-ghost.components-button{background:#fff;border:1px solid rgba(29,209,161,.5);box-shadow:none;color:#0c8f6b}.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled){background:rgba(29,209,161,.08);border-color:#1dd1a1;box-shadow:0 6px 16px -8px rgba(29,209,161,.5);color:#0c8f6b}.oz-shell .oz-btn-danger.components-button{background:#fff;border:1px solid rgba(214,54,56,.5);color:#d63638}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled){background:rgba(214,54,56,.08);border-color:#d63638;box-shadow:0 6px 16px -8px rgba(214,54,56,.5);color:#b32d2e}.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg{transform:scale(1.1) rotate(-90deg)}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg{transform:none}}@keyframes oz-btn-sheen{0%{background-image:linear-gradient(-135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:-100% 0}to{background-image:linear-gradient(-135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:300% 0}}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{transition:background .16s ease,color .16s ease,border-color .16s ease}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:none}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:none}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:none}.oz-shell .oz-btn-danger.components-button.is-busy,.oz-shell .oz-btn-ghost.components-button.is-busy,.oz-shell .oz-btn-primary.components-button.is-busy{animation:none}}.oz-shell .components-button.is-primary:focus:not(:disabled),.oz-shell .components-button:focus,.oz-shell .components-button:focus:not(:disabled){box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle,.oz-shell .components-form-toggle__input,.oz-shell .components-form-toggle__input:active,.oz-shell .components-form-toggle__input:focus,.oz-shell .components-form-toggle__input:focus-visible{border:none!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__track{background-color:#cdd6db!important;border:1px solid transparent!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle.is-checked .components-form-toggle__track{background-color:#1dd1a1!important}.oz-shell .components-form-toggle__input:focus+.components-form-toggle__track,.oz-shell .components-form-toggle__input:focus-visible+.components-form-toggle__track{box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__thumb{background-color:#fff!important;border:none!important;box-shadow:0 1px 2px rgba(15,32,39,.25)!important}.oz-savebar{align-items:center;background:#fff;border-top:1px solid #e7ecef;bottom:0;box-shadow:0 -6px 18px rgba(15,32,39,.06);display:flex;gap:16px;justify-content:flex-end;right:160px;padding:13px 32px;position:fixed;left:0;z-index:9}.oz-savebar__hint{color:#6b7a86;font-size:13px}.oz-savebar.is-dirty .oz-savebar__hint{color:#0c8f6b;font-weight:650}.folded .oz-savebar{right:36px}.oz-toast{bottom:72px;z-index:99}.oz-job,.oz-toast{position:fixed;left:24px}.oz-job{background:#fff;border:1px solid #e7ecef;border-radius:16px;bottom:84px;box-shadow:0 18px 50px rgba(15,32,39,.22);color:#0f2027;font-size:13px;max-width:calc(100vw - 48px);overflow:hidden;width:420px;z-index:100}.oz-job__head{align-items:center;display:flex;gap:12px;padding:16px 16px 12px}.oz-job__head .components-spinner{margin:0}.oz-job__icon-status{font-size:22px;height:22px;width:22px}.oz-job__icon-status--done{color:#10ac81}.oz-job__icon-status--stopped{color:#6b7a86}.oz-job__icon-status--error{color:#d63638}.oz-job__title{flex:1;min-width:0}.oz-job__title strong{display:block;font-size:15px;font-weight:700;line-height:1.2}.oz-job__title span{color:#6b7a86;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.oz-job__pct{color:#0f2027;font-size:13px;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__icon{background:transparent;border:0;border-radius:6px;color:#6b7a86;cursor:pointer;display:inline-flex;padding:4px}.oz-job__icon:hover{background:#f4f7f8;color:#0f2027}.oz-job__icon .dashicons{font-size:18px;height:18px;width:18px}.oz-job__body{padding:0 16px 16px}.oz-job__row{align-items:baseline;display:flex;gap:12px;justify-content:space-between;margin-bottom:8px}.oz-job__row span{line-height:1.4}.oz-job__row strong{flex:0 0 auto;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__bar{background:#e7ecef;border-radius:999px;height:7px;overflow:hidden}.oz-job__bar div{background:linear-gradient(-90deg,#1dd1a1,#10ac81);border-radius:999px;height:100%;transition:width .35s ease}.oz-job__bar.is-indeterminate div{animation:oz-indeterminate 1.1s ease-in-out infinite;border-radius:999px;width:40%!important}.oz-job.is-min .oz-job__bar{margin:2px 16px 16px}.oz-job.is-min .oz-job__head{padding-bottom:10px}.oz-job.is-done .oz-job__bar div{background:linear-gradient(-90deg,#10ac81,#0c8f6b)}.oz-job.is-stopped .oz-job__bar div{background:#c3ccd2}.oz-job.is-error .oz-job__bar div{background:linear-gradient(-90deg,#f16b6b,#d63638)}.oz-job__step{color:#6b7a86;font-size:12px;margin-top:8px}.oz-job__stop{align-items:center;background:transparent;border:0;color:#d63638;cursor:pointer;display:inline-flex;font-size:13px;font-weight:650;gap:6px;margin-top:14px;padding:4px 0}.oz-job__stop .dashicons{font-size:18px}.oz-job__stop:hover{color:#b32d2e}.oz-job__log{background:#11202b;border-radius:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.7;margin-top:14px;max-height:150px;overflow-y:auto;padding:10px 12px}.oz-logline{color:#cdd8df;display:flex;gap:8px}.oz-logline__tag{color:#5fd0ad;flex:0 0 auto;font-weight:700;min-width:42px}.oz-logline--warn .oz-logline__tag{color:#f0b429}.oz-logline--error .oz-logline__tag{color:#ff6b6b}.oz-logline--done .oz-logline__tag{color:#1dd1a1}.oz-logline__text{word-break:break-word}@media(max-width:1100px){.oz-kpis,.oz-quick{grid-template-columns:1fr}}@media(max-width:1024px){.oz-score{align-items:flex-start;flex-direction:column;text-align:right}.oz-convert{align-items:stretch;flex-direction:column;gap:12px}.oz-convert .oz-btn-ghost.components-button,.oz-convert .oz-btn-primary.components-button{justify-content:center;width:100%}}.oz-mobile-toggle,.oz-nav-overlay{display:none!important}@media(max-width:1024px){.oz-shell{flex-direction:column;margin:8px 8px 110px}.oz-content{border-right:0;min-width:0;width:100%}.oz-sidebar{border-bottom:1px solid #e7ecef;border-left:0;flex:none;max-height:none;overflow:visible;padding:14px 12px;position:static;width:100%}.oz-sidebar__brand{margin-bottom:10px;padding-bottom:12px}.oz-sidebar nav{display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}.oz-navitem{border-bottom:3px solid transparent;border-right:0;border-radius:10px;flex:0 0 auto;padding:8px 12px;width:auto}.oz-navitem.is-active{border-bottom-color:#1dd1a1;border-right-color:transparent}.oz-navitem .oz-navitem__text strong{white-space:nowrap}.oz-navitem .oz-navitem__dot,.oz-navitem small,.oz-sidebar__foot{display:none}.oz-topbar{padding:16px 18px}.oz-topbar h1{font-size:18px}.oz-main{padding:16px 18px}.oz-savebar{right:0;padding:12px 18px}.oz-card__body{padding-right:18px;padding-left:18px}.oz-job{right:8px;left:8px;width:auto}}@media(max-width:560px){.oz-tiles{grid-template-columns:1fr}.oz-hero,.oz-score{padding:18px}.oz-card__head{gap:12px;padding:16px}.oz-card__icon{flex-basis:40px;height:40px;width:40px}.oz-savebar__hint{display:none}}@keyframes oz-indeterminate{0%{transform:translateX(100%)}to{transform:translateX(-350%)}}.oz-muted{color:#6b7a86;font-size:13px}.oz-reqs{list-style:none;margin:0;padding:0}.oz-req{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:grid;font-size:13.5px;gap:6px 10px;grid-template-columns:20px 1fr auto;padding:10px 6px}.oz-req:last-child{border-bottom:0}.oz-req__dot{font-size:18px;height:18px;width:18px}.oz-req__label{font-weight:650}.oz-req__value{color:#6b7a86;font-weight:600;justify-self:end}.oz-req__note{color:#6b7a86;font-size:12px;font-weight:400;grid-column:2/-1;line-height:1.45;margin-top:2px}.oz-req--ok>.oz-req__dot{color:#0c8f6b}.oz-req--warn>.oz-req__dot{color:#e0900f}.oz-req--error>.oz-req__dot{color:#e05242}.oz-req--info>.oz-req__dot,.oz-req--info>.oz-req__value{color:#6b7a86}.oz-req--warn{background:rgba(224,144,15,.06)}.oz-req--error{background:rgba(224,82,66,.06)}.oz-notice{align-items:flex-start;background:rgba(224,82,66,.08);border:1px solid rgba(224,82,66,.28);border-radius:10px;color:#7a2018;display:flex;font-size:13px;gap:10px;line-height:1.5;margin:0 0 14px;padding:12px 14px}.oz-notice .dashicons{color:#e05242;flex:0 0 20px;margin-top:1px}.oz-notice--warn{background:rgba(224,144,15,.09);border-color:rgba(224,144,15,.3);color:#7a5406}.oz-notice--warn .dashicons{color:#e0900f}.oz-notice--ok{background:#eaf8f3;border-color:#b8e6d6;color:#0f5e48}.oz-notice--ok .dashicons{color:#0e9f7e}.oz-recs{list-style:none;margin:0;padding:0}.oz-rec{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:flex;gap:12px;padding:12px 8px}.oz-rec:last-child{border-bottom:0}.oz-rec__dot{flex:0 0 20px;font-size:20px;height:20px;margin-top:1px;width:20px}.oz-rec__text{flex:1;min-width:0}.oz-rec__text strong{display:block;font-size:13.5px;font-weight:650}.oz-rec__text small{color:#6b7a86;display:block;font-size:12px;line-height:1.45;margin-top:2px}.oz-rec__btn.components-button{flex:0 0 auto}.oz-rec--high>.oz-rec__dot{color:#e05242}.oz-rec--medium>.oz-rec__dot{color:#e0900f}.oz-rec--low>.oz-rec__dot{color:#6b7a86}.oz-rec--high{background:rgba(224,82,66,.05)}.oz-rec--medium{background:rgba(224,144,15,.05)}.oz-recs-clear{align-items:center;color:#6b7a86;display:flex;font-size:13px;gap:8px}.oz-recs-clear .dashicons{color:#0c8f6b}.oz-select--compact{border-bottom:0!important;flex:none;margin:0 auto 0 0!important;padding:0!important}.oz-select--compact .oz-select__btn{background:#f7faf9;border-radius:9px;font-size:13px;font-weight:650;gap:10px;height:30px;min-width:132px;padding:0 12px 0 10px;width:auto}.oz-select--compact .oz-select__btn:disabled{cursor:progress;opacity:.6}.oz-select--compact .oz-select__btn .dashicons{font-size:16px;height:16px;width:16px}.oz-select--compact .oz-select__menu{right:auto;min-width:190px;left:0}.oz-select--compact .oz-select__item{font-size:13px;padding:8px 10px}.oz-cachewarn--ok{background:#eaf8f3;border-color:#b8e6d6;color:#0f5e48}.oz-cachewarn__text p+p{margin-top:6px}.oz-cachewarn__link{color:inherit;display:inline-block;font-weight:600;margin-top:8px;text-decoration:underline}.oz-network__sites{align-items:start;margin-top:16px}.oz-network__sites .oz-panel__head h2,.oz-network__sites .oz-panel__head p{overflow-wrap:anywhere}.oz-cachelist__link{color:#1dd1a1;font-weight:600;margin-right:auto;text-decoration:none}.oz-cachelist__link:focus,.oz-cachelist__link:hover{text-decoration:underline}
  • optimazz/trunk/build/index.asset.php

    r3723128 r3743505  
    1 <?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'e40d7f0648a583c6302d');
     1<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '5a501c583130cba224b3');
  • optimazz/trunk/build/index.css

    r3723128 r3743505  
    1 .oz-shell{align-items:flex-start;background:#fff;border:1px solid #e7ecef;border-radius:16px;color:#0f2027;display:flex;font-size:14px;margin:10px 20px 130px 0;min-height:640px;overflow:hidden}.oz-shell *{box-sizing:border-box}.oz-shell .dashicons{font-family:dashicons}.oz-sidebar{align-self:flex-start;background:#fff;display:flex;flex:0 0 264px;flex-direction:column;padding:18px 14px}.oz-sidebar__brand{align-items:center;border-bottom:1px solid #e7ecef;display:flex;gap:12px;margin-bottom:10px;padding:10px 10px 18px}.oz-sidebar__brand strong{display:block;font-size:18px;font-weight:750;letter-spacing:-.01em}.oz-sidebar__brand span{color:#6b7a86;font-size:11px}.oz-sidebar__brand-fallback{align-items:center;display:flex;gap:12px}.oz-sidebar__foot{border-top:1px solid #e7ecef;color:#6b7a86;font-size:11px;margin-top:12px;padding:12px 12px 2px}.oz-logo{background:linear-gradient(135deg,#1dd1a1,#10ac81);border-radius:12px;color:#fff;display:grid;font-size:26px;height:44px;place-items:center;width:44px}.oz-logo-img{display:block;height:30px;max-width:100%;width:auto}.oz-navitem{align-items:center;background:transparent;border:0;border-left:3px solid transparent;border-radius:0 10px 10px 0;color:#0f2027;cursor:pointer;display:flex;gap:12px;padding:11px 12px;text-align:left;transition:all .14s ease;width:100%}.oz-navitem>.oz-ico{color:#6b7a86;flex:0 0 20px;height:20px;transition:color .14s ease,transform .14s ease;width:20px}.oz-navitem:hover>.oz-ico{color:#0f2027}.oz-navitem.is-active>.oz-ico{color:#0c8f6b}.oz-navitem__text{flex:1;min-width:0}.oz-navitem__text strong{display:block;font-size:13.5px;font-weight:650;line-height:1.2}.oz-navitem__text small{color:#6b7a86;font-size:11px}.oz-navitem__dot{background:#1dd1a1;border-radius:50%;height:8px;width:8px}.oz-navitem:hover{background:#f4f7f8}.oz-navitem.is-active{background:rgba(29,209,161,.12);border-left-color:#1dd1a1}.oz-navitem.is-active .oz-navitem__text strong{color:#0c8f6b}.oz-content{align-self:stretch;background:#f4f7f8;border-left:1px solid #e7ecef;display:flex;flex:1;flex-direction:column;min-width:0}.oz-topbar{align-items:center;background:#fff;border-bottom:1px solid #e7ecef;display:flex;gap:14px;padding:20px 28px}.oz-topbar__icon{background:rgba(29,209,161,.14);border-radius:12px;color:#0c8f6b;display:grid;font-size:22px;height:44px;place-items:center;width:44px}.oz-topbar h1{font-size:20px;font-weight:700;margin:0;padding:0}.oz-topbar p{color:#6b7a86;font-size:12.5px;margin:0}.oz-main{display:flex;flex-direction:column;gap:16px;padding:24px 28px}.oz-welcome{align-items:center;background:linear-gradient(125deg,rgba(29,209,161,.9),rgba(16,172,129,.92));border-radius:16px;color:#06382c;display:flex;gap:20px;justify-content:space-between;overflow:hidden;padding:22px 26px}.oz-welcome h2{color:#06382c;font-size:18px;font-weight:700;margin:0 0 6px}.oz-welcome p{color:#0c5b46;font-size:13px;margin:0;max-width:620px}.oz-welcome__icon{flex:0 0 auto;font-size:64px;height:64px;opacity:.5;width:64px}.oz-score{align-items:center;background:linear-gradient(125deg,#1dd1a1,#10ac81 62%,#0c8f6b);border-radius:16px;box-shadow:0 16px 34px -18px rgba(12,143,107,.65);color:#06382c;display:flex;gap:30px;overflow:hidden;padding:28px 32px;position:relative}.oz-score:before{background:radial-gradient(circle,hsla(0,0%,100%,.3),transparent 62%);height:440px;right:-8%;top:-55%;width:440px}.oz-score:after,.oz-score:before{border-radius:50%;content:"";pointer-events:none;position:absolute}.oz-score:after{background:radial-gradient(circle,rgba(0,0,0,.08),transparent 60%);bottom:-70%;height:320px;left:8%;width:320px}.oz-score>*{position:relative;z-index:1}.oz-score__gauge{filter:drop-shadow(0 8px 16px rgba(3,40,30,.28));flex:0 0 auto;line-height:0;position:relative}.oz-score__grade{background:#fff;border-radius:50%;box-shadow:0 4px 10px rgba(3,40,30,.28);color:#0c8f6b;display:grid;font-size:14px;font-weight:800;height:30px;place-items:center;position:absolute;right:2px;top:8px;width:30px}.oz-score__body{flex:1;min-width:0}.oz-score__body h2{color:#06382c;font-size:21px;font-weight:750;letter-spacing:-.02em;margin:0 0 4px}.oz-score__body p{color:rgba(6,56,44,.82);font-size:13px;font-weight:500;margin:0 0 15px}.oz-score__chips{display:flex;flex-wrap:wrap;gap:8px}.oz-chip{align-items:center;background:hsla(0,0%,100%,.18);border:1px solid hsla(0,0%,100%,.28);border-radius:999px;color:rgba(6,56,44,.72);display:inline-flex;font-size:11.5px;font-weight:650;gap:6px;padding:5px 12px 5px 9px;transition:transform .15s ease,box-shadow .15s ease}.oz-chip .dashicons{font-size:15px;height:15px;opacity:.65;width:15px}.oz-chip.is-on{background:#fff;border-color:#fff;box-shadow:0 3px 8px -2px rgba(3,40,30,.22);color:#0c8f6b}.oz-chip.is-on .dashicons{color:#1dd1a1;opacity:1}.oz-chip.is-on:hover{box-shadow:0 6px 14px -4px rgba(3,40,30,.3);transform:translateY(-1px)}.oz-convert{align-items:center;background:#f4f7f8;border-radius:12px;display:flex;gap:16px;justify-content:space-between;margin-top:16px;padding:16px}.oz-convert__info{color:#6b7a86;display:flex;flex-direction:column;font-size:12.5px;gap:3px}.oz-convert__info strong{color:#0f2027;font-size:13px}.oz-progress{background:#dfe6e8;border-radius:999px;height:6px;margin-top:4px;overflow:hidden;width:200px}.oz-progress div{background:linear-gradient(90deg,#1dd1a1,#10ac81);height:100%;transition:width .3s ease}.oz-kpis{display:grid;gap:14px;grid-template-columns:repeat(3,1fr)}.oz-kpi{background:#fff;border:1px solid #e7ecef;border-radius:14px;display:flex;flex-direction:column;padding:16px 18px}.oz-kpi>.dashicons{color:#10ac81;font-size:20px;margin-bottom:8px}.oz-kpi strong{font-size:24px;font-weight:750;min-height:28px}.oz-kpi span{color:#6b7a86;font-size:12px}.oz-quick{display:grid;gap:16px;grid-template-columns:1fr 1fr}.oz-quick__actions{display:flex;flex-wrap:wrap;gap:10px}.oz-cachelist{border-top:1px solid #e7ecef;margin:18px 0 0}.oz-cachelist>li{align-items:center;border-bottom:1px solid #f1f4f5;color:#6b7a86;display:flex;font-size:13px;gap:10px;padding:11px 2px}.oz-cachelist>li:last-child{border-bottom:0}.oz-cachelist>li .dashicons{color:#10ac81;font-size:17px}.oz-cachelist>li strong{color:#0f2027;font-weight:650;margin-left:auto}.oz-cachelist>li .oz-row-label{display:flex;flex-direction:column;line-height:1.3}.oz-cachelist>li .oz-row-label small{color:#6b7a86;font-size:11.5px;opacity:.85}.oz-status{margin:0}.oz-status li{align-items:center;border-bottom:1px solid #e7ecef;display:flex;font-size:13.5px;font-weight:600;gap:10px;padding:9px 6px}.oz-status li:last-child{border-bottom:0}.oz-status li>.oz-ico{color:#6b7a86;flex:0 0 18px;height:18px;width:18px}.oz-status__name{background:transparent;border:0;color:#0f2027;cursor:pointer;flex:1;font:inherit;font-weight:600;padding:4px 0;text-align:left}.oz-status__name:hover{color:#0c8f6b}.oz-status .components-toggle-control{margin-left:auto}.oz-card,.oz-panel{background:#fff;border:1px solid #e7ecef;border-radius:16px;overflow:visible}.oz-card .oz-card__head{border-radius:16px 16px 0 0}.oz-panel__head{padding:18px 22px 0}.oz-panel__head h2{font-size:16px;font-weight:650;margin:0}.oz-panel__head p{color:#6b7a86;font-size:12.5px;margin:4px 0 0}.oz-panel__body{padding:8px 22px 20px}.oz-panel__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-panel__body .components-base-control:last-child{border-bottom:0}.oz-card{transition:box-shadow .18s ease,border-color .18s ease}.oz-card.is-enabled{border-color:rgba(29,209,161,.5);box-shadow:0 0 0 1px rgba(29,209,161,.22)}.oz-card__head{align-items:flex-start;display:flex;gap:15px;padding:20px 22px}.oz-card__icon{background:#f4f7f8;border-radius:12px;color:#6b7a86;display:grid;flex:0 0 46px;font-size:22px;height:46px;place-items:center;width:46px}.oz-card__title{flex:1;min-width:0}.oz-card__title h2{align-items:center;display:flex;font-size:16px;font-weight:650;gap:10px;margin:0 0 3px}.oz-card__title p{color:#6b7a86;font-size:12.5px;margin:0}.oz-card__body{border-top:1px solid #e7ecef;padding:4px 22px 16px}.oz-card__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-card__body .components-base-control:last-child{border-bottom:0}.is-enabled .oz-card__icon{background:linear-gradient(135deg,#1dd1a1,#10ac81);color:#fff}.oz-inline-actions{display:flex;gap:10px;padding-top:16px}.oz-dbgroup{border-top:1px solid #eef0f2;padding:18px 0}.oz-dbgroup:first-of-type{border-top:0;padding-top:4px}.oz-dbgroup__title{color:#0e1116;font-size:14px;font-weight:700;margin:0 0 4px}.oz-dbgroup__desc{color:#6b7280;font-size:13px;line-height:1.5;margin:0 0 14px}.oz-dbgroup .components-toggle-control{margin-bottom:14px}.oz-dbgroup .components-toggle-control:last-child{margin-bottom:0}.oz-dbwarn{align-items:flex-start;background:#fff5f5;border:1px solid #fcd9d9;border-radius:10px;color:#b42318;display:flex;font-size:13px;font-weight:600;gap:8px;line-height:1.5;margin:4px 0;padding:12px 14px}.oz-dbwarn .dashicons{color:#e05242;flex:0 0 auto}.oz-cachewarn{align-items:center;background:#fff8ec;border:1px solid #f4dcae;border-radius:12px;color:#7a4b00;display:flex;gap:13px;margin:0 0 18px;padding:14px 16px}.oz-cachewarn .dashicons{color:#e0900f;flex:0 0 auto;font-size:22px;height:22px;width:22px}.oz-cachewarn__text{flex:1 1 auto;min-width:0}.oz-cachewarn__text strong{color:#6b4200;display:block;font-size:14px;font-weight:700}.oz-cachewarn__text p{color:#8a6410;font-size:12.5px;line-height:1.5;margin:3px 0 0}.oz-cachewarn .oz-btn-primary{flex:0 0 auto}@media(max-width:640px){.oz-cachewarn{flex-wrap:wrap}.oz-cachewarn .oz-btn-primary{justify-content:center;width:100%}}.oz-subhead{color:#8a96a0;font-size:11px;font-weight:700;letter-spacing:.08em;margin:18px 0 2px;padding-top:14px;text-transform:uppercase}.oz-health,.oz-subhead{border-top:1px solid #e7ecf0}.oz-health{margin-top:18px;padding-top:16px}.oz-health__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.oz-health__head strong{font-size:14px}.oz-health__hint{color:#6b7280;font-size:12px;line-height:1.5;margin:4px 0 10px}.oz-health__list{display:flex;flex-direction:column;gap:6px;margin:0}.oz-health__row{align-items:center;display:flex;font-size:12.5px;gap:8px;line-height:1.4;margin:0}.oz-health__row .dashicons{flex:0 0 auto;font-size:17px;height:17px;width:17px}.oz-health__row.is-ok{color:#216e4e}.oz-health__row.is-ok .dashicons{color:#1dd1a1}.oz-health__row.is-warn{color:#8a5a00}.oz-health__row.is-warn .dashicons{color:#e0900f}.oz-health__row.is-bad{color:#b42318}.oz-health__row.is-bad .dashicons{color:#e05242}.oz-health__err{color:#b42318;font-size:12.5px}.oz-health__fix{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-top:14px}.oz-health__fix span{color:#6b7280;flex:1 1 160px;font-size:12px}.oz-health__conflict{display:flex;flex-direction:column;gap:8px;margin-top:12px}.oz-health__conflict-row{align-items:center;background:#fff5f5;border:1px solid #fcd9d9;border-radius:9px;display:flex;flex-wrap:wrap;gap:10px;padding:10px 12px}.oz-health__conflict-row .dashicons{color:#e05242;flex:0 0 auto}.oz-health__conflict-row span:not(.dashicons){color:#8a2f22;flex:1 1 200px;font-size:12.5px}.oz-health__conflict-row .oz-btn-danger.components-button{flex:0 0 auto;height:32px}.oz-health__manual{background:#fff8ec;border:1px solid #f4dcae;border-radius:9px;margin-top:12px;padding:10px 12px}.oz-health__manual p{color:#7a4b00;font-size:12px;margin:0 0 6px}.oz-health__manual code{background:#2b2f36;border-radius:6px;color:#e7f0ec;display:inline-block;font-size:12.5px;padding:6px 10px}.oz-btn-ghost.components-button{border:1px solid #d5dbe0;border-radius:8px;color:#384049;font-size:12.5px;height:30px}.oz-btn-ghost.components-button:hover:not(:disabled){border-color:#1dd1a1;color:#0b7a5c}.oz-pill{align-items:center;border-radius:999px;display:inline-flex;font-size:11px;font-weight:700;gap:5px;padding:3px 9px}.oz-pill i{background:currentColor;border-radius:50%;height:6px;width:6px}.oz-pill.is-on{background:rgba(29,209,161,.16);color:#0c8f6b}.oz-pill.is-off{background:#eef1f2;color:#6b7a86}.oz-shell .components-range-control__thumb,.oz-shell .components-range-control__track{background-color:#1dd1a1!important}.oz-fieldgroup{background:rgba(29,209,161,.035);border:1px solid #e7ecef;border-radius:12px;margin-top:18px;padding:16px 16px 4px}.oz-fieldgroup__title{color:#0e1116;font-size:13px;font-weight:700;margin:0 0 4px}.oz-fieldgroup__desc{color:#6b7280;font-size:12.5px;line-height:1.5;margin:0 0 14px}.oz-fieldgroup .components-base-control{margin-bottom:14px}.oz-shell .components-form-token-field__input-container{background:#fff;border:1px solid #e7ecef;border-radius:10px;min-height:44px;padding:5px 8px;transition:border-color .15s ease,box-shadow .15s ease}.oz-shell .components-form-token-field__input-container.is-active,.oz-shell .components-form-token-field__input-container:focus-within{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-shell .components-form-token-field__token{border-radius:8px;font-size:12.5px;margin:3px;overflow:hidden}.oz-shell .components-form-token-field__token-text{background:rgba(29,209,161,.14);border-radius:8px 0 0 8px;color:#0c8f6b;font-weight:650;line-height:1.7;padding:2px 4px 2px 11px}.oz-shell .components-form-token-field__remove-token{background:rgba(29,209,161,.14);border-radius:0 8px 8px 0;color:#0c8f6b;min-width:24px;padding:0 7px;transition:background .15s ease,color .15s ease}.oz-shell .components-form-token-field__remove-token svg{fill:currentColor}.oz-shell .components-form-token-field__remove-token:hover{background:#10ac81;color:#fff}.oz-shell .components-form-token-field__input{font-size:13.5px;margin:4px;min-height:28px}.oz-shell .components-input-control__backdrop{display:none!important}.oz-shell .components-input-control__input,.oz-shell .components-select-control__input,.oz-shell .components-text-control__input{background:#fff!important;border:1px solid #e7ecef!important;border-radius:10px!important;box-shadow:none!important;color:#0f2027;font-size:13.5px;min-height:42px;padding:0 14px}.oz-shell .components-input-control__input:hover,.oz-shell .components-select-control__input:hover,.oz-shell .components-text-control__input:hover{border-color:#bcc6cc!important}.oz-shell .components-input-control__input:focus,.oz-shell .components-select-control__input:focus,.oz-shell .components-text-control__input:focus{border-color:#1dd1a1!important;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-shell select.components-select-control__input{cursor:pointer}.oz-pillgroup .components-base-control__label{display:block;font-weight:600;margin-bottom:8px}.oz-pillgroup__row{background:#f0f2f4;border-radius:12px;display:inline-flex;flex-wrap:wrap;gap:6px;padding:4px}.oz-pillgroup .components-base-control__help{display:block;margin-top:8px}.oz-pillbtn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:9px;color:#4b5563;cursor:pointer;font-size:13px;font-weight:600;min-width:44px;padding:7px 16px;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.oz-pillbtn:hover{color:#0e1116}.oz-pillbtn.is-on{background:#fff;box-shadow:0 1px 3px rgba(4,56,44,.14);color:#0c8f6b}.oz-pillbtn:focus-visible{outline:2px solid #1dd1a1;outline-offset:1px}.oz-select{position:relative}.oz-select .components-base-control__label{display:block;margin-bottom:8px}.oz-select__btn{align-items:center;background:#fff;border:1px solid #e7ecef;border-radius:10px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;height:42px;justify-content:space-between;padding:0 14px;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.oz-select__btn .dashicons{color:#0c8f6b;transition:transform .18s ease}.oz-select__btn:hover{border-color:#bcc6cc}.oz-select__btn:focus,.oz-select__btn:focus-visible{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-select__btn[aria-expanded=true]{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-select__btn[aria-expanded=true] .dashicons{transform:rotate(180deg)}.oz-select__menu{background:#fff;border:1px solid #e7ecef;border-radius:12px;box-shadow:0 14px 34px rgba(15,32,39,.18);left:0;list-style:none;margin:0;padding:6px;position:absolute;right:0;top:calc(100% + 6px);z-index:30}.oz-select__item{align-items:center;border-radius:8px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;justify-content:space-between;padding:9px 12px}.oz-select__item .dashicons{color:#10ac81;font-size:18px}.oz-select__item.is-active,.oz-select__item:hover{background:rgba(29,209,161,.12);color:#0c8f6b}.oz-select__item.is-selected{color:#0c8f6b;font-weight:650}.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{border-radius:10px;font-weight:650;height:38px;transition:transform .16s cubic-bezier(.2,.7,.3,1),box-shadow .18s ease,background .16s ease,border-color .16s ease,color .16s ease}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:transform .22s cubic-bezier(.2,.7,.3,1)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:translateY(-1px)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:scale(1.14)}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled){transform:translateY(0) scale(.975);transition-duration:.05s}.oz-shell .oz-btn-danger.components-button:focus-visible,.oz-shell .oz-btn-ghost.components-button:focus-visible,.oz-shell .oz-btn-primary.components-button:focus-visible{outline:2px solid #1dd1a1;outline-offset:2px}.oz-shell .oz-btn-danger.components-button:disabled,.oz-shell .oz-btn-ghost.components-button:disabled,.oz-shell .oz-btn-primary.components-button:disabled{opacity:.5;transform:none}.oz-shell .oz-btn-primary.components-button{background:linear-gradient(135deg,#1dd1a1,#10ac81);border:0;box-shadow:0 1px 2px rgba(20,60,48,.12);color:#fff;padding:0 18px}.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){background:linear-gradient(135deg,#10ac81,#0c8f6b);box-shadow:0 8px 20px -7px rgba(29,209,161,.65);color:#fff}.oz-shell .oz-btn-primary.components-button.is-busy{animation:oz-btn-sheen 1.4s linear infinite;background-size:200% 100%}.oz-shell .oz-btn-ghost.components-button{background:#fff;border:1px solid rgba(29,209,161,.5);box-shadow:none;color:#0c8f6b}.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled){background:rgba(29,209,161,.08);border-color:#1dd1a1;box-shadow:0 6px 16px -8px rgba(29,209,161,.5);color:#0c8f6b}.oz-shell .oz-btn-danger.components-button{background:#fff;border:1px solid rgba(214,54,56,.5);color:#d63638}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled){background:rgba(214,54,56,.08);border-color:#d63638;box-shadow:0 6px 16px -8px rgba(214,54,56,.5);color:#b32d2e}.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg{transform:scale(1.1) rotate(90deg)}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg{transform:none}}@keyframes oz-btn-sheen{0%{background-image:linear-gradient(135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:200% 0}to{background-image:linear-gradient(135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:-200% 0}}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{transition:background .16s ease,color .16s ease,border-color .16s ease}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:none}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:none}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:none}.oz-shell .oz-btn-danger.components-button.is-busy,.oz-shell .oz-btn-ghost.components-button.is-busy,.oz-shell .oz-btn-primary.components-button.is-busy{animation:none}}.oz-shell .components-button.is-primary:focus:not(:disabled),.oz-shell .components-button:focus,.oz-shell .components-button:focus:not(:disabled){box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle,.oz-shell .components-form-toggle__input,.oz-shell .components-form-toggle__input:active,.oz-shell .components-form-toggle__input:focus,.oz-shell .components-form-toggle__input:focus-visible{border:none!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__track{background-color:#cdd6db!important;border:1px solid transparent!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle.is-checked .components-form-toggle__track{background-color:#1dd1a1!important}.oz-shell .components-form-toggle__input:focus+.components-form-toggle__track,.oz-shell .components-form-toggle__input:focus-visible+.components-form-toggle__track{box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__thumb{background-color:#fff!important;border:none!important;box-shadow:0 1px 2px rgba(15,32,39,.25)!important}.oz-savebar{align-items:center;background:#fff;border-top:1px solid #e7ecef;bottom:0;box-shadow:0 -6px 18px rgba(15,32,39,.06);display:flex;gap:16px;justify-content:flex-end;left:160px;padding:13px 32px;position:fixed;right:0;z-index:9}.oz-savebar__hint{color:#6b7a86;font-size:13px}.oz-savebar.is-dirty .oz-savebar__hint{color:#0c8f6b;font-weight:650}.folded .oz-savebar{left:36px}.oz-toast{bottom:72px;z-index:99}.oz-job,.oz-toast{position:fixed;right:24px}.oz-job{background:#fff;border:1px solid #e7ecef;border-radius:16px;bottom:84px;box-shadow:0 18px 50px rgba(15,32,39,.22);color:#0f2027;font-size:13px;max-width:calc(100vw - 48px);overflow:hidden;width:420px;z-index:100}.oz-job__head{align-items:center;display:flex;gap:12px;padding:16px 16px 12px}.oz-job__head .components-spinner{margin:0}.oz-job__icon-status{font-size:22px;height:22px;width:22px}.oz-job__icon-status--done{color:#10ac81}.oz-job__icon-status--stopped{color:#6b7a86}.oz-job__icon-status--error{color:#d63638}.oz-job__title{flex:1;min-width:0}.oz-job__title strong{display:block;font-size:15px;font-weight:700;line-height:1.2}.oz-job__title span{color:#6b7a86;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.oz-job__pct{color:#0f2027;font-size:13px;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__icon{background:transparent;border:0;border-radius:6px;color:#6b7a86;cursor:pointer;display:inline-flex;padding:4px}.oz-job__icon:hover{background:#f4f7f8;color:#0f2027}.oz-job__icon .dashicons{font-size:18px;height:18px;width:18px}.oz-job__body{padding:0 16px 16px}.oz-job__row{align-items:baseline;display:flex;gap:12px;justify-content:space-between;margin-bottom:8px}.oz-job__row span{line-height:1.4}.oz-job__row strong{flex:0 0 auto;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__bar{background:#e7ecef;border-radius:999px;height:7px;overflow:hidden}.oz-job__bar div{background:linear-gradient(90deg,#1dd1a1,#10ac81);border-radius:999px;height:100%;transition:width .35s ease}.oz-job__bar.is-indeterminate div{animation:oz-indeterminate 1.1s ease-in-out infinite;border-radius:999px;width:40%!important}.oz-job.is-min .oz-job__bar{margin:2px 16px 16px}.oz-job.is-min .oz-job__head{padding-bottom:10px}.oz-job.is-done .oz-job__bar div{background:linear-gradient(90deg,#10ac81,#0c8f6b)}.oz-job.is-stopped .oz-job__bar div{background:#c3ccd2}.oz-job.is-error .oz-job__bar div{background:linear-gradient(90deg,#f16b6b,#d63638)}.oz-job__step{color:#6b7a86;font-size:12px;margin-top:8px}.oz-job__stop{align-items:center;background:transparent;border:0;color:#d63638;cursor:pointer;display:inline-flex;font-size:13px;font-weight:650;gap:6px;margin-top:14px;padding:4px 0}.oz-job__stop .dashicons{font-size:18px}.oz-job__stop:hover{color:#b32d2e}.oz-job__log{background:#11202b;border-radius:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.7;margin-top:14px;max-height:150px;overflow-y:auto;padding:10px 12px}.oz-logline{color:#cdd8df;display:flex;gap:8px}.oz-logline__tag{color:#5fd0ad;flex:0 0 auto;font-weight:700;min-width:42px}.oz-logline--warn .oz-logline__tag{color:#f0b429}.oz-logline--error .oz-logline__tag{color:#ff6b6b}.oz-logline--done .oz-logline__tag{color:#1dd1a1}.oz-logline__text{word-break:break-word}@media(max-width:1100px){.oz-kpis,.oz-quick{grid-template-columns:1fr}}@media(max-width:1024px){.oz-score{align-items:flex-start;flex-direction:column;text-align:left}.oz-convert{align-items:stretch;flex-direction:column;gap:12px}.oz-convert .oz-btn-ghost.components-button,.oz-convert .oz-btn-primary.components-button{justify-content:center;width:100%}}.oz-mobile-toggle,.oz-nav-overlay{display:none!important}@media(max-width:1024px){.oz-shell{flex-direction:column;margin:8px 8px 110px}.oz-content{border-left:0;min-width:0;width:100%}.oz-sidebar{border-bottom:1px solid #e7ecef;border-right:0;flex:none;max-height:none;overflow:visible;padding:14px 12px;position:static;width:100%}.oz-sidebar__brand{margin-bottom:10px;padding-bottom:12px}.oz-sidebar nav{display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}.oz-navitem{border-bottom:3px solid transparent;border-left:0;border-radius:10px;flex:0 0 auto;padding:8px 12px;width:auto}.oz-navitem.is-active{border-bottom-color:#1dd1a1;border-left-color:transparent}.oz-navitem .oz-navitem__text strong{white-space:nowrap}.oz-navitem .oz-navitem__dot,.oz-navitem small,.oz-sidebar__foot{display:none}.oz-topbar{padding:16px 18px}.oz-topbar h1{font-size:18px}.oz-main{padding:16px 18px}.oz-savebar{left:0;padding:12px 18px}.oz-card__body{padding-left:18px;padding-right:18px}.oz-job{left:8px;right:8px;width:auto}}@media(max-width:560px){.oz-tiles{grid-template-columns:1fr}.oz-hero,.oz-score{padding:18px}.oz-card__head{gap:12px;padding:16px}.oz-card__icon{flex-basis:40px;height:40px;width:40px}.oz-savebar__hint{display:none}}@keyframes oz-indeterminate{0%{transform:translateX(-100%)}to{transform:translateX(350%)}}.oz-muted{color:#6b7a86;font-size:13px}.oz-reqs{list-style:none;margin:0;padding:0}.oz-req{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:grid;font-size:13.5px;gap:6px 10px;grid-template-columns:20px 1fr auto;padding:10px 6px}.oz-req:last-child{border-bottom:0}.oz-req__dot{font-size:18px;height:18px;width:18px}.oz-req__label{font-weight:650}.oz-req__value{color:#6b7a86;font-weight:600;justify-self:end}.oz-req__note{color:#6b7a86;font-size:12px;font-weight:400;grid-column:2/-1;line-height:1.45;margin-top:2px}.oz-req--ok>.oz-req__dot{color:#0c8f6b}.oz-req--warn>.oz-req__dot{color:#e0900f}.oz-req--error>.oz-req__dot{color:#e05242}.oz-req--info>.oz-req__dot,.oz-req--info>.oz-req__value{color:#6b7a86}.oz-req--warn{background:rgba(224,144,15,.06)}.oz-req--error{background:rgba(224,82,66,.06)}.oz-notice{align-items:flex-start;background:rgba(224,82,66,.08);border:1px solid rgba(224,82,66,.28);border-radius:10px;color:#7a2018;display:flex;font-size:13px;gap:10px;line-height:1.5;margin:0 0 14px;padding:12px 14px}.oz-notice .dashicons{color:#e05242;flex:0 0 20px;margin-top:1px}.oz-notice--warn{background:rgba(224,144,15,.09);border-color:rgba(224,144,15,.3);color:#7a5406}.oz-notice--warn .dashicons{color:#e0900f}.oz-recs{list-style:none;margin:0;padding:0}.oz-rec{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:flex;gap:12px;padding:12px 8px}.oz-rec:last-child{border-bottom:0}.oz-rec__dot{flex:0 0 20px;font-size:20px;height:20px;margin-top:1px;width:20px}.oz-rec__text{flex:1;min-width:0}.oz-rec__text strong{display:block;font-size:13.5px;font-weight:650}.oz-rec__text small{color:#6b7a86;display:block;font-size:12px;line-height:1.45;margin-top:2px}.oz-rec__btn.components-button{flex:0 0 auto}.oz-rec--high>.oz-rec__dot{color:#e05242}.oz-rec--medium>.oz-rec__dot{color:#e0900f}.oz-rec--low>.oz-rec__dot{color:#6b7a86}.oz-rec--high{background:rgba(224,82,66,.05)}.oz-rec--medium{background:rgba(224,144,15,.05)}.oz-recs-clear{align-items:center;color:#6b7a86;display:flex;font-size:13px;gap:8px}.oz-recs-clear .dashicons{color:#0c8f6b}.oz-select--compact{border-bottom:0!important;flex:none;margin:0 0 0 auto!important;padding:0!important}.oz-select--compact .oz-select__btn{background:#f7faf9;border-radius:9px;font-size:13px;font-weight:650;gap:10px;height:30px;min-width:132px;padding:0 10px 0 12px;width:auto}.oz-select--compact .oz-select__btn:disabled{cursor:progress;opacity:.6}.oz-select--compact .oz-select__btn .dashicons{font-size:16px;height:16px;width:16px}.oz-select--compact .oz-select__menu{left:auto;min-width:190px;right:0}.oz-select--compact .oz-select__item{font-size:13px;padding:8px 10px}
     1.oz-shell{align-items:flex-start;background:#fff;border:1px solid #e7ecef;border-radius:16px;color:#0f2027;display:flex;font-size:14px;margin:10px 20px 130px 0;min-height:640px;overflow:hidden}.oz-shell *{box-sizing:border-box}.oz-shell .dashicons{font-family:dashicons}.oz-sidebar{align-self:flex-start;background:#fff;display:flex;flex:0 0 264px;flex-direction:column;padding:18px 14px}.oz-sidebar__brand{align-items:center;border-bottom:1px solid #e7ecef;display:flex;gap:12px;margin-bottom:10px;padding:10px 10px 18px}.oz-sidebar__brand strong{display:block;font-size:18px;font-weight:750;letter-spacing:-.01em}.oz-sidebar__brand span{color:#6b7a86;font-size:11px}.oz-sidebar__brand-fallback{align-items:center;display:flex;gap:12px}.oz-sidebar__foot{border-top:1px solid #e7ecef;color:#6b7a86;font-size:11px;margin-top:12px;padding:12px 12px 2px}.oz-logo{background:linear-gradient(135deg,#1dd1a1,#10ac81);border-radius:12px;color:#fff;display:grid;font-size:26px;height:44px;place-items:center;width:44px}.oz-logo-img{display:block;height:30px;max-width:100%;width:auto}.oz-navitem{align-items:center;background:transparent;border:0;border-left:3px solid transparent;border-radius:0 10px 10px 0;color:#0f2027;cursor:pointer;display:flex;gap:12px;padding:11px 12px;text-align:left;transition:all .14s ease;width:100%}.oz-navitem>.oz-ico{color:#6b7a86;flex:0 0 20px;height:20px;transition:color .14s ease,transform .14s ease;width:20px}.oz-navitem:hover>.oz-ico{color:#0f2027}.oz-navitem.is-active>.oz-ico{color:#0c8f6b}.oz-navitem__text{flex:1;min-width:0}.oz-navitem__text strong{display:block;font-size:13.5px;font-weight:650;line-height:1.2}.oz-navitem__text small{color:#6b7a86;font-size:11px}.oz-navitem__dot{background:#1dd1a1;border-radius:50%;height:8px;width:8px}.oz-navitem:hover{background:#f4f7f8}.oz-navitem.is-active{background:rgba(29,209,161,.12);border-left-color:#1dd1a1}.oz-navitem.is-active .oz-navitem__text strong{color:#0c8f6b}.oz-content{align-self:stretch;background:#f4f7f8;border-left:1px solid #e7ecef;display:flex;flex:1;flex-direction:column;min-width:0}.oz-topbar{align-items:center;background:#fff;border-bottom:1px solid #e7ecef;display:flex;gap:14px;padding:20px 28px}.oz-topbar__icon{background:rgba(29,209,161,.14);border-radius:12px;color:#0c8f6b;display:grid;font-size:22px;height:44px;place-items:center;width:44px}.oz-topbar h1{font-size:20px;font-weight:700;margin:0;padding:0}.oz-topbar p{color:#6b7a86;font-size:12.5px;margin:0}.oz-main{display:flex;flex-direction:column;gap:16px;padding:24px 28px}.oz-welcome{align-items:center;background:linear-gradient(125deg,rgba(29,209,161,.9),rgba(16,172,129,.92));border-radius:16px;color:#06382c;display:flex;gap:20px;justify-content:space-between;overflow:hidden;padding:22px 26px}.oz-welcome h2{color:#06382c;font-size:18px;font-weight:700;margin:0 0 6px}.oz-welcome p{color:#0c5b46;font-size:13px;margin:0;max-width:620px}.oz-welcome__icon{flex:0 0 auto;font-size:64px;height:64px;opacity:.5;width:64px}.oz-score{align-items:center;background:linear-gradient(125deg,#1dd1a1,#10ac81 62%,#0c8f6b);border-radius:16px;box-shadow:0 16px 34px -18px rgba(12,143,107,.65);color:#06382c;display:flex;gap:30px;overflow:hidden;padding:28px 32px;position:relative}.oz-score:before{background:radial-gradient(circle,hsla(0,0%,100%,.3),transparent 62%);height:440px;right:-8%;top:-55%;width:440px}.oz-score:after,.oz-score:before{border-radius:50%;content:"";pointer-events:none;position:absolute}.oz-score:after{background:radial-gradient(circle,rgba(0,0,0,.08),transparent 60%);bottom:-70%;height:320px;left:8%;width:320px}.oz-score>*{position:relative;z-index:1}.oz-score__gauge{filter:drop-shadow(0 8px 16px rgba(3,40,30,.28));flex:0 0 auto;line-height:0;position:relative}.oz-score__grade{background:#fff;border-radius:50%;box-shadow:0 4px 10px rgba(3,40,30,.28);color:#0c8f6b;display:grid;font-size:14px;font-weight:800;height:30px;place-items:center;position:absolute;right:2px;top:8px;width:30px}.oz-score__body{flex:1;min-width:0}.oz-score__body h2{color:#06382c;font-size:21px;font-weight:750;letter-spacing:-.02em;margin:0 0 4px}.oz-score__body p{color:rgba(6,56,44,.82);font-size:13px;font-weight:500;margin:0 0 15px}.oz-score__chips{display:flex;flex-wrap:wrap;gap:8px}.oz-chip{align-items:center;background:hsla(0,0%,100%,.18);border:1px solid hsla(0,0%,100%,.28);border-radius:999px;color:rgba(6,56,44,.72);display:inline-flex;font-size:11.5px;font-weight:650;gap:6px;padding:5px 12px 5px 9px;transition:transform .15s ease,box-shadow .15s ease}.oz-chip .dashicons{font-size:15px;height:15px;opacity:.65;width:15px}.oz-chip.is-on{background:#fff;border-color:#fff;box-shadow:0 3px 8px -2px rgba(3,40,30,.22);color:#0c8f6b}.oz-chip.is-on .dashicons{color:#1dd1a1;opacity:1}.oz-chip.is-on:hover{box-shadow:0 6px 14px -4px rgba(3,40,30,.3);transform:translateY(-1px)}.oz-convert{align-items:center;background:#f4f7f8;border-radius:12px;display:flex;gap:16px;justify-content:space-between;margin-top:16px;padding:16px}.oz-convert__info{color:#6b7a86;display:flex;flex-direction:column;font-size:12.5px;gap:3px}.oz-convert__info strong{color:#0f2027;font-size:13px}.oz-progress{background:#dfe6e8;border-radius:999px;height:6px;margin-top:4px;overflow:hidden;width:200px}.oz-progress div{background:linear-gradient(90deg,#1dd1a1,#10ac81);height:100%;transition:width .3s ease}.oz-kpis{display:grid;gap:14px;grid-template-columns:repeat(3,1fr)}.oz-kpis--single{grid-template-columns:1fr}.oz-kpi{background:#fff;border:1px solid #e7ecef;border-radius:14px;display:flex;flex-direction:column;padding:16px 18px}.oz-kpi>.dashicons{color:#10ac81;font-size:20px;margin-bottom:8px}.oz-kpi strong{font-size:24px;font-weight:750;min-height:28px}.oz-kpi span{color:#6b7a86;font-size:12px}.oz-quick{display:grid;gap:16px;grid-template-columns:1fr 1fr}.oz-quick__actions{display:flex;flex-wrap:wrap;gap:10px}.oz-cachelist{border-top:1px solid #e7ecef;margin:18px 0 0}.oz-cachelist>li{align-items:center;border-bottom:1px solid #f1f4f5;color:#6b7a86;display:flex;font-size:13px;gap:10px;padding:11px 2px}.oz-cachelist>li:last-child{border-bottom:0}.oz-cachelist>li .dashicons{color:#10ac81;font-size:17px}.oz-cachelist>li strong{color:#0f2027;font-weight:650;margin-left:auto}.oz-cachelist>li .oz-row-label{display:flex;flex-direction:column;line-height:1.3}.oz-cachelist>li .oz-row-label small{color:#6b7a86;font-size:11.5px;opacity:.85}.oz-status{margin:0}.oz-status li{align-items:center;border-bottom:1px solid #e7ecef;display:flex;font-size:13.5px;font-weight:600;gap:10px;padding:9px 6px}.oz-status li:last-child{border-bottom:0}.oz-status li>.oz-ico{color:#6b7a86;flex:0 0 18px;height:18px;width:18px}.oz-status__name{background:transparent;border:0;color:#0f2027;cursor:pointer;flex:1;font:inherit;font-weight:600;padding:4px 0;text-align:left}.oz-status__name:hover{color:#0c8f6b}.oz-status .components-toggle-control{margin-left:auto}.oz-card,.oz-panel{background:#fff;border:1px solid #e7ecef;border-radius:16px;overflow:visible}.oz-card .oz-card__head{border-radius:16px 16px 0 0}.oz-panel__head{padding:18px 22px 0}.oz-panel__head h2{font-size:16px;font-weight:650;margin:0}.oz-panel__head p{color:#6b7a86;font-size:12.5px;margin:4px 0 0}.oz-panel__body{padding:8px 22px 20px}.oz-panel__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-panel__body .components-base-control:last-child{border-bottom:0}.oz-card{transition:box-shadow .18s ease,border-color .18s ease}.oz-card.is-enabled{border-color:rgba(29,209,161,.5);box-shadow:0 0 0 1px rgba(29,209,161,.22)}.oz-card__head{align-items:flex-start;display:flex;gap:15px;padding:20px 22px}.oz-card__icon{background:#f4f7f8;border-radius:12px;color:#6b7a86;display:grid;flex:0 0 46px;font-size:22px;height:46px;place-items:center;width:46px}.oz-card__title{flex:1;min-width:0}.oz-card__title h2{align-items:center;display:flex;font-size:16px;font-weight:650;gap:10px;margin:0 0 3px}.oz-card__title p{color:#6b7a86;font-size:12.5px;margin:0}.oz-card__body{border-top:1px solid #e7ecef;padding:4px 22px 16px}.oz-card__body .components-base-control{border-bottom:1px solid #f1f4f5;padding:14px 0}.oz-card__body .components-base-control:last-child{border-bottom:0}.is-enabled .oz-card__icon{background:linear-gradient(135deg,#1dd1a1,#10ac81);color:#fff}.oz-inline-actions{display:flex;gap:10px;padding-top:16px}.oz-dbgroup{border-top:1px solid #eef0f2;padding:18px 0}.oz-dbgroup:first-of-type{border-top:0;padding-top:4px}.oz-dbgroup__title{color:#0e1116;font-size:14px;font-weight:700;margin:0 0 4px}.oz-dbgroup__desc{color:#6b7280;font-size:13px;line-height:1.5;margin:0 0 14px}.oz-dbgroup .components-toggle-control{margin-bottom:14px}.oz-dbgroup .components-toggle-control:last-child{margin-bottom:0}.oz-dbwarn{align-items:flex-start;background:#fff5f5;border:1px solid #fcd9d9;border-radius:10px;color:#b42318;display:flex;font-size:13px;font-weight:600;gap:8px;line-height:1.5;margin:4px 0;padding:12px 14px}.oz-dbwarn .dashicons{color:#e05242;flex:0 0 auto}.oz-cachewarn{align-items:center;background:#fff8ec;border:1px solid #f4dcae;border-radius:12px;color:#7a4b00;display:flex;gap:13px;margin:0 0 18px;padding:14px 16px}.oz-cachewarn .dashicons{color:#e0900f;flex:0 0 auto;font-size:22px;height:22px;width:22px}.oz-cachewarn__text{flex:1 1 auto;min-width:0}.oz-cachewarn__text strong{color:#6b4200;display:block;font-size:14px;font-weight:700}.oz-cachewarn__text p{color:#8a6410;font-size:12.5px;line-height:1.5;margin:3px 0 0}.oz-cachewarn .oz-btn-primary{flex:0 0 auto}@media(max-width:640px){.oz-cachewarn{flex-wrap:wrap}.oz-cachewarn .oz-btn-primary{justify-content:center;width:100%}}.oz-subhead{color:#8a96a0;font-size:11px;font-weight:700;letter-spacing:.08em;margin:18px 0 2px;padding-top:14px;text-transform:uppercase}.oz-health,.oz-subhead{border-top:1px solid #e7ecf0}.oz-health{margin-top:18px;padding-top:16px}.oz-health__head{align-items:center;display:flex;gap:12px;justify-content:space-between}.oz-health__head strong{font-size:14px}.oz-health__hint{color:#6b7280;font-size:12px;line-height:1.5;margin:4px 0 10px}.oz-health__list{display:flex;flex-direction:column;gap:6px;margin:0}.oz-health__row{align-items:center;display:flex;font-size:12.5px;gap:8px;line-height:1.4;margin:0}.oz-health__row .dashicons{flex:0 0 auto;font-size:17px;height:17px;width:17px}.oz-health__row.is-ok{color:#216e4e}.oz-health__row.is-ok .dashicons{color:#1dd1a1}.oz-health__row.is-warn{color:#8a5a00}.oz-health__row.is-warn .dashicons{color:#e0900f}.oz-health__row.is-bad{color:#b42318}.oz-health__row.is-bad .dashicons{color:#e05242}.oz-health__err{color:#b42318;font-size:12.5px}.oz-health__fix{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-top:14px}.oz-health__fix span{color:#6b7280;flex:1 1 160px;font-size:12px}.oz-health__conflict{display:flex;flex-direction:column;gap:8px;margin-top:12px}.oz-health__conflict-row{align-items:center;background:#fff5f5;border:1px solid #fcd9d9;border-radius:9px;display:flex;flex-wrap:wrap;gap:10px;padding:10px 12px}.oz-health__conflict-row .dashicons{color:#e05242;flex:0 0 auto}.oz-health__conflict-row span:not(.dashicons){color:#8a2f22;flex:1 1 200px;font-size:12.5px}.oz-health__conflict-row .oz-btn-danger.components-button{flex:0 0 auto;height:32px}.oz-health__manual{background:#fff8ec;border:1px solid #f4dcae;border-radius:9px;margin-top:12px;padding:10px 12px}.oz-health__manual p{color:#7a4b00;font-size:12px;margin:0 0 6px}.oz-health__manual code{background:#2b2f36;border-radius:6px;color:#e7f0ec;display:inline-block;font-size:12.5px;padding:6px 10px}.oz-btn-ghost.components-button{border:1px solid #d5dbe0;border-radius:8px;color:#384049;font-size:12.5px;height:30px}.oz-btn-ghost.components-button:hover:not(:disabled){border-color:#1dd1a1;color:#0b7a5c}.oz-pill{align-items:center;border-radius:999px;display:inline-flex;font-size:11px;font-weight:700;gap:5px;padding:3px 9px}.oz-pill i{background:currentColor;border-radius:50%;height:6px;width:6px}.oz-pill.is-on{background:rgba(29,209,161,.16);color:#0c8f6b}.oz-pill.is-off{background:#eef1f2;color:#6b7a86}.oz-shell .components-range-control__thumb,.oz-shell .components-range-control__track{background-color:#1dd1a1!important}.oz-fieldgroup{background:rgba(29,209,161,.035);border:1px solid #e7ecef;border-radius:12px;margin-top:18px;padding:16px 16px 4px}.oz-fieldgroup__title{color:#0e1116;font-size:13px;font-weight:700;margin:0 0 4px}.oz-fieldgroup__desc{color:#6b7280;font-size:12.5px;line-height:1.5;margin:0 0 14px}.oz-fieldgroup .components-base-control{margin-bottom:14px}.oz-shell .components-form-token-field__input-container{background:#fff;border:1px solid #e7ecef;border-radius:10px;min-height:44px;padding:5px 8px;transition:border-color .15s ease,box-shadow .15s ease}.oz-shell .components-form-token-field__input-container.is-active,.oz-shell .components-form-token-field__input-container:focus-within{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-shell .components-form-token-field__token{border-radius:8px;font-size:12.5px;margin:3px;overflow:hidden}.oz-shell .components-form-token-field__token-text{background:rgba(29,209,161,.14);border-radius:8px 0 0 8px;color:#0c8f6b;font-weight:650;line-height:1.7;padding:2px 4px 2px 11px}.oz-shell .components-form-token-field__remove-token{background:rgba(29,209,161,.14);border-radius:0 8px 8px 0;color:#0c8f6b;min-width:24px;padding:0 7px;transition:background .15s ease,color .15s ease}.oz-shell .components-form-token-field__remove-token svg{fill:currentColor}.oz-shell .components-form-token-field__remove-token:hover{background:#10ac81;color:#fff}.oz-shell .components-form-token-field__input{font-size:13.5px;margin:4px;min-height:28px}.oz-shell .components-input-control__backdrop{display:none!important}.oz-shell .components-input-control__input,.oz-shell .components-select-control__input,.oz-shell .components-text-control__input{background:#fff!important;border:1px solid #e7ecef!important;border-radius:10px!important;box-shadow:none!important;color:#0f2027;font-size:13.5px;min-height:42px;padding:0 14px}.oz-shell .components-input-control__input:hover,.oz-shell .components-select-control__input:hover,.oz-shell .components-text-control__input:hover{border-color:#bcc6cc!important}.oz-shell .components-input-control__input:focus,.oz-shell .components-select-control__input:focus,.oz-shell .components-text-control__input:focus{border-color:#1dd1a1!important;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-shell select.components-select-control__input{cursor:pointer}.oz-pillgroup .components-base-control__label{display:block;font-weight:600;margin-bottom:8px}.oz-pillgroup__row{background:#f0f2f4;border-radius:12px;display:inline-flex;flex-wrap:wrap;gap:6px;padding:4px}.oz-pillgroup .components-base-control__help{display:block;margin-top:8px}.oz-pillbtn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:9px;color:#4b5563;cursor:pointer;font-size:13px;font-weight:600;min-width:44px;padding:7px 16px;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.oz-pillbtn:hover{color:#0e1116}.oz-pillbtn.is-on{background:#fff;box-shadow:0 1px 3px rgba(4,56,44,.14);color:#0c8f6b}.oz-pillbtn:focus-visible{outline:2px solid #1dd1a1;outline-offset:1px}.oz-select{position:relative}.oz-select .components-base-control__label{display:block;margin-bottom:8px}.oz-select__btn{align-items:center;background:#fff;border:1px solid #e7ecef;border-radius:10px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;height:42px;justify-content:space-between;padding:0 14px;transition:border-color .15s ease,box-shadow .15s ease;width:100%}.oz-select__btn .dashicons{color:#0c8f6b;transition:transform .18s ease}.oz-select__btn:hover{border-color:#bcc6cc}.oz-select__btn:focus,.oz-select__btn:focus-visible{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)!important;outline:none!important}.oz-select__btn[aria-expanded=true]{border-color:#1dd1a1;box-shadow:0 0 0 3px rgba(29,209,161,.16)}.oz-select__btn[aria-expanded=true] .dashicons{transform:rotate(180deg)}.oz-select__menu{background:#fff;border:1px solid #e7ecef;border-radius:12px;box-shadow:0 14px 34px rgba(15,32,39,.18);left:0;list-style:none;margin:0;padding:6px;position:absolute;right:0;top:calc(100% + 6px);z-index:30}.oz-select__item{align-items:center;border-radius:8px;color:#0f2027;cursor:pointer;display:flex;font-size:13.5px;justify-content:space-between;padding:9px 12px}.oz-select__item .dashicons{color:#10ac81;font-size:18px}.oz-select__item.is-active,.oz-select__item:hover{background:rgba(29,209,161,.12);color:#0c8f6b}.oz-select__item.is-selected{color:#0c8f6b;font-weight:650}.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{border-radius:10px;font-weight:650;height:38px;transition:transform .16s cubic-bezier(.2,.7,.3,1),box-shadow .18s ease,background .16s ease,border-color .16s ease,color .16s ease}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:transform .22s cubic-bezier(.2,.7,.3,1)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:translateY(-1px)}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:scale(1.14)}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled){transform:translateY(0) scale(.975);transition-duration:.05s}.oz-shell .oz-btn-danger.components-button:focus-visible,.oz-shell .oz-btn-ghost.components-button:focus-visible,.oz-shell .oz-btn-primary.components-button:focus-visible{outline:2px solid #1dd1a1;outline-offset:2px}.oz-shell .oz-btn-danger.components-button:disabled,.oz-shell .oz-btn-ghost.components-button:disabled,.oz-shell .oz-btn-primary.components-button:disabled{opacity:.5;transform:none}.oz-shell .oz-btn-primary.components-button{background:linear-gradient(135deg,#1dd1a1,#10ac81);border:0;box-shadow:0 1px 2px rgba(20,60,48,.12);color:#fff;padding:0 18px}.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){background:linear-gradient(135deg,#10ac81,#0c8f6b);box-shadow:0 8px 20px -7px rgba(29,209,161,.65);color:#fff}.oz-shell .oz-btn-primary.components-button.is-busy{animation:oz-btn-sheen 1.4s linear infinite;background-size:200% 100%}.oz-shell .oz-btn-ghost.components-button{background:#fff;border:1px solid rgba(29,209,161,.5);box-shadow:none;color:#0c8f6b}.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled){background:rgba(29,209,161,.08);border-color:#1dd1a1;box-shadow:0 6px 16px -8px rgba(29,209,161,.5);color:#0c8f6b}.oz-shell .oz-btn-danger.components-button{background:#fff;border:1px solid rgba(214,54,56,.5);color:#d63638}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled){background:rgba(214,54,56,.08);border-color:#d63638;box-shadow:0 6px 16px -8px rgba(214,54,56,.5);color:#b32d2e}.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg{transform:scale(1.1) rotate(90deg)}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn--x.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn--x.components-button:hover:not(:disabled) svg{transform:none}}@keyframes oz-btn-sheen{0%{background-image:linear-gradient(135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:200% 0}to{background-image:linear-gradient(135deg,#1dd1a1,#10ac81,#1dd1a1);background-position:-200% 0}}@media(prefers-reduced-motion:reduce){.oz-shell .oz-btn-danger.components-button,.oz-shell .oz-btn-ghost.components-button,.oz-shell .oz-btn-primary.components-button{transition:background .16s ease,color .16s ease,border-color .16s ease}.oz-shell .oz-btn-danger.components-button:active:not(:disabled),.oz-shell .oz-btn-danger.components-button:hover:not(:disabled),.oz-shell .oz-btn-ghost.components-button:active:not(:disabled),.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled),.oz-shell .oz-btn-primary.components-button:active:not(:disabled),.oz-shell .oz-btn-primary.components-button:hover:not(:disabled){transform:none}.oz-shell .oz-btn-danger.components-button .dashicons,.oz-shell .oz-btn-danger.components-button svg,.oz-shell .oz-btn-ghost.components-button .dashicons,.oz-shell .oz-btn-ghost.components-button svg,.oz-shell .oz-btn-primary.components-button .dashicons,.oz-shell .oz-btn-primary.components-button svg{transition:none}.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-danger.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-ghost.components-button:hover:not(:disabled) svg,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) .dashicons,.oz-shell .oz-btn-primary.components-button:hover:not(:disabled) svg{transform:none}.oz-shell .oz-btn-danger.components-button.is-busy,.oz-shell .oz-btn-ghost.components-button.is-busy,.oz-shell .oz-btn-primary.components-button.is-busy{animation:none}}.oz-shell .components-button.is-primary:focus:not(:disabled),.oz-shell .components-button:focus,.oz-shell .components-button:focus:not(:disabled){box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle,.oz-shell .components-form-toggle__input,.oz-shell .components-form-toggle__input:active,.oz-shell .components-form-toggle__input:focus,.oz-shell .components-form-toggle__input:focus-visible{border:none!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__track{background-color:#cdd6db!important;border:1px solid transparent!important;box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle.is-checked .components-form-toggle__track{background-color:#1dd1a1!important}.oz-shell .components-form-toggle__input:focus+.components-form-toggle__track,.oz-shell .components-form-toggle__input:focus-visible+.components-form-toggle__track{box-shadow:none!important;outline:none!important}.oz-shell .components-form-toggle__thumb{background-color:#fff!important;border:none!important;box-shadow:0 1px 2px rgba(15,32,39,.25)!important}.oz-savebar{align-items:center;background:#fff;border-top:1px solid #e7ecef;bottom:0;box-shadow:0 -6px 18px rgba(15,32,39,.06);display:flex;gap:16px;justify-content:flex-end;left:160px;padding:13px 32px;position:fixed;right:0;z-index:9}.oz-savebar__hint{color:#6b7a86;font-size:13px}.oz-savebar.is-dirty .oz-savebar__hint{color:#0c8f6b;font-weight:650}.folded .oz-savebar{left:36px}.oz-toast{bottom:72px;z-index:99}.oz-job,.oz-toast{position:fixed;right:24px}.oz-job{background:#fff;border:1px solid #e7ecef;border-radius:16px;bottom:84px;box-shadow:0 18px 50px rgba(15,32,39,.22);color:#0f2027;font-size:13px;max-width:calc(100vw - 48px);overflow:hidden;width:420px;z-index:100}.oz-job__head{align-items:center;display:flex;gap:12px;padding:16px 16px 12px}.oz-job__head .components-spinner{margin:0}.oz-job__icon-status{font-size:22px;height:22px;width:22px}.oz-job__icon-status--done{color:#10ac81}.oz-job__icon-status--stopped{color:#6b7a86}.oz-job__icon-status--error{color:#d63638}.oz-job__title{flex:1;min-width:0}.oz-job__title strong{display:block;font-size:15px;font-weight:700;line-height:1.2}.oz-job__title span{color:#6b7a86;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.oz-job__pct{color:#0f2027;font-size:13px;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__icon{background:transparent;border:0;border-radius:6px;color:#6b7a86;cursor:pointer;display:inline-flex;padding:4px}.oz-job__icon:hover{background:#f4f7f8;color:#0f2027}.oz-job__icon .dashicons{font-size:18px;height:18px;width:18px}.oz-job__body{padding:0 16px 16px}.oz-job__row{align-items:baseline;display:flex;gap:12px;justify-content:space-between;margin-bottom:8px}.oz-job__row span{line-height:1.4}.oz-job__row strong{flex:0 0 auto;font-variant-numeric:tabular-nums;font-weight:700}.oz-job__bar{background:#e7ecef;border-radius:999px;height:7px;overflow:hidden}.oz-job__bar div{background:linear-gradient(90deg,#1dd1a1,#10ac81);border-radius:999px;height:100%;transition:width .35s ease}.oz-job__bar.is-indeterminate div{animation:oz-indeterminate 1.1s ease-in-out infinite;border-radius:999px;width:40%!important}.oz-job.is-min .oz-job__bar{margin:2px 16px 16px}.oz-job.is-min .oz-job__head{padding-bottom:10px}.oz-job.is-done .oz-job__bar div{background:linear-gradient(90deg,#10ac81,#0c8f6b)}.oz-job.is-stopped .oz-job__bar div{background:#c3ccd2}.oz-job.is-error .oz-job__bar div{background:linear-gradient(90deg,#f16b6b,#d63638)}.oz-job__step{color:#6b7a86;font-size:12px;margin-top:8px}.oz-job__stop{align-items:center;background:transparent;border:0;color:#d63638;cursor:pointer;display:inline-flex;font-size:13px;font-weight:650;gap:6px;margin-top:14px;padding:4px 0}.oz-job__stop .dashicons{font-size:18px}.oz-job__stop:hover{color:#b32d2e}.oz-job__log{background:#11202b;border-radius:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.7;margin-top:14px;max-height:150px;overflow-y:auto;padding:10px 12px}.oz-logline{color:#cdd8df;display:flex;gap:8px}.oz-logline__tag{color:#5fd0ad;flex:0 0 auto;font-weight:700;min-width:42px}.oz-logline--warn .oz-logline__tag{color:#f0b429}.oz-logline--error .oz-logline__tag{color:#ff6b6b}.oz-logline--done .oz-logline__tag{color:#1dd1a1}.oz-logline__text{word-break:break-word}@media(max-width:1100px){.oz-kpis,.oz-quick{grid-template-columns:1fr}}@media(max-width:1024px){.oz-score{align-items:flex-start;flex-direction:column;text-align:left}.oz-convert{align-items:stretch;flex-direction:column;gap:12px}.oz-convert .oz-btn-ghost.components-button,.oz-convert .oz-btn-primary.components-button{justify-content:center;width:100%}}.oz-mobile-toggle,.oz-nav-overlay{display:none!important}@media(max-width:1024px){.oz-shell{flex-direction:column;margin:8px 8px 110px}.oz-content{border-left:0;min-width:0;width:100%}.oz-sidebar{border-bottom:1px solid #e7ecef;border-right:0;flex:none;max-height:none;overflow:visible;padding:14px 12px;position:static;width:100%}.oz-sidebar__brand{margin-bottom:10px;padding-bottom:12px}.oz-sidebar nav{display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}.oz-navitem{border-bottom:3px solid transparent;border-left:0;border-radius:10px;flex:0 0 auto;padding:8px 12px;width:auto}.oz-navitem.is-active{border-bottom-color:#1dd1a1;border-left-color:transparent}.oz-navitem .oz-navitem__text strong{white-space:nowrap}.oz-navitem .oz-navitem__dot,.oz-navitem small,.oz-sidebar__foot{display:none}.oz-topbar{padding:16px 18px}.oz-topbar h1{font-size:18px}.oz-main{padding:16px 18px}.oz-savebar{left:0;padding:12px 18px}.oz-card__body{padding-left:18px;padding-right:18px}.oz-job{left:8px;right:8px;width:auto}}@media(max-width:560px){.oz-tiles{grid-template-columns:1fr}.oz-hero,.oz-score{padding:18px}.oz-card__head{gap:12px;padding:16px}.oz-card__icon{flex-basis:40px;height:40px;width:40px}.oz-savebar__hint{display:none}}@keyframes oz-indeterminate{0%{transform:translateX(-100%)}to{transform:translateX(350%)}}.oz-muted{color:#6b7a86;font-size:13px}.oz-reqs{list-style:none;margin:0;padding:0}.oz-req{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:grid;font-size:13.5px;gap:6px 10px;grid-template-columns:20px 1fr auto;padding:10px 6px}.oz-req:last-child{border-bottom:0}.oz-req__dot{font-size:18px;height:18px;width:18px}.oz-req__label{font-weight:650}.oz-req__value{color:#6b7a86;font-weight:600;justify-self:end}.oz-req__note{color:#6b7a86;font-size:12px;font-weight:400;grid-column:2/-1;line-height:1.45;margin-top:2px}.oz-req--ok>.oz-req__dot{color:#0c8f6b}.oz-req--warn>.oz-req__dot{color:#e0900f}.oz-req--error>.oz-req__dot{color:#e05242}.oz-req--info>.oz-req__dot,.oz-req--info>.oz-req__value{color:#6b7a86}.oz-req--warn{background:rgba(224,144,15,.06)}.oz-req--error{background:rgba(224,82,66,.06)}.oz-notice{align-items:flex-start;background:rgba(224,82,66,.08);border:1px solid rgba(224,82,66,.28);border-radius:10px;color:#7a2018;display:flex;font-size:13px;gap:10px;line-height:1.5;margin:0 0 14px;padding:12px 14px}.oz-notice .dashicons{color:#e05242;flex:0 0 20px;margin-top:1px}.oz-notice--warn{background:rgba(224,144,15,.09);border-color:rgba(224,144,15,.3);color:#7a5406}.oz-notice--warn .dashicons{color:#e0900f}.oz-notice--ok{background:#eaf8f3;border-color:#b8e6d6;color:#0f5e48}.oz-notice--ok .dashicons{color:#0e9f7e}.oz-recs{list-style:none;margin:0;padding:0}.oz-rec{align-items:center;border-bottom:1px solid #e7ecef;border-radius:8px;display:flex;gap:12px;padding:12px 8px}.oz-rec:last-child{border-bottom:0}.oz-rec__dot{flex:0 0 20px;font-size:20px;height:20px;margin-top:1px;width:20px}.oz-rec__text{flex:1;min-width:0}.oz-rec__text strong{display:block;font-size:13.5px;font-weight:650}.oz-rec__text small{color:#6b7a86;display:block;font-size:12px;line-height:1.45;margin-top:2px}.oz-rec__btn.components-button{flex:0 0 auto}.oz-rec--high>.oz-rec__dot{color:#e05242}.oz-rec--medium>.oz-rec__dot{color:#e0900f}.oz-rec--low>.oz-rec__dot{color:#6b7a86}.oz-rec--high{background:rgba(224,82,66,.05)}.oz-rec--medium{background:rgba(224,144,15,.05)}.oz-recs-clear{align-items:center;color:#6b7a86;display:flex;font-size:13px;gap:8px}.oz-recs-clear .dashicons{color:#0c8f6b}.oz-select--compact{border-bottom:0!important;flex:none;margin:0 0 0 auto!important;padding:0!important}.oz-select--compact .oz-select__btn{background:#f7faf9;border-radius:9px;font-size:13px;font-weight:650;gap:10px;height:30px;min-width:132px;padding:0 10px 0 12px;width:auto}.oz-select--compact .oz-select__btn:disabled{cursor:progress;opacity:.6}.oz-select--compact .oz-select__btn .dashicons{font-size:16px;height:16px;width:16px}.oz-select--compact .oz-select__menu{left:auto;min-width:190px;right:0}.oz-select--compact .oz-select__item{font-size:13px;padding:8px 10px}.oz-cachewarn--ok{background:#eaf8f3;border-color:#b8e6d6;color:#0f5e48}.oz-cachewarn__text p+p{margin-top:6px}.oz-cachewarn__link{color:inherit;display:inline-block;font-weight:600;margin-top:8px;text-decoration:underline}.oz-network__sites{align-items:start;margin-top:16px}.oz-network__sites .oz-panel__head h2,.oz-network__sites .oz-panel__head p{overflow-wrap:anywhere}.oz-cachelist__link{color:#1dd1a1;font-weight:600;margin-left:auto;text-decoration:none}.oz-cachelist__link:focus,.oz-cachelist__link:hover{text-decoration:underline}
  • optimazz/trunk/build/index.js

    r3723128 r3743505  
    1 (()=>{"use strict";const e={n:a=>{const t=a&&a.__esModule?()=>a.default:()=>a;return e.d(t,{a:t}),t},d:(a,t)=>{for(var o in t)e.o(t,o)&&!e.o(a,o)&&Object.defineProperty(a,o,{enumerable:!0,get:t[o]})},o:(e,a)=>Object.hasOwn(e,a)},a=window.React,t=window.wp.element,o=window.wp.components,n=window.wp.apiFetch;var l=e.n(n);const i=window.wp.i18n,s=window.wp.hooks,{restUrl:r,nonce:c,settings:m}=window.optimazzData||{};l().use(l().createNonceMiddleware(c));const d=[{key:"dashboard",title:(0,i.__)("Dashboard","optimazz"),sub:(0,i.__)("Overview & quick actions","optimazz"),icon:"admin-home"},{key:"cache",title:(0,i.__)("Page Cache","optimazz"),sub:(0,i.__)("Static HTML caching","optimazz"),icon:"database",mod:"cache"},{key:"files",title:(0,i.__)("File Optimization","optimazz"),sub:(0,i.__)("Optimize CSS & JS","optimazz"),icon:"editor-code"},{key:"media",title:(0,i.__)("Media","optimazz"),sub:(0,i.__)("LazyLoad, dimensions, WebP","optimazz"),icon:"format-image",mod:"media"},{key:"fonts",title:(0,i.__)("Fonts","optimazz"),sub:(0,i.__)("Preload, swap, preconnect","optimazz"),icon:"editor-textcolor",mod:"fonts"},{key:"preload",title:(0,i.__)("Preload","optimazz"),sub:(0,i.__)("Generate cache files","optimazz"),icon:"update"},{key:"advanced",title:(0,i.__)("Advanced Rules","optimazz"),sub:(0,i.__)("Fine-tune cache rules","optimazz"),icon:"list-view"},{key:"database",title:(0,i.__)("Database","optimazz"),sub:(0,i.__)("Optimize, reduce bloat","optimazz"),icon:"database-view",mod:"database"},{key:"heartbeat",title:(0,i.__)("Heartbeat","optimazz"),sub:(0,i.__)("Control Heartbeat API","optimazz"),icon:"heart",mod:"heartbeat"},{key:"tweaks",title:(0,i.__)("Tweaks","optimazz"),sub:(0,i.__)("Remove WordPress bloat","optimazz"),icon:"admin-generic",mod:"tweaks"},{key:"cdn",title:(0,i.__)("CDN","optimazz"),sub:(0,i.__)("Rewrite asset URLs","optimazz"),icon:"cloud",mod:"cdn"},{key:"tools",title:(0,i.__)("Tools","optimazz"),sub:(0,i.__)("Import, export","optimazz"),icon:"admin-tools"}],p=(0,s.applyFilters)("optimazz.adminSections",[]),_=[...d,...p],z=["cache","js","css","media","fonts","database","heartbeat","tweaks","cdn"],h=[{title:(0,i.__)("Content leftovers","optimazz"),desc:(0,i.__)("Old post data that piles up over time. Removing it is permanent, so keep anything you still need.","optimazz"),items:[{key:"revisions",label:(0,i.__)("Post revisions","optimazz"),help:(0,i.__)("Old copies of posts saved automatically. Deleting them keeps only your current content.","optimazz")},{key:"auto_drafts",label:(0,i.__)("Auto drafts","optimazz"),help:(0,i.__)("Empty drafts WordPress creates while you write. Safe to remove.","optimazz")},{key:"trashed_posts",label:(0,i.__)("Trashed posts","optimazz"),help:(0,i.__)("Posts in the trash that have not been permanently deleted yet.","optimazz")}]},{title:(0,i.__)("Comment clutter","optimazz"),desc:(0,i.__)("Spam and trashed comments you are never going to approve. Clearing them keeps the comments table light.","optimazz"),items:[{key:"spam_comments",label:(0,i.__)("Spam comments","optimazz"),help:(0,i.__)("Comments marked as spam. Removing them frees up the comments table.","optimazz")},{key:"trashed_comments",label:(0,i.__)("Trashed comments","optimazz"),help:(0,i.__)("Comments in the trash awaiting permanent deletion.","optimazz")}]},{title:(0,i.__)("Temporary data","optimazz"),desc:(0,i.__)("Short-lived cached values. They are safe to clear and are rebuilt automatically whenever a plugin needs them.","optimazz"),items:[{key:"expired_transients",label:(0,i.__)("Expired transients","optimazz"),help:(0,i.__)("Cached values that have already passed their expiry date.","optimazz")}]},{title:(0,i.__)("Table maintenance","optimazz"),desc:(0,i.__)("Defragment your tables to reclaim wasted space and trim database overhead.","optimazz"),items:[{key:"optimize_tables",label:(0,i.__)("Optimize database tables","optimazz"),help:(0,i.__)("Runs OPTIMIZE on your tables to defragment them and free wasted space.","optimazz")}]}],u=[{label:(0,i.__)("Do not limit","optimazz"),value:"default"},{label:(0,i.__)("Reduce activity","optimazz"),value:"reduce"},{label:(0,i.__)("Disable","optimazz"),value:"disable"}];function g({on:e}){return(0,a.createElement)("span",{className:"oz-pill "+(e?"is-on":"is-off")},(0,a.createElement)("i",null),e?(0,i.__)("Active","optimazz"):(0,i.__)("Off","optimazz"))}function b({icon:e,title:t,description:n,enabled:l,onToggle:i,children:s}){return(0,a.createElement)("section",{className:"oz-card "+(l?"is-enabled":"")},(0,a.createElement)("header",{className:"oz-card__head"},(0,a.createElement)("span",{className:`oz-card__icon dashicons dashicons-${e}`}),(0,a.createElement)("div",{className:"oz-card__title"},(0,a.createElement)("h2",null,t," ",(0,a.createElement)(g,{on:l})),(0,a.createElement)("p",null,n)),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:l,onChange:i})),l&&(0,a.createElement)("div",{className:"oz-card__body"},s))}function f({job:e,onStop:n,onClose:l,onToggleMin:s}){const r=(0,t.useRef)(null);if((0,t.useEffect)(()=>{r.current&&(r.current.scrollTop=r.current.scrollHeight)},[e?.logs?.length]),!e)return null;const c=e.status||(e.running?"running":"done"),m={stopped:(0,i.__)("Cancelled","optimazz"),error:(0,i.__)("Failed","optimazz")},d="running"===c||"done"===c?e.title:`${e.title} — ${m[c]}`;return(0,a.createElement)("div",{className:`oz-job is-${c} ${e.minimized?"is-min":""}`},(0,a.createElement)("div",{className:"oz-job__head"},"running"===c?(0,a.createElement)(o.Spinner,null):(0,a.createElement)("span",{className:`oz-job__icon-status oz-job__icon-status--${c} dashicons dashicons-${{done:"yes-alt",stopped:"dismiss",error:"warning"}[c]||"yes-alt"}`}),(0,a.createElement)("div",{className:"oz-job__title"},(0,a.createElement)("strong",null,d),(0,a.createElement)("span",null,e.minimized?`${e.pct}%${e.step?` · ${e.step}`:""}`:e.subtitle)),(0,a.createElement)("button",{className:"oz-job__icon","aria-label":e.minimized?(0,i.__)("Expand","optimazz"):(0,i.__)("Minimize","optimazz"),onClick:s},(0,a.createElement)("span",{className:"dashicons dashicons-"+(e.minimized?"editor-expand":"minus")})),(0,a.createElement)("button",{className:"oz-job__icon","aria-label":(0,i.__)("Close","optimazz"),onClick:l},(0,a.createElement)("span",{className:"dashicons dashicons-no-alt"}))),e.minimized?(0,a.createElement)("div",{className:"oz-job__bar "+(e.indeterminate&&e.running?"is-indeterminate":"")},(0,a.createElement)("div",{style:{width:`${e.pct}%`}})):(0,a.createElement)("div",{className:"oz-job__body"},(0,a.createElement)("div",{className:"oz-job__row"},(0,a.createElement)("span",null,e.label),(0,a.createElement)("strong",null,e.pct,"%")),(0,a.createElement)("div",{className:"oz-job__bar "+(e.indeterminate&&e.running?"is-indeterminate":"")},(0,a.createElement)("div",{style:{width:`${e.pct}%`}})),e.step&&(0,a.createElement)("div",{className:"oz-job__step"},(0,i.__)("Step","optimazz")," ",e.step),e.running&&(0,a.createElement)("button",{className:"oz-job__stop",onClick:n},(0,a.createElement)("span",{className:"dashicons dashicons-controls-pause"}),(0,i.__)("Stop","optimazz")),(0,a.createElement)("div",{className:"oz-job__log",ref:r},e.logs.map((e,t)=>{const o=e.level||"INFO";return(0,a.createElement)("div",{key:t,className:`oz-logline oz-logline--${o.toLowerCase()}`},(0,a.createElement)("span",{className:"oz-logline__tag"},o),(0,a.createElement)("span",{className:"oz-logline__text"},e.text))}))))}let E=0;function v({label:e,value:o,options:n,onChange:l,compact:i=!1,disabled:s=!1}){const[r,c]=(0,t.useState)(!1),m=Math.max(0,n.findIndex(e=>e.value===o)),[d,p]=(0,t.useState)(m),_=(0,t.useRef)(null),z=(0,t.useRef)("oz-sel-"+ ++E).current;(0,t.useEffect)(()=>{const e=e=>{_.current&&!_.current.contains(e.target)&&c(!1)};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[]);const h=n.find(e=>e.value===o)||n[0],u=e=>{l(n[e].value),c(!1)};return(0,a.createElement)("div",{className:"components-base-control oz-select"+(i?" oz-select--compact":""),ref:_},(0,a.createElement)("span",{className:i?"screen-reader-text":"components-base-control__label",id:`${z}-label`},e),(0,a.createElement)("button",{type:"button",className:"oz-select__btn",disabled:s,role:"combobox","aria-haspopup":"listbox","aria-expanded":r,"aria-labelledby":`${z}-label`,"aria-activedescendant":r?`${z}-opt-${d}`:void 0,onClick:()=>c(e=>!e),onKeyDown:e=>{r?"Escape"===e.key||"Tab"===e.key?c(!1):"ArrowDown"===e.key?(e.preventDefault(),p(e=>Math.min(n.length-1,e+1))):"ArrowUp"===e.key?(e.preventDefault(),p(e=>Math.max(0,e-1))):"Home"===e.key?(e.preventDefault(),p(0)):"End"===e.key?(e.preventDefault(),p(n.length-1)):"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),u(d)):["ArrowDown","ArrowUp","Enter"," "].includes(e.key)&&(e.preventDefault(),p(m),c(!0))}},(0,a.createElement)("span",null,h?.label),(0,a.createElement)("span",{className:"dashicons dashicons-arrow-down-alt2"})),r&&(0,a.createElement)("ul",{className:"oz-select__menu",role:"listbox","aria-labelledby":`${z}-label`},n.map((e,t)=>(0,a.createElement)("li",{key:e.value,id:`${z}-opt-${t}`,role:"option","aria-selected":e.value===o,className:`oz-select__item ${e.value===o?"is-selected":""} ${t===d?"is-active":""}`,onMouseEnter:()=>p(t),onClick:()=>u(t)},(0,a.createElement)("span",null,e.label),e.value===o&&(0,a.createElement)("span",{className:"dashicons dashicons-yes"})))))}const y={dashboard:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M3 9.6 12 3l9 6.6"}),(0,a.createElement)("path",{d:"M5 9.1V20h5v-5h4v5h5V9.1"})),cache:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M12 3 2.5 8 12 13l9.5-5L12 3Z"}),(0,a.createElement)("path",{d:"M2.5 12 12 17l9.5-5"})),files:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"m9 8-4 4 4 4"}),(0,a.createElement)("path",{d:"m15 8 4 4-4 4"})),media:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("rect",{x:"3",y:"4",width:"18",height:"16",rx:"2"}),(0,a.createElement)("circle",{cx:"8.5",cy:"9.5",r:"1.5"}),(0,a.createElement)("path",{d:"m21 16-5-5-6 6"})),fonts:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M5 6V4h14v2"}),(0,a.createElement)("path",{d:"M12 4v16"}),(0,a.createElement)("path",{d:"M9 20h6"})),preload:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M21 12a9 9 0 1 1-2.6-6.4"}),(0,a.createElement)("path",{d:"M21 4v5h-5"})),advanced:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M4 8h9"}),(0,a.createElement)("circle",{cx:"16",cy:"8",r:"2.1"}),(0,a.createElement)("path",{d:"M19 8h1"}),(0,a.createElement)("path",{d:"M4 16h1"}),(0,a.createElement)("circle",{cx:"8",cy:"16",r:"2.1"}),(0,a.createElement)("path",{d:"M11 16h9"})),database:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("ellipse",{cx:"12",cy:"5",rx:"8",ry:"3"}),(0,a.createElement)("path",{d:"M4 5v14c0 1.6 3.6 3 8 3s8-1.4 8-3V5"}),(0,a.createElement)("path",{d:"M4 12c0 1.6 3.6 3 8 3s8-1.4 8-3"})),heartbeat:(0,a.createElement)("path",{d:"M3 12h4l2 6 4-14 2 8h6"}),tweaks:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"m12 4 1.5 4.5L18 10l-4.5 1.5L12 16l-1.5-4.5L6 10l4.5-1.5Z"}),(0,a.createElement)("path",{d:"M18.5 15v3"}),(0,a.createElement)("path",{d:"M17 16.5h3"})),cdn:(0,a.createElement)("path",{d:"M17 18H7a4 4 0 0 1-.3-8A6 6 0 0 1 18.3 9 3.5 3.5 0 0 1 17 18Z"}),tools:(0,a.createElement)("path",{d:"M14.5 5.5a4 4 0 0 0-5 5L4 16v4h4l5.5-5.5a4 4 0 0 0 5-5l-2.6 2.6-2-.4-.4-2 2.5-2.6Z"})};function w({name:e,paths:t}){return(0,a.createElement)("svg",{className:"oz-ico",viewBox:"0 0 24 24",width:"20",height:"20",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",focusable:"false"},t||y[e]||y.dashboard)}function k({ok:e,warn:t,label:o}){const n=e?"ok":t?"warn":"bad",l=e?"yes-alt":t?"warning":"dismiss";return(0,a.createElement)("li",{className:`oz-health__row is-${n}`},(0,a.createElement)("span",{className:`dashicons dashicons-${l}`}),(0,a.createElement)("span",null,o))}function C({label:e,help:t,value:o,options:n,onChange:l}){return(0,a.createElement)("div",{className:"components-base-control oz-pillgroup"},e&&(0,a.createElement)("span",{className:"components-base-control__label"},e),(0,a.createElement)("div",{className:"oz-pillgroup__row",role:"radiogroup","aria-label":e,onKeyDown:e=>{const a=n.findIndex(e=>e.value===o);"ArrowRight"===e.key||"ArrowDown"===e.key?(e.preventDefault(),l(n[Math.min(n.length-1,a+1)].value)):"ArrowLeft"!==e.key&&"ArrowUp"!==e.key||(e.preventDefault(),l(n[Math.max(0,a-1)].value))}},n.map(e=>{const t=e.value===o;return(0,a.createElement)("button",{key:String(e.value),type:"button",role:"radio","aria-checked":t,tabIndex:t?0:-1,className:"oz-pillbtn "+(t?"is-on":""),onClick:()=>l(e.value)},e.label)})),t&&(0,a.createElement)("span",{className:"components-base-control__help"},t))}function N({title:e,description:t,children:o}){return(0,a.createElement)("section",{className:"oz-panel"},(0,a.createElement)("header",{className:"oz-panel__head"},(0,a.createElement)("h2",null,e),t&&(0,a.createElement)("p",null,t)),(0,a.createElement)("div",{className:"oz-panel__body"},o))}const x=document.getElementById("optimazz-app");x&&(0,t.createRoot)(x).render((0,a.createElement)(function(){var e,n,s,c,g,E,y,x;const[S,M]=(0,t.useState)(m||{}),[T,$]=(0,t.useState)(m||{}),[B,R]=(0,t.useState)(null),[P,D]=(0,t.useState)(null),[O,F]=(0,t.useState)(!1),[H,L]=(0,t.useState)("dashboard"),[A,j]=(0,t.useState)(!1),[W,I]=(0,t.useState)(null),[q,U]=(0,t.useState)(!(!window.optimazzData||!window.optimazzData.hasBackup)),J=(0,t.useRef)(null);(0,t.useEffect)(()=>{const e=e=>{const a=e&&e.detail&&e.detail.key;a&&L(a)};return window.addEventListener("optimazz:navigate",e),()=>window.removeEventListener("optimazz:navigate",e)},[]);const G=JSON.stringify(S)!==JSON.stringify(T),V=(0,t.useCallback)((e,a,t)=>M(o=>({...o,[e]:{...o[e],[a]:t}})),[]),K=(e,a)=>V(e,a,!S[e]?.[a]),Y=(0,t.useCallback)(async()=>{try{R(await l()({url:`${r}/cache/stats`}))}catch(e){}},[]),[Z,Q]=(0,t.useState)(null),X=(0,t.useCallback)(async()=>{try{Q(await l()({url:`${r}/system?_=${Date.now()}`}))}catch(e){Q({error:!0})}},[]);(0,t.useEffect)(()=>{"tools"!==H||Z||X()},[H,Z,X]);const[ee,ae]=(0,t.useState)(null),te=(0,t.useCallback)(async()=>{try{ae(await l()({url:`${r}/recommendations?_=${Date.now()}`}))}catch(e){ae({error:!0})}},[]);(0,t.useEffect)(()=>{"dashboard"!==H||ee||te()},[H,ee,te]);const oe=(0,t.useCallback)(async()=>{F(!0);try{D(await l()({url:`${r}/cache/health`}))}catch(e){D({error:!0})}F(!1)},[]),ne=(0,t.useCallback)(async(e,a)=>{if(window.confirm(`${(0,i.__)("Deactivate","optimazz")} ${a}? ${(0,i.__)("Optimazz will keep handling caching.","optimazz")}`)){F(!0);try{const t=await l()({url:`${r}/cache/deactivate-conflict`,method:"POST",data:{plugin:e}});D(t),ie(`${a} ${(0,i.__)("deactivated — Optimazz is now the only cache.","optimazz")}`,"success")}catch(e){ie((0,i.__)("Could not deactivate the plugin.","optimazz"),"error")}F(!1)}},[]),le=(0,t.useCallback)(async()=>{if(window.confirm((0,i.__)("Make Optimazz the caching plugin? This replaces the current advanced-cache.php (from another caching plugin) and turns on WP_CACHE. Deactivate the other caching plugin afterwards to avoid conflicts.","optimazz"))){F(!0);try{const e=await l()({url:`${r}/cache/takeover`,method:"POST"});D(e),ie("hit"===e.live?(0,i.__)("Optimazz is now serving pages from cache.","optimazz"):e.dropin_ours?(0,i.__)("Drop-in installed. Preload the cache, then check again.","optimazz"):(0,i.__)("Could not fully take over — see the cache health details.","optimazz"),e.dropin_ours?"success":"error")}catch(e){ie((0,i.__)("Take-over failed. Check file permissions.","optimazz"),"error")}F(!1)}},[]);(0,t.useEffect)(()=>{Y()},[Y]);const ie=(e,a="success")=>{I({text:e,status:a}),window.clearTimeout(ie._t),ie._t=window.setTimeout(()=>I(null),3e3)};(0,t.useEffect)(()=>{try{const e=new URLSearchParams(window.location.search),a=e.get("optimazz_notice"),t={purged:(0,i.__)("Cache purged.","optimazz"),preloaded:(0,i.__)("Cache preload started in the background.","optimazz"),cleared:(0,i.__)("Cache cleared; preload started in the background.","optimazz")};if(a&&t[a]){ie(t[a]),e.delete("optimazz_notice");const o=e.toString();window.history.replaceState({},"",window.location.pathname+(o?`?${o}`:""))}window.optimazzData?.wpconfigUnwritable&&ie((0,i.__)("wp-config.php is not writable — add WP_CACHE manually to enable page caching.","optimazz"),"error")}catch(e){}},[]);const se=async e=>{j(!0);try{const a=await l()({url:`${r}/settings`,method:"POST",data:e});$(a.settings),M(a.settings),U(!!a.hasBackup),ae(null),ie((0,i.__)("Settings saved.","optimazz")),Y()}catch(e){ie(e.message||(0,i.__)("Save failed.","optimazz"),"error")}j(!1)},re=async()=>{await l()({url:`${r}/cache/purge`,method:"POST"}),ie((0,i.__)("Cache purged.","optimazz")),Y()},ce=()=>ve("preload"),[me,de]=(0,t.useState)(null),[pe,_e]=(0,t.useState)(null),[ze,he]=(0,t.useState)(null),ue=(0,t.useRef)(!1),ge=(0,t.useCallback)(async()=>{try{_e(await l()({url:`${r}/media/stats`}))}catch(e){}},[]);(0,t.useEffect)(()=>{"media"!==H||pe||ge()},[H,pe,ge]);const be={convert:{title:(0,i.__)("Converting images to WebP/AVIF","optimazz"),subtitle:(0,i.__)("Next-gen format generation","optimazz"),endpoint:`${r}/media/convert`,refresh:ge,summarize:e=>`${(0,i.__)("Generated","optimazz")} ${e.generated||0} ${(0,i.__)("files in this batch","optimazz")}${e.skipped?` · ${e.skipped} ${(0,i.__)("skipped","optimazz")}`:""}`},compress:{title:(0,i.__)("Compressing images","optimazz"),subtitle:(0,i.__)("Recompress, resize & strip metadata","optimazz"),endpoint:`${r}/media/optimize`,refresh:ge,summarize:e=>{const a=Math.max(0,(e.before||0)-(e.after||0)),t=e.skipped?` · ${e.skipped} ${(0,i.__)("skipped","optimazz")}`:"";return`${(0,i.__)("Processed","optimazz")} ${e.processed||0} — ${we(a)} ${(0,i.__)("saved","optimazz")}${t}`}},preload:{title:(0,i.__)("Preloading cache","optimazz"),subtitle:(0,i.__)("Warming pages","optimazz"),endpoint:`${r}/cache/preload`,refresh:Y,onBatch:Y,summarize:(e,a)=>`${(0,i.__)("Warmed","optimazz")} ${Math.min(e.next_offset||0,e.total||0)} / ${e.total||0} ${(0,i.__)("pages","optimazz")} · ${a.cached} ${(0,i.__)("cached","optimazz")}${a.missed?` · ${a.missed} ${(0,i.__)("not cached","optimazz")}`:""}`}},fe=e=>!e||e<1?"":e>=3600?`~${Math.round(e/3600)}h`:e>=60?`~${Math.round(e/60)}m`:`~${Math.round(e)}s`,Ee=()=>l()({url:`${r}/media/job`,method:"DELETE"}).catch(()=>{}),ve=async(e,{startOffset:a=0,reconnect:t=!1}={})=>{const o=be[e];if(!o||ze?.running)return;ue.current=!1;const n=t?(0,i.__)("Resuming…","optimazz"):`${o.title}…`;he({title:o.title,subtitle:o.subtitle,pct:0,label:n,step:"",logs:[{level:"INFO",text:n}],running:!0,status:"running",minimized:!1});let s=a,r=0,c=0;const m={cached:0,missed:0,diagnosis:""},d=Date.now();try{for(;;){var p;if(ue.current){await Ee(),he(e=>e&&{...e,running:!1,status:"stopped",logs:[...e.logs,{level:"WARN",text:(0,i.__)("Stopped by user.","optimazz")}]});break}const a=s;let t;try{t=await l()({url:o.endpoint,method:"POST",data:{offset:s}})}catch(e){if(c++,c>20)throw e;if(s=a+1,he(e=>e&&{...e,logs:[...e.logs.slice(-60),{level:"WARN",text:(0,i.__)("A batch failed (likely one slow/large image); skipped it and continued.","optimazz")}]}),r&&s>=r){he(e=>e&&{...e,running:!1,status:"done",pct:100,logs:[...e.logs,{level:"DONE",text:(0,i.__)("Completed (some items skipped).","optimazz")}]});break}await new Promise(e=>setTimeout(e,600));continue}if(c=0,s=null!==(p=t.next_offset)&&void 0!==p?p:s+(t.processed||0),r=t.total||r,!t.done&&s<=a){await Ee(),he(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:(0,i.__)("Job stalled — no progress.","optimazz")}]});break}m.cached+=t.cached||0,m.missed+=t.missed||0,m.diagnosis=t.diagnosis||m.diagnosis;const n=r?Math.min(100,Math.round(s/r*100)):100,_=o.summarize(t,m),z=(Date.now()-d)/1e3,h=s>0?s/Math.max(z,.001):0,u=h>0&&r&&s<r?(r-s)/h:0,g=u?` · ${fe(u)} ${(0,i.__)("left","optimazz")}`:"";if(he(e=>e&&{...e,pct:n,label:_,step:r?`${Math.min(s,r)} / ${r}${g}`:"",logs:[...e.logs.slice(-60),...t.breakdown?[{level:"INFO",text:`${(0,i.sprintf)(/* translators: %d: number of pages to warm. */ /* translators: %d: number of pages to warm. */
    2 (0,i.__)("Found %d pages to warm:","optimazz"),t.total||0)} ${t.breakdown}.`}]:[],{level:"INFO",text:_}]}),o.onBatch&&o.onBatch(),t.unsupported){await Ee(),he(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:t.message||(0,i.__)("Image processing is unavailable on this server (no GD or ImageMagick). See Tools → Server Requirements.","optimazz")}]});break}if(t.blocked){await Ee(),he(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:(0,i.__)('Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the "Optimazz-CacheWarmer" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter.',"optimazz")}]});break}if("preload"===e&&!t.done&&0===m.cached&&m.missed>=3){await Ee(),he(e=>e&&{...e,running:!1,status:"error",label:(0,i.__)("Pages are not being cached","optimazz"),logs:[...e.logs,{level:"ERROR",text:`${(0,i.__)("Stopped: pages are loading but none are being saved to the cache.","optimazz")} ${m.diagnosis}`}]});break}if(t.done){const e=[{level:"DONE",text:(0,i.__)("Completed.","optimazz")}];m.missed&&e.unshift({level:"WARN",text:`${(0,i.sprintf)(/* translators: %d: number of pages. */ /* translators: %d: number of pages. */
    3 (0,i._n)("%d page was not cached.","%d pages were not cached.",m.missed,"optimazz"),m.missed)} ${m.diagnosis}`}),he(a=>a&&{...a,running:!1,status:"done",pct:100,logs:[...a.logs,...e]});break}}}catch(e){he(a=>a&&{...a,running:!1,status:"error",logs:[...a.logs,{level:"ERROR",text:e.message||"Error"}]})}(o.refresh||ge)()};(0,t.useEffect)(()=>{let e=!1;return(async()=>{try{const a=await l()({url:`${r}/media/job`});!e&&a&&a.running&&be[a.type]&&ve(a.type,{startOffset:a.offset||0,reconnect:!0})}catch(e){}})(),()=>{e=!0}},[]);const ye=(0,t.useCallback)(async()=>{try{de(await l()({url:`${r}/database/counts`}))}catch(e){}},[]);(0,t.useEffect)(()=>{"database"!==H||me||ye()},[H,me,ye]);const we=e=>{if(!e)return"0 KB";const a=Math.floor(Math.log(e)/Math.log(1024));return`${(e/Math.pow(1024,a)).toFixed(a?1:0)} ${["B","KB","MB","GB"][a]}`},ke=z.filter(e=>S[e]?.enabled).length,Ce=S.cache||{},Ne=S.js||{},xe=S.css||{},Se=S.media||{},Me=S.fonts||{},Te=S.database||{},$e=S.heartbeat||{},Be=S.tweaks||{},Re=S.cdn||{},Pe=_.find(e=>e.key===H),De=[{on:!!Ce.enabled,w:30,label:(0,i.__)("Page caching","optimazz")},{on:!(!Ne.enabled||!Ne.delay),w:18,label:(0,i.__)("Delay JavaScript","optimazz")},{on:!(!Ne.enabled||!Ne.defer),w:8,label:(0,i.__)("Defer JavaScript","optimazz")},{on:!(!xe.enabled||!xe.minify),w:10,label:(0,i.__)("Minify CSS","optimazz")},{on:!(!Se.enabled||!Se.lazy_load),w:12,label:(0,i.__)("Lazy-load images","optimazz")},{on:!(!Se.enabled||!Se.webp&&!Se.webp_generate),w:8,label:(0,i.__)("Next-gen images","optimazz")},{on:!(!Me.enabled||!Me.display_swap),w:6,label:(0,i.__)("Font optimization","optimazz")},{on:!!Ce.preload,w:5,label:(0,i.__)("Cache preloading","optimazz")},{on:!!$e.enabled,w:3,label:(0,i.__)("Heartbeat control","optimazz")}],Oe=De.reduce((e,a)=>e+(a.on?a.w:0),0),Fe=De.filter(e=>!e.on).sort((e,a)=>a.w-e.w)[0],He=Oe>=90?"A":Oe>=75?"B":Oe>=55?"C":Oe>=35?"D":"E",Le=2*Math.PI*52,Ae=(Ne.enabled||xe.enabled||Se.enabled)&&!Ce.enabled,je=(0,a.createElement)(a.Fragment,null,Ae&&(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-info-outline"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Turn on Page Cache for the real speed gain","optimazz")),(0,a.createElement)("p",null,(0,i.__)("File and media optimizations are on, but Page Cache is off — so the server rebuilds every page on every request. Without caching this can make a site feel slower, not faster, especially on mobile. Enable Page Cache to serve ready-made HTML.","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-primary",onClick:()=>L("cache")},(0,i.__)("Enable Page Cache","optimazz"))),(0,a.createElement)("div",{className:"oz-score"},(0,a.createElement)("div",{className:"oz-score__gauge"},(0,a.createElement)("svg",{viewBox:"0 0 120 120",width:"132",height:"132"},(0,a.createElement)("circle",{cx:"60",cy:"60",r:"52",fill:"none",stroke:"rgba(255,255,255,.25)",strokeWidth:"10"}),(0,a.createElement)("circle",{cx:"60",cy:"60",r:"52",fill:"none",stroke:"#fff",strokeWidth:"10",strokeLinecap:"round",strokeDasharray:Le,strokeDashoffset:Le-Le*Oe/100,transform:"rotate(-90 60 60)",style:{transition:"stroke-dashoffset .6s ease"}}),(0,a.createElement)("text",{x:"60",y:"56",textAnchor:"middle",fontSize:"30",fontWeight:"800",fill:"#fff"},Oe),(0,a.createElement)("text",{x:"60",y:"78",textAnchor:"middle",fontSize:"12",fill:"rgba(255,255,255,.85)"},(0,i.__)("SCORE","optimazz"))),(0,a.createElement)("span",{className:"oz-score__grade"},He)),(0,a.createElement)("div",{className:"oz-score__body"},(0,a.createElement)("h2",null,(0,i.__)("Performance score","optimazz")),(0,a.createElement)("p",null,Fe?`${(0,i.__)("Biggest remaining win:","optimazz")} ${Fe.label} (+${Fe.w})`:(0,i.__)("Every optimization is enabled. Nicely done!","optimazz")),(0,a.createElement)("div",{className:"oz-score__chips"},De.map(e=>(0,a.createElement)("span",{key:e.label,className:"oz-chip "+(e.on?"is-on":"")},(0,a.createElement)("span",{className:"dashicons dashicons-"+(e.on?"yes-alt":"marker")}),e.label))))),(0,a.createElement)("div",{className:"oz-kpis"},(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,a.createElement)("strong",null,B?null!==(e=B.pages)&&void 0!==e?e:B.files:(0,a.createElement)(o.Spinner,null)),(0,a.createElement)("span",null,(0,i.__)("Cached pages","optimazz"))),(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-chart-area"}),(0,a.createElement)("strong",null,B?we(B.bytes):(0,a.createElement)(o.Spinner,null)),(0,a.createElement)("span",null,(0,i.__)("Cache size","optimazz"))),(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,a.createElement)("strong",null,ke,"/",z.length),(0,a.createElement)("span",null,(0,i.__)("Modules active","optimazz")))),ee&&!ee.error&&(ee.items||[]).length>0&&(0,a.createElement)(N,{title:(0,i.__)("Recommendations","optimazz"),description:(0,i.__)("What to fix next to speed up your site — one click each.","optimazz")},(0,a.createElement)("ul",{className:"oz-recs"},(ee.items||[]).map(e=>(0,a.createElement)("li",{key:e.id,className:`oz-rec oz-rec--${e.severity}`},(0,a.createElement)("span",{className:"oz-rec__dot dashicons "+("high"===e.severity?"dashicons-warning":"medium"===e.severity?"dashicons-info":"dashicons-marker")}),(0,a.createElement)("span",{className:"oz-rec__text"},(0,a.createElement)("strong",null,e.title),(0,a.createElement)("small",null,e.desc)),e.fix?(0,a.createElement)(o.Button,{className:"oz-btn-primary oz-rec__btn",isBusy:A,onClick:()=>(async e=>{if(!e||!e.module||!e.key)return;const a={...S,[e.module]:{...S[e.module],[e.key]:e.value}};await se(a),ae(null)})(e.fix)},(0,i.__)("Fix it","optimazz")):e.link?(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-rec__btn",onClick:()=>L(e.link)},(0,i.__)("Open","optimazz")):null)))),ee&&!ee.error&&0===(ee.items||[]).length&&(0,a.createElement)(N,{title:(0,i.__)("Recommendations","optimazz")},(0,a.createElement)("p",{className:"oz-recs-clear"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,i.__)("Nice — everything we recommend is already on. Your site is well optimized.","optimazz"))),(0,a.createElement)("div",{className:"oz-quick"},(0,a.createElement)(N,{title:(0,i.__)("Cache Files","optimazz"),description:(0,i.__)("Clear and preload all cache files.","optimazz")},(0,a.createElement)("div",{className:"oz-quick__actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",isBusy:!!ze?.running,onClick:async()=>{await re(),ce()}},(0,i.__)("Clear & Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",onClick:ce},(0,i.__)("Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-btn--x",icon:"dismiss",onClick:re},(0,i.__)("Purge cache","optimazz"))),(0,a.createElement)("ul",{className:"oz-cachelist"},(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-media-text"}),(0,i.__)("Cached pages","optimazz"),(0,a.createElement)("strong",null,B?null!==(n=B.pages)&&void 0!==n?n:B.files:"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,i.__)("Page cache size","optimazz"),(0,a.createElement)("strong",null,B?we(B.bytes):"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-admin-appearance"}),(0,i.__)("Optimized CSS","optimazz"),(0,a.createElement)("strong",null,B?`${B.css_files} (${we(B.css_bytes)})`:"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-clock"}),(0,i.__)("Cache lifespan","optimazz"),(0,a.createElement)(v,{compact:!0,disabled:A,label:(0,i.__)("Cache lifespan","optimazz"),value:Number(null!==(s=Ce.lifespan_hours)&&void 0!==s?s:0),options:(We=[{value:0,label:(0,i.__)("Never expire","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],Ie=Number(null!==(c=Ce.lifespan_hours)&&void 0!==c?c:0),qe=e=>(0,i.sprintf)(/* translators: %d: number of hours. */ /* translators: %d: number of hours. */
    4 (0,i._n)("%d hour","%d hours",e,"optimazz"),e),We.some(e=>e.value===Ie)?We:[...We,{value:Ie,label:qe(Ie)}].sort((e,a)=>e.value-a.value)),onChange:e=>se({...S,cache:{...S.cache,lifespan_hours:e}})})),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-update"}),(0,a.createElement)("span",{className:"oz-row-label"},(0,i.__)("Auto-preload","optimazz"),(0,a.createElement)("small",null,B?B.preload&&B.next_preload?B.next_preload_overdue?(0,i.__)("next run: due now","optimazz"):`${(0,i.__)("next run in","optimazz")} ${B.next_preload}`:(0,i.__)("off","optimazz"):"")),(0,a.createElement)(v,{compact:!0,disabled:A,label:(0,i.__)("Auto-preload frequency","optimazz"),value:Ce.preload?Ce.preload_interval||"twicedaily":"off",options:[{value:"off",label:(0,i.__)("Off","optimazz")},{value:"hourly",label:(0,i.__)("Every hour","optimazz")},{value:"twicedaily",label:(0,i.__)("Twice a day","optimazz")},{value:"daily",label:(0,i.__)("Once a day","optimazz")}],onChange:e=>{se({...S,cache:{...S.cache,preload:"off"!==e,preload_interval:"off"===e?Ce.preload_interval||"twicedaily":e}})}})),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-backup"}),(0,i.__)("Last auto-preload","optimazz"),(0,a.createElement)("strong",null,B?B.last_preload?(0,i.sprintf)(/* translators: 1: time ago, e.g. "2 hours", 2: number of pages */ /* translators: 1: time ago, e.g. "2 hours", 2: number of pages */
    5 (0,i.__)("%1$s ago · %2$d pages","optimazz"),B.last_preload.ago,B.last_preload.warmed)+(B.last_preload.blocked?` · ${(0,i.__)("blocked by firewall","optimazz")}`:""):(0,i.__)("not yet","optimazz"):"—")))),(0,a.createElement)(N,{title:(0,i.__)("Module Status","optimazz"),description:(0,i.__)("Enable or disable any module right here.","optimazz")},(0,a.createElement)("ul",{className:"oz-status"},d.filter(e=>e.mod).map(e=>(0,a.createElement)("li",{key:e.key},(0,a.createElement)(w,{name:e.key}),(0,a.createElement)("button",{className:"oz-status__name",onClick:()=>L(e.key)},e.title),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:!!S[e.mod]?.enabled,onChange:()=>K(e.mod,"enabled")}))),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-editor-code"}),(0,a.createElement)("button",{className:"oz-status__name",onClick:()=>L("files")},(0,i.__)("File Optimization","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:!(!Ne.enabled&&!xe.enabled),onChange:()=>{const e=!(Ne.enabled||xe.enabled);V("js","enabled",e),V("css","enabled",e)}}))))));var We,Ie,qe;const Ue=(0,a.createElement)(b,{icon:"database",title:(0,i.__)("Page Cache","optimazz"),description:(0,i.__)("Serve static HTML instead of running PHP on every request.","optimazz"),enabled:!!Ce.enabled,onToggle:()=>K("cache","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache mobile devices separately","optimazz"),checked:!!Ce.cache_mobile,onChange:()=>K("cache","cache_mobile")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache logged-in users","optimazz"),help:(0,i.__)("Leave off unless you know what you are doing.","optimazz"),checked:!!Ce.cache_logged_in,onChange:()=>K("cache","cache_logged_in")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Browser cache headers","optimazz"),help:(0,i.__)("Add Expires / Cache-Control rules so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only (written to .htaccess); ignored on Nginx.","optimazz"),checked:!!Ce.browser_cache,onChange:()=>K("cache","browser_cache")}),(0,a.createElement)(C,{label:(0,i.__)("Cache lifetime","optimazz"),help:(0,i.__)("How long a cached page is kept before it is refreshed. Pages are always purged immediately when their content changes.","optimazz"),value:null!==(g=Ce.lifespan_hours)&&void 0!==g?g:0,options:[{value:0,label:(0,i.__)("Never","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],onChange:e=>V("cache","lifespan_hours",e)}),(0,a.createElement)("div",{className:"oz-health"},(0,a.createElement)("div",{className:"oz-health__head"},(0,a.createElement)("strong",null,(0,i.__)("Cache health","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",isBusy:O,onClick:oe},(0,i.__)("Check now","optimazz"))),(0,a.createElement)("p",{className:"oz-health__hint"},(0,i.__)("Confirms caching is actually serving pages. If it is not, the plugin rebuilds every page on each request — which is slower, not faster.","optimazz")),P&&!P.error&&(0,a.createElement)("ul",{className:"oz-health__list"},(0,a.createElement)(k,{ok:"hit"===P.live,warn:"miss"===P.live,label:"hit"===P.live?(0,i.__)("Pages ARE being served from cache (HIT)","optimazz"):"miss"===P.live?(0,i.__)("Pages are NOT being served from cache (MISS) — see below","optimazz"):(0,i.__)("Live check unavailable","optimazz")}),(0,a.createElement)(k,{ok:P.dropin_installed&&P.dropin_ours,warn:P.conflict,label:P.conflict?P.conflict_name?`${(0,i.__)("advanced-cache.php belongs to","optimazz")} ${P.conflict_name} — ${(0,i.__)("take it over below","optimazz")}`:(0,i.__)("advanced-cache.php belongs to ANOTHER plugin — take it over below","optimazz"):P.dropin_ours?(0,i.__)("advanced-cache.php drop-in installed","optimazz"):(0,i.__)("advanced-cache.php drop-in is missing","optimazz")}),(0,a.createElement)(k,{ok:P.wp_cache,label:P.wp_cache?(0,i.__)("WP_CACHE is enabled in wp-config.php","optimazz"):(0,i.__)("WP_CACHE is not enabled in wp-config.php","optimazz")}),!P.wpconfig_writable&&(0,a.createElement)(k,{warn:!0,label:(0,i.__)("wp-config.php is not writable — add the WP_CACHE line manually (below)","optimazz")}),(0,a.createElement)(k,{ok:P.warmed>0,label:`${P.warmed} ${(0,i.__)("pages warmed in cache","optimazz")}`})),P&&!P.error&&(P.conflict||!P.wp_cache&&P.wpconfig_writable||!P.dropin_ours)&&P.enabled&&(0,a.createElement)("div",{className:"oz-health__fix"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:O,onClick:le},(0,i.__)("Take over caching","optimazz")),(0,a.createElement)("span",null,(0,i.__)("Make Optimazz the caching plugin — installs its drop-in and enables WP_CACHE.","optimazz"))),P&&!P.error&&Array.isArray(P.active_conflicts)&&P.active_conflicts.length>0&&(0,a.createElement)("div",{className:"oz-health__conflict"},P.active_conflicts.map(e=>(0,a.createElement)("div",{className:"oz-health__conflict-row",key:e.file},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,`${e.name} ${(0,i.__)("is still active and may reclaim caching. Deactivate it so Optimazz keeps serving the cache.","optimazz")}`),(0,a.createElement)(o.Button,{className:"oz-btn-danger",isBusy:O,onClick:()=>ne(e.file,e.name)},`${(0,i.__)("Deactivate","optimazz")} ${e.name}`)))),P&&!P.error&&!P.wp_cache&&!P.wpconfig_writable&&(0,a.createElement)("div",{className:"oz-health__manual"},(0,a.createElement)("p",null,(0,i.__)("wp-config.php is not writable. Add this line just after the opening <?php tag:","optimazz")),(0,a.createElement)("code",null,"define( 'WP_CACHE', true );")),P&&P.error&&(0,a.createElement)("p",{className:"oz-health__err"},(0,i.__)("Could not run the check. Try reloading the page.","optimazz")))),Je=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(b,{icon:"editor-code",title:(0,i.__)("JavaScript","optimazz"),description:(0,i.__)("Defer and delay scripts to cut Total Blocking Time.","optimazz"),enabled:!!Ne.enabled,onToggle:()=>K("js","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Minify JavaScript","optimazz"),help:(0,i.__)("Strip comments and whitespace from inline scripts. Line breaks are kept for safety; test your site after enabling.","optimazz"),checked:!!Ne.minify,onChange:()=>K("js","minify")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Defer JavaScript","optimazz"),help:(0,i.__)("Load scripts without blocking page render.","optimazz"),checked:!!Ne.defer,onChange:()=>K("js","defer")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Delay JS until interaction","optimazz"),help:(0,i.__)("Run scripts only after the first user interaction.","optimazz"),checked:!!Ne.delay,onChange:()=>K("js","delay")}),Ne.delay&&(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Delay all scripts","optimazz"),help:(0,i.__)("Delay every script until interaction. jQuery, your theme, and Elementor are always kept safe. Still, on sites where content is rendered by JavaScript this can delay the visible page — test before keeping it on.","optimazz"),checked:!!Ne.delay_all,onChange:()=>K("js","delay_all")}),Ne.delay&&Ne.delay_all&&(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)('Delaying all scripts can slow the first paint on JavaScript-rendered sites (Elementor, sliders). If your PageSpeed FCP/LCP get worse, switch to "Delay selected" and delay only tracking scripts.',"optimazz"))),Ne.delay&&!Ne.delay_all&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Delay these scripts","optimazz"),help:(0,i.__)("Only scripts matching these keywords are delayed. Match by filename, handle or URL — e.g. gtag, analytics, facebook.","optimazz"),placeholder:(0,i.__)("e.g. gtag, analytics","optimazz"),value:Ne.delay_list||[],onChange:e=>V("js","delay_list",e)}),(Ne.defer||Ne.delay)&&(0,a.createElement)("div",{className:"oz-fieldgroup"},(0,a.createElement)("h4",{className:"oz-fieldgroup__title"},(0,i.__)("Exclude specific scripts","optimazz")),(0,a.createElement)("p",{className:"oz-fieldgroup__desc"},(0,i.__)("If a script breaks, exclude it from just one optimization instead of both. Match by filename, script handle, or part of the URL.","optimazz")),Ne.defer&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never defer","optimazz"),help:(0,i.__)("Keep loading these render-blocking, but still allow delay. Use for scripts that must run during page load.","optimazz"),placeholder:(0,i.__)("e.g. jquery.min.js","optimazz"),value:Ne.no_defer||[],onChange:e=>V("js","no_defer",e)}),Ne.delay&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never delay","optimazz"),help:(0,i.__)("Run these before any interaction, but still allow defer. Use for sliders, above-the-fold, or consent scripts.","optimazz"),placeholder:(0,i.__)("e.g. slider, swiper","optimazz"),value:Ne.no_delay||[],onChange:e=>V("js","no_delay",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never optimize","optimazz"),help:(0,i.__)("Skip both defer and delay — leave these scripts exactly as they are.","optimazz"),placeholder:(0,i.__)("e.g. recaptcha","optimazz"),value:Ne.exclusions||[],onChange:e=>V("js","exclusions",e)}))),(0,a.createElement)(b,{icon:"admin-appearance",title:(0,i.__)("CSS","optimazz"),description:(0,i.__)("Minify, combine, and remove render-blocking stylesheets.","optimazz"),enabled:!!xe.enabled,onToggle:()=>K("css","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Minify CSS","optimazz"),checked:!!xe.minify,onChange:()=>K("css","minify")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Combine CSS files","optimazz"),help:(0,i.__)("Merge stylesheets to reduce requests.","optimazz"),checked:!!xe.combine,onChange:()=>K("css","combine")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load CSS asynchronously","optimazz"),help:(0,i.__)("Removes render-blocking CSS. Paste your above-the-fold (critical) CSS below, or the page may briefly flash unstyled.","optimazz"),checked:!!xe.async_load,onChange:()=>K("css","async_load")}),xe.async_load&&(0,a.createElement)(a.Fragment,null,!(xe.critical_css||"").trim()&&(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("No critical CSS provided. Async loading is on, so visitors may see a flash of unstyled content until you paste critical CSS below.","optimazz"))),(0,a.createElement)(o.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Critical CSS (above-the-fold)","optimazz"),help:(0,i.__)("Paste the above-the-fold CSS to inline. Generate it with a free tool such as a critical-CSS generator. Leave empty to disable inlining.","optimazz"),value:xe.critical_css||"",onChange:e=>V("css","critical_css",e),rows:6})),(0,a.createElement)("div",{className:"oz-subhead"},(0,i.__)("Remove unused CSS","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove block-editor CSS on the front end","optimazz"),help:(0,i.__)("Dequeues WordPress block-library and global styles. Turn on only if your front end does NOT render Gutenberg blocks (e.g. a page-builder or classic theme). Big byte saving when it applies.","optimazz"),checked:!!xe.remove_block_css,onChange:()=>K("css","remove_block_css")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load WooCommerce CSS only on shop pages","optimazz"),help:(0,i.__)("Dequeues WooCommerce styles on non-shop pages (kept on cart, checkout, account, product and shop). Turn off if you show Woo products or widgets on other pages.","optimazz"),checked:!!xe.remove_woo_css,onChange:()=>K("css","remove_woo_css")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load Easy Digital Downloads CSS only on EDD pages","optimazz"),help:(0,i.__)("Dequeues EDD styles on non-EDD pages (kept on checkout, purchase-confirmation, purchase history and download archives). Turn off if you show download or purchase links elsewhere.","optimazz"),checked:!!xe.remove_edd_css,onChange:()=>K("css","remove_edd_css")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Excluded CSS files (keywords)","optimazz"),value:xe.exclusions||[],onChange:e=>V("css","exclusions",e)}))),Ge=(0,a.createElement)(b,{icon:"format-image",title:(0,i.__)("Image Optimization","optimazz"),description:(0,i.__)("Lazy-load, prevent layout shift, serve next-gen formats, and lighten media.","optimazz"),enabled:!!Se.enabled,onToggle:()=>K("media","enabled")},pe&&!pe.can_process&&(0,a.createElement)("div",{className:"oz-notice oz-notice--error"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)('This server has no GD or ImageMagick extension, so image compression and WebP/AVIF cannot run — every image will be skipped. Ask your host to enable "php-gd" (or "php-imagick"). See Tools → Server Requirements.',"optimazz"))),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load images","optimazz"),help:(0,i.__)("Load off-screen images only when scrolled into view.","optimazz"),checked:!!Se.lazy_load,onChange:()=>K("media","lazy_load")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load iframes","optimazz"),checked:!!Se.lazy_iframes,onChange:()=>K("media","lazy_iframes")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load CSS background images","optimazz"),help:(0,i.__)("Defer inline background-image elements (test with page builders).","optimazz"),checked:!!Se.lazy_bg,onChange:()=>K("media","lazy_bg")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Replace YouTube embeds with a preview","optimazz"),help:(0,i.__)("Load the video iframe only when the visitor clicks play.","optimazz"),checked:!!Se.youtube_preview,onChange:()=>K("media","youtube_preview")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Add missing image dimensions","optimazz"),help:(0,i.__)("Set width/height to prevent layout shift (CLS).","optimazz"),checked:!!Se.add_dimensions,onChange:()=>K("media","add_dimensions")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Preload the LCP image","optimazz"),help:(0,i.__)("Preload the first/hero image with high priority.","optimazz"),checked:!!Se.preload_lcp,onChange:()=>K("media","preload_lcp")}),Se.lazy_load&&(0,a.createElement)(C,{label:(0,i.__)("Keep first images eager (LCP)","optimazz"),help:(0,i.__)("Do not lazy-load the first N images, to protect LCP.","optimazz"),value:null!==(E=Se.skip_first)&&void 0!==E?E:1,onChange:e=>V("media","skip_first",e),options:[0,1,2,3,4,5].map(e=>({label:String(e),value:e}))}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Serve WebP via <picture>","optimazz"),help:(0,i.__)("Deliver WebP to supporting browsers, original as fallback.","optimazz"),checked:!!Se.webp,onChange:()=>K("media","webp")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Generate WebP/AVIF on upload","optimazz"),help:pe&&!pe.can_webp?(0,i.__)("WebP generation is not available on this server.","optimazz"):(0,i.__)("Auto-create next-gen copies for newly uploaded images.","optimazz"),checked:!!Se.webp_generate,onChange:()=>K("media","webp_generate")}),pe?.can_avif&&(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Also generate/serve AVIF","optimazz"),help:(0,i.__)("Smaller than WebP; served first when supported.","optimazz"),checked:!!Se.avif,onChange:()=>K("media","avif")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Compress & resize on upload","optimazz"),help:(0,i.__)("Recompress, cap dimensions, and strip metadata from new uploads.","optimazz"),checked:!!Se.compress,onChange:()=>K("media","compress")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Convert opaque PNG to JPEG","optimazz"),help:(0,i.__)("Re-save non-transparent PNGs as much smaller JPEGs during bulk optimization.","optimazz"),checked:!!Se.png_to_jpg,onChange:()=>K("media","png_to_jpg")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Keep a backup of originals","optimazz"),help:(0,i.__)("Copy originals to uploads/optimazz-backup before optimizing.","optimazz"),checked:!!Se.backup_originals,onChange:()=>K("media","backup_originals")}),(0,a.createElement)(o.RangeControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Image quality","optimazz"),value:null!==(y=Se.quality)&&void 0!==y?y:82,onChange:e=>V("media","quality",e),min:40,max:100,help:(0,i.__)("Used for compression and WebP/AVIF generation.","optimazz")}),(0,a.createElement)(o.RangeControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Max image width (px)","optimazz"),value:null!==(x=Se.max_width)&&void 0!==x?x:2560,onChange:e=>V("media","max_width",e),min:800,max:4e3,step:80}),(0,a.createElement)("div",{className:"oz-convert"},(0,a.createElement)("div",{className:"oz-convert__info"},(0,a.createElement)("strong",null,(0,i.__)("WebP coverage","optimazz")),pe?(0,a.createElement)("span",null,pe.converted," / ",pe.images," ",(0,i.__)("images converted","optimazz")):(0,a.createElement)("span",null,(0,i.__)("Loading…","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"images-alt2",isBusy:ze?.running,disabled:pe&&!pe.can_webp||ze?.running,onClick:()=>ve("convert")},(0,i.__)("Convert to WebP","optimazz"))),(0,a.createElement)("div",{className:"oz-convert"},(0,a.createElement)("div",{className:"oz-convert__info"},(0,a.createElement)("strong",null,(0,i.__)("Bulk compression","optimazz")),pe?(0,a.createElement)("span",null,pe.optimized," ",(0,i.__)("optimized","optimazz")," · ",we(pe.saved)," ",(0,i.__)("saved","optimazz")):(0,a.createElement)("span",null,(0,i.__)("Loading…","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"filter",isBusy:ze?.running,disabled:ze?.running,onClick:()=>ve("compress")},(0,i.__)("Compress existing images","optimazz")))),Ve=(0,a.createElement)(N,{title:(0,i.__)("Preload Cache","optimazz"),description:(0,i.__)("Warm the cache automatically so visitors never hit a cold page.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Activate preloading","optimazz"),help:(0,i.__)("Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop.","optimazz"),checked:!!Ce.preload,onChange:()=>K("cache","preload")}),(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Automatic preload","optimazz"),help:(0,i.__)("How often to re-warm the whole site. Choose “Every hour” if your content changes often or you use a short cache lifespan.","optimazz"),value:Ce.preload_interval||"twicedaily",disabled:!Ce.preload,options:[{label:(0,i.__)("Every hour","optimazz"),value:"hourly"},{label:(0,i.__)("Twice a day","optimazz"),value:"twicedaily"},{label:(0,i.__)("Once a day","optimazz"),value:"daily"}],onChange:e=>V("cache","preload_interval",e)}),B&&B.wp_cron_disabled&&(0,a.createElement)("div",{className:"oz-notice oz-notice--warn",style:{marginTop:12}},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("WP-Cron is disabled on this site (DISABLE_WP_CRON). Scheduled preloads only run if a server cron job calls wp-cron.php — ask your host to set one up (e.g. every 5 minutes).","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"update",isBusy:!!ze?.running,onClick:ce},(0,i.__)("Preload now","optimazz")))),Ke=(0,a.createElement)(N,{title:(0,i.__)("Advanced Rules","optimazz"),description:(0,i.__)("Fine-tune which requests are cached.","optimazz")},(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache these URLs (contains)","optimazz"),value:Ce.excluded_uris||[],onChange:e=>V("cache","excluded_uris",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache when these cookies are set","optimazz"),value:Ce.excluded_cookies||[],onChange:e=>V("cache","excluded_cookies",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache for these user agents","optimazz"),value:Ce.excluded_user_agents||[],onChange:e=>V("cache","excluded_user_agents",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Ignore these query arguments","optimazz"),value:Ce.ignored_query_args||[],onChange:e=>V("cache","ignored_query_args",e)})),Ye=!(!S.tools||!S.tools.safe_mode),Ze=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(N,{title:(0,i.__)("Safe Mode","optimazz"),description:(0,i.__)("If a theme or page builder looks broken after optimizing, turn this on. It pauses the riskiest optimizations — combine CSS, async CSS, remove block CSS and JavaScript delay — while keeping caching, minify, defer and lazy-load. Your saved settings are not changed; they simply pause until you turn Safe Mode off.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Pause risky optimizations (Safe Mode)","optimazz"),checked:Ye,onChange:e=>{const a={...S,tools:{...S.tools||{},safe_mode:e}};M(a),se(a)}}),Ye&&(0,a.createElement)("div",{className:"oz-notice oz-notice--warn",style:{marginTop:12}},(0,a.createElement)("span",{className:"dashicons dashicons-shield"}),(0,a.createElement)("span",null,(0,i.__)("Safe Mode is ON — combine/async CSS, remove-block-CSS and JavaScript delay are paused site-wide. Turn it off to resume full optimization.","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions",style:{marginTop:14}},(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"undo",disabled:!q,onClick:async()=>{try{const e=await l()({url:`${r}/settings/revert`,method:"POST"});if("nothing"===e.message)return U(!1),void ie((0,i.__)("Nothing to undo.","optimazz"),"error");$(e.settings),M(e.settings),U(!!e.hasBackup),ae(null),ie((0,i.__)("Reverted to the previous settings.","optimazz")),Y()}catch(e){ie(e.message||(0,i.__)("Undo failed.","optimazz"),"error")}}},(0,i.__)("Undo last change","optimazz")),(0,a.createElement)("span",{className:"oz-muted",style:{fontSize:12}},q?(0,i.__)("Restores the settings from just before your last save, reset or Safe Mode toggle.","optimazz"):(0,i.__)("Nothing to undo yet — this becomes available after your next change.","optimazz")))),(0,a.createElement)(N,{title:(0,i.__)("Server Requirements","optimazz"),description:(0,i.__)("What your host supports. Warnings here explain why a feature (like WebP/AVIF) may be unavailable or slow.","optimazz")},!Z&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Checking your server…","optimazz")),Z&&Z.error&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Could not read the server report.","optimazz")),Z&&!Z.error&&(0,a.createElement)("ul",{className:"oz-reqs"},(Z.checks||[]).map((e,t)=>(0,a.createElement)("li",{key:t,className:`oz-req oz-req--${e.status}`},(0,a.createElement)("span",{className:"oz-req__dot dashicons "+("ok"===e.status?"dashicons-yes-alt":"warn"===e.status?"dashicons-warning":"info"===e.status?"dashicons-minus":"dashicons-dismiss")}),(0,a.createElement)("span",{className:"oz-req__label"},e.label),(0,a.createElement)("span",{className:"oz-req__value"},e.value),e.note&&(0,a.createElement)("span",{className:"oz-req__note"},e.note)))),(0,a.createElement)("div",{className:"oz-inline-actions",style:{marginTop:14}},(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",isBusy:!Z,onClick:()=>{Q(null),X()}},(0,i.__)("Re-check","optimazz")))),(0,a.createElement)(N,{title:(0,i.__)("Import / Export Settings","optimazz"),description:(0,i.__)("Back up your configuration or copy it to another site.","optimazz")},(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"download",onClick:()=>{const e=new window.Blob([JSON.stringify(S,null,2)],{type:"application/json"}),a=document.createElement("a");a.href=URL.createObjectURL(e),a.download="optimazz-settings.json",a.click(),URL.revokeObjectURL(a.href)}},(0,i.__)("Export settings","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"upload",onClick:()=>J.current?.click()},(0,i.__)("Import settings","optimazz")),(0,a.createElement)("input",{ref:J,type:"file",accept:"application/json",style:{display:"none"},onChange:e=>{const a=e.target.files?.[0];if(!a)return;const t=new window.FileReader;t.onload=()=>{try{const e=JSON.parse(t.result);se(e)}catch(e){ie((0,i.__)("Invalid settings file.","optimazz"),"error")}},t.readAsText(a),e.target.value=""}}))),(0,a.createElement)(N,{title:(0,i.__)("Reset","optimazz"),description:(0,i.__)("Restore every module to its default configuration. This cannot be undone.","optimazz")},(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-danger",icon:"image-rotate",onClick:async()=>{if(window.confirm((0,i.__)('Reset all Optimazz settings to defaults? You can undo this with "Undo last change".',"optimazz")))try{const e=await l()({url:`${r}/settings/reset`,method:"POST"});$(e.settings),M(e.settings),U(!!e.hasBackup),ie((0,i.__)("Settings reset to defaults.","optimazz")),Y()}catch(e){ie(e.message||(0,i.__)("Reset failed.","optimazz"),"error")}}},(0,i.__)("Reset all settings to defaults","optimazz"))))),Qe=(0,a.createElement)(b,{icon:"editor-textcolor",title:(0,i.__)("Fonts","optimazz"),description:(0,i.__)("Swap, preconnect, and preload fonts to remove render-blocking requests.","optimazz"),enabled:!!Me.enabled,onToggle:()=>K("fonts","enabled")},(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Font loading (font-display)","optimazz"),help:(0,i.__)("Optional = no layout shift (best for CLS): the web font is skipped on the first uncached load and used once cached. Swap = text stays visible but the web font swaps in and can shift layout (higher CLS). Block = brief invisible text, then the web font.","optimazz"),value:Me.font_display||(Me.display_swap,"swap"),options:[{label:(0,i.__)("Optional — no layout shift (recommended for CLS)","optimazz"),value:"optional"},{label:(0,i.__)("Swap — always show text (may shift layout)","optimazz"),value:"swap"},{label:(0,i.__)("Block — brief hidden text, then web font","optimazz"),value:"block"}],onChange:e=>V("fonts","font_display",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Combine Google Fonts into one request","optimazz"),help:(0,i.__)("Page builders often load each Google Font family separately. This merges them into a single request.","optimazz"),checked:!!Me.combine_google,onChange:e=>V("fonts","combine_google",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load only the font weights your pages use","optimazz"),help:(0,i.__)("Drops unused weights (e.g. 100, 200, 900) and italics your pages never use — often cutting font CSS by more than half. Weights 400 and 700 are always kept. Turn off if some text looks thinner or bolder than intended.","optimazz"),checked:!!Me.trim_weights,disabled:!Me.combine_google,onChange:e=>V("fonts","trim_weights",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Preload these font files (URLs)","optimazz"),value:Me.preload||[],onChange:e=>V("fonts","preload",e)})),Xe=e=>{var a;if(!me||void 0===me[e.key])return e.help;const t=null!==(a=me[e.key])&&void 0!==a?a:0;
    6 /* translators: %d: number of items found in the database. */return`${(0,i.sprintf)((0,i._n)("%d item in your database.","%d items in your database.",t,"optimazz"),t)} ${e.help}`},ea=(0,a.createElement)(N,{title:(0,i.__)("Database Optimization","optimazz"),description:(0,i.__)("Reduce bloat and reclaim space. Once a cleanup runs it cannot be undone, so back up your database first.","optimazz")},h.map(e=>(0,a.createElement)("div",{key:e.title,className:"oz-dbgroup"},(0,a.createElement)("h3",{className:"oz-dbgroup__title"},e.title),(0,a.createElement)("p",{className:"oz-dbgroup__desc"},e.desc),e.items.map(e=>{var t;return(0,a.createElement)(o.ToggleControl,{key:e.key,__nextHasNoMarginBottom:!0,label:me?`${e.label} (${null!==(t=me[e.key])&&void 0!==t?t:0})`:e.label,help:Xe(e),checked:!!Te[e.key],onChange:()=>K("database",e.key)})}))),(0,a.createElement)("div",{className:"oz-dbgroup"},(0,a.createElement)("h3",{className:"oz-dbgroup__title"},(0,i.__)("Set it and forget it","optimazz")),(0,a.createElement)("p",{className:"oz-dbgroup__desc"},(0,i.__)("Let Optimazz repeat the cleanups you picked above on a schedule, so your database stays lean without you lifting a finger.","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Schedule automatic cleanup","optimazz"),help:(0,i.__)("Uses WP-Cron to repeat the cleanups you enabled above.","optimazz"),checked:!!Te.enabled,onChange:()=>K("database","enabled")}),Te.enabled&&(0,a.createElement)(C,{label:(0,i.__)("Cleanup frequency","optimazz"),value:Te.schedule||"off",onChange:e=>V("database","schedule",e),options:[{label:(0,i.__)("Off","optimazz"),value:"off"},{label:(0,i.__)("Daily","optimazz"),value:"daily"},{label:(0,i.__)("Weekly","optimazz"),value:"weekly"},{label:(0,i.__)("Monthly","optimazz"),value:"monthly"}]})),(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("Back up your database before you run a cleanup — a database optimization cannot be undone.","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",isBusy:!!ze?.running,onClick:async()=>{if(ze?.running)return;const e=(0,i.__)("Cleaning and optimizing database…","optimazz");he({title:(0,i.__)("Optimizing database","optimazz"),subtitle:(0,i.__)("Cleanup & table optimization","optimazz"),pct:100,indeterminate:!0,label:e,step:"",logs:[{level:"INFO",text:e}],running:!0,status:"running",minimized:!1});try{const e=await l()({url:`${r}/database/optimize`,method:"POST",data:S.database||{}});de(e.counts);const a=Object.values(e.removed||{}).reduce((e,a)=>e+a,0),t=`${(0,i.__)("Cleaned","optimazz")} ${a} ${(0,i.__)("rows,","optimazz")} ${e.tables} ${(0,i.__)("tables optimized.","optimazz")}`;he(e=>e&&{...e,running:!1,status:"done",indeterminate:!1,pct:100,label:t,logs:[...e.logs,{level:"INFO",text:t},{level:"DONE",text:(0,i.__)("Completed.","optimazz")}]}),ie(t)}catch(e){he(a=>a&&{...a,running:!1,status:"error",indeterminate:!1,logs:[...a.logs,{level:"ERROR",text:e.message||"Error"}]}),ie(e.message||(0,i.__)("Optimize failed.","optimazz"),"error")}}},(0,i.__)("Optimize now","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",onClick:ye},(0,i.__)("Refresh counts","optimazz")))),aa={dashboard:je,cache:Ue,files:Je,media:Ge,fonts:Qe,preload:Ve,advanced:Ke,database:ea,heartbeat:(0,a.createElement)(N,{title:(0,i.__)("Heartbeat Control","optimazz"),description:(0,i.__)("Reduce or disable the WordPress Heartbeat API to lower server load.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Enable Heartbeat control","optimazz"),checked:!!$e.enabled,onChange:()=>K("heartbeat","enabled")}),$e.enabled&&(0,a.createElement)(a.Fragment,null,(0,a.createElement)(C,{label:(0,i.__)("Behavior in admin dashboard","optimazz"),value:$e.backend||"default",options:u,onChange:e=>V("heartbeat","backend",e)}),(0,a.createElement)(C,{label:(0,i.__)("Behavior in post editor","optimazz"),value:$e.editor||"default",options:u,onChange:e=>V("heartbeat","editor",e)}),(0,a.createElement)(C,{label:(0,i.__)("Behavior on front-end","optimazz"),value:$e.frontend||"default",options:u,onChange:e=>V("heartbeat","frontend",e)}))),tweaks:(0,a.createElement)(b,{icon:"admin-generic",title:(0,i.__)("Tweaks","optimazz"),description:(0,i.__)("Strip unnecessary WordPress front-end weight.","optimazz"),enabled:!!Be.enabled,onToggle:()=>K("tweaks","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable emojis","optimazz"),help:(0,i.__)("Remove the emoji detection script and styles.","optimazz"),checked:!!Be.disable_emojis,onChange:()=>K("tweaks","disable_emojis")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable embeds (oEmbed)","optimazz"),checked:!!Be.disable_embeds,onChange:()=>K("tweaks","disable_embeds")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable dashicons on front-end","optimazz"),help:(0,i.__)("Only for anonymous visitors.","optimazz"),checked:!!Be.disable_dashicons,onChange:()=>K("tweaks","disable_dashicons")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove query strings from assets","optimazz"),help:(0,i.__)("Strip ?ver= for better proxy caching.","optimazz"),checked:!!Be.remove_query_strings,onChange:()=>K("tweaks","remove_query_strings")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable XML-RPC","optimazz"),checked:!!Be.disable_xmlrpc,onChange:()=>K("tweaks","disable_xmlrpc")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable self-pingbacks","optimazz"),checked:!!Be.disable_pingback,onChange:()=>K("tweaks","disable_pingback")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove head meta (generator, RSD, shortlink)","optimazz"),checked:!!Be.remove_meta,onChange:()=>K("tweaks","remove_meta")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("DNS-prefetch domains","optimazz"),value:Be.dns_prefetch||[],onChange:e=>V("tweaks","dns_prefetch",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Preconnect origins","optimazz"),value:Be.preconnect||[],onChange:e=>V("tweaks","preconnect",e)})),cdn:(0,a.createElement)(b,{icon:"cloud",title:(0,i.__)("Content Delivery Network","optimazz"),description:(0,i.__)("Rewrite static asset URLs to your CDN hostname.","optimazz"),enabled:!!Re.enabled,onToggle:()=>K("cdn","enabled")},(0,a.createElement)(o.TextControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("CDN URL (CNAME)","optimazz"),placeholder:"https://cdn.example.com",value:Re.url||"",onChange:e=>V("cdn","url",e),help:(0,i.__)("CSS, JS, image, and font URLs will be served from here.","optimazz")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Exclude paths containing","optimazz"),value:Re.exclusions||[],onChange:e=>V("cdn","exclusions",e)})),tools:Ze};return p.forEach(e=>{aa[e.key]="function"==typeof e.render?e.render():e.render}),(0,a.createElement)("div",{className:"oz-shell"},(0,a.createElement)("aside",{className:"oz-sidebar"},(0,a.createElement)("div",{className:"oz-sidebar__brand"},window.optimazzData?.logoUrl?(0,a.createElement)("img",{className:"oz-logo-img",src:window.optimazzData.logoUrl,alt:"Optimazz"}):(0,a.createElement)("div",{className:"oz-sidebar__brand-fallback"},(0,a.createElement)("span",{className:"oz-logo dashicons dashicons-performance"}),(0,a.createElement)("strong",null,"Optimazz"))),(0,a.createElement)("nav",null,_.map(e=>(0,a.createElement)("button",{key:e.key,className:"oz-navitem "+(H===e.key?"is-active":""),onClick:()=>L(e.key)},(0,a.createElement)(w,{name:e.key,paths:e.iconSvg}),(0,a.createElement)("span",{className:"oz-navitem__text"},(0,a.createElement)("strong",null,e.title),(0,a.createElement)("small",null,e.sub)),e.mod&&S[e.mod]?.enabled&&(0,a.createElement)("i",{className:"oz-navitem__dot"})))),(0,a.createElement)("div",{className:"oz-sidebar__foot"},"v",window.optimazzData?.version)),(0,a.createElement)("div",{className:"oz-content"},(0,a.createElement)("header",{className:"oz-topbar"},(0,a.createElement)("span",{className:`oz-topbar__icon dashicons dashicons-${Pe.icon}`}),(0,a.createElement)("div",null,(0,a.createElement)("h1",null,Pe.title),(0,a.createElement)("p",null,Pe.sub))),(0,a.createElement)("main",{className:"oz-main"},aa[H])),(0,a.createElement)("div",{className:"oz-savebar "+(G?"is-dirty":"")},(0,a.createElement)("span",{className:"oz-savebar__hint"},G?(0,i.__)("You have unsaved changes","optimazz"):(0,i.__)("All changes saved","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:A,disabled:!G,onClick:()=>se(S)},(0,i.__)("Save changes","optimazz"))),W&&(0,a.createElement)("div",{className:"oz-toast"},(0,a.createElement)(o.Snackbar,{status:W.status,onRemove:()=>I(null)},W.text)),(0,a.createElement)(f,{job:ze,onStop:()=>{ue.current=!0},onClose:()=>{ue.current=!0,he(null)},onToggleMin:()=>he(e=>e&&{...e,minimized:!e.minimized})}))},null))})();
     1(()=>{"use strict";const e={n:a=>{const t=a&&a.__esModule?()=>a.default:()=>a;return e.d(t,{a:t}),t},d:(a,t)=>{for(var o in t)e.o(t,o)&&!e.o(a,o)&&Object.defineProperty(a,o,{enumerable:!0,get:t[o]})},o:(e,a)=>Object.hasOwn(e,a)},a=window.React,t=window.wp.element,o=window.wp.components,n=window.wp.apiFetch;var l=e.n(n);const i=window.wp.i18n,s=window.wp.hooks,{restUrl:r,nonce:c,settings:m}=window.optimazzData||{};l().use(l().createNonceMiddleware(c));const d=[{key:"dashboard",title:(0,i.__)("Dashboard","optimazz"),sub:(0,i.__)("Overview & quick actions","optimazz"),icon:"admin-home"},{key:"cache",title:(0,i.__)("Page Cache","optimazz"),sub:(0,i.__)("Static HTML caching","optimazz"),icon:"database",mod:"cache"},{key:"files",title:(0,i.__)("File Optimization","optimazz"),sub:(0,i.__)("Optimize CSS & JS","optimazz"),icon:"editor-code"},{key:"media",title:(0,i.__)("Media","optimazz"),sub:(0,i.__)("LazyLoad, dimensions, WebP","optimazz"),icon:"format-image",mod:"media"},{key:"fonts",title:(0,i.__)("Fonts","optimazz"),sub:(0,i.__)("Preload, swap, preconnect","optimazz"),icon:"editor-textcolor",mod:"fonts"},{key:"preload",title:(0,i.__)("Preload","optimazz"),sub:(0,i.__)("Generate cache files","optimazz"),icon:"update"},{key:"advanced",title:(0,i.__)("Advanced Rules","optimazz"),sub:(0,i.__)("Fine-tune cache rules","optimazz"),icon:"list-view"},{key:"database",title:(0,i.__)("Database","optimazz"),sub:(0,i.__)("Optimize, reduce bloat","optimazz"),icon:"database-view",mod:"database"},{key:"heartbeat",title:(0,i.__)("Heartbeat","optimazz"),sub:(0,i.__)("Control Heartbeat API","optimazz"),icon:"heart",mod:"heartbeat"},{key:"tweaks",title:(0,i.__)("Tweaks","optimazz"),sub:(0,i.__)("Remove WordPress bloat","optimazz"),icon:"admin-generic",mod:"tweaks"},{key:"cdn",title:(0,i.__)("CDN","optimazz"),sub:(0,i.__)("Rewrite asset URLs","optimazz"),icon:"cloud",mod:"cdn"},{key:"tools",title:(0,i.__)("Tools","optimazz"),sub:(0,i.__)("Import, export","optimazz"),icon:"admin-tools"}],p=(0,s.applyFilters)("optimazz.adminSections",[]),_=[...d,...p],z=e=>![!1,"","0",0,void 0,null].includes(e),h=![!1,"","0",0].includes(window.optimazzData?.serverFeatures),u=window.optimazzData?.networkCache||null,g=!h&&!!u&&z(u.enabled),b=h||g,f=z(window.optimazzData?.isSuperAdmin),E=window.optimazzData?.networkAdminUrl||"",v=e=>{if(!e)return"0 KB";const a=Math.floor(Math.log(e)/Math.log(1024));return`${(e/Math.pow(1024,a)).toFixed(a?1:0)} ${["B","KB","MB","GB"][a]}`},y=e=>({hourly:(0,i.__)("every hour","optimazz"),twicedaily:(0,i.__)("twice a day","optimazz"),daily:(0,i.__)("once a day","optimazz")}[e]||(0,i.__)("twice a day","optimazz"));function w(){return f&&E?(0,a.createElement)("a",{className:"oz-cachewarn__link",href:E},(0,i.__)("Open Network Admin → Optimazz","optimazz")):null}function k(){return(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-info-outline"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Page caching is off for this network","optimazz")),(0,a.createElement)("p",null,f?(0,i.__)("Turn it on for every site in Network Admin → Optimazz. Each site gets its own cache, and keeps its own exclusions here under Advanced Rules.","optimazz"):(0,i.__)("Your network administrator can turn it on for every site in Network Admin → Optimazz. All other optimizations — CSS, JavaScript, images, fonts and more — work normally on this site.","optimazz")),(0,a.createElement)(w,null)))}function C(){const e=u||{};return(0,a.createElement)("div",{className:"oz-cachewarn oz-cachewarn--ok",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Page caching is on — managed for the whole network","optimazz")),(0,a.createElement)("p",null,(0,i.sprintf)(/* translators: 1: yes/no, 2: cache lifetime, 3: preload frequency. */ /* translators: 1: yes/no, 2: cache lifetime, 3: preload frequency. */
     2(0,i.__)("Separate mobile cache: %1$s · Cache lifetime: %2$s · Automatic preload: %3$s.","optimazz"),z(e.cache_mobile)?(0,i.__)("yes","optimazz"):(0,i.__)("no","optimazz"),(t=e.lifespan_hours,null!==(o={0:(0,i.__)("never expires","optimazz"),12:(0,i.__)("12 hours","optimazz"),24:(0,i.__)("1 day","optimazz"),168:(0,i.__)("1 week","optimazz"),720:(0,i.__)("1 month","optimazz")}[Number(t)])&&void 0!==o?o:(0,i.sprintf)((0,i._n)("%d hour","%d hours",Number(t),"optimazz"),Number(t))),z(e.preload)?y(e.preload_interval):(0,i.__)("off","optimazz"))),(0,a.createElement)("p",null,(0,i.__)("This site has its own cache. Purge or preload it from the Dashboard or the Preload screen, and set its own exclusions under Advanced Rules.","optimazz")),(0,a.createElement)(w,null)));var t,o}const N=["cache","js","css","media","fonts","database","heartbeat","tweaks","cdn"],x=[{title:(0,i.__)("Content leftovers","optimazz"),desc:(0,i.__)("Old post data that piles up over time. Removing it is permanent, so keep anything you still need.","optimazz"),items:[{key:"revisions",label:(0,i.__)("Post revisions","optimazz"),help:(0,i.__)("Old copies of posts saved automatically. Deleting them keeps only your current content.","optimazz")},{key:"auto_drafts",label:(0,i.__)("Auto drafts","optimazz"),help:(0,i.__)("Empty drafts WordPress creates while you write. Safe to remove.","optimazz")},{key:"trashed_posts",label:(0,i.__)("Trashed posts","optimazz"),help:(0,i.__)("Posts in the trash that have not been permanently deleted yet.","optimazz")}]},{title:(0,i.__)("Comment clutter","optimazz"),desc:(0,i.__)("Spam and trashed comments you are never going to approve. Clearing them keeps the comments table light.","optimazz"),items:[{key:"spam_comments",label:(0,i.__)("Spam comments","optimazz"),help:(0,i.__)("Comments marked as spam. Removing them frees up the comments table.","optimazz")},{key:"trashed_comments",label:(0,i.__)("Trashed comments","optimazz"),help:(0,i.__)("Comments in the trash awaiting permanent deletion.","optimazz")}]},{title:(0,i.__)("Temporary data","optimazz"),desc:(0,i.__)("Short-lived cached values. They are safe to clear and are rebuilt automatically whenever a plugin needs them.","optimazz"),items:[{key:"expired_transients",label:(0,i.__)("Expired transients","optimazz"),help:(0,i.__)("Cached values that have already passed their expiry date.","optimazz")}]},{title:(0,i.__)("Table maintenance","optimazz"),desc:(0,i.__)("Defragment your tables to reclaim wasted space and trim database overhead.","optimazz"),items:[{key:"optimize_tables",label:(0,i.__)("Optimize database tables","optimazz"),help:(0,i.__)("Runs OPTIMIZE on your tables to defragment them and free wasted space.","optimazz")}]}],S=[{label:(0,i.__)("Do not limit","optimazz"),value:"default"},{label:(0,i.__)("Reduce activity","optimazz"),value:"reduce"},{label:(0,i.__)("Disable","optimazz"),value:"disable"}];function $({on:e}){return(0,a.createElement)("span",{className:"oz-pill "+(e?"is-on":"is-off")},(0,a.createElement)("i",null),e?(0,i.__)("Active","optimazz"):(0,i.__)("Off","optimazz"))}function T({icon:e,title:t,description:n,enabled:l,onToggle:i,children:s}){return(0,a.createElement)("section",{className:"oz-card "+(l?"is-enabled":"")},(0,a.createElement)("header",{className:"oz-card__head"},(0,a.createElement)("span",{className:`oz-card__icon dashicons dashicons-${e}`}),(0,a.createElement)("div",{className:"oz-card__title"},(0,a.createElement)("h2",null,t," ",(0,a.createElement)($,{on:l})),(0,a.createElement)("p",null,n)),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:l,onChange:i})),l&&(0,a.createElement)("div",{className:"oz-card__body"},s))}function M({job:e,onStop:n,onClose:l,onToggleMin:s}){const r=(0,t.useRef)(null);if((0,t.useEffect)(()=>{r.current&&(r.current.scrollTop=r.current.scrollHeight)},[e?.logs?.length]),!e)return null;const c=e.status||(e.running?"running":"done"),m={stopped:(0,i.__)("Cancelled","optimazz"),error:(0,i.__)("Failed","optimazz")},d="running"===c||"done"===c?e.title:`${e.title} — ${m[c]}`;return(0,a.createElement)("div",{className:`oz-job is-${c} ${e.minimized?"is-min":""}`},(0,a.createElement)("div",{className:"oz-job__head"},"running"===c?(0,a.createElement)(o.Spinner,null):(0,a.createElement)("span",{className:`oz-job__icon-status oz-job__icon-status--${c} dashicons dashicons-${{done:"yes-alt",stopped:"dismiss",error:"warning"}[c]||"yes-alt"}`}),(0,a.createElement)("div",{className:"oz-job__title"},(0,a.createElement)("strong",null,d),(0,a.createElement)("span",null,e.minimized?`${e.pct}%${e.step?` · ${e.step}`:""}`:e.subtitle)),(0,a.createElement)("button",{className:"oz-job__icon","aria-label":e.minimized?(0,i.__)("Expand","optimazz"):(0,i.__)("Minimize","optimazz"),onClick:s},(0,a.createElement)("span",{className:"dashicons dashicons-"+(e.minimized?"editor-expand":"minus")})),(0,a.createElement)("button",{className:"oz-job__icon","aria-label":(0,i.__)("Close","optimazz"),onClick:l},(0,a.createElement)("span",{className:"dashicons dashicons-no-alt"}))),e.minimized?(0,a.createElement)("div",{className:"oz-job__bar "+(e.indeterminate&&e.running?"is-indeterminate":"")},(0,a.createElement)("div",{style:{width:`${e.pct}%`}})):(0,a.createElement)("div",{className:"oz-job__body"},(0,a.createElement)("div",{className:"oz-job__row"},(0,a.createElement)("span",null,e.label),(0,a.createElement)("strong",null,e.pct,"%")),(0,a.createElement)("div",{className:"oz-job__bar "+(e.indeterminate&&e.running?"is-indeterminate":"")},(0,a.createElement)("div",{style:{width:`${e.pct}%`}})),e.step&&(0,a.createElement)("div",{className:"oz-job__step"},(0,i.__)("Step","optimazz")," ",e.step),e.running&&(0,a.createElement)("button",{className:"oz-job__stop",onClick:n},(0,a.createElement)("span",{className:"dashicons dashicons-controls-pause"}),(0,i.__)("Stop","optimazz")),(0,a.createElement)("div",{className:"oz-job__log",ref:r},e.logs.map((e,t)=>{const o=e.level||"INFO";return(0,a.createElement)("div",{key:t,className:`oz-logline oz-logline--${o.toLowerCase()}`},(0,a.createElement)("span",{className:"oz-logline__tag"},o),(0,a.createElement)("span",{className:"oz-logline__text"},e.text))}))))}let B=0;function P({label:e,value:o,options:n,onChange:l,compact:i=!1,disabled:s=!1}){const[r,c]=(0,t.useState)(!1),m=Math.max(0,n.findIndex(e=>e.value===o)),[d,p]=(0,t.useState)(m),_=(0,t.useRef)(null),z=(0,t.useRef)("oz-sel-"+ ++B).current;(0,t.useEffect)(()=>{const e=e=>{_.current&&!_.current.contains(e.target)&&c(!1)};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[]);const h=n.find(e=>e.value===o)||n[0],u=e=>{l(n[e].value),c(!1)};return(0,a.createElement)("div",{className:"components-base-control oz-select"+(i?" oz-select--compact":""),ref:_},(0,a.createElement)("span",{className:i?"screen-reader-text":"components-base-control__label",id:`${z}-label`},e),(0,a.createElement)("button",{type:"button",className:"oz-select__btn",disabled:s,role:"combobox","aria-haspopup":"listbox","aria-expanded":r,"aria-labelledby":`${z}-label`,"aria-activedescendant":r?`${z}-opt-${d}`:void 0,onClick:()=>c(e=>!e),onKeyDown:e=>{r?"Escape"===e.key||"Tab"===e.key?c(!1):"ArrowDown"===e.key?(e.preventDefault(),p(e=>Math.min(n.length-1,e+1))):"ArrowUp"===e.key?(e.preventDefault(),p(e=>Math.max(0,e-1))):"Home"===e.key?(e.preventDefault(),p(0)):"End"===e.key?(e.preventDefault(),p(n.length-1)):"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),u(d)):["ArrowDown","ArrowUp","Enter"," "].includes(e.key)&&(e.preventDefault(),p(m),c(!0))}},(0,a.createElement)("span",null,h?.label),(0,a.createElement)("span",{className:"dashicons dashicons-arrow-down-alt2"})),r&&(0,a.createElement)("ul",{className:"oz-select__menu",role:"listbox","aria-labelledby":`${z}-label`},n.map((e,t)=>(0,a.createElement)("li",{key:e.value,id:`${z}-opt-${t}`,role:"option","aria-selected":e.value===o,className:`oz-select__item ${e.value===o?"is-selected":""} ${t===d?"is-active":""}`,onMouseEnter:()=>p(t),onClick:()=>u(t)},(0,a.createElement)("span",null,e.label),e.value===o&&(0,a.createElement)("span",{className:"dashicons dashicons-yes"})))))}const O={dashboard:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M3 9.6 12 3l9 6.6"}),(0,a.createElement)("path",{d:"M5 9.1V20h5v-5h4v5h5V9.1"})),cache:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M12 3 2.5 8 12 13l9.5-5L12 3Z"}),(0,a.createElement)("path",{d:"M2.5 12 12 17l9.5-5"})),files:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"m9 8-4 4 4 4"}),(0,a.createElement)("path",{d:"m15 8 4 4-4 4"})),media:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("rect",{x:"3",y:"4",width:"18",height:"16",rx:"2"}),(0,a.createElement)("circle",{cx:"8.5",cy:"9.5",r:"1.5"}),(0,a.createElement)("path",{d:"m21 16-5-5-6 6"})),fonts:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M5 6V4h14v2"}),(0,a.createElement)("path",{d:"M12 4v16"}),(0,a.createElement)("path",{d:"M9 20h6"})),preload:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M21 12a9 9 0 1 1-2.6-6.4"}),(0,a.createElement)("path",{d:"M21 4v5h-5"})),advanced:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"M4 8h9"}),(0,a.createElement)("circle",{cx:"16",cy:"8",r:"2.1"}),(0,a.createElement)("path",{d:"M19 8h1"}),(0,a.createElement)("path",{d:"M4 16h1"}),(0,a.createElement)("circle",{cx:"8",cy:"16",r:"2.1"}),(0,a.createElement)("path",{d:"M11 16h9"})),database:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("ellipse",{cx:"12",cy:"5",rx:"8",ry:"3"}),(0,a.createElement)("path",{d:"M4 5v14c0 1.6 3.6 3 8 3s8-1.4 8-3V5"}),(0,a.createElement)("path",{d:"M4 12c0 1.6 3.6 3 8 3s8-1.4 8-3"})),heartbeat:(0,a.createElement)("path",{d:"M3 12h4l2 6 4-14 2 8h6"}),tweaks:(0,a.createElement)(a.Fragment,null,(0,a.createElement)("path",{d:"m12 4 1.5 4.5L18 10l-4.5 1.5L12 16l-1.5-4.5L6 10l4.5-1.5Z"}),(0,a.createElement)("path",{d:"M18.5 15v3"}),(0,a.createElement)("path",{d:"M17 16.5h3"})),cdn:(0,a.createElement)("path",{d:"M17 18H7a4 4 0 0 1-.3-8A6 6 0 0 1 18.3 9 3.5 3.5 0 0 1 17 18Z"}),tools:(0,a.createElement)("path",{d:"M14.5 5.5a4 4 0 0 0-5 5L4 16v4h4l5.5-5.5a4 4 0 0 0 5-5l-2.6 2.6-2-.4-.4-2 2.5-2.6Z"})};function R({name:e,paths:t}){return(0,a.createElement)("svg",{className:"oz-ico",viewBox:"0 0 24 24",width:"20",height:"20",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",focusable:"false"},t||O[e]||O.dashboard)}function D({ok:e,warn:t,label:o}){const n=e?"ok":t?"warn":"bad",l=e?"yes-alt":t?"warning":"dismiss";return(0,a.createElement)("li",{className:`oz-health__row is-${n}`},(0,a.createElement)("span",{className:`dashicons dashicons-${l}`}),(0,a.createElement)("span",null,o))}function F({label:e,help:t,value:o,options:n,onChange:l}){return(0,a.createElement)("div",{className:"components-base-control oz-pillgroup"},e&&(0,a.createElement)("span",{className:"components-base-control__label"},e),(0,a.createElement)("div",{className:"oz-pillgroup__row",role:"radiogroup","aria-label":e,onKeyDown:e=>{const a=n.findIndex(e=>e.value===o);"ArrowRight"===e.key||"ArrowDown"===e.key?(e.preventDefault(),l(n[Math.min(n.length-1,a+1)].value)):"ArrowLeft"!==e.key&&"ArrowUp"!==e.key||(e.preventDefault(),l(n[Math.max(0,a-1)].value))}},n.map(e=>{const t=e.value===o;return(0,a.createElement)("button",{key:String(e.value),type:"button",role:"radio","aria-checked":t,tabIndex:t?0:-1,className:"oz-pillbtn "+(t?"is-on":""),onClick:()=>l(e.value)},e.label)})),t&&(0,a.createElement)("span",{className:"components-base-control__help"},t))}function H({title:e,description:t,children:o}){return(0,a.createElement)("section",{className:"oz-panel"},(0,a.createElement)("header",{className:"oz-panel__head"},(0,a.createElement)("h2",null,e),t&&(0,a.createElement)("p",null,t)),(0,a.createElement)("div",{className:"oz-panel__body"},o))}function A({className:e="",items:t,nav:n,onNav:l,foot:s,topIcon:r,title:c,sub:m,dirty:d,saving:p,onSave:_,toast:z,onToastRemove:h,children:u,after:g}){return(0,a.createElement)("div",{className:`oz-shell ${e}`},(0,a.createElement)("aside",{className:"oz-sidebar"},(0,a.createElement)("div",{className:"oz-sidebar__brand"},window.optimazzData?.logoUrl?(0,a.createElement)("img",{className:"oz-logo-img",src:window.optimazzData.logoUrl,alt:"Optimazz"}):(0,a.createElement)("div",{className:"oz-sidebar__brand-fallback"},(0,a.createElement)("span",{className:"oz-logo dashicons dashicons-performance"}),(0,a.createElement)("strong",null,"Optimazz"))),(0,a.createElement)("nav",null,t.map(e=>(0,a.createElement)("button",{key:e.key,className:"oz-navitem "+(n===e.key?"is-active":""),onClick:()=>l(e.key)},(0,a.createElement)(R,{name:e.svg||e.key,paths:e.iconSvg}),(0,a.createElement)("span",{className:"oz-navitem__text"},(0,a.createElement)("strong",null,e.title),(0,a.createElement)("small",null,e.sub)),e.dot&&(0,a.createElement)("i",{className:"oz-navitem__dot"})))),(0,a.createElement)("div",{className:"oz-sidebar__foot"},s)),(0,a.createElement)("div",{className:"oz-content"},(0,a.createElement)("header",{className:"oz-topbar"},(0,a.createElement)("span",{className:`oz-topbar__icon dashicons dashicons-${r}`}),(0,a.createElement)("div",null,(0,a.createElement)("h1",null,c),(0,a.createElement)("p",null,m))),(0,a.createElement)("main",{className:"oz-main"},u)),(0,a.createElement)("div",{className:"oz-savebar "+(d?"is-dirty":"")},(0,a.createElement)("span",{className:"oz-savebar__hint"},d?(0,i.__)("You have unsaved changes","optimazz"):(0,i.__)("All changes saved","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:p,disabled:!d||p,onClick:_},(0,i.__)("Save changes","optimazz"))),z&&(0,a.createElement)("div",{className:"oz-toast"},(0,a.createElement)(o.Snackbar,{status:z.status,onRemove:h},z.text)),g)}const L=document.getElementById("optimazz-app");L&&(0,t.createRoot)(L).render((0,a.createElement)(function(){var e,n,s,c,f,E,w,$;const[B,O]=(0,t.useState)(m||{}),[L,W]=(0,t.useState)(m||{}),[j,I]=(0,t.useState)(null),[q,U]=(0,t.useState)(null),[J,G]=(0,t.useState)(!1),[V,K]=(0,t.useState)("dashboard"),[Y,Z]=(0,t.useState)(!1),[Q,X]=(0,t.useState)(null),[ee,ae]=(0,t.useState)(!(!window.optimazzData||!window.optimazzData.hasBackup)),te=(0,t.useRef)(null);(0,t.useEffect)(()=>{const e=e=>{const a=e&&e.detail&&e.detail.key;a&&K(a)};return window.addEventListener("optimazz:navigate",e),()=>window.removeEventListener("optimazz:navigate",e)},[]);const oe=JSON.stringify(B)!==JSON.stringify(L),ne=(0,t.useCallback)((e,a,t)=>O(o=>({...o,[e]:{...o[e],[a]:t}})),[]),le=(e,a)=>ne(e,a,!B[e]?.[a]),ie=(0,t.useCallback)(async()=>{try{I(await l()({url:`${r}/cache/stats`}))}catch(e){}},[]),[se,re]=(0,t.useState)(null),ce=(0,t.useCallback)(async()=>{try{re(await l()({url:`${r}/system?_=${Date.now()}`}))}catch(e){re({error:!0})}},[]);(0,t.useEffect)(()=>{"tools"!==V||se||ce()},[V,se,ce]);const[me,de]=(0,t.useState)(null),pe=(0,t.useCallback)(async()=>{try{de(await l()({url:`${r}/recommendations?_=${Date.now()}`}))}catch(e){de({error:!0})}},[]);(0,t.useEffect)(()=>{"dashboard"!==V||me||pe()},[V,me,pe]);const _e=(0,t.useCallback)(async()=>{G(!0);try{U(await l()({url:`${r}/cache/health`}))}catch(e){U({error:!0})}G(!1)},[]),ze=(0,t.useCallback)(async(e,a)=>{if(window.confirm(`${(0,i.__)("Deactivate","optimazz")} ${a}? ${(0,i.__)("Optimazz will keep handling caching.","optimazz")}`)){G(!0);try{const t=await l()({url:`${r}/cache/deactivate-conflict`,method:"POST",data:{plugin:e}});U(t),ue(`${a} ${(0,i.__)("deactivated — Optimazz is now the only cache.","optimazz")}`,"success")}catch(e){ue((0,i.__)("Could not deactivate the plugin.","optimazz"),"error")}G(!1)}},[]),he=(0,t.useCallback)(async()=>{if(window.confirm((0,i.__)("Make Optimazz the caching plugin? This replaces the current advanced-cache.php (from another caching plugin) and turns on WP_CACHE. Deactivate the other caching plugin afterwards to avoid conflicts.","optimazz"))){G(!0);try{const e=await l()({url:`${r}/cache/takeover`,method:"POST"});U(e),ue("hit"===e.live?(0,i.__)("Optimazz is now serving pages from cache.","optimazz"):e.dropin_ours?(0,i.__)("Drop-in installed. Preload the cache, then check again.","optimazz"):(0,i.__)("Could not fully take over — see the cache health details.","optimazz"),e.dropin_ours?"success":"error")}catch(e){ue((0,i.__)("Take-over failed. Check file permissions.","optimazz"),"error")}G(!1)}},[]);(0,t.useEffect)(()=>{ie()},[ie]);const ue=(e,a="success")=>{X({text:e,status:a}),window.clearTimeout(ue._t),ue._t=window.setTimeout(()=>X(null),3e3)};(0,t.useEffect)(()=>{try{const e=new URLSearchParams(window.location.search),a=e.get("optimazz_notice"),t={purged:(0,i.__)("Cache purged.","optimazz"),preloaded:(0,i.__)("Cache preload started in the background.","optimazz"),cleared:(0,i.__)("Cache cleared; preload started in the background.","optimazz")};if(a&&t[a]){ue(t[a]),e.delete("optimazz_notice");const o=e.toString();window.history.replaceState({},"",window.location.pathname+(o?`?${o}`:""))}window.optimazzData?.wpconfigUnwritable&&ue((0,i.__)("wp-config.php is not writable — add WP_CACHE manually to enable page caching.","optimazz"),"error")}catch(e){}},[]);const ge=async e=>{Z(!0);try{const a=await l()({url:`${r}/settings`,method:"POST",data:e});W(a.settings),O(a.settings),ae(!!a.hasBackup),de(null),ue((0,i.__)("Settings saved.","optimazz")),ie()}catch(e){ue(e.message||(0,i.__)("Save failed.","optimazz"),"error")}Z(!1)},be=async()=>{await l()({url:`${r}/cache/purge`,method:"POST"}),ue((0,i.__)("Cache purged.","optimazz")),ie()},fe=()=>Me("preload"),[Ee,ve]=(0,t.useState)(null),[ye,we]=(0,t.useState)(null),[ke,Ce]=(0,t.useState)(null),Ne=(0,t.useRef)(!1),xe=(0,t.useCallback)(async()=>{try{we(await l()({url:`${r}/media/stats`}))}catch(e){}},[]);(0,t.useEffect)(()=>{"media"!==V||ye||xe()},[V,ye,xe]);const Se={convert:{title:(0,i.__)("Converting images to WebP/AVIF","optimazz"),subtitle:(0,i.__)("Next-gen format generation","optimazz"),endpoint:`${r}/media/convert`,refresh:xe,summarize:e=>`${(0,i.__)("Generated","optimazz")} ${e.generated||0} ${(0,i.__)("files in this batch","optimazz")}${e.skipped?` · ${e.skipped} ${(0,i.__)("skipped","optimazz")}`:""}`},compress:{title:(0,i.__)("Compressing images","optimazz"),subtitle:(0,i.__)("Recompress, resize & strip metadata","optimazz"),endpoint:`${r}/media/optimize`,refresh:xe,summarize:e=>{const a=Math.max(0,(e.before||0)-(e.after||0)),t=e.skipped?` · ${e.skipped} ${(0,i.__)("skipped","optimazz")}`:"";return`${(0,i.__)("Processed","optimazz")} ${e.processed||0} — ${Pe(a)} ${(0,i.__)("saved","optimazz")}${t}`}},preload:{title:(0,i.__)("Preloading cache","optimazz"),subtitle:(0,i.__)("Warming pages","optimazz"),endpoint:`${r}/cache/preload`,refresh:ie,onBatch:ie,summarize:(e,a)=>`${(0,i.__)("Warmed","optimazz")} ${Math.min(e.next_offset||0,e.total||0)} / ${e.total||0} ${(0,i.__)("pages","optimazz")} · ${a.cached} ${(0,i.__)("cached","optimazz")}${a.missed?` · ${a.missed} ${(0,i.__)("not cached","optimazz")}`:""}`}},$e=e=>!e||e<1?"":e>=3600?`~${Math.round(e/3600)}h`:e>=60?`~${Math.round(e/60)}m`:`~${Math.round(e)}s`,Te=()=>l()({url:`${r}/media/job`,method:"DELETE"}).catch(()=>{}),Me=async(e,{startOffset:a=0,reconnect:t=!1}={})=>{const o=Se[e];if(!o||ke?.running)return;Ne.current=!1;const n=t?(0,i.__)("Resuming…","optimazz"):`${o.title}…`;Ce({title:o.title,subtitle:o.subtitle,pct:0,label:n,step:"",logs:[{level:"INFO",text:n}],running:!0,status:"running",minimized:!1});let s=a,r=0,c=0;const m={cached:0,missed:0,diagnosis:""},d=Date.now();try{for(;;){var p;if(Ne.current){await Te(),Ce(e=>e&&{...e,running:!1,status:"stopped",logs:[...e.logs,{level:"WARN",text:(0,i.__)("Stopped by user.","optimazz")}]});break}const a=s;let t;try{t=await l()({url:o.endpoint,method:"POST",data:{offset:s}})}catch(e){if(c++,c>20)throw e;if(s=a+1,Ce(e=>e&&{...e,logs:[...e.logs.slice(-60),{level:"WARN",text:(0,i.__)("A batch failed (likely one slow/large image); skipped it and continued.","optimazz")}]}),r&&s>=r){Ce(e=>e&&{...e,running:!1,status:"done",pct:100,logs:[...e.logs,{level:"DONE",text:(0,i.__)("Completed (some items skipped).","optimazz")}]});break}await new Promise(e=>setTimeout(e,600));continue}if(c=0,s=null!==(p=t.next_offset)&&void 0!==p?p:s+(t.processed||0),r=t.total||r,!t.done&&s<=a){await Te(),Ce(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:(0,i.__)("Job stalled — no progress.","optimazz")}]});break}m.cached+=t.cached||0,m.missed+=t.missed||0,m.diagnosis=t.diagnosis||m.diagnosis;const n=r?Math.min(100,Math.round(s/r*100)):100,_=o.summarize(t,m),z=(Date.now()-d)/1e3,h=s>0?s/Math.max(z,.001):0,u=h>0&&r&&s<r?(r-s)/h:0,g=u?` · ${$e(u)} ${(0,i.__)("left","optimazz")}`:"";if(Ce(e=>e&&{...e,pct:n,label:_,step:r?`${Math.min(s,r)} / ${r}${g}`:"",logs:[...e.logs.slice(-60),...t.breakdown?[{level:"INFO",text:`${(0,i.sprintf)(/* translators: %d: number of pages to warm. */ /* translators: %d: number of pages to warm. */
     3(0,i.__)("Found %d pages to warm:","optimazz"),t.total||0)} ${t.breakdown}.`}]:[],{level:"INFO",text:_}]}),o.onBatch&&o.onBatch(),t.unsupported){await Te(),Ce(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:t.message||(0,i.__)("Image processing is unavailable on this server (no GD or ImageMagick). See Tools → Server Requirements.","optimazz")}]});break}if(t.blocked){await Te(),Ce(e=>e&&{...e,running:!1,status:"error",logs:[...e.logs,{level:"ERROR",text:(0,i.__)('Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the "Optimazz-CacheWarmer" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter.',"optimazz")}]});break}if("preload"===e&&!t.done&&0===m.cached&&m.missed>=3){await Te(),Ce(e=>e&&{...e,running:!1,status:"error",label:(0,i.__)("Pages are not being cached","optimazz"),logs:[...e.logs,{level:"ERROR",text:`${(0,i.__)("Stopped: pages are loading but none are being saved to the cache.","optimazz")} ${m.diagnosis}`}]});break}if(t.done){const e=[{level:"DONE",text:(0,i.__)("Completed.","optimazz")}];m.missed&&e.unshift({level:"WARN",text:`${(0,i.sprintf)(/* translators: %d: number of pages. */ /* translators: %d: number of pages. */
     4(0,i._n)("%d page was not cached.","%d pages were not cached.",m.missed,"optimazz"),m.missed)} ${m.diagnosis}`}),Ce(a=>a&&{...a,running:!1,status:"done",pct:100,logs:[...a.logs,...e]});break}}}catch(e){Ce(a=>a&&{...a,running:!1,status:"error",logs:[...a.logs,{level:"ERROR",text:e.message||"Error"}]})}(o.refresh||xe)()};(0,t.useEffect)(()=>{let e=!1;return(async()=>{try{const a=await l()({url:`${r}/media/job`});!e&&a&&a.running&&Se[a.type]&&Me(a.type,{startOffset:a.offset||0,reconnect:!0})}catch(e){}})(),()=>{e=!0}},[]);const Be=(0,t.useCallback)(async()=>{try{ve(await l()({url:`${r}/database/counts`}))}catch(e){}},[]);(0,t.useEffect)(()=>{"database"!==V||Ee||Be()},[V,Ee,Be]);const Pe=v,Oe=h?N:N.filter(e=>"cache"!==e),Re=Oe.filter(e=>B[e]?.enabled).length,De=B.cache||{},Fe=B.js||{},He=B.css||{},Ae=B.media||{},Le=B.fonts||{},We=B.database||{},je=B.heartbeat||{},Ie=B.tweaks||{},qe=B.cdn||{},Ue=_.find(e=>e.key===V),Je=[{on:!!De.enabled,w:30,label:(0,i.__)("Page caching","optimazz"),server:!0},{on:!(!Fe.enabled||!Fe.delay),w:18,label:(0,i.__)("Delay JavaScript","optimazz")},{on:!(!Fe.enabled||!Fe.defer),w:8,label:(0,i.__)("Defer JavaScript","optimazz")},{on:!(!He.enabled||!He.minify),w:10,label:(0,i.__)("Minify CSS","optimazz")},{on:!(!Ae.enabled||!Ae.lazy_load),w:12,label:(0,i.__)("Lazy-load images","optimazz")},{on:!(!Ae.enabled||!Ae.webp&&!Ae.webp_generate),w:8,label:(0,i.__)("Next-gen images","optimazz")},{on:!(!Le.enabled||!Le.display_swap),w:6,label:(0,i.__)("Font optimization","optimazz")},{on:!!De.preload,w:5,label:(0,i.__)("Cache preloading","optimazz"),server:!0},{on:!!je.enabled,w:3,label:(0,i.__)("Heartbeat control","optimazz")}],Ge=b?Je:Je.filter(e=>!0!==e.server),Ve=Ge.reduce((e,a)=>e+a.w,0)||1,Ke=Math.round(Ge.reduce((e,a)=>e+(a.on?a.w:0),0)/Ve*100),Ye=Ge.filter(e=>!e.on).sort((e,a)=>a.w-e.w)[0],Ze=Ke>=90?"A":Ke>=75?"B":Ke>=55?"C":Ke>=35?"D":"E",Qe=2*Math.PI*52,Xe=h&&(Fe.enabled||He.enabled||Ae.enabled)&&!De.enabled,ea=(0,a.createElement)(a.Fragment,null,Xe&&(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-info-outline"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Turn on Page Cache for the real speed gain","optimazz")),(0,a.createElement)("p",null,(0,i.__)("File and media optimizations are on, but Page Cache is off — so the server rebuilds every page on every request. Without caching this can make a site feel slower, not faster, especially on mobile. Enable Page Cache to serve ready-made HTML.","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-primary",onClick:()=>K("cache")},(0,i.__)("Enable Page Cache","optimazz"))),(0,a.createElement)("div",{className:"oz-score"},(0,a.createElement)("div",{className:"oz-score__gauge"},(0,a.createElement)("svg",{viewBox:"0 0 120 120",width:"132",height:"132"},(0,a.createElement)("circle",{cx:"60",cy:"60",r:"52",fill:"none",stroke:"rgba(255,255,255,.25)",strokeWidth:"10"}),(0,a.createElement)("circle",{cx:"60",cy:"60",r:"52",fill:"none",stroke:"#fff",strokeWidth:"10",strokeLinecap:"round",strokeDasharray:Qe,strokeDashoffset:Qe-Qe*Ke/100,transform:"rotate(-90 60 60)",style:{transition:"stroke-dashoffset .6s ease"}}),(0,a.createElement)("text",{x:"60",y:"56",textAnchor:"middle",fontSize:"30",fontWeight:"800",fill:"#fff"},Ke),(0,a.createElement)("text",{x:"60",y:"78",textAnchor:"middle",fontSize:"12",fill:"rgba(255,255,255,.85)"},(0,i.__)("SCORE","optimazz"))),(0,a.createElement)("span",{className:"oz-score__grade"},Ze)),(0,a.createElement)("div",{className:"oz-score__body"},(0,a.createElement)("h2",null,(0,i.__)("Performance score","optimazz")),(0,a.createElement)("p",null,Ye?`${(0,i.__)("Biggest remaining win:","optimazz")} ${Ye.label} (+${Ye.w})`:(0,i.__)("Every optimization is enabled. Nicely done!","optimazz")),(0,a.createElement)("div",{className:"oz-score__chips"},Ge.map(e=>(0,a.createElement)("span",{key:e.label,className:"oz-chip "+(e.on?"is-on":"")},(0,a.createElement)("span",{className:"dashicons dashicons-"+(e.on?"yes-alt":"marker")}),e.label))))),(0,a.createElement)("div",{className:"oz-kpis"+(b?"":" oz-kpis--single")},b&&(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,a.createElement)("strong",null,j?null!==(e=j.pages)&&void 0!==e?e:j.files:(0,a.createElement)(o.Spinner,null)),(0,a.createElement)("span",null,(0,i.__)("Cached pages","optimazz"))),b&&(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-chart-area"}),(0,a.createElement)("strong",null,j?Pe(j.bytes):(0,a.createElement)(o.Spinner,null)),(0,a.createElement)("span",null,(0,i.__)("Cache size","optimazz"))),(0,a.createElement)("div",{className:"oz-kpi"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,a.createElement)("strong",null,Re,"/",Oe.length),(0,a.createElement)("span",null,(0,i.__)("Modules active","optimazz")))),me&&!me.error&&(me.items||[]).length>0&&(0,a.createElement)(H,{title:(0,i.__)("Recommendations","optimazz"),description:(0,i.__)("What to fix next to speed up your site — one click each.","optimazz")},(0,a.createElement)("ul",{className:"oz-recs"},(me.items||[]).map(e=>(0,a.createElement)("li",{key:e.id,className:`oz-rec oz-rec--${e.severity}`},(0,a.createElement)("span",{className:"oz-rec__dot dashicons "+("high"===e.severity?"dashicons-warning":"medium"===e.severity?"dashicons-info":"dashicons-marker")}),(0,a.createElement)("span",{className:"oz-rec__text"},(0,a.createElement)("strong",null,e.title),(0,a.createElement)("small",null,e.desc)),e.fix?(0,a.createElement)(o.Button,{className:"oz-btn-primary oz-rec__btn",isBusy:Y,onClick:()=>(async e=>{if(!e||!e.module||!e.key)return;const a={...B,[e.module]:{...B[e.module],[e.key]:e.value}};await ge(a),de(null)})(e.fix)},(0,i.__)("Fix it","optimazz")):e.link?(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-rec__btn",onClick:()=>K(e.link)},(0,i.__)("Open","optimazz")):null)))),me&&!me.error&&0===(me.items||[]).length&&(0,a.createElement)(H,{title:(0,i.__)("Recommendations","optimazz")},(0,a.createElement)("p",{className:"oz-recs-clear"},(0,a.createElement)("span",{className:"dashicons dashicons-yes-alt"}),(0,i.__)("Nice — everything we recommend is already on. Your site is well optimized.","optimazz"))),(0,a.createElement)("div",{className:"oz-quick"},b&&(0,a.createElement)(H,{title:(0,i.__)("Cache Files","optimazz"),description:(0,i.__)("Clear and preload all cache files.","optimazz")},(0,a.createElement)("div",{className:"oz-quick__actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",isBusy:!!ke?.running,onClick:async()=>{await be(),fe()}},(0,i.__)("Clear & Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",onClick:fe},(0,i.__)("Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-btn--x",icon:"dismiss",onClick:be},(0,i.__)("Purge cache","optimazz"))),(0,a.createElement)("ul",{className:"oz-cachelist"},(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-media-text"}),(0,i.__)("Cached pages","optimazz"),(0,a.createElement)("strong",null,j?null!==(n=j.pages)&&void 0!==n?n:j.files:"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,i.__)("Page cache size","optimazz"),(0,a.createElement)("strong",null,j?Pe(j.bytes):"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-admin-appearance"}),(0,i.__)("Optimized CSS","optimazz"),(0,a.createElement)("strong",null,j?`${j.css_files} (${Pe(j.css_bytes)})`:"—")),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-clock"}),(0,i.__)("Cache lifespan","optimazz"),(0,a.createElement)(P,{compact:!0,disabled:Y,label:(0,i.__)("Cache lifespan","optimazz"),value:Number(null!==(s=De.lifespan_hours)&&void 0!==s?s:0),options:(aa=[{value:0,label:(0,i.__)("Never expire","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],ta=Number(null!==(c=De.lifespan_hours)&&void 0!==c?c:0),oa=e=>(0,i.sprintf)(/* translators: %d: number of hours. */ /* translators: %d: number of hours. */
     5(0,i._n)("%d hour","%d hours",e,"optimazz"),e),aa.some(e=>e.value===ta)?aa:[...aa,{value:ta,label:oa(ta)}].sort((e,a)=>e.value-a.value)),onChange:e=>ge({...B,cache:{...B.cache,lifespan_hours:e}})})),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-update"}),(0,a.createElement)("span",{className:"oz-row-label"},(0,i.__)("Auto-preload","optimazz"),(0,a.createElement)("small",null,j?j.preload&&j.next_preload?j.next_preload_overdue?(0,i.__)("next run: due now","optimazz"):`${(0,i.__)("next run in","optimazz")} ${j.next_preload}`:(0,i.__)("off","optimazz"):"")),(0,a.createElement)(P,{compact:!0,disabled:Y,label:(0,i.__)("Auto-preload frequency","optimazz"),value:De.preload?De.preload_interval||"twicedaily":"off",options:[{value:"off",label:(0,i.__)("Off","optimazz")},{value:"hourly",label:(0,i.__)("Every hour","optimazz")},{value:"twicedaily",label:(0,i.__)("Twice a day","optimazz")},{value:"daily",label:(0,i.__)("Once a day","optimazz")}],onChange:e=>{ge({...B,cache:{...B.cache,preload:"off"!==e,preload_interval:"off"===e?De.preload_interval||"twicedaily":e}})}})),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-backup"}),(0,i.__)("Last auto-preload","optimazz"),(0,a.createElement)("strong",null,j?j.last_preload?(0,i.sprintf)(/* translators: 1: time ago, e.g. "2 hours", 2: number of pages */ /* translators: 1: time ago, e.g. "2 hours", 2: number of pages */
     6(0,i.__)("%1$s ago · %2$d pages","optimazz"),j.last_preload.ago,j.last_preload.warmed)+(j.last_preload.blocked?` · ${(0,i.__)("blocked by firewall","optimazz")}`:""):(0,i.__)("not yet","optimazz"):"—")))),(0,a.createElement)(H,{title:(0,i.__)("Module Status","optimazz"),description:(0,i.__)("Enable or disable any module right here.","optimazz")},(0,a.createElement)("ul",{className:"oz-status"},d.filter(e=>e.mod&&(h||"cache"!==e.mod)).map(e=>(0,a.createElement)("li",{key:e.key},(0,a.createElement)(R,{name:e.key}),(0,a.createElement)("button",{className:"oz-status__name",onClick:()=>K(e.key)},e.title),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:!!B[e.mod]?.enabled,onChange:()=>le(e.mod,"enabled")}))),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-editor-code"}),(0,a.createElement)("button",{className:"oz-status__name",onClick:()=>K("files")},(0,i.__)("File Optimization","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,checked:!(!Fe.enabled&&!He.enabled),onChange:()=>{const e=!(Fe.enabled||He.enabled);ne("js","enabled",e),ne("css","enabled",e)}}))))));var aa,ta,oa;const na=h?(0,a.createElement)(T,{icon:"database",title:(0,i.__)("Page Cache","optimazz"),description:(0,i.__)("Serve static HTML instead of running PHP on every request.","optimazz"),enabled:!!De.enabled,onToggle:()=>le("cache","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache mobile devices separately","optimazz"),checked:!!De.cache_mobile,onChange:()=>le("cache","cache_mobile")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache logged-in users","optimazz"),help:(0,i.__)("Leave off unless you know what you are doing.","optimazz"),checked:!!De.cache_logged_in,onChange:()=>le("cache","cache_logged_in")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Browser cache headers","optimazz"),help:(0,i.__)("Add Expires / Cache-Control rules so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only (written to .htaccess); ignored on Nginx.","optimazz"),checked:!!De.browser_cache,onChange:()=>le("cache","browser_cache")}),(0,a.createElement)(F,{label:(0,i.__)("Cache lifetime","optimazz"),help:(0,i.__)("How long a cached page is kept before it is refreshed. Pages are always purged immediately when their content changes.","optimazz"),value:null!==(f=De.lifespan_hours)&&void 0!==f?f:0,options:[{value:0,label:(0,i.__)("Never","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],onChange:e=>ne("cache","lifespan_hours",e)}),(0,a.createElement)("div",{className:"oz-health"},(0,a.createElement)("div",{className:"oz-health__head"},(0,a.createElement)("strong",null,(0,i.__)("Cache health","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",isBusy:J,onClick:_e},(0,i.__)("Check now","optimazz"))),(0,a.createElement)("p",{className:"oz-health__hint"},(0,i.__)("Confirms caching is actually serving pages. If it is not, the plugin rebuilds every page on each request — which is slower, not faster.","optimazz")),q&&!q.error&&(0,a.createElement)("ul",{className:"oz-health__list"},(0,a.createElement)(D,{ok:"hit"===q.live,warn:"miss"===q.live,label:"hit"===q.live?(0,i.__)("Pages ARE being served from cache (HIT)","optimazz"):"miss"===q.live?(0,i.__)("Pages are NOT being served from cache (MISS) — see below","optimazz"):(0,i.__)("Live check unavailable","optimazz")}),(0,a.createElement)(D,{ok:q.dropin_installed&&q.dropin_ours,warn:q.conflict,label:q.conflict?q.conflict_name?`${(0,i.__)("advanced-cache.php belongs to","optimazz")} ${q.conflict_name} — ${(0,i.__)("take it over below","optimazz")}`:(0,i.__)("advanced-cache.php belongs to ANOTHER plugin — take it over below","optimazz"):q.dropin_ours?(0,i.__)("advanced-cache.php drop-in installed","optimazz"):(0,i.__)("advanced-cache.php drop-in is missing","optimazz")}),(0,a.createElement)(D,{ok:q.wp_cache,label:q.wp_cache?(0,i.__)("WP_CACHE is enabled in wp-config.php","optimazz"):(0,i.__)("WP_CACHE is not enabled in wp-config.php","optimazz")}),!q.wpconfig_writable&&(0,a.createElement)(D,{warn:!0,label:(0,i.__)("wp-config.php is not writable — add the WP_CACHE line manually (below)","optimazz")}),(0,a.createElement)(D,{ok:q.warmed>0,label:`${q.warmed} ${(0,i.__)("pages warmed in cache","optimazz")}`})),q&&!q.error&&(q.conflict||!q.wp_cache&&q.wpconfig_writable||!q.dropin_ours)&&q.enabled&&(0,a.createElement)("div",{className:"oz-health__fix"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:J,onClick:he},(0,i.__)("Take over caching","optimazz")),(0,a.createElement)("span",null,(0,i.__)("Make Optimazz the caching plugin — installs its drop-in and enables WP_CACHE.","optimazz"))),q&&!q.error&&Array.isArray(q.active_conflicts)&&q.active_conflicts.length>0&&(0,a.createElement)("div",{className:"oz-health__conflict"},q.active_conflicts.map(e=>(0,a.createElement)("div",{className:"oz-health__conflict-row",key:e.file},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,`${e.name} ${(0,i.__)("is still active and may reclaim caching. Deactivate it so Optimazz keeps serving the cache.","optimazz")}`),(0,a.createElement)(o.Button,{className:"oz-btn-danger",isBusy:J,onClick:()=>ze(e.file,e.name)},`${(0,i.__)("Deactivate","optimazz")} ${e.name}`)))),q&&!q.error&&!q.wp_cache&&!q.wpconfig_writable&&(0,a.createElement)("div",{className:"oz-health__manual"},(0,a.createElement)("p",null,(0,i.__)("wp-config.php is not writable. Add this line just after the opening <?php tag:","optimazz")),(0,a.createElement)("code",null,"define( 'WP_CACHE', true );")),q&&q.error&&(0,a.createElement)("p",{className:"oz-health__err"},(0,i.__)("Could not run the check. Try reloading the page.","optimazz")))):g?(0,a.createElement)(C,null):(0,a.createElement)(k,null),la=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(T,{icon:"editor-code",title:(0,i.__)("JavaScript","optimazz"),description:(0,i.__)("Defer and delay scripts to cut Total Blocking Time.","optimazz"),enabled:!!Fe.enabled,onToggle:()=>le("js","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Minify JavaScript","optimazz"),help:(0,i.__)("Strip comments and whitespace from inline scripts. Line breaks are kept for safety; test your site after enabling.","optimazz"),checked:!!Fe.minify,onChange:()=>le("js","minify")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Defer JavaScript","optimazz"),help:(0,i.__)("Load scripts without blocking page render.","optimazz"),checked:!!Fe.defer,onChange:()=>le("js","defer")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Delay JS until interaction","optimazz"),help:(0,i.__)("Run scripts only after the first user interaction.","optimazz"),checked:!!Fe.delay,onChange:()=>le("js","delay")}),Fe.delay&&(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Delay all scripts","optimazz"),help:(0,i.__)("Delay every script until interaction. jQuery, your theme, and Elementor are always kept safe. Still, on sites where content is rendered by JavaScript this can delay the visible page — test before keeping it on.","optimazz"),checked:!!Fe.delay_all,onChange:()=>le("js","delay_all")}),Fe.delay&&Fe.delay_all&&(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)('Delaying all scripts can slow the first paint on JavaScript-rendered sites (Elementor, sliders). If your PageSpeed FCP/LCP get worse, switch to "Delay selected" and delay only tracking scripts.',"optimazz"))),Fe.delay&&!Fe.delay_all&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Delay these scripts","optimazz"),help:(0,i.__)("Only scripts matching these keywords are delayed. Match by filename, handle or URL — e.g. gtag, analytics, facebook.","optimazz"),placeholder:(0,i.__)("e.g. gtag, analytics","optimazz"),value:Fe.delay_list||[],onChange:e=>ne("js","delay_list",e)}),(Fe.defer||Fe.delay)&&(0,a.createElement)("div",{className:"oz-fieldgroup"},(0,a.createElement)("h4",{className:"oz-fieldgroup__title"},(0,i.__)("Exclude specific scripts","optimazz")),(0,a.createElement)("p",{className:"oz-fieldgroup__desc"},(0,i.__)("If a script breaks, exclude it from just one optimization instead of both. Match by filename, script handle, or part of the URL.","optimazz")),Fe.defer&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never defer","optimazz"),help:(0,i.__)("Keep loading these render-blocking, but still allow delay. Use for scripts that must run during page load.","optimazz"),placeholder:(0,i.__)("e.g. jquery.min.js","optimazz"),value:Fe.no_defer||[],onChange:e=>ne("js","no_defer",e)}),Fe.delay&&(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never delay","optimazz"),help:(0,i.__)("Run these before any interaction, but still allow defer. Use for sliders, above-the-fold, or consent scripts.","optimazz"),placeholder:(0,i.__)("e.g. slider, swiper","optimazz"),value:Fe.no_delay||[],onChange:e=>ne("js","no_delay",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never optimize","optimazz"),help:(0,i.__)("Skip both defer and delay — leave these scripts exactly as they are.","optimazz"),placeholder:(0,i.__)("e.g. recaptcha","optimazz"),value:Fe.exclusions||[],onChange:e=>ne("js","exclusions",e)}))),(0,a.createElement)(T,{icon:"admin-appearance",title:(0,i.__)("CSS","optimazz"),description:(0,i.__)("Minify, combine, and remove render-blocking stylesheets.","optimazz"),enabled:!!He.enabled,onToggle:()=>le("css","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Minify CSS","optimazz"),checked:!!He.minify,onChange:()=>le("css","minify")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Combine CSS files","optimazz"),help:(0,i.__)("Merge stylesheets to reduce requests.","optimazz"),checked:!!He.combine,onChange:()=>le("css","combine")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load CSS asynchronously","optimazz"),help:(0,i.__)("Removes render-blocking CSS. Paste your above-the-fold (critical) CSS below, or the page may briefly flash unstyled.","optimazz"),checked:!!He.async_load,onChange:()=>le("css","async_load")}),He.async_load&&(0,a.createElement)(a.Fragment,null,!(He.critical_css||"").trim()&&(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("No critical CSS provided. Async loading is on, so visitors may see a flash of unstyled content until you paste critical CSS below.","optimazz"))),(0,a.createElement)(o.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Critical CSS (above-the-fold)","optimazz"),help:(0,i.__)("Paste the above-the-fold CSS to inline. Generate it with a free tool such as a critical-CSS generator. Leave empty to disable inlining.","optimazz"),value:He.critical_css||"",onChange:e=>ne("css","critical_css",e),rows:6})),(0,a.createElement)("div",{className:"oz-subhead"},(0,i.__)("Remove unused CSS","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove block-editor CSS on the front end","optimazz"),help:(0,i.__)("Dequeues WordPress block-library and global styles. Turn on only if your front end does NOT render Gutenberg blocks (e.g. a page-builder or classic theme). Big byte saving when it applies.","optimazz"),checked:!!He.remove_block_css,onChange:()=>le("css","remove_block_css")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load WooCommerce CSS only on shop pages","optimazz"),help:(0,i.__)("Dequeues WooCommerce styles on non-shop pages (kept on cart, checkout, account, product and shop). Turn off if you show Woo products or widgets on other pages.","optimazz"),checked:!!He.remove_woo_css,onChange:()=>le("css","remove_woo_css")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load Easy Digital Downloads CSS only on EDD pages","optimazz"),help:(0,i.__)("Dequeues EDD styles on non-EDD pages (kept on checkout, purchase-confirmation, purchase history and download archives). Turn off if you show download or purchase links elsewhere.","optimazz"),checked:!!He.remove_edd_css,onChange:()=>le("css","remove_edd_css")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Excluded CSS files (keywords)","optimazz"),value:He.exclusions||[],onChange:e=>ne("css","exclusions",e)}))),ia=(0,a.createElement)(T,{icon:"format-image",title:(0,i.__)("Image Optimization","optimazz"),description:(0,i.__)("Lazy-load, prevent layout shift, serve next-gen formats, and lighten media.","optimazz"),enabled:!!Ae.enabled,onToggle:()=>le("media","enabled")},ye&&!ye.can_process&&(0,a.createElement)("div",{className:"oz-notice oz-notice--error"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)('This server has no GD or ImageMagick extension, so image compression and WebP/AVIF cannot run — every image will be skipped. Ask your host to enable "php-gd" (or "php-imagick"). See Tools → Server Requirements.',"optimazz"))),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load images","optimazz"),help:(0,i.__)("Load off-screen images only when scrolled into view.","optimazz"),checked:!!Ae.lazy_load,onChange:()=>le("media","lazy_load")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load iframes","optimazz"),checked:!!Ae.lazy_iframes,onChange:()=>le("media","lazy_iframes")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Lazy-load CSS background images","optimazz"),help:(0,i.__)("Defer inline background-image elements (test with page builders).","optimazz"),checked:!!Ae.lazy_bg,onChange:()=>le("media","lazy_bg")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Replace YouTube embeds with a preview","optimazz"),help:(0,i.__)("Load the video iframe only when the visitor clicks play.","optimazz"),checked:!!Ae.youtube_preview,onChange:()=>le("media","youtube_preview")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Add missing image dimensions","optimazz"),help:(0,i.__)("Set width/height to prevent layout shift (CLS).","optimazz"),checked:!!Ae.add_dimensions,onChange:()=>le("media","add_dimensions")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Preload the LCP image","optimazz"),help:(0,i.__)("Preload the first/hero image with high priority.","optimazz"),checked:!!Ae.preload_lcp,onChange:()=>le("media","preload_lcp")}),Ae.lazy_load&&(0,a.createElement)(F,{label:(0,i.__)("Keep first images eager (LCP)","optimazz"),help:(0,i.__)("Do not lazy-load the first N images, to protect LCP.","optimazz"),value:null!==(E=Ae.skip_first)&&void 0!==E?E:1,onChange:e=>ne("media","skip_first",e),options:[0,1,2,3,4,5].map(e=>({label:String(e),value:e}))}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Serve WebP via <picture>","optimazz"),help:(0,i.__)("Deliver WebP to supporting browsers, original as fallback.","optimazz"),checked:!!Ae.webp,onChange:()=>le("media","webp")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Generate WebP/AVIF on upload","optimazz"),help:ye&&!ye.can_webp?(0,i.__)("WebP generation is not available on this server.","optimazz"):(0,i.__)("Auto-create next-gen copies for newly uploaded images.","optimazz"),checked:!!Ae.webp_generate,onChange:()=>le("media","webp_generate")}),ye?.can_avif&&(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Also generate/serve AVIF","optimazz"),help:(0,i.__)("Smaller than WebP; served first when supported.","optimazz"),checked:!!Ae.avif,onChange:()=>le("media","avif")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Compress & resize on upload","optimazz"),help:(0,i.__)("Recompress, cap dimensions, and strip metadata from new uploads.","optimazz"),checked:!!Ae.compress,onChange:()=>le("media","compress")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Convert opaque PNG to JPEG","optimazz"),help:(0,i.__)("Re-save non-transparent PNGs as much smaller JPEGs during bulk optimization.","optimazz"),checked:!!Ae.png_to_jpg,onChange:()=>le("media","png_to_jpg")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Keep a backup of originals","optimazz"),help:(0,i.__)("Copy originals to uploads/optimazz-backup before optimizing.","optimazz"),checked:!!Ae.backup_originals,onChange:()=>le("media","backup_originals")}),(0,a.createElement)(o.RangeControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Image quality","optimazz"),value:null!==(w=Ae.quality)&&void 0!==w?w:82,onChange:e=>ne("media","quality",e),min:40,max:100,help:(0,i.__)("Used for compression and WebP/AVIF generation.","optimazz")}),(0,a.createElement)(o.RangeControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Max image width (px)","optimazz"),value:null!==($=Ae.max_width)&&void 0!==$?$:2560,onChange:e=>ne("media","max_width",e),min:800,max:4e3,step:80}),(0,a.createElement)("div",{className:"oz-convert"},(0,a.createElement)("div",{className:"oz-convert__info"},(0,a.createElement)("strong",null,(0,i.__)("WebP coverage","optimazz")),ye?(0,a.createElement)("span",null,ye.converted," / ",ye.images," ",(0,i.__)("images converted","optimazz")):(0,a.createElement)("span",null,(0,i.__)("Loading…","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"images-alt2",isBusy:ke?.running,disabled:ye&&!ye.can_webp||ke?.running,onClick:()=>Me("convert")},(0,i.__)("Convert to WebP","optimazz"))),(0,a.createElement)("div",{className:"oz-convert"},(0,a.createElement)("div",{className:"oz-convert__info"},(0,a.createElement)("strong",null,(0,i.__)("Bulk compression","optimazz")),ye?(0,a.createElement)("span",null,ye.optimized," ",(0,i.__)("optimized","optimazz")," · ",Pe(ye.saved)," ",(0,i.__)("saved","optimazz")):(0,a.createElement)("span",null,(0,i.__)("Loading…","optimazz"))),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"filter",isBusy:ke?.running,disabled:ke?.running,onClick:()=>Me("compress")},(0,i.__)("Compress existing images","optimazz")))),sa=b?(0,a.createElement)(H,{title:(0,i.__)("Preload Cache","optimazz"),description:(0,i.__)("Warm the cache automatically so visitors never hit a cold page.","optimazz")},h?(0,a.createElement)(a.Fragment,null,(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Activate preloading","optimazz"),help:(0,i.__)("Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop.","optimazz"),checked:!!De.preload,onChange:()=>le("cache","preload")}),(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Automatic preload","optimazz"),help:(0,i.__)("How often to re-warm the whole site. Choose “Every hour” if your content changes often or you use a short cache lifespan.","optimazz"),value:De.preload_interval||"twicedaily",disabled:!De.preload,options:[{label:(0,i.__)("Every hour","optimazz"),value:"hourly"},{label:(0,i.__)("Twice a day","optimazz"),value:"twicedaily"},{label:(0,i.__)("Once a day","optimazz"),value:"daily"}],onChange:e=>ne("cache","preload_interval",e)})):(0,a.createElement)("p",{className:"oz-muted"},z(u?.preload)?(0,i.sprintf)(/* translators: %s: preload frequency, e.g. "twice a day". */ /* translators: %s: preload frequency, e.g. "twice a day". */
     7(0,i.__)("Automatic preload runs %s for this site (set for the whole network in Network Admin → Optimazz).","optimazz"),y(u?.preload_interval)):(0,i.__)("Automatic preload is off for the network. You can still preload this site now.","optimazz")),j&&j.wp_cron_disabled&&(0,a.createElement)("div",{className:"oz-notice oz-notice--warn",style:{marginTop:12}},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("WP-Cron is disabled on this site (DISABLE_WP_CRON). Scheduled preloads only run if a server cron job calls wp-cron.php — ask your host to set one up (e.g. every 5 minutes).","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"update",isBusy:!!ke?.running,onClick:fe},(0,i.__)("Preload now","optimazz")))):(0,a.createElement)(k,null),ra=b?(0,a.createElement)(H,{title:(0,i.__)("Advanced Rules","optimazz"),description:(0,i.__)("Fine-tune which requests are cached.","optimazz")},(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache these URLs (contains)","optimazz"),value:De.excluded_uris||[],onChange:e=>ne("cache","excluded_uris",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache when these cookies are set","optimazz"),value:De.excluded_cookies||[],onChange:e=>ne("cache","excluded_cookies",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Never cache for these user agents","optimazz"),value:De.excluded_user_agents||[],onChange:e=>ne("cache","excluded_user_agents",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Ignore these query arguments","optimazz"),value:De.ignored_query_args||[],onChange:e=>ne("cache","ignored_query_args",e)})):(0,a.createElement)(k,null),ca=!(!B.tools||!B.tools.safe_mode),ma=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(H,{title:(0,i.__)("Safe Mode","optimazz"),description:(0,i.__)("If a theme or page builder looks broken after optimizing, turn this on. It pauses the riskiest optimizations — combine CSS, async CSS, remove block CSS and JavaScript delay — while keeping caching, minify, defer and lazy-load. Your saved settings are not changed; they simply pause until you turn Safe Mode off.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Pause risky optimizations (Safe Mode)","optimazz"),checked:ca,onChange:e=>{const a={...B,tools:{...B.tools||{},safe_mode:e}};O(a),ge(a)}}),ca&&(0,a.createElement)("div",{className:"oz-notice oz-notice--warn",style:{marginTop:12}},(0,a.createElement)("span",{className:"dashicons dashicons-shield"}),(0,a.createElement)("span",null,(0,i.__)("Safe Mode is ON — combine/async CSS, remove-block-CSS and JavaScript delay are paused site-wide. Turn it off to resume full optimization.","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions",style:{marginTop:14}},(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"undo",disabled:!ee,onClick:async()=>{try{const e=await l()({url:`${r}/settings/revert`,method:"POST"});if("nothing"===e.message)return ae(!1),void ue((0,i.__)("Nothing to undo.","optimazz"),"error");W(e.settings),O(e.settings),ae(!!e.hasBackup),de(null),ue((0,i.__)("Reverted to the previous settings.","optimazz")),ie()}catch(e){ue(e.message||(0,i.__)("Undo failed.","optimazz"),"error")}}},(0,i.__)("Undo last change","optimazz")),(0,a.createElement)("span",{className:"oz-muted",style:{fontSize:12}},ee?(0,i.__)("Restores the settings from just before your last save, reset or Safe Mode toggle.","optimazz"):(0,i.__)("Nothing to undo yet — this becomes available after your next change.","optimazz")))),(0,a.createElement)(H,{title:(0,i.__)("Server Requirements","optimazz"),description:(0,i.__)("What your host supports. Warnings here explain why a feature (like WebP/AVIF) may be unavailable or slow.","optimazz")},!se&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Checking your server…","optimazz")),se&&se.error&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Could not read the server report.","optimazz")),se&&!se.error&&(0,a.createElement)("ul",{className:"oz-reqs"},(se.checks||[]).map((e,t)=>(0,a.createElement)("li",{key:t,className:`oz-req oz-req--${e.status}`},(0,a.createElement)("span",{className:"oz-req__dot dashicons "+("ok"===e.status?"dashicons-yes-alt":"warn"===e.status?"dashicons-warning":"info"===e.status?"dashicons-minus":"dashicons-dismiss")}),(0,a.createElement)("span",{className:"oz-req__label"},e.label),(0,a.createElement)("span",{className:"oz-req__value"},e.value),e.note&&(0,a.createElement)("span",{className:"oz-req__note"},e.note)))),(0,a.createElement)("div",{className:"oz-inline-actions",style:{marginTop:14}},(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",isBusy:!se,onClick:()=>{re(null),ce()}},(0,i.__)("Re-check","optimazz")))),(0,a.createElement)(H,{title:(0,i.__)("Import / Export Settings","optimazz"),description:(0,i.__)("Back up your configuration or copy it to another site.","optimazz")},(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"download",onClick:()=>{const e=new window.Blob([JSON.stringify(B,null,2)],{type:"application/json"}),a=document.createElement("a");a.href=URL.createObjectURL(e),a.download="optimazz-settings.json",a.click(),URL.revokeObjectURL(a.href)}},(0,i.__)("Export settings","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"upload",onClick:()=>te.current?.click()},(0,i.__)("Import settings","optimazz")),(0,a.createElement)("input",{ref:te,type:"file",accept:"application/json",style:{display:"none"},onChange:e=>{const a=e.target.files?.[0];if(!a)return;const t=new window.FileReader;t.onload=()=>{try{const e=JSON.parse(t.result);ge(e)}catch(e){ue((0,i.__)("Invalid settings file.","optimazz"),"error")}},t.readAsText(a),e.target.value=""}}))),(0,a.createElement)(H,{title:(0,i.__)("Reset","optimazz"),description:(0,i.__)("Restore every module to its default configuration. This cannot be undone.","optimazz")},(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-danger",icon:"image-rotate",onClick:async()=>{if(window.confirm((0,i.__)('Reset all Optimazz settings to defaults? You can undo this with "Undo last change".',"optimazz")))try{const e=await l()({url:`${r}/settings/reset`,method:"POST"});W(e.settings),O(e.settings),ae(!!e.hasBackup),ue((0,i.__)("Settings reset to defaults.","optimazz")),ie()}catch(e){ue(e.message||(0,i.__)("Reset failed.","optimazz"),"error")}}},(0,i.__)("Reset all settings to defaults","optimazz"))))),da=(0,a.createElement)(T,{icon:"editor-textcolor",title:(0,i.__)("Fonts","optimazz"),description:(0,i.__)("Swap, preconnect, and preload fonts to remove render-blocking requests.","optimazz"),enabled:!!Le.enabled,onToggle:()=>le("fonts","enabled")},(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Font loading (font-display)","optimazz"),help:(0,i.__)("Optional = no layout shift (best for CLS): the web font is skipped on the first uncached load and used once cached. Swap = text stays visible but the web font swaps in and can shift layout (higher CLS). Block = brief invisible text, then the web font.","optimazz"),value:Le.font_display||(Le.display_swap,"swap"),options:[{label:(0,i.__)("Optional — no layout shift (recommended for CLS)","optimazz"),value:"optional"},{label:(0,i.__)("Swap — always show text (may shift layout)","optimazz"),value:"swap"},{label:(0,i.__)("Block — brief hidden text, then web font","optimazz"),value:"block"}],onChange:e=>ne("fonts","font_display",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Combine Google Fonts into one request","optimazz"),help:(0,i.__)("Page builders often load each Google Font family separately. This merges them into a single request.","optimazz"),checked:!!Le.combine_google,onChange:e=>ne("fonts","combine_google",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load only the font weights your pages use","optimazz"),help:(0,i.__)("Drops unused weights (e.g. 100, 200, 900) and italics your pages never use — often cutting font CSS by more than half. Weights 400 and 700 are always kept. Turn off if some text looks thinner or bolder than intended.","optimazz"),checked:!!Le.trim_weights,disabled:!Le.combine_google,onChange:e=>ne("fonts","trim_weights",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Load Google Fonts without blocking the page","optimazz"),help:(0,i.__)("The page appears right away in a fallback font and switches to your Google Font as soon as it arrives, instead of waiting for Google first. A big win for the first paint on mobile. Applies when font display is Swap or Fallback; with Optional the stylesheet keeps loading first so text never shifts.","optimazz"),checked:!1!==Le.async_google,disabled:!Le.combine_google,onChange:e=>ne("fonts","async_google",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Preload these font files (URLs)","optimazz"),value:Le.preload||[],onChange:e=>ne("fonts","preload",e)})),pa=e=>{var a;if(!Ee||void 0===Ee[e.key])return e.help;const t=null!==(a=Ee[e.key])&&void 0!==a?a:0;
     8/* translators: %d: number of items found in the database. */return`${(0,i.sprintf)((0,i._n)("%d item in your database.","%d items in your database.",t,"optimazz"),t)} ${e.help}`},_a=(0,a.createElement)(H,{title:(0,i.__)("Database Optimization","optimazz"),description:(0,i.__)("Reduce bloat and reclaim space. Once a cleanup runs it cannot be undone, so back up your database first.","optimazz")},x.map(e=>(0,a.createElement)("div",{key:e.title,className:"oz-dbgroup"},(0,a.createElement)("h3",{className:"oz-dbgroup__title"},e.title),(0,a.createElement)("p",{className:"oz-dbgroup__desc"},e.desc),e.items.map(e=>{var t;return(0,a.createElement)(o.ToggleControl,{key:e.key,__nextHasNoMarginBottom:!0,label:Ee?`${e.label} (${null!==(t=Ee[e.key])&&void 0!==t?t:0})`:e.label,help:pa(e),checked:!!We[e.key],onChange:()=>le("database",e.key)})}))),(0,a.createElement)("div",{className:"oz-dbgroup"},(0,a.createElement)("h3",{className:"oz-dbgroup__title"},(0,i.__)("Set it and forget it","optimazz")),(0,a.createElement)("p",{className:"oz-dbgroup__desc"},(0,i.__)("Let Optimazz repeat the cleanups you picked above on a schedule, so your database stays lean without you lifting a finger.","optimazz")),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Schedule automatic cleanup","optimazz"),help:(0,i.__)("Uses WP-Cron to repeat the cleanups you enabled above.","optimazz"),checked:!!We.enabled,onChange:()=>le("database","enabled")}),We.enabled&&(0,a.createElement)(F,{label:(0,i.__)("Cleanup frequency","optimazz"),value:We.schedule||"off",onChange:e=>ne("database","schedule",e),options:[{label:(0,i.__)("Off","optimazz"),value:"off"},{label:(0,i.__)("Daily","optimazz"),value:"daily"},{label:(0,i.__)("Weekly","optimazz"),value:"weekly"},{label:(0,i.__)("Monthly","optimazz"),value:"monthly"}]})),(0,a.createElement)("div",{className:"oz-dbwarn"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("span",null,(0,i.__)("Back up your database before you run a cleanup — a database optimization cannot be undone.","optimazz"))),(0,a.createElement)("div",{className:"oz-inline-actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",isBusy:!!ke?.running,onClick:async()=>{if(ke?.running)return;const e=(0,i.__)("Cleaning and optimizing database…","optimazz");Ce({title:(0,i.__)("Optimizing database","optimazz"),subtitle:(0,i.__)("Cleanup & table optimization","optimazz"),pct:100,indeterminate:!0,label:e,step:"",logs:[{level:"INFO",text:e}],running:!0,status:"running",minimized:!1});try{const e=await l()({url:`${r}/database/optimize`,method:"POST",data:B.database||{}});ve(e.counts);const a=Object.values(e.removed||{}).reduce((e,a)=>e+a,0),t=`${(0,i.__)("Cleaned","optimazz")} ${a} ${(0,i.__)("rows,","optimazz")} ${e.tables} ${(0,i.__)("tables optimized.","optimazz")}`;Ce(e=>e&&{...e,running:!1,status:"done",indeterminate:!1,pct:100,label:t,logs:[...e.logs,{level:"INFO",text:t},{level:"DONE",text:(0,i.__)("Completed.","optimazz")}]}),ue(t)}catch(e){Ce(a=>a&&{...a,running:!1,status:"error",indeterminate:!1,logs:[...a.logs,{level:"ERROR",text:e.message||"Error"}]}),ue(e.message||(0,i.__)("Optimize failed.","optimazz"),"error")}}},(0,i.__)("Optimize now","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",onClick:Be},(0,i.__)("Refresh counts","optimazz")))),za={dashboard:ea,cache:na,files:la,media:ia,fonts:da,preload:sa,advanced:ra,database:_a,heartbeat:(0,a.createElement)(H,{title:(0,i.__)("Heartbeat Control","optimazz"),description:(0,i.__)("Reduce or disable the WordPress Heartbeat API to lower server load.","optimazz")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Enable Heartbeat control","optimazz"),checked:!!je.enabled,onChange:()=>le("heartbeat","enabled")}),je.enabled&&(0,a.createElement)(a.Fragment,null,(0,a.createElement)(F,{label:(0,i.__)("Behavior in admin dashboard","optimazz"),value:je.backend||"default",options:S,onChange:e=>ne("heartbeat","backend",e)}),(0,a.createElement)(F,{label:(0,i.__)("Behavior in post editor","optimazz"),value:je.editor||"default",options:S,onChange:e=>ne("heartbeat","editor",e)}),(0,a.createElement)(F,{label:(0,i.__)("Behavior on front-end","optimazz"),value:je.frontend||"default",options:S,onChange:e=>ne("heartbeat","frontend",e)}))),tweaks:(0,a.createElement)(T,{icon:"admin-generic",title:(0,i.__)("Tweaks","optimazz"),description:(0,i.__)("Strip unnecessary WordPress front-end weight.","optimazz"),enabled:!!Ie.enabled,onToggle:()=>le("tweaks","enabled")},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable emojis","optimazz"),help:(0,i.__)("Remove the emoji detection script and styles.","optimazz"),checked:!!Ie.disable_emojis,onChange:()=>le("tweaks","disable_emojis")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable embeds (oEmbed)","optimazz"),checked:!!Ie.disable_embeds,onChange:()=>le("tweaks","disable_embeds")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable dashicons on front-end","optimazz"),help:(0,i.__)("Only for anonymous visitors.","optimazz"),checked:!!Ie.disable_dashicons,onChange:()=>le("tweaks","disable_dashicons")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove query strings from assets","optimazz"),help:(0,i.__)("Strip ?ver= for better proxy caching.","optimazz"),checked:!!Ie.remove_query_strings,onChange:()=>le("tweaks","remove_query_strings")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable XML-RPC","optimazz"),checked:!!Ie.disable_xmlrpc,onChange:()=>le("tweaks","disable_xmlrpc")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Disable self-pingbacks","optimazz"),checked:!!Ie.disable_pingback,onChange:()=>le("tweaks","disable_pingback")}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Remove head meta (generator, RSD, shortlink)","optimazz"),checked:!!Ie.remove_meta,onChange:()=>le("tweaks","remove_meta")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("DNS-prefetch domains","optimazz"),value:Ie.dns_prefetch||[],onChange:e=>ne("tweaks","dns_prefetch",e)}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Preconnect origins","optimazz"),value:Ie.preconnect||[],onChange:e=>ne("tweaks","preconnect",e)})),cdn:(0,a.createElement)(T,{icon:"cloud",title:(0,i.__)("Content Delivery Network","optimazz"),description:(0,i.__)("Rewrite static asset URLs to your CDN hostname.","optimazz"),enabled:!!qe.enabled,onToggle:()=>le("cdn","enabled")},(0,a.createElement)(o.TextControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("CDN URL (CNAME)","optimazz"),placeholder:"https://cdn.example.com",value:qe.url||"",onChange:e=>ne("cdn","url",e),help:(0,i.__)("CSS, JS, image, and font URLs will be served from here.","optimazz")}),(0,a.createElement)(o.FormTokenField,{label:(0,i.__)("Exclude paths containing","optimazz"),value:qe.exclusions||[],onChange:e=>ne("cdn","exclusions",e)})),tools:ma};return p.forEach(e=>{za[e.key]="function"==typeof e.render?e.render():e.render}),(0,a.createElement)(A,{items:_.map(e=>({...e,dot:!(!e.mod||!B[e.mod]?.enabled)})),nav:V,onNav:K,foot:`v${window.optimazzData?.version}`,topIcon:Ue.icon,title:Ue.title,sub:Ue.sub,dirty:oe,saving:Y,onSave:()=>ge(B),toast:Q,onToastRemove:()=>X(null),after:(0,a.createElement)(M,{job:ke,onStop:()=>{Ne.current=!0},onClose:()=>{Ne.current=!0,Ce(null)},onToggleMin:()=>Ce(e=>e&&{...e,minimized:!e.minimized})})},za[V])},null));const W={enabled:!1,cache_mobile:!0,cache_logged_in:!1,lifespan_hours:0,preload:!0,preload_interval:"twicedaily",browser_cache:!1},j=document.getElementById("optimazz-network-app");j&&(0,t.createRoot)(j).render((0,a.createElement)(function(){const[e,n]=(0,t.useState)(null),[s,c]=(0,t.useState)(W),[m,d]=(0,t.useState)(!1),[p,_]=(0,t.useState)(!1),[z,h]=(0,t.useState)(""),[u,g]=(0,t.useState)(null),[b,f]=(0,t.useState)("cache"),[E,y]=(0,t.useState)(null),w=(0,t.useRef)(!1),k=async e=>{if(E?.running)return;w.current=!1;const a=(0,i.__)("Preloading cache…","optimazz");y({title:(0,i.__)("Preloading cache","optimazz"),subtitle:e.name||e.url,pct:0,label:a,step:"",logs:[{level:"INFO",text:a}],running:!0,status:"running",minimized:!1});const t={cached:0,missed:0,diagnosis:""},o=Date.now();let s=0,c=0;const m=(e,a,t,o={})=>y(n=>n&&{...n,running:!1,status:e,...o,logs:[...n.logs,{level:a,text:t}]});try{for(;;){var d;if(w.current){m("stopped","WARN",(0,i.__)("Stopped by user.","optimazz"));break}const a=await l()({url:`${r}/network/preload-batch`,method:"POST",data:{site_id:e.id,offset:s}}),n=s;s=null!==(d=a.next_offset)&&void 0!==d?d:s+(a.processed||0),c=a.total||c,t.cached+=a.cached||0,t.missed+=a.missed||0,t.diagnosis=a.diagnosis||t.diagnosis;const p=c?Math.min(100,Math.round(s/c*100)):100,_=`${(0,i.__)("Warmed","optimazz")} ${Math.min(s,c)} / ${c} ${(0,i.__)("pages","optimazz")} · ${t.cached} ${(0,i.__)("cached","optimazz")}${t.missed?` · ${t.missed} ${(0,i.__)("not cached","optimazz")}`:""}`,z=s/Math.max((Date.now()-o)/1e3,.001),h=z>0&&c&&s<c?(c-s)/z:0,u=h>=60?` · ~${Math.round(h/60)}m ${(0,i.__)("left","optimazz")}`:h>=1?` · ~${Math.round(h)}s ${(0,i.__)("left","optimazz")}`:"";if(y(e=>e&&{...e,pct:p,label:_,step:c?`${Math.min(s,c)} / ${c}${u}`:"",logs:[...e.logs.slice(-60),...a.breakdown?[{level:"INFO",text:`${(0,i.sprintf)(/* translators: %d: number of pages to warm. */ /* translators: %d: number of pages to warm. */
     9(0,i.__)("Found %d pages to warm:","optimazz"),a.total||0)} ${a.breakdown}.`}]:[],{level:"INFO",text:_}]}),a.blocked){m("error","ERROR",(0,i.__)('Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the "Optimazz-CacheWarmer" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter.',"optimazz"));break}if(!a.done&&0===t.cached&&t.missed>=3){m("error","ERROR",`${(0,i.__)("Stopped: pages are loading but none are being saved to the cache.","optimazz")} ${t.diagnosis}`,{label:(0,i.__)("Pages are not being cached","optimazz")});break}if(a.done||s<=n){t.missed&&y(e=>e&&{...e,logs:[...e.logs,{level:"WARN",text:`${(0,i.sprintf)(/* translators: %d: number of pages. */ /* translators: %d: number of pages. */
     10(0,i._n)("%d page was not cached.","%d pages were not cached.",t.missed,"optimazz"),t.missed)} ${t.diagnosis}`}]}),m("done","DONE",(0,i.__)("Completed.","optimazz"),{pct:100});break}}}catch(e){m("error","ERROR",e?.message||(0,i.__)("Something went wrong.","optimazz"))}l()({url:`${r}/network`}).then(e=>{n(e)}).catch(()=>{})},C=e=>{n(e),c({...W,...e.settings||{}}),d(!1)},N=e=>g({status:"error",text:e?.message||(0,i.__)("Something went wrong.","optimazz")}),x=async(e,a,t,o)=>{h(t);try{const t=await l()({url:`${r}${e}`,method:"POST",data:a});return n(t),m||c({...W,...t.settings||{}}),o&&g({status:"success",text:o}),h(""),!0}catch(e){N(e)}return h(""),!1};(0,t.useEffect)(()=>{l()({url:`${r}/network`}).then(C).catch(N)},[]);const S=(e,a)=>{c(t=>({...t,[e]:a})),d(!0)};if(!e)return(0,a.createElement)("div",{className:"oz-shell oz-shell--network-app"},(0,a.createElement)("div",{className:"oz-main"},(0,a.createElement)(o.Spinner,null)));const $=e.health||{},B=e.sites||[],P=B.filter(e=>e.active),O=!!e.settings?.enabled,R=[{key:"cache",svg:"cache",icon:"database",title:(0,i.__)("Page Cache","optimazz"),sub:(0,i.__)("Caching for every site","optimazz"),dot:O},{key:"sites",svg:"dashboard",icon:"admin-multisite",title:(0,i.__)("Sites","optimazz"),sub:(0,i.__)("Cache per site","optimazz")}],L=R.find(e=>e.key===b)||R[0],j=(0,a.createElement)(a.Fragment,null,!e.network_active&&(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-info-outline"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.__)("Optimazz is not network-activated","optimazz")),(0,a.createElement)("p",null,(0,i.__)("Only the sites where Optimazz is activated are cached. Network-activate it on Network Admin → Plugins to cache every site.","optimazz")))),(0,a.createElement)(T,{icon:"database",title:(0,i.__)("Page Cache","optimazz"),description:(0,i.__)("Serve ready-made HTML for every site instead of running PHP on each request. Each site keeps its own cache and its own exclusions.","optimazz"),enabled:!!s.enabled,onToggle:()=>S("enabled",!s.enabled)},(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache mobile devices separately","optimazz"),checked:!!s.cache_mobile,onChange:()=>S("cache_mobile",!s.cache_mobile)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Cache logged-in users","optimazz"),help:(0,i.__)("Leave off unless you know what you are doing.","optimazz"),checked:!!s.cache_logged_in,onChange:()=>S("cache_logged_in",!s.cache_logged_in)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Browser cache headers","optimazz"),help:(0,i.__)("Add Expires / Cache-Control rules to the shared .htaccess so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only.","optimazz"),checked:!!s.browser_cache,onChange:()=>S("browser_cache",!s.browser_cache)}),(0,a.createElement)(F,{label:(0,i.__)("Cache lifetime","optimazz"),help:(0,i.__)("How long a cached page is kept. Pages are always purged as soon as their content changes.","optimazz"),value:Number(s.lifespan_hours)||0,options:[{value:0,label:(0,i.__)("Never","optimazz")},{value:12,label:(0,i.__)("12 hours","optimazz")},{value:24,label:(0,i.__)("1 day","optimazz")},{value:168,label:(0,i.__)("1 week","optimazz")},{value:720,label:(0,i.__)("1 month","optimazz")}],onChange:e=>S("lifespan_hours",e)}),(0,a.createElement)(o.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,i.__)("Preload every site automatically","optimazz"),help:(0,i.__)("Each site warms its own pages on a schedule and right after its cache is cleared.","optimazz"),checked:!!s.preload,onChange:()=>S("preload",!s.preload)}),(0,a.createElement)(o.SelectControl,{__nextHasNoMarginBottom:!0,__next40pxDefaultSize:!0,label:(0,i.__)("Automatic preload","optimazz"),value:s.preload_interval||"twicedaily",disabled:!s.preload,options:[{label:(0,i.__)("Every hour","optimazz"),value:"hourly"},{label:(0,i.__)("Twice a day","optimazz"),value:"twicedaily"},{label:(0,i.__)("Once a day","optimazz"),value:"daily"}],onChange:e=>S("preload_interval",e)})),(O||$.conflict)&&(0,a.createElement)(H,{title:(0,i.__)("Cache health","optimazz")},$.conflict?(0,a.createElement)("div",{className:"oz-cachewarn",role:"status"},(0,a.createElement)("span",{className:"dashicons dashicons-warning"}),(0,a.createElement)("div",{className:"oz-cachewarn__text"},(0,a.createElement)("strong",null,(0,i.sprintf)(/* translators: %s: name of another caching plugin. */ /* translators: %s: name of another caching plugin. */
     11(0,i.__)("%s controls page caching (advanced-cache.php)","optimazz"),$.conflict_name||(0,i.__)("Another plugin","optimazz"))),(0,a.createElement)("p",null,(0,i.__)("Optimazz cannot cache while another plugin owns the cache file. Deactivate that plugin, or let Optimazz take over.","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-primary",isBusy:"takeover"===z,onClick:()=>x("/network/takeover",{},"takeover",(0,i.__)("Optimazz now handles page caching.","optimazz"))},(0,i.__)("Use Optimazz for caching","optimazz")))):(0,a.createElement)(a.Fragment,null,(0,a.createElement)(D,{ok:$.dropin_ours,label:(0,i.__)("Cache engine installed (advanced-cache.php)","optimazz")}),(0,a.createElement)(D,{ok:$.wp_cache,label:(0,i.__)("WP_CACHE is on in wp-config.php","optimazz")}),!$.wpconfig_writable&&!$.wp_cache&&(0,a.createElement)(D,{ok:!1,label:(0,i.__)("wp-config.php is not writable — add define( 'WP_CACHE', true ); yourself","optimazz")}),!$.content_writable&&(0,a.createElement)(D,{ok:!1,label:(0,i.__)("The wp-content folder is not writable","optimazz")})))),I=(0,a.createElement)(a.Fragment,null,(0,a.createElement)(H,{title:(0,i.__)("All sites","optimazz"),description:(0,i.sprintf)(/* translators: 1: sites with Optimazz, 2: all sites. */ /* translators: 1: sites with Optimazz, 2: all sites. */
     12(0,i.__)("Optimazz runs on %1$d of %2$d sites. Each site has its own cache.","optimazz"),P.length,B.length)},!O&&(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Page caching is off for the network — turn it on under Page Cache to purge or preload sites.","optimazz")),(0,a.createElement)("div",{className:"oz-quick__actions"},(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-btn--x",icon:"dismiss",disabled:!O,isBusy:"purge-all"===z,onClick:()=>x("/network/purge",{},"purge-all",(0,i.__)("Every site’s cache was purged.","optimazz"))},(0,i.__)("Purge all sites","optimazz")))),(0,a.createElement)("div",{className:"oz-quick oz-network__sites"},B.map(e=>(0,a.createElement)(H,{key:e.id,title:e.name||e.url,description:e.url},e.active?(0,a.createElement)(a.Fragment,null,(0,a.createElement)("div",{className:"oz-quick__actions"},(0,a.createElement)(o.Button,{className:"oz-btn-primary",icon:"trash",disabled:!O||!!E?.running,isBusy:`clear-${e.id}`===z,onClick:()=>(async e=>{await x("/network/purge",{site_id:e.id},`clear-${e.id}`)&&k(e)})(e)},(0,i.__)("Clear & Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost",icon:"update",disabled:!O||!!E?.running,onClick:()=>k(e)},(0,i.__)("Preload","optimazz")),(0,a.createElement)(o.Button,{className:"oz-btn-ghost oz-btn--x",icon:"dismiss",disabled:!O,isBusy:`purge-${e.id}`===z,onClick:()=>x("/network/purge",{site_id:e.id},`purge-${e.id}`,(0,i.__)("Site cache purged.","optimazz"))},(0,i.__)("Purge cache","optimazz"))),(0,a.createElement)("ul",{className:"oz-cachelist"},(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-media-text"}),(0,i.__)("Cached pages","optimazz"),(0,a.createElement)("strong",null,e.pages)),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-database"}),(0,i.__)("Page cache size","optimazz"),(0,a.createElement)("strong",null,v(e.bytes))),(0,a.createElement)("li",null,(0,a.createElement)("span",{className:"dashicons dashicons-admin-generic"}),(0,i.__)("Optimazz settings","optimazz"),(0,a.createElement)("a",{className:"oz-cachelist__link",href:e.admin_url},(0,i.__)("Open","optimazz")," →")))):(0,a.createElement)("p",{className:"oz-muted"},(0,i.__)("Optimazz is not active on this site, so its pages are not cached.","optimazz"))))));return(0,a.createElement)(A,{className:"oz-shell--network-app",items:R,nav:b,onNav:f,foot:`${(0,i.__)("Network","optimazz")} · v${window.optimazzData?.version}`,topIcon:L.icon,title:L.title,sub:L.sub,dirty:m,saving:p,onSave:async()=>{_(!0);try{C(await l()({url:`${r}/network/settings`,method:"POST",data:{settings:s}})),g({status:"success",text:(0,i.__)("Network settings saved.","optimazz")})}catch(e){N(e)}_(!1)},toast:u,onToastRemove:()=>g(null),after:(0,a.createElement)(M,{job:E,onStop:()=>{w.current=!0},onClose:()=>{w.current=!0,y(null)},onToggleMin:()=>y(e=>e&&{...e,minimized:!e.minimized})})},"sites"===b?I:j)},null))})();
  • optimazz/trunk/changelog.txt

    r3723776 r3743505  
    11Optimazz – Website Optimization and Speedup — full changelog
     2
     3= 1.1.0 =
     4* Fixed: Google Fonts never loaded on sites whose theme or plugin requests them in the old weight format through the new Google Fonts address (for example "Open Sans:400,700" or "w400,700"). Google rejects those requests, so the font was missing while the broken stylesheet still held back the first paint on mobile by most of a second. Optimazz now repairs them into a valid request.
     5* New: Google Fonts load without blocking the page (on by default, Fonts settings). With font display Swap or Fallback the page appears right away in a fallback font and switches when the web font arrives, instead of waiting for Google first. With Optional (zero layout shift) the stylesheet still loads first, so text never shifts.
     6* Improved: sites using Jetpack's Site Accelerator load WordPress core stylesheets from c0.wp.com, which Optimazz used to skip as external, so they stayed render-blocking. When the version matches your WordPress, they are now minified, combined and loaded like local files.
     7* Improved: more chat widgets are delayed until the visitor interacts — OpenWidget, Zendesk, Drift, Freshchat / Freshworks, JivoChat, Smartsupp and Small Chat — cutting mobile Total Blocking Time.
     8* New: full multisite support. Network-activate Optimazz and manage page caching for the whole network in Network Admin → Optimazz: switch it on once, choose mobile and logged-in caching, cache lifetime, browser-cache rules and automatic preload, and see every site with its cached pages and its own Purge and Preload buttons. Each site gets its own cache — on subdirectory and subdomain networks — keeps its own exclusions (Advanced Rules) and all its other Optimazz settings, and can purge and preload its own pages.
     9* Improved: on multisite only the network administrator controls the files every site shares (wp-config.php, advanced-cache.php, .htaccess); a site's own settings never change them, so one site can't switch caching on or off for the others.
     10* Fixed: before this version, on multisite one site's settings controlled caching for every site, only one site of a subdomain network was actually cached, any site administrator could rewrite wp-config.php and .htaccess, and deactivating Optimazz on one site removed the cache for the whole network.
     11* Fixed: deleting Optimazz from a multisite network only cleaned up the main site; settings, scheduled tasks, image data and generated CSS of the other sites were left behind. Uninstall now cleans every site.
     12* Fixed: on sites using plain permalinks (addresses like ?page_id=5) the "Cached pages" count showed 1 no matter how many pages were cached.
     13* Improved: the preload summary now names the home page and counts each address once, so its parts add up to the total.
     14* Improved: changing a cache rule (for example a new exclusion) now also clears pages that were cached under the old rules.
     15* Improved: the Changelog section of this readme now lists recent versions only; the complete history is in changelog.txt.
    216
    317= 1.0.24 =
  • optimazz/trunk/includes/Admin/Admin.php

    r3715895 r3743505  
    1818
    1919        /**
     20         * Network Admin page slug (multisite).
     21         */
     22        const NETWORK_SLUG = 'optimazz-network';
     23
     24        /**
    2025         * @var Settings
    2126         */
    … …  
    4045                add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
    4146                add_action( 'admin_notices', array( $this, 'render_notices' ) );
     47                add_action( 'network_admin_menu', array( $this, 'add_network_menu' ) );
    4248                // Hide third-party admin notices on the Optimazz screen to keep it clean.
    4349                add_action( 'in_admin_header', array( $this, 'suppress_foreign_notices' ), 1000 );
    … …  
    120126                }
    121127                $screen = get_current_screen();
    122                 return $screen && 'toplevel_page_' . self::MENU_SLUG === $screen->id;
     128                return $screen && in_array( $screen->id, array( 'toplevel_page_' . self::MENU_SLUG, 'toplevel_page_' . self::NETWORK_SLUG . '-network' ), true );
    123129        }
    124130
    … …  
    276282
    277283        /**
     284         * Network Admin → Optimazz (super admins): network page caching and sites.
     285         *
     286         * @return void
     287         */
     288        public function add_network_menu() {
     289                add_menu_page(
     290                        __( 'Optimazz', 'optimazz' ),
     291                        __( 'Optimazz', 'optimazz' ),
     292                        'manage_network_options',
     293                        self::NETWORK_SLUG,
     294                        array( $this, 'render_network' ),
     295                        'dashicons-performance',
     296                        81
     297                );
     298        }
     299
     300        /**
     301         * Render the Network Admin mount node.
     302         *
     303         * @return void
     304         */
     305        public function render_network() {
     306                echo '<div class="wrap"><div id="optimazz-network-app"></div></div>';
     307        }
     308
     309        /**
    278310         * Enqueue the built React app on our page only.
    279311         *
    … …  
    282314         */
    283315        public function enqueue( $hook ) {
    284                 if ( 'toplevel_page_' . self::MENU_SLUG !== $hook ) {
     316                $network = 'toplevel_page_' . self::NETWORK_SLUG === $hook;
     317                if ( 'toplevel_page_' . self::MENU_SLUG !== $hook && ! $network ) {
    285318                        return;
    286319                }
    … …  
    356389                                'wpconfigUnwritable' => (bool) get_transient( 'optimazz_wpconfig_unwritable' ),
    357390                                'hasBackup'          => $this->settings->has_backup(),
     391                                'serverFeatures'     => \Optimazz\Core\Settings::server_features_allowed(),
     392                                'multisite'          => is_multisite(),
     393                                // 'network' screen = Network Admin → Optimazz; 'site' = a site's own screen.
     394                                'context'            => $network ? 'network' : 'site',
     395                                'networkCache'       => is_multisite() ? \Optimazz\Core\Network::settings() : null,
     396                                'networkAdminUrl'    => is_multisite() ? network_admin_url( 'admin.php?page=' . self::NETWORK_SLUG ) : '',
     397                                'isSuperAdmin'       => is_super_admin(),
    358398                        )
    359399                );
  • optimazz/trunk/includes/Admin/AdminBar.php

    r3617648 r3743505  
    5050
    5151                $items = array(
    52                         'optimazz-settings'      => array( __( 'Settings', 'optimazz' ), $settings ),
    53                         'optimazz-clear-preload' => array( __( 'Clear & Preload Cache', 'optimazz' ), $this->action_url( 'optimazz_clear_preload' ) ),
    54                         'optimazz-preload'       => array( __( 'Preload Cache', 'optimazz' ), $this->action_url( 'optimazz_preload' ) ),
    55                         'optimazz-purge'         => array( __( 'Purge Cache', 'optimazz' ), $this->action_url( 'optimazz_purge' ) ),
     52                        'optimazz-settings' => array( __( 'Settings', 'optimazz' ), $settings ),
    5653                );
     54                // Cache actions only where this site has a page cache (on multisite: when
     55                // the network admin has switched network caching on).
     56                if ( ! is_multisite() || \Optimazz\Core\Network::cache_enabled() ) {
     57                        $items['optimazz-clear-preload'] = array( __( 'Clear & Preload Cache', 'optimazz' ), $this->action_url( 'optimazz_clear_preload' ) );
     58                        $items['optimazz-preload']       = array( __( 'Preload Cache', 'optimazz' ), $this->action_url( 'optimazz_preload' ) );
     59                        $items['optimazz-purge']         = array( __( 'Purge Cache', 'optimazz' ), $this->action_url( 'optimazz_purge' ) );
     60                }
    5761                foreach ( $items as $id => $item ) {
    5862                        $bar->add_node(
    … …  
    9599         */
    96100        private function guard( $action ) {
    97                 if ( ! current_user_can( 'manage_options' ) || ! check_admin_referer( $action ) ) {
     101                if ( ( is_multisite() && ! \Optimazz\Core\Network::cache_enabled() ) || ! current_user_can( 'manage_options' ) || ! check_admin_referer( $action ) ) {
    98102                        wp_die( esc_html__( 'Not allowed.', 'optimazz' ) );
    99103                }
  • optimazz/trunk/includes/Admin/CacheConflictNotice.php

    r3723128 r3743505  
    5959         */
    6060        public function render() {
    61                 if ( ! current_user_can( 'manage_options' ) ) {
     61                if ( is_multisite() || ! current_user_can( 'manage_options' ) ) {
    6262                        return;
    6363                }
    … …  
    121121         */
    122122        private function guard( $action ) {
    123                 if ( ! current_user_can( 'manage_options' ) || ! check_admin_referer( $action ) ) {
     123                // Multisite: never take over the shared drop-in or (network-)deactivate
     124                // another plugin from a site — the same rule as the REST endpoints.
     125                if ( ! \Optimazz\Core\Settings::server_features_allowed() || ! current_user_can( 'manage_options' ) || ! check_admin_referer( $action ) ) {
    124126                        wp_die( esc_html__( 'Not allowed.', 'optimazz' ) );
    125127                }
  • optimazz/trunk/includes/Admin/RestController.php

    r3723128 r3743505  
    278278                // modules emit into the page, so stale cached HTML must be cleared for the
    279279                // change to take effect immediately (the whole point of Safe Mode).
     280                // 'cache' is included so a new exclusion (or lifetime, mobile split …)
     281                // also drops pages that were cached under the old rules.
    280282                $purged = false;
    281                 foreach ( array( 'css', 'js', 'media', 'fonts', 'cdn', 'tweaks', 'tools' ) as $mod ) {
     283                foreach ( array( 'cache', 'css', 'js', 'media', 'fonts', 'cdn', 'tweaks', 'tools' ) as $mod ) {
    282284                        $old = isset( $before[ $mod ] ) ? $before[ $mod ] : array();
    283285                        $new = isset( $saved[ $mod ] ) ? $saved[ $mod ] : array();
     286                        if ( 'cache' === $mod ) {
     287                                // Preload schedule and browser-cache headers don't change the
     288                                // cached HTML — changing them must not throw the cache away.
     289                                foreach ( array( 'preload', 'preload_interval', 'browser_cache' ) as $key ) {
     290                                        unset( $old[ $key ], $new[ $key ] );
     291                                }
     292                        }
    284293                        if ( wp_json_encode( $old ) !== wp_json_encode( $new ) ) {
    285294                                $purged = true;
    … …  
    373382         */
    374383        public function purge_cache() {
     384                if ( is_multisite() && ! \Optimazz\Core\Network::cache_enabled() ) {
     385                        return new WP_REST_Response( array( 'message' => __( 'Page caching is not available on multisite networks.', 'optimazz' ) ), 400 );
     386                }
    375387                Manager::storage()->purge_all();
    376388                return new WP_REST_Response( array( 'message' => 'purged' ), 200 );
    … …  
    573585         */
    574586        public function cache_takeover( WP_REST_Request $request ) {
     587                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     588                        return new WP_REST_Response( array( 'message' => __( 'Page caching is not available on multisite networks.', 'optimazz' ) ), 400 );
     589                }
    575590                // Make sure the page-cache module is on, then force our drop-in in.
    576591                $cache = $this->settings->all();
    … …  
    596611         */
    597612        public function cache_deactivate_conflict( WP_REST_Request $request ) {
     613                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     614                        return new WP_REST_Response( array( 'message' => __( 'Page caching is not available on multisite networks.', 'optimazz' ) ), 400 );
     615                }
    598616                $plugin  = (string) $request->get_param( 'plugin' );
    599617                if ( '' === $plugin || '' === Dropin::known_cache_plugin_name( $plugin ) ) {
  • optimazz/trunk/includes/Core/Lifecycle.php

    r3723128 r3743505  
    1919         * actually enables page caching (handled by the Cache module).
    2020         *
     21         * @param bool $network_wide Whether this is a network-wide activation.
    2122         * @return void
    2223         */
    23         public static function activate() {
     24        public static function activate( $network_wide = false ) {
     25                // Multisite: add this site (or every site) to the cache site map. The
     26                // activated_plugin listener isn't registered yet in the activation
     27                // request, so queue the sync here.
     28                if ( is_multisite() ) {
     29                        Network::queue_sync();
     30                }
     31                // Network activation: every site starts from the defaults the first time
     32                // its settings are read, and the shared cache files follow the network
     33                // settings (Network::on_plugin_change() re-syncs the site map).
     34
    2435                if ( false === get_option( Settings::OPTION_KEY, false ) ) {
    2536                        add_option( Settings::OPTION_KEY, Settings::defaults() );
    … …  
    6879                update_option( self::VERSION_KEY, OPTIMAZZ_VERSION, false );
    6980
     81                // Multisite: the shared files follow the network settings. Sync them once
     82                // per network per version (this also removes a drop-in that a pre-1.1.0
     83                // site installed, unless the network turned caching on); then bring this
     84                // site's own rules and schedule up to date and rebuild its cache.
     85                if ( is_multisite() ) {
     86                        if ( OPTIMAZZ_VERSION !== get_site_option( Network::VERSION_KEY ) ) {
     87                                Network::sync();
     88                                \Optimazz_Cache_Engine::remove_mixed_case_hosts(); // Orphans of pre-1.1.0 mixed-case Host folders.
     89                        }
     90                        if ( class_exists( '\Optimazz\Modules\Cache\Manager' ) ) {
     91                                \Optimazz\Modules\Cache\Manager::sync( $settings->for_module( 'cache' ) );
     92                                \Optimazz\Modules\Cache\Manager::purge_and_rewarm();
     93                        }
     94                        if ( Network::cache_enabled() && class_exists( '\Optimazz\Core\Guard' ) ) {
     95                                Guard::schedule(); // Same post-update self-check as on a single site.
     96                        }
     97                        return;
     98                }
     99
    70100                // Only touch caching when the user actually has it enabled.
    71101                $cache = $settings->for_module( 'cache' );
    … …  
    91121         * so a disabled plugin never serves stale cached pages.
    92122         *
     123         * @param bool $network_wide Whether this is a network-wide deactivation.
    93124         * @return void
    94125         */
    95         public static function deactivate() {
    96                 if ( class_exists( '\Optimazz\Modules\Cache\Dropin' ) ) {
    97                         Dropin::uninstall();
     126        public static function deactivate( $network_wide = false ) {
     127                // Multisite, one site: the shared drop-in still serves the other sites.
     128                // Clear this site's cache and jobs; Network::on_plugin_change() drops it
     129                // from the site map so the engine stops caching it.
     130                if ( is_multisite() && ! $network_wide ) {
     131                        if ( class_exists( '\Optimazz\Modules\Cache\Manager' ) ) {
     132                                \Optimazz\Modules\Cache\Manager::storage()->purge_all();
     133                        }
     134                        self::clear_events();
     135                        return;
    98136                }
    99                 if ( class_exists( '\Optimazz\Modules\Cache\BrowserCache' ) ) {
    100                         \Optimazz\Modules\Cache\BrowserCache::apply( false );
     137                Network::with_shared_writes(
     138                        static function () {
     139                                if ( class_exists( '\Optimazz\Modules\Cache\Dropin' ) ) {
     140                                        Dropin::uninstall();
     141                                }
     142                                if ( class_exists( '\Optimazz\Modules\Cache\BrowserCache' ) ) {
     143                                        \Optimazz\Modules\Cache\BrowserCache::apply( false );
     144                                }
     145                        }
     146                );
     147                // Scheduled events live per site. A network deactivation runs once, so
     148                // clear every site.
     149                if ( is_multisite() && $network_wide ) {
     150                        foreach ( get_sites( array( 'fields' => 'ids', 'number' => 0 ) ) as $site_id ) {
     151                                switch_to_blog( $site_id );
     152                                self::clear_events();
     153                                restore_current_blog();
     154                        }
     155                } else {
     156                        self::clear_events();
    101157                }
    102                 wp_clear_scheduled_hook( 'optimazz_preload_cache' );
    103                 wp_clear_scheduled_hook( 'optimazz_preload_cache_once' );
    104                 wp_clear_scheduled_hook( 'optimazz_purge_expired' );
    105                 wp_clear_scheduled_hook( 'optimazz_db_cleanup' );
    106                 wp_clear_scheduled_hook( 'optimazz_health_check' );
     158        }
     159
     160        /**
     161         * Remove this site's Optimazz scheduled events.
     162         *
     163         * @return void
     164         */
     165        private static function clear_events() {
     166                foreach ( array( 'optimazz_preload_cache', 'optimazz_preload_cache_once', 'optimazz_purge_expired', 'optimazz_db_cleanup', 'optimazz_health_check' ) as $hook ) {
     167                        wp_clear_scheduled_hook( $hook );
     168                }
    107169        }
    108170}
  • optimazz/trunk/includes/Core/Paths.php

    r3617648 r3743505  
    2525        public static function url_to_path( $url ) {
    2626                $host = wp_parse_url( $url, PHP_URL_HOST );
     27                if ( 'c0.wp.com' === $host ) {
     28                        return self::jetpack_core_path( $url );
     29                }
    2730                if ( $host && $host !== wp_parse_url( home_url(), PHP_URL_HOST ) ) {
    2831                        return '';
    … …  
    4851                return ABSPATH . ltrim( $path, '/' );
    4952        }
     53
     54        /**
     55         * Jetpack's Site Accelerator serves WordPress core files from
     56         * c0.wp.com/c/<version>/wp-includes/… . When that version is the one
     57         * installed here, the file is identical to our own copy, so it can be
     58         * minified, combined and loaded without blocking like any local file —
     59         * otherwise these core stylesheets stay render-blocking on every page.
     60         *
     61         * @param string $url c0.wp.com URL.
     62         * @return string Local path, or '' when it is not a matching core file.
     63         */
     64        private static function jetpack_core_path( $url ) {
     65                $path = (string) wp_parse_url( strtok( (string) $url, '?' ), PHP_URL_PATH );
     66                if ( false !== strpos( $path, '..' ) || ! preg_match( '#^/c/([^/]+)/((?:wp-includes|wp-admin)/.+)$#', $path, $m ) ) {
     67                        return '';
     68                }
     69                if ( $m[1] !== get_bloginfo( 'version' ) ) {
     70                        return ''; // A different WordPress version: the files may differ.
     71                }
     72                return ABSPATH . $m[2];
     73        }
    5074}
  • optimazz/trunk/includes/Core/Plugin.php

    r3723128 r3743505  
    1111use Optimazz\Admin\AdminBar;
    1212use Optimazz\Admin\CacheConflictNotice;
     13use Optimazz\Admin\NetworkRestController;
    1314use Optimazz\Admin\RestController;
    1415use Optimazz\Modules\Cache\CacheModule;
    … …  
    9596                // Mode if a post-update front page renders unstyled.
    9697                Guard::register();
     98                Network::register(); // Multisite: keep the site map in step with the network.
    9799
    98100                // One-time reconciliation after a version change (update). Clears cached
    … …  
    109111
    110112                ( new RestController( $this->settings, $this->loader ) )->register();
     113                ( new NetworkRestController() )->register(); // Multisite: Network Admin → Optimazz.
    111114
    112115                // Translations are auto-loaded by WordPress for .org-hosted plugins
  • optimazz/trunk/includes/Core/Recommendations.php

    r3700289 r3743505  
    5656                }
    5757
    58                 // ---- Page cache (biggest single win) ----
    59                 if ( ! $on( $cache, 'enabled' ) ) {
     58                // ---- Page cache (biggest single win; not available on multisite) ----
     59                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     60                        // Nothing to recommend — see Settings::server_features_allowed().
     61                } elseif ( ! $on( $cache, 'enabled' ) ) {
    6062                        $recs[] = self::rec( 'page_cache', 'high',
    6163                                __( 'Enable Page Cache', 'optimazz' ),
    … …  
    6365                                array( 'cache', 'enabled', true )
    6466                        );
    65                 } elseif ( ! $on( $cache, 'preload' ) ) {
     67                } elseif ( ! $on( $cache, 'preload' ) && ! is_multisite() ) {
    6668                        $recs[] = self::rec( 'preload', 'medium',
    6769                                __( 'Turn on cache preloading', 'optimazz' ),
  • optimazz/trunk/includes/Core/Settings.php

    r3723128 r3743505  
    114114                                'combine_google' => true, // Merge all Google Fonts requests into one.
    115115                                'trim_weights'   => true, // Request only the font weights the page uses.
     116                                'async_google'   => true, // Load the Google Fonts stylesheet without blocking rendering.
    116117                                'preload'      => array(),
    117118                        ),
    … …  
    153154                if ( null === $this->cache ) {
    154155                        $stored      = get_option( self::OPTION_KEY, array() );
    155                         $this->cache = $this->merge_deep( self::defaults(), is_array( $stored ) ? $stored : array() );
     156                        $this->cache = self::enforce_platform( $this->merge_deep( self::defaults(), is_array( $stored ) ? $stored : array() ) );
    156157                }
    157158                return $this->cache;
     159        }
     160
     161        /**
     162         * Whether the current code may write SHARED server files.
     163         *
     164         * Page cache (wp-config.php WP_CACHE, wp-content/advanced-cache.php and the
     165         * cache folder) and browser-cache rules (.htaccess) are one-per-install. On
     166         * a single site its own settings control them. On multisite only a network
     167         * action (Network::sync() and friends, run for the super admin) may write
     168         * them — never a site's own settings save, so one site can't change caching
     169         * for the others and a site admin can't rewrite wp-config.php / .htaccess.
     170         *
     171         * @return bool
     172         */
     173        public static function server_features_allowed() {
     174                return ! is_multisite() || Network::is_writing();
     175        }
     176
     177        /**
     178         * On multisite the page-cache switches come from the network settings
     179         * (Network Admin → Optimazz); each site keeps its own exclusions.
     180         *
     181         * @param array $settings Settings.
     182         * @return array
     183         */
     184        private static function enforce_platform( array $settings ) {
     185                if ( is_multisite() && isset( $settings['cache'] ) && is_array( $settings['cache'] ) ) {
     186                        $settings['cache'] = array_merge( $settings['cache'], Network::settings() );
     187                }
     188                return $settings;
    158189        }
    159190
    … …  
    214245                $defaults = self::defaults();
    215246                update_option( self::OPTION_KEY, $defaults );
    216                 $this->cache = $defaults;
    217                 return $defaults;
     247                $this->cache = self::enforce_platform( $defaults ); // On multisite: network cache switches stay.
     248                return $this->cache;
    218249        }
    219250
    … …  
    285316                        }
    286317                }
    287                 return $clean;
     318                return self::enforce_platform( $clean );
    288319        }
    289320
  • optimazz/trunk/includes/Modules/Cache/BrowserCache.php

    r3617648 r3743505  
    2525         */
    2626        public static function apply( $enabled ) {
     27                if ( $enabled && ! \Optimazz\Core\Settings::server_features_allowed() ) {
     28                        $enabled = false; // One .htaccess serves the whole network — never written from a subsite.
     29                }
    2730                $htaccess = ABSPATH . '.htaccess';
    2831
  • optimazz/trunk/includes/Modules/Cache/Dropin.php

    r3723128 r3743505  
    5353         */
    5454        public static function install() {
     55                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     56                        return false; // advanced-cache.php is shared by every site of a network.
     57                }
    5558                $fs = self::filesystem();
    5659                if ( ! $fs || ! $fs->is_readable( self::template() ) ) {
    … …  
    6568
    6669                $ok = $fs->put_contents( self::DROPIN_PATH, $contents );
     70                self::forget_compiled( self::DROPIN_PATH );
    6771                if ( $ok ) {
    6872                        self::set_wp_cache( true );
    … …  
    322326
    323327        /**
     328         * Make PHP's opcode cache drop its compiled copy of a file we rewrote
     329         * (wp-config.php, advanced-cache.php), so the change applies on the very
     330         * next request instead of after the next timestamp check.
     331         *
     332         * @param string $file Absolute path.
     333         * @return void
     334         */
     335        private static function forget_compiled( $file ) {
     336                if ( function_exists( 'opcache_invalidate' ) ) {
     337                        @opcache_invalidate( $file, true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- may be restricted by opcache.restrict_api.
     338                }
     339        }
     340
     341        /**
    324342         * Forcefully make Optimazz the caching drop-in — overwrite whatever
    325343         * advanced-cache.php is present and enable WP_CACHE. Used by the admin
    … …  
    371389         */
    372390        private static function set_wp_cache( $enabled ) {
     391                // Multisite: wp-config.php is shared by the whole network, so a subsite
     392                // never edits it — not even to turn WP_CACHE off. Removing our drop-in is
     393                // enough to stop caching (WordPress only loads advanced-cache.php if the
     394                // file exists).
     395                if ( ! \Optimazz\Core\Settings::server_features_allowed() ) {
     396                        return false;
     397                }
    373398                $config = ABSPATH . 'wp-config.php';
    374399                if ( ! is_writable( $config ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions
    … …  
    405430                // core caching plugins — we toggle that single line here via
    406431                // WP_Filesystem. Nothing else in the file is touched.
    407                 return $fs->put_contents( $config, $contents );
     432                $ok = $fs->put_contents( $config, $contents );
     433                self::forget_compiled( $config ); // WP_CACHE must take effect on the next request.
     434                return $ok;
    408435        }
    409436}
  • optimazz/trunk/includes/Modules/Cache/Manager.php

    r3723128 r3743505  
    5454                }
    5555
     56                // Multisite: the drop-in, WP_CACHE, the engine config and .htaccess are
     57                // shared by the whole network and only Network::sync() (a network admin
     58                // action) writes them. A site writes just its own rules file and keeps
     59                // its own preload schedule in step with the network settings.
     60                if ( is_multisite() && ! \Optimazz\Core\Network::is_writing() ) {
     61                        \Optimazz\Core\Network::write_current_site_rules();
     62                        $on = ! empty( $cache_settings['enabled'] );
     63                        Preloader::schedule( $on && ! empty( $cache_settings['preload'] ), isset( $cache_settings['preload_interval'] ) ? (string) $cache_settings['preload_interval'] : 'twicedaily' );
     64                        if ( ! $on ) {
     65                                wp_clear_scheduled_hook( Preloader::HOOK . '_once' );
     66                                wp_clear_scheduled_hook( 'optimazz_purge_expired' );
     67                        }
     68                        Preloader::sync_due();
     69                        return;
     70                }
     71
    5672                // The engine config is a trimmed, disk-readable copy of the settings.
    5773                \Optimazz_Cache_Engine::write_config(
    … …  
    107123         */
    108124        public static function purge_and_rewarm() {
    109                 self::storage()->purge_all();
     125                if ( is_multisite() && ! \Optimazz\Core\Network::cache_enabled() ) {
     126                        return; // Page caching is off for this network — nothing to rebuild.
     127                }
     128                self::storage()->purge_all(); // On multisite: this site's pages only.
    110129
    111130                $cache = ( new \Optimazz\Core\Settings() )->for_module( 'cache' );
    … …  
    128147                }
    129148
     149                if ( is_multisite() ) {
     150                        // A new site has no rules file yet; write its own.
     151                        if ( ! is_readable( \Optimazz_Cache_Engine::site_rules_file( get_current_blog_id() ) ) ) {
     152                                \Optimazz\Core\Network::write_current_site_rules();
     153                        }
     154                        // Network caching is on but the shared drop-in or engine config is
     155                        // missing/stale (plugin folder moved, a host tool emptied the cache
     156                        // folder): heal it for the whole network — at most every 10 minutes,
     157                        // so a drop-in that can't be installed (another plugin owns it, the
     158                        // folder isn't writable) never makes every request run a full sync.
     159                        if ( ! empty( $cache_settings['enabled'] )
     160                                && null === Dropin::conflict_owner() // Another plugin's drop-in: nothing to heal (Network screen offers take-over).
     161                                && ( ! Dropin::is_current() || ! is_readable( \Optimazz_Cache_Engine::config_file() ) )
     162                                && ! get_site_transient( 'optimazz_heal' ) ) {
     163                                set_site_transient( 'optimazz_heal', 1, 10 * MINUTE_IN_SECONDS );
     164                                \Optimazz\Core\Network::queue_sync();
     165                        }
     166                        return;
     167                }
     168
    130169                if ( ! is_readable( \Optimazz_Cache_Engine::config_file() ) ) {
    131170                        self::sync( $cache_settings );
  • optimazz/trunk/includes/Modules/Cache/Preloader.php

    r3723128 r3743505  
    113113                }
    114114                $parts = array();
     115                if ( ! empty( $counts['__home'] ) ) {
     116                        $parts[] = __( 'the home page', 'optimazz' );
     117                }
    115118                foreach ( $counts as $group => $n ) {
    116                         if ( ! $n || '__terms' === $group ) {
     119                        if ( ! $n || '__terms' === $group || '__home' === $group ) {
    117120                                continue;
    118121                        }
    … …  
    140143                $max    = max( 1, (int) apply_filters( 'optimazz_preload_max', 300 ) );
    141144                $urls   = array( home_url( '/' ) );
    142                 $groups = array(); // Source of each URL, for breakdown().
     145                $groups = array( '__home' => array( home_url( '/' ) ) ); // Source of each URL, for breakdown().
    143146
    144147                // A static front page and/or posts page first, when configured.
    … …  
    214217
    215218                // How many of the FINAL list came from each source (for breakdown()).
     219                // Each URL counts once, under the first source that listed it (the home
     220                // page may also be the static front Page).
     221                $owner = array();
     222                foreach ( $groups as $group => $links ) {
     223                        foreach ( $links as $link ) {
     224                                if ( ! isset( $owner[ $link ] ) ) {
     225                                        $owner[ $link ] = $group;
     226                                }
     227                        }
     228                }
    216229                $counts = array();
    217                 foreach ( $groups as $group => $links ) {
    218                         $counts[ $group ] = count( array_intersect( array_unique( $links ), $urls ) );
     230                foreach ( $urls as $url ) {
     231                        if ( isset( $owner[ $url ] ) ) {
     232                                $counts[ $owner[ $url ] ] = ( isset( $counts[ $owner[ $url ] ] ) ? $counts[ $owner[ $url ] ] : 0 ) + 1;
     233                        }
    219234                }
    220235                set_transient( self::URLS_CACHE . '_breakdown', $counts, HOUR_IN_SECONDS );
    … …  
    275290                        require_once OPTIMAZZ_DIR . 'includes/Modules/Cache/engine.php';
    276291                }
    277                 $config = \Optimazz_Cache_Engine::load_config();
     292                $config = self::engine_config();
    278293                // A new manual run (offset 0) rebuilds the list; later steps reuse it.
    279294                $urls  = self::collect_all_urls( 0 === (int) $offset );
    … …  
    341356
    342357        /**
     358         * Engine config for this site: on a network, the network settings merged
     359         * with this site's own rules (ignored query args decide the cache key).
     360         *
     361         * @return array
     362         */
     363        private static function engine_config() {
     364                $home = (array) wp_parse_url( home_url( '/' ) );
     365                return \Optimazz_Cache_Engine::config_for( \Optimazz_Cache_Engine::url_host( $home ), isset( $home['path'] ) ? $home['path'] : '/' );
     366        }
     367
     368        /**
    343369         * Device copies to warm: desktop, plus mobile when a separate mobile cache is on.
    344370         *
    … …  
    383409                        require_once OPTIMAZZ_DIR . 'includes/Modules/Cache/engine.php';
    384410                }
    385                 $config = \Optimazz_Cache_Engine::load_config();
     411                $config = self::engine_config();
    386412                if ( empty( $config['enabled'] ) ) {
    387413                        delete_option( self::CURSOR );
    … …  
    472498                $parts  = wp_parse_url( $url );
    473499                $scheme = isset( $parts['scheme'] ) ? $parts['scheme'] : 'https';
    474                 $host   = isset( $parts['host'] ) ? $parts['host'] : '';
     500                $host   = \Optimazz_Cache_Engine::url_host( (array) $parts );
    475501                $uri    = ( isset( $parts['path'] ) ? $parts['path'] : '/' ) . ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' );
    476502                $file   = \Optimazz_Cache_Engine::path_for( $scheme, $host, $uri, $device, $config );
    … …  
    848874                        require_once OPTIMAZZ_DIR . 'includes/Modules/Cache/engine.php';
    849875                }
    850                 \Optimazz_Cache_Engine::write_cron_due( self::next_due(), site_url( 'wp-cron.php' ) );
     876                \Optimazz_Cache_Engine::write_cron_due( self::next_due(), site_url( 'wp-cron.php' ), is_multisite() ? get_current_blog_id() : 0 );
    851877        }
    852878
  • optimazz/trunk/includes/Modules/Cache/Storage/DiskStorage.php

    r3723128 r3743505  
    2929
    3030        public function purge_all() {
     31                if ( is_multisite() ) {
     32                        \Optimazz_Cache_Engine::purge_site_dir( self::site_dir() ); // This site only.
     33                        return;
     34                }
    3135                \Optimazz_Cache_Engine::purge_all();
     36        }
     37
     38        /**
     39         * Cache folder of the current network site.
     40         *
     41         * @return string
     42         */
     43        private static function site_dir() {
     44                $site = get_site();
     45                return \Optimazz_Cache_Engine::site_dir( $site ? $site->domain : (string) wp_parse_url( home_url(), PHP_URL_HOST ), $site ? $site->path : '/' );
    3246        }
    3347
    … …  
    3953        public function stats() {
    4054                $root = \Optimazz_Cache_Engine::cache_root();
     55                $skip = array();
     56                if ( is_multisite() ) {
     57                        // Count this site's folder only, not other sites nested inside it.
     58                        $root = self::site_dir();
     59                        $skip = array_values( array_diff( \Optimazz_Cache_Engine::site_roots(), array( $root ) ) );
     60                }
    4161                if ( ! is_dir( $root ) ) {
    4262                        return array(
    … …  
    4969                $files = 0;
    5070                $bytes = 0;
    51                 // A page (one URL) is stored as several variants in its own folder —
    52                 // desktop/mobile, http/https, query strings. "Cached pages" counts the
    53                 // pages, so it matches what the preloader reports; 'files' is kept for
    54                 // the raw variant count.
     71                // A page (one URL) is stored as several variants — desktop/mobile,
     72                // http/https, logged-in buckets. "Cached pages" counts the pages, so it
     73                // matches what the preloader reports; 'files' is kept for the raw
     74                // variant count.
    5575                $dirs  = array();
    5676                $it    = new \RecursiveIteratorIterator(
    57                         new \RecursiveDirectoryIterator( $root, \FilesystemIterator::SKIP_DOTS )
     77                        new \RecursiveCallbackFilterIterator(
     78                                new \RecursiveDirectoryIterator( $root, \FilesystemIterator::SKIP_DOTS ),
     79                                static function ( $file ) use ( $skip ) {
     80                                        // Skip other network sites' folders (empty on a single site).
     81                                        return ! ( $file->isDir() && in_array( rtrim( $file->getPathname(), '/' ) . '/', $skip, true ) );
     82                                }
     83                        )
    5884                );
    5985                foreach ( $it as $file ) {
    … …  
    6692                                ++$files;
    6793                                $bytes += (int) $file->getSize();
    68                                 $dirs[ $file->getPath() ] = true;
     94                                // File name: {scheme}-{device}-{user}[-{query}].html. One page = its
     95                                // folder. At the site root the query part counts too, because plain
     96                                // permalinks (?page_id=5) keep every page there; elsewhere query
     97                                // variants (e.g. /shop/?orderby=price) are the same page.
     98                                $parts   = explode( '-', $file->getBasename( '.html' ), 4 );
     99                                $at_root = false === strpos( trim( substr( $file->getPath(), strlen( $root ) ), '/' ), '/' );
     100                                $dirs[ $file->getPath() . '|' . ( $at_root && isset( $parts[3] ) ? $parts[3] : '' ) ] = true;
    69101                        }
    70102                }
  • optimazz/trunk/includes/Modules/Cache/engine.php

    r3723128 r3743505  
    3838                        'site_host'            => '',
    3939                        'lifespan_hours'       => 0,
     40                        // Multisite: map of "host/path/" => site ID for every site Optimazz
     41                        // caches, and the site the current request belongs to.
     42                        'multisite'            => false,
     43                        'sites'                => array(),
     44                        'site_id'              => 0,
    4045                );
    4146        }
    … …  
    7782
    7883        /**
     84         * Config for a request to $host + $uri. On a network this resolves the
     85         * site (by host and the longest matching path) and merges that site's own
     86         * rules — exclusions and ignored query args — over the network settings.
     87         * A request for a host/path that no cached site owns gets enabled=false.
     88         *
     89         * @param string $host Host header.
     90         * @param string $uri  Request URI.
     91         * @return array
     92         */
     93        public static function config_for( $host, $uri ) {
     94                $config = self::load_config();
     95                if ( empty( $config['multisite'] ) ) {
     96                        return $config;
     97                }
     98                $id = self::resolve_site( $config, $host, $uri );
     99                if ( ! $id ) {
     100                        $config['enabled'] = false;
     101                        return $config;
     102                }
     103                $config['site_id'] = $id;
     104                $file = self::site_rules_file( $id );
     105                if ( is_readable( $file ) ) {
     106                        $rules = include $file;
     107                        if ( is_array( $rules ) ) {
     108                                foreach ( array( 'ignored_query_args', 'excluded_uris', 'excluded_cookies', 'excluded_user_agents' ) as $key ) {
     109                                        if ( isset( $rules[ $key ] ) && is_array( $rules[ $key ] ) ) {
     110                                                $config[ $key ] = $rules[ $key ];
     111                                        }
     112                                }
     113                        }
     114                }
     115                return $config;
     116        }
     117
     118        /**
     119         * Which network site a host + URI belongs to: the site whose "host/path/"
     120         * key is the longest prefix of the request (so /shop/… goes to the /shop/
     121         * site, everything else on that host to the main site).
     122         *
     123         * @param array  $config Config with a 'sites' map.
     124         * @param string $host   Host header.
     125         * @param string $uri    Request URI.
     126         * @return int Site ID, or 0 when no cached site matches.
     127         */
     128        public static function resolve_site( array $config, $host, $uri ) {
     129                // Host as sent (a port is kept, matching how the cache folder is named
     130                // and how WordPress stores a site's domain).
     131                $host = strtolower( (string) $host );
     132                $path = (string) strtok( (string) $uri, '?' );
     133                $path = '/' . ltrim( $path, '/' );
     134                $key  = $host . rtrim( $path, '/' ) . '/';
     135                $best = 0;
     136                $len  = 0;
     137                foreach ( (array) $config['sites'] as $prefix => $id ) {
     138                        $prefix = (string) $prefix;
     139                        if ( strlen( $prefix ) > $len && 0 === strpos( $key, $prefix ) ) {
     140                                $best = (int) $id;
     141                                $len  = strlen( $prefix );
     142                        }
     143                }
     144                return $best;
     145        }
     146
     147        /**
     148         * Host of a parsed URL as a browser sends it in the Host header — with the
     149         * port when there is one. Cache folders and the network site map are keyed
     150         * on that, so URL-based lookups (purge, preload) must use it too.
     151         *
     152         * @param array $parts parse_url() result.
     153         * @return string
     154         */
     155        public static function url_host( array $parts ) {
     156                $host = isset( $parts['host'] ) ? strtolower( (string) $parts['host'] ) : '';
     157                return ( '' !== $host && ! empty( $parts['port'] ) ) ? $host . ':' . (int) $parts['port'] : $host;
     158        }
     159
     160        /**
     161         * File holding one network site's own cache rules.
     162         *
     163         * @param int $id Site ID.
     164         * @return string
     165         */
     166        public static function site_rules_file( $id ) {
     167                return self::cache_root() . 'sites/' . (int) $id . '.php';
     168        }
     169
     170        /**
     171         * Write one network site's own cache rules (only changes when they differ).
     172         *
     173         * @param int   $id    Site ID.
     174         * @param array $rules Rules: ignored_query_args, excluded_uris, excluded_cookies, excluded_user_agents.
     175         * @return bool
     176         */
     177        public static function write_site_rules( $id, array $rules ) {
     178                $file = self::site_rules_file( $id );
     179                // var_export serialises to a fast include file, not for debugging.
     180                $code = "<?php\n// Generated by Optimazz. Do not edit.\nreturn " . var_export( $rules, true ) . ";\n"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
     181                if ( is_readable( $file ) && (string) @file_get_contents( $file ) === $code ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions
     182                        return true;
     183                }
     184                self::mkdir( dirname( $file ) );
     185                self::protect_dir( self::cache_root() );
     186                return self::atomic_write( $file, $code );
     187        }
     188
     189        /**
    79190         * Persist config to disk as a returnable PHP array (fast to include).
    80191         *
    … …  
    87198                // var_export is used to serialize the config to a fast include file, not for debugging.
    88199                $code = "<?php\n// Generated by Optimazz. Do not edit.\nreturn " . var_export( $config, true ) . ";\n"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
     200                if ( is_readable( self::config_file() ) && (string) @file_get_contents( self::config_file() ) === $code ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions
     201                        return true; // Unchanged: keep the compiled copy (no opcode-cache churn).
     202                }
    89203                return self::atomic_write( self::config_file(), $code );
    90204        }
    … …  
    144258         */
    145259        public static function serve() {
    146                 $config = self::load_config();
     260                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- pre-WP drop-in; only used to pick a site and build a sanitized cache key.
     261                $config = self::config_for( isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '', isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/' );
    147262
    148263                if ( empty( $config['enabled'] ) ) {
    … …  
    167282                                header( 'Content-Type: text/html; charset=UTF-8' );
    168283                                readfile( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions
    169                                 self::maybe_kick_cron();
     284                                self::maybe_kick_cron( (int) $config['site_id'] );
    170285                                exit;
    171286                        }
    … …  
    181296         * A .php file, so the cache directory's access rules keep it private.
    182297         *
     298         * @param int $site_id Network site ID (0 on a single site).
    183299         * @return string
    184300         */
    185         private static function cron_due_file() {
    186                 return self::cache_root() . 'cron-due.php';
     301        private static function cron_due_file( $site_id = 0 ) {
     302                return self::cache_root() . ( $site_id ? 'cron-due-' . (int) $site_id . '.php' : 'cron-due.php' );
    187303        }
    188304
    … …  
    193309         * @param int    $timestamp Unix time of the next run (0 = none pending).
    194310         * @param string $cron_url  Absolute URL of wp-cron.php.
    195          * @return void
    196          */
    197         public static function write_cron_due( $timestamp, $cron_url ) {
     311         * @param int    $site_id   Network site ID (0 on a single site).
     312         * @return void
     313         */
     314        public static function write_cron_due( $timestamp, $cron_url, $site_id = 0 ) {
    198315                $data = array(
    199316                        'due' => (int) $timestamp,
    200317                        'url' => (string) $cron_url,
    201318                );
    202                 $file = self::cron_due_file();
     319                $file = self::cron_due_file( $site_id );
    203320                if ( is_readable( $file ) ) {
    204321                        $current = include $file;
    … …  
    224341         * after the page has been handed to the visitor where the server allows.
    225342         *
    226          * @return void
    227          */
    228         private static function maybe_kick_cron() {
    229                 $file = self::cron_due_file();
     343         * @param int $site_id Network site ID of the request (0 on a single site).
     344         * @return void
     345         */
     346        private static function maybe_kick_cron( $site_id = 0 ) {
     347                $file = self::cron_due_file( $site_id );
    230348                if ( ! is_readable( $file ) ) {
    231349                        return;
    … …  
    235353                        return;
    236354                }
    237                 $lock = self::cache_root() . 'cron.lock';
     355                $lock = self::cache_root() . ( $site_id ? 'cron-' . (int) $site_id . '.lock' : 'cron.lock' );
    238356                if ( file_exists( $lock ) && ( time() - (int) @filemtime( $lock ) ) < 60 ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors
    239357                        return;
    … …  
    464582        public static function path_for_request( array $config ) {
    465583                // phpcs:disable WordPress.Security.ValidatedSanitizedInput -- pre-WP drop-in; values feed a sanitized cache key only.
    466                 $host   = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : 'localhost';
     584                $host   = isset( $_SERVER['HTTP_HOST'] ) ? strtolower( $_SERVER['HTTP_HOST'] ) : 'localhost'; // Hosts are case-insensitive; one folder per host.
    467585                $uri    = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/';
    468586                $scheme = ( ! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] ) ? 'https' : 'http';
    … …  
    588706                }
    589707                $parts    = function_exists( 'wp_parse_url' ) ? wp_parse_url( $url ) : parse_url( $url ); // phpcs:ignore WordPress.WP.AlternativeFunctions
    590                 $host     = isset( $parts['host'] ) ? $parts['host'] : '';
     708                $host     = self::url_host( $parts );
    591709                $path     = isset( $parts['path'] ) ? $parts['path'] : '/';
    592710                $path     = str_replace( array( '..', "\0" ), '', $path );
    … …  
    595713                $dir      = self::cache_root() . self::sanitize_segment( $host ) . rtrim( $path, '/' ) . '/';
    596714
    597                 // The homepage's variants live directly in the host dir alongside every
    598                 // other page's SUBdirectory. Recursively deleting the host dir would wipe
    599                 // the whole site cache — so for the homepage, remove only its own .html
    600                 // variant files and leave the per-page subdirectories intact.
    601                 if ( $dir === $host_dir ) {
    602                         foreach ( (array) glob( $host_dir . '*.html' ) as $file ) {
     715                // A site's front page has its variants directly in the site's root
     716                // folder, alongside every other page's SUBdirectory. Recursively deleting
     717                // that folder would wipe the whole site — so for a front page (the host
     718                // root, or a network site's own root like /shop/) remove only its own
     719                // .html variant files and leave the per-page subdirectories intact.
     720                if ( $dir === $host_dir || in_array( $dir, self::site_roots(), true ) ) {
     721                        foreach ( (array) glob( $dir . '*.html' ) as $file ) {
    603722                                wp_delete_file( $file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
    604723                        }
    … …  
    628747                //    HTML while keeping the hashed assets keeps both sides consistent.
    629748                //    (Stale assets are cleared deliberately when CSS/JS settings change.)
    630                 $keep = array( '.', '..', 'config.php', 'index.html', '.htaccess', 'css', 'js', 'cron-due.php', 'cron.lock' );
     749                $keep = array( '.', '..', 'config.php', 'index.html', '.htaccess', 'css', 'js', 'cron-due.php', 'cron.lock', 'sites' );
    631750                foreach ( (array) scandir( $root ) as $item ) {
    632                         if ( in_array( $item, $keep, true ) ) {
     751                        if ( in_array( $item, $keep, true ) || preg_match( '/^cron(?:-due)?-\d+\.(?:php|lock)$/', (string) $item ) ) {
    633752                                continue;
    634753                        }
    … …  
    642761        }
    643762
     763        /**
     764         * Remove cache folders named after a mixed-case Host header. Since 1.1.0
     765         * hosts are lowercased, so such folders are never served or purged again.
     766         *
     767         * @return void
     768         */
     769        public static function remove_mixed_case_hosts() {
     770                $root = self::cache_root();
     771                if ( ! is_dir( $root ) ) {
     772                        return;
     773                }
     774                foreach ( (array) scandir( $root ) as $item ) {
     775                        if ( '.' !== $item && '..' !== $item && is_dir( $root . $item ) && strtolower( $item ) !== $item ) {
     776                                self::rrmdir( $root . $item . '/' );
     777                        }
     778                }
     779        }
     780
     781        /**
     782         * Cache folders of every network site's root ("<root>/<host>/<path>/").
     783         *
     784         * @return string[]
     785         */
     786        public static function site_roots() {
     787                $config = self::load_config();
     788                $roots  = array();
     789                foreach ( array_keys( (array) $config['sites'] ) as $key ) {
     790                        $parts   = explode( '/', (string) $key, 2 );
     791                        $path    = isset( $parts[1] ) ? trim( $parts[1], '/' ) : '';
     792                        $roots[] = self::cache_root() . self::sanitize_segment( $parts[0] ) . ( '' !== $path ? '/' . $path : '' ) . '/';
     793                }
     794                return $roots;
     795        }
     796
     797        /**
     798         * Cache folder of a site (host + path), e.g. "<root>/example.com/shop/".
     799         *
     800         * @param string $host Site host.
     801         * @param string $path Site path ('/' or '/shop/').
     802         * @return string
     803         */
     804        public static function site_dir( $host, $path ) {
     805                $path = trim( str_replace( array( '..', "\0" ), '', (string) $path ), '/' );
     806                $path = preg_replace( '#[^a-zA-Z0-9/_\-.]#', '-', $path );
     807                return self::cache_root() . self::sanitize_segment( strtolower( (string) $host ) ) . ( '' !== $path ? '/' . $path : '' ) . '/';
     808        }
     809
     810        /**
     811         * Purge one network site's pages without touching sites nested inside its
     812         * folder (the main site "/" contains the folder of a subdirectory site).
     813         *
     814         * @param string $dir Site cache folder (from site_dir()).
     815         * @return void
     816         */
     817        public static function purge_site_dir( $dir ) {
     818                $dir = rtrim( (string) $dir, '/' ) . '/';
     819                if ( 0 !== strpos( $dir, self::cache_root() ) || ! is_dir( $dir ) ) {
     820                        return;
     821                }
     822                $others = array_values( array_diff( self::site_roots(), array( $dir ) ) );
     823                self::purge_tree( $dir, $others );
     824        }
     825
     826        /**
     827         * Delete cached pages under $dir, skipping (and descending towards) the
     828         * folders of other sites.
     829         *
     830         * @param string   $dir    Folder with trailing slash.
     831         * @param string[] $others Other sites' root folders.
     832         * @return void
     833         */
     834        private static function purge_tree( $dir, array $others ) {
     835                foreach ( (array) scandir( $dir ) as $item ) {
     836                        if ( '.' === $item || '..' === $item ) {
     837                                continue;
     838                        }
     839                        $path = $dir . $item;
     840                        if ( ! is_dir( $path ) ) {
     841                                if ( '.html' === substr( $item, -5 ) && 'index.html' !== $item ) {
     842                                        wp_delete_file( $path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
     843                                }
     844                                continue;
     845                        }
     846                        $sub = $path . '/';
     847                        if ( in_array( $sub, $others, true ) ) {
     848                                continue; // Another site's folder.
     849                        }
     850                        $contains_other = false;
     851                        foreach ( $others as $other ) {
     852                                if ( 0 === strpos( $other, $sub ) ) {
     853                                        $contains_other = true;
     854                                        break;
     855                                }
     856                        }
     857                        if ( $contains_other ) {
     858                                self::purge_tree( $sub, $others );
     859                        } else {
     860                                self::rrmdir( $sub );
     861                        }
     862                }
     863        }
     864
    644865        /* --------------------------------------------------------------------- *
    645866         *  Filesystem helpers
    … …  
    684905                        return false;
    685906                }
     907                self::forget_compiled( $file );
    686908                return true;
     909        }
     910
     911        /**
     912         * Make PHP's opcode cache drop its compiled copy of a file we just
     913         * rewrote. Config and rules files are PHP includes: without this, a server
     914         * that only re-checks file times every few seconds (or never, with
     915         * opcache.validate_timestamps=0) keeps running the OLD settings — caching
     916         * stays off after being switched on, or a new exclusion is ignored.
     917         *
     918         * @param string $file Absolute path.
     919         * @return void
     920         */
     921        public static function forget_compiled( $file ) {
     922                if ( function_exists( 'opcache_invalidate' ) ) {
     923                        @opcache_invalidate( $file, true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors -- may be restricted by opcache.restrict_api.
     924                }
    687925        }
    688926
  • optimazz/trunk/includes/Modules/Fonts/FontsModule.php

    r3715895 r3743505  
    2121
    2222class FontsModule extends AbstractModule {
     23
     24        /**
     25         * Whether parse_css2() repaired an invalid (CSS1-style) family request on
     26         * the current page, so the rebuilt URL must be output even when nothing
     27         * else changes.
     28         *
     29         * @var bool
     30         */
     31        private static $repaired = false;
    2332
    2433        public function id() {
    … …  
    157166                        return $html;
    158167                }
     168                self::$repaired = false;
    159169
    160170                $links    = array(); // Tags we will merge (in document order).
    … …  
    173183                        }
    174184                        $query  = (string) wp_parse_url( html_entity_decode( $m[2], ENT_QUOTES ), PHP_URL_QUERY ); // decode first: "&#038;" contains a "#" that would truncate the query.
     185                        $was    = self::$repaired;
    175186                        $parsed = 'css2' === strtolower( $m[3] ) ? self::parse_css2( $query ) : self::parse_css1( $query );
    176187                        if ( null === $parsed ) {
     188                                self::$repaired = $was; // This link is left as-is, so nothing was repaired.
    177189                                continue; // unsupported form — keep as-is.
    178190                        }
    … …  
    197209                }
    198210                $trim = (bool) $this->option( 'trim_weights', true );
    199                 // One link and nothing to trim — nothing to gain.
    200                 if ( 1 === count( $links ) && ! $trim ) {
     211                $disp = '' !== $display ? $display : $orig_display;
     212                // Non-blocking load only with swap / fallback. Those swap the font in
     213                // anyway; 'optional' is chosen for zero layout shift, and loading its
     214                // stylesheet late would make a cached font swap in after first paint.
     215                $async = in_array( $disp, array( 'swap', 'fallback' ), true ) && $this->option( 'async_google', true );
     216                // One link, nothing to trim, nothing repaired and no async loading —
     217                // rebuilding the URL would gain nothing (and would drop its other
     218                // attributes and parameters).
     219                if ( 1 === count( $links ) && ! $trim && ! $async && ! self::$repaired ) {
    201220                        return $html;
    202221                }
    … …  
    208227                        $params[] = 'family=' . str_replace( '%20', '+', rawurlencode( $name ) ) . self::css2_axis( $spec, $used );
    209228                }
    210                 $disp = '' !== $display ? $display : $orig_display;
    211229                if ( '' !== $disp ) {
    212230                        $params[] = 'display=' . $disp;
    213231                }
    214                 $url = 'https://fonts.googleapis.com/css2?' . implode( '&', $params );
    215                 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- rewriting existing output-buffer markup, not enqueueing.
    216                 $new = '<link rel="stylesheet" id="optimazz-google-fonts" href="' . esc_url( $url ) . '" media="all">';
     232                $url  = esc_url( 'https://fonts.googleapis.com/css2?' . implode( '&', $params ) );
     233                // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- rewriting existing output-buffer markup, not enqueueing.
     234                if ( $async ) {
     235                        // With swap/fallback the text renders in a fallback font first
     236                        // anyway, so the font stylesheet need not block the page: on
     237                        // mobile it otherwise holds back the first paint by the full round
     238                        // trip to Google. Preload it, apply it when loaded, and keep a
     239                        // <noscript> copy for visitors without JavaScript.
     240                        $new = '<link rel="preload" as="style" href="' . $url . '">'
     241                                . '<link rel="stylesheet" id="optimazz-google-fonts" href="' . $url . '" media="print" onload="this.media=\'all\'">'
     242                                . '<noscript><link rel="stylesheet" href="' . $url . '"></noscript>';
     243                } else {
     244                        $new = '<link rel="stylesheet" id="optimazz-google-fonts" href="' . $url . '" media="all">';
     245                }
     246                // phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
    217247
    218248                // Swap the first link for the merged one; remove the rest (each once).
    … …  
    252282                                        continue;
    253283                                }
    254                                 $spec = array( 'normal' => array(), 'italic' => array(), 'raw' => null );
    255                                 $vars = isset( $parts[1] ) ? explode( ',', $parts[1] ) : array( '400' );
    256                                 foreach ( $vars as $v ) {
    257                                         $v = strtolower( trim( $v ) );
    258                                         if ( '' === $v ) {
    259                                                 continue;
    260                                         }
    261                                         $v    = strtr( $v, array( 'regular' => '400', 'bold' => '700' ) );
    262                                         $ital = (bool) preg_match( '/(italic|i)$/', $v );
    263                                         $w    = (int) preg_replace( '/\D/', '', $v );
    264                                         $w    = $w ? $w : 400; // bare "italic".
    265                                         if ( $w < 100 || $w > 900 ) {
    266                                                 continue;
    267                                         }
    268                                         $spec[ $ital ? 'italic' : 'normal' ][ $w ] = 1;
    269                                 }
    270                                 if ( empty( $spec['normal'] ) && empty( $spec['italic'] ) ) {
    271                                         $spec['normal'][400] = 1;
    272                                 }
    273                                 $out[ $name ] = $spec;
     284                                $out[ $name ] = self::parse_variants( isset( $parts[1] ) ? $parts[1] : '400' );
    274285                        }
    275286                }
    276287                return $out ? $out : null;
     288        }
     289
     290        /**
     291         * Parse a CSS1-style variant list ("400,700i,bold,italic") into a spec.
     292         *
     293         * @param string $list Comma-separated variants.
     294         * @return array{normal:array,italic:array,raw:null}
     295         */
     296        private static function parse_variants( $list ) {
     297                $spec = array( 'normal' => array(), 'italic' => array(), 'raw' => null );
     298                foreach ( explode( ',', $list ) as $v ) {
     299                        $v = strtolower( trim( $v ) );
     300                        if ( '' === $v ) {
     301                                continue;
     302                        }
     303                        $v    = strtr( $v, array( 'regular' => '400', 'bold' => '700' ) );
     304                        $ital = (bool) preg_match( '/(italic|i)$/', $v );
     305                        $w    = (int) preg_replace( '/\D/', '', $v );
     306                        $w    = $w ? $w : 400; // bare "italic".
     307                        if ( $w < 100 || $w > 900 ) {
     308                                continue;
     309                        }
     310                        $spec[ $ital ? 'italic' : 'normal' ][ $w ] = 1;
     311                }
     312                if ( empty( $spec['normal'] ) && empty( $spec['italic'] ) ) {
     313                        $spec['normal'][400] = 1;
     314                }
     315                return $spec;
    277316        }
    278317
    … …  
    314353                                        $spec['normal'][ (int) $w ] = 1;
    315354                                }
     355                        } elseif ( preg_match( '/^(?:w?\d{3}i?|regular|bold|italic|bolditalic|\d{3}italic)(?:,(?:w?\d{3}i?|regular|bold|italic|bolditalic|\d{3}italic))*$/i', trim( $parts[1] ) ) ) {
     356                                // Old CSS1-style weights ("400,700", or the "w400,700" some themes
     357                                // print) sent to the CSS2 API. Google rejects these with HTTP 400, so
     358                                // the font never loads while the broken stylesheet still blocks
     359                                // rendering. Read them the CSS1 way so a valid request is built.
     360                                $spec           = self::parse_variants( str_ireplace( 'w', '', $parts[1] ) );
     361                                self::$repaired = true;
    316362                        } else {
    317363                                $spec['raw'] = $parts[1]; // variable ranges / other axes — keep verbatim.
  • optimazz/trunk/includes/Modules/Js/JsModule.php

    r3692106 r3743505  
    239239                        'tawk.to', 'crisp.chat', 'widget.intercom.io', 'js.intercomcdn.com',
    240240                        'code.tidio.co', 'cdn.livechatinc.com', 'embed.tawk.to', 'messenger/customerchat',
     241                        'cdn.openwidget.com', 'static.zdassets.com', 'js.driftt.com', 'wchat.freshchat.com',
     242                        'widget.freshworks.com', 'code.jivosite.com', 'smartsuppchat.com', 'embed.small.chat',
    241243                        // Marketing / CRM.
    242244                        'js.hs-scripts.com', 'js.hsforms.net', 'cdn.onesignal.com',
  • optimazz/trunk/languages/optimazz.pot

    r3723128 r3743505  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Optimazz – Website Optimization and Speedup 1.0.24\n"
     5"Project-Id-Version: Optimazz – Website Optimization and Speedup 1.1.0\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/optimazz\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-01T12:01:49+00:00\n"
     12"POT-Creation-Date: 2026-10-11T19:05:58+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    4040msgstr ""
    4141
    42 #: includes/Admin/Admin.php:149
    43 #: includes/Admin/Admin.php:150
     42#: includes/Admin/Admin.php:155
     43#: includes/Admin/Admin.php:156
     44#: includes/Admin/Admin.php:290
     45#: includes/Admin/Admin.php:291
    4446msgid "Optimazz"
    4547msgstr ""
    4648
    47 #: includes/Admin/Admin.php:166
     49#: includes/Admin/Admin.php:172
    4850#: src/index.js:36
    4951msgid "Dashboard"
    5052msgstr ""
    5153
    52 #: includes/Admin/Admin.php:167
     54#: includes/Admin/Admin.php:173
    5355#: includes/Modules/Cache/CacheModule.php:24
    5456#: src/index.js:37
    55 #: src/index.js:1171
     57#: src/index.js:1317
     58#: src/index.js:2075
     59#: src/index.js:2091
    5660msgid "Page Cache"
    5761msgstr ""
    5862
    59 #: includes/Admin/Admin.php:168
     63#: includes/Admin/Admin.php:174
    6064#: src/index.js:38
    61 #: src/index.js:1150
     65#: src/index.js:1296
    6266msgid "File Optimization"
    6367msgstr ""
    6468
    65 #: includes/Admin/Admin.php:169
     69#: includes/Admin/Admin.php:175
    6670#: src/index.js:39
    6771msgid "Media"
    6872msgstr ""
    6973
    70 #: includes/Admin/Admin.php:170
     74#: includes/Admin/Admin.php:176
    7175#: includes/Modules/Database/DatabaseModule.php:26
    7276#: src/index.js:43
    … …  
    7478msgstr ""
    7579
    76 #: includes/Admin/Admin.php:171
    77 #: includes/Admin/Admin.php:229
    78 #: includes/Admin/Admin.php:233
     80#: includes/Admin/Admin.php:177
     81#: includes/Admin/Admin.php:235
     82#: includes/Admin/Admin.php:239
    7983#: src/index.js:47
    8084msgid "Tools"
    8185msgstr ""
    8286
    83 #: includes/Admin/Admin.php:193
     87#: includes/Admin/Admin.php:199
    8488#: includes/Admin/AdminBar.php:52
     89#: src/index.js:2198
    8590msgid "Settings"
    8691msgstr ""
    8792
    88 #: includes/Admin/Admin.php:224
     93#: includes/Admin/Admin.php:230
    8994msgid "Optimazz Safe Mode is on."
    9095msgstr ""
    9196
    92 #: includes/Admin/Admin.php:225
     97#: includes/Admin/Admin.php:231
    9398msgid "Some pages were loading without styling — their optimized CSS could not be found, usually because a server or CDN cache was serving old pages. Optimazz paused advanced CSS/JS optimization to keep your site working."
    9499msgstr ""
    95100
    96 #: includes/Admin/Admin.php:227
     101#: includes/Admin/Admin.php:233
    97102msgid "It broke again shortly after full optimization was restored, so Safe Mode will stay on until you switch it off in"
    98103msgstr ""
    99104
    100 #: includes/Admin/Admin.php:228
     105#: includes/Admin/Admin.php:234
    101106msgid "It re-checks automatically and restores full optimization as soon as pages load correctly — clearing your server/CDN cache speeds that up. You can also manage it in"
    102107msgstr ""
    103108
    104 #: includes/Admin/Admin.php:232
     109#: includes/Admin/Admin.php:238
    105110msgid "Your pages are loading correctly again — you can switch Safe Mode off in"
    106111msgstr ""
    107112
    108 #: includes/Admin/Admin.php:236
     113#: includes/Admin/Admin.php:242
    109114msgid "Pages are loading correctly again, so Safe Mode was switched off automatically and full optimization is back on."
    110115msgstr ""
    111116
    112 #: includes/Admin/Admin.php:250
     117#: includes/Admin/Admin.php:256
    113118msgid "wp-config.php is not writable, so page caching could not be enabled. Add"
    114119msgstr ""
    115120
    116 #: includes/Admin/Admin.php:252
     121#: includes/Admin/Admin.php:258
    117122msgid "manually, or make wp-config.php writable."
    118123msgstr ""
    119124
    120 #: includes/Admin/Admin.php:259
     125#: includes/Admin/Admin.php:265
    121126msgid "Optimazz cache purged."
    122127msgstr ""
    123128
    124 #: includes/Admin/Admin.php:260
     129#: includes/Admin/Admin.php:266
    125130msgid "Optimazz cache preload started in the background."
    126131msgstr ""
    127132
    128 #: includes/Admin/Admin.php:261
     133#: includes/Admin/Admin.php:267
    129134msgid "Optimazz cache cleared; preload started in the background."
    130135msgstr ""
    131136
    132 #: includes/Admin/AdminBar.php:53
     137#: includes/Admin/AdminBar.php:57
    133138msgid "Clear & Preload Cache"
    134139msgstr ""
    135140
    136 #: includes/Admin/AdminBar.php:54
    137 #: src/index.js:1451
     141#: includes/Admin/AdminBar.php:58
     142#: src/index.js:1597
    138143msgid "Preload Cache"
    139144msgstr ""
    140145
    141 #: includes/Admin/AdminBar.php:55
     146#: includes/Admin/AdminBar.php:59
    142147msgid "Purge Cache"
    143148msgstr ""
    144149
    145 #: includes/Admin/AdminBar.php:73
     150#: includes/Admin/AdminBar.php:77
    146151msgid "Documentation"
    147152msgstr ""
    148153
    149 #: includes/Admin/AdminBar.php:98
    150 #: includes/Admin/CacheConflictNotice.php:124
    151 #: includes/Admin/CacheConflictNotice.php:154
     154#: includes/Admin/AdminBar.php:102
     155#: includes/Admin/CacheConflictNotice.php:126
     156#: includes/Admin/CacheConflictNotice.php:156
    152157msgid "Not allowed."
    153158msgstr ""
    … …  
    162167
    163168#: includes/Admin/CacheConflictNotice.php:88
    164 #: includes/Modules/Cache/Preloader.php:714
     169#: includes/Modules/Cache/Preloader.php:740
     170#: src/index.js:2136
    165171msgid "Another plugin"
    166172msgstr ""
    … …  
    180186#: includes/Admin/CacheConflictNotice.php:108
    181187#: includes/Admin/CacheConflictNotice.php:110
     188#: src/index.js:2140
    182189msgid "Use Optimazz for caching"
    183190msgstr ""
    … …  
    187194msgstr ""
    188195
    189 #: includes/Admin/RestController.php:467
     196#: includes/Admin/NetworkRestController.php:115
     197#: includes/Admin/NetworkRestController.php:134
     198msgid "Page caching is off for this site."
     199msgstr ""
     200
     201#: includes/Admin/NetworkRestController.php:153
     202msgid "Optimazz is not active on any site yet. Network-activate it (or activate it on a site) first."
     203msgstr ""
     204
     205#: includes/Admin/RestController.php:385
     206#: includes/Admin/RestController.php:588
     207#: includes/Admin/RestController.php:614
     208msgid "Page caching is not available on multisite networks."
     209msgstr ""
     210
     211#: includes/Admin/RestController.php:479
    190212msgid "PHP version"
    191213msgstr ""
    192214
    193 #: includes/Admin/RestController.php:470
     215#: includes/Admin/RestController.php:482
    194216msgid "Optimazz requires PHP 7.4 or newer."
    195217msgstr ""
    196218
    197 #: includes/Admin/RestController.php:473
     219#: includes/Admin/RestController.php:485
    198220msgid "WordPress version"
    199221msgstr ""
    200222
    201 #: includes/Admin/RestController.php:479
     223#: includes/Admin/RestController.php:491
    202224msgid "GD (image library)"
    203225msgstr ""
    204226
    205 #: includes/Admin/RestController.php:480
    206 #: includes/Admin/RestController.php:489
    207 #: includes/Admin/RestController.php:525
     227#: includes/Admin/RestController.php:492
     228#: includes/Admin/RestController.php:501
     229#: includes/Admin/RestController.php:537
    208230msgid "Enabled"
    209231msgstr ""
    210232
    211 #: includes/Admin/RestController.php:480
    212 #: includes/Admin/RestController.php:489
     233#: includes/Admin/RestController.php:492
     234#: includes/Admin/RestController.php:501
    213235msgid "Not installed (optional)"
    214236msgstr ""
    215237
    216 #: includes/Admin/RestController.php:484
     238#: includes/Admin/RestController.php:496
    217239msgid "GD is enabled but without WebP support — ask your host to enable WebP in GD."
    218240msgstr ""
    219241
    220 #: includes/Admin/RestController.php:485
     242#: includes/Admin/RestController.php:497
    221243msgid "The PHP GD extension is not enabled. GD is the most common engine for image compression and WebP. Ask your host to enable the \"php-gd\" extension."
    222244msgstr ""
    223245
    224 #: includes/Admin/RestController.php:488
     246#: includes/Admin/RestController.php:500
    225247msgid "ImageMagick (Imagick)"
    226248msgstr ""
    227249
    228 #: includes/Admin/RestController.php:489
     250#: includes/Admin/RestController.php:501
    229251msgid "Not enabled"
    230252msgstr ""
    231253
    232 #: includes/Admin/RestController.php:496
     254#: includes/Admin/RestController.php:508
    233255msgid "Optional — GD is already handling images. Imagick can add AVIF and higher-quality output if your host enables the \"php-imagick\" extension."
    234256msgstr ""
    235257
    236 #: includes/Admin/RestController.php:497
     258#: includes/Admin/RestController.php:509
    237259msgid "The PHP Imagick extension is not enabled either. With neither GD nor Imagick, image compression and WebP/AVIF cannot run. Ask your host to enable \"php-gd\" or \"php-imagick\"."
    238260msgstr ""
    239261
    240 #: includes/Admin/RestController.php:500
     262#: includes/Admin/RestController.php:512
    241263msgid "WebP support"
    242264msgstr ""
    243265
    244 #: includes/Admin/RestController.php:501
    245 #: includes/Admin/RestController.php:507
     266#: includes/Admin/RestController.php:513
     267#: includes/Admin/RestController.php:519
    246268msgid "Available"
    247269msgstr ""
    248270
    249 #: includes/Admin/RestController.php:501
    250 #: includes/Admin/RestController.php:507
     271#: includes/Admin/RestController.php:513
     272#: includes/Admin/RestController.php:519
    251273msgid "Not available"
    252274msgstr ""
    253275
    254 #: includes/Admin/RestController.php:503
     276#: includes/Admin/RestController.php:515
    255277msgid "This server cannot generate WebP images. Update GD/Imagick or ask your host to enable WebP."
    256278msgstr ""
    257279
    258 #: includes/Admin/RestController.php:506
     280#: includes/Admin/RestController.php:518
    259281msgid "AVIF support"
    260282msgstr ""
    261283
    262 #: includes/Admin/RestController.php:509
     284#: includes/Admin/RestController.php:521
    263285msgid "This server cannot generate AVIF images (needs PHP 8.1+ GD with AVIF, or ImageMagick with the AVIF delegate). WebP still works."
    264286msgstr ""
    265287
    266 #: includes/Admin/RestController.php:512
     288#: includes/Admin/RestController.php:524
    267289msgid "PHP memory limit"
    268290msgstr ""
    269291
    270 #: includes/Admin/RestController.php:515
     292#: includes/Admin/RestController.php:527
    271293msgid "Image optimization works best with 256 MB or more. On lower limits Optimazz processes fewer images per request to stay safe."
    272294msgstr ""
    273295
    274 #: includes/Admin/RestController.php:518
     296#: includes/Admin/RestController.php:530
    275297msgid "Max execution time"
    276298msgstr ""
    277299
    278 #: includes/Admin/RestController.php:519
     300#: includes/Admin/RestController.php:531
    279301msgid "Unlimited"
    280302msgstr ""
    281303
    282 #: includes/Admin/RestController.php:521
     304#: includes/Admin/RestController.php:533
    283305msgid "Under 30 seconds can interrupt bulk AVIF conversion of large images."
    284306msgstr ""
    285307
    286 #: includes/Admin/RestController.php:524
     308#: includes/Admin/RestController.php:536
    287309msgid "WP-Cron"
    288310msgstr ""
    289311
    290 #: includes/Admin/RestController.php:525
     312#: includes/Admin/RestController.php:537
    291313msgid "Disabled"
    292314msgstr ""
    293315
    294 #: includes/Admin/RestController.php:527
     316#: includes/Admin/RestController.php:539
    295317msgid "WP-Cron is disabled, so scheduled cache preloading relies on a real system cron job being configured."
    296318msgstr ""
    297319
    298 #: includes/Admin/RestController.php:541
     320#: includes/Admin/RestController.php:553
    299321msgid "GD and Imagick are"
    300322msgstr ""
    301323
    302 #: includes/Admin/RestController.php:542
     324#: includes/Admin/RestController.php:554
    303325msgid "GD is"
    304326msgstr ""
    305327
    306 #: includes/Admin/RestController.php:542
     328#: includes/Admin/RestController.php:554
    307329msgid "Imagick is"
    308330msgstr ""
    309331
    310 #: includes/Admin/RestController.php:544
     332#: includes/Admin/RestController.php:556
    311333msgid "PHP that runs this site"
    312334msgstr ""
    313335
    314336#. translators: 1: which engine is missing, 2: php.ini path, 3: PHP version
    315 #: includes/Admin/RestController.php:550
     337#: includes/Admin/RestController.php:562
    316338#, php-format
    317339msgid "This is the exact PHP handling your site — it loads its settings from %2$s. If you already enabled an image library in your hosting panel but %1$s still shown as not enabled, you almost certainly enabled it for a different PHP version or handler. It must be enabled for THIS PHP (%3$s). After enabling, restart PHP (or PHP-FPM) and click Re-check."
    … …  
    319341
    320342#. translators: 1: which engine is missing, 2: PHP version
    321 #: includes/Admin/RestController.php:557
     343#: includes/Admin/RestController.php:569
    322344#, php-format
    323345msgid "This is the exact PHP handling your site. If you already enabled an image library in your hosting panel but %1$s still shown as not enabled, you enabled it for a different PHP version. It must be enabled for THIS PHP (%2$s). After enabling, restart PHP and click Re-check."
    324346msgstr ""
    325347
    326 #: includes/Admin/RestController.php:707
     348#: includes/Admin/RestController.php:725
    327349msgid "Image processing is unavailable: this server has no GD or ImageMagick extension. Ask your host to enable \"php-gd\" (or \"php-imagick\"), then try again. See Tools → Server Requirements."
    328350msgstr ""
    … …  
    336358msgstr ""
    337359
    338 #: includes/Core/Recommendations.php:61
    339 #: src/index.js:971
     360#: includes/Core/Recommendations.php:63
     361#: src/index.js:1117
    340362msgid "Enable Page Cache"
    341363msgstr ""
    342364
    343 #: includes/Core/Recommendations.php:62
     365#: includes/Core/Recommendations.php:64
    344366msgid "Serve static HTML instead of running PHP on every request — the single biggest speed win."
    345367msgstr ""
    346368
    347 #: includes/Core/Recommendations.php:67
     369#: includes/Core/Recommendations.php:69
    348370msgid "Turn on cache preloading"
    349371msgstr ""
    350372
    351 #: includes/Core/Recommendations.php:68
     373#: includes/Core/Recommendations.php:70
    352374msgid "Warm the cache automatically so visitors never hit a cold, uncached page."
    353375msgstr ""
    354376
    355 #: includes/Core/Recommendations.php:76
     377#: includes/Core/Recommendations.php:78
    356378msgid "Enable JavaScript optimization"
    357379msgstr ""
    358380
    359 #: includes/Core/Recommendations.php:77
     381#: includes/Core/Recommendations.php:79
    360382msgid "Defer and delay scripts to cut Total Blocking Time — the heaviest-weighted mobile metric."
    361383msgstr ""
    362384
    363 #: includes/Core/Recommendations.php:84
     385#: includes/Core/Recommendations.php:86
    364386msgid "Delay JavaScript until interaction"
    365387msgstr ""
    366388
    367 #: includes/Core/Recommendations.php:85
     389#: includes/Core/Recommendations.php:87
    368390msgid "Delay non-critical scripts (analytics, chat, pixels) until the first interaction to reduce blocking time."
    369391msgstr ""
    370392
    371 #: includes/Core/Recommendations.php:93
     393#: includes/Core/Recommendations.php:95
    372394msgid "Enable CSS optimization"
    373395msgstr ""
    374396
    375 #: includes/Core/Recommendations.php:94
     397#: includes/Core/Recommendations.php:96
    376398msgid "Minify CSS to remove render-blocking bytes and speed up First Contentful Paint."
    377399msgstr ""
    378400
    379 #: includes/Core/Recommendations.php:102
     401#: includes/Core/Recommendations.php:104
    380402msgid "Enable Image optimization"
    381403msgstr ""
    382404
    383 #: includes/Core/Recommendations.php:103
     405#: includes/Core/Recommendations.php:105
    384406msgid "Lazy-load images, reserve their space (to prevent layout shift) and serve next-gen formats."
    385407msgstr ""
    386408
    387 #: includes/Core/Recommendations.php:109
     409#: includes/Core/Recommendations.php:111
    388410msgid "Reserve image space (fix layout shift)"
    389411msgstr ""
    390412
    391 #: includes/Core/Recommendations.php:110
     413#: includes/Core/Recommendations.php:112
    392414msgid "Add missing image dimensions and aspect-ratio so images do not push content down as they load (lowers CLS)."
    393415msgstr ""
    394416
    395 #: includes/Core/Recommendations.php:116
    396 #: src/index.js:948
    397 #: src/index.js:1369
     417#: includes/Core/Recommendations.php:118
     418#: src/index.js:1092
     419#: src/index.js:1515
    398420msgid "Lazy-load images"
    399421msgstr ""
    400422
    401 #: includes/Core/Recommendations.php:117
     423#: includes/Core/Recommendations.php:119
    402424msgid "Load off-screen images only when scrolled into view to speed up the initial load."
    403425msgstr ""
    404426
    405 #: includes/Core/Recommendations.php:124
     427#: includes/Core/Recommendations.php:126
    406428msgid "Serve WebP images"
    407429msgstr ""
    408430
    409 #: includes/Core/Recommendations.php:125
     431#: includes/Core/Recommendations.php:127
    410432msgid "Generate and serve WebP copies — typically 25–35% smaller than JPEG/PNG at the same quality."
    411433msgstr ""
    412434
    413 #: includes/Core/Recommendations.php:136
     435#: includes/Core/Recommendations.php:138
    414436msgid "Set font loading to \"Optional\" (reduce CLS)"
    415437msgstr ""
    416438
    417 #: includes/Core/Recommendations.php:137
     439#: includes/Core/Recommendations.php:139
    418440msgid "The \"swap\" mode lets web fonts reflow the page as they load, raising Cumulative Layout Shift. \"Optional\" removes that shift."
    419441msgstr ""
    420442
    421 #: includes/Core/Recommendations.php:143
     443#: includes/Core/Recommendations.php:145
    422444msgid "Enable Font optimization"
    423445msgstr ""
    424446
    425 #: includes/Core/Recommendations.php:144
     447#: includes/Core/Recommendations.php:146
    426448msgid "Preconnect to font hosts and control font-display to reduce render-blocking and layout shift."
    427449msgstr ""
    428450
    429 #: includes/Core/Recommendations.php:152
     451#: includes/Core/Recommendations.php:154
    430452msgid "Enable Database cleanup"
    431453msgstr ""
    432454
    433 #: includes/Core/Recommendations.php:153
     455#: includes/Core/Recommendations.php:155
    434456msgid "Remove revisions, auto-drafts, spam and expired transients, and optimize tables to keep the database lean."
    435457msgstr ""
    436458
    437 #: includes/Core/Recommendations.php:161
     459#: includes/Core/Recommendations.php:163
    438460msgid "Enable an image library on your server"
    439461msgstr ""
    440462
    441 #: includes/Core/Recommendations.php:162
     463#: includes/Core/Recommendations.php:164
    442464msgid "Your server has neither GD nor ImageMagick, so image compression and WebP/AVIF cannot run. Ask your host to enable the \"php-gd\" (or \"php-imagick\") extension. See Tools → Server Requirements."
    443465msgstr ""
    444466
     467#: includes/Modules/Cache/Preloader.php:116
     468msgid "the home page"
     469msgstr ""
     470
    445471#. translators: %d: number of category / tag archive pages.
    446 #: includes/Modules/Cache/Preloader.php:124
     472#: includes/Modules/Cache/Preloader.php:127
    447473#, php-format
    448474msgid "%d category or tag page"
    … …  
    451477msgstr[1] ""
    452478
    453 #: includes/Modules/Cache/Preloader.php:681
     479#: includes/Modules/Cache/Preloader.php:707
    454480msgid "your host's server cache"
    455481msgstr ""
    456482
    457 #: includes/Modules/Cache/Preloader.php:685
     483#: includes/Modules/Cache/Preloader.php:711
    458484msgid "a server or CDN cache"
    459485msgstr ""
    460486
    461 #: includes/Modules/Cache/Preloader.php:704
     487#: includes/Modules/Cache/Preloader.php:730
    462488msgid "The site could not load its own pages (the loopback request failed or timed out). Some hosts block a server from requesting its own website — ask your host to allow it."
    463489msgstr ""
    464490
    465491#. translators: %s: name of another caching plugin.
    466 #: includes/Modules/Cache/Preloader.php:713
     492#: includes/Modules/Cache/Preloader.php:739
    467493#, php-format
    468494msgid "%s controls page caching (advanced-cache.php), so Optimazz cannot save pages. Deactivate it, or use \"Take over caching\" in Cache health."
    469495msgstr ""
    470496
    471 #: includes/Modules/Cache/Preloader.php:718
     497#: includes/Modules/Cache/Preloader.php:744
    472498msgid "The cache file advanced-cache.php is missing. Turn Page Cache off and on again to reinstall it, and make sure the wp-content folder is writable."
    473499msgstr ""
    474500
    475 #: includes/Modules/Cache/Preloader.php:721
     501#: includes/Modules/Cache/Preloader.php:747
    476502msgid "WP_CACHE is not enabled in wp-config.php, so WordPress never loads the cache. Make wp-config.php writable and save the Cache settings, or add define( 'WP_CACHE', true ); yourself."
    477503msgstr ""
    478504
    479505#. translators: %s: name of a server or CDN cache, e.g. "Cloudflare".
    480 #: includes/Modules/Cache/Preloader.php:726
     506#: includes/Modules/Cache/Preloader.php:752
    481507#, php-format
    482508msgid "%s answered from its own cache before WordPress ran, so Optimazz never saw these requests. Purge that cache, or turn it off for HTML pages if you want Optimazz to cache them."
    483509msgstr ""
    484510
    485 #: includes/Modules/Cache/Preloader.php:730
     511#: includes/Modules/Cache/Preloader.php:756
    486512msgid "The Optimazz cache engine did not run for these requests. Usually a server-level cache or proxy (for example LiteSpeed, Varnish or a host cache) answers before WordPress loads. Check Cache health, or ask your host."
    487513msgstr ""
    488514
    489515#. translators: %s: HTTP status code, e.g. 404.
    490 #: includes/Modules/Cache/Preloader.php:737
     516#: includes/Modules/Cache/Preloader.php:763
    491517#, php-format
    492518msgid "Pages answered with HTTP %s instead of 200, and only normal pages are cached. Check that these URLs open correctly."
    493519msgstr ""
    494520
    495 #: includes/Modules/Cache/Preloader.php:742
     521#: includes/Modules/Cache/Preloader.php:768
    496522msgid "Page Cache is switched off."
    497523msgstr ""
    498524
    499 #: includes/Modules/Cache/Preloader.php:743
     525#: includes/Modules/Cache/Preloader.php:769
    500526msgid "Another plugin or the theme marks these pages \"do not cache\" (DONOTCACHEPAGE), so they are skipped. Membership, security or form plugins often do this."
    501527msgstr ""
    502528
    503 #: includes/Modules/Cache/Preloader.php:744
     529#: includes/Modules/Cache/Preloader.php:770
    504530msgid "The web address used to reach the site does not match the WordPress Site Address (for example www vs non-www). Check Settings → General."
    505531msgstr ""
    506532
    507 #: includes/Modules/Cache/Preloader.php:745
     533#: includes/Modules/Cache/Preloader.php:771
    508534msgid "Cache files could not be written. Make the wp-content/cache folder writable (permissions 755)."
    509535msgstr ""
    510536
    511 #: includes/Modules/Cache/Preloader.php:746
     537#: includes/Modules/Cache/Preloader.php:772
    512538msgid "These pages match your \"Never cache these URLs\" setting."
    513539msgstr ""
    514540
    515 #: includes/Modules/Cache/Preloader.php:747
     541#: includes/Modules/Cache/Preloader.php:773
    516542msgid "A cookie on the request matches your \"Never cache when these cookies are set\" setting."
    517543msgstr ""
    518544
    519 #: includes/Modules/Cache/Preloader.php:748
     545#: includes/Modules/Cache/Preloader.php:774
    520546msgid "The preloader's user-agent matches your \"Never cache for these user agents\" setting."
    521547msgstr ""
    522548
    523 #: includes/Modules/Cache/Preloader.php:749
     549#: includes/Modules/Cache/Preloader.php:775
    524550msgid "The pages came back empty or almost empty, so there was nothing to cache."
    525551msgstr ""
    526552
    527 #: includes/Modules/Cache/Preloader.php:750
     553#: includes/Modules/Cache/Preloader.php:776
    528554msgid "WordPress treated the request as logged in, so it was not cached."
    529555msgstr ""
    530556
    531 #: includes/Modules/Cache/Preloader.php:751
     557#: includes/Modules/Cache/Preloader.php:777
    532558msgid "These URLs show a \"not found\" (404) page, which is never cached."
    533559msgstr ""
    534560
    535561#. translators: %s: internal reason code.
    536 #: includes/Modules/Cache/Preloader.php:757
     562#: includes/Modules/Cache/Preloader.php:783
    537563#, php-format
    538564msgid "The cache skipped these pages (reason: %s)."
    … …  
    552578msgstr ""
    553579
    554 #: includes/Modules/Fonts/FontsModule.php:29
     580#: includes/Modules/Fonts/FontsModule.php:38
    555581#: src/index.js:40
    556 #: src/index.js:1570
     582#: src/index.js:1726
    557583msgid "Fonts"
    558584msgstr ""
    … …  
    568594
    569595#: includes/Modules/Media/MediaModule.php:75
    570 #: src/index.js:1359
     596#: src/index.js:1505
    571597msgid "Image Optimization"
    572598msgstr ""
    … …  
    578604#: includes/Modules/Tweaks/TweaksModule.php:25
    579605#: src/index.js:45
    580 #: src/index.js:1679
     606#: src/index.js:1841
    581607msgid "Tweaks"
    582608msgstr ""
    … …  
    603629
    604630#: src/index.js:41
    605 #: src/index.js:1055
     631#: src/index.js:1201
     632#: src/index.js:2191
    606633msgid "Preload"
    607634msgstr ""
    … …  
    612639
    613640#: src/index.js:42
    614 #: src/index.js:1482
     641#: src/index.js:1638
    615642msgid "Advanced Rules"
    616643msgstr ""
    … …  
    640667msgstr ""
    641668
    642 #: src/index.js:64
     669#: src/index.js:81
     670msgid "every hour"
     671msgstr ""
     672
     673#: src/index.js:82
     674#: src/index.js:84
     675msgid "twice a day"
     676msgstr ""
     677
     678#: src/index.js:83
     679msgid "once a day"
     680msgstr ""
     681
     682#: src/index.js:88
     683msgid "never expires"
     684msgstr ""
     685
     686#: src/index.js:89
     687#: src/index.js:1231
     688#: src/index.js:1333
     689#: src/index.js:2107
     690msgid "12 hours"
     691msgstr ""
     692
     693#: src/index.js:90
     694#: src/index.js:1232
     695#: src/index.js:1334
     696#: src/index.js:2108
     697msgid "1 day"
     698msgstr ""
     699
     700#: src/index.js:91
     701#: src/index.js:1233
     702#: src/index.js:1335
     703#: src/index.js:2109
     704msgid "1 week"
     705msgstr ""
     706
     707#: src/index.js:92
     708#: src/index.js:1234
     709#: src/index.js:1336
     710#: src/index.js:2110
     711msgid "1 month"
     712msgstr ""
     713
     714#. translators: %d: number of hours.
     715#: src/index.js:95
     716#: src/index.js:1237
     717#, js-format
     718msgid "%d hour"
     719msgid_plural "%d hours"
     720msgstr[0] ""
     721msgstr[1] ""
     722
     723#: src/index.js:102
     724msgid "Open Network Admin → Optimazz"
     725msgstr ""
     726
     727#: src/index.js:111
     728msgid "Page caching is off for this network"
     729msgstr ""
     730
     731#: src/index.js:113
     732msgid "Turn it on for every site in Network Admin → Optimazz. Each site gets its own cache, and keeps its own exclusions here under Advanced Rules."
     733msgstr ""
     734
     735#: src/index.js:114
     736msgid "Your network administrator can turn it on for every site in Network Admin → Optimazz. All other optimizations — CSS, JavaScript, images, fonts and more — work normally on this site."
     737msgstr ""
     738
     739#: src/index.js:128
     740msgid "Page caching is on — managed for the whole network"
     741msgstr ""
     742
     743#. translators: 1: yes/no, 2: cache lifetime, 3: preload frequency.
     744#: src/index.js:131
     745#, js-format
     746msgid "Separate mobile cache: %1$s · Cache lifetime: %2$s · Automatic preload: %3$s."
     747msgstr ""
     748
     749#: src/index.js:132
     750msgid "yes"
     751msgstr ""
     752
     753#: src/index.js:132
     754msgid "no"
     755msgstr ""
     756
     757#: src/index.js:134
     758#: src/index.js:1247
     759msgid "off"
     760msgstr ""
     761
     762#: src/index.js:136
     763msgid "This site has its own cache. Purge or preload it from the Dashboard or the Preload screen, and set its own exclusions under Advanced Rules."
     764msgstr ""
     765
     766#: src/index.js:146
    643767msgid "Content leftovers"
    644768msgstr ""
    645769
    646 #: src/index.js:65
     770#: src/index.js:147
    647771msgid "Old post data that piles up over time. Removing it is permanent, so keep anything you still need."
    648772msgstr ""
    649773
    650 #: src/index.js:67
     774#: src/index.js:149
    651775msgid "Post revisions"
    652776msgstr ""
    653777
    654 #: src/index.js:67
     778#: src/index.js:149
    655779msgid "Old copies of posts saved automatically. Deleting them keeps only your current content."
    656780msgstr ""
    657781
    658 #: src/index.js:68
     782#: src/index.js:150
    659783msgid "Auto drafts"
    660784msgstr ""
    661785
    662 #: src/index.js:68
     786#: src/index.js:150
    663787msgid "Empty drafts WordPress creates while you write. Safe to remove."
    664788msgstr ""
    665789
    666 #: src/index.js:69
     790#: src/index.js:151
    667791msgid "Trashed posts"
    668792msgstr ""
    669793
    670 #: src/index.js:69
     794#: src/index.js:151
    671795msgid "Posts in the trash that have not been permanently deleted yet."
    672796msgstr ""
    673797
    674 #: src/index.js:73
     798#: src/index.js:155
    675799msgid "Comment clutter"
    676800msgstr ""
    677801
    678 #: src/index.js:74
     802#: src/index.js:156
    679803msgid "Spam and trashed comments you are never going to approve. Clearing them keeps the comments table light."
    680804msgstr ""
    681805
    682 #: src/index.js:76
     806#: src/index.js:158
    683807msgid "Spam comments"
    684808msgstr ""
    685809
    686 #: src/index.js:76
     810#: src/index.js:158
    687811msgid "Comments marked as spam. Removing them frees up the comments table."
    688812msgstr ""
    689813
    690 #: src/index.js:77
     814#: src/index.js:159
    691815msgid "Trashed comments"
    692816msgstr ""
    693817
    694 #: src/index.js:77
     818#: src/index.js:159
    695819msgid "Comments in the trash awaiting permanent deletion."
    696820msgstr ""
    697821
    698 #: src/index.js:81
     822#: src/index.js:163
    699823msgid "Temporary data"
    700824msgstr ""
    701825
    702 #: src/index.js:82
     826#: src/index.js:164
    703827msgid "Short-lived cached values. They are safe to clear and are rebuilt automatically whenever a plugin needs them."
    704828msgstr ""
    705829
    706 #: src/index.js:84
     830#: src/index.js:166
    707831msgid "Expired transients"
    708832msgstr ""
    709833
    710 #: src/index.js:84
     834#: src/index.js:166
    711835msgid "Cached values that have already passed their expiry date."
    712836msgstr ""
    713837
    714 #: src/index.js:88
     838#: src/index.js:170
    715839msgid "Table maintenance"
    716840msgstr ""
    717841
    718 #: src/index.js:89
     842#: src/index.js:171
    719843msgid "Defragment your tables to reclaim wasted space and trim database overhead."
    720844msgstr ""
    721845
    722 #: src/index.js:91
     846#: src/index.js:173
    723847msgid "Optimize database tables"
    724848msgstr ""
    725849
    726 #: src/index.js:91
     850#: src/index.js:173
    727851msgid "Runs OPTIMIZE on your tables to defragment them and free wasted space."
    728852msgstr ""
    729853
    730 #: src/index.js:97
     854#: src/index.js:179
    731855msgid "Do not limit"
    732856msgstr ""
    733857
    734 #: src/index.js:98
     858#: src/index.js:180
    735859msgid "Reduce activity"
    736860msgstr ""
    737861
    738 #: src/index.js:99
     862#: src/index.js:181
    739863msgid "Disable"
    740864msgstr ""
    741865
    742 #: src/index.js:108
     866#: src/index.js:190
    743867msgid "Active"
    744868msgstr ""
    745869
    746 #: src/index.js:108
    747 #: src/index.js:1110
    748 #: src/index.js:1636
     870#: src/index.js:190
     871#: src/index.js:1256
     872#: src/index.js:1798
    749873msgid "Off"
    750874msgstr ""
    751875
    752 #: src/index.js:150
     876#: src/index.js:232
    753877msgid "Cancelled"
    754878msgstr ""
    755879
    756 #: src/index.js:151
     880#: src/index.js:233
    757881msgid "Failed"
    758882msgstr ""
    759883
    760 #: src/index.js:167
     884#: src/index.js:249
    761885msgid "Expand"
    762886msgstr ""
    763887
    764 #: src/index.js:167
     888#: src/index.js:249
    765889msgid "Minimize"
    766890msgstr ""
    767891
    768 #: src/index.js:170
     892#: src/index.js:252
    769893msgid "Close"
    770894msgstr ""
    771895
    772 #: src/index.js:184
     896#: src/index.js:266
    773897msgid "Step"
    774898msgstr ""
    775899
    776 #: src/index.js:189
     900#: src/index.js:271
    777901msgid "Stop"
    778902msgstr ""
    779903
    780 #: src/index.js:492
    781 #: src/index.js:1239
     904#: src/index.js:538
     905msgid "You have unsaved changes"
     906msgstr ""
     907
     908#: src/index.js:538
     909msgid "All changes saved"
     910msgstr ""
     911
     912#: src/index.js:541
     913msgid "Save changes"
     914msgstr ""
     915
     916#: src/index.js:639
     917#: src/index.js:1385
    782918msgid "Deactivate"
    783919msgstr ""
    784920
    785 #: src/index.js:492
     921#: src/index.js:639
    786922msgid "Optimazz will keep handling caching."
    787923msgstr ""
    788924
    789 #: src/index.js:499
     925#: src/index.js:646
    790926msgid "deactivated — Optimazz is now the only cache."
    791927msgstr ""
    792928
    793 #: src/index.js:501
     929#: src/index.js:648
    794930msgid "Could not deactivate the plugin."
    795931msgstr ""
    796932
    797 #: src/index.js:508
     933#: src/index.js:655
    798934msgid "Make Optimazz the caching plugin? This replaces the current advanced-cache.php (from another caching plugin) and turns on WP_CACHE. Deactivate the other caching plugin afterwards to avoid conflicts."
    799935msgstr ""
    800936
    801 #: src/index.js:515
     937#: src/index.js:662
    802938msgid "Optimazz is now serving pages from cache."
    803939msgstr ""
    804940
    805 #: src/index.js:516
     941#: src/index.js:663
    806942msgid "Drop-in installed. Preload the cache, then check again."
    807943msgstr ""
    808944
    809 #: src/index.js:517
     945#: src/index.js:664
    810946msgid "Could not fully take over — see the cache health details."
    811947msgstr ""
    812948
    813 #: src/index.js:520
     949#: src/index.js:667
    814950msgid "Take-over failed. Check file permissions."
    815951msgstr ""
    816952
    817 #: src/index.js:542
    818 #: src/index.js:588
     953#: src/index.js:689
     954#: src/index.js:735
    819955msgid "Cache purged."
    820956msgstr ""
    821957
    822 #: src/index.js:543
     958#: src/index.js:690
    823959msgid "Cache preload started in the background."
    824960msgstr ""
    825961
    826 #: src/index.js:544
     962#: src/index.js:691
    827963msgid "Cache cleared; preload started in the background."
    828964msgstr ""
    829965
    830 #: src/index.js:553
     966#: src/index.js:700
    831967msgid "wp-config.php is not writable — add WP_CACHE manually to enable page caching."
    832968msgstr ""
    833969
    834 #: src/index.js:567
     970#: src/index.js:714
    835971msgid "Settings saved."
    836972msgstr ""
    837973
    838 #: src/index.js:570
     974#: src/index.js:717
    839975msgid "Save failed."
    840976msgstr ""
    841977
    842 #: src/index.js:620
     978#: src/index.js:767
    843979msgid "Converting images to WebP/AVIF"
    844980msgstr ""
    845981
    846 #: src/index.js:621
     982#: src/index.js:768
    847983msgid "Next-gen format generation"
    848984msgstr ""
    849985
    850 #: src/index.js:624
     986#: src/index.js:771
    851987msgid "Generated"
    852988msgstr ""
    853989
    854 #: src/index.js:624
     990#: src/index.js:771
    855991msgid "files in this batch"
    856992msgstr ""
    857993
    858 #: src/index.js:624
    859 #: src/index.js:633
     994#: src/index.js:771
     995#: src/index.js:780
    860996msgid "skipped"
    861997msgstr ""
    862998
    863 #: src/index.js:627
     999#: src/index.js:774
    8641000msgid "Compressing images"
    8651001msgstr ""
    8661002
    867 #: src/index.js:628
     1003#: src/index.js:775
    8681004msgid "Recompress, resize & strip metadata"
    8691005msgstr ""
    8701006
    871 #: src/index.js:634
     1007#: src/index.js:781
    8721008msgid "Processed"
    8731009msgstr ""
    8741010
    875 #: src/index.js:634
    876 #: src/index.js:1440
     1011#: src/index.js:781
     1012#: src/index.js:1586
    8771013msgid "saved"
    8781014msgstr ""
    8791015
    880 #: src/index.js:638
     1016#: src/index.js:785
     1017#: src/index.js:1953
    8811018msgid "Preloading cache"
    8821019msgstr ""
    8831020
    884 #: src/index.js:639
     1021#: src/index.js:786
    8851022msgid "Warming pages"
    8861023msgstr ""
    8871024
    888 #: src/index.js:643
     1025#: src/index.js:790
     1026#: src/index.js:1974
    8891027msgid "Warmed"
    8901028msgstr ""
    8911029
    892 #: src/index.js:643
     1030#: src/index.js:790
     1031#: src/index.js:1974
    8931032msgid "pages"
    8941033msgstr ""
    8951034
    896 #: src/index.js:643
     1035#: src/index.js:790
     1036#: src/index.js:1974
    8971037msgid "cached"
    8981038msgstr ""
    8991039
    900 #: src/index.js:643
     1040#: src/index.js:790
     1041#: src/index.js:1974
    9011042msgid "not cached"
    9021043msgstr ""
    9031044
    904 #: src/index.js:664
     1045#: src/index.js:811
    9051046msgid "Resuming…"
    9061047msgstr ""
    9071048
    908 #: src/index.js:678
     1049#: src/index.js:825
     1050#: src/index.js:1963
    9091051msgid "Stopped by user."
    9101052msgstr ""
    9111053
    912 #: src/index.js:697
     1054#: src/index.js:844
    9131055msgid "A batch failed (likely one slow/large image); skipped it and continued."
    9141056msgstr ""
    9151057
    916 #: src/index.js:699
     1058#: src/index.js:846
    9171059msgid "Completed (some items skipped)."
    9181060msgstr ""
    9191061
    920 #: src/index.js:711
     1062#: src/index.js:858
    9211063msgid "Job stalled — no progress."
    9221064msgstr ""
    9231065
    924 #: src/index.js:722
     1066#: src/index.js:869
     1067#: src/index.js:1977
    9251068msgid "left"
    9261069msgstr ""
    9271070
    9281071#. translators: %d: number of pages to warm.
    929 #: src/index.js:734
     1072#: src/index.js:881
     1073#: src/index.js:1987
    9301074#, js-format
    9311075msgid "Found %d pages to warm:"
    9321076msgstr ""
    9331077
    934 #: src/index.js:744
     1078#: src/index.js:891
    9351079msgid "Image processing is unavailable on this server (no GD or ImageMagick). See Tools → Server Requirements."
    9361080msgstr ""
    9371081
    938 #: src/index.js:749
     1082#: src/index.js:896
     1083#: src/index.js:1993
    9391084msgid "Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the \"Optimazz-CacheWarmer\" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter."
    9401085msgstr ""
    9411086
    942 #: src/index.js:756
     1087#: src/index.js:903
     1088#: src/index.js:1997
    9431089msgid "Pages are not being cached"
    9441090msgstr ""
    9451091
    946 #: src/index.js:756
     1092#: src/index.js:903
     1093#: src/index.js:1997
    9471094msgid "Stopped: pages are loading but none are being saved to the cache."
    9481095msgstr ""
    9491096
    950 #: src/index.js:760
    951 #: src/index.js:841
     1097#: src/index.js:907
     1098#: src/index.js:988
     1099#: src/index.js:2007
    9521100msgid "Completed."
    9531101msgstr ""
    9541102
    9551103#. translators: %d: number of pages.
    956 #: src/index.js:764
     1104#: src/index.js:911
     1105#: src/index.js:2004
    9571106#, js-format
    9581107msgid "%d page was not cached."
    … …  
    9611110msgstr[1] ""
    9621111
    963 #: src/index.js:823
     1112#: src/index.js:970
    9641113msgid "Cleaning and optimizing database…"
    9651114msgstr ""
    9661115
    967 #: src/index.js:827
     1116#: src/index.js:974
    9681117msgid "Optimizing database"
    9691118msgstr ""
    9701119
    971 #: src/index.js:828
     1120#: src/index.js:975
    9721121msgid "Cleanup & table optimization"
    9731122msgstr ""
    9741123
    975 #: src/index.js:840
     1124#: src/index.js:987
    9761125msgid "Cleaned"
    9771126msgstr ""
    9781127
    979 #: src/index.js:840
     1128#: src/index.js:987
    9801129msgid "rows,"
    9811130msgstr ""
    9821131
    983 #: src/index.js:840
     1132#: src/index.js:987
    9841133msgid "tables optimized."
    9851134msgstr ""
    9861135
    987 #: src/index.js:845
     1136#: src/index.js:992
    9881137msgid "Optimize failed."
    9891138msgstr ""
    9901139
    991 #: src/index.js:851
     1140#: src/index.js:998
    9921141msgid "Reset all Optimazz settings to defaults? You can undo this with \"Undo last change\"."
    9931142msgstr ""
    9941143
    995 #: src/index.js:859
     1144#: src/index.js:1006
    9961145msgid "Settings reset to defaults."
    9971146msgstr ""
    9981147
    999 #: src/index.js:862
     1148#: src/index.js:1009
    10001149msgid "Reset failed."
    10011150msgstr ""
    10021151
    1003 #: src/index.js:872
     1152#: src/index.js:1019
    10041153msgid "Nothing to undo."
    10051154msgstr ""
    10061155
    1007 #: src/index.js:879
     1156#: src/index.js:1026
    10081157msgid "Reverted to the previous settings."
    10091158msgstr ""
    10101159
    1011 #: src/index.js:882
     1160#: src/index.js:1029
    10121161msgid "Undo failed."
    10131162msgstr ""
    10141163
    1015 #: src/index.js:914
     1164#: src/index.js:1061
    10161165msgid "Invalid settings file."
    10171166msgstr ""
    10181167
    1019 #: src/index.js:944
     1168#: src/index.js:1088
    10201169msgid "Page caching"
    10211170msgstr ""
    10221171
    1023 #: src/index.js:945
     1172#: src/index.js:1089
    10241173msgid "Delay JavaScript"
    10251174msgstr ""
    10261175
    1027 #: src/index.js:946
    1028 #: src/index.js:1266
     1176#: src/index.js:1090
     1177#: src/index.js:1412
    10291178msgid "Defer JavaScript"
    10301179msgstr ""
    10311180
    1032 #: src/index.js:947
    1033 #: src/index.js:1319
     1181#: src/index.js:1091
     1182#: src/index.js:1465
    10341183msgid "Minify CSS"
    10351184msgstr ""
    10361185
    1037 #: src/index.js:949
     1186#: src/index.js:1093
    10381187msgid "Next-gen images"
    10391188msgstr ""
    10401189
    1041 #: src/index.js:950
     1190#: src/index.js:1094
    10421191msgid "Font optimization"
    10431192msgstr ""
    10441193
    1045 #: src/index.js:951
     1194#: src/index.js:1095
    10461195msgid "Cache preloading"
    10471196msgstr ""
    10481197
    1049 #: src/index.js:952
     1198#: src/index.js:1096
    10501199msgid "Heartbeat control"
    10511200msgstr ""
    10521201
    1053 #: src/index.js:967
     1202#: src/index.js:1113
    10541203msgid "Turn on Page Cache for the real speed gain"
    10551204msgstr ""
    10561205
    1057 #: src/index.js:968
     1206#: src/index.js:1114
    10581207msgid "File and media optimizations are on, but Page Cache is off — so the server rebuilds every page on every request. Without caching this can make a site feel slower, not faster, especially on mobile. Enable Page Cache to serve ready-made HTML."
    10591208msgstr ""
    10601209
    1061 #: src/index.js:987
     1210#: src/index.js:1133
    10621211msgid "SCORE"
    10631212msgstr ""
    10641213
    1065 #: src/index.js:992
     1214#: src/index.js:1138
    10661215msgid "Performance score"
    10671216msgstr ""
    10681217
    1069 #: src/index.js:995
     1218#: src/index.js:1141
    10701219msgid "Biggest remaining win:"
    10711220msgstr ""
    10721221
    1073 #: src/index.js:996
     1222#: src/index.js:1142
    10741223msgid "Every optimization is enabled. Nicely done!"
    10751224msgstr ""
    10761225
    1077 #: src/index.js:1013
    1078 #: src/index.js:1065
     1226#: src/index.js:1159
     1227#: src/index.js:1211
     1228#: src/index.js:2204
    10791229msgid "Cached pages"
    10801230msgstr ""
    10811231
    1082 #: src/index.js:1018
     1232#: src/index.js:1164
    10831233msgid "Cache size"
    10841234msgstr ""
    10851235
    1086 #: src/index.js:1023
     1236#: src/index.js:1169
    10871237msgid "Modules active"
    10881238msgstr ""
    10891239
    1090 #: src/index.js:1028
    1091 #: src/index.js:1043
     1240#: src/index.js:1174
     1241#: src/index.js:1189
    10921242msgid "Recommendations"
    10931243msgstr ""
    10941244
    1095 #: src/index.js:1028
     1245#: src/index.js:1174
    10961246msgid "What to fix next to speed up your site — one click each."
    10971247msgstr ""
    10981248
    1099 #: src/index.js:1035
     1249#: src/index.js:1181
    11001250msgid "Fix it"
    11011251msgstr ""
    11021252
    1103 #: src/index.js:1036
     1253#: src/index.js:1182
    11041254msgid "Open"
    11051255msgstr ""
    11061256
    1107 #: src/index.js:1044
     1257#: src/index.js:1190
    11081258msgid "Nice — everything we recommend is already on. Your site is well optimized."
    11091259msgstr ""
    11101260
    1111 #: src/index.js:1049
     1261#: src/index.js:1195
    11121262msgid "Cache Files"
    11131263msgstr ""
    11141264
    1115 #: src/index.js:1049
     1265#: src/index.js:1195
    11161266msgid "Clear and preload all cache files."
    11171267msgstr ""
    11181268
    1119 #: src/index.js:1052
     1269#: src/index.js:1198
     1270#: src/index.js:2187
    11201271msgid "Clear & Preload"
    11211272msgstr ""
    11221273
    1123 #: src/index.js:1058
     1274#: src/index.js:1204
     1275#: src/index.js:2195
    11241276msgid "Purge cache"
    11251277msgstr ""
    11261278
    1127 #: src/index.js:1070
     1279#: src/index.js:1216
     1280#: src/index.js:2209
    11281281msgid "Page cache size"
    11291282msgstr ""
    11301283
    1131 #: src/index.js:1075
     1284#: src/index.js:1221
    11321285msgid "Optimized CSS"
    11331286msgstr ""
    11341287
    1135 #: src/index.js:1080
    1136 #: src/index.js:1081
     1288#: src/index.js:1226
     1289#: src/index.js:1227
    11371290msgid "Cache lifespan"
    11381291msgstr ""
    11391292
    1140 #: src/index.js:1084
     1293#: src/index.js:1230
    11411294msgid "Never expire"
    11421295msgstr ""
    11431296
    1144 #: src/index.js:1085
    1145 #: src/index.js:1187
    1146 msgid "12 hours"
    1147 msgstr ""
    1148 
    1149 #: src/index.js:1086
    1150 #: src/index.js:1188
    1151 msgid "1 day"
    1152 msgstr ""
    1153 
    1154 #: src/index.js:1087
    1155 #: src/index.js:1189
    1156 msgid "1 week"
    1157 msgstr ""
    1158 
    1159 #: src/index.js:1088
    1160 #: src/index.js:1190
    1161 msgid "1 month"
    1162 msgstr ""
    1163 
    1164 #. translators: %d: number of hours.
    1165 #: src/index.js:1091
    1166 #, js-format
    1167 msgid "%d hour"
    1168 msgid_plural "%d hours"
    1169 msgstr[0] ""
    1170 msgstr[1] ""
    1171 
    1172 #: src/index.js:1098
     1297#: src/index.js:1244
    11731298msgid "Auto-preload"
    11741299msgstr ""
    11751300
    1176 #: src/index.js:1101
    1177 msgid "off"
    1178 msgstr ""
    1179 
    1180 #: src/index.js:1103
     1301#: src/index.js:1249
    11811302msgid "next run: due now"
    11821303msgstr ""
    11831304
    1184 #: src/index.js:1104
     1305#: src/index.js:1250
    11851306msgid "next run in"
    11861307msgstr ""
    11871308
    1188 #: src/index.js:1107
     1309#: src/index.js:1253
    11891310msgid "Auto-preload frequency"
    11901311msgstr ""
    11911312
    1192 #: src/index.js:1111
    1193 #: src/index.js:1462
     1313#: src/index.js:1257
     1314#: src/index.js:1609
     1315#: src/index.js:2121
    11941316msgid "Every hour"
    11951317msgstr ""
    11961318
    1197 #: src/index.js:1112
    1198 #: src/index.js:1463
     1319#: src/index.js:1258
     1320#: src/index.js:1610
     1321#: src/index.js:2122
    11991322msgid "Twice a day"
    12001323msgstr ""
    12011324
    1202 #: src/index.js:1113
    1203 #: src/index.js:1464
     1325#: src/index.js:1259
     1326#: src/index.js:1611
     1327#: src/index.js:2123
    12041328msgid "Once a day"
    12051329msgstr ""
    12061330
    1207 #: src/index.js:1121
     1331#: src/index.js:1267
    12081332msgid "Last auto-preload"
    12091333msgstr ""
    12101334
    1211 #: src/index.js:1124
     1335#: src/index.js:1270
    12121336msgid "not yet"
    12131337msgstr ""
    12141338
    12151339#. translators: 1: time ago, e.g. "2 hours", 2: number of pages
    1216 #: src/index.js:1127
     1340#: src/index.js:1273
    12171341#, js-format
    12181342msgid "%1$s ago · %2$d pages"
    12191343msgstr ""
    12201344
    1221 #: src/index.js:1130
     1345#: src/index.js:1276
    12221346msgid "blocked by firewall"
    12231347msgstr ""
    12241348
    1225 #: src/index.js:1135
     1349#: src/index.js:1281
    12261350msgid "Module Status"
    12271351msgstr ""
    12281352
    1229 #: src/index.js:1135
     1353#: src/index.js:1281
    12301354msgid "Enable or disable any module right here."
    12311355msgstr ""
    12321356
    1233 #: src/index.js:1172
     1357#: src/index.js:1318
    12341358msgid "Serve static HTML instead of running PHP on every request."
    12351359msgstr ""
    12361360
    1237 #: src/index.js:1174
     1361#: src/index.js:1320
     1362#: src/index.js:2094
    12381363msgid "Cache mobile devices separately"
    12391364msgstr ""
    12401365
    1241 #: src/index.js:1176
     1366#: src/index.js:1322
     1367#: src/index.js:2096
    12421368msgid "Cache logged-in users"
    12431369msgstr ""
    12441370
    1245 #: src/index.js:1177
     1371#: src/index.js:1323
     1372#: src/index.js:2097
    12461373msgid "Leave off unless you know what you are doing."
    12471374msgstr ""
    12481375
    1249 #: src/index.js:1179
     1376#: src/index.js:1325
     1377#: src/index.js:2099
    12501378msgid "Browser cache headers"
    12511379msgstr ""
    12521380
    1253 #: src/index.js:1180
     1381#: src/index.js:1326
    12541382msgid "Add Expires / Cache-Control rules so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only (written to .htaccess); ignored on Nginx."
    12551383msgstr ""
    12561384
    1257 #: src/index.js:1182
     1385#: src/index.js:1328
     1386#: src/index.js:2102
    12581387msgid "Cache lifetime"
    12591388msgstr ""
    12601389
    1261 #: src/index.js:1183
     1390#: src/index.js:1329
    12621391msgid "How long a cached page is kept before it is refreshed. Pages are always purged immediately when their content changes."
    12631392msgstr ""
    12641393
    1265 #: src/index.js:1186
     1394#: src/index.js:1332
     1395#: src/index.js:2106
    12661396msgid "Never"
    12671397msgstr ""
    12681398
    1269 #: src/index.js:1196
     1399#: src/index.js:1342
     1400#: src/index.js:2128
    12701401msgid "Cache health"
    12711402msgstr ""
    12721403
    1273 #: src/index.js:1198
     1404#: src/index.js:1344
    12741405msgid "Check now"
    12751406msgstr ""
    12761407
    1277 #: src/index.js:1201
     1408#: src/index.js:1347
    12781409msgid "Confirms caching is actually serving pages. If it is not, the plugin rebuilds every page on each request — which is slower, not faster."
    12791410msgstr ""
    12801411
    1281 #: src/index.js:1205
     1412#: src/index.js:1351
    12821413msgid "Pages ARE being served from cache (HIT)"
    12831414msgstr ""
    12841415
    1285 #: src/index.js:1206
     1416#: src/index.js:1352
    12861417msgid "Pages are NOT being served from cache (MISS) — see below"
    12871418msgstr ""
    12881419
    1289 #: src/index.js:1207
     1420#: src/index.js:1353
    12901421msgid "Live check unavailable"
    12911422msgstr ""
    12921423
    1293 #: src/index.js:1211
     1424#: src/index.js:1357
    12941425msgid "advanced-cache.php belongs to"
    12951426msgstr ""
    12961427
    1297 #: src/index.js:1211
     1428#: src/index.js:1357
    12981429msgid "take it over below"
    12991430msgstr ""
    13001431
    1301 #: src/index.js:1212
     1432#: src/index.js:1358
    13021433msgid "advanced-cache.php belongs to ANOTHER plugin — take it over below"
    13031434msgstr ""
    13041435
    1305 #: src/index.js:1213
     1436#: src/index.js:1359
    13061437msgid "advanced-cache.php drop-in installed"
    13071438msgstr ""
    13081439
    1309 #: src/index.js:1214
     1440#: src/index.js:1360
    13101441msgid "advanced-cache.php drop-in is missing"
    13111442msgstr ""
    13121443
    1313 #: src/index.js:1216
     1444#: src/index.js:1362
    13141445msgid "WP_CACHE is enabled in wp-config.php"
    13151446msgstr ""
    13161447
    1317 #: src/index.js:1216
     1448#: src/index.js:1362
    13181449msgid "WP_CACHE is not enabled in wp-config.php"
    13191450msgstr ""
    13201451
    1321 #: src/index.js:1218
     1452#: src/index.js:1364
    13221453msgid "wp-config.php is not writable — add the WP_CACHE line manually (below)"
    13231454msgstr ""
    13241455
    1325 #: src/index.js:1221
     1456#: src/index.js:1367
    13261457msgid "pages warmed in cache"
    13271458msgstr ""
    13281459
    1329 #: src/index.js:1227
     1460#: src/index.js:1373
    13301461msgid "Take over caching"
    13311462msgstr ""
    13321463
    1333 #: src/index.js:1229
     1464#: src/index.js:1375
    13341465msgid "Make Optimazz the caching plugin — installs its drop-in and enables WP_CACHE."
    13351466msgstr ""
    13361467
    1337 #: src/index.js:1237
     1468#: src/index.js:1383
    13381469msgid "is still active and may reclaim caching. Deactivate it so Optimazz keeps serving the cache."
    13391470msgstr ""
    13401471
    1341 #: src/index.js:1247
     1472#: src/index.js:1393
    13421473msgid "wp-config.php is not writable. Add this line just after the opening <?php tag:"
    13431474msgstr ""
    13441475
    1345 #: src/index.js:1252
     1476#: src/index.js:1398
    13461477msgid "Could not run the check. Try reloading the page."
    13471478msgstr ""
    13481479
    1349 #: src/index.js:1260
     1480#: src/index.js:1406
    13501481msgid "JavaScript"
    13511482msgstr ""
    13521483
    1353 #: src/index.js:1261
     1484#: src/index.js:1407
    13541485msgid "Defer and delay scripts to cut Total Blocking Time."
    13551486msgstr ""
    13561487
    1357 #: src/index.js:1263
     1488#: src/index.js:1409
    13581489msgid "Minify JavaScript"
    13591490msgstr ""
    13601491
    1361 #: src/index.js:1264
     1492#: src/index.js:1410
    13621493msgid "Strip comments and whitespace from inline scripts. Line breaks are kept for safety; test your site after enabling."
    13631494msgstr ""
    13641495
    1365 #: src/index.js:1267
     1496#: src/index.js:1413
    13661497msgid "Load scripts without blocking page render."
    13671498msgstr ""
    13681499
    1369 #: src/index.js:1269
     1500#: src/index.js:1415
    13701501msgid "Delay JS until interaction"
    13711502msgstr ""
    13721503
    1373 #: src/index.js:1270
     1504#: src/index.js:1416
    13741505msgid "Run scripts only after the first user interaction."
    13751506msgstr ""
    13761507
    1377 #: src/index.js:1273
     1508#: src/index.js:1419
    13781509msgid "Delay all scripts"
    13791510msgstr ""
    13801511
    1381 #: src/index.js:1274
     1512#: src/index.js:1420
    13821513msgid "Delay every script until interaction. jQuery, your theme, and Elementor are always kept safe. Still, on sites where content is rendered by JavaScript this can delay the visible page — test before keeping it on."
    13831514msgstr ""
    13841515
    1385 #: src/index.js:1280
     1516#: src/index.js:1426
    13861517msgid "Delaying all scripts can slow the first paint on JavaScript-rendered sites (Elementor, sliders). If your PageSpeed FCP/LCP get worse, switch to \"Delay selected\" and delay only tracking scripts."
    13871518msgstr ""
    13881519
    1389 #: src/index.js:1284
     1520#: src/index.js:1430
    13901521msgid "Delay these scripts"
    13911522msgstr ""
    13921523
    1393 #: src/index.js:1285
     1524#: src/index.js:1431
    13941525msgid "Only scripts matching these keywords are delayed. Match by filename, handle or URL — e.g. gtag, analytics, facebook."
    13951526msgstr ""
    13961527
    1397 #: src/index.js:1286
     1528#: src/index.js:1432
    13981529msgid "e.g. gtag, analytics"
    13991530msgstr ""
    14001531
    1401 #: src/index.js:1292
     1532#: src/index.js:1438
    14021533msgid "Exclude specific scripts"
    14031534msgstr ""
    14041535
    1405 #: src/index.js:1294
     1536#: src/index.js:1440
    14061537msgid "If a script breaks, exclude it from just one optimization instead of both. Match by filename, script handle, or part of the URL."
    14071538msgstr ""
    14081539
    1409 #: src/index.js:1298
     1540#: src/index.js:1444
    14101541msgid "Never defer"
    14111542msgstr ""
    14121543
    1413 #: src/index.js:1299
     1544#: src/index.js:1445
    14141545msgid "Keep loading these render-blocking, but still allow delay. Use for scripts that must run during page load."
    14151546msgstr ""
    14161547
    1417 #: src/index.js:1300
     1548#: src/index.js:1446
    14181549msgid "e.g. jquery.min.js"
    14191550msgstr ""
    14201551
    1421 #: src/index.js:1304
     1552#: src/index.js:1450
    14221553msgid "Never delay"
    14231554msgstr ""
    14241555
    1425 #: src/index.js:1305
     1556#: src/index.js:1451
    14261557msgid "Run these before any interaction, but still allow defer. Use for sliders, above-the-fold, or consent scripts."
    14271558msgstr ""
    14281559
    1429 #: src/index.js:1306
     1560#: src/index.js:1452
    14301561msgid "e.g. slider, swiper"
    14311562msgstr ""
    14321563
    1433 #: src/index.js:1309
     1564#: src/index.js:1455
    14341565msgid "Never optimize"
    14351566msgstr ""
    14361567
    1437 #: src/index.js:1310
     1568#: src/index.js:1456
    14381569msgid "Skip both defer and delay — leave these scripts exactly as they are."
    14391570msgstr ""
    14401571
    1441 #: src/index.js:1311
     1572#: src/index.js:1457
    14421573msgid "e.g. recaptcha"
    14431574msgstr ""
    14441575
    1445 #: src/index.js:1316
     1576#: src/index.js:1462
    14461577msgid "CSS"
    14471578msgstr ""
    14481579
    1449 #: src/index.js:1317
     1580#: src/index.js:1463
    14501581msgid "Minify, combine, and remove render-blocking stylesheets."
    14511582msgstr ""
    14521583
    1453 #: src/index.js:1321
     1584#: src/index.js:1467
    14541585msgid "Combine CSS files"
    14551586msgstr ""
    14561587
    1457 #: src/index.js:1322
     1588#: src/index.js:1468
    14581589msgid "Merge stylesheets to reduce requests."
    14591590msgstr ""
    14601591
    1461 #: src/index.js:1324
     1592#: src/index.js:1470
    14621593msgid "Load CSS asynchronously"
    14631594msgstr ""
    14641595
    1465 #: src/index.js:1325
     1596#: src/index.js:1471
    14661597msgid "Removes render-blocking CSS. Paste your above-the-fold (critical) CSS below, or the page may briefly flash unstyled."
    14671598msgstr ""
    14681599
    1469 #: src/index.js:1332
     1600#: src/index.js:1478
    14701601msgid "No critical CSS provided. Async loading is on, so visitors may see a flash of unstyled content until you paste critical CSS below."
    14711602msgstr ""
    14721603
    1473 #: src/index.js:1336
     1604#: src/index.js:1482
    14741605msgid "Critical CSS (above-the-fold)"
    14751606msgstr ""
    14761607
    1477 #: src/index.js:1337
     1608#: src/index.js:1483
    14781609msgid "Paste the above-the-fold CSS to inline. Generate it with a free tool such as a critical-CSS generator. Leave empty to disable inlining."
    14791610msgstr ""
    14801611
    1481 #: src/index.js:1342
     1612#: src/index.js:1488
    14821613msgid "Remove unused CSS"
    14831614msgstr ""
    14841615
    1485 #: src/index.js:1343
     1616#: src/index.js:1489
    14861617msgid "Remove block-editor CSS on the front end"
    14871618msgstr ""
    14881619
    1489 #: src/index.js:1344
     1620#: src/index.js:1490
    14901621msgid "Dequeues WordPress block-library and global styles. Turn on only if your front end does NOT render Gutenberg blocks (e.g. a page-builder or classic theme). Big byte saving when it applies."
    14911622msgstr ""
    14921623
    1493 #: src/index.js:1346
     1624#: src/index.js:1492
    14941625msgid "Load WooCommerce CSS only on shop pages"
    14951626msgstr ""
    14961627
    1497 #: src/index.js:1347
     1628#: src/index.js:1493
    14981629msgid "Dequeues WooCommerce styles on non-shop pages (kept on cart, checkout, account, product and shop). Turn off if you show Woo products or widgets on other pages."
    14991630msgstr ""
    15001631
    1501 #: src/index.js:1349
     1632#: src/index.js:1495
    15021633msgid "Load Easy Digital Downloads CSS only on EDD pages"
    15031634msgstr ""
    15041635
    1505 #: src/index.js:1350
     1636#: src/index.js:1496
    15061637msgid "Dequeues EDD styles on non-EDD pages (kept on checkout, purchase-confirmation, purchase history and download archives). Turn off if you show download or purchase links elsewhere."
    15071638msgstr ""
    15081639
    1509 #: src/index.js:1352
     1640#: src/index.js:1498
    15101641msgid "Excluded CSS files (keywords)"
    15111642msgstr ""
    15121643
    1513 #: src/index.js:1360
     1644#: src/index.js:1506
    15141645msgid "Lazy-load, prevent layout shift, serve next-gen formats, and lighten media."
    15151646msgstr ""
    15161647
    1517 #: src/index.js:1366
     1648#: src/index.js:1512
    15181649msgid "This server has no GD or ImageMagick extension, so image compression and WebP/AVIF cannot run — every image will be skipped. Ask your host to enable \"php-gd\" (or \"php-imagick\"). See Tools → Server Requirements."
    15191650msgstr ""
    15201651
    1521 #: src/index.js:1370
     1652#: src/index.js:1516
    15221653msgid "Load off-screen images only when scrolled into view."
    15231654msgstr ""
    15241655
    1525 #: src/index.js:1372
     1656#: src/index.js:1518
    15261657msgid "Lazy-load iframes"
    15271658msgstr ""
    15281659
    1529 #: src/index.js:1374
     1660#: src/index.js:1520
    15301661msgid "Lazy-load CSS background images"
    15311662msgstr ""
    15321663
    1533 #: src/index.js:1375
     1664#: src/index.js:1521
    15341665msgid "Defer inline background-image elements (test with page builders)."
    15351666msgstr ""
    15361667
    1537 #: src/index.js:1377
     1668#: src/index.js:1523
    15381669msgid "Replace YouTube embeds with a preview"
    15391670msgstr ""
    15401671
    1541 #: src/index.js:1378
     1672#: src/index.js:1524
    15421673msgid "Load the video iframe only when the visitor clicks play."
    15431674msgstr ""
    15441675
    1545 #: src/index.js:1380
     1676#: src/index.js:1526
    15461677msgid "Add missing image dimensions"
    15471678msgstr ""
    15481679
    1549 #: src/index.js:1381
     1680#: src/index.js:1527
    15501681msgid "Set width/height to prevent layout shift (CLS)."
    15511682msgstr ""
    15521683
    1553 #: src/index.js:1383
     1684#: src/index.js:1529
    15541685msgid "Preload the LCP image"
    15551686msgstr ""
    15561687
    1557 #: src/index.js:1384
     1688#: src/index.js:1530
    15581689msgid "Preload the first/hero image with high priority."
    15591690msgstr ""
    15601691
    1561 #: src/index.js:1387
     1692#: src/index.js:1533
    15621693msgid "Keep first images eager (LCP)"
    15631694msgstr ""
    15641695
    1565 #: src/index.js:1388
     1696#: src/index.js:1534
    15661697msgid "Do not lazy-load the first N images, to protect LCP."
    15671698msgstr ""
    15681699
    1569 #: src/index.js:1393
     1700#: src/index.js:1539
    15701701msgid "Serve WebP via <picture>"
    15711702msgstr ""
    15721703
    1573 #: src/index.js:1394
     1704#: src/index.js:1540
    15741705msgid "Deliver WebP to supporting browsers, original as fallback."
    15751706msgstr ""
    15761707
    1577 #: src/index.js:1396
     1708#: src/index.js:1542
    15781709msgid "Generate WebP/AVIF on upload"
    15791710msgstr ""
    15801711
    1581 #: src/index.js:1398
     1712#: src/index.js:1544
    15821713msgid "WebP generation is not available on this server."
    15831714msgstr ""
    15841715
    1585 #: src/index.js:1399
     1716#: src/index.js:1545
    15861717msgid "Auto-create next-gen copies for newly uploaded images."
    15871718msgstr ""
    15881719
    1589 #: src/index.js:1402
     1720#: src/index.js:1548
    15901721msgid "Also generate/serve AVIF"
    15911722msgstr ""
    15921723
    1593 #: src/index.js:1403
     1724#: src/index.js:1549
    15941725msgid "Smaller than WebP; served first when supported."
    15951726msgstr ""
    15961727
    1597 #: src/index.js:1407
     1728#: src/index.js:1553
    15981729msgid "Compress & resize on upload"
    15991730msgstr ""
    16001731
    1601 #: src/index.js:1408
     1732#: src/index.js:1554
    16021733msgid "Recompress, cap dimensions, and strip metadata from new uploads."
    16031734msgstr ""
    16041735
    1605 #: src/index.js:1410
     1736#: src/index.js:1556
    16061737msgid "Convert opaque PNG to JPEG"
    16071738msgstr ""
    16081739
    1609 #: src/index.js:1411
     1740#: src/index.js:1557
    16101741msgid "Re-save non-transparent PNGs as much smaller JPEGs during bulk optimization."
    16111742msgstr ""
    16121743
    1613 #: src/index.js:1413
     1744#: src/index.js:1559
    16141745msgid "Keep a backup of originals"
    16151746msgstr ""
    16161747
    1617 #: src/index.js:1414
     1748#: src/index.js:1560
    16181749msgid "Copy originals to uploads/optimazz-backup before optimizing."
    16191750msgstr ""
    16201751
    1621 #: src/index.js:1416
     1752#: src/index.js:1562
    16221753msgid "Image quality"
    16231754msgstr ""
    16241755
    1625 #: src/index.js:1418
     1756#: src/index.js:1564
    16261757msgid "Used for compression and WebP/AVIF generation."
    16271758msgstr ""
    16281759
    1629 #: src/index.js:1419
     1760#: src/index.js:1565
    16301761msgid "Max image width (px)"
    16311762msgstr ""
    16321763
    1633 #: src/index.js:1424
     1764#: src/index.js:1570
    16341765msgid "WebP coverage"
    16351766msgstr ""
    16361767
    1637 #: src/index.js:1426
     1768#: src/index.js:1572
    16381769msgid "images converted"
    16391770msgstr ""
    16401771
    1641 #: src/index.js:1427
    1642 #: src/index.js:1441
     1772#: src/index.js:1573
     1773#: src/index.js:1587
    16431774msgid "Loading…"
    16441775msgstr ""
    16451776
    1646 #: src/index.js:1432
     1777#: src/index.js:1578
    16471778msgid "Convert to WebP"
    16481779msgstr ""
    16491780
    1650 #: src/index.js:1438
     1781#: src/index.js:1584
    16511782msgid "Bulk compression"
    16521783msgstr ""
    16531784
    1654 #: src/index.js:1440
     1785#: src/index.js:1586
    16551786msgid "optimized"
    16561787msgstr ""
    16571788
    1658 #: src/index.js:1444
     1789#: src/index.js:1590
    16591790msgid "Compress existing images"
    16601791msgstr ""
    16611792
    1662 #: src/index.js:1452
     1793#: src/index.js:1598
    16631794msgid "Warm the cache automatically so visitors never hit a cold page."
    16641795msgstr ""
    16651796
    1666 #: src/index.js:1453
     1797#: src/index.js:1600
    16671798msgid "Activate preloading"
    16681799msgstr ""
    16691800
    1670 #: src/index.js:1454
     1801#: src/index.js:1601
    16711802msgid "Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop."
    16721803msgstr ""
    16731804
    1674 #: src/index.js:1457
     1805#: src/index.js:1604
     1806#: src/index.js:2117
    16751807msgid "Automatic preload"
    16761808msgstr ""
    16771809
    1678 #: src/index.js:1458
     1810#: src/index.js:1605
    16791811msgid "How often to re-warm the whole site. Choose “Every hour” if your content changes often or you use a short cache lifespan."
    16801812msgstr ""
    16811813
    1682 #: src/index.js:1470
     1814#. translators: %s: preload frequency, e.g. "twice a day".
     1815#: src/index.js:1618
     1816#, js-format
     1817msgid "Automatic preload runs %s for this site (set for the whole network in Network Admin → Optimazz)."
     1818msgstr ""
     1819
     1820#: src/index.js:1621
     1821msgid "Automatic preload is off for the network. You can still preload this site now."
     1822msgstr ""
     1823
     1824#: src/index.js:1626
    16831825msgid "WP-Cron is disabled on this site (DISABLE_WP_CRON). Scheduled preloads only run if a server cron job calls wp-cron.php — ask your host to set one up (e.g. every 5 minutes)."
    16841826msgstr ""
    16851827
    1686 #: src/index.js:1475
     1828#: src/index.js:1631
    16871829msgid "Preload now"
    16881830msgstr ""
    16891831
    1690 #: src/index.js:1483
     1832#: src/index.js:1639
    16911833msgid "Fine-tune which requests are cached."
    16921834msgstr ""
    16931835
    1694 #: src/index.js:1484
     1836#: src/index.js:1640
    16951837msgid "Never cache these URLs (contains)"
    16961838msgstr ""
    16971839
    1698 #: src/index.js:1486
     1840#: src/index.js:1642
    16991841msgid "Never cache when these cookies are set"
    17001842msgstr ""
    17011843
    1702 #: src/index.js:1488
     1844#: src/index.js:1644
    17031845msgid "Never cache for these user agents"
    17041846msgstr ""
    17051847
    1706 #: src/index.js:1490
     1848#: src/index.js:1646
    17071849msgid "Ignore these query arguments"
    17081850msgstr ""
    17091851
    1710 #: src/index.js:1498
     1852#: src/index.js:1654
    17111853msgid "Safe Mode"
    17121854msgstr ""
    17131855
    1714 #: src/index.js:1499
     1856#: src/index.js:1655
    17151857msgid "If a theme or page builder looks broken after optimizing, turn this on. It pauses the riskiest optimizations — combine CSS, async CSS, remove block CSS and JavaScript delay — while keeping caching, minify, defer and lazy-load. Your saved settings are not changed; they simply pause until you turn Safe Mode off."
    17161858msgstr ""
    17171859
    1718 #: src/index.js:1502
     1860#: src/index.js:1658
    17191861msgid "Pause risky optimizations (Safe Mode)"
    17201862msgstr ""
    17211863
    1722 #: src/index.js:1509
     1864#: src/index.js:1665
    17231865msgid "Safe Mode is ON — combine/async CSS, remove-block-CSS and JavaScript delay are paused site-wide. Turn it off to resume full optimization."
    17241866msgstr ""
    17251867
    1726 #: src/index.js:1514
     1868#: src/index.js:1670
    17271869msgid "Undo last change"
    17281870msgstr ""
    17291871
    1730 #: src/index.js:1518
     1872#: src/index.js:1674
    17311873msgid "Restores the settings from just before your last save, reset or Safe Mode toggle."
    17321874msgstr ""
    17331875
    1734 #: src/index.js:1519
     1876#: src/index.js:1675
    17351877msgid "Nothing to undo yet — this becomes available after your next change."
    17361878msgstr ""
    17371879
    1738 #: src/index.js:1523
     1880#: src/index.js:1679
    17391881msgid "Server Requirements"
    17401882msgstr ""
    17411883
    1742 #: src/index.js:1524
     1884#: src/index.js:1680
    17431885msgid "What your host supports. Warnings here explain why a feature (like WebP/AVIF) may be unavailable or slow."
    17441886msgstr ""
    17451887
    1746 #: src/index.js:1525
     1888#: src/index.js:1681
    17471889msgid "Checking your server…"
    17481890msgstr ""
    17491891
    1750 #: src/index.js:1526
     1892#: src/index.js:1682
    17511893msgid "Could not read the server report."
    17521894msgstr ""
    17531895
    1754 #: src/index.js:1542
     1896#: src/index.js:1698
    17551897msgid "Re-check"
    17561898msgstr ""
    17571899
    1758 #: src/index.js:1546
     1900#: src/index.js:1702
    17591901msgid "Import / Export Settings"
    17601902msgstr ""
    17611903
    1762 #: src/index.js:1547
     1904#: src/index.js:1703
    17631905msgid "Back up your configuration or copy it to another site."
    17641906msgstr ""
    17651907
    1766 #: src/index.js:1550
     1908#: src/index.js:1706
    17671909msgid "Export settings"
    17681910msgstr ""
    17691911
    1770 #: src/index.js:1553
     1912#: src/index.js:1709
    17711913msgid "Import settings"
    17721914msgstr ""
    17731915
    1774 #: src/index.js:1558
     1916#: src/index.js:1714
    17751917msgid "Reset"
    17761918msgstr ""
    17771919
    1778 #: src/index.js:1559
     1920#: src/index.js:1715
    17791921msgid "Restore every module to its default configuration. This cannot be undone."
    17801922msgstr ""
    17811923
    1782 #: src/index.js:1562
     1924#: src/index.js:1718
    17831925msgid "Reset all settings to defaults"
    17841926msgstr ""
    17851927
    1786 #: src/index.js:1571
     1928#: src/index.js:1727
    17871929msgid "Swap, preconnect, and preload fonts to remove render-blocking requests."
    17881930msgstr ""
    17891931
    1790 #: src/index.js:1574
     1932#: src/index.js:1730
    17911933msgid "Font loading (font-display)"
    17921934msgstr ""
    17931935
    1794 #: src/index.js:1575
     1936#: src/index.js:1731
    17951937msgid "Optional = no layout shift (best for CLS): the web font is skipped on the first uncached load and used once cached. Swap = text stays visible but the web font swaps in and can shift layout (higher CLS). Block = brief invisible text, then the web font."
    17961938msgstr ""
    17971939
    1798 #: src/index.js:1578
     1940#: src/index.js:1734
    17991941msgid "Optional — no layout shift (recommended for CLS)"
    18001942msgstr ""
    18011943
    1802 #: src/index.js:1579
     1944#: src/index.js:1735
    18031945msgid "Swap — always show text (may shift layout)"
    18041946msgstr ""
    18051947
    1806 #: src/index.js:1580
     1948#: src/index.js:1736
    18071949msgid "Block — brief hidden text, then web font"
    18081950msgstr ""
    18091951
    1810 #: src/index.js:1584
     1952#: src/index.js:1740
    18111953msgid "Combine Google Fonts into one request"
    18121954msgstr ""
    18131955
    1814 #: src/index.js:1585
     1956#: src/index.js:1741
    18151957msgid "Page builders often load each Google Font family separately. This merges them into a single request."
    18161958msgstr ""
    18171959
    1818 #: src/index.js:1589
     1960#: src/index.js:1745
    18191961msgid "Load only the font weights your pages use"
    18201962msgstr ""
    18211963
    1822 #: src/index.js:1590
     1964#: src/index.js:1746
    18231965msgid "Drops unused weights (e.g. 100, 200, 900) and italics your pages never use — often cutting font CSS by more than half. Weights 400 and 700 are always kept. Turn off if some text looks thinner or bolder than intended."
    18241966msgstr ""
    18251967
    1826 #: src/index.js:1594
     1968#: src/index.js:1751
     1969msgid "Load Google Fonts without blocking the page"
     1970msgstr ""
     1971
     1972#: src/index.js:1752
     1973msgid "The page appears right away in a fallback font and switches to your Google Font as soon as it arrives, instead of waiting for Google first. A big win for the first paint on mobile. Applies when font display is Swap or Fallback; with Optional the stylesheet keeps loading first so text never shifts."
     1974msgstr ""
     1975
     1976#: src/index.js:1756
    18271977msgid "Preload these font files (URLs)"
    18281978msgstr ""
    18291979
    18301980#. translators: %d: number of items found in the database.
    1831 #: src/index.js:1605
     1981#: src/index.js:1767
    18321982#, js-format
    18331983msgid "%d item in your database."
    … …  
    18361986msgstr[1] ""
    18371987
    1838 #: src/index.js:1610
     1988#: src/index.js:1772
    18391989msgid "Database Optimization"
    18401990msgstr ""
    18411991
    1842 #: src/index.js:1611
     1992#: src/index.js:1773
    18431993msgid "Reduce bloat and reclaim space. Once a cleanup runs it cannot be undone, so back up your database first."
    18441994msgstr ""
    18451995
    1846 #: src/index.js:1627
     1996#: src/index.js:1789
    18471997msgid "Set it and forget it"
    18481998msgstr ""
    18491999
    1850 #: src/index.js:1628
     2000#: src/index.js:1790
    18512001msgid "Let Optimazz repeat the cleanups you picked above on a schedule, so your database stays lean without you lifting a finger."
    18522002msgstr ""
    18532003
    1854 #: src/index.js:1629
     2004#: src/index.js:1791
    18552005msgid "Schedule automatic cleanup"
    18562006msgstr ""
    18572007
    1858 #: src/index.js:1630
     2008#: src/index.js:1792
    18592009msgid "Uses WP-Cron to repeat the cleanups you enabled above."
    18602010msgstr ""
    18612011
    1862 #: src/index.js:1633
     2012#: src/index.js:1795
    18632013msgid "Cleanup frequency"
    18642014msgstr ""
    18652015
    1866 #: src/index.js:1637
     2016#: src/index.js:1799
    18672017msgid "Daily"
    18682018msgstr ""
    18692019
    1870 #: src/index.js:1638
     2020#: src/index.js:1800
    18712021msgid "Weekly"
    18722022msgstr ""
    18732023
    1874 #: src/index.js:1639
     2024#: src/index.js:1801
    18752025msgid "Monthly"
    18762026msgstr ""
    18772027
    1878 #: src/index.js:1646
     2028#: src/index.js:1808
    18792029msgid "Back up your database before you run a cleanup — a database optimization cannot be undone."
    18802030msgstr ""
    18812031
    1882 #: src/index.js:1651
     2032#: src/index.js:1813
    18832033msgid "Optimize now"
    18842034msgstr ""
    18852035
    1886 #: src/index.js:1654
     2036#: src/index.js:1816
    18872037msgid "Refresh counts"
    18882038msgstr ""
    18892039
    1890 #: src/index.js:1661
     2040#: src/index.js:1823
    18912041msgid "Heartbeat Control"
    18922042msgstr ""
    18932043
    1894 #: src/index.js:1662
     2044#: src/index.js:1824
    18952045msgid "Reduce or disable the WordPress Heartbeat API to lower server load."
    18962046msgstr ""
    18972047
    1898 #: src/index.js:1663
     2048#: src/index.js:1825
    18992049msgid "Enable Heartbeat control"
    19002050msgstr ""
    19012051
    1902 #: src/index.js:1667
     2052#: src/index.js:1829
    19032053msgid "Behavior in admin dashboard"
    19042054msgstr ""
    19052055
    1906 #: src/index.js:1669
     2056#: src/index.js:1831
    19072057msgid "Behavior in post editor"
    19082058msgstr ""
    19092059
    1910 #: src/index.js:1671
     2060#: src/index.js:1833
    19112061msgid "Behavior on front-end"
    19122062msgstr ""
    19132063
    1914 #: src/index.js:1680
     2064#: src/index.js:1842
    19152065msgid "Strip unnecessary WordPress front-end weight."
    19162066msgstr ""
    19172067
    1918 #: src/index.js:1682
     2068#: src/index.js:1844
    19192069msgid "Disable emojis"
    19202070msgstr ""
    19212071
    1922 #: src/index.js:1683
     2072#: src/index.js:1845
    19232073msgid "Remove the emoji detection script and styles."
    19242074msgstr ""
    19252075
    1926 #: src/index.js:1685
     2076#: src/index.js:1847
    19272077msgid "Disable embeds (oEmbed)"
    19282078msgstr ""
    19292079
    1930 #: src/index.js:1687
     2080#: src/index.js:1849
    19312081msgid "Disable dashicons on front-end"
    19322082msgstr ""
    19332083
    1934 #: src/index.js:1688
     2084#: src/index.js:1850
    19352085msgid "Only for anonymous visitors."
    19362086msgstr ""
    19372087
    1938 #: src/index.js:1690
     2088#: src/index.js:1852
    19392089msgid "Remove query strings from assets"
    19402090msgstr ""
    19412091
    1942 #: src/index.js:1691
     2092#: src/index.js:1853
    19432093msgid "Strip ?ver= for better proxy caching."
    19442094msgstr ""
    19452095
    1946 #: src/index.js:1693
     2096#: src/index.js:1855
    19472097msgid "Disable XML-RPC"
    19482098msgstr ""
    19492099
    1950 #: src/index.js:1695
     2100#: src/index.js:1857
    19512101msgid "Disable self-pingbacks"
    19522102msgstr ""
    19532103
    1954 #: src/index.js:1697
     2104#: src/index.js:1859
    19552105msgid "Remove head meta (generator, RSD, shortlink)"
    19562106msgstr ""
    19572107
    1958 #: src/index.js:1699
     2108#: src/index.js:1861
    19592109msgid "DNS-prefetch domains"
    19602110msgstr ""
    19612111
    1962 #: src/index.js:1701
     2112#: src/index.js:1863
    19632113msgid "Preconnect origins"
    19642114msgstr ""
    19652115
    1966 #: src/index.js:1707
     2116#: src/index.js:1869
    19672117msgid "Content Delivery Network"
    19682118msgstr ""
    19692119
    1970 #: src/index.js:1708
     2120#: src/index.js:1870
    19712121msgid "Rewrite static asset URLs to your CDN hostname."
    19722122msgstr ""
    19732123
    1974 #: src/index.js:1710
     2124#: src/index.js:1872
    19752125msgid "CDN URL (CNAME)"
    19762126msgstr ""
    19772127
    1978 #: src/index.js:1713
     2128#: src/index.js:1875
    19792129msgid "CSS, JS, image, and font URLs will be served from here."
    19802130msgstr ""
    19812131
    1982 #: src/index.js:1714
     2132#: src/index.js:1876
    19832133msgid "Exclude paths containing"
    19842134msgstr ""
    19852135
    1986 #: src/index.js:1781
    1987 msgid "You have unsaved changes"
    1988 msgstr ""
    1989 
    1990 #: src/index.js:1781
    1991 msgid "All changes saved"
    1992 msgstr ""
    1993 
    1994 #: src/index.js:1784
    1995 msgid "Save changes"
    1996 msgstr ""
     2136#: src/index.js:1952
     2137msgid "Preloading cache…"
     2138msgstr ""
     2139
     2140#: src/index.js:2012
     2141#: src/index.js:2025
     2142msgid "Something went wrong."
     2143msgstr ""
     2144
     2145#: src/index.js:2059
     2146msgid "Network settings saved."
     2147msgstr ""
     2148
     2149#: src/index.js:2075
     2150msgid "Caching for every site"
     2151msgstr ""
     2152
     2153#: src/index.js:2076
     2154msgid "Sites"
     2155msgstr ""
     2156
     2157#: src/index.js:2076
     2158msgid "Cache per site"
     2159msgstr ""
     2160
     2161#: src/index.js:2086
     2162msgid "Optimazz is not network-activated"
     2163msgstr ""
     2164
     2165#: src/index.js:2087
     2166msgid "Only the sites where Optimazz is activated are cached. Network-activate it on Network Admin → Plugins to cache every site."
     2167msgstr ""
     2168
     2169#: src/index.js:2092
     2170msgid "Serve ready-made HTML for every site instead of running PHP on each request. Each site keeps its own cache and its own exclusions."
     2171msgstr ""
     2172
     2173#: src/index.js:2100
     2174msgid "Add Expires / Cache-Control rules to the shared .htaccess so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only."
     2175msgstr ""
     2176
     2177#: src/index.js:2103
     2178msgid "How long a cached page is kept. Pages are always purged as soon as their content changes."
     2179msgstr ""
     2180
     2181#: src/index.js:2113
     2182msgid "Preload every site automatically"
     2183msgstr ""
     2184
     2185#: src/index.js:2114
     2186msgid "Each site warms its own pages on a schedule and right after its cache is cleared."
     2187msgstr ""
     2188
     2189#. translators: %s: name of another caching plugin.
     2190#: src/index.js:2135
     2191#, js-format
     2192msgid "%s controls page caching (advanced-cache.php)"
     2193msgstr ""
     2194
     2195#: src/index.js:2138
     2196msgid "Optimazz cannot cache while another plugin owns the cache file. Deactivate that plugin, or let Optimazz take over."
     2197msgstr ""
     2198
     2199#: src/index.js:2139
     2200msgid "Optimazz now handles page caching."
     2201msgstr ""
     2202
     2203#: src/index.js:2146
     2204msgid "Cache engine installed (advanced-cache.php)"
     2205msgstr ""
     2206
     2207#: src/index.js:2147
     2208msgid "WP_CACHE is on in wp-config.php"
     2209msgstr ""
     2210
     2211#: src/index.js:2148
     2212msgid "wp-config.php is not writable — add define( 'WP_CACHE', true ); yourself"
     2213msgstr ""
     2214
     2215#: src/index.js:2149
     2216msgid "The wp-content folder is not writable"
     2217msgstr ""
     2218
     2219#: src/index.js:2167
     2220msgid "All sites"
     2221msgstr ""
     2222
     2223#. translators: 1: sites with Optimazz, 2: all sites.
     2224#: src/index.js:2170
     2225#, js-format
     2226msgid "Optimazz runs on %1$d of %2$d sites. Each site has its own cache."
     2227msgstr ""
     2228
     2229#: src/index.js:2172
     2230msgid "Page caching is off for the network — turn it on under Page Cache to purge or preload sites."
     2231msgstr ""
     2232
     2233#: src/index.js:2175
     2234msgid "Every site’s cache was purged."
     2235msgstr ""
     2236
     2237#: src/index.js:2176
     2238msgid "Purge all sites"
     2239msgstr ""
     2240
     2241#: src/index.js:2194
     2242msgid "Site cache purged."
     2243msgstr ""
     2244
     2245#: src/index.js:2215
     2246msgid "Optimazz is not active on this site, so its pages are not cached."
     2247msgstr ""
     2248
     2249#: src/index.js:2225
     2250msgid "Network"
     2251msgstr ""
  • optimazz/trunk/optimazz.php

    r3723128 r3743505  
    44 * Plugin URI:        https://wordpress.org/plugins/optimazz/
    55 * Description:        All-in-one WordPress performance optimization — page caching, JS delay, and CSS optimization.
    6  * Version:           1.0.24
     6 * Version:           1.1.0
    77 * Requires at least: 6.0
    88 * Requires PHP:      7.4
    … …  
    2626}
    2727
    28 define( 'OPTIMAZZ_VERSION', '1.0.24' );
     28define( 'OPTIMAZZ_VERSION', '1.1.0' );
    2929define( 'OPTIMAZZ_FILE', __FILE__ );
    3030define( 'OPTIMAZZ_DIR', plugin_dir_path( __FILE__ ) );
  • optimazz/trunk/optimazz.pot

    r3723128 r3743505  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Optimazz – Website Optimization and Speedup 1.0.24\n"
     5"Project-Id-Version: Optimazz – Website Optimization and Speedup 1.1.0\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/optimazz\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-01T12:01:49+00:00\n"
     12"POT-Creation-Date: 2026-10-11T19:05:58+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    4040msgstr ""
    4141
    42 #: includes/Admin/Admin.php:149
    43 #: includes/Admin/Admin.php:150
     42#: includes/Admin/Admin.php:155
     43#: includes/Admin/Admin.php:156
     44#: includes/Admin/Admin.php:290
     45#: includes/Admin/Admin.php:291
    4446msgid "Optimazz"
    4547msgstr ""
    4648
    47 #: includes/Admin/Admin.php:166
     49#: includes/Admin/Admin.php:172
    4850#: src/index.js:36
    4951msgid "Dashboard"
    5052msgstr ""
    5153
    52 #: includes/Admin/Admin.php:167
     54#: includes/Admin/Admin.php:173
    5355#: includes/Modules/Cache/CacheModule.php:24
    5456#: src/index.js:37
    55 #: src/index.js:1171
     57#: src/index.js:1317
     58#: src/index.js:2075
     59#: src/index.js:2091
    5660msgid "Page Cache"
    5761msgstr ""
    5862
    59 #: includes/Admin/Admin.php:168
     63#: includes/Admin/Admin.php:174
    6064#: src/index.js:38
    61 #: src/index.js:1150
     65#: src/index.js:1296
    6266msgid "File Optimization"
    6367msgstr ""
    6468
    65 #: includes/Admin/Admin.php:169
     69#: includes/Admin/Admin.php:175
    6670#: src/index.js:39
    6771msgid "Media"
    6872msgstr ""
    6973
    70 #: includes/Admin/Admin.php:170
     74#: includes/Admin/Admin.php:176
    7175#: includes/Modules/Database/DatabaseModule.php:26
    7276#: src/index.js:43
    … …  
    7478msgstr ""
    7579
    76 #: includes/Admin/Admin.php:171
    77 #: includes/Admin/Admin.php:229
    78 #: includes/Admin/Admin.php:233
     80#: includes/Admin/Admin.php:177
     81#: includes/Admin/Admin.php:235
     82#: includes/Admin/Admin.php:239
    7983#: src/index.js:47
    8084msgid "Tools"
    8185msgstr ""
    8286
    83 #: includes/Admin/Admin.php:193
     87#: includes/Admin/Admin.php:199
    8488#: includes/Admin/AdminBar.php:52
     89#: src/index.js:2198
    8590msgid "Settings"
    8691msgstr ""
    8792
    88 #: includes/Admin/Admin.php:224
     93#: includes/Admin/Admin.php:230
    8994msgid "Optimazz Safe Mode is on."
    9095msgstr ""
    9196
    92 #: includes/Admin/Admin.php:225
     97#: includes/Admin/Admin.php:231
    9398msgid "Some pages were loading without styling — their optimized CSS could not be found, usually because a server or CDN cache was serving old pages. Optimazz paused advanced CSS/JS optimization to keep your site working."
    9499msgstr ""
    95100
    96 #: includes/Admin/Admin.php:227
     101#: includes/Admin/Admin.php:233
    97102msgid "It broke again shortly after full optimization was restored, so Safe Mode will stay on until you switch it off in"
    98103msgstr ""
    99104
    100 #: includes/Admin/Admin.php:228
     105#: includes/Admin/Admin.php:234
    101106msgid "It re-checks automatically and restores full optimization as soon as pages load correctly — clearing your server/CDN cache speeds that up. You can also manage it in"
    102107msgstr ""
    103108
    104 #: includes/Admin/Admin.php:232
     109#: includes/Admin/Admin.php:238
    105110msgid "Your pages are loading correctly again — you can switch Safe Mode off in"
    106111msgstr ""
    107112
    108 #: includes/Admin/Admin.php:236
     113#: includes/Admin/Admin.php:242
    109114msgid "Pages are loading correctly again, so Safe Mode was switched off automatically and full optimization is back on."
    110115msgstr ""
    111116
    112 #: includes/Admin/Admin.php:250
     117#: includes/Admin/Admin.php:256
    113118msgid "wp-config.php is not writable, so page caching could not be enabled. Add"
    114119msgstr ""
    115120
    116 #: includes/Admin/Admin.php:252
     121#: includes/Admin/Admin.php:258
    117122msgid "manually, or make wp-config.php writable."
    118123msgstr ""
    119124
    120 #: includes/Admin/Admin.php:259
     125#: includes/Admin/Admin.php:265
    121126msgid "Optimazz cache purged."
    122127msgstr ""
    123128
    124 #: includes/Admin/Admin.php:260
     129#: includes/Admin/Admin.php:266
    125130msgid "Optimazz cache preload started in the background."
    126131msgstr ""
    127132
    128 #: includes/Admin/Admin.php:261
     133#: includes/Admin/Admin.php:267
    129134msgid "Optimazz cache cleared; preload started in the background."
    130135msgstr ""
    131136
    132 #: includes/Admin/AdminBar.php:53
     137#: includes/Admin/AdminBar.php:57
    133138msgid "Clear & Preload Cache"
    134139msgstr ""
    135140
    136 #: includes/Admin/AdminBar.php:54
    137 #: src/index.js:1451
     141#: includes/Admin/AdminBar.php:58
     142#: src/index.js:1597
    138143msgid "Preload Cache"
    139144msgstr ""
    140145
    141 #: includes/Admin/AdminBar.php:55
     146#: includes/Admin/AdminBar.php:59
    142147msgid "Purge Cache"
    143148msgstr ""
    144149
    145 #: includes/Admin/AdminBar.php:73
     150#: includes/Admin/AdminBar.php:77
    146151msgid "Documentation"
    147152msgstr ""
    148153
    149 #: includes/Admin/AdminBar.php:98
    150 #: includes/Admin/CacheConflictNotice.php:124
    151 #: includes/Admin/CacheConflictNotice.php:154
     154#: includes/Admin/AdminBar.php:102
     155#: includes/Admin/CacheConflictNotice.php:126
     156#: includes/Admin/CacheConflictNotice.php:156
    152157msgid "Not allowed."
    153158msgstr ""
    … …  
    162167
    163168#: includes/Admin/CacheConflictNotice.php:88
    164 #: includes/Modules/Cache/Preloader.php:714
     169#: includes/Modules/Cache/Preloader.php:740
     170#: src/index.js:2136
    165171msgid "Another plugin"
    166172msgstr ""
    … …  
    180186#: includes/Admin/CacheConflictNotice.php:108
    181187#: includes/Admin/CacheConflictNotice.php:110
     188#: src/index.js:2140
    182189msgid "Use Optimazz for caching"
    183190msgstr ""
    … …  
    187194msgstr ""
    188195
    189 #: includes/Admin/RestController.php:467
     196#: includes/Admin/NetworkRestController.php:115
     197#: includes/Admin/NetworkRestController.php:134
     198msgid "Page caching is off for this site."
     199msgstr ""
     200
     201#: includes/Admin/NetworkRestController.php:153
     202msgid "Optimazz is not active on any site yet. Network-activate it (or activate it on a site) first."
     203msgstr ""
     204
     205#: includes/Admin/RestController.php:385
     206#: includes/Admin/RestController.php:588
     207#: includes/Admin/RestController.php:614
     208msgid "Page caching is not available on multisite networks."
     209msgstr ""
     210
     211#: includes/Admin/RestController.php:479
    190212msgid "PHP version"
    191213msgstr ""
    192214
    193 #: includes/Admin/RestController.php:470
     215#: includes/Admin/RestController.php:482
    194216msgid "Optimazz requires PHP 7.4 or newer."
    195217msgstr ""
    196218
    197 #: includes/Admin/RestController.php:473
     219#: includes/Admin/RestController.php:485
    198220msgid "WordPress version"
    199221msgstr ""
    200222
    201 #: includes/Admin/RestController.php:479
     223#: includes/Admin/RestController.php:491
    202224msgid "GD (image library)"
    203225msgstr ""
    204226
    205 #: includes/Admin/RestController.php:480
    206 #: includes/Admin/RestController.php:489
    207 #: includes/Admin/RestController.php:525
     227#: includes/Admin/RestController.php:492
     228#: includes/Admin/RestController.php:501
     229#: includes/Admin/RestController.php:537
    208230msgid "Enabled"
    209231msgstr ""
    210232
    211 #: includes/Admin/RestController.php:480
    212 #: includes/Admin/RestController.php:489
     233#: includes/Admin/RestController.php:492
     234#: includes/Admin/RestController.php:501
    213235msgid "Not installed (optional)"
    214236msgstr ""
    215237
    216 #: includes/Admin/RestController.php:484
     238#: includes/Admin/RestController.php:496
    217239msgid "GD is enabled but without WebP support — ask your host to enable WebP in GD."
    218240msgstr ""
    219241
    220 #: includes/Admin/RestController.php:485
     242#: includes/Admin/RestController.php:497
    221243msgid "The PHP GD extension is not enabled. GD is the most common engine for image compression and WebP. Ask your host to enable the \"php-gd\" extension."
    222244msgstr ""
    223245
    224 #: includes/Admin/RestController.php:488
     246#: includes/Admin/RestController.php:500
    225247msgid "ImageMagick (Imagick)"
    226248msgstr ""
    227249
    228 #: includes/Admin/RestController.php:489
     250#: includes/Admin/RestController.php:501
    229251msgid "Not enabled"
    230252msgstr ""
    231253
    232 #: includes/Admin/RestController.php:496
     254#: includes/Admin/RestController.php:508
    233255msgid "Optional — GD is already handling images. Imagick can add AVIF and higher-quality output if your host enables the \"php-imagick\" extension."
    234256msgstr ""
    235257
    236 #: includes/Admin/RestController.php:497
     258#: includes/Admin/RestController.php:509
    237259msgid "The PHP Imagick extension is not enabled either. With neither GD nor Imagick, image compression and WebP/AVIF cannot run. Ask your host to enable \"php-gd\" or \"php-imagick\"."
    238260msgstr ""
    239261
    240 #: includes/Admin/RestController.php:500
     262#: includes/Admin/RestController.php:512
    241263msgid "WebP support"
    242264msgstr ""
    243265
    244 #: includes/Admin/RestController.php:501
    245 #: includes/Admin/RestController.php:507
     266#: includes/Admin/RestController.php:513
     267#: includes/Admin/RestController.php:519
    246268msgid "Available"
    247269msgstr ""
    248270
    249 #: includes/Admin/RestController.php:501
    250 #: includes/Admin/RestController.php:507
     271#: includes/Admin/RestController.php:513
     272#: includes/Admin/RestController.php:519
    251273msgid "Not available"
    252274msgstr ""
    253275
    254 #: includes/Admin/RestController.php:503
     276#: includes/Admin/RestController.php:515
    255277msgid "This server cannot generate WebP images. Update GD/Imagick or ask your host to enable WebP."
    256278msgstr ""
    257279
    258 #: includes/Admin/RestController.php:506
     280#: includes/Admin/RestController.php:518
    259281msgid "AVIF support"
    260282msgstr ""
    261283
    262 #: includes/Admin/RestController.php:509
     284#: includes/Admin/RestController.php:521
    263285msgid "This server cannot generate AVIF images (needs PHP 8.1+ GD with AVIF, or ImageMagick with the AVIF delegate). WebP still works."
    264286msgstr ""
    265287
    266 #: includes/Admin/RestController.php:512
     288#: includes/Admin/RestController.php:524
    267289msgid "PHP memory limit"
    268290msgstr ""
    269291
    270 #: includes/Admin/RestController.php:515
     292#: includes/Admin/RestController.php:527
    271293msgid "Image optimization works best with 256 MB or more. On lower limits Optimazz processes fewer images per request to stay safe."
    272294msgstr ""
    273295
    274 #: includes/Admin/RestController.php:518
     296#: includes/Admin/RestController.php:530
    275297msgid "Max execution time"
    276298msgstr ""
    277299
    278 #: includes/Admin/RestController.php:519
     300#: includes/Admin/RestController.php:531
    279301msgid "Unlimited"
    280302msgstr ""
    281303
    282 #: includes/Admin/RestController.php:521
     304#: includes/Admin/RestController.php:533
    283305msgid "Under 30 seconds can interrupt bulk AVIF conversion of large images."
    284306msgstr ""
    285307
    286 #: includes/Admin/RestController.php:524
     308#: includes/Admin/RestController.php:536
    287309msgid "WP-Cron"
    288310msgstr ""
    289311
    290 #: includes/Admin/RestController.php:525
     312#: includes/Admin/RestController.php:537
    291313msgid "Disabled"
    292314msgstr ""
    293315
    294 #: includes/Admin/RestController.php:527
     316#: includes/Admin/RestController.php:539
    295317msgid "WP-Cron is disabled, so scheduled cache preloading relies on a real system cron job being configured."
    296318msgstr ""
    297319
    298 #: includes/Admin/RestController.php:541
     320#: includes/Admin/RestController.php:553
    299321msgid "GD and Imagick are"
    300322msgstr ""
    301323
    302 #: includes/Admin/RestController.php:542
     324#: includes/Admin/RestController.php:554
    303325msgid "GD is"
    304326msgstr ""
    305327
    306 #: includes/Admin/RestController.php:542
     328#: includes/Admin/RestController.php:554
    307329msgid "Imagick is"
    308330msgstr ""
    309331
    310 #: includes/Admin/RestController.php:544
     332#: includes/Admin/RestController.php:556
    311333msgid "PHP that runs this site"
    312334msgstr ""
    313335
    314336#. translators: 1: which engine is missing, 2: php.ini path, 3: PHP version
    315 #: includes/Admin/RestController.php:550
     337#: includes/Admin/RestController.php:562
    316338#, php-format
    317339msgid "This is the exact PHP handling your site — it loads its settings from %2$s. If you already enabled an image library in your hosting panel but %1$s still shown as not enabled, you almost certainly enabled it for a different PHP version or handler. It must be enabled for THIS PHP (%3$s). After enabling, restart PHP (or PHP-FPM) and click Re-check."
    … …  
    319341
    320342#. translators: 1: which engine is missing, 2: PHP version
    321 #: includes/Admin/RestController.php:557
     343#: includes/Admin/RestController.php:569
    322344#, php-format
    323345msgid "This is the exact PHP handling your site. If you already enabled an image library in your hosting panel but %1$s still shown as not enabled, you enabled it for a different PHP version. It must be enabled for THIS PHP (%2$s). After enabling, restart PHP and click Re-check."
    324346msgstr ""
    325347
    326 #: includes/Admin/RestController.php:707
     348#: includes/Admin/RestController.php:725
    327349msgid "Image processing is unavailable: this server has no GD or ImageMagick extension. Ask your host to enable \"php-gd\" (or \"php-imagick\"), then try again. See Tools → Server Requirements."
    328350msgstr ""
    … …  
    336358msgstr ""
    337359
    338 #: includes/Core/Recommendations.php:61
    339 #: src/index.js:971
     360#: includes/Core/Recommendations.php:63
     361#: src/index.js:1117
    340362msgid "Enable Page Cache"
    341363msgstr ""
    342364
    343 #: includes/Core/Recommendations.php:62
     365#: includes/Core/Recommendations.php:64
    344366msgid "Serve static HTML instead of running PHP on every request — the single biggest speed win."
    345367msgstr ""
    346368
    347 #: includes/Core/Recommendations.php:67
     369#: includes/Core/Recommendations.php:69
    348370msgid "Turn on cache preloading"
    349371msgstr ""
    350372
    351 #: includes/Core/Recommendations.php:68
     373#: includes/Core/Recommendations.php:70
    352374msgid "Warm the cache automatically so visitors never hit a cold, uncached page."
    353375msgstr ""
    354376
    355 #: includes/Core/Recommendations.php:76
     377#: includes/Core/Recommendations.php:78
    356378msgid "Enable JavaScript optimization"
    357379msgstr ""
    358380
    359 #: includes/Core/Recommendations.php:77
     381#: includes/Core/Recommendations.php:79
    360382msgid "Defer and delay scripts to cut Total Blocking Time — the heaviest-weighted mobile metric."
    361383msgstr ""
    362384
    363 #: includes/Core/Recommendations.php:84
     385#: includes/Core/Recommendations.php:86
    364386msgid "Delay JavaScript until interaction"
    365387msgstr ""
    366388
    367 #: includes/Core/Recommendations.php:85
     389#: includes/Core/Recommendations.php:87
    368390msgid "Delay non-critical scripts (analytics, chat, pixels) until the first interaction to reduce blocking time."
    369391msgstr ""
    370392
    371 #: includes/Core/Recommendations.php:93
     393#: includes/Core/Recommendations.php:95
    372394msgid "Enable CSS optimization"
    373395msgstr ""
    374396
    375 #: includes/Core/Recommendations.php:94
     397#: includes/Core/Recommendations.php:96
    376398msgid "Minify CSS to remove render-blocking bytes and speed up First Contentful Paint."
    377399msgstr ""
    378400
    379 #: includes/Core/Recommendations.php:102
     401#: includes/Core/Recommendations.php:104
    380402msgid "Enable Image optimization"
    381403msgstr ""
    382404
    383 #: includes/Core/Recommendations.php:103
     405#: includes/Core/Recommendations.php:105
    384406msgid "Lazy-load images, reserve their space (to prevent layout shift) and serve next-gen formats."
    385407msgstr ""
    386408
    387 #: includes/Core/Recommendations.php:109
     409#: includes/Core/Recommendations.php:111
    388410msgid "Reserve image space (fix layout shift)"
    389411msgstr ""
    390412
    391 #: includes/Core/Recommendations.php:110
     413#: includes/Core/Recommendations.php:112
    392414msgid "Add missing image dimensions and aspect-ratio so images do not push content down as they load (lowers CLS)."
    393415msgstr ""
    394416
    395 #: includes/Core/Recommendations.php:116
    396 #: src/index.js:948
    397 #: src/index.js:1369
     417#: includes/Core/Recommendations.php:118
     418#: src/index.js:1092
     419#: src/index.js:1515
    398420msgid "Lazy-load images"
    399421msgstr ""
    400422
    401 #: includes/Core/Recommendations.php:117
     423#: includes/Core/Recommendations.php:119
    402424msgid "Load off-screen images only when scrolled into view to speed up the initial load."
    403425msgstr ""
    404426
    405 #: includes/Core/Recommendations.php:124
     427#: includes/Core/Recommendations.php:126
    406428msgid "Serve WebP images"
    407429msgstr ""
    408430
    409 #: includes/Core/Recommendations.php:125
     431#: includes/Core/Recommendations.php:127
    410432msgid "Generate and serve WebP copies — typically 25–35% smaller than JPEG/PNG at the same quality."
    411433msgstr ""
    412434
    413 #: includes/Core/Recommendations.php:136
     435#: includes/Core/Recommendations.php:138
    414436msgid "Set font loading to \"Optional\" (reduce CLS)"
    415437msgstr ""
    416438
    417 #: includes/Core/Recommendations.php:137
     439#: includes/Core/Recommendations.php:139
    418440msgid "The \"swap\" mode lets web fonts reflow the page as they load, raising Cumulative Layout Shift. \"Optional\" removes that shift."
    419441msgstr ""
    420442
    421 #: includes/Core/Recommendations.php:143
     443#: includes/Core/Recommendations.php:145
    422444msgid "Enable Font optimization"
    423445msgstr ""
    424446
    425 #: includes/Core/Recommendations.php:144
     447#: includes/Core/Recommendations.php:146
    426448msgid "Preconnect to font hosts and control font-display to reduce render-blocking and layout shift."
    427449msgstr ""
    428450
    429 #: includes/Core/Recommendations.php:152
     451#: includes/Core/Recommendations.php:154
    430452msgid "Enable Database cleanup"
    431453msgstr ""
    432454
    433 #: includes/Core/Recommendations.php:153
     455#: includes/Core/Recommendations.php:155
    434456msgid "Remove revisions, auto-drafts, spam and expired transients, and optimize tables to keep the database lean."
    435457msgstr ""
    436458
    437 #: includes/Core/Recommendations.php:161
     459#: includes/Core/Recommendations.php:163
    438460msgid "Enable an image library on your server"
    439461msgstr ""
    440462
    441 #: includes/Core/Recommendations.php:162
     463#: includes/Core/Recommendations.php:164
    442464msgid "Your server has neither GD nor ImageMagick, so image compression and WebP/AVIF cannot run. Ask your host to enable the \"php-gd\" (or \"php-imagick\") extension. See Tools → Server Requirements."
    443465msgstr ""
    444466
     467#: includes/Modules/Cache/Preloader.php:116
     468msgid "the home page"
     469msgstr ""
     470
    445471#. translators: %d: number of category / tag archive pages.
    446 #: includes/Modules/Cache/Preloader.php:124
     472#: includes/Modules/Cache/Preloader.php:127
    447473#, php-format
    448474msgid "%d category or tag page"
    … …  
    451477msgstr[1] ""
    452478
    453 #: includes/Modules/Cache/Preloader.php:681
     479#: includes/Modules/Cache/Preloader.php:707
    454480msgid "your host's server cache"
    455481msgstr ""
    456482
    457 #: includes/Modules/Cache/Preloader.php:685
     483#: includes/Modules/Cache/Preloader.php:711
    458484msgid "a server or CDN cache"
    459485msgstr ""
    460486
    461 #: includes/Modules/Cache/Preloader.php:704
     487#: includes/Modules/Cache/Preloader.php:730
    462488msgid "The site could not load its own pages (the loopback request failed or timed out). Some hosts block a server from requesting its own website — ask your host to allow it."
    463489msgstr ""
    464490
    465491#. translators: %s: name of another caching plugin.
    466 #: includes/Modules/Cache/Preloader.php:713
     492#: includes/Modules/Cache/Preloader.php:739
    467493#, php-format
    468494msgid "%s controls page caching (advanced-cache.php), so Optimazz cannot save pages. Deactivate it, or use \"Take over caching\" in Cache health."
    469495msgstr ""
    470496
    471 #: includes/Modules/Cache/Preloader.php:718
     497#: includes/Modules/Cache/Preloader.php:744
    472498msgid "The cache file advanced-cache.php is missing. Turn Page Cache off and on again to reinstall it, and make sure the wp-content folder is writable."
    473499msgstr ""
    474500
    475 #: includes/Modules/Cache/Preloader.php:721
     501#: includes/Modules/Cache/Preloader.php:747
    476502msgid "WP_CACHE is not enabled in wp-config.php, so WordPress never loads the cache. Make wp-config.php writable and save the Cache settings, or add define( 'WP_CACHE', true ); yourself."
    477503msgstr ""
    478504
    479505#. translators: %s: name of a server or CDN cache, e.g. "Cloudflare".
    480 #: includes/Modules/Cache/Preloader.php:726
     506#: includes/Modules/Cache/Preloader.php:752
    481507#, php-format
    482508msgid "%s answered from its own cache before WordPress ran, so Optimazz never saw these requests. Purge that cache, or turn it off for HTML pages if you want Optimazz to cache them."
    483509msgstr ""
    484510
    485 #: includes/Modules/Cache/Preloader.php:730
     511#: includes/Modules/Cache/Preloader.php:756
    486512msgid "The Optimazz cache engine did not run for these requests. Usually a server-level cache or proxy (for example LiteSpeed, Varnish or a host cache) answers before WordPress loads. Check Cache health, or ask your host."
    487513msgstr ""
    488514
    489515#. translators: %s: HTTP status code, e.g. 404.
    490 #: includes/Modules/Cache/Preloader.php:737
     516#: includes/Modules/Cache/Preloader.php:763
    491517#, php-format
    492518msgid "Pages answered with HTTP %s instead of 200, and only normal pages are cached. Check that these URLs open correctly."
    493519msgstr ""
    494520
    495 #: includes/Modules/Cache/Preloader.php:742
     521#: includes/Modules/Cache/Preloader.php:768
    496522msgid "Page Cache is switched off."
    497523msgstr ""
    498524
    499 #: includes/Modules/Cache/Preloader.php:743
     525#: includes/Modules/Cache/Preloader.php:769
    500526msgid "Another plugin or the theme marks these pages \"do not cache\" (DONOTCACHEPAGE), so they are skipped. Membership, security or form plugins often do this."
    501527msgstr ""
    502528
    503 #: includes/Modules/Cache/Preloader.php:744
     529#: includes/Modules/Cache/Preloader.php:770
    504530msgid "The web address used to reach the site does not match the WordPress Site Address (for example www vs non-www). Check Settings → General."
    505531msgstr ""
    506532
    507 #: includes/Modules/Cache/Preloader.php:745
     533#: includes/Modules/Cache/Preloader.php:771
    508534msgid "Cache files could not be written. Make the wp-content/cache folder writable (permissions 755)."
    509535msgstr ""
    510536
    511 #: includes/Modules/Cache/Preloader.php:746
     537#: includes/Modules/Cache/Preloader.php:772
    512538msgid "These pages match your \"Never cache these URLs\" setting."
    513539msgstr ""
    514540
    515 #: includes/Modules/Cache/Preloader.php:747
     541#: includes/Modules/Cache/Preloader.php:773
    516542msgid "A cookie on the request matches your \"Never cache when these cookies are set\" setting."
    517543msgstr ""
    518544
    519 #: includes/Modules/Cache/Preloader.php:748
     545#: includes/Modules/Cache/Preloader.php:774
    520546msgid "The preloader's user-agent matches your \"Never cache for these user agents\" setting."
    521547msgstr ""
    522548
    523 #: includes/Modules/Cache/Preloader.php:749
     549#: includes/Modules/Cache/Preloader.php:775
    524550msgid "The pages came back empty or almost empty, so there was nothing to cache."
    525551msgstr ""
    526552
    527 #: includes/Modules/Cache/Preloader.php:750
     553#: includes/Modules/Cache/Preloader.php:776
    528554msgid "WordPress treated the request as logged in, so it was not cached."
    529555msgstr ""
    530556
    531 #: includes/Modules/Cache/Preloader.php:751
     557#: includes/Modules/Cache/Preloader.php:777
    532558msgid "These URLs show a \"not found\" (404) page, which is never cached."
    533559msgstr ""
    534560
    535561#. translators: %s: internal reason code.
    536 #: includes/Modules/Cache/Preloader.php:757
     562#: includes/Modules/Cache/Preloader.php:783
    537563#, php-format
    538564msgid "The cache skipped these pages (reason: %s)."
    … …  
    552578msgstr ""
    553579
    554 #: includes/Modules/Fonts/FontsModule.php:29
     580#: includes/Modules/Fonts/FontsModule.php:38
    555581#: src/index.js:40
    556 #: src/index.js:1570
     582#: src/index.js:1726
    557583msgid "Fonts"
    558584msgstr ""
    … …  
    568594
    569595#: includes/Modules/Media/MediaModule.php:75
    570 #: src/index.js:1359
     596#: src/index.js:1505
    571597msgid "Image Optimization"
    572598msgstr ""
    … …  
    578604#: includes/Modules/Tweaks/TweaksModule.php:25
    579605#: src/index.js:45
    580 #: src/index.js:1679
     606#: src/index.js:1841
    581607msgid "Tweaks"
    582608msgstr ""
    … …  
    603629
    604630#: src/index.js:41
    605 #: src/index.js:1055
     631#: src/index.js:1201
     632#: src/index.js:2191
    606633msgid "Preload"
    607634msgstr ""
    … …  
    612639
    613640#: src/index.js:42
    614 #: src/index.js:1482
     641#: src/index.js:1638
    615642msgid "Advanced Rules"
    616643msgstr ""
    … …  
    640667msgstr ""
    641668
    642 #: src/index.js:64
     669#: src/index.js:81
     670msgid "every hour"
     671msgstr ""
     672
     673#: src/index.js:82
     674#: src/index.js:84
     675msgid "twice a day"
     676msgstr ""
     677
     678#: src/index.js:83
     679msgid "once a day"
     680msgstr ""
     681
     682#: src/index.js:88
     683msgid "never expires"
     684msgstr ""
     685
     686#: src/index.js:89
     687#: src/index.js:1231
     688#: src/index.js:1333
     689#: src/index.js:2107
     690msgid "12 hours"
     691msgstr ""
     692
     693#: src/index.js:90
     694#: src/index.js:1232
     695#: src/index.js:1334
     696#: src/index.js:2108
     697msgid "1 day"
     698msgstr ""
     699
     700#: src/index.js:91
     701#: src/index.js:1233
     702#: src/index.js:1335
     703#: src/index.js:2109
     704msgid "1 week"
     705msgstr ""
     706
     707#: src/index.js:92
     708#: src/index.js:1234
     709#: src/index.js:1336
     710#: src/index.js:2110
     711msgid "1 month"
     712msgstr ""
     713
     714#. translators: %d: number of hours.
     715#: src/index.js:95
     716#: src/index.js:1237
     717#, js-format
     718msgid "%d hour"
     719msgid_plural "%d hours"
     720msgstr[0] ""
     721msgstr[1] ""
     722
     723#: src/index.js:102
     724msgid "Open Network Admin → Optimazz"
     725msgstr ""
     726
     727#: src/index.js:111
     728msgid "Page caching is off for this network"
     729msgstr ""
     730
     731#: src/index.js:113
     732msgid "Turn it on for every site in Network Admin → Optimazz. Each site gets its own cache, and keeps its own exclusions here under Advanced Rules."
     733msgstr ""
     734
     735#: src/index.js:114
     736msgid "Your network administrator can turn it on for every site in Network Admin → Optimazz. All other optimizations — CSS, JavaScript, images, fonts and more — work normally on this site."
     737msgstr ""
     738
     739#: src/index.js:128
     740msgid "Page caching is on — managed for the whole network"
     741msgstr ""
     742
     743#. translators: 1: yes/no, 2: cache lifetime, 3: preload frequency.
     744#: src/index.js:131
     745#, js-format
     746msgid "Separate mobile cache: %1$s · Cache lifetime: %2$s · Automatic preload: %3$s."
     747msgstr ""
     748
     749#: src/index.js:132
     750msgid "yes"
     751msgstr ""
     752
     753#: src/index.js:132
     754msgid "no"
     755msgstr ""
     756
     757#: src/index.js:134
     758#: src/index.js:1247
     759msgid "off"
     760msgstr ""
     761
     762#: src/index.js:136
     763msgid "This site has its own cache. Purge or preload it from the Dashboard or the Preload screen, and set its own exclusions under Advanced Rules."
     764msgstr ""
     765
     766#: src/index.js:146
    643767msgid "Content leftovers"
    644768msgstr ""
    645769
    646 #: src/index.js:65
     770#: src/index.js:147
    647771msgid "Old post data that piles up over time. Removing it is permanent, so keep anything you still need."
    648772msgstr ""
    649773
    650 #: src/index.js:67
     774#: src/index.js:149
    651775msgid "Post revisions"
    652776msgstr ""
    653777
    654 #: src/index.js:67
     778#: src/index.js:149
    655779msgid "Old copies of posts saved automatically. Deleting them keeps only your current content."
    656780msgstr ""
    657781
    658 #: src/index.js:68
     782#: src/index.js:150
    659783msgid "Auto drafts"
    660784msgstr ""
    661785
    662 #: src/index.js:68
     786#: src/index.js:150
    663787msgid "Empty drafts WordPress creates while you write. Safe to remove."
    664788msgstr ""
    665789
    666 #: src/index.js:69
     790#: src/index.js:151
    667791msgid "Trashed posts"
    668792msgstr ""
    669793
    670 #: src/index.js:69
     794#: src/index.js:151
    671795msgid "Posts in the trash that have not been permanently deleted yet."
    672796msgstr ""
    673797
    674 #: src/index.js:73
     798#: src/index.js:155
    675799msgid "Comment clutter"
    676800msgstr ""
    677801
    678 #: src/index.js:74
     802#: src/index.js:156
    679803msgid "Spam and trashed comments you are never going to approve. Clearing them keeps the comments table light."
    680804msgstr ""
    681805
    682 #: src/index.js:76
     806#: src/index.js:158
    683807msgid "Spam comments"
    684808msgstr ""
    685809
    686 #: src/index.js:76
     810#: src/index.js:158
    687811msgid "Comments marked as spam. Removing them frees up the comments table."
    688812msgstr ""
    689813
    690 #: src/index.js:77
     814#: src/index.js:159
    691815msgid "Trashed comments"
    692816msgstr ""
    693817
    694 #: src/index.js:77
     818#: src/index.js:159
    695819msgid "Comments in the trash awaiting permanent deletion."
    696820msgstr ""
    697821
    698 #: src/index.js:81
     822#: src/index.js:163
    699823msgid "Temporary data"
    700824msgstr ""
    701825
    702 #: src/index.js:82
     826#: src/index.js:164
    703827msgid "Short-lived cached values. They are safe to clear and are rebuilt automatically whenever a plugin needs them."
    704828msgstr ""
    705829
    706 #: src/index.js:84
     830#: src/index.js:166
    707831msgid "Expired transients"
    708832msgstr ""
    709833
    710 #: src/index.js:84
     834#: src/index.js:166
    711835msgid "Cached values that have already passed their expiry date."
    712836msgstr ""
    713837
    714 #: src/index.js:88
     838#: src/index.js:170
    715839msgid "Table maintenance"
    716840msgstr ""
    717841
    718 #: src/index.js:89
     842#: src/index.js:171
    719843msgid "Defragment your tables to reclaim wasted space and trim database overhead."
    720844msgstr ""
    721845
    722 #: src/index.js:91
     846#: src/index.js:173
    723847msgid "Optimize database tables"
    724848msgstr ""
    725849
    726 #: src/index.js:91
     850#: src/index.js:173
    727851msgid "Runs OPTIMIZE on your tables to defragment them and free wasted space."
    728852msgstr ""
    729853
    730 #: src/index.js:97
     854#: src/index.js:179
    731855msgid "Do not limit"
    732856msgstr ""
    733857
    734 #: src/index.js:98
     858#: src/index.js:180
    735859msgid "Reduce activity"
    736860msgstr ""
    737861
    738 #: src/index.js:99
     862#: src/index.js:181
    739863msgid "Disable"
    740864msgstr ""
    741865
    742 #: src/index.js:108
     866#: src/index.js:190
    743867msgid "Active"
    744868msgstr ""
    745869
    746 #: src/index.js:108
    747 #: src/index.js:1110
    748 #: src/index.js:1636
     870#: src/index.js:190
     871#: src/index.js:1256
     872#: src/index.js:1798
    749873msgid "Off"
    750874msgstr ""
    751875
    752 #: src/index.js:150
     876#: src/index.js:232
    753877msgid "Cancelled"
    754878msgstr ""
    755879
    756 #: src/index.js:151
     880#: src/index.js:233
    757881msgid "Failed"
    758882msgstr ""
    759883
    760 #: src/index.js:167
     884#: src/index.js:249
    761885msgid "Expand"
    762886msgstr ""
    763887
    764 #: src/index.js:167
     888#: src/index.js:249
    765889msgid "Minimize"
    766890msgstr ""
    767891
    768 #: src/index.js:170
     892#: src/index.js:252
    769893msgid "Close"
    770894msgstr ""
    771895
    772 #: src/index.js:184
     896#: src/index.js:266
    773897msgid "Step"
    774898msgstr ""
    775899
    776 #: src/index.js:189
     900#: src/index.js:271
    777901msgid "Stop"
    778902msgstr ""
    779903
    780 #: src/index.js:492
    781 #: src/index.js:1239
     904#: src/index.js:538
     905msgid "You have unsaved changes"
     906msgstr ""
     907
     908#: src/index.js:538
     909msgid "All changes saved"
     910msgstr ""
     911
     912#: src/index.js:541
     913msgid "Save changes"
     914msgstr ""
     915
     916#: src/index.js:639
     917#: src/index.js:1385
    782918msgid "Deactivate"
    783919msgstr ""
    784920
    785 #: src/index.js:492
     921#: src/index.js:639
    786922msgid "Optimazz will keep handling caching."
    787923msgstr ""
    788924
    789 #: src/index.js:499
     925#: src/index.js:646
    790926msgid "deactivated — Optimazz is now the only cache."
    791927msgstr ""
    792928
    793 #: src/index.js:501
     929#: src/index.js:648
    794930msgid "Could not deactivate the plugin."
    795931msgstr ""
    796932
    797 #: src/index.js:508
     933#: src/index.js:655
    798934msgid "Make Optimazz the caching plugin? This replaces the current advanced-cache.php (from another caching plugin) and turns on WP_CACHE. Deactivate the other caching plugin afterwards to avoid conflicts."
    799935msgstr ""
    800936
    801 #: src/index.js:515
     937#: src/index.js:662
    802938msgid "Optimazz is now serving pages from cache."
    803939msgstr ""
    804940
    805 #: src/index.js:516
     941#: src/index.js:663
    806942msgid "Drop-in installed. Preload the cache, then check again."
    807943msgstr ""
    808944
    809 #: src/index.js:517
     945#: src/index.js:664
    810946msgid "Could not fully take over — see the cache health details."
    811947msgstr ""
    812948
    813 #: src/index.js:520
     949#: src/index.js:667
    814950msgid "Take-over failed. Check file permissions."
    815951msgstr ""
    816952
    817 #: src/index.js:542
    818 #: src/index.js:588
     953#: src/index.js:689
     954#: src/index.js:735
    819955msgid "Cache purged."
    820956msgstr ""
    821957
    822 #: src/index.js:543
     958#: src/index.js:690
    823959msgid "Cache preload started in the background."
    824960msgstr ""
    825961
    826 #: src/index.js:544
     962#: src/index.js:691
    827963msgid "Cache cleared; preload started in the background."
    828964msgstr ""
    829965
    830 #: src/index.js:553
     966#: src/index.js:700
    831967msgid "wp-config.php is not writable — add WP_CACHE manually to enable page caching."
    832968msgstr ""
    833969
    834 #: src/index.js:567
     970#: src/index.js:714
    835971msgid "Settings saved."
    836972msgstr ""
    837973
    838 #: src/index.js:570
     974#: src/index.js:717
    839975msgid "Save failed."
    840976msgstr ""
    841977
    842 #: src/index.js:620
     978#: src/index.js:767
    843979msgid "Converting images to WebP/AVIF"
    844980msgstr ""
    845981
    846 #: src/index.js:621
     982#: src/index.js:768
    847983msgid "Next-gen format generation"
    848984msgstr ""
    849985
    850 #: src/index.js:624
     986#: src/index.js:771
    851987msgid "Generated"
    852988msgstr ""
    853989
    854 #: src/index.js:624
     990#: src/index.js:771
    855991msgid "files in this batch"
    856992msgstr ""
    857993
    858 #: src/index.js:624
    859 #: src/index.js:633
     994#: src/index.js:771
     995#: src/index.js:780
    860996msgid "skipped"
    861997msgstr ""
    862998
    863 #: src/index.js:627
     999#: src/index.js:774
    8641000msgid "Compressing images"
    8651001msgstr ""
    8661002
    867 #: src/index.js:628
     1003#: src/index.js:775
    8681004msgid "Recompress, resize & strip metadata"
    8691005msgstr ""
    8701006
    871 #: src/index.js:634
     1007#: src/index.js:781
    8721008msgid "Processed"
    8731009msgstr ""
    8741010
    875 #: src/index.js:634
    876 #: src/index.js:1440
     1011#: src/index.js:781
     1012#: src/index.js:1586
    8771013msgid "saved"
    8781014msgstr ""
    8791015
    880 #: src/index.js:638
     1016#: src/index.js:785
     1017#: src/index.js:1953
    8811018msgid "Preloading cache"
    8821019msgstr ""
    8831020
    884 #: src/index.js:639
     1021#: src/index.js:786
    8851022msgid "Warming pages"
    8861023msgstr ""
    8871024
    888 #: src/index.js:643
     1025#: src/index.js:790
     1026#: src/index.js:1974
    8891027msgid "Warmed"
    8901028msgstr ""
    8911029
    892 #: src/index.js:643
     1030#: src/index.js:790
     1031#: src/index.js:1974
    8931032msgid "pages"
    8941033msgstr ""
    8951034
    896 #: src/index.js:643
     1035#: src/index.js:790
     1036#: src/index.js:1974
    8971037msgid "cached"
    8981038msgstr ""
    8991039
    900 #: src/index.js:643
     1040#: src/index.js:790
     1041#: src/index.js:1974
    9011042msgid "not cached"
    9021043msgstr ""
    9031044
    904 #: src/index.js:664
     1045#: src/index.js:811
    9051046msgid "Resuming…"
    9061047msgstr ""
    9071048
    908 #: src/index.js:678
     1049#: src/index.js:825
     1050#: src/index.js:1963
    9091051msgid "Stopped by user."
    9101052msgstr ""
    9111053
    912 #: src/index.js:697
     1054#: src/index.js:844
    9131055msgid "A batch failed (likely one slow/large image); skipped it and continued."
    9141056msgstr ""
    9151057
    916 #: src/index.js:699
     1058#: src/index.js:846
    9171059msgid "Completed (some items skipped)."
    9181060msgstr ""
    9191061
    920 #: src/index.js:711
     1062#: src/index.js:858
    9211063msgid "Job stalled — no progress."
    9221064msgstr ""
    9231065
    924 #: src/index.js:722
     1066#: src/index.js:869
     1067#: src/index.js:1977
    9251068msgid "left"
    9261069msgstr ""
    9271070
    9281071#. translators: %d: number of pages to warm.
    929 #: src/index.js:734
     1072#: src/index.js:881
     1073#: src/index.js:1987
    9301074#, js-format
    9311075msgid "Found %d pages to warm:"
    9321076msgstr ""
    9331077
    934 #: src/index.js:744
     1078#: src/index.js:891
    9351079msgid "Image processing is unavailable on this server (no GD or ImageMagick). See Tools → Server Requirements."
    9361080msgstr ""
    9371081
    938 #: src/index.js:749
     1082#: src/index.js:896
     1083#: src/index.js:1993
    9391084msgid "Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the \"Optimazz-CacheWarmer\" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter."
    9401085msgstr ""
    9411086
    942 #: src/index.js:756
     1087#: src/index.js:903
     1088#: src/index.js:1997
    9431089msgid "Pages are not being cached"
    9441090msgstr ""
    9451091
    946 #: src/index.js:756
     1092#: src/index.js:903
     1093#: src/index.js:1997
    9471094msgid "Stopped: pages are loading but none are being saved to the cache."
    9481095msgstr ""
    9491096
    950 #: src/index.js:760
    951 #: src/index.js:841
     1097#: src/index.js:907
     1098#: src/index.js:988
     1099#: src/index.js:2007
    9521100msgid "Completed."
    9531101msgstr ""
    9541102
    9551103#. translators: %d: number of pages.
    956 #: src/index.js:764
     1104#: src/index.js:911
     1105#: src/index.js:2004
    9571106#, js-format
    9581107msgid "%d page was not cached."
    … …  
    9611110msgstr[1] ""
    9621111
    963 #: src/index.js:823
     1112#: src/index.js:970
    9641113msgid "Cleaning and optimizing database…"
    9651114msgstr ""
    9661115
    967 #: src/index.js:827
     1116#: src/index.js:974
    9681117msgid "Optimizing database"
    9691118msgstr ""
    9701119
    971 #: src/index.js:828
     1120#: src/index.js:975
    9721121msgid "Cleanup & table optimization"
    9731122msgstr ""
    9741123
    975 #: src/index.js:840
     1124#: src/index.js:987
    9761125msgid "Cleaned"
    9771126msgstr ""
    9781127
    979 #: src/index.js:840
     1128#: src/index.js:987
    9801129msgid "rows,"
    9811130msgstr ""
    9821131
    983 #: src/index.js:840
     1132#: src/index.js:987
    9841133msgid "tables optimized."
    9851134msgstr ""
    9861135
    987 #: src/index.js:845
     1136#: src/index.js:992
    9881137msgid "Optimize failed."
    9891138msgstr ""
    9901139
    991 #: src/index.js:851
     1140#: src/index.js:998
    9921141msgid "Reset all Optimazz settings to defaults? You can undo this with \"Undo last change\"."
    9931142msgstr ""
    9941143
    995 #: src/index.js:859
     1144#: src/index.js:1006
    9961145msgid "Settings reset to defaults."
    9971146msgstr ""
    9981147
    999 #: src/index.js:862
     1148#: src/index.js:1009
    10001149msgid "Reset failed."
    10011150msgstr ""
    10021151
    1003 #: src/index.js:872
     1152#: src/index.js:1019
    10041153msgid "Nothing to undo."
    10051154msgstr ""
    10061155
    1007 #: src/index.js:879
     1156#: src/index.js:1026
    10081157msgid "Reverted to the previous settings."
    10091158msgstr ""
    10101159
    1011 #: src/index.js:882
     1160#: src/index.js:1029
    10121161msgid "Undo failed."
    10131162msgstr ""
    10141163
    1015 #: src/index.js:914
     1164#: src/index.js:1061
    10161165msgid "Invalid settings file."
    10171166msgstr ""
    10181167
    1019 #: src/index.js:944
     1168#: src/index.js:1088
    10201169msgid "Page caching"
    10211170msgstr ""
    10221171
    1023 #: src/index.js:945
     1172#: src/index.js:1089
    10241173msgid "Delay JavaScript"
    10251174msgstr ""
    10261175
    1027 #: src/index.js:946
    1028 #: src/index.js:1266
     1176#: src/index.js:1090
     1177#: src/index.js:1412
    10291178msgid "Defer JavaScript"
    10301179msgstr ""
    10311180
    1032 #: src/index.js:947
    1033 #: src/index.js:1319
     1181#: src/index.js:1091
     1182#: src/index.js:1465
    10341183msgid "Minify CSS"
    10351184msgstr ""
    10361185
    1037 #: src/index.js:949
     1186#: src/index.js:1093
    10381187msgid "Next-gen images"
    10391188msgstr ""
    10401189
    1041 #: src/index.js:950
     1190#: src/index.js:1094
    10421191msgid "Font optimization"
    10431192msgstr ""
    10441193
    1045 #: src/index.js:951
     1194#: src/index.js:1095
    10461195msgid "Cache preloading"
    10471196msgstr ""
    10481197
    1049 #: src/index.js:952
     1198#: src/index.js:1096
    10501199msgid "Heartbeat control"
    10511200msgstr ""
    10521201
    1053 #: src/index.js:967
     1202#: src/index.js:1113
    10541203msgid "Turn on Page Cache for the real speed gain"
    10551204msgstr ""
    10561205
    1057 #: src/index.js:968
     1206#: src/index.js:1114
    10581207msgid "File and media optimizations are on, but Page Cache is off — so the server rebuilds every page on every request. Without caching this can make a site feel slower, not faster, especially on mobile. Enable Page Cache to serve ready-made HTML."
    10591208msgstr ""
    10601209
    1061 #: src/index.js:987
     1210#: src/index.js:1133
    10621211msgid "SCORE"
    10631212msgstr ""
    10641213
    1065 #: src/index.js:992
     1214#: src/index.js:1138
    10661215msgid "Performance score"
    10671216msgstr ""
    10681217
    1069 #: src/index.js:995
     1218#: src/index.js:1141
    10701219msgid "Biggest remaining win:"
    10711220msgstr ""
    10721221
    1073 #: src/index.js:996
     1222#: src/index.js:1142
    10741223msgid "Every optimization is enabled. Nicely done!"
    10751224msgstr ""
    10761225
    1077 #: src/index.js:1013
    1078 #: src/index.js:1065
     1226#: src/index.js:1159
     1227#: src/index.js:1211
     1228#: src/index.js:2204
    10791229msgid "Cached pages"
    10801230msgstr ""
    10811231
    1082 #: src/index.js:1018
     1232#: src/index.js:1164
    10831233msgid "Cache size"
    10841234msgstr ""
    10851235
    1086 #: src/index.js:1023
     1236#: src/index.js:1169
    10871237msgid "Modules active"
    10881238msgstr ""
    10891239
    1090 #: src/index.js:1028
    1091 #: src/index.js:1043
     1240#: src/index.js:1174
     1241#: src/index.js:1189
    10921242msgid "Recommendations"
    10931243msgstr ""
    10941244
    1095 #: src/index.js:1028
     1245#: src/index.js:1174
    10961246msgid "What to fix next to speed up your site — one click each."
    10971247msgstr ""
    10981248
    1099 #: src/index.js:1035
     1249#: src/index.js:1181
    11001250msgid "Fix it"
    11011251msgstr ""
    11021252
    1103 #: src/index.js:1036
     1253#: src/index.js:1182
    11041254msgid "Open"
    11051255msgstr ""
    11061256
    1107 #: src/index.js:1044
     1257#: src/index.js:1190
    11081258msgid "Nice — everything we recommend is already on. Your site is well optimized."
    11091259msgstr ""
    11101260
    1111 #: src/index.js:1049
     1261#: src/index.js:1195
    11121262msgid "Cache Files"
    11131263msgstr ""
    11141264
    1115 #: src/index.js:1049
     1265#: src/index.js:1195
    11161266msgid "Clear and preload all cache files."
    11171267msgstr ""
    11181268
    1119 #: src/index.js:1052
     1269#: src/index.js:1198
     1270#: src/index.js:2187
    11201271msgid "Clear & Preload"
    11211272msgstr ""
    11221273
    1123 #: src/index.js:1058
     1274#: src/index.js:1204
     1275#: src/index.js:2195
    11241276msgid "Purge cache"
    11251277msgstr ""
    11261278
    1127 #: src/index.js:1070
     1279#: src/index.js:1216
     1280#: src/index.js:2209
    11281281msgid "Page cache size"
    11291282msgstr ""
    11301283
    1131 #: src/index.js:1075
     1284#: src/index.js:1221
    11321285msgid "Optimized CSS"
    11331286msgstr ""
    11341287
    1135 #: src/index.js:1080
    1136 #: src/index.js:1081
     1288#: src/index.js:1226
     1289#: src/index.js:1227
    11371290msgid "Cache lifespan"
    11381291msgstr ""
    11391292
    1140 #: src/index.js:1084
     1293#: src/index.js:1230
    11411294msgid "Never expire"
    11421295msgstr ""
    11431296
    1144 #: src/index.js:1085
    1145 #: src/index.js:1187
    1146 msgid "12 hours"
    1147 msgstr ""
    1148 
    1149 #: src/index.js:1086
    1150 #: src/index.js:1188
    1151 msgid "1 day"
    1152 msgstr ""
    1153 
    1154 #: src/index.js:1087
    1155 #: src/index.js:1189
    1156 msgid "1 week"
    1157 msgstr ""
    1158 
    1159 #: src/index.js:1088
    1160 #: src/index.js:1190
    1161 msgid "1 month"
    1162 msgstr ""
    1163 
    1164 #. translators: %d: number of hours.
    1165 #: src/index.js:1091
    1166 #, js-format
    1167 msgid "%d hour"
    1168 msgid_plural "%d hours"
    1169 msgstr[0] ""
    1170 msgstr[1] ""
    1171 
    1172 #: src/index.js:1098
     1297#: src/index.js:1244
    11731298msgid "Auto-preload"
    11741299msgstr ""
    11751300
    1176 #: src/index.js:1101
    1177 msgid "off"
    1178 msgstr ""
    1179 
    1180 #: src/index.js:1103
     1301#: src/index.js:1249
    11811302msgid "next run: due now"
    11821303msgstr ""
    11831304
    1184 #: src/index.js:1104
     1305#: src/index.js:1250
    11851306msgid "next run in"
    11861307msgstr ""
    11871308
    1188 #: src/index.js:1107
     1309#: src/index.js:1253
    11891310msgid "Auto-preload frequency"
    11901311msgstr ""
    11911312
    1192 #: src/index.js:1111
    1193 #: src/index.js:1462
     1313#: src/index.js:1257
     1314#: src/index.js:1609
     1315#: src/index.js:2121
    11941316msgid "Every hour"
    11951317msgstr ""
    11961318
    1197 #: src/index.js:1112
    1198 #: src/index.js:1463
     1319#: src/index.js:1258
     1320#: src/index.js:1610
     1321#: src/index.js:2122
    11991322msgid "Twice a day"
    12001323msgstr ""
    12011324
    1202 #: src/index.js:1113
    1203 #: src/index.js:1464
     1325#: src/index.js:1259
     1326#: src/index.js:1611
     1327#: src/index.js:2123
    12041328msgid "Once a day"
    12051329msgstr ""
    12061330
    1207 #: src/index.js:1121
     1331#: src/index.js:1267
    12081332msgid "Last auto-preload"
    12091333msgstr ""
    12101334
    1211 #: src/index.js:1124
     1335#: src/index.js:1270
    12121336msgid "not yet"
    12131337msgstr ""
    12141338
    12151339#. translators: 1: time ago, e.g. "2 hours", 2: number of pages
    1216 #: src/index.js:1127
     1340#: src/index.js:1273
    12171341#, js-format
    12181342msgid "%1$s ago · %2$d pages"
    12191343msgstr ""
    12201344
    1221 #: src/index.js:1130
     1345#: src/index.js:1276
    12221346msgid "blocked by firewall"
    12231347msgstr ""
    12241348
    1225 #: src/index.js:1135
     1349#: src/index.js:1281
    12261350msgid "Module Status"
    12271351msgstr ""
    12281352
    1229 #: src/index.js:1135
     1353#: src/index.js:1281
    12301354msgid "Enable or disable any module right here."
    12311355msgstr ""
    12321356
    1233 #: src/index.js:1172
     1357#: src/index.js:1318
    12341358msgid "Serve static HTML instead of running PHP on every request."
    12351359msgstr ""
    12361360
    1237 #: src/index.js:1174
     1361#: src/index.js:1320
     1362#: src/index.js:2094
    12381363msgid "Cache mobile devices separately"
    12391364msgstr ""
    12401365
    1241 #: src/index.js:1176
     1366#: src/index.js:1322
     1367#: src/index.js:2096
    12421368msgid "Cache logged-in users"
    12431369msgstr ""
    12441370
    1245 #: src/index.js:1177
     1371#: src/index.js:1323
     1372#: src/index.js:2097
    12461373msgid "Leave off unless you know what you are doing."
    12471374msgstr ""
    12481375
    1249 #: src/index.js:1179
     1376#: src/index.js:1325
     1377#: src/index.js:2099
    12501378msgid "Browser cache headers"
    12511379msgstr ""
    12521380
    1253 #: src/index.js:1180
     1381#: src/index.js:1326
    12541382msgid "Add Expires / Cache-Control rules so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only (written to .htaccess); ignored on Nginx."
    12551383msgstr ""
    12561384
    1257 #: src/index.js:1182
     1385#: src/index.js:1328
     1386#: src/index.js:2102
    12581387msgid "Cache lifetime"
    12591388msgstr ""
    12601389
    1261 #: src/index.js:1183
     1390#: src/index.js:1329
    12621391msgid "How long a cached page is kept before it is refreshed. Pages are always purged immediately when their content changes."
    12631392msgstr ""
    12641393
    1265 #: src/index.js:1186
     1394#: src/index.js:1332
     1395#: src/index.js:2106
    12661396msgid "Never"
    12671397msgstr ""
    12681398
    1269 #: src/index.js:1196
     1399#: src/index.js:1342
     1400#: src/index.js:2128
    12701401msgid "Cache health"
    12711402msgstr ""
    12721403
    1273 #: src/index.js:1198
     1404#: src/index.js:1344
    12741405msgid "Check now"
    12751406msgstr ""
    12761407
    1277 #: src/index.js:1201
     1408#: src/index.js:1347
    12781409msgid "Confirms caching is actually serving pages. If it is not, the plugin rebuilds every page on each request — which is slower, not faster."
    12791410msgstr ""
    12801411
    1281 #: src/index.js:1205
     1412#: src/index.js:1351
    12821413msgid "Pages ARE being served from cache (HIT)"
    12831414msgstr ""
    12841415
    1285 #: src/index.js:1206
     1416#: src/index.js:1352
    12861417msgid "Pages are NOT being served from cache (MISS) — see below"
    12871418msgstr ""
    12881419
    1289 #: src/index.js:1207
     1420#: src/index.js:1353
    12901421msgid "Live check unavailable"
    12911422msgstr ""
    12921423
    1293 #: src/index.js:1211
     1424#: src/index.js:1357
    12941425msgid "advanced-cache.php belongs to"
    12951426msgstr ""
    12961427
    1297 #: src/index.js:1211
     1428#: src/index.js:1357
    12981429msgid "take it over below"
    12991430msgstr ""
    13001431
    1301 #: src/index.js:1212
     1432#: src/index.js:1358
    13021433msgid "advanced-cache.php belongs to ANOTHER plugin — take it over below"
    13031434msgstr ""
    13041435
    1305 #: src/index.js:1213
     1436#: src/index.js:1359
    13061437msgid "advanced-cache.php drop-in installed"
    13071438msgstr ""
    13081439
    1309 #: src/index.js:1214
     1440#: src/index.js:1360
    13101441msgid "advanced-cache.php drop-in is missing"
    13111442msgstr ""
    13121443
    1313 #: src/index.js:1216
     1444#: src/index.js:1362
    13141445msgid "WP_CACHE is enabled in wp-config.php"
    13151446msgstr ""
    13161447
    1317 #: src/index.js:1216
     1448#: src/index.js:1362
    13181449msgid "WP_CACHE is not enabled in wp-config.php"
    13191450msgstr ""
    13201451
    1321 #: src/index.js:1218
     1452#: src/index.js:1364
    13221453msgid "wp-config.php is not writable — add the WP_CACHE line manually (below)"
    13231454msgstr ""
    13241455
    1325 #: src/index.js:1221
     1456#: src/index.js:1367
    13261457msgid "pages warmed in cache"
    13271458msgstr ""
    13281459
    1329 #: src/index.js:1227
     1460#: src/index.js:1373
    13301461msgid "Take over caching"
    13311462msgstr ""
    13321463
    1333 #: src/index.js:1229
     1464#: src/index.js:1375
    13341465msgid "Make Optimazz the caching plugin — installs its drop-in and enables WP_CACHE."
    13351466msgstr ""
    13361467
    1337 #: src/index.js:1237
     1468#: src/index.js:1383
    13381469msgid "is still active and may reclaim caching. Deactivate it so Optimazz keeps serving the cache."
    13391470msgstr ""
    13401471
    1341 #: src/index.js:1247
     1472#: src/index.js:1393
    13421473msgid "wp-config.php is not writable. Add this line just after the opening <?php tag:"
    13431474msgstr ""
    13441475
    1345 #: src/index.js:1252
     1476#: src/index.js:1398
    13461477msgid "Could not run the check. Try reloading the page."
    13471478msgstr ""
    13481479
    1349 #: src/index.js:1260
     1480#: src/index.js:1406
    13501481msgid "JavaScript"
    13511482msgstr ""
    13521483
    1353 #: src/index.js:1261
     1484#: src/index.js:1407
    13541485msgid "Defer and delay scripts to cut Total Blocking Time."
    13551486msgstr ""
    13561487
    1357 #: src/index.js:1263
     1488#: src/index.js:1409
    13581489msgid "Minify JavaScript"
    13591490msgstr ""
    13601491
    1361 #: src/index.js:1264
     1492#: src/index.js:1410
    13621493msgid "Strip comments and whitespace from inline scripts. Line breaks are kept for safety; test your site after enabling."
    13631494msgstr ""
    13641495
    1365 #: src/index.js:1267
     1496#: src/index.js:1413
    13661497msgid "Load scripts without blocking page render."
    13671498msgstr ""
    13681499
    1369 #: src/index.js:1269
     1500#: src/index.js:1415
    13701501msgid "Delay JS until interaction"
    13711502msgstr ""
    13721503
    1373 #: src/index.js:1270
     1504#: src/index.js:1416
    13741505msgid "Run scripts only after the first user interaction."
    13751506msgstr ""
    13761507
    1377 #: src/index.js:1273
     1508#: src/index.js:1419
    13781509msgid "Delay all scripts"
    13791510msgstr ""
    13801511
    1381 #: src/index.js:1274
     1512#: src/index.js:1420
    13821513msgid "Delay every script until interaction. jQuery, your theme, and Elementor are always kept safe. Still, on sites where content is rendered by JavaScript this can delay the visible page — test before keeping it on."
    13831514msgstr ""
    13841515
    1385 #: src/index.js:1280
     1516#: src/index.js:1426
    13861517msgid "Delaying all scripts can slow the first paint on JavaScript-rendered sites (Elementor, sliders). If your PageSpeed FCP/LCP get worse, switch to \"Delay selected\" and delay only tracking scripts."
    13871518msgstr ""
    13881519
    1389 #: src/index.js:1284
     1520#: src/index.js:1430
    13901521msgid "Delay these scripts"
    13911522msgstr ""
    13921523
    1393 #: src/index.js:1285
     1524#: src/index.js:1431
    13941525msgid "Only scripts matching these keywords are delayed. Match by filename, handle or URL — e.g. gtag, analytics, facebook."
    13951526msgstr ""
    13961527
    1397 #: src/index.js:1286
     1528#: src/index.js:1432
    13981529msgid "e.g. gtag, analytics"
    13991530msgstr ""
    14001531
    1401 #: src/index.js:1292
     1532#: src/index.js:1438
    14021533msgid "Exclude specific scripts"
    14031534msgstr ""
    14041535
    1405 #: src/index.js:1294
     1536#: src/index.js:1440
    14061537msgid "If a script breaks, exclude it from just one optimization instead of both. Match by filename, script handle, or part of the URL."
    14071538msgstr ""
    14081539
    1409 #: src/index.js:1298
     1540#: src/index.js:1444
    14101541msgid "Never defer"
    14111542msgstr ""
    14121543
    1413 #: src/index.js:1299
     1544#: src/index.js:1445
    14141545msgid "Keep loading these render-blocking, but still allow delay. Use for scripts that must run during page load."
    14151546msgstr ""
    14161547
    1417 #: src/index.js:1300
     1548#: src/index.js:1446
    14181549msgid "e.g. jquery.min.js"
    14191550msgstr ""
    14201551
    1421 #: src/index.js:1304
     1552#: src/index.js:1450
    14221553msgid "Never delay"
    14231554msgstr ""
    14241555
    1425 #: src/index.js:1305
     1556#: src/index.js:1451
    14261557msgid "Run these before any interaction, but still allow defer. Use for sliders, above-the-fold, or consent scripts."
    14271558msgstr ""
    14281559
    1429 #: src/index.js:1306
     1560#: src/index.js:1452
    14301561msgid "e.g. slider, swiper"
    14311562msgstr ""
    14321563
    1433 #: src/index.js:1309
     1564#: src/index.js:1455
    14341565msgid "Never optimize"
    14351566msgstr ""
    14361567
    1437 #: src/index.js:1310
     1568#: src/index.js:1456
    14381569msgid "Skip both defer and delay — leave these scripts exactly as they are."
    14391570msgstr ""
    14401571
    1441 #: src/index.js:1311
     1572#: src/index.js:1457
    14421573msgid "e.g. recaptcha"
    14431574msgstr ""
    14441575
    1445 #: src/index.js:1316
     1576#: src/index.js:1462
    14461577msgid "CSS"
    14471578msgstr ""
    14481579
    1449 #: src/index.js:1317
     1580#: src/index.js:1463
    14501581msgid "Minify, combine, and remove render-blocking stylesheets."
    14511582msgstr ""
    14521583
    1453 #: src/index.js:1321
     1584#: src/index.js:1467
    14541585msgid "Combine CSS files"
    14551586msgstr ""
    14561587
    1457 #: src/index.js:1322
     1588#: src/index.js:1468
    14581589msgid "Merge stylesheets to reduce requests."
    14591590msgstr ""
    14601591
    1461 #: src/index.js:1324
     1592#: src/index.js:1470
    14621593msgid "Load CSS asynchronously"
    14631594msgstr ""
    14641595
    1465 #: src/index.js:1325
     1596#: src/index.js:1471
    14661597msgid "Removes render-blocking CSS. Paste your above-the-fold (critical) CSS below, or the page may briefly flash unstyled."
    14671598msgstr ""
    14681599
    1469 #: src/index.js:1332
     1600#: src/index.js:1478
    14701601msgid "No critical CSS provided. Async loading is on, so visitors may see a flash of unstyled content until you paste critical CSS below."
    14711602msgstr ""
    14721603
    1473 #: src/index.js:1336
     1604#: src/index.js:1482
    14741605msgid "Critical CSS (above-the-fold)"
    14751606msgstr ""
    14761607
    1477 #: src/index.js:1337
     1608#: src/index.js:1483
    14781609msgid "Paste the above-the-fold CSS to inline. Generate it with a free tool such as a critical-CSS generator. Leave empty to disable inlining."
    14791610msgstr ""
    14801611
    1481 #: src/index.js:1342
     1612#: src/index.js:1488
    14821613msgid "Remove unused CSS"
    14831614msgstr ""
    14841615
    1485 #: src/index.js:1343
     1616#: src/index.js:1489
    14861617msgid "Remove block-editor CSS on the front end"
    14871618msgstr ""
    14881619
    1489 #: src/index.js:1344
     1620#: src/index.js:1490
    14901621msgid "Dequeues WordPress block-library and global styles. Turn on only if your front end does NOT render Gutenberg blocks (e.g. a page-builder or classic theme). Big byte saving when it applies."
    14911622msgstr ""
    14921623
    1493 #: src/index.js:1346
     1624#: src/index.js:1492
    14941625msgid "Load WooCommerce CSS only on shop pages"
    14951626msgstr ""
    14961627
    1497 #: src/index.js:1347
     1628#: src/index.js:1493
    14981629msgid "Dequeues WooCommerce styles on non-shop pages (kept on cart, checkout, account, product and shop). Turn off if you show Woo products or widgets on other pages."
    14991630msgstr ""
    15001631
    1501 #: src/index.js:1349
     1632#: src/index.js:1495
    15021633msgid "Load Easy Digital Downloads CSS only on EDD pages"
    15031634msgstr ""
    15041635
    1505 #: src/index.js:1350
     1636#: src/index.js:1496
    15061637msgid "Dequeues EDD styles on non-EDD pages (kept on checkout, purchase-confirmation, purchase history and download archives). Turn off if you show download or purchase links elsewhere."
    15071638msgstr ""
    15081639
    1509 #: src/index.js:1352
     1640#: src/index.js:1498
    15101641msgid "Excluded CSS files (keywords)"
    15111642msgstr ""
    15121643
    1513 #: src/index.js:1360
     1644#: src/index.js:1506
    15141645msgid "Lazy-load, prevent layout shift, serve next-gen formats, and lighten media."
    15151646msgstr ""
    15161647
    1517 #: src/index.js:1366
     1648#: src/index.js:1512
    15181649msgid "This server has no GD or ImageMagick extension, so image compression and WebP/AVIF cannot run — every image will be skipped. Ask your host to enable \"php-gd\" (or \"php-imagick\"). See Tools → Server Requirements."
    15191650msgstr ""
    15201651
    1521 #: src/index.js:1370
     1652#: src/index.js:1516
    15221653msgid "Load off-screen images only when scrolled into view."
    15231654msgstr ""
    15241655
    1525 #: src/index.js:1372
     1656#: src/index.js:1518
    15261657msgid "Lazy-load iframes"
    15271658msgstr ""
    15281659
    1529 #: src/index.js:1374
     1660#: src/index.js:1520
    15301661msgid "Lazy-load CSS background images"
    15311662msgstr ""
    15321663
    1533 #: src/index.js:1375
     1664#: src/index.js:1521
    15341665msgid "Defer inline background-image elements (test with page builders)."
    15351666msgstr ""
    15361667
    1537 #: src/index.js:1377
     1668#: src/index.js:1523
    15381669msgid "Replace YouTube embeds with a preview"
    15391670msgstr ""
    15401671
    1541 #: src/index.js:1378
     1672#: src/index.js:1524
    15421673msgid "Load the video iframe only when the visitor clicks play."
    15431674msgstr ""
    15441675
    1545 #: src/index.js:1380
     1676#: src/index.js:1526
    15461677msgid "Add missing image dimensions"
    15471678msgstr ""
    15481679
    1549 #: src/index.js:1381
     1680#: src/index.js:1527
    15501681msgid "Set width/height to prevent layout shift (CLS)."
    15511682msgstr ""
    15521683
    1553 #: src/index.js:1383
     1684#: src/index.js:1529
    15541685msgid "Preload the LCP image"
    15551686msgstr ""
    15561687
    1557 #: src/index.js:1384
     1688#: src/index.js:1530
    15581689msgid "Preload the first/hero image with high priority."
    15591690msgstr ""
    15601691
    1561 #: src/index.js:1387
     1692#: src/index.js:1533
    15621693msgid "Keep first images eager (LCP)"
    15631694msgstr ""
    15641695
    1565 #: src/index.js:1388
     1696#: src/index.js:1534
    15661697msgid "Do not lazy-load the first N images, to protect LCP."
    15671698msgstr ""
    15681699
    1569 #: src/index.js:1393
     1700#: src/index.js:1539
    15701701msgid "Serve WebP via <picture>"
    15711702msgstr ""
    15721703
    1573 #: src/index.js:1394
     1704#: src/index.js:1540
    15741705msgid "Deliver WebP to supporting browsers, original as fallback."
    15751706msgstr ""
    15761707
    1577 #: src/index.js:1396
     1708#: src/index.js:1542
    15781709msgid "Generate WebP/AVIF on upload"
    15791710msgstr ""
    15801711
    1581 #: src/index.js:1398
     1712#: src/index.js:1544
    15821713msgid "WebP generation is not available on this server."
    15831714msgstr ""
    15841715
    1585 #: src/index.js:1399
     1716#: src/index.js:1545
    15861717msgid "Auto-create next-gen copies for newly uploaded images."
    15871718msgstr ""
    15881719
    1589 #: src/index.js:1402
     1720#: src/index.js:1548
    15901721msgid "Also generate/serve AVIF"
    15911722msgstr ""
    15921723
    1593 #: src/index.js:1403
     1724#: src/index.js:1549
    15941725msgid "Smaller than WebP; served first when supported."
    15951726msgstr ""
    15961727
    1597 #: src/index.js:1407
     1728#: src/index.js:1553
    15981729msgid "Compress & resize on upload"
    15991730msgstr ""
    16001731
    1601 #: src/index.js:1408
     1732#: src/index.js:1554
    16021733msgid "Recompress, cap dimensions, and strip metadata from new uploads."
    16031734msgstr ""
    16041735
    1605 #: src/index.js:1410
     1736#: src/index.js:1556
    16061737msgid "Convert opaque PNG to JPEG"
    16071738msgstr ""
    16081739
    1609 #: src/index.js:1411
     1740#: src/index.js:1557
    16101741msgid "Re-save non-transparent PNGs as much smaller JPEGs during bulk optimization."
    16111742msgstr ""
    16121743
    1613 #: src/index.js:1413
     1744#: src/index.js:1559
    16141745msgid "Keep a backup of originals"
    16151746msgstr ""
    16161747
    1617 #: src/index.js:1414
     1748#: src/index.js:1560
    16181749msgid "Copy originals to uploads/optimazz-backup before optimizing."
    16191750msgstr ""
    16201751
    1621 #: src/index.js:1416
     1752#: src/index.js:1562
    16221753msgid "Image quality"
    16231754msgstr ""
    16241755
    1625 #: src/index.js:1418
     1756#: src/index.js:1564
    16261757msgid "Used for compression and WebP/AVIF generation."
    16271758msgstr ""
    16281759
    1629 #: src/index.js:1419
     1760#: src/index.js:1565
    16301761msgid "Max image width (px)"
    16311762msgstr ""
    16321763
    1633 #: src/index.js:1424
     1764#: src/index.js:1570
    16341765msgid "WebP coverage"
    16351766msgstr ""
    16361767
    1637 #: src/index.js:1426
     1768#: src/index.js:1572
    16381769msgid "images converted"
    16391770msgstr ""
    16401771
    1641 #: src/index.js:1427
    1642 #: src/index.js:1441
     1772#: src/index.js:1573
     1773#: src/index.js:1587
    16431774msgid "Loading…"
    16441775msgstr ""
    16451776
    1646 #: src/index.js:1432
     1777#: src/index.js:1578
    16471778msgid "Convert to WebP"
    16481779msgstr ""
    16491780
    1650 #: src/index.js:1438
     1781#: src/index.js:1584
    16511782msgid "Bulk compression"
    16521783msgstr ""
    16531784
    1654 #: src/index.js:1440
     1785#: src/index.js:1586
    16551786msgid "optimized"
    16561787msgstr ""
    16571788
    1658 #: src/index.js:1444
     1789#: src/index.js:1590
    16591790msgid "Compress existing images"
    16601791msgstr ""
    16611792
    1662 #: src/index.js:1452
     1793#: src/index.js:1598
    16631794msgid "Warm the cache automatically so visitors never hit a cold page."
    16641795msgstr ""
    16651796
    1666 #: src/index.js:1453
     1797#: src/index.js:1600
    16671798msgid "Activate preloading"
    16681799msgstr ""
    16691800
    1670 #: src/index.js:1454
     1801#: src/index.js:1601
    16711802msgid "Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop."
    16721803msgstr ""
    16731804
    1674 #: src/index.js:1457
     1805#: src/index.js:1604
     1806#: src/index.js:2117
    16751807msgid "Automatic preload"
    16761808msgstr ""
    16771809
    1678 #: src/index.js:1458
     1810#: src/index.js:1605
    16791811msgid "How often to re-warm the whole site. Choose “Every hour” if your content changes often or you use a short cache lifespan."
    16801812msgstr ""
    16811813
    1682 #: src/index.js:1470
     1814#. translators: %s: preload frequency, e.g. "twice a day".
     1815#: src/index.js:1618
     1816#, js-format
     1817msgid "Automatic preload runs %s for this site (set for the whole network in Network Admin → Optimazz)."
     1818msgstr ""
     1819
     1820#: src/index.js:1621
     1821msgid "Automatic preload is off for the network. You can still preload this site now."
     1822msgstr ""
     1823
     1824#: src/index.js:1626
    16831825msgid "WP-Cron is disabled on this site (DISABLE_WP_CRON). Scheduled preloads only run if a server cron job calls wp-cron.php — ask your host to set one up (e.g. every 5 minutes)."
    16841826msgstr ""
    16851827
    1686 #: src/index.js:1475
     1828#: src/index.js:1631
    16871829msgid "Preload now"
    16881830msgstr ""
    16891831
    1690 #: src/index.js:1483
     1832#: src/index.js:1639
    16911833msgid "Fine-tune which requests are cached."
    16921834msgstr ""
    16931835
    1694 #: src/index.js:1484
     1836#: src/index.js:1640
    16951837msgid "Never cache these URLs (contains)"
    16961838msgstr ""
    16971839
    1698 #: src/index.js:1486
     1840#: src/index.js:1642
    16991841msgid "Never cache when these cookies are set"
    17001842msgstr ""
    17011843
    1702 #: src/index.js:1488
     1844#: src/index.js:1644
    17031845msgid "Never cache for these user agents"
    17041846msgstr ""
    17051847
    1706 #: src/index.js:1490
     1848#: src/index.js:1646
    17071849msgid "Ignore these query arguments"
    17081850msgstr ""
    17091851
    1710 #: src/index.js:1498
     1852#: src/index.js:1654
    17111853msgid "Safe Mode"
    17121854msgstr ""
    17131855
    1714 #: src/index.js:1499
     1856#: src/index.js:1655
    17151857msgid "If a theme or page builder looks broken after optimizing, turn this on. It pauses the riskiest optimizations — combine CSS, async CSS, remove block CSS and JavaScript delay — while keeping caching, minify, defer and lazy-load. Your saved settings are not changed; they simply pause until you turn Safe Mode off."
    17161858msgstr ""
    17171859
    1718 #: src/index.js:1502
     1860#: src/index.js:1658
    17191861msgid "Pause risky optimizations (Safe Mode)"
    17201862msgstr ""
    17211863
    1722 #: src/index.js:1509
     1864#: src/index.js:1665
    17231865msgid "Safe Mode is ON — combine/async CSS, remove-block-CSS and JavaScript delay are paused site-wide. Turn it off to resume full optimization."
    17241866msgstr ""
    17251867
    1726 #: src/index.js:1514
     1868#: src/index.js:1670
    17271869msgid "Undo last change"
    17281870msgstr ""
    17291871
    1730 #: src/index.js:1518
     1872#: src/index.js:1674
    17311873msgid "Restores the settings from just before your last save, reset or Safe Mode toggle."
    17321874msgstr ""
    17331875
    1734 #: src/index.js:1519
     1876#: src/index.js:1675
    17351877msgid "Nothing to undo yet — this becomes available after your next change."
    17361878msgstr ""
    17371879
    1738 #: src/index.js:1523
     1880#: src/index.js:1679
    17391881msgid "Server Requirements"
    17401882msgstr ""
    17411883
    1742 #: src/index.js:1524
     1884#: src/index.js:1680
    17431885msgid "What your host supports. Warnings here explain why a feature (like WebP/AVIF) may be unavailable or slow."
    17441886msgstr ""
    17451887
    1746 #: src/index.js:1525
     1888#: src/index.js:1681
    17471889msgid "Checking your server…"
    17481890msgstr ""
    17491891
    1750 #: src/index.js:1526
     1892#: src/index.js:1682
    17511893msgid "Could not read the server report."
    17521894msgstr ""
    17531895
    1754 #: src/index.js:1542
     1896#: src/index.js:1698
    17551897msgid "Re-check"
    17561898msgstr ""
    17571899
    1758 #: src/index.js:1546
     1900#: src/index.js:1702
    17591901msgid "Import / Export Settings"
    17601902msgstr ""
    17611903
    1762 #: src/index.js:1547
     1904#: src/index.js:1703
    17631905msgid "Back up your configuration or copy it to another site."
    17641906msgstr ""
    17651907
    1766 #: src/index.js:1550
     1908#: src/index.js:1706
    17671909msgid "Export settings"
    17681910msgstr ""
    17691911
    1770 #: src/index.js:1553
     1912#: src/index.js:1709
    17711913msgid "Import settings"
    17721914msgstr ""
    17731915
    1774 #: src/index.js:1558
     1916#: src/index.js:1714
    17751917msgid "Reset"
    17761918msgstr ""
    17771919
    1778 #: src/index.js:1559
     1920#: src/index.js:1715
    17791921msgid "Restore every module to its default configuration. This cannot be undone."
    17801922msgstr ""
    17811923
    1782 #: src/index.js:1562
     1924#: src/index.js:1718
    17831925msgid "Reset all settings to defaults"
    17841926msgstr ""
    17851927
    1786 #: src/index.js:1571
     1928#: src/index.js:1727
    17871929msgid "Swap, preconnect, and preload fonts to remove render-blocking requests."
    17881930msgstr ""
    17891931
    1790 #: src/index.js:1574
     1932#: src/index.js:1730
    17911933msgid "Font loading (font-display)"
    17921934msgstr ""
    17931935
    1794 #: src/index.js:1575
     1936#: src/index.js:1731
    17951937msgid "Optional = no layout shift (best for CLS): the web font is skipped on the first uncached load and used once cached. Swap = text stays visible but the web font swaps in and can shift layout (higher CLS). Block = brief invisible text, then the web font."
    17961938msgstr ""
    17971939
    1798 #: src/index.js:1578
     1940#: src/index.js:1734
    17991941msgid "Optional — no layout shift (recommended for CLS)"
    18001942msgstr ""
    18011943
    1802 #: src/index.js:1579
     1944#: src/index.js:1735
    18031945msgid "Swap — always show text (may shift layout)"
    18041946msgstr ""
    18051947
    1806 #: src/index.js:1580
     1948#: src/index.js:1736
    18071949msgid "Block — brief hidden text, then web font"
    18081950msgstr ""
    18091951
    1810 #: src/index.js:1584
     1952#: src/index.js:1740
    18111953msgid "Combine Google Fonts into one request"
    18121954msgstr ""
    18131955
    1814 #: src/index.js:1585
     1956#: src/index.js:1741
    18151957msgid "Page builders often load each Google Font family separately. This merges them into a single request."
    18161958msgstr ""
    18171959
    1818 #: src/index.js:1589
     1960#: src/index.js:1745
    18191961msgid "Load only the font weights your pages use"
    18201962msgstr ""
    18211963
    1822 #: src/index.js:1590
     1964#: src/index.js:1746
    18231965msgid "Drops unused weights (e.g. 100, 200, 900) and italics your pages never use — often cutting font CSS by more than half. Weights 400 and 700 are always kept. Turn off if some text looks thinner or bolder than intended."
    18241966msgstr ""
    18251967
    1826 #: src/index.js:1594
     1968#: src/index.js:1751
     1969msgid "Load Google Fonts without blocking the page"
     1970msgstr ""
     1971
     1972#: src/index.js:1752
     1973msgid "The page appears right away in a fallback font and switches to your Google Font as soon as it arrives, instead of waiting for Google first. A big win for the first paint on mobile. Applies when font display is Swap or Fallback; with Optional the stylesheet keeps loading first so text never shifts."
     1974msgstr ""
     1975
     1976#: src/index.js:1756
    18271977msgid "Preload these font files (URLs)"
    18281978msgstr ""
    18291979
    18301980#. translators: %d: number of items found in the database.
    1831 #: src/index.js:1605
     1981#: src/index.js:1767
    18321982#, js-format
    18331983msgid "%d item in your database."
    … …  
    18361986msgstr[1] ""
    18371987
    1838 #: src/index.js:1610
     1988#: src/index.js:1772
    18391989msgid "Database Optimization"
    18401990msgstr ""
    18411991
    1842 #: src/index.js:1611
     1992#: src/index.js:1773
    18431993msgid "Reduce bloat and reclaim space. Once a cleanup runs it cannot be undone, so back up your database first."
    18441994msgstr ""
    18451995
    1846 #: src/index.js:1627
     1996#: src/index.js:1789
    18471997msgid "Set it and forget it"
    18481998msgstr ""
    18491999
    1850 #: src/index.js:1628
     2000#: src/index.js:1790
    18512001msgid "Let Optimazz repeat the cleanups you picked above on a schedule, so your database stays lean without you lifting a finger."
    18522002msgstr ""
    18532003
    1854 #: src/index.js:1629
     2004#: src/index.js:1791
    18552005msgid "Schedule automatic cleanup"
    18562006msgstr ""
    18572007
    1858 #: src/index.js:1630
     2008#: src/index.js:1792
    18592009msgid "Uses WP-Cron to repeat the cleanups you enabled above."
    18602010msgstr ""
    18612011
    1862 #: src/index.js:1633
     2012#: src/index.js:1795
    18632013msgid "Cleanup frequency"
    18642014msgstr ""
    18652015
    1866 #: src/index.js:1637
     2016#: src/index.js:1799
    18672017msgid "Daily"
    18682018msgstr ""
    18692019
    1870 #: src/index.js:1638
     2020#: src/index.js:1800
    18712021msgid "Weekly"
    18722022msgstr ""
    18732023
    1874 #: src/index.js:1639
     2024#: src/index.js:1801
    18752025msgid "Monthly"
    18762026msgstr ""
    18772027
    1878 #: src/index.js:1646
     2028#: src/index.js:1808
    18792029msgid "Back up your database before you run a cleanup — a database optimization cannot be undone."
    18802030msgstr ""
    18812031
    1882 #: src/index.js:1651
     2032#: src/index.js:1813
    18832033msgid "Optimize now"
    18842034msgstr ""
    18852035
    1886 #: src/index.js:1654
     2036#: src/index.js:1816
    18872037msgid "Refresh counts"
    18882038msgstr ""
    18892039
    1890 #: src/index.js:1661
     2040#: src/index.js:1823
    18912041msgid "Heartbeat Control"
    18922042msgstr ""
    18932043
    1894 #: src/index.js:1662
     2044#: src/index.js:1824
    18952045msgid "Reduce or disable the WordPress Heartbeat API to lower server load."
    18962046msgstr ""
    18972047
    1898 #: src/index.js:1663
     2048#: src/index.js:1825
    18992049msgid "Enable Heartbeat control"
    19002050msgstr ""
    19012051
    1902 #: src/index.js:1667
     2052#: src/index.js:1829
    19032053msgid "Behavior in admin dashboard"
    19042054msgstr ""
    19052055
    1906 #: src/index.js:1669
     2056#: src/index.js:1831
    19072057msgid "Behavior in post editor"
    19082058msgstr ""
    19092059
    1910 #: src/index.js:1671
     2060#: src/index.js:1833
    19112061msgid "Behavior on front-end"
    19122062msgstr ""
    19132063
    1914 #: src/index.js:1680
     2064#: src/index.js:1842
    19152065msgid "Strip unnecessary WordPress front-end weight."
    19162066msgstr ""
    19172067
    1918 #: src/index.js:1682
     2068#: src/index.js:1844
    19192069msgid "Disable emojis"
    19202070msgstr ""
    19212071
    1922 #: src/index.js:1683
     2072#: src/index.js:1845
    19232073msgid "Remove the emoji detection script and styles."
    19242074msgstr ""
    19252075
    1926 #: src/index.js:1685
     2076#: src/index.js:1847
    19272077msgid "Disable embeds (oEmbed)"
    19282078msgstr ""
    19292079
    1930 #: src/index.js:1687
     2080#: src/index.js:1849
    19312081msgid "Disable dashicons on front-end"
    19322082msgstr ""
    19332083
    1934 #: src/index.js:1688
     2084#: src/index.js:1850
    19352085msgid "Only for anonymous visitors."
    19362086msgstr ""
    19372087
    1938 #: src/index.js:1690
     2088#: src/index.js:1852
    19392089msgid "Remove query strings from assets"
    19402090msgstr ""
    19412091
    1942 #: src/index.js:1691
     2092#: src/index.js:1853
    19432093msgid "Strip ?ver= for better proxy caching."
    19442094msgstr ""
    19452095
    1946 #: src/index.js:1693
     2096#: src/index.js:1855
    19472097msgid "Disable XML-RPC"
    19482098msgstr ""
    19492099
    1950 #: src/index.js:1695
     2100#: src/index.js:1857
    19512101msgid "Disable self-pingbacks"
    19522102msgstr ""
    19532103
    1954 #: src/index.js:1697
     2104#: src/index.js:1859
    19552105msgid "Remove head meta (generator, RSD, shortlink)"
    19562106msgstr ""
    19572107
    1958 #: src/index.js:1699
     2108#: src/index.js:1861
    19592109msgid "DNS-prefetch domains"
    19602110msgstr ""
    19612111
    1962 #: src/index.js:1701
     2112#: src/index.js:1863
    19632113msgid "Preconnect origins"
    19642114msgstr ""
    19652115
    1966 #: src/index.js:1707
     2116#: src/index.js:1869
    19672117msgid "Content Delivery Network"
    19682118msgstr ""
    19692119
    1970 #: src/index.js:1708
     2120#: src/index.js:1870
    19712121msgid "Rewrite static asset URLs to your CDN hostname."
    19722122msgstr ""
    19732123
    1974 #: src/index.js:1710
     2124#: src/index.js:1872
    19752125msgid "CDN URL (CNAME)"
    19762126msgstr ""
    19772127
    1978 #: src/index.js:1713
     2128#: src/index.js:1875
    19792129msgid "CSS, JS, image, and font URLs will be served from here."
    19802130msgstr ""
    19812131
    1982 #: src/index.js:1714
     2132#: src/index.js:1876
    19832133msgid "Exclude paths containing"
    19842134msgstr ""
    19852135
    1986 #: src/index.js:1781
    1987 msgid "You have unsaved changes"
    1988 msgstr ""
    1989 
    1990 #: src/index.js:1781
    1991 msgid "All changes saved"
    1992 msgstr ""
    1993 
    1994 #: src/index.js:1784
    1995 msgid "Save changes"
    1996 msgstr ""
     2136#: src/index.js:1952
     2137msgid "Preloading cache…"
     2138msgstr ""
     2139
     2140#: src/index.js:2012
     2141#: src/index.js:2025
     2142msgid "Something went wrong."
     2143msgstr ""
     2144
     2145#: src/index.js:2059
     2146msgid "Network settings saved."
     2147msgstr ""
     2148
     2149#: src/index.js:2075
     2150msgid "Caching for every site"
     2151msgstr ""
     2152
     2153#: src/index.js:2076
     2154msgid "Sites"
     2155msgstr ""
     2156
     2157#: src/index.js:2076
     2158msgid "Cache per site"
     2159msgstr ""
     2160
     2161#: src/index.js:2086
     2162msgid "Optimazz is not network-activated"
     2163msgstr ""
     2164
     2165#: src/index.js:2087
     2166msgid "Only the sites where Optimazz is activated are cached. Network-activate it on Network Admin → Plugins to cache every site."
     2167msgstr ""
     2168
     2169#: src/index.js:2092
     2170msgid "Serve ready-made HTML for every site instead of running PHP on each request. Each site keeps its own cache and its own exclusions."
     2171msgstr ""
     2172
     2173#: src/index.js:2100
     2174msgid "Add Expires / Cache-Control rules to the shared .htaccess so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only."
     2175msgstr ""
     2176
     2177#: src/index.js:2103
     2178msgid "How long a cached page is kept. Pages are always purged as soon as their content changes."
     2179msgstr ""
     2180
     2181#: src/index.js:2113
     2182msgid "Preload every site automatically"
     2183msgstr ""
     2184
     2185#: src/index.js:2114
     2186msgid "Each site warms its own pages on a schedule and right after its cache is cleared."
     2187msgstr ""
     2188
     2189#. translators: %s: name of another caching plugin.
     2190#: src/index.js:2135
     2191#, js-format
     2192msgid "%s controls page caching (advanced-cache.php)"
     2193msgstr ""
     2194
     2195#: src/index.js:2138
     2196msgid "Optimazz cannot cache while another plugin owns the cache file. Deactivate that plugin, or let Optimazz take over."
     2197msgstr ""
     2198
     2199#: src/index.js:2139
     2200msgid "Optimazz now handles page caching."
     2201msgstr ""
     2202
     2203#: src/index.js:2146
     2204msgid "Cache engine installed (advanced-cache.php)"
     2205msgstr ""
     2206
     2207#: src/index.js:2147
     2208msgid "WP_CACHE is on in wp-config.php"
     2209msgstr ""
     2210
     2211#: src/index.js:2148
     2212msgid "wp-config.php is not writable — add define( 'WP_CACHE', true ); yourself"
     2213msgstr ""
     2214
     2215#: src/index.js:2149
     2216msgid "The wp-content folder is not writable"
     2217msgstr ""
     2218
     2219#: src/index.js:2167
     2220msgid "All sites"
     2221msgstr ""
     2222
     2223#. translators: 1: sites with Optimazz, 2: all sites.
     2224#: src/index.js:2170
     2225#, js-format
     2226msgid "Optimazz runs on %1$d of %2$d sites. Each site has its own cache."
     2227msgstr ""
     2228
     2229#: src/index.js:2172
     2230msgid "Page caching is off for the network — turn it on under Page Cache to purge or preload sites."
     2231msgstr ""
     2232
     2233#: src/index.js:2175
     2234msgid "Every site’s cache was purged."
     2235msgstr ""
     2236
     2237#: src/index.js:2176
     2238msgid "Purge all sites"
     2239msgstr ""
     2240
     2241#: src/index.js:2194
     2242msgid "Site cache purged."
     2243msgstr ""
     2244
     2245#: src/index.js:2215
     2246msgid "Optimazz is not active on this site, so its pages are not cached."
     2247msgstr ""
     2248
     2249#: src/index.js:2225
     2250msgid "Network"
     2251msgstr ""
  • optimazz/trunk/readme.txt

    r3723776 r3743505  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.0.24
     7Stable tag: 1.1.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    4949
    5050== Frequently Asked Questions ==
     51
     52= Does Optimazz work on WordPress multisite? =
     53
     54Yes. Network-activate it, then switch page caching on for every site in Network Admin → Optimazz (super admins only). Each site gets its own cache and keeps its own settings and cache exclusions. You can also activate Optimazz on individual sites only; then only those sites are cached.
    5155
    5256= Does Optimazz work alongside another caching plugin? =
    … …  
    100104
    101105== Changelog ==
     106
     107= 1.1.0 =
     108* Fixed: Google Fonts never loaded on sites whose theme or plugin requests them in the old weight format through the new Google Fonts address (for example "Open Sans:400,700" or "w400,700"). Google rejects those requests, so the font was missing while the broken stylesheet still held back the first paint on mobile by most of a second. Optimazz now repairs them into a valid request.
     109* New: Google Fonts load without blocking the page (on by default, Fonts settings). With font display Swap or Fallback the page appears right away in a fallback font and switches when the web font arrives, instead of waiting for Google first. With Optional (zero layout shift) the stylesheet still loads first, so text never shifts.
     110* Improved: sites using Jetpack's Site Accelerator load WordPress core stylesheets from c0.wp.com, which Optimazz used to skip as external, so they stayed render-blocking. When the version matches your WordPress, they are now minified, combined and loaded like local files.
     111* Improved: more chat widgets are delayed until the visitor interacts — OpenWidget, Zendesk, Drift, Freshchat / Freshworks, JivoChat, Smartsupp and Small Chat — cutting mobile Total Blocking Time.
     112* New: full multisite support. Network-activate Optimazz and manage page caching for the whole network in Network Admin → Optimazz: switch it on once, choose mobile and logged-in caching, cache lifetime, browser-cache rules and automatic preload, and see every site with its cached pages and its own Purge and Preload buttons. Each site gets its own cache — on subdirectory and subdomain networks — keeps its own exclusions (Advanced Rules) and all its other Optimazz settings, and can purge and preload its own pages.
     113* Improved: on multisite only the network administrator controls the files every site shares (wp-config.php, advanced-cache.php, .htaccess); a site's own settings never change them, so one site can't switch caching on or off for the others.
     114* Fixed: before this version, on multisite one site's settings controlled caching for every site, only one site of a subdomain network was actually cached, any site administrator could rewrite wp-config.php and .htaccess, and deactivating Optimazz on one site removed the cache for the whole network.
     115* Fixed: deleting Optimazz from a multisite network only cleaned up the main site; settings, scheduled tasks, image data and generated CSS of the other sites were left behind. Uninstall now cleans every site.
     116* Fixed: on sites using plain permalinks (addresses like ?page_id=5) the "Cached pages" count showed 1 no matter how many pages were cached.
     117* Improved: the preload summary now names the home page and counts each address once, so its parts add up to the total.
     118* Improved: changing a cache rule (for example a new exclusion) now also clears pages that were cached under the old rules.
     119* Improved: the Changelog section of this readme now lists recent versions only; the complete history is in changelog.txt.
    102120
    103121= 1.0.24 =
    … …  
    153171== Upgrade Notice ==
    154172
     173= 1.1.0 =
     174Multisite network support: manage page caching for every site from Network Admin → Optimazz, with a separate cache per site. Also a faster mobile first paint (Google Fonts) and many fixes. Recommended.
     175
    155176= 1.0.24 =
    156177Scheduled cache preloading now runs on time even when every visitor gets a cached page, works behind 6G/7G firewalls, and the cache rebuilds itself after you change settings. Also warns you when another caching plugin is blocking the Optimazz page cache. Recommended.
  • optimazz/trunk/src/index.js

    r3723128 r3743505  
    5858const NAV_ALL = [ ...NAV, ...PRO_SECTIONS ];
    5959
     60// wp_localize_script() sends top-level scalars as strings: false arrives as ""
     61// and true as "1", so test for every "off" form.
     62const isOn = ( v ) => ! [ false, '', '0', 0, undefined, null ].includes( v );
     63// Single site: this site controls page caching itself. On multisite a super
     64// admin switches it on for the whole network (Network Admin → Optimazz) and
     65// each site keeps its own cache, exclusions, purge and preload.
     66const SERVER_FEATURES = ! [ false, '', '0', 0 ].includes( window.optimazzData?.serverFeatures );
     67const NETWORK_CACHE = window.optimazzData?.networkCache || null;
     68const NETWORK_CACHE_ON = ! SERVER_FEATURES && !! NETWORK_CACHE && isOn( NETWORK_CACHE.enabled );
     69const CACHE_AVAILABLE = SERVER_FEATURES || NETWORK_CACHE_ON;
     70const IS_SUPER_ADMIN = isOn( window.optimazzData?.isSuperAdmin );
     71const NETWORK_URL = window.optimazzData?.networkAdminUrl || '';
     72
     73const formatBytes = ( b ) => {
     74        if ( ! b ) return '0 KB';
     75        const u = [ 'B', 'KB', 'MB', 'GB' ];
     76        const i = Math.floor( Math.log( b ) / Math.log( 1024 ) );
     77        return `${ ( b / Math.pow( 1024, i ) ).toFixed( i ? 1 : 0 ) } ${ u[ i ] }`;
     78};
     79
     80const intervalLabel = ( v ) => ( {
     81        hourly: __( 'every hour', 'optimazz' ),
     82        twicedaily: __( 'twice a day', 'optimazz' ),
     83        daily: __( 'once a day', 'optimazz' ),
     84}[ v ] || __( 'twice a day', 'optimazz' ) );
     85
     86const lifespanLabel = ( h ) => {
     87        const map = {
     88                0: __( 'never expires', 'optimazz' ),
     89                12: __( '12 hours', 'optimazz' ),
     90                24: __( '1 day', 'optimazz' ),
     91                168: __( '1 week', 'optimazz' ),
     92                720: __( '1 month', 'optimazz' ),
     93        };
     94        /* translators: %d: number of hours. */
     95        return map[ Number( h ) ] ?? sprintf( _n( '%d hour', '%d hours', Number( h ), 'optimazz' ), Number( h ) );
     96};
     97
     98function NetworkLink() {
     99        if ( ! IS_SUPER_ADMIN || ! NETWORK_URL ) {
     100                return null;
     101        }
     102        return <a className="oz-cachewarn__link" href={ NETWORK_URL }>{ __( 'Open Network Admin → Optimazz', 'optimazz' ) }</a>;
     103}
     104
     105// Multisite, network caching off.
     106function MultisiteCacheNotice() {
     107        return (
     108                <div className="oz-cachewarn" role="status">
     109                        <span className="dashicons dashicons-info-outline" />
     110                        <div className="oz-cachewarn__text">
     111                                <strong>{ __( 'Page caching is off for this network', 'optimazz' ) }</strong>
     112                                <p>{ IS_SUPER_ADMIN
     113                                        ? __( 'Turn it on for every site in Network Admin → Optimazz. Each site gets its own cache, and keeps its own exclusions here under Advanced Rules.', 'optimazz' )
     114                                        : __( 'Your network administrator can turn it on for every site in Network Admin → Optimazz. All other optimizations — CSS, JavaScript, images, fonts and more — work normally on this site.', 'optimazz' ) }</p>
     115                                <NetworkLink />
     116                        </div>
     117                </div>
     118        );
     119}
     120
     121// Multisite, network caching on: this site's Page Cache screen.
     122function NetworkCacheOn() {
     123        const n = NETWORK_CACHE || {};
     124        return (
     125                <div className="oz-cachewarn oz-cachewarn--ok" role="status">
     126                        <span className="dashicons dashicons-yes-alt" />
     127                        <div className="oz-cachewarn__text">
     128                                <strong>{ __( 'Page caching is on — managed for the whole network', 'optimazz' ) }</strong>
     129                                <p>{ sprintf(
     130                                        /* translators: 1: yes/no, 2: cache lifetime, 3: preload frequency. */
     131                                        __( 'Separate mobile cache: %1$s · Cache lifetime: %2$s · Automatic preload: %3$s.', 'optimazz' ),
     132                                        isOn( n.cache_mobile ) ? __( 'yes', 'optimazz' ) : __( 'no', 'optimazz' ),
     133                                        lifespanLabel( n.lifespan_hours ),
     134                                        isOn( n.preload ) ? intervalLabel( n.preload_interval ) : __( 'off', 'optimazz' )
     135                                ) }</p>
     136                                <p>{ __( 'This site has its own cache. Purge or preload it from the Dashboard or the Preload screen, and set its own exclusions under Advanced Rules.', 'optimazz' ) }</p>
     137                                <NetworkLink />
     138                        </div>
     139                </div>
     140        );
     141}
    60142const MODULES = [ 'cache', 'js', 'css', 'media', 'fonts', 'database', 'heartbeat', 'tweaks', 'cdn' ];
    61143
    … …  
    407489
    408490/* ------------------------------------------------------------------ */
     491
     492/**
     493 * The Optimazz admin layout shared by the site screen and Network Admin:
     494 * brand + sidebar navigation, page header, content, save bar and toast.
     495 */
     496function AppShell( { className = '', items, nav, onNav, foot, topIcon, title, sub, dirty, saving, onSave, toast, onToastRemove, children, after } ) {
     497        return (
     498                <div className={ `oz-shell ${ className }` }>
     499                        <aside className="oz-sidebar">
     500                                <div className="oz-sidebar__brand">
     501                                        { window.optimazzData?.logoUrl ? (
     502                                                <img className="oz-logo-img" src={ window.optimazzData.logoUrl } alt="Optimazz" />
     503                                        ) : (
     504                                                <div className="oz-sidebar__brand-fallback">
     505                                                        <span className="oz-logo dashicons dashicons-performance" />
     506                                                        <strong>Optimazz</strong>
     507                                                </div>
     508                                        ) }
     509                                </div>
     510                                <nav>
     511                                        { items.map( ( n ) => (
     512                                                <button key={ n.key } className={ `oz-navitem ${ nav === n.key ? 'is-active' : '' }` } onClick={ () => onNav( n.key ) }>
     513                                                        <Icon name={ n.svg || n.key } paths={ n.iconSvg } />
     514                                                        <span className="oz-navitem__text">
     515                                                                <strong>{ n.title }</strong>
     516                                                                <small>{ n.sub }</small>
     517                                                        </span>
     518                                                        { n.dot && <i className="oz-navitem__dot" /> }
     519                                                </button>
     520                                        ) ) }
     521                                </nav>
     522                                <div className="oz-sidebar__foot">{ foot }</div>
     523                        </aside>
     524
     525                        <div className="oz-content">
     526                                <header className="oz-topbar">
     527                                        <span className={ `oz-topbar__icon dashicons dashicons-${ topIcon }` } />
     528                                        <div>
     529                                                <h1>{ title }</h1>
     530                                                <p>{ sub }</p>
     531                                        </div>
     532                                </header>
     533                                <main className="oz-main">{ children }</main>
     534                        </div>
     535
     536                        <div className={ `oz-savebar ${ dirty ? 'is-dirty' : '' }` }>
     537                                <span className="oz-savebar__hint">
     538                                        { dirty ? __( 'You have unsaved changes', 'optimazz' ) : __( 'All changes saved', 'optimazz' ) }
     539                                </span>
     540                                <Button className="oz-btn-primary" isBusy={ saving } disabled={ ! dirty || saving } onClick={ onSave }>
     541                                        { __( 'Save changes', 'optimazz' ) }
     542                                </Button>
     543                        </div>
     544
     545                        { toast && (
     546                                <div className="oz-toast">
     547                                        <Snackbar status={ toast.status } onRemove={ onToastRemove }>
     548                                                { toast.text }
     549                                        </Snackbar>
     550                                </div>
     551                        ) }
     552                        { after }
     553                </div>
     554        );
     555}
    409556
    410557function App() {
    … …  
    9191066        };
    9201067
    921         const fmtBytes = ( b ) => {
    922                 if ( ! b ) return '0 KB';
    923                 const u = [ 'B', 'KB', 'MB', 'GB' ];
    924                 const i = Math.floor( Math.log( b ) / Math.log( 1024 ) );
    925                 return `${ ( b / Math.pow( 1024, i ) ).toFixed( i ? 1 : 0 ) } ${ u[ i ] }`;
    926         };
    927 
    928         const enabledCount = MODULES.filter( ( k ) => settings[ k ]?.enabled ).length;
     1068        const fmtBytes = formatBytes;
     1069
     1070        // On multisite the page cache is unavailable, so it is not counted.
     1071        const usableModules = SERVER_FEATURES ? MODULES : MODULES.filter( ( k ) => k !== 'cache' );
     1072        const enabledCount = usableModules.filter( ( k ) => settings[ k ]?.enabled ).length;
    9291073        const c = settings.cache || {};
    9301074        const j = settings.js || {};
    … …  
    9421086        // Live performance score — weighted by which optimizations are enabled.
    9431087        const scoreParts = [
    944                 { on: !! c.enabled, w: 30, label: __( 'Page caching', 'optimazz' ) },
     1088                { on: !! c.enabled, w: 30, label: __( 'Page caching', 'optimazz' ), server: true },
    9451089                { on: !! ( j.enabled && j.delay ), w: 18, label: __( 'Delay JavaScript', 'optimazz' ) },
    9461090                { on: !! ( j.enabled && j.defer ), w: 8, label: __( 'Defer JavaScript', 'optimazz' ) },
    … …  
    9491093                { on: !! ( m.enabled && ( m.webp || m.webp_generate ) ), w: 8, label: __( 'Next-gen images', 'optimazz' ) },
    9501094                { on: !! ( f.enabled && f.display_swap ), w: 6, label: __( 'Font optimization', 'optimazz' ) },
    951                 { on: !! c.preload, w: 5, label: __( 'Cache preloading', 'optimazz' ) },
     1095                { on: !! c.preload, w: 5, label: __( 'Cache preloading', 'optimazz' ), server: true },
    9521096                { on: !! h.enabled, w: 3, label: __( 'Heartbeat control', 'optimazz' ) },
    9531097        ];
    954         const score = scoreParts.reduce( ( a, p ) => a + ( p.on ? p.w : 0 ), 0 );
    955         const nextWin = scoreParts.filter( ( p ) => ! p.on ).sort( ( a, b ) => b.w - a.w )[ 0 ];
     1098        const usableParts = CACHE_AVAILABLE ? scoreParts : scoreParts.filter( ( p ) => p.server !== true );
     1099        const maxScore = usableParts.reduce( ( a, p ) => a + p.w, 0 ) || 1;
     1100        const score = Math.round( ( usableParts.reduce( ( a, p ) => a + ( p.on ? p.w : 0 ), 0 ) / maxScore ) * 100 );
     1101        const nextWin = usableParts.filter( ( p ) => ! p.on ).sort( ( a, b ) => b.w - a.w )[ 0 ];
    9561102        const grade = score >= 90 ? 'A' : score >= 75 ? 'B' : score >= 55 ? 'C' : score >= 35 ? 'D' : 'E';
    9571103        const ring = 2 * Math.PI * 52;
    9581104
    959         const optimizingWithoutCache = ( j.enabled || s.enabled || m.enabled ) && ! c.enabled;
     1105        const optimizingWithoutCache = SERVER_FEATURES && ( j.enabled || s.enabled || m.enabled ) && ! c.enabled;
    9601106
    9611107        const dashboard = (
    … …  
    9971143                                        </p>
    9981144                                        <div className="oz-score__chips">
    999                                                 { scoreParts.map( ( p ) => (
     1145                                                { usableParts.map( ( p ) => (
    10001146                                                        <span key={ p.label } className={ `oz-chip ${ p.on ? 'is-on' : '' }` }>
    10011147                                                                <span className={ `dashicons dashicons-${ p.on ? 'yes-alt' : 'marker' }` } />
    … …  
    10071153                        </div>
    10081154
    1009                         <div className="oz-kpis">
    1010                                 <div className="oz-kpi">
     1155                        <div className={ `oz-kpis${ CACHE_AVAILABLE ? '' : ' oz-kpis--single' }` }>
     1156                                { CACHE_AVAILABLE && <div className="oz-kpi">
    10111157                                        <span className="dashicons dashicons-database" />
    10121158                                        <strong>{ stats ? ( stats.pages ?? stats.files ) : <Spinner /> }</strong>
    10131159                                        <span>{ __( 'Cached pages', 'optimazz' ) }</span>
    1014                                 </div>
    1015                                 <div className="oz-kpi">
     1160                                </div> }
     1161                                { CACHE_AVAILABLE && <div className="oz-kpi">
    10161162                                        <span className="dashicons dashicons-chart-area" />
    10171163                                        <strong>{ stats ? fmtBytes( stats.bytes ) : <Spinner /> }</strong>
    10181164                                        <span>{ __( 'Cache size', 'optimazz' ) }</span>
    1019                                 </div>
     1165                                </div> }
    10201166                                <div className="oz-kpi">
    10211167                                        <span className="dashicons dashicons-yes-alt" />
    1022                                         <strong>{ enabledCount }/{ MODULES.length }</strong>
     1168                                        <strong>{ enabledCount }/{ usableModules.length }</strong>
    10231169                                        <span>{ __( 'Modules active', 'optimazz' ) }</span>
    10241170                                </div>
    … …  
    10471193
    10481194                        <div className="oz-quick">
    1049                                 <Panel title={ __( 'Cache Files', 'optimazz' ) } description={ __( 'Clear and preload all cache files.', 'optimazz' ) }>
     1195                                { CACHE_AVAILABLE && <Panel title={ __( 'Cache Files', 'optimazz' ) } description={ __( 'Clear and preload all cache files.', 'optimazz' ) }>
    10501196                                        <div className="oz-quick__actions">
    10511197                                                <Button className="oz-btn-primary" icon="trash" isBusy={ !! job?.running } onClick={ async () => { await purge(); preload(); } }>
    … …  
    11321278                                                </li>
    11331279                                        </ul>
    1134                                 </Panel>
     1280                                </Panel> }
    11351281                                <Panel title={ __( 'Module Status', 'optimazz' ) } description={ __( 'Enable or disable any module right here.', 'optimazz' ) }>
    11361282                                        <ul className="oz-status">
    1137                                                 { NAV.filter( ( n ) => n.mod ).map( ( n ) => (
     1283                                                { NAV.filter( ( n ) => n.mod && ( SERVER_FEATURES || n.mod !== 'cache' ) ).map( ( n ) => (
    11381284                                                        <li key={ n.key }>
    11391285                                                                <Icon name={ n.key } />
    … …  
    11681314        );
    11691315
    1170         const cacheSection = (
     1316        const cacheSection = ! SERVER_FEATURES ? ( NETWORK_CACHE_ON ? <NetworkCacheOn /> : <MultisiteCacheNotice /> ) : (
    11711317                <Card icon="database" title={ __( 'Page Cache', 'optimazz' ) }
    11721318                        description={ __( 'Serve static HTML instead of running PHP on every request.', 'optimazz' ) }
    … …  
    14481594        );
    14491595
    1450         const preloadSection = (
     1596        const preloadSection = ! CACHE_AVAILABLE ? <MultisiteCacheNotice /> : (
    14511597                <Panel title={ __( 'Preload Cache', 'optimazz' ) }
    14521598                        description={ __( 'Warm the cache automatically so visitors never hit a cold page.', 'optimazz' ) }>
     1599                        { SERVER_FEATURES ? ( <>
    14531600                        <ToggleControl __nextHasNoMarginBottom label={ __( 'Activate preloading', 'optimazz' ) }
    14541601                                help={ __( 'Warms the cache on a schedule and right after every full purge (for example after changing a setting). Warming follows the sitemap — home, pages, posts, custom post types, then the busiest category and tag archives — most-important first, for both mobile and desktop.', 'optimazz' ) }
    … …  
    14651612                                ] }
    14661613                                onChange={ ( v ) => set( 'cache', 'preload_interval', v ) } />
     1614                        </> ) : (
     1615                                <p className="oz-muted">{ isOn( NETWORK_CACHE?.preload )
     1616                                        ? sprintf(
     1617                                                /* translators: %s: preload frequency, e.g. "twice a day". */
     1618                                                __( 'Automatic preload runs %s for this site (set for the whole network in Network Admin → Optimazz).', 'optimazz' ),
     1619                                                intervalLabel( NETWORK_CACHE?.preload_interval )
     1620                                        )
     1621                                        : __( 'Automatic preload is off for the network. You can still preload this site now.', 'optimazz' ) }</p>
     1622                        ) }
    14671623                        { stats && stats.wp_cron_disabled && (
    14681624                                <div className="oz-notice oz-notice--warn" style={ { marginTop: 12 } }>
    … …  
    14791635        );
    14801636
    1481         const advancedSection = (
     1637        const advancedSection = ! CACHE_AVAILABLE ? <MultisiteCacheNotice /> : (
    14821638                <Panel title={ __( 'Advanced Rules', 'optimazz' ) }
    14831639                        description={ __( 'Fine-tune which requests are cached.', 'optimazz' ) }>
    … …  
    15921748                                disabled={ ! f.combine_google }
    15931749                                onChange={ ( v ) => set( 'fonts', 'trim_weights', v ) } />
     1750                        <ToggleControl __nextHasNoMarginBottom
     1751                                label={ __( 'Load Google Fonts without blocking the page', 'optimazz' ) }
     1752                                help={ __( 'The page appears right away in a fallback font and switches to your Google Font as soon as it arrives, instead of waiting for Google first. A big win for the first paint on mobile. Applies when font display is Swap or Fallback; with Optional the stylesheet keeps loading first so text never shifts.', 'optimazz' ) }
     1753                                checked={ f.async_google !== false }
     1754                                disabled={ ! f.combine_google }
     1755                                onChange={ ( v ) => set( 'fonts', 'async_google', v ) } />
    15941756                        <FormTokenField label={ __( 'Preload these font files (URLs)', 'optimazz' ) }
    15951757                                value={ f.preload || [] } onChange={ ( v ) => set( 'fonts', 'preload', v ) } />
    … …  
    17391901
    17401902        return (
    1741                 <div className="oz-shell">
    1742                         <aside className="oz-sidebar">
    1743                                 <div className="oz-sidebar__brand">
    1744                                         { window.optimazzData?.logoUrl ? (
    1745                                                 <img className="oz-logo-img" src={ window.optimazzData.logoUrl } alt="Optimazz" />
    1746                                         ) : (
    1747                                                 <div className="oz-sidebar__brand-fallback">
    1748                                                         <span className="oz-logo dashicons dashicons-performance" />
    1749                                                         <strong>Optimazz</strong>
    1750                                                 </div>
    1751                                         ) }
    1752                                 </div>
    1753                                 <nav>
    1754                                         { NAV_ALL.map( ( n ) => (
    1755                                                 <button key={ n.key } className={ `oz-navitem ${ nav === n.key ? 'is-active' : '' }` } onClick={ () => setNav( n.key ) }>
    1756                                                         <Icon name={ n.key } paths={ n.iconSvg } />
    1757                                                         <span className="oz-navitem__text">
    1758                                                                 <strong>{ n.title }</strong>
    1759                                                                 <small>{ n.sub }</small>
    1760                                                         </span>
    1761                                                         { n.mod && settings[ n.mod ]?.enabled && <i className="oz-navitem__dot" /> }
    1762                                                 </button>
    1763                                         ) ) }
    1764                                 </nav>
    1765                                 <div className="oz-sidebar__foot">v{ window.optimazzData?.version }</div>
    1766                         </aside>
    1767 
    1768                         <div className="oz-content">
    1769                                 <header className="oz-topbar">
    1770                                         <span className={ `oz-topbar__icon dashicons dashicons-${ active.icon }` } />
    1771                                         <div>
    1772                                                 <h1>{ active.title }</h1>
    1773                                                 <p>{ active.sub }</p>
    1774                                         </div>
    1775                                 </header>
    1776                                 <main className="oz-main">{ sections[ nav ] }</main>
    1777                         </div>
    1778 
    1779                         <div className={ `oz-savebar ${ dirty ? 'is-dirty' : '' }` }>
    1780                                 <span className="oz-savebar__hint">
    1781                                         { dirty ? __( 'You have unsaved changes', 'optimazz' ) : __( 'All changes saved', 'optimazz' ) }
    1782                                 </span>
    1783                                 <Button className="oz-btn-primary" isBusy={ saving } disabled={ ! dirty } onClick={ save }>
    1784                                         { __( 'Save changes', 'optimazz' ) }
    1785                                 </Button>
    1786                         </div>
    1787 
    1788                         { toast && (
    1789                                 <div className="oz-toast">
    1790                                         <Snackbar status={ toast.status } onRemove={ () => setToast( null ) }>
    1791                                                 { toast.text }
    1792                                         </Snackbar>
    1793                                 </div>
    1794                         ) }
    1795 
    1796                         <JobPanel
    1797                                 job={ job }
    1798                                 onStop={ stopJob }
    1799                                 onClose={ closeJob }
    1800                                 onToggleMin={ () => setJob( ( j ) => j && { ...j, minimized: ! j.minimized } ) }
    1801                         />
    1802                 </div>
     1903                <AppShell
     1904                        items={ NAV_ALL.map( ( n ) => ( { ...n, dot: !! ( n.mod && settings[ n.mod ]?.enabled ) } ) ) }
     1905                        nav={ nav } onNav={ setNav }
     1906                        foot={ `v${ window.optimazzData?.version }` }
     1907                        topIcon={ active.icon } title={ active.title } sub={ active.sub }
     1908                        dirty={ dirty } saving={ saving } onSave={ save }
     1909                        toast={ toast } onToastRemove={ () => setToast( null ) }
     1910                        after={
     1911                                <JobPanel
     1912                                        job={ job }
     1913                                        onStop={ stopJob }
     1914                                        onClose={ closeJob }
     1915                                        onToggleMin={ () => setJob( ( j ) => j && { ...j, minimized: ! j.minimized } ) }
     1916                                />
     1917                        }
     1918                >
     1919                        { sections[ nav ] }
     1920                </AppShell>
    18031921        );
    18041922}
    … …  
    18081926        createRoot( mount ).render( <App /> );
    18091927}
     1928
     1929/* ------------------------------------------------------------------ *
     1930 *  Network Admin → Optimazz (multisite, super admins)
     1931 * ------------------------------------------------------------------ */
     1932
     1933const NET_DEFAULTS = { enabled: false, cache_mobile: true, cache_logged_in: false, lifespan_hours: 0, preload: true, preload_interval: 'twicedaily', browser_cache: false };
     1934
     1935function NetworkApp() {
     1936        const [ data, setData ] = useState( null );
     1937        const [ draft, setDraft ] = useState( NET_DEFAULTS );
     1938        const [ dirty, setDirty ] = useState( false );
     1939        const [ saving, setSaving ] = useState( false );
     1940        const [ busy, setBusy ] = useState( '' );
     1941        const [ notice, setNotice ] = useState( null );
     1942        const [ nav, setNav ] = useState( 'cache' );
     1943        const [ job, setJob ] = useState( null );
     1944        const stopRef = useRef( false );
     1945
     1946        // Live preload of one site, in the same progress panel as the site screen.
     1947        const runPreload = async ( site ) => {
     1948                if ( job?.running ) {
     1949                        return;
     1950                }
     1951                stopRef.current = false;
     1952                const opening = __( 'Preloading cache…', 'optimazz' );
     1953                setJob( { title: __( 'Preloading cache', 'optimazz' ), subtitle: site.name || site.url, pct: 0, label: opening, step: '', logs: [ { level: 'INFO', text: opening } ], running: true, status: 'running', minimized: false } );
     1954                const acc = { cached: 0, missed: 0, diagnosis: '' };
     1955                const started = Date.now();
     1956                let offset = 0;
     1957                let total = 0;
     1958                const end = ( status, level, text, extra = {} ) => setJob( ( j ) => j && { ...j, running: false, status, ...extra, logs: [ ...j.logs, { level, text } ] } );
     1959                try {
     1960                        // eslint-disable-next-line no-constant-condition
     1961                        while ( true ) {
     1962                                if ( stopRef.current ) {
     1963                                        end( 'stopped', 'WARN', __( 'Stopped by user.', 'optimazz' ) );
     1964                                        break;
     1965                                }
     1966                                const res = await apiFetch( { url: `${ restUrl }/network/preload-batch`, method: 'POST', data: { site_id: site.id, offset } } );
     1967                                const prev = offset;
     1968                                offset = res.next_offset ?? offset + ( res.processed || 0 );
     1969                                total = res.total || total;
     1970                                acc.cached += res.cached || 0;
     1971                                acc.missed += res.missed || 0;
     1972                                acc.diagnosis = res.diagnosis || acc.diagnosis;
     1973                                const pct = total ? Math.min( 100, Math.round( ( offset / total ) * 100 ) ) : 100;
     1974                                const line = `${ __( 'Warmed', 'optimazz' ) } ${ Math.min( offset, total ) } / ${ total } ${ __( 'pages', 'optimazz' ) } · ${ acc.cached } ${ __( 'cached', 'optimazz' ) }${ acc.missed ? ` · ${ acc.missed } ${ __( 'not cached', 'optimazz' ) }` : '' }`;
     1975                                const rate = offset / Math.max( ( Date.now() - started ) / 1000, 0.001 );
     1976                                const left = rate > 0 && total && offset < total ? ( total - offset ) / rate : 0;
     1977                                const eta = left >= 60 ? ` · ~${ Math.round( left / 60 ) }m ${ __( 'left', 'optimazz' ) }` : ( left >= 1 ? ` · ~${ Math.round( left ) }s ${ __( 'left', 'optimazz' ) }` : '' );
     1978                                setJob( ( j ) => j && {
     1979                                        ...j,
     1980                                        pct,
     1981                                        label: line,
     1982                                        step: total ? `${ Math.min( offset, total ) } / ${ total }${ eta }` : '',
     1983                                        logs: [
     1984                                                ...j.logs.slice( -60 ),
     1985                                                ...( res.breakdown ? [ { level: 'INFO', text: `${ sprintf(
     1986                                                        /* translators: %d: number of pages to warm. */
     1987                                                        __( 'Found %d pages to warm:', 'optimazz' ), res.total || 0
     1988                                                ) } ${ res.breakdown }.` } ] : [] ),
     1989                                                { level: 'INFO', text: line },
     1990                                        ],
     1991                                } );
     1992                                if ( res.blocked ) {
     1993                                        end( 'error', 'ERROR', __( 'Warming stopped: the server returned 403/429, so a firewall or security plugin is blocking the preload requests. Cached what we could; allow-list the "Optimazz-CacheWarmer" user-agent in your firewall or security plugin (e.g. All-In-One Security, Wordfence, Cloudflare), or slow warming with the optimazz_preload_delay_ms filter.', 'optimazz' ) );
     1994                                        break;
     1995                                }
     1996                                if ( ! res.done && acc.cached === 0 && acc.missed >= 3 ) {
     1997                                        end( 'error', 'ERROR', `${ __( 'Stopped: pages are loading but none are being saved to the cache.', 'optimazz' ) } ${ acc.diagnosis }`, { label: __( 'Pages are not being cached', 'optimazz' ) } );
     1998                                        break;
     1999                                }
     2000                                if ( res.done || offset <= prev ) {
     2001                                        if ( acc.missed ) {
     2002                                                setJob( ( j ) => j && { ...j, logs: [ ...j.logs, { level: 'WARN', text: `${ sprintf(
     2003                                                        /* translators: %d: number of pages. */
     2004                                                        _n( '%d page was not cached.', '%d pages were not cached.', acc.missed, 'optimazz' ), acc.missed
     2005                                                ) } ${ acc.diagnosis }` } ] } );
     2006                                        }
     2007                                        end( 'done', 'DONE', __( 'Completed.', 'optimazz' ), { pct: 100 } );
     2008                                        break;
     2009                                }
     2010                        }
     2011                } catch ( e ) {
     2012                        end( 'error', 'ERROR', e?.message || __( 'Something went wrong.', 'optimazz' ) );
     2013                }
     2014                // Refresh the cached-page counts on the cards.
     2015                apiFetch( { url: `${ restUrl }/network` } ).then( ( res ) => {
     2016                        setData( res );
     2017                } ).catch( () => {} );
     2018        };
     2019
     2020        const apply = ( res ) => {
     2021                setData( res );
     2022                setDraft( { ...NET_DEFAULTS, ...( res.settings || {} ) } );
     2023                setDirty( false );
     2024        };
     2025        const fail = ( e ) => setNotice( { status: 'error', text: e?.message || __( 'Something went wrong.', 'optimazz' ) } );
     2026        const call = async ( path, body, key, okText ) => {
     2027                setBusy( key );
     2028                try {
     2029                        const res = await apiFetch( { url: `${ restUrl }${ path }`, method: 'POST', data: body } );
     2030                        // Keep any unsaved Page Cache edits when acting on sites.
     2031                        setData( res );
     2032                        if ( ! dirty ) {
     2033                                setDraft( { ...NET_DEFAULTS, ...( res.settings || {} ) } );
     2034                        }
     2035                        if ( okText ) {
     2036                                setNotice( { status: 'success', text: okText } );
     2037                        }
     2038                        setBusy( '' );
     2039                        return true;
     2040                } catch ( e ) {
     2041                        fail( e );
     2042                }
     2043                setBusy( '' );
     2044                return false;
     2045        };
     2046
     2047        useEffect( () => {
     2048                apiFetch( { url: `${ restUrl }/network` } ).then( apply ).catch( fail );
     2049        }, [] );
     2050
     2051        const set = ( key, value ) => {
     2052                setDraft( ( d ) => ( { ...d, [ key ]: value } ) );
     2053                setDirty( true );
     2054        };
     2055        const save = async () => {
     2056                setSaving( true );
     2057                try {
     2058                        apply( await apiFetch( { url: `${ restUrl }/network/settings`, method: 'POST', data: { settings: draft } } ) );
     2059                        setNotice( { status: 'success', text: __( 'Network settings saved.', 'optimazz' ) } );
     2060                } catch ( e ) {
     2061                        fail( e );
     2062                }
     2063                setSaving( false );
     2064        };
     2065
     2066        if ( ! data ) {
     2067                return <div className="oz-shell oz-shell--network-app"><div className="oz-main"><Spinner /></div></div>;
     2068        }
     2069        const h = data.health || {};
     2070        const sites = data.sites || [];
     2071        const cached = sites.filter( ( s ) => s.active );
     2072        const on = !! data.settings?.enabled;
     2073
     2074        const NET_NAV = [
     2075                { key: 'cache', svg: 'cache', icon: 'database', title: __( 'Page Cache', 'optimazz' ), sub: __( 'Caching for every site', 'optimazz' ), dot: on },
     2076                { key: 'sites', svg: 'dashboard', icon: 'admin-multisite', title: __( 'Sites', 'optimazz' ), sub: __( 'Cache per site', 'optimazz' ) },
     2077        ];
     2078        const active = NET_NAV.find( ( n ) => n.key === nav ) || NET_NAV[ 0 ];
     2079
     2080        const cacheSection = (
     2081                <>
     2082                        { ! data.network_active && (
     2083                                <div className="oz-cachewarn" role="status">
     2084                                        <span className="dashicons dashicons-info-outline" />
     2085                                        <div className="oz-cachewarn__text">
     2086                                                <strong>{ __( 'Optimazz is not network-activated', 'optimazz' ) }</strong>
     2087                                                <p>{ __( 'Only the sites where Optimazz is activated are cached. Network-activate it on Network Admin → Plugins to cache every site.', 'optimazz' ) }</p>
     2088                                        </div>
     2089                                </div>
     2090                        ) }
     2091                        <Card icon="database" title={ __( 'Page Cache', 'optimazz' ) }
     2092                                description={ __( 'Serve ready-made HTML for every site instead of running PHP on each request. Each site keeps its own cache and its own exclusions.', 'optimazz' ) }
     2093                                enabled={ !! draft.enabled } onToggle={ () => set( 'enabled', ! draft.enabled ) }>
     2094                                <ToggleControl __nextHasNoMarginBottom label={ __( 'Cache mobile devices separately', 'optimazz' ) }
     2095                                        checked={ !! draft.cache_mobile } onChange={ () => set( 'cache_mobile', ! draft.cache_mobile ) } />
     2096                                <ToggleControl __nextHasNoMarginBottom label={ __( 'Cache logged-in users', 'optimazz' ) }
     2097                                        help={ __( 'Leave off unless you know what you are doing.', 'optimazz' ) }
     2098                                        checked={ !! draft.cache_logged_in } onChange={ () => set( 'cache_logged_in', ! draft.cache_logged_in ) } />
     2099                                <ToggleControl __nextHasNoMarginBottom label={ __( 'Browser cache headers', 'optimazz' ) }
     2100                                        help={ __( 'Add Expires / Cache-Control rules to the shared .htaccess so repeat visits load CSS, JS, images and fonts from the browser cache. Apache and LiteSpeed only.', 'optimazz' ) }
     2101                                        checked={ !! draft.browser_cache } onChange={ () => set( 'browser_cache', ! draft.browser_cache ) } />
     2102                                <PillGroup label={ __( 'Cache lifetime', 'optimazz' ) }
     2103                                        help={ __( 'How long a cached page is kept. Pages are always purged as soon as their content changes.', 'optimazz' ) }
     2104                                        value={ Number( draft.lifespan_hours ) || 0 }
     2105                                        options={ [
     2106                                                { value: 0, label: __( 'Never', 'optimazz' ) },
     2107                                                { value: 12, label: __( '12 hours', 'optimazz' ) },
     2108                                                { value: 24, label: __( '1 day', 'optimazz' ) },
     2109                                                { value: 168, label: __( '1 week', 'optimazz' ) },
     2110                                                { value: 720, label: __( '1 month', 'optimazz' ) },
     2111                                        ] }
     2112                                        onChange={ ( v ) => set( 'lifespan_hours', v ) } />
     2113                                <ToggleControl __nextHasNoMarginBottom label={ __( 'Preload every site automatically', 'optimazz' ) }
     2114                                        help={ __( 'Each site warms its own pages on a schedule and right after its cache is cleared.', 'optimazz' ) }
     2115                                        checked={ !! draft.preload } onChange={ () => set( 'preload', ! draft.preload ) } />
     2116                                <SelectControl __nextHasNoMarginBottom __next40pxDefaultSize
     2117                                        label={ __( 'Automatic preload', 'optimazz' ) }
     2118                                        value={ draft.preload_interval || 'twicedaily' }
     2119                                        disabled={ ! draft.preload }
     2120                                        options={ [
     2121                                                { label: __( 'Every hour', 'optimazz' ), value: 'hourly' },
     2122                                                { label: __( 'Twice a day', 'optimazz' ), value: 'twicedaily' },
     2123                                                { label: __( 'Once a day', 'optimazz' ), value: 'daily' },
     2124                                        ] }
     2125                                        onChange={ ( v ) => set( 'preload_interval', v ) } />
     2126                        </Card>
     2127                        { ( on || h.conflict ) && (
     2128                                <Panel title={ __( 'Cache health', 'optimazz' ) }>
     2129                                        { h.conflict ? (
     2130                                                <div className="oz-cachewarn" role="status">
     2131                                                        <span className="dashicons dashicons-warning" />
     2132                                                        <div className="oz-cachewarn__text">
     2133                                                                <strong>{ sprintf(
     2134                                                                        /* translators: %s: name of another caching plugin. */
     2135                                                                        __( '%s controls page caching (advanced-cache.php)', 'optimazz' ),
     2136                                                                        h.conflict_name || __( 'Another plugin', 'optimazz' )
     2137                                                                ) }</strong>
     2138                                                                <p>{ __( 'Optimazz cannot cache while another plugin owns the cache file. Deactivate that plugin, or let Optimazz take over.', 'optimazz' ) }</p>
     2139                                                                <Button className="oz-btn-primary" isBusy={ 'takeover' === busy } onClick={ () => call( '/network/takeover', {}, 'takeover', __( 'Optimazz now handles page caching.', 'optimazz' ) ) }>
     2140                                                                        { __( 'Use Optimazz for caching', 'optimazz' ) }
     2141                                                                </Button>
     2142                                                        </div>
     2143                                                </div>
     2144                                        ) : (
     2145                                                <>
     2146                                                        <HealthRow ok={ h.dropin_ours } label={ __( 'Cache engine installed (advanced-cache.php)', 'optimazz' ) } />
     2147                                                        <HealthRow ok={ h.wp_cache } label={ __( 'WP_CACHE is on in wp-config.php', 'optimazz' ) } />
     2148                                                        { ! h.wpconfig_writable && ! h.wp_cache && <HealthRow ok={ false } label={ __( 'wp-config.php is not writable — add define( \'WP_CACHE\', true ); yourself', 'optimazz' ) } /> }
     2149                                                        { ! h.content_writable && <HealthRow ok={ false } label={ __( 'The wp-content folder is not writable', 'optimazz' ) } /> }
     2150                                                </>
     2151                                        ) }
     2152                                </Panel>
     2153                        ) }
     2154                </>
     2155        );
     2156
     2157        // Clear this site's cache, then warm it live in the progress panel.
     2158        const clearAndPreload = async ( site ) => {
     2159                const ok = await call( '/network/purge', { site_id: site.id }, `clear-${ site.id }` );
     2160                if ( ok ) {
     2161                        runPreload( site );
     2162                }
     2163        };
     2164
     2165        const sitesSection = (
     2166                <>
     2167                        <Panel title={ __( 'All sites', 'optimazz' ) }
     2168                                description={ sprintf(
     2169                                        /* translators: 1: sites with Optimazz, 2: all sites. */
     2170                                        __( 'Optimazz runs on %1$d of %2$d sites. Each site has its own cache.', 'optimazz' ), cached.length, sites.length
     2171                                ) }>
     2172                                { ! on && <p className="oz-muted">{ __( 'Page caching is off for the network — turn it on under Page Cache to purge or preload sites.', 'optimazz' ) }</p> }
     2173                                <div className="oz-quick__actions">
     2174                                        <Button className="oz-btn-ghost oz-btn--x" icon="dismiss" disabled={ ! on } isBusy={ 'purge-all' === busy }
     2175                                                onClick={ () => call( '/network/purge', {}, 'purge-all', __( 'Every site’s cache was purged.', 'optimazz' ) ) }>
     2176                                                { __( 'Purge all sites', 'optimazz' ) }
     2177                                        </Button>
     2178                                </div>
     2179                        </Panel>
     2180                        <div className="oz-quick oz-network__sites">
     2181                                { sites.map( ( s ) => (
     2182                                        <Panel key={ s.id } title={ s.name || s.url } description={ s.url }>
     2183                                                { s.active ? (
     2184                                                        <>
     2185                                                                <div className="oz-quick__actions">
     2186                                                                        <Button className="oz-btn-primary" icon="trash" disabled={ ! on || !! job?.running } isBusy={ `clear-${ s.id }` === busy } onClick={ () => clearAndPreload( s ) }>
     2187                                                                                { __( 'Clear & Preload', 'optimazz' ) }
     2188                                                                        </Button>
     2189                                                                        <Button className="oz-btn-ghost" icon="update" disabled={ ! on || !! job?.running }
     2190                                                                                onClick={ () => runPreload( s ) }>
     2191                                                                                { __( 'Preload', 'optimazz' ) }
     2192                                                                        </Button>
     2193                                                                        <Button className="oz-btn-ghost oz-btn--x" icon="dismiss" disabled={ ! on } isBusy={ `purge-${ s.id }` === busy }
     2194                                                                                onClick={ () => call( '/network/purge', { site_id: s.id }, `purge-${ s.id }`, __( 'Site cache purged.', 'optimazz' ) ) }>
     2195                                                                                { __( 'Purge cache', 'optimazz' ) }
     2196                                                                        </Button>
     2197                                                                        <Button className="oz-btn-ghost" icon="admin-generic" href={ s.admin_url }>
     2198                                                                                { __( 'Settings', 'optimazz' ) }
     2199                                                                        </Button>
     2200                                                                </div>
     2201                                                                <ul className="oz-cachelist">
     2202                                                                        <li>
     2203                                                                                <span className="dashicons dashicons-media-text" />
     2204                                                                                { __( 'Cached pages', 'optimazz' ) }
     2205                                                                                <strong>{ s.pages }</strong>
     2206                                                                        </li>
     2207                                                                        <li>
     2208                                                                                <span className="dashicons dashicons-database" />
     2209                                                                                { __( 'Page cache size', 'optimazz' ) }
     2210                                                                                <strong>{ formatBytes( s.bytes ) }</strong>
     2211                                                                        </li>
     2212                                                                </ul>
     2213                                                        </>
     2214                                                ) : (
     2215                                                        <p className="oz-muted">{ __( 'Optimazz is not active on this site, so its pages are not cached.', 'optimazz' ) }</p>
     2216                                                ) }
     2217                                        </Panel>
     2218                                ) ) }
     2219                        </div>
     2220                </>
     2221        );
     2222        return (
     2223                <AppShell className="oz-shell--network-app"
     2224                        items={ NET_NAV } nav={ nav } onNav={ setNav }
     2225                        foot={ `${ __( 'Network', 'optimazz' ) } · v${ window.optimazzData?.version }` }
     2226                        topIcon={ active.icon } title={ active.title } sub={ active.sub }
     2227                        dirty={ dirty } saving={ saving } onSave={ save }
     2228                        toast={ notice } onToastRemove={ () => setNotice( null ) }
     2229                        after={
     2230                                <JobPanel
     2231                                        job={ job }
     2232                                        onStop={ () => {
     2233                                                stopRef.current = true;
     2234                                        } }
     2235                                        onClose={ () => {
     2236                                                stopRef.current = true;
     2237                                                setJob( null );
     2238                                        } }
     2239                                        onToggleMin={ () => setJob( ( j ) => j && { ...j, minimized: ! j.minimized } ) }
     2240                                />
     2241                        }
     2242                >
     2243                        { 'sites' === nav ? sitesSection : cacheSection }
     2244                </AppShell>
     2245        );
     2246}
     2247
     2248const networkMount = document.getElementById( 'optimazz-network-app' );
     2249if ( networkMount ) {
     2250        createRoot( networkMount ).render( <NetworkApp /> );
     2251}
  • optimazz/trunk/src/index.scss

    r3723128 r3743505  
    402402        grid-template-columns: repeat(3, 1fr);
    403403        gap: 14px;
     404
     405        // Multisite: only "Modules active" remains, so it spans the row.
     406        &--single {
     407                grid-template-columns: 1fr;
     408        }
    404409}
    405410
    … …  
    18021807                .dashicons { color: #e0900f; }
    18031808        }
     1809        &--ok {
     1810                background: #eaf8f3;
     1811                border-color: #b8e6d6;
     1812                color: #0f5e48;
     1813                .dashicons { color: #0e9f7e; }
     1814        }
    18041815}
    18051816
    … …  
    18821893        }
    18831894}
     1895
     1896/* ------------------------------------------------------------------ *
     1897 *  Multisite: network-managed cache states + Network Admin screen
     1898 * ------------------------------------------------------------------ */
     1899.oz-cachewarn--ok {
     1900        background: #eaf8f3;
     1901        border-color: #b8e6d6;
     1902        color: #0f5e48;
     1903}
     1904
     1905.oz-cachewarn__text p + p {
     1906        margin-top: 6px;
     1907}
     1908
     1909.oz-cachewarn__link {
     1910        display: inline-block;
     1911        margin-top: 8px;
     1912        font-weight: 600;
     1913        color: inherit;
     1914        text-decoration: underline;
     1915}
     1916
     1917// Network → Sites: one Cache Files-style card per site, two per row.
     1918.oz-network__sites {
     1919        margin-top: 16px;
     1920        align-items: start;
     1921
     1922        .oz-panel__head h2,
     1923        .oz-panel__head p {
     1924                overflow-wrap: anywhere;
     1925        }
     1926}
  • optimazz/trunk/uninstall.php

    r3723128 r3743505  
    1818// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
    1919
    20 global $wpdb;
     20// Per-site data (options, transients, image meta, scheduled events). On a
     21// multisite network uninstall runs once, so it is repeated for every site.
     22$optimazz_cleanup_site = static function () {
     23        global $wpdb;
    2124
    22 // 1. Settings option (and the single-level undo backup).
    23 delete_option( 'optimazz_settings' );
    24 delete_option( 'optimazz_settings_backup' );
    25 delete_option( 'optimazz_version' ); // Post-update reconciliation marker.
    26 delete_option( 'optimazz_guard' );   // Update safety guard state.
    27 delete_option( 'optimazz_last_preload' ); // Last auto-preload run (dashboard).
    28 delete_option( 'optimazz_preload_cursor' ); // Position of a half-finished automatic preload.
    29 delete_transient( 'optimazz_preload_urls' ); // Cached preload URL list.
    30 delete_transient( 'optimazz_preload_urls_breakdown' ); // Counts behind the preload list.
    31 delete_transient( 'optimazz_preload_lock' ); // Running-chunk lock.
    32 delete_metadata( 'user', 0, 'optimazz_conflict_dismissed', '', true ); // Dismissed cache-conflict notice.
    33 wp_clear_scheduled_hook( 'optimazz_health_check' );
     25        // 1. Settings option (and the single-level undo backup).
     26        delete_option( 'optimazz_settings' );
     27        delete_option( 'optimazz_settings_backup' );
     28        delete_option( 'optimazz_version' ); // Post-update reconciliation marker.
     29        delete_option( 'optimazz_guard' );   // Update safety guard state.
     30        delete_option( 'optimazz_last_preload' ); // Last auto-preload run (dashboard).
     31        delete_option( 'optimazz_preload_cursor' ); // Position of a half-finished automatic preload.
     32        delete_transient( 'optimazz_preload_urls' ); // Cached preload URL list.
     33        delete_transient( 'optimazz_preload_urls_breakdown' ); // Counts behind the preload list.
     34        delete_transient( 'optimazz_preload_lock' ); // Running-chunk lock.
     35        wp_clear_scheduled_hook( 'optimazz_health_check' );
    3436
    35 // 2. Per-image optimization meta.
    36 $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_optimazz_image' ) ); // phpcs:ignore WordPress.DB.SlowDBQuery
     37        // 2. Per-image optimization meta.
     38        $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_optimazz_image' ) ); // phpcs:ignore WordPress.DB.SlowDBQuery
    3739
    38 // 3. Scheduled events.
    39 foreach ( array( 'optimazz_preload_cache', 'optimazz_preload_cache_once', 'optimazz_purge_expired', 'optimazz_db_cleanup' ) as $hook ) {
    40         wp_clear_scheduled_hook( $hook );
    41 }
     40        // 3. Scheduled events.
     41        foreach ( array( 'optimazz_preload_cache', 'optimazz_preload_cache_once', 'optimazz_purge_expired', 'optimazz_db_cleanup' ) as $hook ) {
     42                wp_clear_scheduled_hook( $hook );
     43        }
    4244
    43 // 4. Transients (incl. the per-stylesheet font-weight cache).
    44 delete_transient( 'optimazz_wpconfig_unwritable' );
    45 $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-off cleanup of our own transients on uninstall.
    46         $wpdb->prepare(
    47                 "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
    48                 $wpdb->esc_like( '_transient_optimazz_fw_' ) . '%',
    49                 $wpdb->esc_like( '_transient_timeout_optimazz_fw_' ) . '%'
    50         )
    51 );
     45        // 4. Transients (incl. the per-stylesheet font-weight cache).
     46        delete_transient( 'optimazz_wpconfig_unwritable' );
     47        $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-off cleanup of our own transients on uninstall.
     48                $wpdb->prepare(
     49                        "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
     50                        $wpdb->esc_like( '_transient_optimazz_fw_' ) . '%',
     51                        $wpdb->esc_like( '_transient_timeout_optimazz_fw_' ) . '%'
     52                )
     53        );
     54};
     55
     56delete_metadata( 'user', 0, 'optimazz_conflict_dismissed', '', true ); // Dismissed cache-conflict notice (users are network-wide).
     57delete_site_option( 'optimazz_network' ); // Network page-cache settings (multisite).
     58delete_site_option( 'optimazz_network_version' );
     59delete_site_transient( 'optimazz_heal' ); // Self-heal throttle (multisite).
    5260
    5361// 5. advanced-cache.php drop-in (only if it is ours).
    … …  
    111119$optimazz_rrmdir( WP_CONTENT_DIR . '/cache/optimazz' );
    112120
    113 $uploads = wp_get_upload_dir();
    114 if ( ! empty( $uploads['basedir'] ) ) {
    115         $optimazz_rrmdir( trailingslashit( $uploads['basedir'] ) . 'optimazz-backup' );
    116         // Generated CSS now lives under uploads/optimazz (moved out of cache/).
    117         $optimazz_rrmdir( trailingslashit( $uploads['basedir'] ) . 'optimazz' );
     121// Generated CSS (uploads/optimazz) and original-image backups
     122// (uploads/optimazz-backup) live in each site's own uploads folder.
     123$optimazz_clean_uploads = static function () use ( $optimazz_rrmdir ) {
     124        $uploads = wp_get_upload_dir();
     125        if ( ! empty( $uploads['basedir'] ) ) {
     126                $optimazz_rrmdir( trailingslashit( $uploads['basedir'] ) . 'optimazz-backup' );
     127                $optimazz_rrmdir( trailingslashit( $uploads['basedir'] ) . 'optimazz' );
     128        }
     129};
     130// 8. Per-site cleanup — every site of a network in one pass (uninstall runs once).
     131if ( is_multisite() ) {
     132        foreach ( get_sites( array( 'fields' => 'ids', 'number' => 0 ) ) as $optimazz_site_id ) {
     133                switch_to_blog( $optimazz_site_id );
     134                $optimazz_cleanup_site();
     135                $optimazz_clean_uploads();
     136                restore_current_blog();
     137        }
     138} else {
     139        $optimazz_cleanup_site();
     140        $optimazz_clean_uploads();
    118141}
Note: See TracChangeset for help on using the changeset viewer.