Skip to content

Redesign: new editorial design language, drop tailwind for UnoCSS#71

Open
felipebalbi wants to merge 13 commits into
OpenDevicePartnership:mainfrom
felipebalbi:redesign
Open

Redesign: new editorial design language, drop tailwind for UnoCSS#71
felipebalbi wants to merge 13 commits into
OpenDevicePartnership:mainfrom
felipebalbi:redesign

Conversation

@felipebalbi
Copy link
Copy Markdown
Contributor

@felipebalbi felipebalbi commented May 15, 2026

Ground-up redesign of the marketing site. Drops the legacy Tailwind-based component zoo in favor of a small, opinionated design system built on tokens + UnoCSS (via the unocss-classes Rust crate). The voice is editorial and warm, the layout breathes from 320px up to 4K, and the chrome stays out of the way of the content.

Screenshots attached below.

What's new

Design language

  • Warm bone (#fbf8f4) page surface, deep ink (#161922) typography, sunset-amber (#d6651f) accent, deep blue-grey (#2d4a63) trust accent. Patina green is now a project-tag color only.
  • System serif fallback stack for the display face (Iowan Old Style → Palatino → Georgia). No new font shipped.
  • Full dark-mode token set with prefers-color-scheme fallback and a persistent data-theme toggle in the nav.
  • Capped content widths so 4K screens don't stretch lines past readable measure.
  • Reduced-motion respected throughout.

Information architecture

  • New / editorial hero → 3-pillar value proposition → project trio → trust block → CTA.
  • New /getting-started page (3-step onboarding).
  • Merged /contributing + /documentation_training → /community with anchored sections.
  • Project pages share one layout (ProjectLayout).
  • Partners section shrunk to a slim greyscale trust strip in the footer.
  • YouTube embed (and the large camera icon next to it) removed for now — happy to add back as a clean framed media block if desired.

Component primitives (src/components/)

  • theme.rs — ThemeProvider, ThemeToggle, persisted via localStorage.
  • layout.rs — Container, Section, Stack, Cluster, Grid, Surface.
  • typography.rs — Display, Heading, Lead, Body, Eyebrow, Mono.
  • controls.rs — Button, LinkButton, Tag, ArrowLink, InlineLink.
  • media.rs — theme-aware Logo, lucide Icon, BrandIcon.
  • nav.rs — slim sticky NavBar, mobile drawer (blur-only, no fill overlay), Footer.
  • cards.rs — ProjectCard (CSS-rendered colored monogram, no dark-only icon dependency), ValueCard, TeamCard, DocCard, TrustStrip, AnnouncementDetail.

Build pipeline

  • Tailwind removed (style/tailwind.css and tailwind.config.js deleted).
  • UnoCSS pre-build hook in Trunk.toml (cmd /C npm run build:css for cross-platform spawn) emits style/uno.generated.css.
  • unocss.config.ts maps semantic tokens (bg-surface-page, text-ink-primary, project tag inks, …) to the CSS vars in
    style/base.css.

Migration notes

  • data/teams.rs now owns the TeamMember struct (was in the deleted team_grid.rs).
  • web-sys opts into Storage + MediaQueryList features for the theme provider.
  • tests/wasm.rs rewritten around the new component surface and gated to #![cfg(target_arch = "wasm32")] so it doesn't show as dead code under native cargo test.

Verified

  • cargo check --target wasm32-unknown-unknown — ✅
  • cargo clippy --target wasm32-unknown-unknown --all-targets -- -D warnings — ✅
  • cargo clippy --all-targets -- -D warnings — ✅
  • cargo test --lib — 12/12 ✅
  • trunk build — ✅
  • Repo-wide dos2unix, leptosfmt src tests, cargo fmt applied as final pass.

felipebalbi and others added 6 commits May 15, 2026 14:35
Wires up the unocss-classes Rust crate (compile-time uno! macro)
together with the @unocss/cli Node package and a Trunk pre-build
hook so we can author class strings inline in view! blocks while
emitting a single tree-shaken stylesheet.

Pipeline:
  * Cargo.toml: add unocss-classes = "2".
  * package.json + package-lock.json: pin @unocss/cli, unocss,
    @unocss/preset-wind3 (Tailwind-compatible utilities, eases
    migration), @unocss/preset-icons (lucide collection), the
    variant-group + directives transformers, and the lucide icon
    set. CLI version family is 66.x.
  * unocss.config.ts: theme references the design tokens that will
    land in style/base.css (color, type, spacing, radius, shadow as
    var(--...)) so palette swaps don't require a CSS regenerate.
  * Trunk.toml [[hooks]]: pre-build step runs `npm run build:css`,
    which scans src/**/*.rs + index.html and emits
    style/uno.generated.css.
  * .gitignore: ignore /node_modules and the generated stylesheet.

Smoke: `npm run build:css` extracts 257 utilities from the existing
Tailwind classes already present in the source. wasm cargo check +
clippy are clean.

This commit only adds tooling -- no Rust source changes, no asset
graph changes. Tailwind is still wired up in index.html and will be
removed in a later commit once the new design language is in place.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sets up the new design language as CSS custom properties on :root
so the rest of the redesign can reference them via either UnoCSS
utilities (which resolve `bg-surface-page` to var(--color-surface-
page)) or raw CSS in repo_graph.css and similar legacy stylesheets.

Layout:
  1. Small opinionated reset (only what we actually need).
  2. @font-face for self-hosted Geist (already shipped in t48) and
     Fraunces variable serif (latin subset; r05 will commit the
     woff2 file itself).
  3. :root tokens for the light theme.
  4. [data-theme="dark"] and prefers-color-scheme: dark fallback
     for the no-flash first paint.
  5. prefers-reduced-motion override clamps animations + scroll.
  6. ::selection styling.

Design language (autonomous decisions, see plan.md):
  * Surfaces:  warm bone (#fbf8f4), white raised, blush sunken,
               ink-blue inverse.
  * Ink:       deep ink-blue (#161922) primary, calm secondary,
               soft muted, accent-ink for inline links.
  * Accent:    sunset amber (#d6651f) for CTAs, focus rings, and
               brand mark. Softer halo for hover/focus states.
  * Trust:     deep blue-grey (#2d4a63) for stability/security
               messaging (footer brand, project status badges).
  * Project tags:  Patina green moves here (project-tag colour
               only), joined by warm copper (EC) and dusty
               violet (Services).
  * Typography:  Geist for UI/body, Fraunces for editorial display.
                 Fluid clamp() type scale across all heading sizes.
  * Spacing:   4px base; section/gutter use clamp() so the layout
               breathes from 320px up to 4K.
  * Radius:    sm 6, md 10, lg 16, xl 24, pill.
  * Elevation: warm shadows with low alpha; dark theme uses higher
               alpha pure black.
  * Motion:    fast/base/slow timings + emphasized/standard/decel
               easings; everything goes flat under reduced-motion.

This commit only adds the file -- nothing references it yet. r05
will preload Fraunces and r06 will swap the index.html link from
tailwind to base.css + uno.generated.css.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This squashes the planned per-task commits (r04 through r24, plus the
final cleanup r34) into a single change because the components and
pages share so much surface area that splitting them per-task would
have produced a long sequence of intermediate states that did not
build. The redesign is opinionated and self-contained:

* Drop tailwind entirely; ship a hand-tuned `style/base.css` (design
  tokens, reset, type scale, dark mode, reduced motion) plus the
  unocss-generated utility sheet (`style/uno.generated.css`).
* New design system under `src/components/`:
  - `theme.rs` -- `ThemeProvider`, `ThemeToggle`, persisted to
    `localStorage`, syncs to `<html data-theme>`.
  - `layout.rs` -- `Container`, `Section`, `Stack`, `Cluster`,
    `Grid`, `Surface` primitives with capped widths so the layout
    breathes from 320px up to 4K.
  - `typography.rs` -- `Display`, `Heading`, `Lead`, `Body`,
    `Eyebrow`, `Mono`. Display uses a system serif fallback stack
    (Iowan Old Style -> Palatino -> Georgia) -- no extra font file
    shipped.
  - `controls.rs` -- `Button`, `LinkButton`, `Tag`, `ArrowLink`,
    `InlineLink`.
  - `media.rs` -- theme-aware `Logo`, lucide-backed `Icon`,
    `BrandIcon` for github/zulip/discord.
  - `nav.rs` -- slim sticky `NavBar`, mobile drawer, `Footer` with
    trust strip + 4-column link grid + copyright row.
  - `cards.rs` -- `ProjectCard`, `ValueCard`, `TeamCard`, `DocCard`,
    `TrustStrip`, `AnnouncementDetail`.
  - `project_layout.rs` -- shared layout for the three project pages.
* New pages under `src/pages/`: `home`, `projects`,
  `boot_firmware`, `embedded_controller`, `unified_ec_services`,
  `community` (merges contributing + docs/training + governance with
  anchored sections), `getting_started`, `announcements`,
  `not_found`, plus `team_page` shared layout used by `team_patina`,
  `team_ec`, `team_ec_services`.
* `lib.rs`: single `App` with `ThemeProvider` -> `Router`, flat
  routes wrapped by one `NavBar`/`Footer` chrome.
* Trim partners section to a slim trust strip in the footer.
* Drop the camera icon next to the YouTube embed and the embed
  itself for now -- to be re-added as a quiet framed media block if
  the new design lands.
* `data/teams.rs`: move `TeamMember` struct here (was in the
  deleted `team_grid.rs`).
* `Cargo.toml`: enable `Storage` + `MediaQueryList` features on
  `web-sys` for the theme provider.
* `Trunk.toml`: pre-build hook now invokes `cmd /C npm` so it works
  on Windows without `npm.cmd` shenanigans.
* `index.html`: drop tailwind link, add `base.css` +
  `uno.generated.css`.
* `tests/wasm.rs`: rewrite around the new component surface.

Final cleanup pass (r34): repo-wide `dos2unix`, `leptosfmt src tests`,
`cargo fmt`. Clippy and tests are green:

* `cargo check --target wasm32-unknown-unknown` -- ok
* `cargo clippy --target wasm32-unknown-unknown --all-targets -- -D warnings` -- ok
* `cargo test --lib` -- 12 passed
* `trunk build` -- ok

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… native cargo test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ght bg

* Mobile hamburger drawer now uses backdrop-blur only, no dark fill overlay.
* Added darker -ink variants for project tag colours so the tag label
  meets AA contrast against the soft 15%-alpha tag background in light
  mode. Dark mode keeps the original (already-bright) hue.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mode-only icon

The small project icons (P, EC, ES) were white-on-transparent assets
intended for dark mode; on the new light-mode landing page they were
unreadable on the soft thumbnail background.

Replace the <img> with a CSS-rendered monogram (P / EC / ES) coloured
with the project's -ink token on a 12%-alpha tint of the project hue.
This works in both themes without needing a light-mode icon set.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@felipebalbi felipebalbi requested a review from dhillonk May 15, 2026 22:14
@felipebalbi
Copy link
Copy Markdown
Contributor Author

This redesign MUST be carefully looked over before merging. Here are some screenshots, but it's much better to run the website locally:

odp_home odp_getting-started odp_projects odp_embedded-controller odp_community odp_announcements

@felipebalbi felipebalbi requested a review from jerrysxie May 15, 2026 22:17
felipebalbi and others added 2 commits May 15, 2026 15:19
The redesign moved CSS generation from a Tailwind binary to
@unocss/cli (invoked via npm by the Trunk pre_build hook). CI runners
did not have npm dependencies installed, so trunk build failed with
`unocss: not found`. Add a setup-node step + `npm ci` to both the
CI build job and the Cloudflare Pages deploy job, and update the asset
size budget to track `base-*.css` and `uno.generated-*.css` instead
of the now-deleted `tailwind-*.css`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The pre-build hook writes style/uno.generated.css. Trunk's watcher
sees the write and triggers another build, which runs the hook again,
looping forever during `trunk serve`. Add the generated CSS file (and
node_modules + dist for good measure) to [watch].ignore so the loop
breaks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

@williampMSFT williampMSFT left a comment

Choose a reason for hiding this comment

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

Seems fine to me, although I have basically no experience with web development so it's unclear how much my assessment is worth :) Would definitely recommend clearing it with Karan, not sure if there's any sort of branding/theming stuff we're going for that I'm not tracking

@felipebalbi
Copy link
Copy Markdown
Contributor Author

Seems fine to me, although I have basically no experience with web development so it's unclear how much my assessment is worth :) Would definitely recommend clearing it with Karan, not sure if there's any sort of branding/theming stuff we're going for that I'm not tracking

Yes, we definitely need Karan's and Massimo's review. Let's wait until they're back in town

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Ground-up redesign of the ODP marketing site, replacing the legacy Tailwind component set with a token-driven design system built on UnoCSS (generated via a Trunk pre-build hook) and introducing a persistent light/dark theme toggle.

Changes:

  • Added UnoCSS pipeline (config + Node tooling + Trunk hook) and new tokenized base stylesheet.
  • Replaced legacy page/components with new editorial layouts and primitives (layout/typography/controls/cards/nav/theme).
  • Refactored projects + teams into shared layouts and updated wasm integration tests accordingly.

Reviewed changes

Copilot reviewed 75 out of 79 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
unocss.config.ts Adds UnoCSS configuration mapping semantic utilities to CSS variables.
Trunk.toml Adds pre-build hook to generate UnoCSS bundle and ignores generated output to prevent rebuild loops.
tests/wasm.rs Rewrites browser-side wasm tests to cover new primitives/components.
tailwind.config.js Removes legacy Tailwind configuration.
style/tailwind.css Removes legacy Tailwind entry stylesheet and custom utility layer.
style/base.css Introduces CSS reset + design tokens + dark-mode tokens + reduced-motion handling.
src/pages/unified_ec_services.rs Switches EC Services page to shared ProjectLayout.
src/pages/team_patina.rs Switches Patina team page to shared TeamPage.
src/pages/team_page.rs Adds shared layout for team pages.
src/pages/team_ec.rs Switches Secure EC team page to shared TeamPage.
src/pages/team_ec_services.rs Switches EC Services team page to shared TeamPage.
src/pages/projects.rs Rebuilds /projects as an editorial page using new primitives/cards.
src/pages/not_found.rs Replaces simple 404 with redesigned Not Found page using new primitives.
src/pages/mod.rs Updates pages module exports to include new pages and ordering.
src/pages/home.rs Rebuilds / as editorial multi-section home page with new primitives.
src/pages/getting_started.rs Adds redesigned /getting-started page and “Step” cards.
src/pages/embedded_controller.rs Switches Embedded Controller page to shared ProjectLayout.
src/pages/community.rs Replaces legacy community content with anchored editorial sections.
src/pages/boot_firmware.rs Switches Boot Firmware page to shared ProjectLayout.
src/pages/announcements.rs Redesigns announcements into list + detail layout using new cards/primitives.
src/lib.rs Updates app chrome to new NavBar/Footer + theme provider and routes.
src/data/teams.rs Moves TeamMember struct ownership into data::teams.
src/data/projects.rs Adds monogram field for project cards and populates it for each project.
src/components/ui/value_prop_card.rs Removes legacy Tailwind-era ValuePropCard.
src/components/ui/two_column_intro.rs Removes legacy Tailwind-era TwoColumnIntro.
src/components/ui/text.rs Removes legacy Tailwind-era text primitive.
src/components/ui/stack.rs Removes legacy Tailwind-era stack primitive.
src/components/ui/project_tabs.rs Removes legacy Tailwind-era project tabs.
src/components/ui/mono.rs Removes legacy Tailwind-era mono primitive.
src/components/ui/mod.rs Removes legacy components::ui design system module.
src/components/ui/link.rs Removes legacy Tailwind-era link primitive.
src/components/ui/labeled_section.rs Removes legacy Tailwind-era labeled section helper.
src/components/ui/icon_block.rs Removes legacy Tailwind-era icon block primitive.
src/components/ui/heading.rs Removes legacy Tailwind-era heading primitive.
src/components/ui/grid.rs Removes legacy Tailwind-era grid primitive.
src/components/ui/doc_link_item.rs Removes legacy Tailwind-era doc link item.
src/components/ui/container.rs Removes legacy Tailwind-era container primitive.
src/components/ui/button.rs Removes legacy Tailwind-era button primitives.
src/components/ui/arrow_link.rs Removes legacy Tailwind-era arrow link implementation.
src/components/ui/announcement_card.rs Removes legacy announcements card wrapper.
src/components/typography.rs Adds new typography primitives backed by UnoCSS tokens.
src/components/themed_icon.rs Removes ThemedIcon picture-based theme icon helper.
src/components/theme.rs Adds theme provider/state + toggle + localStorage persistence.
src/components/team_hero.rs Removes legacy team hero component.
src/components/team_grid.rs Removes legacy team grid component.
src/components/site_shell.rs Removes legacy router-level chrome shells.
src/components/section.rs Removes legacy section/surface wrapper tied to Tailwind classes.
src/components/projects_component.rs Removes legacy projects page component implementation.
src/components/project_layout.rs Adds shared editorial layout for project pages (hero, facts, graph, other projects).
src/components/project_introduction.rs Removes legacy project introduction component.
src/components/partners_grid.rs Removes legacy partners grid from body layout.
src/components/partner.rs Removes legacy partner component.
src/components/nav.rs Adds new sticky nav + mobile drawer + redesigned footer.
src/components/mod.rs Replaces components module tree with new primitives-focused structure.
src/components/media.rs Adds theme-aware logo and icon helpers (lucide + brand icons).
src/components/main.rs Removes legacy home page main content (incl. YouTube embed).
src/components/layout.rs Adds layout primitives (Container/Section/Stack/Cluster/Grid/Surface).
src/components/landing/value_proposition_section.rs Removes legacy landing section implementation.
src/components/landing/projects_section.rs Removes legacy landing section implementation.
src/components/landing/mod.rs Removes legacy landing module.
src/components/landing/hero_section.rs Removes legacy landing section implementation.
src/components/landing/closing_columns_section.rs Removes legacy landing section implementation.
src/components/landing_page.rs Removes legacy landing page shell.
src/components/image_button.rs Removes legacy image-button component.
src/components/header.rs Removes legacy header implementation.
src/components/footer.rs Removes legacy footer implementation.
src/components/documentation_training.rs Removes legacy documentation/training footer block.
src/components/controls.rs Adds new button/link/tag primitives for the redesign.
src/components/community_teams.rs Removes legacy community page component.
src/components/cards.rs Adds new card components (Project/Value/Team/Doc/TrustStrip/AnnouncementDetail).
src/components/announce_banner.rs Removes legacy announcement banner.
package.json Adds Node devDependencies and scripts for UnoCSS generation.
index.html Switches from Tailwind CSS link to base + generated UnoCSS stylesheets.
Cargo.toml Adds unocss-classes and enables required web-sys features.
Cargo.lock Locks new Rust dependencies (unocss-classes and transitive crates).
.gitignore Ignores UnoCSS generated CSS and Node node_modules.
.github/workflows/cloudflare-pages-deploy.yaml Installs Node deps in deploy workflow so UnoCSS hook can run.
.github/workflows/ci.yaml Installs Node deps in CI and updates bundle-size checks for new CSS outputs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +85 to +103
/// `<a>` styled like a `Button`. Used for CTAs that navigate.
#[component]
pub fn LinkButton(
#[prop(into)] href: String,
#[prop(optional)] variant: ButtonVariant,
#[prop(optional)] size: ButtonSize,
#[prop(optional, default = false)] external: bool,
#[prop(into, optional)] class: String,
children: Children,
) -> impl IntoView {
let final_class = format!("{} {class}", button_classes(variant, size));
let target = if external { Some("_blank") } else { None };
let rel = if external { Some("noopener noreferrer") } else { None };
view! {
<a href=href class=final_class target=target rel=rel>
{children()}
</a>
}
}
Comment on lines +140 to +171
/// Inline link with an arrow affordance and underline-on-hover.
#[component]
pub fn ArrowLink(
#[prop(into)] href: String,
#[prop(optional, default = false)] external: bool,
#[prop(into, optional)] class: String,
children: Children,
) -> impl IntoView {
let target = if external { Some("_blank") } else { None };
let rel = if external { Some("noopener noreferrer") } else { None };
let final_class = format!(
"{} {class}",
uno!(
"group inline-flex items-baseline gap-2 text-ink-primary",
"border-b border-transparent hover:border-current",
"transition-colors duration-200"
)
);
view! {
<a href=href class=final_class target=target rel=rel>
<span>{children()}</span>
<span
class=uno![
"i-lucide-arrow-up-right w-4 h-4 text-ink-muted",
"group-hover:(text-accent translate-x-0.5 translate-y--0.5)",
"transition-transform duration-200"
]
aria-hidden="true"
></span>
</a>
}
}
Comment on lines +173 to +192
/// Plain inline link (underline on hover).
#[component]
pub fn InlineLink(
#[prop(into)] href: String,
#[prop(optional, default = false)] external: bool,
#[prop(into, optional)] class: String,
children: Children,
) -> impl IntoView {
let target = if external { Some("_blank") } else { None };
let rel = if external { Some("noopener noreferrer") } else { None };
let final_class = format!(
"{} {class}",
uno!("text-ink-accent underline decoration-from-font underline-offset-4 hover:decoration-2")
);
view! {
<a href=href class=final_class target=target rel=rel>
{children()}
</a>
}
}
Comment thread src/components/cards.rs
Comment on lines +136 to +163
<a
href=href
target=if external { Some("_blank") } else { None }
rel=if external { Some("noopener noreferrer") } else { None }
class="group block"
>
<Surface
tone=SurfaceTone::Outline
elevation=SurfaceElevation::Flat
class="h-full flex flex-col gap-3 group-hover:(border-border-accent bg-surface-raised) transition-colors duration-200"
>
<div class=uno!("flex items-start justify-between gap-3")>
<Heading level=HeadingLevel::H3 class="!text-h3">
{title}
</Heading>
<span
class=uno![
"i-lucide-arrow-up-right w-5 h-5 text-ink-muted flex-shrink-0",
"group-hover:(text-accent translate-x-0.5 translate-y--0.5) transition-transform duration-200"
]
aria-hidden="true"
></span>
</div>
<Body tone=BodyTone::Secondary class="!text-small">
{description}
</Body>
</Surface>
</a>
Comment thread src/components/media.rs
Comment on lines +6 to +20
/// Theme-aware ODP logo. Uses `<picture>` so the browser swaps
/// between the light and dark SVG without a JS round trip.
#[component]
pub fn Logo(#[prop(into, optional)] class: String) -> impl IntoView {
let class = if class.is_empty() {
uno!("h-8 md:h-10 w-auto").to_string()
} else {
class
};
view! {
<picture>
<source srcset="/images/dark/odplogo.svg" media="(prefers-color-scheme: dark)" />
<img src="/images/light/odplogo.svg" alt="Open Device Partnership" class=class />
</picture>
}
Comment thread src/components/theme.rs
Comment on lines +139 to +145
/// Direct DOM helper used by `App` to apply the *initial* theme as
/// early as possible -- before Leptos hydration -- to avoid a
/// "theme flash" on first paint. Safe to call from any context.
pub fn paint_initial_theme() {
let theme = read_initial_theme();
apply_theme(theme);
}
Comment thread src/pages/community.rs
Comment on lines +1 to +3
//! Community page (`/`) -- merges governance, contributing, docs &
//! training, and team directory into one editorial page with
//! anchored sections.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants