Skip to content

💄 Redesign /consulting index + shared card color tokens - #4939

Open
hveraus wants to merge 7 commits into
mainfrom
feature/consulting-card-tokens
Open

💄 Redesign /consulting index + shared card color tokens#4939
hveraus wants to merge 7 commits into
mainfrom
feature/consulting-card-tokens

Conversation

@hveraus

@hveraus hveraus commented Jul 29, 2026

Copy link
Copy Markdown
Member

Unifies card styling between the /consulting index and the homepage service cards behind shared design tokens.

Changes:

  • ✨ New dark-theme tokens: --card: #101010, --card-hover: #151515 (styles.css), exposed to Tailwind as bg-card / bg-card-hover. Card borders use the existing --hairline: #212121.
  • ✨ Consulting index page styling update
  • 💄 Consulting index page now support light mode and dark mode switcher
  • 📱Optimized for Mobile view fix
  • 📛 Add Popular badge for the consultings

hveraus added 3 commits July 29, 2026 12:26
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.
@github-actions

Copy link
Copy Markdown
Contributor

No linked issues found. Please add the corresponding issues in the pull request description.
Use GitHub automation to close the issue when a PR is merged

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Coverage report

St.
Category Percentage Covered / Total
🔴 Statements 0.66% 313/47466
🔴 Branches 9.85% 52/528
🔴 Functions 1.26% 6/477
🔴 Lines 0.66% 313/47466

Test suite run success

19 tests passing in 2 suites.

Report generated by 🧪jest coverage report action from 6d4ed94

hveraus added 2 commits July 29, 2026 12:47
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'.
@hveraus

hveraus commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

/deploy

@github-actions

Copy link
Copy Markdown
Contributor

Deployed changes to https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net

ℹ️ Staging slots are no longer created automatically - comment /deploy to deploy new commits.

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Lighthouse Report

🌐 URL ⚡ Performance ♿ Accessibility ✅ Best Practices 🔍 SEO 📦 Bundle Size 🗑️ Unused Bundle
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/ 3 100 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/articles 93 97 59 57 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/company/about-us 67 100 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/company/clients 99 95 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/company/contact-us 81 92 59 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/consulting 98 100 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/consulting/net-upgrade 85 100 56 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/consulting/web-applications 80 100 59 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/employment 80 95 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/events/angular-superpowers-tour 75 97 56 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/events/ai-workshop 97 91 56 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/events/ai-workshop-essentials 66 96 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/netug/sydney 80 94 56 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/training/internship-fullstack 81 91 56 61 0.00 MB 0.00 MB

hveraus added 2 commits July 29, 2026 16:55
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.
@hveraus

hveraus commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

/deploy

@github-actions

Copy link
Copy Markdown
Contributor

Deployed changes to https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net

ℹ️ Staging slots are no longer created automatically - comment /deploy to deploy new commits.

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Lighthouse Report

🌐 URL ⚡ Performance ♿ Accessibility ✅ Best Practices 🔍 SEO 📦 Bundle Size 🗑️ Unused Bundle
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/ 45 95 56 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/articles 92 97 59 57 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/company/about-us 63 100 56 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/company/clients 99 95 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/company/contact-us 72 92 56 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/consulting 90 97 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/consulting/net-upgrade 85 100 56 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/consulting/web-applications 80 100 59 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/employment 68 95 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/events/angular-superpowers-tour 72 97 56 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/events/ai-workshop 86 91 56 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/events/ai-workshop-essentials 67 96 59 69 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/netug/sydney 76 94 56 61 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4939.azurewebsites.net/training/internship-fullstack 80 91 56 61 0.00 MB 0.00 MB

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We stick away form using these .index.module.css files

Also creates a BUNCH of new vars that may just be used in this directory.

If we create css variables they should be global

Comment thread app/consulting/index.tsx
const gridRef = useRef(null);
const router = useRouter();
const [selectedTag, setSelectedTag] = useState(allServices);
const slugify = (value: string) =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a utility function for this all ready?

Comment thread app/consulting/index.tsx
.replace(/\s+/g, "-");

const filterDidChange = useRef<boolean>(false);
const mapPageUrl = (page) =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a utility function for this all ready?

Comment thread app/consulting/index.tsx
return pageAcc;
const [activeSectionId, setActiveSectionId] = useState<string>("");

const tags = useMemo(() => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this wasn't you but https://www.ssw.com.au/rules/follow-boy-scout-rule)

all 4 memos can be combined into one useMemo hook instead of a chain of 4 of them

Comment thread app/consulting/index.tsx

<div className={styles.cardGrid}>
{section.pages.map((page) => (
<a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this and all of it's descendents into their own component that can be re-used... i.e. consulting card or something along those lines

@joshbermanssw joshbermanssw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple changes

@joshbermanssw joshbermanssw added the no-issue This label is used to bypass the PR-Lint check. Should be used for non-coding changes label Jul 29, 2026

@joshbermanssw joshbermanssw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 8 cards stop rendering. 6 pages carry no tags at 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.
  2. The mega menu's two /consulting?tag=... deep links are dead, since the query-param reader is gone. content/megamenu/menu.json lines 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.

Comment thread app/consulting/index.tsx
}, [allMappedPages, tags]);

const contentSections = useMemo(
() => sections.filter((section) => section.name !== allServices),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 services disappear from the page here.

Sections come from node.sidebar and this drops the All SSW Services entry, so a card only renders if it carries a real tag. But 6 pages in content/consulting/index/index.json have no tags key at all, so they hit the : [allServices] fallback on line 64 and now match no section:

  • Azure DevOps to GitHub Migration
  • Odoo
  • Clean Architecture
  • Vertical Slice Architecture
  • ServiceNow
  • Chinafy - Mini Apps

They render today because selectedTag defaulted to allServices, so the old page showed them under their category heading.

Either tag those 6 in the content, or keep a fallback section for anything that matched nothing. Making tags required in the Tina schema would stop this recurring silently the next time someone adds a card.

Comment thread app/consulting/index.tsx
Comment on lines +70 to +76
const unique = new Map();
pages.forEach((page) => {
if (!unique.has(page.url)) {
unique.set(page.url, page);
}
return acc;
}, []);
}, [node, selectedTag]);

const tags = useMemo(
() =>
node.sidebar?.map((item) => {
return {
label: item.label,
name: item.tag?.name,
};
}),
[node]
});
return Array.from(unique.values());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dedup drops distinct cards, not duplicates.

