Skip to content

SEO overhaul, motion system and UI polish - #32

Merged
NotAwar merged 44 commits into
mainfrom
improve/seo-and-site-fixes
Oct 16, 2026
Merged

NotAwar merged 44 commits into
mainfrom
improve/seo-and-site-fixes

Conversation

@NotAwar

@NotAwar NotAwar commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Continuous site improvements focused on search visibility, then motion and UI polish. main requires signed commits and PRs, so work lands here for review. The branch commits are unsigned; the squash merge produces a signed commit on main.

SEO

  • Deployment fix: moved robots.txt, site.webmanifest and CNAME to public/. Before this they were never deployed.
  • Metadata: canonical URLs, robots directives, keyword-focused titles and descriptions, noindex on the 404 page, a /resume → /cv/ redirect and a filtered sitemap.
  • Structured data: JSON-LD for WebSite, Person (with sameAs), ProfilePage, BreadcrumbList, one Event per conference, and credentials for certifications.
  • Crawlable content: the talks, events, certifications, About section and hero tagline are now rendered at build time instead of by JS embeds. Also added /llms.txt.
  • Share images: a generated 1200×630 OG image plus icons (npm run generate-images). Speaking, CV and Socials each get their own share card with matching alt text, and check:seo fails if a page's og:image is missing.
  • Performance and accessibility: Orbitron is self-hosted via the Astro Fonts API. Added a skip link, a current-page nav state and visible focus styles.
  • CI: pull requests are built, and npm run check:seo checks the SEO essentials.

Motion and UI

Animations only touch transform/opacity where possible. All content is visible without JS, and everything respects prefers-reduced-motion.

  • Scroll-reveal with staggered entrances and a choreographed hero entrance.
  • CSS scroll-driven effects:
    • reading progress bar
    • header settle
    • hero exit
    • sprite parallax
    • CV timeline line drawing as you read
    • a progress ring on the new back-to-top button
  • Cross-document view transitions with a sliding nav pill, and a circular reveal when switching theme. Pages slide in the direction of travel through the nav order (forward or back, including history navigation).
  • Scroll-linked text and band: the About lede lights up word by word as you read, and a full-bleed topic band below the hero drifts in opposite directions as you scroll.
  • Count-up stats (@property), a wave reveal on the contribution graph, a styled tooltip that glides between graph cells, a cursor spotlight on cards, a shine sweep on buttons, tactile press states and lazy images that fade in.
  • Home:
    • fuller hero with the bio and a next/latest talk chip
    • the contribution graph fills the panel, and on mobile it opens on the latest weeks
    • nav cards with CTAs
    • GitHub activity cards that link straight to the PR, branch commits, release or issue, with relative times and no repeats
  • Speaking: long abstracts collapse behind an animated "Read more" (::details-content), and past events show as a card grid.
  • Socials: icon cards with handles, rel="me", and a "best way to reach me" panel.
  • CV: an experience timeline and print styles that fit on paper.
  • Footer: a full-width AWAR//SYS wordmark whose letters rise out of the page floor, left to right, when you reach the end of any page.
  • 404: a one-off 'signal lost' glitch on the error code, plus a link to the speaking page.
  • Theme polish: scrollbars, form controls and the mobile browser bar follow the chosen theme. Selection colours match the brand, and text links get a smooth underline sweep.
  • Fix: the page background no longer shows a seam on short pages.

Performance

  • React removed: the hero, contribution graph and activity feed are now Astro components with small vanilla scripts. They render the same markup and keep the same motion.
  • anime.js removed: the radar ring now uses CSS keyframes.
  • Result: the home page ships about 10 KB of JS instead of about 229 KB (roughly 70 KB less gzipped), with no hydration step.
  • Build-time GitHub data: the repo count, contribution graph and activity cards are fetched once per build (using the workflow GITHUB_TOKEN) and rendered into the HTML. Visitors make no third-party API calls, and crawlers see real stats and activity. If the build-time fetch fails, the components fall back to fetching on idle in the browser.
  • Self-hosted avatars: GitHub avatars are downloaded at build time and served as responsive WebP (1x/2x). The home page now makes no third-party requests. If the download fails, the remote URL is used instead.
  • Self-hosted badges: Credly badges were 600–2048px PNGs (2.1 MB in total) shown at 88px. They now ship as 88px WebP at 1x/2x (71 KB in total), so the CV page also makes no third-party requests.

