Forums

The great place to discuss topics with other users

change your site appearance

'
Join the Conversation Post Reply
Edy Lee
Admin
Joined: 2024-11-24 00:57:42
2025-12-17 20:57:20

This code can be further developed, including integrating your own database and more. You can also place it anywhere you like, such as in _header.tpl.

{if $user->_logged_in}

{literal}
<style>
:root {
  --unitymix-accent:#30ce7d;
  --unitymix-font:"Inter",sans-serif;
  --unitymix-font-size:14px;
}

/* Global */
html, body {
  font-family: var(--unitymix-font) !important;
  font-size: var(--unitymix-font-size) !important;
  scroll-behavior: smooth;
}

/* Scrollbar */
body::-webkit-scrollbar { width:12px; height:12px; background:#e1e1e1; }
body::-webkit-scrollbar-thumb { background: var(--unitymix-accent); border-radius:6px; }

/* Panel */
#personalizationPanel { position: relative; display: inline-block; z-index: 9999; }
#appearanceBtn { padding: 8px 12px; border:1px solid #ccc; border-radius:8px; background:#fff; cursor:pointer; font-weight:500; }
#appearanceDropdown { display:none; position:absolute; top:100%; left:0; min-width:300px; max-height:400px; overflow-y:auto; background:#fff; border:1px solid #ccc; border-radius:12px; padding:12px; box-shadow:0 8px 20px rgba(0,0,0,.15); z-index:9999; }

/* Accessibility classes */
body.contrast-plus {
  background-color:#fff !important;
  color:#000 !important;
}
body.contrast-plus a,
body.contrast-plus h1,h2,h3,h4,h5,h6,
body.contrast-plus button,
body.contrast-plus input,
body.contrast-plus select,
body.contrast-plus textarea {
  color:#000 !important;
  background-color:#fff !important;
  border-color:#000 !important;
}

body.smart-contrast p, 
body.smart-contrast span, 
body.smart-contrast li, 
body.smart-contrast h1,h2,h3,h4,h5,h6, 
body.smart-contrast a, 
body.smart-contrast button {
    color: #000 !important; 
    background-color: #fff !important;
}

body.text-spacing { letter-spacing:.06em; word-spacing:.16em; line-height:1.8; }
body.reduce-motion *, body.reduce-motion *::before, body.reduce-motion *::after { animation:none !important; transition:none !important; }

/* High-specificity focus outlines */
body.focus-outline a:focus,
body.focus-outline button:focus,
body.focus-outline input:focus,
body.focus-outline select:focus,
body.focus-outline textarea:focus,
body.focus-outline [tabindex]:focus {
    outline: 3px solid var(--unitymix-accent) !important;
    outline-offset: 2px !important;
}

/* Big cursor */
body.big-cursor, body.big-cursor * { cursor: pointer !important; }

/* Tooltips */
.tooltip-enhanced .tooltip-element:hover::after,
.tooltip-enhanced .tooltip-element:focus::after {
  content: attr(data-tooltip);
  position: absolute;
  top:-28px; left:0;
  background:#333; color:#fff; padding:2px 6px; font-size:11px; border-radius:4px; white-space:nowrap; z-index:9999;
}

/* Voice overlay */
#voiceOverlay { position:fixed; bottom:10px; right:10px; background:#222; color:#fff; padding:8px 12px; border-radius:8px; box-shadow:0 2px 8px rgba(0,0,0,.3); font-size:12px; font-family:var(--unitymix-font); display:none; z-index:10000; }

/* Collapsible details summary */
details summary { cursor:pointer; font-weight:500; }
</style>
{/literal}

<div id="personalizationPanel" aria-label="Appearance and Accessibility Settings">
  <div id="appearanceBtn" role="button" aria-haspopup="true" aria-expanded="false">Appearance ▾</div>

  <div id="appearanceDropdown" role="menu">

    <!-- Accent Color -->
    <label for="colorPicker">Accent Color</label>
    <div class="color-presets" id="presetColors" role="group" aria-label="Color presets"></div>
    <input type="color" id="colorPicker" value="#30ce7d" aria-label="Pick accent color">

    <!-- Font & Size -->
    <label for="fontSelect">Font</label>
    <select id="fontSelect" aria-label="Select font">
      <option value="theme-default">Theme Default</option>
      <option value="Inter, sans-serif">Inter</option>
      <option value="Arial, sans-serif">Arial</option>
      <option value="Verdana, sans-serif">Verdana</option>
      <option value="Roboto, sans-serif">Roboto</option>
      <option value="Times New Roman, serif">Times New Roman</option>
    </select>

    <label for="fontSizeRange">Font Size</label>
    <input type="range" id="fontSizeRange" min="12" max="20" value="14" aria-label="Font size slider">

    <!-- Accessibility Options -->
    <details style="margin-bottom:12px;">
      <summary>Accessibility Options ▸</summary>
      <div style="display:flex;flex-direction:column;gap:6px;margin-top:6px;">
        <label><input type="checkbox" id="contrastPlusToggle"> Contrast+</label>
        <label><input type="checkbox" id="smartContrastToggle"> Smart Contrast</label>
        <label><input type="checkbox" id="spacingToggle"> Extra text spacing</label>
        <label><input type="checkbox" id="motionToggle"> Pause animations</label>
        <label><input type="checkbox" id="focusToggle"> Focus outlines</label>
        <label><input type="checkbox" id="cursorToggle"> Big cursor</label>
        <label><input type="checkbox" id="tooltipToggle"> Enhanced tooltips</label>
        <label><input type="checkbox" id="voiceNavToggle"> Voice navigation</label>
      </div>
    </details>

    <!-- Dyslexia-friendly Font -->
    <label for="dysFont">Dyslexia-friendly Font</label>
    <select id="dysFont" aria-label="Dyslexia friendly font">
      <option value="">Off</option>
      <option value="Arial, sans-serif">Arial</option>
      <option value="Verdana, sans-serif">Verdana</option>
    </select>

    <hr style="margin:12px 0;">
    <button id="resetAccessibility" style="width:100%;padding:8px;border-radius:8px;border:1px solid #ccc;background:#f5f5f5;font-weight:500;cursor:pointer;">
      Reset All Accessibility Settings
    </button>

  </div>
</div>

<div id="voiceOverlay">Voice commands: scroll up/down, top, bottom, open appearance</div>

{literal}
<script>
document.addEventListener('DOMContentLoaded',()=>{
const root=document.documentElement;
const body=document.body;
const btn=document.getElementById('appearanceBtn');
const drop=document.getElementById('appearanceDropdown');
const colorPicker=document.getElementById('colorPicker');
const presetBox=document.getElementById('presetColors');
const fontSelect=document.getElementById('fontSelect');
const fontSizeRange=document.getElementById('fontSizeRange');
const contrastPlus=document.getElementById('contrastPlusToggle');
const smartContrast=document.getElementById('smartContrastToggle');
const spacing=document.getElementById('spacingToggle');
const motion=document.getElementById('motionToggle');
const focus=document.getElementById('focusToggle');
const cursor=document.getElementById('cursorToggle');
const tooltip=document.getElementById('tooltipToggle');
const voiceNav=document.getElementById('voiceNavToggle');
const dysFont=document.getElementById('dysFont');
const overlay=document.getElementById('voiceOverlay');
const resetBtn=document.getElementById('resetAccessibility');

// Color presets
const presets=["#30ce7d","#67d69f","#24a85f","#ff5a5f","#ffcc00","#0077ff","#9b59b6","#e67e22","#1abc9c","#f39c12"];
presets.forEach(c=>{
  const d=document.createElement('div');
  d.style.background=c;
  d.addEventListener('click',()=>setColor(c,d));
  presetBox.appendChild(d);
});
function setColor(c,el){
  root.style.setProperty('--unitymix-accent',c);
  root.style.setProperty('--scrollbar-thumb-color',c);
  colorPicker.value=c;
  localStorage.setItem('unitymixAccent',c);
  document.querySelectorAll('.color-presets div').forEach(d=>d.classList.remove('active'));
  if(el) el.classList.add('active');
}

// Toggle class with localStorage
function toggleClass(cls,key,el){ body.classList.toggle(cls,el.checked); localStorage.setItem(key,el.checked?'1':''); }

// Smart Contrast safe
function applySmartContrast(enable){
  if(enable) body.classList.add('smart-contrast');
  else body.classList.remove('smart-contrast');
}

// Pause animations
function toggleMotion(enable){
  document.querySelectorAll('*').forEach(el=>{
    el.style.animationPlayState=enable?'paused':'running';
    el.style.transition=enable?'none':'';
  });
}

// Tooltips
function enableTooltips(enable){
  document.querySelectorAll('[title],[aria-label]').forEach(el=>{
    if(enable){
      const text = el.getAttribute('title') || el.getAttribute('aria-label');
      if(text){ el.dataset.tooltip=text; el.classList.add('tooltip-element'); }
      body.classList.add('tooltip-enhanced');
    } else {
      el.removeAttribute('data-tooltip'); el.classList.remove('tooltip-element'); body.classList.remove('tooltip-enhanced');
    }
  });
}

// Voice navigation
let recognition=null;
function startVoiceNav(){
  if(!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)) return;
  const SpeechRecognition=window.SpeechRecognition||window.webkitSpeechRecognition;
  recognition=new SpeechRecognition();
  recognition.continuous=true; recognition.lang='en-US';
  recognition.onresult=e=>{
    const cmd=e.results[e.results.length-1][0].transcript.trim().toLowerCase();
    if(cmd.includes('scroll down')) window.scrollBy(0,200);
    if(cmd.includes('scroll up')) window.scrollBy(0,-200);
    if(cmd.includes('top')) window.scrollTo(0,0);
    if(cmd.includes('bottom')) window.scrollTo(0,document.body.scrollHeight);
    if(cmd.includes('open appearance')) drop.style.display='block';
  };
  recognition.start(); overlay.style.display='block';
}
function stopVoiceNav(){ if(recognition) recognition.stop(); overlay.style.display='none'; }

// Event listeners
colorPicker.oninput=e=>setColor(e.target.value,null);
fontSelect.onchange=e=>{ const v=e.target.value==='theme-default'?'Inter, sans-serif':e.target.value; root.style.setProperty('--unitymix-font',v); localStorage.setItem('unitymixFont',v); }
fontSizeRange.oninput=e=>{ root.style.setProperty('--unitymix-font-size',e.target.value+'px'); localStorage.setItem('unitymixFontSize',e.target.value); }
contrastPlus.onchange=e=>toggleClass('contrast-plus','a11yContrastPlus',contrastPlus);
smartContrast.onchange=e=>{ applySmartContrast(e.target.checked); localStorage.setItem('a11ySmartContrast',e.target.checked?'1':''); };
spacing.onchange=()=>toggleClass('text-spacing','a11ySpacing',spacing);
motion.onchange=e=>toggleMotion(e.target.checked);
focus.onchange=()=>toggleClass('focus-outline','a11yFocus',focus);
cursor.onchange=()=>toggleClass('big-cursor','a11yCursor',cursor);
tooltip.onchange=e=>enableTooltips(e.target.checked);
voiceNav.onchange=e=>{ if(e.target.checked){ startVoiceNav(); localStorage.setItem('a11yVoiceNav','1'); } else { stopVoiceNav(); localStorage.setItem('a11yVoiceNav',''); } };
dysFont.onchange=e=>{ const v=e.target.value||'Inter, sans-serif'; root.style.setProperty('--unitymix-font',v); localStorage.setItem('a11yDysFont',e.target.value); };

// Reset All
resetBtn.onclick=()=>{
  body.classList.remove('contrast-plus','smart-contrast','text-spacing','reduce-motion','focus-outline','big-cursor','tooltip-enhanced');
  root.style.setProperty('--unitymix-font','Inter, sans-serif'); fontSelect.value='theme-default';
  root.style.setProperty('--unitymix-font-size','14px'); fontSizeRange.value=14;
  setColor('#30ce7d', presetBox.querySelector('div'));
  [contrastPlus, smartContrast, spacing, motion, focus, cursor, tooltip, voiceNav].forEach(cb=>cb.checked=false);
  dysFont.value=''; stopVoiceNav();
  localStorage.clear();
};

// Load saved settings
setColor(localStorage.getItem('unitymixAccent')||'#30ce7d', presetBox.querySelector('div'));
fontSelect.value=localStorage.getItem('unitymixFont')||'theme-default';
root.style.setProperty('--unitymix-font',localStorage.getItem('unitymixFont')||'Inter, sans-serif');
fontSizeRange.value=localStorage.getItem('unitymixFontSize')||14;
root.style.setProperty('--unitymix-font-size',fontSizeRange.value+'px');
contrastPlus.checked=localStorage.getItem('a11yContrastPlus')==='1';
smartContrast.checked=localStorage.getItem('a11ySmartContrast')==='1';
spacing.checked=localStorage.getItem('a11ySpacing')==='1';
motion.checked=localStorage.getItem('a11yMotion')==='1';
focus.checked=localStorage.getItem('a11yFocus')==='1';
cursor.checked=localStorage.getItem('a11yCursor')==='1';
tooltip.checked=localStorage.getItem('a11yTooltip')==='1';
voiceNav.checked=localStorage.getItem('a11yVoiceNav')==='1';
dysFont.value=localStorage.getItem('a11yDysFont')||'';
if(contrastPlus.checked) body.classList.add('contrast-plus');
if(smartContrast.checked) applySmartContrast(true);
if(spacing.checked) body.classList.add('text-spacing');
if(focus.checked) body.classList.add('focus-outline');
if(cursor.checked) body.classList.add('big-cursor');
if(tooltip.checked) enableTooltips(true);
if(voiceNav.checked) startVoiceNav();
if(dysFont.value) root.style.setProperty('--unitymix-font',dysFont.value);

// Dropdown toggle
btn.onclick=e=>{ e.stopPropagation(); drop.style.display=drop.style.display==='block'?'none':'block'; };
drop.onclick=e=>e.stopPropagation();
document.onclick=()=>{ drop.style.display='none'; };
});
</script>
{/literal}

{/if}