Kutumb

Your translation assistant

Hi! How can I help you translate today?
10:20 AM
Good morning, how are you?
10:21 AM  ✓✓
Good morning! I am doing well, thank you. How can I assist you today?
10:21 AM
Grocery
🛒 Grocery
Fresh groceries delivered to your door daily
Market
🏪 Market
Browse local markets and vendors near you
Labour
👷 Labour
Find skilled workers for any job, anytime
Plumber / Electrician
🔧 Plumber / Electrician
Certified plumbers and electricians on call
House in Rent
🏠 House in Rent
Find verified rental homes in your area
Property Sale
🏷️ Property Sale
List or discover properties for sale nearby
Luxury Rent
💎 Luxury Rent
Premium villas, penthouses & more

Notifications

You have 3 new updates

Ad Performance
This Week
0 100 200 300
Mon
Tue
Wed
Thu
Fri
Sat
Sun

Explore Map

Discover places and services around you

Grocery

Fresh groceries delivered to your door

All
🥬 Vegetables
🍎 Fruits
🥛 Dairy
🌾 Grains
🧴 Essentials
🥦
Fresh Vegetables
Locally sourced, farm-fresh picks
₹30 delivery
🍅
Seasonal Fruits
Hand-picked, ripe & ready
₹30 delivery
🥛
Dairy & Eggs
Daily fresh milk, curd, paneer
Free delivery
🌾
Grains & Pulses
Rice, dal, atta & more
Bulk offers
🧴
Household Essentials
Oil, soap, spices & staples
₹30 delivery
🫙
Pickles & Preserves
Homemade & artisan jars
Local sellers

Market

Browse local vendors & markets near you

All
🏪 General Stores
🧵 Textile
🍱 Food Stalls
🪴 Nursery
🔩 Hardware
🏪
Mandai Bazaar
General goods, 0.8 km away
Open Now
🧵
Kapda Market
Fabrics & garments, 1.2 km
Open Now
🍱
Street Food Lane
Local snacks & tiffin stalls
5 stalls open
🪴
Plant Nursery Row
Indoor & outdoor plants
Weekend only
🔩
Hardware Galli
Tools, pipes & fittings
Open Now
📦
Wholesale Hub
Bulk buying at trade prices
By appointment

Labour

Find skilled workers for any job, anytime

All
🧱 Mason
🪚 Carpenter
🎨 Painter
🏗️ Helper
🌿 Gardener
🧱
Mason / Raj
Brick, plaster & tile work experts
12 available
🪚
Carpenter
Furniture, doors & wood fitting
8 available
🎨
Painter
Interior & exterior painting
5 available
🏗️
General Helper
Loading, cleaning & daily tasks
Daily hire
🌿
Gardener
Garden maintenance & landscaping
Weekly plans
🧹
House Cleaner
Deep cleaning & daily help
Verified

Plumber / Electrician

Certified professionals on call

All
🚿 Plumber
⚡ Electrician
❄️ AC Service
🔥 Gas/Pipe
🚿
Plumber
Leaks, pipes & bathroom fitting
Emergency call
Electrician
Wiring, switches & board work
Certified
❄️
AC Service
Installation, repair & gas refill
Same day
🔥
Gas / Pipeline
LPG line fitting & leak check
Licensed
📡
DTH / Internet
Cable, DTH & broadband setup
Quick visit
🔧
Appliance Repair
TV, fridge, washing machine & more
Warranty given

House in Rent

Find verified rental homes in your area

All
🛏️ 1 BHK
🏡 2 BHK
🏘️ 3 BHK
🏢 Flat
🏠 Bungalow
🛏️
1 BHK Flat
Savedi Road · Fully furnished
₹6,000/mo
🏡
2 BHK Flat
Station area · Semi-furnished
₹9,500/mo
🏘️
3 BHK Row House
Cidco Colony · With parking
₹14,000/mo
🏢
Studio Flat
Near college · For bachelors
₹4,500/mo
🏠
Independent House
Kranti Chowk · 4 rooms
₹18,000/mo
🏗️
PG / Paying Guest
Meals included · All genders
₹3,000/mo

Property Sale

List or discover properties for sale nearby

All
🏢 Flat
🏠 House
📐 Plot
🏬 Commercial
🌾 Farm Land
🏢
2 BHK Flat
Pundlik Nagar · 850 sq ft
₹28 Lakh
📐
Residential Plot
MIDC Road · 1200 sq ft
₹15 Lakh
🏠
Independent House
Tilaknagar · 3 BHK
₹55 Lakh
🏬
Shop / Gala
Main market · Ground floor
₹22 Lakh
🌾
Farm Land
Near Shrirampur · 2 acres
₹40 Lakh
🏗️
Under Construction
New colony · Book now
Pre-launch

Luxury Rent

Premium villas, penthouses & exclusive stays

All
🏯 Villa
🏙️ Penthouse
🌅 Farmhouse
🏊 With Pool
🎪 Event Space
🏯
Luxury Villa
4 BHK · Private garden & parking
₹65,000/mo
🏙️
Penthouse Suite
Rooftop terrace · City view
₹80,000/mo
🌅
Farmhouse Stay
Serene outskirts · 5 acres
₹45,000/mo
🏊
Pool Villa
Private pool · Fully serviced
₹1.2L/mo
🎪
Event Farmhouse
Weddings & corporate events
On request
💎
Premium Bungalow
Gated society · 5 BHK
₹95,000/mo
Navigation
Dashboard
Marketplace
Properties
Explore Map
Notifications
My Profile
My Profile
K
Kartik Anarse
Founder, Kritzaar
Member since Jan 2024
12 Orders
4.8 Rating
3 Properties
Account Info
Full Name Kartik Anarse
Username @kartik
Email kartik@kartava.in
Phone +91 81338 51834
Location Ahilyanagar, Maharashtra
Appearance
Dark Themes
Gold
Default
Forest
Ocean
Royal
Bronze
Light Themes
Mint
Sky
Lavender
Amber
Silver
Colour Studio
#
Live Preview
Kartava
K
Kutumb
Hi! How can I help?
Good morning!
Type…
Grocery
Market
Labour
Ad Performance
Settings
Notifications
Dark Mode
Privacy & Security
Help & Support
About Kartava

Sign Out?

You'll need to log in again to access your Kartava account.

🌏
Your Community. Your World.
Skip for now

Notifications

Clear All
🛒
Order Ready for Pickup
Your grocery order from Mandai Bazaar is packed and ready.
2 minutes ago
🧱
Labour Booking Confirmed
Rajesh (Mason) confirmed for tomorrow 9 AM. Contact: 98765 43210
18 minutes ago
🏠
New Listing Near You
A 2 BHK flat at ₹9,000/mo was listed 0.4 km from your location.
1 hour ago
Electrician Visit Reminder
Suresh Electricians will visit your home at 3 PM today.
3 hours ago
💎
Luxury Villa Available
A 4 BHK villa with pool is now available at ₹65,000/mo in your saved area.
Yesterday
🏷️
Property Price Drop
Residential plot on MIDC Road dropped from ₹18L to ₹15L.
2 days ago

My Cart

🛒 Your cart is empty

Add items from Grocery, Market or any service panel