Privacy

  • The LinkedIn sync no longer requests personal email, phone number, inferred salary, Facebook ID or demographic extras.

Notes

  • Assumption: the Person schema address is set to Oslo, NO.
  • After merge: submit https://awar.no/sitemap-index.xml in Google Search Console and Bing Webmaster Tools, then request indexing for the home page.
  • Scroll-driven CSS: use longhand properties only. The Lightning CSS minifier folds animation-timeline into the animation shorthand, which Chromium rejects. Panels use overflow: clip rather than hidden; a hidden ancestor becomes the scroller and silently captures view timelines.

NotAwar and others added 3 commits October 16, 2026 01:01
Astro only copies files from public/ into dist, so robots.txt and
site.webmanifest were never deployed (the manifest link 404'd).
Moving CNAME there too removes the manual copy step in the workflow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add canonical links, robots directives and keyword-focused titles and
  descriptions for every page; noindex the 404 page
- Emit a schema.org JSON-LD graph (WebSite, Person with sameAs links,
  ProfilePage on the home page, BreadcrumbList on sub pages)
- Replace the missing SVG og:image with a generated 1200x630 JPEG and add
  full Open Graph/Twitter image metadata plus og:type=profile
- Add apple-touch-icon and PNG manifest icons, rel=me identity links and
  colour-scheme aware theme-color
- Turn /resume into a proper redirect to /cv/ and keep redirects and the
  404 page out of the sitemap
- Add scripts/generate-images.mjs (npm run generate-images)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The speaking page relied on Sessionize JavaScript embeds, so none of the
talk titles, abstracts or conference names were in the HTML that search
engines index. A new sync script snapshots the public Sessionize speaker
JSON into src/data/sessionize.json, and the page now renders upcoming
talks, session abstracts and past events at build time, with a
schema.org Event (performer = Person) for each conference.

The JS embeds remain as a fallback when no snapshot data is available.
The deploy workflow refreshes the snapshot on every run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T23:09:40.538066Z 7200ac6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

NotAwar and others added 23 commits October 16, 2026 01:08
- The typewriter tagline server-rendered as an empty <h2>, so search
  engines saw no role description. It now renders fully typed, is a <p>
  (not a heading), exposes the real headline to screen readers and
  stays static for users who prefer reduced motion
- Add an About section with the profile summary, current focus,
  expertise and keyword-rich internal links to /speaking/ and /cv/
- Avoid repeating the summary in the LinkedIn fallback card (use bio)
- Prioritise the hero avatar image load

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the render-blocking Google Fonts stylesheet (two extra origins)
with a single preloaded 12 KB variable woff2 served from the site,
plus a metric-adjusted fallback to reduce layout shift. Improves LCP
and CLS, which feed into Core Web Vitals ranking signals.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gives AI assistants and answer engines (ChatGPT, Perplexity, Copilot,
Gemini) a concise, plain-text summary of who Awar is, key pages,
experience, expertise, talks and conferences, rebuilt with every
deploy so it never goes stale.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The CV page embedded ten Credly iframes via a protocol-relative script,
so search engines never saw certification names and the page paid for
ten third-party documents.

- Add scripts/sync-credly.js: pulls public badges from the Credly
  profile feed into src/data/credly.json (snapshot kept on failure),
  wired into build:sync/dev:sync and the daily deploy workflow
- Render certifications and badges as server-side HTML cards with
  lazy, resized images, validity/expiry status and verification links
- Add active certifications to the Person schema as
  EducationalOccupationalCredential (hasCredential)
- Mention top certification codes in the CV meta description and list
  certifications in /llms.txt

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…entity

- Mention current certifications (CNPA, KCSA, CGOA, GitHub Foundations)
  in the About section with a deep link to /cv/#certifications
- Add the Credly profile to Person sameAs and rel=me so search engines
  can connect the verified badge profile to the same entity

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The toggle wrote the detected theme to localStorage on every page load,
so visitors stopped following OS light/dark changes after one visit.
Only persist on an explicit click, follow prefers-color-scheme changes
until then, and give the button a descriptive, state-aware label.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add a skip-to-content link and make <main> focusable as its target
- Label the main nav and mark the active link with aria-current=page
- Add a theme-aware :focus-visible outline for keyboard users
- Drop the post-card aria-label that hid the post text from screen
  readers and always said "LinkedIn"; use the real service in the title
- Format post dates deterministically (en-GB, UTC) at build time

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Contribution cells used hard-coded dark-theme colours, so the graph
  was washed out in light mode. Levels are now CSS classes driven by
  custom properties with light-theme overrides
- The hero stats and the graph each fetched the same contributions
  API; both now share a memoised loader with timeout and retry
- Expose the calendar as a single labelled image to screen readers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- New CI workflow builds every pull request (including Dependabot
  updates) so broken dependency bumps are caught before merge
- scripts/check-seo.mjs validates dist/: lang, title, description,
  a single h1, img alt text, parseable JSON-LD, absolute canonicals,
  og:image, and sitemap/noindex consistency; run in CI and before
  each deploy (npm run check:seo)
- Serialise Pages deployments with a concurrency group
- Give the 404 page a more helpful description

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
_data/navigation.yml and _data/profile.yml are pre-Astro Jekyll data
that nothing reads any more (navigation and profile now live in
src/data). Drop them and the Jekyll-only .gitignore entries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- IntersectionObserver reveal for .fx-slide-in, [data-reveal] and
  section headings; items entering together stagger in reading order
- MutationObserver picks up cards rendered later by React islands
- Content stays visible without JS, under reduced motion and in print
- Section heading accent bars grow in after the heading slides in
- Hero: name wipes in, subtitle/CTAs/stats rise in sequence, avatar
  and radar spin into place (fixes hero-tilt-in being overridden)
- Individual transform properties (translate/scale) so reveal never
  fights card hover transforms
- Smooth anchor scrolling with scroll-padding for the sticky header

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All effects use CSS scroll/view timelines (compositor-driven, no JS)
and are gated behind @supports and prefers-reduced-motion:

- Reading progress bar along the top of the viewport
- Hero eases out (fade, lift, slight scale) as it slides under the
  header; the avatar drifts faster for a layered parallax feel
- Background sprites grouped into far/mid/near layers that move at
  different speeds while scrolling
- Header floats 0.6rem from the top and settles (border + deeper
  shadow) once the page scrolls

Scroll-timeline rules use longhands because the CSS minifier folds
animation-timeline into the shorthand, which Chromium rejects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Cross-document View Transitions (CSS only, no client router): the
  header stays put, content cross-fades and the new page's reveal
  choreography plays; disabled under reduced motion
- Nav gets a single pill that glides to the hovered/focused link and
  rests on the current page; it morphs between pages during
  navigation. Without JS the per-link pill styling still applies
- Theme toggle reveals the new theme as a circle expanding from the
  button (startViewTransition), with a quick icon spin; instant
  fallback where unsupported or with reduced motion
- render-blocking rel=expect on #main so the pill is positioned before
  the first frame of a transition
- Compact two-row header on small screens (brand + toggle, then nav):
  ~91px tall instead of ~160px of sticky chrome

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Registered @Property --num integer + counter() renders numbers that count up from zero (hero stats on load, speaking stats and graph total on reveal); later value changes such as live GitHub stats roll via a transition
- Real values stay in .sr-only text for screen readers and crawlers
- Contribution cells pop in as a diagonal wave once the panel is revealed
- Hero fallbacks are now numeric with a + suffix until live data arrives; repo fallback corrected from 45+ to 20+
- Reduced motion and no-@Property browsers show final values immediately

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Cards, badges, events, posts and stat cards get a soft radial spotlight that follows the cursor (fine pointers only; one delegated rAF-throttled pointermove listener)
- Buttons get a light shine sweep on hover and tactile press states (cards too)
- Lazy images fade/scale in once loaded instead of popping
- Motion pieces stay behind prefers-reduced-motion: no-preference

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Hero now carries the bio as a lede plus a Next/Latest talk chip linking to /speaking/ (pulsing dot while upcoming; flips to Latest client-side once the build-time event has passed)
- Hero stat cards sit side by side; fixed count-up digits inheriting the small label style (.stat-card > span)
- Speaking stats stay on one row of three on mobile; label shortened to Events
- Entrance choreography re-timed for the new hero elements

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Experience roles are now an ordered timeline with a track and per-role dots
- Where scroll timelines are supported, the track fills along a reading line 40% down the viewport and each dot lights up as the line reaches it (view timelines with an inset, longhands only)
- Static fallback: full track with filled dots; roles still reveal on scroll

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Print uses a white, high-contrast palette whatever the theme, drops header/background/sprites/CTAs and flattens shadows and hover transforms
- Gradient-clipped headings print as solid text; cards avoid page breaks
- All animations/transitions are off in print so beforeprint reveals don't restart count-ups
- CV adds a print-only contact line (site, LinkedIn, GitHub)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Socials: icon cards with handle, sliding-arrow CTA and rel=me, plus a
  'best way to reach me' panel; compact icon-beside-title layout on mobile
- Speaking: long session abstracts collapse behind an animated Read more
  (::details-content + interpolate-size, instant under reduced motion and
  fully expanded in print); past events become a card grid; event items
  get the same lift-on-hover as cards

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Contribution graph cells size from the panel width (cqi + round() to
  whole pixels, 14-21px stride) so the calendar fills the card instead of
  leaving a third of it empty; falls back to the fixed 11px grid
- On narrow screens the graph opens scrolled to the latest weeks, with a
  sticky Mon/Wed/Fri column; widened so 'Wed' no longer overlaps cells
- Badge cards lift with the same offset shadow as other cards and the
  badge art tilts slightly on hover

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The three cards are now single links (no nested article) with a
bottom-aligned CTA whose arrow nudges forward on hover/focus. The arrow
style is shared with the socials cards as .card-cta.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Proxycurl call asked for personal email, phone number, inferred
salary, Facebook ID and demographic extras that the site never uses.
Drop them (data minimisation, fewer API credits); keep the GitHub and
Twitter IDs that the profile transform reads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A round button eases in (fade, rise, scale) once the reader is past the
first screen and returns them to the top, using the page's own
scroll-behavior so reduced motion jumps instantly. Focus moves to the
brand link for keyboard users. Where scroll timelines exist, a ring around
it fills with reading progress (CSS only). Hidden in print.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NotAwar NotAwar changed the title SEO overhaul and site improvements SEO overhaul, motion system and UI polish Oct 16, 2026
NotAwar and others added 2 commits October 16, 2026 02:08
- html gets min-height: 100% so the propagated body gradient covers the
  viewport instead of tiling below short pages (404, socials on tall
  screens). Set on the root rather than body so the footer margin cannot
  collapse through and add a stray scrollbar
- The 404 code glitches in with stepped clip-path slices and a brief
  chromatic split, once, then settles (skipped under reduced motion)
- 404 offers Speaking alongside Home, CV and Socials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
anime.js was only driving two endless rotations and a pulse around the
avatar. Native keyframes do the same without a per-frame JS loop, and the
hero island shrinks from 35.4 KB to 5.4 KB (about 9 KB less gzipped JS on
the home page). The core now pulses around its own centre
(transform-box: fill-box). Reduced motion keeps the ring static as before.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
NotAwar and others added 16 commits October 16, 2026 02:14
The public Events API no longer includes commit messages or PR titles,
so the cards only ever showed 'Pushed to repo' and a date. They now:

- show what the API still returns (PR number and source branch, pushed
  branch, release tag, issue number)
- link straight to the pull request, branch commits, release or issue
  instead of the repo root
- show one card per action/repo so a burst of pushes doesn't fill all three
- show relative times in a <time> element with the full date as a tooltip
- keep the owner prefix for repos outside your own account
- use the shared nav-card/card-cta pattern (single link, type icon, arrow
  nudge on hover, 'opens in a new tab' hint for screen readers)
- give the skeleton the same height as the real cards to avoid layout shift

Skeleton and real cards use distinct keys so React mounts fresh nodes
and the reveal observer handles them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
React was only used for three home-page islands, so every visit downloaded
and hydrated about 216 KB of runtime (~70 KB gzipped) to drive a
typewriter, two stat fetches and two fetch-and-render widgets. They are now
.astro components with small bundled module scripts:

- NeonHero/RadarRing: identical server-rendered markup. The script runs the
  typewriter (settles on a whole title when reduced motion is switched on
  mid-word), demotes a passed 'Next talk' and updates the stat count-ups.
- ContributionGraph: builds the same cells and labels from the shared
  memoised contributions fetch. It shows a GitHub link instead of an empty
  panel if the API is down.
- GitHubActivity: same cards as before. Event text is HTML-escaped and only
  github.com URLs are used as links.

Network work still waits for idle (requestIdleCallback). The home page now
ships about 10 KB of JS in total, down from about 229 KB.

Also removes @astrojs/react, react and react-dom, the Dependabot react group
and the React mention in the package description.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The site already rebuilds daily, so the hero stats, contribution graph and
recent-activity cards are now fetched during the build and shipped as
static HTML:

- Crawlable: numbers and activity links are in the HTML, not added by JS.
- No skeletons or post-load swaps: no layout shift, and the graph wave and
  count-ups play on reveal as intended.
- Visitors make no requests to api.github.com or the contributions API, so
  no rate-limit 403s (the only Lighthouse best-practices deduction) and no
  third-party calls.

src/lib/github-snapshot.ts loads the data once per build, authenticated
with GITHUB_TOKEN when set (now passed to the build step in CI and deploy),
with a 10s timeout. If a source fails it logs a warning and that component
falls back to the existing client-side fetch. The markup builders live in
src/lib/github-render.ts and are shared by both paths, so the output is
identical either way.

Also:
- Static activity cards show dates; the client turns them into relative
  times so the HTML never shows a stale '2 hours ago'.
- Graph cells take the wave delay from nth-child rules instead of inline
  styles (about 5 KB less HTML).
- The client repo-count fallback no longer retries on 403/429.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- color-scheme and scrollbar colours follow the site theme, not just the OS
- theme-color meta updates on toggle so mobile browser chrome matches
- branded ::selection colours
- prose links get a soft underline that firms up on hover; standalone
  links (CV/speaking/Credly) get a left-to-right underline sweep

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The hero avatar (the fetchpriority=high image) and the profile card avatar
were hot-linked from avatars.githubusercontent.com, costing an extra
origin connection on the critical path. They're now downloaded at build
time via astro:assets and served as WebP at 1x/2x (2.9 KB / 7.4 KB for
the hero).

Astro fails the build if a remote image can't be fetched, so the URL is
probed first; if it's unreachable the page falls back to the remote URL.
With the build-time GitHub snapshot, the home page now makes no
third-party requests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Words brighten from a dimmed tone to full colour in reading order as the
paragraph scrolls up the viewport. It's pure CSS: a view-timeline on the
paragraph, with each word's range offset by its index (split at build
time, text unchanged). It animates colour rather than opacity, so the
dimmed state still has 5:1 contrast in both themes. Disabled for reduced
motion and in browsers without scroll-driven animations.

