Repository navigation
SEO overhaul, motion system and UI polish - #32
Merged
Merged
Conversation
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>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
- 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>
- 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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Continuous site improvements focused on search visibility, then motion and UI polish.
mainrequires signed commits and PRs, so work lands here for review. The branch commits are unsigned; the squash merge produces a signed commit onmain.SEO
robots.txt,site.webmanifestandCNAMEtopublic/. Before this they were never deployed.noindexon the 404 page, a/resume→/cv/redirect and a filtered sitemap.WebSite,Person(withsameAs),ProfilePage,BreadcrumbList, oneEventper conference, and credentials for certifications./llms.txt.npm run generate-images). Speaking, CV and Socials each get their own share card with matching alt text, andcheck:seofails if a page'sog:imageis missing.npm run check:seochecks 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.@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.::details-content), and past events show as a card grid.rel="me", and a "best way to reach me" panel.Performance
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.Privacy
Notes
Personschema address is set to Oslo, NO.https://awar.no/sitemap-index.xmlin Google Search Console and Bing Webmaster Tools, then request indexing for the home page.animation-timelineinto theanimationshorthand, which Chromium rejects. Panels useoverflow: cliprather thanhidden; ahiddenancestor becomes the scroller and silently captures view timelines.