Skip to content

Commit a788285

Browse files
authored
feat(home): add an accessible pre-release banner (#6)
* feat(home): add an accessible pre-release banner Adds a status strip above the hero: v0.1.0-rc.1 is in final testing, with no download links yet, plus a defensive footer-links wrap. Refs #5 Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> * fix(home): drop the duplicate GitHub mention in the release banner The body sentence already said "follow progress on GitHub" right next to a link with the same label; the sentence no longer repeats what the link already says. Refs #5 Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> * feat(home): add decorative emoji to the H1 and section headings My call: the README's lite emoji treatment (one emoji per H1/H2, decorative, nothing in code blocks or below H2) carries over to this page's headings -- a lock on the hero title, and one icon per section chosen to match its own wording (anchor for "trust anchor", key for "your keys", satellite for the fleet of linked nodes). Each icon sits in its content object as a Unicode escape rather than a literal character, since this repo's hooks block emoji in source files outside Markdown/GitHub config; the escape still renders as the real character in the built page. Every icon is wrapped in an aria-hidden span so screen readers read only the heading text. Refs #5 Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> * fix(site): keep the hero headline to three lines The two-line headline was wrapping to four visual lines on narrow phones and again in the two-column desktop layout, where this column is well under half the viewport. Scaled the font-size with a container query (clamp against cqw on the hero's own copy column, not the viewport) instead of a vw-based clamp, since the viewport width was never the real constraint, and added text-wrap: balance so a wrapped line breaks evenly rather than leaving a short orphan. Also bound the leading emoji to the headline's first word in a white-space: nowrap span, so they can never land on separate lines. Checked with a headless-Chrome script in scratch (line count from the h1's rendered height over its line-height, and scrollWidth vs clientWidth for overflow) at 320, 360, 390, 768, 1024 and 1440px: 2 lines and no horizontal overflow at every width. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> * fix(site): put heading emoji after the text Moves each decorative emoji from the front of its heading to right after the word it pictures: the lock after "break into", the anchor after "anchor", the key after "keys", the satellite after "node". Added a shared splitTrailingWord helper (app/lib/text.ts) so both the hero H1 and the H2 Section headings can split off that last word and pair it with the emoji inside one white-space: nowrap span, still aria-hidden so screen readers only read the heading text. Re-checked the hero stays at or under 3 lines with the emoji now trailing: 3 lines at 320px, 2 lines everywhere else, no horizontal overflow at any of the 6 widths. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> * fix(site): restore full-width trust model and manager sections The trust-model and fleetos-manager sections each put a single block of prose in a 42rem-capped column with nothing beside it, so on a wide screen the visible content sat in roughly the left half of the container while the hero and the what-it-is feature grid used the full width. Added an opt-in Section layout="split": head and body side by side in the hero's own 1.1fr/0.9fr column ratio, stacking to one column at the existing 860px breakpoint. The prose still wraps at its own readable max-width inside that column; only the section's layout changed, not its copy. what-it-is keeps the default stacked layout -- its feature grid already fills the width on its own. Extended the scratch check to measure the true rendered content width of each section (via Range rects over its text, not a wrapper div's box, since a block wrapper stretches to its parent's width even when its content doesn't) against the hero's, at 1024 and 1440px (the widths above the two-column breakpoint; narrower widths already stack to one column everywhere). It failed before this change (roughly 200-320px short at those two widths) and passes now (within 100px, matching how two columns of ragged prose and card text naturally differ). Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> --------- Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> Co-authored-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
1 parent 989c3b0 commit a788285

7 files changed

Lines changed: 301 additions & 49 deletions

File tree

‎app/components/marketing.css‎

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,63 @@
8989
}
9090
}
9191

