diff --git a/astro.config.mjs b/astro.config.mjs index 79fc334..93a70d4 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -10,8 +10,8 @@ export default defineConfig({ // The control planes page became the terragucci, HCP Terraform and Spacelift arenas. redirects: { '/compare/control-planes/': '/compare/terraform-platforms/', - // chant, CDK and Pulumi is argued on the blog; the scorecard compares them property by property. - '/compare/chant-cdk-and-pulumi/': '/accessible-ops/', + // chant, CDK and Pulumi is argued on the blog and in chant's docs. + '/compare/chant-cdk-and-pulumi/': 'https://intentius.io/chant/', }, integrations: [ starlight({ @@ -36,6 +36,9 @@ export default defineConfig({ social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/intentius' }], head: [ { tag: 'link', attrs: { rel: 'icon', href: '/favicon-48.png', sizes: '48x48', type: 'image/png' } }, + // The preview card for links to any page; the comparison sets its own. + { tag: 'meta', attrs: { property: 'og:image', content: 'https://intentius.io/og.png' } }, + { tag: 'meta', attrs: { name: 'twitter:image', content: 'https://intentius.io/og.png' } }, { tag: 'script', content: ` @@ -73,7 +76,7 @@ export default defineConfig({ { label: 'behold', link: 'https://intentius.io/behold/' }, { label: 'chant', link: 'https://intentius.io/chant/' }, { label: 'choudoufu', link: 'https://intentius.io/choudoufu/' }, - 'terragucci', + { label: 'terragucci', link: 'https://intentius.io/terragucci/' }, 'accessible-ops', ], }, diff --git a/public/brand/vendors/aws-cdk-on-dark.svg b/public/brand/vendors/aws-cdk-on-dark.svg new file mode 100644 index 0000000..813c47c --- /dev/null +++ b/public/brand/vendors/aws-cdk-on-dark.svg @@ -0,0 +1,11 @@ + + + + AWS-Cloud-Development-Kit_64_White + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/public/brand/vendors/aws-cdk-on-light.svg b/public/brand/vendors/aws-cdk-on-light.svg new file mode 100644 index 0000000..7d71198 --- /dev/null +++ b/public/brand/vendors/aws-cdk-on-light.svg @@ -0,0 +1,11 @@ + + + + AWS-Cloud-Development-Kit_64_Squid + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/public/brand/vendors/chant-on-dark.svg b/public/brand/vendors/chant-on-dark.svg new file mode 100644 index 0000000..3e87cb3 --- /dev/null +++ b/public/brand/vendors/chant-on-dark.svg @@ -0,0 +1 @@ + diff --git a/public/brand/vendors/chant-on-light.svg b/public/brand/vendors/chant-on-light.svg new file mode 100644 index 0000000..b823fb4 --- /dev/null +++ b/public/brand/vendors/chant-on-light.svg @@ -0,0 +1 @@ + diff --git a/public/brand/vendors/opentofu-on-dark.svg b/public/brand/vendors/opentofu-on-dark.svg new file mode 100644 index 0000000..33acd28 --- /dev/null +++ b/public/brand/vendors/opentofu-on-dark.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/brand/vendors/opentofu-on-light.svg b/public/brand/vendors/opentofu-on-light.svg new file mode 100644 index 0000000..7e8ff27 --- /dev/null +++ b/public/brand/vendors/opentofu-on-light.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/brand/vendors/pulumi-on-dark.svg b/public/brand/vendors/pulumi-on-dark.svg new file mode 100644 index 0000000..20f304a --- /dev/null +++ b/public/brand/vendors/pulumi-on-dark.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/brand/vendors/pulumi-on-light.svg b/public/brand/vendors/pulumi-on-light.svg new file mode 100644 index 0000000..89ca208 --- /dev/null +++ b/public/brand/vendors/pulumi-on-light.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/brand/vendors/terraform-on-dark.svg b/public/brand/vendors/terraform-on-dark.svg new file mode 100644 index 0000000..d1f825e --- /dev/null +++ b/public/brand/vendors/terraform-on-dark.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/brand/vendors/terraform-on-light.svg b/public/brand/vendors/terraform-on-light.svg new file mode 100644 index 0000000..542053d --- /dev/null +++ b/public/brand/vendors/terraform-on-light.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/og-compare.png b/public/og-compare.png new file mode 100644 index 0000000..fef31e1 Binary files /dev/null and b/public/og-compare.png differ diff --git a/public/og.png b/public/og.png new file mode 100644 index 0000000..9c3a6a9 Binary files /dev/null and b/public/og.png differ diff --git a/src/components/Arenas.astro b/src/components/Arenas.astro index c4de312..e4c7fb2 100644 --- a/src/components/Arenas.astro +++ b/src/components/Arenas.astro @@ -2,9 +2,8 @@ // A comparison as arenas, one per thing a reader decides on. `data` is a // module like src/data/arenas.mjs (the default: terragucci against HCP // Terraform and Spacelift): VENDORS, ARENAS, and optionally PLANS and GATED -// for a "what's included" arena, READ_ON and TRADEMARKS. The first vendor is -// ours. A scoreboard links the arenas. In each -// one, hovering or focusing a vendor grows its logo, shrinks the others and +// for a "what's included" arena, READ_ON, TRADEMARKS and TRY. The first +// vendor is ours. In each arena, hovering or focusing a vendor grows its logo, shrinks the others and // lights its column; the logos themselves are never tinted or recoloured, as // HashiCorp's and Spacelift's logo rules ask. Each cell links its source. // The gating arena takes the features you tick and names the plan each @@ -38,15 +37,9 @@ const need = (v, ids) => { }; const all = GATED.map((f) => f.id); -// The scoreboard counts marks that meet, per vendor and arena. The gating -// arena counts features on the vendor's cheapest plan. +// The best-answer key shows only where some cell is marked best. +const hasBest = GATED.length > 0 || ARENAS.some((a) => a.rows.some((r) => VENDORS.some((v) => r[v.id][0] === 'good'))); const lead = (a, v) => (v.id === OURS && a.lead ? a.lead.name : v.name); -const tally = (a) => Object.fromEntries(VENDORS.map((v) => [v.id, a.rows.filter((r) => r[v.id][0] === 'good').length])); -const board = [ - ...(GATED.length ? [{ id: 'gating', title: "What's included", of: GATED.length, - score: Object.fromEntries(VENDORS.map((v) => [v.id, GATED.filter((f) => f[v.id].plan === 0).length])) }] : []), - ...ARENAS.map((a) => ({ id: a.id, title: a.title, of: a.rows.length, score: tally(a), lead: a.lead?.name })), -]; const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.replace(/^www\./, '')); ---
v.name).join(', ')} compared`} style={[`--n:${VENDORS.length}`, ...VENDORS.map((v) => `--c-${v.id}:${v.color ?? 'var(--muted)'}`)].join(';')}> @@ -55,7 +48,7 @@ const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.re {TRY &&

{TRY.map(([text, href]) => {text} →)}

}

- the best answer in the row + {hasBest && the best answer in the row} ✗ doesn't do it {READ_ON && Sources read {READ_ON}.}

@@ -75,13 +68,13 @@ const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.re {a.rows.map((r) => (
- {r.q} + {r.q}{r.note && {r.note}} {VENDORS.map((v) => { const [m, text, src] = r[v.id]; return ( {GLYPH[m] && <>{GLYPH[m]} }{text} - {host(src)} + {src && {host(src)}} ); })} @@ -92,7 +85,7 @@ const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.re ))} {GATED.length > 0 &&
-

What's included

Tick what you need.

+

What's included

Tick what you need. These are terragucci's features; what it lacks is under The basics.

@@ -147,13 +140,13 @@ const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.re
{a.rows.map((r) => (
- {r.q} + {r.q}{r.note && {r.note}} {VENDORS.map((v) => { const [m, text, src] = r[v.id]; return ( {GLYPH[m] && <>{GLYPH[m]} }{text} - {host(src)} + {src && {host(src)}} ); })} @@ -223,21 +216,6 @@ const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.re :root[data-theme='light'] .arenas { --good: #11805a; --warn: #8f6200; --bad: #c23434; } .arenas a { text-decoration: none; color: var(--in-accent); } -/* ---- scoreboard ---- */ -.ar-board { display: grid; grid-template-columns: repeat(auto-fill, minmax(13.5rem, 1fr)); gap: 0.7rem; margin: 0 0 1.2rem; } -.ar-card { - display: grid; gap: 0.3rem; padding: 0.75rem 0.85rem; border: 1px solid var(--rule); border-radius: 10px; - background: var(--panel); color: var(--ink) !important; transition: transform .2s ease, border-color .2s ease; -} -.ar-card:hover, .ar-card:focus-visible { transform: translateY(-2px); border-color: var(--in-accent); } -.ar-card > b { font-size: 0.9rem; margin-bottom: 0.2rem; } -.ar-bar { display: grid; grid-template-columns: 6.5rem 1fr 2.2rem; align-items: center; gap: 0.45rem; font-size: 0.72rem; color: var(--muted); } -.ar-bar-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.ar-bar-track { height: 6px; border-radius: 3px; background: var(--rule); overflow: hidden; } -.ar-bar-fill { display: block; height: 100%; width: var(--w); background: var(--muted); border-radius: 3px; } -.ar-bar.ar-ours .ar-bar-fill { background: var(--in-accent); } -.ar-bar-n { text-align: right; font-variant-numeric: tabular-nums; } - .ar-legend { display: flex; flex-wrap: wrap; gap: 0.4rem 1.2rem; font-size: 0.82rem; color: var(--muted); margin: 0 0 1.4rem; } .ar-hint { color: var(--faint); font-style: italic; } .ar-mark { font-weight: 700; } @@ -260,7 +238,7 @@ const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.re /* ---- an arena ---- */ .ar-arena { margin: 0 0 2.4rem; scroll-margin-top: 5rem; } -.ar-arena header h2 { font-size: 1.25rem; margin: 0 0 0.2rem; padding-left: 0.6rem; border-left: 4px solid; border-image: linear-gradient(var(--c-tg, var(--in-accent)), var(--c-hcp, var(--in-accent)), var(--c-sl, var(--in-accent))) 1; } +.arenas .ar-arena header h2 { font-size: 1.25rem; margin: 0 0 0.2rem; padding-left: 0.6rem; border-left: 4px solid !important; border-image: linear-gradient(var(--c-tg, var(--in-accent)), var(--c-hcp, var(--in-accent)), var(--c-sl, var(--in-accent))) 1 !important; } .ar-arena header p { margin: 0 0 0.8rem; color: var(--muted); font-size: 0.9rem; } .ar-grid { border: 1px solid var(--rule); border-radius: 10px; overflow: hidden; } .ar-row { display: grid; grid-template-columns: var(--cols); align-items: stretch; border-top: 1px solid var(--rule); } @@ -268,9 +246,11 @@ const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.re .ar-head { border-top: none; align-items: center; background: var(--panel); position: sticky; top: 0; z-index: 1; } .ar-head .ar-vendor { box-shadow: inset 0 -3px 0 var(--vc, transparent); } .ar-q { font-weight: 600; color: var(--ink); } +.ar-note { display: block; margin-top: 0.2rem; font-weight: 400; font-size: 0.78rem; line-height: 1.35; color: var(--muted) !important; } .ar-cell { color: var(--ink); transition: background-color .2s ease; } .ar-cell small { display: block; color: var(--muted); font-size: 0.78rem; } -.ar-src { display: block; font-size: 0.7rem; margin-top: 0.15rem; opacity: 0.8; } +.ar-src { display: block; font-size: 0.7rem; margin-top: 0.15rem; opacity: 0.8; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.ar-cell { min-width: 0; overflow-wrap: anywhere; } /* The logo for each vendor: it grows when followed, the others shrink. */ .ar-vendor { @@ -282,6 +262,7 @@ const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.re .ar-vendor img { height: 2rem; width: auto; max-width: 100%; object-fit: contain; } .ar-vendor img.ar-pixel { image-rendering: pixelated; } .ar-vendor img.ar-wide { height: 1.7rem; } +.ar-vendor img.ar-wide.ar-half { height: 1.45rem; } .ar-logo-name { font: 700 0.8rem/1 var(--in-display); } /* One logo file per theme: the dark one by default, the light one under data-theme='light'. */ .ar-vendor .ar-on-light { display: none; } @@ -317,5 +298,7 @@ const host = (u) => (u.startsWith('/') ? 'intentius.io' : new URL(u).hostname.re .ar-wordmark { font-size: 0.8rem; } .ar-vendor img { height: 1.4rem; } .ar-vendor img.ar-wide { height: 1.05rem; } + .ar-total .ar-cell b { font-size: 0.85rem; } + .ar-src { font-size: 0.62rem; } } diff --git a/src/components/CompareTeaser.astro b/src/components/CompareTeaser.astro index c1e36a9..7a5518c 100644 --- a/src/components/CompareTeaser.astro +++ b/src/components/CompareTeaser.astro @@ -18,7 +18,7 @@ const rows = [ { q: 'The plan you need for all of it', cells: Object.fromEntries(VENDORS.map((v) => [v.id, ['', need(v.id)]])) }, { q: 'How you pay', cells: row('basics', 'How you pay') }, { q: 'Your pipeline, if you leave', cells: row('leaving', 'Your pipeline') }, - { q: 'Two applies on different resources', cells: row('concurrency', 'Two applies on different resources') }, + { q: 'What one apply locks', cells: row('concurrency', 'What one apply locks') }, ]; const ours = VENDORS[0].id; --- diff --git a/src/components/Landing.astro b/src/components/Landing.astro index 5cbf360..46cb397 100644 --- a/src/components/Landing.astro +++ b/src/components/Landing.astro @@ -1,20 +1,29 @@ --- // The home page's parts. `part="top"` (the default) is the pixel eye, the -// wordmark, the mark between two gold hairlines and the tagline, then the -// picker (Picker.astro). `part="tools"` is the four open-source tools as -// tiles, under the "Open source" heading in index.mdx. +// wordmark, the mark between two gold hairlines and one line on what this is. +// `part="picker"` is the situations picker (Picker.astro). `part="tools"` is +// the four open-source tools as tiles, under the "Open source" heading in +// index.mdx. // One import keeps index.mdx's prose free of import lines. import Picker from './Picker.astro'; interface Props { - part?: 'top' | 'tools'; + part?: 'top' | 'picker' | 'tools' | 'cards'; } const { part = 'top' } = Astro.props; +// One row of cards under the header, each straight into a page of the site. +const cards = [ + { title: 'terragucci, HCP Terraform and Spacelift', body: 'Everything included, against Enterprise plans.', href: '/compare/terraform-platforms/' }, + { title: 'choudoufu and the state file', body: 'The owner on the resource, no state lock.', href: '/compare/choudoufu-and-terraform-state/' }, + { title: 'Move off another runner', body: 'Atlantis, Terraform Cloud or Spacelift, one repo at a time.', href: '/for/moving/' }, + { title: 'Carve up a large estate', body: 'Hundreds of roots, or one state file shared by too many teams.', href: '/for/estate/' }, +]; + const tools = [ { name: 'behold', href: 'https://intentius.io/behold/', body: 'The whole estate as one picture, coloured by what has drifted.' }, { name: 'chant', href: 'https://intentius.io/chant/', body: 'Infrastructure as typed code, checked before anything runs.' }, - { name: 'choudoufu', href: 'https://intentius.io/choudoufu/', body: 'OpenTofu with a lock and an owner on every resource.' }, + { name: 'choudoufu', href: 'https://intentius.io/choudoufu/', body: 'OpenTofu with the owner on every resource, and no state lock.' }, { name: 'terragucci', href: 'https://intentius.io/terragucci/', body: 'One workflow for every Terraform repo, run in your own CI.' }, ]; --- @@ -24,11 +33,17 @@ const tools = [

INTENTIUS

-

Infrastructure consulting

- )} +{part === 'picker' && } +{part === 'cards' && ( + +)} {part === 'tools' && (