From 2bd7996b8a03850d53b6acd02815584dc6a61483 Mon Sep 17 00:00:00 2001 From: Rodolfo Hansen Date: Thu, 11 Jun 2026 16:08:17 +0200 Subject: [PATCH 1/3] Landing redesign: typed-systems editorial with GSAP motion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructures the home page into a full studio narrative — hero, positioning, services, process, case studies, technical excellence, research/open source, writing, final CTA — while keeping the founder copy substance (narrowing intro, ideal-customer paragraph, kinds of work, recent outcomes, Fixed, libraries, articles) and both hero CTAs. Visual system extends the existing tokens: warm paper base (deep ink in dark mode), Fraunces variable serif for display headlines (landing page only), monospace chapter marks, fine indigo linework, and a dark full-bleed engineering-depth section. Anchors #main, #consulting, #libraries, #fixed, #writing are preserved. Motion (GSAP + ScrollTrigger via CDN, defer): hero line-mask entrance with bars that literally narrow, scroll-triggered section reveals, scrubbed process rail, case-study parallax, layer-stack draw-in, and magnetic CTAs. All motion is additive — content is fully visible with JS disabled — and skipped entirely under prefers-reduced-motion. The hero canvas flow-field pauses off-screen and on hidden tabs. Fixes a latent layout trap: .site-header is a full-width left float the theme never clears, which squeezes flex sections (BFCs) to zero width; .cp-home now clears it. Co-Authored-By: Claude Fable 5 --- _layouts/home.html | 557 +++++++++++++++++++++--------- _sass/home.scss | 798 +++++++++++++++++++++++++++++++++++++++++++ assets/css/main.scss | 7 + assets/js/home.js | 211 ++++++++++++ index.html | 1 - 5 files changed, 1404 insertions(+), 170 deletions(-) create mode 100644 _sass/home.scss create mode 100644 assets/js/home.js diff --git a/_layouts/home.html b/_layouts/home.html index 6fa6299..bbc06ba 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -1,199 +1,418 @@ --- layout: default -bootstrap: true --- -
- - {% comment %} - Plain hero — no theme call-out, no background-image trick. - {% endcomment %} -
-

{{ site.header_text | default: site.title }}

- {% if site.header_subtitle %} -

{{ site.header_subtitle }}

- {% endif %} -

- What we do - - Book a scoping call - -

-
- A type signature being narrowed three times: String (space for bugs: ∞), Int (bugs can still hide in an Int), and a domain-specific type alias (not so much when you clearly delimit your domain values). -
- Narrowing: the same shape that runs through every engagement. -
-
-
- -
-
-
-

+{% comment %} + Landing page — "typed-systems editorial" redesign. + + Structure: hero → positioning → services → process → case studies → + technical excellence (dark) → research/open source → writing → CTA. + + Anchors #main, #consulting, #libraries, #fixed and #writing are kept + from the previous layout so the navbar "What we do" link and any + external deep links keep resolving. + + Motion lives in assets/js/home.js (GSAP + ScrollTrigger, CDN, defer). + Every element is visible by default — JS only *adds* motion, so the + page reads fine with JS disabled or prefers-reduced-motion set. +{% endcomment %} + + + + + +

+ + +
+ + +
+

+ Consulting · Open source · Research +

+ +

+ Types as guardrails + for AI agents. +

+ +

+ We ship production code paired with LLMs, narrowed by + construction — so fewer wrong programs compile, and the ones + that do are easier for humans and machines to trust. +

+ + + +
+
+
String
+

space for bugs: ∞

+
+
+
Int
+

bugs can still hide in an Int

+
+
+
type YourDomainValues = ...
+

not so much when you clearly delimit your domain values

+
+
+ Narrowing: the same shape that runs through every engagement. +
+
+
+
+ + +
+
+
+

01 — Positioning

+

A consultancy for serious codebases.

+
+ +
+

You're a fit if you run a platform team using a typed language (C++, Rust, Scala, TypeScript) — typically Series B–D, - 20–80 engineers — with a regression you can name, an improvement - plan you've already postponed twice, and a real interest in - letting LLMs accelerate the work without quietly eroding what - types guarantee. + 20–80 engineers — with a regression you can name, an + improvement plan you've already postponed twice, and a real + interest in letting LLMs accelerate the work without quietly + eroding what types guarantee.

+ +
    +
  • aTechnical strategy A defensible second opinion before you commit a quarter to the wrong plan.
  • +
  • bType-driven architecture Design review through a cardinality lens — fewer representable states, fewer bugs.
  • +
  • cFull-stack delivery Typed-FP features shipped in Scala, Rust, and TypeScript, inside your repo.
  • +
  • dAgent-native AI workflows LLMs constrained by types, properties, and evals — output you can trust.
  • +
  • eCloud-native systems P95s, uptime, and the infra bill treated as design inputs, not afterthoughts.
  • +
  • fTeam enablement Workshops distilled from a decade of constructive-programming practice.
  • +
-
+ -
- -

Consulting

-

- For teams that can't afford another regressive improvement plan. -

-
-
+ +
+
+
+

02 — Services

+

For teams that can't afford another regressive improvement plan.

+
+

- Constructive Programming partners with teams on their codebases, - which have accumulated the usual debts of growth — typed - boundaries treated as untyped, required fields treated as - optional, 800-line objects nobody dares touch. For us, often, - a single thread runs through every engagement: narrowing. - Shrinking the set of values a type can hold so that fewer wrong - programs compile, so thousands of lines of code go red and can be - safely deleted. So the lines that do stay are - that much easier for both humans and machines to reason about. + Constructive Programming partners with teams on codebases that + have accumulated the usual debts of growth — typed boundaries + treated as untyped, required fields treated as optional, + 800-line objects nobody dares touch. A single thread runs + through every engagement: narrowing. Shrinking the set + of values a type can hold so that fewer wrong programs compile, + so thousands of lines go red and can be safely + deleted.

-

- What is different about our practice: we ship production - code paired with LLMs. Constructive programming - principles are how we keep generative models useful — narrow - types and machine-checked properties are the strictest - guardrails an agent can be made to operate against, and they - turn non-deterministic output into a verifiable artefact. We - help teams adopt agent-native development strategies: pairing - the non-deterministic behaviour of agents with the deterministic - harnesses — static analysis, refinement types, property tests, - goal-enriched context — that grant the ability to trust the - output of a self-correcting development session. + What is different about our practice: we ship + production code paired with LLMs. Narrow types and + machine-checked properties are the strictest guardrails an + agent can operate against — they turn non-deterministic output + into a verifiable artefact.

+
-

The kinds of work we take on:

