Skip to content
Back to skills

Visual Review

ASecurity

Start the website dev server and screenshot pages in both light and dark mode, at desktop and mobile widths, to catch visual issues. Use when asked to visually review changes, check light and dark mode, or screenshot pages.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 6, 2026
developmentrustgoshellnextjsgit

Works with

  • cli

Security analysis

A100/100

Scanned November 11, 2026

npx -y skills add robritacca-dotcom/rift-ds --skill visual-review --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Visual Review?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Visual Review
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/robritacca-dotcom-visual-review/badge)](https://www.skillsdirectory.com/skills/robritacca-dotcom-visual-review)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: visual-review
description: Start the website dev server and screenshot pages in both light and dark mode, at desktop and mobile widths, to catch visual issues. Use when asked to visually review changes, check light and dark mode, or screenshot pages.
icon: preview
displayDescription: "Opens the site in a browser preview, drives each page through both light and dark mode at desktop and mobile widths, and screenshots them. Checks for invisible text, broken layouts, overflow, and stuck hover states, then reports findings or confirms all clear."
invoke: ["check how this looks","review light and dark","visual check","screenshot the page"]
---

# visual-review

Start the website dev server and screenshot pages in both light and dark mode, at desktop and mobile widths, to catch visual issues.

## When invoked

Use this skill when asked to visually review changes — phrases like "check how this looks", "review light and dark", "does this look right", "screenshot the page", "visual check".

This skill catches what is **broken** — clipped text, invisible elements, layout collapse. For whether something is **good** — craft, consistency, polish, taste — use the `design-qa` skill instead.

## Instructions

Use the browser/preview tools available in the current environment for every step below — this skill describes *what* to do; map it to whatever tools the harness currently provides. Never launch the dev server through a raw shell command.

1. **Determine which URLs to review.** If not specified, default to the page(s) most recently modified in the current conversation. Ask if unclear.

2. **Open the website's Next.js dev server** (the `website` configuration in `.claude/launch.json`; port 3000 by default, but the config sets `autoPort`, so read the URL the preview actually reports rather than assuming 3000) in the browser preview and wait for it to be ready.

3. **For each URL, check both themes at both viewports.** The rendered theme is the `data-theme` attribute on `<html>`. To switch: click the theme toggle in the top nav (`MegaNav`: in the bar's end cluster at every width from 720px up, as the three-icon strip while the full row fits and as one circle that steps System, Light, Dark once the bar condenses; in the drawer's footer, as the strip, only below 720px) — on the strip the explicit Light or Dark segment, not System; on the circle, press until its tooltip reads Light or Dark — or set both `data-theme` and `data-theme-setting` programmatically (the only route on the chromeless stage pages, which render no nav). While `data-theme-setting` is `system` (the default on a fresh profile), the theme follows the browser's colour scheme live, so forcing `data-theme` alone can be overridden mid-review by an OS theme change. Verify the attribute actually changed before screenshotting, then take a screenshot in each theme. Repeat at a mobile viewport (~375px wide — mobile is a first-class surface: type and spacing collapse at the token layer, and navigation moves into a drawer): screenshot both themes there too, and on at least one page open the drawer nav, expand a section, and screenshot it open. The site chat changes form with the viewport and the default desktop width shows the least interesting one: a full-viewport takeover with its own stacked welcome screen on phones, a modal overlay behind a scrim at mid widths, and a docked panel that insets the whole page beside it at the widest (design.md's ChatWidget spec owns the exact breakpoints, and the seats below). On at least one page, open the chat at the mobile width and at a desktop width wide enough to dock, and screenshot both — the docked form is the one that moves the page, so a layout that survives a mid width can still break there. At that dock-wide width, also use the header's Chat position menu to capture the other two seats: Dock left, where the page's inset (and the header inside it) mirrors to the other side, and Floating, a card with no scrim over a page at its full width. The background is a third axis: the header's background menu (between the theme dot and the toggle in the bar, in the drawer's footer below 720px) sets or removes `data-background` on `<html>`; for a change to a background surface, capture at least one page under each row of `SITE_BACKGROUNDS` (`website/src/lib/shaders/background-storage.ts`).

