diff --git a/assets/js/scroll-reveal.js b/assets/js/scroll-reveal.js new file mode 100644 index 000000000..5f7ba4ad2 --- /dev/null +++ b/assets/js/scroll-reveal.js @@ -0,0 +1,35 @@ +/* + * 스크롤 리빌 — .skt-reveal 요소가 뷰포트에 들어오면 .is-visible 을 부여해 + * 부드럽게 나타나게 한다. 의존성 없는 순수 JS(IntersectionObserver). + * + * 초기 숨김(opacity:0)은 CSS에서 html.js-reveal 일 때만 적용되며, 이 클래스는 + * 동작 줄이기(prefers-reduced-motion)가 아닐 때만 head 인라인 스크립트가 부여한다. + * 따라서 JS 비활성/실패 또는 동작 줄이기 환경에서는 콘텐츠가 항상 보인다. + */ +(function () { + "use strict"; + + var root = document.documentElement; + + // js-reveal 게이트가 없으면(동작 줄이기 등) 아무것도 숨기지 않은 상태이므로 종료 + if (!root.classList.contains("js-reveal")) return; + + var els = document.querySelectorAll(".skt-reveal"); + + // 관찰 대상이 없거나 IO 미지원 → 게이트를 풀어 전부 보이게 하고 종료 + if (!els.length || !("IntersectionObserver" in window)) { + root.classList.remove("js-reveal"); + return; + } + + var io = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (entry.isIntersecting) { + entry.target.classList.add("is-visible"); + io.unobserve(entry.target); + } + }); + }, { rootMargin: "0px 0px -8% 0px", threshold: 0.08 }); + + els.forEach(function (el) { io.observe(el); }); +})(); diff --git a/assets/js/ui-enhancements.js b/assets/js/ui-enhancements.js new file mode 100644 index 000000000..2757887ae --- /dev/null +++ b/assets/js/ui-enhancements.js @@ -0,0 +1,55 @@ +/* + * UI 보조 요소 — 스크롤 진행 바 + 맨 위로 버튼. 의존성 없는 순수 JS. + * 동작 줄이기(prefers-reduced-motion)일 때 부드러운 스크롤 대신 즉시 이동한다. + */ +(function () { + "use strict"; + + var reduce = window.matchMedia && + window.matchMedia("(prefers-reduced-motion: reduce)").matches; + var isEn = (document.documentElement.lang || "").slice(0, 2) === "en"; + + // 0) 스킵 링크 — 키보드 사용자가 반복되는 내비를 건너뛰고 본문으로 (접근성) + var main = document.querySelector("main"); + if (main) { + if (!main.id) main.id = "main-content"; + var skip = document.createElement("a"); + skip.className = "skt-skip-link"; + skip.href = "#" + main.id; + skip.textContent = isEn ? "Skip to content" : "본문 바로가기"; + document.body.insertBefore(skip, document.body.firstChild); + } + + // 1) 스크롤 진행 바 (상단 고정, 액센트색) + var bar = document.createElement("div"); + bar.className = "skt-progress"; + bar.setAttribute("aria-hidden", "true"); + document.body.appendChild(bar); + + // 2) 맨 위로 버튼 + var btn = document.createElement("button"); + btn.type = "button"; + btn.className = "skt-to-top"; + btn.setAttribute("aria-label", isEn ? "Back to top" : "맨 위로"); + btn.innerHTML = ''; + btn.addEventListener("click", function () { + window.scrollTo({ top: 0, behavior: reduce ? "auto" : "smooth" }); + }); + document.body.appendChild(btn); + + var ticking = false; + function update() { + var doc = document.documentElement; + var st = window.scrollY || doc.scrollTop; + var h = doc.scrollHeight - doc.clientHeight; + bar.style.transform = "scaleX(" + (h > 0 ? st / h : 0) + ")"; + btn.classList.toggle("is-visible", st > 400); + ticking = false; + } + function onScroll() { + if (!ticking) { window.requestAnimationFrame(update); ticking = true; } + } + window.addEventListener("scroll", onScroll, { passive: true }); + window.addEventListener("resize", onScroll, { passive: true }); + update(); +})(); diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 52352fcc6..fca850467 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -44,6 +44,10 @@ https://www.docsy.dev/docs/content/lookandfeel/#project-style-files --skt-gradient: #{$skt-gradient}; --skt-focus: rgba(0, 0, 0, .6); --skt-hero-bg: #ffffff; + /* 액센트(SK Red) — 핵심 지점에만 절제 사용. 히어로 글로우는 저채도 */ + --skt-accent: #{$skt-accent}; + --skt-accent-glow: #{rgba($skt-accent, .14)}; + --skt-hero-glow: var(--skt-accent-glow); } /* 다크 = Vercel 시그니처: 순검정 배경 / 흰 강조 / 불투명 헤어라인 */ @@ -63,6 +67,10 @@ https://www.docsy.dev/docs/content/lookandfeel/#project-style-files --skt-focus: rgba(255, 255, 255, .7); --skt-gradient: linear-gradient(135deg, #1a1a1a 0%, #000 100%); --skt-hero-bg: #000000; + /* 다크: 액센트는 밝게 보정, 히어로 글로우는 Vercel 시그니처 흰색 유지 */ + --skt-accent: #{$skt-accent-dark}; + --skt-accent-glow: #{rgba($skt-accent-dark, .14)}; + --skt-hero-glow: rgba(255, 255, 255, .14); } /* 다크 페이지 배경을 순검정으로 (Docsy 기본 회색 덮기) */ @@ -190,6 +198,16 @@ h1, h2, h3, h4, h5, h6, gap: 1.5rem; } +/* 블로그 목록 — 연도 그룹 헤더 + 카드 그리드(홈과 통일) */ +.td-blog-posts .skt-blog-year { + margin: 2.75rem 0 1.25rem; + font-size: 1.4rem; + font-weight: 700; + letter-spacing: -0.02em; +} +.td-blog-posts .skt-blog-year:first-child { margin-top: 1.5rem; } +.td-blog-posts .skt-grid { margin-bottom: 1rem; } + /* 카드 — 그림자 없이 1px 보더, hover 시 보더 강조(lift 없음) */ .skt-card { display: flex; @@ -199,13 +217,16 @@ h1, h2, h3, h4, h5, h6, border: 1px solid var(--skt-border); border-radius: $border-radius-lg; overflow: hidden; - transition: border-color var(--skt-transition), background var(--skt-transition); + transition: border-color var(--skt-transition), background var(--skt-transition), + transform var(--skt-transition), box-shadow var(--skt-transition); color: inherit; text-decoration: none; } a.skt-card:hover, .skt-card:hover { border-color: var(--skt-border-strong); + transform: translateY(-4px); + box-shadow: var(--skt-shadow-lg); text-decoration: none; } .skt-card__media { @@ -334,13 +355,14 @@ a.skt-card:hover, mask-image: radial-gradient(ellipse 60% 50% at 50% 45%, #000 30%, transparent 75%); opacity: .7; } -[data-bs-theme='dark'] .skt-hero::before { +/* 히어로 상단 글로우 — 라이트는 저채도 SK Red 액센트, 다크는 흰색(토큰) */ +.skt-hero::before { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; - background: radial-gradient(ellipse 50% 40% at 50% 0%, rgba(255, 255, 255, .08), transparent 70%); + background: radial-gradient(ellipse 75% 55% at 50% -8%, var(--skt-hero-glow), transparent 72%); } .skt-hero__inner { position: relative; @@ -367,6 +389,109 @@ a.skt-card:hover, flex-wrap: wrap; } +/* 히어로 진입 애니메이션 — 제목→부제→CTA 순차(최초 1회). + no-preference로 감싸 동작 줄이기 사용자에겐 적용되지 않음 */ +@media (prefers-reduced-motion: no-preference) { + .skt-hero__title { animation: skt-fade-up .7s var(--skt-ease) both; } + .skt-hero__subtitle { animation: skt-fade-up .7s var(--skt-ease) .12s both; } + .skt-hero__actions { animation: skt-fade-up .7s var(--skt-ease) .24s both; } +} +@keyframes skt-fade-up { + from { opacity: 0; transform: translateY(24px); } + to { opacity: 1; transform: none; } +} + +/* CTA 화살표 — hover 시 살짝 전진 */ +.btn .fa-arrow-right { transition: transform var(--skt-transition) var(--skt-ease); } +.btn:hover .fa-arrow-right { transform: translateX(3px); } + +/* 스크롤 리빌 — html.js-reveal 일 때만 초기 숨김 적용(JS 비활성/실패 시 항상 표시). + js-reveal 클래스는 동작 줄이기가 아닐 때만 head 인라인 스크립트가 부여한다. */ +.js-reveal .skt-reveal { + opacity: 0; + transform: translateY(24px); + transition: opacity .65s var(--skt-ease), transform .65s var(--skt-ease); + transition-delay: var(--skt-reveal-delay, 0s); + will-change: opacity, transform; +} +.js-reveal .skt-reveal.is-visible { + opacity: 1; + transform: none; +} +/* 카드 stagger — 3열 기준 행마다 좌→우 미세 지연 */ +.js-reveal .skt-grid > .skt-reveal:nth-child(3n+2), +.js-reveal .row > .skt-reveal:nth-child(3n+2) { --skt-reveal-delay: .06s; } +.js-reveal .skt-grid > .skt-reveal:nth-child(3n), +.js-reveal .row > .skt-reveal:nth-child(3n) { --skt-reveal-delay: .12s; } + +/* 스크롤 진행 바 — 상단 고정(navbar 위), 절제된 액센트 */ +.skt-progress { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 2px; + transform: scaleX(0); + transform-origin: 0 50%; + background: var(--skt-accent); + z-index: 1040; + pointer-events: none; + will-change: transform; +} +@media (prefers-reduced-motion: no-preference) { + .skt-progress { transition: transform .1s linear; } +} + +/* 맨 위로 버튼 — 무채색 원형, 우하단(스크롤 시 노출) */ +.skt-to-top { + position: fixed; + right: 1.25rem; + bottom: 1.25rem; + width: 42px; + height: 42px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: var(--skt-surface); + color: var(--skt-text); + border: 1px solid var(--skt-border); + box-shadow: var(--skt-shadow-lg); + cursor: pointer; + opacity: 0; + visibility: hidden; + transform: translateY(8px); + transition: opacity var(--skt-transition), transform var(--skt-transition), + visibility var(--skt-transition), border-color var(--skt-transition), + background var(--skt-transition); + z-index: 1030; +} +.skt-to-top.is-visible { opacity: 1; visibility: visible; transform: none; } +.skt-to-top:hover { border-color: var(--skt-border-strong); background: var(--skt-surface-subtle); } + +/* 스킵 링크 — 평소 숨김, 키보드 포커스 시 좌상단에 노출(접근성) */ +.skt-skip-link { + position: fixed; + top: 0; + left: 0; + z-index: 1100; + padding: .65rem 1rem; + background: var(--skt-surface); + color: var(--skt-text); + border: 1px solid var(--skt-border); + border-radius: 0 0 $border-radius 0; + font-size: .9rem; + font-weight: 500; + text-decoration: none; + transform: translateY(-150%); + transition: transform var(--skt-transition); +} +.skt-skip-link:focus { + transform: none; + color: var(--skt-text); + text-decoration: none; +} + /* 섹션 공통 */ .skt-section { padding: 5rem 0; @@ -520,7 +645,19 @@ a.skt-card:hover, border-radius: $border-radius-lg; padding: 1rem 1.15rem; } - .highlight { border-radius: $border-radius-lg; } + .highlight { border-radius: $border-radius-lg; position: relative; } + /* 코드 복사 버튼(Docsy click-to-copy) — 무채색 톤 정비, hover 시 또렷 */ + .highlight button.td-click-to-copy { + color: var(--skt-text-muted); + border: 1px solid transparent; + border-radius: $border-radius-sm; + transition: color var(--skt-transition), background var(--skt-transition), border-color var(--skt-transition); + } + .highlight button.td-click-to-copy:hover { + color: var(--skt-text); + background: var(--skt-surface-muted); + border-color: var(--skt-border); + } /* 인용 — 절제(2px 보더 + subtle 배경) */ blockquote { @@ -533,16 +670,33 @@ a.skt-card:hover, } blockquote p:last-child { margin-bottom: 0; } - /* 표 — 헤더 배경 + 행 구분선 */ - table { - th, td { padding: .65rem .85rem; } - thead th { - background: var(--skt-surface-subtle); - border-bottom: 1px solid var(--skt-border-strong); - font-weight: 600; - } - td { border-color: var(--skt-border); } + /* 표 — .skt-table 컨테이너로 카드화(외곽 테두리 + radius + 모바일 가로 스크롤). + render-table 훅이 모든 마크다운 표를 .skt-table 로 감싼다. */ + .skt-table { + margin: 1.5rem 0; + border: 1px solid var(--skt-border); + border-radius: $border-radius-lg; + overflow: auto; /* 모바일 가로 스크롤 + radius 클립 */ + } + .skt-table > table { + margin: 0; + width: 100%; + border-collapse: collapse; } + .skt-table th, + .skt-table td { + padding: .7rem 1rem; + border-bottom: 1px solid var(--skt-border); + } + .skt-table thead th { + background: var(--skt-surface-subtle); + border-bottom: 1px solid var(--skt-border-strong); + font-weight: 600; + white-space: nowrap; + } + .skt-table tbody tr:last-child > * { border-bottom: 0; } + .skt-table tbody tr { transition: background var(--skt-transition); } + .skt-table tbody tr:hover > * { background: var(--skt-surface-subtle); } /* 본문 링크 — 읽는 중 식별 위해 옅은 밑줄 (표 셀 안 링크 포함) */ p a, li a, td a, th a { @@ -573,15 +727,49 @@ a.skt-card:hover, padding-bottom: 4rem; } -/* alert/callout — 전역 무채색 통일 (본문·인트로 공통, 의미색/노랑·초록 제거) */ +/* alert/callout — 좌측 아이콘 callout. 박스·본문은 무채색, 아이콘만 절제된 의미색. + HTML 구조: .alert > .alert-heading(.h4) + p/ul (Docsy alert 숏코드) */ .alert { + display: grid; + grid-template-columns: auto 1fr; + column-gap: .8rem; + align-items: start; + padding: 1.1rem 1.25rem; background: var(--skt-surface-subtle); border: 1px solid var(--skt-border); - border-left: 3px solid var(--skt-border-strong); - border-radius: $border-radius; + border-radius: $border-radius-lg; + color: var(--skt-text); +} +/* 좌측 아이콘 — color별 글리프(Font Awesome 6 Free), 절제된 의미색 */ +.alert::before { + grid-column: 1; + grid-row: 1; + font-family: "Font Awesome 6 Free"; + font-weight: 900; + content: "\f05a"; /* circle-info (기본) */ + font-size: 1.05rem; + line-height: 1.55; + color: var(--skt-text-muted); +} +.alert-warning::before { content: "\f071"; color: #{$skt-orange}; } /* triangle-exclamation */ +.alert-danger::before { content: "\f06a"; color: #{$skt-red}; } /* circle-exclamation */ +.alert-success::before { content: "\f058"; color: #{$skt-green}; } /* circle-check */ +.alert-info::before, +.alert-primary::before { content: "\f05a"; color: var(--skt-text-muted); } +/* 콘텐츠는 모두 우측 컬럼 */ +.alert > * { grid-column: 2; min-width: 0; } +.alert .alert-heading { + font-size: 1rem; + font-weight: 600; + line-height: 1.4; color: var(--skt-text); + margin: 0 0 .35rem; } -.alert .alert-heading { color: var(--skt-text); } +.alert p { margin-bottom: .5rem; color: var(--skt-text-muted); } +.alert ul, .alert ol { margin-bottom: 0; padding-left: 1.15rem; color: var(--skt-text-muted); } +.alert > :last-child { margin-bottom: 0; } +.alert a { color: var(--skt-text); text-decoration: underline; text-decoration-color: var(--skt-border-strong); text-underline-offset: .15em; } +.alert a:hover { text-decoration-color: currentColor; } [data-bs-theme='dark'] .alert { background: var(--skt-surface-muted); border-color: var(--skt-border-strong); @@ -602,6 +790,27 @@ a.skt-card:hover, .td-toc #TableOfContents a:hover { color: var(--skt-text); } .td-toc #TableOfContents a.active { color: var(--skt-text); font-weight: 600; } +/* 우측 사이드바 전반 — 페이지 메타·택소노미 폰트가 과하게 굵고 커서 절제 */ +.td-sidebar-toc { font-size: .85rem; } +/* 페이지 메타 링크(페이지 보기/편집, Create … 등) */ +.td-page-meta a { + font-size: .85rem; + font-weight: 500; + color: var(--skt-text-muted); +} +.td-page-meta a:hover { color: var(--skt-text); } +/* 섹션 헤더(On this page / Tag Cloud / Categories) — 작고 조용한 라벨 */ +.td-toc .td-toc-title__text, +.taxonomy-title { + font-size: .8rem; + font-weight: 600; + letter-spacing: .02em; + color: var(--skt-text-muted); + margin-bottom: .5rem; +} +/* 택소노미 배지(태그/카테고리) */ +.td-sidebar-toc .taxonomy-term { font-size: .8rem; } + /* =================================================================== 8. 좌측 사이드바 — 위계·액티브를 모노크롬으로 정교화 =================================================================== */ @@ -681,8 +890,10 @@ a.skt-card:hover, pre, :not(pre) > code { background: var(--skt-surface-muted); border-color: var(--skt-border); } blockquote { background: var(--skt-surface-muted); } - table thead th { background: var(--skt-surface-muted); } - table th, - table td { border-color: var(--skt-border-strong); } + .skt-table { border-color: var(--skt-border-strong); } + .skt-table thead th { background: var(--skt-surface-muted); } + .skt-table th, + .skt-table td { border-color: var(--skt-border-strong); } + .skt-table tbody tr:hover > * { background: var(--skt-surface-muted); } img { border-color: var(--skt-border-strong); } } diff --git a/assets/scss/_variables_project.scss b/assets/scss/_variables_project.scss index 846ac5520..45b929da3 100644 --- a/assets/scss/_variables_project.scss +++ b/assets/scss/_variables_project.scss @@ -38,36 +38,40 @@ $skt-green: #00A651; // SK Green // Bootstrap 변수 오버라이드 // ============================================ +// 주의: 이 파일은 Bootstrap 변수 이후에 로드되므로 !default 를 붙이면 +// Bootstrap 기본값(예: $primary=#0d6efd 파랑)을 덮지 못한다. 오버라이드를 +// 실제로 적용하려면 !default 없이 강제 할당해야 한다. + // Primary: 모노크롬 강조 (라이트=near-black #171717). 다크는 _styles_project에서 반전. -$primary: $skt-t-blue !default; +$primary: $skt-t-blue; // Secondary: 무채색 회색 (모노크롬) -$secondary: $skt-gray-600 !default; +$secondary: $skt-gray-600; // 기타 색상 — success/warning/danger 는 의미색이라 유지(경고/성공 접근성) -$success: $skt-green !default; -$info: $skt-gray-500 !default; -$warning: $skt-orange !default; -$danger: $skt-red !default; +$success: $skt-green; +$info: $skt-gray-500; +$warning: $skt-orange; +$danger: $skt-red; // ============================================ // 링크 색상 (모노크롬) // ============================================ -$link-color: $skt-gray-900 !default; -$link-decoration: none !default; -$link-hover-color: $skt-gray-600 !default; -$link-hover-decoration: underline !default; +$link-color: $skt-gray-900; +$link-decoration: none; +$link-hover-color: $skt-gray-600; +$link-hover-decoration: underline; // ============================================ // 버튼 (Vercel 미니멀) // ============================================ -$btn-link-color: $skt-gray-900 !default; -$btn-link-hover-color: $skt-gray-600 !default; -$btn-font-weight: 500 !default; -$btn-border-radius: $border-radius !default; // 6px -$btn-border-radius-sm: $border-radius-sm !default; -$btn-border-radius-lg: $border-radius !default; // lg도 6px (Vercel은 안 키움) +$btn-link-color: $skt-gray-900; +$btn-link-hover-color: $skt-gray-600; +$btn-font-weight: 500; +$btn-border-radius: $border-radius; // 6px +$btn-border-radius-sm: $border-radius-sm; +$btn-border-radius-lg: $border-radius; // lg도 6px (Vercel은 안 키움) diff --git a/assets/scss/tokens/_colors.scss b/assets/scss/tokens/_colors.scss index 9ce47dec9..7ff58914c 100644 --- a/assets/scss/tokens/_colors.scss +++ b/assets/scss/tokens/_colors.scss @@ -31,3 +31,8 @@ $skt-gray-900: #0a0a0a; // text (라이트) // ── 그라데이션: 삭제 대신 무채색화 (참조처 안전) ── $skt-gradient: linear-gradient(135deg, #{$skt-gray-900} 0%, #{$skt-gray-700} 100%); + +// ── 액센트 = SK Red. 모노크롬 기조를 깨지 않도록 히어로 글로우 등 +// "핵심 1~2지점"에만 저채도로 절제해서 쓴다. ── +$skt-accent: #EA002C; // SK Red (PANTONE 186C) +$skt-accent-dark: #ff4d6a; // 다크 배경 위 가독성 보정용 diff --git a/layouts/404.html b/layouts/404.html new file mode 100644 index 000000000..1d4684a50 --- /dev/null +++ b/layouts/404.html @@ -0,0 +1,14 @@ +{{ define "main" }} +{{ $en := eq .Site.Language.Lang "en" }} +
+
+
404
+

{{ if $en }}Page not found{{ else }}페이지를 찾을 수 없습니다{{ end }}

+

{{ if $en }}The page you're looking for doesn't exist or may have moved.{{ else }}찾으시는 페이지가 없거나 이동되었을 수 있습니다.{{ end }}

+ +
+
+{{ end }} diff --git a/layouts/_markup/render-image.html b/layouts/_markup/render-image.html new file mode 100644 index 000000000..cc845327e --- /dev/null +++ b/layouts/_markup/render-image.html @@ -0,0 +1,15 @@ +{{- /* 이미지 렌더 훅 — 본문 마크다운 이미지에 치수·디코딩·로딩 힌트를 부여해 + CLS/LCP를 개선한다. 첫 이미지(.Ordinal 0)는 LCP 후보로 보고 eager+high, + 나머지는 lazy. 내부 번들 이미지면 width/height 추출, 외부 URL은 생략. */ -}} +{{- $dest := .Destination -}} +{{- $img := "" -}} +{{- if not (strings.HasPrefix $dest "http") -}} + {{- $path := strings.TrimPrefix "./" $dest -}} + {{- with .Page.Resources.GetMatch $path -}}{{- $img = . -}}{{- end -}} + {{- if not $img -}}{{- with resources.Get $path -}}{{- $img = . -}}{{- end -}}{{- end -}} +{{- end -}} +{{- $eager := eq .Ordinal 0 -}} +{{ .Text }} diff --git a/layouts/_markup/render-table.html b/layouts/_markup/render-table.html new file mode 100644 index 000000000..940a4961d --- /dev/null +++ b/layouts/_markup/render-table.html @@ -0,0 +1,28 @@ +{{- /* 표 렌더 훅 — 마크다운 표를 .skt-table 컨테이너로 감싸 외곽 테두리·라운드· + 모바일 가로 스크롤을 부여한다. Docsy/Hugo 기본 출력과 동일한 구조 유지. */ -}} +
+ + {{- with .THead -}} + + {{- range . -}} + + {{- range . -}} + {{ .Text }} + {{- end -}} + + {{- end -}} + + {{- end -}} + {{- with .TBody -}} + + {{- range . -}} + + {{- range . -}} + {{ .Text }} + {{- end -}} + + {{- end -}} + + {{- end -}} +
+
diff --git a/layouts/blog/list.html b/layouts/blog/list.html new file mode 100644 index 000000000..86ab358af --- /dev/null +++ b/layouts/blog/list.html @@ -0,0 +1,28 @@ +{{ define "main" -}} +
+

{{ .Title }}

+ {{ with .Content }}{{ . }}{{ end -}} +
+{{ if (and .Parent .Parent.IsHome) -}} + {{ $.Scratch.Set "blog-pages" (where .Site.RegularPages "Section" .Section) -}} +{{ else -}} + {{ $.Scratch.Set "blog-pages" .Pages -}} +{{ end -}} + +{{ if .Pages -}} +
+ {{ $pager := .Paginate (( $.Scratch.Get "blog-pages").GroupByDate "2006" ) -}} + {{ range $pager.PageGroups -}} +

{{ T "post_posts_in" }} {{ .Key }}

+
+ {{ range .Pages -}} + {{ partial "home/blog-card.html" . }} + {{ end -}} +
+ {{ end -}} +
+
+ {{ partial "pagination.html" . -}} +
+{{- end -}} +{{ end -}} diff --git a/layouts/partials/home/blog-card.html b/layouts/partials/home/blog-card.html index 0ac6f6cda..f15980dd2 100644 --- a/layouts/partials/home/blog-card.html +++ b/layouts/partials/home/blog-card.html @@ -1,5 +1,5 @@ {{/* 블로그 카드 — 컨텍스트(.)는 블로그 글 페이지 */}} - +
diff --git a/layouts/partials/home/cta-github.html b/layouts/partials/home/cta-github.html index 6bac23451..02577643f 100644 --- a/layouts/partials/home/cta-github.html +++ b/layouts/partials/home/cta-github.html @@ -1,6 +1,6 @@ {{/* GitHub CTA — 모노크롬 (subtle 밴드 + 테마 텍스트) */}}
-
+

{{ T "home_github_title" }}

{{ T "home_github_cta" }} diff --git a/layouts/partials/home/features.html b/layouts/partials/home/features.html index f7f034e02..77df76262 100644 --- a/layouts/partials/home/features.html +++ b/layouts/partials/home/features.html @@ -1,13 +1,13 @@ {{/* 피처 카드 3개 — 텍스트는 _index.md front matter(features)에서 */}}
-
+

{{ T "home_features_title" }}

{{ T "home_features_lead" }}

{{ range .Params.features }} -
+
diff --git a/layouts/partials/home/latest-blog.html b/layouts/partials/home/latest-blog.html index 2c50c3af4..a2b2f7a3f 100644 --- a/layouts/partials/home/latest-blog.html +++ b/layouts/partials/home/latest-blog.html @@ -4,7 +4,7 @@ {{ with $latest }}
-
+

{{ T "home_blog_title" }}

{{ T "home_blog_lead" }}

diff --git a/layouts/partials/home/latest-projects.html b/layouts/partials/home/latest-projects.html index 625a3690a..9c87610d2 100644 --- a/layouts/partials/home/latest-projects.html +++ b/layouts/partials/home/latest-projects.html @@ -5,7 +5,7 @@ {{ with $items }}
-
+

{{ T "home_projects_title" }}

{{ T "home_projects_lead" }}

diff --git a/layouts/partials/home/stats.html b/layouts/partials/home/stats.html index 94a24cde9..0cff2088f 100644 --- a/layouts/partials/home/stats.html +++ b/layouts/partials/home/stats.html @@ -7,7 +7,7 @@ {{ $years := sub now.Year 2019 }}
-
+
{{ $projects }}
{{ T "home_stat_projects" }}
diff --git a/layouts/partials/home/supply-chain.html b/layouts/partials/home/supply-chain.html index 6fd44f977..5112bc5ce 100644 --- a/layouts/partials/home/supply-chain.html +++ b/layouts/partials/home/supply-chain.html @@ -1,12 +1,12 @@ {{/* 공급망 보안 / SBOM Generator 스포트라이트 밴드 */}}
-
+

{{ T "home_supplychain_title" }}

{{ T "home_supplychain_lead" }}

-
+
{{ T "home_supplychain_cta_guide" }} diff --git a/layouts/partials/hooks/body-end.html b/layouts/partials/hooks/body-end.html new file mode 100644 index 000000000..5c9be5285 --- /dev/null +++ b/layouts/partials/hooks/body-end.html @@ -0,0 +1,13 @@ +{{/* 전역 스크립트 번들 (Docsy hooks/body-end). 프로덕션은 minify+fingerprint+SRI. */}} +{{ $scripts := slice }} +{{ with resources.Get "js/scroll-reveal.js" }}{{ $scripts = $scripts | append . }}{{ end }} +{{ with resources.Get "js/ui-enhancements.js" }}{{ $scripts = $scripts | append . }}{{ end }} +{{ with $scripts }} + {{ $js := . | resources.Concat "js/skt-bundle.js" }} + {{ if hugo.IsProduction }} + {{ $js = $js | minify | fingerprint }} + + {{ else }} + + {{ end }} +{{ end }} diff --git a/layouts/partials/hooks/head-end.html b/layouts/partials/hooks/head-end.html index 512b19a64..3f812a35a 100644 --- a/layouts/partials/hooks/head-end.html +++ b/layouts/partials/hooks/head-end.html @@ -2,6 +2,17 @@ +{{/* 스크롤 리빌 게이트 — paint 전에 html.js-reveal 을 부여해 FOUC 방지. + 동작 줄이기 사용자에겐 부여하지 않아 모션 없이 콘텐츠가 즉시 보인다. */}} + + {{/* Open Graph / Twitter 카드는 Docsy 내장 템플릿(opengraph·twitter_cards)이 생성하므로 여기서 중복 출력하지 않는다. 사이트 기본 OG 이미지는 hugo.yaml params.images로 지정. */}} @@ -20,3 +31,53 @@ {{ end -}} {{ end -}} {{ end -}} + +{{/* ===== JSON-LD 구조화 데이터 (검색 엔진 리치 결과) ===== */}} +{{/* Organization — 전 페이지 공통 (조직 식별) */}} + + +{{/* BlogPosting — 블로그 글 */}} +{{ if and (eq .Kind "page") (eq .Section "blog") -}} +{{ $img := absURL (index (.Site.Params.images | default (slice "og-default.png")) 0) -}} +{{ with .Params.images }}{{ $img = absURL (index . 0) }}{{ end -}} + +{{ end -}} + +{{/* BreadcrumbList — 조상이 있는 하위 페이지/섹션(가이드/컴플라이언스 등) */}} +{{ if and (or (eq .Kind "page") (eq .Kind "section")) .Ancestors -}} +{{ $items := slice -}} +{{ $crumbs := .Ancestors.Reverse -}} +{{ range $i, $a := $crumbs -}} + {{ $items = $items | append (dict "@type" "ListItem" "position" (add $i 1) "name" $a.LinkTitle "item" $a.Permalink) -}} +{{ end -}} +{{ $items = $items | append (dict "@type" "ListItem" "position" (add (len $crumbs) 1) "name" .LinkTitle "item" .Permalink) -}} + +{{ end -}} diff --git a/layouts/partials/project/card.html b/layouts/partials/project/card.html index 9dba3b25e..0cff33125 100644 --- a/layouts/partials/project/card.html +++ b/layouts/partials/project/card.html @@ -2,7 +2,7 @@ {{ $imgs := .Resources.ByType "image" }} {{ $img := $imgs.GetMatch "*logo*" }} {{ if and (not $img) $imgs }}{{ $img = index $imgs 0 }}{{ end }} - +
{{ with $img }} {{ if eq .MediaType.SubType "svg" }} diff --git a/static/favicons/apple-touch-icon.png b/static/favicons/apple-touch-icon.png index 173eaf1f2..21f5a0815 100644 Binary files a/static/favicons/apple-touch-icon.png and b/static/favicons/apple-touch-icon.png differ diff --git a/static/favicons/favicon-16x16.png b/static/favicons/favicon-16x16.png index 46ae47da3..3c8dbafc3 100644 Binary files a/static/favicons/favicon-16x16.png and b/static/favicons/favicon-16x16.png differ diff --git a/static/favicons/favicon-32x32.png b/static/favicons/favicon-32x32.png index 5589a4c82..0df6e618c 100644 Binary files a/static/favicons/favicon-32x32.png and b/static/favicons/favicon-32x32.png differ diff --git a/static/favicons/favicon.ico b/static/favicons/favicon.ico index 391467b6b..90a8cf735 100644 Binary files a/static/favicons/favicon.ico and b/static/favicons/favicon.ico differ diff --git a/static/favicons/icon-192.png b/static/favicons/icon-192.png index 31f8f5dc7..1e4959323 100644 Binary files a/static/favicons/icon-192.png and b/static/favicons/icon-192.png differ diff --git a/static/favicons/icon-512.png b/static/favicons/icon-512.png index 4f9c8a8b4..75c1d5446 100644 Binary files a/static/favicons/icon-512.png and b/static/favicons/icon-512.png differ