💄 Redesign /consulting index + card colour tokens - #4939
Conversation
Dark-theme surface tokens for card backgrounds (#101010 default, #151515 hover), exposed to Tailwind as bg-card / bg-card-hover so cards across the site can share one definition.
Replace the tag-filter grid with a sticky sidebar + sectioned card layout driven by the home theme (light/dark). Extend the HomeTheme boundary, pre-paint script and mega menu dark scope to /consulting, and give the shell the .dark class so design tokens (incl. the new --card tokens and --hairline border) resolve on this route.
Swap the dark-mode 60%-opacity background trick for the explicit --card / --card-hover tokens, matching the /consulting cards. Border already uses --hairline.
Coverage report
Test suite run success27 tests passing in 3 suites. Report generated by 🧪jest coverage report action from dd3a9e3 |
category/pageCard/tag were only used by the previous tag-filter layout; pageCard still imported the removed useFilterContext export, breaking the next build type check.
Also express the conditional dark class via cn() — prettier-plugin-tailwindcss strips the leading space inside a template-literal conditional, which mangled the class into 'shelldark'.
|
/deploy |
|
Deployed changes to https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net ℹ️ Staging slots are no longer created automatically - comment |
New 'Popular' boolean on the consulting index page entries (Tina toggle). When on, the card shows a small uppercase pill next to the title, styled with the existing --consulting-accent token so it follows the light/dark theme.
|
/deploy |
|
Deployed changes to https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net ℹ️ Staging slots are no longer created automatically - comment |
🚀 Lighthouse Report
|
joshbermanssw
left a comment
There was a problem hiding this comment.
Nice direction on the redesign, and the imageCards change is a clean simplification (opacity fade to a real colour transition on shared tokens).
Two things I'd want fixed before this merges, both content losses rather than styling:
- 8 cards stop rendering. 6 pages carry no
tagsat all, so they fall to the[allServices]default and now match no section. On top of that, the new URL dedup drops two distinct cards (Microsoft Azure, Document Management) because they share a page reference with a differently-titled card. Details inline. - The mega menu's two
/consulting?tag=...deep links are dead, since the query-param reader is gone.content/megamenu/menu.jsonlines 38 and 65 need rewriting to the new hashes.
Rest of the comments are smaller: the themed-route list is now duplicated in three places, /consulting has no pre-paint script so it will flash dark to light for light-mode users, and there's a nested <main>.
I've deliberately left the index.module.css file alone here given my earlier comment on it. Worth knowing for whatever replaces it: the inactive nav colour (#aaaaaa on #fafafa) is 2.2:1 in light mode against a 4.5:1 requirement, and the POPULAR badge lands around 3.2:1 in dark, so both need new values wherever those styles end up.
There was a problem hiding this comment.
Pull request overview
This PR aligns the /consulting index page card styling with the homepage by introducing shared “card” design tokens, and updates the consulting index UX to a sectioned, scroll-linked layout with dark/light theme support and a new “Popular” badge flag coming from Tina content.
Changes:
- Adds shared card background tokens (
--card,--card-hover) and exposes them via Tailwind (bg-card,bg-card-hover). - Rebuilds the
/consultingindex into a sidebar + section layout with intersection-based active navigation and “Popular” badge rendering. - Expands homepage-scoped theming behavior to also include
/consulting(menu + theme boundary logic).
Reviewed changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tina/collections/consulting.tsx | Adds a new popular boolean field to the consulting index schema. |
| tailwind.config.js | Exposes CSS variables as Tailwind colors card and card-hover. |
| styles.css | Defines new dark-mode design tokens --card and --card-hover. |
| content/consulting/index/index.json | Sets popular: true on a consulting card entry. |
| components/server/MegaMenuWrapper.tsx | Treats /consulting like home for themed nav rendering/prepaint. |
| components/layout/homeTheme.tsx | Extends themed-route gating and prepaint logic to /consulting. |
| components/consulting/index/tag.tsx | Removes old tag list UI component (no longer used). |
| components/consulting/index/pageCard.tsx | Removes old consulting card component (replaced by new layout). |
| components/consulting/index/category.tsx | Removes old category renderer (replaced by new layout). |
| components/blocks/v3/imageCards/imageCards.tsx | Switches dark-mode card fill to shared --card/--card-hover tokens. |
| app/consulting/index.tsx | Implements the new consulting index layout, section navigation, and Popular badge. |
| app/consulting/index.module.css | Adds the new consulting index styling (responsive + theme-aware). |
Suppressed comments (1)
app/consulting/index.tsx:150
- This
scrollIntoView({ behavior: "smooth" })ignores the user’sprefers-reduced-motionsetting. For accessibility, useautowhen reduced motion is requested.
document.getElementById(id)?.scrollIntoView({
behavior: "smooth",
block: "start",
});
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Addresses review feedback: avoid *.module.css files, and don't add CSS variables that are only used in one directory — new tokens should be global. - Delete app/consulting/index.module.css (334 lines, ~30 page-scoped --consulting-* vars) and restyle index.tsx with Tailwind utilities only. Colours now resolve to the existing global design tokens through `dark:` variants, so the page declares no CSS variables of its own and the data-theme attribute (read by nothing else) is gone. - max-md:/max-xl: mirror the old 767px/1279px media queries and the repo's custom not-first: variant replaces `.section + .section`, so cascade order is unchanged. - Add only the genuinely missing values to tailwind.config.js theme.extend, global and reusable alongside the existing token mappings: brand / brand-subtle colours, sunken-glow / sunken-scrim background images, sidebar grid columns, z-15, and header-offset spacing. Visual fixes requested alongside the refactor: - Sidebar heading is now "SSW Services", pinned to one line, and shares a single type scale with the category headings (semibold, text-xl at md, text-2xl at xl) so the two stay in lockstep. - Card and chip borders use border-0.75 instead of a bare `border`, which is 3px in this repo's borderWidth scale (the module had 1px). - Left nav items no longer take a hover background; the label still brightens. Note for anyone touching this file: cn() is twMerge, which silently drops utilities built from custom theme keys. It collapses two bg-* classes into one, so the flat page colour is baked into the sunken-glow gradient as a second layer; and it eats a bare `outline` next to outline-2, so focus uses ring-* instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review comments on PR #4939. Content losses: - 6 pages with no `tags` at all fell to the `[allServices]` default and matched zero rendered sections, silently disappearing. They now also get `otherServices`, landing in the existing "Other SSW Services" catch-all — the same bucket every other untagged-by-convention page already uses. - The url-only dedup in allMappedPages dropped "Microsoft Azure" and "Document Management" because they share a page reference with a differently-titled card ("Azure AI", "SharePoint"). Re-keyed on `${url}|${title}` so distinct cards survive but an actual copy-paste duplicate (same url *and* title) is still caught. - content/megamenu/menu.json's two `/consulting?tag=...` links were dead (the query-param reader was removed in the sidebar redesign). Rewritten to the equivalent `#consulting-<slug>` hashes. Smaller fixes: - The `/`+`/consulting` themed-route list was duplicated in homeTheme.tsx (twice) and MegaMenuWrapper.tsx. Consolidated into one `THEMED_ROUTES` array, re-exported as `isThemedRoute` for MegaMenuWrapper to reuse. - /consulting had no pre-paint script, so light-mode users saw a dark-to-light flash on load. It now renders via HomeThemeShell (extended with a `className` override) instead of a hand-rolled wrapper, picking up the same script the homepage uses. - The page rendered its own <main>, nested inside PageLayout's — changed to a <div>. Filter behaviour (follow-up ask): re-added "All SSW Services" as a real, clickable sidebar entry — selecting it shows every section; selecting any other tag now hides the rest, instead of just scrolling to it among everything else. A hash on load (e.g. the mega menu's #consulting-platform-development) selects that filter directly. In the "All" view specifically, a page tagged under several sections (e.g. "Microsoft Azure" is both Cloud and Infrastructure and Platform Development) is claimed by its smallest matching section rather than repeating in every one. Claiming has to run smallest-first, not sidebar order: every Content Management Systems page is also tagged Website Development, so claiming top-down handed all 8 to the broader bucket and erased the CMS heading entirely. Verified against the real content data (no browser available in this sandbox) that every section keeps at least one card and the total count across the All view matches the total unique-page count exactly. Also extracted the card markup into components/consulting/consultingCard/, per review feedback to make it reusable rather than inline in the .map(). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There are too many items on that indexing page, we should add a tracker to review how many of them are not clicked, and hide them, we should do it in another PBI |
Clicking a sidebar filter scrolled immediately, before React re-rendered the (now much shorter) filtered grid. The browser then clamped that scroll position against the new, shorter page, which could dump the viewport at the very bottom with the section heading and sidebar both scrolled off-screen — or, if the fix was to drop the scroll entirely, leave it clamped there with nothing to correct it. Scroll now happens inside requestAnimationFrame, after the filtered content has committed, and only ever moves the viewport up to bring the filtered section's top into view — so a click from the top of the page no longer causes a gratuitous jump either. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…into feature/consulting-card-tokens
Done. except the first one, there are subtle background change on both dark and light mode, I think it's nice.
Done.
Done.
Not done, we'll clean up the index page in another PBI.
Done. |
- imageCards: restore the Tina card-background picker. Merging the shared token after cardBackgroundClass made tailwind-merge drop every configured bg-*, so the CMS control was silently inert. - consultingCard: use CustomLink again, restoring next/link soft nav and prefetch for internal cards and target/rel for external ones. - /consulting: honour hashchange and popstate, so the mega menu's deep links work when already on the page; re-accept legacy ?tag= URLs. - /consulting: use "instant", not "auto", for reduced motion. scroll-smooth is set globally on html, and "auto" defers to it. - /consulting: let modified clicks through so categories can open in a new tab. - Extract the section-building logic to helpers/consultingSections.ts and cover the smallest-section-claims-a-shared-page rule with tests. - Revert unrelated tsconfig.json churn auto-written by next dev. Next forces jsx: preserve, so react-jsx would have churned back on the next build. - Rename isHome to isThemed and correct the now-stale homepage-only comments. - Trim the heaviest inline comments. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRcoUyMuvLA51GPLK1Bdwo
- Revert imageCards.tsx to main. Moving it onto the card tokens broke the Tina "Card Background Colour" picker, and the reorder that fixed that made the dark hover a no-op (home.json picks #151515, same as --card-hover) and repainted light-mode cards. The opacity mechanism also works for the gradient presets, which a background-colour token cannot. Better as its own PR than riding along with the /consulting redesign. - /consulting: reset to All Services when the URL matches no section, so going Back out of a filtered view doesn't leave a stale filter. - /consulting: drop empty sections instead of rendering a bare heading. - /consulting: document that hashchange/popstate cannot cover same-page mega-menu clicks (App Router uses pushState; vercel/next.js#62670). - consultingCard: description is optional, matching its source type. - consultingSections: unexport helpers with no external callers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRcoUyMuvLA51GPLK1Bdwo
- The legacy ?tag= fallback was ORed with the hash, so with both present
sidebar order decided the winner: /consulting?tag=Design#consulting-cloud-
and-infrastructure resolved to Design. The hash now wins outright and
?tag= is only consulted when there is no hash.
- That state was reachable because replaceState("#id") resolves relatively
and preserved an existing query string. Rebuild the URL from pathname.
- Drop sidebar rows with no tag reference. The Tina field isn't required, and
such a row rendered a filter that selected nothing and blanked the page.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRcoUyMuvLA51GPLK1Bdwo
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRcoUyMuvLA51GPLK1Bdwo
|
/deploy |
|
Deployed changes to https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net ℹ️ Staging slots are no longer created automatically - comment |
🚀 Lighthouse Report
|
The 72px offset was clearing a sticky site header that doesn't exist, so cards scrolled through the gap in full view above the bar instead of sliding under its scrim and blur. Verified on the deploy preview. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRcoUyMuvLA51GPLK1Bdwo
|
/deploy |
|
Deployed changes to https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net ℹ️ Staging slots are no longer created automatically - comment |
🚀 Lighthouse Report
|
isaaclombardssw
left a comment
There was a problem hiding this comment.
✅ This is good to me, but would be good to get another review?
text-brand was layered on top of dark:text-muted-foreground. tailwind-merge keeps both since they are different variant groups, and '.dark .dark:text-muted-foreground' outranks a bare '.text-brand' on specificity, so in dark mode the active item rendered rgba(255,255,255,0.78) - identical to the inactive ones. Active and inactive colours are now mutually exclusive rather than layered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRcoUyMuvLA51GPLK1Bdwo
|
/deploy |
|
Deployed changes to https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net ℹ️ Staging slots are no longer created automatically - comment |
🚀 Lighthouse Report
|
Tiago's 'Update branch' merge (19ac2b4) landed with no workflow runs and no check-runs at all, leaving the PR BLOCKED on required checks. Actions is healthy repo-wide, so this just re-fires the pull_request synchronize event. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRcoUyMuvLA51GPLK1Bdwo
Resolved conflict in tina/tina-lock.json by regenerating it from the merged schema rather than hand-merging — the file is 30MB of minified single-line JSON, so the whole file is one conflict hunk and a textual merge is not possible. Verified the regenerated lock contains both sides' schema changes: footer.bottomBar.poweredBy has the new `logo` field (this branch) and the consulting collection reflects main's redesign (#4939).
Redesigns the /consulting services index and introduces shared card colour tokens.
Affected routes:
/consultingFixed 💄 Redesign the /consulting services index (dark mode, filtering, shared card tokens) #4971
Changes:
styles.css, exposed to Tailwind asbg-card/bg-card-hover. Defined in both light (#f9f9f9/#ffffff) and dark (#101010/#151515) scopes. Card borders use the existing--hairline./consultingindex redesigned: sticky sidebar, tag-filtered sections, newConsultingCardcomponent. Replaces the old category grid and the?tag=query-param filter with hash anchors (old?tag=URLs are still honoured)./consultingopts into the existing home theming system, so it supports the light/dark switcher. Route list is now a singleTHEMED_ROUTESconstant shared by the theme boundary, the pre-paint script and the mega menu.popularboolean on consulting index pages, rendering a POPULAR badge.helpers/consultingSections.tsand is unit-tested.components/consulting/index/{category,pageCard,tag}.tsxand the framer-motion card animation they carried.Not included: the homepage
imageCardsblock was briefly moved onto the new tokens and has been reverted. Itsdark:opacity-60 → group-hover:opacity-100mechanism works for gradient presets, which a background-colour token cannot, and merging a token after the Tina-configured class silently disabled the "Card Background Colour" picker. Worth a focused follow-up rather than riding along here.If adding a new page, I have followed the 📃 New Webpage issue template
If updating the livestream banner, I have tested and followed the steps in Wiki - Testing the live banner
Include Done Video or screenshots