diff --git a/frontend/app/assets/css/main.css b/frontend/app/assets/css/main.css index 742b8bc..1f25fba 100644 --- a/frontend/app/assets/css/main.css +++ b/frontend/app/assets/css/main.css @@ -1,13 +1,20 @@ :root { - --color-text: #16181d; - --color-muted: #5b6270; - --color-bg: #ffffff; - --color-surface: #f5f6f8; - --color-border: #e2e5ea; - --color-accent: #2f4bd8; - --radius: 10px; - --width: 68rem; - --space: 1.25rem; + /* Neutral editorial palette: warm off-white paper, near-black ink. */ + --color-ink: #14130f; + --color-body: #3d3a34; + --color-muted: #857f74; + --color-bg: #fbfaf7; + --color-surface: #f1efe9; + --color-border: #e0ddd4; + --color-rule: #14130f; + + --font-display: Georgia, "Iowan Old Style", "Times New Roman", serif; + --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + + --width: 76rem; + --width-prose: 40rem; + --space: 1.5rem; + --radius: 2px; } *, @@ -18,32 +25,38 @@ body { margin: 0; - color: var(--color-text); + color: var(--color-body); background: var(--color-bg); - font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; - font-size: 1.05rem; - line-height: 1.65; + font-family: var(--font-sans); + font-size: 1rem; + line-height: 1.7; + -webkit-font-smoothing: antialiased; } img { + display: block; max-width: 100%; height: auto; } a { - color: var(--color-accent); + color: inherit; } h1, h2, h3 { - line-height: 1.25; + margin: 0; + color: var(--color-ink); + font-family: var(--font-display); + font-weight: 400; + line-height: 1.2; text-wrap: balance; } :focus-visible { - outline: 3px solid var(--color-accent); - outline-offset: 2px; + outline: 2px solid var(--color-ink); + outline-offset: 3px; } .container { @@ -64,52 +77,129 @@ h3 { z-index: 10; padding: 0.5rem 0.75rem; background: var(--color-bg); - border: 1px solid var(--color-border); - border-radius: var(--radius); + border: 1px solid var(--color-ink); } .muted { color: var(--color-muted); } +/* Small uppercase label used for categories, dates and section headings. */ +.kicker { + margin: 0; + font-family: var(--font-sans); + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--color-muted); +} + +.section-heading { + display: flex; + align-items: center; + gap: 1.25rem; + margin-block: 4rem 2rem; +} + +.section-heading::after { + content: ""; + flex: 1; + height: 1px; + background: var(--color-border); +} + +/* Centred title block at the top of a listing page. */ +.page-header { + margin-bottom: 3.5rem; + padding-bottom: 2rem; + text-align: center; + border-bottom: 1px solid var(--color-border); +} + +.page-header h1 { + font-size: clamp(2rem, 5vw, 3rem); +} + +.page-header .kicker { + margin-bottom: 0.75rem; +} + .card-grid { display: grid; - gap: 1.75rem; - grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); + gap: 3rem 2.5rem; + grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); padding: 0; margin: 0; list-style: none; } +/* Article body */ .prose { - max-width: 44rem; + max-width: var(--width-prose); + font-size: 1.075rem; +} + +.prose h2, +.prose h3 { + margin-block: 2.5rem 0.75rem; +} + +.prose h2 { + font-size: 1.6rem; +} + +.prose h3 { + font-size: 1.25rem; +} + +.prose p, +.prose ul, +.prose ol { + margin-block: 0 1.4rem; +} + +.prose a { + text-decoration: underline; + text-underline-offset: 0.2em; +} + +.prose blockquote { + margin: 2.5rem 0; + padding-left: 1.5rem; + border-left: 2px solid var(--color-ink); + font-family: var(--font-display); + font-size: 1.3rem; + line-height: 1.45; + color: var(--color-ink); } .prose img { - border-radius: var(--radius); + margin-block: 2rem; } .prose pre { overflow-x: auto; - padding: 1rem; + padding: 1rem 1.25rem; background: var(--color-surface); border-radius: var(--radius); + font-size: 0.9rem; } -.tag-list { +.prose code { + font-size: 0.9em; +} + +.pagination { display: flex; flex-wrap: wrap; - gap: 0.5rem; - padding: 0; - margin: 2rem 0 0; - list-style: none; -} - -.tag-list a { - display: inline-block; - padding: 0.15rem 0.6rem; - font-size: 0.9rem; - background: var(--color-surface); - border-radius: 999px; - text-decoration: none; + gap: 2rem; + align-items: center; + justify-content: center; + margin-top: 4rem; + padding-top: 2rem; + border-top: 1px solid var(--color-border); + font-size: 0.85rem; + letter-spacing: 0.06em; + text-transform: uppercase; } diff --git a/frontend/app/components/ArticleCard.vue b/frontend/app/components/ArticleCard.vue index 402af78..6a4bcf6 100644 --- a/frontend/app/components/ArticleCard.vue +++ b/frontend/app/components/ArticleCard.vue @@ -9,58 +9,77 @@ const cover = computed(() => mediaUrl(props.article.cover)) diff --git a/frontend/app/layouts/default.vue b/frontend/app/layouts/default.vue index 2a73339..8ccf999 100644 --- a/frontend/app/layouts/default.vue +++ b/frontend/app/layouts/default.vue @@ -12,17 +12,21 @@ const { data: categories } = await useFetch('/api/categories', {
@@ -30,9 +34,34 @@ const { data: categories } = await useFetch('/api/categories', {
@@ -44,51 +73,111 @@ const { data: categories } = await useFetch('/api/categories', { min-height: 100vh; } +/* Header: centred wordmark over a thin rule of links. */ .site-header { + padding-top: 2.5rem; + text-align: center; border-bottom: 1px solid var(--color-border); } -.header-inner { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 1rem 2rem; - padding-block: 1rem; +.wordmark { + margin: 0; + font-family: var(--font-display); + font-size: clamp(1.75rem, 5vw, 2.75rem); + letter-spacing: 0.02em; + color: var(--color-ink); } -.brand { - font-size: 1.25rem; - font-weight: 700; - color: inherit; +.wordmark a { text-decoration: none; } -nav ul { +.tagline { + margin-top: 0.6rem; +} + +.site-nav { + margin-top: 2rem; + border-top: 1px solid var(--color-border); +} + +.site-nav ul { display: flex; flex-wrap: wrap; - gap: 1rem; - padding: 0; + gap: 0.25rem 2rem; + justify-content: center; + padding-block: 0.9rem; margin: 0; list-style: none; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; } -nav a { - color: inherit; +.site-nav a { text-decoration: none; + color: var(--color-body); + padding-block: 0.25rem; + border-bottom: 1px solid transparent; } -nav a:hover, -nav a.router-link-active { - text-decoration: underline; +.site-nav a:hover, +.site-nav a.router-link-active { + color: var(--color-ink); + border-bottom-color: var(--color-ink); } .site-main { flex: 1; - padding-block: 2.5rem 4rem; + padding-block: 3.5rem 5rem; } +/* Footer: columns, then a centred copyright line. */ .site-footer { - padding-block: 1.5rem; + padding-block: 3.5rem 2rem; + background: var(--color-surface); + border-top: 1px solid var(--color-border); +} + +.footer-inner { + display: grid; + gap: 2.5rem; + grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); + padding-bottom: 2.5rem; +} + +.footer-block p:last-of-type { + margin-bottom: 0; +} + +.wordmark.small { + font-size: 1.4rem; +} + +.footer-block ul { + padding: 0; + margin: 0.9rem 0 0; + list-style: none; + font-size: 0.95rem; +} + +.footer-block li + li { + margin-top: 0.35rem; +} + +.footer-block a { + text-decoration: none; +} + +.footer-block a:hover { + text-decoration: underline; + text-underline-offset: 0.2em; +} + +.copyright { + padding-top: 1.75rem; + text-align: center; border-top: 1px solid var(--color-border); } diff --git a/frontend/app/pages/blog/[slug].vue b/frontend/app/pages/blog/[slug].vue index dfb0474..78307bd 100644 --- a/frontend/app/pages/blog/[slug].vue +++ b/frontend/app/pages/blog/[slug].vue @@ -48,19 +48,25 @@ useHead({ diff --git a/frontend/app/pages/blog/index.vue b/frontend/app/pages/blog/index.vue index 675fcc7..0061fe0 100644 --- a/frontend/app/pages/blog/index.vue +++ b/frontend/app/pages/blog/index.vue @@ -27,7 +27,10 @@ useHead({ link: [{ rel: 'canonical', href: canonical }] }) - diff --git a/frontend/app/pages/category/[slug].vue b/frontend/app/pages/category/[slug].vue index 40efffd..c8ea808 100644 --- a/frontend/app/pages/category/[slug].vue +++ b/frontend/app/pages/category/[slug].vue @@ -35,11 +35,10 @@ useHead({ link: [{ rel: 'canonical', href: canonical }] }) - diff --git a/frontend/app/pages/index.vue b/frontend/app/pages/index.vue index 61ffc99..5f4747c 100644 --- a/frontend/app/pages/index.vue +++ b/frontend/app/pages/index.vue @@ -6,7 +6,12 @@ const { data, error } = await useFetch>('/api/articles query: { page: 1 }, }) -const articles = computed(() => data.value?.items.slice(0, 6) ?? []) +/** The newest article leads the page; the next six fill the grid below it. */ +const featured = computed(() => data.value?.items[0] ?? null) +const rest = computed(() => data.value?.items.slice(1, 7) ?? []) + +const mediaUrl = useMediaUrl() +const featuredCover = computed(() => mediaUrl(featured.value?.cover)) const { public: config } = useRuntimeConfig() @@ -25,40 +30,139 @@ useSeoMeta({