FAQ 게시판 만들기¶
개요¶
아코디언 스타일의 FAQ(자주 묻는 질문) 게시판을 만드는 방법을 설명합니다.
디렉토리 구조¶
skins/board/faq/
├── board.default.css
├── board.default.js
├── list.html
├── write_form.html
├── view_document.html
├── info.xml
└── img/
├── icon-q.png
└── icon-a.png
info.xml 설정¶
<?xml version="1.0" encoding="UTF-8"?>
<skin version="0.2">
<title xml:lang="ko">FAQ 게시판</title>
<title xml:lang="en">FAQ Board</title>
<description xml:lang="ko">아코디언 스타일의 FAQ 게시판 스킨</description>
<version>1.0.0</version>
<author email_address="dev@example.com" link="https://example.com">
<name xml:lang="ko">개발자</name>
</author>
<extra_vars>
<var id="expand_all" type="checkbox">
<title xml:lang="ko">모든 답변 펼치기</title>
<default>N</default>
</var>
<var id="use_category_tab" type="checkbox">
<title xml:lang="ko">카테고리 탭 사용</title>
<default>Y</default>
</var>
<var id="animation_speed" type="select">
<title xml:lang="ko">애니메이션 속도</title>
<options value="fast">빠르게</options>
<options value="normal">보통</options>
<options value="slow">느리게</options>
<default>normal</default>
</var>
<var id="icon_style" type="select">
<title xml:lang="ko">아이콘 스타일</title>
<options value="default">기본</options>
<options value="circle">원형</options>
<options value="square">사각형</options>
<default>circle</default>
</var>
<var id="search_position" type="select">
<title xml:lang="ko">검색창 위치</title>
<options value="top">상단</options>
<options value="bottom">하단</options>
<options value="both">상단과 하단</options>
<default>top</default>
</var>
</extra_vars>
</skin>
list.html - FAQ 목록¶
{@
$expand_all = $module_info->expand_all == 'Y';
$use_category_tab = $module_info->use_category_tab == 'Y';
$animation_speed = $module_info->animation_speed ?: 'normal';
$icon_style = $module_info->icon_style ?: 'circle';
$search_position = $module_info->search_position ?: 'top';
}
<load target="board.default.css" />
<load target="board.default.js" />
<div class="faq-board" data-expand-all="{$expand_all}" data-animation-speed="{$animation_speed}">
<!-- 제목 영역 -->
<div class="faq-header">
<h2>{$module_info->browser_title}</h2>
<!--@if($module_info->description)-->
<p class="description">{$module_info->description}</p>
<!--@end-->
</div>
<!-- 검색 영역 (상단) -->
<!--@if($search_position == 'top' || $search_position == 'both')-->
<div class="faq-search-box">
<form action="{getUrl()}" method="get" class="search-form">
<input type="hidden" name="vid" value="{$vid}" />
<input type="hidden" name="mid" value="{$mid}" />
<input type="hidden" name="category" value="{$category}" />
<div class="search-input-group">
<input type="text" name="search_keyword" value="{$search_keyword}"
placeholder="검색어를 입력하세요" class="search-input" />
<button type="submit" class="search-button">
<i class="xi-search"></i> 검색
</button>
</div>
</form>
<!-- 인기 검색어 -->
<!--@if($popular_tags)-->
<div class="popular-tags">
<span class="label">인기 검색어:</span>
<!--@foreach($popular_tags as $tag)-->
<a href="{getUrl('search_keyword', $tag->tag)}" class="tag">{$tag->tag}</a>
<!--@end-->
</div>
<!--@end-->
</div>
<!--@end-->
<!-- 카테고리 탭 -->
<!--@if($use_category_tab && $module_info->use_category == 'Y' && $category_list)-->
<div class="category-tabs">
<ul class="tab-list">
<li class="<!--@if(!$category)-->active<!--@end-->">
<a href="{getUrl('category','','page','')}" data-category="0">
전체 ({$total_count})
</a>
</li>
<!--@foreach($category_list as $val)-->
<li class="<!--@if($category == $val->category_srl)-->active<!--@end-->">
<a href="{getUrl('category', $val->category_srl, 'page', '')}"
data-category="{$val->category_srl}">
{$val->title} ({$val->document_count})
</a>
</li>
<!--@end-->
</ul>
</div>
<!--@end-->
<!-- FAQ 목록 -->
<div class="faq-list icon-style-{$icon_style}">
<!--@if(!$document_list || count($document_list) == 0)-->
<div class="no-data">
<i class="xi-info-o"></i>
<p>등록된 FAQ가 없습니다.</p>
</div>
<!--@else-->
<!--@foreach($document_list as $no => $document)-->
{@
// 답변 내용 추출 (확장변수 사용 시)
$answer = $document->getExtraValueHTML('answer') ?: $document->getContent(false);
$is_new = $document->isNew();
$is_updated = $document->isUpdated();
}
<div class="faq-item" data-document-srl="{$document->document_srl}">
<div class="faq-question">
<span class="icon icon-q">Q</span>
<div class="question-content">
<h3 class="title">
<!--@if($document->get('is_notice') == 'Y')-->
<span class="notice-badge">공지</span>
<!--@end-->
<!--@if($is_new)-->
<span class="new-badge">NEW</span>
<!--@end-->
<!--@if($is_updated)-->
<span class="update-badge">UPDATE</span>
<!--@end-->
{$document->getTitleText()}
</h3>
<!--@if($module_info->use_category == 'Y' && $document->get('category_srl'))-->
<span class="category">{$category_list[$document->get('category_srl')]->title}</span>
<!--@end-->
</div>
<button class="toggle-button" aria-expanded="false">
<i class="xi-angle-down"></i>
</button>
</div>
<div class="faq-answer" style="display: none;">
<span class="icon icon-a">A</span>
<div class="answer-content">
{$answer}
<!-- 첨부파일 -->
<!--@if($document->hasUploadedFiles())-->
<div class="attached-files">
<h4><i class="xi-paperclip"></i> 첨부파일</h4>
<ul>
{@$uploaded_list = $document->getUploadedFiles()}
<!--@foreach($uploaded_list as $file)-->
<li>
<a href="{$file->download_url}" class="file-link">
<i class="xi-download"></i>
{$file->source_filename}
<span class="file-size">({FileHandler::filesize($file->file_size)})</span>
</a>
</li>
<!--@end-->
</ul>
</div>
<!--@end-->
<!-- 메타 정보 -->
<div class="answer-meta">
<span class="author">작성자: {$document->getNickName()}</span>
<span class="date">작성일: {$document->getRegdate('Y.m.d')}</span>
<!--@if($document->get('last_update'))-->
<span class="update">수정일: {$document->getUpdate('Y.m.d')}</span>
<!--@end-->
<span class="views">조회수: {$document->get('readed_count')}</span>
</div>
<!-- 유용함 투표 -->
<div class="helpfulness">
<p>이 답변이 도움이 되셨나요?</p>
<button class="vote-button vote-up" data-vote="up">
<i class="xi-thumbs-up"></i> 예
<span class="count">({$document->get('voted_count')})</span>
</button>
<button class="vote-button vote-down" data-vote="down">
<i class="xi-thumbs-down"></i> 아니오
<span class="count">({$document->get('blamed_count')})</span>
</button>
</div>
</div>
</div>
</div>
<!--@end-->
<!--@end-->
</div>
<!-- 페이지네이션 -->
<!--@if($page_navigation)-->
<div class="pagination">
<a href="{getUrl('page','')}" class="first-page" title="첫 페이지">
<i class="xi-angle-left-min"></i>
</a>
<!--@if($page_navigation->first_page > 1)-->
<a href="{getUrl('page', $page_navigation->first_page - 1)}" class="prev-block">
<i class="xi-angle-left"></i>
</a>
<!--@end-->
<!--@foreach($page_navigation->page_list as $val)-->
<a href="{getUrl('page', $val)}" class="page-number <!--@if($page == $val)-->active<!--@end-->">
{$val}
</a>
<!--@end-->
<!--@if($page_navigation->last_page < $page_navigation->total_page)-->
<a href="{getUrl('page', $page_navigation->last_page + 1)}" class="next-block">
<i class="xi-angle-right"></i>
</a>
<!--@end-->
<a href="{getUrl('page', $page_navigation->last_page)}" class="last-page" title="마지막 페이지">
<i class="xi-angle-right-min"></i>
</a>
</div>
<!--@end-->
<!-- 검색 영역 (하단) -->
<!--@if($search_position == 'bottom' || $search_position == 'both')-->
<div class="faq-search-box bottom">
<!-- 상단과 동일한 검색 폼 -->
</div>
<!--@end-->
<!-- 관리자 버튼 -->
<!--@if($grant->write_document || $grant->manager)-->
<div class="admin-buttons">
<!--@if($grant->write_document)-->
<a href="{getUrl('act','dispBoardWrite')}" class="btn btn-primary">
<i class="xi-pen"></i> FAQ 작성
</a>
<!--@end-->
<!--@if($grant->manager)-->
<a href="{getUrl('act','dispBoardAdminBoardInfo')}" class="btn btn-secondary">
<i class="xi-cog"></i> 게시판 설정
</a>
<!--@end-->
</div>
<!--@end-->
</div>
board.default.css - 스타일시트¶
/* FAQ 게시판 기본 스타일 */
.faq-board {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
/* 헤더 */
.faq-header {
text-align: center;
margin-bottom: 40px;
}
.faq-header h2 {
font-size: 28px;
margin-bottom: 10px;
}
.faq-header .description {
color: #666;
font-size: 16px;
}
/* 검색 영역 */
.faq-search-box {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
}
.search-form {
max-width: 600px;
margin: 0 auto;
}
.search-input-group {
display: flex;
gap: 10px;
}
.search-input {
flex: 1;
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.search-button {
padding: 12px 24px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.search-button:hover {
background: #0056b3;
}
/* 인기 태그 */
.popular-tags {
margin-top: 15px;
text-align: center;
}
.popular-tags .label {
color: #666;
margin-right: 10px;
}
.popular-tags .tag {
display: inline-block;
padding: 5px 12px;
margin: 2px;
background: #e9ecef;
color: #495057;
border-radius: 20px;
text-decoration: none;
font-size: 13px;
transition: all 0.3s;
}
.popular-tags .tag:hover {
background: #007bff;
color: white;
}
/* 카테고리 탭 */
.category-tabs {
margin-bottom: 30px;
border-bottom: 2px solid #e9ecef;
}
.tab-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.tab-list li {
margin-right: 5px;
}
.tab-list a {
display: block;
padding: 12px 20px;
color: #666;
text-decoration: none;
border: 1px solid transparent;
border-bottom: none;
transition: all 0.3s;
}
.tab-list li.active a,
.tab-list a:hover {
color: #007bff;
background: #f8f9fa;
border-color: #e9ecef;
border-bottom: 2px solid white;
margin-bottom: -2px;
}
/* FAQ 아이템 */
.faq-item {
border: 1px solid #e9ecef;
border-radius: 8px;
margin-bottom: 10px;
overflow: hidden;
transition: box-shadow 0.3s;
}
.faq-item:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.faq-item.active {
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
/* 질문 영역 */
.faq-question {
display: flex;
align-items: center;
padding: 20px;
cursor: pointer;
background: #fff;
transition: background 0.3s;
}
.faq-question:hover {
background: #f8f9fa;
}
/* 아이콘 스타일 */
.icon {
flex-shrink: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 15px;
}
.icon-q {
background: #007bff;
color: white;
}
.icon-a {
background: #28a745;
color: white;
}
/* 아이콘 스타일 - 원형 */
.icon-style-circle .icon {
border-radius: 50%;
}
/* 아이콘 스타일 - 사각형 */
.icon-style-square .icon {
border-radius: 4px;
}
/* 질문 내용 */
.question-content {
flex: 1;
}
.question-content .title {
font-size: 16px;
margin: 0;
line-height: 1.5;
}
/* 배지 */
.notice-badge,
.new-badge,
.update-badge {
display: inline-block;
padding: 2px 8px;
font-size: 11px;
font-weight: normal;
border-radius: 3px;
margin-right: 5px;
}
.notice-badge {
background: #dc3545;
color: white;
}
.new-badge {
background: #007bff;
color: white;
}
.update-badge {
background: #ffc107;
color: #212529;
}
/* 카테고리 */
.category {
display: inline-block;
margin-left: 10px;
padding: 2px 8px;
background: #e9ecef;
color: #666;
font-size: 12px;
border-radius: 3px;
}
/* 토글 버튼 */
.toggle-button {
background: none;
border: none;
font-size: 20px;
color: #666;
cursor: pointer;
padding: 10px;
transition: transform 0.3s;
}
.faq-item.active .toggle-button {
transform: rotate(180deg);
}
/* 답변 영역 */
.faq-answer {
display: none;
padding: 0 20px 20px;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
}
.faq-answer {
display: flex;
align-items: flex-start;
}
.answer-content {
flex: 1;
line-height: 1.8;
}
/* 답변 내용 스타일 */
.answer-content p {
margin: 0 0 15px;
}
.answer-content ul,
.answer-content ol {
margin: 0 0 15px;
padding-left: 20px;
}
.answer-content img {
max-width: 100%;
height: auto;
margin: 10px 0;
}
/* 첨부파일 */
.attached-files {
margin-top: 20px;
padding: 15px;
background: white;
border-radius: 4px;
}
.attached-files h4 {
font-size: 14px;
margin: 0 0 10px;
}
.attached-files ul {
list-style: none;
margin: 0;
padding: 0;
}
.attached-files li {
margin-bottom: 5px;
}
.file-link {
color: #007bff;
text-decoration: none;
}
.file-link:hover {
text-decoration: underline;
}
.file-size {
color: #666;
font-size: 12px;
}
/* 메타 정보 */
.answer-meta {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #dee2e6;
font-size: 13px;
color: #666;
}
.answer-meta span {
margin-right: 15px;
}
/* 유용함 투표 */
.helpfulness {
margin-top: 20px;
padding: 15px;
background: white;
border-radius: 4px;
text-align: center;
}
.helpfulness p {
margin: 0 0 10px;
font-size: 14px;
}
.vote-button {
padding: 8px 16px;
margin: 0 5px;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.vote-button:hover {
background: #f8f9fa;
}
.vote-button.voted {
background: #007bff;
color: white;
border-color: #007bff;
}
/* 데이터 없음 */
.no-data {
text-align: center;
padding: 60px 20px;
color: #666;
}
.no-data i {
font-size: 48px;
color: #dee2e6;
margin-bottom: 20px;
}
/* 페이지네이션 */
.pagination {
text-align: center;
margin: 40px 0;
}
.pagination a {
display: inline-block;
padding: 8px 12px;
margin: 0 2px;
color: #666;
text-decoration: none;
border: 1px solid #dee2e6;
border-radius: 4px;
transition: all 0.3s;
}
.pagination a:hover,
.pagination a.active {
background: #007bff;
color: white;
border-color: #007bff;
}
/* 관리자 버튼 */
.admin-buttons {
text-align: center;
margin-top: 40px;
}
.btn {
display: inline-block;
padding: 10px 20px;
margin: 0 5px;
text-decoration: none;
border-radius: 4px;
transition: all 0.3s;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-primary:hover {
background: #0056b3;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #545b62;
}
/* 반응형 디자인 */
@media (max-width: 768px) {
.faq-board {
padding: 10px;
}
.faq-header h2 {
font-size: 24px;
}
.search-input-group {
flex-direction: column;
}
.search-button {
width: 100%;
}
.tab-list {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.faq-question {
padding: 15px;
}
.icon {
width: 32px;
height: 32px;
font-size: 14px;
}
.question-content .title {
font-size: 14px;
}
.answer-meta span {
display: block;
margin-bottom: 5px;
}
}
board.default.js - JavaScript¶
jQuery(function($) {
const $faqBoard = $('.faq-board');
const expandAll = $faqBoard.data('expand-all');
const animationSpeed = $faqBoard.data('animation-speed');
// 애니메이션 속도 설정
const speeds = {
fast: 200,
normal: 400,
slow: 600
};
const speed = speeds[animationSpeed] || 400;
// 모든 답변 펼치기 옵션
if (expandAll) {
$('.faq-answer').show();
$('.faq-item').addClass('active');
$('.toggle-button').attr('aria-expanded', 'true');
}
// FAQ 토글
$('.faq-question').on('click', function(e) {
e.preventDefault();
const $item = $(this).closest('.faq-item');
const $answer = $item.find('.faq-answer');
const $button = $item.find('.toggle-button');
const isExpanded = $button.attr('aria-expanded') === 'true';
// 다른 항목 닫기 (아코디언 효과)
if (!expandAll) {
$('.faq-item').not($item).removeClass('active');
$('.faq-answer').not($answer).slideUp(speed);
$('.toggle-button').not($button).attr('aria-expanded', 'false');
}
// 현재 항목 토글
$item.toggleClass('active');
$answer.slideToggle(speed);
$button.attr('aria-expanded', !isExpanded);
});
// 직접 링크로 접근 시 해당 FAQ 열기
const hash = window.location.hash;
if (hash) {
const documentSrl = hash.replace('#faq-', '');
const $targetItem = $(`.faq-item[data-document-srl="${documentSrl}"]`);
if ($targetItem.length) {
$targetItem.addClass('active');
$targetItem.find('.faq-answer').show();
$targetItem.find('.toggle-button').attr('aria-expanded', 'true');
// 스크롤 이동
$('html, body').animate({
scrollTop: $targetItem.offset().top - 100
}, 500);
}
}
// 검색 기능 강화
let searchTimer;
$('.search-input').on('input', function() {
clearTimeout(searchTimer);
const keyword = $(this).val().toLowerCase();
searchTimer = setTimeout(function() {
if (keyword.length >= 2) {
$('.faq-item').each(function() {
const $item = $(this);
const title = $item.find('.title').text().toLowerCase();
const content = $item.find('.answer-content').text().toLowerCase();
if (title.includes(keyword) || content.includes(keyword)) {
$item.show();
// 검색어 하이라이트
highlightKeyword($item, keyword);
} else {
$item.hide();
}
});
// 검색 결과 없음 표시
if ($('.faq-item:visible').length === 0) {
if (!$('.no-search-result').length) {
$('.faq-list').append(`
<div class="no-search-result">
<i class="xi-search"></i>
<p>"${keyword}"에 대한 검색 결과가 없습니다.</p>
</div>
`);
}
} else {
$('.no-search-result').remove();
}
} else {
$('.faq-item').show();
$('.no-search-result').remove();
removeHighlight();
}
}, 300);
});
// 검색어 하이라이트
function highlightKeyword($element, keyword) {
const regex = new RegExp(`(${keyword})`, 'gi');
$element.find('.title, .answer-content').each(function() {
const $this = $(this);
const html = $this.html();
const highlighted = html.replace(regex, '<mark>$1</mark>');
$this.html(highlighted);
});
}
// 하이라이트 제거
function removeHighlight() {
$('mark').each(function() {
const $this = $(this);
$this.replaceWith($this.text());
});
}
// 유용함 투표
$('.vote-button').on('click', function(e) {
e.stopPropagation();
const $button = $(this);
const $item = $button.closest('.faq-item');
const documentSrl = $item.data('document-srl');
const voteType = $button.data('vote');
// 이미 투표한 경우
if ($button.hasClass('voted')) {
alert('이미 평가하셨습니다.');
return;
}
// AJAX 요청
$.ajax({
url: request_uri,
type: 'POST',
data: {
module: 'document',
act: voteType === 'up' ? 'procDocumentVoteUp' : 'procDocumentVoteDown',
target_srl: documentSrl
},
success: function(response) {
if (response.error) {
alert(response.message);
} else {
$button.addClass('voted');
const $count = $button.find('.count');
const currentCount = parseInt($count.text().match(/\d+/)[0]);
$count.text(`(${currentCount + 1})`);
// 다른 버튼 비활성화
$button.siblings('.vote-button').prop('disabled', true);
}
}
});
});
// 카테고리 필터 (AJAX)
$('.tab-list a').on('click', function(e) {
if ($(this).data('ajax') === 'true') {
e.preventDefault();
const $link = $(this);
const categorySrl = $link.data('category');
// 탭 활성화
$('.tab-list li').removeClass('active');
$link.parent().addClass('active');
// FAQ 목록 로드
$.ajax({
url: request_uri,
type: 'GET',
data: {
mid: current_mid,
category: categorySrl,
page: 1
},
beforeSend: function() {
$('.faq-list').html('<div class="loading"><i class="xi-spinner-3 xi-spin"></i> 로딩 중...</div>');
},
success: function(response) {
const $newContent = $(response).find('.faq-list').html();
$('.faq-list').html($newContent);
// 이벤트 재바인딩
bindEvents();
}
});
}
});
// 키보드 접근성
$('.faq-item').attr('tabindex', '0');
$('.faq-item').on('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
$(this).find('.faq-question').click();
}
});
// 인쇄 시 모든 답변 표시
window.onbeforeprint = function() {
$('.faq-answer').show();
};
window.onafterprint = function() {
if (!expandAll) {
$('.faq-item:not(.active) .faq-answer').hide();
}
};
});
write_form.html - FAQ 작성 폼¶
<load target="write_form.css" />
<div class="faq-write-form">
<h2>FAQ 작성</h2>
<form action="{getUrl()}" method="post" enctype="multipart/form-data">
<input type="hidden" name="module" value="board" />
<input type="hidden" name="act" value="procBoardInsertDocument" />
<input type="hidden" name="mid" value="{$mid}" />
<input type="hidden" name="document_srl" value="{$document_srl}" />
<!-- 카테고리 -->
<!--@if($module_info->use_category == 'Y' && $category_list)-->
<div class="form-group">
<label for="category_srl">카테고리 <span class="required">*</span></label>
<select name="category_srl" id="category_srl" required>
<option value="">카테고리 선택</option>
<!--@foreach($category_list as $val)-->
<option value="{$val->category_srl}" <!--@if($oDocument->get('category_srl') == $val->category_srl)-->selected<!--@end-->>
{$val->title}
</option>
<!--@end-->
</select>
</div>
<!--@end-->
<!-- 질문 -->
<div class="form-group">
<label for="title">질문 <span class="required">*</span></label>
<input type="text" name="title" id="title" value="{$oDocument->getTitleText()}"
placeholder="자주 묻는 질문을 입력하세요" required />
<p class="help-text">명확하고 구체적인 질문을 작성해주세요.</p>
</div>
<!-- 답변 -->
<div class="form-group">
<label for="content">답변 <span class="required">*</span></label>
{@$oDocument->getEditor()}
<p class="help-text">상세하고 이해하기 쉬운 답변을 작성해주세요.</p>
</div>
<!-- 첨부파일 -->
<!--@if($module_info->use_file_box)-->
<div class="form-group">
<label>첨부파일</label>
{@$oDocument->printUploadForm()}
</div>
<!--@end-->
<!-- 추가 옵션 -->
<div class="form-options">
<!--@if($grant->manager)-->
<label>
<input type="checkbox" name="is_notice" value="Y"
<!--@if($oDocument->isNotice())-->checked<!--@end--> />
공지사항으로 등록
</label>
<!--@end-->
<label>
<input type="checkbox" name="notify_message" value="Y"
<!--@if($oDocument->useNotify())-->checked<!--@end--> />
댓글 알림 받기
</label>
</div>
<!-- 버튼 -->
<div class="form-buttons">
<button type="submit" class="btn btn-primary">
<i class="xi-check"></i> 저장
</button>
<a href="{getUrl('act','')}" class="btn btn-secondary">
<i class="xi-close"></i> 취소
</a>
</div>
</form>
</div>
<style>
.faq-write-form {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.form-group {
margin-bottom: 25px;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 8px;
}
.form-group input[type="text"],
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.required {
color: #dc3545;
}
.help-text {
font-size: 13px;
color: #666;
margin-top: 5px;
}
.form-options {
margin: 20px 0;
}
.form-options label {
display: block;
margin-bottom: 10px;
}
.form-buttons {
text-align: center;
margin-top: 30px;
}
</style>