HoboDev

Developer & SEO Tools Hub

',m:['HTML Enc','HTML Dec','JS Esc','JS Unesc','URL Enc','URL Dec']}, timestamp:{n:'HoboTimestamp',i:'fa-clock',c:'time',d:'Unix timestamp \u2194 date converter',p:'',m:['Now','To Date','To Unix']}, cron:{n:'HoboCron',i:'fa-calendar-check',c:'time',d:'Cron expression parser & next runs',p:'*/15 9-17 * * 1-5',m:['Parse']}, beautify:{n:'HoboBeautify',i:'fa-wand-magic-sparkles',c:'qual',d:'Beautify JS, CSS, HTML & JSON',p:'{"a":1,"b":[2,3],"c":{"d":true}}',m:['JSON','HTML','CSS']}, minify:{n:'HoboMinify',i:'fa-compress',c:'qual',d:'Minify JS, CSS, HTML & JSON',p:'{\n "name": "hobo",\n "version": 1\n}',m:['JSON','HTML','CSS']}, diff:{n:'HoboDiff',i:'fa-code-compare',c:'qual',d:'Side-by-side text diff checker',p:'Line one\nLine two\nLine three\nLine four',p2:'Line one\nLine 2 modified\nLine three\nLine five\nLine six',dual:1,m:['Compare']}, regex:{n:'HoboRegex',i:'fa-magnifying-glass',c:'qual',d:'Regex tester with match highlighting',p:'\\b[A-Z][a-z]+\\b',p2:'Hello World from HoboTools today',dual:1,m:['Test']}, slug:{n:'HoboSlug',i:'fa-link',c:'qual',d:'URL slug generator',p:'Hello World! This is a Test -- 2024',m:['Generate']}, lorem:{n:'HoboLorem',i:'fa-paragraph',c:'qual',d:'Lorem ipsum generator',p:'',m:['1 Para','3 Para','5 Para','10 Sent','50 Words']}, curl:{n:'HoboCurl',i:'fa-terminal',c:'http',d:'Parse curl \u2192 fetch, Python, Node.js',p:"curl -X POST https://api.example.com/users -H 'Content-Type: application/json' -H 'Authorization: Bearer tok123' -d '{\"name\":\"hobo\"}'",m:['To fetch','To Python','To Node','Parsed']}, webhook:{n:'HoboWebhook',i:'fa-satellite-dish',c:'http',d:'Webhook request inspector bin',p:'',m:['Create Bin']}, color:{n:'HoboColor',i:'fa-palette',c:'fe',d:'Color picker & HEX/RGB/HSL converter',p:'#a78bfa',m:['Convert']}, opengraph:{n:'HoboOpenGraph',i:'fa-share-nodes',c:'fe',d:'Open Graph & Twitter Card preview',p:'https://hobostreamer.com',m:['Preview']} }; const CATS={data:{l:'Data & Formats',i:'fa-database'},enc:{l:'Encoding & Crypto',i:'fa-lock'},time:{l:'Time & Scheduling',i:'fa-clock'},qual:{l:'Code Quality',i:'fa-wand-magic-sparkles'},http:{l:'HTTP & API',i:'fa-terminal'},fe:{l:'Frontend & SEO',i:'fa-palette'}}; const host=location.hostname,sub=host.replace(/\.hobo\.tools$/,''); let ACT=T[sub]?sub:'dev',MODE=''; document.addEventListener('DOMContentLoaded',()=>{ const tok=localStorage.getItem('hobo_token'); if(window.HoboNavbar)try{HoboNavbar.init({service:'hobodev',apiBase:'https://hobo.tools',token:tok})}catch(e){} ACT==='dev'?showHub():showTool(); }); function showHub(){ $('hub').style.display='';$('tool').style.display='none'; let h=''; for(const[cid,cat]of Object.entries(CATS)){ const tools=Object.entries(T).filter(([,t])=>t.c===cid); if(!tools.length)continue; h+='
'+cat.l+'
'; for(const[id,t]of tools) h+='
'+t.n+'
'+t.d+'
'; h+='
'; } $('hub').innerHTML=h; buildRel(); } function showTool(){ $('hub').style.display='none';$('tool').style.display=''; const t=T[ACT];if(!t)return showHub(); $('hi').innerHTML=''; $('ht').textContent=t.n;$('hd').textContent=t.d; document.title=t.n+' \u2014 Free Online '+t.d; let mh=''; if(t.m)t.m.forEach((m,i)=>{mh+=''}); $('modes').innerHTML=mh;MODE=t.m?t.m[0]:''; let ioh=''; if(t.dual)ioh+=''; $('io').innerHTML=ioh; let ah=''; ah+=''; ah+=''; ah+=''; $('acts').innerHTML=ah; buildRel(); const q=new URLSearchParams(location.search).get('q'); if(q){$('in1').value=q;run()} else if(['uuid','lorem','timestamp','color'].includes(ACT)){if(t.p)$('in1').value=t.p;run()} } function setMode(el){document.querySelectorAll('.mb').forEach(b=>b.classList.remove('on'));el.classList.add('on');MODE=el.dataset.m;run()} function buildRel(){ const cat=T[ACT]?.c;let h=''; for(const[id,t]of Object.entries(T)){if(id===ACT||t.hub||t.c!==cat)continue;h+=' '+t.n+''} if(['curl','webhook'].includes(ACT))h+=' Headers SSL'; if(ACT==='opengraph')h+=' HoboLogo'; $('rel').innerHTML=h; } // ═══════════════ PROCESSORS ═══════════════ async function run(){ const i1=$('in1')?.value||'',i2=$('in2')?.value||'',o=$('out'); try{const r=await P[ACT](i1,MODE,i2);if(r==null)return;o.innerHTML=typeof r==='object'?r.html:H(String(r))} catch(e){o.innerHTML='\u2717 '+H(e.message)+''} } const P={}; P.json=(s,m)=>{const o=JSON.parse(s);if(m==='Minify')return JSON.stringify(o);if(m==='Validate')return'\u2713 Valid JSON \u2014 '+(Array.isArray(o)?o.length+' items':typeof o==='object'?Object.keys(o).length+' keys':typeof o);return JSON.stringify(o,null,2)}; P.yaml=(s,m)=>{ if(m.includes('JSON\u2192')){const o=JSON.parse(s);return j2y(o,'')} const ln=s.split('\n').filter(l=>l.trim()&&!l.trim().startsWith('#')),r={}; for(const l of ln){const mt=l.match(/^(\s*)([^:]+):\s*(.*)$/);if(mt){let v=mt[3].trim();v=v===''||v==='~'||v==='null'?null:v==='true'?true:v==='false'?false:/^-?\d+(\.\d+)?$/.test(v)?Number(v):v.replace(/^["']|["']$/g,'');r[mt[2].trim()]=v}} return JSON.stringify(r,null,2); }; function j2y(o,ind){let r='';if(Array.isArray(o))o.forEach(v=>{r+=ind+'- '+(typeof v==='object'&&v?'\n'+j2y(v,ind+' '):v)+'\n'}); else if(typeof o==='object'&&o)for(const[k,v]of Object.entries(o))r+=typeof v==='object'&&v?ind+k+':\n'+j2y(v,ind+' '):ind+k+': '+v+'\n'; else r+=ind+o+'\n';return r} P.xml=(s,m)=>{ if(m==='Validate'){const d=new DOMParser().parseFromString(s,'text/xml');if(d.querySelector('parsererror'))throw new Error(d.querySelector('parsererror').textContent);return'\u2713 Valid XML'} if(m==='Minify')return s.replace(/>\s+<').replace(/\s{2,}/g,' ').trim(); let f='',ind=0;s.replace(/>\s*<').split(/(<[^>]+>)/g).filter(Boolean).forEach(n=>{if(n.match(/^<\//))ind--;f+=' '.repeat(Math.max(0,ind))+n.trim()+'\n';if(n.match(/^<[^\/!?]/)&&!n.match(/\/>/))ind++});return f.trim(); }; P.csv=(s,m)=>{ const rows=s.trim().split('\n').map(r=>{const c=[];let cur='',q=false;for(let i=0;i{const o={};hd.forEach((h,i)=>o[h]=r[i]||'');return o}),null,2)} return{html:'
'+rows.map((r,ri)=>''+r.map(c=>'<'+(ri?'td':'th')+' style="border:1px solid var(--brd);padding:6px 10px;text-align:left;'+(ri?'':'background:var(--bg3);font-weight:700')+'">'+H(c)+'').join('')+'').join('')+'
'}; }; P.sql=(s,m)=>{ if(m==='Compact')return s.replace(/\s+/g,' ').trim(); const kw=['SELECT','DISTINCT','FROM','WHERE','JOIN','LEFT JOIN','RIGHT JOIN','INNER JOIN','ON','AND','OR','ORDER BY','GROUP BY','HAVING','LIMIT','OFFSET','INSERT INTO','VALUES','UPDATE','SET','DELETE FROM','CREATE TABLE','ALTER TABLE','UNION','CASE','WHEN','THEN','ELSE','END']; let r=s;if(m==='Uppercase')kw.forEach(k=>{r=r.replace(new RegExp('\\b'+k.replace(/ /g,'\\s+')+'\\b','gi'),k)}); kw.slice(0,20).forEach(k=>{r=r.replace(new RegExp('(\\b)('+k.replace(/ /g,'\\s+')+')(\\b)','gi'),'\n$1$2$3')}); return r.replace(/^\n+/,'').replace(/\n{2,}/g,'\n'); }; P.markdown=(s,m)=>{ let h=s.replace(/^######\s(.+)/gm,'
$1
').replace(/^#####\s(.+)/gm,'
$1
').replace(/^####\s(.+)/gm,'

$1

') .replace(/^###\s(.+)/gm,'

$1

').replace(/^##\s(.+)/gm,'

$1

').replace(/^#\s(.+)/gm,'

$1

') .replace(/\*\*(.+?)\*\*/g,'$1').replace(/\*(.+?)\*/g,'$1') .replace(/`([^`]+)`/g,'$1') .replace(/^\- (.+)/gm,'
  • $1
  • ').replace(/^> (.+)/gm,'
    $1
    ') .replace(/\[([^\]]+)\]\(([^)]+)\)/g,'$1') .replace(/^---$/gm,'
    '); if(m==='To HTML')return h; return{html:'
    '+h+'
    '}; }; P.html=(s,m)=>{ if(m==='Entities')return s.replace(/[\u00A0-\u9999<>&'"]/g,c=>'&#'+c.charCodeAt(0)+';'); if(m==='Unescape'){const d=document.createElement('div');d.innerHTML=s;return d.textContent} if(m==='Minify')return s.replace(/\s+/g,' ').replace(/>\s+<').trim(); let f='',ind=0;s.replace(/>\s*<').split(/(<[^>]+>)/g).filter(Boolean).forEach(n=>{if(n.match(/^<\//))ind--;f+=' '.repeat(Math.max(0,ind))+n.trim()+'\n';if(n.match(/^<[^\/!?]/)&&!n.match(/\/>/))ind++});return f.trim(); }; P.base64=(s,m)=>m==='Decode'?decodeURIComponent(escape(atob(s.trim()))):btoa(unescape(encodeURIComponent(s))); P.url=(s,m)=>{if(m==='Encode')return encodeURIComponent(s);if(m==='Decode')return decodeURIComponent(s); try{const u=new URL(s);return'Protocol: '+u.protocol+'\nHost: '+u.host+'\nPath: '+u.pathname+'\nSearch: '+u.search+'\nHash: '+u.hash+'\nOrigin: '+u.origin}catch{return'Invalid URL \u2014 include protocol (https://)'}}; P.jwt=(s)=>{ const p=s.trim().split('.');if(p.length!==3)throw new Error('JWT must have 3 dot-separated parts'); const d=v=>JSON.parse(decodeURIComponent(escape(atob(v.replace(/-/g,'+').replace(/_/g,'/'))))); const hdr=d(p[0]),pay=d(p[1]);let ex=''; if(pay.exp){const dt=new Date(pay.exp*1000),left=dt-Date.now();ex=left>0?'\n\u23f1 Expires: '+dt.toISOString()+' ('+Math.floor(left/36e5)+'h '+Math.floor(left%36e5/6e4)+'m left)':'\n\u26a0 EXPIRED: '+dt.toISOString()} return{html:'
    Header
    '+H(JSON.stringify(hdr,null,2))+'
    Payload
    '+H(JSON.stringify(pay,null,2))+(ex?'
    '+H(ex)+'
    ':'')+'
    Signature
    '+H(p[2])+'
    '}; }; P.uuid=(s,m)=>{ if(m==='Validate')return/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(s.trim())?'\u2713 Valid UUID v4':'\u2717 Invalid UUID v4'; const n=m.includes('100')?100:m.includes('10')?10:1;return Array.from({length:n},()=>crypto.randomUUID()).join('\n'); }; P.hash=async(s,m)=>{const a=m==='SHA-1'?'SHA-1':m==='SHA-512'?'SHA-512':'SHA-256'; const buf=await crypto.subtle.digest(a,new TextEncoder().encode(s));return a+': '+Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,'0')).join('')}; P.hex=(s,m)=>{if(m==='From Hex')return s.trim().replace(/\s+/g,'').match(/.{2}/g).map(h=>String.fromCharCode(parseInt(h,16))).join(''); return[...s].map(c=>c.charCodeAt(0).toString(16).padStart(2,'0')).join(' ')}; P.escape=(s,m)=>{ if(m==='HTML Enc')return s.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); if(m==='HTML Dec'){const d=document.createElement('div');d.innerHTML=s;return d.textContent} if(m==='JS Esc')return s.replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/\n/g,'\\n').replace(/\r/g,'\\r').replace(/\t/g,'\\t'); if(m==='JS Unesc')return s.replace(/\\n/g,'\n').replace(/\\r/g,'\r').replace(/\\t/g,'\t').replace(/\\"/g,'"').replace(/\\'/g,"'").replace(/\\\\/g,'\\'); if(m==='URL Enc')return encodeURIComponent(s);return decodeURIComponent(s); }; P.timestamp=(s,m)=>{ if(m==='Now'){const n=Date.now();return'Unix (s): '+Math.floor(n/1000)+'\nUnix (ms): '+n+'\nISO 8601: '+new Date(n).toISOString()+'\nLocal: '+new Date(n).toLocaleString()+'\nUTC: '+new Date(n).toUTCString()} if(m==='To Date'){const v=Number(s.trim()),d=new Date(v<1e12?v*1000:v);if(isNaN(d.getTime()))throw new Error('Invalid timestamp');return'ISO 8601: '+d.toISOString()+'\nLocal: '+d.toLocaleString()+'\nUTC: '+d.toUTCString()+'\nUnix (s): '+Math.floor(d/1000)+'\nUnix (ms): '+Number(d)} const d=new Date(s.trim());if(isNaN(d.getTime()))throw new Error('Invalid date string');return'Unix (s): '+Math.floor(d/1000)+'\nUnix (ms): '+Number(d)+'\nISO 8601: '+d.toISOString()+'\nUTC: '+d.toUTCString(); }; P.cron=(s)=>{ const p=s.trim().split(/\s+/);if(p.length<5)throw new Error('Need 5 fields: minute hour day month weekday'); const N=[['minute',0,59],['hour',0,23],['day',1,31],['month',1,12],['weekday',0,6]]; function desc(v,i){if(v==='*')return'every '+N[i][0];if(v.includes('/'))return'every '+v.split('/')[1]+' '+N[i][0]+'s';return N[i][0]+' '+v} function expand(v,mn,mx){if(v==='*')return Array.from({length:mx-mn+1},(_,i)=>i+mn);if(v.includes('/')){const[b,st]=v.split('/');const start=b==='*'?mn:+b;const r=[];for(let i=start;i<=mx;i+=+st)r.push(i);return r} if(v.includes(','))return v.split(',').map(Number);if(v.includes('-')){const[a,b]=v.split('-').map(Number);return Array.from({length:b-a+1},(_,i)=>i+a)}return[+v]} const mins=expand(p[0],0,59),hrs=expand(p[1],0,23),doms=expand(p[2],1,31),mos=expand(p[3],1,12),dws=expand(p[4],0,6); const nx=[];const d=new Date();d.setSeconds(0,0);d.setMinutes(d.getMinutes()+1); for(let i=0;i<525600&&nx.length<5;i++){if(mins.includes(d.getMinutes())&&hrs.includes(d.getHours())&&doms.includes(d.getDate())&&mos.includes(d.getMonth()+1)&&dws.includes(d.getDay()))nx.push(new Date(d));d.setMinutes(d.getMinutes()+1)} return'Schedule: '+p.map((v,i)=>desc(v,i)).join(', ')+'\n\nNext 5 runs:\n'+nx.map((d,i)=>' '+(i+1)+'. '+d.toLocaleString()).join('\n'); }; P.beautify=(s,m)=>{ if(m==='JSON')return JSON.stringify(JSON.parse(s),null,2); if(m==='CSS'){let r='',ind=0;for(const c of s){if(c==='{'){r+=' {\n';ind++;r+=' '.repeat(ind)}else if(c==='}'){ind--;r+='\n'+' '.repeat(ind)+'}\n'+' '.repeat(ind)}else if(c===';')r+=';\n'+' '.repeat(ind);else r+=c}return r.trim()} let f='',ind=0;s.replace(/>\s*<').split(/(<[^>]+>)/g).filter(Boolean).forEach(n=>{if(n.match(/^<\//))ind--;f+=' '.repeat(Math.max(0,ind))+n.trim()+'\n';if(n.match(/^<[^\/!?]/)&&!n.match(/\/>/))ind++});return f.trim(); }; P.minify=(s,m)=>{if(m==='JSON')return JSON.stringify(JSON.parse(s));if(m==='CSS')return s.replace(/\/\*[\s\S]*?\*\//g,'').replace(/\s+/g,' ').replace(/\s*([{}:;,])\s*/g,'$1').trim(); if(m==='HTML')return s.replace(/\s+/g,' ').replace(/>\s+<').trim();return s.replace(/\/\/.*$/gm,'').replace(/\/\*[\s\S]*?\*\//g,'').replace(/\s+/g,' ').trim()}; P.diff=(a,m,b)=>{ const al=a.split('\n'),bl=b.split('\n'),r=[];let ai=0,bi=0; while(ai=al.length||bl.slice(bi,bi+4).includes(al[ai])===false)){r.push({t:'a',n:bi+1,l:bl[bi]});bi++} else{r.push({t:'d',n:ai+1,l:al[ai]});ai++} } const st={a:r.filter(x=>x.t==='a').length,d:r.filter(x=>x.t==='d').length,c:r.filter(x=>x.t==='c').length}; return{html:'
    +'+st.a+' added \u00b7 -'+st.d+' removed \u00b7 '+st.c+' unchanged
    '+r.map(x=>'
    '+x.n+''+H(x.l)+'
    ').join('')}; }; P.regex=(pat,m,test)=>{ if(!pat)throw new Error('Enter a regex pattern'); const re=new RegExp(pat,'gm');let match,matches=[],last=0,html=''; while((match=re.exec(test))!==null){if(match.index>last)html+=H(test.slice(last,match.index));html+=''+H(match[0])+'';matches.push({m:match[0],i:match.index,g:match.slice(1)});last=match.index+match[0].length;if(!match[0].length){re.lastIndex++;if(re.lastIndex>test.length)break}} if(last
    '+(html||H(test))+'
    '; if(matches.some(x=>x.g.length)){info+='
    Groups:
    ';matches.forEach((x,i)=>{if(x.g.length)info+='
    #'+(i+1)+': '+x.g.map((g,j)=>'$'+(j+1)+'='+H(g||'')).join(', ')+'
    '})} return{html:info}; }; P.slug=s=>s.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,''); P.lorem=(s,m)=>{ const W='lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua enim ad minim veniam quis nostrud exercitation ullamco laboris nisi aliquip ex ea commodo consequat duis aute irure in reprehenderit voluptate velit esse cillum fugiat nulla pariatur excepteur sint occaecat cupidatat non proident sunt culpa qui officia deserunt mollit anim id est laborum'.split(' '); const rw=()=>W[Math.random()*W.length|0]; const sent=()=>{const n=8+(Math.random()*10|0);let s=Array.from({length:n},rw).join(' ');return s[0].toUpperCase()+s.slice(1)+'.'}; const para=()=>Array.from({length:4+(Math.random()*4|0)},sent).join(' '); if(m.includes('Words'))return Array.from({length:50},rw).join(' '); if(m.includes('Sent'))return Array.from({length:10},sent).join(' '); return Array.from({length:parseInt(m)||3},para).join('\n\n'); }; P.curl=(s,m)=>{ const cmd=s.trim().replace(/\\\n/g,' ');let method='GET',url='',hd={},body=''; const tok=[];let cur='',inQ=false,qc=''; for(let i=0;i0)hd[h.slice(0,ci).trim()]=h.slice(ci+1).trim()} else if(t==='-d'||t==='--data'||t==='--data-raw')body=tok[++i];else if(!t.startsWith('-')&&t!=='curl')url=t} if(body&&method==='GET')method='POST'; if(m==='Parsed')return'Method: '+method+'\nURL: '+url+'\nHeaders:\n'+Object.entries(hd).map(([k,v])=>' '+k+': '+v).join('\n')+'\nBody: '+(body||'(none)'); if(m==='To fetch')return"fetch('"+url+"', {\n method: '"+method+"',\n headers: "+JSON.stringify(hd,null,4).replace(/\n/g,'\n ')+','+(body?"\n body: '"+body+"',":'')+'\n});'; if(m==='To Python')return"import requests\n\nresponse = requests."+method.toLowerCase()+"(\n '"+url+"',\n headers="+JSON.stringify(hd)+","+(body?"\n data='"+body+"',":'')+"\n)\nprint(response.json())"; return"const https = require('https');\n\nconst req = https.request('"+url+"', {\n method: '"+method+"',\n headers: "+JSON.stringify(hd,null,4).replace(/\n/g,'\n ')+"\n}, res => {\n let data = '';\n res.on('data', c => data += c);\n res.on('end', () => console.log(data));\n});\n"+(body?"req.write('"+body+"');\n":"")+"req.end();"; }; P.webhook=async()=>{ const o=$('out'); const r=await fetch(API+'/webhook/bins',{method:'POST'});const d=await r.json();if(!d.ok)throw new Error(d.error||'Failed'); const binUrl=API+'/webhook/bins/'+d.binId+'/in'; o.innerHTML='
    Your Webhook URL
    "+binUrl+'
    Click to copy \u00b7 Polling for requests...
    Waiting for requests...
    '; const iv=setInterval(async()=>{try{const r2=await fetch(API+'/webhook/bins/'+d.binId);const d2=await r2.json(); if(d2.ok&&d2.requests.length){$('whr').innerHTML=d2.requests.map((rq,i)=>'
    #'+(i+1)+' '+rq.method+' \u00b7 '+new Date(rq.timestamp).toLocaleTimeString()+'
    Headers:\n'+Object.entries(rq.headers||{}).map(([k,v])=>k+': '+v).join('\n')+'\n\nBody:\n'+H(typeof rq.body==='object'?JSON.stringify(rq.body,null,2):String(rq.body||'(empty)'))+'
    ').join('')}}catch{}},2000); setTimeout(()=>clearInterval(iv),3600000);return null; }; P.color=(s)=>{ let r,g,b;const v=s.trim(); if(v.startsWith('#')){const hx=v.slice(1);r=parseInt(hx.slice(0,2),16);g=parseInt(hx.slice(2,4),16);b=parseInt(hx.slice(4,6),16)} else if(v.startsWith('rgb')){[r,g,b]=v.match(/\d+/g).map(Number)} else throw new Error('Enter HEX (#ff0000) or RGB (rgb(255,0,0))'); if([r,g,b].some(x=>isNaN(x)))throw new Error('Invalid color value'); const mx=Math.max(r,g,b)/255,mn=Math.min(r,g,b)/255,d=mx-mn;let h=0,s2=0,l=(mx+mn)/2; if(d){s2=l>.5?d/(2-mx-mn):d/(mx+mn);if(mx===r/255)h=((g/255-b/255)/d+(gc.toString(16).padStart(2,'0')).join(''); const hsl='hsl('+Math.round(h)+', '+Math.round(s2*100)+'%, '+Math.round(l*100)+'%)'; const comp='#'+[255-r,255-g,255-b].map(c=>c.toString(16).padStart(2,'0')).join(''); return{html:'
    HEX: '+hex+'\nRGB: rgb('+r+', '+g+', '+b+')\nHSL: '+hsl+'\nComplement: '+comp+'
    CSS:\n --color: '+hex+';\n --color-rgb: '+r+', '+g+', '+b+';
    '}; }; P.opengraph=async(s)=>{ const r=await fetch(API+'/opengraph?url='+encodeURIComponent(s.trim()));const d=await r.json();if(!d.ok)throw new Error(d.error||'Failed to fetch'); const og=d.tags||{};let u;try{u=new URL(s.trim()).hostname}catch{u=''} return{html:'
    '+(og['og:image']?'':'')+'
    '+H(og['og:site_name']||u)+'
    '+H(og['og:title']||og.title||'No title')+'
    '+H(og['og:description']||og.description||'No description')+'
    ' +'
    All Meta Tags
    '+Object.entries(og).map(([k,v])=>'
    '+H(k)+': '+H(String(v))+'
    ').join('')+'
    ' +(d.seo?'
    SEO Recommendations
    '+d.seo.map(x=>'
    '+(x.type==='error'?'\u2717':x.type==='warning'?'\u26a0':'\u2713')+' '+H(x.message)+'
    ').join('')+'
    ':'')}; }; // ═══════════════ UTILITY ═══════════════ function copyOut(){navigator.clipboard.writeText(($('out').innerText||'')).then(()=>toast('Copied!')).catch(()=>{})} function clr(){const a=$('in1');if(a)a.value='';const b=$('in2');if(b)b.value='';$('out').innerHTML=''} function sample(){const t=T[ACT];if(t?.p){$('in1').value=t.p;if(t.p2&&$('in2'))$('in2').value=t.p2}run()} function toast(msg){const t=$('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2000)}