92+
/* Release banner */
93+
.release-banner {
94+
border-bottom: 1px solid hsl(var(--border));
95+
background: color-mix(in srgb, hsl(var(--warning)) 10%, hsl(var(--card)));
96+
}
97+
98+
.release-banner__row {
99+
display: flex;
100+
flex-wrap: wrap;
101+
align-items: center;
102+
gap: 0.5rem 1rem;
103+
padding-block: 0.75rem;
104+
border-inline-start: 4px solid hsl(var(--warning));
105+
}
106+
107+
.release-banner__badge {
108+
display: inline-flex;
109+
align-items: center;
110+
gap: 0.4rem;
111+
font-family: var(--font-mono);
112+
font-size: 0.75rem;
113+
font-weight: 700;
114+
letter-spacing: 0.08em;
115+
text-transform: uppercase;
116+
color: hsl(var(--foreground));
117+
white-space: nowrap;
118+
}
119+
120+
.release-banner__dot {
121+
width: 0.5rem;
122+
height: 0.5rem;
123+
border-radius: 50%;
124+
background: hsl(var(--warning));
125+
flex-shrink: 0;
126+
}
127+
128+
.release-banner__text {
129+
margin: 0;
130+
font-size: 0.875rem;
131+
color: hsl(var(--muted-foreground));
132+
}
133+
134+
.release-banner__text strong {
135+
color: hsl(var(--foreground));
136+
}
137+
138+
.release-banner__link {
139+
color: hsl(var(--primary));
140+
text-decoration: underline;
141+
text-underline-offset: 2px;
142+
}
143+
144+
.release-banner__link:hover,
145+
.release-banner__link:focus-visible {
146+
color: hsl(var(--foreground));
147+
}
148+
92149
/* Hero */
93150
.hero {
94151
position: relative;
@@ -138,6 +195,14 @@
138195
grid-template-columns: 1.1fr 0.9fr;
139196
}
140197

198+
.hero__copy {
199+
/* The headline's available width is this column's width, not the
200+
viewport's -- at the two-column desktop breakpoint this column is well
201+
under half the viewport. A container query (not a vw-based clamp) lets
202+
the headline size track its actual column. */
203+
container-type: inline-size;
204+
}
205+
141206
.hero__eyebrow {
142207
font-family: var(--font-mono);
143208
font-size: 0.875rem;
@@ -148,18 +213,33 @@
148213
}
149214

150215
.hero__headline {
151-
font-size: 3.25rem;
152-
line-height: 1.1;
216+
/* A floor for the smallest phones, a cqw-driven middle so it scales with
217+
this column's own width (see .hero__copy's container-type), a ceiling
218+
so it never grows past the original design size. Tuned so the two-line
219+
headline stays at or under 3 wrapped lines at every width this site is
220+
checked at, including the narrower column the two-column desktop
221+
layout leaves it. */
222+
font-size: clamp(1.25rem, 0.7rem + 3.3cqw, 2.6rem);
223+
line-height: 1.15;
153224
font-weight: 700;
154225
display: flex;
155226
flex-direction: column;
156227
margin-bottom: 1.5rem;
157228
}
158229

230+
.hero__headline span {
231+
text-wrap: balance;
232+
}
233+
159234
.hero__headline-accent {
160235
color: hsl(var(--primary));
161236
}
162237

238+
.hero__headline-icon {
239+
display: inline-block;
240+
white-space: nowrap;
241+
}
242+
163243
.hero__lede {
164244
font-size: 1.125rem;
165245
color: hsl(var(--muted-foreground));
@@ -184,10 +264,6 @@
184264
.hero__grid {
185265
grid-template-columns: 1fr;
186266
}
187-
188-
.hero__headline {
189-
font-size: 2.5rem;
190-
}
191267
}
192268

193269
/* Terminal card */
@@ -282,6 +358,28 @@
282358
margin-bottom: 2rem;
283359
}
284360

361+
/* Split layout: head and body side by side in the same column ratio as the
362+
hero's grid, so a section whose body is a single block of prose still
363+
uses the section's full width instead of sitting in a narrow column with
364+
empty space beside it. The prose itself still wraps at a readable width
365+
(its own max-width, below) inside that column. */
366+
.section__grid {
367+
display: grid;
368+
gap: 3rem;
369+
grid-template-columns: 1.1fr 0.9fr;
370+
align-items: start;
371+
}
372+
373+
.section__grid .section__head {
374+
margin-bottom: 0;
375+
}
376+
377+
@media (max-width: 860px) {
378+
.section__grid {
379+
grid-template-columns: 1fr;
380+
}
381+
}
382+
285383
.section__eyebrow {
286384
font-family: var(--font-mono);
287385
font-size: 0.875rem;
@@ -297,6 +395,11 @@
297395
line-height: 1.1;
298396
}
299397

