@atelic-action/ui 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/artifact/Note.tsx +188 -0
- package/src/artifact/Shot.tsx +32 -0
- package/src/artifact/Writeup.tsx +2 -26
- package/src/artifact/index.ts +1 -0
- package/src/styles/artifact.css +248 -1
package/README.md
CHANGED
|
@@ -229,7 +229,7 @@ The two pieces a reader sees are ordinary components, in `@atelic-action/ui/comp
|
|
|
229
229
|
|
|
230
230
|
`@atelic-action/ui/artifact` is everything the artifact template shared across its cuts, so a fix reaches every business's documents with one version bump:
|
|
231
231
|
|
|
232
|
-
- **The documents:** `Writeup`, `Report`, and `Gate` (the sealed document, with its `GatePayload` type; a reader's address is the key, so a payload never lists it, and the component identifies a reader to HubSpot by the address they typed), plus the `GradeChip` and `SignOff`
|
|
232
|
+
- **The documents:** `Writeup`, `Report`, `Note`, and `Gate` (the sealed document, with its `GatePayload` type; a reader's address is the key, so a payload never lists it, and the component identifies a reader to HubSpot by the address they typed), plus the `GradeChip` and `SignOff` they share, and every content type they take (`WriteupProps`, `ReportProps`, `NoteProps`, `GateProps`, and the rest).
|
|
233
233
|
- **The shell:** `ArtifactShell`, the `.mkt` scope with the skip link, the dark header, the credit band, and the watermark around a page.
|
|
234
234
|
- **The contract and the head:** the `ArtifactConfig` types, and `buildPageHead` and `canonicalUrl` with their `PageMeta` type.
|
|
235
235
|
- **Who opened it:** `mintToken`, `sealReader`, `openReader`, `readerId`, `readerLink`, and `whoIsReading`. The same six are also built and exported alone as `@atelic-action/ui/artifact/readers`, because a Playwright spec and a script run under Node, which compiles nothing inside `node_modules`.
|
|
@@ -248,6 +248,8 @@ export function ArtifactShell(props: Omit<ArtifactShellProps, "currentPath">) {
|
|
|
248
248
|
}
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
+
**The note** is the first touch for a business whose site publishes no email address, served at `/notes` and wrapped in `ArtifactShell` exactly as the writeup is. It is the writeup's sibling, not a variant: a title and a dateline, the opener, two lanes of findings (what the customer sees, what sits under the hood), the steps the work would start with, and the close over the shared `SignOff`. Every prop is plain data, so a routine can write a note as JSON; a paragraph is a string or an array of strings and `{ text, href }` links, and the sign off person's `role` is the one field that also takes a node. A finding is a `Callout` with its `WriteupShot` under it. It reads on a phone first, sets the two lanes side by side when wide and on paper, and prints to one letter sheet under the writeup's `@page`.
|
|
252
|
+
|
|
251
253
|
The documents import no stylesheet of their own. A site imports `@atelic-action/ui/styles/artifact.css` in its base sheet after `gate.css` and before its `theme.css`, so every page carries the rules whichever route it is. The sheet holds two `@page` rules, the monthly page's and then the writeup's, and the later one wins for every page a site prints; the order inside the sheet is the order the template's build bundled them in and is load bearing.
|
|
252
254
|
|
|
253
255
|
### The Artifact Vite Config
|
package/package.json
CHANGED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Callout } from "../components";
|
|
2
|
+
import { newTabProps } from "../lib/newTabProps";
|
|
3
|
+
import { Shot, type WriteupShot } from "./Shot";
|
|
4
|
+
import { SignOff, type WriteupPerson } from "./SignOff";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The note: the first touch, for a business whose site publishes no email
|
|
8
|
+
* address, written as a short page and left at `<short>.atelic.me/notes`. It
|
|
9
|
+
* is the first touch email set as a document, so it carries no grades, no
|
|
10
|
+
* sections, and no tables: a title, the opener, what the customer sees and
|
|
11
|
+
* what sits under the hood, where the work would start, and the close. It
|
|
12
|
+
* reads on a phone first and prints to one letter sheet.
|
|
13
|
+
*
|
|
14
|
+
* It is the writeup's sibling, not a variant of it: a site wraps it in
|
|
15
|
+
* ArtifactShell exactly as it wraps the writeup, and it reuses the writeup's
|
|
16
|
+
* pieces where they fit (the Callout a finding is set in, the owner's screen,
|
|
17
|
+
* the sign off). Every prop is plain data, so a routine can write a note as
|
|
18
|
+
* JSON; the one exception is the sign off person's `role`, which takes a node
|
|
19
|
+
* and is equally happy with a string.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** A link inside running text: the words the reader clicks and where they go. */
|
|
23
|
+
export interface NoteLink {
|
|
24
|
+
text: string;
|
|
25
|
+
href: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A paragraph: a plain string, or runs of text and links in order. */
|
|
29
|
+
export type NoteParagraph = string | Array<string | NoteLink>;
|
|
30
|
+
|
|
31
|
+
/** One defect and what it costs, with the owner's screen under it when there is one. */
|
|
32
|
+
export interface NoteFinding {
|
|
33
|
+
/** The defect, in a full sentence. */
|
|
34
|
+
lead: string;
|
|
35
|
+
/** The consequence: what it costs the business. */
|
|
36
|
+
body: string;
|
|
37
|
+
/**
|
|
38
|
+
* Where the defect can be seen for oneself. The lead links there: in a new
|
|
39
|
+
* tab for an http(s) link, in place otherwise.
|
|
40
|
+
*/
|
|
41
|
+
href?: string;
|
|
42
|
+
/** The owner's own screen, rendered under the finding. */
|
|
43
|
+
shot?: WriteupShot;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One side of the read, e.g. "What your customer sees", with one or two findings. */
|
|
47
|
+
export interface NoteLane {
|
|
48
|
+
heading: string;
|
|
49
|
+
findings: NoteFinding[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One place the work would start. */
|
|
53
|
+
export interface NoteStep {
|
|
54
|
+
/** The step, set in bold. */
|
|
55
|
+
lead: string;
|
|
56
|
+
/** What it does, after the lead. */
|
|
57
|
+
body?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Where the work would start: the line that introduces it, then the steps. */
|
|
61
|
+
export interface NoteStart {
|
|
62
|
+
/** The line over the steps, e.g. "I would start by:". */
|
|
63
|
+
lead: string;
|
|
64
|
+
items: NoteStep[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The close: who is writing and the offer, then the sign off. */
|
|
68
|
+
export interface NoteClose {
|
|
69
|
+
/** The sender paragraph, then the offer line with its scheduling link and the exit sentence. */
|
|
70
|
+
paragraphs: NoteParagraph[];
|
|
71
|
+
signOff: string;
|
|
72
|
+
person: WriteupPerson;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface NoteProps {
|
|
76
|
+
title: string;
|
|
77
|
+
/** The business, set in the dateline: "Prepared for <business>, <date>". */
|
|
78
|
+
preparedFor: string;
|
|
79
|
+
/** The ISO date, e.g. "2026-10-09". */
|
|
80
|
+
date: string;
|
|
81
|
+
/** The street sighting and a kudos, then the gap sentence. */
|
|
82
|
+
opener: NoteParagraph;
|
|
83
|
+
/** "What your customer sees" and "What I see under the hood", in that order. */
|
|
84
|
+
lanes: NoteLane[];
|
|
85
|
+
start: NoteStart;
|
|
86
|
+
close: NoteClose;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A link opens in a new tab when it leaves the page, and in place when it is an anchor or a mail link. */
|
|
90
|
+
function NoteAnchor({ href, children }: { href: string; children: string }) {
|
|
91
|
+
return (
|
|
92
|
+
<a href={href} {...newTabProps(href, /^https?:/i.test(href))}>
|
|
93
|
+
{children}
|
|
94
|
+
</a>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A paragraph with nothing to read: a blank string, or runs that are all blank strings. A link always counts. */
|
|
99
|
+
function isEmpty(paragraph: NoteParagraph): boolean {
|
|
100
|
+
const runs = typeof paragraph === "string" ? [paragraph] : paragraph;
|
|
101
|
+
return runs.every((run) => typeof run === "string" && run.trim() === "");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function Paragraph({ paragraph, className }: { paragraph: NoteParagraph; className?: string }) {
|
|
105
|
+
const runs = typeof paragraph === "string" ? [paragraph] : paragraph;
|
|
106
|
+
return (
|
|
107
|
+
<p className={className}>
|
|
108
|
+
{runs.map((run, i) =>
|
|
109
|
+
typeof run === "string" ? (
|
|
110
|
+
run
|
|
111
|
+
) : (
|
|
112
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: runs are static content in a fixed order
|
|
113
|
+
<NoteAnchor href={run.href} key={i}>
|
|
114
|
+
{run.text}
|
|
115
|
+
</NoteAnchor>
|
|
116
|
+
),
|
|
117
|
+
)}
|
|
118
|
+
</p>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function Finding({ finding }: { finding: NoteFinding }) {
|
|
123
|
+
return (
|
|
124
|
+
<li className="nt-finding">
|
|
125
|
+
<Callout
|
|
126
|
+
tone="warn"
|
|
127
|
+
title={
|
|
128
|
+
finding.href ? <NoteAnchor href={finding.href}>{finding.lead}</NoteAnchor> : finding.lead
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
{finding.body}
|
|
132
|
+
</Callout>
|
|
133
|
+
{finding.shot && <Shot shot={finding.shot} />}
|
|
134
|
+
</li>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function Note({ title, preparedFor, date, opener, lanes, start, close }: NoteProps) {
|
|
139
|
+
const shownLanes = lanes.filter((lane) => lane.findings.length > 0);
|
|
140
|
+
const paragraphs = close.paragraphs.filter((paragraph) => !isEmpty(paragraph));
|
|
141
|
+
return (
|
|
142
|
+
<div className="nt">
|
|
143
|
+
<article className="nt-doc">
|
|
144
|
+
<header className="nt-head">
|
|
145
|
+
<p className="nt-meta">
|
|
146
|
+
Prepared for <b>{preparedFor}</b>, <span className="nt-meta-date">{date}</span>
|
|
147
|
+
</p>
|
|
148
|
+
<h1>{title}</h1>
|
|
149
|
+
</header>
|
|
150
|
+
{!isEmpty(opener) && <Paragraph paragraph={opener} className="nt-opener" />}
|
|
151
|
+
{shownLanes.length > 0 && (
|
|
152
|
+
<div className="nt-lanes">
|
|
153
|
+
{shownLanes.map((lane) => (
|
|
154
|
+
<section className="nt-lane" key={lane.heading}>
|
|
155
|
+
<h2>{lane.heading}</h2>
|
|
156
|
+
<ul className="nt-findings">
|
|
157
|
+
{lane.findings.map((finding) => (
|
|
158
|
+
<Finding finding={finding} key={finding.lead} />
|
|
159
|
+
))}
|
|
160
|
+
</ul>
|
|
161
|
+
</section>
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
{start.items.length > 0 && (
|
|
166
|
+
<section className="nt-start">
|
|
167
|
+
<p className="nt-start-lead">{start.lead}</p>
|
|
168
|
+
<ul>
|
|
169
|
+
{start.items.map((step) => (
|
|
170
|
+
<li key={step.lead}>
|
|
171
|
+
<b>{step.lead}</b>
|
|
172
|
+
{step.body && <> {step.body}</>}
|
|
173
|
+
</li>
|
|
174
|
+
))}
|
|
175
|
+
</ul>
|
|
176
|
+
</section>
|
|
177
|
+
)}
|
|
178
|
+
<footer className="nt-close">
|
|
179
|
+
{paragraphs.map((paragraph, i) => (
|
|
180
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: paragraphs are static content in a fixed order
|
|
181
|
+
<Paragraph paragraph={paragraph} key={i} />
|
|
182
|
+
))}
|
|
183
|
+
<SignOff signOff={close.signOff} person={close.person} />
|
|
184
|
+
</footer>
|
|
185
|
+
</article>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { newTabProps } from "../lib/newTabProps";
|
|
2
|
+
|
|
3
|
+
export interface WriteupShot {
|
|
4
|
+
/** The crop shown on the page. */
|
|
5
|
+
src: string;
|
|
6
|
+
alt: string;
|
|
7
|
+
/** The full screenshot the crop opens in a new tab; omit to render the crop alone. */
|
|
8
|
+
href?: string;
|
|
9
|
+
caption: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The owner's own screen: the crop, opening the full screenshot in a new tab
|
|
14
|
+
* when it has one, over its caption. The writeup and the note both wear it;
|
|
15
|
+
* its styles (.op-shot) are the writeup's, and the note only sizes it.
|
|
16
|
+
*/
|
|
17
|
+
export function Shot({ shot }: { shot: WriteupShot }) {
|
|
18
|
+
return (
|
|
19
|
+
<figure className="op-shot">
|
|
20
|
+
{shot.href ? (
|
|
21
|
+
<a href={shot.href} {...newTabProps(shot.href, true)}>
|
|
22
|
+
<img src={shot.src} alt={shot.alt} loading="lazy" />
|
|
23
|
+
</a>
|
|
24
|
+
) : (
|
|
25
|
+
<span className="op-shot-frame">
|
|
26
|
+
<img src={shot.src} alt={shot.alt} loading="lazy" />
|
|
27
|
+
</span>
|
|
28
|
+
)}
|
|
29
|
+
<figcaption>{shot.caption}</figcaption>
|
|
30
|
+
</figure>
|
|
31
|
+
);
|
|
32
|
+
}
|
package/src/artifact/Writeup.tsx
CHANGED
|
@@ -19,9 +19,11 @@ import {
|
|
|
19
19
|
} from "../components";
|
|
20
20
|
import { newTabProps } from "../lib/newTabProps";
|
|
21
21
|
import { GradeChip, gradeLabel, type WriteupGrade } from "./GradeChip";
|
|
22
|
+
import { Shot, type WriteupShot } from "./Shot";
|
|
22
23
|
import { SignOff, type WriteupPerson } from "./SignOff";
|
|
23
24
|
|
|
24
25
|
export type { WriteupGrade } from "./GradeChip";
|
|
26
|
+
export type { WriteupShot } from "./Shot";
|
|
25
27
|
export type { WriteupPerson } from "./SignOff";
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -43,15 +45,6 @@ export type { WriteupPerson } from "./SignOff";
|
|
|
43
45
|
/** How a mark, a bar segment, or a zone reads; the shared package's tones. */
|
|
44
46
|
export type WriteupTone = Tone;
|
|
45
47
|
|
|
46
|
-
export interface WriteupShot {
|
|
47
|
-
/** The crop shown on the page. */
|
|
48
|
-
src: string;
|
|
49
|
-
alt: string;
|
|
50
|
-
/** The full screenshot the crop opens in a new tab; omit to render the crop alone. */
|
|
51
|
-
href?: string;
|
|
52
|
-
caption: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
48
|
export interface WriteupBullet {
|
|
56
49
|
/** The point, in a full sentence. The icon sits beside it. */
|
|
57
50
|
lead: string;
|
|
@@ -391,23 +384,6 @@ function Table({ block }: { block: WriteupTable }) {
|
|
|
391
384
|
);
|
|
392
385
|
}
|
|
393
386
|
|
|
394
|
-
function Shot({ shot }: { shot: WriteupShot }) {
|
|
395
|
-
return (
|
|
396
|
-
<figure className="op-shot">
|
|
397
|
-
{shot.href ? (
|
|
398
|
-
<a href={shot.href} {...newTabProps(shot.href, true)}>
|
|
399
|
-
<img src={shot.src} alt={shot.alt} loading="lazy" />
|
|
400
|
-
</a>
|
|
401
|
-
) : (
|
|
402
|
-
<span className="op-shot-frame">
|
|
403
|
-
<img src={shot.src} alt={shot.alt} loading="lazy" />
|
|
404
|
-
</span>
|
|
405
|
-
)}
|
|
406
|
-
<figcaption>{shot.caption}</figcaption>
|
|
407
|
-
</figure>
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
387
|
function Findings({ block, brief }: { block: WriteupFindings; brief?: string }) {
|
|
412
388
|
const hasShots = Boolean(block.shot || brief);
|
|
413
389
|
return (
|
package/src/artifact/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type {
|
|
|
11
11
|
export { ErrorFallback } from "./ErrorFallback";
|
|
12
12
|
export { Gate, type GatePayload, type GatePerson, type GateProps, normalizeEmail } from "./Gate";
|
|
13
13
|
export { GradeChip, gradeLabel } from "./GradeChip";
|
|
14
|
+
export * from "./Note";
|
|
14
15
|
export { buildPageHead, canonicalUrl, type PageMeta } from "./pageHead";
|
|
15
16
|
export * from "./Report";
|
|
16
17
|
export {
|
package/src/styles/artifact.css
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
share. Moved whole from the artifact template, in the order its build
|
|
4
4
|
bundled them (the shared classes, the stage, the monthly page, the
|
|
5
5
|
writeup), which the two @page rules depend on: the later one wins, and
|
|
6
|
-
the writeup's is the later one.
|
|
6
|
+
the writeup's is the later one. The note follows the writeup and sets
|
|
7
|
+
no @page, so the writeup's stays the one that wins. They sit in the package's layer like
|
|
7
8
|
every sheet here, so any rule a site writes outside a layer still wins
|
|
8
9
|
over them whatever its specificity. */
|
|
9
10
|
@layer atelic-ui {
|
|
@@ -1859,4 +1860,250 @@
|
|
|
1859
1860
|
display: block;
|
|
1860
1861
|
}
|
|
1861
1862
|
}
|
|
1863
|
+
|
|
1864
|
+
/* =================================================================
|
|
1865
|
+
THE NOTE: the first touch set as a page (Note.tsx). A letter, not a
|
|
1866
|
+
report: one narrow column of running text, read on a phone first.
|
|
1867
|
+
|
|
1868
|
+
- Phone first: the base rules are the phone's. The two lanes stack,
|
|
1869
|
+
each finding a Callout with the owner's screen under it.
|
|
1870
|
+
- Wide (721px and up) and print: the two lanes sit side by side.
|
|
1871
|
+
- Print: one letter sheet. The chrome steps aside (the writeup's print
|
|
1872
|
+
rules already hide the header and the credit band on any artifact
|
|
1873
|
+
page), the type comes down, and the screens shrink. The note sets
|
|
1874
|
+
no @page of its own: the writeup's, the later one in this sheet,
|
|
1875
|
+
sets letter paper for every artifact page.
|
|
1876
|
+
|
|
1877
|
+
Tokens only. The finding is the shared Callout (styles/components.css),
|
|
1878
|
+
the screen the writeup's .op-shot, the sign off the shared .op-who;
|
|
1879
|
+
this sheet sets the letter around them and their sizes here.
|
|
1880
|
+
================================================================= */
|
|
1881
|
+
/* The borrowed .op-shot reads the writeup's own two tokens, mapped here as
|
|
1882
|
+
the writeup's .op root maps them. */
|
|
1883
|
+
:is(.mkt, .atelic-ui) .nt {
|
|
1884
|
+
--op-card: var(--card);
|
|
1885
|
+
--op-ink-faint: var(--neutral-500);
|
|
1886
|
+
font-family: var(--font-sans);
|
|
1887
|
+
color: var(--ink);
|
|
1888
|
+
background: var(--surface);
|
|
1889
|
+
}
|
|
1890
|
+
:is(.mkt, .atelic-ui) .nt-doc {
|
|
1891
|
+
max-width: 760px;
|
|
1892
|
+
margin: 0 auto;
|
|
1893
|
+
padding: calc(var(--nav-height) + 22px) 18px 48px;
|
|
1894
|
+
font-size: 17px;
|
|
1895
|
+
line-height: 1.6;
|
|
1896
|
+
print-color-adjust: exact;
|
|
1897
|
+
-webkit-print-color-adjust: exact;
|
|
1898
|
+
}
|
|
1899
|
+
:is(.mkt, .atelic-ui) .nt a {
|
|
1900
|
+
color: var(--primary);
|
|
1901
|
+
text-decoration: underline;
|
|
1902
|
+
text-underline-offset: 2px;
|
|
1903
|
+
text-decoration-thickness: 1px;
|
|
1904
|
+
overflow-wrap: anywhere;
|
|
1905
|
+
}
|
|
1906
|
+
:is(.mkt, .atelic-ui) .nt a:hover {
|
|
1907
|
+
text-decoration-thickness: 2px;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
/* ---- the head: who it is for, then the title ---- */
|
|
1911
|
+
:is(.mkt, .atelic-ui) .nt-meta {
|
|
1912
|
+
margin: 0 0 10px;
|
|
1913
|
+
font-family: var(--font-mono);
|
|
1914
|
+
font-size: 12.5px;
|
|
1915
|
+
letter-spacing: 0.04em;
|
|
1916
|
+
color: var(--neutral-500);
|
|
1917
|
+
}
|
|
1918
|
+
:is(.mkt, .atelic-ui) .nt-meta b {
|
|
1919
|
+
font-weight: 500;
|
|
1920
|
+
color: var(--ink-soft);
|
|
1921
|
+
}
|
|
1922
|
+
:is(.mkt, .atelic-ui) .nt-meta-date {
|
|
1923
|
+
white-space: nowrap;
|
|
1924
|
+
}
|
|
1925
|
+
:is(.mkt, .atelic-ui) .nt h1 {
|
|
1926
|
+
font-size: clamp(30px, 6vw, 40px);
|
|
1927
|
+
font-weight: 650;
|
|
1928
|
+
letter-spacing: -0.03em;
|
|
1929
|
+
line-height: 1.1;
|
|
1930
|
+
margin: 0;
|
|
1931
|
+
text-wrap: balance;
|
|
1932
|
+
color: var(--ink);
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
/* ---- the opener: the letter's first paragraph ---- */
|
|
1936
|
+
:is(.mkt, .atelic-ui) .nt-opener {
|
|
1937
|
+
margin: 18px 0 0;
|
|
1938
|
+
font-size: 18px;
|
|
1939
|
+
line-height: 1.6;
|
|
1940
|
+
color: var(--ink);
|
|
1941
|
+
text-wrap: pretty;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
/* ---- the two lanes, each a heading and its findings ---- */
|
|
1945
|
+
:is(.mkt, .atelic-ui) .nt-lanes {
|
|
1946
|
+
display: grid;
|
|
1947
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1948
|
+
gap: 26px;
|
|
1949
|
+
margin-top: 30px;
|
|
1950
|
+
padding-top: 24px;
|
|
1951
|
+
border-top: 1px solid var(--neutral-100);
|
|
1952
|
+
}
|
|
1953
|
+
:is(.mkt, .atelic-ui) .nt-lane {
|
|
1954
|
+
min-width: 0;
|
|
1955
|
+
}
|
|
1956
|
+
:is(.mkt, .atelic-ui) .nt-lane h2 {
|
|
1957
|
+
margin: 0 0 14px;
|
|
1958
|
+
font-size: 19px;
|
|
1959
|
+
font-weight: 650;
|
|
1960
|
+
letter-spacing: -0.015em;
|
|
1961
|
+
line-height: 1.25;
|
|
1962
|
+
color: var(--ink);
|
|
1963
|
+
}
|
|
1964
|
+
:is(.mkt, .atelic-ui) .nt-findings {
|
|
1965
|
+
list-style: none;
|
|
1966
|
+
margin: 0;
|
|
1967
|
+
padding: 0;
|
|
1968
|
+
display: flex;
|
|
1969
|
+
flex-direction: column;
|
|
1970
|
+
gap: 18px;
|
|
1971
|
+
}
|
|
1972
|
+
:is(.mkt, .atelic-ui) .nt-finding .callout {
|
|
1973
|
+
font-size: 16px;
|
|
1974
|
+
line-height: 1.5;
|
|
1975
|
+
}
|
|
1976
|
+
/* The screen sits under its finding, set in from the icon so it lines up
|
|
1977
|
+
with the words. It keeps its own shape, never wider than the lane and
|
|
1978
|
+
never so tall it carries the page. */
|
|
1979
|
+
:is(.mkt, .atelic-ui) .nt-finding .op-shot {
|
|
1980
|
+
margin: 10px 0 0 28px;
|
|
1981
|
+
}
|
|
1982
|
+
:is(.mkt, .atelic-ui) .nt-finding .op-shot a,
|
|
1983
|
+
:is(.mkt, .atelic-ui) .nt-finding .op-shot-frame {
|
|
1984
|
+
width: fit-content;
|
|
1985
|
+
max-width: 100%;
|
|
1986
|
+
}
|
|
1987
|
+
:is(.mkt, .atelic-ui) .nt-finding .op-shot img {
|
|
1988
|
+
width: auto;
|
|
1989
|
+
max-width: 100%;
|
|
1990
|
+
max-height: 340px;
|
|
1991
|
+
}
|
|
1992
|
+
:is(.mkt, .atelic-ui) .nt-finding .op-shot figcaption {
|
|
1993
|
+
font-size: 12.5px;
|
|
1994
|
+
line-height: 1.45;
|
|
1995
|
+
color: var(--neutral-500);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
/* ---- where the work would start ---- */
|
|
1999
|
+
:is(.mkt, .atelic-ui) .nt-start {
|
|
2000
|
+
margin-top: 30px;
|
|
2001
|
+
padding-top: 24px;
|
|
2002
|
+
border-top: 1px solid var(--neutral-100);
|
|
2003
|
+
}
|
|
2004
|
+
:is(.mkt, .atelic-ui) .nt-start-lead {
|
|
2005
|
+
margin: 0 0 10px;
|
|
2006
|
+
font-weight: 650;
|
|
2007
|
+
color: var(--ink);
|
|
2008
|
+
}
|
|
2009
|
+
/* The site base strips list markers; the steps want theirs back. */
|
|
2010
|
+
:is(.mkt, .atelic-ui) .nt-start ul {
|
|
2011
|
+
margin: 0;
|
|
2012
|
+
padding-left: 1.2em;
|
|
2013
|
+
list-style: disc;
|
|
2014
|
+
display: grid;
|
|
2015
|
+
gap: 8px;
|
|
2016
|
+
color: var(--ink-soft);
|
|
2017
|
+
}
|
|
2018
|
+
:is(.mkt, .atelic-ui) .nt-start li {
|
|
2019
|
+
padding-left: 2px;
|
|
2020
|
+
}
|
|
2021
|
+
:is(.mkt, .atelic-ui) .nt-start li::marker {
|
|
2022
|
+
color: var(--primary);
|
|
2023
|
+
}
|
|
2024
|
+
:is(.mkt, .atelic-ui) .nt-start b {
|
|
2025
|
+
font-weight: 600;
|
|
2026
|
+
color: var(--ink);
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
/* ---- the close: who is writing, the offer, then the sign off ---- */
|
|
2030
|
+
:is(.mkt, .atelic-ui) .nt-close {
|
|
2031
|
+
margin-top: 30px;
|
|
2032
|
+
padding-top: 24px;
|
|
2033
|
+
border-top: 1px solid var(--neutral-100);
|
|
2034
|
+
}
|
|
2035
|
+
:is(.mkt, .atelic-ui) .nt-close p {
|
|
2036
|
+
margin: 0 0 14px;
|
|
2037
|
+
text-wrap: pretty;
|
|
2038
|
+
}
|
|
2039
|
+
:is(.mkt, .atelic-ui) .nt-close .op-who {
|
|
2040
|
+
margin-top: 22px;
|
|
2041
|
+
gap: 14px;
|
|
2042
|
+
font-size: 14px;
|
|
2043
|
+
line-height: 1.6;
|
|
2044
|
+
color: var(--neutral-500);
|
|
2045
|
+
}
|
|
2046
|
+
:is(.mkt, .atelic-ui) .nt-close .op-who img {
|
|
2047
|
+
width: 56px;
|
|
2048
|
+
height: 56px;
|
|
2049
|
+
}
|
|
2050
|
+
:is(.mkt, .atelic-ui) .nt-close .op-sign {
|
|
2051
|
+
font-size: 24px;
|
|
2052
|
+
margin-bottom: 8px;
|
|
2053
|
+
}
|
|
2054
|
+
/* The sign off's own links read as the writeup's do: quiet, in the text color. */
|
|
2055
|
+
:is(.mkt, .atelic-ui) .nt-close .op-who a {
|
|
2056
|
+
color: inherit;
|
|
2057
|
+
text-decoration: none;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
/* ---- wide, and on paper: the two lanes side by side ---- */
|
|
2061
|
+
@media print, (min-width: 721px) {
|
|
2062
|
+
:is(.mkt, .atelic-ui) .nt-doc {
|
|
2063
|
+
padding: calc(var(--nav-height) + 36px) 24px 64px;
|
|
2064
|
+
}
|
|
2065
|
+
:is(.mkt, .atelic-ui) .nt-lanes {
|
|
2066
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2067
|
+
gap: 32px;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
/* ---- print: one letter sheet ---- */
|
|
2072
|
+
@media print {
|
|
2073
|
+
html:has(:is(.mkt, .atelic-ui) .nt-doc) {
|
|
2074
|
+
zoom: 0.8;
|
|
2075
|
+
}
|
|
2076
|
+
:is(.mkt, .atelic-ui) .nt {
|
|
2077
|
+
background: none;
|
|
2078
|
+
}
|
|
2079
|
+
:is(.mkt, .atelic-ui) .nt-doc {
|
|
2080
|
+
max-width: none;
|
|
2081
|
+
padding: 0;
|
|
2082
|
+
font-size: 15px;
|
|
2083
|
+
line-height: 1.5;
|
|
2084
|
+
}
|
|
2085
|
+
:is(.mkt, .atelic-ui) .nt-opener {
|
|
2086
|
+
font-size: 16px;
|
|
2087
|
+
}
|
|
2088
|
+
:is(.mkt, .atelic-ui) .nt-lanes,
|
|
2089
|
+
:is(.mkt, .atelic-ui) .nt-start,
|
|
2090
|
+
:is(.mkt, .atelic-ui) .nt-close {
|
|
2091
|
+
margin-top: 20px;
|
|
2092
|
+
padding-top: 16px;
|
|
2093
|
+
}
|
|
2094
|
+
:is(.mkt, .atelic-ui) .nt-finding .callout {
|
|
2095
|
+
font-size: 14.5px;
|
|
2096
|
+
}
|
|
2097
|
+
:is(.mkt, .atelic-ui) .nt-finding .op-shot img {
|
|
2098
|
+
max-height: 1.9in;
|
|
2099
|
+
}
|
|
2100
|
+
:is(.mkt, .atelic-ui) .nt-finding .op-shot a {
|
|
2101
|
+
cursor: auto;
|
|
2102
|
+
}
|
|
2103
|
+
:is(.mkt, .atelic-ui) .nt-finding,
|
|
2104
|
+
:is(.mkt, .atelic-ui) .nt-start,
|
|
2105
|
+
:is(.mkt, .atelic-ui) .nt-close {
|
|
2106
|
+
break-inside: avoid;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
1862
2109
|
}
|