first commit 2

This commit is contained in:
hmw1001
2026-06-11 18:47:38 +09:00
parent c768729ce6
commit 6f534e33a6
11095 changed files with 1595758 additions and 0 deletions
@@ -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">&times;</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>