PageSourceSearch

https://monochroam.com/components.js

js monochroam.com collected 2026-10-02 01:36:18 UTC 19,767 bytes, 412 lines download raw bytes

1/* ============================================================
2   MONOCHROAM — shared chrome + interactions
3   ============================================================ */
4(function(){
5  "use strict";
6
7  /* ---------- brand assets ---------- */
8  function logoMark(cls){
9    return '<svg viewBox="0 0 40 40" class="mark '+(cls||'')+'" aria-hidden="true">'+
10      '<circle cx="19" cy="20.5" r="16.4" fill="none" stroke="currentColor" stroke-width="3"/>'+
11      '<circle cx="19" cy="20.5" r="8.4" fill="currentColor"/>'+
12      '<circle cx="19" cy="20.5" r="3.5" fill="#08080a"/>'+
13      '<circle cx="33.2" cy="7.2" r="4.4" fill="#ff3b30"/>'+
14    '</svg>';
15  }
16  var LOGO_SRC='https://lirp.cdn-website.com/beb2a5ff/dms3rep/multi/opt/Logo_Monochroam_white-1920w.png';
17  function logo(extra){
18    var sm=(extra||'').indexOf('logo--sm')>=0;
19    return '<a href="index.html" class="logo '+(extra||'')+'" aria-label="Monochroam home">'+
20      '<img class="logo-img" src="'+LOGO_SRC+'" alt="Monochroam" style="height:'+(sm?'24px':'30px')+';width:auto;display:block">'+
21    '</a>';
22  }
23
24  var NAV = [
25    ['hosts.html','Hosts'],
26    ['studio.html','Studio'],
27    ['refer.html','Free credits'],
28    ['faq.html','FAQ'],
29    ['contact.html','Contact']
30  ];
31
32  /* ---------- header ---------- */
33  function buildHeader(page){
34    var links = NAV.map(function(n){
35      var active = n[0].indexOf(page)===0 && page ? ' class="active"' : '';
36      return '<a href="'+n[0]+'"'+active+'>'+n[1]+'</a>';
37    }).join('');
38    return ''+
39    '<div class="wrap">'+
40      logo()+
41      '<nav class="nav" id="nav">'+links+'</nav>'+
42      '<div class="header-cta">'+
43        '<a href="index.html#get-app" class="btn btn--sm">Get the app</a>'+
44        '<button class="nav-toggle" id="navToggle" aria-label="Menu" aria-expanded="false">'+
45          '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M3 12h18M3 18h18"/></svg>'+
46        '</button>'+
47      '</div>'+
48    '</div>';
49  }
50
51  /* ---------- footer ---------- */
52  var iconMail='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg>';
53  var iconIg='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="1" fill="currentColor" stroke="none"/></svg>';
54  var iconTk='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M16.5 5.6a4.3 4.3 0 0 1-2.7-1.6v8.9a5 5 0 1 1-5-5c.3 0 .5 0 .8.1v2.6a2.4 2.4 0 1 0 1.7 2.3V2h2.5a4.3 4.3 0 0 0 2.7 3.6z"/></svg>';
55
56  function buildFooter(){
57    return ''+
58    '<div class="wrap">'+
59      '<div class="footer-grid">'+
60        '<div class="footer-col footer-brand">'+
61          logo('logo--sm')+
62          '<p class="muted" style="font-size:.95rem;max-width:30ch;margin-bottom:18px">Curated photography sessions & studio spaces for the spotlight-worthy.</p>'+
63          '<div class="socials">'+
64            '<a class="soc" href="contact.html" aria-label="Email">'+iconMail+'</a>'+
65            '<a class="soc" href="#" aria-label="Instagram">'+iconIg+'</a>'+
66            '<a class="soc" href="#" aria-label="TikTok">'+iconTk+'</a>'+
67          '</div>'+
68        '</div>'+
69        '<div class="footer-col">'+
70          '<h4>Company</h4>'+
71          '<ul>'+
72            '<li><a href="meet-the-hosts.html">Meet the hosts</a></li>'+
73            '<li><a href="#">Terms & Conditions</a></li>'+
74            '<li><a href="#">Cancellation policy</a></li>'+
75            '<li><a href="#">Accessibility</a></li>'+
76            '<li><a href="#">Privacy</a></li>'+
77          '</ul>'+
78        '</div>'+
79        '<div class="footer-col">'+
80          '<h4>Community</h4>'+
81          '<ul>'+
82            '<li><a href="studio.html">Studio</a></li>'+
83            '<li><a href="meet-the-hosts.html">Hosts</a></li>'+
84            '<li><a href="contact.html">Contact</a></li>'+
85            '<li><a href="faq.html">FAQ</a></li>'+
86          '</ul>'+
87        '</div>'+
88        '<div class="footer-col">'+
89          '<h4>Stay in the loop</h4>'+
90          '<p class="muted" style="font-size:.95rem;margin-bottom:16px">New drops, sessions & member perks — straight to your inbox.</p>'+
91          '<form class="news" data-newsletter novalidate>'+
92            '<div class="inline-form">'+
93              '<input class="input" type="email" placeholder="[email protected]" aria-label="Email" required>'+
94              '<button class="btn" type="submit">Subscribe</button>'+
95            '</div>'+
96            '<p class="field-msg" data-msg></p>'+
97          '</form>'+
98        '</div>'+
99      '</div>'+
100      '<div class="footer-bottom">'+
101        '<p>© 2026 Monochroam LP. All rights reserved.</p>'+
102        '<div class="mini-nav"><a href="#">Terms</a><a href="#">Privacy</a><a href="#">Cookies</a></div>'+
103      '</div>'+
104    '</div>';
105  }
106
107  /* ---------- mount chrome ---------- */
108  function mount(){
109    var body = document.body;
110    var page = body.getAttribute('data-page')||'';
111    var header = document.querySelector('[data-site-header]');
112    if(header){ header.className='site-header'; header.innerHTML=buildHeader(page); }
113    var footer = document.querySelector('[data-site-footer]');
114    if(footer){ footer.className='site-footer'; footer.innerHTML=buildFooter(); }
115
116    // header scroll state
117    var onScroll=function(){ if(header) header.classList.toggle('is-scrolled', window.scrollY>12); };
118    onScroll(); window.addEventListener('scroll',onScroll,{passive:true});
119
120    // mobile nav
121    var nav=document.getElementById('nav'), toggle=document.getElementById('navToggle');
122    if(toggle){
123      toggle.addEventListener('click',function(){
124        var open=nav.classList.toggle('is-open');
125        header.classList.toggle('menu-open',open);
126        toggle.setAttribute('aria-expanded',open);
127      });
128      nav.querySelectorAll('a').forEach(function(a){a.addEventListener('click',function(){
129        nav.classList.remove('is-open');header.classList.remove('menu-open');
130      });});
131    }
132  }
133
134  /* ---------- reveals ---------- */
135  function reveals(){
136    var els=[].slice.call(document.querySelectorAll('.reveal'));
137    // auto-stagger groups
138    document.querySelectorAll('[data-stagger]').forEach(function(g){
139      var step=parseInt(g.getAttribute('data-stagger'),10)||90;
140      [].slice.call(g.children).forEach(function(c,i){ if(c.classList.contains('reveal')) c.style.setProperty('--d',(i*step)+'ms'); });
141    });
142    if(!els.length) return;
143    function check(){
144      var vh=window.innerHeight||document.documentElement.clientHeight;
145      els=els.filter(function(e){
146        var r=e.getBoundingClientRect();
147        if(r.top<vh*0.92 && r.bottom>-40){ e.classList.add('in'); return false; }
148        return true;
149      });
150    }
151    check();
152    requestAnimationFrame(check);
153    window.addEventListener('scroll',check,{passive:true});
154    window.addEventListener('resize',check);
155    // safety net: if the animation timeline is frozen (hidden/throttled tab),
156    // never leave content hidden — force the visible end-state.
157    setTimeout(function(){
158      if(document.timeline && document.timeline.currentTime===0){
159        var st=document.createElement('style');
160        st.id='reveal-fallback';
161        st.textContent='.reveal{opacity:1!important;animation:none!important}';
162        document.head.appendChild(st);
163      }
164      els.forEach(function(e){e.classList.add('in');});
165    },1400);
166  }
167
168  /* ---------- accordions ---------- */
169  function accordions(){
170    document.querySelectorAll('[data-acc]').forEach(function(group){
171      var single=group.hasAttribute('data-acc-single');
172      group.querySelectorAll('.acc').forEach(function(acc){
173        var head=acc.querySelector('.acc-head');
174        head.addEventListener('click',function(){
175          var willOpen=!acc.classList.contains('open');
176          if(single) group.querySelectorAll('.acc.open').forEach(function(o){o.classList.remove('open');});
177          acc.classList.toggle('open',willOpen);
178        });
179      });
180    });
181  }
182
183  /* ---------- count-up ---------- */
184  function counters(){
185    var nums=[].slice.call(document.querySelectorAll('[data-count]'));
186    if(!nums.length) return;
187    function run(el){
188      var end=parseFloat(el.getAttribute('data-count')), suf=el.getAttribute('data-suffix')||'', dec=(end%1!==0)?1:0, t0=null, dur=1400;
189      function step(t){ if(!t0)t0=t; var p=Math.min((t-t0)/dur,1); var e2=1-Math.pow(1-p,3);
190        el.textContent=(end*e2).toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g,',')+suf; if(p<1)requestAnimationFrame(step); }
191      requestAnimationFrame(step);
192    }
193    function check(){
194      var vh=window.innerHeight||document.documentElement.clientHeight;
195      nums=nums.filter(function(el){
196        var r=el.getBoundingClientRect();
197        if(r.top<vh*0.9 && r.bottom>0){ run(el); return false; }
198        return true;
199      });
200    }
201    check();
202    window.addEventListener('scroll',check,{passive:true});
203    setTimeout(function(){nums.forEach(run);},2600);
204  }
205
206  /* ---------- tilt ---------- */
207  function tilt(){
208    if(matchMedia('(hover:none)').matches) return;
209    document.querySelectorAll('[data-tilt]').forEach(function(el){
210      var max=6;
211      el.addEventListener('mousemove',function(e){
212        if(document.documentElement.dataset.motion==='off') return;
213        var r=el.getBoundingClientRect(), x=(e.clientX-r.left)/r.width-.5, y=(e.clientY-r.top)/r.height-.5;
214        el.style.transform='perspective(900px) rotateY('+(x*max)+'deg) rotateX('+(-y*max)+'deg) translateY(-4px)';
215      });
216      el.addEventListener('mouseleave',function(){el.style.transform='';});
217    });
218  }
219
220  /* ---------- 3D tilt + glare (cards) ---------- */
221  function tilt3d(root){
222    if(matchMedia('(hover:none)').matches||matchMedia('(prefers-reduced-motion:reduce)').matches) return;
223    (root||document).querySelectorAll('[data-tilt3d]').forEach(function(el){
224      if(el.__t3d) return; el.__t3d=true;
225      var max=parseFloat(el.getAttribute('data-tilt3d'))||10, raf;
226      var glare=el.querySelector('.tilt-glare');
227      if(!glare){glare=document.createElement('span');glare.className='tilt-glare';el.appendChild(glare);}
228      el.addEventListener('pointermove',function(e){
229        if(document.documentElement.dataset.motion==='off')return;
230        var r=el.getBoundingClientRect();
231        var px=(e.clientX-r.left)/r.width, py=(e.clientY-r.top)/r.height;
232        cancelAnimationFrame(raf);
233        raf=requestAnimationFrame(function(){
234          el.classList.add('is-tilting');
235          el.style.setProperty('--tly',((px-0.5)*max).toFixed(2)+'deg');
236          el.style.setProperty('--tlx',((0.5-py)*max).toFixed(2)+'deg');
237          el.style.setProperty('--gx',(px*100).toFixed(1)+'%');
238          el.style.setProperty('--gy',(py*100).toFixed(1)+'%');
239        });
240      });
241      el.addEventListener('pointerleave',function(){
242        el.classList.remove('is-tilting');
243        el.style.setProperty('--tly','0deg');
244        el.style.setProperty('--tlx','0deg');
245      });
246    });
247  }
248
249  /* ---------- forms ---------- */
250  function isEmail(v){return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);}
251  function forms(){
252    // newsletter (footer + inline)
253    document.querySelectorAll('[data-newsletter]').forEach(function(f){
254      f.addEventListener('submit',function(e){
255        e.preventDefault();
256        var inp=f.querySelector('input[type=email]'), msg=f.querySelector('[data-msg]');
257        if(!isEmail(inp.value)){inp.classList.add('invalid');if(msg){msg.style.color='var(--red)';msg.textContent='Enter a valid email.';}return;}
258        inp.classList.remove('invalid');
259        if(msg){msg.style.color='var(--green)';msg.textContent='You\u2019re in. Welcome to the spotlight. ✦';}
260        f.querySelector('button').textContent='Subscribed ✓';
261        inp.value='';inp.disabled=true;
262      });
263    });
264    // generic validated forms
265    document.querySelectorAll('[data-validate]').forEach(function(f){
266      f.addEventListener('submit',function(e){
267        e.preventDefault();var ok=true;
268        f.querySelectorAll('[required]').forEach(function(inp){
269          var bad = !inp.value.trim() || (inp.type==='email' && !isEmail(inp.value));
270          inp.classList.toggle('invalid',bad); if(bad)ok=false;
271        });
272        var msg=f.querySelector('[data-form-msg]');
273        if(!ok){if(msg){msg.style.color='var(--red)';msg.textContent='Please fill in the highlighted fields.';}return;}
274        var btn=f.querySelector('button[type=submit]');
275        var done=function(){
276          if(msg){msg.style.color='var(--green)';msg.textContent='Sent! We\u2019ll be in touch soon. ✦';}
277          if(btn){btn.textContent='Sent ✓';btn.disabled=true;}
278          f.querySelectorAll('.input').forEach(function(i){i.value='';i.disabled=true;});
279        };
280        var inbox=f.getAttribute('data-contact');
281        if(!inbox){done();return;}
282        // relay the message to the inbox via formsubmit.co (static site — no server of our own)
283        var data={_subject:'New message from monochroam.com',_template:'table'};
284        f.querySelectorAll('input[name],textarea[name]').forEach(function(inp){
285          if(inp.type==='radio'&&!inp.checked)return;
286          data[inp.name]=inp.value;
287        });
288        if(btn){btn.textContent='Sending\u2026';btn.disabled=true;}
289        fetch('https://formsubmit.co/ajax/'+inbox,{
290          method:'POST',
291          headers:{'Content-Type':'application/json','Accept':'application/json'},
292          body:JSON.stringify(data)
293        }).then(function(r){
294          if(!r.ok)throw new Error('bad status');
295          return r.json();
296        }).then(done).catch(function(){
297          if(btn){btn.innerHTML='Send message <span class="arrow">\u2192</span>';btn.disabled=false;}
298          if(msg){msg.style.color='var(--red)';msg.textContent='Couldn\u2019t send right now \u2014 please email [email protected] directly.';}
299        });
300      });
301    });
302    // clear invalid on type
303    document.addEventListener('input',function(e){ if(e.target.classList&&e.target.classList.contains('invalid'))e.target.classList.remove('invalid'); });
304  }
305
306  /* ---------- hero parallax ---------- */
307  var pxEls=[], pxBound=false;
308  function parallaxCollect(root){
309    (root||document).querySelectorAll('[data-parallax]').forEach(function(el){
310      if(pxEls.indexOf(el)<0) pxEls.push(el);
311    });
312  }
313  function parallax(){
314    parallaxCollect(document);
315    if(pxBound) return; pxBound=true;
316    window.addEventListener('scroll',function(){
317      if(document.documentElement.dataset.motion==='off')return;
318      var y=window.scrollY;
319      pxEls.forEach(function(el){var s=parseFloat(el.getAttribute('data-parallax'))||.15;el.style.transform='translateY('+(y*s)+'px)';});
320    },{passive:true});
321  }
322
323  /* ---------- public: bind behaviours on dynamically added content ---------- */
324  window.MC={
325    enhance:function(root){ tilt3d(root); parallaxCollect(root); }
326  };
327
328  /* ---------- TWEAKS ---------- */
329  var ACCENTS={
330    violet:['#6c4df6','#8a72ff'],
331    indigo:['#4f46e5','#7c79ff'],
332    orange:['#ff5e2c','#ff8a5c'],
333    green:['#1fb36b','#3fd98c']
334  };
335  var MOTION=['off','subtle','lively','maximal'];
336  function applyMotion(v){
337    document.documentElement.dataset.motion=v;
338    var scale = v==='maximal'?1.2:(v==='lively'?1:(v==='subtle'?0.7:0));
339    document.documentElement.style.setProperty('--motion-scale', scale||0.0001);
340  }
341  function applyAccent(k){
342    var a=ACCENTS[k]||ACCENTS.violet, r=document.documentElement.style;
343    r.setProperty('--violet',a[0]); r.setProperty('--violet-bright',a[1]);
344  }
345  function getState(){
346    var m=localStorage.getItem('mc_motion')||'maximal';
347    var a=localStorage.getItem('mc_accent')||'violet';
348    return {motion:m,accent:a};
349  }
350  function tweaks(){
351    var s=getState(); applyMotion(s.motion); applyAccent(s.accent);
352
353    var panel=document.createElement('div');
354    panel.id='tweaks-panel';
355    panel.style.cssText='position:fixed;right:18px;bottom:18px;z-index:9999;width:264px;background:rgba(15,15,19,.96);backdrop-filter:blur(14px);border:1px solid var(--border-strong);border-radius:18px;padding:18px;display:none;font-family:var(--font-body);box-shadow:0 24px 60px -20px rgba(0,0,0,.8)';
356    panel.innerHTML=
357      '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px">'+
358        '<strong style="font-family:var(--font-display);text-transform:uppercase;letter-spacing:.04em;font-size:.95rem">Tweaks</strong>'+
359        '<button id="tw-close" aria-label="Close" style="background:none;border:0;color:var(--text-dim);font-size:20px;line-height:1;cursor:pointer">×</button>'+
360      '</div>'+
361      '<div style="font-size:.74rem;letter-spacing:.14em;text-transform:uppercase;color:var(--text-faint);margin-bottom:8px;font-weight:700">Motion</div>'+
362      '<div id="tw-motion" style="display:grid;grid-template-columns:repeat(2,1fr);gap:6px;margin-bottom:18px"></div>'+
363      '<div style="font-size:.74rem;letter-spacing:.14em;text-transform:uppercase;color:var(--text-faint);margin-bottom:8px;font-weight:700">Accent</div>'+
364      '<div id="tw-accent" style="display:flex;gap:10px"></div>';
365    document.body.appendChild(panel);
366
367    function seg(host,opts,current,onpick){
368      host.innerHTML='';
369      opts.forEach(function(o){
370        var b=document.createElement('button');
371        b.textContent=o[0].toUpperCase()+o.slice(1);
372        b.style.cssText='padding:8px 6px;border-radius:9px;border:1px solid '+(o===current?'var(--violet-bright)':'var(--border)')+';background:'+(o===current?'var(--violet-soft)':'transparent')+';color:'+(o===current?'#fff':'var(--text-dim)')+';font-size:.82rem;font-weight:600;cursor:pointer;font-family:inherit;transition:.15s';
373        b.onclick=function(){onpick(o);};
374        host.appendChild(b);
375      });
376    }
377    function swatches(host,current,onpick){
378      host.innerHTML='';
379      Object.keys(ACCENTS).forEach(function(k){
380        var b=document.createElement('button');
381        b.setAttribute('aria-label',k);
382        b.style.cssText='width:38px;height:38px;border-radius:11px;cursor:pointer;background:linear-gradient(135deg,'+ACCENTS[k][0]+','+ACCENTS[k][1]+');border:2px solid '+(k===current?'#fff':'transparent')+';box-shadow:0 0 0 1px var(--border)';
383        b.onclick=function(){onpick(k);};
384        host.appendChild(b);
385      });
386    }
387    function render(){
388      var st=getState();
389      seg(panel.querySelector('#tw-motion'),MOTION,st.motion,function(v){localStorage.setItem('mc_motion',v);applyMotion(v);post();render();});
390      swatches(panel.querySelector('#tw-accent'),st.accent,function(k){localStorage.setItem('mc_accent',k);applyAccent(k);post();render();});
391    }
392    function post(){
393      try{var st=getState();window.parent.postMessage({type:'__edit_mode_set_keys',edits:{motion:st.motion,accent:st.accent}},'*');}catch(e){}
394    }
395    render();
396
397    panel.querySelector('#tw-close').onclick=function(){
398      panel.style.display='none';
399      try{window.parent.postMessage({type:'__edit_mode_dismissed'},'*');}catch(e){}
400    };
401    window.addEventListener('message',function(e){
402      var t=e.data&&e.data.type;
403      if(t==='__activate_edit_mode'){panel.style.display='block';render();}
404      else if(t==='__deactivate_edit_mode'){panel.style.display='none';}
405    });
406    try{window.parent.postMessage({type:'__edit_mode_available'},'*');}catch(e){}
407  }
408
409  /* ---------- boot ---------- */
410  function init(){ mount();reveals();accordions();counters();tilt();tilt3d();forms();parallax();tweaks(); }
411  if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',init); else init();
412})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.