studio.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. // Studio page — vanilla fade-carousel + tab sync + per-slide choreography.
  2. // Replaces Bootstrap Carousel/Tab, jQuery, and backstretch. Keeps DrawFillSVG
  3. // (window.DrawFillSVG, self-exposed from the v4-script bundle).
  4. // --- shared chrome: navbar hide/show on scroll (ported from jQuery dw_hidenav) ---
  5. (function () {
  6. const navbar = document.querySelector('.navbar');
  7. if (!navbar) return;
  8. const title = document.querySelector('.site-title a');
  9. const headerHeight = navbar.offsetHeight;
  10. let previousTop = 0;
  11. window.addEventListener('scroll', function () {
  12. const currentTop = window.scrollY;
  13. if (currentTop < previousTop) {
  14. if (currentTop > 0 && navbar.classList.contains('fixed')) {
  15. navbar.classList.add('visible');
  16. if (title) title.classList.remove('light');
  17. } else {
  18. navbar.classList.remove('visible', 'fixed');
  19. if (title) title.classList.add('light');
  20. }
  21. } else {
  22. navbar.classList.remove('visible');
  23. if (currentTop > headerHeight && !navbar.classList.contains('fixed')) {
  24. navbar.classList.add('fixed');
  25. }
  26. }
  27. previousTop = currentTop;
  28. }, { passive: true });
  29. })();
  30. document.querySelectorAll('.nav-toggle').forEach(function (el) {
  31. el.addEventListener('click', function (e) {
  32. e.preventDefault();
  33. el.classList.toggle('active');
  34. });
  35. });
  36. // --- studio fade-carousel ---
  37. (function () {
  38. const stage = document.getElementById('studio-caro');
  39. const caro = document.getElementById('caro');
  40. if (!stage || !caro) return;
  41. const card = stage.querySelector('.dw-studio-stage') || stage; // contained rounded hero card that carries the per-slide bg
  42. const slides = Array.from(caro.querySelectorAll('.dw-fade-slide'));
  43. const tabs = Array.from(document.querySelectorAll('.dw-studio-tab'));
  44. const panes = Array.from(document.querySelectorAll('#studio-tab .tab-pane'));
  45. const bg = stage.querySelector('.dw-studio-bg');
  46. const video = stage.querySelector('.dw-studio-video'); // camera → Media slide
  47. const tv = stage.querySelector('.dw-studio-tv'); // grayscale → fact intro
  48. const title = document.querySelector('.site-title a');
  49. const studioTab = tabs.find((t) => t.getAttribute('href') === '#studio') || tabs[0];
  50. const GRADS = ['caro-grad', 'caro-grad2', 'caro-grad3', 'caro-grad5', 'caro-grad-dadada'];
  51. // per slide: gradient class · background image · show video · navbar-title light
  52. // Slides 0-4 are the tabbed sections; 5-7 the fact intro; 8 the Projects hero.
  53. const CONFIG = [
  54. { grad: 'caro-grad5', img: stage.dataset.bg0 || null, video: false, light: true, filter: 'url(#dw-duotone-slate)' }, // 0 Studio (slate-duotone hero)
  55. { grad: 'caro-grad2', img: null, video: false, light: true }, // 1 Web/Dev (was UX)
  56. { grad: 'caro-grad3', img: null, video: false, light: false }, // 2 Graphic
  57. { grad: 'caro-grad5', img: null, video: true, light: true }, // 3 Media
  58. { grad: 'caro-grad5', img: stage.dataset.bg5 || null, video: false, light: true }, // 4 Art
  59. { grad: 'caro-grad5', img: null, video: false, tv: true, light: true }, // 5 fact: web sites
  60. { grad: 'caro-grad5', img: null, video: false, tv: true, light: true }, // 6 fact: stars
  61. { grad: 'caro-grad5', img: null, video: false, tv: true, light: true }, // 7 fact: brain
  62. { grad: 'caro-grad-dadada', img: null, video: false, light: true }, // 8 Projects (#dadada card)
  63. ];
  64. const INTERVAL = 5000;
  65. let index = 0;
  66. let timer = null;
  67. function drawSVGs(slide) {
  68. if (!window.DrawFillSVG) return;
  69. slide.querySelectorAll('svg[id]').forEach(function (svg) {
  70. try {
  71. new window.DrawFillSVG({ elementId: svg.id }).replay();
  72. } catch (e) {
  73. /* svg not animatable — skip */
  74. }
  75. });
  76. }
  77. // Set the hero carousel slide (slide visibility, gradient, bg image, video/tv,
  78. // navbar-title colour). Does NOT touch the subnav tabs or content panes — those
  79. // are driven by setActiveTab, so the Wonder auto-cycle and the content-only
  80. // Projects tab can move the hero and the active tab independently.
  81. function setHero(i) {
  82. i = (i + slides.length) % slides.length;
  83. index = i;
  84. const cfg = CONFIG[i];
  85. slides.forEach((s, n) => {
  86. const on = n === i;
  87. s.classList.toggle('active', on);
  88. // reveal the active slide's hidden content (.animac.hide-svg, #brain.hide-svg …)
  89. if (on) s.querySelectorAll('.hide-svg').forEach((el) => el.classList.remove('hide-svg'));
  90. });
  91. GRADS.forEach((g) => card.classList.remove(g));
  92. card.classList.add(cfg.grad);
  93. if (bg) {
  94. bg.style.backgroundImage = cfg.img ? 'url("' + cfg.img + '")' : '';
  95. bg.style.filter = cfg.filter || ''; // slate duotone on the Studio hero only
  96. }
  97. if (video) {
  98. if (cfg.video) {
  99. video.style.display = 'block';
  100. video.play().catch(function () {});
  101. } else {
  102. video.pause();
  103. video.style.display = 'none';
  104. }
  105. }
  106. if (tv) {
  107. if (cfg.tv) {
  108. tv.style.display = 'block';
  109. tv.play().catch(function () {});
  110. } else {
  111. tv.pause();
  112. tv.style.display = 'none';
  113. }
  114. }
  115. if (title) title.classList.toggle('light', cfg.light);
  116. drawSVGs(slides[i]);
  117. }
  118. // Mark a subnav tab active and show its matching content pane, matched by the
  119. // tab's href hash → pane id (#studio → id="studio", #projects → id="projects").
  120. // Tabs with no pane (Wonder) simply hide every pane.
  121. function setActiveTab(tab) {
  122. tabs.forEach((t) => t.classList.toggle('active', t === tab));
  123. const hash = tab ? tab.getAttribute('href') || '' : '';
  124. const id = hash.charAt(0) === '#' ? hash.slice(1) : '';
  125. panes.forEach((p) => p.classList.toggle('active', p.id === id));
  126. }
  127. // "Craft" (the .dw-studio-wonder tab) auto-rotates the hero through the fact
  128. // slides (5,6,7) once and stops on the last, leaving the Craft tab + pane active.
  129. // Every other tab just switches on click — no autoplay.
  130. const WONDER = [5, 6, 7];
  131. let wonderTimer = null;
  132. function stopWonder() { if (wonderTimer) { window.clearTimeout(wonderTimer); wonderTimer = null; } }
  133. function runWonder() {
  134. stopWonder();
  135. let step = 0;
  136. (function tick() {
  137. setHero(WONDER[step]);
  138. step++;
  139. if (step < WONDER.length) wonderTimer = window.setTimeout(tick, INTERVAL);
  140. // ends on the last fact slide — the Craft tab + its pane stay active
  141. })();
  142. }
  143. tabs.forEach(function (t) {
  144. t.addEventListener('click', function (e) {
  145. e.preventDefault();
  146. stopWonder();
  147. setActiveTab(t);
  148. if (t.classList.contains('dw-studio-wonder')) runWonder();
  149. else setHero(parseInt(t.dataset.slide, 10) || 0);
  150. });
  151. });
  152. // No autoplay — the carousel advances only on subnav clicks (Wonder runs its own set).
  153. setActiveTab(studioTab);
  154. setHero(0);
  155. })();