Panels and cards now use overflow: clip instead of hidden. It clips the
same way but doesn't create a scroll container, which would otherwise
capture view timelines inside them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A full-bleed band of large topics (Kubernetes, GitOps, Platform
Engineering...) sits between the hero and About. Its two rows slide in
opposite directions as the band crosses the viewport, driven by a CSS
view-timeline, so it uses no JS and runs on the compositor. Neon and
dimmed words alternate, with edges masked to fade out. The band is
decorative (aria-hidden), static under reduced motion or without
scroll-timeline support, and hidden in print.

body now clips horizontal overflow, since 100vw includes the scrollbar.
This also removes a pre-existing 2px horizontal scroll from the hero
under reduced motion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Page transitions now follow the nav order (Home, Speaking, CV, Socials).
Going forward, the old page drifts left and the new one slides in from
the right; going back (including browser history) reverses it. The
header stays put and the nav pill glides as before.

A pagereveal listener compares navigation.activation's from/entry URLs
against the nav order (injected with define:vars) and adds a
slide-forward or slide-back type. CSS targets these with
:active-view-transition-type(). Other navigations, and browsers without
type support, keep the cross-fade; reduced motion disables transitions
entirely as before.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Hovering a day now shows a styled tooltip (count plus a readable date,
e.g. "3 contributions / Tue, 14 Apr 2026") that glides between cells. It
replaces the slow, unstyled native title. One fixed element is driven by
event delegation; it clamps to the viewport, flips below when it would
hit the header, and hides on leave or scroll.