4. **Examine each screenshot for:**
   - Text that is invisible or the same colour as its background
   - Components that appear broken, overflow their container, or clip
   - Spacing that looks inconsistent or misaligned compared to other pages
   - Hover/focus states that appear stuck in an active state
   - Images or assets that failed to load (broken image icons)
   - Any layout that differs unexpectedly between light and dark
   - At mobile width: horizontal overflow (a page that scrolls sideways), content clipped by the viewport, and drawer navigation that fails to open, scroll, or close

5. **Report findings** concisely:
   - Format: `[URL] [dark|light] [desktop|mobile] — description of issue`
   - If no issues found, say: `[URL] — looks correct in both themes at both widths`

6. **Stop the preview server** when all pages are reviewed, unless the session is still using it.

## Key context

- Theme state lives on `document.documentElement`: `data-theme-setting` is the visitor's choice (`system`, `light` or `dark`) and `data-theme` is what it resolves to (`light` or `dark`) — every style reads the latter. The choice persists via the localStorage key `theme` and both attributes are applied before first paint by an inline script in the root layout, which also keeps a `system` setting following the OS
- The theme preset is a second, independent attribute: `data-brand` on `<html>`, restored before first paint from the localStorage key `brand` (`BRAND_STORAGE_KEY` in `website/src/lib/theme/brand.ts`). A browser profile that once picked another look keeps it, so before capturing, confirm `data-brand` is the served theme (`SERVED_THEME_ID` in the same file) or the theme under review — a page judged in a stale preset is judged against the wrong palette. The header's theme switcher sets it, and so does the attribute route in step 3
- The background pick is a third persisted attribute: `data-background` on `<html>`, restored before first paint from the localStorage key `background` (`BACKGROUND_ATTR` and `BACKGROUND_STORAGE_KEY` in `website/src/lib/shaders/background-storage.ts`); absent means the field. A profile that once picked Horizon screenshots Horizon on every page, so confirm it is the default or the background under review before capturing. The header's background menu sets it
- The theme toggle is rendered by `MegaNav`, which pages mount themselves — the chromeless stage routes (`CHROMELESS_ROUTES` in `website/src/config/chromeless.ts`, `/graph` included) render no nav and so no toggle; switch themes there programmatically via the attribute route in step 3
- The sitemap footer (`SiteFooter`), the chat button/panel, and the site-wide command palette (`SitePalette`, opened from the header's search button or Cmd+K, so invisible in a static screenshot until triggered) are site chrome mounted once from the root layout, not per page — expect the first two in every screenshot's lower region (the chat as a floating button when closed; open, it is a takeover on phones, a modal overlay on mid widths, and a docked panel at the widest that pads the page to make room, on whichever edge the visitor chose; popped out from its Chat position menu it is a free card over a full-width page, which is the Floating seat and not a failed dock — see step 3). Hovering the closed chat button opens its per-page AI-summary panel (a frosted TLDR card with prompt chips, fed from the page-summaries registry) — expected chrome, not a stray overlay, so a pointer resting near the bottom-right corner can legitimately summon it mid-capture. Deliberately hover it on at least one page per theme and let the reveal settle (the think beat plus the typed reveal take a few seconds; the constants in `src/tokens/motion.ts` are the timings) before capturing, since no automated gate sees that panel render. The chat panel itself animates open and closed: a capture right after opening catches it low and part-transparent mid-entrance, and one right after a close catches the still-mounted dying panel — both are the animation, not a layout bug or a stuck overlay; let it settle before capturing. A change of seat glides the same way, so a panel caught between two edges is mid-move. The welcome starters and a fresh answer's follow-up chips also stage a shimmer-then-pop reveal (design.md's ChatWidget spec owns the choreography), so shimmer pills where chips belong are a loading state, not missing content. Three quick-reply states are by design too (design.md's ChatWidget spec, the Quick replies paragraph, owns them): a reply that asks shows a question card and no chips; a used chip row, or an answered card, is a record, the pick outlined and the rest faded and inert; and a set of more than one question holds the composer, dimmed under its own placeholder, until it is answered or skipped. Also by design, per the same spec: a reply that is only a card, with no prose; an entry button reporting the agent's state, with `done` or `error` for a beat as a reply ends; and a composer that is one row or two depending on what it holds. The playground's chat director stages each one (its Quick replies events), and its Flow lever shows the bottom-up thread, a second layout to capture. On the mobile pass, check the footer's collapse against design.md's Site footer pattern (it owns the column ladder): the brand block leaves the row and sits above the links at the same breakpoint the page's nav rail disappears at. The footer is identical on every page, so a difference between two pages is a finding; the brand block's width matching the rail is deliberate, not a coincidence to report
- The routes in `CHROMELESS_ROUTES` (`website/src/config/chromeless.ts`) deliberately render none of the shared chrome — footer, chat, and command palette — so their absence there is not a finding
- Nav items flagged `desktopOnly` in `website/src/config/navigation.ts` (the flags there are the authoritative list; the field can be set on any item, so check whether one carries it before expecting this) disappear at and below the tablet step (`BREAKPOINT_TABLET_PX` in `src/tokens/breakpoints.ts`, the number's one home) from every surface that renders at all widths — the footer's Design system column and the command palette — and never appear in the mobile drawer. A link present at desktop and gone on the mobile pass is that flag working, not a finding. The pages behind those flags need a pointer and a wide viewport, so a ~375px screenshot of the page itself proves nothing either way
- The `animate-in` class on page elements triggers CSS entry animations — these are normal on first load
- A chat welcome with a widget board enters in stages (composer, then greeting, then the tiles popping in one after another, after the hold `--ds-chat-widget-welcome-hold` sets; design.md's ChatWidget spec owns the choreography), so an early capture of the chat home template, the home page's chat frame or the playground's chat card shows a missing greeting or missing tiles. Let it settle before capturing
- The ambient background (`BlurBackground`) is layout-mounted chrome too, and it is the largest thing in every screenshot. Four of its behaviours produce **false findings** if you do not expect them:
  - **What paints is the visitor's pick.** With the field picked (the default), a WebGL2 field normally and the CSS blobs underneath as the fallback, and which one you capture depends on the machine's GPU, so the same page can legitimately screenshot two different ways on two runs. With a scene picked (a row of `SITE_BACKGROUNDS`), the library's ShaderScene paints in the field's place on every page, tuned per band by its row (`home` under a hero, `page` under a title), and the band stays bare while the scene's chunk loads. The doc block of `BlurBackground.tsx` owns all of it. A background that differs between runs is not a finding; a *broken-looking* one is. The band also sinks at 30% of the scroll on every page (the `bg-parallax` rule in `website/src/app/globals.css`), so a band offset after scrolling is not a finding either.
  - **There is a moment with no background at all.** While the renderer resolves, the blobs are hidden and the canvas has not faded in. A screenshot caught in those first frames shows bare page floor. Let the page settle before capturing.
  - **Most pages get a fixed-height band, not a full screen (the doc block of `website/src/components/BlurBackground/BlurBackground.tsx` owns the modes and the number) — and the immersive stages get no ambient background at all.** Pages rendering `FullBleedBackground` fill the viewport; pages rendering `HiddenBackground` (beside `DotBackground`, the dotted stage ground) hide the ambient layer entirely, by design — a dotted stage with no glow is that mode working, not a missing background. Grep `FullBleedBackground` and `HiddenBackground` under `website/src` for the current sets rather than trusting a list here. Judge each page against its own variant.
  - **It is deliberately fainter in light mode.** The whole layer, either renderer, runs at reduced opacity over a light floor (the comment on the `html[data-theme="light"] .blur-container` rule in `website/src/app/globals.css` owns the value and the reason). A glow that is weaker in light than in dark is that rule working, not a theme-parity finding.
- To rule the background in or out of a finding, `?tune=1` on any page in dev opens its control panel, which reports the live renderer and lets you A/B the shader against the CSS blobs, with the field picked (pick Field from the header menu first; the tuner drives only the field)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.