diff --git a/src/styles/base.css b/src/styles/base.css new file mode 100644 index 0000000..c1defcd --- /dev/null +++ b/src/styles/base.css @@ -0,0 +1,57 @@ +/* Base layer: html/body defaults, links, focus rings, text selection. */ +@layer base { + html { + background: var(--color-bg); + scroll-behavior: smooth; + } + + body { + min-block-size: 100vh; + min-block-size: 100dvh; + display: flex; + flex-direction: column; + background: var(--color-bg); + color: var(--color-fg); + font-family: var(--font-sans); + font-size: var(--fs-body); + line-height: var(--leading-snug); + transition: + background-color 200ms ease, + color 200ms ease; + } + + address { + font-style: normal; + } + + a { + color: var(--color-link); + text-decoration-thickness: 0.08em; + text-underline-offset: 0.18em; + transition: color 150ms ease; + } + + a:hover { + color: var(--color-link-hover); + } + + a:active { + opacity: 0.85; + transition: opacity 80ms ease; + } + + :focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 3px; + } + + main:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: -2px; + } + + ::selection { + background: var(--color-selection-bg); + color: var(--color-fg); + } +} diff --git a/src/styles/components/headings-and-tags.css b/src/styles/components/headings-and-tags.css new file mode 100644 index 0000000..6e82b97 --- /dev/null +++ b/src/styles/components/headings-and-tags.css @@ -0,0 +1,160 @@ +/* Section headings, breadcrumbs, tag cloud / list / filter. */ +@layer components { + .eyebrow { + margin: 0 0 var(--space-3); + color: var(--color-muted); + font-size: var(--fs-caption); + line-height: 1.4; + font-weight: var(--weight-semibold); + letter-spacing: 0.08em; + text-transform: uppercase; + } + + .section-heading { + width: 100%; + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--space-4); + flex-wrap: wrap; + margin-bottom: var(--space-4); + padding-top: var(--space-6); + } + + :where(.section-heading, .archive-year, .project-section, .facts, .at-a-glance) h2 { + font-size: var(--fs-lg); + font-weight: var(--weight-semibold); + line-height: var(--leading-snug); + } + + .section-heading a { + min-height: 44px; + display: inline-flex; + align-items: center; + color: var(--color-muted); + font-size: var(--fs-caption); + text-decoration: none; + } + + .section-heading a:hover { + color: var(--color-fg); + text-decoration: underline; + text-underline-offset: 0.25em; + } + + .section-heading__text { + flex: 1 1 11rem; + min-width: 0; + } + + .section-heading__text p { + max-width: var(--measure); + margin-top: var(--space-1); + color: var(--color-muted); + font-size: var(--fs-caption); + line-height: 1.4; + } + + .breadcrumbs { + margin: 0 0 var(--space-3); + padding: 0; + list-style: none; + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + color: var(--color-muted); + font-size: var(--fs-caption); + } + + .breadcrumbs li { + display: inline-flex; + align-items: baseline; + min-width: 0; + } + + .breadcrumbs li:not(:last-child)::after { + content: '›'; + margin-left: var(--space-2); + color: var(--color-rule-medium); + flex: none; + } + + .breadcrumbs a { + color: var(--color-muted); + text-decoration: none; + } + + .breadcrumbs a:hover { + color: var(--color-fg); + text-decoration: underline; + text-underline-offset: 0.25em; + } + + .breadcrumbs [aria-current='page'] { + color: var(--color-fg); + } + + .tag-cloud { + margin-top: var(--space-2); + } + + .tag-list { + display: flex; + flex-wrap: wrap; + gap: var(--space-1) var(--space-2); + margin: var(--space-2) 0 0; + padding: 0; + list-style: none; + color: var(--color-muted); + font-size: var(--fs-caption); + } + + .tag-list a { + min-height: 2rem; + display: inline-flex; + align-items: center; + padding-inline: var(--space-1); + margin-inline: calc(-1 * var(--space-1)); + color: var(--color-muted); + text-decoration: none; + } + + .tag-list a::before { + content: '#'; + color: var(--color-rule-medium); + } + + .tag-list .tag-more::before { + content: none; + } + + .tag-list .tag-count { + margin-inline-start: 0.35em; + padding: 0 0.4em; + border-radius: var(--radius-pill); + background: var(--color-code-bg); + color: var(--color-fg); + font-size: var(--fs-caption); + font-variant-numeric: tabular-nums; + } + + .tag-list a:hover, + .tag-list a[aria-current='page'] { + color: var(--color-fg); + } + + .tag-filter { + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: var(--space-2) var(--space-4); + margin: 0 0 var(--space-8); + padding-block: var(--space-3); + border-block: 1px solid var(--color-rule); + } + + .tag-filter > span { + color: var(--color-muted); + font-size: var(--fs-caption); + } +} diff --git a/src/styles/components/listings.css b/src/styles/components/listings.css new file mode 100644 index 0000000..871171b --- /dev/null +++ b/src/styles/components/listings.css @@ -0,0 +1,198 @@ +/* Article list (+ timeline variant), project-list grid, entry thumbnails. */ +@layer components { + .article-list, + .project-list { + list-style: none; + margin: 0; + padding: 0; + width: 100%; + } + + .project-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); + gap: var(--space-4); + align-items: stretch; + } + + .article-list > li { + display: grid; + grid-template-columns: 4.5rem minmax(0, 1fr) minmax(6rem, 8rem); + grid-template-areas: 'date content thumb'; + align-items: center; + gap: var(--space-4); + padding-block: var(--space-6); + border-top: 1px solid var(--color-rule); + } + + .article-list > li:first-child { + border-top: 0; + } + + .article-list time { + grid-area: date; + color: var(--color-muted); + font-size: var(--fs-caption); + text-align: end; + } + + .article-list > li > article { + grid-area: content; + min-width: 0; + padding-right: var(--space-3); + } + + .article-list h3, + .project-list h3 { + font-size: var(--fs-base); + line-height: var(--leading-snug); + } + + .project-list h3 { + color: var(--color-fg); + font-weight: var(--weight-semibold); + } + + .article-list .entry-title { + display: inline-flex; + align-items: center; + min-height: 28px; + color: var(--color-fg); + font-weight: var(--weight-semibold); + text-decoration: none; + } + + .article-list .entry-title:hover { + color: var(--color-link-hover); + } + + .article-list p, + .project-list p { + margin: var(--space-1) 0 0; + color: var(--color-muted); + font-size: var(--fs-base); + line-height: var(--leading-snug); + } + + .entry-thumbnail { + display: block; + overflow: hidden; + border: 1px solid var(--color-rule); + border-radius: var(--radius-md); + background: var(--color-code-bg); + aspect-ratio: 4 / 3; + transition: border-color 150ms ease; + } + + .entry-thumbnail img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center center; + } + + a.entry-thumbnail { + text-decoration: none; + } + + a.entry-thumbnail:hover, + a.entry-thumbnail:focus-visible { + border-color: var(--color-rule-strong); + } + + .article-list > li:focus-within .entry-thumbnail { + border-color: var(--color-rule-strong); + } + + .article-thumbnail { + grid-area: thumb; + align-self: center; + } + + .article-list--timeline { + --timeline-date-column: 6.25rem; + --timeline-marker-column: 1.125rem; + --timeline-marker-offset: 0.5625rem; + --timeline-gap: var(--space-4); + --timeline-date-offset: 1.125rem; + --timeline-dot-top: 50%; + --timeline-marker-center: calc( + var(--timeline-date-column) + var(--timeline-gap) + var(--timeline-marker-offset) + ); + } + + .article-list--timeline > li { + position: relative; + grid-template-columns: + var(--timeline-date-column) var(--timeline-marker-column) minmax(0, 1fr) + minmax(6rem, 8rem); + grid-template-areas: 'date marker content thumb'; + column-gap: var(--timeline-gap); + border-top: 0; + } + + .article-list--timeline > li::before, + .article-list--timeline > li::after { + content: ''; + position: absolute; + left: var(--timeline-marker-center); + z-index: 0; + pointer-events: none; + } + + .article-list--timeline > li::before { + inset-block: 0; + width: 1px; + transform: translateX(-50%); + background: var(--color-rule-medium); + } + + .article-list--timeline > li:first-child::before { + inset-block-start: var(--timeline-dot-top); + } + + .article-list--timeline > li:last-child::before { + inset-block-end: calc(100% - var(--timeline-dot-top)); + } + + .article-list--timeline > li:only-child::before { + content: none; + } + + .article-list--timeline > li::after { + top: var(--timeline-dot-top); + width: 0.75rem; + height: 0.75rem; + border: 2px solid var(--color-bg); + border-radius: var(--radius-pill); + background: var(--color-accent); + box-shadow: 0 0 0 1px var(--color-rule-strong); + transform: translate(-50%, -50%); + } + + .article-list--timeline > li > article, + .article-list--timeline time, + .article-list--timeline .entry-thumbnail { + position: relative; + z-index: 1; + } + + .article-list--timeline time { + justify-self: end; + align-self: center; + width: max-content; + min-block-size: 0; + margin-inline-end: calc(-1 * var(--timeline-date-offset)); + padding-inline-end: 4px; + display: inline-block; + color: var(--color-muted); + font-size: var(--fs-sm); + line-height: 1.2; + text-align: center; + white-space: nowrap; + top: 5px; + transform: rotate(-45deg); + transform-origin: right center; + } +} diff --git a/src/styles/components/post.css b/src/styles/components/post.css new file mode 100644 index 0000000..56e9bdb --- /dev/null +++ b/src/styles/components/post.css @@ -0,0 +1,212 @@ +/* At-a-glance, facts, media figures, post nav, TOC, gallery, related posts. */ +@layer components { + .at-a-glance { + margin-top: var(--space-8); + padding-block: var(--space-5); + border-block: 1px solid var(--color-rule); + } + + .at-a-glance dl, + .facts dl { + display: flex; + flex-direction: column; + gap: var(--space-2); + margin: var(--space-4) 0 0; + } + + .at-a-glance__row, + .facts dl > div { + display: grid; + grid-template-columns: minmax(6rem, max-content) minmax(0, 1fr); + gap: var(--space-4); + } + + .at-a-glance dt, + .facts dt { + color: var(--color-muted); + font-size: var(--fs-caption); + } + + .facts { + margin-top: var(--space-8); + padding-top: var(--space-6); + border-top: 1px solid var(--color-rule); + } + + /* Let prose wrap beside .at-a-glance and continue below it. */ + @media (min-width: 1100px) { + .post > .at-a-glance { + float: right; + width: min(18rem, 42%); + margin-top: var(--space-8); + margin-right: 0; + margin-bottom: var(--space-4); + margin-left: var(--space-8); + } + + .post > .post-media, + .post > .post-gallery, + .related-posts, + .post-nav { + clear: both; + } + } + + .post-media { + max-inline-size: min(100%, var(--measure-wide)); + margin: var(--space-8) 0 0; + } + + .post-media img, + .post-media video { + border: 1px solid var(--color-rule); + border-radius: var(--radius-md); + background: var(--color-code-bg); + width: 100%; + } + + .post-media figcaption, + .media-transcript { + max-width: var(--measure); + margin-top: var(--space-2); + color: var(--color-muted); + font-size: var(--fs-caption); + line-height: 1.45; + } + + .media-transcript strong { + color: var(--color-fg); + font-weight: var(--weight-semibold); + } + + .post-nav { + margin-top: var(--space-12); + padding-top: var(--space-6); + border-top: 1px solid var(--color-rule); + } + + .post-nav__list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); + gap: var(--space-4); + list-style: none; + padding: 0; + margin: 0; + } + + .post-nav__next { + justify-self: end; + } + + .post-nav a { + display: flex; + flex-direction: column; + gap: var(--space-1); + padding: var(--space-3) var(--space-4); + border: 1px solid var(--color-rule); + border-radius: var(--radius-md); + color: var(--color-fg); + text-decoration: none; + min-height: 44px; + transition: border-color 150ms ease; + } + + .post-nav a:hover, + .post-nav a:focus-visible { + border-color: var(--color-rule-strong); + } + + .post-nav a.next { + text-align: end; + } + + .post-nav .post-nav__label { + color: var(--color-muted); + font-size: var(--fs-caption); + } + + .post-nav .post-nav__title { + color: var(--color-fg); + font-weight: var(--weight-semibold); + } + + .post-toc { + margin-top: var(--space-6); + padding: var(--space-3) var(--space-4); + border-inline-start: 2px solid var(--color-rule); + font-size: var(--fs-caption); + color: var(--color-muted); + max-height: 60vh; + overflow-y: auto; + } + + .post-header h1, + .post-nav .post-nav__title, + .project-card h3 { + overflow-wrap: anywhere; + } + + .post-toc ol { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: var(--space-1); + } + + .post-toc a { + display: inline-flex; + align-items: center; + min-block-size: 24px; + padding-block: 2px; + color: var(--color-muted); + text-decoration: none; + } + + .post-toc a:hover, + .post-toc a:focus-visible { + color: var(--color-fg); + } + + .post-gallery { + list-style: none; + padding: 0; + margin: var(--space-8) 0 0; + display: grid; + gap: var(--space-6); + } + + .post > .post-gallery { + width: 100%; + } + + .post-gallery .post-media { + max-inline-size: 100%; + margin: 0; + } + + .external-link-icon { + display: inline-block; + margin-inline-start: 0.25em; + vertical-align: -0.125em; + opacity: 0.85; + } + + .related-posts { + margin-top: var(--space-12); + padding-top: var(--space-6); + border-top: 1px solid var(--color-rule); + } + + .related-posts h2 { + font-size: var(--fs-lg); + font-weight: var(--weight-semibold); + margin-bottom: var(--space-4); + } + + .empty-state { + max-width: var(--measure); + padding-block: var(--space-6); + } +} diff --git a/src/styles/components/project-card.css b/src/styles/components/project-card.css new file mode 100644 index 0000000..975666d --- /dev/null +++ b/src/styles/components/project-card.css @@ -0,0 +1,155 @@ +/* Project card: thumbnail, summary, stretched-link Open control, links. */ +@layer components { + .project-card { + position: relative; + display: grid; + grid-template-columns: 1fr; + grid-template-areas: + 'thumb' + 'summary'; + min-width: 0; + overflow: hidden; + border: 1px solid var(--color-rule); + border-radius: var(--radius-lg); + background: var(--color-bg); + transition: border-color 150ms ease; + } + + .project-card:hover, + .project-card:focus-within, + .project-card:target { + border-color: var(--color-rule-strong); + } + + .project-card .project-thumbnail { + grid-area: thumb; + width: 100%; + height: auto; + border: 0; + border-bottom: 1px solid var(--color-rule); + border-radius: 0; + aspect-ratio: 4 / 3; + } + + .project-card__summary { + grid-area: summary; + display: flex; + flex-direction: column; + justify-content: space-between; + gap: var(--space-3); + min-width: 0; + padding: var(--space-3) var(--space-4); + } + + .project-card p { + margin: 0; + } + + .project-card .project-description { + color: var(--color-fg); + font-size: var(--fs-base); + line-height: var(--leading-snug); + } + + .project-card__head { + display: flex; + flex-direction: column; + gap: var(--space-1); + min-width: 0; + } + + .project-card__actions { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: var(--space-2) var(--space-3); + margin-top: var(--space-3); + } + + /* Stretched link: the Open control's overlay covers the whole card so any + click opens the project website. The Article link sits above the overlay + via a higher z-index and stays independently clickable. */ + .project-card__open::after { + content: ''; + position: absolute; + inset: 0; + z-index: 1; + /* Cover the whole card, not the Open button's pill radius. The card's own + overflow:hidden + border-radius clips this to the rounded card shape. */ + border-radius: 0; + } + + .project-card__open { + margin-left: auto; + display: inline-flex; + align-items: center; + gap: 0.3em; + min-height: 36px; + padding: 0.25em 0.85em; + border: 1px solid var(--color-rule-strong); + border-radius: var(--radius-pill); + background: var(--color-bg); + color: var(--color-link); + font-size: var(--fs-caption); + font-weight: var(--weight-semibold); + text-decoration: none; + transition: + background-color 150ms ease, + border-color 150ms ease, + color 150ms ease; + } + + .project-card:hover .project-card__open, + .project-card:focus-within .project-card__open { + border-color: var(--color-link); + background: var(--color-callout-bg); + color: var(--color-link-hover); + } + + .project-article-link { + position: relative; + z-index: 2; + display: inline-flex; + align-items: center; + gap: 0.3em; + min-height: 36px; + color: var(--color-link); + font-size: var(--fs-caption); + font-weight: var(--weight-medium); + text-decoration: none; + } + + .project-article-link:hover, + .project-article-link:focus-visible { + color: var(--color-link-hover); + text-decoration: underline; + } + + .project-links { + display: flex; + flex-wrap: wrap; + gap: var(--space-1) var(--space-3); + margin: var(--space-3) 0 0; + padding: 0; + list-style: none; + } + + .project-links a { + min-height: 44px; + min-width: 44px; + display: inline-flex; + align-items: center; + color: var(--color-link); + } + + .project-links a:hover, + .project-links a:focus-visible { + color: var(--color-link-hover); + } + + .project-links a .download-indicator { + margin-left: 0.25em; + color: var(--color-muted); + font-size: 0.85em; + } +} diff --git a/src/styles/components/prose.css b/src/styles/components/prose.css new file mode 100644 index 0000000..2def8bb --- /dev/null +++ b/src/styles/components/prose.css @@ -0,0 +1,196 @@ +/* Long-form article prose: headings, lists, quotes, tables, code (Shiki). */ +@layer components { + .prose { + max-inline-size: var(--measure); + line-height: var(--leading-prose); + hyphens: auto; + hyphenate-limit-chars: 7 3 3; + } + + .prose > * + * { + margin-top: 1.05em; + } + + .prose > h2:first-child, + .prose > h3:first-child { + margin-top: 0; + } + + .prose p { + text-wrap: pretty; + } + + .prose h2, + .prose h3 { + position: relative; + color: var(--color-fg); + line-height: var(--leading-tight); + scroll-margin-top: var(--space-8); + } + + .prose h2 { + margin-top: var(--space-12); + font-size: var(--fs-xl); + font-weight: var(--weight-semibold); + } + + .prose h3 { + margin-top: var(--space-8); + font-size: var(--fs-lg); + font-weight: var(--weight-semibold); + } + + .prose h2:target, + .prose h3:target { + background: var(--color-callout-bg); + border-inline-start: 2px solid var(--color-accent); + padding-inline-start: var(--space-3); + margin-inline-start: calc(-1 * var(--space-3) - 2px); + } + + .prose .heading-anchor { + margin-inline-start: 0.4em; + color: var(--color-muted); + font-weight: var(--weight-regular); + font-size: 0.85em; + text-decoration: none; + opacity: 0; + transition: opacity 150ms ease; + } + + .prose .heading-anchor:focus-visible { + opacity: 1; + text-decoration: underline; + } + + .prose .heading-anchor::before { + content: '#'; + } + + .prose h2:hover .heading-anchor, + .prose h3:hover .heading-anchor, + .prose .heading-anchor:hover { + opacity: 1; + } + + @media (hover: none) { + .prose .heading-anchor { + opacity: 1; + } + } + + .prose ul, + .prose ol { + padding-inline-start: 1.25rem; + } + + .prose li + li { + margin-top: var(--space-1); + } + + .prose strong { + font-weight: var(--weight-bold); + color: var(--color-fg); + } + + .prose em { + font-style: italic; + } + + .prose blockquote { + margin-inline: 0; + padding: var(--space-2) var(--space-4); + border-inline-start: 3px solid var(--color-rule-medium); + background: var(--color-callout-bg); + color: var(--color-muted); + font-style: italic; + } + + .prose blockquote > * + * { + margin-top: 0.6em; + } + + .prose hr { + margin-block: var(--space-8); + border: 0; + height: 1px; + background: var(--color-rule); + } + + .prose table { + width: 100%; + max-width: 100%; + border-collapse: collapse; + font-size: 0.95em; + display: block; + overflow-x: auto; + } + + .prose thead { + background: var(--color-code-bg); + text-align: start; + } + + .prose th, + .prose td { + padding: var(--space-2) var(--space-3); + border-bottom: 1px solid var(--color-rule); + vertical-align: top; + } + + .prose th { + font-weight: var(--weight-semibold); + } + + .prose code { + font-family: var(--font-mono); + font-size: 0.88em; + background: var(--color-code-bg); + border-radius: var(--radius-sm); + padding: 0.08em 0.25em; + } + + .prose pre { + max-width: 100%; + overflow-x: auto; + scrollbar-gutter: stable; + padding: var(--space-4); + background: var(--color-code-bg); + border: 1px solid var(--color-rule); + border-radius: var(--radius-md); + line-height: 1.55; + scrollbar-width: thin; + scrollbar-color: var(--color-rule-medium) var(--color-code-bg); + } + + .prose pre::-webkit-scrollbar { + height: 8px; + } + + .prose pre::-webkit-scrollbar-track { + background: var(--color-code-bg); + } + + .prose pre::-webkit-scrollbar-thumb { + background: var(--color-rule-medium); + border-radius: var(--radius-sm); + } + + .prose pre code { + background: transparent; + padding: 0; + } + + /* Shiki dual-theme: defaultColor: false emits --shiki-light / --shiki-dark + vars on every token; light-dark() picks the active variant from + color-scheme on :root. */ + .prose pre.astro-code, + .prose pre.astro-code code, + .prose pre.astro-code span { + color: light-dark(var(--shiki-light), var(--shiki-dark)); + background-color: light-dark( + var(--shiki-light-bg, var(--color-code-bg)), + var(--shiki-dark-bg, var(--color-code-bg)) + ); + } +} diff --git a/src/styles/components/sections.css b/src/styles/components/sections.css new file mode 100644 index 0000000..398ab95 --- /dev/null +++ b/src/styles/components/sections.css @@ -0,0 +1,125 @@ +/* Post-content width wrappers, archive/project sections, about, starting points. */ +@layer components { + .post > .post-media { + max-width: var(--measure); + margin-inline: auto; + } + + .post > .prose { + max-width: var(--measure-wide); + max-inline-size: var(--measure-wide); + margin-inline: auto; + } + + .post > .at-a-glance, + .post > .post-thumbnail, + .post > .post-gallery, + .post-nav { + max-width: var(--measure-wide); + margin-inline: auto; + } + + .archive-year, + .project-section { + width: 100%; + } + + .archive-year + .archive-year, + .project-section + .project-section { + margin-top: var(--space-10); + } + + .archive-year h2, + .project-section h2 { + margin-bottom: var(--space-3); + } + + .about-section { + width: 100%; + margin-top: var(--space-10); + } + + .about-section--starting-points { + margin-top: var(--space-6); + padding-top: var(--space-6); + border-top: 1px solid var(--color-rule); + } + + .about-section--starting-points .section-heading { + align-items: flex-start; + padding-top: 0; + } + + .about-section.facts { + max-width: none; + } + + .about-section.facts > .prose { + margin-top: var(--space-4); + } + + .prose.about-copy { + width: 100%; + max-inline-size: none; + } + + .starting-points { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: var(--space-3); + margin: 0; + padding: var(--space-4) 0 0; + list-style: none; + } + + .starting-points > li { + min-width: 0; + } + + .starting-point__thumbnail { + aspect-ratio: 4 / 3; + } + + .starting-point__body { + min-width: 0; + padding-block-start: var(--space-2); + } + + .starting-point__body h3 { + font-size: var(--fs-sm); + font-weight: var(--weight-semibold); + line-height: var(--leading-snug); + } + + .starting-point__body a { + color: var(--color-fg); + text-decoration: none; + } + + .starting-point__body a:hover { + color: var(--color-link-hover); + text-decoration: underline; + text-underline-offset: 0.2em; + } + + .starting-point__body p { + margin-top: var(--space-1); + color: var(--color-muted); + font-size: var(--fs-xs); + line-height: var(--leading-snug); + } + + .starting-points > li:focus-within .entry-thumbnail { + border-color: var(--color-rule-strong); + } + + .about-links { + display: flex; + flex-wrap: wrap; + gap: var(--space-1) var(--space-4); + } + + .post > .prose { + margin-top: var(--space-8); + } +} diff --git a/src/styles/components/theme-switcher.css b/src/styles/components/theme-switcher.css new file mode 100644 index 0000000..51add57 --- /dev/null +++ b/src/styles/components/theme-switcher.css @@ -0,0 +1,118 @@ +/* Light/dark theme toggle switch + forced-colors fallback. */ +@layer components { + .theme-switcher { + --switcher-w: 2.75rem; + --switcher-h: 1.5rem; + --switcher-icon: 1.05rem; + --switcher-gap: 0.22rem; + + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--switcher-w); + height: var(--switcher-h); + /* Adjacent header targets remain at least 44px apart while the visual + track stays compact. */ + margin: max(var(--space-2), calc((44px - var(--switcher-h)) / 2)) 0; + overflow: hidden; + border: 1px solid var(--color-rule-medium); + border-radius: var(--radius-pill); + appearance: none; + cursor: pointer; + background: var(--theme-switcher-track); + color: inherit; + transition: + background-color 200ms ease, + border-color 150ms ease; + box-shadow: inset 0 1px 2px rgb(0 0 0 / 18%); + } + + .theme-switcher:hover { + border-color: var(--color-rule-strong); + } + + .no-js .theme-switcher { + display: none !important; + } + + .theme-switcher-icon { + position: absolute; + top: 50%; + left: 0; + width: var(--switcher-icon); + height: var(--switcher-icon); + pointer-events: none; + transition: + transform 180ms ease, + opacity 180ms ease, + color 180ms ease; + } + + .theme-switcher-icon-sun { + color: var(--theme-switcher-icon-light); + } + + .theme-switcher-icon-moon { + color: var(--theme-switcher-icon-dark); + } + + .theme-switcher[aria-pressed='false'] .theme-switcher-icon-sun { + transform: translateY(-50%) + translateX(calc(var(--switcher-w) - var(--switcher-icon) - var(--switcher-gap))); + opacity: 1; + } + + .theme-switcher[aria-pressed='false'] .theme-switcher-icon-moon { + transform: translateY(-50%) translateX(calc(-1 * var(--switcher-icon))); + opacity: 0; + } + + .theme-switcher[aria-pressed='true'] .theme-switcher-icon-sun { + transform: translateY(-50%) translateX(var(--switcher-w)); + opacity: 0; + } + + .theme-switcher[aria-pressed='true'] .theme-switcher-icon-moon { + transform: translateY(-50%) translateX(var(--switcher-gap)); + opacity: 1; + } + + /* High-contrast / forced-colors fallback: render a text label. */ + @media (forced-colors: active) { + .theme-switcher { + width: auto; + height: auto; + min-block-size: 44px; + min-inline-size: 44px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: var(--space-1) var(--space-2); + overflow: visible; + background: ButtonFace; + color: ButtonText; + border: 1px solid ButtonBorder; + box-shadow: none; + } + + .theme-switcher-icon { + display: none; + } + + .theme-switcher::before { + content: 'Light'; + position: static; + width: auto; + height: auto; + transform: none; + background: transparent; + border-radius: 0; + } + + .theme-switcher[aria-pressed='true']::before { + content: 'Dark'; + transform: none; + } + } +} diff --git a/src/styles/components/thumbnails.css b/src/styles/components/thumbnails.css new file mode 100644 index 0000000..a5b59f7 --- /dev/null +++ b/src/styles/components/thumbnails.css @@ -0,0 +1,243 @@ +/* Post thumbnail (iframe embed) and click-to-play video thumbnail. */ +@layer components { + .post-thumbnail { + margin: 0; + } + + .post-thumbnail img { + width: 100%; + border: 1px solid var(--color-rule); + border-radius: var(--radius-md); + background: var(--color-code-bg); + } + + .post-thumbnail--iframe { + position: relative; + aspect-ratio: var(--post-thumbnail-aspect, 16 / 9); + overflow: hidden; + border: 1px solid var(--color-rule); + border-radius: var(--radius-md); + background: var(--color-code-bg); + } + + .post-thumbnail--iframe picture, + .post-thumbnail--iframe img { + width: 100%; + height: 100%; + } + + .post-thumbnail--iframe picture { + display: block; + } + + .post-thumbnail--iframe img { + object-fit: cover; + object-position: center center; + border: 0; + border-radius: 0; + } + + .post-thumbnail__play { + position: absolute; + inset: 0; + width: 100%; + border: 0; + padding: 0; + display: grid; + place-items: center; + background: color-mix(in oklch, #000 22%, transparent); + color: var(--color-fg); + cursor: pointer; + } + + .post-thumbnail__play:hover, + .post-thumbnail__play:focus-visible { + background: color-mix(in oklch, #000 30%, transparent); + } + + .post-thumbnail__play-icon { + width: clamp(3.25rem, 9vw, 4.75rem); + aspect-ratio: 1; + display: grid; + place-items: center; + border: 1px solid var(--color-rule-strong); + border-radius: var(--radius-pill); + background: color-mix(in oklch, var(--color-bg) 88%, transparent); + box-shadow: 0 0.75rem 2rem color-mix(in oklch, #000 28%, transparent); + transition: + background-color 150ms ease, + transform 150ms ease; + } + + .post-thumbnail__play:hover .post-thumbnail__play-icon, + .post-thumbnail__play:focus-visible .post-thumbnail__play-icon { + background: var(--color-bg); + transform: scale(1.04); + } + + .post-thumbnail__play svg { + width: 42%; + height: 42%; + transform: translateX(8%); + fill: currentColor; + } + + .post-thumbnail__iframe { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border: 0; + background: var(--color-code-bg); + } + + .post-thumbnail--iframe.is-active picture, + .post-thumbnail--iframe.is-active .post-thumbnail__play { + display: none; + } + + .post-thumbnail__noscript { + position: absolute; + inset-inline: var(--space-3); + inset-block-end: var(--space-3); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); + background: var(--color-bg); + font-size: var(--fs-caption); + } + + /* Click-to-play header video: a poster with a play button that swaps in an + inline