Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions app/(site)/(components-ui)/components-ui/examples/content.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Note, Tip, Warning } from "@/app/ui/callout";
import { ContentBlock } from "@/app/ui/content-block";
import { Term, Terms } from "@/app/ui/content-elements";
import { Faq } from "@/app/ui/faq";
import { CodeBlock } from "@/app/ui/site/code-block";
import { proseClassName, proseFlowClassName } from "@/app/ui/site/prose";
Expand Down Expand Up @@ -68,24 +67,6 @@ Each month returns $80, so the cost comes back in 15 months.
</div>
</Example>

<Example
components={["Terms", "Term"]}
note="Words a reader may not know, each with its meaning."
source="app/ui/content-elements.tsx"
title="Terms"
usage={{ label: "MDX", code: `<Terms>
<Term name="TTL">How long a resolver may keep a record before it asks again.</Term>
<Term name="Resolver">The DNS server that looks up names for your device.</Term>
</Terms>` }}
>
<div className={flow}>
<Terms>
<Term name="TTL">How long a resolver may keep a record before it asks again.</Term>
<Term name="Resolver">The DNS server that looks up names for your device.</Term>
</Terms>
</div>
</Example>

<Example
components={["Faq"]}
note={<>Questions stay open, one under another. Course pages use this. In MDX, write each question as a <code>###</code> heading under a <code>##</code> heading instead.</>}
Expand Down
4 changes: 2 additions & 2 deletions app/(site)/(components-ui)/components-ui/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ export const gallerySections: GallerySection[] = [
{
slug: "content",
label: "Content",
description: "Notes, warnings, tips, formulas, examples, terms, questions, and code.",
description: "Notes, warnings, tips, formulas, examples, questions, and code.",
icon: FileText,
components: ["Callout", "Note", "Warning", "Tip", "ContentBlock", "Terms", "Term", "Faq", "CodeBlock"],
components: ["Callout", "Note", "Warning", "Tip", "ContentBlock", "Faq", "CodeBlock"],
},
{
slug: "prose",
Expand Down
14 changes: 7 additions & 7 deletions app/(site)/(directories)/ad-networks/content/article.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ Each card shows the joining rule under "To join" when the network publishes one.

Networks count traffic in different ways, and the numbers aren't interchangeable. Check the exact word and period the rule uses:

<Terms>
<Term name="Pageviews">Every page loaded. One visit that reads three articles is three pageviews.</Term>
<Term name="Sessions">One visit, which often includes several pageviews, so 50,000 sessions is harder to reach than 50,000 pageviews. Journey by Mediavine counts only Tier 1 sessions over 30 days.</Term>
<Term name="Visits">Sovrn's word. It isn't defined the same way as Google Analytics sessions, so don't swap one for the other.</Term>
<Term name="Visitors and active users">People. Someone who comes back five times in a month usually counts once. Ezoic counts monthly active users, MGID daily or monthly visitors, and The Moneytizer organic unique visitors from three countries.</Term>
<Term name="Ad revenue">What your ads already earn. Mediavine counts the previous calendar year, while Publift and PubGuru Premium look at a month.</Term>
</Terms>
| Word | What it counts |
| --- | --- |
| Pageviews | Every page loaded. One visit that reads three articles is three pageviews. |
| Sessions | One visit, which often includes several pageviews, so 50,000 sessions is harder to reach than 50,000 pageviews. Journey by Mediavine counts only Tier 1 sessions over 30 days. |
| Visits | Sovrn's word. It isn't defined the same way as Google Analytics sessions, so don't swap one for the other. |
| Visitors and active users | People. Someone who comes back five times in a month usually counts once. Ezoic counts monthly active users, MGID daily or monthly visitors, and The Moneytizer organic unique visitors from three countries. |
| Ad revenue | What your ads already earn. Mediavine counts the previous calendar year, while Publift and PubGuru Premium look at a month. |

Traffic isn't the only rule. Many networks also check where your visitors come from, whether your content is original, and whether your site follows Google's ad policies. Each network's page lists its full rules.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ Card prices show the lowest price we found, and "From" means higher plans exist.

We don't have a checked answer for that detail, and we don't guess one.

<Terms>
<Term name="Not stated on their site">The site's own pages don't publish it.</Term>
<Term name="Couldn't verify">A page blocked us, needed a login, or didn't load. It doesn't mean the site has closed.</Term>
<Term name="Conflicting info">Two of the site's own pages disagree. The place's page links both.</Term>
<Term name="Submit page down">We found a submission page, but it didn't load when we checked.</Term>
<Term name="No submit page found">We couldn't find where submissions start. Look on the site itself before you give up on it.</Term>
</Terms>
| Label | What it means |
| --- | --- |
| Not stated on their site | The site's own pages don't publish it. |
| Couldn't verify | A page blocked us, needed a login, or didn't load. It doesn't mean the site has closed. |
| Conflicting info | Two of the site's own pages disagree. The place's page links both. |
| Submit page down | We found a submission page, but it didn't load when we checked. |
| No submit page found | We couldn't find where submissions start. Look on the site itself before you give up on it. |
26 changes: 13 additions & 13 deletions app/(site)/(directories)/launch-directories/content/link-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ Choose Dofollow under Link type to see them. Each result comes from one publishe

[Google says](https://developers.google.com/search/docs/crawling-indexing/qualify-outbound-links) links marked nofollow, ugc, or sponsored will generally not be followed. A dofollow link still doesn't promise indexing, rankings, or traffic.

<Terms>
<Term name="Dofollow">The sampled link had no nofollow, ugc, or sponsored value, or the site's terms promise that.</Term>
<Term name="Nofollow">The link asks search engines not to follow it.</Term>
<Term name="UGC">The link is marked as user-generated content.</Term>
<Term name="Sponsored">The link is marked as paid.</Term>
<Term name="Mixed">The link carried more than one value, such as ugc and nofollow.</Term>
<Term name="Varies">Free, paid, or top-ranked listings get different links. The place's page says which.</Term>
<Term name="No link">The sampled listing doesn't link to the maker's own site.</Term>
</Terms>
| Link type | What it means |
| --- | --- |
| Dofollow | The sampled link had no nofollow, ugc, or sponsored value, or the site's terms promise that. |
| Nofollow | The link asks search engines not to follow it. |
| UGC | The link is marked as user-generated content. |
| Sponsored | The link is marked as paid. |
| Mixed | The link carried more than one value, such as ugc and nofollow. |
| Varies | Free, paid, or top-ranked listings get different links. The place's page says which. |
| No link | The sampled listing doesn't link to the maker's own site. |

These two labels mean we have no checked result:

<Terms>
<Term name="Likely nofollow">We couldn't read a sampled link, but other evidence points to nofollow.</Term>
<Term name="Couldn't verify">We couldn't open a published listing or read its link.</Term>
</Terms>
| Label | What it means |
| --- | --- |
| Likely nofollow | We couldn't read a sampled link, but other evidence points to nofollow. |
| Couldn't verify | We couldn't open a published listing or read its link. |
16 changes: 8 additions & 8 deletions app/(site)/(directories)/platform-fees/content/article.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ Open the link at the bottom of a card, such as "+ 3 more rates" or "All rates an

Because the platform doesn't take a simple cut of your price. Subscription sites that split a shared pool by downloads, ad programs, and fixed pay per item show their own terms instead. These labels stand in for a rate:

<Terms>
<Term name="Share not disclosed">The platform pays creators a share but doesn't publish it.</Term>
<Term name="Fee not disclosed">The platform charges a fee but doesn't publish the rate.</Term>
<Term name="Set by agreement">The rate is in a contract that isn't public, as with console stores.</Term>
<Term name="Shown in dashboard">The platform shows the rate only inside your seller account.</Term>
<Term name="Base cost">You set the price, and the cost of making the product comes out of it.</Term>
<Term name="Your own checkout">The platform doesn't sell for you, so you need your own payment provider.</Term>
</Terms>
| Label | What it means |
| --- | --- |
| Share not disclosed | The platform pays creators a share but doesn't publish it. |
| Fee not disclosed | The platform charges a fee but doesn't publish the rate. |
| Set by agreement | The rate is in a contract that isn't public, as with console stores. |
| Shown in dashboard | The platform shows the rate only inside your seller account. |
| Base cost | You set the price, and the cost of making the product comes out of it. |
| Your own checkout | The platform doesn't sell for you, so you need your own payment provider. |

## Is the lowest fee the cheapest?

Expand Down
14 changes: 7 additions & 7 deletions app/(site)/(tools)/caa-checker/content/article.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ If a name is an alias (a CNAME), its target's CAA records count. The search does

## What do issue, issuewild, and iodef mean?

<Terms>
<Term name="issue">Allows one certificate authority to issue normal certificates. With no `issuewild`, it covers wildcards too.</Term>
<Term name="issuewild">Allows one certificate authority to issue wildcard certificates, such as `*.example.com`. When any `issuewild` exists, wildcards ignore `issue`.</Term>
<Term name="iodef">An email or web address for reports about blocked requests. It doesn't allow or block anything.</Term>
<Term name='issue ";"'>An empty value. On its own, it allows no certificate authority.</Term>
<Term name="Flag 128">Marks a record as critical. A certificate authority that doesn't understand it must refuse to issue.</Term>
</Terms>
| Setting | What it does |
| --- | --- |
| issue | Allows one certificate authority to issue normal certificates. With no `issuewild`, it covers wildcards too. |
| issuewild | Allows one certificate authority to issue wildcard certificates, such as `*.example.com`. When any `issuewild` exists, wildcards ignore `issue`. |
| iodef | An email or web address for reports about blocked requests. It doesn't allow or block anything. |
| issue ";" | An empty value. On its own, it allows no certificate authority. |
| Flag 128 | Marks a record as critical. A certificate authority that doesn't understand it must refuse to issue. |

Several `issue` records add up, and each one allows one more certificate authority. A policy with only `issuewild` records leaves normal certificates unrestricted.

Expand Down
10 changes: 5 additions & 5 deletions app/(site)/(tools)/churn-rate-converter/content/usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ Annualized churn is 45.96%, so 54.04% of customers are still with you after a ye

</Example>

<Terms>
<Term name="Wrong shortcut">What you'd get by just multiplying or dividing, such as 5% × 12 = 60%. It's there to compare, not to use.</Term>
<Term name="About 46">The model's expected count. A real group of 100 can lose a few more or fewer.</Term>
<Term name="Average lifetime">How long a customer stays on average, in the period you entered. It's a rough model estimate, not your customers' real tenure.</Term>
</Terms>
| Result | What it means |
| --- | --- |
| Wrong shortcut | What you'd get by just multiplying or dividing, such as 5% × 12 = 60%. It's there to compare, not to use. |
| About 46 | The model's expected count. A real group of 100 can lose a few more or fewer. |
| Average lifetime | How long a customer stays on average, in the period you entered. It's a rough model estimate, not your customers' real tenure. |
12 changes: 6 additions & 6 deletions app/(site)/(tools)/domain-age/content/article.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ The lookup goes from stack127 to the registry. The website itself isn't contacte

## What do the dates in the result mean?

<Terms>
<Term name="First registered">The original registration date. It shows when the registry also publishes a later re-registration date.</Term>
<Term name="Last changed">When the registration record last changed. It doesn't say what changed, and it isn't when the website was edited.</Term>
<Term name="Registry expiry">When the current paid term ends, as the registry reports it. It isn't a deletion date.</Term>
<Term name="Registry data">When the registry last updated the database it answered from.</Term>
</Terms>
| Date | What it means |
| --- | --- |
| First registered | The original registration date. It shows when the registry also publishes a later re-registration date. |
| Last changed | When the registration record last changed. It doesn't say what changed, and it isn't when the website was edited. |
| Registry expiry | When the current paid term ends, as the registry reports it. It isn't a deletion date. |
| Registry data | When the registry last updated the database it answered from. |
10 changes: 5 additions & 5 deletions app/(site)/(tools)/startup-runway-calculator/content/usage.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## What to enter

<Terms>
<Term name="Cash now">Business cash you can spend today. Not personal savings, unused credit, or money you haven't received.</Term>
<Term name="Cash collected">What customers pay into your account in a normal month. Use cash, not MRR. Leave out yearly payments, and add the next one as a planned change, so it's counted once. Leave out loans and investment.</Term>
<Term name="Cash paid out">Everything that left the account in a typical month, including your own pay, payment fees, and taxes. The calculator adds no fees of its own.</Term>
</Terms>
| Field | What to enter |
| --- | --- |
| Cash now | Business cash you can spend today. Not personal savings, unused credit, or money you haven't received. |
| Cash collected | What customers pay into your account in a normal month. Use cash, not MRR. Leave out yearly payments, and add the next one as a planned change, so it's counted once. Leave out loans and investment. |
| Cash paid out | Everything that left the account in a typical month, including your own pay, payment fees, and taxes. The calculator adds no fees of its own. |

If your months vary, use an average. [Pilot suggests](https://pilot.com/blog/burn-rate-calculation-startup-runway-calculator) at least three months.

Expand Down
12 changes: 0 additions & 12 deletions app/ui/content-elements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,3 @@ export function ContentImage({ alt, src, ...props }: ComponentPropsWithoutRef<"i
if (typeof src === "string" && src.startsWith("marker-art://")) return <ContentMarkerArt alt={alt} reference={src} />;
return <img src={src} alt={alt} {...props} />;
}

export function Terms({ children }: { children: ReactNode }) {
return <dl>{children}</dl>;
}

/** A meaning written as one paragraph sits directly in the dd, as Markdown definition lists render it. */
export function Term({ children, name }: { children: ReactNode; name: string }) {
const items = Children.toArray(children);
const only = items.length === 1 ? items[0] : null;
const meaning = isValidElement<{ children?: ReactNode }>(only) && (only.type === "p" || only.type === ContentParagraph) ? only.props.children : children;
return <><dt>{name}</dt><dd>{meaning}</dd></>;
}
15 changes: 9 additions & 6 deletions app/ui/content-markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { UiChartSpec } from "./charts/chart-option";
type MarkdownNode = {
type: string;
value?: string;
align?: null[];
data?: { hName?: string; hProperties?: Record<string, string> };
children?: MarkdownNode[];
};
Expand All @@ -32,13 +33,15 @@ function remarkDefinitionLists() {
index += 1;
}
if (entries.length) {
// Terms show as a two-column table, the same as terms in MDX pages.
const row = (cells: string[]): MarkdownNode => ({
type: "tableRow",
children: cells.map((value) => ({ type: "tableCell", children: [{ type: "text", value }] })),
});
children.push({
type: "paragraph",
data: { hName: "dl" },
children: entries.flatMap((entry) => [
{ type: "paragraph", data: { hName: "dt" }, children: [{ type: "text", value: entry.term }] },
{ type: "paragraph", data: { hName: "dd" }, children: [{ type: "text", value: entry.description }] },
]),
type: "table",
align: [null, null],
children: [row(["Term", "Meaning"]), ...entries.map((entry) => row([entry.term, entry.description]))],
});
continue;
}
Expand Down
26 changes: 0 additions & 26 deletions app/ui/site/prose.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,32 +97,6 @@
width: 100%;
}

/* Terms sit beside their meanings, the same layout tool articles use, and stack on phones. */
.prose dl {
display: grid;
gap: var(--space-2) var(--space-5);
grid-template-columns: minmax(8rem, max-content) minmax(0, 1fr);
margin: 0;
}

.prose dt {
font-weight: var(--weight-semibold);
}

.prose dd {
margin: 0;
}

@media (max-width: 640px) {
.prose dl {
grid-template-columns: 1fr;
}

.prose dd + dt {
margin-top: var(--space-3);
}
}

.flow,
.sectionFlow > section {
display: grid;
Expand Down
4 changes: 0 additions & 4 deletions mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import {
ContentParagraph,
ContentPre,
ContentTable,
Term,
Terms,
} from "@/app/ui/content-elements";
import { UiDataTable } from "@/app/ui/display";

Expand All @@ -31,8 +29,6 @@ const contentComponents: MDXComponents = {
Tip: ({ children, title }: BlockProps) => <ContentCallout kind="tip" title={title}>{children}</ContentCallout>,
Formula: ({ children, title }: BlockProps) => <ContentCallout kind="formula" title={title}>{children}</ContentCallout>,
Example: ({ children, title }: BlockProps) => <ContentCallout kind="example" title={title}>{children}</ContentCallout>,
Terms,
Term,
UiStaticChart,
UiDataTable,
MarkerArt: ({ alt, figure }: { alt: string; figure: string }) => <ContentMarkerArt alt={alt} reference={`marker-art://${figure}`} />,
Expand Down
2 changes: 1 addition & 1 deletion scripts/stack127/content/allowed-components.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Keep this list aligned with the shared components in mdx-components.tsx.
export const allowedContentComponents = [
'Note', 'Warning', 'Tip', 'Formula', 'Example', 'Terms', 'Term',
'Note', 'Warning', 'Tip', 'Formula', 'Example',
'UiStaticChart', 'UiChart', 'UiDataTable', 'MarkerArt',
];
20 changes: 16 additions & 4 deletions scripts/stack127/content/convert-markdown.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ function escapeText(value) {
.replace(/^(import|export)(?=\s)/gm, word => `&#${word.charCodeAt(0)};${word.slice(1)}`);
}

// A table cell is one line, and an unescaped pipe would end it.
function tableCell(value) {
return value.replace(/[ \t]*\n[ \t]*/g, ' ').replace(/\\[\s\S]|\|/g, match => match === '|' ? '\\|' : match);
}

function linkLabel(value) {
return value.replace(/[\\`*_~[\]{}<&|]/g, character => `&#${character.charCodeAt(0)};`);
}
Expand Down Expand Up @@ -131,12 +136,19 @@ export function convertMarkdown(markdown, { source = 'Markdown', lineOffset = 0
if (entries.length) {
const start = node.children[first];
const end = node.children[index - 1];
const items = entries.map(entry => {
const description = raw(entry.node).replace(/^[^\n]+\n[ \t]*:\s+/, '');
return `<Term name="${attribute(entry.term)}">\n\n${nested(description, entry.node, 1)}\n\n</Term>`;
// Terms become a two-column table. Rename the headers to fit the page.
const rows = entries.map(entry => {
const [term, ...rest] = raw(entry.node).split('\n');
const description = rest.join('\n').replace(/^[ \t]*:\s+/, '');
const meaning = nested(description, entry.node, 1).trim();
// A table cell holds one paragraph; more would show as raw Markdown.
if (/\n[ \t]*\n/.test(meaning) || /^[ \t]*(?:\||[-*+][ \t]|>|#{1,6}[ \t]|\d+[.)][ \t]|```|~~~|<[A-Z])/m.test(meaning)) {
fail(source, entry.node, 'A term meaning with more than one paragraph or block needs a manual MDX conversion', lineOffset);
}
return `| ${tableCell(escapeText(term.trim()))} | ${tableCell(meaning)} |`;
});
edits.push({ start: start.position.start.offset, end: end.position.end.offset,
value: indent(`<Terms>\n\n${items.join('\n\n')}\n\n</Terms>`, start) });
value: indent(['| Term | Meaning |', '| --- | --- |', ...rows].join('\n'), start) });
index--;
} else visit(node.children[index]);
}
Expand Down
Loading
Loading