Changeset 3454888
- Timestamp:
- 02/05/2026 06:51:20 PM (2 weeks ago)
- Location:
- faecursor
- Files:
-
- 598 added
- 5 edited
-
assets/screenshot-2.png (modified) (previous)
-
trunk/README.txt (modified) (3 diffs)
-
trunk/assets/css/fae-cursor-admin.css (modified) (18 diffs)
-
trunk/assets/effects/cursor (added)
-
trunk/assets/effects/cursor/bubbles-effect (added)
-
trunk/assets/effects/cursor/bubbles-effect/script.js (added)
-
trunk/assets/effects/cursor/bubbles-effect/style.css (added)
-
trunk/assets/effects/cursor/drop-effect (added)
-
trunk/assets/effects/cursor/drop-effect/script.js (added)
-
trunk/assets/effects/cursor/drop-effect/style.css (added)
-
trunk/assets/effects/cursor/duo-circle (added)
-
trunk/assets/effects/cursor/duo-circle-2 (added)
-
trunk/assets/effects/cursor/duo-circle-2/script.js (added)
-
trunk/assets/effects/cursor/duo-circle-2/styles.css (added)
-
trunk/assets/effects/cursor/duo-circle/script.js (added)
-
trunk/assets/effects/cursor/duo-circle/styles.css (added)
-
trunk/assets/effects/cursor/fireworks-effect (added)
-
trunk/assets/effects/cursor/fireworks-effect/script.js (added)
-
trunk/assets/effects/cursor/fireworks-effect/style.css (added)
-
trunk/assets/effects/cursor/flag-effect (added)
-
trunk/assets/effects/cursor/flag-effect/script.js (added)
-
trunk/assets/effects/cursor/flag-effect/style.css (added)
-
trunk/assets/effects/cursor/genuine-effect (added)
-
trunk/assets/effects/cursor/genuine-effect/script.js (added)
-
trunk/assets/effects/cursor/genuine-effect/style.css (added)
-
trunk/assets/effects/cursor/gradient-trail (added)
-
trunk/assets/effects/cursor/gradient-trail/script.js (added)
-
trunk/assets/effects/cursor/gradient-trail/style.css (added)
-
trunk/assets/effects/cursor/leaf-effect (added)
-
trunk/assets/effects/cursor/leaf-effect/FREEPIK_LICENSE.txt (added)
-
trunk/assets/effects/cursor/leaf-effect/script.js (added)
-
trunk/assets/effects/cursor/leaf-effect/style.css (added)
-
trunk/assets/effects/cursor/line-effect (added)
-
trunk/assets/effects/cursor/line-effect/script.js (added)
-
trunk/assets/effects/cursor/line-effect/style.css (added)
-
trunk/assets/effects/cursor/magic-trail (added)
-
trunk/assets/effects/cursor/magic-trail/script.js (added)
-
trunk/assets/effects/cursor/magic-trail/style.css (added)
-
trunk/assets/effects/cursor/rise-effect (added)
-
trunk/assets/effects/cursor/rise-effect/script.js (added)
-
trunk/assets/effects/cursor/rise-effect/style.css (added)
-
trunk/assets/effects/cursor/spark-effect (added)
-
trunk/assets/effects/cursor/spark-effect/script.js (added)
-
trunk/assets/effects/cursor/spark-effect/style.css (added)
-
trunk/assets/effects/keyboard (added)
-
trunk/assets/effects/keyboard/bubble-keys (added)
-
trunk/assets/effects/keyboard/bubble-keys/script.js (added)
-
trunk/assets/effects/keyboard/bubble-keys/style.css (added)
-
trunk/assets/effects/keyboard/ink-keys (added)
-
trunk/assets/effects/keyboard/ink-keys/script.js (added)
-
trunk/assets/effects/keyboard/ink-keys/style.css (added)
-
trunk/assets/effects/keyboard/matrix-keys (added)
-
trunk/assets/effects/keyboard/matrix-keys/script.js (added)
-
trunk/assets/effects/keyboard/matrix-keys/style.css (added)
-
trunk/assets/effects/keyboard/sparkle-keys (added)
-
trunk/assets/effects/keyboard/sparkle-keys/script.js (added)
-
trunk/assets/effects/keyboard/sparkle-keys/style.css (added)
-
trunk/assets/effects/particles (added)
-
trunk/assets/effects/particles/color-borrower (added)
-
trunk/assets/effects/particles/color-borrower/script.js (added)
-
trunk/assets/effects/particles/color-borrower/style.css (added)
-
trunk/assets/effects/particles/morph-grid (added)
-
trunk/assets/effects/particles/morph-grid/script.js (added)
-
trunk/assets/effects/particles/morph-grid/style.css (added)
-
trunk/assets/effects/particles/repel-cursor (added)
-
trunk/assets/effects/particles/repel-cursor/script.js (added)
-
trunk/assets/effects/particles/repel-cursor/style.css (added)
-
trunk/assets/effects/particles/snowfall (added)
-
trunk/assets/effects/particles/snowfall/script.js (added)
-
trunk/assets/effects/particles/snowfall/style.css (added)
-
trunk/assets/effects/particles/swirl-cursor (added)
-
trunk/assets/effects/particles/swirl-cursor/script.js (added)
-
trunk/assets/effects/particles/swirl-cursor/style.css (added)
-
trunk/assets/flags (added)
-
trunk/assets/flags/ad.svg (added)
-
trunk/assets/flags/ae.svg (added)
-
trunk/assets/flags/af.svg (added)
-
trunk/assets/flags/ag.svg (added)
-
trunk/assets/flags/ai.svg (added)
-
trunk/assets/flags/al.svg (added)
-
trunk/assets/flags/am.svg (added)
-
trunk/assets/flags/ao.svg (added)
-
trunk/assets/flags/aq.svg (added)
-
trunk/assets/flags/ar.svg (added)
-
trunk/assets/flags/arab.svg (added)
-
trunk/assets/flags/as.svg (added)
-
trunk/assets/flags/asean.svg (added)
-
trunk/assets/flags/at.svg (added)
-
trunk/assets/flags/au.svg (added)
-
trunk/assets/flags/aw.svg (added)
-
trunk/assets/flags/ax.svg (added)
-
trunk/assets/flags/az.svg (added)
-
trunk/assets/flags/ba.svg (added)
-
trunk/assets/flags/bb.svg (added)
-
trunk/assets/flags/bd.svg (added)
-
trunk/assets/flags/be.svg (added)
-
trunk/assets/flags/bf.svg (added)
-
trunk/assets/flags/bg.svg (added)
-
trunk/assets/flags/bh.svg (added)
-
trunk/assets/flags/bi.svg (added)
-
trunk/assets/flags/bj.svg (added)
-
trunk/assets/flags/bl.svg (added)
-
trunk/assets/flags/bm.svg (added)
-
trunk/assets/flags/bn.svg (added)
-
trunk/assets/flags/bo.svg (added)
-
trunk/assets/flags/bq.svg (added)
-
trunk/assets/flags/br.svg (added)
-
trunk/assets/flags/bs.svg (added)
-
trunk/assets/flags/bt.svg (added)
-
trunk/assets/flags/bv.svg (added)
-
trunk/assets/flags/bw.svg (added)
-
trunk/assets/flags/by.svg (added)
-
trunk/assets/flags/bz.svg (added)
-
trunk/assets/flags/ca.svg (added)
-
trunk/assets/flags/cc.svg (added)
-
trunk/assets/flags/cd.svg (added)
-
trunk/assets/flags/cefta.svg (added)
-
trunk/assets/flags/cf.svg (added)
-
trunk/assets/flags/cg.svg (added)
-
trunk/assets/flags/ch.svg (added)
-
trunk/assets/flags/ci.svg (added)
-
trunk/assets/flags/ck.svg (added)
-
trunk/assets/flags/cl.svg (added)
-
trunk/assets/flags/cm.svg (added)
-
trunk/assets/flags/cn.svg (added)
-
trunk/assets/flags/co.svg (added)
-
trunk/assets/flags/cp.svg (added)
-
trunk/assets/flags/cr.svg (added)
-
trunk/assets/flags/cu.svg (added)
-
trunk/assets/flags/cv.svg (added)
-
trunk/assets/flags/cw.svg (added)
-
trunk/assets/flags/cx.svg (added)
-
trunk/assets/flags/cy.svg (added)
-
trunk/assets/flags/cz.svg (added)
-
trunk/assets/flags/de.svg (added)
-
trunk/assets/flags/dg.svg (added)
-
trunk/assets/flags/dj.svg (added)
-
trunk/assets/flags/dk.svg (added)
-
trunk/assets/flags/dm.svg (added)
-
trunk/assets/flags/do.svg (added)
-
trunk/assets/flags/dz.svg (added)
-
trunk/assets/flags/eac.svg (added)
-
trunk/assets/flags/ec.svg (added)
-
trunk/assets/flags/ee.svg (added)
-
trunk/assets/flags/eg.svg (added)
-
trunk/assets/flags/eh.svg (added)
-
trunk/assets/flags/er.svg (added)
-
trunk/assets/flags/es-ct.svg (added)
-
trunk/assets/flags/es-ga.svg (added)
-
trunk/assets/flags/es-pv.svg (added)
-
trunk/assets/flags/es.svg (added)
-
trunk/assets/flags/et.svg (added)
-
trunk/assets/flags/eu.svg (added)
-
trunk/assets/flags/fi.svg (added)
-
trunk/assets/flags/fj.svg (added)
-
trunk/assets/flags/fk.svg (added)
-
trunk/assets/flags/fm.svg (added)
-
trunk/assets/flags/fo.svg (added)
-
trunk/assets/flags/fr.svg (added)
-
trunk/assets/flags/ga.svg (added)
-
trunk/assets/flags/gb-eng.svg (added)
-
trunk/assets/flags/gb-nir.svg (added)
-
trunk/assets/flags/gb-sct.svg (added)
-
trunk/assets/flags/gb-wls.svg (added)
-
trunk/assets/flags/gb.svg (added)
-
trunk/assets/flags/gd.svg (added)
-
trunk/assets/flags/ge.svg (added)
-
trunk/assets/flags/gf.svg (added)
-
trunk/assets/flags/gg.svg (added)
-
trunk/assets/flags/gh.svg (added)
-
trunk/assets/flags/gi.svg (added)
-
trunk/assets/flags/gl.svg (added)
-
trunk/assets/flags/gm.svg (added)
-
trunk/assets/flags/gn.svg (added)
-
trunk/assets/flags/gp.svg (added)
-
trunk/assets/flags/gq.svg (added)
-
trunk/assets/flags/gr.svg (added)
-
trunk/assets/flags/gs.svg (added)
-
trunk/assets/flags/gt.svg (added)
-
trunk/assets/flags/gu.svg (added)
-
trunk/assets/flags/gw.svg (added)
-
trunk/assets/flags/gy.svg (added)
-
trunk/assets/flags/hk.svg (added)
-
trunk/assets/flags/hm.svg (added)
-
trunk/assets/flags/hn.svg (added)
-
trunk/assets/flags/hr.svg (added)
-
trunk/assets/flags/ht.svg (added)
-
trunk/assets/flags/hu.svg (added)
-
trunk/assets/flags/ic.svg (added)
-
trunk/assets/flags/id.svg (added)
-
trunk/assets/flags/ie.svg (added)
-
trunk/assets/flags/il.svg (added)
-
trunk/assets/flags/im.svg (added)
-
trunk/assets/flags/in.svg (added)
-
trunk/assets/flags/io.svg (added)
-
trunk/assets/flags/iq.svg (added)
-
trunk/assets/flags/ir.svg (added)
-
trunk/assets/flags/is.svg (added)
-
trunk/assets/flags/it.svg (added)
-
trunk/assets/flags/je.svg (added)
-
trunk/assets/flags/jm.svg (added)
-
trunk/assets/flags/jo.svg (added)
-
trunk/assets/flags/jp.svg (added)
-
trunk/assets/flags/ke.svg (added)
-
trunk/assets/flags/kg.svg (added)
-
trunk/assets/flags/kh.svg (added)
-
trunk/assets/flags/ki.svg (added)
-
trunk/assets/flags/km.svg (added)
-
trunk/assets/flags/kn.svg (added)
-
trunk/assets/flags/kp.svg (added)
-
trunk/assets/flags/kr.svg (added)
-
trunk/assets/flags/kw.svg (added)
-
trunk/assets/flags/ky.svg (added)
-
trunk/assets/flags/kz.svg (added)
-
trunk/assets/flags/la.svg (added)
-
trunk/assets/flags/lb.svg (added)
-
trunk/assets/flags/lc.svg (added)
-
trunk/assets/flags/li.svg (added)
-
trunk/assets/flags/lk.svg (added)
-
trunk/assets/flags/lr.svg (added)
-
trunk/assets/flags/ls.svg (added)
-
trunk/assets/flags/lt.svg (added)
-
trunk/assets/flags/lu.svg (added)
-
trunk/assets/flags/lv.svg (added)
-
trunk/assets/flags/ly.svg (added)
-
trunk/assets/flags/ma.svg (added)
-
trunk/assets/flags/mc.svg (added)
-
trunk/assets/flags/md.svg (added)
-
trunk/assets/flags/me.svg (added)
-
trunk/assets/flags/mf.svg (added)
-
trunk/assets/flags/mg.svg (added)
-
trunk/assets/flags/mh.svg (added)
-
trunk/assets/flags/mk.svg (added)
-
trunk/assets/flags/ml.svg (added)
-
trunk/assets/flags/mm.svg (added)
-
trunk/assets/flags/mn.svg (added)
-
trunk/assets/flags/mo.svg (added)
-
trunk/assets/flags/mp.svg (added)
-
trunk/assets/flags/mq.svg (added)
-
trunk/assets/flags/mr.svg (added)
-
trunk/assets/flags/ms.svg (added)
-
trunk/assets/flags/mt.svg (added)
-
trunk/assets/flags/mu.svg (added)
-
trunk/assets/flags/mv.svg (added)
-
trunk/assets/flags/mw.svg (added)
-
trunk/assets/flags/mx.svg (added)
-
trunk/assets/flags/my.svg (added)
-
trunk/assets/flags/mz.svg (added)
-
trunk/assets/flags/na.svg (added)
-
trunk/assets/flags/nc.svg (added)
-
trunk/assets/flags/ne.svg (added)
-
trunk/assets/flags/nf.svg (added)
-
trunk/assets/flags/ng.svg (added)
-
trunk/assets/flags/ni.svg (added)
-
trunk/assets/flags/nl.svg (added)
-
trunk/assets/flags/no.svg (added)
-
trunk/assets/flags/np.svg (added)
-
trunk/assets/flags/nr.svg (added)
-
trunk/assets/flags/nu.svg (added)
-
trunk/assets/flags/nz.svg (added)
-
trunk/assets/flags/om.svg (added)
-
trunk/assets/flags/pa.svg (added)
-
trunk/assets/flags/pc.svg (added)
-
trunk/assets/flags/pe.svg (added)
-
trunk/assets/flags/pf.svg (added)
-
trunk/assets/flags/pg.svg (added)
-
trunk/assets/flags/ph.svg (added)
-
trunk/assets/flags/pk.svg (added)
-
trunk/assets/flags/pl.svg (added)
-
trunk/assets/flags/pm.svg (added)
-
trunk/assets/flags/pn.svg (added)
-
trunk/assets/flags/pr.svg (added)
-
trunk/assets/flags/ps.svg (added)
-
trunk/assets/flags/pt.svg (added)
-
trunk/assets/flags/pw.svg (added)
-
trunk/assets/flags/py.svg (added)
-
trunk/assets/flags/qa.svg (added)
-
trunk/assets/flags/re.svg (added)
-
trunk/assets/flags/ro.svg (added)
-
trunk/assets/flags/rs.svg (added)
-
trunk/assets/flags/ru.svg (added)
-
trunk/assets/flags/rw.svg (added)
-
trunk/assets/flags/sa.svg (added)
-
trunk/assets/flags/sb.svg (added)
-
trunk/assets/flags/sc.svg (added)
-
trunk/assets/flags/sd.svg (added)
-
trunk/assets/flags/se.svg (added)
-
trunk/assets/flags/sg.svg (added)
-
trunk/assets/flags/sh-ac.svg (added)
-
trunk/assets/flags/sh-hl.svg (added)
-
trunk/assets/flags/sh-ta.svg (added)
-
trunk/assets/flags/sh.svg (added)
-
trunk/assets/flags/si.svg (added)
-
trunk/assets/flags/sj.svg (added)
-
trunk/assets/flags/sk.svg (added)
-
trunk/assets/flags/sl.svg (added)
-
trunk/assets/flags/sm.svg (added)
-
trunk/assets/flags/sn.svg (added)
-
trunk/assets/flags/so.svg (added)
-
trunk/assets/flags/sr.svg (added)
-
trunk/assets/flags/ss.svg (added)
-
trunk/assets/flags/st.svg (added)
-
trunk/assets/flags/sv.svg (added)
-
trunk/assets/flags/sx.svg (added)
-
trunk/assets/flags/sy.svg (added)
-
trunk/assets/flags/sz.svg (added)
-
trunk/assets/flags/tc.svg (added)
-
trunk/assets/flags/td.svg (added)
-
trunk/assets/flags/tf.svg (added)
-
trunk/assets/flags/tg.svg (added)
-
trunk/assets/flags/th.svg (added)
-
trunk/assets/flags/tj.svg (added)
-
trunk/assets/flags/tk.svg (added)
-
trunk/assets/flags/tl.svg (added)
-
trunk/assets/flags/tm.svg (added)
-
trunk/assets/flags/tn.svg (added)
-
trunk/assets/flags/to.svg (added)
-
trunk/assets/flags/tr.svg (added)
-
trunk/assets/flags/tt.svg (added)
-
trunk/assets/flags/tv.svg (added)
-
trunk/assets/flags/tw.svg (added)
-
trunk/assets/flags/tz.svg (added)
-
trunk/assets/flags/ua.svg (added)
-
trunk/assets/flags/ug.svg (added)
-
trunk/assets/flags/um.svg (added)
-
trunk/assets/flags/un.svg (added)
-
trunk/assets/flags/us.svg (added)
-
trunk/assets/flags/uy.svg (added)
-
trunk/assets/flags/uz.svg (added)
-
trunk/assets/flags/va.svg (added)
-
trunk/assets/flags/vc.svg (added)
-
trunk/assets/flags/ve.svg (added)
-
trunk/assets/flags/vg.svg (added)
-
trunk/assets/flags/vi.svg (added)
-
trunk/assets/flags/vn.svg (added)
-
trunk/assets/flags/vu.svg (added)
-
trunk/assets/flags/wf.svg (added)
-
trunk/assets/flags/ws.svg (added)
-
trunk/assets/flags/xk.svg (added)
-
trunk/assets/flags/xx.svg (added)
-
trunk/assets/flags/ye.svg (added)
-
trunk/assets/flags/yt.svg (added)
-
trunk/assets/flags/za.svg (added)
-
trunk/assets/flags/zm.svg (added)
-
trunk/assets/flags/zw.svg (added)
-
trunk/assets/icons/icon.svg (added)
-
trunk/assets/js/fae-cursor-admin.js (modified) (10 diffs)
-
trunk/assets/js/fae-cursor-scope.js (added)
-
trunk/assets/seasonal (added)
-
trunk/assets/seasonal/valentines (added)
-
trunk/assets/seasonal/valentines/valentines.css (added)
-
trunk/assets/seasonal/valentines/valentines.php (added)
-
trunk/config (added)
-
trunk/config/fae-cursor-effects-config.js (added)
-
trunk/config/fae-cursor-effects-config.php (added)
-
trunk/config/fae-keyboard-effects-config.php (added)
-
trunk/config/fae-particle-effects-config.js (added)
-
trunk/config/fae-particle-effects-config.php (added)
-
trunk/faecursor.php (modified) (4 diffs)
-
trunk/includes (added)
-
trunk/includes/class-fae-cursor-admin.php (added)
-
trunk/includes/class-fae-cursor-conflict.php (added)
-
trunk/includes/class-fae-cursor-device.php (added)
-
trunk/includes/class-fae-cursor-enqueue.php (added)
-
trunk/includes/class-fae-cursor-loader.php (added)
-
trunk/includes/class-fae-cursor-pro.php (added)
-
trunk/includes/class-fae-cursor-scope.php (added)
-
trunk/includes/class-fae-cursor-settings.php (added)
-
trunk/includes/class-fae-keyboard-settings.php (added)
-
trunk/includes/class-fae-particle-settings.php (added)
-
trunk/includes/fae-cursor-helpers.php (added)
-
trunk/includes/views (added)
-
trunk/includes/views/admin-page.php (added)
-
trunk/includes/views/components (added)
-
trunk/includes/views/partials (added)
-
trunk/includes/views/preview-embed.php (added)
-
trunk/includes/views/preview-page.php (added)
-
trunk/vendor (added)
-
trunk/vendor/freemius (added)
-
trunk/vendor/freemius/LICENSE.txt (added)
-
trunk/vendor/freemius/README.md (added)
-
trunk/vendor/freemius/assets (added)
-
trunk/vendor/freemius/assets/css (added)
-
trunk/vendor/freemius/assets/css/admin (added)
-
trunk/vendor/freemius/assets/css/admin/account.css (added)
-
trunk/vendor/freemius/assets/css/admin/add-ons.css (added)
-
trunk/vendor/freemius/assets/css/admin/affiliation.css (added)
-
trunk/vendor/freemius/assets/css/admin/checkout.css (added)
-
trunk/vendor/freemius/assets/css/admin/clone-resolution.css (added)
-
trunk/vendor/freemius/assets/css/admin/common.css (added)
-
trunk/vendor/freemius/assets/css/admin/connect.css (added)
-
trunk/vendor/freemius/assets/css/admin/debug.css (added)
-
trunk/vendor/freemius/assets/css/admin/dialog-boxes.css (added)
-
trunk/vendor/freemius/assets/css/admin/gdpr-optin-notice.css (added)
-
trunk/vendor/freemius/assets/css/admin/index.php (added)
-
trunk/vendor/freemius/assets/css/admin/optout.css (added)
-
trunk/vendor/freemius/assets/css/admin/plugins.css (added)
-
trunk/vendor/freemius/assets/css/customizer.css (added)
-
trunk/vendor/freemius/assets/css/index.php (added)
-
trunk/vendor/freemius/assets/img (added)
-
trunk/vendor/freemius/assets/img/faecursor.png (added)
-
trunk/vendor/freemius/assets/img/index.php (added)
-
trunk/vendor/freemius/assets/img/plugin-icon.png (added)
-
trunk/vendor/freemius/assets/img/theme-icon.png (added)
-
trunk/vendor/freemius/assets/index.php (added)
-
trunk/vendor/freemius/assets/js (added)
-
trunk/vendor/freemius/assets/js/index.php (added)
-
trunk/vendor/freemius/assets/js/jquery.form.js (added)
-
trunk/vendor/freemius/assets/js/nojquery.ba-postmessage.js (added)
-
trunk/vendor/freemius/assets/js/postmessage.js (added)
-
trunk/vendor/freemius/assets/js/pricing (added)
-
trunk/vendor/freemius/assets/js/pricing/14fb1bd5b7c41648488b06147f50a0dc.svg (added)
-
trunk/vendor/freemius/assets/js/pricing/178afa6030e76635dbe835e111d2c507.png (added)
-
trunk/vendor/freemius/assets/js/pricing/27b5a722a5553d9de0170325267fccec.png (added)
-
trunk/vendor/freemius/assets/js/pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png (added)
-
trunk/vendor/freemius/assets/js/pricing/45da596e2b512ffc3bb638baaf0fdc4e.png (added)
-
trunk/vendor/freemius/assets/js/pricing/a34e046aee1702a5690679750a7f4d0f.svg (added)
-
trunk/vendor/freemius/assets/js/pricing/b09d0b38b627c2fa564d050f79f2f064.svg (added)
-
trunk/vendor/freemius/assets/js/pricing/c03f665db27af43971565560adfba594.png (added)
-
trunk/vendor/freemius/assets/js/pricing/cb5fc4f6ec7ada72e986f6e7dde365bf.png (added)
-
trunk/vendor/freemius/assets/js/pricing/d65812c447b4523b42d59018e1c0bb53.png (added)
-
trunk/vendor/freemius/assets/js/pricing/f3aac72a8e63997d6bb888f816457e9b.png (added)
-
trunk/vendor/freemius/assets/js/pricing/fde48e4609a6ddc11d639fc2421f2afd.png (added)
-
trunk/vendor/freemius/assets/js/pricing/freemius-pricing.js (added)
-
trunk/vendor/freemius/assets/js/pricing/freemius-pricing.js.LICENSE.txt (added)
-
trunk/vendor/freemius/composer.json (added)
-
trunk/vendor/freemius/config.php (added)
-
trunk/vendor/freemius/includes (added)
-
trunk/vendor/freemius/includes/class-freemius-abstract.php (added)
-
trunk/vendor/freemius/includes/class-freemius.php (added)
-
trunk/vendor/freemius/includes/class-fs-admin-notices.php (added)
-
trunk/vendor/freemius/includes/class-fs-api.php (added)
-
trunk/vendor/freemius/includes/class-fs-garbage-collector.php (added)
-
trunk/vendor/freemius/includes/class-fs-hook-snapshot.php (added)
-
trunk/vendor/freemius/includes/class-fs-lock.php (added)
-
trunk/vendor/freemius/includes/class-fs-logger.php (added)
-
trunk/vendor/freemius/includes/class-fs-options.php (added)
-
trunk/vendor/freemius/includes/class-fs-plugin-updater.php (added)
-
trunk/vendor/freemius/includes/class-fs-security.php (added)
-
trunk/vendor/freemius/includes/class-fs-storage.php (added)
-
trunk/vendor/freemius/includes/class-fs-user-lock.php (added)
-
trunk/vendor/freemius/includes/customizer (added)
-
trunk/vendor/freemius/includes/customizer/class-fs-customizer-support-section.php (added)
-
trunk/vendor/freemius/includes/customizer/class-fs-customizer-upsell-control.php (added)
-
trunk/vendor/freemius/includes/customizer/index.php (added)
-
trunk/vendor/freemius/includes/debug (added)
-
trunk/vendor/freemius/includes/debug/class-fs-debug-bar-panel.php (added)
-
trunk/vendor/freemius/includes/debug/debug-bar-start.php (added)
-
trunk/vendor/freemius/includes/debug/index.php (added)
-
trunk/vendor/freemius/includes/entities (added)
-
trunk/vendor/freemius/includes/entities/class-fs-affiliate-terms.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-affiliate.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-billing.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-entity.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-payment.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-plugin-info.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-plugin-license.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-plugin-plan.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-plugin-tag.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-plugin.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-pricing.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-scope-entity.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-site.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-subscription.php (added)
-
trunk/vendor/freemius/includes/entities/class-fs-user.php (added)
-
trunk/vendor/freemius/includes/entities/index.php (added)
-
trunk/vendor/freemius/includes/fs-core-functions.php (added)
-
trunk/vendor/freemius/includes/fs-essential-functions.php (added)
-
trunk/vendor/freemius/includes/fs-html-escaping-functions.php (added)
-
trunk/vendor/freemius/includes/fs-plugin-info-dialog.php (added)
-
trunk/vendor/freemius/includes/index.php (added)
-
trunk/vendor/freemius/includes/l10n.php (added)
-
trunk/vendor/freemius/includes/managers (added)
-
trunk/vendor/freemius/includes/managers/class-fs-admin-menu-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-admin-notice-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-cache-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-checkout-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-clone-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-contact-form-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-debug-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-gdpr-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-key-value-storage.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-license-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-option-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-permission-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-plan-manager.php (added)
-
trunk/vendor/freemius/includes/managers/class-fs-plugin-manager.php (added)
-
trunk/vendor/freemius/includes/managers/index.php (added)
-
trunk/vendor/freemius/includes/sdk (added)
-
trunk/vendor/freemius/includes/sdk/Exceptions (added)
-
trunk/vendor/freemius/includes/sdk/Exceptions/ArgumentNotExistException.php (added)
-
trunk/vendor/freemius/includes/sdk/Exceptions/EmptyArgumentException.php (added)
-
trunk/vendor/freemius/includes/sdk/Exceptions/Exception.php (added)
-
trunk/vendor/freemius/includes/sdk/Exceptions/InvalidArgumentException.php (added)
-
trunk/vendor/freemius/includes/sdk/Exceptions/OAuthException.php (added)
-
trunk/vendor/freemius/includes/sdk/Exceptions/index.php (added)
-
trunk/vendor/freemius/includes/sdk/FreemiusBase.php (added)
-
trunk/vendor/freemius/includes/sdk/FreemiusWordPress.php (added)
-
trunk/vendor/freemius/includes/sdk/LICENSE.txt (added)
-
trunk/vendor/freemius/includes/sdk/index.php (added)
-
trunk/vendor/freemius/includes/supplements (added)
-
trunk/vendor/freemius/includes/supplements/fs-essential-functions-1.1.7.1.php (added)
-
trunk/vendor/freemius/includes/supplements/fs-essential-functions-2.2.1.php (added)
-
trunk/vendor/freemius/includes/supplements/fs-migration-2.5.1.php (added)
-
trunk/vendor/freemius/includes/supplements/index.php (added)
-
trunk/vendor/freemius/index.php (added)
-
trunk/vendor/freemius/languages (added)
-
trunk/vendor/freemius/languages/freemius-cs_CZ.mo (added)
-
trunk/vendor/freemius/languages/freemius-da_DK.mo (added)
-
trunk/vendor/freemius/languages/freemius-de_DE.mo (added)
-
trunk/vendor/freemius/languages/freemius-es_ES.mo (added)
-
trunk/vendor/freemius/languages/freemius-fr_FR.mo (added)
-
trunk/vendor/freemius/languages/freemius-he_IL.mo (added)
-
trunk/vendor/freemius/languages/freemius-hu_HU.mo (added)
-
trunk/vendor/freemius/languages/freemius-it_IT.mo (added)
-
trunk/vendor/freemius/languages/freemius-ja.mo (added)
-
trunk/vendor/freemius/languages/freemius-nl_NL.mo (added)
-
trunk/vendor/freemius/languages/freemius-ru_RU.mo (added)
-
trunk/vendor/freemius/languages/freemius-ta.mo (added)
-
trunk/vendor/freemius/languages/freemius-zh_CN.mo (added)
-
trunk/vendor/freemius/languages/freemius.pot (added)
-
trunk/vendor/freemius/languages/index.php (added)
-
trunk/vendor/freemius/require.php (added)
-
trunk/vendor/freemius/start.php (added)
-
trunk/vendor/freemius/templates (added)
-
trunk/vendor/freemius/templates/account (added)
-
trunk/vendor/freemius/templates/account.php (added)
-
trunk/vendor/freemius/templates/account/billing.php (added)
-
trunk/vendor/freemius/templates/account/index.php (added)
-
trunk/vendor/freemius/templates/account/partials (added)
-
trunk/vendor/freemius/templates/account/partials/activate-license-button.php (added)
-
trunk/vendor/freemius/templates/account/partials/addon.php (added)
-
trunk/vendor/freemius/templates/account/partials/deactivate-license-button.php (added)
-
trunk/vendor/freemius/templates/account/partials/disconnect-button.php (added)
-
trunk/vendor/freemius/templates/account/partials/index.php (added)
-
trunk/vendor/freemius/templates/account/partials/site.php (added)
-
trunk/vendor/freemius/templates/account/payments.php (added)
-
trunk/vendor/freemius/templates/add-ons.php (added)
-
trunk/vendor/freemius/templates/add-trial-to-pricing.php (added)
-
trunk/vendor/freemius/templates/admin-notice.php (added)
-
trunk/vendor/freemius/templates/ajax-loader.php (added)
-
trunk/vendor/freemius/templates/api-connectivity-message-js.php (added)
-
trunk/vendor/freemius/templates/auto-installation.php (added)
-
trunk/vendor/freemius/templates/checkout (added)
-
trunk/vendor/freemius/templates/checkout.php (added)
-
trunk/vendor/freemius/templates/checkout/frame.php (added)
-
trunk/vendor/freemius/templates/checkout/process-redirect.php (added)
-
trunk/vendor/freemius/templates/checkout/redirect.php (added)
-
trunk/vendor/freemius/templates/clone-resolution-js.php (added)
-
trunk/vendor/freemius/templates/connect (added)
-
trunk/vendor/freemius/templates/connect.php (added)
-
trunk/vendor/freemius/templates/connect/index.php (added)
-
trunk/vendor/freemius/templates/connect/permission.php (added)
-
trunk/vendor/freemius/templates/connect/permissions-group.php (added)
-
trunk/vendor/freemius/templates/contact.php (added)
-
trunk/vendor/freemius/templates/debug (added)
-
trunk/vendor/freemius/templates/debug.php (added)
-
trunk/vendor/freemius/templates/debug/api-calls.php (added)
-
trunk/vendor/freemius/templates/debug/index.php (added)
-
trunk/vendor/freemius/templates/debug/logger.php (added)
-
trunk/vendor/freemius/templates/debug/plugins-themes-sync.php (added)
-
trunk/vendor/freemius/templates/debug/scheduled-crons.php (added)
-
trunk/vendor/freemius/templates/email.php (added)
-
trunk/vendor/freemius/templates/forms (added)
-
trunk/vendor/freemius/templates/forms/affiliation.php (added)
-
trunk/vendor/freemius/templates/forms/data-debug-mode.php (added)
-
trunk/vendor/freemius/templates/forms/deactivation (added)
-
trunk/vendor/freemius/templates/forms/deactivation/contact.php (added)
-
trunk/vendor/freemius/templates/forms/deactivation/form.php (added)
-
trunk/vendor/freemius/templates/forms/deactivation/index.php (added)
-
trunk/vendor/freemius/templates/forms/deactivation/retry-skip.php (added)
-
trunk/vendor/freemius/templates/forms/email-address-update.php (added)
-
trunk/vendor/freemius/templates/forms/index.php (added)
-
trunk/vendor/freemius/templates/forms/license-activation.php (added)
-
trunk/vendor/freemius/templates/forms/optout.php (added)
-
trunk/vendor/freemius/templates/forms/premium-versions-upgrade-handler.php (added)
-
trunk/vendor/freemius/templates/forms/premium-versions-upgrade-metadata.php (added)
-
trunk/vendor/freemius/templates/forms/resend-key.php (added)
-
trunk/vendor/freemius/templates/forms/subscription-cancellation.php (added)
-
trunk/vendor/freemius/templates/forms/trial-start.php (added)
-
trunk/vendor/freemius/templates/forms/user-change.php (added)
-
trunk/vendor/freemius/templates/gdpr-optin-js.php (added)
-
trunk/vendor/freemius/templates/index.php (added)
-
trunk/vendor/freemius/templates/js (added)
-
trunk/vendor/freemius/templates/js/index.php (added)
-
trunk/vendor/freemius/templates/js/jquery.content-change.php (added)
-
trunk/vendor/freemius/templates/js/open-license-activation.php (added)
-
trunk/vendor/freemius/templates/js/permissions.php (added)
-
trunk/vendor/freemius/templates/js/style-premium-theme.php (added)
-
trunk/vendor/freemius/templates/partials (added)
-
trunk/vendor/freemius/templates/partials/index.php (added)
-
trunk/vendor/freemius/templates/partials/network-activation.php (added)
-
trunk/vendor/freemius/templates/plugin-icon.php (added)
-
trunk/vendor/freemius/templates/plugin-info (added)
-
trunk/vendor/freemius/templates/plugin-info/description.php (added)
-
trunk/vendor/freemius/templates/plugin-info/features.php (added)
-
trunk/vendor/freemius/templates/plugin-info/index.php (added)
-
trunk/vendor/freemius/templates/plugin-info/screenshots.php (added)
-
trunk/vendor/freemius/templates/pricing.php (added)
-
trunk/vendor/freemius/templates/secure-https-header.php (added)
-
trunk/vendor/freemius/templates/sticky-admin-notice-js.php (added)
-
trunk/vendor/freemius/templates/tabs-capture-js.php (added)
-
trunk/vendor/freemius/templates/tabs.php (added)
Legend:
- Unmodified
- Added
- Removed
-
faecursor/trunk/README.txt
r3384653 r3454888 1 === FaeCursor | WordPress Custom Cursor Plugin===2 Contributors: psakhilsoman 1 === FaeCursor | WordPress Custom Cursor, Keyboard & Screen Effects === 2 Contributors: psakhilsoman, faecursor 3 3 Author: FaeCursor Plugin Team 4 Version: 1. 15 Tags: cursor effects, animation, custom icons, user interaction, visual effects4 Version: 1.2 5 Tags: wordpress custom cursor, custom cursor wordpress plugin, mouse cursor effects, keyboard effects, screen effects, particle effects, interaction effects, animation effects, UI animation 6 6 Requires at least: 5.6 7 Tested up to: 6. 8.38 Stable tag: 1. 17 Tested up to: 6.9.1 8 Stable tag: 1.2 9 9 Requires PHP: 7.4 10 10 License: GPLv2 or later 11 11 License URI: https://www.gnu.org/licenses/gpl-2.0.html 12 12 13 FaeCursor adds unique mouse cursor effects.13 Bring your WordPress site to life with lightweight **custom cursor, keyboard, and screen effects** — star trails, sparkles, particle animations, and smooth interactive UI effects, fully optimized for performance. 14 14 15 15 == Description == 16 16 17 FaeCursor is a highly customizable WordPress plugin that allows you to add beautiful mouse cursor effects to your website. With a variety of effects to choose from, you can create engaging user experiences that leave a lasting impression. Whether you want sparkles, stars etc, FaeCursor has you covered.17 FaeCursor is a lightweight **WordPress custom cursor plugin** that adds interactive cursor, keyboard, and screen effects to your website. Create engaging experiences with smooth, responsive animations such as **cursor trails, sparkles, stars, and subtle screen particles**. Perfect for Elementor, Divi, and any WordPress theme, FaeCursor enhances user engagement without slowing your pages. 18 18 19 19 **Features:** 20 - Multiple cursor effects, including star trails, sparkles, and magic aura. 21 - Easy-to-use settings page with real-time effect previews. 22 - Fully responsive and optimized for performance. 23 - Works with any WordPress theme. 20 - Multiple **cursor effects**, including star trails, sparkles, dual circles, and magic aura. 21 - **Keyboard interaction effects** and shortcut animations for dynamic UI engagement. 22 - **Screen effects and particle animations** triggered by user actions. 23 - Easy-to-use admin panel with **real-time preview** of all effects. 24 - Fully responsive, lightweight, and optimized for **performance**. 25 - Works seamlessly with any WordPress theme, Elementor, and Divi. 24 26 25 27 **Why Choose FaeCursor?** 26 - Add an extra layer of engagement to your site with stunning visual effects. 27 - Easily manage and update effects from an intuitive plugin settings screen. 28 - Boost user engagement with interactive **cursor, keyboard, and screen effects**. 29 - Customize animations easily through an intuitive plugin settings screen. 30 - Lightweight and performance-optimized — won’t slow your site. 28 31 29 32 == Installation == 30 33 31 1. Upload the ` FaeCursor` folder to the `/wp-content/plugins/` directory.34 1. Upload the `faecursor` folder to the `/wp-content/plugins/` directory. 32 35 2. Activate the plugin through the 'Plugins' screen in WordPress. 33 3. Go to the FaeCursor settings page in your WordPress dashboard to configure theeffects.36 3. Go to the FaeCursor settings page in your WordPress dashboard to configure effects. 34 37 35 38 == Frequently Asked Questions == 36 39 37 40 = Does FaeCursor work with all themes? = 38 Absolutely! FaeCursor is designed to work with any WordPress theme without interfering with your existing design.41 Yes! FaeCursor is fully compatible with any WordPress theme, including Elementor and Divi, without affecting page layout. 39 42 40 43 = Will FaeCursor slow down my site? = 41 No, FaeCursor is optimized for performance. We’ve ensured that the plugin is lightweight and won’t negatively impact your website speed.44 No, the plugin is lightweight and optimized for performance. Smooth animations will not impact your site speed. 42 45 43 46 == Screenshots == … … 45 48 1. **Plugin Settings Screen** screenshot-1.png 46 49 2. **Sparkle Effect** screenshot-2.png 50 3. **Keyboard Interaction Effects** screenshot-3.png 51 4. **Screen Particle Effects** screenshot-4.png 47 52 48 53 == Changelog == 54 55 = 05 Feb 2026 - ver 1.2.0 = 56 * Updated admin UI with enhanced usability and live preview. 57 * Added new cursor, keyboard, and screen effects. 58 * Optimized performance for modern browsers and WordPress versions. 49 59 50 60 = 25 Oct 2025 - ver 1.1.0 = … … 59 69 == Upgrade Notice == 60 70 71 = 1.2.0 = 72 This update adds keyboard and screen interaction effects with a fully improved admin UI for live previews. 73 61 74 = 1.1.0 = 62 This update brings a refreshed admin UI, new icons for enhanced customization, and a fix for Safari users experiencing issues with the dual circle effect. Make sure to update to enjoy the improved experience! 75 Refreshed admin UI, new icons, and Safari dual circle fix. Update for the improved experience. 63 76 64 77 = 1.0.0 = 65 Initial release. Enjoy the new visual effects!78 Initial release. Enjoy interactive cursor effects on your WordPress site! 66 79 67 80 == License == -
faecursor/trunk/assets/css/fae-cursor-admin.css
r3384653 r3454888 1 1 /* FaeCursor Admin Dashboard Styles */ 2 3 /* Hide or relocate WordPress admin notices on FaeCursor page */ 4 .toplevel_page_fae_cursor .notice, 5 .toplevel_page_fae_cursor .update-nag, 6 .toplevel_page_fae_cursor .error, 7 .toplevel_page_fae_cursor .updated, 8 .toplevel_page_fae_cursor .is-dismissible { 9 margin: 20px 0 20px 0 !important; 10 position: relative; 11 z-index: 1; 12 } 13 14 /* Move notices below the header */ 15 .toplevel_page_fae_cursor .wrap { 16 position: relative; 17 } 18 19 .toplevel_page_fae_cursor .fae-cursor-dashboard { 20 position: relative; 21 z-index: 2; 22 } 23 24 /* Ensure notices don't appear above header */ 25 .toplevel_page_fae_cursor .notice:not(.fae-notice) { 26 margin-top: 0 !important; 27 margin-bottom: 20px !important; 28 } 2 29 3 30 /* Admin Menu Icon */ … … 13 40 .fae-cursor-dashboard { 14 41 max-width: 1200px; 15 margin: 20px 0;42 margin: 20px auto; 16 43 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; 17 44 } … … 27 54 } 28 55 29 .fae-dashboard-header img {30 object-fit: contain;31 opacity: 1 !important;32 height: 50px;33 padding: 0 !important;34 }35 36 56 .fae-dashboard-title { 37 57 color: rgb(255, 255, 255); … … 43 63 align-items: center; 44 64 gap: 20px; 65 position: relative; 66 z-index: 2; 45 67 } 46 68 … … 49 71 } 50 72 73 .fae-header-actions { 74 display: flex; 75 align-items: center; 76 } 77 51 78 .fae-dashboard-header h1 { 52 margin: 0 0 10px 0;79 margin: 0; 53 80 font-size: 2.5em; 54 81 font-weight: 700; 55 82 display: flex; 56 83 align-items: center; 84 gap: 12px; 57 85 } 58 86 … … 63 91 } 64 92 65 .fae-dashboard-header p { 66 margin: 0; 67 font-size: 1.1em; 68 opacity: 0.9; 69 } 70 71 .fae-header-actions { 72 display: flex; 73 align-items: center; 74 } 75 76 .fae-btn-feedback { 93 .fae-title-group { 94 display: flex; 95 flex-direction: column; 96 gap: 2px; 97 } 98 99 .fae-title-name { 100 display: flex; 101 align-items: center; 102 gap: 10px; 103 position: relative; 104 } 105 106 .fae-tagline { 107 font-size: 0.30em; 108 font-weight: 400; 109 opacity: 0.85; 110 letter-spacing: 1px; 111 } 112 113 .fae-version-badge { 114 font-size: 0.45em; 115 font-weight: 600; 77 116 background: rgba(255, 255, 255, 0.2); 78 color: white; 79 border: 2px solid rgba(255, 255, 255, 0.3); 80 padding: 12px 20px; 81 border-radius: 8px; 82 text-decoration: none; 83 font-weight: 600; 84 display: flex; 85 align-items: center; 86 gap: 8px; 87 transition: all 0.3s ease; 88 backdrop-filter: blur(10px); 89 } 90 91 .fae-btn-feedback:hover { 92 background: rgba(255, 255, 255, 0.3); 93 border-color: rgba(255, 255, 255, 0.5); 94 color: white; 95 transform: translateY(-2px); 96 box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); 97 } 98 99 .fae-btn-feedback .fae-icon { 100 width: 16px; 101 height: 16px; 102 fill: currentColor; 117 padding: 3px 8px; 118 border-radius: 20px; 119 vertical-align: middle; 120 letter-spacing: 0.5px; 103 121 } 104 122 … … 106 124 .fae-stats-grid { 107 125 display: grid; 108 grid-template-columns: repeat( auto-fit, minmax(200px, 1fr));126 grid-template-columns: repeat(3, 1fr); 109 127 gap: 20px; 110 128 margin-bottom: 30px; 129 } 130 131 @media (max-width: 900px) { 132 .fae-stats-grid { 133 grid-template-columns: repeat(2, 1fr); 134 } 135 } 136 137 @media (max-width: 600px) { 138 .fae-stats-grid { 139 grid-template-columns: 1fr; 140 } 111 141 } 112 142 … … 140 170 } 141 171 172 /* Inactive state - dull/muted styling */ 173 .fae-stat-card-inactive { 174 border-left-color: #9ca3af; 175 opacity: 0.75; 176 } 177 178 .fae-stat-card-inactive .fae-stat-value { 179 color: #9ca3af; 180 } 181 182 .fae-stat-card-inactive:hover { 183 transform: translateY(-3px); 184 box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1); 185 } 186 187 /* Multiple effects - smaller text */ 188 .fae-stat-value-small { 189 font-size: 2em; 190 } 191 192 /* Detail text for multiple effects */ 193 .fae-stat-detail { 194 margin: 8px 0 0 0; 195 font-size: 0.85em; 196 color: #6b7280; 197 font-weight: 500; 198 line-height: 1.4; 199 } 200 142 201 /* Main Content */ 143 202 .fae-main-content { … … 153 212 } 154 213 155 .fae-settings-panel h2 {156 margin: 0 0 25px 0;157 color: #333;158 font-size: 1.5em;159 display: flex;160 align-items: center;161 gap: 10px;162 }163 214 164 215 .fae-settings-panel .fae-icon { … … 171 222 .fae-effect-grid { 172 223 display: grid; 173 grid-template-columns: repeat(auto-fi t, minmax(150px, 1fr));174 gap: 1 5px;224 grid-template-columns: repeat(auto-fill, minmax(140px, 170px)); 225 gap: 12px; 175 226 margin-bottom: 25px; 176 227 } … … 181 232 border: 2px solid #e1e5e9; 182 233 border-radius: 8px; 183 padding: 20px;234 padding: 12px 10px; 184 235 text-align: center; 185 236 transition: all 0.3s ease; 186 237 background: white; 238 height: 75px; 239 display: flex; 240 align-items: center; 241 justify-content: center; 187 242 } 188 243 … … 198 253 pointer-events: none; 199 254 } 200 201 .fae-effect-option input[type="radio"]:checked + .fae-effect-content { 255 /* Visual "selected" state for effect card (minimal styling) */ 256 .fae-effect-option.fae-effect-selected { 257 border-color: #667eea; 258 background: #ffffff; 202 259 color: #667eea; 203 260 } 204 261 205 .fae-effect-option input[type="radio"]:checked {206 border-color: #667eea;207 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);208 color: white;209 }210 211 262 .fae-effect-content { 212 263 display: flex; 213 264 flex-direction: column; 214 265 align-items: center; 266 justify-content: center; 215 267 gap: 10px; 268 position: relative; 269 width: 100%; 216 270 } 217 271 … … 227 281 } 228 282 283 /* Letter Avatar - Clean Subtle Design */ 284 .fae-effect-letter { 285 width: 36px; 286 height: 36px; 287 border-radius: 8px; 288 background: #f1f5f9; 289 color: #64748b; 290 display: flex; 291 align-items: center; 292 justify-content: center; 293 font-size: 12px; 294 font-weight: 600; 295 letter-spacing: 0.5px; 296 text-transform: uppercase; 297 flex-shrink: 0; 298 transition: all 0.2s ease; 299 border: 1px solid #e2e8f0; 300 } 301 302 .fae-effect-option:hover .fae-effect-letter { 303 background: #e2e8f0; 304 color: #475569; 305 border-color: #cbd5e1; 306 } 307 /* Letter style when card is selected */ 308 .fae-effect-option.fae-effect-selected .fae-effect-letter { 309 background: #667eea; 310 color: #ffffff; 311 border-color: #667eea; 312 } 313 314 /* Active Effect Badge */ 315 .fae-effect-active-badge { 316 position: absolute; 317 top: 6px; 318 right: 6px; 319 background: linear-gradient(135deg, #4caf50 0%, #45a049 100%); 320 color: white; 321 font-size: 9px; 322 font-weight: 700; 323 padding: 3px 6px; 324 border-radius: 4px; 325 text-transform: uppercase; 326 letter-spacing: 0.5px; 327 box-shadow: 0 2px 6px rgba(76, 175, 80, 0.3); 328 z-index: 10; 329 pointer-events: none; 330 line-height: 1.2; 331 } 332 333 .fae-effect-active { 334 border-color: #4caf50 !important; 335 box-shadow: 0 0 0 1px rgba(76, 175, 80, 0.2); 336 } 337 338 .fae-effect-active:hover { 339 border-color: #4caf50 !important; 340 box-shadow: 0 5px 15px rgba(76, 175, 80, 0.25); 341 } 342 229 343 /* Effect Settings */ 230 344 .fae-effect-settings { 231 345 margin-top: 25px; 232 346 } 347 348 /* ═══════════════════════════════════════════════════════════════ */ 349 /* NEW GROUPED SETTINGS LAYOUT */ 350 /* ═══════════════════════════════════════════════════════════════ */ 351 352 .fae-settings-group-container { 353 background: #ffffff; 354 border-radius: 16px; 355 margin-bottom: 24px; 356 box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); 357 border: 1px solid #e8eaed; 358 overflow: hidden; 359 } 360 361 .fae-settings-group-header { 362 background: linear-gradient(135deg, #f8f9ff 0%, #f3f4f8 100%); 363 padding: 18px 24px; 364 border-bottom: 1px solid #e8eaed; 365 display: flex; 366 align-items: center; 367 } 368 369 .fae-settings-group-text { 370 flex: 1; 371 } 372 373 .fae-settings-group-title { 374 margin: 0 0 2px 0; 375 font-size: 1.1em; 376 font-weight: 700; 377 color: #1a1a2e; 378 } 379 380 .fae-settings-group-description { 381 margin: 0; 382 font-size: 0.85em; 383 color: #6b7280; 384 } 385 386 .fae-settings-group-content { 387 padding: 24px; 388 } 389 390 /* Settings Row - Horizontal layout for related fields */ 391 .fae-settings-row { 392 display: grid; 393 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 394 gap: 20px; 395 margin-bottom: 0; 396 } 397 398 .fae-setting-item { 399 display: flex; 400 flex-direction: column; 401 gap: 8px; 402 } 403 404 .fae-setting-item-wide { 405 grid-column: span 2; 406 } 407 408 .fae-setting-label { 409 font-size: 14px; 410 font-weight: 600; 411 color: #374151; 412 } 413 414 .fae-setting-hint { 415 font-size: 12px; 416 color: #6b7280; 417 margin: 4px 0 0 0; 418 } 419 420 /* Settings Divider */ 421 .fae-settings-divider { 422 display: flex; 423 align-items: center; 424 margin: 24px 0 20px 0; 425 gap: 12px; 426 } 427 428 .fae-settings-divider::before, 429 .fae-settings-divider::after { 430 content: ''; 431 flex: 1; 432 height: 1px; 433 background: #e5e7eb; 434 } 435 436 .fae-settings-divider span { 437 font-size: 12px; 438 font-weight: 600; 439 color: #9ca3af; 440 text-transform: uppercase; 441 letter-spacing: 0.5px; 442 } 443 444 /* Subgroups within a group */ 445 .fae-settings-subgroup { 446 background: #f9fafb; 447 border-radius: 12px; 448 padding: 20px; 449 margin-bottom: 16px; 450 border: 1px solid #f0f1f3; 451 } 452 453 .fae-settings-subgroup:last-child { 454 margin-bottom: 0; 455 } 456 457 .fae-settings-subgroup-header { 458 display: flex; 459 align-items: center; 460 gap: 10px; 461 margin-bottom: 16px; 462 padding-bottom: 12px; 463 border-bottom: 1px solid #e5e7eb; 464 } 465 466 .fae-subgroup-icon { 467 width: 20px; 468 height: 20px; 469 fill: #667eea; 470 stroke: #667eea; 471 flex-shrink: 0; 472 } 473 474 .fae-subgroup-title { 475 font-size: 14px; 476 font-weight: 600; 477 color: #374151; 478 } 479 480 .fae-settings-subgroup-content { 481 padding: 16px 0; 482 } 483 484 /* Device Toggles */ 485 .fae-device-toggles { 486 display: flex; 487 flex-wrap: wrap; 488 gap: 16px; 489 } 490 491 .fae-device-toggles .fae-toggle-switch { 492 display: flex; 493 align-items: center; 494 gap: 10px; 495 cursor: pointer; 496 padding: 8px 16px; 497 background: white; 498 border-radius: 8px; 499 border: 1px solid #e5e7eb; 500 transition: all 0.2s ease; 501 } 502 503 .fae-device-toggles .fae-toggle-switch:hover { 504 border-color: #667eea; 505 box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1); 506 } 507 508 .fae-device-toggles .fae-toggle-label { 509 font-size: 13px; 510 font-weight: 500; 511 color: #374151; 512 } 513 514 /* Radio Group */ 515 .fae-radio-group { 516 display: flex; 517 flex-direction: column; 518 gap: 12px; 519 margin-bottom: 16px; 520 } 521 522 .fae-radio-item-disabled { 523 opacity: 0.6; 524 cursor: not-allowed; 525 pointer-events: none; 526 } 527 528 .fae-radio-item-disabled .fae-radio-label { 529 color: #9ca3af; 530 } 531 532 .fae-radio-item-disabled input[type="radio"] { 533 cursor: not-allowed; 534 } 535 536 .fae-radio-item { 537 display: flex; 538 align-items: flex-start; 539 gap: 12px; 540 padding: 12px 16px; 541 background: white; 542 border-radius: 8px; 543 border: 2px solid #e5e7eb; 544 cursor: pointer; 545 transition: all 0.2s ease; 546 } 547 548 .fae-radio-item:hover { 549 border-color: #c7d2fe; 550 background: #fafaff; 551 } 552 553 .fae-radio-item:has(input:checked) { 554 border-color: #667eea; 555 background: #f5f3ff; 556 } 557 558 .fae-radio-item input[type="radio"] { 559 margin-top: 2px; 560 accent-color: #667eea; 561 } 562 563 .fae-radio-label { 564 display: flex; 565 flex-direction: column; 566 gap: 2px; 567 } 568 569 .fae-radio-label strong { 570 font-size: 14px; 571 color: #1f2937; 572 } 573 574 .fae-radio-label small { 575 font-size: 12px; 576 color: #6b7280; 577 } 578 579 /* Checkbox List */ 580 .fae-checkbox-list { 581 max-height: 200px; 582 overflow-y: auto; 583 background: white; 584 border: 1px solid #e5e7eb; 585 border-radius: 8px; 586 padding: 12px; 587 } 588 589 .fae-checkbox-item { 590 display: flex; 591 align-items: center; 592 gap: 8px; 593 padding: 8px 12px; 594 cursor: pointer; 595 border-radius: 6px; 596 transition: background 0.15s ease; 597 } 598 599 .fae-checkbox-item:hover { 600 background: #f3f4f6; 601 } 602 603 .fae-checkbox-item input[type="checkbox"] { 604 accent-color: #667eea; 605 } 606 607 .fae-checkbox-item span { 608 font-size: 14px; 609 color: #374151; 610 } 611 612 /* Roles Checkbox Grid */ 613 .fae-roles-checkbox-grid { 614 display: grid; 615 grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); 616 gap: 8px; 617 margin-bottom: 16px; 618 } 619 620 .fae-checkbox-highlighted { 621 background: #fef3c7; 622 border: 1px solid #fcd34d; 623 border-radius: 8px; 624 margin-top: 12px; 625 padding-top: 12px; 626 } 627 628 .fae-checkbox-highlighted:hover { 629 background: #fef3c7; 630 } 631 632 /* Specific Roles Wrapper */ 633 .fae-specific-roles-wrapper { 634 background: #f9fafb; 635 border-radius: 8px; 636 padding: 16px; 637 margin-top: 12px; 638 border: 1px solid #e5e7eb; 639 } 640 641 /* Scope Pages Wrapper - matches roles wrapper styling */ 642 .fae-scope-pages-wrapper { 643 background: #f9fafb; 644 border-radius: 8px; 645 padding: 16px; 646 margin-top: 12px; 647 border: 1px solid #e5e7eb; 648 } 649 650 /* Pages Checkbox Grid - matches roles grid */ 651 .fae-pages-checkbox-grid { 652 display: grid; 653 grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); 654 gap: 8px; 655 max-height: 250px; 656 overflow-y: auto; 657 padding-right: 8px; 658 } 659 660 /* Scrollbar for pages grid */ 661 .fae-pages-checkbox-grid::-webkit-scrollbar { 662 width: 6px; 663 } 664 665 .fae-pages-checkbox-grid::-webkit-scrollbar-track { 666 background: #f1f1f1; 667 border-radius: 3px; 668 } 669 670 .fae-pages-checkbox-grid::-webkit-scrollbar-thumb { 671 background: #c1c1c1; 672 border-radius: 3px; 673 } 674 675 .fae-pages-checkbox-grid::-webkit-scrollbar-thumb:hover { 676 background: #a8a8a8; 677 } 678 679 /* CSS Selector Wrapper */ 680 .fae-scope-selector-wrapper { 681 background: #f9fafb; 682 border-radius: 8px; 683 padding: 16px; 684 margin-top: 12px; 685 border: 1px solid #e5e7eb; 686 } 687 688 .fae-scope-selector-wrapper input[type="text"] { 689 width: 100%; 690 padding: 12px 16px; 691 border: 2px solid #e5e7eb; 692 border-radius: 8px; 693 font-size: 14px; 694 font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; 695 background: white; 696 transition: all 0.2s ease; 697 } 698 699 .fae-scope-selector-wrapper input[type="text"]:focus { 700 outline: none; 701 border-color: #667eea; 702 box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); 703 } 704 705 .fae-scope-selector-wrapper input[type="text"]:hover { 706 border-color: #c7d2fe; 707 } 708 709 /* Icon Settings Wrapper */ 710 .fae-icon-settings-wrapper { 711 margin-top: 0; 712 } 713 714 /* ═══════════════════════════════════════════════════════════════ */ 715 /* LEGACY SUPPORT - Old section styles (for other tabs) */ 716 /* ═══════════════════════════════════════════════════════════════ */ 233 717 234 718 .fae-settings-section { … … 285 769 } 286 770 771 /* Responsive for grouped settings */ 772 @media (max-width: 768px) { 773 .fae-settings-group-header { 774 flex-direction: column; 775 align-items: flex-start; 776 text-align: left; 777 } 778 779 .fae-settings-row { 780 grid-template-columns: 1fr; 781 } 782 783 .fae-setting-item-wide { 784 grid-column: span 1; 785 } 786 787 .fae-device-toggles { 788 flex-direction: column; 789 } 790 791 .fae-device-toggles .fae-toggle-switch { 792 width: 100%; 793 } 794 795 .fae-roles-checkbox-grid { 796 grid-template-columns: 1fr; 797 } 798 } 799 287 800 .fae-info-card { 288 801 background: white; … … 347 860 border-color: #667eea; 348 861 box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); 862 } 863 864 /* New grouped layout input styles */ 865 .fae-setting-item input[type="text"], 866 .fae-setting-item input[type="number"], 867 .fae-setting-item select { 868 width: 100%; 869 padding: 10px 15px; 870 border: 2px solid #e1e5e9; 871 border-radius: 8px; 872 font-size: 14px; 873 transition: all 0.2s ease; 874 background: white; 875 } 876 877 .fae-setting-item input[type="text"]:focus, 878 .fae-setting-item input[type="number"]:focus, 879 .fae-setting-item select:focus { 880 outline: none; 881 border-color: #667eea; 882 box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); 883 } 884 885 .fae-setting-item input[type="text"]:hover, 886 .fae-setting-item input[type="number"]:hover, 887 .fae-setting-item select:hover { 888 border-color: #c7d2fe; 349 889 } 350 890 … … 662 1202 } 663 1203 1204 .fae-btn-danger { 1205 background: #dc3545; 1206 color: white; 1207 } 1208 1209 .fae-btn-danger:hover { 1210 background: #c82333; 1211 transform: translateY(-2px); 1212 box-shadow: 0 5px 15px rgba(220, 53, 69, 0.4); 1213 } 1214 664 1215 .fae-btn-outline { 665 1216 background: transparent; … … 718 1269 flex-direction: column; 719 1270 gap: 15px; 720 }721 722 .fae-header-actions {723 align-self: flex-start;724 1271 } 725 1272 … … 757 1304 } 758 1305 } 1306 1307 1308 /* Info Button */ 1309 .fae-info-button { 1310 position: relative; 1311 background: none; 1312 border: none; 1313 padding: 4px; 1314 cursor: pointer; 1315 display: inline-flex; 1316 align-items: center; 1317 justify-content: center; 1318 margin-left: auto; 1319 color: #667eea; 1320 transition: all 0.3s ease; 1321 border-radius: 50%; 1322 width: 24px; 1323 height: 24px; 1324 flex-shrink: 0; 1325 } 1326 1327 .fae-info-button:hover { 1328 background: rgba(102, 126, 234, 0.1); 1329 color: #764ba2; 1330 } 1331 1332 .fae-info-button .fae-icon { 1333 width: 18px; 1334 height: 18px; 1335 fill: currentColor; 1336 } 1337 1338 /* Inline Info Button (for settings) */ 1339 .fae-info-button-inline { 1340 margin-left: 4px; 1341 width: 18px; 1342 height: 18px; 1343 padding: 2px; 1344 } 1345 1346 .fae-info-button-inline .fae-icon { 1347 width: 14px; 1348 height: 14px; 1349 } 1350 1351 /* Tooltip */ 1352 .fae-tooltip { 1353 position: absolute; 1354 bottom: calc(100% + 10px); 1355 right: 0; 1356 background: white; 1357 border-radius: 8px; 1358 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); 1359 padding: 0; 1360 min-width: 280px; 1361 max-width: 320px; 1362 opacity: 0; 1363 visibility: hidden; 1364 transform: translateY(5px); 1365 transition: all 0.3s ease; 1366 z-index: 1000; 1367 pointer-events: none; 1368 } 1369 1370 .fae-info-button:hover .fae-tooltip, 1371 .fae-info-button:focus .fae-tooltip, 1372 .fae-info-button.active .fae-tooltip { 1373 opacity: 1; 1374 visibility: visible; 1375 transform: translateY(0); 1376 pointer-events: auto; 1377 } 1378 1379 .fae-tooltip-content { 1380 padding: 15px; 1381 font-size: 13px; 1382 color: #666; 1383 line-height: 1.6; 1384 } 1385 1386 .fae-tooltip-content strong { 1387 color: #333; 1388 display: block; 1389 margin-bottom: 8px; 1390 font-size: 14px; 1391 } 1392 1393 .fae-tooltip-content p { 1394 margin: 0; 1395 color: #666; 1396 } 1397 1398 /* Tooltip Arrow */ 1399 .fae-tooltip::after { 1400 content: ''; 1401 position: absolute; 1402 top: 100%; 1403 right: 20px; 1404 width: 0; 1405 height: 0; 1406 border-left: 8px solid transparent; 1407 border-right: 8px solid transparent; 1408 border-top: 8px solid white; 1409 } 1410 1411 1412 1413 /* Inline Tooltip (for settings) */ 1414 .fae-settings-header .fae-tooltip-inline { 1415 bottom: calc(100% + 8px); 1416 left: auto; 1417 right: 0; 1418 min-width: 250px; 1419 max-width: 300px; 1420 } 1421 1422 .fae-settings-header .fae-tooltip-inline::after { 1423 top: 100%; 1424 left: auto; 1425 right: 20px; 1426 } 1427 1428 1429 /* Inline Tooltip (for settings) */ 1430 .fae-settings-section .fae-tooltip-inline { 1431 bottom: calc(100% + 8px); 1432 left: 0; 1433 right: auto; 1434 min-width: 250px; 1435 max-width: 300px; 1436 } 1437 1438 .fae-settings-section .fae-tooltip-inline::after { 1439 top: 100%; 1440 left: 20px; 1441 right: auto; 1442 } 1443 1444 1445 1446 1447 1448 1449 /* Hide Default Cursor Toggle Switch */ 1450 .fae-hide-cursor-toggle-wrapper { 1451 display: flex; 1452 align-items: center; 1453 gap: 6px; 1454 margin-left: auto; 1455 } 1456 1457 .fae-toggle-switch { 1458 position: relative; 1459 display: inline-flex; 1460 align-items: center; 1461 gap: 8px; 1462 cursor: pointer; 1463 user-select: none; 1464 } 1465 1466 .fae-toggle-input { 1467 position: absolute; 1468 opacity: 0; 1469 width: 0; 1470 height: 0; 1471 } 1472 1473 .fae-toggle-slider { 1474 position: relative; 1475 display: inline-block; 1476 width: 36px; 1477 height: 20px; 1478 background-color: #ccc; 1479 border-radius: 20px; 1480 transition: background-color 0.3s ease; 1481 flex-shrink: 0; 1482 } 1483 1484 .fae-toggle-slider:before { 1485 content: ""; 1486 position: absolute; 1487 height: 14px; 1488 width: 14px; 1489 left: 3px; 1490 bottom: 3px; 1491 background-color: white; 1492 border-radius: 50%; 1493 transition: transform 0.3s ease, box-shadow 0.3s ease; 1494 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); 1495 } 1496 1497 .fae-toggle-input:checked + .fae-toggle-slider { 1498 background-color: #667eea; 1499 } 1500 1501 .fae-toggle-input:checked + .fae-toggle-slider:before { 1502 transform: translateX(16px); 1503 box-shadow: 0 1px 4px rgba(102, 126, 234, 0.4); 1504 } 1505 1506 .fae-toggle-input:focus + .fae-toggle-slider { 1507 outline: 2px solid #667eea; 1508 outline-offset: 2px; 1509 } 1510 1511 .fae-toggle-label { 1512 font-size: 14px; 1513 font-weight: 600; 1514 color: #333; 1515 white-space: nowrap; 1516 } 1517 1518 .fae-toggle-input:checked ~ .fae-toggle-label { 1519 color: #667eea; 1520 } 1521 1522 /* Responsive adjustments for toggle */ 1523 @media (max-width: 768px) { 1524 .fae-settings-header { 1525 flex-direction: column; 1526 align-items: flex-start !important; 1527 } 1528 1529 .fae-hide-cursor-toggle-wrapper { 1530 margin-left: 0; 1531 width: 100%; 1532 justify-content: space-between; 1533 } 1534 1535 .fae-tabs-nav { 1536 flex-direction: column; 1537 gap: 0; 1538 } 1539 1540 .fae-tab-button { 1541 width: 100%; 1542 justify-content: center; 1543 border-bottom: 2px solid #e1e5e9; 1544 border-left: 3px solid transparent; 1545 margin-bottom: 0; 1546 padding: 12px 20px; 1547 } 1548 1549 .fae-tab-button.active { 1550 border-bottom-color: #e1e5e9; 1551 border-left-color: #667eea; 1552 } 1553 } 1554 1555 /* Tabs Wrapper */ 1556 .fae-tabs-wrapper { 1557 display: flex; 1558 align-items: center; 1559 justify-content: space-between; 1560 gap: 20px; 1561 margin-bottom: 20px; 1562 flex-wrap: wrap; 1563 } 1564 1565 /* Tabs Navigation */ 1566 .fae-tabs-nav { 1567 display: flex; 1568 gap: 10px; 1569 border-bottom: 2px solid #e1e5e9; 1570 padding-bottom: 0; 1571 flex: 1; 1572 } 1573 1574 .fae-tabs-actions { 1575 display: flex; 1576 align-items: center; 1577 gap: 10px; 1578 } 1579 1580 .fae-tab-button { 1581 background: transparent; 1582 border: none; 1583 padding: 15px 25px; 1584 position: relative; 1585 font-size: 16px; 1586 font-weight: 600; 1587 color: #666; 1588 cursor: pointer; 1589 display: flex; 1590 align-items: center; 1591 gap: 10px; 1592 border-bottom: 3px solid transparent; 1593 margin-bottom: -2px; 1594 transition: all 0.3s ease; 1595 position: relative; 1596 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; 1597 } 1598 1599 .fae-tab-button:hover { 1600 color: #667eea; 1601 background: rgba(102, 126, 234, 0.05); 1602 } 1603 1604 .fae-tab-button.active { 1605 color: #667eea; 1606 border-bottom-color: #667eea; 1607 background: transparent; 1608 } 1609 1610 .fae-tab-button .fae-icon { 1611 width: 20px; 1612 height: 20px; 1613 fill: currentColor; 1614 } 1615 1616 /* Tab Status Indicator */ 1617 .fae-tab-status { 1618 display: inline-flex; 1619 align-items: center; 1620 margin-left: 8px; 1621 vertical-align: middle; 1622 } 1623 1624 .fae-status-dot { 1625 width: 8px; 1626 height: 8px; 1627 border-radius: 50%; 1628 background: #dc3545; 1629 display: inline-block; 1630 transition: all 0.3s ease; 1631 box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); 1632 } 1633 1634 .fae-tab-status.active .fae-status-dot { 1635 background: #4caf50; 1636 box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2); 1637 animation: pulse 2s infinite; 1638 } 1639 1640 @keyframes pulse { 1641 0%, 100% { 1642 box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4); 1643 } 1644 50% { 1645 box-shadow: 0 0 0 4px rgba(76, 175, 80, 0); 1646 } 1647 } 1648 1649 .fae-tab-content { 1650 display: none; 1651 } 1652 1653 .fae-tab-content.active { 1654 display: block; 1655 animation: fae-tab-fade-in 0.3s ease; 1656 } 1657 1658 @keyframes fae-tab-fade-in { 1659 from { 1660 opacity: 0; 1661 transform: translateY(10px); 1662 } 1663 to { 1664 opacity: 1; 1665 transform: translateY(0); 1666 } 1667 } 1668 1669 /* Keyboard Effects Section */ 1670 .fae-keyboard-effects-panel { 1671 margin-top: 0; 1672 } 1673 1674 .fae-keyboard-effect-grid { 1675 min-height: 200px; 1676 } 1677 1678 .fae-keyboard-effect-placeholder { 1679 grid-column: 1 / -1; 1680 display: flex; 1681 align-items: center; 1682 justify-content: center; 1683 padding: 60px 20px; 1684 border: 2px dashed #e1e5e9; 1685 border-radius: 8px; 1686 background: #fafbfc; 1687 transition: all 0.3s ease; 1688 } 1689 1690 .fae-keyboard-effect-placeholder:hover { 1691 border-color: #667eea; 1692 background: #f8f9ff; 1693 } 1694 1695 .fae-placeholder-content { 1696 display: flex; 1697 flex-direction: column; 1698 align-items: center; 1699 justify-content: center; 1700 text-align: center; 1701 } 1702 1703 .fae-placeholder-content .fae-icon { 1704 fill: #667eea; 1705 } 1706 1707 .fae-keyboard-effect-settings { 1708 margin-top: 25px; 1709 } 1710 1711 /* ═══════════════════════════════════════════════════════════════ */ 1712 /* STICKY SIDEBAR LAYOUT */ 1713 /* ═══════════════════════════════════════════════════════════════ */ 1714 1715 .fae-split-layout { 1716 display: grid; 1717 grid-template-columns: 1fr 340px; 1718 gap: 24px; 1719 align-items: start; 1720 } 1721 1722 @media (max-width: 1100px) { 1723 .fae-split-layout { 1724 grid-template-columns: 1fr; 1725 } 1726 } 1727 1728 .fae-main-content { 1729 min-width: 0; 1730 } 1731 1732 /* Sticky Sidebar */ 1733 .fae-sidebar-preview { 1734 position: sticky; 1735 top: 42px; /* Below WP admin bar */ 1736 max-height: calc(100vh - 62px); 1737 overflow: visible; 1738 } 1739 1740 @media (max-width: 1100px) { 1741 .fae-sidebar-preview { 1742 position: static; 1743 max-height: none; 1744 } 1745 } 1746 1747 .fae-preview-card { 1748 background: #ffffff; 1749 border-radius: 16px; 1750 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); 1751 border: 1px solid #e8eaed; 1752 overflow: visible; 1753 } 1754 1755 /* Preview Section */ 1756 .fae-preview-section { 1757 background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); 1758 border-radius: 16px 16px 0 0; 1759 overflow: hidden; 1760 } 1761 1762 .fae-preview-header-inline { 1763 padding: 14px 18px; 1764 background: rgba(255,255,255,0.05); 1765 border-bottom: 1px solid rgba(255,255,255,0.1); 1766 font-size: 11px; 1767 font-weight: 700; 1768 color: rgba(255,255,255,0.6); 1769 text-transform: uppercase; 1770 letter-spacing: 1px; 1771 display: flex; 1772 align-items: center; 1773 justify-content: space-between; 1774 } 1775 1776 .fae-preview-header-actions { 1777 display: flex; 1778 align-items: center; 1779 gap: 8px; 1780 } 1781 1782 .fae-preview-bg-toggle { 1783 position: relative; 1784 width: 44px; 1785 height: 24px; 1786 padding: 0; 1787 background: rgba(255,255,255,0.15); 1788 border: 1px solid rgba(255,255,255,0.2); 1789 border-radius: 12px; 1790 cursor: pointer; 1791 transition: all 0.3s ease; 1792 outline: none; 1793 overflow: hidden; 1794 } 1795 1796 .fae-preview-bg-toggle:hover { 1797 background: rgba(255,255,255,0.2); 1798 border-color: rgba(255,255,255,0.3); 1799 } 1800 1801 .fae-preview-bg-toggle:focus { 1802 border-color: #667eea; 1803 box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.3); 1804 } 1805 1806 /* .fae-preview-bg-toggle[data-bg="light"] { 1807 background: rgba(255, 193, 7, 0.3); 1808 border-color: rgba(255, 193, 7, 0.5); 1809 } */ 1810 1811 .fae-bg-toggle-icon { 1812 position: absolute; 1813 top: 50%; 1814 transform: translateY(-50%); 1815 width: 18px; 1816 height: 18px; 1817 display: flex; 1818 align-items: center; 1819 justify-content: center; 1820 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 1821 color: rgba(255,255,255,0.9); 1822 } 1823 1824 .fae-bg-toggle-icon svg { 1825 width: 14px; 1826 height: 14px; 1827 stroke: currentColor; 1828 } 1829 1830 .fae-bg-icon-moon { 1831 left: 3px; 1832 opacity: 1; 1833 transform: translateY(-50%) scale(1) rotate(0deg); 1834 } 1835 1836 .fae-bg-icon-sun { 1837 right: 3px; 1838 opacity: 0; 1839 transform: translateY(-50%) scale(0.5) rotate(90deg); 1840 } 1841 1842 .fae-preview-bg-toggle[data-bg="light"] .fae-bg-icon-moon { 1843 opacity: 0; 1844 transform: translateY(-50%) scale(0.5) rotate(-90deg); 1845 } 1846 1847 .fae-preview-bg-toggle[data-bg="light"] .fae-bg-icon-sun { 1848 opacity: 1; 1849 transform: translateY(-50%) scale(1) rotate(0deg); 1850 color: #ffc107; 1851 } 1852 1853 /* Expand Button */ 1854 .fae-expand-btn { 1855 display: flex; 1856 align-items: center; 1857 gap: 5px; 1858 padding: 5px 10px; 1859 background: rgba(255,255,255,0.1); 1860 border: 1px solid rgba(255,255,255,0.15); 1861 border-radius: 5px; 1862 color: rgba(255,255,255,0.7); 1863 font-size: 10px; 1864 font-weight: 600; 1865 cursor: pointer; 1866 transition: all 0.2s; 1867 text-transform: uppercase; 1868 letter-spacing: 0.5px; 1869 } 1870 .fae-expand-btn:hover { 1871 background: rgba(255,255,255,0.15); 1872 color: #fff; 1873 } 1874 .fae-expand-btn svg { 1875 width: 12px; 1876 height: 12px; 1877 fill: currentColor; 1878 } 1879 1880 /* Fullscreen Preview Modal */ 1881 .fae-preview-modal { 1882 display: none; 1883 position: fixed; 1884 top: 0; 1885 left: 0; 1886 right: 0; 1887 bottom: 0; 1888 background: rgba(10,10,20,0.95); 1889 z-index: 99999; 1890 padding: 20px; 1891 } 1892 .fae-preview-modal.active { 1893 display: flex; 1894 flex-direction: column; 1895 } 1896 .fae-preview-modal-header { 1897 display: flex; 1898 align-items: center; 1899 justify-content: space-between; 1900 padding: 15px 20px; 1901 background: rgba(255,255,255,0.05); 1902 border-radius: 12px 12px 0 0; 1903 } 1904 .fae-preview-modal-title { 1905 color: #fff; 1906 font-size: 14px; 1907 font-weight: 600; 1908 } 1909 .fae-preview-modal-close { 1910 display: flex; 1911 align-items: center; 1912 gap: 6px; 1913 padding: 8px 16px; 1914 background: rgba(255,255,255,0.1); 1915 border: 1px solid rgba(255,255,255,0.2); 1916 border-radius: 6px; 1917 color: #fff; 1918 font-size: 12px; 1919 font-weight: 600; 1920 cursor: pointer; 1921 transition: all 0.2s; 1922 } 1923 .fae-preview-modal-close:hover { 1924 background: rgba(239,68,68,0.3); 1925 border-color: rgba(239,68,68,0.5); 1926 } 1927 .fae-preview-modal-close svg { 1928 width: 14px; 1929 height: 14px; 1930 fill: currentColor; 1931 } 1932 .fae-preview-modal-body { 1933 flex: 1; 1934 background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); 1935 border-radius: 0 0 12px 12px; 1936 position: relative; 1937 min-height: 0; 1938 } 1939 .fae-preview-modal-iframe { 1940 position: absolute; 1941 top: 0; 1942 left: 0; 1943 width: 100%; 1944 height: 100%; 1945 border: none; 1946 border-radius: 0 0 12px 12px; 1947 } 1948 .fae-preview-modal-hint { 1949 position: absolute; 1950 bottom: 20px; 1951 left: 50%; 1952 transform: translateX(-50%); 1953 padding: 8px 16px; 1954 background: rgba(0,0,0,0.5); 1955 border-radius: 20px; 1956 color: rgba(255,255,255,0.5); 1957 font-size: 12px; 1958 } 1959 1960 .fae-preview-iframe-wrapper { 1961 position: relative; 1962 height: 280px; 1963 } 1964 1965 .fae-preview-iframe { 1966 position: absolute; 1967 top: 0; 1968 left: 0; 1969 width: 100%; 1970 height: 100%; 1971 border: none; 1972 opacity: 0; 1973 transition: opacity 0.2s ease-in; 1974 } 1975 1976 .fae-preview-iframe.loaded { 1977 opacity: 1; 1978 } 1979 1980 /* Appearance Section */ 1981 .fae-appearance-section { 1982 background: #fafbff; 1983 border-radius: 0 0 16px 16px; 1984 overflow: visible; 1985 } 1986 1987 .fae-appearance-section .fae-preview-header-inline { 1988 background: linear-gradient(135deg, #667eea10 0%, #764ba210 100%); 1989 color: #4b5563; 1990 border-bottom: 1px solid #e8eaed; 1991 border-top: 1px solid #e8eaed; 1992 } 1993 1994 .fae-appearance-settings { 1995 padding: 18px; 1996 display: flex; 1997 flex-direction: column; 1998 gap: 16px; 1999 overflow: visible; 2000 } 2001 2002 /* Hide old container styles */ 2003 .fae-preview-appearance-container { 2004 display: none !important; 2005 } 2006 2007 .fae-inline-setting { 2008 display: flex; 2009 flex-direction: column; 2010 gap: 6px; 2011 } 2012 2013 .fae-inline-setting label { 2014 font-size: 11px; 2015 font-weight: 600; 2016 color: #6b7280; 2017 text-transform: uppercase; 2018 letter-spacing: 0.3px; 2019 } 2020 2021 .fae-inline-setting select { 2022 padding: 8px 12px; 2023 border: 2px solid #e5e7eb; 2024 border-radius: 6px; 2025 font-size: 13px; 2026 background: white; 2027 cursor: pointer; 2028 transition: border-color 0.2s; 2029 } 2030 2031 .fae-inline-setting select:hover { 2032 border-color: #c7d2fe; 2033 } 2034 2035 .fae-inline-setting select:focus { 2036 outline: none; 2037 border-color: #667eea; 2038 } 2039 2040 /* Color Input Inline */ 2041 .fae-color-input-inline { 2042 display: flex; 2043 gap: 8px; 2044 } 2045 2046 .fae-color-picker-inline { 2047 width: 44px; 2048 height: 36px; 2049 border: 2px solid #e5e7eb; 2050 border-radius: 6px; 2051 cursor: pointer; 2052 padding: 2px; 2053 } 2054 2055 .fae-color-input-inline input[type="text"] { 2056 flex: 1; 2057 padding: 8px 10px; 2058 border: 2px solid #e5e7eb; 2059 border-radius: 6px; 2060 font-size: 13px; 2061 font-family: 'SF Mono', monospace; 2062 } 2063 2064 .fae-color-input-inline input[type="text"]:focus { 2065 outline: none; 2066 border-color: #667eea; 2067 } 2068 2069 /* Icon Picker Inline */ 2070 .fae-icon-picker-inline { 2071 position: relative; 2072 z-index: 50; 2073 } 2074 2075 .fae-icon-trigger { 2076 width: 100%; 2077 padding: 8px 12px; 2078 background: white; 2079 border: 2px solid #e5e7eb; 2080 border-radius: 6px; 2081 cursor: pointer; 2082 display: flex; 2083 align-items: center; 2084 gap: 10px; 2085 transition: border-color 0.2s; 2086 } 2087 2088 .fae-icon-trigger:hover { 2089 border-color: #c7d2fe; 2090 } 2091 2092 .fae-icon-chevron { 2093 width: 12px; 2094 height: 12px; 2095 fill: #9ca3af; 2096 margin-left: auto; 2097 transition: transform 0.2s; 2098 } 2099 2100 .fae-icon-dropdown.active + .fae-icon-trigger .fae-icon-chevron, 2101 .fae-icon-picker-inline:has(.fae-icon-dropdown.active) .fae-icon-chevron { 2102 transform: rotate(180deg); 2103 } 2104 2105 .fae-icon-preview-small { 2106 width: 20px; 2107 height: 20px; 2108 display: flex; 2109 align-items: center; 2110 justify-content: center; 2111 } 2112 2113 .fae-icon-preview-small svg { 2114 width: 18px; 2115 height: 18px; 2116 fill: #667eea; 2117 } 2118 2119 .fae-icon-name-small { 2120 font-size: 13px; 2121 color: #374151; 2122 } 2123 2124 /* Icon Dropdown - Opens UPWARD */ 2125 .fae-icon-dropdown { 2126 position: absolute; 2127 bottom: 100%; 2128 left: 0; 2129 right: 0; 2130 margin-bottom: 4px; 2131 background: white; 2132 border: 1px solid #d1d5db; 2133 border-radius: 8px; 2134 box-shadow: 0 -10px 40px rgba(0,0,0,0.2); 2135 z-index: 9999; 2136 display: none; 2137 max-height: 240px; 2138 overflow-y: auto; 2139 } 2140 2141 .fae-icon-dropdown.active { 2142 display: block; 2143 } 2144 2145 /* Ensure parent doesn't clip dropdown */ 2146 .fae-appearance-settings { 2147 overflow: visible !important; 2148 } 2149 2150 /* .fae-appearance-settings { 2151 overflow-y: scroll; 2152 height: 300px; 2153 } */ 2154 .fae-appearance-section { 2155 overflow: visible !important; 2156 } 2157 .fae-icon-picker-inline { 2158 position: relative; 2159 z-index: 100; 2160 } 2161 .fae-sidebar-preview { 2162 overflow: visible !important; 2163 } 2164 .fae-preview-card { 2165 overflow: visible !important; 2166 } 2167 2168 .fae-icon-dropdown-grid { 2169 display: grid; 2170 grid-template-columns: repeat(5, 1fr); 2171 gap: 4px; 2172 padding: 8px; 2173 } 2174 2175 .fae-icon-dropdown-item { 2176 width: 36px; 2177 height: 36px; 2178 display: flex; 2179 align-items: center; 2180 justify-content: center; 2181 border: 2px solid transparent; 2182 border-radius: 6px; 2183 cursor: pointer; 2184 transition: all 0.15s; 2185 } 2186 2187 .fae-icon-dropdown-item:hover { 2188 background: #f3f4f6; 2189 border-color: #e5e7eb; 2190 } 2191 2192 .fae-icon-dropdown-item.selected { 2193 background: #667eea15; 2194 border-color: #667eea; 2195 } 2196 2197 .fae-icon-dropdown-item svg { 2198 width: 20px; 2199 height: 20px; 2200 fill: #374151; 2201 } 2202 2203 /* Flag Picker Styles */ 2204 .fae-flag-picker-inline { 2205 position: relative; 2206 z-index: 50; 2207 } 2208 2209 .fae-flag-trigger { 2210 width: 100%; 2211 padding: 8px 12px; 2212 background: white; 2213 border: 2px solid #e5e7eb; 2214 border-radius: 6px; 2215 cursor: pointer; 2216 display: flex; 2217 align-items: center; 2218 gap: 10px; 2219 transition: border-color 0.2s; 2220 } 2221 2222 .fae-flag-trigger:hover { 2223 border-color: #c7d2fe; 2224 } 2225 2226 .fae-flag-preview-small { 2227 width: 24px; 2228 height: 18px; 2229 display: flex; 2230 align-items: center; 2231 justify-content: center; 2232 flex-shrink: 0; 2233 } 2234 2235 .fae-flag-preview-small img { 2236 width: 24px; 2237 height: 18px; 2238 object-fit: cover; 2239 border-radius: 2px; 2240 border: 1px solid #e5e7eb; 2241 } 2242 2243 .fae-flag-name-small { 2244 font-size: 13px; 2245 color: #374151; 2246 flex: 1; 2247 text-align: left; 2248 } 2249 2250 /* Flag Dropdown */ 2251 .fae-flag-dropdown { 2252 position: absolute; 2253 bottom: 100%; 2254 left: 0; 2255 right: 0; 2256 margin-bottom: 4px; 2257 background: white; 2258 border: 1px solid #d1d5db; 2259 border-radius: 8px; 2260 box-shadow: 0 -10px 40px rgba(0,0,0,0.2); 2261 z-index: 9999; 2262 display: none; 2263 max-height: 400px; 2264 overflow: hidden; 2265 flex-direction: column; 2266 } 2267 2268 .fae-flag-dropdown.active { 2269 display: flex; 2270 } 2271 2272 .fae-flag-dropdown-header { 2273 padding: 12px; 2274 border-bottom: 1px solid #e1e5e9; 2275 } 2276 2277 .fae-flag-search { 2278 width: 100%; 2279 padding: 8px 12px; 2280 border: 2px solid #e1e5e9; 2281 border-radius: 6px; 2282 font-size: 14px; 2283 transition: border-color 0.3s ease; 2284 } 2285 2286 .fae-flag-search:focus { 2287 outline: none; 2288 border-color: #667eea; 2289 box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); 2290 } 2291 2292 .fae-flag-dropdown-grid { 2293 padding: 12px; 2294 display: grid; 2295 grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); 2296 gap: 8px; 2297 max-height: 320px; 2298 overflow-y: auto; 2299 } 2300 2301 .fae-flag-dropdown-item { 2302 display: flex; 2303 flex-direction: column; 2304 align-items: center; 2305 padding: 10px 8px; 2306 border: 2px solid #e1e5e9; 2307 border-radius: 6px; 2308 cursor: pointer; 2309 transition: all 0.2s ease; 2310 background: white; 2311 text-align: center; 2312 } 2313 2314 .fae-flag-dropdown-item:hover { 2315 border-color: #667eea; 2316 transform: translateY(-2px); 2317 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15); 2318 } 2319 2320 .fae-flag-dropdown-item.selected { 2321 border-color: #667eea; 2322 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 2323 } 2324 2325 .fae-flag-preview-img { 2326 width: 48px; 2327 height: 36px; 2328 object-fit: cover; 2329 border-radius: 4px; 2330 border: 1px solid rgba(0,0,0,0.1); 2331 margin-bottom: 6px; 2332 } 2333 2334 .fae-flag-dropdown-item.selected .fae-flag-preview-img { 2335 border-color: rgba(255,255,255,0.5); 2336 } 2337 2338 .fae-flag-preview-placeholder { 2339 width: 48px; 2340 height: 36px; 2341 display: flex; 2342 align-items: center; 2343 justify-content: center; 2344 background: #f3f4f6; 2345 border-radius: 4px; 2346 margin-bottom: 6px; 2347 border: 1px solid #e5e7eb; 2348 } 2349 2350 .fae-flag-preview-placeholder svg { 2351 width: 24px; 2352 height: 24px; 2353 stroke: #9ca3af; 2354 } 2355 2356 .fae-flag-dropdown-item.selected .fae-flag-preview-placeholder { 2357 background: rgba(255,255,255,0.2); 2358 border-color: rgba(255,255,255,0.3); 2359 } 2360 2361 .fae-flag-dropdown-item.selected .fae-flag-preview-placeholder svg { 2362 stroke: white; 2363 } 2364 2365 .fae-flag-label { 2366 display: none; /* Hide country code label - search uses country names */ 2367 font-size: 10px; 2368 font-weight: 500; 2369 color: #374151; 2370 line-height: 1.2; 2371 word-break: break-word; 2372 } 2373 2374 .fae-flag-dropdown-item.selected .fae-flag-label { 2375 color: white; 2376 } 2377 2378 /* Scrollbar styling for flag grid */ 2379 .fae-flag-dropdown-grid::-webkit-scrollbar { 2380 width: 6px; 2381 } 2382 2383 .fae-flag-dropdown-grid::-webkit-scrollbar-track { 2384 background: #f1f1f1; 2385 border-radius: 3px; 2386 } 2387 2388 .fae-flag-dropdown-grid::-webkit-scrollbar-thumb { 2389 background: #c1c1c1; 2390 border-radius: 3px; 2391 } 2392 2393 .fae-flag-dropdown-grid::-webkit-scrollbar-thumb:hover { 2394 background: #a8a8a8; 2395 } 2396 2397 /* Pro Badge Styles */ 2398 .fae-pro-badge { 2399 display: inline-block; 2400 background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); 2401 color: white; 2402 font-size: 9px; 2403 font-weight: 700; 2404 padding: 2px 6px; 2405 border-radius: 3px; 2406 text-transform: uppercase; 2407 letter-spacing: 0.5px; 2408 margin-left: 6px; 2409 vertical-align: middle; 2410 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 2411 } 2412 2413 .fae-effect-option .fae-pro-badge { 2414 position: static; 2415 font-size: 9px; 2416 font-weight: 700; 2417 padding: 3px 6px; 2418 border-radius: 4px; 2419 text-transform: uppercase; 2420 letter-spacing: 0.5px; 2421 box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3); 2422 z-index: 10; 2423 pointer-events: none; 2424 line-height: 1.2; 2425 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 2426 color: white; 2427 white-space: nowrap; 2428 display: inline-flex; 2429 align-items: center; 2430 justify-content: center; 2431 margin-bottom: 4px; 2432 order: -1; 2433 } 2434 2435 /* Match multi-color setting pro badge to disabled effects style */ 2436 .fae-multi-color-setting .fae-pro-badge { 2437 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 2438 box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3); 2439 padding: 3px 6px; 2440 border-radius: 4px; 2441 } 2442 2443 /* Match interactive cursor setting pro badge to effect option style */ 2444 .fae-interactive-cursor-setting .fae-pro-badge { 2445 position: static; 2446 font-size: 9px; 2447 font-weight: 700; 2448 padding: 3px 6px; 2449 border-radius: 4px; 2450 text-transform: uppercase; 2451 letter-spacing: 0.5px; 2452 box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3); 2453 z-index: 10; 2454 pointer-events: none; 2455 line-height: 1.2; 2456 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 2457 color: white; 2458 white-space: nowrap; 2459 display: inline-flex; 2460 align-items: center; 2461 justify-content: center; 2462 margin-left: 6px; 2463 vertical-align: middle; 2464 } 2465 2466 /* Pro Locked Effect Styles */ 2467 .fae-effect-pro-locked { 2468 position: relative; 2469 cursor: not-allowed !important; 2470 pointer-events: auto; 2471 border-color: #e1e5e9 !important; 2472 opacity: 0.7; 2473 } 2474 2475 .fae-effect-pro-locked::before { 2476 content: ''; 2477 position: absolute; 2478 top: 0; 2479 left: 0; 2480 right: 0; 2481 bottom: 0; 2482 background: linear-gradient(135deg, rgba(102, 126, 234, 0.06) 0%, rgba(118, 75, 162, 0.06) 100%); 2483 border-radius: 8px; 2484 z-index: 1; 2485 pointer-events: none; 2486 border: 1px dashed rgba(102, 126, 234, 0.25); 2487 } 2488 2489 .fae-effect-pro-locked .fae-effect-content { 2490 position: relative; 2491 z-index: 0; 2492 } 2493 2494 .fae-effect-pro-locked .fae-effect-letter { 2495 display: none; 2496 visibility: hidden; 2497 } 2498 2499 .fae-effect-pro-locked .fae-effect-name { 2500 opacity: 0.6; 2501 color: #64748b; 2502 order: 1; 2503 } 2504 2505 .fae-effect-pro-locked:hover { 2506 transform: none !important; 2507 box-shadow: none !important; 2508 border-color: #e1e5e9 !important; 2509 } 2510 2511 .fae-effect-pro-locked:hover::before { 2512 border-color: rgba(102, 126, 234, 0.35); 2513 } 2514 2515 .fae-effect-pro-locked .fae-pro-badge { 2516 z-index: 10; 2517 opacity: 1; 2518 filter: none; 2519 } 2520 2521 /* Upgrade Notice Styles */ 2522 .fae-upgrade-notice { 2523 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 2524 border-radius: 8px; 2525 padding: 16px; 2526 margin: 16px 0; 2527 color: white; 2528 } 2529 2530 .fae-upgrade-notice-content { 2531 display: flex; 2532 align-items: center; 2533 gap: 16px; 2534 } 2535 2536 .fae-upgrade-icon { 2537 width: 32px; 2538 height: 32px; 2539 flex-shrink: 0; 2540 stroke: white; 2541 } 2542 2543 .fae-upgrade-text { 2544 flex: 1; 2545 } 2546 2547 .fae-upgrade-text strong { 2548 display: block; 2549 font-size: 14px; 2550 margin-bottom: 4px; 2551 } 2552 2553 .fae-upgrade-text p { 2554 margin: 0; 2555 font-size: 12px; 2556 opacity: 0.9; 2557 } 2558 2559 .fae-upgrade-button { 2560 background: white; 2561 color: #667eea; 2562 padding: 8px 16px; 2563 border-radius: 6px; 2564 text-decoration: none; 2565 font-weight: 600; 2566 font-size: 13px; 2567 transition: transform 0.2s, box-shadow 0.2s; 2568 white-space: nowrap; 2569 flex-shrink: 0; 2570 } 2571 2572 .fae-upgrade-button:hover { 2573 transform: translateY(-1px); 2574 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 2575 color: #764ba2; 2576 } 2577 2578 /* Effect Type Disabled Styles (Free Version Restriction) */ 2579 /* Effects appear normal but show upgrade modal when clicked */ 2580 .fae-effect-type-disabled { 2581 position: relative; 2582 cursor: pointer !important; 2583 pointer-events: auto; 2584 /* No visual dimming - keep completely normal appearance */ 2585 } 2586 2587 /* Upgrade Notice Modal */ 2588 .fae-upgrade-modal { 2589 position: fixed; 2590 top: 0; 2591 left: 0; 2592 right: 0; 2593 bottom: 0; 2594 z-index: 100000; 2595 display: flex; 2596 align-items: center; 2597 justify-content: center; 2598 opacity: 0; 2599 visibility: hidden; 2600 transition: opacity 0.3s ease, visibility 0.3s ease; 2601 } 2602 2603 .fae-upgrade-modal.active { 2604 opacity: 1; 2605 visibility: visible; 2606 } 2607 2608 .fae-upgrade-modal-backdrop { 2609 position: absolute; 2610 top: 0; 2611 left: 0; 2612 right: 0; 2613 bottom: 0; 2614 background: rgba(0, 0, 0, 0.6); 2615 backdrop-filter: blur(4px); 2616 } 2617 2618 .fae-upgrade-modal-content { 2619 position: relative; 2620 background: white; 2621 border-radius: 16px; 2622 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); 2623 max-width: 480px; 2624 width: 90%; 2625 max-height: 90vh; 2626 overflow-y: auto; 2627 transform: scale(0.95); 2628 transition: transform 0.3s ease; 2629 z-index: 1; 2630 } 2631 2632 .fae-upgrade-modal.active .fae-upgrade-modal-content { 2633 transform: scale(1); 2634 } 2635 2636 .fae-upgrade-modal-close { 2637 position: absolute; 2638 top: 16px; 2639 right: 16px; 2640 background: #f1f5f9; 2641 border: none; 2642 border-radius: 8px; 2643 width: 32px; 2644 height: 32px; 2645 display: flex; 2646 align-items: center; 2647 justify-content: center; 2648 cursor: pointer; 2649 color: #64748b; 2650 transition: all 0.2s ease; 2651 z-index: 10; 2652 } 2653 2654 .fae-upgrade-modal-close:hover { 2655 background: #e2e8f0; 2656 color: #475569; 2657 transform: rotate(90deg); 2658 } 2659 2660 .fae-upgrade-modal-body { 2661 padding: 40px; 2662 text-align: center; 2663 } 2664 2665 .fae-upgrade-modal-body .fae-upgrade-icon { 2666 width: 64px; 2667 height: 64px; 2668 margin: 0 auto 24px; 2669 stroke: #667eea; 2670 stroke-width: 2; 2671 } 2672 2673 .fae-upgrade-modal-body h3 { 2674 font-size: 24px; 2675 font-weight: 700; 2676 color: #1e293b; 2677 margin: 0 0 12px; 2678 } 2679 2680 .fae-upgrade-modal-body p { 2681 font-size: 15px; 2682 line-height: 1.6; 2683 color: #64748b; 2684 margin: 0 0 32px; 2685 } 2686 2687 .fae-upgrade-modal-body .fae-upgrade-button { 2688 display: inline-block; 2689 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 2690 color: white; 2691 padding: 12px 32px; 2692 border-radius: 8px; 2693 text-decoration: none; 2694 font-weight: 600; 2695 font-size: 15px; 2696 transition: transform 0.2s, box-shadow 0.2s; 2697 } 2698 2699 .fae-upgrade-modal-body .fae-upgrade-button:hover { 2700 transform: translateY(-2px); 2701 box-shadow: 0 8px 16px rgba(102, 126, 234, 0.4); 2702 color: white; 2703 } 2704 2705 @media (max-width: 640px) { 2706 .fae-upgrade-modal-content { 2707 width: 95%; 2708 margin: 20px; 2709 } 2710 2711 .fae-upgrade-modal-body { 2712 padding: 32px 24px; 2713 } 2714 2715 .fae-upgrade-modal-body h3 { 2716 font-size: 20px; 2717 } 2718 2719 .fae-upgrade-modal-body p { 2720 font-size: 14px; 2721 } 2722 } 2723 2724 /* ======================================== 2725 LIMITED CUSTOMIZATION - LOCKED STYLES 2726 ======================================== */ 2727 2728 /* Inline Pro badge for limited customization */ 2729 .fae-pro-badge-inline { 2730 display: inline-flex; 2731 align-items: center; 2732 padding: 2px 8px; 2733 font-size: 10px; 2734 font-weight: 700; 2735 border-radius: 4px; 2736 box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3); 2737 line-height: 1.2; 2738 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 2739 color: white; 2740 white-space: nowrap; 2741 margin-left: 6px; 2742 vertical-align: middle; 2743 } 2744 2745 /* Locked color swatch for free users */ 2746 .fae-color-locked { 2747 display: flex; 2748 align-items: center; 2749 gap: 8px; 2750 } 2751 2752 .fae-color-swatch-locked { 2753 width: 32px; 2754 height: 32px; 2755 border-radius: 6px; 2756 border: 2px solid #e5e7eb; 2757 cursor: not-allowed; 2758 position: relative; 2759 flex-shrink: 0; 2760 } 2761 2762 .fae-color-swatch-locked::after { 2763 content: ''; 2764 position: absolute; 2765 top: 50%; 2766 left: 50%; 2767 transform: translate(-50%, -50%); 2768 width: 16px; 2769 height: 16px; 2770 background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='11' width='18' height='11' rx='2' ry='2'%3E%3C/rect%3E%3Cpath d='M7 11V7a5 5 0 0 1 10 0v4'%3E%3C/path%3E%3C/svg%3E"); 2771 background-size: contain; 2772 background-repeat: no-repeat; 2773 opacity: 0.8; 2774 } 2775 2776 .fae-color-locked-text { 2777 font-family: 'SF Mono', SFMono-Regular, ui-monospace, monospace; 2778 font-size: 12px; 2779 color: #9ca3af; 2780 text-transform: uppercase; 2781 } 2782 2783 /* Locked speed select for free users */ 2784 .fae-speed-locked { 2785 background-color: #f9fafb; 2786 cursor: not-allowed; 2787 } 2788 2789 .fae-speed-locked option:disabled { 2790 color: #9ca3af; 2791 font-style: italic; 2792 } 2793 2794 /* Show Pro badge on color setting when customization is limited */ 2795 .fae-color-setting[data-limited-customization="1"] .fae-pro-badge-inline { 2796 display: inline-block; 2797 } 2798 2799 /* Hide Pro badge when customization is not limited */ 2800 .fae-color-setting[data-limited-customization="0"] .fae-pro-badge-inline { 2801 display: none; 2802 } 2803 2804 /* Plugin title + version below settings panel (bottom left) */ 2805 .fae-dashboard-footer-version { 2806 margin: 20px 0 0 0; 2807 padding: 0; 2808 font-size: 13px; 2809 color: #646970; 2810 text-align: left; 2811 } 2812 -
faecursor/trunk/assets/js/fae-cursor-admin.js
r3384653 r3454888 1 1 /** 2 2 * FaeCursor Admin Dashboard JavaScript 3 * Handles live preview, settings management,and interactive features3 * Handles settings management and interactive features 4 4 */ 5 5 … … 7 7 "use strict"; 8 8 9 // Debounce utility function (with browser compatibility) 10 const debounce = (func, wait) => { 11 let timeout; 12 return function executedFunction() { 13 const args = arguments; 14 const context = this; 15 const later = function() { 16 clearTimeout(timeout); 17 func.apply(context, args); 18 }; 19 clearTimeout(timeout); 20 timeout = setTimeout(later, wait); 21 }; 22 }; 23 24 // Fetch polyfill check for older browsers 25 if (typeof fetch === 'undefined') { 26 // If fetch is not available, use XMLHttpRequest as fallback 27 window.fetch = function(url) { 28 return new Promise(function(resolve, reject) { 29 const xhr = new XMLHttpRequest(); 30 xhr.open('GET', url); 31 xhr.onload = function() { 32 if (xhr.status >= 200 && xhr.status < 300) { 33 resolve({ 34 ok: true, 35 status: xhr.status, 36 text: function() { return Promise.resolve(xhr.responseText); } 37 }); 38 } else { 39 reject(new Error('HTTP ' + xhr.status)); 40 } 41 }; 42 xhr.onerror = reject; 43 xhr.send(); 44 }); 45 }; 46 } 47 48 // Effects with FULL customization (no color/speed limits) 49 // All other cursor effects have limited customization for free users 50 const FULL_CUSTOMIZATION_EFFECTS = [ 51 'none', 52 'drop-effect', 53 'rise-effect', 54 'line-effect', 55 'duo-circle', 56 'duo-circle-2', 57 ]; 58 59 // Keyboard effects with FULL color customization (no limits) 60 // All other keyboard effects have limited color for free users 61 const KEYBOARD_FULL_COLOR_EFFECTS = [ 62 'none', 63 'sparkle-keys', // Uses multi-color feature instead 64 ]; 65 66 // Particle effects with FULL customization (no limits) 67 // All other particle effects have limited customization for free users 68 const PARTICLE_FULL_CUSTOMIZATION_EFFECTS = [ 69 'none', 70 'particle-10' // Snowfall - fully customizable 71 ]; 72 73 // Default colors for free users on limited effects 74 const FREE_DEFAULT_COLORS = { 75 cursor: '#fcba03', 76 keyboard: '#667eea', 77 particle: '#a855f7' 78 }; 79 9 80 // Admin Dashboard Controller 10 81 const FaeAdmin = { 82 // Store saved effects state (what's actually saved, not form selections) 83 savedEffects: { 84 cursor: 'none', 85 keyboard: 'none', 86 particle: 'none' 87 }, 88 89 // Store user-selected colors (to preserve when switching between limited/full effects) 90 userSelectedColors: { 91 cursor: null, 92 keyboard: null, 93 particle: null 94 }, 95 96 // Store user-selected speeds (to preserve when switching between limited/full effects) 97 userSelectedSpeeds: { 98 cursor: null, 99 particle: null 100 }, 101 102 // Track which tabs have shown the upgrade modal (to avoid showing multiple times per tab) 103 upgradeModalShownForTab: { 104 cursor: false, 105 keyboard: false, 106 particle: false 107 }, 108 109 // Check if Pro plugin is active (from server-side detection) 110 // Use truthy check to handle PHP type coercion (boolean/integer/string) 111 isPremium: typeof faeAdminData !== 'undefined' && !!faeAdminData.isPremium, 112 113 // Check if a cursor effect has limited customization 114 effectHasLimitedCustomization: function(effectId) { 115 return !FULL_CUSTOMIZATION_EFFECTS.includes(effectId); 116 }, 117 118 // Check if user can customize color for cursor effect (free: only for non-limited effects) 119 canCustomizeColor: function(effectId) { 120 if (!this.effectHasLimitedCustomization(effectId)) { 121 return true; 122 } 123 return this.isPremium; 124 }, 125 126 // Check if user can customize speed for cursor effect (free: only for non-limited effects) 127 canCustomizeSpeed: function(effectId) { 128 if (!this.effectHasLimitedCustomization(effectId)) { 129 return true; 130 } 131 return this.isPremium; 132 }, 133 134 // Check if a keyboard effect has limited color customization 135 keyboardEffectHasLimitedColor: function(effectId) { 136 return !KEYBOARD_FULL_COLOR_EFFECTS.includes(effectId); 137 }, 138 139 // Check if user can customize color for keyboard effect (free: only for non-limited effects) 140 canCustomizeKeyboardColor: function(effectId) { 141 if (!this.keyboardEffectHasLimitedColor(effectId)) { 142 return true; 143 } 144 return this.isPremium; 145 }, 146 147 // Check if a particle effect has limited customization 148 particleEffectHasLimitedCustomization: function(effectId) { 149 return !PARTICLE_FULL_CUSTOMIZATION_EFFECTS.includes(effectId); 150 }, 151 152 // Check if user can customize color for particle effect (free: only for non-limited effects) 153 canCustomizeParticleColor: function(effectId) { 154 if (!this.particleEffectHasLimitedCustomization(effectId)) { 155 return true; 156 } 157 return this.isPremium; 158 }, 159 160 // Check if user can customize speed for particle effect (free: only for non-limited effects) 161 canCustomizeParticleSpeed: function(effectId) { 162 if (!this.particleEffectHasLimitedCustomization(effectId)) { 163 return true; 164 } 165 return this.isPremium; 166 }, 167 11 168 init: function () { 169 // Initialize saved effects state from form values (which reflect saved state on page load) 170 this.savedEffects.cursor = $('input[name="fae_cursor_options[effect]"]:checked').val() || 'none'; 171 this.savedEffects.keyboard = $('input[name="fae_keyboard_options[effect]"]:checked').val() || 'none'; 172 this.savedEffects.particle = $('input[name="fae_particle_options[effect]"]:checked').val() || 'none'; 173 174 // Initialize user-selected colors from saved values (to preserve across effect switches) 175 this.userSelectedColors.cursor = $('#fae-color').val() || $('#cursor-effects-tab input[name="fae_cursor_options[color]"]').val() || FREE_DEFAULT_COLORS.cursor; 176 this.userSelectedColors.keyboard = $('#fae-keyboard-color').val() || $('#keyboard-effects-tab input[name="fae_keyboard_options[color]"]').val() || FREE_DEFAULT_COLORS.keyboard; 177 this.userSelectedColors.particle = $('#fae-particle-color').val() || $('#particle-effects-tab input[name="fae_particle_options[color]"]').val() || FREE_DEFAULT_COLORS.particle; 178 179 // Initialize user-selected speeds from saved values 180 this.userSelectedSpeeds.cursor = $('#fae-speed').val() || 'normal'; 181 this.userSelectedSpeeds.particle = $('#fae-particle-speed').val() || 'normal'; 182 12 183 this.bindEvents(); 13 184 this.updateStats(); 14 185 this.loadSettings(); 186 this.updateEffectTypeRestrictions(); 15 187 }, 16 188 17 189 bindEvents: function () { 18 // Effect selection 19 $(".fae-effect-option").on("click", this.handleEffectChange); 190 // Tab switching 191 $(".fae-tab-button").on("click", this.handleTabSwitch); 192 193 // Cursor effect selection 194 $("#cursor-effects-tab .fae-effect-option").on("click", function(e) { 195 // Prevent clicking on Pro locked effects 196 if ($(this).hasClass('fae-effect-pro-locked')) { 197 e.preventDefault(); 198 e.stopPropagation(); 199 return false; 200 } 201 // Only handle if clicking on the option itself, not on nested elements 202 if ($(e.target).closest('.fae-effect-option').is($(this))) { 203 const $radio = $(this).find('input[type="radio"]'); 204 if ($radio.length && !$radio.is(':disabled')) { 205 const effectValue = $radio.val(); 206 const wasChecked = $radio.is(':checked'); 207 208 // Select the effect first (for preview) 209 $radio.prop('checked', true); 210 211 // Show upgrade modal on first click if another effect type is active 212 if (effectValue !== 'none' && FaeAdmin.isAnotherEffectTypeActive('cursor')) { 213 // Only show modal once per tab session 214 if (!FaeAdmin.upgradeModalShownForTab.cursor) { 215 FaeAdmin.showUpgradeNoticeModal(); 216 FaeAdmin.upgradeModalShownForTab.cursor = true; 217 } 218 } 219 220 // Trigger change event to ensure preview updates (allow preview) 221 if (!wasChecked) { 222 $radio.trigger('change'); 223 } else { 224 // If already checked, call handler directly to update preview 225 FaeAdmin.handleEffectChange.call($radio[0]); 226 } 227 } 228 } 229 }); 20 230 $('input[name="fae_cursor_options[effect]"]').on( 21 231 "change", 22 232 this.handleEffectChange 23 233 ); 234 235 // Keyboard effect selection 236 $('input[name="fae_keyboard_options[effect]"]').on( 237 "change", 238 this.handleKeyboardEffectChange 239 ); 240 241 // Also handle keyboard effect option clicks 242 $("#keyboard-effects-tab .fae-effect-option").on("click", function(e) { 243 // Prevent clicking on Pro locked effects 244 if ($(this).hasClass('fae-effect-pro-locked')) { 245 e.preventDefault(); 246 e.stopPropagation(); 247 return false; 248 } 249 const $radio = $(this).find('input[type="radio"]'); 250 if ($radio.length && !$radio.is(':disabled')) { 251 const effectValue = $radio.val(); 252 const wasChecked = $radio.is(':checked'); 253 254 // Select the effect first (for preview) 255 $radio.prop('checked', true); 256 257 // Show upgrade modal on first click if another effect type is active 258 if (effectValue !== 'none' && FaeAdmin.isAnotherEffectTypeActive('keyboard')) { 259 // Only show modal once per tab session 260 if (!FaeAdmin.upgradeModalShownForTab.keyboard) { 261 FaeAdmin.showUpgradeNoticeModal(); 262 FaeAdmin.upgradeModalShownForTab.keyboard = true; 263 } 264 } 265 266 // Trigger change event to ensure preview updates (allow preview) 267 if (!wasChecked) { 268 $radio.trigger('change'); 269 } else { 270 // If already checked, call handler directly to update preview 271 FaeAdmin.handleKeyboardEffectChange.call($radio[0]); 272 } 273 } 274 }); 275 276 // Particle effect selection 277 $('input[name="fae_particle_options[effect]"]').on( 278 "change", 279 this.handleParticleEffectChange 280 ); 281 282 // Also handle particle effect option clicks 283 $("#particle-effects-tab .fae-effect-option").on("click", function(e) { 284 // Prevent clicking on Pro locked effects 285 if ($(this).hasClass('fae-effect-pro-locked')) { 286 e.preventDefault(); 287 e.stopPropagation(); 288 return false; 289 } 290 const $radio = $(this).find('input[type="radio"]'); 291 if ($radio.length && !$radio.is(':disabled')) { 292 const effectValue = $radio.val(); 293 const wasChecked = $radio.is(':checked'); 294 295 // Select the effect first (for preview) 296 $radio.prop('checked', true); 297 298 // Show upgrade modal on first click if another effect type is active 299 if (effectValue !== 'none' && FaeAdmin.isAnotherEffectTypeActive('particle')) { 300 // Only show modal once per tab session 301 if (!FaeAdmin.upgradeModalShownForTab.particle) { 302 FaeAdmin.showUpgradeNoticeModal(); 303 FaeAdmin.upgradeModalShownForTab.particle = true; 304 } 305 } 306 307 // Trigger change event to ensure preview updates (allow preview) 308 if (!wasChecked) { 309 $radio.trigger('change'); 310 } else { 311 // If already checked, call handler directly to update preview 312 FaeAdmin.handleParticleEffectChange.call($radio[0]); 313 } 314 } 315 }); 24 316 25 317 // Settings changes … … 28 320 this.handleSettingChange 29 321 ); 30 31 // Color picker changes 322 323 // Checkbox changes (need special handling) - including toggle switch 324 $('input[type="checkbox"][name*="fae_cursor_options"]').on( 325 "change", 326 this.handleCheckboxChange 327 ); 328 329 // Also handle toggle switch specifically 330 $('#fae_hide_default_cursor_toggle').on( 331 "change", 332 this.handleCheckboxChange 333 ); 334 335 // Color picker changes - use 'input' for real-time updates while selecting 336 $(".fae-color-picker").on("input", this.handleColorChange); 32 337 $(".fae-color-picker").on("change", this.handleColorChange); 33 338 … … 40 345 $(document).on("keydown", this.handleKeydown); 41 346 42 // Form submission 43 $("#fae-cursor-form").on("submit", this.handleFormSubmit); 44 45 // Reset settings 46 $(".fae-reset-settings").on("click", this.resetSettings); 347 // Form submission - bind all three forms 348 $("#fae-cursor-form, #fae-keyboard-form, #fae-particle-form").on("submit", this.handleFormSubmit); 349 350 // Scope type dropdown changes 351 $(".fae-scope-type-select").on("change", this.handleScopeTypeChange); 352 353 // User restriction type radio button changes 354 $(".fae-user-restriction-type").on("change", this.handleUserRestrictionTypeChange); 355 356 // Preview iframe updates 357 this.initPreviewIframes(); 358 359 // Info button tooltip (close on outside click) 360 $(document).on("click", function(e) { 361 if (!$(e.target).closest('.fae-info-button').length) { 362 $('.fae-info-button').removeClass('active'); 363 } 364 }); 365 366 // Toggle tooltip on click for mobile/touch devices 367 $(document).on("click", ".fae-info-button", function(e) { 368 e.stopPropagation(); 369 $(this).toggleClass('active'); 370 }); 371 }, 372 373 handleTabSwitch: function (e) { 374 e.preventDefault(); 375 const $button = $(this); 376 const targetTab = $button.data("tab"); 377 378 // Remove active class from all buttons and tabs 379 $(".fae-tab-button").removeClass("active"); 380 $(".fae-tab-content").removeClass("active"); 381 382 // Add active class to clicked button and corresponding tab 383 $button.addClass("active"); 384 $("#" + targetTab + "-tab").addClass("active"); 385 386 // Update visibility of settings when switching to keyboard effects tab 387 if (targetTab === 'keyboard-effects') { 388 const keyboardEffect = $('input[name="fae_keyboard_options[effect]"]:checked').val() || 'none'; 389 if (keyboardEffect === 'sparkle-keys') { 390 $('#keyboard-effects-tab .fae-multi-color-setting').show(); 391 $('#keyboard-effects-tab .fae-color-setting').hide(); 392 // Always show color picker for sparkle-keys (multi-color is disabled - Pro feature) 393 $('#keyboard-effects-tab .fae-color-setting-sparkle').show(); 394 } else { 395 $('#keyboard-effects-tab .fae-multi-color-setting').hide(); 396 $('#keyboard-effects-tab .fae-color-setting').show(); 397 $('#keyboard-effects-tab .fae-color-setting-sparkle').hide(); 398 } 399 } 400 401 // Don't reset the upgrade modal flag - it should persist across tab switches 402 // Modal will only show once per tab per session, even if user switches away and comes back 403 }, 404 405 handleKeyboardEffectChange: function () { 406 const effect = $(this).val(); 407 const effectSettings = $(".fae-keyboard-effect-settings"); 408 409 // Show/hide settings based on effect 410 if (effect === "none") { 411 effectSettings.hide(); 412 } else { 413 effectSettings.show(); 414 } 415 416 // Show/hide multi-color setting for sparkle-keys 417 if (effect === 'sparkle-keys') { 418 $('#keyboard-effects-tab .fae-multi-color-setting').show(); 419 $('#keyboard-effects-tab .fae-color-setting').hide(); 420 // Always show color picker for sparkle-keys (multi-color is disabled - Pro feature) 421 $('#keyboard-effects-tab .fae-color-setting-sparkle').show(); 422 } else { 423 $('#keyboard-effects-tab .fae-multi-color-setting').hide(); 424 $('#keyboard-effects-tab .fae-color-setting').show(); 425 $('#keyboard-effects-tab .fae-color-setting-sparkle').hide(); 426 } 427 428 // Hide/show entire appearance section in preview when effect is 'none' 429 const $appearanceSection = $('#keyboard-effects-tab .fae-appearance-section'); 430 if (effect === 'none') { 431 $appearanceSection.hide(); 432 } else { 433 $appearanceSection.show(); 434 } 435 436 // Visually mark selected option within its grid (purple state) 437 const $option = $(this).closest(".fae-effect-option"); 438 if ($option.length) { 439 const $grid = $option.closest(".fae-effect-grid"); 440 if ($grid.length) { 441 $grid.find(".fae-effect-option").removeClass("fae-effect-selected"); 442 } else { 443 $("#keyboard-effects-tab .fae-effect-option").removeClass("fae-effect-selected"); 444 } 445 $option.addClass("fae-effect-selected"); 446 } 447 448 // Update preview iframe 449 if (typeof FaeAdmin.updatePreviewIframe === 'function') { 450 FaeAdmin.updatePreviewIframe('keyboard'); 451 } 452 453 // Update effect type restrictions when effect changes 454 FaeAdmin.updateEffectTypeRestrictions(); 455 456 // Update limited customization UI for keyboard effects 457 FaeAdmin.updateKeyboardLimitedCustomization(effect); 458 }, 459 460 // Update keyboard effect color UI based on limited customization rules 461 updateKeyboardLimitedCustomization: function(effect) { 462 const hasLimitedColor = this.keyboardEffectHasLimitedColor(effect); 463 const canCustomizeColor = this.canCustomizeKeyboardColor(effect); 464 465 // Update data attributes for CSS 466 $('#keyboard-effects-tab .fae-keyboard-color-setting').attr('data-limited-customization', hasLimitedColor ? '1' : '0'); 467 468 // Update color setting (but not for sparkle-keys which uses multi-color) 469 if (effect !== 'sparkle-keys') { 470 const $colorSetting = $('#keyboard-effects-tab .fae-keyboard-color-setting'); 471 const $colorLabel = $colorSetting.find('label'); 472 const $colorInput = $colorSetting.find('.fae-color-input-inline'); 473 474 // Update Pro badge in label 475 if (hasLimitedColor && !canCustomizeColor) { 476 // SAVE user's current color before locking (if not already the default) 477 const currentColorVal = $('#fae-keyboard-color').val(); 478 if (currentColorVal && currentColorVal !== FREE_DEFAULT_COLORS.keyboard) { 479 this.userSelectedColors.keyboard = currentColorVal; 480 } 481 482 // Add Pro badge if not present 483 if (!$colorLabel.find('.fae-pro-badge-inline').length) { 484 $colorLabel.append('<span class="fae-pro-badge fae-pro-badge-inline">PRO</span>'); 485 } 486 // Replace color input with locked swatch 487 $colorInput.addClass('fae-color-locked'); 488 $colorInput.html( 489 '<div class="fae-color-swatch-locked" style="background-color: ' + FREE_DEFAULT_COLORS.keyboard + ';" title="Upgrade to Pro to customize color"></div>' + 490 '<input type="hidden" name="fae_keyboard_options[color]" value="' + FREE_DEFAULT_COLORS.keyboard + '" id="fae-keyboard-color">' + 491 '<span class="fae-color-locked-text">' + FREE_DEFAULT_COLORS.keyboard + '</span>' 492 ); 493 } else { 494 // Remove Pro badge if present 495 $colorLabel.find('.fae-pro-badge-inline').remove(); 496 // Restore color input with user's SAVED color (not current hidden input value) 497 $colorInput.removeClass('fae-color-locked'); 498 const restoreColor = this.userSelectedColors.keyboard || FREE_DEFAULT_COLORS.keyboard; 499 $colorInput.html( 500 '<input type="color" class="fae-color-picker-inline" name="fae_keyboard_options[color]" value="' + restoreColor + '" id="fae-keyboard-color">' + 501 '<input type="text" value="' + restoreColor + '" id="fae-keyboard-color-text">' 502 ); 503 // Re-bind color change events 504 this.bindColorEvents(); 505 } 506 } 507 }, 508 509 handleParticleEffectChange: function () { 510 const effect = $(this).val(); 511 const effectSettings = $("#particle-effects-tab .fae-effect-settings"); 512 const $appearanceSection = $('#particle-effects-tab .fae-appearance-section'); 513 514 // Show/hide settings and appearance based on effect 515 if (effect === "none") { 516 effectSettings.hide(); 517 $appearanceSection.hide(); 518 $('#particle-effects-tab .fae-hide-cursor-toggle-wrapper').hide(); 519 } else { 520 effectSettings.show(); 521 $appearanceSection.show(); 522 523 // Get effect config for showing/hiding settings 524 const effectConfig = typeof FAE_PARTICLE_EFFECTS_CONFIG !== 'undefined' && FAE_PARTICLE_EFFECTS_CONFIG[effect] 525 ? FAE_PARTICLE_EFFECTS_CONFIG[effect] 526 : {}; 527 528 // Show/hide toggle in header based on config 529 const showHideCursor = effectConfig.supportsHideCursor !== false; 530 $('#particle-effects-tab .fae-hide-cursor-toggle-wrapper').toggle(showHideCursor); 531 532 // Show/hide color setting based on effect config (supportsColor) 533 const showColor = effectConfig.supportsColor !== false; 534 $('#particle-effects-tab .fae-particle-color-setting').toggle(showColor); 535 536 // Show/hide speed setting based on effect config (supportsSpeed) 537 const showSpeed = effectConfig.supportsSpeed !== false; 538 $('#particle-effects-tab .fae-particle-speed-setting').toggle(showSpeed); 539 540 // Show/hide Interactive Cursor option based on effect config 541 const showInteractiveCursor = effectConfig.supportsInteractiveCursor === true; 542 $('#particle-effects-tab .fae-interactive-cursor-setting').toggle(showInteractiveCursor); 543 } 544 545 // Visually mark selected option within its grid (purple state) 546 const $option = $(this).closest(".fae-effect-option"); 547 if ($option.length) { 548 const $grid = $option.closest(".fae-effect-grid"); 549 if ($grid.length) { 550 $grid.find(".fae-effect-option").removeClass("fae-effect-selected"); 551 } else { 552 $("#particle-effects-tab .fae-effect-option").removeClass("fae-effect-selected"); 553 } 554 $option.addClass("fae-effect-selected"); 555 } 556 557 // Update preview iframe 558 if (typeof FaeAdmin.updatePreviewIframe === 'function') { 559 FaeAdmin.updatePreviewIframe('particle'); 560 } 561 562 // Update effect type restrictions when effect changes 563 FaeAdmin.updateEffectTypeRestrictions(); 564 565 // Update limited customization UI for particle effects 566 FaeAdmin.updateParticleLimitedCustomization(effect); 567 }, 568 569 // Update particle effect color/speed UI based on limited customization rules 570 updateParticleLimitedCustomization: function(effect) { 571 const hasLimitedCustomization = this.particleEffectHasLimitedCustomization(effect); 572 const canCustomizeColor = this.canCustomizeParticleColor(effect); 573 const canCustomizeSpeed = this.canCustomizeParticleSpeed(effect); 574 575 // Update data attributes for CSS 576 $('#particle-effects-tab .fae-particle-color-setting').attr('data-limited-customization', hasLimitedCustomization ? '1' : '0'); 577 $('#particle-effects-tab .fae-particle-speed-setting').attr('data-limited-customization', hasLimitedCustomization ? '1' : '0'); 578 579 // Update color setting 580 const $colorSetting = $('#particle-effects-tab .fae-particle-color-setting'); 581 const $colorLabel = $colorSetting.find('label'); 582 const $colorInput = $colorSetting.find('.fae-color-input-inline'); 583 584 // Update Pro badge in label 585 if (hasLimitedCustomization && !canCustomizeColor) { 586 // SAVE user's current color before locking (if not already the default) 587 const currentColorVal = $('#fae-particle-color').val(); 588 if (currentColorVal && currentColorVal !== FREE_DEFAULT_COLORS.particle) { 589 this.userSelectedColors.particle = currentColorVal; 590 } 591 592 // Add Pro badge if not present 593 if (!$colorLabel.find('.fae-pro-badge-inline').length) { 594 $colorLabel.append('<span class="fae-pro-badge fae-pro-badge-inline">PRO</span>'); 595 } 596 // Replace color input with locked swatch 597 $colorInput.addClass('fae-color-locked'); 598 $colorInput.html( 599 '<div class="fae-color-swatch-locked" style="background-color: ' + FREE_DEFAULT_COLORS.particle + ';" title="Upgrade to Pro to customize color"></div>' + 600 '<input type="hidden" name="fae_particle_options[color]" value="' + FREE_DEFAULT_COLORS.particle + '" id="fae-particle-color">' + 601 '<span class="fae-color-locked-text">' + FREE_DEFAULT_COLORS.particle + '</span>' 602 ); 603 } else { 604 // Remove Pro badge if present 605 $colorLabel.find('.fae-pro-badge-inline').remove(); 606 // Restore color input with user's SAVED color (not current hidden input value) 607 $colorInput.removeClass('fae-color-locked'); 608 const restoreColor = this.userSelectedColors.particle || FREE_DEFAULT_COLORS.particle; 609 $colorInput.html( 610 '<input type="color" class="fae-color-picker-inline" name="fae_particle_options[color]" value="' + restoreColor + '" id="fae-particle-color">' + 611 '<input type="text" value="' + restoreColor + '" id="fae-particle-color-text">' 612 ); 613 // Re-bind color change events 614 this.bindColorEvents(); 615 } 616 617 // Update speed setting 618 const $speedSetting = $('#particle-effects-tab .fae-particle-speed-setting'); 619 const $speedSelect = $speedSetting.find('select'); 620 621 if (hasLimitedCustomization && !canCustomizeSpeed) { 622 // SAVE user's current speed before locking 623 const currentSpeedVal = $speedSelect.val(); 624 if (currentSpeedVal && currentSpeedVal !== 'normal') { 625 this.userSelectedSpeeds.particle = currentSpeedVal; 626 } 627 628 // Disable slow and fast options, force normal 629 $speedSelect.addClass('fae-speed-locked'); 630 $speedSelect.find('option').each(function() { 631 const val = $(this).val(); 632 if (val === 'slow' || val === 'fast') { 633 $(this).prop('disabled', true); 634 if (!$(this).text().includes('(Pro)')) { 635 $(this).text($(this).text() + ' (Pro)'); 636 } 637 } else { 638 $(this).prop('disabled', false); 639 } 640 }); 641 // Force to normal 642 $speedSelect.val('normal'); 643 } else { 644 // Enable all speed options and RESTORE user's saved speed 645 $speedSelect.removeClass('fae-speed-locked'); 646 $speedSelect.find('option').each(function() { 647 $(this).prop('disabled', false); 648 $(this).text($(this).text().replace(' (Pro)', '')); 649 }); 650 // Restore user's saved speed 651 if (this.userSelectedSpeeds.particle) { 652 $speedSelect.val(this.userSelectedSpeeds.particle); 653 } 654 } 47 655 }, 48 656 49 657 handleEffectChange: function () { 50 const effect = $(this).find('input[type="radio"]').val() || $(this).val(); 658 // Support both label click and direct radio change 659 const $option = $(this).closest(".fae-effect-option").length 660 ? $(this).closest(".fae-effect-option") 661 : $(this).parent(".fae-effect-option"); 662 const effect = 663 $option.find('input[type="radio"]').val() || $(this).val(); 664 665 // Visually mark selected option within its grid (purple state) 666 if ($option.length) { 667 const $grid = $option.closest(".fae-effect-grid"); 668 if ($grid.length) { 669 $grid.find(".fae-effect-option").removeClass("fae-effect-selected"); 670 } else { 671 // Fallback: clear across all options if grid not found 672 $(".fae-effect-option").removeClass("fae-effect-selected"); 673 } 674 $option.addClass("fae-effect-selected"); 675 } 676 51 677 FaeAdmin.updateAdvancedSettings(effect); 678 679 // Hide/show entire appearance section in preview when effect is 'none' 680 const $appearanceSection = $('#cursor-effects-tab .fae-appearance-section'); 681 if (effect === 'none') { 682 $appearanceSection.hide(); 683 } else { 684 $appearanceSection.show(); 685 // For flag-effect, color visibility is handled by flag selection logic 686 // Other settings visibility is handled by updateAdvancedSettings 687 } 688 689 // Update preview iframe 690 if (typeof FaeAdmin.updatePreviewIframe === 'function') { 691 FaeAdmin.updatePreviewIframe('cursor'); 692 } 693 694 // Update effect type restrictions when effect changes 695 FaeAdmin.updateEffectTypeRestrictions(); 696 697 // Update limited customization UI for cursor effects 698 FaeAdmin.updateCursorLimitedCustomization(effect); 699 700 // Status indicators only update after save (from PHP), not on selection change 701 }, 702 703 // Update cursor effect color/speed UI based on limited customization rules 704 updateCursorLimitedCustomization: function(effect) { 705 const hasLimitedCustomization = this.effectHasLimitedCustomization(effect); 706 const canCustomizeColor = this.canCustomizeColor(effect); 707 const canCustomizeSpeed = this.canCustomizeSpeed(effect); 708 709 // Update data attributes for CSS 710 $('#cursor-effects-tab .fae-color-setting').attr('data-limited-customization', hasLimitedCustomization ? '1' : '0'); 711 $('#cursor-effects-tab .fae-speed-setting').attr('data-limited-customization', hasLimitedCustomization ? '1' : '0'); 712 713 // Update color setting 714 const $colorSetting = $('#cursor-effects-tab .fae-color-setting'); 715 const $colorLabel = $colorSetting.find('label'); 716 const $colorInput = $colorSetting.find('.fae-color-input-inline'); 717 718 // Update Pro badge in label 719 if (hasLimitedCustomization && !canCustomizeColor) { 720 // SAVE user's current color before locking (if not already the default) 721 const currentColorVal = $('#fae-cursor-color').val(); 722 if (currentColorVal && currentColorVal !== FREE_DEFAULT_COLORS.cursor) { 723 this.userSelectedColors.cursor = currentColorVal; 724 } 725 726 // Add Pro badge if not present 727 if (!$colorLabel.find('.fae-pro-badge-inline').length) { 728 $colorLabel.append('<span class="fae-pro-badge fae-pro-badge-inline">PRO</span>'); 729 } 730 // Replace color input with locked swatch 731 $colorInput.addClass('fae-color-locked'); 732 $colorInput.html( 733 '<div class="fae-color-swatch-locked" style="background-color: ' + FREE_DEFAULT_COLORS.cursor + ';" title="Upgrade to Pro to customize color"></div>' + 734 '<input type="hidden" name="fae_cursor_options[color]" value="' + FREE_DEFAULT_COLORS.cursor + '" id="fae-cursor-color">' + 735 '<span class="fae-color-locked-text">' + FREE_DEFAULT_COLORS.cursor + '</span>' 736 ); 737 } else { 738 // Remove Pro badge if present 739 $colorLabel.find('.fae-pro-badge-inline').remove(); 740 // Restore color input with user's SAVED color (not current hidden input value) 741 $colorInput.removeClass('fae-color-locked'); 742 const restoreColor = this.userSelectedColors.cursor || FREE_DEFAULT_COLORS.cursor; 743 $colorInput.html( 744 '<input type="color" class="fae-color-picker-inline" name="fae_cursor_options[color]" value="' + restoreColor + '" id="fae-cursor-color">' + 745 '<input type="text" value="' + restoreColor + '" id="fae-cursor-color-text">' 746 ); 747 // Re-bind color change events 748 this.bindColorEvents(); 749 } 750 751 // Update speed setting 752 const $speedSetting = $('#cursor-effects-tab .fae-speed-setting'); 753 const $speedSelect = $speedSetting.find('select'); 754 755 if (hasLimitedCustomization && !canCustomizeSpeed) { 756 // SAVE user's current speed before locking 757 const currentSpeedVal = $speedSelect.val(); 758 if (currentSpeedVal && currentSpeedVal !== 'normal') { 759 this.userSelectedSpeeds.cursor = currentSpeedVal; 760 } 761 762 // Disable slow and fast options, enable only normal 763 $speedSelect.addClass('fae-speed-locked'); 764 $speedSelect.find('option').each(function() { 765 const val = $(this).val(); 766 if (val === 'slow' || val === 'fast') { 767 $(this).prop('disabled', true); 768 if (!$(this).text().includes('(Pro)')) { 769 $(this).text($(this).text() + ' (Pro)'); 770 } 771 } else { 772 $(this).prop('disabled', false); 773 } 774 }); 775 // Force to normal 776 $speedSelect.val('normal'); 777 } else { 778 // Enable all speed options and RESTORE user's saved speed 779 $speedSelect.removeClass('fae-speed-locked'); 780 $speedSelect.find('option').each(function() { 781 $(this).prop('disabled', false); 782 $(this).text($(this).text().replace(' (Pro)', '')); 783 }); 784 // Restore user's saved speed 785 if (this.userSelectedSpeeds.cursor) { 786 $speedSelect.val(this.userSelectedSpeeds.cursor); 787 } 788 } 789 }, 790 791 // Bind/re-bind color picker events 792 bindColorEvents: function() { 793 // Cursor color 794 $('#fae-cursor-color').off('input change').on('input change', function() { 795 const color = $(this).val(); 796 $('#fae-cursor-color-text').val(color); 797 // Track user's color choice 798 FaeAdmin.userSelectedColors.cursor = color; 799 FaeAdmin.updatePreviewIframe('cursor'); 800 }); 801 802 $('#fae-cursor-color-text').off('input change').on('input change', function() { 803 let color = $(this).val(); 804 if (/^#[0-9A-Fa-f]{6}$/.test(color)) { 805 $('#fae-cursor-color').val(color); 806 // Track user's color choice 807 FaeAdmin.userSelectedColors.cursor = color; 808 FaeAdmin.updatePreviewIframe('cursor'); 809 } 810 }); 811 812 // Keyboard color 813 $('#fae-keyboard-color').off('input change').on('input change', function() { 814 const color = $(this).val(); 815 $('#fae-keyboard-color-text').val(color); 816 // Track user's color choice 817 FaeAdmin.userSelectedColors.keyboard = color; 818 FaeAdmin.updatePreviewIframe('keyboard'); 819 }); 820 821 // Particle color 822 $('#fae-particle-color').off('input change').on('input change', function() { 823 const color = $(this).val(); 824 $('#fae-particle-color-text').val(color); 825 // Track user's color choice 826 FaeAdmin.userSelectedColors.particle = color; 827 FaeAdmin.updatePreviewIframe('particle'); 828 }); 829 830 // Particle color text input 831 $('#fae-particle-color-text').off('input change').on('input change', function() { 832 let color = $(this).val(); 833 if (/^#[0-9A-Fa-f]{6}$/.test(color)) { 834 $('#fae-particle-color').val(color); 835 // Track user's color choice 836 FaeAdmin.userSelectedColors.particle = color; 837 FaeAdmin.updatePreviewIframe('particle'); 838 } 839 }); 840 841 // Keyboard color text input 842 $('#fae-keyboard-color-text').off('input change').on('input change', function() { 843 let color = $(this).val(); 844 if (/^#[0-9A-Fa-f]{6}$/.test(color)) { 845 $('#fae-keyboard-color').val(color); 846 // Track user's color choice 847 FaeAdmin.userSelectedColors.keyboard = color; 848 FaeAdmin.updatePreviewIframe('keyboard'); 849 } 850 }); 851 }, 852 853 handleCheckboxChange: function () { 854 const setting = $(this).attr("name"); 855 const value = $(this).is(':checked') ? '1' : '0'; 856 857 // Update hidden input if it exists 858 const hiddenInput = $('input[type="hidden"][name="' + setting + '"]'); 859 if (hiddenInput.length) { 860 hiddenInput.val(value); 861 } 52 862 }, 53 863 54 864 handleSettingChange: function () { 55 const setting = $(this).attr("name"); 56 const value = $(this).val(); 57 58 // Update settings in real-time if needed 59 FaeAdmin.updatePreviewSetting(setting, value); 865 // Settings change handler 60 866 }, 61 867 62 868 handleColorChange: function () { 63 869 const color = $(this).val(); 64 const setting = $(this).data("setting");65 870 66 871 // Update the text input if it exists … … 76 881 stroke: color, 77 882 }); 78 79 FaeAdmin.updatePreviewSetting(setting, color);80 883 }, 81 884 82 885 handleFormSubmit: function (e) { 83 886 e.preventDefault(); 84 85 // Show loading state 86 const submitBtn = $(this).find('input[type="submit"]'); 87 const originalText = submitBtn.val(); 88 submitBtn.val("Saving...").prop("disabled", true); 89 90 // Submit the form 91 this.submit(); 92 93 // Reset button after a delay 94 setTimeout(() => { 95 submitBtn.val(originalText).prop("disabled", false); 96 }, 2000); 887 888 const $form = $(this); 889 const formId = $form.attr('id'); 890 891 // Determine which form type and action 892 let action, nonce, formData, effectType; 893 894 if (formId === 'fae-cursor-form') { 895 action = 'fae_save_cursor_settings'; 896 effectType = 'cursor'; 897 nonce = typeof faeAdminData !== 'undefined' && faeAdminData.nonces ? faeAdminData.nonces.cursor : ''; 898 formData = $form.serialize(); 899 } else if (formId === 'fae-keyboard-form') { 900 action = 'fae_save_keyboard_settings'; 901 effectType = 'keyboard'; 902 nonce = typeof faeAdminData !== 'undefined' && faeAdminData.nonces ? faeAdminData.nonces.keyboard : ''; 903 904 // Security: Force multi_color to always be 0 (Pro feature protection) 905 // Even if someone inspects and removes disabled attribute, this ensures it's always 0 906 // 1. Remove name attribute from checkbox so it won't be submitted 907 // 2. Hidden input with same name will ensure value is always 0 908 $form.find('#fae-keyboard-multi-color').removeAttr('name').prop('disabled', true).prop('checked', false); 909 910 formData = $form.serialize(); 911 } else if (formId === 'fae-particle-form') { 912 action = 'fae_save_particle_settings'; 913 effectType = 'particle'; 914 nonce = typeof faeAdminData !== 'undefined' && faeAdminData.nonces ? faeAdminData.nonces.particle : ''; 915 formData = $form.serialize(); 916 } else { 917 return; 918 } 919 920 // Show loading state first (before validation check) 921 const submitBtn = $form.find('button[type="submit"], input[type="submit"]'); 922 const originalHtml = submitBtn.html(); 923 const originalText = submitBtn.text() || submitBtn.val(); 924 // Store original HTML for restoration 925 submitBtn.data('original-html', originalHtml); 926 submitBtn.data('original-text', originalText); 927 submitBtn.prop("disabled", true); 928 submitBtn.html('<span style="display: inline-flex; align-items: center; gap: 6px;"><svg class="fae-icon" viewBox="0 0 24 24" style="width: 16px; height: 16px; animation: spin 1s linear infinite;"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" opacity="0.25"/><path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/></svg>Saving...</span>'); 929 930 // Check if trying to save a non-none effect while another effect type is active 931 // Allow preview but prevent saving 932 const selectedEffect = $form.find('input[name*="[effect]"]:checked').val() || 'none'; 933 if (selectedEffect !== 'none' && FaeAdmin.isAnotherEffectTypeActive(effectType)) { 934 // Show upgrade modal and prevent form submission 935 FaeAdmin.showUpgradeNoticeModal(); 936 // Reset button state 937 submitBtn.prop("disabled", false); 938 submitBtn.html(originalHtml); 939 return false; 940 } 941 942 // Prepare AJAX data 943 const ajaxData = formData + '&action=' + action + '&nonce=' + nonce; 944 945 // Make AJAX request 946 $.ajax({ 947 url: typeof faeAdminData !== 'undefined' ? faeAdminData.ajaxUrl : ajaxurl, 948 type: 'POST', 949 data: ajaxData, 950 success: function(response) { 951 if (response.success) { 952 FaeAdmin.showNotice(response.data.message || 'Settings saved successfully!', 'success'); 953 954 // Determine which tab was saved and update only that tab's status indicator 955 let savedType = null; 956 if (formId === 'fae-cursor-form') { 957 savedType = 'cursor'; 958 } else if (formId === 'fae-keyboard-form') { 959 savedType = 'keyboard'; 960 } else if (formId === 'fae-particle-form') { 961 savedType = 'particle'; 962 } 963 964 // Update only the saved tab's status indicator using saved options from response 965 if (savedType && response.data && response.data.options) { 966 FaeAdmin.updateTabStatusIndicator(savedType, response.data.options); 967 // Update active effect badges for the saved tab only 968 FaeAdmin.updateActiveEffectBadges(savedType, response.data.options); 969 // Update saved effects state 970 if (response.data.options.effect) { 971 FaeAdmin.savedEffects[savedType] = response.data.options.effect; 972 } else { 973 FaeAdmin.savedEffects[savedType] = 'none'; 974 } 975 } 976 977 // Update stats after successful save 978 FaeAdmin.updateStats(); 979 // Update status indicators (legacy) 980 FaeAdmin.updateStatusIndicators(); 981 // Update effect type restrictions after save 982 FaeAdmin.updateEffectTypeRestrictions(); 983 } else { 984 FaeAdmin.showNotice(response.data && response.data.message ? response.data.message : 'Failed to save settings. Please try again.', 'error'); 985 } 986 }, 987 error: function(xhr, status, error) { 988 let errorMessage = 'An error occurred while saving settings. Please try again.'; 989 let showUpgradeNotice = false; 990 991 // Check if response contains error data with upgrade notice 992 if (xhr.responseJSON && xhr.responseJSON.data) { 993 if (xhr.responseJSON.data.message) { 994 errorMessage = xhr.responseJSON.data.message; 995 } 996 if (xhr.responseJSON.data.upgrade_notice) { 997 showUpgradeNotice = true; 998 } 999 } 1000 1001 FaeAdmin.showNotice(errorMessage, 'error'); 1002 1003 // Show upgrade notice modal if needed 1004 if (showUpgradeNotice) { 1005 FaeAdmin.showUpgradeNoticeModal(); 1006 } 1007 1008 console.error('AJAX Error:', error); 1009 }, 1010 complete: function() { 1011 // Reset button state 1012 submitBtn.prop("disabled", false); 1013 submitBtn.html(originalHtml); 1014 } 1015 }); 97 1016 }, 98 1017 99 1018 updateAdvancedSettings: function (effect) { 100 const effectSettings = $(" .fae-effect-settings");1019 const effectSettings = $("#cursor-effects-tab .fae-effect-settings"); 101 1020 102 1021 // Show/hide settings based on effect 103 1022 if (effect === "none") { 104 1023 effectSettings.hide(); 1024 // Hide cursor appearance subgroup 1025 $('#fae-cursor-appearance-subgroup').hide(); 1026 // Trigger height update 1027 $(document).trigger('faeSettingsUpdated'); 105 1028 } else { 106 1029 effectSettings.show(); 107 1030 108 // Show effect-specific sections 1031 // Get effect config 1032 const effectConfig = typeof getEffectConfig !== 'undefined' 1033 ? getEffectConfig(effect) 1034 : {}; 1035 1036 // Show/hide cursor appearance subgroup based on config 1037 const showHideCursor = effectConfig.supportsHideCursor !== false; 1038 const $hideCursorSubgroup = $('#fae-cursor-appearance-subgroup'); 1039 if (showHideCursor) { 1040 $hideCursorSubgroup.show(); 1041 } else { 1042 $hideCursorSubgroup.hide(); 1043 } 1044 1045 // Show/hide icon settings wrapper based on effect config 1046 const showIcon = effectConfig.effectType === 'icon'; 1047 if (showIcon) { 1048 $('.fae-icon-settings-wrapper').show(); 1049 } else { 1050 $('.fae-icon-settings-wrapper').hide(); 1051 } 1052 1053 // Show/hide icon settings (Size and Icon) based on effect config 1054 // Size should show for effects that support size (drop-effect, rise-effect, flag-effect) 1055 // Icon should show for effects that support icon (drop-effect, rise-effect) 1056 const showSize = effectConfig.supportsSize === true; 1057 const showIconSetting = effectConfig.supportsIcon === true; 1058 1059 $('.fae-icon-setting').each(function() { 1060 const $setting = $(this); 1061 // Check if this is the Size setting or Icon setting 1062 const isSizeSetting = $setting.find('select[name*="[size]"]').length > 0; 1063 const isIconSetting = $setting.find('input[name*="[icon]"]').length > 0 || $setting.find('.fae-icon-picker-inline').length > 0; 1064 1065 if (isSizeSetting) { 1066 // Size setting: show if effect supports size 1067 $setting.toggle(showSize); 1068 } else if (isIconSetting) { 1069 // Icon setting: show if effect supports icon 1070 $setting.toggle(showIconSetting); 1071 } 1072 }); 1073 1074 // Show/hide multi-color setting for bubbles-effect and magic-trail 1075 if (effect === 'bubbles-effect' || effect === 'magic-trail') { 1076 $('.fae-multi-color-setting').show(); 1077 } else { 1078 $('.fae-multi-color-setting').hide(); 1079 } 1080 1081 // Show/hide flag setting for flag-effect 1082 if (effect === 'flag-effect') { 1083 $('.fae-flag-setting').show(); 1084 // For flag-effect, handle color and flag position based on flag selection 1085 const selectedFlag = $('#fae-cursor-flag').val(); 1086 if (selectedFlag) { 1087 $('.fae-color-setting').hide(); 1088 $('.fae-flag-position-setting').show(); 1089 } else { 1090 $('.fae-color-setting').show(); 1091 $('.fae-flag-position-setting').hide(); 1092 } 1093 } else { 1094 // Hide all flag-related settings for non-flag effects 1095 $('.fae-flag-setting').hide(); 1096 $('.fae-flag-position-setting').hide(); 1097 // Show color setting if effect supports color 1098 if (effectConfig.supportsColor !== false) { 1099 $('.fae-color-setting').show(); 1100 } else { 1101 $('.fae-color-setting').hide(); 1102 } 1103 } 1104 1105 // Show/hide settings based on config 109 1106 $(".fae-settings-section").each(function () { 110 1107 const $section = $(this); 111 const attr = $section.data("effect"); 112 113 if (!attr) { 114 // Global settings - always show 1108 const effectType = $section.data("effect-type"); 1109 1110 if (!effectType) { 1111 // Global settings - show/hide based on config 1112 const showColor = effectConfig.supportsColor !== false; 1113 const showSpeed = effectConfig.supportsSpeed !== false; 1114 1115 // Show color setting if supported 1116 $section.find('.fae-setting-group').each(function() { 1117 const $group = $(this); 1118 if ($group.find('input[name*="[color]"]').length && !showColor) { 1119 $group.hide(); 1120 } else if ($group.find('input[name*="[color]"]').length && showColor) { 1121 $group.show(); 1122 } 1123 if ($group.find('select[name*="[speed]"]').length && !showSpeed) { 1124 $group.hide(); 1125 } else if ($group.find('select[name*="[speed]"]').length && showSpeed) { 1126 $group.show(); 1127 } 1128 }); 115 1129 $section.show(); 116 1130 return; 117 1131 } 118 1132 119 const list = String(attr) 120 .split(",") 121 .map((s) => s.trim()); 122 if (list.includes(effect)) { 1133 // Show section if effect type matches 1134 if (effectType === effectConfig.effectType) { 123 1135 $section.show(); 124 1136 } else { … … 126 1138 } 127 1139 }); 128 } 129 }, 130 131 updatePreviewSetting: function (setting, value) { 132 // Update settings in real-time if needed 133 switch (setting) { 134 case "fae_cursor_options[icon]": 135 // Update the global icon setting 136 if (window.faeCursorSettings) { 137 window.faeCursorSettings.icon = value; 138 } 139 break; 140 } 141 }, 142 143 resetSettings: function () { 144 if ( 145 confirm( 146 "Are you sure you want to reset all settings to default values?" 147 ) 148 ) { 149 // Set default values 150 $('input[name="fae_cursor_options[effect]"][value="drop-effect"]').prop( 151 "checked", 152 true 153 ); 154 $('input[name="fae_cursor_options[color]"]').val("#fcba03"); 155 $('input[name="fae_cursor_options[size]"]').val("1.5rem"); 156 $('input[name="fae_cursor_options[speed]"]').val("fast"); 157 $("#fae-selected-icon-input").val("star.svg"); 158 159 // Update the icon picker display 160 const selectedIcon = $("#fae-icon-picker-trigger .fae-selected-icon"); 161 const iconNameSpan = $("#fae-icon-picker-trigger .fae-icon-name"); 162 163 // Update icon display to star 164 selectedIcon.html( 165 '<svg viewBox="0 0 512 512"><path d="M394,480a16,16,0,0,1-9.39-3L256,383.76,127.39,477a16,16,0,0,1-24.55-18.08L153,310.35,23,221.2A16,16,0,0,1,32,192H192.38l48.4-148.95a16,16,0,0,1,30.44,0l48.4,149H480a16,16,0,0,1,9.05,29.2L359,310.35l50.13,148.53A16,16,0,0,1,394,480Z"/></svg>' 166 ); 167 iconNameSpan.text("star"); 168 169 // Apply color to the selected icon 170 selectedIcon.find("svg path").css({ 171 fill: "#fcba03", 172 stroke: "#fcba03", 1140 1141 // Also handle new grouped layout setting items 1142 $("#cursor-effects-tab .fae-setting-item").each(function() { 1143 const $item = $(this); 1144 const hasColor = $item.find('input[name*="[color]"]').length > 0; 1145 const hasSpeed = $item.find('select[name*="[speed]"]').length > 0; 1146 1147 if (hasColor) { 1148 const showColor = effectConfig.supportsColor !== false; 1149 $item.toggle(showColor); 1150 } 1151 if (hasSpeed) { 1152 const showSpeed = effectConfig.supportsSpeed !== false; 1153 $item.toggle(showSpeed); 1154 } 173 1155 }); 174 175 // Update selection in grid 176 $(".fae-icon-option").removeClass("selected"); 177 $('.fae-icon-option[data-icon="star.svg"]').addClass("selected"); 178 179 // Update settings sections 180 FaeAdmin.updateAdvancedSettings("drop-effect"); 181 182 // Show success message 183 FaeAdmin.showNotice( 184 "Settings reset to default values: Drop Effect, Color #fcba03, Fast Speed, Star Icon", 185 "success" 186 ); 187 } 1156 1157 // Handle inline settings in appearance section 1158 $(".fae-inline-setting").each(function() { 1159 const $setting = $(this); 1160 1161 // Skip flag-specific settings (handled separately above) 1162 if ($setting.hasClass('fae-flag-setting') || $setting.hasClass('fae-flag-position-setting')) { 1163 return; 1164 } 1165 1166 // Skip color setting for all effects (handled separately above based on effect and flag selection) 1167 if ($setting.hasClass('fae-color-setting')) { 1168 return; 1169 } 1170 1171 // Skip multi-color setting (handled separately above) 1172 if ($setting.hasClass('fae-multi-color-setting')) { 1173 return; 1174 } 1175 1176 // Skip icon settings (Size and Icon - handled separately above) 1177 if ($setting.hasClass('fae-icon-setting')) { 1178 return; 1179 } 1180 1181 const hasColor = $setting.find('input[name*="[color]"]').length > 0; 1182 const hasSpeed = $setting.find('select[name*="[speed]"]').length > 0; 1183 1184 if (hasColor) { 1185 const showColor = effectConfig.supportsColor !== false; 1186 $setting.toggle(showColor); 1187 } 1188 if (hasSpeed) { 1189 const showSpeed = effectConfig.supportsSpeed !== false; 1190 $setting.toggle(showSpeed); 1191 } 1192 }); 1193 } 1194 1195 // Trigger height update after settings change 1196 setTimeout(() => { 1197 $(document).trigger('faeSettingsUpdated'); 1198 }, 150); 188 1199 }, 189 1200 190 1201 loadSettings: function () { 191 // Load saved settings and update UI 1202 // Initially hide all effect settings and appearance sections on page load 1203 // They will only be shown when user actually clicks/selects an effect 1204 $("#cursor-effects-tab .fae-effect-settings").hide(); 1205 $("#keyboard-effects-tab .fae-keyboard-effect-settings").hide(); 1206 $("#keyboard-effects-tab .fae-appearance-section").hide(); 1207 $("#particle-effects-tab .fae-effect-settings").hide(); 1208 $("#particle-effects-tab .fae-appearance-section").hide(); 1209 $('#fae-cursor-appearance-subgroup').hide(); 1210 $('.fae-icon-settings-wrapper').hide(); 1211 1212 // Load saved cursor effect settings - but don't show them yet 192 1213 const effect = $( 193 1214 'input[name="fae_cursor_options[effect]"]:checked' 194 1215 ).val(); 195 FaeAdmin.updateAdvancedSettings(effect); 1216 // Don't call updateAdvancedSettings here - wait for user to click 1217 FaeAdmin.updateStatusIndicators(); 1218 1219 // Note: Visual "selected" state (purple highlight) is not applied on initial load 1220 // It will be applied when user clicks on an effect 1221 // Settings will also be shown when user clicks on an effect 1222 1223 // Load saved keyboard effect settings - but don't show them yet 1224 const keyboardEffect = $( 1225 'input[name="fae_keyboard_options[effect]"]:checked' 1226 ).val(); 1227 // Don't show settings here - wait for user to click 1228 // Note: Visual "selected" state is not applied on initial load 1229 // It will be applied when user clicks on an effect 1230 1231 // Load saved particle effect settings 1232 const particleEffect = $( 1233 'input[name="fae_particle_options[effect]"]:checked' 1234 ).val(); 1235 1236 // Initialize particle appearance settings visibility on page load 1237 if (particleEffect && particleEffect !== 'none') { 1238 // Show effect settings and appearance section for saved effect 1239 $("#particle-effects-tab .fae-effect-settings").show(); 1240 $("#particle-effects-tab .fae-appearance-section").show(); 1241 1242 // Get effect config to show/hide color, speed, and interactive cursor settings 1243 const effectConfig = typeof FAE_PARTICLE_EFFECTS_CONFIG !== 'undefined' && FAE_PARTICLE_EFFECTS_CONFIG[particleEffect] 1244 ? FAE_PARTICLE_EFFECTS_CONFIG[particleEffect] 1245 : {}; 1246 1247 // Show/hide color setting based on effect config 1248 const showColor = effectConfig.supportsColor !== false; 1249 $('#particle-effects-tab .fae-particle-color-setting').toggle(showColor); 1250 1251 // Show/hide speed setting based on effect config 1252 const showSpeed = effectConfig.supportsSpeed !== false; 1253 $('#particle-effects-tab .fae-particle-speed-setting').toggle(showSpeed); 1254 1255 // Show/hide Interactive Cursor option based on effect config 1256 const showInteractiveCursor = effectConfig.supportsInteractiveCursor === true; 1257 $('#particle-effects-tab .fae-interactive-cursor-setting').toggle(showInteractiveCursor); 1258 1259 // Update limited customization UI 1260 FaeAdmin.updateParticleLimitedCustomization(particleEffect); 1261 } 196 1262 197 1263 // Update the selected icon display 198 if ($(" .fae-color-picker").val()) {199 const color = $(" .fae-color-picker").val();1264 if ($("#cursor-effects-tab .fae-color-picker").val()) { 1265 const color = $("#cursor-effects-tab .fae-color-picker").val(); 200 1266 const selectedIcon = $("#fae-icon-picker-trigger .fae-selected-icon"); 201 1267 selectedIcon.find("svg path").css({ … … 204 1270 }); 205 1271 } 1272 1273 // Update all tab status indicators on page load 1274 FaeAdmin.updateAllTabStatusIndicators(); 1275 1276 // Update active effect badges based on saved state (not form selections) 1277 FaeAdmin.updateActiveEffectBadges(); 1278 1279 // Update effect type restrictions on page load 1280 FaeAdmin.updateEffectTypeRestrictions(); 206 1281 }, 207 1282 208 1283 updateStats: function () { 209 1284 // Update dashboard statistics 210 const effect = $( 211 'input[name="fae_cursor_options[effect]"]:checked' 212 ).val(); 213 const isActive = effect !== "none"; 214 215 $(".fae-stat-value") 216 .eq(0) 217 .text(isActive ? "Active" : "Inactive"); 218 $(".fae-stat-value") 219 .eq(1) 220 .text( 221 effect === "none" 222 ? "None" 223 : effect.replace("-", " ").replace(/\b\w/g, (l) => l.toUpperCase()) 1285 // Use saved effects state (what's actually saved), not form selections 1286 const cursorEffect = FaeAdmin.savedEffects.cursor || 'none'; 1287 const keyboardEffect = FaeAdmin.savedEffects.keyboard || 'none'; 1288 const particleEffect = FaeAdmin.savedEffects.particle || 'none'; 1289 1290 // Collect active effects 1291 let activeEffects = []; 1292 if (cursorEffect && cursorEffect !== "none") { 1293 const cursorConfig = typeof FAE_CURSOR_EFFECTS_CONFIG !== 'undefined' && FAE_CURSOR_EFFECTS_CONFIG[cursorEffect] 1294 ? FAE_CURSOR_EFFECTS_CONFIG[cursorEffect] 1295 : null; 1296 const cursorName = cursorConfig && cursorConfig.displayName 1297 ? cursorConfig.displayName 1298 : cursorEffect.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()); 1299 activeEffects.push(cursorName); 1300 } 1301 if (keyboardEffect && keyboardEffect !== "none") { 1302 // Keyboard effects don't have a JS config, so use formatted name 1303 activeEffects.push(keyboardEffect.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase())); 1304 } 1305 if (particleEffect && particleEffect !== "none") { 1306 const particleConfig = typeof FAE_PARTICLE_EFFECTS_CONFIG !== 'undefined' && FAE_PARTICLE_EFFECTS_CONFIG[particleEffect] 1307 ? FAE_PARTICLE_EFFECTS_CONFIG[particleEffect] 1308 : null; 1309 const particleName = particleConfig && particleConfig.displayName 1310 ? particleConfig.displayName 1311 : particleEffect.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()); 1312 activeEffects.push(particleName); 1313 } 1314 1315 const isActive = activeEffects.length > 0; 1316 const $statCards = $(".fae-stat-card"); 1317 const $statusCard = $statCards.eq(0); // First card: Status 1318 const $activeEffectsCard = $statCards.eq(1); // Second card: Active Effects 1319 // Third card (Effects Available) doesn't need JS updates 1320 1321 // Update Status card 1322 $statusCard.find(".fae-stat-value").text(isActive ? "Active" : "Inactive"); 1323 $statusCard.toggleClass("fae-stat-card-inactive", !isActive); 1324 1325 // Update Active Effects card 1326 $activeEffectsCard.toggleClass("fae-stat-card-inactive", !isActive); 1327 const $effectsValue = $activeEffectsCard.find(".fae-stat-value"); 1328 let $effectsDetail = $activeEffectsCard.find(".fae-stat-detail"); 1329 1330 if (activeEffects.length === 0) { 1331 $effectsValue.text("None").removeClass("fae-stat-value-small"); 1332 $effectsDetail.remove(); 1333 } else if (activeEffects.length === 1) { 1334 $effectsValue.text(activeEffects[0]).removeClass("fae-stat-value-small"); 1335 $effectsDetail.remove(); 1336 } else { 1337 $effectsValue.text(activeEffects.length + " Active").addClass("fae-stat-value-small"); 1338 if ($effectsDetail.length === 0) { 1339 $activeEffectsCard.append('<p class="fae-stat-detail">' + activeEffects.join(", ") + '</p>'); 1340 } else { 1341 $effectsDetail.text(activeEffects.join(", ")); 1342 } 1343 } 1344 }, 1345 1346 updateStatusIndicators: function () { 1347 // Status indicators are now based on saved settings only (from PHP) 1348 // They update only after form submission, not on selection change 1349 // This function is kept for potential future use but doesn't update UI dynamically 1350 }, 1351 1352 updateAllTabStatusIndicators: function () { 1353 // Update cursor effect tab status - read from saved options, not form values 1354 // This prevents showing green dots on tabs with unsaved selections 1355 const cursorEffect = $('input[name="fae_cursor_options[effect]"]:checked').val() || 'none'; 1356 const $cursorStatus = $('.fae-tab-status[data-effect-type="cursor"]'); 1357 if (cursorEffect !== 'none') { 1358 $cursorStatus.addClass('active').attr('title', 'Active'); 1359 } else { 1360 $cursorStatus.removeClass('active').attr('title', 'Inactive'); 1361 } 1362 1363 // Update keyboard effect tab status - read from saved options, not form values 1364 const keyboardEffect = $('input[name="fae_keyboard_options[effect]"]:checked').val() || 'none'; 1365 const $keyboardStatus = $('.fae-tab-status[data-effect-type="keyboard"]'); 1366 if (keyboardEffect !== 'none') { 1367 $keyboardStatus.addClass('active').attr('title', 'Active'); 1368 } else { 1369 $keyboardStatus.removeClass('active').attr('title', 'Inactive'); 1370 } 1371 1372 // Update particle effect tab status - read from saved options, not form values 1373 const particleEffect = $('input[name="fae_particle_options[effect]"]:checked').val() || 'none'; 1374 const $particleStatus = $('.fae-tab-status[data-effect-type="particle"]'); 1375 if (particleEffect !== 'none') { 1376 $particleStatus.addClass('active').attr('title', 'Active'); 1377 } else { 1378 $particleStatus.removeClass('active').attr('title', 'Inactive'); 1379 } 1380 }, 1381 1382 updateTabStatusIndicator: function (type, savedOptions) { 1383 // Update status indicator for a specific tab using saved options 1384 // This ensures we only show green dots for actually saved effects 1385 let effect = 'none'; 1386 if (savedOptions && savedOptions.effect) { 1387 effect = savedOptions.effect; 1388 } 1389 1390 const $status = $('.fae-tab-status[data-effect-type="' + type + '"]'); 1391 if (effect !== 'none') { 1392 $status.addClass('active').attr('title', 'Active'); 1393 } else { 1394 $status.removeClass('active').attr('title', 'Inactive'); 1395 } 1396 }, 1397 1398 // Check if another effect type is currently active (using saved state) 1399 isAnotherEffectTypeActive: function(currentType) { 1400 // Use saved effects state (what's actually saved, not form selections) 1401 const cursorEffect = FaeAdmin.savedEffects.cursor || 'none'; 1402 const keyboardEffect = FaeAdmin.savedEffects.keyboard || 'none'; 1403 const particleEffect = FaeAdmin.savedEffects.particle || 'none'; 1404 1405 if (currentType === 'cursor') { 1406 return (keyboardEffect !== 'none' || particleEffect !== 'none'); 1407 } else if (currentType === 'keyboard') { 1408 return (cursorEffect !== 'none' || particleEffect !== 'none'); 1409 } else if (currentType === 'particle') { 1410 return (cursorEffect !== 'none' || keyboardEffect !== 'none'); 1411 } 1412 return false; 1413 }, 1414 1415 // Update UI to mark other effect types when one is active (for visual indication) 1416 // But don't disable them - allow preview, only prevent saving 1417 updateEffectTypeRestrictions: function() { 1418 // Use saved effects state (what's actually saved, not form selections) 1419 const cursorEffect = FaeAdmin.savedEffects.cursor || 'none'; 1420 const keyboardEffect = FaeAdmin.savedEffects.keyboard || 'none'; 1421 const particleEffect = FaeAdmin.savedEffects.particle || 'none'; 1422 1423 // Check which effect types are active 1424 const cursorActive = cursorEffect !== 'none'; 1425 const keyboardActive = keyboardEffect !== 'none'; 1426 const particleActive = particleEffect !== 'none'; 1427 1428 // Mark keyboard and particle effects if cursor is active (for visual indication only) 1429 // Don't disable - allow preview, only prevent saving 1430 if (cursorActive) { 1431 $('#keyboard-effects-tab .fae-effect-option:not([data-effect-id="none"])').addClass('fae-effect-type-disabled'); 1432 $('#particle-effects-tab .fae-effect-option:not([data-effect-id="none"])').addClass('fae-effect-type-disabled'); 1433 // Don't disable radio buttons - allow selection for preview 1434 } else { 1435 $('#keyboard-effects-tab .fae-effect-option').removeClass('fae-effect-type-disabled'); 1436 $('#particle-effects-tab .fae-effect-option').removeClass('fae-effect-type-disabled'); 1437 } 1438 1439 // Mark cursor and particle effects if keyboard is active (for visual indication only) 1440 if (keyboardActive) { 1441 $('#cursor-effects-tab .fae-effect-option:not([data-effect-id="none"])').addClass('fae-effect-type-disabled'); 1442 $('#particle-effects-tab .fae-effect-option:not([data-effect-id="none"])').addClass('fae-effect-type-disabled'); 1443 // Don't disable radio buttons - allow selection for preview 1444 } else { 1445 $('#cursor-effects-tab .fae-effect-option').removeClass('fae-effect-type-disabled'); 1446 $('#particle-effects-tab .fae-effect-option').removeClass('fae-effect-type-disabled'); 1447 } 1448 1449 // Mark cursor and keyboard effects if particle is active (for visual indication only) 1450 if (particleActive) { 1451 $('#cursor-effects-tab .fae-effect-option:not([data-effect-id="none"])').addClass('fae-effect-type-disabled'); 1452 $('#keyboard-effects-tab .fae-effect-option:not([data-effect-id="none"])').addClass('fae-effect-type-disabled'); 1453 // Don't disable radio buttons - allow selection for preview 1454 } else { 1455 $('#cursor-effects-tab .fae-effect-option').removeClass('fae-effect-type-disabled'); 1456 $('#keyboard-effects-tab .fae-effect-option').removeClass('fae-effect-type-disabled'); 1457 } 1458 }, 1459 1460 // Show upgrade notice modal 1461 showUpgradeNoticeModal: function() { 1462 const upgradeUrl = typeof faeAdminData !== 'undefined' && faeAdminData.upgradeUrl 1463 ? faeAdminData.upgradeUrl 1464 : 'https://faecursor.com/'; 1465 1466 // Create modal if it doesn't exist 1467 if ($('#fae-upgrade-notice-modal').length === 0) { 1468 const modalHtml = ` 1469 <div id="fae-upgrade-notice-modal" class="fae-upgrade-modal"> 1470 <div class="fae-upgrade-modal-backdrop"></div> 1471 <div class="fae-upgrade-modal-content"> 1472 <button type="button" class="fae-upgrade-modal-close" aria-label="Close"> 1473 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width: 20px; height: 20px;"> 1474 <path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/> 1475 </svg> 1476 </button> 1477 <div class="fae-upgrade-modal-body"> 1478 <svg class="fae-upgrade-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 1479 <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/> 1480 </svg> 1481 <h3>Pro Feature</h3> 1482 <p>Only one effect type can be active at a time in the free version. Upgrade to Pro to use multiple effects (Cursor, Keyboard, and Screen) simultaneously.</p> 1483 <a href="${upgradeUrl}" target="_blank" class="fae-upgrade-button">Upgrade to Pro</a> 1484 </div> 1485 </div> 1486 </div> 1487 `; 1488 $('body').append(modalHtml); 1489 1490 // Close modal handlers 1491 $('#fae-upgrade-notice-modal .fae-upgrade-modal-close, #fae-upgrade-notice-modal .fae-upgrade-modal-backdrop').on('click', function() { 1492 $('#fae-upgrade-notice-modal').removeClass('active'); 1493 }); 1494 1495 // Close on Escape key 1496 $(document).on('keydown', function(e) { 1497 if (e.key === 'Escape' && $('#fae-upgrade-notice-modal').hasClass('active')) { 1498 $('#fae-upgrade-notice-modal').removeClass('active'); 1499 } 1500 }); 1501 } 1502 1503 // Show modal 1504 $('#fae-upgrade-notice-modal').addClass('active'); 1505 }, 1506 1507 updateActiveEffectBadges: function (specificType, savedOptions) { 1508 // If specificType and savedOptions are provided, update only that tab 1509 // Otherwise, update all tabs based on saved state 1510 if (specificType && savedOptions) { 1511 // Remove badges and classes only for the specific tab being updated 1512 const $tab = $('#' + specificType + '-effects-tab'); 1513 $tab.find('.fae-effect-active-badge').remove(); 1514 $tab.find('.fae-effect-option').removeClass('fae-effect-active'); 1515 1516 // Update only the specific tab that was saved 1517 const effect = savedOptions.effect || 'none'; 1518 if (effect !== 'none') { 1519 // Update saved state 1520 FaeAdmin.savedEffects[specificType] = effect; 1521 1522 // Update badge for this specific tab 1523 const $option = $tab.find('.fae-effect-option[data-effect-id="' + effect + '"]'); 1524 if ($option.length) { 1525 $option.addClass('fae-effect-active'); 1526 $option.prepend('<span class="fae-effect-active-badge" title="Currently active on website">Active</span>'); 1527 } 1528 } else { 1529 // Effect was disabled 1530 FaeAdmin.savedEffects[specificType] = 'none'; 1531 } 1532 } else { 1533 // Remove all existing active badges and classes (for full refresh) 1534 $('.fae-effect-active-badge').remove(); 1535 $('.fae-effect-option').removeClass('fae-effect-active'); 1536 // Update all tabs based on saved state (not form values) 1537 // Update cursor effects (within cursor effects tab) 1538 const cursorEffect = FaeAdmin.savedEffects.cursor; 1539 if (cursorEffect && cursorEffect !== 'none') { 1540 const $cursorOption = $('#cursor-effects-tab .fae-effect-option[data-effect-id="' + cursorEffect + '"]'); 1541 if ($cursorOption.length) { 1542 $cursorOption.addClass('fae-effect-active'); 1543 $cursorOption.prepend('<span class="fae-effect-active-badge" title="Currently active on website">Active</span>'); 1544 } 1545 } 1546 1547 // Update keyboard effects (within keyboard effects tab) 1548 const keyboardEffect = FaeAdmin.savedEffects.keyboard; 1549 if (keyboardEffect && keyboardEffect !== 'none') { 1550 const $keyboardOption = $('#keyboard-effects-tab .fae-effect-option[data-effect-id="' + keyboardEffect + '"]'); 1551 if ($keyboardOption.length) { 1552 $keyboardOption.addClass('fae-effect-active'); 1553 $keyboardOption.prepend('<span class="fae-effect-active-badge" title="Currently active on website">Active</span>'); 1554 } 1555 } 1556 1557 // Update particle effects (within particle effects tab) 1558 const particleEffect = FaeAdmin.savedEffects.particle; 1559 if (particleEffect && particleEffect !== 'none') { 1560 const $particleOption = $('#particle-effects-tab .fae-effect-option[data-effect-id="' + particleEffect + '"]'); 1561 if ($particleOption.length) { 1562 $particleOption.addClass('fae-effect-active'); 1563 $particleOption.prepend('<span class="fae-effect-active-badge" title="Currently active on website">Active</span>'); 1564 } 1565 } 1566 } 1567 }, 1568 1569 showNotice: function (message, type) { 1570 // Remove any existing notices first with smooth fade out 1571 $('.fae-ajax-notice').each(function() { 1572 const $existing = $(this); 1573 if (!$existing.hasClass('fade-out')) { 1574 $existing.addClass('fade-out'); 1575 setTimeout(() => { 1576 $existing.remove(); 1577 }, 350); 1578 } 1579 }); 1580 1581 // Wait a bit for existing notice to fade out before showing new one 1582 setTimeout(() => { 1583 const icon = type === 'success' 1584 ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width: 20px; height: 20px; flex-shrink: 0;"><path d="M20 6L9 17l-5-5" stroke-linecap="round" stroke-linejoin="round"/></svg>' 1585 : '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width: 20px; height: 20px; flex-shrink: 0;"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>'; 1586 1587 // Calculate top position - account for WordPress admin bar 1588 const getTopPosition = function() { 1589 const adminBar = $('#wpadminbar'); 1590 if (adminBar.length && adminBar.is(':visible')) { 1591 return (adminBar.outerHeight() + 20) + 'px'; 1592 } 1593 // Check if we're in WordPress admin 1594 if ($('body').hasClass('wp-admin')) { 1595 return '32px'; 1596 } 1597 return '20px'; 1598 }; 1599 const topPosition = getTopPosition(); 1600 1601 const notice = $( 1602 '<div class="fae-ajax-notice fae-ajax-notice-' + type + '">' + 1603 '<div class="fae-notice-content">' + 1604 '<span class="fae-notice-icon">' + icon + '</span>' + 1605 '<span class="fae-notice-message">' + message + '</span>' + 1606 '<button type="button" class="fae-notice-close" title="Dismiss">' + 1607 '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width: 18px; height: 18px;"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>' + 1608 '</button>' + 1609 '</div>' + 1610 '</div>' 224 1611 ); 225 }, 226 227 showNotice: function (message, type) { 228 const notice = $( 229 '<div class="fae-notice fae-notice-' + type + '">' + message + "</div>" 230 ); 231 $(".fae-cursor-dashboard").prepend(notice); 232 1612 1613 // Add animation styles if not already added 1614 if (!$('#fae-notice-styles').length) { 1615 $('head').append( 1616 '<style id="fae-notice-styles">' + 1617 '.fae-ajax-notice { ' + 1618 ' position: fixed; ' + 1619 ' right: 20px; ' + 1620 ' z-index: 100001; ' + 1621 ' min-width: 320px; ' + 1622 ' max-width: 500px; ' + 1623 ' pointer-events: none; ' + 1624 ' opacity: 0; ' + 1625 ' transform: translateX(calc(100% + 20px)); ' + 1626 ' transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); ' + 1627 '}' + 1628 '.fae-ajax-notice.show { ' + 1629 ' opacity: 1; ' + 1630 ' transform: translateX(0); ' + 1631 ' pointer-events: auto; ' + 1632 '}' + 1633 '.fae-ajax-notice.fade-out { ' + 1634 ' opacity: 0; ' + 1635 ' transform: translateX(calc(100% + 20px)); ' + 1636 ' pointer-events: none; ' + 1637 '}' + 1638 '.fae-notice-content { ' + 1639 ' background: ' + (type === 'success' ? '#10b981' : '#ef4444') + '; ' + 1640 ' color: white; ' + 1641 ' padding: 16px 20px; ' + 1642 ' border-radius: 12px; ' + 1643 ' box-shadow: 0 20px 40px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.05); ' + 1644 ' display: flex; ' + 1645 ' align-items: center; ' + 1646 ' gap: 14px; ' + 1647 ' backdrop-filter: blur(10px); ' + 1648 '}' + 1649 '.fae-notice-icon { ' + 1650 ' flex-shrink: 0; ' + 1651 ' display: flex; ' + 1652 ' align-items: center; ' + 1653 ' justify-content: center; ' + 1654 '}' + 1655 '.fae-notice-message { ' + 1656 ' flex: 1; ' + 1657 ' font-size: 14px; ' + 1658 ' line-height: 1.5; ' + 1659 ' font-weight: 500; ' + 1660 ' letter-spacing: -0.01em; ' + 1661 '}' + 1662 '.fae-notice-close { ' + 1663 ' background: rgba(255,255,255,0.2); ' + 1664 ' border: none; ' + 1665 ' color: white; ' + 1666 ' cursor: pointer; ' + 1667 ' padding: 6px; ' + 1668 ' margin-left: 4px; ' + 1669 ' border-radius: 6px; ' + 1670 ' display: flex; ' + 1671 ' align-items: center; ' + 1672 ' justify-content: center; ' + 1673 ' opacity: 0.8; ' + 1674 ' transition: all 0.2s ease; ' + 1675 ' flex-shrink: 0; ' + 1676 '}' + 1677 '.fae-notice-close:hover { ' + 1678 ' opacity: 1; ' + 1679 ' background: rgba(255,255,255,0.3); ' + 1680 ' transform: scale(1.1); ' + 1681 '}' + 1682 '.fae-notice-close:active { ' + 1683 ' transform: scale(0.95); ' + 1684 '}' + 1685 '@keyframes spin { ' + 1686 ' from { transform: rotate(0deg); } ' + 1687 ' to { transform: rotate(360deg); } ' + 1688 '}' + 1689 '@media (max-width: 782px) { ' + 1690 ' .fae-ajax-notice { ' + 1691 ' right: 10px; ' + 1692 ' left: 10px; ' + 1693 ' min-width: auto; ' + 1694 ' max-width: none; ' + 1695 ' }' + 1696 '}' + 1697 '</style>' 1698 ); 1699 } 1700 1701 // Set top position dynamically 1702 notice.css('top', topPosition); 1703 1704 $('body').append(notice); 1705 1706 // Trigger animation by adding show class after a tiny delay 233 1707 setTimeout(() => { 234 notice.fadeOut(500, function () { 1708 notice.addClass('show'); 1709 }, 10); 1710 1711 // Auto-dismiss after 4.5 seconds 1712 const autoDismiss = setTimeout(() => { 1713 notice.removeClass('show').addClass('fade-out'); 1714 setTimeout(() => { 235 1715 notice.remove(); 1716 }, 400); 1717 }, 4500); 1718 1719 // Manual dismiss 1720 notice.find('.fae-notice-close').on('click', function(e) { 1721 e.stopPropagation(); 1722 clearTimeout(autoDismiss); 1723 notice.removeClass('show').addClass('fade-out'); 1724 setTimeout(() => { 1725 notice.remove(); 1726 }, 400); 236 1727 }); 237 }, 3000);1728 }, 100); 238 1729 }, 239 1730 … … 320 1811 $(".fae-icon-option").removeClass("selected"); 321 1812 $this.addClass("selected"); 322 323 // Update preview if effect is active324 FaeAdmin.updatePreviewSetting("fae_cursor_options[icon]", iconFile);325 326 // Close modal327 FaeAdmin.closeIconPicker(e);328 1813 }, 329 1814 … … 343 1828 }); 344 1829 }, 1830 1831 handleScopeTypeChange: function () { 1832 const $select = $(this); 1833 const scopeType = $select.val(); 1834 // Look for scope options in multiple possible parent structures (old and new layout) 1835 let $scopeOptions = $select.closest('.fae-settings-section').find('.fae-scope-option'); 1836 if ($scopeOptions.length === 0) { 1837 $scopeOptions = $select.closest('.fae-settings-subgroup').find('.fae-scope-option'); 1838 } 1839 if ($scopeOptions.length === 0) { 1840 $scopeOptions = $select.closest('.fae-settings-subgroup-content').find('.fae-scope-option'); 1841 } 1842 1843 // Hide all scope options with animation 1844 $scopeOptions.slideUp(200); 1845 1846 // Show the selected scope option with animation 1847 $scopeOptions.filter('[data-scope-type="' + scopeType + '"]').slideDown(200); 1848 }, 1849 1850 handleUserRestrictionTypeChange: function () { 1851 const $radio = $(this); 1852 1853 // Don't handle if radio is disabled (Pro feature) 1854 if ($radio.is(':disabled')) { 1855 return; 1856 } 1857 1858 const restrictionType = $radio.val(); 1859 // Look for roles wrapper in multiple possible parent structures (old and new layout) 1860 let $wrapper = $radio.closest('.fae-setting-group').find('.fae-specific-roles-wrapper'); 1861 if ($wrapper.length === 0) { 1862 $wrapper = $radio.closest('.fae-settings-subgroup-content').find('.fae-specific-roles-wrapper'); 1863 } 1864 if ($wrapper.length === 0) { 1865 $wrapper = $radio.closest('.fae-settings-subgroup').find('.fae-specific-roles-wrapper'); 1866 } 1867 1868 if (restrictionType === 'specific') { 1869 $wrapper.slideDown(200); 1870 } else { 1871 $wrapper.slideUp(200); 1872 // Uncheck all role checkboxes when switching to "All Users" 1873 $wrapper.find('input[type="checkbox"][name*="[user_roles]"]').prop('checked', false); 1874 } 1875 }, 1876 1877 initPreviewIframes: function() { 1878 const adminUrl = typeof faeAdminData !== 'undefined' ? faeAdminData.adminUrl : (window.location.origin + '/wp-admin/admin.php'); 1879 1880 // Effects that support icon/size (from config) 1881 const iconEffects = ['drop-effect', 'rise-effect']; 1882 1883 // Show/hide icon settings based on selected effect 1884 function updateIconSettingsVisibility(effect) { 1885 const showIcon = iconEffects.includes(effect); 1886 $('.fae-icon-setting').toggle(showIcon); 1887 } 1888 1889 // Debounced iframe update - make it accessible to handlers 1890 const updateIframe = debounce(function(type) { 1891 let iframe, effect, color, speed, size, icon, flag, flagPosition, multiColor, bg; 1892 1893 if (type === 'cursor') { 1894 iframe = document.getElementById('fae-cursor-preview-iframe'); 1895 effect = $('input[name="fae_cursor_options[effect]"]:checked').val() || 'none'; 1896 color = $('#fae-cursor-color').val() || '#667eea'; 1897 speed = $('#fae-cursor-speed').val() || 'normal'; 1898 size = $('#fae-cursor-size').val() || '1.5rem'; 1899 icon = $('#fae-cursor-icon').val() || 'star.svg'; 1900 flag = $('#fae-cursor-flag').val() || ''; 1901 flagPosition = $('#fae-cursor-flag-position').val() || 'center'; 1902 multiColor = $('#fae-cursor-multi-color').is(':checked') ? '1' : '0'; 1903 bg = $('#fae-cursor-preview-bg').attr('data-bg') || 'dark'; 1904 1905 // Update icon settings visibility 1906 updateIconSettingsVisibility(effect); 1907 } else if (type === 'keyboard') { 1908 iframe = document.getElementById('fae-keyboard-preview-iframe'); 1909 effect = $('input[name="fae_keyboard_options[effect]"]:checked').val() || 'none'; 1910 // Use sparkle color picker if sparkle-keys is selected, otherwise use regular color picker 1911 if (effect === 'sparkle-keys') { 1912 color = $('#fae-keyboard-color-sparkle').val() || $('#fae-keyboard-color').val() || '#667eea'; 1913 } else { 1914 color = $('#fae-keyboard-color').val() || '#667eea'; 1915 } 1916 speed = 'normal'; 1917 size = '1.5rem'; 1918 icon = 'star.svg'; 1919 flag = ''; 1920 flagPosition = 'center'; 1921 multiColor = '0'; // Multi-color is disabled (Pro feature) 1922 bg = $('#fae-keyboard-preview-bg').attr('data-bg') || 'dark'; 1923 } else if (type === 'particle') { 1924 iframe = document.getElementById('fae-particle-preview-iframe'); 1925 effect = $('input[name="fae_particle_options[effect]"]:checked').val() || 'none'; 1926 color = $('#fae-particle-color').val() || '#667eea'; 1927 speed = $('#fae-particle-speed').val() || 'normal'; 1928 size = '1.5rem'; 1929 icon = 'star.svg'; 1930 flag = ''; 1931 flagPosition = 'center'; 1932 multiColor = '0'; 1933 bg = $('#fae-particle-preview-bg').attr('data-bg') || 'dark'; 1934 } 1935 1936 if (iframe) { 1937 // Remove loaded class to hide iframe during reload 1938 iframe.classList.remove('loaded'); 1939 1940 const params = new URLSearchParams({ 1941 fae_embed_preview: '1', 1942 type: type, 1943 effect: effect, 1944 color: color, 1945 speed: speed, 1946 size: size, 1947 icon: icon, 1948 flag: flag || '', 1949 flag_position: flagPosition || 'center', 1950 multi_color: multiColor, 1951 bg: bg || 'dark', 1952 _t: Date.now() // Cache-busting parameter to ensure iframe reloads 1953 }); 1954 iframe.src = adminUrl + '?' + params.toString(); 1955 1956 // Add loaded class when iframe finishes loading 1957 iframe.onload = function() { 1958 this.classList.add('loaded'); 1959 }; 1960 } 1961 }, 300); 1962 1963 // Make updateIframe accessible to handlers 1964 FaeAdmin.updatePreviewIframe = updateIframe; 1965 1966 // Cursor effect changes 1967 $('input[name="fae_cursor_options[effect]"]').on('change', function() { 1968 const effect = $(this).val(); 1969 // Update all settings visibility based on effect 1970 FaeAdmin.updateAdvancedSettings(effect); 1971 // For flag-effect, also update color picker visibility based on flag selection 1972 if (effect === 'flag-effect') { 1973 updateColorPickerVisibility(); 1974 } 1975 updateIframe('cursor'); 1976 }); 1977 1978 // Initialize icon settings visibility on page load 1979 const initialEffect = $('input[name="fae_cursor_options[effect]"]:checked').val() || 'none'; 1980 updateIconSettingsVisibility(initialEffect); 1981 // Initialize all settings visibility using updateAdvancedSettings 1982 FaeAdmin.updateAdvancedSettings(initialEffect); 1983 // For flag-effect, also update color picker visibility based on flag selection 1984 if (initialEffect === 'flag-effect') { 1985 updateColorPickerVisibility(); 1986 } 1987 $('#fae-cursor-color').on('input', function() { 1988 $('#fae-cursor-color-text').val(this.value); 1989 updateIframe('cursor'); 1990 }); 1991 $('#fae-cursor-color-text').on('change', function() { 1992 $('#fae-cursor-color').val(this.value); 1993 updateIframe('cursor'); 1994 }); 1995 $('#fae-cursor-speed').on('change', function() { 1996 // Track user's speed choice 1997 FaeAdmin.userSelectedSpeeds.cursor = $(this).val(); 1998 updateIframe('cursor'); 1999 }); 2000 $('#fae-cursor-size, #fae-cursor-flag-position').on('change', function() { 2001 updateIframe('cursor'); 2002 }); 2003 $('#fae-cursor-multi-color').on('change', function() { 2004 updateIframe('cursor'); 2005 }); 2006 2007 // Icon picker toggle 2008 $('#fae-icon-trigger-cursor').on('click', function(e) { 2009 e.preventDefault(); 2010 $('#fae-icon-dropdown-cursor').toggleClass('active'); 2011 }); 2012 2013 // Flag picker toggle 2014 $('#fae-flag-trigger-cursor').on('click', function(e) { 2015 e.preventDefault(); 2016 $('#fae-flag-dropdown-cursor').toggleClass('active'); 2017 }); 2018 2019 // Flag selection 2020 $('#fae-flag-dropdown-cursor').on('click', '.fae-flag-dropdown-item', function() { 2021 const $item = $(this); 2022 const flagFile = $item.data('flag') || ''; 2023 const flagCode = $item.data('name') || ''; 2024 2025 // Update hidden input 2026 $('#fae-cursor-flag').val(flagFile); 2027 2028 // Update button display 2029 if (flagFile) { 2030 const flagUrl = (typeof faeAdminData !== 'undefined' ? faeAdminData.assetsUrl : '') + 'flags/' + flagFile; 2031 $('#fae-flag-preview-cursor').html('<img src="' + flagUrl + '" alt="' + flagCode + '" style="width: 24px; height: 18px; object-fit: cover; border-radius: 2px; border: 1px solid #e5e7eb;">'); 2032 $('#fae-flag-name-cursor').text(flagCode); 2033 // Hide color picker and show flag position when flag is selected 2034 $('.fae-color-setting').hide(); 2035 $('.fae-flag-position-setting').show(); 2036 } else { 2037 $('#fae-flag-preview-cursor').html('<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 20px; height: 20px; color: #9ca3af;"><path d="M19 11H5m14 0a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-6a2 2 0 0 1 2-2m14 0V9a2 2 0 0 0-2-2M5 11V9a2 2 0 0 1 2-2m0 0V5a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v2M7 7h10"/></svg>'); 2038 $('#fae-flag-name-cursor').text('FILL'); 2039 // Show color picker and hide flag position when FILL is selected 2040 $('.fae-color-setting').show(); 2041 $('.fae-flag-position-setting').hide(); 2042 } 2043 2044 // Update selected state 2045 $('#fae-flag-dropdown-cursor .fae-flag-dropdown-item').removeClass('selected'); 2046 $item.addClass('selected'); 2047 2048 // Close dropdown 2049 $('#fae-flag-dropdown-cursor').removeClass('active'); 2050 2051 // Update preview 2052 updateIframe('cursor'); 2053 }); 2054 2055 // Show/hide color picker and flag position based on initial flag selection 2056 function updateColorPickerVisibility() { 2057 const selectedFlag = $('#fae-cursor-flag').val(); 2058 if ($('input[name="fae_cursor_options[effect]"]:checked').val() === 'flag-effect') { 2059 if (selectedFlag) { 2060 $('.fae-color-setting').hide(); 2061 $('.fae-flag-position-setting').show(); 2062 } else { 2063 $('.fae-color-setting').show(); 2064 $('.fae-flag-position-setting').hide(); 2065 } 2066 } 2067 } 2068 2069 // Initialize color picker visibility on page load 2070 updateColorPickerVisibility(); 2071 2072 // Note: Effect change handling is done in the handler above (line 776) 2073 // This ensures all settings are properly updated via updateAdvancedSettings 2074 2075 // Flag search - search by country name 2076 $('#fae-flag-search-cursor').on('input', function() { 2077 const searchTerm = $(this).val().toLowerCase(); 2078 $('#fae-flag-grid-cursor .fae-flag-dropdown-item').each(function() { 2079 const countryName = $(this).data('country-name') || ''; 2080 if (countryName.includes(searchTerm)) { 2081 $(this).show(); 2082 } else { 2083 $(this).hide(); 2084 } 2085 }); 2086 }); 2087 2088 // Close flag dropdown when clicking outside 2089 $(document).on('click', function(e) { 2090 if (!$(e.target).closest('.fae-flag-picker-inline').length) { 2091 $('#fae-flag-dropdown-cursor').removeClass('active'); 2092 } 2093 }); 2094 2095 // Icon selection 2096 $('#fae-icon-dropdown-cursor').on('click', '.fae-icon-dropdown-item', function() { 2097 const $item = $(this); 2098 const iconFile = $item.data('icon'); 2099 const iconSvg = $item.html(); 2100 const iconName = iconFile.replace('.svg', ''); 2101 2102 // Update hidden input 2103 $('#fae-cursor-icon').val(iconFile); 2104 2105 // Update button display 2106 $('#fae-icon-preview-cursor').html(iconSvg); 2107 $('#fae-icon-name-cursor').text(iconName); 2108 2109 // Update selected state 2110 $('#fae-icon-dropdown-cursor .fae-icon-dropdown-item').removeClass('selected'); 2111 $item.addClass('selected'); 2112 2113 // Close dropdown 2114 $('#fae-icon-dropdown-cursor').removeClass('active'); 2115 2116 // Update preview 2117 updateIframe('cursor'); 2118 }); 2119 2120 // Close dropdown when clicking outside 2121 $(document).on('click', function(e) { 2122 if (!$(e.target).closest('.fae-icon-picker-inline').length) { 2123 $('.fae-icon-dropdown').removeClass('active'); 2124 } 2125 if (!$(e.target).closest('.fae-flag-picker-inline').length) { 2126 $('.fae-flag-dropdown').removeClass('active'); 2127 } 2128 }); 2129 2130 // Keyboard effect changes 2131 $('input[name="fae_keyboard_options[effect]"]').on('change', function() { 2132 updateIframe('keyboard'); 2133 }); 2134 $('#fae-keyboard-color, #fae-keyboard-color-sparkle').on('input', function() { 2135 const $textInput = $(this).attr('id') === 'fae-keyboard-color' ? $('#fae-keyboard-color-text') : $('#fae-keyboard-color-text-sparkle'); 2136 $textInput.val(this.value); 2137 // Sync both color inputs if sparkle-keys 2138 if ($('input[name="fae_keyboard_options[effect]"]:checked').val() === 'sparkle-keys') { 2139 if ($(this).attr('id') === 'fae-keyboard-color') { 2140 $('#fae-keyboard-color-sparkle').val(this.value); 2141 $('#fae-keyboard-color-text-sparkle').val(this.value); 2142 } else { 2143 $('#fae-keyboard-color').val(this.value); 2144 $('#fae-keyboard-color-text').val(this.value); 2145 } 2146 } 2147 updateIframe('keyboard'); 2148 }); 2149 $('#fae-keyboard-color-text, #fae-keyboard-color-text-sparkle').on('change', function() { 2150 const $colorInput = $(this).attr('id') === 'fae-keyboard-color-text' ? $('#fae-keyboard-color') : $('#fae-keyboard-color-sparkle'); 2151 $colorInput.val(this.value); 2152 // Sync both color inputs if sparkle-keys 2153 if ($('input[name="fae_keyboard_options[effect]"]:checked').val() === 'sparkle-keys') { 2154 if ($(this).attr('id') === 'fae-keyboard-color-text') { 2155 $('#fae-keyboard-color-sparkle').val(this.value); 2156 $('#fae-keyboard-color-text-sparkle').val(this.value); 2157 } else { 2158 $('#fae-keyboard-color').val(this.value); 2159 $('#fae-keyboard-color-text').val(this.value); 2160 } 2161 } 2162 updateIframe('keyboard'); 2163 }); 2164 // Multi-color is disabled (Pro feature) - prevent interaction 2165 $('#fae-keyboard-multi-color').on('click', function(e) { 2166 e.preventDefault(); 2167 return false; 2168 }); 2169 2170 // Particle effect changes 2171 $('input[name="fae_particle_options[effect]"]').on('change', function() { 2172 updateIframe('particle'); 2173 }); 2174 $('#fae-particle-color').on('input', function() { 2175 $('#fae-particle-color-text').val(this.value); 2176 updateIframe('particle'); 2177 }); 2178 $('#fae-particle-color-text').on('change', function() { 2179 $('#fae-particle-color').val(this.value); 2180 updateIframe('particle'); 2181 }); 2182 $('#fae-particle-speed').on('change', function() { 2183 // Track user's speed choice 2184 FaeAdmin.userSelectedSpeeds.particle = $(this).val(); 2185 updateIframe('particle'); 2186 }); 2187 2188 // Load saved background preference from localStorage (fallback to cookie, then default) 2189 function getSavedBg() { 2190 // Try localStorage first 2191 let savedBg = localStorage.getItem('fae_preview_bg'); 2192 if (!savedBg) { 2193 // Fallback to reading from existing toggle button data attribute (set by PHP from cookie) 2194 savedBg = $('.fae-preview-bg-toggle').first().attr('data-bg') || 'dark'; 2195 // Save to localStorage for future use 2196 localStorage.setItem('fae_preview_bg', savedBg); 2197 } 2198 return savedBg || 'dark'; 2199 } 2200 2201 // Set cookie helper function 2202 function setPreviewBgCookie(bg) { 2203 const expires = new Date(); 2204 expires.setTime(expires.getTime() + (365 * 24 * 60 * 60 * 1000)); // 1 year 2205 document.cookie = 'fae_preview_bg=' + bg + ';expires=' + expires.toUTCString() + ';path=/'; 2206 } 2207 2208 // Background color toggle button clicks 2209 $('.fae-preview-bg-toggle').on('click', function() { 2210 const $toggle = $(this); 2211 const currentBg = $toggle.attr('data-bg'); 2212 const newBg = currentBg === 'dark' ? 'light' : 'dark'; 2213 2214 // Save to both localStorage and cookie 2215 localStorage.setItem('fae_preview_bg', newBg); 2216 setPreviewBgCookie(newBg); 2217 2218 // Apply to all toggles (keep them in sync) 2219 $('.fae-preview-bg-toggle').attr('data-bg', newBg); 2220 2221 // Update iframe wrapper backgrounds immediately to prevent flash 2222 const bgGradient = newBg === 'light' 2223 ? 'linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%)' 2224 : 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)'; 2225 $('.fae-preview-iframe-wrapper').css('background', bgGradient); 2226 2227 const type = $toggle.attr('id').replace('fae-', '').replace('-preview-bg', ''); 2228 updateIframe(type); 2229 }); 2230 2231 // Fullscreen preview modal 2232 const $modal = $('#fae-preview-modal'); 2233 const $modalIframe = $('#fae-modal-iframe'); 2234 const $modalHint = $('#fae-modal-hint'); 2235 2236 // Hint messages for each type 2237 const hintMessages = { 2238 cursor: 'Move mouse around to see the effect', 2239 keyboard: 'Click inside preview and type to see the effect', 2240 particle: 'Move mouse around to see the effect' 2241 }; 2242 2243 // Expand button click 2244 $('.fae-expand-btn').on('click', function() { 2245 const type = $(this).data('preview-type'); 2246 const $iframe = $('#fae-' + type + '-preview-iframe'); 2247 if ($iframe.length) { 2248 // Get current iframe src and update modal 2249 $modalIframe.attr('src', $iframe.attr('src')); 2250 $modalHint.text(hintMessages[type] || hintMessages.cursor); 2251 $modal.addClass('active'); 2252 $('body').css('overflow', 'hidden'); 2253 } 2254 }); 2255 2256 // Close modal 2257 $('#fae-modal-close').on('click', function() { 2258 $modal.removeClass('active'); 2259 $modalIframe.attr('src', ''); 2260 $('body').css('overflow', ''); 2261 }); 2262 2263 // Close on Escape key 2264 $(document).on('keydown', function(e) { 2265 if (e.key === 'Escape' && $modal.hasClass('active')) { 2266 $modal.removeClass('active'); 2267 $modalIframe.attr('src', ''); 2268 $('body').css('overflow', ''); 2269 } 2270 }); 2271 2272 // Close on backdrop click 2273 $modal.on('click', function(e) { 2274 if (e.target === this) { 2275 $modal.removeClass('active'); 2276 $modalIframe.attr('src', ''); 2277 $('body').css('overflow', ''); 2278 } 2279 }); 2280 }, 345 2281 }; 346 2282 … … 348 2284 $(document).ready(function () { 349 2285 FaeAdmin.init(); 2286 2287 // Move WordPress admin notices below the header 2288 FaeAdmin.moveNoticesBelowHeader(); 2289 2290 // Watch for dynamically added notices and move them too 2291 const noticeObserver = new MutationObserver(function(mutations) { 2292 FaeAdmin.moveNoticesBelowHeader(); 2293 }); 2294 2295 // Observe the body for new notices 2296 if (document.body) { 2297 noticeObserver.observe(document.body, { 2298 childList: true, 2299 subtree: true 2300 }); 2301 } 350 2302 }); 2303 2304 // Function to move notices below header 2305 FaeAdmin.moveNoticesBelowHeader = function() { 2306 const dashboard = $('.fae-cursor-dashboard'); 2307 if (dashboard.length === 0) return; 2308 2309 const header = dashboard.find('.fae-dashboard-header'); 2310 if (header.length === 0) return; 2311 2312 // Find all WordPress notices that are not FaeCursor notices 2313 const notices = $('.notice:not(.fae-notice), .update-nag, .error:not(.fae-notice), .updated:not(.fae-notice)'); 2314 2315 notices.each(function() { 2316 const $notice = $(this); 2317 // Only process if notice is not already positioned correctly 2318 const isAfterHeader = $notice.prevAll('.fae-dashboard-header').length > 0; 2319 const isInDashboard = $notice.closest('.fae-cursor-dashboard').length > 0; 2320 2321 if (!isAfterHeader || !isInDashboard) { 2322 // Move notice to appear right after the header 2323 header.after($notice); 2324 } 2325 }); 2326 }; 351 2327 })(jQuery); -
faecursor/trunk/faecursor.php
r3384653 r3454888 5 5 /* 6 6 Plugin Name: FaeCursor 7 Description: Add stunning and customizable mouse cursor effects to your WordPress site. FaeCursor enhances user engagement with interactive, lightweight, and responsive cursor animations. Perfect for portfolios, creative websites, and business sites seeking to stand out. Try FaeCursor today and give your visitors a memorable experience!8 Version: 1. 17 Description: Lightweight WordPress custom cursor plugin — add cursor, keyboard, and screen effects like trails and sparkles. 8 Version: 1.2 9 9 Author: FaeCursor Plugin Team 10 Author URI: https://faecursor. wordpress.com10 Author URI: https://faecursor.com 11 11 License: GPLv2 or later 12 12 Text Domain: faecursor … … 18 18 if ( ! defined( 'ABSPATH' ) ) { 19 19 exit( 'You are not allowed to access this file directly.' ); 20 } 21 22 /** 23 * Load conflict handler class 24 */ 25 require_once __DIR__ . '/includes/class-fae-cursor-conflict.php'; 26 27 /** 28 * Initialize conflict prevention 29 * This prevents free plugin from loading if Pro is active 30 */ 31 if ( Fae_Cursor_Conflict_Free::init() ) { 32 return; // Exit early if Pro is active 33 } 34 35 /** 36 * Initialize Freemius SDK (WordPress.org compliant) 37 * Following the same approach as Ultimate Cursor 38 */ 39 if ( ! function_exists( 'faecursor_fs' ) ) { 40 // Create a helper function for easy SDK access. 41 function faecursor_fs() { 42 global $faecursor_fs; 43 44 if ( ! isset( $faecursor_fs ) ) { 45 // Activate multisite network integration. 46 if ( ! defined( 'WP_FS__PRODUCT_22561_MULTISITE' ) ) { 47 define( 'WP_FS__PRODUCT_22561_MULTISITE', true ); 48 } 49 50 // Include Freemius SDK. 51 require_once dirname( __FILE__ ) . '/vendor/freemius/start.php'; 52 53 $faecursor_fs = fs_dynamic_init( array( 54 'id' => '22561', 55 'slug' => 'faecursor', 56 'premium_slug' => 'faecursor-pro', 57 'type' => 'plugin', 58 'public_key' => 'pk_22357c6e5eb4d6802700ca1aa120d', 59 'is_premium' => false, 60 'is_premium_only' => false, 61 'has_addons' => false, 62 'has_paid_plans' => true, 63 'is_live' => true, 64 'is_org_compliant' => true, 65 // No parallel_activation - Pro REPLACES Free (standalone model) 66 'menu' => array( 67 'slug' => 'fae_cursor', 68 'first-path' => 'admin.php?page=fae_cursor', 69 'support' => false, 70 'contact' => false, 71 'pricing' => true, 72 ), 73 ) ); 74 } 75 76 return $faecursor_fs; 77 } 78 79 // Init Freemius. 80 faecursor_fs(); 81 // Signal that SDK was initiated. 82 do_action( 'faecursor_fs_loaded' ); 20 83 } 21 84 … … 30 93 } 31 94 95 // Include effects configuration 96 require_once FAE_CURSOR_DIR . '/config/fae-cursor-effects-config.php'; 97 require_once FAE_CURSOR_DIR . '/config/fae-keyboard-effects-config.php'; 98 require_once FAE_CURSOR_DIR . '/config/fae-particle-effects-config.php'; 99 32 100 // Custom Debug Constant, intended for developer use. 33 101 if ( ! defined( 'FAE_CURSOR_DEBUG' ) ) { … … 37 105 // Constants. 38 106 define( 'FAE_CURSOR_PLUGIN_NAME', 'faecursor' ); // Updated to match text domain 39 define( 'FAE_CURSOR_VERSION', '1. 1' ); // Update this version as necessary107 define( 'FAE_CURSOR_VERSION', '1.2' ); // Update this version as necessary 40 108 41 // Define default options 42 function fae_cursor_get_default_options() { 43 return array( 44 'effect' => 'none', 45 'color' => '#fcba03', 46 'size' => '1.5rem', 47 'speed' => 'fast', 48 'icon' => 'star.svg' 49 ); 109 // Load plugin classes 110 require_once FAE_CURSOR_DIR . '/includes/class-fae-cursor-loader.php'; 111 112 // Initialize the plugin 113 Fae_Cursor_Loader::init(); 114 115 // Backward compatibility functions 116 if ( ! function_exists( 'fae_cursor_get_default_options' ) ) { 117 function fae_cursor_get_default_options() { 118 return Fae_Cursor_Settings::get_default_options(); 119 } 50 120 } 51 121 52 function fae_cursor_enqueue_scripts() { 53 $options = get_option('fae_cursor_options', fae_cursor_get_default_options()); 54 $effect = isset($options['effect']) ? $options['effect'] : 'none'; 55 $timestamp = time(); // Current timestamp for cache busting 56 57 if ($effect !== 'none') { 58 $effect_dir = plugin_dir_path(__FILE__) . 'assets/effects/' . $effect; 59 60 foreach (glob($effect_dir . '/*.css') as $css_file) { 61 $handle = 'fae-cursor-style-' . basename($css_file, '.css'); 62 $css_url = plugin_dir_url(__FILE__) . 'assets/effects/' . $effect . '/' . basename($css_file); 63 wp_enqueue_style($handle, $css_url, array(), $timestamp); 64 } 65 66 // Enqueue scripts and localize the first one 67 $localized = false; 68 foreach (glob($effect_dir . '/*.js') as $js_file) { 69 $handle = 'fae-cursor-script-' . basename($js_file, '.js'); 70 $js_url = plugin_dir_url(__FILE__) . 'assets/effects/' . $effect . '/' . basename($js_file); 71 wp_enqueue_script($handle, $js_url, array('jquery'), $timestamp, true); 72 73 if (! $localized) { 74 wp_localize_script($handle, 'faeCursorSettings', array( 75 'effect' => $effect, 76 'color' => isset($options['color']) ? $options['color'] : '#667eea', 77 'size' => isset($options['size']) ? $options['size'] : '1.5rem', 78 'speed' => isset($options['speed']) ? $options['speed'] : 'normal', 79 'icon' => isset($options['icon']) ? $options['icon'] : 'star.svg', 80 'assetsUrl' => plugin_dir_url(__FILE__) . 'assets/', 81 )); 82 $localized = true; 83 } 84 } 85 } 86 } 87 add_action('wp_enqueue_scripts', 'fae_cursor_enqueue_scripts'); 88 89 function fae_cursor_add_admin_menu() { 90 add_menu_page( 91 'FaeCursor Settings', 92 'FaeCursor', 93 'manage_options', 94 'fae_cursor', 95 'fae_cursor_options_page', 96 plugin_dir_url(__FILE__) . 'assets/icons/icon.ico', // Path to your custom image 97 100 98 ); 99 } 100 add_action('admin_menu', 'fae_cursor_add_admin_menu'); 101 102 function fae_cursor_settings_init() { 103 register_setting( 104 'fae_cursor', // Option group 105 'fae_cursor_options', // Option name 106 array( 107 'type' => 'array', // Expected data type 108 'sanitize_callback' => 'fae_cursor_sanitize_options', // Custom sanitization callback 109 ) 110 ); 111 112 // Add settings section 113 add_settings_section( 114 'fae_cursor_section', // Section ID 115 __('Mouse Effect Settings', 'faecursor'), // Title 116 null, // Callback (not needed here) 117 'fae_cursor' // Page 118 ); 119 120 // Add settings field 121 add_settings_field( 122 'fae_cursor_effect', // Field ID 123 __('Mouse Effect', 'faecursor'), // Title 124 'fae_cursor_effect_render', // Callback to render the field 125 'fae_cursor', // Page 126 'fae_cursor_section' // Section 127 ); 128 } 129 add_action('admin_init', 'fae_cursor_settings_init'); 130 131 function fae_cursor_sanitize_options($input) { 132 $sanitized = array(); 133 134 // Sanitize effect 135 if (isset($input['effect'])) { 136 $allowed_effects = array('none', 'drop-effect', 'rise-effect', 'line-effect', 'duo-circle', 'duo-circle-2'); 137 $sanitized['effect'] = in_array($input['effect'], $allowed_effects) ? $input['effect'] : 'none'; // Default to 'none' 138 } 139 140 // Sanitize color 141 if (isset($input['color'])) { 142 $sanitized['color'] = sanitize_hex_color($input['color']) ?: '#fcba03'; 143 } 144 145 // Sanitize size 146 if (isset($input['size'])) { 147 $allowed_sizes = array('1rem', '1.5rem', '2rem', '2.5rem'); 148 $sanitized['size'] = in_array($input['size'], $allowed_sizes) ? $input['size'] : '1.5rem'; 149 } 150 151 // Sanitize speed 152 if (isset($input['speed'])) { 153 $allowed_speeds = array('slow', 'normal', 'fast'); 154 $sanitized['speed'] = in_array($input['speed'], $allowed_speeds) ? $input['speed'] : 'fast'; 155 } 156 157 // Sanitize icon (must be an existing SVG in assets/ionicons) 158 if (isset($input['icon'])) { 159 $icon = basename(sanitize_text_field($input['icon'])); 160 $icon_path = plugin_dir_path(__FILE__) . 'assets/ionicons/' . $icon; 161 if (substr($icon, -4) === '.svg' && file_exists($icon_path)) { 162 $sanitized['icon'] = $icon; 163 } else { 164 $sanitized['icon'] = 'star.svg'; 165 } 166 } 167 168 return $sanitized; 122 if ( ! function_exists( 'fae_detectDeviceType' ) ) { 123 function fae_detectDeviceType() { 124 return Fae_Cursor_Device::detect(); 125 } 169 126 } 170 127 171 function fae_cursor_enqueue_admin_styles($hook) { 172 // Only load on FaeCursor admin pages 173 if ($hook !== 'toplevel_page_fae_cursor') { 174 175 wp_enqueue_style( 176 'fae-cursor-admin-styles', 177 plugin_dir_url(__FILE__) . 'assets/css/fae-cursor-general.css', 178 array(), 179 '1.1.0' 180 ); 181 182 } 183 184 // Ionicons are embedded as SVG, no external dependencies needed 185 186 wp_enqueue_style( 187 'fae-cursor-admin-styles', 188 plugin_dir_url(__FILE__) . 'assets/css/fae-cursor-admin.css', 189 array(), 190 '1.1.0' 191 ); 192 193 wp_enqueue_script( 194 'fae-cursor-admin-script', 195 plugin_dir_url(__FILE__) . 'assets/js/fae-cursor-admin.js', 196 array('jquery'), 197 '1.1.0', 198 true 199 ); 128 if ( ! function_exists( 'fae_cursor_get_effect_icon_svg' ) ) { 129 function fae_cursor_get_effect_icon_svg($effect_id) { 130 return Fae_Cursor_Admin::get_effect_icon_svg($effect_id); 131 } 200 132 } 201 133 202 add_action('admin_enqueue_scripts', 'fae_cursor_enqueue_admin_styles'); 203 204 function fae_cursor_effect_render() { 205 $options = get_option('fae_cursor_options', fae_cursor_get_default_options()); 206 $selected_effect = isset($options['effect']) ? $options['effect'] : 'none'; 207 // Map effect to preview CSS class suffix 208 $preview_map = array( 209 'drop-effect' => 'drop', 210 'rise-effect' => 'rise', 211 'line-effect' => 'line', 212 'duo-circle' => 'duo', 213 'none' => 'none' 214 ); 215 $preview_class = isset($preview_map[$selected_effect]) ? $preview_map[$selected_effect] : 'none'; 216 ?> 217 <select name="fae_cursor_options[effect]"> 218 <option value="none" <?php selected($selected_effect, 'none'); ?>>None</option> 219 <option value="drop-effect" <?php selected($selected_effect, 'drop-effect'); ?>>Drop Effect</option> 220 <option value="rise-effect" <?php selected($selected_effect, 'rise-effect'); ?>>Rise Effect</option> 221 <option value="line-effect" <?php selected($selected_effect, 'line-effect'); ?>>Line Effect</option> 222 <option value="duo-circle" <?php selected($selected_effect, 'duo-circle'); ?>>Duo Circle</option> 223 </select> 224 <?php 134 // Legacy function for backward compatibility 135 if ( ! function_exists( 'fae_cursor_options_page' ) ) { 136 function fae_cursor_options_page() { 137 Fae_Cursor_Admin::render_options_page(); 138 } 225 139 } 226 227 function fae_cursor_options_page() {228 $options = get_option('fae_cursor_options', fae_cursor_get_default_options());229 $selected_effect = isset($options['effect']) ? $options['effect'] : 'none';230 $selected_color = isset($options['color']) ? $options['color'] : '#fcba03';231 $selected_size = isset($options['size']) ? $options['size'] : '1.5rem';232 $selected_speed = isset($options['speed']) ? $options['speed'] : 'fast';233 $selected_icon = isset($options['icon']) ? $options['icon'] : 'star.svg';234 ?>235 <div class="wrap fae-cursor-dashboard">236 <!-- Header Section -->237 <div class="fae-dashboard-header">238 <div class="fae-header-content">239 <div class="fae-header-main">240 <h1 class="fae-dashboard-title">241 <img src="<?php echo plugin_dir_url(__FILE__) . 'assets/icons/icon.ico'; ?>" alt="">242 FaeCursor243 </h1>244 </div>245 <div class="fae-header-actions">246 <a href="https://faecursor.wordpress.com/feedback/" target="_blank" class="fae-btn fae-btn-feedback" title="We'd love your thoughts! Click to share feedback.">247 <svg class="fae-icon" viewBox="0 0 512 512">248 <path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48Zm0,384c-97,0-176-79-176-176S159,80,256,80s176,79,176,176S353,432,256,432Z"/>249 </svg>250 Submit Feedback251 </a>252 </div>253 </div>254 </div>255 256 <!-- Stats Cards -->257 <div class="fae-stats-grid">258 <div class="fae-stat-card">259 <h3>Status</h3>260 <p class="fae-stat-value"><?php echo $selected_effect !== 'none' ? 'Active' : 'Inactive'; ?></p>261 </div>262 <div class="fae-stat-card">263 <h3>Current Effect</h3>264 <p class="fae-stat-value"><?php echo $selected_effect === 'none' ? 'None' : ucwords(str_replace('-', ' ', $selected_effect)); ?></p>265 </div>266 <div class="fae-stat-card">267 <h3>Version</h3>268 <p class="fae-stat-value">1.1</p>269 </div>270 <div class="fae-stat-card">271 <h3>Effects Available</h3>272 <p class="fae-stat-value">5</p>273 </div>274 </div>275 276 <!-- Main Content -->277 <div class="fae-main-content">278 <!-- Settings Panel -->279 <div class="fae-settings-panel">280 <h2>281 <svg class="fae-icon" viewBox="0 0 512 512">282 <path d="M470.39,300l-.47-.38-31.56-24.75a16.11,16.11,0,0,1-6.1-13.33l0-11.56a16,16,0,0,1,6.11-13.22L469.92,212l.47-.38a26.68,26.68,0,0,0,5.9-34.06l-42.71-73.9a1.59,1.59,0,0,1-.13-.22A26.86,26.86,0,0,0,401,92.14l-.35.13L363.55,107.2a15.94,15.94,0,0,1-14.47-1.29q-4.92-3.1-10-5.86a15.94,15.94,0,0,1-8.19-11.82L325.3,48.64l-.12-.72A27.22,27.22,0,0,0,298.76,26H213.24a26.92,26.92,0,0,0-26.45,22.39l-.09.56-5.57,39.67A16,16,0,0,1,173,100.44c-3.42,1.84-6.76,3.79-10,5.82a15.92,15.92,0,0,1-14.43-1.27l-37.13-15-.35-.14a26.87,26.87,0,0,0-32.48,11.34l-.13.22L35.71,177.9A26.71,26.71,0,0,0,41.61,212l.47.38,31.56,24.75a16.11,16.11,0,0,1,6.1,13.33l0,11.56a16,16,0,0,1-6.11,13.22L42.08,300l-.47.38a26.68,26.68,0,0,0-5.9,34.06l42.71,73.9a1.59,1.59,0,0,1,.13.22A26.86,26.86,0,0,0,111,419.86l.35-.13,37.07-14.93a15.94,15.94,0,0,1,14.47,1.29q4.92,3.11,10,5.86a15.94,15.94,0,0,1,8.19,11.82l5.56,39.59.12.72A27.22,27.22,0,0,0,213.24,486h85.52a26.92,26.92,0,0,0,26.45-22.39l.09-.56,5.57-39.67a16,16,0,0,1,8.18-11.82c3.42-1.84,6.76-3.79,10-5.82a15.92,15.92,0,0,1,14.43-1.27l37.13,14.95.35.14a26.85,26.85,0,0,0,32.48-11.34,2.53,2.53,0,0,1,.13-.22l42.71-73.89A26.7,26.7,0,0,0,470.39,300ZM335.91,259.76a80,80,0,1,1-83.66-83.67A80.21,80.21,0,0,1,335.91,259.76Z"/>283 </svg>284 Effect Settings285 </h2>286 287 <form id="fae-cursor-form" action="options.php" method="post">288 <?php settings_fields('fae_cursor'); ?>289 290 <!-- Effect Selection -->291 <div class="fae-effect-grid">292 <label class="fae-effect-option">293 <input type="radio" name="fae_cursor_options[effect]" value="none" <?php checked($selected_effect, 'none'); ?>>294 <div class="fae-effect-content">295 <svg class="fae-effect-icon" viewBox="0 0 512 512">296 <path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48Zm0,384c-97,0-176-79-176-176S159,80,256,80s176,79,176,176S353,432,256,432Z"/>297 </svg>298 <span class="fae-effect-name">None</span>299 </div>300 </label>301 302 <label class="fae-effect-option">303 <input type="radio" name="fae_cursor_options[effect]" value="drop-effect" <?php checked($selected_effect, 'drop-effect'); ?>>304 <div class="fae-effect-content">305 <svg class="fae-effect-icon" viewBox="0 0 512 512">306 <path d="M394,480a16,16,0,0,1-9.39-3L256,383.76,127.39,477a16,16,0,0,1-24.55-18.08L153,310.35,23,221.2A16,16,0,0,1,32,192H192.38l48.4-148.95a16,16,0,0,1,30.44,0l48.4,149H480a16,16,0,0,1,9.05,29.2L359,310.35l50.13,148.53A16,16,0,0,1,394,480Z"/>307 </svg>308 <span class="fae-effect-name">Drop Effect</span>309 </div>310 </label>311 312 <label class="fae-effect-option">313 <input type="radio" name="fae_cursor_options[effect]" value="rise-effect" <?php checked($selected_effect, 'rise-effect'); ?>>314 <div class="fae-effect-content">315 <svg class="fae-effect-icon" viewBox="0 0 512 512">316 <path d="M394,480a16,16,0,0,1-9.39-3L256,383.76,127.39,477a16,16,0,0,1-24.55-18.08L153,310.35,23,221.2A16,16,0,0,1,32,192H192.38l48.4-148.95a16,16,0,0,1,30.44,0l48.4,149H480a16,16,0,0,1,9.05,29.2L359,310.35l50.13,148.53A16,16,0,0,1,394,480Z"/>317 </svg>318 <span class="fae-effect-name">Rise Effect</span>319 </div>320 </label>321 322 <label class="fae-effect-option">323 <input type="radio" name="fae_cursor_options[effect]" value="line-effect" <?php checked($selected_effect, 'line-effect'); ?>>324 <div class="fae-effect-content">325 <svg class="fae-effect-icon" viewBox="0 0 512 512">326 <!-- Main diagonal line with thick fading tail effect -->327 <path d="M100,100 L400,400" stroke="currentColor" stroke-width="16" stroke-linecap="round" fill="none"/>328 329 <!-- Fading trail using multiple lines with decreasing opacity and thickness -->330 <path d="M120,120 L400,400" stroke="currentColor" stroke-width="12" stroke-linecap="round" fill="none" opacity="0.7"/>331 <path d="M140,140 L400,400" stroke="currentColor" stroke-width="10" stroke-linecap="round" fill="none" opacity="0.5"/>332 <path d="M160,160 L400,400" stroke="currentColor" stroke-width="8" stroke-linecap="round" fill="none" opacity="0.3"/>333 <path d="M180,180 L400,400" stroke="currentColor" stroke-width="6" stroke-linecap="round" fill="none" opacity="0.2"/>334 <path d="M200,200 L400,400" stroke="currentColor" stroke-width="4" stroke-linecap="round" fill="none" opacity="0.1"/>335 </svg>336 <span class="fae-effect-name">Line Effect</span>337 </div>338 </label>339 340 <label class="fae-effect-option">341 <input type="radio" name="fae_cursor_options[effect]" value="duo-circle" <?php checked($selected_effect, 'duo-circle'); ?>>342 <div class="fae-effect-content">343 <svg class="fae-effect-icon" viewBox="0 0 512 512">344 <path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48Zm0,384c-97,0-176-79-176-176S159,80,256,80s176,79,176,176S353,432,256,432Z"/>345 </svg>346 <span class="fae-effect-name">Duo Circle</span>347 </div>348 </label>349 350 <label class="fae-effect-option">351 <input type="radio" name="fae_cursor_options[effect]" value="duo-circle-2" <?php checked($selected_effect, 'duo-circle-2'); ?>>352 <div class="fae-effect-content">353 <svg class="fae-effect-icon" viewBox="0 0 512 512">354 <!-- Outer thick circle -->355 <circle cx="256" cy="256" r="160" stroke="currentColor" stroke-width="20" fill="none"/>356 <!-- Inner thin circle -->357 <circle cx="256" cy="256" r="60" stroke="currentColor" stroke-width="8" fill="none"/>358 </svg>359 <span class="fae-effect-name">Duo Circle 2</span>360 </div>361 </label>362 </div>363 364 <!-- Effect-Specific Settings -->365 <div class="fae-effect-settings" style="<?php echo $selected_effect === 'none' ? 'display: none;' : ''; ?>">366 367 <!-- Global Settings (for all effects) -->368 <div class="fae-settings-section">369 <h3>370 <svg class="fae-icon" viewBox="0 0 512 512">371 <path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48Zm0,384c-97,0-176-79-176-176S159,80,256,80s176,79,176,176S353,432,256,432Z"/>372 </svg>373 Global Settings374 </h3>375 <div class="fae-settings-grid">376 <div class="fae-setting-group">377 <label for="fae_cursor_color">Effect Color</label>378 <div class="fae-color-input">379 <input type="color" class="fae-color-picker" name="fae_cursor_options[color]" value="<?php echo esc_attr($selected_color); ?>" data-setting="fae_cursor_options[color]">380 <input type="text" name="fae_cursor_options[color]" value="<?php echo esc_attr($selected_color); ?>" placeholder="#667eea">381 </div>382 </div>383 384 <div class="fae-setting-group">385 <label for="fae_cursor_speed">Animation Speed</label>386 <select name="fae_cursor_options[speed]">387 <option value="slow" <?php selected($selected_speed, 'slow'); ?>>Slow</option>388 <option value="normal" <?php selected($selected_speed, 'normal'); ?>>Normal</option>389 <option value="fast" <?php selected($selected_speed, 'fast'); ?>>Fast</option>390 </select>391 </div>392 </div>393 </div>394 395 <!-- Icon-based Effects Settings (Drop & Rise) -->396 <div class="fae-settings-section" data-effect="drop-effect,rise-effect" style="<?php echo !in_array($selected_effect, ['drop-effect', 'rise-effect']) ? 'display: none;' : ''; ?>">397 <h3>398 <svg class="fae-icon" viewBox="0 0 512 512">399 <path d="M394,480a16,16,0,0,1-9.39-3L256,383.76,127.39,477a16,16,0,0,1-24.55-18.08L153,310.35,23,221.2A16,16,0,0,1,32,192H192.38l48.4-148.95a16,16,0,0,1,30.44,0l48.4,149H480a16,16,0,0,1,9.05,29.2L359,310.35l50.13,148.53A16,16,0,0,1,394,480Z"/>400 </svg>401 Icon Settings402 <span class="fae-section-badge">Drop & Rise Effects</span>403 </h3>404 <div class="fae-icon-settings-row">405 <div class="fae-setting-group">406 <label for="fae_cursor_icon">Choose Icon</label>407 <div class="fae-icon-picker-container">408 <button type="button" class="fae-icon-picker-trigger" id="fae-icon-picker-trigger">409 <div class="fae-selected-icon">410 <?php411 $selected_icon_path = plugin_dir_path(__FILE__) . 'assets/ionicons/' . $selected_icon;412 if (file_exists($selected_icon_path)) {413 echo file_get_contents($selected_icon_path);414 } else {415 echo '<svg viewBox="0 0 512 512"><path d="M394,480a16,16,0,0,1-9.39-3L256,383.76,127.39,477a16,16,0,0,1-24.55-18.08L153,310.35,23,221.2A16,16,0,0,1,32,192H192.38l48.4-148.95a16,16,0,0,1,30.44,0l48.4,149H480a16,16,0,0,1,9.05,29.2L359,310.35l50.13,148.53A16,16,0,0,1,394,480Z"/></svg>';416 }417 ?>418 </div>419 <span class="fae-icon-name"><?php echo esc_html(str_replace(array('-outline.svg','-sharp.svg','.svg'), '', $selected_icon)); ?></span>420 <svg class="fae-dropdown-arrow" viewBox="0 0 512 512">421 <path d="M98,190.06a13.06,13.06,0,0,1,9.17-3.95,12.78,12.78,0,0,1,8.95,3.95L256,343.21,395.88,190.06a13.06,13.06,0,0,1,9.17-3.95,12.78,12.78,0,0,1,8.95,3.95,13.61,13.61,0,0,1,0,19.21L264.12,373.18a12.78,12.78,0,0,1-8.95,3.95,13.06,13.06,0,0,1-9.17-3.95L98,209.27A13.61,13.61,0,0,1,98,190.06Z"/>422 </svg>423 </button>424 <input type="hidden" name="fae_cursor_options[icon]" value="<?php echo esc_attr($selected_icon); ?>" id="fae-selected-icon-input">425 426 <!-- Icon Picker Modal -->427 <div class="fae-icon-picker-modal" id="fae-icon-picker-modal">428 <div class="fae-icon-picker-content">429 <div class="fae-icon-picker-header">430 <h3>Choose an Icon</h3>431 <button type="button" class="fae-icon-picker-close" id="fae-icon-picker-close">432 <svg viewBox="0 0 512 512">433 <path d="M289.94,256l95-95A24,24,0,0,0,351,127l-95,95-95-95A24,24,0,0,0,127,161l95,95-95,95a24,24,0,1,0,34,34l95-95,95,95a24,24,0,0,0,34-34Z"/>434 </svg>435 </button>436 </div>437 <div class="fae-icon-picker-search">438 <input type="text" placeholder="Search icons..." id="fae-icon-search">439 </div>440 <div class="fae-icon-picker-grid" id="fae-icon-picker-grid">441 <?php442 $icons_dir = plugin_dir_path(__FILE__) . 'assets/ionicons/';443 $icons_url = plugin_dir_url(__FILE__) . 'assets/ionicons/';444 $icon_files = glob($icons_dir . '*.svg');445 446 // Define priority icons in the order you want them displayed447 $priority_icons = [448 'star.svg',449 'star-half.svg',450 'star-outline.svg',451 'sparkles.svg',452 'balloon.svg',453 'heart-circle.svg',454 'heart-half-outline.svg',455 'heart-half.svg',456 'heart-outline.svg',457 'heart.svg'458 ];459 460 // Build ordered icons list.461 $ordered_icons = [];462 463 // If a user-selected icon exists, show it first464 if ( ! empty( $selected_icon ) ) {465 $selected_path = $icons_dir . $selected_icon;466 if ( file_exists( $selected_path ) ) {467 $ordered_icons[] = $selected_path;468 }469 }470 471 // Add priority icons (skip the selected icon if already added)472 foreach ( $priority_icons as $file ) {473 $path = $icons_dir . $file;474 if ( file_exists( $path ) ) {475 if ( empty( $ordered_icons ) || $ordered_icons[0] !== $path ) {476 $ordered_icons[] = $path;477 }478 }479 }480 481 // Add remaining icons, excluding those already added482 foreach ( $icon_files as $path ) {483 // Skip if already in ordered list484 if ( in_array( $path, $ordered_icons, true ) ) {485 continue;486 }487 $file = basename( $path );488 if ( ! in_array( $file, $priority_icons, true ) ) {489 $ordered_icons[] = $path;490 }491 }492 493 $displayed = 0;494 foreach ($ordered_icons as $path) {495 $file = basename($path);496 $icon_name = str_replace(['-outline.svg', '-sharp.svg', '.svg'], '', $file);497 $is_selected = ($file === $selected_icon) ? 'selected' : '';498 499 // Limit to 5000 icons for performance500 if ($displayed >= 5000) break;501 502 echo '<div class="fae-icon-option ' . $is_selected . '" data-icon="' . esc_attr($file) . '" data-name="' . esc_attr($icon_name) . '">';503 echo '<div class="fae-icon-preview">';504 echo file_get_contents($path);505 echo '</div>';506 echo '<span class="fae-icon-label">' . esc_html($icon_name) . '</span>';507 echo '</div>';508 $displayed++;509 }510 ?>511 </div>512 </div>513 </div>514 </div>515 </div>516 517 <div class="fae-setting-group">518 <label for="fae_cursor_size">Icon Size</label>519 <select name="fae_cursor_options[size]">520 <option value="1rem" <?php selected($selected_size, '1rem'); ?>>Small</option>521 <option value="1.5rem" <?php selected($selected_size, '1.5rem'); ?>>Medium</option>522 <option value="2rem" <?php selected($selected_size, '2rem'); ?>>Large</option>523 <option value="2.5rem" <?php selected($selected_size, '2.5rem'); ?>>Extra Large</option>524 </select>525 </div>526 </div>527 </div>528 529 </div>530 531 <!-- Action Buttons -->532 <div class="fae-action-buttons">533 <button type="submit" class="fae-btn fae-btn-primary">534 <svg class="fae-icon" viewBox="0 0 512 512" style="width: 16px; height: 16px;">535 <path d="M173.898,439.404l-166.4-166.4c-9.997-9.997-9.997-26.206,0-36.204l36.203-36.204c9.997-9.998,26.207-9.998,36.204,0L192,312.69,432.095,72.596c9.997-9.997,26.207-9.997,36.204,0l36.203,36.204c9.997,9.997,9.997,26.206,0,36.204l-294.4,294.401C383.105,449.401,366.896,449.401,356.898,439.404z"/>536 </svg>537 Save Settings538 </button>539 540 541 <button type="button" class="fae-btn fae-btn-secondary fae-reset-settings">542 <svg class="fae-icon" viewBox="0 0 512 512" style="width: 16px; height: 16px;">543 <path d="M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48Zm0,384c-97,0-176-79-176-176S159,80,256,80s176,79,176,176S353,432,256,432Z"/>544 </svg>545 Reset to Defaults546 </button>547 </div>548 </form>549 </div>550 </div>551 </div>552 <?php553 }554 555 // Font Awesome dependency removed - using embedded Ionicons SVGs556 557 function fae_cursor_custom_admin_footer($footer_text) {558 $screen = get_current_screen();559 560 if ($screen->id === 'toplevel_page_fae_cursor') {561 return '<p>' . __('FaeCursor v1.1', 'faecursor') . '</p><br>' . $footer_text; // Updated to match text domain562 }563 return $footer_text;564 }565 add_filter('admin_footer_text', 'fae_cursor_custom_admin_footer');566 567 function fae_cursor_save_settings_notice() {568 // Verify nonce and check if settings were updated569 if (570 isset($_GET['settings-updated']) &&571 '1' === sanitize_text_field(wp_unslash($_GET['settings-updated'])) &&572 isset($_GET['_wpnonce']) &&573 wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'update-options')574 ) {575 ?>576 <div class="notice notice-success is-dismissible">577 <p>578 <?php esc_html_e('Settings saved! ', 'faecursor'); ?>579 <a href="<?php echo esc_url(home_url()); ?>" target="_blank">580 <?php esc_html_e('Visit your site\'s frontend', 'faecursor'); ?>581 </a>582 <?php esc_html_e(' to see the mouse effects in action.', 'faecursor'); ?>583 </p>584 </div>585 <?php586 }587 }588 589 590 add_action('admin_notices', 'fae_cursor_save_settings_notice');
Note: See TracChangeset
for help on using the changeset viewer.