From f27e9ec3fd7cd8ad48d08b34bd0d7e982cdb6255 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Mon, 25 May 2026 13:12:10 +0100 Subject: [PATCH] Build the Astro site UI --- src/components/ArticleList.astro | 53 + src/components/AtAGlance.astro | 50 + src/components/Breadcrumbs.astro | 33 + src/components/EntryThumbnail.astro | 56 + src/components/Footer.astro | 32 + src/components/Header.astro | 128 ++ src/components/PostMedia.astro | 30 + src/components/PostMediaFigure.astro | 80 + src/components/ProjectLinks.astro | 64 + src/components/ProjectList.astro | 69 + src/components/TagList.astro | 40 + src/helper/accessibility.ts | 18 - src/helper/get-height.ts | 12 - src/helper/mix.ts | 2 - src/helper/random.ts | 19 - src/helper/scroll-to-fragment.ts | 6 - src/helper/sum.ts | 3 - src/helper/title-to-fragment.ts | 5 - src/index.html | 37 - src/index.scss | 105 -- src/index.ts | 33 - src/layouts/Base.astro | 178 ++ src/layouts/Page.astro | 22 + src/layouts/Post.astro | 207 +++ src/lib/site.ts | 235 +++ src/page/contact/contact.html.ts | 33 - src/page/contact/contact.scss | 30 - .../figure/bordered-image/bordered-image.scss | 0 .../figure/bordered-image/bordered-image.ts | 23 - src/page/figure/figure.html.ts | 21 - src/page/figure/figure.scss | 33 - src/page/figure/figure.ts | 21 - src/page/figure/preview/preview.html.ts | 21 - src/page/figure/preview/preview.scss | 38 - src/page/figure/preview/preview.ts | 32 - src/page/figure/video/video-parameters.ts | 10 - src/page/figure/video/video.html.ts | 14 - src/page/figure/video/video.scss | 16 - src/page/figure/video/video.ts | 19 - src/page/header/header.html.ts | 21 - src/page/header/header.scss | 95 - src/page/header/header.ts | 42 - .../theme-switcher/theme-switcher.html.ts | 6 - .../header/theme-switcher/theme-switcher.scss | 92 - .../header/theme-switcher/theme-switcher.ts | 62 - src/page/image-anchor/image-anchor.html.ts | 16 - src/page/image-anchor/image-anchor.scss | 20 - src/page/image-button/image-button.html.ts | 23 - src/page/image-button/image-button.scss | 25 - src/page/image-viewer/image-viewer.html.ts | 10 - src/page/image-viewer/image-viewer.scss | 37 - src/page/image-viewer/image-viewer.ts | 29 - src/page/image/image.html.ts | 29 - src/page/image/image.scss | 10 - src/page/link/link.html.ts | 5 - src/page/link/link.scss | 5 - src/page/main/main.html.ts | 6 - src/page/main/main.scss | 50 - src/page/main/main.ts | 113 -- src/page/page-element.ts | 43 - .../timeline-element-parameters.ts | 11 - .../timeline-element/timeline-element.html.ts | 42 - .../timeline-element/timeline-element.scss | 166 -- src/page/timeline-element/timeline-element.ts | 88 - .../up-arrow-button/up-arrow-button.html.ts | 9 - src/page/up-arrow-button/up-arrow-button.scss | 33 - src/page/up-arrow-button/up-arrow-button.ts | 57 - src/pages/404.astro | 33 + src/pages/about.astro | 120 ++ src/pages/articles/[slug].astro | 16 + src/pages/articles/index.astro | 74 + src/pages/index.astro | 69 + src/pages/projects/index.astro | 42 + src/pages/rss.xml.ts | 103 + src/pages/tags/[tag].astro | 49 + src/pages/tags/index.astro | 45 + src/scripts/theme-init.js | 26 + src/style/fonts.scss | 25 - src/style/mixins.scss | 168 -- src/style/vars.scss | 44 - src/styles/global.css | 1656 +++++++++++++++++ src/types/html.ts | 1 - src/types/responsive-image.ts | 14 - src/types/url.ts | 1 - 84 files changed, 3510 insertions(+), 1949 deletions(-) create mode 100644 src/components/ArticleList.astro create mode 100644 src/components/AtAGlance.astro create mode 100644 src/components/Breadcrumbs.astro create mode 100644 src/components/EntryThumbnail.astro create mode 100644 src/components/Footer.astro create mode 100644 src/components/Header.astro create mode 100644 src/components/PostMedia.astro create mode 100644 src/components/PostMediaFigure.astro create mode 100644 src/components/ProjectLinks.astro create mode 100644 src/components/ProjectList.astro create mode 100644 src/components/TagList.astro delete mode 100644 src/helper/accessibility.ts delete mode 100644 src/helper/get-height.ts delete mode 100644 src/helper/mix.ts delete mode 100644 src/helper/random.ts delete mode 100644 src/helper/scroll-to-fragment.ts delete mode 100644 src/helper/sum.ts delete mode 100644 src/helper/title-to-fragment.ts delete mode 100644 src/index.html delete mode 100644 src/index.scss delete mode 100644 src/index.ts create mode 100644 src/layouts/Base.astro create mode 100644 src/layouts/Page.astro create mode 100644 src/layouts/Post.astro create mode 100644 src/lib/site.ts delete mode 100644 src/page/contact/contact.html.ts delete mode 100644 src/page/contact/contact.scss delete mode 100644 src/page/figure/bordered-image/bordered-image.scss delete mode 100644 src/page/figure/bordered-image/bordered-image.ts delete mode 100644 src/page/figure/figure.html.ts delete mode 100644 src/page/figure/figure.scss delete mode 100644 src/page/figure/figure.ts delete mode 100644 src/page/figure/preview/preview.html.ts delete mode 100644 src/page/figure/preview/preview.scss delete mode 100644 src/page/figure/preview/preview.ts delete mode 100644 src/page/figure/video/video-parameters.ts delete mode 100644 src/page/figure/video/video.html.ts delete mode 100644 src/page/figure/video/video.scss delete mode 100644 src/page/figure/video/video.ts delete mode 100644 src/page/header/header.html.ts delete mode 100644 src/page/header/header.scss delete mode 100644 src/page/header/header.ts delete mode 100644 src/page/header/theme-switcher/theme-switcher.html.ts delete mode 100644 src/page/header/theme-switcher/theme-switcher.scss delete mode 100644 src/page/header/theme-switcher/theme-switcher.ts delete mode 100644 src/page/image-anchor/image-anchor.html.ts delete mode 100644 src/page/image-anchor/image-anchor.scss delete mode 100644 src/page/image-button/image-button.html.ts delete mode 100644 src/page/image-button/image-button.scss delete mode 100644 src/page/image-viewer/image-viewer.html.ts delete mode 100644 src/page/image-viewer/image-viewer.scss delete mode 100644 src/page/image-viewer/image-viewer.ts delete mode 100644 src/page/image/image.html.ts delete mode 100644 src/page/image/image.scss delete mode 100644 src/page/link/link.html.ts delete mode 100644 src/page/link/link.scss delete mode 100644 src/page/main/main.html.ts delete mode 100644 src/page/main/main.scss delete mode 100644 src/page/main/main.ts delete mode 100644 src/page/page-element.ts delete mode 100644 src/page/timeline-element/timeline-element-parameters.ts delete mode 100644 src/page/timeline-element/timeline-element.html.ts delete mode 100644 src/page/timeline-element/timeline-element.scss delete mode 100644 src/page/timeline-element/timeline-element.ts delete mode 100644 src/page/up-arrow-button/up-arrow-button.html.ts delete mode 100644 src/page/up-arrow-button/up-arrow-button.scss delete mode 100644 src/page/up-arrow-button/up-arrow-button.ts create mode 100644 src/pages/404.astro create mode 100644 src/pages/about.astro create mode 100644 src/pages/articles/[slug].astro create mode 100644 src/pages/articles/index.astro create mode 100644 src/pages/index.astro create mode 100644 src/pages/projects/index.astro create mode 100644 src/pages/rss.xml.ts create mode 100644 src/pages/tags/[tag].astro create mode 100644 src/pages/tags/index.astro create mode 100644 src/scripts/theme-init.js delete mode 100644 src/style/fonts.scss delete mode 100644 src/style/mixins.scss delete mode 100644 src/style/vars.scss create mode 100644 src/styles/global.css delete mode 100644 src/types/html.ts delete mode 100644 src/types/responsive-image.ts delete mode 100644 src/types/url.ts diff --git a/src/components/ArticleList.astro b/src/components/ArticleList.astro new file mode 100644 index 0000000..791686e --- /dev/null +++ b/src/components/ArticleList.astro @@ -0,0 +1,53 @@ +--- +import type { CollectionEntry } from 'astro:content'; +import EntryThumbnail from './EntryThumbnail.astro'; +import TagList from './TagList.astro'; +import { ARTICLE_THUMBNAIL, articlePath, formatDate, formatDateShort } from '../lib/site'; + +interface Props { + posts: CollectionEntry<'posts'>[]; + showYear?: boolean; + tagLimit?: number; + // Opt-in: eagerly load the first thumbnail. Only set when the list is + // reliably above the fold (home, tag pages). Lists below substantial + // content (related, archives by year, 404) should leave this off. + eagerFirstThumbnail?: boolean; +} + +const { posts, showYear = true, tagLimit = 3, eagerFirstThumbnail = false } = Astro.props; +--- + +
    + { + posts.map((post, index) => { + const href = articlePath(post); + return ( +
  1. + + + +
  2. + ); + }) + } +
diff --git a/src/components/AtAGlance.astro b/src/components/AtAGlance.astro new file mode 100644 index 0000000..c5417ea --- /dev/null +++ b/src/components/AtAGlance.astro @@ -0,0 +1,50 @@ +--- +import type { CollectionEntry } from 'astro:content'; +import ProjectLinks from './ProjectLinks.astro'; + +type Link = CollectionEntry<'projects'>['data']['links'][number]; + +interface Props { + role?: string; + projectPeriod?: string; + stack?: string[]; + scale?: string; + outcome?: string; + links?: Link[]; + headingId: string; +} + +const { + role, + projectPeriod, + stack = [], + scale, + outcome, + links = [], + headingId, +} = Astro.props; + +const rows: Array<[string, string]> = []; +if (role) rows.push(['Role', role]); +if (projectPeriod) rows.push(['Period', projectPeriod]); +if (stack.length > 0) rows.push(['Stack', stack.join(', ')]); +if (scale) rows.push(['Scale', scale]); +if (outcome) rows.push(['Outcome', outcome]); +--- + +{ + rows.length > 0 && ( + + ) +} diff --git a/src/components/Breadcrumbs.astro b/src/components/Breadcrumbs.astro new file mode 100644 index 0000000..ba039af --- /dev/null +++ b/src/components/Breadcrumbs.astro @@ -0,0 +1,33 @@ +--- +interface Crumb { + href?: string; + label: string; +} + +interface Props { + items: Crumb[]; +} + +const { items } = Astro.props; +const lastIndex = items.length - 1; +--- + + diff --git a/src/components/EntryThumbnail.astro b/src/components/EntryThumbnail.astro new file mode 100644 index 0000000..90eae9e --- /dev/null +++ b/src/components/EntryThumbnail.astro @@ -0,0 +1,56 @@ +--- +import type { ImageMetadata } from 'astro'; +import { Picture } from 'astro:assets'; + +interface Props { + src: ImageMetadata; + alt: string; + href?: string; + class?: string; + widths: number[]; + sizes: string; + loading?: 'lazy' | 'eager'; + fetchpriority?: 'high' | 'low' | 'auto'; + ariaLabel?: string; + // When the listing already has a focusable, screen-reader-visible title + // link, the thumbnail link is visually duplicative. We keep it clickable + // for pointer users but drop it from the tab order. The link still needs + // a name because some assistive tech exposes non-tabbable links. + decorative?: boolean; +} + +const { + src, + alt, + href, + class: extraClass, + widths, + sizes, + loading = 'lazy', + fetchpriority, + ariaLabel, + decorative = true, +} = Astro.props; + +const Tag = href ? 'a' : 'div'; +const isDecorativeLink = Boolean(href) && decorative; +--- + + + + diff --git a/src/components/Footer.astro b/src/components/Footer.astro new file mode 100644 index 0000000..45f2c8f --- /dev/null +++ b/src/components/Footer.astro @@ -0,0 +1,32 @@ +--- +import { navItems, site } from '../lib/site'; + +const year = new Date().getFullYear(); + +// Footer shows all nav items except Home (which is implicit via the site title). +const footerNavItems = navItems.filter((item) => item.href !== '/'); +--- + + diff --git a/src/components/Header.astro b/src/components/Header.astro new file mode 100644 index 0000000..115432c --- /dev/null +++ b/src/components/Header.astro @@ -0,0 +1,128 @@ +--- +import { navItems, site } from '../lib/site'; + +const currentPath = Astro.url.pathname; +const current = + currentPath === '/' || currentPath.endsWith('/') || /\.[^/]+$/.test(currentPath) + ? currentPath + : `${currentPath}/`; + +// Exact match for the current page; section match (descendant URLs) for +// ancestor links. `aria-current="page"` is reserved for the exact page, +// `"true"` indicates an ancestor section. +function currentState(href: string): 'page' | 'true' | undefined { + if (current === href) return 'page'; + if (href !== '/' && current.startsWith(href)) return 'true'; + return undefined; +} + +// Header shows nav items except Home and footer-only entries. RSS lives as a +// dedicated icon link to the right of the nav. +const headerNavItems = navItems.filter((item) => item.href !== '/' && !item.footerOnly); +--- + + + + + + + diff --git a/src/components/PostMedia.astro b/src/components/PostMedia.astro new file mode 100644 index 0000000..2a5f425 --- /dev/null +++ b/src/components/PostMedia.astro @@ -0,0 +1,30 @@ +--- +import type { CollectionEntry } from 'astro:content'; +import PostMediaFigure from './PostMediaFigure.astro'; + +type MediaItem = CollectionEntry<'posts'>['data']['media'][number]; + +interface Props { + items: MediaItem[]; +} + +const { items } = Astro.props; + +// Wrap in a gallery `