Titles stay in the HTML as a no-JS fallback and are moved aside lazily on
hover, before the native tooltip's delay elapses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The footer now closes with a full-width AWAR//SYS wordmark. Its letters
rise out of the floor left to right (55 ms stagger, ease-out-expo) once
it is half in view, and each letter lights up neon on hover.

- Sized with container units (13.4cqi) so it spans the footer exactly at
  any width. A mask fades it into the page floor.
- It has its own IntersectionObserver because, at the very bottom of the
  page, it often sits inside the -6% margin the shared reveal observer
  excludes.
- Decorative (aria-hidden, user-select: none) and hidden in print.
  Without JS or with reduced motion it simply shows.
- The footer now uses overflow: clip instead of hidden, so it no longer
  becomes a scroll container.

Lighthouse is 100 across the board on home and socials, with CLS 0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Credly's /size/220x220/ URLs just redirect to the original artwork:
600-2048px PNGs totalling 2.1 MB (GitHub Foundations alone is 1 MB), all
shown in an 88px slot. Badges now go through optimizeRemoteImage, like
the avatars, and ship as 1x/2x WebP. That is 71 KB for all ten at 2x.

The CV page now makes no third-party requests and scores 100 in every
Lighthouse category (mobile and desktop). The unused badgeImage helper is
removed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Links to /speaking/, /cv/ and /socials/ shared on LinkedIn, Slack or X
now preview a matching 1200x630 card: same brand art, its own heading,
topics and URL pill, e.g. 'Speaking / Conference talks on Kubernetes,
GitOps & platform engineering / awar.no/speaking'.

