Merge branch 'main-design' into devs/design-merge
# Conflicts: # src/main/resources/logback-spring.xml
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.eactive.apim.portal.apps.apis.controller;
|
||||
|
||||
|
||||
import com.eactive.apim.portal.apps.apis.dto.ApiSearchData;
|
||||
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
|
||||
import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade;
|
||||
import com.eactive.apim.portal.apps.apis.service.ApiService;
|
||||
@@ -24,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
@RequestMapping("/apis")
|
||||
@RequiredArgsConstructor
|
||||
public class ApiController {
|
||||
|
||||
private static final String NOT_FOUND_MESSAGE = "찾을 수 없습니다.";
|
||||
private final ApiService apiService;
|
||||
private final ApiServiceService apiServiceService;
|
||||
@@ -33,15 +35,20 @@ public class ApiController {
|
||||
|
||||
@GetMapping("/detail")
|
||||
public String apidetail(@RequestParam(value = "id", required = false) String id, ModelMap model) {
|
||||
if( id ==null) {
|
||||
if (id == null) {
|
||||
return "redirect:/apis/common";
|
||||
}
|
||||
|
||||
ApiSpecInfoDto api = apiService.selectDetail(id);
|
||||
if(api==null){
|
||||
if (api == null) {
|
||||
throw new NotFoundException(NOT_FOUND_MESSAGE);
|
||||
}
|
||||
|
||||
Map<String, Object> searchResult = apiSearchFacade.searchApis(new ApiGroupSearch());
|
||||
|
||||
model.addAttribute("apiSpecInfo", api);
|
||||
model.addAttribute("totalApiCount", searchResult.get("totalApiCount"));
|
||||
model.addAttribute("services", searchResult.get("services"));
|
||||
return "apps/apis/mainApiDetail";
|
||||
}
|
||||
|
||||
@@ -54,7 +61,7 @@ public class ApiController {
|
||||
model.addAttribute("apis", searchResult.get("apis"));
|
||||
model.addAttribute("totalApiCount", searchResult.get("totalApiCount"));
|
||||
model.addAttribute("selectedApiCount", searchResult.get("selectedApiCount"));
|
||||
model.addAttribute("selected", search.getGroupIds().size() >0 ? search.getGroupIds().get(0) : "-1");
|
||||
model.addAttribute("selected", search.getGroupIds().size() > 0 ? search.getGroupIds().get(0) : "-1");
|
||||
|
||||
return "apps/apis/mainApiList";
|
||||
}
|
||||
|
||||
+11
-6
@@ -1,7 +1,11 @@
|
||||
package com.eactive.apim.portal.apps.apis.controller;
|
||||
|
||||
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
|
||||
import com.eactive.apim.portal.apispec.service.ApiSpecInfoService;
|
||||
import com.eactive.apim.portal.apps.apis.service.ApiService;
|
||||
import com.eactive.apim.portal.apps.apiservice.service.ApiSpecService;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
@@ -10,6 +14,7 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -21,7 +26,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
public class TestbedSpecController {
|
||||
|
||||
@Autowired
|
||||
ApiSpecService apiSpecService;
|
||||
private ApiSpecInfoService apiSpecInfoService;
|
||||
|
||||
private static final String DEFAULT_TOKEN_API_ID = "default-token-api-spec";
|
||||
private static final String DEFAULT_SPEC_PATH = "swagger/default-token-api-spec.json";
|
||||
@@ -40,12 +45,12 @@ public class TestbedSpecController {
|
||||
}
|
||||
}
|
||||
|
||||
String testbedSpec = apiSpecService.generateApiServiceTestbedSpec(id);
|
||||
Optional<ApiSpecInfo> spec = apiSpecInfoService.findById(id);
|
||||
|
||||
if (testbedSpec.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
} else {
|
||||
return ResponseEntity.ok().body(testbedSpec);
|
||||
if (!spec.isPresent()) {
|
||||
StringUtils.hasText(spec.get().getTestbedSpec());
|
||||
}
|
||||
return ResponseEntity.ok().body(spec.get().getTestbedSpec());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,4 +44,6 @@ public class ApiSpecInfoDto {
|
||||
private String displayOrg;
|
||||
|
||||
private String displayRoleCode;
|
||||
|
||||
private String apiGroupName;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,11 @@ public class ApiSearchFacadeImpl implements ApiSearchFacade {
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
filteredApis.forEach(api -> {
|
||||
api.setMainIcon(apiData.getServicesByApiId().get(api.getApiId()).getMainIcon());
|
||||
ApiServiceDTO service = apiData.getServicesByApiId().get(api.getApiId());
|
||||
if (service != null) {
|
||||
api.setMainIcon(service.getMainIcon());
|
||||
api.setApiGroupName(service.getGroupName());
|
||||
}
|
||||
});
|
||||
|
||||
List<ApiServiceDTO> servicesToReturn = apiData.getAllServices().stream()
|
||||
@@ -143,6 +147,7 @@ public class ApiSearchFacadeImpl implements ApiSearchFacade {
|
||||
result.put("services", servicesToReturn);
|
||||
result.put("apis", sortedApis);
|
||||
result.put("selectedApiCount", sortedApis.size());
|
||||
result.put("totalApiCount", apiData.getApiSpecsById().size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -51,9 +52,12 @@ public class MainApiFacade {
|
||||
|
||||
public List<String> getHashTags() {
|
||||
Map<String, String> property = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY);
|
||||
String hashTags = property.getOrDefault("main.keyword.list", "#인증, #대출, #예금");
|
||||
//split with , and return as a list
|
||||
return Arrays.asList(hashTags.split(","));
|
||||
String hashTags = property.getOrDefault("main.keyword.list", "#계좌조회, #송금API, #실시간결제, #오픈뱅킹");
|
||||
//split with , and clean each tag (remove # and trim)
|
||||
return Arrays.stream(hashTags.split(","))
|
||||
.map(tag -> tag.trim().replaceFirst("^#", ""))
|
||||
.filter(tag -> !tag.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<ApiSpecInfoDto> getOpenApis(List<String> apiIds) {
|
||||
|
||||
@@ -69,7 +69,7 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer {
|
||||
registry.addResourceHandler("/img/**").addResourceLocations("/img/", "classpath:/static/img/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
||||
registry.addResourceHandler("/js/**").addResourceLocations("/js/", "classpath:/static/js/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
||||
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/", "classpath:/static/plugins/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
||||
registry.addResourceHandler("/favicon_16px.png").addResourceLocations("/favicon_16px.png", "classpath:/static/favicon_16px.png").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
||||
registry.addResourceHandler("/favicon.ico").addResourceLocations("/favicon.ico", "classpath:/static/favicon.ico").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
<property name="LOG_PATH" value="${profileLogPath}/${inst.Name:-devSvr00}"/>
|
||||
|
||||
<!-- <springProfile name="gf63">-->
|
||||
<!-- <property name="LOG_PATH" value="d:/kjb-logs/${inst.Name:-devSvr00}"/>-->
|
||||
<!-- </springProfile>-->
|
||||
<!-- <springProfile name="gf63">-->
|
||||
<!-- <property name="LOG_PATH" value="d:/kjb-logs/${inst.Name:-devSvr00}"/>-->
|
||||
<!-- </springProfile>-->
|
||||
|
||||
<!-- <property name="CONSOLE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%thread]) %highlight([%-3level]) %logger{5} - %msg %n" />-->
|
||||
<!-- <property name="CONSOLE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%thread]) %highlight([%-3level]) %logger{5} - %msg %n" />-->
|
||||
<property name="CONSOLE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%thread]) %highlight([%-3level]) %logger - %msg %n" />
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
</logger>
|
||||
<logger name="org.hibernate.engine.jdbc.spi.SqlExceptionHelper" additivity="false">
|
||||
<appender-ref ref="HIBERNATE_APPENDER" />
|
||||
></logger>
|
||||
></logger>
|
||||
|
||||
<logger name="com.eactive.eapim" level="DEBUG" additivity="false"/>
|
||||
<logger name="kjb.safedb" level="DEBUG" additivity="false">
|
||||
@@ -116,9 +116,9 @@
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
<!-- <root level="DEBUG">-->
|
||||
<!-- <appender-ref ref="ROLLING"/>-->
|
||||
<!-- <appender-ref ref="CONSOLE"/>-->
|
||||
<!-- </root>-->
|
||||
<!-- <root level="DEBUG">-->
|
||||
<!-- <appender-ref ref="ROLLING"/>-->
|
||||
<!-- <appender-ref ref="CONSOLE"/>-->
|
||||
<!-- </root>-->
|
||||
</springProfile>
|
||||
</configuration>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+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 |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,144 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file contains all application-wide SASS mixins
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Media query mixins
|
||||
@mixin respond-to($breakpoint) {
|
||||
@if $breakpoint == 'xs' {
|
||||
@media (max-width: #{$breakpoint-xs}) { @content; }
|
||||
} @else if $breakpoint == 'sm' {
|
||||
@media (max-width: #{$breakpoint-sm}) { @content; }
|
||||
} @else if $breakpoint == 'md' {
|
||||
@media (max-width: #{$breakpoint-md}) { @content; }
|
||||
} @else if $breakpoint == 'lg' {
|
||||
@media (min-width: #{$breakpoint-lg}) { @content; }
|
||||
} @else if $breakpoint == 'xl' {
|
||||
@media (min-width: #{$breakpoint-xl}) { @content; }
|
||||
} @else if $breakpoint == 'mobile' {
|
||||
@media (max-width: #{$breakpoint-lg}) { @content; }
|
||||
} @else if $breakpoint == 'tablet' {
|
||||
@media (max-width: #{$breakpoint-md}) { @content; }
|
||||
}
|
||||
}
|
||||
|
||||
// Flexbox mixins
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@mixin flex-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// Button mixin
|
||||
@mixin button-base {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
padding: $button-padding-y $button-padding-x;
|
||||
border-radius: $border-radius-lg;
|
||||
font-weight: $font-weight-semibold;
|
||||
font-size: $font-size-base;
|
||||
text-decoration: none;
|
||||
transition: $transition-base;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
// Card mixin
|
||||
@mixin card {
|
||||
background: $white;
|
||||
border-radius: $border-radius-2xl;
|
||||
padding: $card-padding;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: $shadow-lg;
|
||||
}
|
||||
}
|
||||
|
||||
// Gradient text
|
||||
@mixin gradient-text($gradient) {
|
||||
background: $gradient;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
// Truncate text
|
||||
@mixin text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Absolute center
|
||||
@mixin absolute-center {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
// Backdrop blur
|
||||
@mixin backdrop-blur($amount: 10px) {
|
||||
backdrop-filter: blur($amount);
|
||||
-webkit-backdrop-filter: blur($amount);
|
||||
}
|
||||
|
||||
// Animation mixins
|
||||
@mixin animate-float {
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@mixin animate-bounce {
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
@mixin animate-slide-in-down {
|
||||
animation: slideInDown 0.6s ease-out;
|
||||
}
|
||||
|
||||
@mixin animate-slide-in-up {
|
||||
animation: slideInUp 0.8s ease-out;
|
||||
}
|
||||
|
||||
@mixin animate-fade-in {
|
||||
animation: fadeIn 1.2s ease-out;
|
||||
}
|
||||
|
||||
// Hover lift effect
|
||||
@mixin hover-lift {
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: $shadow-xl;
|
||||
}
|
||||
}
|
||||
|
||||
// Glass effect
|
||||
@mixin glass-effect {
|
||||
background: rgba($white, 0.95);
|
||||
@include backdrop-blur(10px);
|
||||
border: 1px solid rgba($white, 0.18);
|
||||
}
|
||||
|
||||
// Pattern circle
|
||||
@mixin pattern-circle($size, $color) {
|
||||
width: $size;
|
||||
height: $size;
|
||||
background: $color;
|
||||
border-radius: 50%;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file contains all application-wide SASS variables
|
||||
// Based on design/styles-v2.css - Modern vibrant design system
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Color Palette - Vibrant and Modern
|
||||
// Primary colors
|
||||
$primary-blue: #4B9BFF; // Bright blue (main brand color)
|
||||
$secondary-blue: #2E7FF7; // Deep blue
|
||||
$accent-cyan: #00D4FF; // Sky blue accent
|
||||
$accent-yellow: #FFD93D; // Yellow accent
|
||||
$accent-orange: #FF6B6B; // Orange accent
|
||||
$accent-green: #6BCF7F; // Green accent
|
||||
$accent-purple: #A78BFA; // Purple accent
|
||||
|
||||
// Neutral colors
|
||||
$text-dark: #1A1A2E; // Main text color
|
||||
$text-gray: #64748B; // Secondary text
|
||||
$text-light: #94A3B8; // Light text
|
||||
$white: #FFFFFF; // Pure white
|
||||
$gray-bg: #F8FAFC; // Light background
|
||||
$light-bg: #EFF6FF; // Blue tinted background
|
||||
$border-gray: #E2E8F0; // Border color
|
||||
$black: #000000; // Pure black
|
||||
$gray-800: #1F2937; // Dark gray
|
||||
$gray-900: #111827; // Darker gray
|
||||
|
||||
// Gradients
|
||||
$gradient-primary: linear-gradient(135deg, $primary-blue 0%, $secondary-blue 100%);
|
||||
$gradient-accent: linear-gradient(135deg, $accent-cyan 0%, $primary-blue 100%);
|
||||
$gradient-warm: linear-gradient(135deg, $accent-yellow 0%, $accent-orange 100%);
|
||||
|
||||
// Shadows
|
||||
$shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
$shadow-md: 0 4px 12px rgba(75, 155, 255, 0.1);
|
||||
$shadow-lg: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||
$shadow-xl: 0 16px 40px rgba(75, 155, 255, 0.2);
|
||||
|
||||
// Typography
|
||||
$font-family-primary: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans KR', sans-serif;
|
||||
$font-family-mono: 'Fira Code', 'Courier New', monospace;
|
||||
|
||||
// Font sizes
|
||||
$font-size-xs: 12px;
|
||||
$font-size-sm: 14px;
|
||||
$font-size-base: 16px;
|
||||
$font-size-md: 18px;
|
||||
$font-size-lg: 20px;
|
||||
$font-size-xl: 24px;
|
||||
$font-size-2xl: 32px;
|
||||
$font-size-3xl: 40px;
|
||||
$font-size-4xl: 48px;
|
||||
$font-size-5xl: 56px;
|
||||
|
||||
// Font weights
|
||||
$font-weight-regular: 400;
|
||||
$font-weight-medium: 500;
|
||||
$font-weight-semibold: 600;
|
||||
$font-weight-bold: 700;
|
||||
$font-weight-extrabold: 800;
|
||||
|
||||
// Line heights
|
||||
$line-height-tight: 1.2;
|
||||
$line-height-normal: 1.6;
|
||||
$line-height-loose: 1.8;
|
||||
|
||||
// Spacing
|
||||
$spacing-xs: 4px;
|
||||
$spacing-sm: 8px;
|
||||
$spacing-md: 16px;
|
||||
$spacing-lg: 24px;
|
||||
$spacing-xl: 32px;
|
||||
$spacing-2xl: 40px;
|
||||
$spacing-3xl: 48px;
|
||||
$spacing-4xl: 64px;
|
||||
$spacing-5xl: 80px;
|
||||
$spacing-6xl: 100px;
|
||||
|
||||
// Border radius
|
||||
$border-radius-sm: 6px;
|
||||
$border-radius-md: 8px;
|
||||
$border-radius-lg: 12px;
|
||||
$border-radius-xl: 16px;
|
||||
$border-radius-2xl: 20px;
|
||||
$border-radius-full: 50px;
|
||||
$border-radius-circle: 50%;
|
||||
|
||||
// Layout
|
||||
$container-max-width: 1280px;
|
||||
$header-height: 80px;
|
||||
|
||||
// Breakpoints
|
||||
$breakpoint-xs: 480px;
|
||||
$breakpoint-sm: 768px;
|
||||
$breakpoint-md: 1024px;
|
||||
$breakpoint-lg: 1280px;
|
||||
$breakpoint-xl: 1440px;
|
||||
|
||||
// Z-index
|
||||
$z-index-dropdown: 100;
|
||||
$z-index-sticky: 200;
|
||||
$z-index-fixed: 300;
|
||||
$z-index-header: 350;
|
||||
$z-index-modal-backdrop: 400;
|
||||
$z-index-modal: 500;
|
||||
$z-index-popover: 600;
|
||||
$z-index-tooltip: 700;
|
||||
$z-index-notification: 800;
|
||||
|
||||
// Animation
|
||||
$transition-base: all 0.3s ease;
|
||||
$transition-fast: all 0.15s ease;
|
||||
$transition-slow: all 0.5s ease;
|
||||
|
||||
// Component specific variables
|
||||
$button-padding-x: 32px;
|
||||
$button-padding-y: 16px;
|
||||
$card-padding: 32px;
|
||||
$input-padding-x: 16px;
|
||||
$input-padding-y: 12px;
|
||||
$input-height: 48px;
|
||||
@@ -0,0 +1,330 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Animation keyframes and utilities
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Float animation
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-30px) rotate(10deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Bounce animation
|
||||
@keyframes bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
// Slide in from top
|
||||
@keyframes slideInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Slide in from bottom
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Slide in from left
|
||||
@keyframes slideInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Slide in from right
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Fade in
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Fade in and scale
|
||||
@keyframes fadeInScale {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Spin
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Pulse
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Shimmer effect
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%) rotate(45deg);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%) rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Wave
|
||||
@keyframes wave {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
10% {
|
||||
transform: rotate(14deg);
|
||||
}
|
||||
20% {
|
||||
transform: rotate(-8deg);
|
||||
}
|
||||
30% {
|
||||
transform: rotate(14deg);
|
||||
}
|
||||
40% {
|
||||
transform: rotate(-4deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
60% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Pop in
|
||||
@keyframes popIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.5);
|
||||
}
|
||||
80% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Popup fade in (for modals)
|
||||
@keyframes popupFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Ripple effect
|
||||
@keyframes ripple {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(4);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Gradient animation
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
// Animation utility classes
|
||||
.animate-float {
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-bounce {
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
.animate-slide-up {
|
||||
animation: slideInUp 0.8s ease-out;
|
||||
}
|
||||
|
||||
.animate-slide-down {
|
||||
animation: slideInDown 0.6s ease-out;
|
||||
}
|
||||
|
||||
.animate-pop {
|
||||
animation: popIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
|
||||
// Animation delays
|
||||
.delay-1 {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.delay-2 {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.delay-3 {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.delay-4 {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.delay-5 {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
// Animation durations
|
||||
.duration-fast {
|
||||
animation-duration: 0.3s;
|
||||
}
|
||||
|
||||
.duration-normal {
|
||||
animation-duration: 0.6s;
|
||||
}
|
||||
|
||||
.duration-slow {
|
||||
animation-duration: 1s;
|
||||
}
|
||||
|
||||
.duration-slower {
|
||||
animation-duration: 2s;
|
||||
}
|
||||
|
||||
// Hover animation triggers
|
||||
.hover-grow {
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.hover-lift {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: $shadow-lg;
|
||||
}
|
||||
}
|
||||
|
||||
.hover-rotate {
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
}
|
||||
|
||||
.hover-shine {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
transparent 30%,
|
||||
rgba(255, 255, 255, 0.2) 50%,
|
||||
transparent 70%
|
||||
);
|
||||
transform: rotate(45deg);
|
||||
transition: transform 0.6s ease;
|
||||
transform: translateX(-200%);
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
transform: translateX(200%) rotate(45deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Reset and normalize styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font-family-primary;
|
||||
line-height: $line-height-normal;
|
||||
color: $text-dark;
|
||||
background-color: $white;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// Lists
|
||||
ul, ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
// Links
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: $transition-base;
|
||||
}
|
||||
|
||||
// Images
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
button {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Forms
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// Tables
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
// Headings
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: $font-weight-bold;
|
||||
line-height: $line-height-tight;
|
||||
}
|
||||
|
||||
// Semantic HTML5 elements
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, main, menu, nav,
|
||||
section, summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Selection
|
||||
::selection {
|
||||
background-color: rgba($primary-blue, 0.2);
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background-color: rgba($primary-blue, 0.2);
|
||||
color: $text-dark;
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Basic typography styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Body text
|
||||
body {
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-regular;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
// Headings
|
||||
h1, .h1 {
|
||||
font-size: $font-size-5xl;
|
||||
font-weight: $font-weight-extrabold;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
@include respond-to('sm') {
|
||||
font-size: $font-size-3xl;
|
||||
}
|
||||
|
||||
@include respond-to('xs') {
|
||||
font-size: $font-size-2xl;
|
||||
}
|
||||
}
|
||||
|
||||
h2, .h2 {
|
||||
font-size: $font-size-4xl;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-bottom: $spacing-md;
|
||||
|
||||
@include respond-to('sm') {
|
||||
font-size: $font-size-2xl;
|
||||
}
|
||||
|
||||
@include respond-to('xs') {
|
||||
font-size: $font-size-xl;
|
||||
}
|
||||
}
|
||||
|
||||
h3, .h3 {
|
||||
font-size: $font-size-3xl;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-bottom: $spacing-md;
|
||||
|
||||
@include respond-to('sm') {
|
||||
font-size: $font-size-xl;
|
||||
}
|
||||
}
|
||||
|
||||
h4, .h4 {
|
||||
font-size: $font-size-2xl;
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-bottom: $spacing-sm;
|
||||
}
|
||||
|
||||
h5, .h5 {
|
||||
font-size: $font-size-xl;
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-bottom: $spacing-sm;
|
||||
}
|
||||
|
||||
h6, .h6 {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-bottom: $spacing-sm;
|
||||
}
|
||||
|
||||
// Paragraphs
|
||||
p {
|
||||
margin-bottom: $spacing-md;
|
||||
line-height: $line-height-normal;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Text utilities
|
||||
.text-gradient {
|
||||
@include gradient-text($gradient-primary);
|
||||
}
|
||||
|
||||
.text-gradient-accent {
|
||||
@include gradient-text($gradient-accent);
|
||||
}
|
||||
|
||||
.text-gradient-warm {
|
||||
@include gradient-text($gradient-warm);
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: $primary-blue;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: $secondary-blue;
|
||||
}
|
||||
|
||||
.text-dark {
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.text-light {
|
||||
color: $text-light;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
// Text alignment
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// Font weights
|
||||
.font-regular {
|
||||
font-weight: $font-weight-regular;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.font-semibold {
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.font-extrabold {
|
||||
font-weight: $font-weight-extrabold;
|
||||
}
|
||||
|
||||
// Text sizes
|
||||
.text-xs {
|
||||
font-size: $font-size-xs;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: $font-size-lg;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: $font-size-xl;
|
||||
}
|
||||
|
||||
// Special text styles
|
||||
.lead {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: $font-weight-regular;
|
||||
line-height: $line-height-loose;
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.caption {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: $font-family-mono;
|
||||
font-size: $font-size-sm;
|
||||
background: $gray-bg;
|
||||
padding: 2px 6px;
|
||||
border-radius: $border-radius-sm;
|
||||
}
|
||||
|
||||
// Links
|
||||
a {
|
||||
color: $primary-blue;
|
||||
|
||||
&:hover {
|
||||
color: $secondary-blue;
|
||||
}
|
||||
}
|
||||
|
||||
// Blockquote
|
||||
blockquote {
|
||||
padding: $spacing-md;
|
||||
margin: $spacing-md 0;
|
||||
border-left: 4px solid $primary-blue;
|
||||
background: $gray-bg;
|
||||
font-style: italic;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal rule
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid $border-gray;
|
||||
margin: $spacing-xl 0;
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Utility classes
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Container
|
||||
.container {
|
||||
max-width: $container-max-width;
|
||||
margin: 0 auto;
|
||||
padding: 0 $spacing-lg;
|
||||
|
||||
@include respond-to('sm') {
|
||||
padding: 0 $spacing-md;
|
||||
}
|
||||
}
|
||||
|
||||
// Display utilities
|
||||
.d-none {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.d-block {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.d-inline-block {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.d-inline-flex {
|
||||
display: inline-flex !important;
|
||||
}
|
||||
|
||||
.d-grid {
|
||||
display: grid !important;
|
||||
}
|
||||
|
||||
// Flexbox utilities
|
||||
.flex-center {
|
||||
@include flex-center;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
@include flex-between;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.align-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.align-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
// Gap utilities
|
||||
.gap-xs {
|
||||
gap: $spacing-xs;
|
||||
}
|
||||
|
||||
.gap-sm {
|
||||
gap: $spacing-sm;
|
||||
}
|
||||
|
||||
.gap-md {
|
||||
gap: $spacing-md;
|
||||
}
|
||||
|
||||
.gap-lg {
|
||||
gap: $spacing-lg;
|
||||
}
|
||||
|
||||
.gap-xl {
|
||||
gap: $spacing-xl;
|
||||
}
|
||||
|
||||
// Margin utilities
|
||||
@each $size, $value in (
|
||||
0: 0,
|
||||
xs: $spacing-xs,
|
||||
sm: $spacing-sm,
|
||||
md: $spacing-md,
|
||||
lg: $spacing-lg,
|
||||
xl: $spacing-xl,
|
||||
2xl: $spacing-2xl,
|
||||
3xl: $spacing-3xl,
|
||||
4xl: $spacing-4xl,
|
||||
auto: auto
|
||||
) {
|
||||
.m-#{$size} {
|
||||
margin: $value !important;
|
||||
}
|
||||
|
||||
.mt-#{$size} {
|
||||
margin-top: $value !important;
|
||||
}
|
||||
|
||||
.mb-#{$size} {
|
||||
margin-bottom: $value !important;
|
||||
}
|
||||
|
||||
.ml-#{$size} {
|
||||
margin-left: $value !important;
|
||||
}
|
||||
|
||||
.mr-#{$size} {
|
||||
margin-right: $value !important;
|
||||
}
|
||||
|
||||
.mx-#{$size} {
|
||||
margin-left: $value !important;
|
||||
margin-right: $value !important;
|
||||
}
|
||||
|
||||
.my-#{$size} {
|
||||
margin-top: $value !important;
|
||||
margin-bottom: $value !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Padding utilities
|
||||
@each $size, $value in (
|
||||
0: 0,
|
||||
xs: $spacing-xs,
|
||||
sm: $spacing-sm,
|
||||
md: $spacing-md,
|
||||
lg: $spacing-lg,
|
||||
xl: $spacing-xl,
|
||||
2xl: $spacing-2xl,
|
||||
3xl: $spacing-3xl,
|
||||
4xl: $spacing-4xl
|
||||
) {
|
||||
.p-#{$size} {
|
||||
padding: $value !important;
|
||||
}
|
||||
|
||||
.pt-#{$size} {
|
||||
padding-top: $value !important;
|
||||
}
|
||||
|
||||
.pb-#{$size} {
|
||||
padding-bottom: $value !important;
|
||||
}
|
||||
|
||||
.pl-#{$size} {
|
||||
padding-left: $value !important;
|
||||
}
|
||||
|
||||
.pr-#{$size} {
|
||||
padding-right: $value !important;
|
||||
}
|
||||
|
||||
.px-#{$size} {
|
||||
padding-left: $value !important;
|
||||
padding-right: $value !important;
|
||||
}
|
||||
|
||||
.py-#{$size} {
|
||||
padding-top: $value !important;
|
||||
padding-bottom: $value !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Width utilities
|
||||
.w-25 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.w-50 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.w-75 {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
// Height utilities
|
||||
.h-25 {
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
.h-50 {
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.h-75 {
|
||||
height: 75%;
|
||||
}
|
||||
|
||||
.h-100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h-auto {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.vh-100 {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
// Position utilities
|
||||
.position-relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.position-absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.position-fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.position-sticky {
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
// Background utilities
|
||||
.bg-primary {
|
||||
background: $primary-blue;
|
||||
}
|
||||
|
||||
.bg-secondary {
|
||||
background: $secondary-blue;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.bg-gray {
|
||||
background: $gray-bg;
|
||||
}
|
||||
|
||||
.bg-light {
|
||||
background: $light-bg;
|
||||
}
|
||||
|
||||
.bg-gradient-primary {
|
||||
background: $gradient-primary;
|
||||
}
|
||||
|
||||
.bg-gradient-accent {
|
||||
background: $gradient-accent;
|
||||
}
|
||||
|
||||
.bg-gradient-warm {
|
||||
background: $gradient-warm;
|
||||
}
|
||||
|
||||
// Border utilities
|
||||
.border {
|
||||
border: 1px solid $border-gray;
|
||||
}
|
||||
|
||||
.border-0 {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid $border-gray;
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid $border-gray;
|
||||
}
|
||||
|
||||
.border-left {
|
||||
border-left: 1px solid $border-gray;
|
||||
}
|
||||
|
||||
.border-right {
|
||||
border-right: 1px solid $border-gray;
|
||||
}
|
||||
|
||||
.border-primary {
|
||||
border-color: $primary-blue;
|
||||
}
|
||||
|
||||
// Border radius utilities
|
||||
.rounded-0 {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.rounded-sm {
|
||||
border-radius: $border-radius-sm;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: $border-radius-md;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: $border-radius-lg;
|
||||
}
|
||||
|
||||
.rounded-xl {
|
||||
border-radius: $border-radius-xl;
|
||||
}
|
||||
|
||||
.rounded-full {
|
||||
border-radius: $border-radius-full;
|
||||
}
|
||||
|
||||
.rounded-circle {
|
||||
border-radius: $border-radius-circle;
|
||||
}
|
||||
|
||||
// Shadow utilities
|
||||
.shadow-none {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.shadow-sm {
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
|
||||
.shadow-lg {
|
||||
box-shadow: $shadow-lg;
|
||||
}
|
||||
|
||||
.shadow-xl {
|
||||
box-shadow: $shadow-xl;
|
||||
}
|
||||
|
||||
// Overflow utilities
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.overflow-auto {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.overflow-x-hidden {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.overflow-y-hidden {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.overflow-x-auto {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
// Z-index utilities
|
||||
.z-0 {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.z-20 {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.z-30 {
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.z-40 {
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
// Responsive utilities
|
||||
@include respond-to('sm') {
|
||||
.sm-hide {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to('md') {
|
||||
.md-hide {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-only {
|
||||
@media (min-width: #{$breakpoint-sm + 1px}) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.desktop-only {
|
||||
@include respond-to('sm') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor utilities
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cursor-default {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.cursor-not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// Opacity utilities
|
||||
.opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.opacity-25 {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.opacity-50 {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.opacity-75 {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.opacity-100 {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Transition utilities
|
||||
.transition {
|
||||
transition: $transition-base;
|
||||
}
|
||||
|
||||
.transition-fast {
|
||||
transition: $transition-fast;
|
||||
}
|
||||
|
||||
.transition-slow {
|
||||
transition: $transition-slow;
|
||||
}
|
||||
|
||||
.transition-none {
|
||||
transition: none;
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Button styles - Modern vibrant design
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Base button
|
||||
.btn {
|
||||
@include button-base;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// Button sizes
|
||||
&-sm {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
&-md {
|
||||
padding: $spacing-md $spacing-lg;
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
|
||||
&-lg {
|
||||
padding: $spacing-md $spacing-xl;
|
||||
font-size: $font-size-md;
|
||||
}
|
||||
|
||||
&-xl {
|
||||
padding: $spacing-lg $spacing-2xl;
|
||||
font-size: $font-size-lg;
|
||||
}
|
||||
|
||||
// Button variants
|
||||
&-primary {
|
||||
background: $gradient-primary;
|
||||
color: $white;
|
||||
box-shadow: $shadow-md;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: $shadow-xl;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
&-secondary {
|
||||
background: $white;
|
||||
color: $primary-blue;
|
||||
border: 2px solid $primary-blue;
|
||||
|
||||
&:hover {
|
||||
background: $light-bg;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
&-accent {
|
||||
background: $gradient-accent;
|
||||
color: $white;
|
||||
box-shadow: $shadow-md;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: $shadow-xl;
|
||||
}
|
||||
}
|
||||
|
||||
&-warm {
|
||||
background: $gradient-warm;
|
||||
color: $white;
|
||||
box-shadow: $shadow-md;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: $shadow-xl;
|
||||
}
|
||||
}
|
||||
|
||||
&-success {
|
||||
background: $accent-green;
|
||||
color: $white;
|
||||
|
||||
&:hover {
|
||||
background: darken($accent-green, 10%);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
&-danger {
|
||||
background: $accent-orange;
|
||||
color: $white;
|
||||
|
||||
&:hover {
|
||||
background: darken($accent-orange, 10%);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
&-ghost {
|
||||
background: transparent;
|
||||
color: $primary-blue;
|
||||
border: 2px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background: rgba($primary-blue, 0.1);
|
||||
border-color: $primary-blue;
|
||||
}
|
||||
}
|
||||
|
||||
&-outline {
|
||||
background: transparent;
|
||||
color: $text-dark;
|
||||
border: 2px solid $border-gray;
|
||||
|
||||
&:hover {
|
||||
border-color: $primary-blue;
|
||||
color: $primary-blue;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
// Button states
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
color: transparent;
|
||||
pointer-events: none;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@include absolute-center;
|
||||
border: 2px solid $white;
|
||||
border-radius: 50%;
|
||||
border-top-color: transparent;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
// Button with icon
|
||||
&-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
padding: 0;
|
||||
border-radius: $border-radius-circle;
|
||||
|
||||
&.btn-sm {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
&.btn-lg {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
// Full width button
|
||||
&-block {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// Button group
|
||||
&-group {
|
||||
display: inline-flex;
|
||||
gap: -1px;
|
||||
|
||||
.btn {
|
||||
border-radius: 0;
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: $border-radius-lg;
|
||||
border-bottom-left-radius: $border-radius-lg;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: $border-radius-lg;
|
||||
border-bottom-right-radius: $border-radius-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Floating action button
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: $spacing-xl;
|
||||
right: $spacing-xl;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: $gradient-primary;
|
||||
border: none;
|
||||
border-radius: $border-radius-circle;
|
||||
color: $white;
|
||||
font-size: $font-size-xl;
|
||||
cursor: pointer;
|
||||
box-shadow: $shadow-lg;
|
||||
transition: $transition-base;
|
||||
z-index: $z-index-fixed;
|
||||
@include flex-center;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: $shadow-xl;
|
||||
|
||||
.fab-label {
|
||||
opacity: 1;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
.fab-label {
|
||||
position: absolute;
|
||||
right: 70px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: $text-dark;
|
||||
color: $white;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
border-radius: $border-radius-md;
|
||||
font-size: $font-size-sm;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
transition: $transition-base;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// CTA buttons
|
||||
.btn-cta {
|
||||
&-primary {
|
||||
@include button-base;
|
||||
background: $white;
|
||||
color: $primary-blue;
|
||||
padding: $spacing-md $spacing-2xl;
|
||||
font-size: $font-size-md;
|
||||
box-shadow: $shadow-lg;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: $shadow-xl;
|
||||
}
|
||||
}
|
||||
|
||||
&-secondary {
|
||||
@include button-base;
|
||||
background: transparent;
|
||||
color: $white;
|
||||
border: 2px solid $white;
|
||||
padding: $spacing-md $spacing-2xl;
|
||||
font-size: $font-size-md;
|
||||
|
||||
&:hover {
|
||||
background: rgba($white, 0.1);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Card styles - Modern vibrant design
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Base card
|
||||
.card {
|
||||
@include card;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// Card hover effect
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
transparent 30%,
|
||||
rgba($primary-blue, 0.03) 50%,
|
||||
transparent 70%
|
||||
);
|
||||
transform: rotate(45deg);
|
||||
transition: $transition-slow;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
animation: shimmer 0.6s ease;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// API showcase cards
|
||||
.api-card {
|
||||
@include card;
|
||||
text-align: center;
|
||||
border: 2px solid $border-gray;
|
||||
|
||||
&:hover {
|
||||
border-color: $primary-blue;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
|
||||
// Popular variant
|
||||
&-popular {
|
||||
border-color: $accent-yellow;
|
||||
background: linear-gradient(180deg, rgba($accent-yellow, 0.05) 0%, $white 100%);
|
||||
}
|
||||
|
||||
// New variant
|
||||
&-new {
|
||||
border-color: $accent-green;
|
||||
background: linear-gradient(180deg, rgba($accent-green, 0.05) 0%, $white 100%);
|
||||
}
|
||||
|
||||
// API badge
|
||||
.api-badge {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: 20px;
|
||||
padding: $spacing-xs $spacing-md;
|
||||
background: $accent-yellow;
|
||||
color: $text-dark;
|
||||
border-radius: $border-radius-2xl;
|
||||
font-size: $font-size-xs;
|
||||
font-weight: $font-weight-bold;
|
||||
text-transform: uppercase;
|
||||
|
||||
&.new {
|
||||
background: $accent-green;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&.beta {
|
||||
background: $accent-purple;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// API icon
|
||||
.api-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 0 auto $spacing-lg;
|
||||
@include flex-center;
|
||||
font-size: 28px;
|
||||
background: $light-bg;
|
||||
color: $primary-blue;
|
||||
border-radius: $border-radius-xl;
|
||||
transition: $transition-base;
|
||||
}
|
||||
|
||||
&:hover .api-icon {
|
||||
transform: scale(1.1);
|
||||
background: $gradient-primary;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
// API content
|
||||
.api-name {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-bottom: $spacing-md;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
.api-description {
|
||||
color: $text-gray;
|
||||
line-height: $line-height-normal;
|
||||
margin-bottom: $spacing-lg;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
// API features
|
||||
.api-features {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: $spacing-sm;
|
||||
margin-bottom: $spacing-lg;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.feature-tag {
|
||||
padding: $spacing-xs $spacing-sm;
|
||||
background: $gray-bg;
|
||||
color: $text-gray;
|
||||
border-radius: $border-radius-2xl;
|
||||
font-size: $font-size-xs;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
}
|
||||
|
||||
// API link
|
||||
.api-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $spacing-xs;
|
||||
color: $primary-blue;
|
||||
font-weight: $font-weight-semibold;
|
||||
font-size: $font-size-sm;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
gap: $spacing-sm;
|
||||
color: $secondary-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Experience cards
|
||||
.experience-card {
|
||||
@include card;
|
||||
text-align: center;
|
||||
|
||||
.card-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 0 auto $spacing-lg;
|
||||
@include flex-center;
|
||||
font-size: $font-size-xl;
|
||||
background: $gradient-primary;
|
||||
color: $white;
|
||||
border-radius: $border-radius-xl;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-bottom: $spacing-md;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $text-gray;
|
||||
line-height: $line-height-normal;
|
||||
margin-bottom: $spacing-lg;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
// Card metric
|
||||
.card-metric {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: $spacing-lg;
|
||||
border-top: 1px solid $border-gray;
|
||||
|
||||
.metric-value {
|
||||
font-size: $font-size-xl;
|
||||
font-weight: $font-weight-extrabold;
|
||||
color: $primary-blue;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: $font-size-xs;
|
||||
color: $text-gray;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Feature cards
|
||||
.feature-card {
|
||||
@include card;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: $spacing-lg;
|
||||
|
||||
.feature-icon {
|
||||
flex-shrink: 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
@include flex-center;
|
||||
background: $gradient-primary;
|
||||
color: $white;
|
||||
border-radius: $border-radius-lg;
|
||||
font-size: $font-size-xl;
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
flex: 1;
|
||||
|
||||
h4 {
|
||||
font-size: $font-size-md;
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-bottom: $spacing-sm;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $text-gray;
|
||||
line-height: $line-height-normal;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Testimonial cards
|
||||
.testimonial-card {
|
||||
@include card;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '"';
|
||||
position: absolute;
|
||||
top: $spacing-md;
|
||||
left: $spacing-md;
|
||||
font-size: 60px;
|
||||
color: $primary-blue;
|
||||
opacity: 0.1;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.testimonial-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: $font-size-base;
|
||||
line-height: $line-height-loose;
|
||||
color: $text-dark;
|
||||
margin-bottom: $spacing-lg;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.testimonial-author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-md;
|
||||
|
||||
.author-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: $border-radius-circle;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.author-info {
|
||||
.author-name {
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $text-dark;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.author-title {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pricing cards
|
||||
.pricing-card {
|
||||
@include card;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
&.featured {
|
||||
border: 2px solid $primary-blue;
|
||||
transform: scale(1.05);
|
||||
|
||||
.pricing-badge {
|
||||
position: absolute;
|
||||
top: -14px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: $spacing-xs $spacing-md;
|
||||
background: $gradient-primary;
|
||||
color: $white;
|
||||
border-radius: $border-radius-full;
|
||||
font-size: $font-size-xs;
|
||||
font-weight: $font-weight-bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.pricing-header {
|
||||
padding-bottom: $spacing-lg;
|
||||
border-bottom: 1px solid $border-gray;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
h3 {
|
||||
font-size: $font-size-xl;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-bottom: $spacing-sm;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
.price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
gap: $spacing-xs;
|
||||
|
||||
.currency {
|
||||
font-size: $font-size-lg;
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: $font-size-4xl;
|
||||
font-weight: $font-weight-extrabold;
|
||||
@include gradient-text($gradient-primary);
|
||||
}
|
||||
|
||||
.period {
|
||||
font-size: $font-size-base;
|
||||
color: $text-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pricing-features {
|
||||
list-style: none;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
li {
|
||||
padding: $spacing-sm 0;
|
||||
color: $text-gray;
|
||||
font-size: $font-size-sm;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
|
||||
&::before {
|
||||
content: '✓';
|
||||
color: $accent-green;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
|
||||
&::before {
|
||||
content: '×';
|
||||
color: $text-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pricing-cta {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// CTA (Call to Action) section styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.final-cta {
|
||||
position: relative;
|
||||
padding: $spacing-6xl * 1.5 0;
|
||||
background: $gradient-primary;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Form styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Form group
|
||||
.form-group {
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
color: $text-dark;
|
||||
margin-bottom: $spacing-sm;
|
||||
|
||||
.required {
|
||||
color: $accent-orange;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
font-size: $font-size-xs;
|
||||
color: $text-gray;
|
||||
margin-top: $spacing-xs;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
font-size: $font-size-xs;
|
||||
color: $accent-orange;
|
||||
margin-top: $spacing-xs;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-xs;
|
||||
}
|
||||
}
|
||||
|
||||
// Form control base
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
font-size: $font-size-base;
|
||||
font-family: $font-family-primary;
|
||||
color: $text-dark;
|
||||
background: $white;
|
||||
border: 2px solid $border-gray;
|
||||
border-radius: $border-radius-md;
|
||||
transition: $transition-base;
|
||||
|
||||
&::placeholder {
|
||||
color: $text-light;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $primary-blue;
|
||||
box-shadow: 0 0 0 3px rgba($primary-blue, 0.1);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: $gray-bg;
|
||||
color: $text-gray;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// Size variations
|
||||
&-sm {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
&-lg {
|
||||
padding: $spacing-md $spacing-lg;
|
||||
font-size: $font-size-md;
|
||||
}
|
||||
|
||||
// State variations
|
||||
&.is-valid {
|
||||
border-color: $accent-green;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 3px rgba($accent-green, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-invalid {
|
||||
border-color: $accent-orange;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 3px rgba($accent-orange, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Textarea
|
||||
textarea.form-control {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
// Select
|
||||
select.form-control {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M6 8L0 0h12z' fill='%2364748B'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right $spacing-md center;
|
||||
background-size: 12px;
|
||||
padding-right: $spacing-2xl;
|
||||
}
|
||||
|
||||
// Checkbox and Radio
|
||||
.form-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $spacing-md;
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: $spacing-sm;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
|
||||
input,
|
||||
label {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switch toggle
|
||||
.form-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-md;
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 24px;
|
||||
background: $border-gray;
|
||||
border-radius: $border-radius-full;
|
||||
cursor: pointer;
|
||||
transition: $transition-base;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: $white;
|
||||
border-radius: $border-radius-circle;
|
||||
transition: $transition-base;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $primary-blue;
|
||||
|
||||
&::after {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input group
|
||||
.input-group {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.input-group-text {
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
font-size: $font-size-base;
|
||||
color: $text-gray;
|
||||
background: $gray-bg;
|
||||
border: 2px solid $border-gray;
|
||||
border-radius: $border-radius-md 0 0 $border-radius-md;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border-radius: 0 $border-radius-md $border-radius-md 0;
|
||||
}
|
||||
|
||||
&.input-group-append {
|
||||
.input-group-text {
|
||||
border-radius: 0 $border-radius-md $border-radius-md 0;
|
||||
border-right: 2px solid $border-gray;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border-radius: $border-radius-md 0 0 $border-radius-md;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Form inline
|
||||
.form-inline {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: $spacing-md;
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@include respond-to('sm') {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.form-group {
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Form row
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: $spacing-lg;
|
||||
|
||||
@include respond-to('sm') {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Modal styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Modal backdrop
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: $z-index-modal-backdrop;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: $transition-base;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
// Modal wrapper
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: $z-index-modal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $spacing-lg;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: $transition-base;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
|
||||
.modal-dialog {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modal dialog
|
||||
.modal-dialog {
|
||||
position: relative;
|
||||
background: $white;
|
||||
border-radius: $border-radius-xl;
|
||||
box-shadow: $shadow-xl;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: scale(0.95);
|
||||
transition: $transition-base;
|
||||
|
||||
// Size variations
|
||||
&.modal-sm {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
&.modal-lg {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
&.modal-xl {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
&.modal-fullscreen {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Modal header
|
||||
.modal-header {
|
||||
padding: $spacing-lg;
|
||||
border-bottom: 1px solid $border-gray;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.modal-title {
|
||||
font-size: $font-size-xl;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $text-dark;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
@include flex-center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: $border-radius-md;
|
||||
color: $text-gray;
|
||||
font-size: $font-size-xl;
|
||||
cursor: pointer;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
background: $gray-bg;
|
||||
color: $text-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modal body
|
||||
.modal-body {
|
||||
padding: $spacing-lg;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
color: $text-dark;
|
||||
line-height: $line-height-normal;
|
||||
}
|
||||
|
||||
// Modal footer
|
||||
.modal-footer {
|
||||
padding: $spacing-lg;
|
||||
border-top: 1px solid $border-gray;
|
||||
display: flex;
|
||||
gap: $spacing-md;
|
||||
justify-content: flex-end;
|
||||
|
||||
&.modal-footer-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.modal-footer-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
// Alert modal
|
||||
.modal-alert {
|
||||
.modal-dialog {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
text-align: center;
|
||||
padding: $spacing-xl;
|
||||
|
||||
.alert-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 0 auto $spacing-lg;
|
||||
@include flex-center;
|
||||
font-size: $font-size-2xl;
|
||||
border-radius: $border-radius-circle;
|
||||
|
||||
&.alert-success {
|
||||
background: rgba($accent-green, 0.1);
|
||||
color: $accent-green;
|
||||
}
|
||||
|
||||
&.alert-warning {
|
||||
background: rgba($accent-yellow, 0.1);
|
||||
color: $accent-yellow;
|
||||
}
|
||||
|
||||
&.alert-danger {
|
||||
background: rgba($accent-orange, 0.1);
|
||||
color: $accent-orange;
|
||||
}
|
||||
|
||||
&.alert-info {
|
||||
background: rgba($primary-blue, 0.1);
|
||||
color: $primary-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-size: $font-size-xl;
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-bottom: $spacing-sm;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
.alert-message {
|
||||
color: $text-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Navigation component styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Breadcrumb navigation
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
padding: $spacing-md 0;
|
||||
font-size: $font-size-sm;
|
||||
|
||||
.breadcrumb-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
color: $text-gray;
|
||||
|
||||
a {
|
||||
color: $text-gray;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
color: $primary-blue;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $text-dark;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
&:not(:last-child)::after {
|
||||
content: '/';
|
||||
color: $text-light;
|
||||
margin-left: $spacing-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab navigation
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
gap: $spacing-xs;
|
||||
border-bottom: 2px solid $border-gray;
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
.nav-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
padding: $spacing-md $spacing-lg;
|
||||
color: $text-gray;
|
||||
font-weight: $font-weight-medium;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $primary-blue;
|
||||
border-bottom-color: $primary-blue;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 2px 6px;
|
||||
background: $gray-bg;
|
||||
color: $text-gray;
|
||||
border-radius: $border-radius-full;
|
||||
font-size: 11px;
|
||||
font-weight: $font-weight-semibold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pills navigation
|
||||
.nav-pills {
|
||||
display: flex;
|
||||
gap: $spacing-sm;
|
||||
padding: $spacing-xs;
|
||||
background: $gray-bg;
|
||||
border-radius: $border-radius-lg;
|
||||
|
||||
.nav-link {
|
||||
padding: $spacing-sm $spacing-lg;
|
||||
color: $text-gray;
|
||||
font-weight: $font-weight-medium;
|
||||
border-radius: $border-radius-md;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
color: $text-dark;
|
||||
background: rgba($white, 0.5);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $white;
|
||||
color: $primary-blue;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar navigation
|
||||
.sidebar-nav {
|
||||
.nav-section {
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
.nav-title {
|
||||
font-size: $font-size-xs;
|
||||
font-weight: $font-weight-semibold;
|
||||
text-transform: uppercase;
|
||||
color: $text-gray;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
list-style: none;
|
||||
|
||||
.nav-item {
|
||||
margin-bottom: $spacing-xs;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-md;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
color: $text-dark;
|
||||
border-radius: $border-radius-md;
|
||||
transition: $transition-base;
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@include flex-center;
|
||||
font-size: $font-size-md;
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $gray-bg;
|
||||
color: $primary-blue;
|
||||
|
||||
.nav-icon {
|
||||
color: $primary-blue;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $light-bg;
|
||||
color: $primary-blue;
|
||||
font-weight: $font-weight-medium;
|
||||
|
||||
.nav-icon {
|
||||
color: $primary-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nested menu
|
||||
.nav-submenu {
|
||||
margin-left: $spacing-2xl;
|
||||
margin-top: $spacing-xs;
|
||||
list-style: none;
|
||||
|
||||
.nav-link {
|
||||
font-size: $font-size-sm;
|
||||
padding: $spacing-xs $spacing-md;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-xs;
|
||||
|
||||
.page-item {
|
||||
&.disabled {
|
||||
.page-link {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.page-link {
|
||||
background: $primary-blue;
|
||||
color: $white;
|
||||
border-color: $primary-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
padding: 0 $spacing-sm;
|
||||
background: $white;
|
||||
border: 1px solid $border-gray;
|
||||
border-radius: $border-radius-md;
|
||||
color: $text-dark;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
background: $gray-bg;
|
||||
border-color: $primary-blue;
|
||||
color: $primary-blue;
|
||||
}
|
||||
|
||||
&.page-prev,
|
||||
&.page-next {
|
||||
font-size: $font-size-md;
|
||||
}
|
||||
}
|
||||
|
||||
.page-dots {
|
||||
padding: 0 $spacing-sm;
|
||||
color: $text-gray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Partners section styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.partners {
|
||||
padding: $spacing-5xl 0;
|
||||
background: $white;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Container and section styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Main container
|
||||
.container {
|
||||
max-width: $container-max-width;
|
||||
margin: 0 auto;
|
||||
padding: 0 $spacing-lg;
|
||||
|
||||
@include respond-to('sm') {
|
||||
padding: 0 $spacing-md;
|
||||
}
|
||||
|
||||
// Container variations
|
||||
&-fluid {
|
||||
max-width: 100%;
|
||||
padding: 0 $spacing-lg;
|
||||
}
|
||||
|
||||
&-narrow {
|
||||
max-width: 960px;
|
||||
}
|
||||
|
||||
&-wide {
|
||||
max-width: 1440px;
|
||||
}
|
||||
}
|
||||
|
||||
// Section spacing
|
||||
.section {
|
||||
padding: $spacing-6xl 0;
|
||||
|
||||
@include respond-to('sm') {
|
||||
padding: $spacing-4xl 0;
|
||||
}
|
||||
|
||||
// Section variations
|
||||
&-sm {
|
||||
padding: $spacing-3xl 0;
|
||||
}
|
||||
|
||||
&-lg {
|
||||
padding: $spacing-6xl * 1.5 0;
|
||||
}
|
||||
|
||||
&-no-top {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&-no-bottom {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Section backgrounds
|
||||
.section-bg {
|
||||
&-gray {
|
||||
background: $gray-bg;
|
||||
}
|
||||
|
||||
&-light {
|
||||
background: $light-bg;
|
||||
}
|
||||
|
||||
&-gradient {
|
||||
background: linear-gradient(180deg, $white 0%, $light-bg 100%);
|
||||
}
|
||||
|
||||
&-gradient-reverse {
|
||||
background: linear-gradient(180deg, $light-bg 0%, $white 100%);
|
||||
}
|
||||
|
||||
&-primary {
|
||||
background: $gradient-primary;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&-dark {
|
||||
background: $text-dark;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// Section header
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: $spacing-4xl;
|
||||
|
||||
@include respond-to('sm') {
|
||||
margin-bottom: $spacing-3xl;
|
||||
}
|
||||
|
||||
.section-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $spacing-xs;
|
||||
padding: $spacing-xs $spacing-md;
|
||||
background: $light-bg;
|
||||
color: $primary-blue;
|
||||
border-radius: $border-radius-full;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: $font-size-3xl;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-bottom: $spacing-md;
|
||||
color: $text-dark;
|
||||
|
||||
@include respond-to('sm') {
|
||||
font-size: $font-size-2xl;
|
||||
}
|
||||
|
||||
.title-highlight {
|
||||
@include gradient-text($gradient-accent);
|
||||
}
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: $font-size-md;
|
||||
color: $text-gray;
|
||||
line-height: $line-height-normal;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
|
||||
@include respond-to('sm') {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content wrapper
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding-top: $header-height;
|
||||
}
|
||||
}
|
||||
|
||||
// Page wrapper
|
||||
.page-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Inner content
|
||||
.inner-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Footer styles - Modern vibrant design
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.global-footer {
|
||||
background: $text-dark;
|
||||
color: $white;
|
||||
padding: $spacing-5xl 0 $spacing-2xl;
|
||||
|
||||
@include respond-to('sm') {
|
||||
padding: $spacing-3xl 0 $spacing-xl;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-top {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 3fr;
|
||||
gap: $spacing-5xl;
|
||||
margin-bottom: $spacing-4xl;
|
||||
max-width: $container-max-width;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 0 $spacing-lg;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: 1fr;
|
||||
gap: $spacing-2xl;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer brand section
|
||||
.footer-brand {
|
||||
max-width: 360px;
|
||||
|
||||
.footer-logo {
|
||||
height: 48px;
|
||||
margin-bottom: $spacing-lg;
|
||||
}
|
||||
|
||||
.footer-desc {
|
||||
font-size: $font-size-sm;
|
||||
line-height: $line-height-loose;
|
||||
color: rgba($white, 0.7);
|
||||
margin-bottom: $spacing-lg;
|
||||
}
|
||||
|
||||
.footer-newsletter {
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
h4 {
|
||||
font-size: $font-size-md;
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.newsletter-form {
|
||||
display: flex;
|
||||
gap: $spacing-sm;
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
background: rgba($white, 0.1);
|
||||
border: 1px solid rgba($white, 0.2);
|
||||
border-radius: $border-radius-md;
|
||||
color: $white;
|
||||
font-size: $font-size-sm;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba($white, 0.5);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: rgba($white, 0.15);
|
||||
border-color: $primary-blue;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
padding: $spacing-sm $spacing-lg;
|
||||
background: $gradient-primary;
|
||||
color: $white;
|
||||
border-radius: $border-radius-md;
|
||||
font-weight: $font-weight-semibold;
|
||||
font-size: $font-size-sm;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Social links
|
||||
.social-links {
|
||||
display: flex;
|
||||
gap: $spacing-md;
|
||||
|
||||
a {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: rgba($white, 0.1);
|
||||
border-radius: $border-radius-lg;
|
||||
@include flex-center;
|
||||
color: $white;
|
||||
transition: $transition-base;
|
||||
font-size: $font-size-lg;
|
||||
|
||||
&:hover {
|
||||
background: $primary-blue;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
&.facebook:hover {
|
||||
background: #1877f2;
|
||||
}
|
||||
|
||||
&.twitter:hover {
|
||||
background: #1da1f2;
|
||||
}
|
||||
|
||||
&.linkedin:hover {
|
||||
background: #0077b5;
|
||||
}
|
||||
|
||||
&.github:hover {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
&.youtube:hover {
|
||||
background: #ff0000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Footer links
|
||||
.footer-links {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: $spacing-2xl;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@include respond-to('sm') {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-column {
|
||||
h4 {
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-bottom: $spacing-lg;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin-bottom: $spacing-md;
|
||||
|
||||
a {
|
||||
color: rgba($white, 0.7);
|
||||
font-size: $font-size-sm;
|
||||
transition: $transition-base;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background: $accent-cyan;
|
||||
transition: $transition-base;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $accent-cyan;
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special badges
|
||||
.footer-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $spacing-xs;
|
||||
padding: $spacing-xs $spacing-sm;
|
||||
background: rgba($accent-green, 0.2);
|
||||
color: $accent-green;
|
||||
border-radius: $border-radius-full;
|
||||
font-size: $font-size-xs;
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-left: $spacing-sm;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer bottom
|
||||
.footer-bottom {
|
||||
padding-top: $spacing-2xl;
|
||||
border-top: 1px solid rgba($white, 0.1);
|
||||
max-width: $container-max-width;
|
||||
margin: 0 auto;
|
||||
padding-left: $spacing-lg;
|
||||
padding-right: $spacing-lg;
|
||||
}
|
||||
|
||||
.footer-legal {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@include respond-to('sm') {
|
||||
flex-direction: column;
|
||||
gap: $spacing-lg;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgba($white, 0.5);
|
||||
font-size: $font-size-sm;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.legal-links {
|
||||
display: flex;
|
||||
gap: $spacing-lg;
|
||||
|
||||
a {
|
||||
color: rgba($white, 0.7);
|
||||
font-size: $font-size-sm;
|
||||
transition: $transition-base;
|
||||
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Footer decoration
|
||||
.footer-decoration {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -100px;
|
||||
right: -100px;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: radial-gradient(circle, rgba($primary-blue, 0.1) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -150px;
|
||||
left: -150px;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, rgba($accent-cyan, 0.05) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Grid system - Modern responsive layout
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Grid container
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: $spacing-lg;
|
||||
|
||||
// Column templates
|
||||
&-cols-1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&-cols-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
&-cols-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
&-cols-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
&-cols-5 {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
|
||||
&-cols-6 {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
|
||||
&-cols-12 {
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
}
|
||||
|
||||
// Auto-fit grid
|
||||
&-auto {
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
}
|
||||
|
||||
&-auto-sm {
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
}
|
||||
|
||||
&-auto-lg {
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
}
|
||||
|
||||
// Gap variations
|
||||
&.gap-0 {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
&.gap-sm {
|
||||
gap: $spacing-sm;
|
||||
}
|
||||
|
||||
&.gap-md {
|
||||
gap: $spacing-md;
|
||||
}
|
||||
|
||||
&.gap-lg {
|
||||
gap: $spacing-lg;
|
||||
}
|
||||
|
||||
&.gap-xl {
|
||||
gap: $spacing-xl;
|
||||
}
|
||||
|
||||
// Responsive grids
|
||||
@include respond-to('md') {
|
||||
&-md-cols-1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&-md-cols-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to('sm') {
|
||||
&-sm-cols-1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&-sm-cols-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grid item spans
|
||||
.col-span-1 {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.col-span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.col-span-3 {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.col-span-4 {
|
||||
grid-column: span 4;
|
||||
}
|
||||
|
||||
.col-span-5 {
|
||||
grid-column: span 5;
|
||||
}
|
||||
|
||||
.col-span-6 {
|
||||
grid-column: span 6;
|
||||
}
|
||||
|
||||
.col-span-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.row-span-1 {
|
||||
grid-row: span 1;
|
||||
}
|
||||
|
||||
.row-span-2 {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
.row-span-3 {
|
||||
grid-row: span 3;
|
||||
}
|
||||
|
||||
// API Grid
|
||||
.api-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: $spacing-lg;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@include respond-to('sm') {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// Experience Grid
|
||||
.experience-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: $spacing-lg;
|
||||
|
||||
@include respond-to('sm') {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// Partners Grid
|
||||
.partners-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: $spacing-2xl;
|
||||
align-items: center;
|
||||
|
||||
@include respond-to('sm') {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
// Feature Grid
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: $spacing-xl;
|
||||
|
||||
@include respond-to('sm') {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// Demo Grid
|
||||
.demo-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: $spacing-2xl;
|
||||
align-items: start;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer Grid
|
||||
.footer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 3fr;
|
||||
gap: $spacing-5xl;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: 1fr;
|
||||
gap: $spacing-2xl;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: $spacing-2xl;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@include respond-to('sm') {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pricing Grid
|
||||
.pricing-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: $spacing-lg;
|
||||
align-items: center;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: 1fr;
|
||||
gap: $spacing-xl;
|
||||
}
|
||||
}
|
||||
|
||||
// Testimonial Grid
|
||||
.testimonial-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: $spacing-lg;
|
||||
|
||||
@include respond-to('sm') {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// Stats Grid
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: $spacing-xl;
|
||||
text-align: center;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@include respond-to('sm') {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// Two Column Layout
|
||||
.two-column {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: $spacing-3xl;
|
||||
align-items: center;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: 1fr;
|
||||
gap: $spacing-xl;
|
||||
}
|
||||
|
||||
&.reverse {
|
||||
direction: rtl;
|
||||
|
||||
> * {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
@include respond-to('md') {
|
||||
direction: ltr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Three Column Layout
|
||||
.three-column {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: $spacing-xl;
|
||||
|
||||
@include respond-to('md') {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,805 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Header styles - Modern & Responsive Design
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// CSS Variables for modern header styling
|
||||
:root {
|
||||
// Enhanced color palette from design
|
||||
--primary-blue: #4B9BFF;
|
||||
--secondary-blue: #2E7FF7;
|
||||
--accent-cyan: #00D4FF;
|
||||
--accent-yellow: #FFD93D;
|
||||
--accent-orange: #FF6B6B;
|
||||
--accent-green: #6BCF7F;
|
||||
--accent-purple: #A78BFA;
|
||||
|
||||
// Base colors
|
||||
--text-dark: #1A1A2E;
|
||||
--text-gray: #64748B;
|
||||
--text-light: #94A3B8;
|
||||
--white: #FFFFFF;
|
||||
--gray-bg: #F8FAFC;
|
||||
--light-bg: #EFF6FF;
|
||||
--border-gray: #E2E8F0;
|
||||
|
||||
// Gradients
|
||||
--gradient-primary: linear-gradient(135deg, #4B9BFF 0%, #2E7FF7 100%);
|
||||
--gradient-accent: linear-gradient(135deg, #00D4FF 0%, #4B9BFF 100%);
|
||||
--gradient-warm: linear-gradient(135deg, #FFD93D 0%, #FF6B6B 100%);
|
||||
|
||||
// Shadows
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 12px rgba(75, 155, 255, 0.1);
|
||||
--shadow-lg: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||
--shadow-xl: 0 16px 40px rgba(75, 155, 255, 0.2);
|
||||
}
|
||||
|
||||
// Blind text for screen readers
|
||||
.blind {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Modern Header Structure
|
||||
// ===========================
|
||||
.global-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
z-index: 1000;
|
||||
height: 80px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 80px;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Header Layout Components
|
||||
// ===========================
|
||||
|
||||
// Header Left Section
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.logo {
|
||||
height: 45px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// Logo Wrapper
|
||||
.logo-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
// Logo with modern styling
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 45px;
|
||||
z-index: 10;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 45px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--primary-blue);
|
||||
padding: 4px 12px;
|
||||
background: var(--light-bg);
|
||||
border-radius: 20px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
// When logo-text is a link
|
||||
&:hover {
|
||||
background: var(--primary-blue);
|
||||
color: var(--white);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: var(--primary-blue);
|
||||
}
|
||||
}
|
||||
|
||||
// Logo link styles
|
||||
.logo-link,
|
||||
.mobile-logo-link,
|
||||
.drawer-logo-link {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Header Right Section
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
// Search Button
|
||||
.search-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--gray-bg);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: var(--text-gray);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--light-bg);
|
||||
color: var(--primary-blue);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Desktop/Mobile Layout Control
|
||||
// ===========================
|
||||
.desktop-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-header {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Mobile Header Components
|
||||
// ===========================
|
||||
.mobile-left {
|
||||
.mobile-logo {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-center {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
.mobile-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--text-dark);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-right {
|
||||
.mobile-menu-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--gray-bg);
|
||||
}
|
||||
|
||||
.hamburger-line {
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: var(--text-dark);
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.hamburger-line {
|
||||
&:nth-child(1) {
|
||||
transform: rotate(45deg) translateY(6px);
|
||||
}
|
||||
&:nth-child(2) {
|
||||
opacity: 0;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
transform: rotate(-45deg) translateY(-6px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Mobile Drawer/Modal
|
||||
// ===========================
|
||||
.mobile-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.active {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
|
||||
.drawer-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-overlay {
|
||||
display: none; // Not needed for full-screen drawer
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--white);
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.drawer-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
border-bottom: 2px solid var(--border-gray);
|
||||
background: var(--white);
|
||||
box-shadow: var(--shadow-sm);
|
||||
min-height: 60px;
|
||||
|
||||
.drawer-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.logo {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--primary-blue);
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-close {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
color: var(--text-gray);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--white);
|
||||
color: var(--text-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-nav {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
background: var(--white);
|
||||
|
||||
.drawer-menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 20px 0;
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
|
||||
.drawer-link {
|
||||
display: block;
|
||||
padding: 16px 24px;
|
||||
margin: 0 20px;
|
||||
color: var(--text-dark);
|
||||
text-decoration: none;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover, &:active {
|
||||
background: var(--light-bg);
|
||||
color: var(--primary-blue);
|
||||
transform: translateX(4px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-actions {
|
||||
padding: 24px;
|
||||
border-top: 2px solid var(--border-gray);
|
||||
background: var(--white);
|
||||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.drawer-login-btn {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 14px 20px;
|
||||
color: var(--text-dark);
|
||||
text-decoration: none;
|
||||
border: 2px solid var(--primary-blue);
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--light-bg);
|
||||
color: var(--primary-blue);
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-signup-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 14px 20px;
|
||||
background: var(--gradient-primary);
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-bottom: 12px;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-search-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 14px 20px;
|
||||
background: var(--gray-bg);
|
||||
color: var(--text-dark);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--light-bg);
|
||||
color: var(--primary-blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-mobilemenu {
|
||||
position: absolute;
|
||||
right: $spacing-md;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
z-index: $z-index-modal + 1; // Above the nav
|
||||
|
||||
span {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: $text-dark;
|
||||
position: relative;
|
||||
transition: all $transition-fast;
|
||||
text-indent: -9999px;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: $text-dark;
|
||||
transition: all $transition-fast;
|
||||
}
|
||||
|
||||
&::before { top: -8px; }
|
||||
&::after { top: 8px; }
|
||||
}
|
||||
|
||||
&.on span {
|
||||
background: transparent;
|
||||
&::before { top: 0; transform: rotate(45deg); }
|
||||
&::after { top: 0; transform: rotate(-45deg); }
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Unified Navigation (Mobile-First)
|
||||
// ===========================
|
||||
.main-nav.m-nav {
|
||||
// Mobile: Off-canvas menu
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: $white;
|
||||
z-index: $z-index-modal;
|
||||
right: -100%; // Initial state for JS animation
|
||||
transition: none; // JS handles animation
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// The JS will directly manipulate the 'right' property, not toggle a class for this element.
|
||||
// So, no .active or .on state here for the main nav panel.
|
||||
|
||||
.nav-header {
|
||||
padding: $spacing-lg;
|
||||
background: $gray-bg;
|
||||
border-bottom: 1px solid $border-gray;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-grow: 1;
|
||||
|
||||
.nav-item {
|
||||
border-bottom: 1px solid $border-gray;
|
||||
|
||||
> a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: $spacing-lg;
|
||||
color: $text-dark;
|
||||
font-size: $font-size-md;
|
||||
font-weight: $font-weight-medium;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&.has-submenu > a::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: $spacing-lg;
|
||||
top: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-right: 2px solid $text-gray;
|
||||
border-bottom: 2px solid $text-gray;
|
||||
transform: translateY(-50%) rotate(45deg);
|
||||
transition: transform $transition-fast;
|
||||
}
|
||||
|
||||
&.expanded > a::after {
|
||||
transform: translateY(-25%) rotate(-135deg);
|
||||
}
|
||||
|
||||
.sub-menu {
|
||||
display: none;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: $gray-bg;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: $spacing-md $spacing-lg $spacing-md ($spacing-lg * 2);
|
||||
color: $text-gray;
|
||||
font-size: $font-size-sm;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: $white;
|
||||
color: $primary-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded .sub-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info_wrap {
|
||||
padding: $spacing-xl $spacing-lg;
|
||||
background: $white;
|
||||
border-top: 1px solid $border-gray;
|
||||
margin-top: auto;
|
||||
// styles for tit1, tit2...
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Modern Navigation Menu
|
||||
// ===========================
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
text-decoration: none;
|
||||
color: var(--text-dark);
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--light-bg);
|
||||
color: var(--primary-blue);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.signup-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
background: var(--gradient-primary);
|
||||
color: var(--white);
|
||||
padding: 12px 24px;
|
||||
border-radius: 50px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Desktop Navigation Styles
|
||||
// ===========================
|
||||
@include respond-to('desktop') {
|
||||
.m-only { display: none !important; }
|
||||
.pc-only { display: block !important; }
|
||||
|
||||
.btn-mobilemenu { display: none; }
|
||||
|
||||
.main-nav.m-nav {
|
||||
position: static;
|
||||
right: auto;
|
||||
transition: none;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
|
||||
.nav-header { display: none; }
|
||||
.info_wrap { display: none; }
|
||||
|
||||
.nav-list {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
|
||||
.nav-item {
|
||||
border-bottom: none;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
> a {
|
||||
height: 100%;
|
||||
padding: 0 24px;
|
||||
color: var(--text-dark);
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-blue);
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
height: 3px;
|
||||
background: var(--primary-blue);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-submenu > a::after { display: none; }
|
||||
|
||||
.sub-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background: var(--white);
|
||||
border: 1px solid var(--border-gray);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
min-width: 200px;
|
||||
padding: 8px 0;
|
||||
display: none;
|
||||
z-index: 1001;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 8px 16px;
|
||||
color: var(--text-dark);
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--light-bg);
|
||||
color: var(--primary-blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .sub-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Body Layout Compensation
|
||||
// ===========================
|
||||
body {
|
||||
padding-top: 80px; // Compensate for fixed header
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Mobile Responsive Design
|
||||
// ===========================
|
||||
@media (max-width: 768px) {
|
||||
.global-header {
|
||||
height: 60px;
|
||||
|
||||
.container .header-content {
|
||||
padding: 0 16px;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 60px; // Smaller header on mobile
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.global-header {
|
||||
.header-content {
|
||||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
.logo-text {
|
||||
display: none; // Hide logo text on very small screens
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Main SASS Entry Point - Version 2 (Modern Vibrant Design)
|
||||
// Based on design/styles-v2.css
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@charset "utf-8";
|
||||
|
||||
// 1. Configuration and helpers (no CSS output)
|
||||
@import 'abstracts/variables';
|
||||
@import 'abstracts/mixins';
|
||||
|
||||
// 2. Base styles
|
||||
@import 'base/reset';
|
||||
@import 'base/typography';
|
||||
@import 'base/animations';
|
||||
|
||||
// 3. Layout
|
||||
@import 'layout/header';
|
||||
@import 'layout/footer';
|
||||
@import 'layout/grid';
|
||||
@import 'layout/container';
|
||||
|
||||
// 4. Components
|
||||
@import 'components/buttons';
|
||||
@import 'components/cards';
|
||||
@import 'components/forms';
|
||||
@import 'components/modals';
|
||||
@import 'components/navigation';
|
||||
@import 'components/partners';
|
||||
@import 'components/cta';
|
||||
|
||||
// 5. Page-specific styles
|
||||
@import 'pages/index';
|
||||
@import 'pages/home';
|
||||
@import 'pages/api-portal';
|
||||
@import 'pages/api-market';
|
||||
@import 'pages/documentation';
|
||||
@import 'pages/login';
|
||||
|
||||
// 6. Themes
|
||||
@import 'themes/dark';
|
||||
|
||||
// 7. Utilities (should be last)
|
||||
@import 'base/utilities';
|
||||
|
||||
// 8. Vendor overrides
|
||||
@import 'vendors/overrides';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// API Portal page specific styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.api-portal-page {
|
||||
// Any API portal-specific styles go here
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Documentation page specific styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.documentation-page {
|
||||
// Any documentation-specific styles go here
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Home page specific styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.home-page {
|
||||
// Any home-specific styles go here
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,308 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Login Page Styles - Modern Design
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.login-page {
|
||||
min-height: calc(100vh - 140px); // Account for header and footer
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, var(--light-bg) 0%, var(--gray-bg) 100%);
|
||||
padding: 40px 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// Decorative background elements
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -20%;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
background: radial-gradient(circle, var(--accent-cyan) 0%, transparent 70%);
|
||||
opacity: 0.1;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -30%;
|
||||
left: -10%;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, var(--primary-blue) 0%, transparent 70%);
|
||||
opacity: 0.1;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: var(--white);
|
||||
border-radius: 24px;
|
||||
box-shadow: var(--shadow-xl);
|
||||
padding: 48px 40px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
|
||||
@media (max-width: 480px) {
|
||||
padding: 32px 24px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
|
||||
.login-logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
img {
|
||||
height: 48px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--primary-blue);
|
||||
padding: 6px 16px;
|
||||
background: var(--light-bg);
|
||||
border-radius: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-dark);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: 15px;
|
||||
color: var(--text-gray);
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-dark);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
font-size: 15px;
|
||||
border: 2px solid var(--border-gray);
|
||||
border-radius: 12px;
|
||||
background: var(--white);
|
||||
transition: all 0.3s ease;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--primary-blue);
|
||||
box-shadow: 0 0 0 4px rgba(75, 155, 255, 0.1);
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: var(--accent-orange);
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: var(--primary-blue);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
color: var(--text-gray);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
padding: 16px 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--white);
|
||||
background: var(--gradient-primary);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.login-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-top: 32px;
|
||||
padding-top: 32px;
|
||||
border-top: 1px solid var(--border-gray);
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
color: var(--text-gray);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-blue);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.link-separator {
|
||||
color: var(--border-gray);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
// Alert messages
|
||||
.login-alert {
|
||||
margin-bottom: 20px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&.alert-error {
|
||||
background: rgba(255, 107, 107, 0.1);
|
||||
color: var(--accent-orange);
|
||||
border: 1px solid rgba(255, 107, 107, 0.2);
|
||||
}
|
||||
|
||||
&.alert-success {
|
||||
background: rgba(107, 207, 127, 0.1);
|
||||
color: var(--accent-green);
|
||||
border: 1px solid rgba(107, 207, 127, 0.2);
|
||||
}
|
||||
|
||||
&.alert-info {
|
||||
background: rgba(75, 155, 255, 0.1);
|
||||
color: var(--primary-blue);
|
||||
border: 1px solid rgba(75, 155, 255, 0.2);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
// Loading state
|
||||
.login-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 24px;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--border-gray);
|
||||
border-top-color: var(--primary-blue);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Dark theme styles (optional)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Dark theme variables can be defined here
|
||||
// This file can be left empty if dark theme is not needed yet
|
||||
@@ -0,0 +1,6 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Vendor library overrides
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Place any third-party library style overrides here
|
||||
// This file can be left empty if no vendor libraries are used
|
||||
@@ -1,204 +1,454 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_api_layout}">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/kjbank_base_layout}">
|
||||
|
||||
<body>
|
||||
|
||||
<section layout:fragment="contentFragment" class="content api_con">
|
||||
<style>
|
||||
/* Custom dark theme for CodeMirror */
|
||||
.cm-s-custom-dark.CodeMirror {
|
||||
background-color: #252B37;
|
||||
color: white;
|
||||
}
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<section class="api-market-container">
|
||||
|
||||
.cm-s-custom-dark .CodeMirror-gutters {
|
||||
background-color: #1a1a1a;
|
||||
border-right: 1px solid #3a3a3a;
|
||||
}
|
||||
|
||||
.cm-s-custom-dark .CodeMirror-linenumber {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.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}">
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="api-market-sidebar" id="apiSidebar">
|
||||
<nav class="api-sidebar-nav">
|
||||
<!-- All APIs -->
|
||||
<div class="menu-section">
|
||||
<div class="menu-title" th:classappend="${selected == '-1'} ? 'active'" data-group="">
|
||||
전체
|
||||
<span class="api-count" th:text="${totalApiCount}">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inner i_cs h_inner2">
|
||||
<div class="api_stit m-only">
|
||||
<table class="api_table" cellspacing="0" cellpadding="0" width="100%">
|
||||
<colgroup>
|
||||
<col width="110px">
|
||||
<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}">
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Level 2: API List -->
|
||||
<div class="api-list" th:if="${service.apiGroupApiList != null and !service.apiGroupApiList.isEmpty()}">
|
||||
<div class="api-item"
|
||||
th:each="api : ${service.apiGroupApiList}"
|
||||
th:data-api-id="${api.apiId}"
|
||||
th:attr="data-href=@{/apis/detail(apiId=${api.apiId})}">
|
||||
<span class="api-name" th:text="${api.apiDesc}">API Name</span>
|
||||
</div>
|
||||
</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>서비스</h1>
|
||||
<h2 th:text="${apiSpecInfo.apiName}">API 이름</h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- API Content -->
|
||||
<div class="api-detail-content">
|
||||
|
||||
<div class="api_stit api_sc api_tb_type api_sc2">
|
||||
<h3>요청 메시지</h3>
|
||||
<div class="pc-only" th:utext="${apiSpecInfo.apiRequestSpec}">
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="api-detail-tabs">
|
||||
<button class="tab-button active" data-tab="api-info">API 정보</button>
|
||||
<button class="tab-button" data-tab="testbed">테스트베드</button>
|
||||
</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 class="api_stit api_sc">
|
||||
<h3>요청 메시지 예제</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p class="tit">
|
||||
<span>Header</span>
|
||||
</p>
|
||||
<div class="api_sauce">
|
||||
<p>
|
||||
Content-Type: application/json
|
||||
</p>
|
||||
|
||||
<!-- API Details Grid -->
|
||||
<div class="api-details-grid">
|
||||
|
||||
<!-- Request Specification -->
|
||||
<div class="api-detail-card" th:if="${apiSpecInfo.apiRequestSpec != null}">
|
||||
<h3>Request Specification</h3>
|
||||
<div class="detail-content" th:utext="${apiSpecInfo.apiRequestSpec}">
|
||||
Request spec
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="api_stit api_sc api_sc3">
|
||||
<ul>
|
||||
<li>
|
||||
<p class="tit">
|
||||
<span>Body</span>
|
||||
</p>
|
||||
<div class="api_sauce">
|
||||
<textarea id="sampleRequest" th:text="${apiSpecInfo.sampleRequest}"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Sample Request -->
|
||||
<div class="api-detail-card" th:if="${apiSpecInfo.sampleRequest != null}">
|
||||
<h3>샘플 요청</h3>
|
||||
<div class="detail-content">
|
||||
<pre><code th:text="${apiSpecInfo.sampleRequest}">Sample request</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 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 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>
|
||||
|
||||
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const commonOptions = {
|
||||
lineNumbers: false,
|
||||
mode: {name: 'javascript', json: true},
|
||||
theme: 'custom-dark',
|
||||
readOnly: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true,
|
||||
foldGutter: true
|
||||
};
|
||||
const sampleRequestEditor = CodeMirror.fromTextArea(document.getElementById('sampleRequest'), commonOptions);
|
||||
<script th:inline="javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// DOM Elements
|
||||
const sidebar = document.getElementById('apiSidebar');
|
||||
const mobileToggle = document.getElementById('mobileToggle');
|
||||
const mobileOverlay = document.getElementById('mobileOverlay');
|
||||
|
||||
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
|
||||
sampleRequestEditor.refresh();
|
||||
sampleResponseEditor.refresh();
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 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>
|
||||
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,153 +1,250 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/kjbank_base_layout}">
|
||||
|
||||
<body>
|
||||
|
||||
<section layout:fragment="contentFragment" class="content">
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<section class="api-market-container">
|
||||
|
||||
<div class="content_wrap">
|
||||
<div class="api_service_type m-only">
|
||||
<img th:src="@{/img/bg_api_visual03.png}" alt="Kbank API 서비스">
|
||||
<div class="service_txt">
|
||||
<p>Kbank API 서비스</p>
|
||||
<span>
|
||||
API를 활용해 당신의 비즈니스 모델을<br/>
|
||||
혁신하여 창의적이고 차별화된 서비스를<br/>
|
||||
제작하세요.
|
||||
</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>
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="api-market-sidebar" id="apiSidebar">
|
||||
<nav class="api-sidebar-nav">
|
||||
<!-- All APIs -->
|
||||
<div class="menu-section">
|
||||
<div class="menu-title" th:classappend="${selected == '-1'} ? 'active'" data-group="">
|
||||
전체
|
||||
<span class="api-count" th:text="${totalApiCount}">0</span>
|
||||
</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 class="api_keyword">
|
||||
<ul class="keyword_list tab_list grt">
|
||||
<li>
|
||||
<a href="#">전체<span class="number" th:text="${selectedApiCount}" th:classppend="${selected == '-1'}?'active'"></span></a>
|
||||
</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>
|
||||
|
||||
<!-- Result Count -->
|
||||
<div class="api-result-count">
|
||||
총 <strong th:text="${selectedApiCount}">0</strong>건
|
||||
</div>
|
||||
<div class="m-only">
|
||||
<div class="api_info_container card_top">
|
||||
<div class="api_box" th:each="api : ${apis}" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<div class="api_info_box">
|
||||
<div class="api_list" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<p class="txt1" th:text="${api.apiName}"></p>
|
||||
<p class="txt2" th:text="${api.apiSimpleDescription}"></p>
|
||||
</div>
|
||||
<div class="api_img" style="height: 48px;">
|
||||
<img th:if="${#strings.isEmpty(api.mainIcon) == false}" th:src="${api.mainIcon}" alt="">
|
||||
</div>
|
||||
|
||||
<!-- API Cards Grid -->
|
||||
<div class="api-card-grid" th:if="${apis != null and !apis.isEmpty()}">
|
||||
<div class="api-card"
|
||||
th:each="api : ${apis}"
|
||||
th:data-href="@{/apis/detail(id=${api.apiId})}"
|
||||
role="button"
|
||||
tabindex="0">
|
||||
<div>
|
||||
<!-- Card Header with Badge -->
|
||||
<div class="api-card-header">
|
||||
<span class="api-card-badge" th:if="${api.apiGroupName}" th:text="${api.apiGroupName}">
|
||||
카테고리
|
||||
</span>
|
||||
</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 class="pc-only">
|
||||
<div class="ser_result" >총 <span>[[${selectedApiCount}]]건</span> 검색</div>
|
||||
<div class="api_info_container box_list mt_0">
|
||||
<div class="api_box" th:each="api : ${apis}" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<div class="api_info_box pc-only">
|
||||
<div class="api_list" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<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>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div class="api-empty-state" th:if="${apis == null or apis.isEmpty()}">
|
||||
<div class="empty-icon">🔍</div>
|
||||
<h3>검색 결과가 없습니다</h3>
|
||||
<p>다른 키워드로 검색하거나 카테고리를 변경해 보세요.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</section>
|
||||
</th:block>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const apiListItems = document.querySelectorAll('.api_list');
|
||||
apiListItems.forEach(function(item) {
|
||||
item.addEventListener('click', function() {
|
||||
var href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
<script th:inline="javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// DOM Elements
|
||||
const sidebar = document.getElementById('apiSidebar');
|
||||
const mobileToggle = document.getElementById('mobileToggle');
|
||||
const mobileOverlay = document.getElementById('mobileOverlay');
|
||||
const searchForm = document.getElementById('searchForm');
|
||||
const groupIdsInput = searchForm.querySelector('input[name="groupIds"]');
|
||||
const searchInput = searchForm.querySelector('input[name="keyword"]');
|
||||
|
||||
const apiItems = document.querySelectorAll('.api_box');
|
||||
apiItems.forEach(function(item) {
|
||||
item.removeEventListener('click',null);
|
||||
item.addEventListener('click', function() {
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
|
||||
// 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');
|
||||
var form = document.getElementById('searchForm');
|
||||
var groupIdsInput = form.querySelector('input[name="groupIds"]');
|
||||
// Category/Service Selection
|
||||
const menuTitles = document.querySelectorAll('.menu-title');
|
||||
menuTitles.forEach(function(title) {
|
||||
title.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
keywordListItems.forEach(function(item) {
|
||||
item.addEventListener('click', function(e) {
|
||||
e.preventDefault(); // Prevent default anchor behavior
|
||||
var anchor = this.querySelector('a');
|
||||
var groupId = anchor.getAttribute('data-group');
|
||||
const groupId = this.getAttribute('data-group');
|
||||
|
||||
// Update groupIds input
|
||||
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;
|
||||
} else {
|
||||
if (groupIdsInput) {
|
||||
groupIdsInput.remove();
|
||||
}
|
||||
groupIdsInput.value = '';
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,104 +1,90 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
<body>
|
||||
<section layout:fragment="contentFragment" class="content">
|
||||
<div class="content_wrap">
|
||||
<div class="sub_title2">
|
||||
<h2 class="title add7">로그인</h2>
|
||||
</div>
|
||||
<div class="inner i_cs9 h_inner9">
|
||||
<div class="form_type">
|
||||
<form id="loginForm" role="form" name="loginForm" th:action="@{/actionLogin.do}" method="post">
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<div class="login-page">
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<!-- Login Header with Logo -->
|
||||
<div class="login-header">
|
||||
<h1 class="login-title">로그인</h1>
|
||||
<p class="login-subtitle">광주은행 개발자 포털에 오신 것을 환영합니다</p>
|
||||
</div>
|
||||
|
||||
<!-- Alert Messages -->
|
||||
<div th:if="${param.logout}" class="login-alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<span>로그아웃되었습니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form id="loginForm" role="form" name="loginForm" th:action="@{/actionLogin.do}" method="post" class="login-form">
|
||||
<input type="hidden" id="message" name="message" th:value="${loginMessage}">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
|
||||
<div class="form_type_box">
|
||||
<div class="info1 form_top">
|
||||
<p class="title">
|
||||
<span>아이디</span>
|
||||
</p>
|
||||
<div class="info_line info_add">
|
||||
<div class="info_box1 add add2">
|
||||
<input type="text" id="id" name="id" th:value="${loginId}"
|
||||
class="common_input_type_1 h_inp"
|
||||
placeholder="이메일 아이디를 입력해 주세요." required>
|
||||
<input type="hidden" id="loginId" th:value="${loginId}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" id="loginId" th:value="${loginId}">
|
||||
|
||||
<div class="info1">
|
||||
<p class="title">
|
||||
<span>비밀번호</span>
|
||||
</p>
|
||||
<div class="info_line info_add">
|
||||
<div class="info_box1 add add2">
|
||||
<input type="password" id="password" name="password"
|
||||
class="common_input_type_1 h_inp"
|
||||
placeholder="비밀번호를 입력해 주세요." required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Email Field -->
|
||||
<div class="form-group">
|
||||
<label for="id" class="form-label">이메일</label>
|
||||
<input type="text"
|
||||
id="id"
|
||||
name="id"
|
||||
th:value="${loginId}"
|
||||
class="form-input"
|
||||
placeholder="이메일 아이디를 입력해 주세요"
|
||||
required>
|
||||
</div>
|
||||
<div class="box_inp">
|
||||
<span class="inp_check f_size">
|
||||
<input id="checkId" name="checkId" type="checkbox">
|
||||
<label for="checkId">아이디 저장</label>
|
||||
</span>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div class="form-group">
|
||||
<label for="password" class="form-label">비밀번호</label>
|
||||
<input type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="form-input"
|
||||
placeholder="비밀번호를 입력해 주세요"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<!-- Remember Me Checkbox -->
|
||||
<div class="form-checkbox">
|
||||
<input id="checkId" name="checkId" type="checkbox">
|
||||
<label for="checkId">아이디 저장</label>
|
||||
</div>
|
||||
</form>
|
||||
<div class="btn_login">
|
||||
<a href="#" class="common_btn_type_1 h_btn" id="actionLogin"><span>로그인</span></a>
|
||||
|
||||
<!-- Login Button -->
|
||||
<button type="button" class="login-button" id="actionLogin">
|
||||
로그인
|
||||
</button>
|
||||
|
||||
<!-- Additional Links -->
|
||||
<div class="login-links">
|
||||
<a th:href="@{/account_recovery(tab='findId')}">
|
||||
<i class="fas fa-search"></i>
|
||||
<span>아이디 찾기</span>
|
||||
</a>
|
||||
<span class="link-separator">|</span>
|
||||
<a th:href="@{/account_recovery(tab='resetPassword')}">
|
||||
<i class="fas fa-lock"></i>
|
||||
<span>비밀번호 초기화</span>
|
||||
</a>
|
||||
<span class="link-separator">|</span>
|
||||
<a th:href="@{/signup}">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
<span>회원가입</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="login_list pc-only">
|
||||
<ul>
|
||||
<li class="icon_search">
|
||||
<img th:src="@{/img/icon/icon_login_search.png}" alt="">
|
||||
<a th:href="@{/account_recovery(tab='findId')}">아이디 찾기</a>
|
||||
</li>
|
||||
<li>
|
||||
<span>|</span>
|
||||
</li>
|
||||
<li class="icon_lock">
|
||||
<img th:src="@{/img/icon/icon_login_lock.png}" alt="">
|
||||
<a th:href="@{/account_recovery(tab='resetPassword')}">비밀번호 초기화</a>
|
||||
</li>
|
||||
<li>
|
||||
<span>|</span>
|
||||
</li>
|
||||
<li class="icon_join">
|
||||
<img th:src="@{/img/icon/icon_login_join.png}" alt="">
|
||||
<a th:href="@{/signup}">회원가입</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="login_list m-only">
|
||||
<ul>
|
||||
<li class="icon_search">
|
||||
<a th:href="@{/account_recovery(tab='findId')}">아이디 찾기</a>
|
||||
</li>
|
||||
<li>
|
||||
<span>|</span>
|
||||
</li>
|
||||
<li class="icon_lock">
|
||||
<a th:href="@{/account_recovery(tab='resetPassword')}">비밀번호 초기화</a>
|
||||
</li>
|
||||
<li>
|
||||
<span>|</span>
|
||||
</li>
|
||||
<li class="icon_join">
|
||||
<a th:href="@{/signup}">회원가입</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div th:if="${param.logout}" class="alert alert-info mt-3">
|
||||
<p>로그아웃되었습니다.</p>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div class="login-loading" id="loginLoading">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</th:block>
|
||||
</body>
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script th:inline="javascript">
|
||||
@@ -179,6 +165,10 @@
|
||||
|
||||
let actionLogin = function (event) {
|
||||
let form = document.loginForm;
|
||||
|
||||
// Show loading state
|
||||
$('#loginLoading').addClass('active');
|
||||
|
||||
if ($('#checkId').is(':checked')) {
|
||||
setCookie('saveid', $('#id').val(), new Date(new Date().getTime() + 1000 * 3600 * 24 * 30));
|
||||
} else {
|
||||
@@ -188,9 +178,11 @@
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$('#loginLoading').removeClass('active');
|
||||
customPopups.showAlert('입력 정보 확인');
|
||||
} else {
|
||||
if (form.password.value.length < 8) {
|
||||
$('#loginLoading').removeClass('active');
|
||||
customPopups.showAlert('[[#{login.passLengthShort}]]');
|
||||
} else {
|
||||
const originalPassword = form.password.value;
|
||||
@@ -201,7 +193,10 @@
|
||||
form.classList.add('was-validated');
|
||||
};
|
||||
|
||||
$('#actionLogin').on('click', actionLogin);
|
||||
$('#actionLogin').on('click', function(event) {
|
||||
event.preventDefault();
|
||||
actionLogin(event);
|
||||
});
|
||||
$('#password').on('keydown', function (event) {
|
||||
if (event.key === 'Enter' || event.keyCode === 13 || event.code === 'Enter') {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -1,459 +1,372 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_index_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_index_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
<section layout:fragment="contentFragment" class="content main">
|
||||
<script th:src="@{/js/htmx.min.js}"></script>
|
||||
<!-- main slick -->
|
||||
<div class="slider-container">
|
||||
<div class="slider m-only">
|
||||
<div class="slider_box">
|
||||
|
||||
<img th:src="@{/img/img_mainslick_m.png}" alt="슬라이드 1">
|
||||
<div class="slick_txt">
|
||||
<p>디지털 금융 리더 <span>케이뱅크</span>가<br/>새로운 비즈니스 기회를<br/>제공합니다.</p>
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<section class="hero-section">
|
||||
<div class="hero-patterns">
|
||||
<div class="pattern-circle pattern-1"></div>
|
||||
<div class="pattern-circle pattern-2"></div>
|
||||
<div class="pattern-dots"></div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="hero-content">
|
||||
<div class="hero-badge">
|
||||
<span class="badge-icon">🚀</span>
|
||||
<span>Wa bank 생태계에 오신 것을 환영합니다!</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick2_m.png}" alt="슬라이드 2">
|
||||
<div class="slick_txt">
|
||||
<p>차별화된 <span>API와<br> 편리한 테스트 환경</span>을<br> 지원합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick3_m.png}" alt="슬라이드 3">
|
||||
<div class="slick_txt">
|
||||
<p><span>디지털 금융 혁신</span>을<br> 케이뱅크가<br> 함께합니다</p>
|
||||
<h1 class="main-headline">
|
||||
<span class="headline-wa">Wa한</span> 금융 혁신,<br>
|
||||
여러분의 아이디어로 시작됩니다
|
||||
</h1>
|
||||
<h2 class="sub-headline">
|
||||
광주은행 오픈 API로 새로운 금융 서비스를 만들어보세요.<br>
|
||||
안전하고 빠르게, 그리고 쉽게 연동할 수 있습니다.
|
||||
</h2>
|
||||
<div class="hero-buttons">
|
||||
<a href="/apis" class="btn btn-primary">
|
||||
<i class="fas fa-code"></i> API 둘러보기
|
||||
</a>
|
||||
<a href="#get-started" class="btn btn-secondary">
|
||||
<i class="fas fa-play"></i> 5분 만에 시작하기
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider pc-only">
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick_pc.png}" alt="슬라이드 1">
|
||||
<div class="slick_txt">
|
||||
<p><span>케이뱅크</span>가 새로운 비즈니스<br> 기회를 제공합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick2_pc.png}" alt="슬라이드 2">
|
||||
<div class="slick_txt">
|
||||
<p>차별화된 <span>API와</span><br/>
|
||||
<span>편리한 테스트 환경</span>을<br/>지원합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick3_pc.png}" alt="슬라이드 3">
|
||||
<div class="slick_txt">
|
||||
<p><span>디지털 금융 혁신</span>을<br>케이뱅크가<br>함께합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="slick-prev" aria-label="Previous" type="button">이전</button>
|
||||
<button class="slick-next" aria-label="Next" type="button">다음</button>
|
||||
<ul class="slick-dots">
|
||||
<li>
|
||||
<button type="button">1</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button">2</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button">3</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- // main slick -->
|
||||
</section>
|
||||
|
||||
<div class="main-container">
|
||||
<div class="search_type">
|
||||
<form th:action="@{/apis}" id="searchForm" method="get">
|
||||
<div class="search-container">
|
||||
<input type="text" name="keyword" class="search-box" id="search-box" placeholder="어떤 API를 찾고 계신가요?">
|
||||
<button class="search-button" type="submit"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="hashtag">
|
||||
<ul>
|
||||
<li th:each="tag: ${hashtags}"><a href="#" th:text="${tag}">#인증</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main_keyword">
|
||||
<div class="api_tit">
|
||||
<h2>가장 많이 찾는 API</h2>
|
||||
<p class="total_view">
|
||||
<a href="/apis">전체보기</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="api_keyword">
|
||||
<ul class="keyword_list tab_list">
|
||||
<li>
|
||||
<a href="#"
|
||||
hx-get="/api_fragments"
|
||||
hx-target="#api_spec_list"
|
||||
hx-trigger="click"
|
||||
onclick="setActive(this)">전체<span class="number" th:text="${totalApiCount}"></span></a>
|
||||
</li>
|
||||
<li th:each="item : ${services}">
|
||||
<a href="#"
|
||||
th:hx-get="@{/api_fragments(id=${item.id})}"
|
||||
hx-target="#api_spec_list"
|
||||
hx-trigger="click"
|
||||
onclick="setActive(this)">
|
||||
[[${item.groupName}]]<span class="number" th:text="${item.apiGroupApiList.size()}"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="more_type m-only">
|
||||
<button class="showmore" type="button">
|
||||
<img th:src="@{/img/icon/icon_more_btn.png}" alt="더보기 버튼">
|
||||
</button>
|
||||
</div>
|
||||
<div class="more_type pc-only">
|
||||
<button class="showmore" type="button">
|
||||
<img th:src="@{/img/icon/icon_more_btn2.png}" alt="더보기 버튼">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="api_info_container box_list" id="api_spec_list">
|
||||
<div class="api_info_box" th:each="api : ${apis}" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<div class="api_list" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<p class="txt1" th:text="${api.apiName}"></p>
|
||||
<p class="txt2" th:text="${api.apiSimpleDescription}"></p>
|
||||
<!-- API 검색 섹션 -->
|
||||
<section class="api-search-section">
|
||||
<div class="container">
|
||||
<div class="search-container">
|
||||
<form th:action="@{/apis}" method="get" class="search-form">
|
||||
<div class="search-wrapper">
|
||||
<input type="text" class="search-input" placeholder="원하는 API를 검색해보세요" id="apiSearchInput" name="keyword">
|
||||
<button class="search-button" type="submit">
|
||||
<i class="fas fa-search"></i>
|
||||
<span>검색하기</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="api_img" style="height: 48px;">
|
||||
<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>
|
||||
</form>
|
||||
<div class="hashtag-suggestions" th:if="${hashtags != null and not #lists.isEmpty(hashtags)}">
|
||||
<span class="hashtag-label">추천 검색어:</span>
|
||||
<a href="#" class="hashtag" th:each="tag : ${hashtags}"
|
||||
th:data-search="${tag}"
|
||||
th:text="${'#' + tag}">
|
||||
</a>
|
||||
</div>
|
||||
<!-- Fallback hashtags if no hashtags from controller -->
|
||||
<div class="hashtag-suggestions" th:unless="${hashtags != null and not #lists.isEmpty(hashtags)}">
|
||||
<span class="hashtag-label">추천 검색어:</span>
|
||||
<a href="#" class="hashtag" data-search="계좌조회">#계좌조회</a>
|
||||
<a href="#" class="hashtag" data-search="송금API">#송금API</a>
|
||||
<a href="#" class="hashtag" data-search="실시간결제">#실시간결제</a>
|
||||
<a href="#" class="hashtag" data-search="오픈뱅킹">#오픈뱅킹</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="api-showcase">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="title-highlight">인기 API</span>로 바로 시작하세요
|
||||
</h2>
|
||||
<p class="section-subtitle">가장 많이 사용되는 API를 확인하고 바로 테스트해보세요</p>
|
||||
</div>
|
||||
<div class="api-grid">
|
||||
<!-- Dynamic API Cards from Controller (max 4) -->
|
||||
<div th:each="api, iterStat : ${apis}"
|
||||
th:if="${iterStat.index < 4}"
|
||||
class="api-card"
|
||||
th:classappend="${iterStat.index == 0} ? 'api-card-popular' : (${iterStat.index == 3} ? 'api-card-new' : '')">
|
||||
|
||||
<!-- Show badge for first (인기) and last (NEW) items -->
|
||||
<div th:if="${iterStat.index == 0}" class="api-badge">인기</div>
|
||||
<div th:if="${iterStat.index == 3}" class="api-badge new">NEW</div>
|
||||
|
||||
<!-- API Icon - using different icons based on index or API type -->
|
||||
<div class="api-icon">
|
||||
<!-- Icon selection based on API name or service type -->
|
||||
<i th:if="${api.apiName != null}"
|
||||
th:class="${#strings.containsIgnoreCase(api.apiName, '계좌') or #strings.containsIgnoreCase(api.apiName, '조회')} ? 'fas fa-wallet' :
|
||||
(${#strings.containsIgnoreCase(api.apiName, '송금') or #strings.containsIgnoreCase(api.apiName, '이체')} ? 'fas fa-exchange-alt' :
|
||||
(${#strings.containsIgnoreCase(api.apiName, '카드') or #strings.containsIgnoreCase(api.apiName, '결제')} ? 'fas fa-credit-card' :
|
||||
(${#strings.containsIgnoreCase(api.apiName, 'AI') or #strings.containsIgnoreCase(api.apiName, '분석')} ? 'fas fa-chart-line' :
|
||||
(${iterStat.index == 0} ? 'fas fa-wallet' :
|
||||
(${iterStat.index == 1} ? 'fas fa-exchange-alt' :
|
||||
(${iterStat.index == 2} ? 'fas fa-credit-card' : 'fas fa-chart-line'))))))"></i>
|
||||
</div>
|
||||
|
||||
<!-- API Name -->
|
||||
<h3 class="api-name" th:text="${api.apiName}">API 이름</h3>
|
||||
|
||||
<!-- API Description -->
|
||||
<p class="api-description" th:text="${api.apiSimpleDescription != null ? api.apiSimpleDescription : api.description}">API 설명</p>
|
||||
|
||||
<!-- API Features/Method Info -->
|
||||
<div class="api-features">
|
||||
<span class="feature-tag" th:if="${api.apiMethod != null}" th:text="${api.apiMethod}">GET</span>
|
||||
<span class="feature-tag" th:if="${api.service != null}" th:text="${api.service}">서비스</span>
|
||||
</div>
|
||||
|
||||
<!-- Test Link -->
|
||||
<a th:href="@{/apis/detail(id=${api.apiId})}" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Fallback: Show static cards if no APIs from controller -->
|
||||
<div th:if="${apis == null or #lists.isEmpty(apis)}" class="api-card api-card-popular">
|
||||
<div class="api-badge">인기</div>
|
||||
<div class="api-icon">
|
||||
<i class="fas fa-wallet"></i>
|
||||
</div>
|
||||
<h3 class="api-name">계좌 조회 API</h3>
|
||||
<p class="api-description">실시간 잔액 조회와 거래내역을 안전하게 확인</p>
|
||||
<div class="api-features">
|
||||
<span class="feature-tag">실시간</span>
|
||||
<span class="feature-tag">OAuth 2.0</span>
|
||||
</div>
|
||||
<a href="#" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div th:if="${apis == null or #lists.isEmpty(apis)}" class="api-card">
|
||||
<div class="api-icon">
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
</div>
|
||||
<h3 class="api-name">송금 API</h3>
|
||||
<p class="api-description">간편하고 안전한 송금 서비스 구현</p>
|
||||
<div class="api-features">
|
||||
<span class="feature-tag">간편송금</span>
|
||||
<span class="feature-tag">실명확인</span>
|
||||
</div>
|
||||
<a href="#" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div th:if="${apis == null or #lists.isEmpty(apis)}" class="api-card">
|
||||
<div class="api-icon">
|
||||
<i class="fas fa-credit-card"></i>
|
||||
</div>
|
||||
<h3 class="api-name">카드 결제 API</h3>
|
||||
<p class="api-description">온/오프라인 결제 솔루션 통합</p>
|
||||
<div class="api-features">
|
||||
<span class="feature-tag">PG연동</span>
|
||||
<span class="feature-tag">간편결제</span>
|
||||
</div>
|
||||
<a href="#" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div th:if="${apis == null or #lists.isEmpty(apis)}" class="api-card api-card-new">
|
||||
<div class="api-badge new">NEW</div>
|
||||
<div class="api-icon">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
<h3 class="api-name">AI 신용평가 API</h3>
|
||||
<p class="api-description">머신러닝 기반 실시간 신용 분석</p>
|
||||
<div class="api-features">
|
||||
<span class="feature-tag">AI/ML</span>
|
||||
<span class="feature-tag">실시간</span>
|
||||
</div>
|
||||
<a href="#" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 정보 섹션 -->
|
||||
<section class="info-section">
|
||||
<div class="container">
|
||||
<div class="info-wrapper">
|
||||
<div class="image-container">
|
||||
<div class="image-placeholder">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
<span>금융 혁신</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main_con">
|
||||
<p class="con_tit m-only"><img th:src="@{/img/img_con_tit.png}" alt="Kbank API PORTAL"></p>
|
||||
<p class="con_tit pc-only"><img th:src="@{/img/img_con_tit2.png}" alt="Kbank API PORTAL"></p>
|
||||
<p class="con_img m-only"><img th:src="@{/img/img_mask_group.png}" alt="Kbank API PORTAL"></p>
|
||||
<p class="con_img pc-only"><img th:src="@{/img/img_mask_group2.png}" alt="Kbank API PORTAL"></p>
|
||||
<p class="con_txt m-only">
|
||||
<span>Kbank API Portal</span>은 기업이 금융서비스를<br>
|
||||
편리하게 개발할 수 있도록 차별화된<br>
|
||||
API와 테스트환경을 제공합니다.<br>
|
||||
</p>
|
||||
<p class="con_txt pc-only">
|
||||
<span>Kbank API Portal</span>은 기업이 금융서비스를 편리하게 개발할 수 있도록 차별화된 API와 테스트환경을 제공합니다.
|
||||
</p>
|
||||
<div class="main_btn">
|
||||
<a th:href="@{/intro}" class="common_btn_type_5"><span>처음 만나는 케이뱅크 API</span></a>
|
||||
<a th:href="@{/partnership}" class="common_btn_type_5 m_btn"><span>사업 제휴 문의</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tiding_box">
|
||||
<div class="api_type">
|
||||
<div class="api_tiding">
|
||||
<div class="api_tit">
|
||||
<h2>새로운 케이뱅크 API 소식을<br> 만나보세요</h2>
|
||||
<p class="total_view">
|
||||
<a href="/portalnotice">전체보기</a>
|
||||
<div class="info-content">
|
||||
<p class="highlight-text">
|
||||
광주은행 <span class="text-accent">API Portal</span>은 기업이 금융서비스를 편리하게 개발할 수 있도록<br>
|
||||
차별화된 API와 테스트환경을 제공합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="api_tiding_list">
|
||||
<ul>
|
||||
<li th:each="notice : ${portalNotices}">
|
||||
<a th:href="@{/portalnotice/detail(id=${notice.id})}">
|
||||
<p th:text="${notice.noticeSubject}"></p>
|
||||
</a>
|
||||
<span th:text="${#temporals.format(notice.createdDate, 'yyyy.MM.dd')}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="action-buttons">
|
||||
<a href="#" class="action-btn">
|
||||
<span class="btn-number">1</span>
|
||||
<span class="btn-text">처음 만나는 광주뱅크 API</span>
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
<a href="#" class="action-btn">
|
||||
<span class="btn-number">2</span>
|
||||
<span class="btn-text">사업 제휴 문의</span>
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api_info">
|
||||
<ul>
|
||||
<li class="img1">
|
||||
<a th:href="@{/faq_list}">
|
||||
<p>FAQ</p>
|
||||
<span>자주 묻는 질문과 답변을<br> 확인해보세요</span>
|
||||
<img th:src="@{/img/icon/icon_petals01.png}" alt="faq 이미지">
|
||||
</a>
|
||||
</li>
|
||||
<li class="img2">
|
||||
<a th:href="@{/inquiry}">
|
||||
<p>Q&A</p>
|
||||
<span>문의사항에<br> 친절하게 답변 드리겠습니다</span>
|
||||
<img th:src="@{/img/icon/icon_petals02.png}" alt="Q&A 이미지">
|
||||
</a>
|
||||
</li>
|
||||
<li class="img3">
|
||||
<a th:href="@{/partnership}">
|
||||
<p>사업제휴</p>
|
||||
<span>온라인 비즈니스 혁신을 위한<br> 사업 제안을 환영합니다</span>
|
||||
<img th:src="@{/img/icon/icon_petals03.png}" alt="사업제휴 이미지">
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 지원 센터 섹션 -->
|
||||
<section class="support-center">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
도움이 <span class="title-highlight">필요하신가요?</span>
|
||||
</h2>
|
||||
<p class="section-subtitle">다양한 방법으로 필요한 정보를 찾아보세요</p>
|
||||
</div>
|
||||
<div class="support-grid">
|
||||
<a th:href="@{/portalnotice}" class="support-card">
|
||||
<div class="card-icon">
|
||||
<i class="fas fa-bullhorn"></i>
|
||||
</div>
|
||||
<h3>공지사항</h3>
|
||||
<p>최신 업데이트와 중요한 소식을 확인하세요</p>
|
||||
<div class="card-arrow">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</div>
|
||||
</a>
|
||||
<a th:href="@{/faq_list}" class="support-card">
|
||||
<div class="card-icon">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
</div>
|
||||
<h3>자주 묻는 질문</h3>
|
||||
<p>자주 묻는 질문과 답변을 빠르게 찾아보세요</p>
|
||||
<div class="card-arrow">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</div>
|
||||
</a>
|
||||
<a th:href="@{/inquiry}" class="support-card">
|
||||
<div class="card-icon">
|
||||
<i class="fas fa-comments"></i>
|
||||
</div>
|
||||
<h3>문의하기</h3>
|
||||
<p>추가 도움이 필요하시면 직접 문의해주세요</p>
|
||||
<div class="card-arrow">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="api_count">
|
||||
<div class="api_total_box">
|
||||
<p class="txt m-only">우리곁의 수많은 서비스들이<br> <em>케이뱅크API</em>와<br> 함께하고 있습니다</p>
|
||||
<p class="txt pc-only">우리곁의 수많은 서비스들이<br> <em>케이뱅크API와 함께</em>하고 있습니다</p>
|
||||
<div class="api_total">
|
||||
<ul>
|
||||
<li>
|
||||
<img th:src="@{/img/icon/icon_api01.png}" alt="서비스 이용 수">
|
||||
<div class="api_num">
|
||||
<p th:text="${serviceCount}"></p>
|
||||
<span>서비스 이용</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<img th:src="@{/img/icon/icon_api02.png}" alt="API 이용건수">
|
||||
<div class="api_num">
|
||||
<p th:text="${selectedApiCount}"></p>
|
||||
<span>API 이용</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<img th:src="@{/img/icon/icon_api03.png}" alt="API 활용기업">
|
||||
<div class="api_num">
|
||||
<p th:text="${approvedOrgCount}"></p>
|
||||
<span>API 활용 기업</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- API 활용 현황 섹션 -->
|
||||
<section class="api-stats-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
우리곁의 수많은 서비스들이<br>
|
||||
<span class="title-highlight">광주은행 API</span>와 함께하고 있습니다
|
||||
</h2>
|
||||
</div>
|
||||
<div class="stats-cards">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-store"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" th:attr="data-target=${serviceCount != null ? serviceCount : 0}" th:text="${serviceCount != null ? serviceCount : 0}">0</div>
|
||||
<div class="stat-label">서비스 이용 수</div>
|
||||
<div class="stat-description">다양한 분야의 서비스가 활용중</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" th:attr="data-target=${totalApiCount != null ? totalApiCount : 0}" th:text="${totalApiCount != null ? totalApiCount : 0}">0</div>
|
||||
<div class="stat-label">API 이용 건수</div>
|
||||
<div class="stat-description">일일 평균 API 호출 수</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-building"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" th:attr="data-target=${approvedOrgCount != null ? approvedOrgCount : 0}" th:text="${approvedOrgCount != null ? approvedOrgCount : 0}">0</div>
|
||||
<div class="stat-label">API 활용 기업</div>
|
||||
<div class="stat-description">혁신적인 파트너사와 함께</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main_bg"></div>
|
||||
</section>
|
||||
</section>
|
||||
</th:block>
|
||||
</body>
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script th:inline="javascript">
|
||||
$(document).ready(function() {
|
||||
// 기존 success 메시지 처리
|
||||
var successMsg = [[${success}]];
|
||||
console.log("Success message:", successMsg);
|
||||
if (successMsg) {
|
||||
console.log("Showing alert");
|
||||
customPopups.showAlert(successMsg);
|
||||
}
|
||||
|
||||
// 장기미사용 계정 체크
|
||||
const dormantAccount = [[${session.dormantAccount}]];
|
||||
if (dormantAccount) {
|
||||
const dormantMsg = /*[[${session.success}]]*/ '';
|
||||
const redirectUrl = /*[[${session.redirectUrl}]]*/ '';
|
||||
|
||||
console.log('dormantMsg:', dormantMsg);
|
||||
console.log('redirectUrl:', redirectUrl);
|
||||
|
||||
customPopups.showAlert(dormantMsg);
|
||||
$('#customAlertOkButton').off('click.custom').on('click.custom', function () {
|
||||
customPopups.hideAlert('#customAlert');
|
||||
window.location.href = redirectUrl;
|
||||
});
|
||||
return; // 다른 체크 중단
|
||||
}
|
||||
|
||||
// 비밀번호 만료 체크 추가
|
||||
const passwordExpired = [[${session.passwordExpired}]];
|
||||
if (passwordExpired) {
|
||||
const pwdExpiredMsg = /*[[${session.success}]]*/ '';
|
||||
const formattedMsg = pwdExpiredMsg.replace(/\n/g, '<br>');
|
||||
const redirectUrl = /*[[${session.redirectUrl}]]*/ '';
|
||||
|
||||
console.log('pwdExpiredMsg:', pwdExpiredMsg);
|
||||
console.log('redirectUrl:', redirectUrl);
|
||||
|
||||
$('#customAlertMessage').html(formattedMsg);
|
||||
$('#customAlert').css('display','flex');
|
||||
|
||||
$('#customAlertOkButton').off('click.custom').on('click.custom', function () {
|
||||
customPopups.hideAlert('#customAlert');
|
||||
window.location.href = redirectUrl;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
function setActive(element) {
|
||||
// Remove 'active' class from all links
|
||||
document.querySelectorAll('.keyword_list a').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
// Add 'active' class to clicked link
|
||||
element.classList.add('active');
|
||||
}
|
||||
// 숫자 카운트업 애니메이션
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const observerOptions = {
|
||||
threshold: 0.5,
|
||||
rootMargin: '0px 0px -100px 0px'
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const ITEMS_PER_PAGE = 4;
|
||||
let isExpanded = false;
|
||||
const animateNumbers = (entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const numbers = entry.target.querySelectorAll('.stat-number');
|
||||
|
||||
// 더보기 버튼 초기화
|
||||
function initMoreButton() {
|
||||
const mobileMoreBtn = document.querySelector('.more_type.m-only .showmore');
|
||||
const pcMoreBtn = document.querySelector('.more_type.pc-only .showmore');
|
||||
const apiInfoBoxes = document.querySelectorAll('.api_info_box');
|
||||
numbers.forEach(number => {
|
||||
const target = parseInt(number.getAttribute('data-target'));
|
||||
const duration = 2000; // 2초
|
||||
const increment = target / (duration / 16);
|
||||
let current = 0;
|
||||
|
||||
// 5개 미만이면 더보기 버튼 숨기기
|
||||
const mobileMoreContainer = document.querySelector('.more_type.m-only');
|
||||
const pcMoreContainer = document.querySelector('.more_type.pc-only');
|
||||
const updateNumber = () => {
|
||||
current += increment;
|
||||
if (current < target) {
|
||||
number.textContent = Math.floor(current).toLocaleString('ko-KR');
|
||||
requestAnimationFrame(updateNumber);
|
||||
} else {
|
||||
number.textContent = target.toLocaleString('ko-KR');
|
||||
}
|
||||
};
|
||||
|
||||
console.log('더보기 버튼 초기화:', {
|
||||
항목개수: apiInfoBoxes.length,
|
||||
보여질상태: apiInfoBoxes.length >= 5 ? 'block' : 'none'
|
||||
});
|
||||
updateNumber();
|
||||
});
|
||||
|
||||
// 더보기 버튼 표시 여부 설정
|
||||
if (apiInfoBoxes.length < 5) {
|
||||
mobileMoreContainer.style.setProperty('display', 'none', 'important');
|
||||
pcMoreContainer.style.setProperty('display', 'none', 'important');
|
||||
} else {
|
||||
mobileMoreContainer.style.setProperty('display', 'block', 'important');
|
||||
pcMoreContainer.style.setProperty('display', 'block', 'important');
|
||||
}
|
||||
|
||||
// 초기 상태 설정
|
||||
updateApiListVisibility();
|
||||
|
||||
// 더보기 버튼 이벤트 리스너 설정
|
||||
[mobileMoreBtn, pcMoreBtn].forEach(btn => {
|
||||
if (btn) {
|
||||
btn.removeEventListener('click', handleMoreClick);
|
||||
btn.addEventListener('click', handleMoreClick);
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(animateNumbers, observerOptions);
|
||||
const statsSection = document.querySelector('.api-stats-section');
|
||||
|
||||
if (statsSection) {
|
||||
observer.observe(statsSection);
|
||||
}
|
||||
|
||||
function handleMoreClick(e) {
|
||||
e.preventDefault();
|
||||
console.log('더보기 버튼 클릭');
|
||||
isExpanded = !isExpanded; // 상태 토글
|
||||
// API 검색 기능
|
||||
const searchInput = document.getElementById('apiSearchInput');
|
||||
const searchForm = document.querySelector('.search-form');
|
||||
const hashtags = document.querySelectorAll('.hashtag');
|
||||
|
||||
// 버튼 이미지/텍스트 변경
|
||||
const mobileMoreBtn = document.querySelector('.more_type.m-only .showmore img');
|
||||
const pcMoreBtn = document.querySelector('.more_type.pc-only .showmore img');
|
||||
if (mobileMoreBtn) {
|
||||
mobileMoreBtn.alt = isExpanded ? "접기 버튼" : "더보기 버튼";
|
||||
}
|
||||
if (pcMoreBtn) {
|
||||
pcMoreBtn.alt = isExpanded ? "접기 버튼" : "더보기 버튼";
|
||||
}
|
||||
|
||||
updateApiListVisibility();
|
||||
}
|
||||
|
||||
// API 리스트 표시 상태 업데이트
|
||||
function updateApiListVisibility() {
|
||||
const apiInfoBoxes = document.querySelectorAll('.api_info_box');
|
||||
const endIndex = isExpanded ? apiInfoBoxes.length : ITEMS_PER_PAGE;
|
||||
|
||||
console.log('업데이트 - API 목록:', {
|
||||
총개수: apiInfoBoxes.length,
|
||||
표시상태: isExpanded ? '전체표시' : '일부표시',
|
||||
표시할개수: endIndex
|
||||
});
|
||||
|
||||
apiInfoBoxes.forEach((box, index) => {
|
||||
box.style.display = index < endIndex ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// HTMX 로드 완료 이벤트 리스너
|
||||
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target.id === 'api_spec_list') {
|
||||
console.log('HTMX 컨텐츠 로드됨 - 페이지 초기화');
|
||||
isExpanded = false;
|
||||
initMoreButton();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:afterSettle', function(evt) {
|
||||
const apiListItems = document.querySelectorAll('.api_list');
|
||||
apiListItems.forEach(function(item) {
|
||||
item.removeEventListener('click',null);
|
||||
item.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
const apiItems = document.querySelectorAll('.api_info_box');
|
||||
apiItems.forEach(function(item) {
|
||||
item.removeEventListener('click',null);
|
||||
item.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 초기 실행
|
||||
console.log('초기 실행');
|
||||
initMoreButton();
|
||||
|
||||
// All 카테고리를 기본으로 active 설정
|
||||
document.querySelector('.keyword_list li:first-child a').classList.add('active');
|
||||
|
||||
const apiListItems = document.querySelectorAll('.api_list');
|
||||
apiListItems.forEach(function(item) {
|
||||
item.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const apiItems = document.querySelectorAll('.api_info_box');
|
||||
apiItems.forEach(function(item) {
|
||||
item.removeEventListener('click',null);
|
||||
item.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const hashtagItems = document.querySelectorAll('.hashtag ul li a');
|
||||
const searchBox = document.getElementById('search-box');
|
||||
|
||||
hashtagItems.forEach(function(item) {
|
||||
item.addEventListener('click', function(e) {
|
||||
// 해시태그 클릭 이벤트
|
||||
hashtags.forEach(tag => {
|
||||
tag.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
// Remove the # symbol if present and set the text in search box
|
||||
const searchText = this.textContent.replace('#', '').trim();
|
||||
searchBox.value = searchText;
|
||||
|
||||
document.getElementById("searchForm").submit();
|
||||
});
|
||||
});
|
||||
|
||||
const infoItems = document.querySelectorAll('.api_info ul li');
|
||||
infoItems.forEach(function (item) {
|
||||
item.style.cursor = 'pointer';
|
||||
item.addEventListener('click', function() {
|
||||
const link = this.querySelector('a');
|
||||
if (link) {
|
||||
location.href = link.getAttribute('href');
|
||||
const searchTerm = this.getAttribute('data-search');
|
||||
searchInput.value = searchTerm;
|
||||
// 폼 제출
|
||||
if (searchForm) {
|
||||
searchForm.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</th:block>
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||
<meta content="index, follow" name="robots"/>
|
||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap" class="wrap_top">
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||
<meta content="index, follow" name="robots"/>
|
||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap" class="wrap_top">
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||
<meta content="index, follow" name="robots"/>
|
||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon_16px.png}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap" class="wrap_top">
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
<aside layout:fragment="apiAside" class="lnb">
|
||||
<nav th:with="services=${@apiServiceService.searchApiGroupsForLnb()}">
|
||||
<ul class="lnb_list_type">
|
||||
<li>
|
||||
<a href="#none">공통안내</a>
|
||||
<ul class="lnb_list_nav">
|
||||
<li> <a th:href="@{/apis/common}">API 개발 공통</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U3}">가상계좌 응답 코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U4}">가상계좌 배치 설계</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U5}">가상계좌 VAN사 코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U6}">펌뱅킹 응답코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U7}">펌뱅킹 배치 설계</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U8}">대출금리 응답코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U9}">케이뱅크 페이 응답코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U10}">케이뱅크 페이 복합과세 예제</a></li>
|
||||
<li> <a th:href="@{/apis/token-spec}">케이뱅크 OAuth 2.0 토큰 발급</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li th:each="apiService : ${services}">
|
||||
<a href="#none" th:text="${apiService.groupName}">API Group Name</a>
|
||||
<ul class="lnb_list_nav">
|
||||
<li th:each="api : ${apiService.apiGroupApiList}">
|
||||
<a th:href="@{/apis/detail(id=${api.apiId})}" th:text="${api.apiDesc}">API Description</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
@@ -0,0 +1,69 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
>
|
||||
|
||||
<body>
|
||||
<footer th:fragment="footerFragment" class="global-footer">
|
||||
<div class="container">
|
||||
<div class="footer-top">
|
||||
<div class="footer-brand">
|
||||
<img src="/img/logo/logo.png" alt="광주은행" class="footer-logo">
|
||||
<p class="footer-desc">
|
||||
Wa bank와 함께 만드는 새로운 금융의 미래<br>
|
||||
개발자 여러분과 함께 성장합니다.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<div class="footer-column">
|
||||
<h4>제품</h4>
|
||||
<ul>
|
||||
<li><a href="#">API 카탈로그</a></li>
|
||||
<li><a href="#">SDK 다운로드</a></li>
|
||||
<li><a href="#">Playground</a></li>
|
||||
<li><a href="#">요금제</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-column">
|
||||
<h4>개발자</h4>
|
||||
<ul>
|
||||
<li><a href="#">시작 가이드</a></li>
|
||||
<li><a href="#">API 문서</a></li>
|
||||
<li><a href="#">튜토리얼</a></li>
|
||||
<li><a href="#">샘플 코드</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-column">
|
||||
<h4>지원</h4>
|
||||
<ul>
|
||||
<li><a href="#">도움말 센터</a></li>
|
||||
<li><a href="#">커뮤니티</a></li>
|
||||
<li><a href="#">기술 지원</a></li>
|
||||
<li><a href="#">시스템 상태</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-column">
|
||||
<h4>회사</h4>
|
||||
<ul>
|
||||
<li><a href="#">광주은행 소개</a></li>
|
||||
<li><a href="#">뉴스룸</a></li>
|
||||
<li><a href="#">채용</a></li>
|
||||
<li><a href="#">제휴 문의</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<div class="footer-legal">
|
||||
<p>© 2025 광주은행. All rights reserved.</p>
|
||||
<div class="legal-links">
|
||||
<a href="#">이용약관</a>
|
||||
<a href="#">개인정보처리방침</a>
|
||||
<a href="#">쿠키 정책</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,52 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
<head th:fragment="headFragment">
|
||||
<title th:text="#{title.html}">개발자 포털</title>
|
||||
|
||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||
<meta content="index, follow" name="robots"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/all.min.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/slick.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/daterangepicker.css}">
|
||||
|
||||
<link rel="stylesheet" th:href="@{/plugins/codemirror/codemirror.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/jquery-ui/jquery-ui.min.css}" />
|
||||
<link rel="icon" type="image/ico" th:href="@{/favicon.ico}">
|
||||
|
||||
<!-- 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:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<script th:src="@{/plugins/jquery/jquery-3.7.1.min.js}"></script>
|
||||
<script th:src="@{/js/lodash.js}"></script>
|
||||
<script th:src="@{/js/common.js}"></script>
|
||||
<script th:src="@{/js/moment.min.js}"></script>
|
||||
<script th:src="@{/js/daterangepicker.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/codemirror.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/mode/clike.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/mode/javascript.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/addon/display/fullscreen.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/addon/display/placeholder.js}"></script>
|
||||
<script th:src="@{/plugins/summernote/summernote-lite.min.js}"></script>
|
||||
<script th:src="@{/plugins/summernote/summernote-cleaner.js}"></script>
|
||||
<script th:src="@{/plugins/jquery-ui/jquery-ui.min.js}"></script>
|
||||
<script th:src="@{/js/htmx.min.js}"></script>
|
||||
</head>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,135 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
|
||||
<body>
|
||||
<th:block th:fragment="headerFragment">
|
||||
<!-- Global Header Container -->
|
||||
<header class="global-header">
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
|
||||
<!-- Desktop Header Layout -->
|
||||
<div class="desktop-header">
|
||||
<div class="header-left">
|
||||
<div class="logo-wrapper">
|
||||
<a th:href="@{/}" class="logo-link">
|
||||
<img src="/img/logo/logo.png" alt="광주은행" class="logo">
|
||||
</a>
|
||||
<a th:href="@{/}" class="logo-text">개발자 포털</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="header-center">
|
||||
<ul class="nav-menu">
|
||||
<li><a href="#docs" class="nav-link">서비스 소개</a></li>
|
||||
<li><a href="/apis" class="nav-link">오픈 API</a></li>
|
||||
<li><a href="#playground" class="nav-link">고객지원</a></li>
|
||||
<li><a href="#community" class="nav-link">MY</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="header-right">
|
||||
<button class="search-btn"><i class="fas fa-search"></i></button>
|
||||
<a th:href="@{/login}" class="login-btn">로그인</a>
|
||||
<a href="#signup" class="signup-btn">
|
||||
<span>회원 가입</span>
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Header Layout -->
|
||||
<div class="mobile-header">
|
||||
<div class="mobile-left">
|
||||
<a th:href="@{/}" class="mobile-logo-link">
|
||||
<img src="/img/logo/logo.png" alt="광주은행" class="mobile-logo">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mobile-center">
|
||||
<h1 class="mobile-title">개발자 포털</h1>
|
||||
</div>
|
||||
|
||||
<div class="mobile-right">
|
||||
<button class="mobile-menu-btn" id="mobileMenuToggle">
|
||||
<span class="hamburger-line"></span>
|
||||
<span class="hamburger-line"></span>
|
||||
<span class="hamburger-line"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Mobile Drawer/Modal -->
|
||||
<div class="mobile-drawer" id="mobileDrawer">
|
||||
<div class="drawer-overlay" id="drawerOverlay"></div>
|
||||
<div class="drawer-content">
|
||||
<div class="drawer-header">
|
||||
<div class="drawer-logo">
|
||||
<a th:href="@{/}" class="drawer-logo-link">
|
||||
<img src="/img/logo/logo.png" alt="광주은행" class="logo">
|
||||
</a>
|
||||
<a th:href="@{/}" class="logo-text">개발자 포털</a>
|
||||
</div>
|
||||
<button class="drawer-close" id="drawerClose">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="drawer-nav">
|
||||
<ul class="drawer-menu">
|
||||
<li><a href="#docs" class="drawer-link">서비스 소개</a></li>
|
||||
<li><a href="/apis" class="drawer-link">오픈 API</a></li>
|
||||
<li><a href="#playground" class="drawer-link">고객지원</a></li>
|
||||
<li><a href="#community" class="drawer-link">MY</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="drawer-actions">
|
||||
<a th:href="@{/login}" class="drawer-login-btn">로그인</a>
|
||||
<a href="#signup" class="drawer-signup-btn">
|
||||
<span>회원 가입</span>
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<!-- Mobile Menu JavaScript -->
|
||||
<th:block th:fragment="headerScript">
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const menuToggle = document.getElementById('mobileMenuToggle');
|
||||
const drawer = document.getElementById('mobileDrawer');
|
||||
const drawerClose = document.getElementById('drawerClose');
|
||||
const drawerOverlay = document.getElementById('drawerOverlay');
|
||||
|
||||
function openDrawer() {
|
||||
drawer.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
drawer.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
menuToggle?.addEventListener('click', openDrawer);
|
||||
drawerClose?.addEventListener('click', closeDrawer);
|
||||
drawerOverlay?.addEventListener('click', closeDrawer);
|
||||
|
||||
// Close drawer when clicking on nav links
|
||||
document.querySelectorAll('.drawer-link').forEach(link => {
|
||||
link.addEventListener('click', closeDrawer);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</th:block>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div th:fragment="headerFragment" class="navigation" id="navigation">
|
||||
<ul class="navigation-area">
|
||||
<li th:each="crumb : ${breadcrumb}">
|
||||
<a th:href="${crumb.path}" class="navigation-button" th:text="${crumb.name}">Unknown Page</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<aside class="sidebar" th:fragment="menuFragment">
|
||||
|
||||
</aside>
|
||||
|
||||
<!-- menu script -->
|
||||
<th:block th:fragment="menuScript">
|
||||
<script th:inline="javascript">
|
||||
|
||||
$(function () {
|
||||
});
|
||||
</script>
|
||||
</th:block>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/kjbank_base_layout}">
|
||||
<body>
|
||||
<section layout:fragment="contentFragment" class="content">
|
||||
<div class="content_wrap">
|
||||
<div class="sub_title2">
|
||||
<h2 class="title" th:classappend="${isTermsOfUse ? 'add3' : 'add4'}"
|
||||
th:text="${agreementTitle}">이용약관</h2>
|
||||
</div>
|
||||
<div class="inner cs2">
|
||||
<div class="tab-wrap">
|
||||
<div class="tabs">
|
||||
<ul class="tab_nav tab_bt">
|
||||
<li th:class="${isTermsOfUse ? 'active' : ''}">
|
||||
<a th:href="@{/agreements/terms(tab='terms')}">이용약관</a>
|
||||
</li>
|
||||
<li th:class="${isPrivacyPolicy ? 'active' : ''}">
|
||||
<a th:href="@{/agreements/terms(tab='privacy')}">개인정보처리방침</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab active" th:id="${isTermsOfUse ? 'tab1' : 'tab2'}">
|
||||
<div class="form_type">
|
||||
<div class="info1 ts_top">
|
||||
<div class="info_line ts_left">
|
||||
<div class="info_box1 info_add3 info_top">
|
||||
<span class="input">
|
||||
<form id="agreementForm" th:action="@{/agreements/terms}" method="get">
|
||||
<input type="hidden" name="tab"
|
||||
th:value="${currentTab}">
|
||||
<div class="custom_select" id="agreement_select">
|
||||
<div class="select_selected">
|
||||
시행일자 : <span th:text="${#temporals.format(selectedAgreement.publishedOn, 'yyyy.MM.dd')}"></span>
|
||||
</div>
|
||||
<ul class="common_selecttype select_items select_hide">
|
||||
<li th:each="agreement : ${agreementsList}"
|
||||
th:data-id="${#temporals.format(agreement.publishedOn, 'yyyy-MM-dd')}"
|
||||
th:text="'시행일자 : ' + ${#temporals.format(agreement.publishedOn, 'yyyy.MM.dd')}"
|
||||
th:class="${#temporals.format(agreement.publishedOn, 'yyyy-MM-dd') == selectedDate ? 'selected' : ''}">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input type="hidden" name="publishedOn" id="publishedOnInput">
|
||||
</form>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="terms_contents m_termst" th:utext="${selectedAgreement.contents}"
|
||||
style="white-space: pre-wrap; word-wrap: break-word;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script layout:fragment="contentScript">
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const selects = document.getElementById('agreement_select');
|
||||
const form = document.getElementById('agreementForm');
|
||||
const publishedOnInput = document.getElementById('publishedOnInput');
|
||||
|
||||
createCustomSelect(selects, {
|
||||
onSelect: (item) => {
|
||||
publishedOnInput.value = item;
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
<head th:replace="fragment/kjbank/head :: headFragment">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<th:block th:replace="fragment/kjbank/header_container :: headerFragment"></th:block>
|
||||
<th:block th:replace="fragment/kjbank/header_container :: headerScript"></th:block>
|
||||
<div class="loading-overlay" style="display: none;">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
<th:block layout:fragment="contentFragment">
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="contentScript">
|
||||
</th:block>
|
||||
|
||||
<footer th:replace="fragment/kjbank/footer :: footerFragment"></footer>
|
||||
<section th:replace="fragment/popup/customPopups :: customPopups"></section>
|
||||
<section layout:fragment="pagePopups"></section>
|
||||
<script th:src="@{/js/popup/custom-popups.js}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
|
||||
<head th:replace="fragment/kjbank/head :: headFragment">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- header -->
|
||||
<th:block th:replace="fragment/kjbank/header_container :: headerFragment"></th:block>
|
||||
<th:block th:replace="fragment/kjbank/header_container :: headerScript"></th:block>
|
||||
|
||||
<th:block layout:fragment="contentFragment">
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="contentScript">
|
||||
</th:block>
|
||||
|
||||
<footer th:replace="fragment/kjbank/footer :: footerFragment"></footer>
|
||||
|
||||
<section th:replace="fragment/popup/customPopups :: customPopups"></section>
|
||||
<script th:src="@{/js/popup/custom-popups.js}"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user