v0.1.1 테마 구성 및 레이아웃 수정

This commit is contained in:
2026-04-13 10:35:20 +09:00
commit 890b3c1902
31 changed files with 1813 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
AGENTS.md
docs/
theme-export/
*.zip

23
README.md Normal file
View File

@@ -0,0 +1,23 @@
# Ghost Theme: Thred-Inspired
This repository contains a Ghost theme scaffold inspired by the `Thred` reference layout.
## Included
- Three-column editorial shell
- Left sidebar navigation with categories and authors
- Sticky top search trigger with local search overlay
- Homepage hero with tabbed content sections
- Tag and author archive pages
- Post template with membership CTA and comments area
- Light and dark theme toggle
## Upload to Ghost
1. Zip the theme files.
2. Upload the zip in Ghost Admin under `Settings -> Design -> Change theme`.
3. Configure navigation, tags, authors, site icon, and custom footer links in Ghost Admin.
## Important note
The layout closely follows the provided reference, but Ghost content, membership, comments, and recommendation data depend on the site configuration and content model inside your Ghost installation.

974
assets/built/screen.css Normal file
View File

@@ -0,0 +1,974 @@
@import url("../fonts/pretendard.css");
:root {
--bg: #fcfbf9;
--surface: #ffffff;
--surface-muted: #f4f1ed;
--surface-strong: #131313;
--text: #1b1918;
--text-soft: #6e6661;
--border: #e6dfd7;
--accent: #f0632f;
--accent-strong: #d84d1d;
--shadow: 0 10px 30px rgba(20, 16, 12, 0.05);
--radius: 16px;
--radius-sm: 12px;
--sidebar-left: 240px;
--sidebar-right: 320px;
--content-column: 720px;
--content-measure: 680px;
--topbar-height: 72px;
--shell-width: calc(var(--sidebar-left) + var(--content-column) + var(--sidebar-right));
--font-sans: "Pretendard", "Apple SD Gothic Neo", "Noto Sans KR", "Segoe UI", sans-serif;
}
body.theme-dark {
--bg: #111111;
--surface: #131313;
--surface-muted: #1a1a1a;
--surface-strong: #f4f1ed;
--text: #f3efe9;
--text-soft: #ada59c;
--border: #272727;
--shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
a {
color: inherit;
text-decoration: none;
}
img {
display: block;
max-width: 100%;
}
button,
input {
font: inherit;
}
.site-shell {
display: grid;
grid-template-columns: var(--sidebar-left) minmax(0, var(--content-column)) var(--sidebar-right);
min-height: 100vh;
width: min(100%, var(--shell-width));
margin: 0 auto;
}
.sidebar,
.site-main {
min-width: 0;
}
.sidebar {
border-right: 1px solid var(--border);
}
.sidebar--right {
border-right: 0;
border-left: 1px solid var(--border);
}
.sidebar__inner {
position: sticky;
top: 0;
height: 100vh;
padding: 18px 14px;
overflow-y: auto;
}
.sidebar__inner--right {
display: flex;
flex-direction: column;
gap: 18px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
margin-bottom: 16px;
font-weight: 800;
}
.brand__mark {
width: 14px;
height: 14px;
border: 2px solid currentColor;
border-radius: 3px;
}
.brand__logo {
max-height: 32px;
}
.menu-groups {
display: grid;
gap: 6px;
}
.menu-group {
border-bottom: 1px solid var(--border);
}
.menu-group__trigger {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 13px 0;
border: 0;
background: none;
color: inherit;
cursor: pointer;
}
.menu-group__content {
padding: 0 0 10px;
display: none;
}
.menu-group.is-open .menu-group__content {
display: block;
}
.menu-group .nav {
margin: 0;
padding: 0;
list-style: none;
}
.menu-group .nav li + li,
.link-list li + li {
margin-top: 8px;
}
.menu-group .nav a,
.link-list a {
color: var(--text-soft);
}
.link-list {
margin: 0;
padding: 0;
list-style: none;
}
.sidebar-card {
padding: 16px 0 0;
border-top: 1px solid var(--border);
}
.sidebar-card--tight {
padding-top: 14px;
}
.sidebar-card__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.sidebar-card__header h2 {
margin: 0;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-soft);
}
.category-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 12px;
}
.category-chip {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 10px;
transition: background 0.2s ease;
}
.category-chip:hover,
.author-list__item:hover,
.recommended-list a:hover {
background: var(--surface-muted);
}
.category-chip__dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: linear-gradient(135deg, #5d66ff, #29b6f6);
}
.category-chip__count {
margin-left: auto;
color: var(--text-soft);
font-size: 12px;
}
.author-list {
display: grid;
gap: 10px;
}
.author-list__item {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 8px;
border-radius: 12px;
}
.author-list__item strong,
.author-list__item small {
display: block;
}
.author-list__item small {
color: var(--text-soft);
}
.sidebar-footer {
display: flex;
flex-wrap: wrap;
gap: 10px 14px;
align-items: center;
margin-top: auto;
padding-top: 16px;
color: var(--text-soft);
font-size: 13px;
}
.topbar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
justify-content: space-between;
gap: 16px;
align-items: center;
height: var(--topbar-height);
padding: 16px 20px;
border-bottom: 1px solid var(--border);
background: color-mix(in srgb, var(--bg) 88%, transparent);
backdrop-filter: blur(14px);
}
.topbar__search {
flex: 1;
max-width: 320px;
}
.search-trigger,
.search-modal__input {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 12px 16px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: var(--shadow);
color: var(--text-soft);
}
.search-trigger {
cursor: pointer;
}
.search-shortcut {
margin-left: auto;
padding: 1px 7px;
border: 1px solid var(--border);
border-radius: 8px;
font-size: 12px;
}
.topbar__actions {
display: flex;
align-items: center;
gap: 10px;
}
.button,
.gh-subscribe-form button {
display: inline-flex;
justify-content: center;
align-items: center;
padding: 11px 16px;
border: 0;
border-radius: 10px;
background: var(--surface-strong);
color: var(--bg);
font-weight: 700;
cursor: pointer;
}
.button--accent {
background: var(--accent);
color: #fff;
}
.button--accent:hover {
background: var(--accent-strong);
}
.button--light {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
}
.button--subscribe {
padding-inline: 14px;
}
.icon-button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface);
color: inherit;
cursor: pointer;
}
.icon-button--plain {
border: 0;
background: transparent;
}
.content-area {
padding: 0 20px 44px;
}
.hero {
display: grid;
justify-items: center;
gap: 16px;
padding: 38px 20px 28px;
text-align: center;
border-bottom: 1px solid var(--border);
}
.hero__title {
max-width: 680px;
margin: 0;
font-size: clamp(2rem, 3vw, 3.1rem);
line-height: 1.08;
letter-spacing: -0.04em;
font-weight: 800;
}
.hero__description,
.section-description {
max-width: 620px;
margin: 0;
color: var(--text-soft);
font-size: 1rem;
line-height: 1.7;
}
.subscribe-form,
.gh-subscribe-form {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.gh-subscribe-form {
width: 100%;
}
.gh-subscribe-form .gh-subscribe-input,
.subscribe-form input {
min-width: 0;
flex: 1 1 180px;
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--surface);
color: inherit;
}
.subscribe-form--hero {
justify-content: center;
}
.stack-section {
padding: 18px 0;
}
.section-header {
padding-bottom: 18px;
border-bottom: 1px solid var(--border);
}
.section-header--author {
padding-top: 8px;
}
.section-title {
margin: 0 0 8px;
font-size: clamp(1.7rem, 2vw, 2.15rem);
line-height: 1.12;
letter-spacing: -0.03em;
}
.author-header {
display: flex;
align-items: center;
gap: 18px;
}
.tab-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding-bottom: 14px;
border-bottom: 1px solid var(--border);
}
.tab-row__button {
padding: 10px 2px;
border: 0;
border-bottom: 2px solid transparent;
background: none;
color: var(--text-soft);
font-weight: 700;
cursor: pointer;
}
.tab-row__button.is-active {
color: var(--text);
border-color: var(--accent);
}
.tab-panel {
display: none;
}
.tab-panel.is-active {
display: block;
}
.post-list {
display: grid;
}
.post-card {
display: grid;
grid-template-columns: 96px minmax(0, 1fr);
gap: 16px;
padding: 16px 0;
border-bottom: 1px solid var(--border);
}
.post-card__media img,
.post-card__media-fallback {
width: 96px;
height: 72px;
object-fit: cover;
border-radius: 10px;
background: var(--surface-muted);
}
.post-card__media-fallback {
display: grid;
place-items: center;
padding: 8px;
color: var(--text-soft);
font-size: 12px;
text-align: center;
}
.post-card__content h2 {
margin: 0 0 6px;
font-size: 1.04rem;
line-height: 1.34;
letter-spacing: -0.01em;
}
.post-card__content p {
margin: 0 0 8px;
color: var(--text-soft);
font-size: 0.94rem;
line-height: 1.55;
}
.post-card__meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: var(--text-soft);
font-size: 13px;
}
.meta-pill {
display: inline-flex;
align-items: center;
padding: 4px 8px;
border-radius: 999px;
background: var(--surface-muted);
color: var(--text);
font-size: 12px;
font-weight: 700;
}
.category-overview {
display: grid;
}
.category-overview__row {
display: grid;
grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
gap: 24px;
padding: 20px 0;
border-bottom: 1px solid var(--border);
}
.category-overview__row h2 {
margin: 0 0 10px;
}
.category-overview__row p {
margin: 0 0 14px;
color: var(--text-soft);
line-height: 1.6;
}
.category-overview__row ol {
margin: 0;
padding-left: 18px;
display: grid;
gap: 10px;
}
.site-icon,
.site-icon--fallback {
width: 54px;
height: 54px;
border-radius: 16px;
object-fit: cover;
background: var(--surface-strong);
color: var(--bg);
display: grid;
place-items: center;
font-weight: 800;
}
.about-block h2,
.author-feature h3 {
margin: 0 0 4px;
}
.about-block p,
.author-feature p,
.copyright {
margin: 0;
color: var(--text-soft);
line-height: 1.55;
}
.social-row {
display: flex;
gap: 12px;
}
.social-row a {
color: var(--text-soft);
text-transform: uppercase;
font-size: 12px;
font-weight: 700;
}
.recommended-list {
margin: 0;
padding: 0;
list-style: none;
display: grid;
gap: 8px;
}
.recommended-list a {
display: block;
padding: 6px 8px;
border-radius: 10px;
line-height: 1.5;
font-size: 0.94rem;
}
.author-feature {
display: flex;
gap: 14px;
align-items: center;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
.avatar,
.avatar--fallback {
width: 34px;
height: 34px;
border-radius: 999px;
object-fit: cover;
background: linear-gradient(135deg, #ff8a65, #ffb74d);
color: #fff;
display: inline-grid;
place-items: center;
font-weight: 800;
}
.avatar--large {
width: 56px;
height: 56px;
}
.post-template {
max-width: var(--content-measure);
margin: 0 auto;
padding: 24px 0 10px;
}
.post-header {
display: grid;
gap: 16px;
padding-bottom: 24px;
border-bottom: 1px solid var(--border);
}
.post-tag {
color: var(--accent);
font-weight: 700;
}
.post-title {
margin: 0;
font-size: clamp(2rem, 3vw, 2.8rem);
line-height: 1.08;
letter-spacing: -0.04em;
}
.post-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
color: var(--text-soft);
}
.post-feature-image img {
width: 100%;
border-radius: 18px;
}
.kg-content {
font-size: 1.02rem;
line-height: 1.88;
}
.kg-content > * + * {
margin-top: 1.2em;
}
.kg-content .kg-width-wide {
position: relative;
width: min(100vw - 48px, 820px);
max-width: none;
margin-left: 50%;
transform: translateX(-50%);
}
.kg-content .kg-width-full {
position: relative;
width: 100vw;
max-width: none;
margin-left: 50%;
transform: translateX(-50%);
}
.kg-content .kg-width-full img,
.kg-content .kg-width-wide img {
width: 100%;
}
.page-template .kg-content > .kg-width-full:first-child,
.page-template .kg-content > .kg-width-wide:first-child {
margin-top: 0;
}
.page-template .kg-content > .kg-width-full:last-child,
.page-template .kg-content > .kg-width-wide:last-child {
margin-bottom: 0;
}
.kg-content .kg-width-full + .kg-width-full:not(.kg-card-hascaption),
.kg-content .kg-width-wide + .kg-width-wide:not(.kg-card-hascaption),
.kg-content .kg-width-full + .kg-width-wide:not(.kg-card-hascaption),
.kg-content .kg-width-wide + .kg-width-full:not(.kg-card-hascaption) {
margin-top: 0;
}
.kg-content blockquote {
margin: 1.4em 0;
padding: 1em 1.1em;
border-left: 4px solid var(--accent);
background: color-mix(in srgb, var(--accent) 10%, var(--surface));
border-radius: 12px;
}
.membership-cta,
.comments-empty {
display: grid;
justify-items: center;
gap: 12px;
padding: 36px 24px;
margin-top: 28px;
background: var(--surface-strong);
color: var(--bg);
border-radius: 20px;
text-align: center;
}
.comments-empty {
background: var(--surface-muted);
color: var(--text);
border: 1px solid var(--border);
}
.author-inline-card,
.discussion-panel,
.post-navigation {
max-width: var(--content-measure);
margin-inline: auto;
}
.author-inline-card {
display: flex;
gap: 14px;
align-items: center;
padding-top: 18px;
border-top: 1px solid var(--border);
}
.discussion-panel {
margin-top: 24px;
padding-top: 18px;
border-top: 1px solid var(--border);
}
.discussion-header {
display: flex;
justify-content: space-between;
margin-bottom: 18px;
}
.discussion-header h2 {
margin: 0;
}
.post-navigation {
display: flex;
justify-content: space-between;
gap: 14px;
padding: 24px 0;
border-top: 1px solid var(--border);
}
.post-navigation__link {
display: block;
flex: 1 1 0;
padding: 12px 0;
}
.post-navigation__link small {
display: block;
margin-bottom: 4px;
color: var(--text-soft);
text-transform: uppercase;
}
.post-navigation__link--next {
text-align: right;
}
.pagination {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
padding-top: 20px;
}
.pagination__link {
padding: 10px 14px;
border: 1px solid var(--border);
border-radius: 10px;
}
.pagination__status {
color: var(--text-soft);
}
.search-modal[hidden] {
display: none;
}
.search-modal {
position: fixed;
inset: 0;
z-index: 80;
}
.search-modal__backdrop {
position: absolute;
inset: 0;
background: rgba(10, 10, 10, 0.42);
backdrop-filter: blur(4px);
}
.search-modal__panel {
position: relative;
width: min(100%, 720px);
margin: 56px auto;
padding: 0 18px;
}
.search-modal__input input {
flex: 1;
border: 0;
background: transparent;
color: inherit;
outline: none;
}
.search-modal__body {
margin-top: 14px;
padding: 18px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 18px;
box-shadow: var(--shadow);
max-height: 70vh;
overflow: auto;
}
.search-modal__hint,
.search-empty {
color: var(--text-soft);
}
.search-result {
display: block;
padding: 10px 4px;
border-bottom: 1px solid var(--border);
}
.search-result:last-child {
border-bottom: 0;
}
@media (max-width: 1240px) {
.site-shell {
width: 100%;
grid-template-columns: 240px minmax(0, 1fr);
}
.sidebar--right {
display: none;
}
}
@media (max-width: 960px) {
.site-shell {
grid-template-columns: 1fr;
}
.sidebar--left {
display: none;
}
.topbar {
padding-inline: 14px;
}
.content-area {
padding-inline: 14px;
}
.hero {
padding-inline: 0;
}
.category-overview__row {
grid-template-columns: 1fr;
gap: 10px;
}
}
@media (max-width: 640px) {
.topbar {
gap: 10px;
}
.topbar__search {
max-width: none;
}
.search-trigger span:nth-child(2) {
display: none;
}
.button--accent {
padding-inline: 12px;
font-size: 14px;
}
.post-card {
grid-template-columns: 1fr;
}
.post-card__media img,
.post-card__media-fallback {
width: 100%;
height: 180px;
}
.post-navigation {
flex-direction: column;
}
.post-navigation__link--next {
text-align: left;
}
}

134
assets/built/theme.js Normal file
View File

@@ -0,0 +1,134 @@
(function () {
var root = document.documentElement;
var body = document.body;
var storageKey = "ghost-thred-theme";
function setTheme(theme) {
body.classList.toggle("theme-dark", theme === "dark");
localStorage.setItem(storageKey, theme);
}
var savedTheme = localStorage.getItem(storageKey);
if (savedTheme) {
setTheme(savedTheme);
}
document.querySelectorAll("[data-theme-toggle]").forEach(function (button) {
button.addEventListener("click", function () {
setTheme(body.classList.contains("theme-dark") ? "light" : "dark");
});
});
document.querySelectorAll("[data-accordion]").forEach(function (button) {
button.addEventListener("click", function () {
var section = button.parentElement;
section.classList.toggle("is-open");
});
});
var tabRoot = document.querySelector("[data-tabs]");
if (tabRoot) {
var triggers = tabRoot.querySelectorAll("[data-tab-trigger]");
var panels = tabRoot.querySelectorAll("[data-tab-panel]");
triggers.forEach(function (trigger) {
trigger.addEventListener("click", function () {
var target = trigger.getAttribute("data-tab-trigger");
triggers.forEach(function (item) {
item.classList.toggle("is-active", item === trigger);
});
panels.forEach(function (panel) {
panel.classList.toggle("is-active", panel.getAttribute("data-tab-panel") === target);
});
});
});
}
var searchModal = document.querySelector("[data-search-modal]");
var searchInput = document.querySelector("[data-search-input]");
var searchResults = document.querySelector("[data-search-results]");
function getSearchItems() {
var items = [];
document.querySelectorAll(".post-card h2 a, .recommended-list a, .category-chip, .author-list__item").forEach(function (link) {
items.push({
title: (link.textContent || "").trim(),
url: link.getAttribute("href") || "#"
});
});
return items.filter(function (item, index, array) {
return item.title && array.findIndex(function (candidate) {
return candidate.title === item.title && candidate.url === item.url;
}) === index;
});
}
function renderSearchResults(keyword) {
var normalized = keyword.trim().toLowerCase();
if (!normalized) {
searchResults.innerHTML = '<p class="search-modal__hint">Start typing to filter visible posts, tags, and authors in the current page.</p>';
return;
}
var items = getSearchItems().filter(function (item) {
return item.title.toLowerCase().indexOf(normalized) !== -1;
});
if (!items.length) {
searchResults.innerHTML = '<p class="search-empty">No matching items in the current view.</p>';
return;
}
searchResults.innerHTML = items.map(function (item) {
return '<a class="search-result" href="' + item.url + '">' + item.title + "</a>";
}).join("");
}
function toggleSearch(open) {
if (!searchModal) {
return;
}
searchModal.hidden = !open;
body.style.overflow = open ? "hidden" : "";
if (open && searchInput) {
window.setTimeout(function () {
searchInput.focus();
}, 10);
}
}
document.querySelectorAll("[data-search-open]").forEach(function (button) {
button.addEventListener("click", function () {
toggleSearch(true);
});
});
document.querySelectorAll("[data-search-close]").forEach(function (button) {
button.addEventListener("click", function () {
toggleSearch(false);
});
});
if (searchInput) {
searchInput.addEventListener("input", function (event) {
renderSearchResults(event.target.value);
});
}
document.addEventListener("keydown", function (event) {
if (event.key === "/" && !/input|textarea/i.test(document.activeElement.tagName)) {
event.preventDefault();
toggleSearch(true);
}
if (event.key === "Escape") {
toggleSearch(false);
}
});
})();

View File

@@ -0,0 +1,71 @@
/*
Copyright (c) 2021 Kil Hyung-jin, with Reserved Font Name Pretendard.
https://github.com/orioncactus/pretendard
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
*/
@font-face {
font-family: 'Pretendard';
font-weight: 900;
font-display: swap;
src: local('Pretendard Black'), url(./woff2/Pretendard-Black.woff2) format('woff2');
}
@font-face {
font-family: 'Pretendard';
font-weight: 800;
font-display: swap;
src: local('Pretendard ExtraBold'), url(./woff2/Pretendard-ExtraBold.woff2) format('woff2');
}
@font-face {
font-family: 'Pretendard';
font-weight: 700;
font-display: swap;
src: local('Pretendard Bold'), url(./woff2/Pretendard-Bold.woff2) format('woff2');
}
@font-face {
font-family: 'Pretendard';
font-weight: 600;
font-display: swap;
src: local('Pretendard SemiBold'), url(./woff2/Pretendard-SemiBold.woff2) format('woff2');
}
@font-face {
font-family: 'Pretendard';
font-weight: 500;
font-display: swap;
src: local('Pretendard Medium'), url(./woff2/Pretendard-Medium.woff2) format('woff2');
}
@font-face {
font-family: 'Pretendard';
font-weight: 400;
font-display: swap;
src: local('Pretendard Regular'), url(./woff2/Pretendard-Regular.woff2) format('woff2');
}
@font-face {
font-family: 'Pretendard';
font-weight: 300;
font-display: swap;
src: local('Pretendard Light'), url(./woff2/Pretendard-Light.woff2) format('woff2');
}
@font-face {
font-family: 'Pretendard';
font-weight: 200;
font-display: swap;
src: local('Pretendard ExtraLight'), url(./woff2/Pretendard-ExtraLight.woff2) format('woff2');
}
@font-face {
font-family: 'Pretendard';
font-weight: 100;
font-display: swap;
src: local('Pretendard Thin'), url(./woff2/Pretendard-Thin.woff2) format('woff2');
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

23
author.hbs Normal file
View File

@@ -0,0 +1,23 @@
{{!< default}}
{{#author}}
<main class="content-area">
<section class="stack-section">
<header class="section-header section-header--author">
<div class="author-header">
{{#if profile_image}}
<img class="avatar avatar--large" src="{{img_url profile_image size="s"}}" alt="{{name}}">
{{else}}
<div class="avatar avatar--large avatar--fallback">A</div>
{{/if}}
<div>
<h1 class="section-title">{{name}}</h1>
{{#if bio}}<p class="section-description">{{bio}}</p>{{/if}}
<div class="meta-pill">{{plural count.posts empty="No posts" singular="% post" plural="% posts"}}</div>
</div>
</div>
</header>
{{> "lists/post-feed"}}
</section>
</main>
{{/author}}

26
default.hbs Normal file
View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="{{@site.locale}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{meta_title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="{{asset "built/screen.css"}}">
{{ghost_head}}
</head>
<body class="{{body_class}}">
<div class="site-shell">
{{> "site/sidebar-left"}}
<div class="site-main">
{{> "site/topbar"}}
{{{body}}}
</div>
{{> "site/sidebar-right"}}
</div>
{{ghost_foot}}
<script src="{{asset "built/theme.js"}}"></script>
</body>
</html>

6
home.hbs Normal file
View File

@@ -0,0 +1,6 @@
{{!< default}}
<main class="content-area">
{{> "home/hero"}}
{{> "home/tabbed-feed"}}
</main>

18
index.hbs Normal file
View File

@@ -0,0 +1,18 @@
{{!< default}}
<main class="content-area">
{{#is "home"}}
{{> "home/hero"}}
{{> "home/tabbed-feed"}}
{{else}}
<section class="stack-section">
<header class="section-header">
<h1 class="section-title">{{@site.title}}</h1>
{{#if @site.description}}
<p class="section-description">{{@site.description}}</p>
{{/if}}
</header>
{{> "lists/post-feed" show_filter=true}}
</section>
{{/is}}
</main>

73
package.json Normal file
View File

@@ -0,0 +1,73 @@
{
"name": "ghost-theme-thred-clone",
"version": "0.1.1",
"private": true,
"description": "A Ghost theme inspired by the Thred reference layout.",
"author": {
"name": "OpenAI Codex",
"email": "support@openai.com"
},
"engines": {
"ghost-api": "v5"
},
"config": {
"posts_per_page": 10,
"image_sizes": {
"xxs": {
"width": 30
},
"xs": {
"width": 100
},
"s": {
"width": 300
},
"m": {
"width": 600
},
"l": {
"width": 1000
},
"xl": {
"width": 2000
}
},
"custom": {
"footer_primary_link": {
"type": "text",
"default": "Portal"
},
"footer_primary_url": {
"type": "text",
"default": "/"
},
"footer_secondary_link": {
"type": "text",
"default": "Docs"
},
"footer_secondary_url": {
"type": "text",
"default": "/"
},
"footer_tertiary_link": {
"type": "text",
"default": "Get theme"
},
"footer_tertiary_url": {
"type": "text",
"default": "/"
},
"footer_quaternary_link": {
"type": "text",
"default": "Changelog"
},
"footer_quaternary_url": {
"type": "text",
"default": "/"
}
}
},
"scripts": {
"zip": "zip -r theme.zip . -x '*.git*' -x 'node_modules/*' -x 'theme.zip'"
}
}

24
page.hbs Normal file
View File

@@ -0,0 +1,24 @@
{{!< default}}
{{#post}}
<main class="content-area content-area--post">
<article class="post-template page-template {{post_class}}">
{{#match @page.show_title_and_feature_image}}
<header class="post-header">
<h1 class="post-title">{{title}}</h1>
{{#if custom_excerpt}}
<p class="section-description">{{custom_excerpt}}</p>
{{/if}}
{{#if feature_image}}
<figure class="post-feature-image">
<img src="{{img_url feature_image size="l"}}" alt="{{title}}">
</figure>
{{/if}}
</header>
{{/match}}
<section class="post-content kg-content">
{{content}}
</section>
</article>
</main>
{{/post}}

11
partials/home/hero.hbs Normal file
View File

@@ -0,0 +1,11 @@
<section class="hero">
<h1 class="hero__title">{{@site.title}} ideas published for meaningful conversation, discussed and shaped by the community</h1>
{{#if @site.description}}
<p class="hero__description">{{@site.description}}</p>
{{/if}}
{{subscribe_form
placeholder="Your email"
button_class="button button--light"
form_class="subscribe-form subscribe-form--hero"
}}
</section>

View File

@@ -0,0 +1,51 @@
<section class="stack-section" data-tabs>
<div class="tab-row" role="tablist" aria-label="Homepage sections">
<button class="tab-row__button is-active" type="button" data-tab-trigger="latest">Latest</button>
<button class="tab-row__button" type="button" data-tab-trigger="featured">Featured</button>
<button class="tab-row__button" type="button" data-tab-trigger="updated">Updated</button>
<button class="tab-row__button" type="button" data-tab-trigger="categories">Categories</button>
</div>
<div class="tab-panel is-active" data-tab-panel="latest">
{{> "lists/post-feed"}}
</div>
<div class="tab-panel" data-tab-panel="featured">
{{#get "posts" filter="featured:true" limit="8" include="authors,tags"}}
{{> "lists/post-items" posts=posts}}
{{/get}}
</div>
<div class="tab-panel" data-tab-panel="updated">
{{#get "posts" order="updated_at desc" limit="8" include="authors,tags"}}
{{> "lists/post-items" posts=posts}}
{{/get}}
</div>
<div class="tab-panel" data-tab-panel="categories">
<div class="category-overview">
{{#get "tags" limit="4" include="count.posts"}}
{{#foreach tags}}
<section class="category-overview__row">
<div>
<h2>{{name}}</h2>
{{#if description}}
<p>{{description}}</p>
{{else}}
<p>{{plural count.posts empty="No posts yet" singular="% post in this category" plural="% posts in this category"}}</p>
{{/if}}
<a href="{{url}}">View all ↗</a>
</div>
<ol>
{{#get "posts" filter=(concat "tag:" slug) limit="5"}}
{{#foreach posts}}
<li><a href="{{url}}">{{title}}</a></li>
{{/foreach}}
{{/get}}
</ol>
</section>
{{/foreach}}
{{/get}}
</div>
</div>
</section>

View File

@@ -0,0 +1,7 @@
{{#if show_filter}}
<div class="section-filter">
<span>Latest posts</span>
</div>
{{/if}}
{{> "lists/post-items" posts=posts}}
{{pagination}}

View File

@@ -0,0 +1,23 @@
<div class="post-list">
{{#foreach posts}}
<article class="post-card">
<a class="post-card__media" href="{{url}}">
{{#if feature_image}}
<img src="{{img_url feature_image size="s"}}" alt="{{title}}">
{{else}}
<span class="post-card__media-fallback">{{title}}</span>
{{/if}}
</a>
<div class="post-card__content">
<h2><a href="{{url}}">{{title}}</a></h2>
{{#if excerpt}}<p>{{excerpt words="22"}}</p>{{/if}}
<div class="post-card__meta">
<time datetime="{{date format="YYYY-MM-DD"}}">{{date format="MMM D"}}</time>
{{#primary_author}}<span>{{name}}</span>{{/primary_author}}
{{#primary_tag}}<a class="meta-pill" href="{{url}}">{{name}}</a>{{/primary_tag}}
{{#if comments}}{{comment_count empty="0" singular="" plural="" autowrap="false"}}{{/if}}
</div>
</div>
</article>
{{/foreach}}
</div>

9
partials/pagination.hbs Normal file
View File

@@ -0,0 +1,9 @@
<nav class="pagination" role="navigation">
{{#if prev}}
<a class="pagination__link" href="{{page_url prev}}">Newer</a>
{{/if}}
<span class="pagination__status">Page {{page}} of {{pages}}</span>
{{#if next}}
<a class="pagination__link" href="{{page_url next}}">Older</a>
{{/if}}
</nav>

View File

@@ -0,0 +1,14 @@
<nav class="post-navigation">
{{#prev_post}}
<a class="post-navigation__link" href="{{url}}">
<small>Previous post</small>
<strong>{{title}}</strong>
</a>
{{/prev_post}}
{{#next_post}}
<a class="post-navigation__link post-navigation__link--next" href="{{url}}">
<small>Next post</small>
<strong>{{title}}</strong>
</a>
{{/next_post}}
</nav>

View File

@@ -0,0 +1,118 @@
<aside class="sidebar sidebar--left">
<div class="sidebar__inner">
<a class="brand" href="{{@site.url}}">
{{#if @site.logo}}
<img class="brand__logo" src="{{@site.logo}}" alt="{{@site.title}}">
{{else}}
<span class="brand__mark"></span>
<span class="brand__name">{{@site.title}}</span>
{{/if}}
</a>
<nav class="menu-groups">
<section class="menu-group is-open">
<button class="menu-group__trigger" type="button" data-accordion>
<span>Home pages</span>
<span>⌄</span>
</button>
<div class="menu-group__content">
{{navigation}}
</div>
</section>
<section class="menu-group">
<button class="menu-group__trigger" type="button" data-accordion>
<span>Tags</span>
<span>⌄</span>
</button>
<div class="menu-group__content">
<ul class="link-list">
{{#get "tags" limit="6" include="count.posts"}}
{{#foreach tags}}
<li><a href="{{url}}">{{name}}</a></li>
{{/foreach}}
{{/get}}
</ul>
</div>
</section>
<section class="menu-group">
<button class="menu-group__trigger" type="button" data-accordion>
<span>Authors</span>
<span>⌄</span>
</button>
<div class="menu-group__content">
<ul class="link-list">
{{#get "authors" limit="6" include="count.posts"}}
{{#foreach authors}}
<li><a href="{{url}}">{{name}}</a></li>
{{/foreach}}
{{/get}}
</ul>
</div>
</section>
<section class="menu-group">
<button class="menu-group__trigger" type="button" data-accordion>
<span>Members</span>
<span>⌄</span>
</button>
<div class="menu-group__content">
<ul class="link-list">
<li><a href="#/portal/signup">Sign up</a></li>
<li><a href="#/portal/signin">Sign in</a></li>
<li><a href="#/portal/account">Account</a></li>
</ul>
</div>
</section>
</nav>
<section class="sidebar-card">
<div class="sidebar-card__header">
<h2>Categories</h2>
</div>
<div class="category-grid">
{{#get "tags" limit="10" include="count.posts"}}
{{#foreach tags}}
<a class="category-chip" href="{{url}}">
<span class="category-chip__dot"></span>
<span>{{name}}</span>
<span class="category-chip__count">{{count.posts}}</span>
</a>
{{/foreach}}
{{/get}}
</div>
</section>
<section class="sidebar-card">
<div class="sidebar-card__header">
<h2>Authors</h2>
</div>
<div class="author-list">
{{#get "authors" limit="4" include="count.posts"}}
{{#foreach authors}}
<a class="author-list__item" href="{{url}}">
{{#if profile_image}}
<img class="avatar" src="{{img_url profile_image size="xxs"}}" alt="{{name}}">
{{else}}
<span class="avatar avatar--fallback">A</span>
{{/if}}
<span>
<strong>{{name}}</strong>
<small>{{plural count.posts empty="No posts" singular="% post" plural="% posts"}}</small>
</span>
</a>
{{/foreach}}
{{/get}}
</div>
</section>
<footer class="sidebar-footer">
<a href="{{@custom.footer_primary_url}}">{{@custom.footer_primary_link}}</a>
<a href="{{@custom.footer_secondary_url}}">{{@custom.footer_secondary_link}}</a>
<a href="{{@custom.footer_tertiary_url}}">{{@custom.footer_tertiary_link}}</a>
<a href="{{@custom.footer_quaternary_url}}">{{@custom.footer_quaternary_link}}</a>
<button class="icon-button icon-button--plain" type="button" data-theme-toggle aria-label="Toggle theme">◐</button>
</footer>
</div>
</aside>

View File

@@ -0,0 +1,84 @@
<aside class="sidebar sidebar--right">
<div class="sidebar__inner sidebar__inner--right">
{{#if @site.icon}}
<img class="site-icon" src="{{@site.icon}}" alt="{{@site.title}}">
{{else}}
<div class="site-icon site-icon--fallback">T</div>
{{/if}}
<div class="about-block">
<h2>{{@site.title}}</h2>
{{#if @site.description}}
<p>{{@site.description}}</p>
{{/if}}
</div>
{{subscribe_form
placeholder="Your email"
button_class="button button--subscribe"
form_class="subscribe-form"
}}
<section class="sidebar-card sidebar-card--tight">
<div class="sidebar-card__header">
<h2>Follow</h2>
</div>
<div class="social-row">
{{#if @site.facebook}}<a href="{{social_url type="facebook"}}" aria-label="Facebook">f</a>{{/if}}
{{#if @site.twitter}}<a href="{{social_url type="twitter"}}" aria-label="X">x</a>{{/if}}
<a href="{{@site.url}}/rss/" aria-label="RSS">rss</a>
</div>
</section>
<section class="sidebar-card sidebar-card--tight">
<div class="sidebar-card__header">
<h2>Recommended</h2>
</div>
<ul class="recommended-list">
{{#get "posts" filter="featured:true" limit="3" include="tags"}}
{{#foreach posts}}
<li><a href="{{url}}">{{title}}</a></li>
{{/foreach}}
{{else}}
<li><a href="{{@site.url}}">{{@site.title}}</a></li>
{{/get}}
</ul>
</section>
<section class="sidebar-card sidebar-card--about">
<p>{{@site.title}} is a thoughtfully designed Ghost theme inspired by editorial communities, with flexible settings that let you shape it to your publication.</p>
<a class="button button--accent" href="{{@site.url}}">About {{@site.title}}</a>
</section>
{{#is "post"}}
{{#post}}
{{#primary_author}}
<section class="author-feature">
{{#if profile_image}}
<img class="avatar avatar--large" src="{{img_url profile_image size="s"}}" alt="{{name}}">
{{else}}
<div class="avatar avatar--large avatar--fallback">A</div>
{{/if}}
<div>
<h3>{{name}}</h3>
{{#if bio}}<p>{{bio}}</p>{{/if}}
</div>
</section>
{{/primary_author}}
<section class="sidebar-card sidebar-card--tight">
<div class="sidebar-card__header">
<h2>Read next</h2>
</div>
<ul class="recommended-list">
{{#get "posts" filter=(concat "id:-" id) limit="4"}}
{{#foreach posts}}
<li><a href="{{url}}">{{title}}</a></li>
{{/foreach}}
{{/get}}
</ul>
</section>
{{/post}}
{{/is}}
<footer class="copyright">©{{date format="YYYY"}} {{@site.title}}. Published with Ghost.</footer>
</div>
</aside>

30
partials/site/topbar.hbs Normal file
View File

@@ -0,0 +1,30 @@
<header class="topbar">
<div class="topbar__search">
<button class="search-trigger" type="button" data-search-open aria-label="Open search">
<span class="search-trigger__icon">⌕</span>
<span>Search</span>
<span class="search-shortcut">/</span>
</button>
</div>
<div class="topbar__actions">
{{#if @member}}
<a class="button button--accent" href="#/portal/account">Account</a>
{{else}}
<a class="button button--accent" href="#/portal/signup">Buy {{@site.title}}</a>
{{/if}}
<button class="icon-button" type="button" data-theme-toggle aria-label="Toggle theme">◐</button>
</div>
</header>
<div class="search-modal" data-search-modal hidden>
<div class="search-modal__backdrop" data-search-close></div>
<div class="search-modal__panel">
<div class="search-modal__input">
<button type="button" class="icon-button icon-button--plain" data-search-close aria-label="Close search">×</button>
<input type="search" placeholder="Search posts, tags and authors" data-search-input>
</div>
<div class="search-modal__body" data-search-results>
<p class="search-modal__hint">Start typing to filter visible posts, tags, and authors in the current page.</p>
</div>
</div>
</div>

74
post.hbs Normal file
View File

@@ -0,0 +1,74 @@
{{!< default}}
{{#post}}
<main class="content-area content-area--post">
<article class="post-template {{post_class}}">
<header class="post-header">
{{#if primary_tag}}
<a class="post-tag" href="{{primary_tag.url}}">{{primary_tag.name}}</a>
{{/if}}
<h1 class="post-title">{{title}}</h1>
<div class="post-meta">
<time datetime="{{date format="YYYY-MM-DD"}}">{{date format="MMM D, YYYY"}}</time>
{{#primary_author}}
<span>{{name}}</span>
{{/primary_author}}
<span>{{reading_time}}</span>
</div>
{{#if feature_image}}
<figure class="post-feature-image">
<img
srcset="{{img_url feature_image size="s"}} 300w, {{img_url feature_image size="m"}} 600w, {{img_url feature_image size="l"}} 1000w"
sizes="(max-width: 900px) 100vw, 760px"
src="{{img_url feature_image size="l"}}"
alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}"
>
</figure>
{{/if}}
</header>
<section class="post-content kg-content">
{{content}}
</section>
{{#unless access}}
<section class="membership-cta">
<h2>This post is for paying subscribers only</h2>
<a class="button button--light" href="#/portal/signup">Subscribe Now</a>
<p>Already have an account? <a href="#/portal/signin">Sign in</a></p>
</section>
{{/unless}}
<footer class="post-footer">
{{#primary_author}}
<div class="author-inline-card">
{{#if profile_image}}
<img class="avatar" src="{{img_url profile_image size="xs"}}" alt="{{name}}">
{{else}}
<div class="avatar avatar--fallback">A</div>
{{/if}}
<div>
<h3>{{name}}</h3>
{{#if bio}}<p>{{bio}}</p>{{/if}}
</div>
</div>
{{/primary_author}}
</footer>
</article>
{{> "post/post-navigation"}}
<section class="discussion-panel">
{{#if comments}}
{{comments title="Comments" count=true}}
{{else}}
<div class="comments-empty">
<h3>Join the discussion</h3>
<p>Become a member of {{@site.title}} to start commenting.</p>
<a class="button" href="#/portal/signup">Sign up now</a>
<p>Already a member? <a href="#/portal/signin">Sign in</a></p>
</div>
{{/if}}
</section>
</main>
{{/post}}

15
tag.hbs Normal file
View File

@@ -0,0 +1,15 @@
{{!< default}}
{{#tag}}
<main class="content-area">
<section class="stack-section">
<header class="section-header">
<h1 class="section-title">{{name}}</h1>
{{#if description}}
<p class="section-description">{{description}}</p>
{{/if}}
</header>
{{> "lists/post-feed"}}
</section>
</main>
{{/tag}}