Keying on url alone assumes one card per page reference, but two entries in the content share content/consulting/azure.mdx with different titles, descriptions, logos and tags:

  • "Azure AI" (tags: ai-development)
  • "Microsoft Azure" (tags: cloud-and-infrastructure, platform-development)

First-wins keeps Azure AI, so Microsoft Azure vanishes from both the Cloud and Infrastructure and Platform Development sections. Same shape for content/consulting/sharepoint.mdx: "SharePoint" wins and "Document Management" disappears from Other SSW Services.

Keying on the rendered card instead of the page (${page.url}|${page.title}) fixes it. Worth checking whether cross-category dedup is needed at all, since sections are tag-driven now rather than category-driven.

Comment thread app/consulting/index.tsx
setActiveSectionId(contentSections[0].sectionId);
}

const tagParam = searchParams.get("tag");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the ?tag= reader breaks two live links in the primary nav. content/megamenu/menu.json still points at:

  • line 38: /consulting?tag=Platform-Development
  • line 65: /consulting?tag=Cloud-and+Infrastructure

Both now land at the top of the page with no filter and no scroll. They want updating to the new anchors (/consulting#consulting-platform-development and /consulting#consulting-cloud-and-infrastructure).

These URLs are public and indexed, so a small ?tag= to hash shim on mount would also be worth it for anyone arriving from an external link or a bookmark.

Comment thread app/consulting/index.tsx
<Container className="flex-1 pt-2">
<div className="min-h-8 w-full max-w-9xl md:min-h-12">
<div
className={cn(styles.shell, isDark && "dark")}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shell gets .dark from React state only, and nothing renders HomeThemePrePaint inside it. HomeThemePrePaint only appears in HomeThemeShell (homepage) and in MegaMenuWrapper, so the menu is protected but this page is not: the SSR'd HTML ships with DEFAULT_THEME = "dark" and light-mode users will see the page body flip on hydration.

Wrapping the content in HomeThemeShell instead of hand-rolling the shell fixes the flash, and gets you bg-background text-foreground plus one theming mechanism rather than two (.dark and data-theme are both being set here).

Comment thread app/consulting/index.tsx
className={cn(styles.shell, isDark && "dark")}
data-theme={isDark ? "dark" : "light"}
>
<main className={styles.page}>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested <main>. components/layout/layout.tsx:130 already wraps every page in <main className="grow bg-white">, so this is a second main landmark inside the first, which isn't valid HTML. A div does the job here.

While you're in there: that outer wrapper's bg-white is unconditional, and .shell's min-height: 100% resolves against a flex grow parent, which is fragile. Worth a look in dark mode on a tall screen to confirm no white band shows below the last section.

Comment thread app/consulting/index.tsx
Comment on lines +164 to +165
<aside className={styles.sidebar}>
<h1 className={styles.heading}>Consulting Services</h1>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The page's <h1> is inside <aside>, so the document title sits in the complementary landmark and <main> has no heading at all. Screen reader users navigating by landmark will find "Consulting Services" under "complementary".

Moving the <h1> out to sit above the layout grid (or making this wrapper a plain div) sorts it.

Comment thread app/consulting/index.tsx
onClick={(event) =>
onNavClick(event, section.sectionId)
}
aria-current={isActive ? "true" : undefined}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For in-page anchor nav this should be aria-current="location" rather than "true". "true" is valid ARIA but it reads as "current item" generically; "location" is the value specified for "the current position within a flow", which is what the scroll-spy is expressing.

// choice or `prefers-color-scheme`.
const DEFAULT_THEME: HomeThemeMode = "dark";

const isThemedRoute = (pathname: string) =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The themed-route list now lives in three places that have to stay in sync: this helper, the PRE_PAINT_SCRIPT string on line 71, and a hand-rolled copy of the same OR in MegaMenuWrapper.tsx:21.

Exporting a single THEMED_ROUTES array, deriving both this helper and the script's inlined check from it, and importing isThemedRoute in MegaMenuWrapper would leave one place to edit when the next route gets themed.

// utilities (bg-background/text-foreground/border-hairline) flip via the CSS
// variables under the `dark` class on this element.
const isHome = pathName === "/";
const isHome = pathName === "/" || pathName === "/consulting";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isHome is no longer accurate now that it covers /consulting, and it's a duplicate of isThemedRoute in homeTheme.tsx:22 (not currently exported). Importing that helper and renaming this to isThemedRoute would keep the two lists from drifting apart, since this one also gates HomeThemePrePaint on line 38.

Comment thread styles.css
Comment on lines +188 to +189
--card: #101010;
--card-hover: #151515;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two are defined only in the dark block, with no light counterpart, unlike every token around them (--hairline is set in both, at line 108 and line 177). So bg-card resolves to nothing in light mode.

The only consumer today guards it (bg-white dark:bg-card), so nothing is visibly broken, but for a PR whose headline is shared card tokens it's worth adding the light values now. Otherwise the next person to reach for bg-card gets a transparent card and no clue why.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-issue This label is used to bypass the PR-Lint check. Should be used for non-coding changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants