first commit 2
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* ==========================================================================
|
||||
* 💡 tech_section 모듈 전용 스타일시트
|
||||
* ==========================================================================
|
||||
*/
|
||||
|
||||
/* --- 1. 섹션 기본 스타일 --- */
|
||||
.products-trend-section {
|
||||
width: 100%;
|
||||
padding: 80px 0;
|
||||
background-color: #ffffff; /* 또는 원하는 배경색 */
|
||||
}
|
||||
|
||||
/* --- 2. 섹션 헤더 (제목, 부제) --- */
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.section-header .subtitle {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #0056b3; /* 포인트 색상 */
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 36px;
|
||||
font-weight: 900;
|
||||
color: #25282B; /* 기본 텍스트 색상 */
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.section-header p {
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
color: #666;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* --- 3. 제품 카드 그리드 --- */
|
||||
.product-grid {
|
||||
display: grid;
|
||||
/* 💡 화면 크기에 따라 1~3개의 컬럼으로 자동 조정됩니다. */
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
/* --- 4. 개별 제품 카드 --- */
|
||||
.product-card-trend {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.product-card-trend:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* --- 5. 카드 내부 요소 --- */
|
||||
.product-image-trend {
|
||||
width: 100%;
|
||||
/* 💡 이미지 비율을 4:3으로 유지합니다. */
|
||||
aspect-ratio: 4 / 3;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.product-image-trend.is-patent {
|
||||
padding: 20px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.product-image-trend img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* 이미지가 잘리지 않고 꽉 차도록 설정 */
|
||||
transition: transform 0.4s ease;
|
||||
}
|
||||
|
||||
.product-card-trend:hover .product-image-trend img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.product-info-trend {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.product-info-trend h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.product-info-trend p {
|
||||
font-size: 0.95rem;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* --- 9. 스크롤 애니메이션 (style_prestige_1.css에서 가져옴) --- */
|
||||
.reveal-up, .reveal-fade {
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||
}
|
||||
.reveal-up { transform: translateY(40px); }
|
||||
.reveal-fade { transform: scale(0.95); }
|
||||
.reveal-up.is-revealed, .reveal-fade.is-revealed {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
/* --- 10. [모듈 전용] 이미지 팝업 모달 스타일 (개선) --- */
|
||||
.image-modal {
|
||||
display: none; /* 기본적으로 숨김 */
|
||||
position: fixed;
|
||||
z-index: 1050;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto; /* 내용이 클 경우 스크롤 */
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40px 20px; /* 화면 가장자리와 여백 */
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.image-modal.is-active {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
width: auto; /* 너비를 자동으로 설정 */
|
||||
max-width: 100%; /* 화면 너비를 넘지 않도록 설정 */
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,.5);
|
||||
animation: modal-slide-down 0.4s ease-out;
|
||||
display: flex; /* 내부 요소 정렬을 위해 flex 사용 */
|
||||
flex-direction: column; /* 이미지와 텍스트를 세로로 쌓음 */
|
||||
}
|
||||
|
||||
@keyframes modal-slide-down {
|
||||
from {
|
||||
transform: translateY(-50px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content .modal-image {
|
||||
display: block;
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
object-fit: contain;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.modal-info {
|
||||
padding: 20px 25px;
|
||||
max-width: 800px; /* 텍스트 영역의 최대 너비는 제한 */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-info .modal-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.modal-info .modal-desc {
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 25px;
|
||||
font-size: 35px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.5);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.close-btn:hover,
|
||||
.close-btn:focus {
|
||||
color: #ccc;
|
||||
text-decoration: none;
|
||||
}
|
||||
/* --- 11. [개선] 가독성 향상을 위한 줄바꿈 처리 --- */
|
||||
.section-header h2,
|
||||
.section-header p,
|
||||
.product-info-trend h3,
|
||||
.product-info-trend p,
|
||||
.item-info h3,
|
||||
.item-info p {
|
||||
word-break: keep-all;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
/* --- 12. [개선] 카드 설명 텍스트 잘림 방지 및 높이 고정 --- */
|
||||
.product-info-trend p,
|
||||
.item-info p {
|
||||
height: 3.04rem;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.6;
|
||||
}
|
||||
/* -------------------------------------------------- */
|
||||
/* 💡 [최종 수정] 모달 스크롤바 완전 숨김 처리 */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
/* 1. 기본 상태: 스크롤바를 완전히 투명하게 만듭니다. */
|
||||
.rb-modal-overlay,
|
||||
.rb-modal-body {
|
||||
/* Firefox: 스크롤바 색상을 양쪽 모두 투명하게 설정 */
|
||||
scrollbar-color: transparent transparent;
|
||||
scrollbar-width: thin;
|
||||
transition: scrollbar-color 0.3s ease; /* 부드러운 효과를 위해 추가 */
|
||||
}
|
||||
|
||||
.rb-modal-overlay::-webkit-scrollbar,
|
||||
.rb-modal-body::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.rb-modal-overlay::-webkit-scrollbar-track,
|
||||
.rb-modal-body::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.rb-modal-overlay::-webkit-scrollbar-thumb,
|
||||
.rb-modal-body::-webkit-scrollbar-thumb {
|
||||
/* Webkit: 스크롤바 막대를 투명하게 */
|
||||
background-color: transparent;
|
||||
border-radius: 10px;
|
||||
border: 3px solid transparent;
|
||||
background-clip: padding-box;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
/* 2. :hover 상태: 마우스를 올리면 스크롤바가 나타납니다. */
|
||||
.rb-modal-overlay:hover,
|
||||
.rb-modal-body:hover {
|
||||
/* Firefox: 스크롤바 색상을 보이게 변경 */
|
||||
scrollbar-color: rgba(0, 0, 0, 0.4) transparent;
|
||||
}
|
||||
|
||||
.rb-modal-overlay:hover::-webkit-scrollbar-thumb,
|
||||
.rb-modal-body:hover::-webkit-scrollbar-thumb {
|
||||
/* Webkit: 스크롤바 막대 색상을 보이게 변경 */
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// 💡 [핵심 수정] 모든 로직을 초기화 함수 안으로 옮기고, 모듈의 고유 ID를 인자로 받도록 변경합니다.
|
||||
function initTechSectionModule(moduleId) {
|
||||
// 💡 [핵심 수정] document가 아닌, 전달받은 moduleId를 기준으로 요소를 찾습니다.
|
||||
const moduleElement = document.getElementById(moduleId);
|
||||
if (!moduleElement) return;
|
||||
|
||||
// 이미 초기화된 모듈은 다시 실행하지 않습니다.
|
||||
if (moduleElement.classList.contains('initialized')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const section = moduleElement.querySelector('.products-trend-section');
|
||||
const patentGrid = moduleElement.querySelector('.product-grid');
|
||||
const modal = moduleElement.querySelector('.image-modal');
|
||||
|
||||
if (!section || !patentGrid || !modal) {
|
||||
console.error('Module elements not found in:', moduleId);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 그리드에 특허 카드를 렌더링하는 함수
|
||||
*/
|
||||
function renderGrid() {
|
||||
const patentsJson = section.dataset.patents;
|
||||
let patentsData;
|
||||
|
||||
try {
|
||||
patentsData = JSON.parse(patentsJson);
|
||||
} catch (e) {
|
||||
patentGrid.innerHTML = '<p style="text-align:center; padding: 40px 0; color: #d9534f;">기술 자료를 불러오는 데 실패했습니다.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!patentsData || patentsData.length === 0) {
|
||||
patentGrid.innerHTML = '<p style="text-align:center; padding: 40px 0; color: #888;">표시할 기술 자료가 없습니다.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const patentCardsHTML = patentsData.map(item => `
|
||||
<div class="product-card-trend js-modal-trigger" data-title="${item.title}" data-desc="${item.description}" data-img="${item.image}">
|
||||
<div class="product-image-trend is-patent">
|
||||
<img src="${item.image}" alt="${item.title}">
|
||||
</div>
|
||||
<div class="product-info-trend">
|
||||
<h3>${item.title}</h3>
|
||||
<p>${item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
patentGrid.innerHTML = patentCardsHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* 모달 관련 이벤트를 설정하는 함수
|
||||
*/
|
||||
function setupModalEvents() {
|
||||
const modalImage = modal.querySelector('.modal-image');
|
||||
const modalTitle = modal.querySelector('.modal-title');
|
||||
const modalDesc = modal.querySelector('.modal-desc');
|
||||
const closeBtn = modal.querySelector('.close-btn');
|
||||
|
||||
// 모달 열기 (이벤트 위임)
|
||||
patentGrid.addEventListener('click', function(event) {
|
||||
const card = event.target.closest('.js-modal-trigger');
|
||||
if (card) {
|
||||
modalImage.src = card.dataset.img;
|
||||
modalTitle.textContent = card.dataset.title;
|
||||
modalDesc.textContent = card.dataset.desc;
|
||||
modal.classList.add('is-active');
|
||||
}
|
||||
});
|
||||
|
||||
// 모달 닫기 함수
|
||||
function closeModal() {
|
||||
modal.classList.remove('is-active');
|
||||
}
|
||||
|
||||
// 이벤트 리스너 등록
|
||||
closeBtn.addEventListener('click', closeModal);
|
||||
modal.addEventListener('click', function(event) {
|
||||
if (event.target === modal) closeModal();
|
||||
});
|
||||
// ESC 키 이벤트는 document에 등록해야 하므로, 모달이 활성화 상태일 때만 동작하도록 조건 추가
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Escape' && modal.classList.contains('is-active')) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 함수 실행
|
||||
renderGrid();
|
||||
setupModalEvents();
|
||||
|
||||
// 초기화 완료 클래스 추가
|
||||
moduleElement.classList.add('initialized');
|
||||
}
|
||||
|
||||
// 💡 [핵심 수정] 다른 곳에서 이 함수를 다시 호출할 수 있도록 전역에 노출시킵니다.
|
||||
window.initTechSectionModule = initTechSectionModule;
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
|
||||
// --- 데이터 처리 ---
|
||||
$patents_data = array();
|
||||
$sql = " SELECT wr_id, wr_subject, wr_content FROM {$g5['write_prefix']}patents WHERE wr_is_comment = 0 ORDER BY wr_num, wr_reply LIMIT 3 ";
|
||||
$result = sql_query($sql);
|
||||
for ($i=0; $row=sql_fetch_array($result); $i++) {
|
||||
$file = get_file('patents', $row['wr_id']);
|
||||
$image_url = (isset($file[0]['path']) && isset($file[0]['file'])) ? $file[0]['path'].'/'.$file[0]['file'] : G5_THEME_URL.'/img/no_image.png';
|
||||
|
||||
$patents_data[] = array(
|
||||
'id' => $row['wr_id'],
|
||||
'title' => get_text($row['wr_subject']),
|
||||
'description' => get_text(cut_str(strip_tags($row['wr_content']), 100)),
|
||||
'image' => $image_url
|
||||
);
|
||||
}
|
||||
$patents_json = json_encode($patents_data, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// 💡 [개선] CSS와 JS 파일의 버전을 파일 수정 시간으로 자동 갱신하여 캐시 관리를 최적화합니다.
|
||||
$module_css_path = G5_THEME_PATH.'/rb.custom/tech_section/module.css';
|
||||
$module_js_path = G5_THEME_PATH.'/rb.custom/tech_section/module.js';
|
||||
$module_css_ver = file_exists($module_css_path) ? filemtime($module_css_path) : G5_CSS_VER;
|
||||
$module_js_ver = file_exists($module_js_path) ? filemtime($module_js_path) : G5_JS_VER;
|
||||
|
||||
// 💡 [핵심] 이 모듈만의 고유 ID를 생성합니다.
|
||||
$module_id = 'tech_section_'.uniqid();
|
||||
?>
|
||||
|
||||
<!-- 💡 [핵심 수정] 모듈의 가장 바깥 요소에 고유 ID를 부여합니다. -->
|
||||
<div id="<?php echo $module_id; ?>" class="tech-section-module">
|
||||
<!-- TECH: 기술력 및 특허 섹션 -->
|
||||
<section class="products-trend-section" data-patents='<?php echo htmlspecialchars($patents_json, ENT_QUOTES, 'UTF-8'); ?>'>
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<span class="subtitle">Technology</span>
|
||||
<h2>문 하나에 사람을 담았습니다.</h2>
|
||||
<p>당신의 공간이 더욱 안전하고 조용해지도록, 우리는 문 하나를 넘어서고 있습니다.</p>
|
||||
</div>
|
||||
<div class="product-grid">
|
||||
<!-- JS로 특허증 카드 생성 -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 💡 [핵심 수정] 모듈 내부에 모달 HTML을 포함시킵니다. -->
|
||||
<div class="image-modal">
|
||||
<div class="modal-content">
|
||||
<span class="close-btn">×</span>
|
||||
<img class="modal-image" src="" alt="제품 이미지">
|
||||
<div class="modal-info">
|
||||
<h3 class="modal-title"></h3>
|
||||
<p class="modal-desc"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 이 모듈에 필요한 CSS 파일을 불러옵니다. -->
|
||||
<link rel="stylesheet" href="<?php echo G5_THEME_URL; ?>/rb.custom/tech_section/module.css?ver=<?php echo $module_css_ver; ?>">
|
||||
|
||||
<?php // 💡 [핵심 수정] AJAX로 로드될 때도 스크립트가 확실하게 실행되도록 동적 로딩 방식으로 변경합니다. ?>
|
||||
<script>
|
||||
(function() {
|
||||
// 💡 [핵심 수정] 이 모듈의 고유 ID를 자바스크립트로 전달합니다.
|
||||
const currentModuleId = '<?php echo $module_id; ?>';
|
||||
const scriptId = 'tech-section-module-script';
|
||||
|
||||
// 스크립트가 이미 로드되었는지 확인 (중복 실행 방지)
|
||||
if (document.getElementById(scriptId)) {
|
||||
// 이미 로드되었다면, 초기화 함수만 다시 호출
|
||||
if (typeof window.initTechSectionModule === 'function') {
|
||||
window.initTechSectionModule(currentModuleId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.id = scriptId;
|
||||
script.src = '<?php echo G5_THEME_URL; ?>/rb.custom/tech_section/module.js?ver=<?php echo $module_js_ver; ?>';
|
||||
script.async = true;
|
||||
|
||||
// 💡 [핵심 수정] 스크립트 로드가 완료되면, 고유 ID를 인자로 전달하여 초기화 함수를 호출합니다.
|
||||
script.onload = () => {
|
||||
if (typeof window.initTechSectionModule === 'function') {
|
||||
window.initTechSectionModule(currentModuleId);
|
||||
}
|
||||
};
|
||||
|
||||
script.onerror = () => {
|
||||
console.error('Failed to load tech_section/module.js');
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user