- generate-images renders every card through one template (the home card
  and icons come out byte-identical) and writes public/og/*.jpg. The cards
  stay evergreen (no counts) because they aren't regenerated on deploy.
- seo.ts defines the cards with their alt text. BaseLayout takes an image
  object, so every page now sends og:image type, width and height.
- check:seo fails if a page's og:image is missing from dist/.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Section and page eyebrows scramble through random glyphs and resolve left to
right when revealed. Each character is locked to its final, untransformed
width and only cycles through glyphs that fit, so nothing overlaps or shifts.
Screen readers get the real text throughout, plain text is restored when done,
and the effect is skipped for reduced motion and resolved instantly for print.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The counter printed by counter() grew from one digit to its final length while
counting, pushing following text such as 'contributions in the last year'
along and registering small layout shifts while scrolling. An invisible copy of
the final value now sizes the box and the counting value grows into it from a
fixed start edge. The finished layout is pixel-identical; CLS while scrolling
the home page drops from 0.0037 to 0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Headings and badge titles use text-wrap: balance, and paragraphs and list
  items use text-wrap: pretty to avoid lone words on the last line. No element
  changes height on any page at 390px or 1280px; only line distribution does.
- The middle-dot separator in badge, job and education meta lines is joined to
  the preceding text with a non-breaking space, and badge dates never split,
  so wrapped lines no longer start with a stray dot.
- Sections and headings with ids get scroll-margin-top so in-page jumps land
  clear of the sticky header.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With no LinkedIn posts synced, the section promised 'recent updates straight
from LinkedIn' and its profile card repeated the hero bio word for word with
only a GitHub button. The lede now matches whichever state renders, and the
fallback card explains how to reach out, linking to speaking invitations
(an internal link) alongside GitHub.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NotAwar
NotAwar merged commit 41e3a60 into main Oct 16, 2026
4 checks passed
@NotAwar
NotAwar deleted the improve/seo-and-site-fixes branch October 16, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant