메인 페이지 화면설계서 적용

This commit is contained in:
현성필
2025-09-24 17:34:51 +09:00
parent 8d07897e42
commit 91eec29e98
41 changed files with 11966 additions and 505 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
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: 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
}
}
}
+46
View File
@@ -0,0 +1,46 @@
// -----------------------------------------------------------------------------
// 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/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,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,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&amp;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>
@@ -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_16px.png}">
<!-- 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="#api-market" 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="#api-market" 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/kbank/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>