more changes
- Restore the SHARP-ER in the homepage hero section - Match the design for the 3-image section in the homepage
This commit is contained in:
@@ -42,11 +42,17 @@ export interface BrandProps {
|
|||||||
/** Accessible name of the link; the images are decorative. */
|
/** Accessible name of the link; the images are decorative. */
|
||||||
label: string;
|
label: string;
|
||||||
surface: NavSurface;
|
surface: NavSurface;
|
||||||
|
/**
|
||||||
|
* Run the "-ER" reveal and its breathing loop. Off on the home page, whose
|
||||||
|
* hero plays the same reveal once - a second, looping copy beside it is
|
||||||
|
* jarring. Anywhere else the two animations run as designed.
|
||||||
|
*/
|
||||||
|
animate?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The SHARP-ER wordmark, linking home. Sits at the inline start of the bar. */
|
/** The SHARP-ER wordmark, linking home. Sits at the inline start of the bar. */
|
||||||
export function Brand({ href, label, surface, className }: BrandProps) {
|
export function Brand({ href, label, surface, animate = true, className }: BrandProps) {
|
||||||
const { base, suffix } = BRAND_LOCKUP[surface];
|
const { base, suffix } = BRAND_LOCKUP[surface];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -75,10 +81,12 @@ export function Brand({ href, label, surface, className }: BrandProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* The clipping box starts at the P's right edge, so the mark inside
|
{/* The clipping box starts at the P's right edge, so the mark inside
|
||||||
it can slide out from behind the P (Navigation.css). */}
|
it can slide out from behind the P (Navigation.css). Without
|
||||||
|
`animate` the box and the reveal classes are dropped and the full
|
||||||
|
lockup simply sits there. */}
|
||||||
<span
|
<span
|
||||||
style={{ flex: `${suffix.drawnWidth} 1 0%` }}
|
style={{ flex: `${suffix.drawnWidth} 1 0%` }}
|
||||||
className="sh-brand-suffix block min-w-0"
|
className={cx("block min-w-0", animate && "sh-brand-suffix")}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={suffix.src}
|
src={suffix.src}
|
||||||
@@ -88,7 +96,7 @@ export function Brand({ href, label, surface, className }: BrandProps) {
|
|||||||
loading="eager"
|
loading="eager"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
draggable={false}
|
draggable={false}
|
||||||
className="sh-brand-suffix__mark block h-auto w-full"
|
className={cx("block h-auto w-full", animate && "sh-brand-suffix__mark")}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -268,7 +268,12 @@ export function MobileNav({
|
|||||||
{/* Mirrors the compact bar it replaces (20:220): the same 64px row and
|
{/* Mirrors the compact bar it replaces (20:220): the same 64px row and
|
||||||
the same gutter, so the wordmark does not shift when it opens. */}
|
the same gutter, so the wordmark does not shift when it opens. */}
|
||||||
<div className="flex h-[64px] shrink-0 items-center justify-between gap-md px-[var(--layout-gutter)]">
|
<div className="flex h-[64px] shrink-0 items-center justify-between gap-md px-[var(--layout-gutter)]">
|
||||||
<Brand href={homeHref} label={strings.homeLinkLabel} surface={surface} />
|
<Brand
|
||||||
|
href={homeHref}
|
||||||
|
label={strings.homeLinkLabel}
|
||||||
|
surface={surface}
|
||||||
|
animate={!isCurrentPath(homeHref, pathname)}
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
ref={closeRef}
|
ref={closeRef}
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -34,7 +34,10 @@
|
|||||||
After that one-time reveal the mark keeps breathing: after a long rest
|
After that one-time reveal the mark keeps breathing: after a long rest
|
||||||
(~26s of stillness per cycle) it slides back out from behind the P and
|
(~26s of stillness per cycle) it slides back out from behind the P and
|
||||||
returns, so the wordmark quietly draws the eye without the glyphs moving
|
returns, so the wordmark quietly draws the eye without the glyphs moving
|
||||||
while they are visible. The two
|
while they are visible. On the home page none of this runs (Brand.tsx
|
||||||
|
drops the classes when the lockup links the page you are on): the hero
|
||||||
|
there plays the same reveal once, and a second, looping copy right above
|
||||||
|
it would be jarring. The two
|
||||||
animations are sequenced by delay - the reveal (once, `both` fill, so the
|
animations are sequenced by delay - the reveal (once, `both` fill, so the
|
||||||
suffix stays hidden through its delay instead of flashing in) hands over
|
suffix stays hidden through its delay instead of flashing in) hands over
|
||||||
to the loop, whose later position in the list wins from its start, and
|
to the loop, whose later position in the list wins from its start, and
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useMemo } from "react";
|
|||||||
import {
|
import {
|
||||||
buildLocalizedHref,
|
buildLocalizedHref,
|
||||||
getDirection,
|
getDirection,
|
||||||
|
isCurrentPath,
|
||||||
LanguageToggle,
|
LanguageToggle,
|
||||||
type Locale,
|
type Locale,
|
||||||
} from "../LanguageSwitcher";
|
} from "../LanguageSwitcher";
|
||||||
@@ -114,6 +115,9 @@ export function Navigation({
|
|||||||
href={resolvedHomeHref}
|
href={resolvedHomeHref}
|
||||||
label={strings.homeLinkLabel}
|
label={strings.homeLinkLabel}
|
||||||
surface={surface}
|
surface={surface}
|
||||||
|
/* The home page's own hero plays the reveal once; the brand beside
|
||||||
|
it stays still there instead of running a second copy. */
|
||||||
|
animate={!isCurrentPath(resolvedHomeHref, pathname)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DesktopNav
|
<DesktopNav
|
||||||
|
|||||||
@@ -203,22 +203,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A picture pushing in a little while the frame around it is hovered. The
|
/* A gallery tile that grows as a WHOLE frame instead of pushing the picture
|
||||||
frame clips, so the picture grows into its own box and not over the page.
|
inside it - the draft hovers the frame, not the image. It keeps `.sh-lift`'s
|
||||||
Paired with .sh-lift the tile rises and its picture closes in, which is
|
rise and adds a uniform scale to it. Written after `.sh-lift:hover` so the
|
||||||
how the draft hovers the gallery. */
|
compound transform wins at equal specificity; `.sh-lift`'s transition
|
||||||
.sh-zoom {
|
already covers `transform`, so the two move together. */
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sh-zoom img {
|
|
||||||
transition: transform var(--motion-duration-slow)
|
|
||||||
var(--motion-ease-standard);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.sh-zoom:hover img {
|
.sh-lift--grow:hover {
|
||||||
transform: scale(1.09);
|
transform: translateY(-5px) scale(1.04);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,10 +89,47 @@ import {
|
|||||||
PageHero,
|
PageHero,
|
||||||
PageSection,
|
PageSection,
|
||||||
SectionHeader,
|
SectionHeader,
|
||||||
|
type PageHeroWordmark,
|
||||||
} from "./_page-kit/sections";
|
} from "./_page-kit/sections";
|
||||||
|
|
||||||
import "./_page-kit/motion.css";
|
import "./_page-kit/motion.css";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hero lockup above the headline: the full SHARP-ER pair at the SVGs' own
|
||||||
|
* box (218 + 112 wide), picked per surface the way the navigation's brand
|
||||||
|
* picks it (Brand.tsx) - white "inverse" exports on the dark hero, the ink
|
||||||
|
* pair on a light one. `alt` is empty: the sticky bar's brand link already
|
||||||
|
* carries the accessible project name.
|
||||||
|
*/
|
||||||
|
const HERO_WORDMARK: Record<AppTheme, PageHeroWordmark> = {
|
||||||
|
dark: {
|
||||||
|
src: "/assets/sharp-wordmark-inverse.svg",
|
||||||
|
alt: "",
|
||||||
|
width: 218,
|
||||||
|
height: 46,
|
||||||
|
drawnWidth: 218,
|
||||||
|
suffix: {
|
||||||
|
src: "/assets/sharp-er-suffix-inverse.svg",
|
||||||
|
width: 112,
|
||||||
|
height: 46,
|
||||||
|
drawnWidth: 112,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
src: "/assets/sharp-wordmark.svg",
|
||||||
|
alt: "",
|
||||||
|
width: 218,
|
||||||
|
height: 46,
|
||||||
|
drawnWidth: 218,
|
||||||
|
suffix: {
|
||||||
|
src: "/assets/sharp-er-suffix.svg",
|
||||||
|
width: 112,
|
||||||
|
height: 46,
|
||||||
|
drawnWidth: 112,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/** The network logo wall - a wide but finite grid. */
|
/** The network logo wall - a wide but finite grid. */
|
||||||
const PARTNER_PAGE_SIZE = 36;
|
const PARTNER_PAGE_SIZE = 36;
|
||||||
/**
|
/**
|
||||||
@@ -312,24 +349,70 @@ function StatusPill({ label }: { label: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The approach band's three pictures. The CMS alt text doubles as the visible
|
* The approach band's pictures. The CMS alt text doubles as the visible
|
||||||
* caption - so the picture is described once, in the caption, and the <img>
|
* caption - so the picture is described once, in the caption, and the <img>
|
||||||
* itself is marked decorative rather than having a screen reader read the same
|
* itself is marked decorative rather than having a screen reader read the same
|
||||||
* sentence twice.
|
* sentence twice.
|
||||||
*
|
*
|
||||||
* The frame and the picture hover apart, as the third draft has them: the
|
* The frame and the picture move as one: the frame lifts and takes the accent
|
||||||
* frame lifts and takes the accent hairline (`sh-lift`) while the picture
|
* hairline (`sh-lift`) and the whole tile grows under the pointer
|
||||||
* closes in inside it (`sh-zoom`, which also does the clipping).
|
* (`sh-lift--grow`) - the draft grows the frame, it does not push the picture
|
||||||
|
* inside it.
|
||||||
|
*
|
||||||
|
* THREE PICTURES RENDER AS THE DRAFT'S MOSAIC (figma 559:4089): the middle
|
||||||
|
* tile dominates - 605px wide, full height, full opacity - and the two flanks
|
||||||
|
* sit shorter beside it (427/426px wide, 0.72 of its height) at 50% opacity,
|
||||||
|
* vertically centred on the middle. One or two pictures fall back to the
|
||||||
|
* uniform grid, where the mosaic's "faded flank" reading has no partner.
|
||||||
*/
|
*/
|
||||||
function GalleryShots({ images }: { images: readonly ResolvedImage[] }) {
|
function GalleryShots({ images }: { images: readonly ResolvedImage[] }) {
|
||||||
if (images.length === 0) return null;
|
if (images.length === 0) return null;
|
||||||
|
|
||||||
|
const mosaic = images.length === 3;
|
||||||
|
|
||||||
|
if (mosaic) {
|
||||||
|
return (
|
||||||
|
/* The track widths are the draft's own drawing (605 centre, ~427/426
|
||||||
|
flanks); the aspect ratios keep each tile at its drawn proportions, so
|
||||||
|
the row only reads as a mosaic from lg up and stacks full-width, full
|
||||||
|
opacity below it. `lg:items-center` sits the shorter flanks level with
|
||||||
|
the middle one, as the draft has them. gap-3xl is the closest step to
|
||||||
|
the draft's 60px. */
|
||||||
|
<ul className="grid list-none grid-cols-1 gap-3xl lg:grid-cols-[427fr_605fr_426fr] lg:items-center">
|
||||||
|
{images.map((image, index) => (
|
||||||
|
<li key={image.src} className="sh-reveal">
|
||||||
|
<figure className="flex flex-col gap-xs">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"sh-lift sh-lift--grow rounded-control border border-[var(--border-subtle)] hover:border-[var(--border-accent)]",
|
||||||
|
index === 1 ? "aspect-[605/310]" : "aspect-[427/228] lg:opacity-50",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={image.src}
|
||||||
|
alt=""
|
||||||
|
loading="lazy"
|
||||||
|
className="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{image.alt ? (
|
||||||
|
<figcaption className="body-sm-bold text-[var(--text-secondary)]">
|
||||||
|
{image.alt}
|
||||||
|
</figcaption>
|
||||||
|
) : null}
|
||||||
|
</figure>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="grid list-none grid-cols-1 gap-lg sm:grid-cols-2 lg:grid-cols-3">
|
<ul className="grid list-none grid-cols-1 gap-lg sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{images.map((image) => (
|
{images.map((image) => (
|
||||||
<li key={image.src} className="sh-reveal">
|
<li key={image.src} className="sh-reveal">
|
||||||
<figure className="flex flex-col gap-xs">
|
<figure className="flex flex-col gap-xs">
|
||||||
<div className="sh-lift sh-zoom rounded-control border border-[var(--border-subtle)] hover:border-[var(--border-accent)]">
|
<div className="sh-lift sh-lift--grow rounded-control border border-[var(--border-subtle)] hover:border-[var(--border-accent)]">
|
||||||
<img
|
<img
|
||||||
src={image.src}
|
src={image.src}
|
||||||
alt=""
|
alt=""
|
||||||
@@ -497,7 +580,8 @@ export default function Home({ loaderData }: Route.ComponentProps) {
|
|||||||
reverse: true,
|
reverse: true,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
reveal
|
reveal
|
||||||
|
wordmark={HERO_WORDMARK[theme]}
|
||||||
/* EU visibility sits at the top of the hero (the draft's badge). The
|
/* EU visibility sits at the top of the hero (the draft's badge). The
|
||||||
emblem matches the page surface the hero stands on, which the theme
|
emblem matches the page surface the hero stands on, which the theme
|
||||||
cookie decides - dark emblem in dark mode, light emblem in light
|
cookie decides - dark emblem in dark mode, light emblem in light
|
||||||
@@ -521,11 +605,17 @@ export default function Home({ loaderData }: Route.ComponentProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* WHAT THE PROJECT IS, AND WHO DELIVERS IT */}
|
{/* WHAT THE PROJECT IS, AND WHO DELIVERS IT */}
|
||||||
|
{/* `bleed` drops the 80rem Container so the band's content - the three
|
||||||
|
pictures especially - runs the full width of the viewport, the way
|
||||||
|
the draft draws the mosaic. The gutter the Container used to add is
|
||||||
|
re-applied here, and the header column still constrains itself to its
|
||||||
|
own measure, so only the gallery actually strays to the edges. */}
|
||||||
<PageSection
|
<PageSection
|
||||||
aria-labelledby="home-approach-heading"
|
aria-labelledby="home-approach-heading"
|
||||||
band
|
band
|
||||||
|
bleed
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-3xl">
|
<div className="flex w-full flex-col gap-3xl px-[var(--layout-gutter)] lg:px-[var(--layout-gutter-lg)]">
|
||||||
<div className="sh-reveal mx-auto flex max-w-[55rem] flex-col items-center gap-lg text-center">
|
<div className="sh-reveal mx-auto flex max-w-[55rem] flex-col items-center gap-lg text-center">
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
id="home-approach-heading"
|
id="home-approach-heading"
|
||||||
|
|||||||
Reference in New Issue
Block a user