first commit 2
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* ==========================================================================
|
||||
* 💡 베이스 모듈(Base Module) 전용 스타일시트
|
||||
* ==========================================================================
|
||||
*/
|
||||
|
||||
/* --- 1. 섹션 기본 스타일 --- */
|
||||
.item-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. 아이템 카드 그리드 --- */
|
||||
.item-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
/* --- 4. 개별 아이템 카드 --- */
|
||||
.item-card {
|
||||
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;
|
||||
}
|
||||
|
||||
.item-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* --- 5. 카드 내부 요소 --- */
|
||||
.item-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-image.is-patent {
|
||||
padding: 20px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.item-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.4s ease;
|
||||
}
|
||||
|
||||
.item-card:hover .item-image img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.item-info {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.item-info h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-info p {
|
||||
font-size: 0.95rem;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* --- 9. 스크롤 애니메이션 (공용) --- */
|
||||
.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-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;
|
||||
}
|
||||
|
||||
/* --- 13. [최종 수정] 모듈 스크롤바 완전 숨김 처리 --- */
|
||||
/* 1. 기본 상태: 스크롤바를 완전히 투명하게 만듭니다. */
|
||||
.image-modal {
|
||||
/* Firefox: 스크롤바 색상을 양쪽 모두 투명하게 설정 */
|
||||
scrollbar-color: transparent transparent;
|
||||
scrollbar-width: thin;
|
||||
transition: scrollbar-color 0.3s ease;
|
||||
}
|
||||
|
||||
.image-modal::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.image-modal::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.image-modal::-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 상태: 마우스를 올리면 스크롤바가 나타납니다. */
|
||||
.image-modal:hover {
|
||||
/* Firefox: 스크롤바 색상을 보이게 변경 */
|
||||
scrollbar-color: rgba(0, 0, 0, 0.4) transparent;
|
||||
}
|
||||
|
||||
.image-modal:hover::-webkit-scrollbar-thumb {
|
||||
/* Webkit: 스크롤바 막대 색상을 보이게 변경 */
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// TODO: 'initBaseModule'을 고유한 함수 이름으로 변경하세요. (예: initNewsModule)
|
||||
function initBaseModule(moduleId) {
|
||||
const moduleElement = document.getElementById(moduleId);
|
||||
if (!moduleElement || moduleElement.classList.contains('initialized')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const section = moduleElement.querySelector('.base-section');
|
||||
const itemGrid = moduleElement.querySelector('.item-grid');
|
||||
const modal = moduleElement.querySelector('.image-modal');
|
||||
|
||||
if (!section || !itemGrid || !modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
function renderGrid() {
|
||||
const itemsJson = section.dataset.items;
|
||||
let itemsData;
|
||||
|
||||
try {
|
||||
itemsData = JSON.parse(itemsJson);
|
||||
} catch (e) {
|
||||
itemGrid.innerHTML = '<p>데이터를 불러오는 데 실패했습니다.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!itemsData || itemsData.length === 0) {
|
||||
itemGrid.innerHTML = '<p>표시할 항목이 없습니다.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const itemCardsHTML = itemsData.map(item => `
|
||||
<div class="item-card js-modal-trigger" data-title="${item.title}" data-desc="${item.description}" data-img="${item.image}">
|
||||
<div class="item-image">
|
||||
<img src="${item.image}" alt="${item.title}">
|
||||
</div>
|
||||
<div class="item-info">
|
||||
<h3>${item.title}</h3>
|
||||
<p>${item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
itemGrid.innerHTML = itemCardsHTML;
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
function closeModal() {
|
||||
modal.classList.remove('is-active');
|
||||
}
|
||||
|
||||
itemGrid.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');
|
||||
}
|
||||
});
|
||||
|
||||
closeBtn.addEventListener('click', closeModal);
|
||||
modal.addEventListener('click', (e) => (e.target === modal) && closeModal());
|
||||
document.addEventListener('keydown', (e) => (e.key === 'Escape' && modal.classList.contains('is-active')) && closeModal());
|
||||
}
|
||||
|
||||
renderGrid();
|
||||
setupModalEvents();
|
||||
moduleElement.classList.add('initialized');
|
||||
}
|
||||
|
||||
// TODO: 'initBaseModule'을 고유한 함수 이름으로 변경하세요.
|
||||
window.initBaseModule = initBaseModule;
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// =================================================================================
|
||||
// 💡 [사용법] 이 파일을 복사하여 새 모듈을 만들 때 아래 TODO 항목들을 수정하세요.
|
||||
// =================================================================================
|
||||
|
||||
// TODO: 'board_name'을 실제 게시판 테이블명으로 변경하세요. (예: 'gallery', 'news' 등)
|
||||
$bo_table_name = 'board_name';
|
||||
|
||||
// TODO: 가져올 게시물 수를 변경할 수 있습니다.
|
||||
$limit = 3;
|
||||
|
||||
// --- 데이터 처리 ---
|
||||
$module_data = array();
|
||||
$sql = " SELECT wr_id, wr_subject, wr_content FROM {$g5['write_prefix']}{$bo_table_name} WHERE wr_is_comment = 0 ORDER BY wr_num, wr_reply LIMIT {$limit} ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
for ($i=0; $row=sql_fetch_array($result); $i++) {
|
||||
$files = get_file($bo_table_name, $row['wr_id']);
|
||||
$image_url = (isset($files[0]['path']) && isset($files[0]['file'])) ? $files[0]['path'].'/'.$files[0]['file'] : G5_THEME_URL.'/img/no_image.png';
|
||||
|
||||
$module_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
|
||||
);
|
||||
}
|
||||
$module_json = json_encode($module_data, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// CSS와 JS 파일의 버전을 파일 수정 시간으로 자동 갱신
|
||||
$module_css_path = G5_THEME_PATH.'/rb.custom/_base_module/module.css';
|
||||
$module_js_path = G5_THEME_PATH.'/rb.custom/_base_module/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 = 'base_module_'.uniqid();
|
||||
?>
|
||||
|
||||
<!-- 모듈의 가장 바깥 요소에 고유 ID를 부여합니다. -->
|
||||
<div id="<?php echo $module_id; ?>" class="base-module">
|
||||
<section class="item-section" data-items='<?php echo htmlspecialchars($module_json, ENT_QUOTES, 'UTF-8'); ?>'>
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<!-- TODO: 모듈의 제목과 설명을 수정하세요. -->
|
||||
<span class="subtitle">Module Subtitle</span>
|
||||
<h2>모듈 제목</h2>
|
||||
<p>이곳에 모듈에 대한 간단한 설명을 입력하세요.</p>
|
||||
</div>
|
||||
<div class="item-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/_item_module/module.css?ver=<?php echo $module_css_ver; ?>">
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const currentModuleId = '<?php echo $module_id; ?>';
|
||||
// TODO: 'inititemModule'을 고유한 함수 이름으로 변경하세요. (예: initNewsModule)
|
||||
const initFunctionName = 'inititemModule';
|
||||
// TODO: 'item-module-script'를 고유한 스크립트 ID로 변경하세요. (예: news-module-script)
|
||||
const scriptId = 'item-module-script';
|
||||
|
||||
if (document.getElementById(scriptId)) {
|
||||
if (typeof window[initFunctionName] === 'function') {
|
||||
window[initFunctionName](currentModuleId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.id = scriptId;
|
||||
// TODO: JS 파일 경로를 실제 모듈 경로로 수정하세요.
|
||||
script.src = '<?php echo G5_THEME_URL; ?>/rb.custom/_base_module/module.js?ver=<?php echo $module_js_ver; ?>';
|
||||
script.async = true;
|
||||
|
||||
script.onload = () => {
|
||||
if (typeof window[initFunctionName] === 'function') {
|
||||
window[initFunctionName](currentModuleId);
|
||||
}
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user