398+
.section__heading-icon {
399+
display: inline-block;
400+
white-space: nowrap;
401+
}
402+
300403
.section__lede {
301404
margin-top: 0.75rem;
302405
color: hsl(var(--muted-foreground));
@@ -371,6 +474,7 @@
371474
.footer__links {
372475
list-style: none;
373476
display: flex;
477+
flex-wrap: wrap;
374478
gap: 1.5rem;
375479
margin: 0;
376480
padding: 0;

‎app/components/release-banner.tsx‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
Copyright The CryptOS Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
import "./marketing.css";
17+
18+
interface ReleaseBannerCta {
19+
label: string;
20+
href: string;
21+
}
22+
23+
interface ReleaseBannerProps {
24+
badge: string;
25+
heading: string;
26+
body: string;
27+
cta?: ReleaseBannerCta;
28+
}
29+
30+
// Base-layer component: a pre-release status strip shown above the hero.
31+
// Text stays in the foreground/muted-foreground pair against the card
32+
// background (both exceed WCAG AA contrast in light and dark) -- the
33+
// warning token is used only as a left accent border and the badge icon,
34+
// never as a text/background text pair, because `--warning-foreground` on
35+
// `--warning` falls short of AA for normal text in the light theme.
36+
export const ReleaseBanner = ({ badge, heading, body, cta }: ReleaseBannerProps) => (
37+
<div className="release-banner" role="note" aria-label="Release status">
38+
<div className="container release-banner__row">
39+
<span className="release-banner__badge">
40+
<span className="release-banner__dot" aria-hidden="true" />
41+
{badge}
42+
</span>
43+
<p className="release-banner__text">
44+
<strong>{heading}</strong> {body}{" "}
45+
{cta ? (
46+
<a className="release-banner__link" href={cta.href}>
47+
{cta.label}
48+
</a>
49+
) : null}
50+
</p>
51+
</div>
52+
</div>
53+
);

‎app/components/section.tsx‎

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,61 @@ limitations under the License.
1515
*/
1616
import type { ReactNode } from "react";
1717

18+
import { splitTrailingWord } from "~/lib/text";
19+
1820
import "./marketing.css";
1921

2022
interface SectionProps {
2123
id?: string;
2224
eyebrow?: string;
25+
icon?: string;
2326
heading: string;
2427
lede?: string;
2528
tone?: "default" | "card";
29+
// "stack": head above body, full container width (what-it-is's feature
30+
// grid already fills that width on its own). "split": head and body sit
31+
// side by side in the hero's own column ratio, for a section whose body
32+
// is a single block of prose that would otherwise sit in a narrow column
33+
// on the left with the rest of the wide container empty.
34+
layout?: "stack" | "split";
2635
children: ReactNode;
2736
}
2837

2938
// Base-layer component: a content section with an optional eyebrow/heading,
3039
// consistent width and vertical rhythm. Mirrors the Astro proof's
31-
// Section.astro.
40+
// Section.astro. `icon` is a single decorative character (an emoji escape
41+
// from the content file, never a literal emoji in this source) shown after
42+
// the heading's last word, wrapped aria-hidden so screen readers only read
43+
// the heading itself, and kept in a `white-space: nowrap` unit with that
44+
// word so the two can never land on separate lines.
3245
export const Section = ({
3346
id,
3447
eyebrow,
48+
icon,
3549
heading,
3650
lede,
3751
tone = "default",
52+
layout = "stack",
3853
children,
39-
}: SectionProps) => (
40-
<section id={id} className={tone === "card" ? "section section--card" : "section"}>
41-
<div className="container">
42-
<div className="section__head">
43-
{eyebrow ? <p className="section__eyebrow">{eyebrow}</p> : null}
44-
<h2 className="section__heading">{heading}</h2>
45-
{lede ? <p className="section__lede">{lede}</p> : null}
54+
}: SectionProps) => {
55+
const [headingLead, headingTail] = splitTrailingWord(heading);
56+
57+
return (
58+
<section id={id} className={tone === "card" ? "section section--card" : "section"}>
59+
<div className={layout === "split" ? "container section__grid" : "container"}>
60+
<div className="section__head">
61+
{eyebrow ? <p className="section__eyebrow">{eyebrow}</p> : null}
62+
<h2 className="section__heading">
63+
{headingLead}
64+
<span className="section__heading-icon">
65+
{headingTail}
66+
{icon ? <span aria-hidden="true"> {icon}</span> : null}
67+
</span>
68+
</h2>
69+
{lede ? <p className="section__lede">{lede}</p> : null}
70+
</div>
71+
<div className="section__body">{children}</div>
4672
</div>
47-
<div className="section__body">{children}</div>
48-
</div>
49-
</section>
50-
);
73+
</section>
74+
);
75+
};

0 commit comments

Comments
 (0)