PageSourceSearch

https://www.st-grp.co.jp/js/index.js?260331

js st-grp.co.jp collected 2026-10-02 03:46:54 UTC 19,006 bytes, 548 lines download raw bytes

1// --- ここから mainVisual モバイル高さ調整 ---
2
3// DOMが読み込まれたら実行
4document.addEventListener('DOMContentLoaded', () => {
5  const header = document.querySelector('#global_header');
6  const mainVisual = document.querySelector('.main-visual');
7
8  // ヘッダーまたはメインビジュアルが存在しない場合は処理を中断
9  if (!header || !mainVisual) {
10    return;
11  }
12
13  // モバイルブレークポイントを定義
14  const MOBILE_BREAKPOINT = 767;
15
16  // 1vh相当の高さを計算し、CSSカスタムプロパティ --vh に設定する関数
17  const setViewportUnit = () => {
18    // 現在のウィンドウ幅を取得
19    const currentWidth = window.innerWidth;
20
21    // モバイルサイズ以下の場合のみ処理を実行
22    if (currentWidth <= MOBILE_BREAKPOINT) {
23      // 表示領域の高さ(100vh相当)を計算
24      let vh = window.innerHeight * 0.01;
25      
26      // CSSカスタムプロパティ --vh に値を設定
27      // これにより、index.cssで 100*var(--vh) の計算が可能になります。
28      document.documentElement.style.setProperty('--vh', `${vh}px`);
29      
30      // main-visualのmarginTopはCSSで制御するため、JavaScriptから削除
31      // mainVisual.style.marginTop = `${header.offsetHeight}px`;
32    } else {
33      // デスクトップサイズの場合はカスタムプロパティをリセット
34      document.documentElement.style.removeProperty('--vh');
35      // mainVisual.style.marginTop もリセット(CSSで設定された値を使用)
36    }
37  };
38
39  // ページ読み込み時とウィンドウリサイズ時に高さを再計算
40  window.addEventListener('resize', setViewportUnit);
41  setViewportUnit(); // 初期読み込み時にも実行
42});
43
44// --- ここまで mainVisual モバイル高さ調整 ---
45
46// --- ここから Slider ---
47$(function () {
48	$('#gh_nav_home').addClass('is-active');
49
50	$('#top_visual').slick({
51		dots: true,
52		fade: true,
53		autoplay: true,
54		autoplaySpeed: 4000,
55		slidesToShow: 1,
56		slidesToScroll: 1,
57		infinite: true,
58		speed: 800,
59	});
60
61	$('#special_bnn_wrap ul').slick({
62		dots: true,
63		appendArrows: '#special_bnn',
64		appendDots: '#special_bnn',
65		autoplay: true,
66		autoplaySpeed: 4000,
67		slidesToShow: 4,
68		slidesToScroll: 1,
69		infinite: true,
70		speed: 800,
71		responsive: [
72			{
73				breakpoint: 641,
74				settings: {
75					centerMode: true,
76					slidesToShow: 2,
77				},
78			},
79			{
80				breakpoint: 450,
81				settings: {
82					centerMode: true,
83					slidesToShow: 1,
84				},
85			},
86		],
87	});
88});
89
90// --- ここから Top Slider ---
91$(document).ready(function () {
92	$('.top-slider').slick({
93		infinite: true,
94		speed: 500,
95		slidesToShow: 3, //スライドの数「2.5 → 3」に変更_260309
96		centerMode: true,
97		//centerPadding: '225px',
98		centerPadding: '0',
99		dots: false,
100		arrows: true,
101		prevArrow: '<button type="button" class="slick-prev"></button>',
102		nextArrow: '<button type="button" class="slick-next"></button>',
103		responsive: [
104			{
105				breakpoint: 1024,
106				settings: {
107					slidesToShow: 3, //スライドの数「2 → 3」に変更_260309
108					centerPadding: '100px',
109					arrows: true,
110				},
111			},
112			{
113				breakpoint: 767,
114				settings: {
115					slidesToShow: 1,
116					centerPadding: '130px',
117					arrows: true,
118				},
119			},
120			{
121				breakpoint: 640,
122				settings: {
123					slidesToShow: 1,
124					centerPadding: '40px',
125					arrows: true,
126				},
127			},
128		],
129	});
130});
131// --- ここまで Top Slider ---
132
133// --- ここから Group Synergy ---
134$(document).ready(function () {
135	// --- DOM要素の取得 ---
136	const $prevImg = $('#prev-image-container img');
137	const $mainImg = $('#main-image-container img');
138	const $nextImg = $('#next-image-container img');
139	const $prevContainer = $('#prev-image-container');
140	const $mainContainer = $('#main-image-container');
141	const $nextContainer = $('#next-image-container');
142	const $navItems = $('.slide-nav-item');
143	const $sliderWrapper = $('.slider-content-wrapper'); // スワイプイベント用
144	const $detailTrack = $('.detail-slider-track');
145	const $detailButton = $('.detail-info-box .detail-button'); // ボタン要素を取得
146	const $mainImageLink = $('#main-image-link');
147	const $textContentLink = $('#text-content-link');
148	const $root = $(':root'); // CSS変数を操作するため
149
150	// --- スライドデータ ---
151	// HTMLのdata属性からスライドデータを動的に生成
152	const slideData = $navItems
153		.map(function () {
154			return $(this).data();
155		})
156		.get();
157	const totalSlides = slideData.length;
158
159	// --- 状態管理 ---
160	let currentIndex = 0;
161	let isAnimating = false;
162	const animationDuration = 400; // アニメーションの時間を短くして速度を上げる
163
164	/**
165	 * 指定したインデックスにスライドを移動させる
166	 * @param {number} newIndex - 新しいスライドのインデックス
167	 * @param {number} [explicitDirection] - アニメーション方向を強制する場合に指定 (1: 次へ, -1: 前へ)
168	 */
169	function goToSlide(newIndex, explicitDirection) {
170		if (isAnimating || newIndex === currentIndex) return;
171
172		// アニメーションの方向を決定
173		let direction = explicitDirection;
174		if (!direction) {
175			// explicitDirectionが未指定の場合、インデックスの差から方向を計算
176			const diff = newIndex - currentIndex;
177			if (Math.abs(diff) > 1) {
178				// ラップアラウンドする場合 (例: 0 -> 4, 4 -> 0)
179				direction = diff > 0 ? -1 : 1;
180			} else {
181				direction = diff;
182			}
183		}
184
185		isAnimating = true;
186		// CSS変数を設定してアニメーションの方向を決定
187		$root.get(0).style.setProperty('--direction', direction);
188
189		// アニメーション開始時の画像を設定(次の画像を事前に読み込ませる)
190		updateDisplay(currentIndex, newIndex, direction);
191
192		// --- テキストアニメーションの準備 ---
193		// is-activeでない方のスライドを次のコンテンツ用に準備
194		const $activeTextSlide = $detailTrack.find('.detail-content-slide.is-active');
195		const $nextTextSlide = $detailTrack.find('.detail-content-slide:not(.is-active)');
196
197		// 次のスライドの情報をセット
198		const newData = slideData[newIndex];
199		$nextTextSlide.find('.slider-title').text(newData.title);
200		$nextTextSlide.find('.slider-subtitle').text(newData.subtitle);
201		$nextTextSlide.find('.slider-description').text(newData.desc);
202
203		// 方向に応じてスライドの順序とアニメーションの向きを決定
204		if (direction === 1) {
205			$detailTrack.removeClass('is-reversing');
206			$nextTextSlide.insertAfter($activeTextSlide);
207		} else {
208			$detailTrack.addClass('is-reversing');
209			$nextTextSlide.insertBefore($activeTextSlide);
210		}
211
212		// ボタンのリンク先を更新
213		$detailButton.attr('href', newData.link);
214		$mainImageLink.attr('href', newData.link);
215		$textContentLink.attr('href', newData.link);
216
217		// アニメーション(プログレスを0から1へ)
218		// イージング関数 (cubic-bezier(0.65, 0, 0.35, 1))
219		const ease = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);
220		let startTime = null;
221		function animate(currentTime) {
222			if (!startTime) startTime = currentTime;
223			const elapsedTime = currentTime - startTime;
224			const progress = Math.min(elapsedTime / animationDuration, 1);
225
226			// CSS変数 --progress を更新
227			const easedProgress = ease(progress);
228			$root.get(0).style.setProperty(
229				'--progress',
230				easedProgress // 画像スライダー
231			);
232			$root.get(0).style.setProperty(
233				'--text-progress',
234				easedProgress // テキストスライダー
235			);
236
237			if (progress < 1) {
238				requestAnimationFrame(animate);
239			} else {
240				// アニメーション終了処理
241				currentIndex = newIndex;
242				// アニメーション完了後の最終状態に更新
243				updateDisplay(currentIndex);
244				// プログレスとtransformをリセット
245				$root.get(0).style.setProperty('--progress', 0);
246				$root.get(0).style.setProperty('--text-progress', 0);
247
248				// --- テキストアニメーションの後処理 ---
249				$activeTextSlide.removeClass('is-active');
250				$nextTextSlide.addClass('is-active');
251
252				isAnimating = false;
253			}
254		}
255		requestAnimationFrame(animate);
256	}
257
258	/**
259	 * 表示をまとめて更新する関数
260	 * @param {number} currentIdx 表示の基準となるスライドのインデックス
261	 * @param {number} [nextActiveIdx] アニメーション中にアクティブになるスライドのインデックス
262	 * @param {number} [direction] アニメーションの方向 (1 or -1)
263	 */
264	function updateDisplay(currentIdx, nextActiveIdx, direction) {
265		// 無限ループのためのインデックス計算
266		const prevIndex = (currentIdx - 1 + totalSlides) % totalSlides;
267		const nextIndex = (currentIdx + 1) % totalSlides;
268
269		// 1. 画像ソースを更新
270		$prevImg.attr('src', slideData[prevIndex].src);
271		$mainImg.attr('src', slideData[currentIdx].src);
272		$nextImg.attr('src', slideData[nextIndex].src);
273
274		// 疑似要素に次の画像を設定
275		if (nextActiveIdx !== undefined && direction !== undefined) {
276			const nextPrevIndex = (prevIndex + direction + totalSlides) % totalSlides;
277			const nextMainIndex = (currentIdx + direction + totalSlides) % totalSlides;
278			const nextNextIndex = (nextIndex + direction + totalSlides) % totalSlides;
279			$prevContainer.get(0).style.setProperty('--next-src', `url(${slideData[nextPrevIndex].src})`);
280			$mainContainer.get(0).style.setProperty('--next-src', `url(${slideData[nextMainIndex].src})`);
281			$nextContainer.get(0).style.setProperty('--next-src', `url(${slideData[nextNextIndex].src})`);
282		}
283
284		// 2. テキストを更新
285		// アニメーション中は次のテキストを、完了後は現在のテキストを表示
286		const textDisplayIndex = nextActiveIdx !== undefined ? nextActiveIdx : currentIdx;
287		if (nextActiveIdx === undefined) {
288			// アニメーション完了時のみDOMを更新
289			const currentData = slideData[textDisplayIndex];
290			const $activeSlide = $detailTrack.find('.detail-content-slide.is-active');
291			$activeSlide.find('.slider-title').text(currentData.title);
292			$activeSlide.find('.slider-subtitle').text(currentData.subtitle);
293			$activeSlide.find('.slider-description').text(currentData.desc);
294			$detailButton.attr('href', currentData.link);
295			$('#main-image-link').attr('href', currentData.link);
296			$('#text-content-link').attr('href', currentData.link);
297		}
298
299		// 3. ナビゲーションのハイライトを更新
300		$navItems.removeClass('is-active');
301		$navItems.eq(textDisplayIndex).addClass('is-active');
302	}
303
304	// --- イベントリスナーの設定 ---
305
306	// 左側のタイトルナビゲーション
307	$navItems.on('click', function () {
308		const index = $(this).data('index');
309		goToSlide(index);
310	});
311
312	// カスタム矢印:前へ
313	$('#custom-prev').on('click', function () {
314		const newIndex = (currentIndex - 1 + totalSlides) % totalSlides;
315		goToSlide(newIndex, -1); // 方向を-1(前へ)と明示
316	});
317
318	// カスタム矢印:次へ
319	$('#custom-next').on('click', function () {
320		const newIndex = (currentIndex + 1) % totalSlides;
321		goToSlide(newIndex, 1); // 方向を1(次へ)と明示
322	});
323
324	// スワイプ操作
325	let touchStartX = 0;
326	let touchEndX = 0;
327	const swipeThreshold = 50; // スワイプと判定する最小移動ピクセル数
328
329	$sliderWrapper.on('touchstart', function (e) {
330		touchStartX = e.originalEvent.changedTouches[0].screenX;
331	});
332
333	$sliderWrapper.on('touchend', function (e) {
334		touchEndX = e.originalEvent.changedTouches[0].screenX;
335		handleSwipe();
336	});
337
338	function handleSwipe() {
339		const swipeDistance = touchEndX - touchStartX;
340		if (swipeDistance > swipeThreshold) {
341			// 右へスワイプ -> 前のスライドへ
342			$('#custom-prev').trigger('click');
343		} else if (swipeDistance < -swipeThreshold) {
344			// 左へスワイプ -> 次のスライドへ
345			$('#custom-next').trigger('click');
346		}
347	}
348
349	// マウスドラッグ操作
350	let mouseStartX = 0;
351	let mouseEndX = 0;
352	let isDragging = false; // マウスがドラッグ中かどうかを追跡するフラグ
353
354	$sliderWrapper.on('mousedown', function (e) {
355		// 左クリックのみを対象とする
356		if (e.button !== 0) return;
357
358		isDragging = true;
359		mouseStartX = e.clientX;
360		// デフォルトのブラウザのドラッグ動作(画像ドラッグやテキスト選択)を防止
361		e.preventDefault();
362	});
363
364	$(document).on('mousemove', function (e) {
365		if (!isDragging) return;
366		mouseEndX = e.clientX;
367	});
368
369	$(document).on('mouseup', function (e) {
370		if (!isDragging) return;
371		isDragging = false;
372		mouseEndX = e.clientX;
373		handleMouseDrag();
374	});
375
376	// ドラッグが終了した際に、開始位置と終了位置をリセット
377	function handleMouseDrag() {
378		const dragDistance = mouseEndX - mouseStartX;
379		// handleSwipeと同じロジックでクリックイベントをトリガー
380		if (dragDistance > swipeThreshold) {
381			$('#custom-prev').trigger('click'); // 右へドラッグ -> 前のスライドへ
382		} else if (dragDistance < -swipeThreshold) {
383			$('#custom-next').trigger('click'); // 左へドラッグ -> 次のスライドへ
384		}
385		// 次のドラッグのために座標をリセット
386		mouseStartX = 0;
387		mouseEndX = 0;
388	}
389
390	// --- 初期化 ---
391	function init() {
392		// CSS変数を初期化
393		$root.get(0).style.setProperty('--progress', 0);
394		$root.get(0).style.setProperty('--direction', 1);
395		$root.get(0).style.setProperty('--text-progress', 0);
396		// 初期表示
397		$detailTrack.find('.detail-content-slide').first().addClass('is-active');
398		updateDisplay(currentIndex);
399	}
400
401	init();
402});
403// --- ここまで Group Synergy ---
404
405// --- ここから News ---
406document.addEventListener('alpine:init', () => {
407	Alpine.data('newsData', () => ({
408		newsItems: [],
409		selectedTab: '新着情報',
410		tabs: [
411			{ name: '新着情報', categories: ['all'] },
412			{
413				name: 'ニュースリリース',
414				// フィルタリングに必要な情報を保持
415				target_key: 'news_release',
416				//categories: ['ニュースリリース', 'プレスリリース', '建材事業', 'マテリアル事業', '商業施設事業', '国際事業', '領域拡大'],
417				categories: ['ニュースリリース', 'プレスリリース', 'お知らせ', , '大切なお知らせ', '建材事業', 'マテリアル事業', '商業施設事業', '国際事業', '領域拡大'],
418			},
419			{
420				name: 'IR情報',
421				// フィルタリングに必要な情報を保持
422				target_key: 'disclosure',
423				//categories: ['開示情報', '開示資料', 'お知らせ', '株主総会', '株主通信'],
424				categories: ['開示情報', '開示資料', 'IRお知らせ', '株主総会', '株主通信'],
425			},
426		],
427		// 特定のcategoryの色を変更
428		categoryStyles: {
429			建材事業: 'is-group',
430			マテリアル事業: 'is-group',
431			商業施設事業: 'is-group',
432			国際事業: 'is-group',
433			領域拡大: 'is-group',
434			開示資料: 'is-accounts',
435			お知らせ: 'is-notice',
436			大切なお知らせ: 'is-notice',
437			IRお知らせ: 'is-notice',
438			株主総会: 'is-meeting',
439			株主通信: 'is-stockholder',
440		},
441
442		fetchNews(url) {
443			fetch(url)
444				.then((response) => response.json())
445				.then((data) => {
446					this.newsItems = data.map((item) => ({
447						...item,
448						title_html: item.title ? item.title.replace(/\n/g, '<br>') : '',
449						targetBlank: item.targetBlank === true,
450					}));
451				});
452		},
453
454		// 選択されたカテゴリに基づいてニュースをフィルタリング
455		filteredNews() {
456			const sortedNews = [...this.newsItems].sort((a, b) => {
457				const dateA = a.date.replace(/\./g, '');
458				const dateB = b.date.replace(/\./g, '');
459				return dateB.localeCompare(dateA);
460			});
461
462			const currentTab = this.tabs.find((tab) => tab.name === this.selectedTab);
463
464			if (!currentTab) {
465				return [];
466			}
467
468			if (currentTab.categories.includes('all')) {
469				return sortedNews.slice(0, 6);
470			} else {
471				const targetKey = currentTab.target_key; // target_key を取得
472
473				const filtered = sortedNews.filter((item) => {
474					// 【重要】 カテゴリが一致するか、または tab_target が一致するかでフィルタリング
475					// target_key が定義されているタブの場合、tab_target もチェックする
476					const isCategoryMatch = currentTab.categories.includes(item.category);
477					const isTargetMatch = targetKey && (item.tab_target === targetKey || item.tab_target === 'both');
478
479					// 「ニュースリリース」と「開示情報」タブでは、カテゴリと tab_target の両方、またはどちらかでフィルタリング
480					// ここでは tab_target を優先して、完全に分離します。
481					// ただし、建材事業などのニュースは tab_target が無くても category で表示させる必要があるため、
482					// カテゴリによるフィルタリングも維持します。
483
484					// 1. categoryがtabsのcategoriesに含まれる AND
485					// 2. JSONで定義された tab_target が currentTabのtarget_keyに一致するか'both'である
486
487					if (currentTab.name === 'ニュースリリース') {
488						// ニュースリリース関連タブのカテゴリ(建材事業など)をチェック
489						if (currentTab.categories.includes(item.category)) {
490							// ただし、「開示情報」は除外する
491							if (
492								item.category === '開示情報' ||
493								item.category === '開示資料' ||
494								item.category === 'IRお知らせ' ||
495								item.category === '株主総会' ||
496								item.category === '株主通信'
497							) {
498								return false;
499							}
500							return true;
501						}
502					}
503
504					// 「IR情報」タブの場合
505					if (currentTab.name === 'IR情報') {
506						// 開示情報関連のカテゴリをチェック
507						if (currentTab.categories.includes(item.category)) {
508							return true;
509						}
510					}
511
512					// JSONに tab_target がある場合は、それをフィルタリングの主要な条件とします。
513					return item.tab_target === targetKey || item.tab_target === 'both';
514				});
515				return filtered.slice(0, 6);
516			}
517		},
518	}));
519});
520
521// --- ここまで News ---
522
523// --- hero-section movie ---
524document.addEventListener('DOMContentLoaded', () => {
525	const hero = document.getElementById('hero-section');
526	const video = document.getElementById('hero-video');
527	const playBtn = document.getElementById('play-button');
528	const muteBtn = document.getElementById('mute-button');
529
530	hero.classList.add('is-playing');
531
532	// 再生・一時停止ボタンクリック
533	playBtn.addEventListener('click', () => {
534		if (video.paused) {
535			video.play();
536			hero.classList.add('is-playing');
537		} else {
538			video.pause();
539			hero.classList.remove('is-playing');
540		}
541	});
542
543	// 動画が終了した時の処理(ループ設定にしているが、手動で戻す場合など)
544	video.addEventListener('ended', () => {
545		// 必要に応じて初期状態に戻す
546		hero.classList.remove('is-playing');
547	});
548});

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.