-
    -
  • Architecture and advisory — design review - through a cardinality lens; narrowing type signatures; - identifying where capability-based abstractions, refinement - types, or algebraic effects unlock simpler code.
  • -
  • IC contributions — embedded in your team - to ship typed-FP features in Scala or Rust, with the same - constructive discipline applied to PRs as to architecture.
  • -
  • Agent-native development — building the - deterministic harness around your codebase (linters, types, - properties, eval suites) so your team can move faster with - LLMs and trust the output.
  • -
  • Workshops and training — distilled from a - decade of constructive-programming workshops, tailored to - your stack and team seniority.
  • -
+
+
+

Advisory

+

Architecture & advisory

+

+ Design review through a cardinality lens; narrowing type + signatures; identifying where capability-based abstractions, + refinement types, or algebraic effects unlock simpler code. +

+

→ A written, defensible improvement plan — with effort estimates — before you spend two quarters on the wrong one.

+ Architecture review +
-

- - Our three packaged offerings — - Architecture review, Fractional advisory, Embedded IC - — with cadence, deliverables, and how a scoping call goes. - -

+
+

Delivery

+

Embedded IC contributions

+

+ Embedded in your team to ship typed-FP features in Scala or + Rust, with the same constructive discipline applied to PRs + as to architecture. +

+

→ Senior delivery hands in your repo within weeks — and a team the discipline rubs off on.

+ Embedded IC +
-

- - Recent outcomes · - 11h → 15min data pipeline at Otravo · - P95 <20ms, 100% uptime, −30% infra at Bodireel · - Total Program Cardinality static analysis at Royal Philips - -

+
+

AI

+

Agent-native development

+

+ Building the deterministic harness around your codebase — + linters, types, properties, eval suites — so the + non-deterministic behaviour of agents becomes a + self-correcting development session. +

+

→ Your team moves faster with LLMs, and can trust the output.

+ Fractional advisory +
-

- Engagements range from a focused two-week review to a multi-month - embedded role. Available for select engagements; based in the - Netherlands; comfortable with EU MSAs and standard NDAs. -

+
+

Enablement

+

Workshops & training

+

+ Distilled from a decade of constructive-programming + workshops, tailored to your stack and your team's + seniority mix. +

+

→ The practice stays after we leave.

+ All engagements +
+
-

- - Book a scoping call - -

-

- - or email consulting@constructive.dev - -

+ +
+
+ +
+
+
+

03 — Process

+

Systematic, collaborative, low-risk.

+
+ +
+ +
    +
  1. +

    01

    +

    Discover

    +

    A 30-minute scoping call — free, and honest about fit and + start dates. You describe the problem; we work out together + which offering fits. A written one-page scope follows within + 48 hours.

    +
  2. +
  3. +

    02

    +

    Design

    +

    Architecture review through a cardinality lens: observed + structure, the strongest risks, and three to five concrete + narrowing moves with effort estimates.

    +
  4. +
  5. +

    03

    +

    Build

    +

    Pull requests, design docs, and eval suites — shipped + under your repo and your release cadence, paired with LLMs + against deterministic harnesses.

    +
  6. +
  7. +

    04

    +

    Launch

    +

    Machine-checked before it merges: types, properties, and + evals green. Pair sessions on demand so your team owns what + ships.

    +
  8. +
  9. +

    05

    +

    Improve

    +

    Monthly written summaries for stakeholders, a hiring + rubric, team rituals — the constructive discipline stays + after we leave.

    +
  10. +
+ +
+

If the readout doesn't surface at least one narrowing move + worth more than the fee, we refund half. The report is yours to + keep regardless.

+ +
-
- -
- -

Open source contributions

- {% include libraries.html featured=true %} -

- All libraries → -

-
- -
- -

A new functional language

-

- Program against capabilities, not data structures. -

-
-
-

- Fixed takes recent advances in programming language design — - algebraic effects with evidence passing, Perceus reference counting, - functional-but-in-place updates — and pushes the whole language up to - a capabilities level. You describe what your methods need in order to function (cap); - the compiler picks the datastructures and machine layout. Properties (prop) - live alongside the capabilities they govern; refinement types are - first-class. -

-

- The design draws on - Quine's predicate functor logic - and Quantitative Type Theory. The language is still at Phase 0 — design — it is in flight, - with eleven example programs exercising the language. The compiler - (planned in Scala, targeting C) and the formal spec on their way. + + + +

+
+
+

04 — Recent outcomes

+

Results you can put in a board deck.

+
+ +
+
+ +
+

Otravo · Travel e-commerce

+

A data pipeline measured in hours

+

A core pipeline ran for eleven hours — too slow to rerun, + too central to ignore. Rebuilt with optics and a narrowed + data model.

+

Eleven hours to fifteen minutes.

+
+
+ +
+ +
+

Bodireel · Consumer product

+

Fast, up, and cheaper to run

+

A consumer-facing system that had to stay fast and stay up + while the infrastructure bill came down — performance and + cost treated as design inputs.

+

P95 under 20ms, 100% uptime, 30% less infra.

+
+
+ +
+ +
+

Royal Philips · Health technology

+

Correctness at programme scale

+

Static analysis grounded in Total Program Cardinality — + counting the inhabitants of a program's types to keep + quality measurable as the codebase grows.

+

A code-quality metric the compiler can enforce.

+
+
+
+
+
+ + +
+
+
+

05 — Engineering depth

+

Machine-checked, by construction.

+

The harness we build around every codebase — each layer + narrows what the one above it can get wrong.

+
+ +
+ + +
    +
  • Scalable architecture Capability-based abstractions and algebraic effects that stay simple as the system grows.
  • +
  • APIs & contracts Narrowed type signatures at every boundary — required means required.
  • +
  • Security Illegal states unrepresentable beats illegal states validated.
  • +
  • Performance P95s and pipeline runtimes treated as deliverables, not side effects.
  • +
  • Clean code Fewer representable states means thousands of lines can go red — and be safely deleted.
  • +
  • Maintainable systems Deterministic harnesses your team — and your agents — keep building against.
  • +
+
+
+
+ + +
+
+
+

06 — Research & open source

+

The practice, in public.

+
+ +
+ {% include libraries.html featured=true %} +
+ +
+

Fixed — a new functional language

+

+ Program against capabilities, not data structures. Fixed + pushes algebraic effects, Perceus reference counting, and + functional-but-in-place updates up to a capabilities level: + you describe what your methods need (cap), the + compiler picks the data structures and machine layout. + Properties (prop) live alongside the capabilities + they govern; refinement types are first-class. Phase 0 — + design — with eleven example programs exercising the language.

-

+

-
- -
- -

