diff --git a/.agents/skills/contribute-blog/SKILL.md b/.agents/skills/contribute-blog/SKILL.md
new file mode 100644
index 0000000000..a1ea5c693e
--- /dev/null
+++ b/.agents/skills/contribute-blog/SKILL.md
@@ -0,0 +1,73 @@
+---
+name: contribute-blog
+description: >-
+ Create, import, edit, or review posts for the
+ official Flutter blog at flutter.dev/blog,
+ including metadata, media, authors, and publication checks.
+---
+
+# Contribute to the Flutter blog
+
+## Editorial approach
+
+Preserve the author's voice, intent, and requested scope.
+First person, contractions, anecdotes, humor, enthusiasm,
+and future-facing announcements can all be appropriate.
+Don't rewrite them merely to match documentation conventions.
+
+Apply `write-technical-docs` and `format-markdown` selectively for clarity,
+accuracy, accessibility, links, code formatting, and sound structure.
+Their documentation-specific preferences aren't blanket blog requirements:
+semantic line breaks and an 80-character limit aren't required,
+especially when importing or lightly editing an existing post.
+
+Fix or flag substantive issues with source fidelity,
+technical accuracy, accessibility, metadata, or rendering.
+Treat preference-only rewrites as suggestions
+unless copyediting was requested.
+
+## Read relevant reference material
+
+- For a new post, import, metadata or media change,
+ structural edit, or full review, read
+ [Flutter blog post format](references/post-format.md).
+- For a Google Doc import, also read
+ [Import from Google Docs](references/google-doc-import.md).
+- To add or update an author or resolve an unknown author ID, read
+ [Manage blog authors](references/authors.md).
+
+A small prose-only correction might need no reference.
+Consult `sites/www/content/blog/data.yaml`,
+`sites/www/content/blog/authors.yaml`, comparable recent posts,
+or implementation source when a convention is unclear.
+
+## Code and quality checks
+
+- If changing or writing Dart or Flutter samples,
+ apply `write-dart` and `write-flutter` as relevant.
+ Validate complete examples when practical and
+ label intentional omissions or pseudocode clearly.
+- Before calling a post ready,
+ search the changed content for unresolved markers and placeholders,
+ such as `TODO`, `TBD`, or placeholder notes.
+ Report intentional markers or blockers instead of silently removing them.
+
+## Validate proportionally
+
+For a new post or a change to metadata or content,
+run the build and link-reference checks:
+
+```bash
+dart run dash_site --site=www build
+dart run dash_site --site=www check-link-references
+```
+
+When layout, media, metadata, or content significantly changes,
+consider previewing the site locally:
+
+```bash
+dart run dash_site --site=www serve
+```
+
+For a prose-only edit that doesn't change links,
+direct inspection of the content can be enough.
diff --git a/.agents/skills/contribute-blog/references/authors.md b/.agents/skills/contribute-blog/references/authors.md
new file mode 100644
index 0000000000..6bd6327c4f
--- /dev/null
+++ b/.agents/skills/contribute-blog/references/authors.md
@@ -0,0 +1,70 @@
+# Manage blog authors
+
+Use this reference when adding or updating an author,
+or when a post's author ID doesn't resolve.
+
+Authors are defined in `sites/www/content/blog/authors.yaml`, and
+their local profile images live in `sites/www/content/blog/author_images/`.
+
+The top-level key in `authors.yaml` is a stable content ID,
+not necessarily the author's current social handle.
+
+## Add or resolve an author
+
+Before adding a record,
+search `authors.yaml` by ID, display name, and profile URL.
+Use verified or author-provided details.
+Don't invent a name, profile, or image.
+For a new ID, choose a recognizable lowercase handle or concise stable slug,
+then insert the record alphabetically by ID.
+
+`name` is required while `image`, `imageUrl`, and `link` are optional:
+
+```yaml
+author-id:
+ name: "Author Display Name"
+ image: "author-id.webp"
+ link: "https://github.com/author-id"
+```
+
+- `image` names a local image file in `author_images/`.
+- `imageUrl` accepts an external URL to use as a fallback to `image`.
+ Prefer a local `image` over setting `imageUrl`. Don't set both.
+- `link` is the destination linked from the byline. Prefer their GitHub profile.
+
+## Author images
+
+Name a local image after the exact author ID and
+use its real lowercase `.jpg` or `.webp` extension,
+such as `author-id.webp`.
+Optimize it for a small square display and a centered circular crop.
+Use an author-supplied or appropriately reusable image.
+
+## Update an author
+
+Keep the existing ID when an author's name, handle, link, or image changes.
+If the ID must change, update every occurrence in post frontmatter.
+
+When replacing an image with a different filename or extension,
+search for references to the old file before removing it.
+Don't remove an apparently unused author without checking all posts.
+
+## Configure the authors of a post
+
+In a post's `index.md` frontmatter:
+
+For a single author, use a YAML scalar:
+
+```yaml
+author: author-id
+```
+
+For multiple authors, use an ordered list:
+
+```yaml
+author:
+ - first-author
+ - second-author
+```
+
+List order controls the displayed byline.
diff --git a/.agents/skills/contribute-blog/references/google-doc-import.md b/.agents/skills/contribute-blog/references/google-doc-import.md
new file mode 100644
index 0000000000..fc0fb55d21
--- /dev/null
+++ b/.agents/skills/contribute-blog/references/google-doc-import.md
@@ -0,0 +1,99 @@
+# Import from Google Docs
+
+Use this reference to convert an authored Google Doc into a Flutter blog post
+without turning the import into an unsolicited rewrite.
+
+## Get the source
+
+First, try to download the source yourself.
+Open the document in an authenticated browser and choose
+**File > Download > Web Page (.html, zipped)**.
+An authenticated Google Docs or Drive connector is also suitable
+if it can produce the complete HTML export and original media.
+
+If you can't access the document, the download fails,
+or the export is incomplete,
+ask the user to download and provide the HTML zip.
+Don't reconstruct content that the available source omits.
+
+Import the document body as exported.
+Ignore comment threads and suggestion metadata,
+don't reproduce or act on them.
+Extract exports into a temporary directory and
+don't commit the source archive or intermediate conversion files.
+
+## Convert content, not export debris
+
+Preserve the document's structure and meaning,
+including headings, prose, lists, tables, emphasis, links,
+code, images, and captions.
+Convert ordinary content to clean Markdown,
+using site components when they provide necessary behavior.
+
+Strip Google Docs export artifacts:
+
+- **Redirects**:
+ Replace Google redirect and tracking URLs with their direct destinations.
+- **Styling spans & non-breaking spaces**:
+ Remove non-relevant markup and structure, such as
+ inline CSS, font declarations, classes, empty anchors, and wrapping spans.
+- Replace ` ` with standard whitespace.
+- **Code snippets**:
+ Convert exported code to fenced Markdown blocks
+ with appropriate language identifiers, such as `dart` or `bash`.
+ Google Docs might represent code as `
` tags with
+ ` ` indentation or as single-cell tables.
+- **Titles**:
+ Don't replicate the document title as an `# H1` heading in the Markdown body.
+ The `title` in the post's frontmatter supplies the page H1.
+- **Footnotes**:
+ Preserve footnote content,
+ but omit generated Google Docs anchor navigation links.
+- **Videos**: Convert standalone YouTube links into `` components.
+
+Keep the author's person, tone, pacing, spelling variety,
+heading style, and rhetorical choices unless editing was requested.
+Correct conversion errors,
+but surface optional prose changes separately.
+
+## Resolve authors
+
+Identify the ordered authors from an explicit byline or user context,
+not from document ownership or editor metadata.
+
+To resolve their IDs and configure the post's frontmatter,
+follow the steps in [Manage blog authors](authors.md).
+If the source lacks enough information to identify or resolve an author,
+ask the user for their GitHub or other profile URL.
+
+## Reconcile images
+
+Map each exported image to
+its source position, caption, and purpose
+before renaming or moving it:
+
+- Place images in the post's `images/` directory
+ with concise, descriptive names.
+- Keep the highest-quality intended source.
+ Convert static raster images to WebP when quality and clarity are preserved.
+- If the image is a JPEG, prefer a `.jpg` extension over `.jpeg`.
+- Preserve intentional animation
+ and provide a static `socialImage` when needed.
+- Follow the alt text and `` guidance in
+ [Flutter blog post format](post-format.md).
+- If an image's purpose or placement is ambiguous,
+ ask for guidance rather than guessing.
+
+## Check fidelity
+
+Compare the converted post with the source section by section.
+Confirm that no prose, list item, code block, table row, image, caption,
+footnote, or intended link is missing or misplaced.
+Check heading hierarchy, link destinations, image-caption pairings,
+and meaningful emphasis.
+Report any placeholder or editorial hold in the document body
+that prevents the post from being ready.
+
+Finish with the format and accessibility checks in
+[Flutter blog post format](post-format.md)
+and the validation workflow in the [parent skill](../SKILL.md).
diff --git a/.agents/skills/contribute-blog/references/post-format.md b/.agents/skills/contribute-blog/references/post-format.md
new file mode 100644
index 0000000000..19b010852a
--- /dev/null
+++ b/.agents/skills/contribute-blog/references/post-format.md
@@ -0,0 +1,168 @@
+# Flutter blog post format
+
+Use this reference for new posts, imports, metadata or media changes,
+structural edits, and full reviews in `sites/www/content/blog/`.
+
+## Location and publication
+
+Each post uses this structure:
+
+```text
+- sites/www/content/blog//
+ - index.md
+ - images/
+```
+
+The directory name becomes the public URL path, like `/blog/`.
+Don't rename a published post unless a URL change is requested
+and redirects are handled.
+Keep source documents, export archives, notes, and scratch files elsewhere.
+
+A future `publishDate` doesn't hide a post,
+and there is no `draft` frontmatter field.
+
+## Post frontmatter
+
+A typical post begins with the following frontmatter:
+
+```yaml
+---
+title: "A concise post title"
+description: >-
+ A short, plain-text summary that stands on its own.
+publishDate: YYYY-MM-DD
+author: author-id
+image: images/card-image.webp
+category: deep-dive
+layout: blog
+---
+```
+
+`title` (required)
+: Supplies the page H1, card title, social title, and feed title.
+ Don't repeat it as an `# H1` heading in the Markdown body.
+
+`description` (required)
+: Plain text used in the subtitle, cards, metadata, and feed.
+ Avoid Markdown formatting or links here.
+
+`publishDate` (required)
+: The publication date in `YYYY-MM-DD` format.
+ Preserve it when editing an existing post unless a date change is requested.
+
+`author` (required)
+: One or more author IDs from `sites/www/content/blog/authors.yaml`.
+ To set up authors, follow [Manage blog authors](authors.md).
+
+`category` (required)
+: A valid category slug from `sites/www/content/blog/data.yaml`.
+ Current categories include `release`, `news`, `deep-dive`, and `case-study`.
+
+`layout` (required)
+: Must be `blog`.
+
+`image` (optional)
+: Post-relative path (for example, `images/hero.webp`) to the card and
+ default social preview image.
+
+`socialImage` (optional)
+: Static image override (WebP or PNG under 5 MB) for social sharing metadata
+ when `image` is animated or exceeds 5 MB.
+
+## Card and social images
+
+`image` is optional in the data model but
+recommended when suitable artwork exists.
+It supplies the blog card image and social preview.
+The body layout doesn't render `image` automatically.
+If the hero image should appear in the post's body,
+include it explicitly in the Markdown content with ``.
+
+Use post-local paths such as `images/hero.webp`.
+Prefer optimized WebP for new static raster artwork when quality permits.
+Don't upscale sources or commit unused variants.
+
+Set `socialImage` when `image` is animated (such as a GIF),
+larger than 5 MB, or otherwise unsuitable for social previews:
+
+```yaml
+image: images/animated-hero.gif
+socialImage: images/social-cover.webp
+```
+
+Use a static WebP or PNG under 5 MB for `socialImage`.
+
+## Media in the body
+
+### Images
+
+Use `` for post-local figures, asset processing, or captions:
+
+```markdown
+
+```
+
+Keep the entire component tag on one source line.
+
+The common `` attributes are:
+
+- `src` (required):
+ Post-local path to the image file (for example, `images/diagram.webp`).
+- `alt` (required for new images):
+ Alternative text for accessibility.
+- `caption` (optional):
+ Visible figure caption text.
+- `figure` (optional):
+ Wraps the image and caption in a `` element.
+ Use it when setting `caption`.
+
+Make the accessibility choice explicit:
+
+- Give informative images concise, purposeful alt text.
+- Use `alt=""` for decorative images or images that only repeat nearby text.
+- Explain a chart's key findings in surrounding prose or its caption.
+ Don't rely on pixels alone.
+- Ensure animation isn't the only way to perceive important information.
+
+### Videos
+
+Embed YouTube videos using `` on a single line:
+
+```markdown
+
+```
+
+## Body content and formatting
+
+### Structure
+
+- Start the body with an introduction, hero image, or `## H2` heading.
+ The layout renders the title, description, and author metadata automatically.
+- Use semantic headings without skipping levels.
+
+### Markdown source formatting
+
+- Preserve valid source formatting and the author's stylistic choices.
+- Use semantic line breaks when drafting new prose,
+ but don't reflow imported or existing text only to meet a line-length rule.
+- Keep each Markdown link, inline code span, table row,
+ or component tag on a single line.
+
+### Links
+
+- Use descriptive link text.
+- Verify all destination URLs.
+- Use root-relative paths for internal blog links,
+ such as `/blog/`.
+- Use absolute URLs for docs pages,
+ such as `https://docs.flutter.dev/...`.
+
+### Code, tables, and custom markup
+
+- Use fenced code blocks with accurate language identifiers.
+- Verify commands, API names, and other technical details.
+- Make intentional omissions in code samples clear.
+- Don't use tables only for visual layout.
+ Give them a header row and sensible reading order.
+- Only use raw HTML or site components when
+ Markdown can't express the required behavior.
diff --git a/.agents/skills/publish-blog/SKILL.md b/.agents/skills/publish-blog/SKILL.md
deleted file mode 100644
index af90ef5579..0000000000
--- a/.agents/skills/publish-blog/SKILL.md
+++ /dev/null
@@ -1,64 +0,0 @@
----
-name: publish-blog
-description: Publish a new article to the Flutter blog from a Google Doc.
----
-
-# Publish Blog Article
-
-## Overview
-
-Use this skill to add a new article to the Flutter blog
-by extracting content from a Google Doc,
-formatting it,
-and placing it in the correct directory.
-
-## Workflow
-
-### 1. Get content and images from Google Doc
-
-- [ ] Ask the user to provide a link to the Google Doc.
-- [ ] In your browser, open the Google Doc.
-- [ ] Download the document as a Web Page (.html, zipped) via
- **File > Download > Web Page**
- (or ask the user to provide the zip if download fails).
-- [ ] Extract the text content from the downloaded HTML file
- and convert it to Markdown.
-- [ ] Extract the images from the `images/` folder in the zip file.
-- [ ] If the hero/cover image is an animated GIF,
- ask the author for a high-quality static image (WebP or PNG)
- for social media previews.
- If none is available, extract a representative frame from the GIF
- and convert it to WebP.
- Keep the social image under 5 MB.
-- [ ] Create a new folder for the blog post in `sites/www/content/blog/`
- using a concise title.
-- [ ] Save the Markdown content to `index.md`
- and move the images to the `images/` directory in that folder.
-- [ ] In the frontmatter, set `image:` to the image intended for blog cards.
- If that image isn't suitable for social previews, such as when it's an
- animated GIF or exceeds 5 MB, set `socialImage:` to a static WebP or PNG.
- `socialImage` controls `twitter:image` and `og:image` and
- falls back to `image` when omitted.
-- [ ] In the Markdown body,
- reference the intended hero image using ``.
- An animated GIF or static image are both ok.
-- [ ] Reference other images in the Markdown file using `` or
- standard Markdown image tags as appropriate.
-
-### 2. Format the markdown
-
-- [ ] Apply semantic line breaks to any lines over 80 characters.
-- [ ] Ensure there is a blank line after each heading.
-
-### 3. Add the author
-
-- [ ] Ask the user to provide the author's GitHub handle.
- Search in `sites/www/content/blog/authors.yaml` for the handle.
-- [ ] If the author is not found, add a new author entry.
- Add the image to `sites/www/content/blog/author_images/`
- and any other metadata you can find from the user's GitHub profile.
-- [ ] Update the frontmatter for the blog post with the author's handle.
-
-### 4. Review
-
-- [ ] Don't create a PR until the user has looked over the article.