Book Service
🧱
Mason
Brick, plaster & tile work
{ emoji:'🏪', name:'Mandai Bazaar', cat:'Markets', dist:'0.8 km', x:28, y:35 }, { emoji:'🥦', name:'Veggie Corner', cat:'Grocery', dist:'0.3 km', x:55, y:22 }, { emoji:'🧵', name:'Kapda Market', cat:'Markets', dist:'1.2 km', x:72, y:45 }, { emoji:'🧱', name:'Rajesh (Mason)', cat:'Labour', dist:'0.6 km', x:38, y:65 }, { emoji:'⚡', name:'Suresh Electric', cat:'Services', dist:'1.1 km', x:62, y:70 }, { emoji:'🏠', name:'2 BHK - Savedi Rd', cat:'Rental', dist:'0.5 km', x:20, y:55 }, { emoji:'🌾', name:'Grain Depot', cat:'Grocery', dist:'1.4 km', x:80, y:28 }, { emoji:'🔧', name:'Fix-It Workshop', cat:'Services', dist:'0.9 km', x:45, y:48 }, { emoji:'🏡', name:'3 BHK Row House', cat:'Rental', dist:'1.8 km', x:15, y:30 }, { emoji:'🏪', name:'Hardware Galli', cat:'Markets', dist:'1.0 km', x:68, y:58 }, ]; let activeMapFilter = 'All'; function openMapModal(){ document.getElementById('mapModalOverlay').classList.add('open'); document.getElementById('mapModal').style.display='flex'; document.getElementById('mapModal').style.flexDirection='column'; document.body.style.overflow='hidden'; renderMap(); } function closeMapModal(){ document.getElementById('mapModalOverlay').classList.remove('open'); document.getElementById('mapModal').style.display='none'; document.body.style.overflow=''; } function setMapFilter(el){ document.querySelectorAll('.map-filter-pill').forEach(p=>p.classList.remove('active')); el.classList.add('active'); activeMapFilter = el.textContent.replace(/^[^\s]+\s/,'').trim(); if(el.textContent.includes('All')) activeMapFilter='All'; renderMap(); } function renderMap(){ const area = document.getElementById('mapSvgArea'); const list = document.getElementById('mapListRow'); if(!area) return; const filtered = activeMapFilter==='All' ? MAP_PLACES : MAP_PLACES.filter(p=>p.cat===activeMapFilter); // Build SVG map const cols = { Grocery:'#4caf7d', Markets:'#c9a96e', Labour:'#9b6dff', Services:'#4a9eff', Rental:'#ff7043' }; let pins = filtered.map(p=>{ const c = cols[p.cat] || 'var(--gold)'; return ` ${p.emoji} `; }).join(''); // Grid lines background area.innerHTML = ` ${pins}
You are here
`; // List row list.innerHTML = filtered.map(p=>`
${p.emoji}
${p.name}
${p.dist} away
`).join(''); } function showMapPlace(name){ showToast('📍 ' + name); } // ══════════════════════════════════════════ // ── TOAST ── // ══════════════════════════════════════════ let toastTimer; function showToast(msg){ const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(()=>t.classList.remove('show'), 2800); } // ══════════════════════════════════════════ // ── NAV ── // ══════════════════════════════════════════ let hamOpen = false; function setNav(el){ if(!el) return; document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active')); el.classList.add('active'); } function toggleHamburger(){ hamOpen ? closeHamburger() : openHamburger(); } function openHamburger(){ hamOpen=true; document.getElementById('hamIcon').classList.add('open'); document.getElementById('hamOverlay').classList.add('open'); document.getElementById('hamMenu').classList.add('open'); document.getElementById('hamburgerBtn').classList.add('active'); } function closeHamburger(){ hamOpen=false; document.getElementById('hamIcon').classList.remove('open'); document.getElementById('hamOverlay').classList.remove('open'); document.getElementById('hamMenu').classList.remove('open'); document.getElementById('hamburgerBtn').classList.remove('active'); } // ══════════════════════════════════════════ // ── SERVICE PANELS ── // ══════════════════════════════════════════ function _openPanel(overlayId, panelId){ document.getElementById(overlayId).classList.add('open'); document.getElementById(panelId).classList.add('open'); document.body.style.overflow='hidden'; } function _closePanel(overlayId, panelId){ document.getElementById(overlayId).classList.remove('open'); document.getElementById(panelId).classList.remove('open'); document.body.style.overflow=''; } function openGroceryPanel(){ _openPanel('groceryOverlay','groceryPanel'); } function closeGroceryPanel(){ _closePanel('groceryOverlay','groceryPanel'); } function openMarketPanel(){ _openPanel('marketOverlay','marketPanel'); } function closeMarketPanel(){ _closePanel('marketOverlay','marketPanel'); } function openLabourPanel(){ _openPanel('labourOverlay','labourPanel'); } function closeLabourPanel(){ _closePanel('labourOverlay','labourPanel'); } function openPlumberPanel(){ _openPanel('plumberOverlay','plumberPanel'); } function closePlumberPanel(){ _closePanel('plumberOverlay','plumberPanel'); } function openHouseRentPanel(){ _openPanel('houseRentOverlay','houseRentPanel'); } function closeHouseRentPanel(){ _closePanel('houseRentOverlay','houseRentPanel'); } function openPropertySalePanel(){ _openPanel('propertySaleOverlay','propertySalePanel'); } function closePropertySalePanel(){ _closePanel('propertySaleOverlay','propertySalePanel'); } function openLuxuryRentPanel(){ _openPanel('luxuryRentOverlay','luxuryRentPanel'); } function closeLuxuryRentPanel(){ _closePanel('luxuryRentOverlay','luxuryRentPanel'); } function setActiveCat(el){ const parent = el.closest('.svc-panel-cats'); parent.querySelectorAll('.cat-pill').forEach(p=>p.classList.remove('active')); el.classList.add('active'); } // ══════════════════════════════════════════ // ── LONG-PRESS TOOLTIP (lp-item) ── // ══════════════════════════════════════════ let lpTimer; document.addEventListener('DOMContentLoaded',()=>{ document.querySelectorAll('.lp-item').forEach(item=>{ item.addEventListener('mouseenter',()=>item.classList.add('pressing')); item.addEventListener('mouseleave',()=>item.classList.remove('pressing')); item.addEventListener('touchstart',()=>{ lpTimer=setTimeout(()=>item.classList.add('pressing'),300); },{passive:true}); item.addEventListener('touchend',()=>{ clearTimeout(lpTimer); setTimeout(()=>item.classList.remove('pressing'),400); }); }); }); // ══════════════════════════════════════════ // ── NOTIFICATION CARD click ── // ══════════════════════════════════════════ document.addEventListener('DOMContentLoaded',()=>{ const nc = document.querySelector('.notif-card'); if(nc) nc.addEventListener('click', openNotifPanel); }); // ══════════════════════════════════════════ // ── THEME SYSTEM ── // ══════════════════════════════════════════ const THEME_PRESETS = { 'dark-gold': { bg:'#0d1210', accent:'#c9a96e', card:'#131e16', text:'#ddd0ba', nav:'#0b110d' }, 'dark-green': { bg:'#060d08', accent:'#4caf7d', card:'#0a1a0d', text:'#c8e0cf', nav:'#040b06' }, 'dark-blue': { bg:'#060810', accent:'#4a9eff', card:'#0a0e1a', text:'#c8d4f0', nav:'#04060d' }, 'dark-purple': { bg:'#080610', accent:'#9b6dff', card:'#0e0a1c', text:'#d4c8f0', nav:'#060410' }, 'dark-bronze': { bg:'#0c0806', accent:'#cd7f32', card:'#181008', text:'#e8d4b8', nav:'#0e0904' }, 'light-green': { bg:'#f0f9f4', accent:'#287a52', card:'#ffffff', text:'#152d22', nav:'#f8fdf9' }, 'light-blue': { bg:'#f0f5ff', accent:'#1a6abf', card:'#ffffff', text:'#101e38', nav:'#f6faff' }, 'light-purple': { bg:'#f6f0ff', accent:'#7b3fc4', card:'#ffffff', text:'#1c1030', nav:'#faf6ff' }, 'light-orange': { bg:'#fff8ef', accent:'#c06a18', card:'#ffffff', text:'#251507', nav:'#fffcf6' }, 'light-silver': { bg:'#f4f6f8', accent:'#5a6878', card:'#ffffff', text:'#1a2030', nav:'#fafcfe' }, }; const CTP_STORE = { bg:'#050a07', accent:'#c9a96e', card:'#0d160f', text:'#ddd0ba', nav:'#060a07' }; const CTP_PROP_NAMES = { bg:'Background', accent:'Accent', card:'Card', text:'Text', nav:'Nav' }; let ctpActiveProp='bg', ctpH=0, ctpS=0, ctpB=0, ctpDragging=false, ctpHueDrag=false; function hexToRgb(hex){ const h=hex.replace('#',''); return {r:parseInt(h.slice(0,2),16),g:parseInt(h.slice(2,4),16),b:parseInt(h.slice(4,6),16)}; } function rgbToHex(r,g,b){ return '#'+[r,g,b].map(v=>Math.round(v).toString(16).padStart(2,'0')).join(''); } function hexToHsb(hex){ let {r,g,b}=hexToRgb(hex); r/=255;g/=255;b/=255; const max=Math.max(r,g,b),min=Math.min(r,g,b),d=max-min; let h=0,s=max===0?0:d/max,bv=max; if(d!==0){if(max===r)h=((g-b)/d)%6;else if(max===g)h=(b-r)/d+2;else h=(r-g)/d+4;h=((h*60)+360)%360;} return{h,s:s*100,b:bv*100}; } function hsbToHex(h,s,b){ s/=100;b/=100; const k=n=>(n+h/60)%6; const f=n=>b-b*s*Math.max(Math.min(k(n),4-k(n),1),0); return rgbToHex(f(5)*255,f(3)*255,f(1)*255); } function lightenHex(hex,amt){ let {r,g,b}=hexToRgb(hex); return rgbToHex(Math.min(255,r+amt),Math.min(255,g+amt),Math.min(255,b+amt)); } function darkenHex(hex,amt){ let {r,g,b}=hexToRgb(hex); return rgbToHex(Math.max(0,r-amt),Math.max(0,g-amt),Math.max(0,b-amt)); } function hexToRgbStr(hex){ let {r,g,b}=hexToRgb(hex); return `${r},${g},${b}`; } function ctpDrawSB(){ const canvas=document.getElementById('ctpCanvas'); if(!canvas)return; const ctx=canvas.getContext('2d'),W=canvas.width,H=canvas.height; ctx.fillStyle=`hsl(${ctpH},100%,50%)`; ctx.fillRect(0,0,W,H); const gW=ctx.createLinearGradient(0,0,W,0); gW.addColorStop(0,'rgba(255,255,255,1)'); gW.addColorStop(1,'rgba(255,255,255,0)'); ctx.fillStyle=gW; ctx.fillRect(0,0,W,H); const gB=ctx.createLinearGradient(0,0,0,H); gB.addColorStop(0,'rgba(0,0,0,0)'); gB.addColorStop(1,'rgba(0,0,0,1)'); ctx.fillStyle=gB; ctx.fillRect(0,0,W,H); } function ctpDrawHue(){ const canvas=document.getElementById('ctpHue'); if(!canvas)return; const ctx=canvas.getContext('2d'),W=canvas.width,H=canvas.height; const g=ctx.createLinearGradient(0,0,W,0); for(let i=0;i<=360;i+=10)g.addColorStop(i/360,`hsl(${i},100%,50%)`); ctx.fillStyle=g; ctx.fillRect(0,0,W,H); } function ctpPositionCursor(){ const wrap=document.getElementById('ctpCanvasWrap'),canvas=document.getElementById('ctpCanvas'),cursor=document.getElementById('ctpCursor'); if(!wrap||!cursor||!canvas)return; const rect=canvas.getBoundingClientRect(),x=(ctpS/100)*rect.width,y=(1-ctpB/100)*rect.height,wrapRect=wrap.getBoundingClientRect(); cursor.style.left=(rect.left-wrapRect.left+x)+'px'; cursor.style.top=(rect.top-wrapRect.top+y)+'px'; cursor.style.borderColor=ctpB>50?'rgba(255,255,255,0.9)':'rgba(200,200,200,0.7)'; } function ctpPositionHueThumb(){ const canvas=document.getElementById('ctpHue'),thumb=document.getElementById('ctpHueThumb'); if(!canvas||!thumb)return; const rect=canvas.getBoundingClientRect(),x=(ctpH/360)*rect.width,wrapRect=document.getElementById('ctpHue').parentElement.getBoundingClientRect(); thumb.style.left=(rect.left-wrapRect.left+x)+'px'; thumb.style.background=`hsl(${ctpH},100%,50%)`; } function ctpUpdatePreview(){ const hex=hsbToHex(ctpH,ctpS,ctpB),ps=document.getElementById('ctpPreviewSwatch'),hi=document.getElementById('ctpHexInput'); if(ps)ps.style.background=hex; if(hi)hi.value=hex.replace('#','').toUpperCase(); } function ctpRefreshAll(){ ctpDrawSB(); ctpDrawHue(); setTimeout(()=>{ctpPositionCursor();ctpPositionHueThumb();},0); ctpUpdatePreview(); } function ctpLoadProp(prop){ const hex=CTP_STORE[prop]||'#c9a96e',hsb=hexToHsb(hex); ctpH=hsb.h;ctpS=hsb.s;ctpB=hsb.b; ctpRefreshAll(); const cursor=document.getElementById('ctpCursor'); if(cursor)cursor.style.background=hex; } function ctpSelectProp(btn){ document.querySelectorAll('.ctp-tab').forEach(t=>t.classList.remove('active')); btn.classList.add('active'); ctpActiveProp=btn.dataset.prop; ctpLoadProp(ctpActiveProp); } function ctpCommit(){ const hex=hsbToHex(ctpH,ctpS,ctpB); CTP_STORE[ctpActiveProp]=hex; ctpUpdateTabDot(ctpActiveProp,hex); ctpUpdateToggleDot(ctpActiveProp,hex); applyCustom(); showToast('✓ '+CTP_PROP_NAMES[ctpActiveProp]+' colour applied'); } function ctpUpdateTabDot(prop,hex){ const ids={bg:'tdBg',accent:'tdAccent',card:'tdCard',text:'tdText',nav:'tdNav'},el=document.getElementById(ids[prop]); if(el)el.style.background=hex; } function ctpUpdateToggleDot(prop,hex){ const ids={bg:'cttDotBg',accent:'cttDotAccent',card:'cttDotCard',text:'cttDotText',nav:'cttDotNav'},el=document.getElementById(ids[prop]); if(el)el.style.background=hex; } function ctpRefreshAllDots(){ Object.keys(CTP_STORE).forEach(prop=>{ctpUpdateTabDot(prop,CTP_STORE[prop]);ctpUpdateToggleDot(prop,CTP_STORE[prop]);}); } function ctpHexInput(val){ val=val.replace(/[^0-9a-fA-F]/g,''); if(val.length===6){const hsb=hexToHsb('#'+val);ctpH=hsb.h;ctpS=hsb.s;ctpB=hsb.b;ctpDrawSB();setTimeout(()=>{ctpPositionCursor();ctpPositionHueThumb();},0);const ps=document.getElementById('ctpPreviewSwatch');if(ps)ps.style.background='#'+val;ctpUpdateTabDot(ctpActiveProp,'#'+val);CTP_STORE[ctpActiveProp]='#'+val;applyCustom();} } function ctpSBFromEvent(e,canvas){ const rect=canvas.getBoundingClientRect(),clientX=e.touches?e.touches[0].clientX:e.clientX,clientY=e.touches?e.touches[0].clientY:e.clientY; ctpS=Math.max(0,Math.min(100,(clientX-rect.left)/rect.width*100)); ctpB=Math.max(0,Math.min(100,(1-(clientY-rect.top)/rect.height)*100)); } function ctpHueFromEvent(e,canvas){ const rect=canvas.getBoundingClientRect(),clientX=e.touches?e.touches[0].clientX:e.clientX; ctpH=Math.max(0,Math.min(360,(clientX-rect.left)/rect.width*360)); } function ctpInitEvents(){ const sbCanvas=document.getElementById('ctpCanvas'),hueCanvas=document.getElementById('ctpHue'); if(!sbCanvas||!hueCanvas)return; const sbDown=e=>{ctpDragging=true;ctpSBFromEvent(e,sbCanvas);ctpDrawSB();ctpPositionCursor();ctpUpdatePreview();e.preventDefault();}; const sbMove=e=>{if(!ctpDragging)return;ctpSBFromEvent(e,sbCanvas);ctpPositionCursor();ctpUpdatePreview();e.preventDefault();}; const sbUp=e=>{if(ctpDragging){ctpDragging=false;ctpCommit();}}; sbCanvas.addEventListener('mousedown',sbDown);sbCanvas.addEventListener('touchstart',sbDown,{passive:false});window.addEventListener('mousemove',sbMove);window.addEventListener('touchmove',sbMove,{passive:false});window.addEventListener('mouseup',sbUp);window.addEventListener('touchend',sbUp); const hDown=e=>{ctpHueDrag=true;ctpHueFromEvent(e,hueCanvas);ctpDrawSB();ctpPositionCursor();ctpPositionHueThumb();ctpUpdatePreview();e.preventDefault();}; const hMove=e=>{if(!ctpHueDrag)return;ctpHueFromEvent(e,hueCanvas);ctpDrawSB();ctpPositionCursor();ctpPositionHueThumb();ctpUpdatePreview();e.preventDefault();}; const hUp=e=>{if(ctpHueDrag){ctpHueDrag=false;ctpCommit();}}; hueCanvas.addEventListener('mousedown',hDown);hueCanvas.addEventListener('touchstart',hDown,{passive:false});window.addEventListener('mousemove',hMove);window.addEventListener('touchmove',hMove,{passive:false});window.addEventListener('mouseup',hUp);window.addEventListener('touchend',hUp); } function ctpSizeCanvas(){ const sbCanvas=document.getElementById('ctpCanvas'),hueCanvas=document.getElementById('ctpHue'); if(!sbCanvas||!hueCanvas)return; const sbRect=sbCanvas.getBoundingClientRect(),hueRect=hueCanvas.getBoundingClientRect(); if(sbRect.width>0){sbCanvas.width=sbRect.width*window.devicePixelRatio;sbCanvas.height=sbRect.height*window.devicePixelRatio;sbCanvas.getContext('2d').scale(window.devicePixelRatio,window.devicePixelRatio);} if(hueRect.width>0){hueCanvas.width=hueRect.width*window.devicePixelRatio;hueCanvas.height=hueRect.height*window.devicePixelRatio;hueCanvas.getContext('2d').scale(window.devicePixelRatio,window.devicePixelRatio);} ctpRefreshAll(); } function toggleCustomTheme(){ const toggle=document.getElementById('ctpToggle'),panel=document.getElementById('ctpPanel'); toggle.classList.toggle('open'); panel.classList.toggle('open'); if(panel.classList.contains('open')){ const cur=document.documentElement.getAttribute('data-theme')||'dark-gold'; try{const saved=JSON.parse(localStorage.getItem('kartava-custom')||'null');const src=saved||THEME_PRESETS[cur]||THEME_PRESETS['dark-gold'];Object.assign(CTP_STORE,src);}catch(e){Object.assign(CTP_STORE,THEME_PRESETS[cur]||THEME_PRESETS['dark-gold']);} ctpRefreshAllDots(); requestAnimationFrame(()=>{ctpSizeCanvas();ctpInitEvents._done||(ctpInitEvents(),ctpInitEvents._done=true);ctpLoadProp(ctpActiveProp);updateMiniPreview(CTP_STORE);}); } } function applyCustom(){ const{bg,accent,card,text,nav}=CTP_STORE,root=document.documentElement,rgb=hexToRgbStr(accent); root.style.setProperty('--bg',bg);root.style.setProperty('--accent-rgb',rgb);root.style.setProperty('--gold',accent);root.style.setProperty('--gold-light',lightenHex(accent,30));root.style.setProperty('--gold-dim',darkenHex(accent,40));root.style.setProperty('--gold-faint',`rgba(${rgb},0.07)`);root.style.setProperty('--gold-faint2',`rgba(${rgb},0.12)`);root.style.setProperty('--gold-border',`rgba(${rgb},0.16)`);root.style.setProperty('--gold-glow',`rgba(${rgb},0.26)`);root.style.setProperty('--card-bg',card+'f8');root.style.setProperty('--card-bg2',darkenHex(card,8)+'fc');root.style.setProperty('--nav-bg',nav+'f7');root.style.setProperty('--text',text);root.style.setProperty('--text-2',darkenHex(text,20));root.style.setProperty('--border',`rgba(${rgb},0.13)`);root.style.setProperty('--border2',`rgba(${rgb},0.20)`);root.style.setProperty('--input-bg',darkenHex(bg,2)+'f6');root.style.setProperty('--bubble-bot',darkenHex(card,6)+'f0');root.style.setProperty('--bubble-user',`rgba(${rgb},0.08)`);root.style.setProperty('--chart-bar',`rgba(${rgb},0.88)`);root.style.setProperty('--chart-bar2',`rgba(${rgb},0.40)`); ctpRefreshAllDots(); updateMiniPreview({bg,accent,card,text,nav}); try{localStorage.setItem('kartava-custom',JSON.stringify({...CTP_STORE}));}catch(e){} } function updateMiniPreview(colors){ const{bg,accent,card,text,nav}=colors,rgb=hexToRgbStr(accent),accentFaint=`rgba(${rgb},0.08)`,accentFaint2=`rgba(${rgb},0.13)`,accentBorder=`rgba(${rgb},0.18)`,cardDark=darkenHex(card,8),textDim=darkenHex(text,30),accentDim=darkenHex(accent,40),bubbleBot=darkenHex(card,6); const s=(id,styles)=>{const el=document.getElementById(id);if(!el)return;Object.assign(el.style,styles);}; s('ctpMiniApp',{background:bg});s('pmNav',{background:nav,borderBottom:`1px solid ${accentBorder}`});s('pmBrand',{color:accent});s('pmAvatar',{background:`linear-gradient(135deg,${accentFaint2},${accentFaint})`,border:`1.5px solid ${accent}`,color:bg});s('pmBody',{background:bg});s('pmChat',{background:card,border:`1px solid ${accentBorder}`});s('pmChatTitle',{color:accent});s('pmBubbleBot',{background:bubbleBot,borderColor:accentBorder,color:text});s('pmBubbleUser',{background:accentFaint,borderColor:accentBorder,color:text});s('pmInput',{background:darkenHex(bg,2),borderColor:accentBorder,color:text});s('pmSend',{background:accent,color:bg});s('pmServices',{background:card,border:`1px solid ${accentBorder}`});['1','2','3'].forEach(n=>{s('pmSvc'+n,{border:`1px solid ${accentBorder}`});s('pmSvcIcon'+n,{background:accentFaint,border:`1px solid ${accentBorder}`});s('pmSvcDot'+n,{background:accent});s('pmSvcLbl'+n,{color:textDim});});s('pmChartCard',{background:card,border:`1px solid ${accentBorder}`});s('pmChartTitle',{color:textDim});['1','2','3','4','5','6','7'].forEach(n=>s('pmBar'+n,{background:`rgba(${rgb},0.85)`}));s('pmBottomNav',{background:nav,borderTopColor:accentBorder});s('pmND1',{background:accent});['2','3','4'].forEach(n=>s('pmND'+n,{background:accentDim})); } function resetCustomTheme(){ const cur=document.documentElement.getAttribute('data-theme')||'dark-gold',props=['--bg','--accent-rgb','--gold','--gold-light','--gold-dim','--gold-faint','--gold-faint2','--gold-border','--gold-glow','--card-bg','--card-bg2','--nav-bg','--text','--text-2','--border','--border2','--input-bg','--bubble-bot','--bubble-user','--chart-bar','--chart-bar2']; props.forEach(p=>document.documentElement.style.removeProperty(p)); const preset=THEME_PRESETS[cur]||THEME_PRESETS['dark-gold']; Object.assign(CTP_STORE,preset); ctpRefreshAllDots(); if(document.getElementById('ctpPanel').classList.contains('open')){ctpLoadProp(ctpActiveProp);updateMiniPreview(preset);} try{localStorage.removeItem('kartava-custom');}catch(e){} showToast('Colours reset to theme defaults'); } function applyTheme(swatchEl){ const props=['--bg','--accent-rgb','--gold','--gold-light','--gold-dim','--gold-faint','--gold-faint2','--gold-border','--gold-glow','--card-bg','--card-bg2','--nav-bg','--text','--text-2','--border','--border2','--input-bg','--bubble-bot','--bubble-user','--chart-bar','--chart-bar2']; props.forEach(p=>document.documentElement.style.removeProperty(p)); document.querySelectorAll('.theme-swatch').forEach(s=>s.classList.remove('active')); swatchEl.classList.add('active'); const name=swatchEl.dataset.theme; document.documentElement.setAttribute('data-theme',name); const preset=THEME_PRESETS[name]; if(preset){Object.assign(CTP_STORE,preset);ctpRefreshAllDots();if(document.getElementById('ctpPanel').classList.contains('open')){ctpLoadProp(ctpActiveProp);updateMiniPreview(preset);}} try{localStorage.removeItem('kartava-custom');localStorage.setItem('kartava-theme',name);}catch(e){} showToast('Theme applied: '+name.replace('-',' ')); } // ══════════════════════════════════════════ // ── INIT ── // ══════════════════════════════════════════ document.addEventListener('DOMContentLoaded',()=>{ // Restore saved theme try{ const t=localStorage.getItem('kartava-theme'); if(t){ document.documentElement.setAttribute('data-theme',t); const sw=document.querySelector(`.theme-swatch[data-theme="${t}"]`); if(sw)sw.classList.add('active'); } const custom=JSON.parse(localStorage.getItem('kartava-custom')||'null'); if(custom){ Object.assign(CTP_STORE,custom); applyCustom(); } }catch(e){} }); // ── ANIMATE BARS (scaleY → compositor-only, zero layout cost) ── window.addEventListener('load',()=>{ const bars = document.querySelectorAll('.bar'); // Set final heights first (no animation yet) bars.forEach(bar=>{ bar.style.height = Math.round((parseInt(bar.dataset.v)/100)*80)+'px'; }); // Double rAF ensures paint is complete before transform animates requestAnimationFrame(()=>requestAnimationFrame(()=>{ setTimeout(()=>{ bars.forEach(bar=>{ bar.style.transform='scaleY(1)'; }); }, 80); })); }); // ── SEND MESSAGE ── function sendMsg(){ const inp=document.getElementById('msgInput'); const txt=inp.value.trim(); if(!txt)return; const area=document.getElementById('chatArea'); const ub=document.createElement('div'); ub.className='bubble user'; ub.innerHTML=`${txt}
${getTime()}  ✓✓
`; area.appendChild(ub); pop(ub); inp.value=''; area.scrollTop=area.scrollHeight; setTimeout(()=>{ const replies=['Which language would you like this translated into?','Translation complete! Here is the result for you.','Processing now — just a moment.','Done! Would you like me to read it aloud?','Got it. Do you need text, voice, or camera translation?']; const bb=document.createElement('div'); bb.className='bubble bot'; bb.innerHTML=`${replies[Math.floor(Math.random()*replies.length)]}
${getTime()}
`; area.appendChild(bb); pop(bb); area.scrollTop=area.scrollHeight; },900); } function pop(el){ // CSS class approach: no inline style conflicts, fully compositor-driven el.classList.add('bubble-pop'); el.addEventListener('animationend', ()=>el.classList.remove('bubble-pop'), {once:true}); } document.getElementById('msgInput').addEventListener('keydown',e=>{if(e.key==='Enter')sendMsg();}); function getTime(){ const d=new Date(); let h=d.getHours(),m=d.getMinutes(); const ap=h>=12?'PM':'AM'; h=h%12||12; return `${h}:${m<10?'0'+m:m} ${ap}`; } // ── FLASH SVC (CSS class-based, no forced-style recalc) ── function flashSvc(el){ el.classList.add('flash-active'); setTimeout(()=>el.classList.remove('flash-active'),1200); } // ── SET NAV ── function setNav(el){ document.querySelectorAll('.nav-item').forEach(i=>i.classList.remove('active')); el.classList.add('active'); } // ══════════════════════════════════════════ // ── SEARCH EXPAND / COLLAPSE ── // ══════════════════════════════════════════ function openSearch(){ document.getElementById('searchPill').style.display='none'; document.getElementById('userChip').style.display='none'; const bar = document.getElementById('searchExpanded'); bar.classList.add('open'); setTimeout(()=>document.getElementById('searchInput').focus(), 50); } function closeSearch(){ document.getElementById('searchExpanded').classList.remove('open'); document.getElementById('searchPill').style.display=''; document.getElementById('userChip').style.display=''; } // ══════════════════════════════════════════ // ── COLOUR STUDIO — Canvas HSB Picker ── // ══════════════════════════════════════════ const THEME_PRESETS = { 'dark-gold': { bg:'#0d1210', accent:'#c9a96e', card:'#131e16', text:'#ddd0ba', nav:'#0b110d' }, 'dark-green': { bg:'#060d08', accent:'#4caf7d', card:'#08140c', text:'#c8e8d4', nav:'#050a06' }, 'dark-blue': { bg:'#060810', accent:'#4a9eff', card:'#080c1a', text:'#c8d4f0', nav:'#050710' }, 'dark-purple': { bg:'#080610', accent:'#9b6dff', card:'#0e0a1a', text:'#d4c8f0', nav:'#070510' }, 'dark-bronze': { bg:'#0c0806', accent:'#cd7f32', card:'#181008', text:'#e8d4b8', nav:'#0e0904' }, 'light-green': { bg:'#f0f9f4', accent:'#287a52', card:'#ffffff', text:'#152d22', nav:'#f8fdf9' }, 'light-blue': { bg:'#f0f5ff', accent:'#1a6abf', card:'#ffffff', text:'#101e38', nav:'#f6faff' }, 'light-purple': { bg:'#f6f0ff', accent:'#7b3fc4', card:'#ffffff', text:'#1c1030', nav:'#faf6ff' }, 'light-orange': { bg:'#fff8ef', accent:'#c06a18', card:'#ffffff', text:'#251507', nav:'#fffcf6' }, 'light-silver': { bg:'#f4f6f8', accent:'#5a6878', card:'#ffffff', text:'#1a2030', nav:'#fafcfe' }, }; // Per-property colour store const CTP_STORE = { bg:'#050a07', accent:'#c9a96e', card:'#0d160f', text:'#ddd0ba', nav:'#060a07' }; const CTP_PROP_NAMES = { bg:'Background', accent:'Accent', card:'Card', text:'Text', nav:'Nav' }; let ctpActiveProp = 'bg'; let ctpH = 0, ctpS = 0, ctpB = 0; // current HSB let ctpDragging = false, ctpHueDrag = false; /* ─── colour math ─── */ function hexToRgb(hex){ const h = hex.replace('#',''); return { r:parseInt(h.slice(0,2),16), g:parseInt(h.slice(2,4),16), b:parseInt(h.slice(4,6),16) }; } function rgbToHex(r,g,b){ return '#'+[r,g,b].map(v=>Math.round(v).toString(16).padStart(2,'0')).join(''); } function hexToHsb(hex){ let {r,g,b} = hexToRgb(hex); r/=255; g/=255; b/=255; const max=Math.max(r,g,b), min=Math.min(r,g,b), d=max-min; let h=0, s=max===0?0:d/max, bv=max; if(d!==0){ if(max===r) h=((g-b)/d)%6; else if(max===g) h=(b-r)/d+2; else h=(r-g)/d+4; h=((h*60)+360)%360; } return { h, s:s*100, b:bv*100 }; } function hsbToHex(h,s,b){ s/=100; b/=100; const k=n=>(n+h/60)%6; const f=n=>b-b*s*Math.max(Math.min(k(n),4-k(n),1),0); return rgbToHex(f(5)*255,f(3)*255,f(1)*255); } function lightenHex(hex, amt){ let {r,g,b}=hexToRgb(hex); return rgbToHex(Math.min(255,r+amt),Math.min(255,g+amt),Math.min(255,b+amt)); } function darkenHex(hex, amt){ let {r,g,b}=hexToRgb(hex); return rgbToHex(Math.max(0,r-amt),Math.max(0,g-amt),Math.max(0,b-amt)); } function hexToRgbStr(hex){ let {r,g,b}=hexToRgb(hex); return `${r},${g},${b}`; } /* ─── canvas drawing ─── */ function ctpDrawSB(){ const canvas = document.getElementById('ctpCanvas'); if(!canvas) return; const ctx = canvas.getContext('2d'); const W = canvas.width, H = canvas.height; // Base hue fill ctx.fillStyle = `hsl(${ctpH},100%,50%)`; ctx.fillRect(0,0,W,H); // White → transparent left-to-right const gW = ctx.createLinearGradient(0,0,W,0); gW.addColorStop(0,'rgba(255,255,255,1)'); gW.addColorStop(1,'rgba(255,255,255,0)'); ctx.fillStyle = gW; ctx.fillRect(0,0,W,H); // Black → transparent top-to-bottom const gB = ctx.createLinearGradient(0,0,0,H); gB.addColorStop(0,'rgba(0,0,0,0)'); gB.addColorStop(1,'rgba(0,0,0,1)'); ctx.fillStyle = gB; ctx.fillRect(0,0,W,H); } function ctpDrawHue(){ const canvas = document.getElementById('ctpHue'); if(!canvas) return; const ctx = canvas.getContext('2d'); const W = canvas.width, H = canvas.height; const g = ctx.createLinearGradient(0,0,W,0); for(let i=0;i<=360;i+=10) g.addColorStop(i/360,`hsl(${i},100%,50%)`); ctx.fillStyle=g; ctx.fillRect(0,0,W,H); } function ctpPositionCursor(){ const wrap = document.getElementById('ctpCanvasWrap'); const canvas= document.getElementById('ctpCanvas'); const cursor= document.getElementById('ctpCursor'); if(!wrap||!cursor||!canvas) return; const rect = canvas.getBoundingClientRect(); const x = (ctpS/100) * rect.width; const y = (1 - ctpB/100) * rect.height; const wrapRect = wrap.getBoundingClientRect(); cursor.style.left = (rect.left - wrapRect.left + x) + 'px'; cursor.style.top = (rect.top - wrapRect.top + y) + 'px'; cursor.style.borderColor = ctpB > 50 ? 'rgba(255,255,255,0.9)' : 'rgba(200,200,200,0.7)'; } function ctpPositionHueThumb(){ const canvas = document.getElementById('ctpHue'); const thumb = document.getElementById('ctpHueThumb'); if(!canvas||!thumb) return; const rect = canvas.getBoundingClientRect(); const x = (ctpH/360) * rect.width; const wrapRect = document.getElementById('ctpHue').parentElement.getBoundingClientRect(); thumb.style.left = (rect.left - wrapRect.left + x) + 'px'; thumb.style.background = `hsl(${ctpH},100%,50%)`; } function ctpUpdatePreview(){ const hex = hsbToHex(ctpH, ctpS, ctpB); const ps = document.getElementById('ctpPreviewSwatch'); const hi = document.getElementById('ctpHexInput'); if(ps) ps.style.background = hex; if(hi) hi.value = hex.replace('#','').toUpperCase(); } function ctpRefreshAll(){ ctpDrawSB(); ctpDrawHue(); setTimeout(()=>{ ctpPositionCursor(); ctpPositionHueThumb(); }, 0); ctpUpdatePreview(); } function ctpLoadProp(prop){ const hex = CTP_STORE[prop] || '#c9a96e'; const hsb = hexToHsb(hex); ctpH = hsb.h; ctpS = hsb.s; ctpB = hsb.b; ctpRefreshAll(); // Update active tab dot cursor colour const cursor = document.getElementById('ctpCursor'); if(cursor) cursor.style.background = hex; } function ctpSelectProp(btn){ document.querySelectorAll('.ctp-tab').forEach(t=>t.classList.remove('active')); btn.classList.add('active'); ctpActiveProp = btn.dataset.prop; ctpLoadProp(ctpActiveProp); } /* ─── commit colour to store + UI ─── */ function ctpCommit(){ const hex = hsbToHex(ctpH, ctpS, ctpB); CTP_STORE[ctpActiveProp] = hex; ctpUpdateTabDot(ctpActiveProp, hex); ctpUpdateToggleDot(ctpActiveProp, hex); applyCustom(); showToast('✓ ' + CTP_PROP_NAMES[ctpActiveProp] + ' colour applied'); } function ctpUpdateTabDot(prop, hex){ const ids = { bg:'tdBg', accent:'tdAccent', card:'tdCard', text:'tdText', nav:'tdNav' }; const el = document.getElementById(ids[prop]); if(el) el.style.background = hex; } function ctpUpdateToggleDot(prop, hex){ const ids = { bg:'cttDotBg', accent:'cttDotAccent', card:'cttDotCard', text:'cttDotText', nav:'cttDotNav' }; const el = document.getElementById(ids[prop]); if(el) el.style.background = hex; } function ctpRefreshAllDots(){ Object.keys(CTP_STORE).forEach(prop=>{ ctpUpdateTabDot(prop, CTP_STORE[prop]); ctpUpdateToggleDot(prop, CTP_STORE[prop]); }); } /* ─── hex text input ─── */ function ctpHexInput(val){ val = val.replace(/[^0-9a-fA-F]/g,''); if(val.length===6){ const hsb = hexToHsb('#'+val); ctpH=hsb.h; ctpS=hsb.s; ctpB=hsb.b; ctpDrawSB(); setTimeout(()=>{ ctpPositionCursor(); ctpPositionHueThumb(); },0); const ps = document.getElementById('ctpPreviewSwatch'); if(ps) ps.style.background='#'+val; ctpUpdateTabDot(ctpActiveProp,'#'+val); CTP_STORE[ctpActiveProp]='#'+val; applyCustom(); } } /* ─── canvas mouse/touch ─── */ function ctpSBFromEvent(e, canvas){ const rect = canvas.getBoundingClientRect(); const clientX = e.touches ? e.touches[0].clientX : e.clientX; const clientY = e.touches ? e.touches[0].clientY : e.clientY; ctpS = Math.max(0,Math.min(100, (clientX-rect.left)/rect.width*100)); ctpB = Math.max(0,Math.min(100, (1-(clientY-rect.top)/rect.height)*100)); } function ctpHueFromEvent(e, canvas){ const rect = canvas.getBoundingClientRect(); const clientX = e.touches ? e.touches[0].clientX : e.clientX; ctpH = Math.max(0,Math.min(360, (clientX-rect.left)/rect.width*360)); } function ctpInitEvents(){ const sbCanvas = document.getElementById('ctpCanvas'); const hueCanvas = document.getElementById('ctpHue'); if(!sbCanvas || !hueCanvas) return; // SB canvas const sbDown = e=>{ ctpDragging=true; ctpSBFromEvent(e,sbCanvas); ctpDrawSB(); ctpPositionCursor(); ctpUpdatePreview(); e.preventDefault(); }; const sbMove = e=>{ if(!ctpDragging) return; ctpSBFromEvent(e,sbCanvas); ctpPositionCursor(); ctpUpdatePreview(); e.preventDefault(); }; const sbUp = e=>{ if(ctpDragging){ ctpDragging=false; ctpCommit(); } }; sbCanvas.addEventListener('mousedown', sbDown); sbCanvas.addEventListener('touchstart', sbDown, {passive:false}); window.addEventListener('mousemove', sbMove); window.addEventListener('touchmove', sbMove, {passive:false}); window.addEventListener('mouseup', sbUp); window.addEventListener('touchend', sbUp); // Hue canvas const hDown = e=>{ ctpHueDrag=true; ctpHueFromEvent(e,hueCanvas); ctpDrawSB(); ctpPositionCursor(); ctpPositionHueThumb(); ctpUpdatePreview(); e.preventDefault(); }; const hMove = e=>{ if(!ctpHueDrag) return; ctpHueFromEvent(e,hueCanvas); ctpDrawSB(); ctpPositionCursor(); ctpPositionHueThumb(); ctpUpdatePreview(); e.preventDefault(); }; const hUp = e=>{ if(ctpHueDrag){ ctpHueDrag=false; ctpCommit(); } }; hueCanvas.addEventListener('mousedown', hDown); hueCanvas.addEventListener('touchstart', hDown, {passive:false}); window.addEventListener('mousemove', hMove); window.addEventListener('touchmove', hMove, {passive:false}); window.addEventListener('mouseup', hUp); window.addEventListener('touchend', hUp); } /* ─── canvas resize ─── */ function ctpSizeCanvas(){ const sbCanvas = document.getElementById('ctpCanvas'); const hueCanvas = document.getElementById('ctpHue'); if(!sbCanvas||!hueCanvas) return; const sbRect = sbCanvas.getBoundingClientRect(); const hueRect = hueCanvas.getBoundingClientRect(); if(sbRect.width > 0){ sbCanvas.width = sbRect.width * window.devicePixelRatio; sbCanvas.height = sbRect.height * window.devicePixelRatio; sbCanvas.getContext('2d').scale(window.devicePixelRatio, window.devicePixelRatio); } if(hueRect.width > 0){ hueCanvas.width = hueRect.width * window.devicePixelRatio; hueCanvas.height = hueRect.height * window.devicePixelRatio; hueCanvas.getContext('2d').scale(window.devicePixelRatio, window.devicePixelRatio); } ctpRefreshAll(); } /* ─── panel open/close ─── */ function toggleCustomTheme(){ const toggle = document.getElementById('ctpToggle'); const panel = document.getElementById('ctpPanel'); toggle.classList.toggle('open'); panel.classList.toggle('open'); if(panel.classList.contains('open')){ // Load current theme colours into store const cur = document.documentElement.getAttribute('data-theme') || 'dark-gold'; try { const saved = JSON.parse(localStorage.getItem('kartava-custom')||'null'); const src = saved || THEME_PRESETS[cur] || THEME_PRESETS['dark-gold']; Object.assign(CTP_STORE, src); } catch(e){ Object.assign(CTP_STORE, THEME_PRESETS[cur]||THEME_PRESETS['dark-gold']); } ctpRefreshAllDots(); // Need layout to settle before sizing canvas requestAnimationFrame(()=>{ ctpSizeCanvas(); ctpInitEvents._done || (ctpInitEvents(), ctpInitEvents._done=true); ctpLoadProp(ctpActiveProp); updateMiniPreview(CTP_STORE); }); } } /* ─── apply all to CSS vars ─── */ function applyCustom(){ const { bg, accent, card, text, nav } = CTP_STORE; const root = document.documentElement; const rgb = hexToRgbStr(accent); root.style.setProperty('--bg', bg); root.style.setProperty('--accent-rgb', rgb); root.style.setProperty('--gold', accent); root.style.setProperty('--gold-light', lightenHex(accent,30)); root.style.setProperty('--gold-dim', darkenHex(accent,40)); root.style.setProperty('--gold-faint', `rgba(${rgb},0.07)`); root.style.setProperty('--gold-faint2', `rgba(${rgb},0.12)`); root.style.setProperty('--gold-border', `rgba(${rgb},0.16)`); root.style.setProperty('--gold-glow', `rgba(${rgb},0.26)`); root.style.setProperty('--card-bg', card+'f8'); root.style.setProperty('--card-bg2', darkenHex(card,8)+'fc'); root.style.setProperty('--nav-bg', nav+'f7'); root.style.setProperty('--text', text); root.style.setProperty('--text-2', darkenHex(text,20)); root.style.setProperty('--border', `rgba(${rgb},0.13)`); root.style.setProperty('--border2', `rgba(${rgb},0.20)`); root.style.setProperty('--input-bg', darkenHex(bg,2)+'f6'); root.style.setProperty('--bubble-bot', darkenHex(card,6)+'f0'); root.style.setProperty('--bubble-user', `rgba(${rgb},0.08)`); root.style.setProperty('--chart-bar', `rgba(${rgb},0.88)`); root.style.setProperty('--chart-bar2', `rgba(${rgb},0.40)`); ctpRefreshAllDots(); updateMiniPreview({ bg, accent, card, text, nav }); try { localStorage.setItem('kartava-custom', JSON.stringify({...CTP_STORE})); } catch(e){} } /* ─── mini preview updater ─── */ function updateMiniPreview(colors){ const { bg, accent, card, text, nav } = colors; const rgb = hexToRgbStr(accent); const accentFaint = `rgba(${rgb},0.08)`; const accentFaint2 = `rgba(${rgb},0.13)`; const accentBorder = `rgba(${rgb},0.18)`; const cardDark = darkenHex(card, 8); const textDim = darkenHex(text, 30); const accentDim = darkenHex(accent, 40); const bubbleBot = darkenHex(card, 6); const s = (id, styles) => { const el = document.getElementById(id); if(!el) return; Object.assign(el.style, styles); }; s('ctpMiniApp', { background: bg }); s('pmNav', { background: nav, borderBottom: `1px solid ${accentBorder}` }); s('pmBrand', { color: accent }); s('pmAvatar', { background: `linear-gradient(135deg,${accentFaint2},${accentFaint})`, border:`1.5px solid ${accent}`, color:bg }); s('pmBody', { background: bg }); s('pmChat', { background: card, border:`1px solid ${accentBorder}` }); s('pmChatTitle', { color: accent }); s('pmBubbleBot', { background: bubbleBot, borderColor: accentBorder, color: text }); s('pmBubbleUser', { background: accentFaint, borderColor: accentBorder, color: text }); s('pmInput', { background: darkenHex(bg,2), borderColor: accentBorder, color: text }); s('pmSend', { background: accent, color: bg }); s('pmServices', { background: card, border:`1px solid ${accentBorder}` }); ['1','2','3'].forEach(n => { s('pmSvc'+n, { border:`1px solid ${accentBorder}` }); s('pmSvcIcon'+n, { background: accentFaint, border:`1px solid ${accentBorder}` }); s('pmSvcDot'+n, { background: accent }); s('pmSvcLbl'+n, { color: textDim }); }); s('pmChartCard', { background: card, border:`1px solid ${accentBorder}` }); s('pmChartTitle', { color: textDim }); ['1','2','3','4','5','6','7'].forEach(n => s('pmBar'+n, { background:`rgba(${rgb},0.85)` })); s('pmBottomNav', { background: nav, borderTopColor: accentBorder }); s('pmND1', { background: accent }); ['2','3','4'].forEach(n => s('pmND'+n, { background: accentDim })); } /* ─── reset ─── */ function resetCustomTheme(){ const cur = document.documentElement.getAttribute('data-theme') || 'dark-gold'; const props = ['--bg','--accent-rgb','--gold','--gold-light','--gold-dim','--gold-faint','--gold-faint2', '--gold-border','--gold-glow','--card-bg','--card-bg2','--nav-bg','--text','--text-2', '--border','--border2','--input-bg','--bubble-bot','--bubble-user','--chart-bar','--chart-bar2']; props.forEach(p=>document.documentElement.style.removeProperty(p)); const preset = THEME_PRESETS[cur] || THEME_PRESETS['dark-gold']; Object.assign(CTP_STORE, preset); ctpRefreshAllDots(); if(document.getElementById('ctpPanel').classList.contains('open')){ ctpLoadProp(ctpActiveProp); updateMiniPreview(preset); } try { localStorage.removeItem('kartava-custom'); } catch(e){} showToast('Colours reset to theme defaults'); } /* ─── override applyTheme to clear custom ─── */ function applyTheme(swatchEl){ const props=['--bg','--accent-rgb','--gold','--gold-light','--gold-dim','--gold-faint','--gold-faint2', '--gold-border','--gold-glow','--card-bg','--card-bg2','--nav-bg','--text','--text-2', '--border','--border2','--input-bg','--bubble-bot','--bubble-user','--chart-bar','--chart-bar2']; props.forEach(p=>document.documentElement.style.removeProperty(p)); const theme = swatchEl.dataset.theme; document.documentElement.setAttribute('data-theme', theme); document.querySelectorAll('.theme-swatch').forEach(s=>s.classList.remove('active')); swatchEl.classList.add('active'); try { localStorage.setItem('kartava-theme', theme); localStorage.removeItem('kartava-custom'); } catch(e){} // Sync store const preset = THEME_PRESETS[theme]||THEME_PRESETS['dark-gold']; Object.assign(CTP_STORE, preset); ctpRefreshAllDots(); if(document.getElementById('ctpPanel').classList.contains('open')){ ctpLoadProp(ctpActiveProp); updateMiniPreview(preset); } // Toast with name from swatch label (sibling div) const lbl = swatchEl.closest('div')?.querySelector('.theme-label')?.textContent || theme; showToast('Theme: ' + lbl); } /* ─── restore custom on load ─── */ (function(){ try{ const saved = JSON.parse(localStorage.getItem('kartava-custom')||'null'); if(!saved) return; Object.assign(CTP_STORE, saved); const apply = ()=>{ const root=document.documentElement; const rgb=hexToRgbStr(saved.accent); root.style.setProperty('--bg', saved.bg); root.style.setProperty('--accent-rgb', rgb); root.style.setProperty('--gold', saved.accent); root.style.setProperty('--gold-light', lightenHex(saved.accent,30)); root.style.setProperty('--gold-dim', darkenHex(saved.accent,40)); root.style.setProperty('--gold-faint', `rgba(${rgb},0.07)`); root.style.setProperty('--gold-faint2',`rgba(${rgb},0.12)`); root.style.setProperty('--gold-border',`rgba(${rgb},0.16)`); root.style.setProperty('--gold-glow', `rgba(${rgb},0.26)`); root.style.setProperty('--card-bg', saved.card+'f8'); root.style.setProperty('--card-bg2', darkenHex(saved.card,8)+'fc'); root.style.setProperty('--nav-bg', (saved.nav||saved.bg)+'f7'); root.style.setProperty('--text', saved.text); root.style.setProperty('--text-2', darkenHex(saved.text,20)); root.style.setProperty('--border', `rgba(${rgb},0.13)`); root.style.setProperty('--border2', `rgba(${rgb},0.20)`); root.style.setProperty('--input-bg', darkenHex(saved.bg,2)+'f6'); root.style.setProperty('--bubble-bot', darkenHex(saved.card,6)+'f0'); root.style.setProperty('--bubble-user',`rgba(${rgb},0.08)`); root.style.setProperty('--chart-bar', `rgba(${rgb},0.88)`); root.style.setProperty('--chart-bar2', `rgba(${rgb},0.40)`); ctpRefreshAllDots(); }; document.readyState==='loading' ? document.addEventListener('DOMContentLoaded', apply) : apply(); }catch(e){} })(); // Restore saved theme on load (function(){ try{ const saved = localStorage.getItem('kartava-theme') || 'dark-gold'; document.documentElement.setAttribute('data-theme', saved); document.addEventListener('DOMContentLoaded', ()=>{ const sw = document.querySelector('.theme-swatch[data-theme="'+saved+'"]'); if(sw){ document.querySelectorAll('.theme-swatch').forEach(s=>s.classList.remove('active')); sw.classList.add('active'); } }); }catch(e){} })(); function startLongPress(el){ lpTimer = setTimeout(()=>{ el.classList.add('pressing'); lpActive = el; // haptic feedback if available if(navigator.vibrate) navigator.vibrate(30); }, 400); } function endLongPress(el){ clearTimeout(lpTimer); if(lpActive){ setTimeout(()=>{ el.classList.remove('pressing'); lpActive = null; }, 600); } } document.querySelectorAll('.lp-item').forEach(item=>{ item.addEventListener('mousedown', ()=>startLongPress(item)); item.addEventListener('mouseup', ()=>endLongPress(item)); item.addEventListener('mouseleave', ()=>endLongPress(item)); item.addEventListener('touchstart', (e)=>{ startLongPress(item); }, {passive:true}); item.addEventListener('touchend', ()=>endLongPress(item)); item.addEventListener('touchcancel', ()=>endLongPress(item)); }); // ══════════════════════════════════════════ // ── PROFILE PANEL ── // ══════════════════════════════════════════ function openProfile(){ document.getElementById('profileOverlay').classList.add('open'); document.getElementById('profilePanel').classList.add('open'); document.body.style.overflow = 'hidden'; } function closeProfile(){ document.getElementById('profileOverlay').classList.remove('open'); document.getElementById('profilePanel').classList.remove('open'); document.body.style.overflow = ''; } function switchToEdit(){ document.getElementById('profileView').style.display = 'none'; document.getElementById('profileEdit').style.display = 'block'; document.getElementById('profileBody').scrollTop = 0; } function switchToView(){ document.getElementById('profileView').style.display = 'block'; document.getElementById('profileEdit').style.display = 'none'; document.getElementById('profileBody').scrollTop = 0; } function saveProfile(){ const name = document.getElementById('editName').value.trim(); const username = document.getElementById('editUsername').value.trim(); const email = document.getElementById('editEmail').value.trim(); const phone = document.getElementById('editPhone').value.trim(); const location = document.getElementById('editLocation').value.trim(); if(!name){ showToast('Name cannot be empty'); return; } // Update view fields document.getElementById('infoName').textContent = name; document.getElementById('infoUsername').textContent = '@' + username; document.getElementById('infoEmail').textContent = email; document.getElementById('infoPhone').textContent = phone; document.getElementById('infoLocation').textContent = location; // Update nav chip const firstName = name.split(' ')[0]; document.getElementById('navUserName').textContent = firstName; document.getElementById('pfNameDisplay').textContent = firstName; // Update avatars with initials const initials = name.split(' ').map(w=>w[0]).join('').slice(0,2).toUpperCase(); document.getElementById('pfAvatar').textContent = initials; document.getElementById('navAvatar').textContent = initials[0]; switchToView(); showToast('✓ Profile updated'); } function triggerAvatarUpload(){ document.getElementById('avatarFileInput').click(); } function handleAvatarUpload(e){ const file = e.target.files[0]; if(!file) return; const reader = new FileReader(); reader.onload = function(ev){ const src = ev.target.result; const pf = document.getElementById('pfAvatar'); pf.innerHTML = `avatar`; document.getElementById('navAvatar').style.backgroundImage = `url(${src})`; document.getElementById('navAvatar').style.backgroundSize = 'cover'; document.getElementById('navAvatar').textContent = ''; showToast('✓ Photo updated'); }; reader.readAsDataURL(file); } function toggleNotifPref(row){ const t = document.getElementById('notifToggle'); t.classList.toggle('on'); showToast(t.classList.contains('on') ? 'Notifications on' : 'Notifications off'); } function toggleDarkPref(row){ const t = document.getElementById('darkToggle'); t.classList.toggle('on'); showToast(t.classList.contains('on') ? 'Dark mode on' : 'Dark mode off'); } function confirmLogout(){ document.getElementById('logoutOverlay').classList.add('open'); } function closeLogout(){ document.getElementById('logoutOverlay').classList.remove('open'); } function doLogout(){ closeLogout(); closeProfile(); showToast('Signed out. See you soon!'); } let toastTimer; function showToast(msg){ const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(()=>t.classList.remove('show'), 2800); } // ══════════════════════════════════════════ // ── GROCERY PANEL ── // ══════════════════════════════════════════ function openGroceryPanel(){ document.getElementById('groceryOverlay').classList.add('open'); document.getElementById('groceryPanel').classList.add('open'); document.body.style.overflow = 'hidden'; } function closeGroceryPanel(){ document.getElementById('groceryOverlay').classList.remove('open'); document.getElementById('groceryPanel').classList.remove('open'); document.body.style.overflow = ''; } // ══════════════════════════════════════════ // ── MARKET PANEL ── // ══════════════════════════════════════════ function openMarketPanel(){ document.getElementById('marketOverlay').classList.add('open'); document.getElementById('marketPanel').classList.add('open'); document.body.style.overflow = 'hidden'; } function closeMarketPanel(){ document.getElementById('marketOverlay').classList.remove('open'); document.getElementById('marketPanel').classList.remove('open'); document.body.style.overflow = ''; } // ══════════════════════════════════════════ // ── LABOUR PANEL ── // ══════════════════════════════════════════ function openLabourPanel(){ document.getElementById('labourOverlay').classList.add('open'); document.getElementById('labourPanel').classList.add('open'); document.body.style.overflow = 'hidden'; } function closeLabourPanel(){ document.getElementById('labourOverlay').classList.remove('open'); document.getElementById('labourPanel').classList.remove('open'); document.body.style.overflow = ''; } // ══════════════════════════════════════════ // ── PLUMBER / ELECTRICIAN PANEL ── // ══════════════════════════════════════════ function openPlumberPanel(){ document.getElementById('plumberOverlay').classList.add('open'); document.getElementById('plumberPanel').classList.add('open'); document.body.style.overflow = 'hidden'; } function closePlumberPanel(){ document.getElementById('plumberOverlay').classList.remove('open'); document.getElementById('plumberPanel').classList.remove('open'); document.body.style.overflow = ''; } // ══════════════════════════════════════════ // ── HOUSE IN RENT PANEL ── // ══════════════════════════════════════════ function openHouseRentPanel(){ document.getElementById('houseRentOverlay').classList.add('open'); document.getElementById('houseRentPanel').classList.add('open'); document.body.style.overflow = 'hidden'; } function closeHouseRentPanel(){ document.getElementById('houseRentOverlay').classList.remove('open'); document.getElementById('houseRentPanel').classList.remove('open'); document.body.style.overflow = ''; } // ══════════════════════════════════════════ // ── PROPERTY SALE PANEL ── // ══════════════════════════════════════════ function openPropertySalePanel(){ document.getElementById('propertySaleOverlay').classList.add('open'); document.getElementById('propertySalePanel').classList.add('open'); document.body.style.overflow = 'hidden'; } function closePropertySalePanel(){ document.getElementById('propertySaleOverlay').classList.remove('open'); document.getElementById('propertySalePanel').classList.remove('open'); document.body.style.overflow = ''; } // ══════════════════════════════════════════ // ── LUXURY RENT PANEL ── // ══════════════════════════════════════════ function openLuxuryRentPanel(){ document.getElementById('luxuryRentOverlay').classList.add('open'); document.getElementById('luxuryRentPanel').classList.add('open'); document.body.style.overflow = 'hidden'; } function closeLuxuryRentPanel(){ document.getElementById('luxuryRentOverlay').classList.remove('open'); document.getElementById('luxuryRentPanel').classList.remove('open'); document.body.style.overflow = ''; } function setActiveCat(el){ const parent = el.closest('.svc-panel-cats'); parent.querySelectorAll('.cat-pill').forEach(p=>p.classList.remove('active')); el.classList.add('active'); } // ══════════════════════════════════════════ function toggleHamburger(){ hamOpen ? closeHamburger() : openHamburger(); } function openHamburger(){ hamOpen = true; document.getElementById('hamIcon').classList.add('open'); document.getElementById('hamOverlay').classList.add('open'); document.getElementById('hamMenu').classList.add('open'); document.getElementById('hamburgerBtn').classList.add('active'); } function closeHamburger(){ hamOpen = false; document.getElementById('hamIcon').classList.remove('open'); document.getElementById('hamOverlay').classList.remove('open'); document.getElementById('hamMenu').classList.remove('open'); document.getElementById('hamburgerBtn').classList.remove('active'); } // ══════════════════════════════════════════ // ── END OF SCRIPTS ── // ══════════════════════════════════════════