Long-form on type systems, optics, cardinality, and LLM-augmented engineering.

- {% include medium-posts.html %} -

- - More on Medium → - -

-
+ + + +
+
+
+

07 — Writing

+

Long-form on type systems, optics, cardinality, and LLM-augmented engineering.

+
+ +
+ {% include medium-posts.html %} +
+ +

+ More on Medium → +

+
+
+ + +
+
+

+ Available for select engagements +

+

Build something useful, scalable, and beautifully crafted.

+

+ Engagements range from a focused two-week review to a multi-month + embedded role. Based in the Netherlands; comfortable with EU MSAs + and standard NDAs. The scoping call is free — and honest about fit. +

+ +
+
+ + + + diff --git a/_sass/home.scss b/_sass/home.scss new file mode 100644 index 0000000..d8bc07a --- /dev/null +++ b/_sass/home.scss @@ -0,0 +1,798 @@ +/* ============================================================ + Landing page — "typed-systems editorial" layer. + + Scoped to .cp-home (plus a body background hook). Tokens are CSS + custom properties so the existing light/dark theme toggle + (html[data-theme]) keeps working. Fraunces (variable serif) is the + display face for the landing page only; body copy stays on the + system sans stack defined in main.scss. + + Motion notes: everything here is visible by default. GSAP + (assets/js/home.js) only *adds* entrances — no content is hidden + in CSS, so no-JS and prefers-reduced-motion users get the full + page, static. + ============================================================ */ + +$hp-serif: "Fraunces", "Iowan Old Style", Georgia, "Times New Roman", serif; + +.cp-home { + /* --- Tokens: light (warm paper) ------------------------------- */ + --hp-bg: #faf8f3; + --hp-ink: #1b1b26; + --hp-muted: rgba(27, 27, 38, 0.62); + --hp-faint: rgba(27, 27, 38, 0.45); + --hp-line: rgba(27, 27, 38, 0.14); + --hp-line-soft: rgba(27, 27, 38, 0.07); + --hp-accent: #3b3b8c; + --hp-accent-ink: #32327a; + --hp-accent-soft: rgba(59, 59, 140, 0.08); + --hp-accent-mid: rgba(59, 59, 140, 0.18); + --hp-card-bg: #fffdf9; + --hp-stat-bg: linear-gradient(135deg, rgba(59, 59, 140, 0.10), rgba(59, 59, 140, 0.03)); + + background: var(--hp-bg); + color: var(--hp-ink); + overflow-x: clip; + /* .site-header is a full-width left float the theme never clears. + Block children overlap it harmlessly, but flex sections (BFCs) + would be squeezed into the zero remaining space beside it. */ + clear: both; +} + +html[data-theme="dark"] .cp-home { + --hp-bg: #1f242c; + --hp-ink: #d9e0ea; + --hp-muted: rgba(217, 224, 234, 0.66); + --hp-faint: rgba(217, 224, 234, 0.45); + --hp-line: rgba(217, 224, 234, 0.16); + --hp-line-soft: rgba(217, 224, 234, 0.08); + --hp-accent: #9b9bf2; + --hp-accent-ink: #b3b3f7; + --hp-accent-soft: rgba(138, 138, 240, 0.10); + --hp-accent-mid: rgba(138, 138, 240, 0.22); + --hp-card-bg: #262c35; + --hp-stat-bg: linear-gradient(135deg, rgba(138, 138, 240, 0.14), rgba(138, 138, 240, 0.04)); +} + +/* Page background must extend behind navbar + footer, not just the + .cp-home block, or the warm paper ends in a hard seam. */ +html body:has(.cp-home) { background: #faf8f3; } +html[data-theme="dark"] body:has(.cp-home) { background: #1f242c; } + +/* The theme paints header/footer with --background (pure white / + #22272e), which seams against the warm paper. Let them inherit. */ +body:has(.cp-home) .site-header, +body:has(.cp-home) .site-footer { background: transparent; } + +/* --- Shared primitives ------------------------------------------ */ + +.cp-home .hp-wrap { + max-width: 76rem; + margin-inline: auto; + padding-inline: clamp(1.25rem, 4vw, 3rem); +} + +.cp-home .hp-section { + padding-block: clamp(4.5rem, 9vw, 8rem); + border-top: 1px solid var(--hp-line-soft); +} + +.cp-home .hp-eyebrow { + font-family: $cp-mono; + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--hp-accent); + margin: 0 0 1.25rem; +} + +.cp-home .hp-h2 { + font-family: $hp-serif; + font-weight: 560; + font-variation-settings: "opsz" 90; + font-size: clamp(2.1rem, 4.5vw, 3.4rem); + line-height: 1.06; + letter-spacing: -0.015em; + margin: 0; + max-width: 22ch; + text-wrap: balance; +} + +.cp-home .hp-h2 em { + font-style: italic; + color: var(--hp-accent-ink); +} + +.cp-home .hp-section-head { margin-bottom: clamp(2.5rem, 5vw, 4.5rem); } + +.cp-home .hp-section-lead { + font-size: 1.15rem; + color: var(--hp-muted); + max-width: 52ch; + margin: 1.25rem 0 0; + line-height: 1.6; +} + +.cp-home .hp-large { + font-size: clamp(1.2rem, 1.8vw, 1.45rem); + line-height: 1.55; + letter-spacing: -0.008em; + margin: 0; +} + +.cp-home .hp-aside { + font-size: 0.92rem; + color: var(--hp-muted); + margin: 0.85rem 0 0; +} + +.cp-home h3 { + font-family: $hp-serif; + font-weight: 600; + font-size: 1.45rem; + letter-spacing: -0.01em; + line-height: 1.15; + margin: 0 0 0.6rem; +} + +.cp-home a:focus-visible, +.cp-home .btn:focus-visible { + outline: 2px solid var(--hp-accent); + outline-offset: 3px; +} + +/* Buttons inherit main.scss; re-derive colours from tokens so the + dark hero/dark section stay legible. */ +.cp-home .btn-primary { + background-color: var(--hp-accent); + border-color: var(--hp-accent); + color: var(--hp-bg); +} +.cp-home .btn-primary:hover { background-color: var(--hp-accent-ink); border-color: var(--hp-accent-ink); color: var(--hp-bg); } +.cp-home .btn-outline-primary { color: var(--hp-accent); border-color: var(--hp-accent-mid); background: transparent; } +.cp-home .btn-outline-primary:hover { background: var(--hp-accent-soft); color: var(--hp-accent-ink); border-color: var(--hp-accent); } + +/* The theme's html[data-theme="dark"] button rules (teal --link) outrank + the .cp-home overrides above; restate them at dark specificity. */ +html[data-theme="dark"] .cp-home .btn-primary { + background-color: var(--hp-accent); + border-color: var(--hp-accent); + color: #14141d; +} +html[data-theme="dark"] .cp-home .btn-primary:hover, +html[data-theme="dark"] .cp-home .btn-primary:focus { + background-color: var(--hp-accent-ink); + border-color: var(--hp-accent-ink); + color: #14141d; +} +html[data-theme="dark"] .cp-home .btn-outline-primary { + color: var(--hp-accent); + border-color: var(--hp-accent-mid); +} +html[data-theme="dark"] .cp-home .btn-outline-primary:hover, +html[data-theme="dark"] .cp-home .btn-outline-primary:focus { + background: var(--hp-accent-soft); + color: var(--hp-accent-ink); + border-color: var(--hp-accent); +} + +/* ============================================================ Hero */ + +.cp-home .hp-hero { + position: relative; + padding-block: clamp(4rem, 9vh, 7rem) clamp(4rem, 8vw, 6.5rem); + min-height: min(92vh, 60rem); + display: flex; + align-items: center; +} + +.cp-home .hp-hero-bg { + position: absolute; + inset: 0; + pointer-events: none; + /* fine blueprint grid, faded at the edges */ + background-image: + linear-gradient(var(--hp-line-soft) 1px, transparent 1px), + linear-gradient(90deg, var(--hp-line-soft) 1px, transparent 1px); + background-size: 72px 72px; + -webkit-mask-image: radial-gradient(120% 90% at 50% 10%, black 30%, transparent 75%); + mask-image: radial-gradient(120% 90% at 50% 10%, black 30%, transparent 75%); +} + +.cp-home .hp-hero-bg canvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + opacity: 0.5; +} + +.cp-home .hp-hero .hp-wrap { position: relative; width: 100%; } + +.cp-home .hp-hero-eyebrow { margin-bottom: 2rem; color: var(--hp-faint); } +.cp-home .hp-hero-eyebrow span { color: var(--hp-accent); } + +.cp-home .hp-hero-title { + font-family: $hp-serif; + font-weight: 540; + font-variation-settings: "opsz" 144; + font-size: clamp(2.9rem, 8.2vw, 6.4rem); + line-height: 1.0; + letter-spacing: -0.022em; + margin: 0 0 1.75rem; + max-width: 14ch; +} + +.cp-home .hp-hero-title em { + font-style: italic; + color: var(--hp-accent-ink); +} + +.cp-home .hp-line { display: block; overflow: hidden; padding-block: 0.06em; } +.cp-home .hp-line-inner { display: inline-block; will-change: transform; } + +.cp-home .hp-hero-lead { + font-size: clamp(1.1rem, 1.6vw, 1.3rem); + line-height: 1.6; + color: var(--hp-muted); + max-width: 46ch; + margin: 0 0 2.25rem; +} + +.cp-home .hp-hero-cta { + display: flex; + flex-wrap: wrap; + gap: 0.85rem; + align-items: center; +} + +.cp-home .hp-magnetic { will-change: transform; } + +/* --- Narrowing diagram (HTML rebuild of hero-narrowing.svg) ------ */ + +.cp-home .hp-narrowing { + margin: clamp(3rem, 6vw, 4.5rem) 0 0; + max-width: 64rem; +} + +.cp-home .hp-bar-row { margin-bottom: 1.1rem; } +.cp-home .hp-bar-row-2 { margin-left: 7%; } +.cp-home .hp-bar-row-3 { margin-left: 14%; } + +.cp-home .hp-bar { + box-sizing: border-box; + display: flex; + align-items: center; + height: clamp(2.6rem, 5vw, 3.3rem); + padding-inline: clamp(0.9rem, 2vw, 1.4rem); + border: 1px solid var(--hp-accent); + border-radius: 0.55rem; + background: var(--hp-accent-soft); + transform-origin: left center; + overflow: hidden; + white-space: nowrap; +} +.cp-home .hp-bar-1 { width: 100%; } +.cp-home .hp-bar-2 { width: 61.8%; background: var(--hp-accent-mid); } +.cp-home .hp-bar-3 { width: 38.2%; background: var(--hp-accent-mid); border-width: 1.4px; } + +.cp-home .hp-bar code { + font-family: $cp-mono; + font-size: clamp(0.82rem, 1.5vw, 1.05rem); + font-weight: 500; + background: none; + color: var(--hp-ink); +} +.cp-home .hp-key { color: var(--hp-accent-ink); font-weight: 600; } +.cp-home .hp-dim { color: var(--hp-faint); } + +.cp-home .hp-bar-sub { + font-size: 0.88rem; + color: var(--hp-muted); + margin: 0.45rem 0 0 0.15rem; +} + +.cp-home .hp-bar-caption { + font-family: $cp-mono; + font-size: 0.78rem; + letter-spacing: 0.06em; + color: var(--hp-faint); + margin-top: 1.75rem; +} + +@media (max-width: 640px) { + .cp-home .hp-bar-row-2 { margin-left: 3%; } + .cp-home .hp-bar-row-3 { margin-left: 6%; } + .cp-home .hp-bar-2 { width: 82%; } + .cp-home .hp-bar-3 { width: 70%; } + .cp-home .hp-bar-3 code { font-size: 0.7rem; } +} + +/* ==================================================== Positioning */ + +.cp-home .hp-positioning-grid { + display: grid; + gap: clamp(2.5rem, 5vw, 5rem); + align-items: start; +} +@media (min-width: 900px) { + .cp-home .hp-positioning-grid { grid-template-columns: 5fr 6fr; } +} + +.cp-home .hp-capabilities { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: 1fr; + gap: 0; +} +@media (min-width: 560px) { + .cp-home .hp-capabilities { grid-template-columns: 1fr 1fr; column-gap: 2.5rem; } +} + +.cp-home .hp-capabilities li { + border-top: 1px solid var(--hp-line); + padding: 1.1rem 0 1.3rem; + font-size: 0.97rem; + line-height: 1.5; + color: var(--hp-muted); +} + +.cp-home .hp-capabilities strong { + display: block; + color: var(--hp-ink); + font-weight: 650; + letter-spacing: -0.01em; + margin-bottom: 0.3rem; + font-size: 1.05rem; +} + +.cp-home .hp-cap-index { + float: right; + font-family: $cp-mono; + font-size: 0.72rem; + color: var(--hp-faint); + padding-top: 0.25rem; +} + +/* ======================================================= Services */ + +.cp-home .hp-services-intro { + display: grid; + gap: clamp(1.5rem, 3vw, 3rem); + margin-bottom: clamp(2.5rem, 5vw, 4rem); + max-width: 100%; +} +@media (min-width: 900px) { + .cp-home .hp-services-intro { grid-template-columns: 1fr 1fr; } +} +.cp-home .hp-services-intro p { + margin: 0; + font-size: 1.04rem; + line-height: 1.65; + color: var(--hp-muted); +} +.cp-home .hp-services-intro strong, +.cp-home .hp-services-intro em { color: var(--hp-ink); } + +.cp-home .hp-services-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1.1rem; +} +@media (min-width: 760px) { + .cp-home .hp-services-grid { grid-template-columns: 1fr 1fr; } +} + +.cp-home .hp-card { + display: flex; + flex-direction: column; + border: 1px solid var(--hp-line); + border-radius: 0.9rem; + background: var(--hp-card-bg); + padding: clamp(1.4rem, 2.5vw, 2rem); + transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), + border-color 0.35s ease, + box-shadow 0.35s ease; +} + +.cp-home .hp-card:hover { + transform: translateY(-5px); + border-color: var(--hp-accent-mid); + box-shadow: 0 18px 40px -22px rgba(27, 27, 60, 0.35); +} + +.cp-home .hp-card-tag { + font-family: $cp-mono; + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--hp-faint); + margin: 0 0 1.4rem; +} + +.cp-home .hp-card-body { + font-size: 0.99rem; + line-height: 1.6; + color: var(--hp-muted); + margin: 0 0 1.4rem; +} + +.cp-home .hp-card-outcome { + font-size: 1.02rem; + line-height: 1.5; + color: var(--hp-ink); + font-weight: 550; + letter-spacing: -0.006em; + border-top: 1px solid var(--hp-line-soft); + padding-top: 1.1rem; + margin: auto 0 1.4rem; +} + +.cp-home .hp-card-link { + font-family: $cp-mono; + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--hp-accent); + text-decoration: none; + align-self: flex-start; + position: relative; +} +.cp-home .hp-card-link::after { + content: ""; + position: absolute; + left: 0; bottom: -3px; + width: 100%; + height: 1px; + background: var(--hp-accent); + transform: scaleX(0); + transform-origin: left center; + transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1); +} +.cp-home .hp-card-link:hover::after { transform: scaleX(1); } + +.cp-home .hp-services-cta { + margin-top: clamp(2.5rem, 5vw, 4rem); + text-align: center; +} + +/* ======================================================== Process */ + +.cp-home .hp-process { + position: relative; + max-width: 46rem; +} + +.cp-home .hp-process-rail { + position: absolute; + top: 0.4rem; + bottom: 0.4rem; + left: 0.45rem; + width: 1px; + background: var(--hp-line); +} +.cp-home .hp-process-line { + position: absolute; + inset: 0; + background: var(--hp-accent); + transform-origin: top center; + /* JS scrubs scaleY 0→1; without JS the line is simply full. */ +} + +.cp-home .hp-steps { + list-style: none; + margin: 0; + padding: 0; +} + +.cp-home .hp-step { + position: relative; + padding: 0 0 clamp(2.2rem, 4vw, 3.2rem) clamp(2.4rem, 5vw, 3.5rem); +} +.cp-home .hp-step:last-child { padding-bottom: 0; } + +.cp-home .hp-step::before { + content: ""; + position: absolute; + left: 0.18rem; + top: 0.42rem; + width: 0.6rem; + height: 0.6rem; + border-radius: 50%; + background: var(--hp-bg); + border: 1.5px solid var(--hp-accent); +} + +.cp-home .hp-step-num { + font-family: $cp-mono; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.16em; + color: var(--hp-accent); + margin: 0 0 0.35rem; +} + +.cp-home .hp-step p:last-child { + margin: 0; + font-size: 0.99rem; + line-height: 1.6; + color: var(--hp-muted); + max-width: 52ch; +} + +.cp-home .hp-pullquote { + margin: clamp(3rem, 6vw, 5rem) 0 0 auto; + max-width: 38rem; + border: 0; + border-left: 2px solid var(--hp-accent); + padding: 0.4rem 0 0.4rem 1.75rem; +} +.cp-home .hp-pullquote p { + font-family: $hp-serif; + font-style: italic; + font-size: clamp(1.25rem, 2.2vw, 1.6rem); + line-height: 1.4; + margin: 0 0 0.85rem; +} +.cp-home .hp-pullquote footer { + font-family: $cp-mono; + font-size: 0.78rem; + letter-spacing: 0.08em; + color: var(--hp-faint); +} + +/* =================================================== Case studies */ + +.cp-home .hp-cases { + display: flex; + flex-direction: column; + gap: clamp(2.5rem, 5vw, 4.5rem); +} + +.cp-home .hp-case { + display: grid; + gap: clamp(1.5rem, 3vw, 3rem); + align-items: center; +} +@media (min-width: 860px) { + .cp-home .hp-case { grid-template-columns: 7fr 5fr; } + .cp-home .hp-case:nth-child(even) .hp-case-visual { order: 2; } +} + +.cp-home .hp-case-visual { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.5rem; + aspect-ratio: 16 / 9; + border: 1px solid var(--hp-line); + border-radius: 1.1rem; + background: + linear-gradient(var(--hp-line-soft) 1px, transparent 1px) 0 0 / 48px 48px, + linear-gradient(90deg, var(--hp-line-soft) 1px, transparent 1px) 0 0 / 48px 48px, + var(--hp-stat-bg); + overflow: hidden; +} + +.cp-home .hp-case-stat { + font-family: $hp-serif; + font-weight: 540; + font-variation-settings: "opsz" 144; + font-size: clamp(3rem, 7vw, 5.5rem); + letter-spacing: -0.03em; + color: var(--hp-accent-ink); + line-height: 1; +} + +.cp-home .hp-case-statsub { + font-family: $cp-mono; + font-size: 0.78rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--hp-muted); +} + +.cp-home .hp-case-copy .hp-eyebrow { margin-bottom: 0.85rem; } +.cp-home .hp-case-copy h3 { max-width: 18ch; } +.cp-home .hp-case-copy p { + font-size: 0.99rem; + line-height: 1.6; + color: var(--hp-muted); + max-width: 44ch; + margin: 0 0 1rem; +} + +.cp-home .hp-case-result { + font-weight: 650; + color: var(--hp-ink) !important; + letter-spacing: -0.008em; +} + +/* =========================================== Technical excellence */ + +.cp-home .hp-dark { + --hp-bg: #14141d; + --hp-ink: #e8e8f2; + --hp-muted: rgba(232, 232, 242, 0.64); + --hp-faint: rgba(232, 232, 242, 0.42); + --hp-line: rgba(232, 232, 242, 0.16); + --hp-line-soft: rgba(232, 232, 242, 0.08); + --hp-accent: #a3a3f5; + --hp-accent-ink: #bcbcf8; + --hp-accent-soft: rgba(163, 163, 245, 0.10); + --hp-accent-mid: rgba(163, 163, 245, 0.26); + + background: #14141d; + color: var(--hp-ink); + border-top: 0; +} + +.cp-home .hp-excellence { + display: grid; + gap: clamp(2.5rem, 5vw, 5rem); + align-items: center; +} +@media (min-width: 900px) { + .cp-home .hp-excellence { grid-template-columns: 6fr 5fr; } +} + +.cp-home .hp-layers { + display: flex; + flex-direction: column; + gap: 0.7rem; +} + +.cp-home .hp-layer { + display: flex; + align-items: baseline; + gap: 1rem; + border: 1px solid var(--hp-accent-mid); + border-radius: 0.6rem; + background: var(--hp-accent-soft); + padding: 0.85rem 1.25rem; + margin-inline: auto; + width: 100%; + white-space: nowrap; + overflow: hidden; +} +/* each layer narrows the one above it — the hero motif, restated */ +.cp-home .hp-layer:nth-child(2) { width: 86%; } +.cp-home .hp-layer:nth-child(3) { width: 72%; } +.cp-home .hp-layer:nth-child(4) { width: 58%; } +.cp-home .hp-layer:nth-child(5) { width: 44%; background: var(--hp-accent-mid); } + +.cp-home .hp-layer code { + font-family: $cp-mono; + font-size: 0.92rem; + font-weight: 600; + color: var(--hp-accent-ink); + background: none; +} +.cp-home .hp-layer span { + font-size: 0.85rem; + color: var(--hp-muted); + text-overflow: ellipsis; + overflow: hidden; +} + +@media (max-width: 560px) { + .cp-home .hp-layer span { display: none; } + .cp-home .hp-layer:nth-child(4) { width: 66%; } + .cp-home .hp-layer:nth-child(5) { width: 54%; } +} + +.cp-home .hp-depth { + list-style: none; + margin: 0; + padding: 0; +} +.cp-home .hp-depth li { + border-top: 1px solid var(--hp-line-soft); + padding: 0.95rem 0; + font-size: 0.95rem; + line-height: 1.55; + color: var(--hp-muted); +} +.cp-home .hp-depth li:last-child { border-bottom: 1px solid var(--hp-line-soft); } +.cp-home .hp-depth strong { + color: var(--hp-ink); + font-weight: 650; + margin-right: 0.5rem; +} + +/* ===================================== Research & open source */ + +.cp-home .hp-fixed { + margin-top: clamp(3rem, 6vw, 5rem); + border-top: 1px solid var(--hp-line); + padding-top: clamp(2rem, 4vw, 3rem); + max-width: 56rem; +} +.cp-home .hp-fixed .hp-large { color: var(--hp-muted); } +.cp-home .hp-fixed .hp-large strong, +.cp-home .hp-fixed .hp-large code { color: var(--hp-ink); } +.cp-home .hp-fixed-links { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin: 1.75rem 0 0; +} + +/* Cards from the shared includes, re-skinned to the landing tokens. */ +.cp-home .card { + background: var(--hp-card-bg); + border: 1px solid var(--hp-line); + border-radius: 0.9rem; + transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.35s ease; +} +.cp-home .card:hover { transform: translateY(-4px); border-color: var(--hp-accent-mid); } +html[data-theme="dark"] .cp-home .card { background: var(--hp-card-bg); border-color: var(--hp-line); } + +.cp-home .hp-writing-more { + margin: 2rem 0 0; + font-family: $cp-mono; + font-size: 0.85rem; + letter-spacing: 0.06em; +} + +/* ====================================================== Final CTA */ + +.cp-home .hp-final { + text-align: center; + padding-block: clamp(6rem, 12vw, 10rem); +} + +.cp-home .hp-final .hp-eyebrow { margin-bottom: 1.75rem; } + +.cp-home .hp-availability { + display: inline-flex; + align-items: center; + gap: 0.55rem; + border: 1px solid var(--hp-line); + border-radius: 99px; + padding: 0.45rem 1rem; + color: var(--hp-muted); +} +.cp-home .hp-availability-dot { + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background: #3e9c5f; + animation: hp-pulse 2.4s ease-in-out infinite; +} +@keyframes hp-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(62, 156, 95, 0.45); } + 50% { box-shadow: 0 0 0 6px rgba(62, 156, 95, 0); } +} + +.cp-home .hp-final-title { + margin-inline: auto; + max-width: 18ch; + font-size: clamp(2.4rem, 5.5vw, 4.2rem); +} + +.cp-home .hp-final-lead { + font-size: 1.08rem; + line-height: 1.65; + color: var(--hp-muted); + max-width: 52ch; + margin: 1.75rem auto 2.5rem; +} + +.cp-home .hp-final .hp-hero-cta { justify-content: center; } + +/* ====================================== Reduced motion / no-JS */ + +@media (prefers-reduced-motion: reduce) { + .cp-home .hp-availability-dot { animation: none; } + .cp-home .hp-card, + .cp-home .card, + .cp-home .hp-card-link::after { transition: none; } +} diff --git a/assets/css/main.scss b/assets/css/main.scss index f9e6903..e2a3c42 100644 --- a/assets/css/main.scss +++ b/assets/css/main.scss @@ -766,3 +766,10 @@ html[data-theme="dark"] .site-footer .footer-icons a:hover { color: $cp-accent-d back to invisible on stripped Linux font sets. */ .fa-adjust::before { content: "\25D0"; margin-right: .25em; } /* ◐ left half black */ .fa-adjust.fa-rotate-180::before { content: "\25D1"; margin-right: .25em; } /* ◑ right half black */ + +/* ============================================================ + Landing page redesign — "typed-systems editorial" layer. + Lives in _sass/home.scss; scoped to .cp-home. Imported last so + its tokens win over the generic card/button rules above. + ============================================================ */ +@import 'home'; diff --git a/assets/js/home.js b/assets/js/home.js new file mode 100644 index 0000000..983ef21 --- /dev/null +++ b/assets/js/home.js @@ -0,0 +1,211 @@ +/* Landing page motion — GSAP + ScrollTrigger (loaded via CDN, defer). + * + * Design constraints: + * - Content is fully visible without JS; this file only ADDS motion. + * - prefers-reduced-motion: no entrances, no parallax, no canvas. + * - Canvas pauses when the hero is off-screen or the tab is hidden. + */ +(function () { + 'use strict'; + + var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + var home = document.querySelector('.cp-home'); + if (!home) return; + + /* ---------------------------------------------------- Canvas */ + // A sparse field of drifting nodes; near nodes get a connecting + // line. Reads as "flowing logic" without demanding attention. + function startFlowField() { + var canvas = document.getElementById('hp-flow'); + if (!canvas) return; + var ctx = canvas.getContext('2d'); + var dpr = Math.min(window.devicePixelRatio || 1, 2); + var w = 0, h = 0, nodes = [], running = false, raf = 0; + + function accent() { + return document.documentElement.getAttribute('data-theme') === 'dark' + ? { r: 155, g: 155, b: 242 } + : { r: 59, g: 59, b: 140 }; + } + + function resize() { + var rect = canvas.parentElement.getBoundingClientRect(); + w = rect.width; h = rect.height; + canvas.width = w * dpr; canvas.height = h * dpr; + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + var count = Math.min(70, Math.floor(w * h / 26000)); + nodes = []; + for (var i = 0; i < count; i++) { + nodes.push({ + x: Math.random() * w, + y: Math.random() * h, + vx: (Math.random() - 0.5) * 0.22, + vy: (Math.random() - 0.5) * 0.22 + }); + } + } + + function tick() { + if (!running) return; + ctx.clearRect(0, 0, w, h); + var c = accent(); + var i, j, a, b, dx, dy, d2; + for (i = 0; i < nodes.length; i++) { + a = nodes[i]; + // gentle sine drift — "flow", not bounce + a.x += a.vx + Math.sin((a.y + performance.now() * 0.02) * 0.004) * 0.12; + a.y += a.vy; + if (a.x < -20) a.x = w + 20; else if (a.x > w + 20) a.x = -20; + if (a.y < -20) a.y = h + 20; else if (a.y > h + 20) a.y = -20; + ctx.beginPath(); + ctx.arc(a.x, a.y, 1.4, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',0.35)'; + ctx.fill(); + } + for (i = 0; i < nodes.length; i++) { + for (j = i + 1; j < nodes.length; j++) { + a = nodes[i]; b = nodes[j]; + dx = a.x - b.x; dy = a.y - b.y; d2 = dx * dx + dy * dy; + if (d2 < 110 * 110) { + ctx.beginPath(); + ctx.moveTo(a.x, a.y); + ctx.lineTo(b.x, b.y); + ctx.strokeStyle = 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + + (0.12 * (1 - d2 / (110 * 110))).toFixed(3) + ')'; + ctx.lineWidth = 1; + ctx.stroke(); + } + } + } + raf = requestAnimationFrame(tick); + } + + function setRunning(on) { + if (on === running) return; + running = on; + if (on) raf = requestAnimationFrame(tick); + else cancelAnimationFrame(raf); + } + + resize(); + window.addEventListener('resize', resize); + document.addEventListener('visibilitychange', function () { + setRunning(!document.hidden && visible); + }); + var visible = true; + new IntersectionObserver(function (entries) { + visible = entries[0].isIntersecting; + setRunning(visible && !document.hidden); + }).observe(canvas.parentElement); + setRunning(true); + } + + if (!reduced) startFlowField(); + + /* ------------------------------------------------ GSAP motion */ + if (reduced || typeof gsap === 'undefined') return; + if (typeof ScrollTrigger !== 'undefined') gsap.registerPlugin(ScrollTrigger); + + var EASE = 'power3.out'; + + // Hero entrance — one timeline, compiler-pass pacing. + var hero = gsap.timeline({ defaults: { ease: EASE } }); + hero + .from('[data-hero="eyebrow"]', { y: 14, autoAlpha: 0, duration: 0.7 }) + .from('[data-hero="line"]', { + yPercent: 115, duration: 1.0, stagger: 0.12 + }, '-=0.45') + .from('[data-hero="fade"]', { + y: 26, autoAlpha: 0, duration: 0.9, stagger: 0.14 + }, '-=0.55') + // the narrowing: each bar enters wide, then narrows to its type + .from('.hp-bar-row', { autoAlpha: 0, y: 14, duration: 0.5, stagger: 0.28 }, '-=0.6') + .from('.hp-bar-2', { width: '100%', duration: 0.9, ease: 'power4.inOut' }, '-=0.7') + .from('.hp-bar-3', { width: '100%', duration: 0.9, ease: 'power4.inOut' }, '-=0.55') + .from('.hp-bar-caption', { autoAlpha: 0, duration: 0.6 }, '-=0.3'); + + if (typeof ScrollTrigger === 'undefined') return; + + // Generic reveals. + gsap.utils.toArray('[data-reveal]').forEach(function (el) { + gsap.from(el, { + y: 32, autoAlpha: 0, duration: 0.95, ease: EASE, + scrollTrigger: { trigger: el, start: 'top 84%', once: true } + }); + }); + + // Grouped reveals — children stagger. + gsap.utils.toArray('[data-reveal-group]').forEach(function (group) { + gsap.from(group.children, { + y: 24, autoAlpha: 0, duration: 0.8, ease: EASE, stagger: 0.08, + scrollTrigger: { trigger: group, start: 'top 84%', once: true } + }); + }); + + // Service cards. + gsap.utils.toArray('[data-stagger]').forEach(function (grid) { + gsap.from(grid.children, { + y: 44, autoAlpha: 0, duration: 0.9, ease: EASE, stagger: 0.12, + scrollTrigger: { trigger: grid, start: 'top 82%', once: true } + }); + }); + + // Process — the rail draws as you scroll; steps pop in. + var rail = document.querySelector('.hp-process-line'); + if (rail) { + gsap.fromTo(rail, { scaleY: 0 }, { + scaleY: 1, ease: 'none', + scrollTrigger: { + trigger: '.hp-process', + start: 'top 75%', end: 'bottom 55%', scrub: 0.6 + } + }); + } + gsap.utils.toArray('[data-step]').forEach(function (step) { + gsap.from(step, { + x: -28, autoAlpha: 0, duration: 0.8, ease: EASE, + scrollTrigger: { trigger: step, start: 'top 82%', once: true } + }); + }); + + // Case studies — copy reveals; the stat block drifts (parallax). + gsap.utils.toArray('[data-case]').forEach(function (item) { + gsap.from(item.querySelector('.hp-case-copy'), { + y: 36, autoAlpha: 0, duration: 0.95, ease: EASE, + scrollTrigger: { trigger: item, start: 'top 80%', once: true } + }); + gsap.from(item.querySelector('.hp-case-visual'), { + autoAlpha: 0, scale: 0.96, duration: 1.0, ease: EASE, + scrollTrigger: { trigger: item, start: 'top 80%', once: true } + }); + gsap.fromTo(item.querySelector('.hp-case-stat'), { y: 18 }, { + y: -18, ease: 'none', + scrollTrigger: { trigger: item, start: 'top bottom', end: 'bottom top', scrub: 0.8 } + }); + }); + + // Excellence layers — each layer draws in, narrowing as it goes. + var layers = gsap.utils.toArray('[data-layer]'); + if (layers.length) { + gsap.from(layers, { + width: 0, autoAlpha: 0, duration: 0.85, ease: 'power4.inOut', stagger: 0.12, + scrollTrigger: { trigger: '.hp-layers', start: 'top 78%', once: true } + }); + } + + /* --------------------------------------------- Magnetic CTAs */ + if (window.matchMedia('(pointer: fine)').matches) { + document.querySelectorAll('.hp-magnetic').forEach(function (btn) { + var setX = gsap.quickTo(btn, 'x', { duration: 0.35, ease: 'power3.out' }); + var setY = gsap.quickTo(btn, 'y', { duration: 0.35, ease: 'power3.out' }); + btn.addEventListener('pointermove', function (e) { + var r = btn.getBoundingClientRect(); + setX((e.clientX - (r.left + r.width / 2)) * 0.22); + setY((e.clientY - (r.top + r.height / 2)) * 0.3); + }); + btn.addEventListener('pointerleave', function () { + gsap.to(btn, { x: 0, y: 0, duration: 0.6, ease: 'elastic.out(1, 0.45)' }); + }); + }); + } +})(); diff --git a/index.html b/index.html index 1349d33..e4d427d 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,3 @@ --- layout: home -bootstrap: true --- From c9f2ba1e72cc918f96c81f50df7552001567f9f4 Mon Sep 17 00:00:00 2001 From: Rodolfo Hansen Date: Thu, 11 Jun 2026 17:56:34 +0200 Subject: [PATCH 2/3] Hero copy: CEO-legible headline, narrowing bars, and lead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two iteration rounds with the founder replaced the type-jargon hero with boardroom language while keeping the narrowing subtext: - Headline: "Ship at AI speed. / Skip the gamble." - Bars: the "move fast and break things" slogan arc, narrowing to bare "move fast" with failure-count captions (ten thousand -> a hundred -> three ways to fail). - Lead: guardrails framing — "the speed compounds, while the breakage doesn't." The accent span now highlights the constant "move fast" prefix so the part that narrows away is what reads as expendable. Bar tweens get clearProps so GSAP's inline widths don't override the responsive media-query widths after a viewport change. Co-Authored-By: Claude Fable 5 --- _layouts/home.html | 24 ++++++++++++------------ assets/js/home.js | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/_layouts/home.html b/_layouts/home.html index bbc06ba..e8dbb8b 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -35,14 +35,14 @@

- Types as guardrails - for AI agents. + Ship at AI speed. + Skip the gamble.

- We ship production code paired with LLMs, narrowed by - construction — so fewer wrong programs compile, and the ones - that do are easier for humans and machines to trust. + We ship production code with AI, inside engineering guardrails + that make most wrong versions impossible to build — so the + speed compounds, while the breakage doesn't.

@@ -53,18 +53,18 @@

+ aria-label="Three bars narrowing in sequence: move fast and break things (ten thousand ways to fail, each one somebody's 2 a.m.), move fast and fix things (a hundred ways left — you're still gambling), move fast (three ways, one chosen — the rest won't even compile).">
-
String
-

space for bugs: ∞

+
move fast and break things
+

ten thousand ways to fail, each one somebody's 2 a.m.

-
Int
-

bugs can still hide in an Int

+
move fast and fix things
+

a hundred ways left — you're still gambling

-
type YourDomainValues = ...
-

not so much when you clearly delimit your domain values

+
move fast
+

three ways, one chosen — the rest won't even compile

Narrowing: the same shape that runs through every engagement. diff --git a/assets/js/home.js b/assets/js/home.js index 983ef21..1af06d2 100644 --- a/assets/js/home.js +++ b/assets/js/home.js @@ -120,8 +120,8 @@ }, '-=0.55') // the narrowing: each bar enters wide, then narrows to its type .from('.hp-bar-row', { autoAlpha: 0, y: 14, duration: 0.5, stagger: 0.28 }, '-=0.6') - .from('.hp-bar-2', { width: '100%', duration: 0.9, ease: 'power4.inOut' }, '-=0.7') - .from('.hp-bar-3', { width: '100%', duration: 0.9, ease: 'power4.inOut' }, '-=0.55') + .from('.hp-bar-2', { width: '100%', duration: 0.9, ease: 'power4.inOut', clearProps: 'width' }, '-=0.7') + .from('.hp-bar-3', { width: '100%', duration: 0.9, ease: 'power4.inOut', clearProps: 'width' }, '-=0.55') .from('.hp-bar-caption', { autoAlpha: 0, duration: 0.6 }, '-=0.3'); if (typeof ScrollTrigger === 'undefined') return; From 25dda32c889af42a1ef689c50b8cc133e55a7839 Mon Sep 17 00:00:00 2001 From: Rodolfo Hansen Date: Thu, 11 Jun 2026 17:59:35 +0200 Subject: [PATCH 3/3] SEO: align site description and header_subtitle with new positioning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The meta description (via jekyll-seo-tag) now leads with what buyers search for — software consultancy, AI-accelerated delivery, type-driven guardrails, Scala/Rust/TypeScript — instead of the insider phrasing. header_subtitle (no longer rendered, kept as canonical copy) mirrors the new hero. Co-Authored-By: Claude Fable 5 --- _config.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/_config.yml b/_config.yml index d79a867..e87b679 100644 --- a/_config.yml +++ b/_config.yml @@ -1,5 +1,8 @@ name: Constructive Programming -description: Type-driven and LLM-augmented engineering consultancy +description: >- + Software consultancy for platform teams: AI-accelerated delivery inside + type-driven guardrails. Architecture reviews, embedded senior engineers + (Scala, Rust, TypeScript), and agent-native development workflows. url: https://www.constructive.dev title: Constructive Programming @@ -8,7 +11,7 @@ favicon: assets/img/favicon.svg # Hero header_feature_image: "" header_text: Constructive Programming -header_subtitle: Types as guardrails for AI agents. We ship production code paired with LLMs, narrowed by construction. +header_subtitle: Ship at AI speed. Skip the gamble. Production code with AI, inside engineering guardrails that make most wrong versions impossible to build. # Footer footer_text: "Copyright Constructive Programming,"