API 목록, API 상세 페이지 적용
This commit is contained in:
@@ -4060,6 +4060,694 @@ select.form-control {
|
|||||||
animation: countUp 0.6s ease-out forwards;
|
animation: countUp 0.6s ease-out forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.api-market-container {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #F8FAFC;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-market-sidebar {
|
||||||
|
width: 280px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-right: 1px solid #E2E8F0;
|
||||||
|
padding: 40px 24px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.api-market-sidebar {
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.api-market-sidebar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 60px;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||||
|
z-index: 500;
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
}
|
||||||
|
.api-market-sidebar.mobile-open {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-sidebar-nav .menu-section {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .menu-title {
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2E7FF7;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .menu-title:hover {
|
||||||
|
background-color: #F8FAFC;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .menu-title.active {
|
||||||
|
background-color: #EFF6FF;
|
||||||
|
color: #2E7FF7;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .menu-title .api-count {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 8px;
|
||||||
|
background-color: rgba(75, 155, 255, 0.1);
|
||||||
|
color: #4B9BFF;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 50px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .menu-title.active .api-count {
|
||||||
|
background-color: #4B9BFF;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .api-list {
|
||||||
|
padding-left: 16px;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .api-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #64748B;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .api-item::before {
|
||||||
|
content: "•";
|
||||||
|
margin-right: 8px;
|
||||||
|
color: #4B9BFF;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .api-item:hover {
|
||||||
|
background-color: rgba(75, 155, 255, 0.05);
|
||||||
|
color: #1A1A2E;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .api-item:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .api-item.active {
|
||||||
|
background-color: rgba(75, 155, 255, 0.1);
|
||||||
|
color: #4B9BFF;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .api-item.active::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.api-sidebar-nav .api-item .api-name {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-market-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 40px 48px;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.api-market-content {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-mobile-toggle {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||||
|
z-index: 501;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.api-mobile-toggle:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2);
|
||||||
|
}
|
||||||
|
.api-mobile-toggle:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.api-mobile-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-market-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.api-market-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-market-title h1 {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #64748B;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.api-market-title h2 {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1A1A2E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-market-search {
|
||||||
|
position: relative;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.api-market-search {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.api-market-search form {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.api-market-search input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 48px 12px 16px;
|
||||||
|
border: 1px solid #E2E8F0;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.api-market-search input:focus {
|
||||||
|
border-color: #4B9BFF;
|
||||||
|
box-shadow: 0 0 0 3px rgba(75, 155, 255, 0.1);
|
||||||
|
}
|
||||||
|
.api-market-search input::placeholder {
|
||||||
|
color: #94A3B8;
|
||||||
|
}
|
||||||
|
.api-market-search .search-submit-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: #64748B;
|
||||||
|
}
|
||||||
|
.api-market-search .search-submit-btn:hover {
|
||||||
|
background: #EFF6FF;
|
||||||
|
color: #4B9BFF;
|
||||||
|
transform: translateY(-50%) scale(1.05);
|
||||||
|
}
|
||||||
|
.api-market-search .search-submit-btn:active {
|
||||||
|
transform: translateY(-50%) scale(0.95);
|
||||||
|
}
|
||||||
|
.api-market-search .search-submit-btn .search-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-result-count {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #64748B;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
.api-result-count strong {
|
||||||
|
color: #4B9BFF;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.api-card-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 32px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 220px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.api-card::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: left;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
.api-card:hover {
|
||||||
|
box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
.api-card:hover::before {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
.api-card:hover .api-card-icon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
.api-card:active {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: #F8FAFC;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748B;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.api-card-badge::before {
|
||||||
|
content: "◉";
|
||||||
|
color: #4B9BFF;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1A1A2E;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-description {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #64748B;
|
||||||
|
line-height: 1.6;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-icon {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 24px;
|
||||||
|
font-size: 48px;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
.api-card-icon img {
|
||||||
|
max-width: 60px;
|
||||||
|
max-height: 60px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card:nth-child(3n+1) .api-card-icon {
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card:nth-child(3n+2) .api-card-icon {
|
||||||
|
color: #34d399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card:nth-child(3n+3) .api-card-icon {
|
||||||
|
color: #818cf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 80px 24px;
|
||||||
|
color: #64748B;
|
||||||
|
}
|
||||||
|
.api-empty-state .empty-icon {
|
||||||
|
font-size: 80px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.api-empty-state h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1A1A2E;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.api-empty-state p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #64748B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 80px;
|
||||||
|
}
|
||||||
|
.api-loading .spinner {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 4px solid #E2E8F0;
|
||||||
|
border-top-color: #4B9BFF;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.api-mobile-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 60px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 499;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.api-mobile-overlay {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.api-mobile-overlay.active {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-detail-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-detail-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
border-bottom: 2px solid #E2E8F0;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
.api-detail-tabs .tab-button {
|
||||||
|
padding: 16px 24px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #64748B;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
bottom: -2px;
|
||||||
|
}
|
||||||
|
.api-detail-tabs .tab-button:hover {
|
||||||
|
color: #4B9BFF;
|
||||||
|
background-color: rgba(75, 155, 255, 0.05);
|
||||||
|
}
|
||||||
|
.api-detail-tabs .tab-button.active {
|
||||||
|
color: #4B9BFF;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom-color: #4B9BFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
.tab-content.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-overview-card {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 32px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
.api-overview-card .api-overview-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.api-overview-card .api-overview-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.api-overview-card .api-method-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.api-overview-card .api-endpoint {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.api-overview-card .api-endpoint code {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #F8FAFC;
|
||||||
|
border: 1px solid #E2E8F0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: "Courier New", monospace;
|
||||||
|
color: #1A1A2E;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.api-overview-card .api-simple-description {
|
||||||
|
padding: 16px 0;
|
||||||
|
border-bottom: 1px solid #E2E8F0;
|
||||||
|
}
|
||||||
|
.api-overview-card .api-simple-description p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #64748B;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.api-overview-card .api-overview-info h4 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1A1A2E;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-details-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-detail-card {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 32px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.api-detail-card:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1);
|
||||||
|
}
|
||||||
|
.api-detail-card h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1A1A2E;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 2px solid #EFF6FF;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #64748B;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content pre {
|
||||||
|
background-color: #F8FAFC;
|
||||||
|
border: 1px solid #E2E8F0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content pre code {
|
||||||
|
font-family: "Courier New", monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #1A1A2E;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 16px 0;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border: 1px solid #E2E8F0;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table thead {
|
||||||
|
background-color: #EFF6FF;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1A1A2E;
|
||||||
|
border-bottom: 2px solid #E2E8F0;
|
||||||
|
background-color: rgba(75, 155, 255, 0.05);
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table th:not(:last-child) {
|
||||||
|
border-right: 1px solid #E2E8F0;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table td {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #64748B;
|
||||||
|
border-bottom: 1px solid #E2E8F0;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table td:not(:last-child) {
|
||||||
|
border-right: 1px solid #E2E8F0;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table tr {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table tr:hover {
|
||||||
|
background-color: rgba(75, 155, 255, 0.02);
|
||||||
|
}
|
||||||
|
.api-detail-card .detail-content table tbody tr:nth-child(even) {
|
||||||
|
background-color: rgba(248, 250, 252, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-info-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.api-info-table tr {
|
||||||
|
border-bottom: 1px solid #E2E8F0;
|
||||||
|
}
|
||||||
|
.api-info-table tr:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.api-info-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1A1A2E;
|
||||||
|
width: 30%;
|
||||||
|
background-color: #F8FAFC;
|
||||||
|
}
|
||||||
|
.api-info-table td {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #64748B;
|
||||||
|
}
|
||||||
|
.api-info-table td code {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background-color: #F8FAFC;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: "Courier New", monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4B9BFF;
|
||||||
|
}
|
||||||
|
|
||||||
.login-page {
|
.login-page {
|
||||||
min-height: calc(100vh - 140px);
|
min-height: calc(100vh - 140px);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -33,6 +33,7 @@
|
|||||||
@import 'pages/index';
|
@import 'pages/index';
|
||||||
@import 'pages/home';
|
@import 'pages/home';
|
||||||
@import 'pages/api-portal';
|
@import 'pages/api-portal';
|
||||||
|
@import 'pages/api-market';
|
||||||
@import 'pages/documentation';
|
@import 'pages/documentation';
|
||||||
@import 'pages/login';
|
@import 'pages/login';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,780 @@
|
|||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// API Market Page - Modern Sidebar + Card Grid Layout
|
||||||
|
// Reference: design/api-market.html
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.api-market-container {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: $gray-bg;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sidebar Navigation
|
||||||
|
.api-market-sidebar {
|
||||||
|
width: 280px;
|
||||||
|
background-color: $white;
|
||||||
|
border-right: 1px solid $border-gray;
|
||||||
|
padding: $spacing-2xl $spacing-lg;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-md) {
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 60px; // Below mobile header
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
box-shadow: $shadow-lg;
|
||||||
|
z-index: $z-index-modal;
|
||||||
|
height: calc(100vh - 60px); // Full height minus header
|
||||||
|
|
||||||
|
&.mobile-open {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-sidebar-nav {
|
||||||
|
.menu-section {
|
||||||
|
margin-bottom: $spacing-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-title {
|
||||||
|
padding: 12px $spacing-md;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
color: $secondary-blue;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
transition: $transition-base;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $gray-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $light-bg;
|
||||||
|
color: $secondary-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-count {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 $spacing-sm;
|
||||||
|
background-color: rgba($primary-blue, 0.1);
|
||||||
|
color: $primary-blue;
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
border-radius: $border-radius-full;
|
||||||
|
transition: $transition-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .api-count {
|
||||||
|
background-color: $primary-blue;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level 2: API List
|
||||||
|
.api-list {
|
||||||
|
padding-left: $spacing-md;
|
||||||
|
margin-top: $spacing-xs;
|
||||||
|
margin-bottom: $spacing-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-item {
|
||||||
|
padding: 8px $spacing-md;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: $text-gray;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: $border-radius-sm;
|
||||||
|
transition: $transition-base;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '•';
|
||||||
|
margin-right: $spacing-sm;
|
||||||
|
color: $primary-blue;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($primary-blue, 0.05);
|
||||||
|
color: $text-dark;
|
||||||
|
padding-left: calc($spacing-md + 4px);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: rgba($primary-blue, 0.1);
|
||||||
|
color: $primary-blue;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-name {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main Content Area
|
||||||
|
.api-market-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: $spacing-2xl $spacing-3xl;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
padding: $spacing-lg $spacing-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile Menu Toggle
|
||||||
|
.api-mobile-toggle {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
bottom: $spacing-lg;
|
||||||
|
right: $spacing-lg;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
background: $gradient-primary;
|
||||||
|
border-radius: $border-radius-circle;
|
||||||
|
border: none;
|
||||||
|
color: $white;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: $shadow-lg;
|
||||||
|
z-index: $z-index-modal + 1;
|
||||||
|
transition: $transition-base;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: $shadow-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header Section
|
||||||
|
.api-market-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: $spacing-2xl;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: $spacing-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-market-title {
|
||||||
|
h1 {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: $text-gray;
|
||||||
|
font-weight: $font-weight-regular;
|
||||||
|
margin-bottom: $spacing-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: $font-size-2xl;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
color: $text-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search Box
|
||||||
|
.api-market-search {
|
||||||
|
position: relative;
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 48px 12px $spacing-md;
|
||||||
|
border: 1px solid $border-gray;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
outline: none;
|
||||||
|
transition: $transition-base;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: $primary-blue;
|
||||||
|
box-shadow: 0 0 0 3px rgba($primary-blue, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: $text-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-submit-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: $transition-base;
|
||||||
|
color: $text-gray;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $light-bg;
|
||||||
|
color: $primary-blue;
|
||||||
|
transform: translateY(-50%) scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(-50%) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result Count
|
||||||
|
.api-result-count {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
color: $text-gray;
|
||||||
|
margin-bottom: $spacing-xl;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: $primary-blue;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card Grid
|
||||||
|
.api-card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: $spacing-lg;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Card
|
||||||
|
.api-card {
|
||||||
|
background: $white;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
padding: $spacing-xl;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
|
transition: $transition-base;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 220px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: $gradient-primary;
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: left;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: $shadow-lg;
|
||||||
|
transform: translateY(-5px);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-icon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $spacing-sm;
|
||||||
|
margin-bottom: $spacing-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: $gray-bg;
|
||||||
|
border-radius: $border-radius-sm;
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
color: $text-gray;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "◉";
|
||||||
|
color: $primary-blue;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-title {
|
||||||
|
font-size: $font-size-md;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
color: $text-dark;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: $line-height-tight;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-description {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: $text-gray;
|
||||||
|
line-height: $line-height-normal;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card-icon {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: $spacing-lg;
|
||||||
|
font-size: 48px;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 60px;
|
||||||
|
max-height: 60px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon color variations
|
||||||
|
.api-card:nth-child(3n+1) .api-card-icon {
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card:nth-child(3n+2) .api-card-icon {
|
||||||
|
color: #34d399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-card:nth-child(3n+3) .api-card-icon {
|
||||||
|
color: #818cf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty State
|
||||||
|
.api-empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: $spacing-5xl $spacing-lg;
|
||||||
|
color: $text-gray;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 80px;
|
||||||
|
margin-bottom: $spacing-lg;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: $font-size-xl;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
color: $text-dark;
|
||||||
|
margin-bottom: $spacing-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
color: $text-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading State
|
||||||
|
.api-loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: $spacing-5xl;
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 4px solid $border-gray;
|
||||||
|
border-top-color: $primary-blue;
|
||||||
|
border-radius: $border-radius-circle;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile Overlay
|
||||||
|
.api-mobile-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 60px; // Below mobile header
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba($black, 0.5);
|
||||||
|
z-index: $z-index-modal - 1;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Detail Content
|
||||||
|
.api-detail-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $spacing-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab Navigation
|
||||||
|
.api-detail-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: $spacing-sm;
|
||||||
|
border-bottom: 2px solid $border-gray;
|
||||||
|
margin-bottom: $spacing-xl;
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
padding: $spacing-md $spacing-lg;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
color: $text-gray;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: $transition-base;
|
||||||
|
position: relative;
|
||||||
|
bottom: -2px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary-blue;
|
||||||
|
background-color: rgba($primary-blue, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: $primary-blue;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
border-bottom-color: $primary-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab Content
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $spacing-xl;
|
||||||
|
min-height: 200px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Overview Card
|
||||||
|
.api-overview-card {
|
||||||
|
background: $white;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
padding: $spacing-xl;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $spacing-lg;
|
||||||
|
|
||||||
|
.api-overview-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: $spacing-md;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-sm) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-method-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: $gradient-primary;
|
||||||
|
color: $white;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
border-radius: $border-radius-sm;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-endpoint {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: block;
|
||||||
|
padding: $spacing-sm $spacing-md;
|
||||||
|
background-color: $gray-bg;
|
||||||
|
border: 1px solid $border-gray;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
font-size: $font-size-md;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
color: $text-dark;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-simple-description {
|
||||||
|
padding: $spacing-md 0;
|
||||||
|
border-bottom: 1px solid $border-gray;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
color: $text-gray;
|
||||||
|
line-height: $line-height-normal;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-overview-info {
|
||||||
|
h4 {
|
||||||
|
font-size: $font-size-md;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
color: $text-dark;
|
||||||
|
margin-bottom: $spacing-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Details Grid
|
||||||
|
.api-details-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $spacing-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Detail Card
|
||||||
|
.api-detail-card {
|
||||||
|
background: $white;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
padding: $spacing-xl;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
|
transition: $transition-base;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: $shadow-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: $font-size-lg;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
color: $text-dark;
|
||||||
|
margin-bottom: $spacing-md;
|
||||||
|
padding-bottom: $spacing-sm;
|
||||||
|
border-bottom: 2px solid $light-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-content {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: $text-gray;
|
||||||
|
line-height: $line-height-normal;
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: $gray-bg;
|
||||||
|
border: 1px solid $border-gray;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
padding: $spacing-md;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: $text-dark;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table styling for HTML tables in specifications
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: $spacing-md 0;
|
||||||
|
background-color: $white;
|
||||||
|
border: 1px solid $border-gray;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background-color: $light-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
padding: $spacing-sm $spacing-md;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
color: $text-dark;
|
||||||
|
border-bottom: 2px solid $border-gray;
|
||||||
|
background-color: rgba($primary-blue, 0.05);
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-right: 1px solid $border-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: $spacing-sm $spacing-md;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: $text-gray;
|
||||||
|
border-bottom: 1px solid $border-gray;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-right: 1px solid $border-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
transition: $transition-base;
|
||||||
|
|
||||||
|
&:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($primary-blue, 0.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(even) {
|
||||||
|
background-color: rgba($gray-bg, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Info Table
|
||||||
|
.api-info-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
border-bottom: 1px solid $border-gray;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
padding: $spacing-sm $spacing-md;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
color: $text-dark;
|
||||||
|
width: 30%;
|
||||||
|
background-color: $gray-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: $spacing-sm $spacing-md;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: $text-gray;
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background-color: $gray-bg;
|
||||||
|
border-radius: $border-radius-sm;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
color: $primary-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,204 +1,454 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
|
xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{layout/kjbank_base_layout}">
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<section layout:fragment="contentFragment" class="content api_con">
|
<th:block layout:fragment="contentFragment">
|
||||||
<style>
|
<section class="api-market-container">
|
||||||
/* Custom dark theme for CodeMirror */
|
|
||||||
.cm-s-custom-dark.CodeMirror {
|
|
||||||
background-color: #252B37;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-custom-dark .CodeMirror-gutters {
|
<!-- Sidebar Navigation -->
|
||||||
background-color: #1a1a1a;
|
<aside class="api-market-sidebar" id="apiSidebar">
|
||||||
border-right: 1px solid #3a3a3a;
|
<nav class="api-sidebar-nav">
|
||||||
}
|
<!-- All APIs -->
|
||||||
|
<div class="menu-section">
|
||||||
.cm-s-custom-dark .CodeMirror-linenumber {
|
<div class="menu-title" th:classappend="${selected == '-1'} ? 'active'" data-group="">
|
||||||
color: #777;
|
전체
|
||||||
}
|
<span class="api-count" th:text="${totalApiCount}">0</span>
|
||||||
|
|
||||||
.cm-s-custom-dark .CodeMirror-cursor {
|
|
||||||
border-left: 1px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-custom-dark .cm-string {
|
|
||||||
color: #a6e22e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-custom-dark .cm-number {
|
|
||||||
color: #ae81ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-custom-dark .cm-atom {
|
|
||||||
color: #f92672;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-custom-dark .cm-keyword {
|
|
||||||
color: #66d9ef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-custom-dark .cm-property {
|
|
||||||
color: #ccffee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-custom-dark .cm-punctuation {
|
|
||||||
color: #f8f8f2;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="content_wrap w_api">
|
|
||||||
<div class="sub_title4">
|
|
||||||
<h2 class="title" th:text="${apiSpecInfo.apiName}"></h2>
|
|
||||||
<p class="infotxt" th:text="${apiSpecInfo.apiSimpleDescription}"></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pc-only">
|
|
||||||
<div class="inner i_cs h_inner2">
|
|
||||||
<div class="api_stit">
|
|
||||||
<table class="api_table" cellspacing="0" cellpadding="0" width="100%">
|
|
||||||
<colgroup>
|
|
||||||
<col width="200px">
|
|
||||||
<col width="auto">
|
|
||||||
</colgroup>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>API 명 (URI)</th>
|
|
||||||
<td th:text="${apiSpecInfo.apiUrl}"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Method</th>
|
|
||||||
<td>[[${apiSpecInfo.apiMethod}]]</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Content-Type</th>
|
|
||||||
<td>[[${apiSpecInfo.apiContentType}]]</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="api_stit api_dot" th:utext="${apiSpecInfo.description}">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<!-- Service Categories -->
|
||||||
<div class="inner i_cs h_inner2">
|
<div class="menu-section" th:each="service : ${services}">
|
||||||
<div class="api_stit m-only">
|
<div class="menu-title"
|
||||||
<table class="api_table" cellspacing="0" cellpadding="0" width="100%">
|
th:classappend="${selected == service.id} ? 'active'"
|
||||||
<colgroup>
|
th:data-group="${service.id}">
|
||||||
<col width="110px">
|
[[${service.groupName}]]
|
||||||
<col width="auto">
|
<span class="api-count" th:text="${service.apiGroupApiList.size()}">0</span>
|
||||||
</colgroup>
|
</div>
|
||||||
<tbody>
|
|
||||||
<tr>
|
<!-- Level 2: API List -->
|
||||||
<th>API 명 (URI)</th>
|
<div class="api-list" th:if="${service.apiGroupApiList != null and !service.apiGroupApiList.isEmpty()}">
|
||||||
<td th:text="${apiSpecInfo.apiUrl}"></td>
|
<div class="api-item"
|
||||||
</tr>
|
th:each="api : ${service.apiGroupApiList}"
|
||||||
<tr>
|
th:data-api-id="${api.apiId}"
|
||||||
<th>Method</th>
|
th:attr="data-href=@{/apis/detail(apiId=${api.apiId})}">
|
||||||
<td>[[${apiSpecInfo.apiMethod}]]</td>
|
<span class="api-name" th:text="${api.apiDesc}">API Name</span>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<th>Content-Type</th>
|
</div>
|
||||||
<td>[[${apiSpecInfo.apiContentType}]]</td>
|
</nav>
|
||||||
</tr>
|
</aside>
|
||||||
</tbody>
|
|
||||||
</table>
|
<!-- Mobile Menu Toggle -->
|
||||||
<div class="api_stit api_dot" th:utext="${apiSpecInfo.description}">
|
<button class="api-mobile-toggle" id="mobileToggle" aria-label="메뉴 열기">
|
||||||
|
☰
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Mobile Overlay -->
|
||||||
|
<div class="api-mobile-overlay" id="mobileOverlay"></div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="api-market-content">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="api-market-header">
|
||||||
|
<div class="api-market-title">
|
||||||
|
<h1>서비스</h1>
|
||||||
|
<h2 th:text="${apiSpecInfo.apiName}">API 이름</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- API Content -->
|
||||||
|
<div class="api-detail-content">
|
||||||
|
|
||||||
<div class="api_stit api_sc api_tb_type api_sc2">
|
<!-- Tab Navigation -->
|
||||||
<h3>요청 메시지</h3>
|
<div class="api-detail-tabs">
|
||||||
<div class="pc-only" th:utext="${apiSpecInfo.apiRequestSpec}">
|
<button class="tab-button active" data-tab="api-info">API 정보</button>
|
||||||
|
<button class="tab-button" data-tab="testbed">테스트베드</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-only" th:utext="${apiSpecInfo.apiRequestSpec}">
|
|
||||||
|
<!-- Tab Content -->
|
||||||
|
<div class="tab-content active" id="api-info-tab">
|
||||||
|
<!-- API Overview Card (Merged with Additional Information) -->
|
||||||
|
<div class="api-overview-card">
|
||||||
|
<div class="api-overview-header">
|
||||||
|
<div class="api-method-badge" th:text="${apiSpecInfo.apiMethod}">GET</div>
|
||||||
|
<div class="api-endpoint">
|
||||||
|
<code th:text="${apiSpecInfo.apiUrl}">/api/v1/example</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- API Information Table -->
|
||||||
|
<div class="api-overview-info">
|
||||||
|
<table class="api-info-table">
|
||||||
|
<tr th:if="${apiSpecInfo.apiContentType != null}">
|
||||||
|
<th>Content Type</th>
|
||||||
|
<td th:text="${apiSpecInfo.apiContentType}">application/json</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="api-simple-description" th:if="${apiSpecInfo.apiSimpleDescription != null}">
|
||||||
|
<p th:text="${apiSpecInfo.apiSimpleDescription}">Simple API description</p>
|
||||||
|
</div>
|
||||||
|
<div class="detail-content" th:utext="${apiSpecInfo.description}">
|
||||||
|
Detailed API description
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="api_stit api_sc">
|
<!-- API Details Grid -->
|
||||||
<h3>요청 메시지 예제</h3>
|
<div class="api-details-grid">
|
||||||
<ul>
|
|
||||||
<li>
|
<!-- Request Specification -->
|
||||||
<p class="tit">
|
<div class="api-detail-card" th:if="${apiSpecInfo.apiRequestSpec != null}">
|
||||||
<span>Header</span>
|
<h3>Request Specification</h3>
|
||||||
</p>
|
<div class="detail-content" th:utext="${apiSpecInfo.apiRequestSpec}">
|
||||||
<div class="api_sauce">
|
Request spec
|
||||||
<p>
|
|
||||||
Content-Type: application/json
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
</div>
|
<!-- Sample Request -->
|
||||||
<div class="api_stit api_sc api_sc3">
|
<div class="api-detail-card" th:if="${apiSpecInfo.sampleRequest != null}">
|
||||||
<ul>
|
<h3>샘플 요청</h3>
|
||||||
<li>
|
<div class="detail-content">
|
||||||
<p class="tit">
|
<pre><code th:text="${apiSpecInfo.sampleRequest}">Sample request</code></pre>
|
||||||
<span>Body</span>
|
|
||||||
</p>
|
|
||||||
<div class="api_sauce">
|
|
||||||
<textarea id="sampleRequest" th:text="${apiSpecInfo.sampleRequest}"></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
|
<!-- Response Specification -->
|
||||||
|
<div class="api-detail-card" th:if="${apiSpecInfo.apiResponseSpec != null}">
|
||||||
|
<h3>Response Specification</h3>
|
||||||
|
<div class="detail-content" th:utext="${apiSpecInfo.apiResponseSpec}">
|
||||||
|
Response spec
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sample Response -->
|
||||||
|
<div class="api-detail-card" th:if="${apiSpecInfo.sampleResponse != null}">
|
||||||
|
<h3>샘플 응답</h3>
|
||||||
|
<div class="detail-content">
|
||||||
|
<pre><code th:text="${apiSpecInfo.sampleResponse}">Sample response</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Testbed Tab Content -->
|
||||||
|
<div class="tab-content" id="testbed-tab">
|
||||||
|
<link rel="stylesheet" type="text/css" th:href="@{/plugins/swaggerUI/swagger-ui.css}"/>
|
||||||
|
<link rel="stylesheet" type="text/css" th:href="@{/plugins/swaggerUI/index.css}"/>
|
||||||
|
|
||||||
|
<div class="api-overview-card">
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="api_line"></div>
|
</main>
|
||||||
|
</section>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
<div class="api_stit api_sc api_sc2 api_tb_type">
|
|
||||||
<h3>응답 메시지</h3>
|
|
||||||
<div class="pc-only" th:utext="${apiSpecInfo.apiResponseSpec}"></div>
|
|
||||||
<div class="m-only" th:utext="${apiSpecInfo.apiResponseSpec}"></div>
|
|
||||||
</div>
|
|
||||||
<div class="api_stit api_sc">
|
|
||||||
<h3>응답 메시지 예제</h3>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<p class="tit">
|
|
||||||
<span>Body</span>
|
|
||||||
</p>
|
|
||||||
<div class="api_sauce">
|
|
||||||
<textarea id="sampleResponse" th:text="${apiSpecInfo.sampleResponse}"></textarea>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="api_btn">
|
|
||||||
<a th:href="@{/apis/testbed/api(id=${apiSpecInfo.apiId})}" class="common_btn_type_1 gray">테스트</a>
|
|
||||||
<div class="btn_gap"></div>
|
|
||||||
<a href="#none" class="common_btn_type_1" id="requestApiKey">API 이용 신청</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
||||||
<th:block layout:fragment="contentScript">
|
<th:block layout:fragment="contentScript">
|
||||||
<script>
|
<script th:inline="javascript">
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const commonOptions = {
|
// DOM Elements
|
||||||
lineNumbers: false,
|
const sidebar = document.getElementById('apiSidebar');
|
||||||
mode: {name: 'javascript', json: true},
|
const mobileToggle = document.getElementById('mobileToggle');
|
||||||
theme: 'custom-dark',
|
const mobileOverlay = document.getElementById('mobileOverlay');
|
||||||
readOnly: true,
|
|
||||||
autoCloseBrackets: true,
|
|
||||||
matchBrackets: true,
|
|
||||||
foldGutter: true
|
|
||||||
};
|
|
||||||
const sampleRequestEditor = CodeMirror.fromTextArea(document.getElementById('sampleRequest'), commonOptions);
|
|
||||||
|
|
||||||
const sampleResponseEditor = CodeMirror.fromTextArea(document.getElementById('sampleResponse'), commonOptions);
|
// API Card Click Handlers
|
||||||
|
const apiCards = document.querySelectorAll('.api-card');
|
||||||
|
apiCards.forEach(function(card) {
|
||||||
|
// Click handler
|
||||||
|
card.addEventListener('click', function() {
|
||||||
|
const href = this.getAttribute('data-href');
|
||||||
|
if (href) {
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Refresh CodeMirror instances to ensure proper rendering
|
// Keyboard accessibility
|
||||||
sampleRequestEditor.refresh();
|
card.addEventListener('keypress', function(e) {
|
||||||
sampleResponseEditor.refresh();
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
const href = this.getAttribute('data-href');
|
||||||
|
if (href) {
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Level 2 API Item Click Handlers
|
||||||
|
const apiItems = document.querySelectorAll('.api-item');
|
||||||
|
apiItems.forEach(function(item) {
|
||||||
|
// Click handler
|
||||||
|
item.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation(); // Prevent parent menu-title click
|
||||||
|
const href = this.getAttribute('data-href');
|
||||||
|
if (href) {
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard accessibility
|
||||||
|
item.addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const href = this.getAttribute('data-href');
|
||||||
|
if (href) {
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Category/Service Selection - removed as not needed in detail page
|
||||||
|
|
||||||
|
// Mobile Menu Toggle
|
||||||
|
if (mobileToggle && sidebar && mobileOverlay) {
|
||||||
|
mobileToggle.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
sidebar.classList.toggle('mobile-open');
|
||||||
|
mobileOverlay.classList.toggle('active');
|
||||||
|
|
||||||
|
// Update button text
|
||||||
|
if (sidebar.classList.contains('mobile-open')) {
|
||||||
|
this.innerHTML = '✕';
|
||||||
|
this.setAttribute('aria-label', '메뉴 닫기');
|
||||||
|
} else {
|
||||||
|
this.innerHTML = '☰';
|
||||||
|
this.setAttribute('aria-label', '메뉴 열기');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add touch event for better mobile support
|
||||||
|
mobileToggle.addEventListener('touchend', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu on overlay click
|
||||||
|
mobileOverlay.addEventListener('click', function() {
|
||||||
|
sidebar.classList.remove('mobile-open');
|
||||||
|
this.classList.remove('active');
|
||||||
|
mobileToggle.innerHTML = '☰';
|
||||||
|
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('Mobile toggle elements not found:', {
|
||||||
|
mobileToggle: !!mobileToggle,
|
||||||
|
sidebar: !!sidebar,
|
||||||
|
mobileOverlay: !!mobileOverlay
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close mobile menu on resize to desktop
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
if (window.innerWidth > 768 && sidebar.classList.contains('mobile-open')) {
|
||||||
|
sidebar.classList.remove('mobile-open');
|
||||||
|
mobileOverlay.classList.remove('active');
|
||||||
|
mobileToggle.innerHTML = '☰';
|
||||||
|
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tab functionality
|
||||||
|
const tabButtons = document.querySelectorAll('.tab-button');
|
||||||
|
const tabContents = document.querySelectorAll('.tab-content');
|
||||||
|
|
||||||
|
tabButtons.forEach(function(button) {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const targetTab = this.getAttribute('data-tab');
|
||||||
|
|
||||||
|
// Remove active class from all buttons and contents
|
||||||
|
tabButtons.forEach(function(btn) {
|
||||||
|
btn.classList.remove('active');
|
||||||
|
});
|
||||||
|
tabContents.forEach(function(content) {
|
||||||
|
content.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add active class to clicked button and corresponding content
|
||||||
|
this.classList.add('active');
|
||||||
|
document.getElementById(targetTab + '-tab').classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Swagger UI functionality for testbed tab
|
||||||
|
let swaggerInitialized = false;
|
||||||
|
const currentApiId = /*[[${apiSpecInfo.apiId}]]*/ 'default';
|
||||||
|
|
||||||
|
// Initialize Swagger UI when testbed tab is clicked
|
||||||
|
tabButtons.forEach(function(button) {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const targetTab = this.getAttribute('data-tab');
|
||||||
|
if (targetTab === 'testbed' && !swaggerInitialized) {
|
||||||
|
initializeSwagger(currentApiId);
|
||||||
|
swaggerInitialized = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function initializeSwagger(apiId) {
|
||||||
|
if (!apiId || apiId === 'default') {
|
||||||
|
document.getElementById('swagger-ui').innerHTML = '<p>API 정보를 불러올 수 없습니다.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const swaggerUrl = `/api/apis/${apiId}/swagger.json`;
|
||||||
|
|
||||||
|
// Load Swagger UI scripts dynamically
|
||||||
|
const loadScript = (src) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = src;
|
||||||
|
script.onload = resolve;
|
||||||
|
script.onerror = reject;
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load required scripts
|
||||||
|
Promise.all([
|
||||||
|
loadScript(/*[[@{/plugins/swaggerUI/swagger-ui-bundle.js}]]*/ ''),
|
||||||
|
loadScript(/*[[@{/plugins/swaggerUI/swagger-ui-standalone-preset.js}]]*/ '')
|
||||||
|
]).then(() => {
|
||||||
|
// Load code template script
|
||||||
|
return loadScript(/*[[@{/js/code_template.js}]]*/ '');
|
||||||
|
}).then(() => {
|
||||||
|
// Initialize Swagger UI
|
||||||
|
const generateSnippet = (parsedRequest, language) => {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
$.ajax({
|
||||||
|
url: /*[[@{/api/sample_code/}]]*/ '' + language,
|
||||||
|
type: 'POST',
|
||||||
|
async: false,
|
||||||
|
contentType: 'application/json',
|
||||||
|
headers: {
|
||||||
|
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ ''
|
||||||
|
},
|
||||||
|
data: JSON.stringify(parsedRequest),
|
||||||
|
success: function(data) {
|
||||||
|
result = data;
|
||||||
|
},
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
console.error('Error:', errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SnippetGeneratorPlugin = {
|
||||||
|
fn: {
|
||||||
|
requestSnippetGenerator_csharp: (request) => {
|
||||||
|
var parsedRequest = parseSwaggerRequest(request);
|
||||||
|
return generateSnippet(parsedRequest, 'csharp');
|
||||||
|
},
|
||||||
|
requestSnippetGenerator_ecma5: (request) => {
|
||||||
|
var parsedRequest = parseSwaggerRequest(request);
|
||||||
|
return generateSnippet(parsedRequest, 'ecma5');
|
||||||
|
},
|
||||||
|
requestSnippetGenerator_java: (request) => {
|
||||||
|
var parsedRequest = parseSwaggerRequest(request);
|
||||||
|
return generateSnippet(parsedRequest, 'java');
|
||||||
|
},
|
||||||
|
requestSnippetGenerator_python: (request) => {
|
||||||
|
var parsedRequest = parseSwaggerRequest(request);
|
||||||
|
return generateSnippet(parsedRequest, 'python');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear existing Swagger UI
|
||||||
|
document.getElementById('swagger-ui').innerHTML = '';
|
||||||
|
|
||||||
|
// Initialize Swagger UI
|
||||||
|
const ui = SwaggerUIBundle({
|
||||||
|
url: swaggerUrl,
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
requestInterceptor: function(req) {
|
||||||
|
if (!req.loadSpec) {
|
||||||
|
const urlPath = new URL(req.url).pathname;
|
||||||
|
const method = req.method.toLowerCase();
|
||||||
|
|
||||||
|
const spec = ui.spec();
|
||||||
|
const specString = JSON.stringify(spec);
|
||||||
|
const specData = JSON.parse(specString);
|
||||||
|
|
||||||
|
if (specData && specData.json && specData.json.paths && specData.json.paths[urlPath]) {
|
||||||
|
const operation = specData.json.paths[urlPath][method];
|
||||||
|
if (operation && operation['x-original-api-id']) {
|
||||||
|
req.headers['original-api-id'] = operation['x-original-api-id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.headers['original-url'] = req.url;
|
||||||
|
req.headers['X-XSRF-TOKEN'] = /*[[${_csrf.token}]]*/ '';
|
||||||
|
|
||||||
|
if ($('#apps').val() != null) {
|
||||||
|
req.headers['client-id'] = $('#apps').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
req.url = window.location.origin + /*[[@{/api/call-api}]]*/ '';
|
||||||
|
}
|
||||||
|
return req;
|
||||||
|
},
|
||||||
|
showMutatedRequest: false,
|
||||||
|
validatorUrl: '',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset.slice(1)
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl,
|
||||||
|
SnippetGeneratorPlugin
|
||||||
|
],
|
||||||
|
layout: 'StandaloneLayout',
|
||||||
|
requestSnippetsEnabled: true,
|
||||||
|
requestSnippets: {
|
||||||
|
generators: {
|
||||||
|
'csharp': {
|
||||||
|
title: 'C#',
|
||||||
|
syntax: 'csharp'
|
||||||
|
},
|
||||||
|
'ecma5': {
|
||||||
|
title: 'ECMA5',
|
||||||
|
syntax: 'javascript'
|
||||||
|
},
|
||||||
|
'java': {
|
||||||
|
title: 'Java',
|
||||||
|
syntax: 'java'
|
||||||
|
},
|
||||||
|
'python': {
|
||||||
|
title: 'Python',
|
||||||
|
syntax: 'python'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultExpanded: true,
|
||||||
|
languages: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.ui = ui;
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Failed to load Swagger UI:', error);
|
||||||
|
document.getElementById('swagger-ui').innerHTML = '<p>Swagger UI를 불러오는 중 오류가 발생했습니다.</p>';
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,153 +1,250 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}">
|
xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{layout/kjbank_base_layout}">
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<section layout:fragment="contentFragment" class="content">
|
<th:block layout:fragment="contentFragment">
|
||||||
|
<section class="api-market-container">
|
||||||
|
|
||||||
<div class="content_wrap">
|
<!-- Sidebar Navigation -->
|
||||||
<div class="api_service_type m-only">
|
<aside class="api-market-sidebar" id="apiSidebar">
|
||||||
<img th:src="@{/img/bg_api_visual03.png}" alt="Kbank API 서비스">
|
<nav class="api-sidebar-nav">
|
||||||
<div class="service_txt">
|
<!-- All APIs -->
|
||||||
<p>Kbank API 서비스</p>
|
<div class="menu-section">
|
||||||
<span>
|
<div class="menu-title" th:classappend="${selected == '-1'} ? 'active'" data-group="">
|
||||||
API를 활용해 당신의 비즈니스 모델을<br/>
|
전체
|
||||||
혁신하여 창의적이고 차별화된 서비스를<br/>
|
<span class="api-count" th:text="${totalApiCount}">0</span>
|
||||||
제작하세요.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="api_service_type pc-only">
|
|
||||||
<img th:src="@{/img/bg_api_visual02.png}" alt="Kbank API 서비스">
|
|
||||||
<div class="service_txt">
|
|
||||||
<p>Kbank API 서비스</p>
|
|
||||||
<span>
|
|
||||||
Kbank가 제공하는 다양한 API를 소개합니다.<br/>
|
|
||||||
API를 활용해 당신의 비즈니스 모델을 혁신하여 창의적이고<br/>
|
|
||||||
차별화된 서비스를 제작하세요.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="api_inner api_pd">
|
|
||||||
<div class="main_keyword">
|
|
||||||
<div class="search_type2">
|
|
||||||
<form id="searchForm" th:action="@{/apis}" method="get">
|
|
||||||
<div class="search-container2">
|
|
||||||
<input type="hidden" name="groupIds" th:value="${#strings.replace(search.groupIds, '[', '').replace(']', '')}"/>
|
|
||||||
<input type="text" class="search-box2" name="keyword" th:value="${search.keyword}" placeholder="어떤 API를 찾고 계신가요?">
|
|
||||||
<button class="search-button2" type="submit"></button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
|
<!-- Service Categories -->
|
||||||
|
<div class="menu-section" th:each="service : ${services}">
|
||||||
|
<div class="menu-title"
|
||||||
|
th:classappend="${selected == service.id} ? 'active'"
|
||||||
|
th:data-group="${service.id}">
|
||||||
|
[[${service.groupName}]]
|
||||||
|
<span class="api-count" th:text="${service.apiGroupApiList.size()}">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Toggle -->
|
||||||
|
<button class="api-mobile-toggle" id="mobileToggle" aria-label="메뉴 열기">
|
||||||
|
☰
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Mobile Overlay -->
|
||||||
|
<div class="api-mobile-overlay" id="mobileOverlay"></div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="api-market-content">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="api-market-header">
|
||||||
|
<div class="api-market-title">
|
||||||
|
<h1>오픈API</h1>
|
||||||
|
<h2>API 마켓</h2>
|
||||||
|
</div>
|
||||||
|
<div class="api-market-search">
|
||||||
|
<form id="searchForm" th:action="@{/apis}" method="get">
|
||||||
|
<input type="hidden" name="groupIds" th:value="${search.groupIds != null and !search.groupIds.isEmpty() ? search.groupIds[0] : ''}"/>
|
||||||
|
<input type="text"
|
||||||
|
class="search-input"
|
||||||
|
name="keyword"
|
||||||
|
th:value="${search.keyword}"
|
||||||
|
placeholder="어떤 API를 찾고 계신가요?">
|
||||||
|
<button type="submit" class="search-submit-btn" aria-label="검색">
|
||||||
|
<span class="search-icon">🔍</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="api_keyword">
|
|
||||||
<ul class="keyword_list tab_list grt">
|
<!-- Result Count -->
|
||||||
<li>
|
<div class="api-result-count">
|
||||||
<a href="#">전체<span class="number" th:text="${selectedApiCount}" th:classppend="${selected == '-1'}?'active'"></span></a>
|
총 <strong th:text="${selectedApiCount}">0</strong>건
|
||||||
</li>
|
|
||||||
<li th:each="item : ${services}">
|
|
||||||
<a href="#" th:data-group="${item.id}" th:classappend="${selected == item.id}?'active'">
|
|
||||||
[[${item.groupName}]]<span class="number" th:text="${item.apiGroupApiList.size()}"></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="m-only">
|
|
||||||
<div class="api_info_container card_top">
|
<!-- API Cards Grid -->
|
||||||
<div class="api_box" th:each="api : ${apis}" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
<div class="api-card-grid" th:if="${apis != null and !apis.isEmpty()}">
|
||||||
<div class="api_info_box">
|
<div class="api-card"
|
||||||
<div class="api_list" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
th:each="api : ${apis}"
|
||||||
<p class="txt1" th:text="${api.apiName}"></p>
|
th:data-href="@{/apis/detail(id=${api.apiId})}"
|
||||||
<p class="txt2" th:text="${api.apiSimpleDescription}"></p>
|
role="button"
|
||||||
</div>
|
tabindex="0">
|
||||||
<div class="api_img" style="height: 48px;">
|
<div>
|
||||||
<img th:if="${#strings.isEmpty(api.mainIcon) == false}" th:src="${api.mainIcon}" alt="">
|
<!-- Card Header with Badge -->
|
||||||
</div>
|
<div class="api-card-header">
|
||||||
|
<span class="api-card-badge" th:if="${api.apiGroupName}" th:text="${api.apiGroupName}">
|
||||||
|
카테고리
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Card Title -->
|
||||||
|
<h3 class="api-card-title" th:text="${api.apiName}">
|
||||||
|
API 이름
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Card Description -->
|
||||||
|
<p class="api-card-description" th:text="${api.apiSimpleDescription ?: 'API 설명이 없습니다.'}">
|
||||||
|
API 설명
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card Icon -->
|
||||||
|
<div class="api-card-icon">
|
||||||
|
<img th:if="${api.mainIcon}"
|
||||||
|
th:src="${api.mainIcon}"
|
||||||
|
th:alt="${api.apiName}"
|
||||||
|
onerror="this.style.display='none'; this.parentElement.innerHTML='📄'">
|
||||||
|
<span th:if="${#strings.isEmpty(api.mainIcon)}">📄</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pc-only">
|
|
||||||
<div class="ser_result" >총 <span>[[${selectedApiCount}]]건</span> 검색</div>
|
<!-- Empty State -->
|
||||||
<div class="api_info_container box_list mt_0">
|
<div class="api-empty-state" th:if="${apis == null or apis.isEmpty()}">
|
||||||
<div class="api_box" th:each="api : ${apis}" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
<div class="empty-icon">🔍</div>
|
||||||
<div class="api_info_box pc-only">
|
<h3>검색 결과가 없습니다</h3>
|
||||||
<div class="api_list" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
<p>다른 키워드로 검색하거나 카테고리를 변경해 보세요.</p>
|
||||||
<p class="txt1" th:text="${api.apiName}"></p>
|
|
||||||
<p class="txt2" th:text="${api.apiSimpleDescription}"></p>
|
|
||||||
</div>
|
|
||||||
<div class="api_img">
|
|
||||||
<a th:href="@{/apis/detail(id=${api.apiId})}" class="btn btn-primary"><img th:if="${#strings.isEmpty(api.mainIcon) == false}" th:src="${api.mainIcon}" alt=""></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</main>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
</th:block>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
||||||
<th:block layout:fragment="contentScript">
|
<th:block layout:fragment="contentScript">
|
||||||
<script>
|
<script th:inline="javascript">
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const apiListItems = document.querySelectorAll('.api_list');
|
// DOM Elements
|
||||||
apiListItems.forEach(function(item) {
|
const sidebar = document.getElementById('apiSidebar');
|
||||||
item.addEventListener('click', function() {
|
const mobileToggle = document.getElementById('mobileToggle');
|
||||||
var href = this.getAttribute('data-href');
|
const mobileOverlay = document.getElementById('mobileOverlay');
|
||||||
if (href) {
|
const searchForm = document.getElementById('searchForm');
|
||||||
window.location.href = href;
|
const groupIdsInput = searchForm.querySelector('input[name="groupIds"]');
|
||||||
}
|
const searchInput = searchForm.querySelector('input[name="keyword"]');
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const apiItems = document.querySelectorAll('.api_box');
|
// API Card Click Handlers
|
||||||
apiItems.forEach(function(item) {
|
const apiCards = document.querySelectorAll('.api-card');
|
||||||
item.removeEventListener('click',null);
|
apiCards.forEach(function(card) {
|
||||||
item.addEventListener('click', function() {
|
// Click handler
|
||||||
|
card.addEventListener('click', function() {
|
||||||
const href = this.getAttribute('data-href');
|
const href = this.getAttribute('data-href');
|
||||||
if (href) {
|
if (href) {
|
||||||
window.location.href = href;
|
window.location.href = href;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keyboard accessibility
|
||||||
|
card.addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
const href = this.getAttribute('data-href');
|
||||||
|
if (href) {
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var keywordListItems = document.querySelectorAll('.keyword_list li');
|
// Category/Service Selection
|
||||||
var form = document.getElementById('searchForm');
|
const menuTitles = document.querySelectorAll('.menu-title');
|
||||||
var groupIdsInput = form.querySelector('input[name="groupIds"]');
|
menuTitles.forEach(function(title) {
|
||||||
|
title.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
keywordListItems.forEach(function(item) {
|
const groupId = this.getAttribute('data-group');
|
||||||
item.addEventListener('click', function(e) {
|
|
||||||
e.preventDefault(); // Prevent default anchor behavior
|
// Update groupIds input
|
||||||
var anchor = this.querySelector('a');
|
|
||||||
var groupId = anchor.getAttribute('data-group');
|
|
||||||
if (groupId) {
|
if (groupId) {
|
||||||
groupId = groupId.replace(/[\[\]]/g, '');
|
|
||||||
}
|
|
||||||
if (groupId) {
|
|
||||||
// If specific group is selected, create/set input value
|
|
||||||
if (!groupIdsInput) {
|
|
||||||
groupIdsInput = document.createElement('input');
|
|
||||||
groupIdsInput.type = 'hidden';
|
|
||||||
groupIdsInput.name = 'groupIds';
|
|
||||||
form.appendChild(groupIdsInput);
|
|
||||||
}
|
|
||||||
groupIdsInput.value = groupId;
|
groupIdsInput.value = groupId;
|
||||||
} else {
|
} else {
|
||||||
if (groupIdsInput) {
|
groupIdsInput.value = '';
|
||||||
groupIdsInput.remove();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
form.submit();
|
// Submit form
|
||||||
|
searchForm.submit();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Search on Enter key
|
||||||
|
searchInput.addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
searchForm.submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mobile Menu Toggle
|
||||||
|
if (mobileToggle && sidebar && mobileOverlay) {
|
||||||
|
mobileToggle.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
sidebar.classList.toggle('mobile-open');
|
||||||
|
mobileOverlay.classList.toggle('active');
|
||||||
|
|
||||||
|
// Update button text
|
||||||
|
if (sidebar.classList.contains('mobile-open')) {
|
||||||
|
this.innerHTML = '✕';
|
||||||
|
this.setAttribute('aria-label', '메뉴 닫기');
|
||||||
|
} else {
|
||||||
|
this.innerHTML = '☰';
|
||||||
|
this.setAttribute('aria-label', '메뉴 열기');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add touch event for better mobile support
|
||||||
|
mobileToggle.addEventListener('touchend', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu on overlay click
|
||||||
|
mobileOverlay.addEventListener('click', function() {
|
||||||
|
sidebar.classList.remove('mobile-open');
|
||||||
|
this.classList.remove('active');
|
||||||
|
mobileToggle.innerHTML = '☰';
|
||||||
|
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu on category selection (mobile)
|
||||||
|
menuTitles.forEach(function(title) {
|
||||||
|
title.addEventListener('click', function() {
|
||||||
|
if (window.innerWidth <= 768) {
|
||||||
|
sidebar.classList.remove('mobile-open');
|
||||||
|
mobileOverlay.classList.remove('active');
|
||||||
|
mobileToggle.innerHTML = '☰';
|
||||||
|
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('Mobile toggle elements not found:', {
|
||||||
|
mobileToggle: !!mobileToggle,
|
||||||
|
sidebar: !!sidebar,
|
||||||
|
mobileOverlay: !!mobileOverlay
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close mobile menu on resize to desktop
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
if (window.innerWidth > 768 && sidebar.classList.contains('mobile-open')) {
|
||||||
|
sidebar.classList.remove('mobile-open');
|
||||||
|
mobileOverlay.classList.remove('active');
|
||||||
|
mobileToggle.innerHTML = '☰';
|
||||||
|
mobileToggle.setAttribute('aria-label', '메뉴 열기');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||||
<meta content="index, follow" name="robots"/>
|
<meta content="index, follow" name="robots"/>
|
||||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="wrap" class="wrap_top">
|
<div id="wrap" class="wrap_top">
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||||
<meta content="index, follow" name="robots"/>
|
<meta content="index, follow" name="robots"/>
|
||||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="wrap" class="wrap_top">
|
<div id="wrap" class="wrap_top">
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||||
<meta content="index, follow" name="robots"/>
|
<meta content="index, follow" name="robots"/>
|
||||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="wrap" class="wrap_top">
|
<div id="wrap" class="wrap_top">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<link rel="stylesheet" th:href="@{/plugins/codemirror/theme/monokai.css}" type="text/css"/>
|
<link rel="stylesheet" th:href="@{/plugins/codemirror/theme/monokai.css}" type="text/css"/>
|
||||||
<link rel="stylesheet" th:href="@{/plugins/summernote/summernote-lite.css}" type="text/css" />
|
<link rel="stylesheet" th:href="@{/plugins/summernote/summernote-lite.css}" type="text/css" />
|
||||||
<link rel="stylesheet" th:href="@{/plugins/jquery-ui/jquery-ui.min.css}" />
|
<link rel="stylesheet" th:href="@{/plugins/jquery-ui/jquery-ui.min.css}" />
|
||||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||||
|
|
||||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<nav class="header-center">
|
<nav class="header-center">
|
||||||
<ul class="nav-menu">
|
<ul class="nav-menu">
|
||||||
<li><a href="#docs" class="nav-link">서비스 소개</a></li>
|
<li><a href="#docs" class="nav-link">서비스 소개</a></li>
|
||||||
<li><a href="#api-market" class="nav-link">오픈 API</a></li>
|
<li><a href="/apis" class="nav-link">오픈 API</a></li>
|
||||||
<li><a href="#playground" class="nav-link">고객지원</a></li>
|
<li><a href="#playground" class="nav-link">고객지원</a></li>
|
||||||
<li><a href="#community" class="nav-link">MY</a></li>
|
<li><a href="#community" class="nav-link">MY</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
<nav class="drawer-nav">
|
<nav class="drawer-nav">
|
||||||
<ul class="drawer-menu">
|
<ul class="drawer-menu">
|
||||||
<li><a href="#docs" class="drawer-link">서비스 소개</a></li>
|
<li><a href="#docs" class="drawer-link">서비스 소개</a></li>
|
||||||
<li><a href="#api-market" class="drawer-link">오픈 API</a></li>
|
<li><a href="/apis" class="drawer-link">오픈 API</a></li>
|
||||||
<li><a href="#playground" class="drawer-link">고객지원</a></li>
|
<li><a href="#playground" class="drawer-link">고객지원</a></li>
|
||||||
<li><a href="#community" class="drawer-link">MY</a></li>
|
<li><a href="#community" class="drawer-link">MY</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user