Initial commit 🚀
This commit is contained in:
commit
fd58a8d32f
235 files changed
+19775
No files matched your search
@@ -0,0 +1,21 @@
|
||||
---
|
||||
category: Controls
|
||||
---
|
||||
|
||||
# Button
|
||||
|
||||
The filled action. Ink on paper, and paper on ink inside a night surface, which it handles
|
||||
itself. There is no accent colour and no secondary button variant: a second action on the
|
||||
screen is a TextLink.
|
||||
|
||||
Pass `href` for navigation and it renders an anchor. Omit it for an in-page action.
|
||||
|
||||
The label names the action. "Talk to us", "Explore the method", "Request a verification".
|
||||
Never "Learn more", and never an arrow inside the label.
|
||||
|
||||
```jsx
|
||||
<Button href="/contact">Talk to us</Button>
|
||||
<Button size="small">Copy the report</Button>
|
||||
```
|
||||
|
||||
Astro equivalent: `<a class="button" href="/contact">`.
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { cx } from './cx';
|
||||
|
||||
export interface ButtonProps {
|
||||
/** Renders an anchor when set, a button element otherwise. */
|
||||
href?: string;
|
||||
size?: 'default' | 'small';
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** A filled action. Its label names the action: "Talk to us", never "Learn more". */
|
||||
export function Button({ href, size = 'default', children }: ButtonProps) {
|
||||
const className = cx('button', size === 'small' && 'button--small');
|
||||
if (href) {
|
||||
return (
|
||||
<a className={className} href={href}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<button className={className} type="button">
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
category: Layout
|
||||
---
|
||||
|
||||
# Col
|
||||
|
||||
One block on the grid. `span` is how many of the twelve columns it occupies; `start` is the
|
||||
column it begins at, counting from 1.
|
||||
|
||||
Use `start` to leave a gap. An offset block is the house style: content that begins at
|
||||
column 7 or 9 rather than immediately after its neighbour reads as deliberate, and the empty
|
||||
columns are doing work.
|
||||
|
||||
A full-width block is `span={12}`, which is also the default.
|
||||
|
||||
```jsx
|
||||
<Col span={5} start={8}>
|
||||
<Text measure>Every interface between components is an attack surface.</Text>
|
||||
</Col>
|
||||
```
|
||||
|
||||
Astro equivalent: `<div style="grid-column: 8 / span 5;">`. Grid positions are the one place
|
||||
a raw number is allowed; every other value comes from a token.
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface ColProps {
|
||||
/** How many of the twelve columns this block occupies. */
|
||||
span?: number;
|
||||
/** The column it starts at, counting from 1. Offsets are how asymmetry is made. */
|
||||
start?: number;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** One block on the grid. A headline takes 7 or 8 columns, its body copy 4 or 5. */
|
||||
export function Col({ span = 12, start, children }: ColProps) {
|
||||
return <div style={{ gridColumn: start ? `${start} / span ${span}` : `span ${span}` }}>{children}</div>;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
category: Layout
|
||||
---
|
||||
|
||||
# Container
|
||||
|
||||
Centres content to the page width and holds the page margins. Every band of text sits in
|
||||
one. The only things that may escape it are full-bleed images and video.
|
||||
|
||||
Container and Grid are separate on purpose: a band with a single column of text needs the
|
||||
Container and no grid at all.
|
||||
|
||||
```jsx
|
||||
<Container>
|
||||
<Grid>…</Grid>
|
||||
</Container>
|
||||
```
|
||||
|
||||
Astro equivalent: `<div class="container">`.
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface ContainerProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** Centres content to the page width and holds the page margins. */
|
||||
export function Container({ children }: ContainerProps) {
|
||||
return <div className="container">{children}</div>;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
category: Type
|
||||
---
|
||||
|
||||
# Display
|
||||
|
||||
The largest type on the page, and the one place the page raises its voice. One per screen,
|
||||
in the hero.
|
||||
|
||||
Break its lines by hand with `<br />`. A display headline is a composed object, not a
|
||||
paragraph that happens to be large, and where the line turns changes how it reads.
|
||||
|
||||
It renders an `h1` by default. Keep one `h1` per page; if the hero headline is not the
|
||||
page's heading, set `as="p"`.
|
||||
|
||||
```jsx
|
||||
<Display>
|
||||
Security engineering<br />for critical software.
|
||||
</Display>
|
||||
```
|
||||
|
||||
Astro equivalent: `<h1 class="display">`.
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface DisplayProps {
|
||||
/** h1 by default. One h1 per page. */
|
||||
as?: 'h1' | 'h2' | 'p';
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** The largest type on the page. Break its lines by hand; it is a composed object. */
|
||||
export function Display({ as: Tag = 'h1', children }: DisplayProps) {
|
||||
return <Tag className="display">{children}</Tag>;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
category: Type
|
||||
---
|
||||
|
||||
# Eyebrow
|
||||
|
||||
A short label above a headline, in sentence case, muted, at small size.
|
||||
|
||||
Use it only when the section genuinely needs naming. An eyebrow above every heading is an
|
||||
anti-pattern here, and it must never be set in all caps.
|
||||
|
||||
```jsx
|
||||
<Eyebrow>Insights</Eyebrow>
|
||||
<Heading>What a verification report owes you.</Heading>
|
||||
```
|
||||
|
||||
Astro equivalent: `<p class="eyebrow">`.
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface EyebrowProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** A label above a headline, in sentence case. Never all caps, and never decorative. */
|
||||
export function Eyebrow({ children }: EyebrowProps) {
|
||||
return <p className="eyebrow">{children}</p>;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
category: Layout
|
||||
---
|
||||
|
||||
# Grid
|
||||
|
||||
The twelve-column grid. It is the tool for asymmetry, which is how this site composes a
|
||||
band: a headline across 7 or 8 columns, its supporting copy across 4 or 5, offset against
|
||||
each other rather than centred or evenly split.
|
||||
|
||||
Place children with Col. Do not nest a Grid inside a Col unless the inner block genuinely
|
||||
has its own twelve-column structure.
|
||||
|
||||
```jsx
|
||||
<Grid>
|
||||
<Col span={7}><Heading>Security is built in, not bolted on.</Heading></Col>
|
||||
<Col span={4} start={9}><Text measure>…</Text></Col>
|
||||
</Grid>
|
||||
```
|
||||
|
||||
Astro equivalent: `<div class="grid">`.
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface GridProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** The twelve-column grid. Place children with Col, asymmetrically. */
|
||||
export function Grid({ children }: GridProps) {
|
||||
return <div className="grid">{children}</div>;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
category: Type
|
||||
---
|
||||
|
||||
# Heading
|
||||
|
||||
A section headline. `level={2}` is the headline of a band, `level={3}` a block inside it.
|
||||
The level sets both the element and the size, so the document outline and the visual
|
||||
hierarchy cannot drift apart. Never skip a level.
|
||||
|
||||
A headline may state a claim. It never advertises, and it ends with a full stop.
|
||||
Contrast pairs carry a lot of weight here: "Verifiable, not asserted."
|
||||
|
||||
```jsx
|
||||
<Heading>Four stages. One method.</Heading>
|
||||
<Heading level={3}>Threat analysis</Heading>
|
||||
```
|
||||
|
||||
Astro equivalent: `<h2 class="h2">` and `<h3 class="h3">`.
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface HeadingProps {
|
||||
/** 2 for a section headline, 3 for a block within it. Never skip a level. */
|
||||
level?: 2 | 3;
|
||||
id?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** A section headline. It may state a claim, but it never advertises. */
|
||||
export function Heading({ level = 2, id, children }: HeadingProps) {
|
||||
const Tag = level === 3 ? 'h3' : 'h2';
|
||||
return (
|
||||
<Tag id={id} className={level === 3 ? 'h3' : 'h2'}>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
category: Content
|
||||
---
|
||||
|
||||
# Prose
|
||||
|
||||
Long-form text: an insight, a legal page, anything written as a document rather than
|
||||
composed as a layout. It styles the headings, lists, quotes, links and figures inside it and
|
||||
caps the measure, so the markup underneath stays plain.
|
||||
|
||||
Use it for a block of authored copy. Do not use it as a general text wrapper; a single
|
||||
paragraph in a layout is a Text.
|
||||
|
||||
```jsx
|
||||
<Prose>
|
||||
<h2>What a verification report owes you</h2>
|
||||
<p>Every finding names the file, the line and the reasoning.</p>
|
||||
<ul><li>Reproduction steps</li><li>The fix we would ship</li></ul>
|
||||
</Prose>
|
||||
```
|
||||
|
||||
Astro equivalent: `<div class="prose">`.
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface ProseProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** Long-form text: an insight, a legal page. Styles the headings and lists inside it. */
|
||||
export function Prose({ children }: ProseProps) {
|
||||
return <div className="prose">{children}</div>;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
category: Content
|
||||
---
|
||||
|
||||
# Rule
|
||||
|
||||
A hairline. It separates, and it is allowed only where the separation carries information:
|
||||
between the items of a list that genuinely divide, or under the header.
|
||||
|
||||
There are no shadows and no borders around cards in this system. Separation comes from a
|
||||
hairline or from a change of surface, and nothing else.
|
||||
|
||||
```jsx
|
||||
<Rule />
|
||||
```
|
||||
|
||||
Astro equivalent: `<hr class="rule">`.
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface RuleProps {
|
||||
/** Nothing. A rule separates; it never decorates. */
|
||||
children?: never;
|
||||
}
|
||||
|
||||
/** A hairline. Use it only where it carries information. */
|
||||
export function Rule(_props: RuleProps) {
|
||||
return <hr className="rule" />;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
category: Layout
|
||||
---
|
||||
|
||||
# Section
|
||||
|
||||
One horizontal band of the page. It carries the vertical air above and below its content,
|
||||
which is the single biggest reason the site reads as composed rather than busy. Put a
|
||||
Container inside it; never put page margins on the Section itself.
|
||||
|
||||
`spacing="tight"` for a subordinate band, `spacing="intro"` for a page head, where the air
|
||||
above is full and the air below is reduced so the content follows the title.
|
||||
|
||||
`tone="night"` makes this band a night surface and flips the ink tokens inside it.
|
||||
|
||||
```jsx
|
||||
<Section tone="night" spacing="tight">
|
||||
<Container>
|
||||
<Heading>Verifiable, not asserted.</Heading>
|
||||
</Container>
|
||||
</Section>
|
||||
```
|
||||
|
||||
Astro equivalent: `<section class="section section--tight night">`.
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { cx } from './cx';
|
||||
|
||||
export interface SectionProps {
|
||||
/** Paper is the default. Night is a dramatic device: the hero and at most one more. */
|
||||
tone?: 'paper' | 'night';
|
||||
/** `tight` for a subordinate band, `intro` for a page head. */
|
||||
spacing?: 'default' | 'tight' | 'intro';
|
||||
id?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** One horizontal band of the page, carrying the vertical air around its content. */
|
||||
export function Section({ tone = 'paper', spacing = 'default', id, children }: SectionProps) {
|
||||
return (
|
||||
<section
|
||||
id={id}
|
||||
className={cx(
|
||||
'section',
|
||||
spacing === 'tight' && 'section--tight',
|
||||
spacing === 'intro' && 'section--intro',
|
||||
tone === 'night' && 'night',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
category: Layout
|
||||
---
|
||||
|
||||
# Stack
|
||||
|
||||
Vertical rhythm between blocks. Nothing in this system carries its own margins, so two
|
||||
headings placed next to each other sit flush; Stack is what separates them.
|
||||
|
||||
`gap` is a step on the space scale, 1 to 9. It is never a raw pixel value: hard-coded
|
||||
spacing is a defect in this system, and this component is how you avoid one.
|
||||
|
||||
`align="start"` keeps children at their natural width, which is what a button or a link
|
||||
wants. The default stretches them to the column.
|
||||
|
||||
```jsx
|
||||
<Stack gap={6}>
|
||||
<Heading>Four stages. One method.</Heading>
|
||||
<Text measure>Threat analysis, engineering, verification and monitoring.</Text>
|
||||
<Stack gap={4} align="start">
|
||||
<Button href="/contact">Talk to us</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
```
|
||||
|
||||
Astro equivalent: `<div style="display: grid; gap: var(--space-6);">`.
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface StackProps {
|
||||
/** A step on the space scale, 1 (tightest) to 9. Defaults to 5. */
|
||||
gap?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
/** `start` keeps children at their natural width, which is what buttons want. */
|
||||
align?: 'start' | 'stretch';
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** Vertical rhythm between blocks, always from the space scale. Never a raw pixel gap. */
|
||||
export function Stack({ gap = 5, align = 'stretch', children }: StackProps) {
|
||||
return (
|
||||
<div style={{ display: 'grid', gap: `var(--space-${gap})`, justifyItems: align === 'start' ? 'start' : undefined }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
category: Foundations
|
||||
---
|
||||
|
||||
# Surface
|
||||
|
||||
The root of every screen. It sets the ground and the base type, so put it outermost and
|
||||
put everything else inside it. Without it a design inherits the canvas defaults and none
|
||||
of CipherCell's typography.
|
||||
|
||||
`tone="paper"` is the base surface for the whole site, a warm paper tone, never pure white.
|
||||
`tone="night"` flips the ink tokens as well as the background, so muted text, hairlines and
|
||||
buttons inside it correct themselves with nothing to set.
|
||||
|
||||
Night is a dramatic device. A page gets the hero and at most one other night band; more than
|
||||
that and the move stops working.
|
||||
|
||||
```jsx
|
||||
<Surface tone="paper">
|
||||
<Section>…</Section>
|
||||
</Surface>
|
||||
```
|
||||
|
||||
In the Astro site this is the `body` element, styled in `src/styles/base.css`.
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface SurfaceProps {
|
||||
/** Warm paper, the base surface for the whole site, or the rare night surface. */
|
||||
tone?: 'paper' | 'night';
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** The root of every screen: sets the paper or night ground and the base type. */
|
||||
export function Surface({ tone = 'paper', children }: SurfaceProps) {
|
||||
return (
|
||||
<div
|
||||
className={tone === 'night' ? 'night' : undefined}
|
||||
style={{
|
||||
fontFamily: 'var(--font-sans)',
|
||||
fontSize: 'var(--step-body)',
|
||||
lineHeight: 'var(--leading-body)',
|
||||
fontWeight: 'var(--weight-regular)',
|
||||
background: tone === 'night' ? undefined : 'var(--paper)',
|
||||
color: tone === 'night' ? undefined : 'var(--ink)',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
category: Type
|
||||
---
|
||||
|
||||
# Text
|
||||
|
||||
Body copy and metadata. Set `measure` on every paragraph: it caps the line at 68 characters,
|
||||
and long unmeasured lines are the fastest way to make this layout look careless.
|
||||
|
||||
`muted` is for secondary text and metadata. `size="small"` is for metadata and captions.
|
||||
Neither is a way to fit more in.
|
||||
|
||||
Write short declarative sentences with a full stop. Concrete nouns over categories.
|
||||
|
||||
```jsx
|
||||
<Text measure>Threat models are written once and then run in the pipeline.</Text>
|
||||
<Text size="small" muted>Passau, Innstr. 43</Text>
|
||||
```
|
||||
|
||||
Astro equivalent: `<p class="body measure">`, `<p class="small muted">`.
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { cx } from './cx';
|
||||
|
||||
export interface TextProps {
|
||||
size?: 'body' | 'small';
|
||||
/** Secondary text and metadata. */
|
||||
muted?: boolean;
|
||||
/** Caps the line at a readable measure. Use it on every paragraph. */
|
||||
measure?: boolean;
|
||||
as?: 'p' | 'span' | 'div';
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** Body copy and metadata. Short declarative sentences, with a full stop. */
|
||||
export function Text({ size = 'body', muted, measure, as: Tag = 'p', children }: TextProps) {
|
||||
return (
|
||||
<Tag className={cx(size === 'small' ? 'small' : 'body', muted && 'muted', measure && 'measure')}>{children}</Tag>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
category: Controls
|
||||
---
|
||||
|
||||
# TextLink
|
||||
|
||||
An inline or standalone link. The underline carries the signal, and it deepens slightly on
|
||||
hover; there is no colour change, because the system has no accent colour.
|
||||
|
||||
This is the right control for a secondary action next to a Button, and for any link inside
|
||||
a sentence.
|
||||
|
||||
```jsx
|
||||
<TextLink href="/method">Explore the method</TextLink>
|
||||
```
|
||||
|
||||
Astro equivalent: `<a class="link" href="/method">`.
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface TextLinkProps {
|
||||
href: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** An underlined link. There is no accent colour; the underline carries the signal. */
|
||||
export function TextLink({ href, children }: TextLinkProps) {
|
||||
return (
|
||||
<a className="link" href={href}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Joins class names, dropping anything falsy. Every class this package emits is
|
||||
// a primitive that already exists in the site's own stylesheet - these
|
||||
// components add no CSS of their own, so the look can never drift from the site.
|
||||
export const cx = (...parts: Array<string | false | null | undefined>): string =>
|
||||
parts.filter(Boolean).join(' ');
|
||||
@@ -0,0 +1,28 @@
|
||||
export { Surface } from './Surface';
|
||||
export type { SurfaceProps } from './Surface';
|
||||
export { Stack } from './Stack';
|
||||
export type { StackProps } from './Stack';
|
||||
export { Section } from './Section';
|
||||
export type { SectionProps } from './Section';
|
||||
export { Container } from './Container';
|
||||
export type { ContainerProps } from './Container';
|
||||
export { Grid } from './Grid';
|
||||
export type { GridProps } from './Grid';
|
||||
export { Col } from './Col';
|
||||
export type { ColProps } from './Col';
|
||||
export { Display } from './Display';
|
||||
export type { DisplayProps } from './Display';
|
||||
export { Heading } from './Heading';
|
||||
export type { HeadingProps } from './Heading';
|
||||
export { Text } from './Text';
|
||||
export type { TextProps } from './Text';
|
||||
export { Eyebrow } from './Eyebrow';
|
||||
export type { EyebrowProps } from './Eyebrow';
|
||||
export { Button } from './Button';
|
||||
export type { ButtonProps } from './Button';
|
||||
export { TextLink } from './TextLink';
|
||||
export type { TextLinkProps } from './TextLink';
|
||||
export { Rule } from './Rule';
|
||||
export type { RuleProps } from './Rule';
|
||||
export { Prose } from './Prose';
|
||||
export type { ProseProps } from './Prose';
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "../ds-types"
|
||||
},
|
||||
"include": ["*.ts", "*.tsx"]
|
||||
}
|
||||
Reference in new issue
Block a user