PageSourceSearch

https://replicants.ai/whats-new

html replicants.ai collected 2026-10-05 12:29:29 UTC 39,976 bytes, 604 lines download raw bytes

1<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>What&rsquo;s New — Replicants.ai</title><link rel="icon" type="image/png" href="/replicants-icon.png"><link rel="preconnect" href="https://fonts.googleapis.com"><link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,wght@0,600;0,700;1,600&display=swap" rel="stylesheet">
2<!-- ⚠️ BEFORE the inline <style> BELOW, DELIBERATELY, so this page keeps winning
3     every collision. Loaded so the top bar's Sign in and the footer are THE home
4     page's, not a second copy of them — Alex, 2026-08-25: *"Login should be
5     following the design on the home page, so it's the Sign In button that we got
6     on the home page… fix the footer so the footer matches the same footer as the
7     home page."*
8
9     Safe to load over a page with its own design system because marketing-site.css
10     is almost entirely class-scoped: its only unscoped element rules are
11     `html,body{background}` and `body{margin:0}`, and this page sets its own `body`
12     further down. Its `:root` vars are likewise overridden by the `:root` below for
13     every name the two share. Checked before adding, not assumed.
14
15     ⚠️ `.rhome` IS THE SCOPE THE FOOTER NEEDS. marketing-site.css styles the footer
16     as `.rhome .ends`, which is why the footer at the bottom of this page is wrapped
17     in a `.rhome` div — the same trick free-coaching-community.html already uses. -->
18<link rel="stylesheet" href="/css/marketing-site.css?v=20260902film">
19<style>
20  :root {
21    --ground: #f5f0e4;         /* warm cream */
22    --ground-grad-a: #f7f2e8;
23    --ground-grad-b: #f1ebdb;
24    --band: #efe9f4;           /* soft lavender-cream */
25    --card: #ffffff;
26    --ink: #1b1726;            /* near-black, faint purple bias */
27    --ink-2: #6b6456;          /* warm grey */
28    --ink-3: #9a9184;          /* warm light grey */
29    --line: #e6dcc7;           /* cream hairline */
30    --line-2: #efe7d6;
31    --purple: #6d4aff;
32    --purple-ink: #5a34ec;     /* link/text contrast on cream */
33    --purple-wash: #ece6fb;
34    --purple-line: #ddd2f7;
35    --amber: #a06e1c;          /* eyebrow labels */
36    --dark: #1d1a2b;           /* header pill + footer */
37    --dark-ink: #b6afc4;
38    --shadow: 0 1px 2px rgba(29,26,43,.05), 0 10px 30px rgba(29,26,43,.07);
39    --shadow-hover: 0 2px 6px rgba(29,26,43,.07), 0 20px 46px rgba(109,74,255,.16);
40    --glow: 0 12px 34px rgba(109,74,255,.42);
41    --maxw: 1080px;
42    --serif: "Fraunces", Georgia, "Iowan Old Style", "Times New Roman", serif;
43    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, system-ui, sans-serif;
44
45    /* ── The tokens the borrowed home-page rules read ──────────────────────
46       ⚠️ marketing-site.css HAS NO DARK MODE — its `:root` is light-only
47       (`--paper:#ffffff`, `--ink2:#4d5168`, `--ink3:#8a8fa6`, `--accent:#6a4cf1`),
48       with no `prefers-color-scheme` block anywhere in the file. This page DOES
49       have dark mode. Left alone, the Sign in pill it now borrows would paint a
50       white background with mid-grey text onto a near-black header, and the
51       footer links would be light-mode grey on dark — the classic
52       one-theme's-text-on-the-other-theme's-ground fault.
53
54       Aliased to THIS page's own tokens rather than restated as hex, so they
55       follow every theme block below automatically: custom properties resolve at
56       use time, so `--paper: var(--card)` tracks `--card` through the media query,
57       `[data-theme="dark"]` and `[data-theme="light"]` without those blocks
58       needing to know these names exist. One line each, and no fourth place to
59       keep in step.
60
61       Only the four names the borrowed rules actually use are defined. `--line`
62       is deliberately NOT among them — this page already defines it, and its
63       cream hairline is the right one here. */
64    --paper: var(--card);
65    --ink2: var(--ink-2);
66    --ink3: var(--ink-3);
67    --accent: var(--purple-ink);
68  }
69  @media (prefers-color-scheme: dark) {
70    :root {
71      --ground: #16131f; --ground-grad-a: #18141f; --ground-grad-b: #120f19;
72      --band: #1c1730; --card: #201b2c;
73      --ink: #f3eee2; --ink-2: #a89f92; --ink-3: #766e63;
74      --line: #322c3f; --line-2: #262133;
75      --purple: #8f74ff; --purple-ink: #a892ff; --purple-wash: #241d3b; --purple-line: #332b50;
76      --amber: #cf9a3e; --dark: #0e0b16; --dark-ink: #9a92a8;
77      --shadow: 0 1px 2px rgba(0,0,0,.5), 0 12px 32px rgba(0,0,0,.4);
78      --shadow-hover: 0 2px 6px rgba(0,0,0,.55), 0 22px 50px rgba(0,0,0,.55);
79      --glow: 0 12px 34px rgba(143,116,255,.4);
80    }
81  }
82  :root[data-theme="dark"] {
83    --ground: #16131f; --ground-grad-a: #18141f; --ground-grad-b: #120f19;
84    --band: #1c1730; --card: #201b2c;
85    --ink: #f3eee2; --ink-2: #a89f92; --ink-3: #766e63;
86    --line: #322c3f; --line-2: #262133;
87    --purple: #8f74ff; --purple-ink: #a892ff; --purple-wash: #241d3b; --purple-line: #332b50;
88    --amber: #cf9a3e; --dark: #0e0b16; --dark-ink: #9a92a8;
89    --shadow: 0 1px 2px rgba(0,0,0,.5), 0 12px 32px rgba(0,0,0,.4);
90    --shadow-hover: 0 2px 6px rgba(0,0,0,.55), 0 22px 50px rgba(0,0,0,.55);
91    --glow: 0 12px 34px rgba(143,116,255,.4);
92  }
93  :root[data-theme="light"] {
94    --ground: #f5f0e4; --ground-grad-a: #f7f2e8; --ground-grad-b: #f1ebdb;
95    --band: #efe9f4; --card: #ffffff;
96    --ink: #1b1726; --ink-2: #6b6456; --ink-3: #9a9184;
97    --line: #e6dcc7; --line-2: #efe7d6;
98    --purple: #6d4aff; --purple-ink: #5a34ec; --purple-wash: #ece6fb; --purple-line: #ddd2f7;
99    --amber: #a06e1c; --dark: #1d1a2b; --dark-ink: #b6afc4;
100    --shadow: 0 1px 2px rgba(29,26,43,.05), 0 10px 30px rgba(29,26,43,.07);
101    --shadow-hover: 0 2px 6px rgba(29,26,43,.07), 0 20px 46px rgba(109,74,255,.16);
102    --glow: 0 12px 34px rgba(109,74,255,.42);
103  }
104
105  * { box-sizing: border-box; }
106  body {
107    margin: 0; color: var(--ink); font-family: var(--sans); line-height: 1.6;
108    background: linear-gradient(180deg, var(--ground-grad-a), var(--ground-grad-b) 40%, var(--ground));
109    -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
110  }
111  .wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
112  a { color: inherit; text-decoration: none; }
113
114  /* Header */
115  header.site {
116    position: sticky; top: 0; z-index: 50;
117    background: color-mix(in srgb, var(--ground) 84%, transparent);
118    backdrop-filter: saturate(1.3) blur(10px);
119    border-bottom: 1px solid var(--line);
120  }
121  .nav { display: flex; align-items: center; justify-content: space-between; height: 64px; }
122  .brand { display: flex; align-items: center; gap: 11px; }
123  .brand .mark { width: 30px; height: 30px; border-radius: 8px; background: radial-gradient(circle at 30% 25%, #fff, var(--purple-wash)); border: 1px solid var(--purple-line); display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(109,74,255,.18); }
124  .brand .mark b { font-family: var(--serif); font-weight: 700; font-size: 16px; color: var(--purple-ink); line-height: 1; }
125  .brand .word { font-weight: 700; letter-spacing: .16em; font-size: 15px; }
126  .nav-right { display: flex; align-items: center; gap: 14px; }
127  .nav-link { color: var(--ink-2); font-size: 14px; font-weight: 500; white-space: nowrap; }
128  .nav-link[aria-current] { color: var(--ink); }
129  .btn { font: inherit; font-size: 14px; font-weight: 600; border-radius: 999px; padding: 10px 20px; cursor: pointer; border: 1px solid transparent; transition: transform .15s ease, box-shadow .15s ease, background .15s ease; white-space: nowrap; display: inline-flex; align-items: center; gap: 8px; }
130  .btn-dark { color: #fff; background: var(--dark); }
131  .btn-dark:hover { transform: translateY(-1px); }
132  .btn-purple { color: #fff; background: linear-gradient(135deg, #7c5cff, #6134f0); box-shadow: var(--glow); }
133  .btn-purple:hover { transform: translateY(-1px); }
134  @media (max-width: 560px){ .hide-sm { display: none; } .nav-right { gap: 10px; } }
135  /* ⚠️ `.brand span`, NOT `.brand .word`. The wordmark used to be
136     `<span class="word">`; swapping in the home page's brand block dropped that
137     class, so this rule silently stopped matching and the nav overflowed the
138     viewport at 390px with "Get updates" running off the right edge. A media
139     query that no longer selects anything fails exactly this quietly. */
140  @media (max-width: 440px){ .brand span { display: none; } }
141
142  /* Eyebrow */
143  .eyebrow { text-transform: uppercase; letter-spacing: .18em; font-size: 12px; font-weight: 700; color: var(--amber); display: inline-flex; align-items: center; gap: 9px; }
144  .eyebrow .pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--purple); box-shadow: 0 0 0 0 var(--purple);
144 animation: pulse 2.6s infinite; }
145  @keyframes pulse { 0%{ box-shadow: 0 0 0 0 color-mix(in srgb, var(--purple) 55%, transparent);} 70%{ box-shadow: 0 0 0 9px transparent;} 100%{ box-shadow: 0 0 0 0 transparent;} }
146
147  /* Hero */
148  .hero { padding: 66px 0 44px; }
149  h1.title { font-family: var(--serif); font-weight: 700; font-size: clamp(36px, 6.4vw, 60px); line-height: 1.06; letter-spacing: -.01em; margin: 20px 0 0; max-width: 16ch; text-wrap: balance; }
150  h1.title em { font-style: italic; color: var(--purple-ink); }
151  .lede { font-size: clamp(16px, 2.2vw, 19px); color: var(--ink-2); max-width: 54ch; margin: 20px 0 0; }
152  .stats { display: flex; flex-wrap: wrap; gap: 30px; margin-top: 32px; }
153  .stat .n { font-family: var(--serif); font-size: 30px; font-weight: 700; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
154  .stat .n .accent { color: var(--purple-ink); }
155  .stat .l { font-size: 13px; color: var(--ink-3); margin-top: 3px; }
156
157  section { padding: 46px 0; }
158  .section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 24px; }
159  .section-head .eyebrow { font-size: 12px; }
160  .date { font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
161
162  /* 72h band */
163  .band { background: var(--band); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
164  .fresh-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
165  @media (max-width: 820px){ .fresh-grid { grid-template-columns: 1fr; } }
166  .fresh { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 22px; box-shadow: var(--shadow); position: relative; transition: transform .18s ease, box-shadow .18s ease; }
167  .fresh:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); }
168  .fresh .new { position: absolute; top: 16px; right: 16px; font-size: 11px; font-weight: 700; letter-spacing: .04em; color: #fff; background: linear-gradient(135deg,#7c5cff,#6134f0); padding: 3px 10px; border-radius: 999px; }
169  .fresh h3 { font-family: var(--serif); font-weight: 700; font-size: 19px; margin: 28px 0 8px; line-height: 1.24; letter-spacing: -.01em; }
170  .fresh p { font-size: 14px; color: var(--ink-2); margin: 0; }
171  .icon { width: 36px; height: 36px; border-radius: 10px; background: var(--purple-wash); color: var(--purple-ink); display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--purple-line); }
172  .icon svg { width: 18px; height: 18px; }
173
174  /* Feed timeline */
175  .bucket { margin-bottom: 10px; }
176  .bucket-label { position: sticky; top: 64px; z-index: 5; background: color-mix(in srgb, var(--ground) 88%, transparent); backdrop-filter: blur(6px); padding: 12px 0; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .14em; color: var(--amber); }
177  .rail { position: relative; padding-left: 32px; }
178  .rail::before { content: ""; position: absolute; left: 7px; top: 8px; bottom: 8px; width: 2px; background: linear-gradient(var(--purple-line), transparent); }
179  .entry { position: relative; padding: 16px 0 26px; }
180  .entry::before { content: ""; position: absolute; left: -32px; top: 26px; width: 16px; height: 16px; border-radius: 50%; background: var(--card); border: 2px solid var(--purple); box-shadow: 0 0 0 4px var(--ground); }
181  .card { background: var(--card); border: 1px solid var(--line); border-radius: 18px; box-shadow: var(--shadow); overflow: hidden; display: grid; grid-template-columns: 1.3fr 1fr; transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease; }
182  .card:hover { transform: translateY(-2px); box-shadow: var(--shadow-hover); border-color: var(--purple-line); }
183  @media (max-width: 720px){ .card { grid-template-columns: 1fr; } }
184  .card-body { padding: 24px 26px; }
185  .chips { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
186  .chip { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; padding: 5px 11px; border-radius: 999px; background: var(--card); color: var(--ink-2); border: 1px solid var(--line); box-shadow: 0 1px 2px rgba(29,26,43,.04); }
187  .chip.hl { color: var(--purple-ink); background: var(--purple-wash); border-color: var(--purple-line); }
188  .chip.beta { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 45%, transparent); background: color-mix(in srgb, var(--amber) 10%, var(--card)); }
189  .card h3 { font-family: var(--serif); font-weight: 700; font-size: 22px; letter-spacing: -.01em; margin: 14px 0 9px; line-height: 1.2; }
190  .card p { color: var(--ink-2); font-size: 15px; margin: 0 0 18px; }
191  .see { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; font-size: 14px; color: var(--purple-ink); }
192  .see .arw { transition: transform .18s ease; }
193  .card:hover .see .arw, .fresh:hover .see .arw { transform: translateX(4px); }
194
195  .shot { position: relative; background: linear-gradient(160deg, var(--band), color-mix(in srgb, var(--purple-wash) 55%, var(--band))); border-left: 1px solid var(--line-2); display: flex; align-items: center; justify-content: center; min-height: 200px; }
196  @media (max-width: 720px){ .shot { border-left: none; border-top: 1px solid var(--line-2); min-height: 160px; } }
197  .shot .frame { width: 78%; height: 70%; border-radius: 11px; background: var(--card); border: 1px solid var(--line); box-shadow: var(--shadow); display: flex; flex-direction: column; overflow: hidden; }
198  .shot .bar { height: 22px; background: var(--band); border-bottom: 1px solid var(--line-2); display: flex; align-items: center; gap: 5px; padding: 0 10px; }
199  .shot .bar i { width: 7px; height: 7px; border-radius: 50%; background: var(--line); display: inline-block; }
200  .shot .ph { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 9px; color: var(--ink-3); }
201  .shot .ph svg { width: 26px;
201 height: 26px; opacity: .75; }
202  .shot .ph span { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
203
204  /* Closing CTA (mirrors homepage) */
205  .cta-band { text-align: center; }
206  .cta-inner { background: linear-gradient(180deg, var(--band), var(--ground)); border: 1px solid var(--line); border-radius: 24px; padding: 48px 24px; box-shadow: var(--shadow); }
207  .cta-inner h2 { font-family: var(--serif); font-weight: 700; font-size: clamp(26px, 4.5vw, 40px); line-height: 1.1; letter-spacing: -.01em; margin: 0 0 6px; max-width: 20ch; margin-inline: auto; text-wrap: balance; }
208  .cta-inner h2 em { font-style: italic; color: var(--purple-ink); }
209  .cta-inner .big { margin-top: 26px; font-size: 16px; padding: 16px 34px; }
210  .cta-sub { margin-top: 16px; font-size: 14px; color: var(--ink-3); }
211  .cta-sub b { color: var(--ink-2); font-weight: 600; }
212
213  /* ── Borrowed-from-home bits, and the two overrides they need ──────────
214     ⚠️ `.rtop-in` IS `position:absolute` IN marketing-site.css. It is written for
215     the home page's `.rtop`, which is `position:relative` and holds a centred
216     wordmark with the button pinned out to the right. Dropped into this page's
217     flex `.nav-right` unchanged it would position against whatever ancestor
218     happens to be positioned and land somewhere arbitrary — so it is put back in
219     flow here. The LOOK (the pill, the border, the hover) is inherited, which is
220     the part Alex asked to match. */
221  .nav-right .rtop-in { position: static; transform: none; }
222
223  /* The home page's sub-headline under the pitch. Its own `.who` lives in the
224     `.rhome` scope, which this band is not inside, so it is restated rather than
225     the whole landing-page scope being pulled in around a changelog. */
226  .cta-who { margin: 10px auto 0; max-width: 46ch; font-size: 16px; line-height: 1.5; color: var(--ink-2); }
227  .cta-trust { margin-top: 12px; display: flex; justify-content: center; gap: 8px 22px; flex-wrap: wrap; font-size: 13.5px; color: var(--ink-3); }
228  .cta-trust span::before { content: "\2022"; color: var(--purple); margin-right: 7px; }
229  /* ⚠️ TWO LINES, THE WAY HOME SETS IT — the sentence and the promise, not one
230     run-on paragraph. marketing-site.css does this with `.rhome h1 span,.rhome h1
231     em{display:block}`, and without the equivalent here the italic half started
232     mid-line and the whole thing read as prose. The home headline is also longer
233     than the one this band used to carry, so 20ch broke it into a column of
234     two-word lines. */
235  .cta-inner h2 { max-width: 30ch; }
236  .cta-inner h2 span, .cta-inner h2 em { display: block; text-wrap: balance; }
237
238  /* Footer */
239  footer.site { background: var(--dark); color: var(--dark-ink); margin-top: 8px; }
240  .foot { display: flex; flex-wrap: wrap; gap: 20px 40px; justify-content: space-between; align-items: center; padding: 36px 0; }
241  .foot .word { color: #fff; }
242  .foot .mark { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.16); }
243  .foot .mark b { color: #fff; }
244  .foot-links { display: flex; gap: 22px; flex-wrap: wrap; }
245  .foot-links a { color: var(--dark-ink); font-size: 14px; }
246  .foot-links a:hover { color: #fff; }
247  .foot .copy { width: 100%; border-top: 1px solid rgba(255,255,255,.09); padding-top: 20px; margin-top: 6px; font-size: 13px; color: color-mix(in srgb, var(--dark-ink) 78%, transparent); }
248
249  .reveal { opacity: 0; transform: translateY(14px); transition: opacity .6s ease, transform .6s ease; }
250  .reveal.in { opacity: 1; transform: none; }
251  @media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; transition: none; } .eyebrow .pulse { animation: none; } }
252  :focus-visible { outline: 2px solid var(--purple); outline-offset: 2px; border-radius: 4px; }
253
254  .note { max-width: var(--maxw); margin: 0 auto; padding: 14px 24px 0; }
255  .note .inner { font-size: 12.5px; color: var(--ink-3); background: color-mix(in srgb, var(--band) 60%, var(--ground)); border: 1px dashed var(--line); border-radius: 10px; padding: 10px 14px; }
256
257  /* Subscribe popover (Alex 2026-08-02 — status-page style "subscribe to
258     updates", restyled to this page's design). Anchored under the sticky
259     header on desktop; centred sheet on small screens. */
260  .sub-pop { position: fixed; top: 74px; right: max(24px, calc((100vw - var(--maxw)) / 2 + 24px)); width: min(360px, calc(100vw - 32px)); background: var(--card); border: 1px solid var(--purple-line); border-radius: 18px; box-shadow: var(--shadow-hover); padding: 22px; z-index: 60; }
261  .sub-pop[hidden] { display: none; }
262  .sub-pop h3 { font-family: var(--serif); font-weight: 700; font-size: 20px; margin: 0 0 6px; letter-spacing: -.01em; }
263  .sub-pop .sub-copy { font-size: 13.5px; color: var(--ink-2); margin: 0 0 16px; line-height: 1.5; }
264  .sub-pop label { display: block; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); margin: 12px 0 5px; }
265  .sub-pop input { width: 100%; font: inherit; font-size: 14.5px; color: var(--ink); background: color-mix(in srgb, var(--band) 40%, var(--card)); border: 1px solid var(--line); border-radius: 10px; padding: 10px 13px; outline: none; transition: border-color .15s ease, box-shadow .15s ease; }
266  .sub-pop input:focus { border-color: var(--purple); box-shadow: 0 0 0 3px color-mix(in srgb, var(--purple) 18%, transparent); }
267  .sub-pop input::placeholder { color: var(--ink-3); }
268  .sub-pop .sub-go { width: 100%; justify-content: center; margin-top: 16px; }
269  .sub-pop .sub-fine { font-size: 11.5px; color: var(--ink-3); margin: 12px 0 0; text-align: center; }
270  .sub-pop .sub-status { font-size: 13px; margin: 10px 0 0; display: none; }
271  .sub-pop .sub-status.err { display: block; color: #b3392f; }
272  .sub-pop .sub-done { display: none; text-align: center; padding: 10px 0 4px; }
273  .sub-pop.done .sub-done { display: block; }
274  .sub-pop.done .sub-form { display: none; }
275  .sub-pop .sub-done .tick { width: 44px; height: 44px; border-radius: 50%; background: var(--purple-wash); border: 1px solid var(--purple-line); color: var(--purple-ink); display: inline-flex; align-items: center; justify-content: center; margin-bottom: 10px; }
276  .sub-pop .sub-done .tick svg { width: 22px; height: 22px; }
277  .sub-pop .sub-done h3 { margin-bottom: 4px; }
278  .sub-pop .sub-done p { font-size: 13.5px; color: var(--ink-2); margin: 0; }
279  .sub-pop .sub-x { position: absolute; top: 12px; right: 12px; width: 30px; height: 30px; border: none; border-radius: 8px; background: transparent; color: var(--ink-3); cursor: pointer; font-size: 16px; line-height: 1; }
280  .sub-pop .sub-x:hover { background: var(--band); color: var(--ink); }
281  .sub-scrim { position: fixed; inset: 0; z-index: 55; background: transparent; }
282  .sub-scrim[hidden] { display: none; }
283  @media (max-width: 560px) {
284    .sub-pop { position: fixed; top: auto; bottom: 16px; left: 16px; right: 16px; width: auto; }
285    .sub-scrim { background: rgba(29,26,43,.35); }
286  }
287</style>
288</head><body>
289
290<header class="site">
291  <div class="wrap nav">
292    <!-- ⚠️ THE REAL LOGO, NOT A CSS-DRAWN LETTER. This was a `<span class="mark">`
293         holding a styled "R" in a gradient square — a lookalike, not the mark.
294         Alex, 2026-08-25: *"On the What's New page we need to use a proper
295         replicants logo, the same one that we're using on the home page."* This is
296         now byte-for-byte the home page's brand block, so there is one logo to
297         change rather than two. -->
298    <a class="brand" href="/" aria-label="Replicants home">
299      <img src="/replicants-icon.png" alt="">
300      <span>Replicants.ai</span>
301    </a>
302    <nav class="nav-right">
303      <!-- ⚠️ "What's New" IS GONE FROM HERE ON PURPOSE — it linked to the page it
304           was already on. Alex: *"What's New doesn't need to be in the top menu."*
305
306           ⚠️ AND SO IS "Start free forever". *"Login can be in there but Start Free
307           Forever shouldn't be in there… Maybe 'Start your 14-day free trial'
308           should be there instead of the Free Forever thing."* Taken as: where a
309           paid CTA sits on this page, it is the trial — which is also what he was
310           unambiguous about for the closing band below. Free-forever is the
311           free-community offer and belongs on the offer page, not next to a
312           changelog.
313
314           ⚠️ Sign in is a LINK, not the home page's modal. `data-login` needs the
315           whole `#login-overlay` markup copied in as well, and a second copy of the
316           login form on a changelog page is exactly the duplication this round is
317           removing. `?login=1` is the same deliberate suffix home uses: bare
318           /app.html bounces anyone without a token straight back out, which is what
319           once made "Sign in" look dead. -->
320      <a class="rtop-in" href="/app.html?login=1">Sign in</a>
321      <button class="btn btn-purple" id="subOpen" type="button" aria-expanded="false" aria-controls="subPop">
322        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg>
323        Get updates
324      </button>
325      <a class="btn btn-dark hide-sm" href="/">Start your 14-day trial</a>
326    </nav>
327  </div>
328</header>
329
330<!-- Outside the header: its backdrop-filter makes it the containing block
331     for position:fixed, which pinned this to the header instead of the
332     viewport (clipped it on mobile). -->
333<div class="sub-scrim" id="subScrim" hidden></div>
334<div class="sub-pop" id="subPop" role="dialog" aria-modal="false" aria-labelledby="subTitle" hidden>
335    <button class="sub-x" id="subClose" type="button" aria-label="Close">&times;</button>
336    <form class="sub-form" id="subForm" novalidate>
337      <h3 id="subTitle">Get updates as they ship</h3>
338      <p class="sub-copy">New features land here first. Pop in your email, your mobile, or both, and we&rsquo;
338ll let you know when something new goes live.</p>
339      <label for="subEmail">Email</label>
340      <input id="subEmail" name="email" type="email" autocomplete="email" placeholder="[email protected]" inputmode="email">
341      <label for="subPhone">Mobile (for texts)</label>
342      <input id="subPhone" name="phone" type="tel" autocomplete="tel" placeholder="+44 7000 000000" inputmode="tel">
343      <button class="btn btn-purple sub-go" id="subGo" type="submit">Subscribe</button>
344      <p class="sub-status" id="subStatus"></p>
345      <p class="sub-fine">No spam. Unsubscribe any time.</p>
346    </form>
347    <div class="sub-done">
348      <span class="tick"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="m4.5 12.5 5 5 10-11"/></svg></span>
349      <h3>You&rsquo;re on the list</h3>
350      <p>We&rsquo;ll let you know when something new ships.</p>
351    </div>
352  </div>
353
354<main>
355  <section class="hero">
356    <div class="wrap">
357      <span class="eyebrow"><span class="pulse"></span>Product updates</span>
358      <h1 class="title">Your platform gets <em>better every day.</em></h1>
359      <p class="lede">We ship improvements to the websites, community and outreach tools almost every day &mdash; from small polish to whole new tools. Here&rsquo;s what&rsquo;s landed recently.</p>
360      <p style="margin:18px 0 0"><a class="see" href="https://start.replicants.ai/">🧪 Some features below are in beta &mdash; join the current beta <span class="arw">&rarr;</span></a></p>
361      <div class="stats" id="wnStats" hidden>
362        <div class="stat"><div class="n"><span class="accent" id="st72"></span></div><div class="l">shipped in the last 72 hours</div></div>
363        <div class="stat"><div class="n" id="stMonth"></div><div class="l">updates this month</div></div>
364        <div class="stat"><div class="n" id="stLast"></div><div class="l">last update</div></div>
365      </div>
366    </div>
367  </section>
368
369  <section class="band" id="freshBand" hidden>
370    <div class="wrap">
371      <div class="section-head"><span class="eyebrow">Last 72 hours</span><span class="date" id="freshDate"></span></div>
372      <div class="fresh-grid" id="freshGrid"></div>
373    </div>
374  </section>
375
376  <section>
377    <div class="wrap">
378      <div class="section-head"><span class="eyebrow">Everything shipped</span></div>
379      <div id="feed"><div class="date" id="wnEmpty">Loading updates&hellip;</div></div>
380    </div>
381  </section>
382
383  <section class="band" id="betaBand" hidden>
384    <div class="wrap">
385      <div class="section-head"><span class="eyebrow" style="color:var(--amber)">🧪 In beta &mdash; not generally available yet</span><a class="see" href="https://start.replicants.ai/">Join the current beta <span class="arw">&rarr;</span></a></div>
386      <div id="betaFeed"></div>
387    </div>
388  </section>
389
390  <section class="cta-band">
391    <div class="wrap">
392      <!-- ⚠️ THIS IS THE HOME PAGE'S PITCH, WORD FOR WORD. Alex, 2026-08-25:
393           *"in the footer the sign-up message should basically be the same as the
394           home page: 'Describe the coaching business you want' message. Again it
395           should be the 14-day free trial that should go there."*
396
397           It replaced "Your business, always improving — without you lifting a
398           finger" + Start free forever: a second pitch and the wrong offer. Somebody
399           who has just read a changelog is being asked to buy the product, so they
400           should be asked in the words the product is actually sold in.
401
402           ⚠️ THE CTA IS A LINK TO THE HOME PAGE, NOT AN EMAIL BOX, and that is a
403           deliberate limit. home-signup.js would build the real box here — it only
404           needs a `.acts` host — but it opens with `gateOnSession`, which sends a
405           signed-in visitor straight to /app.html. On the home page that is the
406           point; on the changelog it would mean any logged-in coach following "See
407           what shipped this week" gets thrown into their dashboard instead of the
408           page they asked for. The box stays where the flow lives.
409
410           ⚠️ Copy kept in step with home.html by hand — if the hero headline
411           changes there, change it here. -->
412      <div class="cta-inner reveal in">
413        <h2><span>Describe the coaching business you want.</span> <em>Watch it get built in front of you.</em></h2>
414        <p class="cta-who">Your website, your community, your courses, your funnels and your
415          emails. Six months of setting up, done in an afternoon.</p>
416        <a class="btn btn-purple big" href="/">Start your 14-day trial</a>
417        <div class="cta-trust"><span>No charge today</span><span>Cancel any time</span></div>
418        <div class="cta-sub"><b>New updates land here first.</b> <a class="see" href="#" id="subOpen2" style="font-size:14px">
418Get them by email or text <span class="arw">&rarr;</span></a></div>
419      </div>
420    </div>
421  </section>
422</main>
423
424<!-- ⚠️ THE HOME PAGE'S FOOTER, NOT A THIRD ONE. Alex, 2026-08-25: *"fix the footer
425     so the footer matches the same footer as the home page."*
426
427     What was here was a slim dark bar carrying the fake "R" mark, a self-referential
428     "What's New" link and "Log in" — three things this round is removing everywhere.
429     This is now the same markup as home.html's `<footer class="ends">`, styled by
430     the same `.rhome .ends` rules in marketing-site.css, which is why it is wrapped
431     in `.rhome` (`min-height:0` because that class is written for a full-height
432     landing page). free-coaching-community.html already does exactly this — the
433     pattern is established, not invented here.
434
435     ⚠️ "What's new" IS still in the footer, unlike the top bar, and points at this
436     page. That is the home page's link list reproduced faithfully; a footer that
437     silently differs from home's is the thing being fixed. -->
438<div class="rhome" style="min-height:0;padding-top:8px">
439  <footer class="ends">
440    <nav>
441      <a href="/whats-new">What&rsquo;s new</a>
442      <a href="https://ridingthewave.coach" target="_blank" rel="noopener">Join our community</a>
443      <a href="/app.html?login=1">Sign in</a>
444    </nav>
445    <nav class="legal">
446      <a href="/terms" target="_blank" rel="noopener">Terms</a>
447      <a href="/privacy" target="_blank" rel="noopener">Privacy</a>
448    </nav>
449    <p>Replicants.ai</p>
450  </footer>
451</div>
452
453<script src="/js/date-format.js?v=20261002ae2"></script>
vendor: 1 bytes, line 453
453
454<script>
455(function () {
456  function esc(s) { var d = document.createElement('div'); d.textContent = String(s == null ? '' : s); return d.innerHTML; }
457  function when(ts) { return App.Dates.short(ts, { year: 'auto' }); }
458  function ago(ts) {
459    var m = Math.round((Date.now() - new Date(ts).getTime()) / 60000);
460    if (m < 60) return m + 'm ago';
461    if (m < 48 * 60) return Math.round(m / 60) + 'h ago';
462    return Math.round(m / 1440) + 'd ago';
463  }
464  function weekLabel(d) {
465    var day = d.getDay() || 7; var mon = new Date(d); mon.setDate(d.getDate() - day + 1);
466    var sun = new Date(mon); sun.setDate(mon.getDate() + 6);
467    var f = function (x) { return App.Dates.short(x, { year: false }); };
468    return f(mon) + ' – ' + f(sun);
469  }
470  var shotPh = '<div class="shot" aria-hidden="true"><div class="frame"><div class="bar"><i></i><i></i><i></i></div>'
471    + '<div class="ph"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="3" y="3" width="18" height="18" rx="3"/><path d="m3 15 5-5 4 4 3-3 6 6"/><circle cx="8.5" cy="8.5" r="1.5"/></svg><span>Screenshot</span></div></div></div>';
472
473  fetch('/api/whats-new?ts=' + Date.now(), { cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (data) {
474    var all = (data && data.entries) || [];
475    var now = Date.now();
476    var pub = function (e) { return new Date(e.published_at || e.shipped_at); };
477
478    // Live first, beta in its own band underneath (Alex 2026-07-22): mixing
479    // them made the feed read as "everything is beta". Stats + fresh grid +
480    // the weekly timeline are LIVE-only; beta renders as one separate rail.
481    var entries = all.filter(function (e) { return e.stage !== 'beta'; });
482    var betaEntries = all.filter(function (e) { return e.stage === 'beta'; });
483
484    if (!all.length) { document.getElementById('wnEmpty').textContent = 'Updates are on their way — check back soon.'; return; }
485
486    var entryCard = function (e) {
487      // Two rendering modes. A real screenshot (raster) fills the panel
488      // edge-to-edge with object-fit: cover. An SVG mock renders in a box
489      // FIXED to the mock canvas's own 1200:760 shape, centred on the band —
490      // the .shot panel's height follows the entry's TEXT, so every entry's
491      // box was a different shape and identical margins were geometrically
492      // impossible from the image side (Alex, 1–2 Aug, three rounds of it).
493      // aspect-ratio pins the box; the whole canvas, margins and all, is
494      // always visible and every entry frames identically.
495      var isMock = /\.svg(\?|$)/i.test(e.screenshot_url || '');
496      var shot = e.screenshot_url
497        ? (isMock
498          ? '<div class="shot"><img src="' + esc(e.screenshot_url) + '" alt="" style="width:100%;aspect-ratio:1200/760;object-fit:cover;display:block"></div>'
499          : '<div class="shot" style="background:none;padding:0"><img src="' + esc(e.screenshot_url) + '" alt="" style="width:100%;height:100%;object-fit:cover;display:block"></div>')
500        : shotPh;
501      return '<div class="entry reveal in"><article class="card"><div class="card-body">'
502        + '<div class="chips">' + (e.area ? '<span class="chip hl">
502' + esc(e.area) + '</span>' : '') + (e.stage === 'beta' ? '<span class="chip beta">🧪 In beta</span>' : '') + '<span class="date">' + when(pub(e)) + '</span></div>'
503        + '<h3>' + esc(e.title) + '</h3><p>' + esc(e.blurb) + '</p>'
504        + (e.cta_url ? '<a class="see" href="' + esc(e.cta_url) + '">Take a look <span class="arw">&rarr;</span></a>' : (e.stage === 'beta' ? '<a class="see" href="https://start.replicants.ai/">Join the current beta <span class="arw">&rarr;</span></a>' : ''))
505        + '</div>' + shot + '</article></div>';
506    };
507
508    if (betaEntries.length) {
509      document.getElementById('betaFeed').innerHTML = '<div class="bucket"><div class="rail">' + betaEntries.map(entryCard).join('') + '</div></div>';
510      document.getElementById('betaBand').hidden = false;
511    }
512    if (!entries.length) { document.getElementById('wnEmpty').textContent = 'Updates are on their way — check back soon.'; return; }
513
514    var fresh = entries.filter(function (e) { return now - pub(e).getTime() < 72 * 3600 * 1000; });
515    var monthCount = entries.filter(function (e) { var d = pub(e), n = new Date(); return d.getMonth() === n.getMonth() && d.getFullYear() === n.getFullYear(); }).length;
516    document.getElementById('st72').textContent = fresh.length;
517    document.getElementById('stMonth').textContent = monthCount;
518    document.getElementById('stLast').textContent = ago(pub(entries[0]));
519    document.getElementById('wnStats').hidden = false;
520
521    if (fresh.length) {
522      document.getElementById('freshDate').textContent = when(pub(fresh[0]));
523      document.getElementById('freshGrid').innerHTML = fresh.slice(0, 3).map(function (e) {
524        return '<article class="fresh reveal in"><span class="new">' + (e.stage === 'beta' ? 'Beta' : 'New') + '</span>'
525          + '<span class="icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 2 2 7l10 5 10-5-10-5Z"/><path d="m2 17 10 5 10-5M2 12l10 5 10-5"/></svg></span>'
526          + '<h3>' + esc(e.title) + '</h3><p>' + esc(e.blurb) + '</p></article>';
527      }).join('');
528      document.getElementById('freshBand').hidden = false;
529    }
530
531    var buckets = {}; var order = [];
532    entries.forEach(function (e) {
533      var l = weekLabel(pub(e));
534      if (!buckets[l]) { buckets[l] = []; order.push(l); }
535      buckets[l].push(e);
536    });
537    document.getElementById('feed').innerHTML = order.map(function (label) {
538      return '<div class="bucket"><div class="bucket-label">' + esc(label) + '</div><div class="rail">'
539        + buckets[label].map(entryCard).join('') + '</div></div>';
540    }).join('');
541  }).catch(function () {
542    document.getElementById('wnEmpty').textContent = 'Couldn’t load updates right now.';
543  });
544})();
545
546// ── Subscribe popover ──
547(function () {
548  var pop = document.getElementById('subPop');
549  var scrim = document.getElementById('subScrim');
550  var openBtn = document.getElementById('subOpen');
551  var form = document.getElementById('subForm');
552  var status = document.getElementById('subStatus');
553  var go = document.getElementById('subGo');
554
555  function open() {
556    pop.hidden = false; scrim.hidden = false;
557    openBtn.setAttribute('aria-expanded', 'true');
558    var first = document.getElementById('subEmail');
559    if (first && !pop.classList.contains('done')) first.focus();
560  }
561  function close() {
562    pop.hidden = true; scrim.hidden = true;
563    openBtn.setAttribute('aria-expanded', 'false');
564  }
565  openBtn.addEventListener('click', function () { pop.hidden ? open() : close(); });
566  var open2 = document.getElementById('subOpen2');
567  if (open2) open2.addEventListener('click', function (ev) {
568    ev.preventDefault();
569    window.scrollTo({ top: 0, behavior: 'smooth' });
570    open();
571  });
572  document.getElementById('subClose').addEventListener('click', close);
573  scrim.addEventListener('click', close);
574  document.addEventListener('keydown', function (ev) { if (ev.key === 'Escape' && !pop.hidden) close(); });
575
576  function showErr(msg) { status.textContent = msg; status.className = 'sub-status err'; }
577
578  form.addEventListener('submit', function (ev) {
579    ev.preventDefault();
580    var email = document.getElementById('subEmail').value.trim();
581    var phone = document.getElementById('subPhone').value.trim();
582    if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return showErr('That email doesn’t look right.');
583    if (!email && phone.replace(/\D/g, '').length < 7) return showErr(phone ? 'That number doesn’t look right.' : 'Add an email or a mobile number.');
584    status.className = 'sub-status'; status.textContent = '';
585    go.disabled = true; go.textContent = 'Subscribing…';
586    fetch('/api/whats-new/subscribe', {
587      method: 'POST',
588      headers: { 'Content-Type': 'application/json' },
589      body: JSON.stringify({ email: email, phone: phone })
590    }).then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
591      .then(function (res) {
592        go.disabled = false; go.textContent = 'Subscribe';
593        if (res.ok && res.d && res.d.ok) { pop.classList.add('done'); return; }
594        if (res.d && res.d.error === 'rate_limited') return showErr('Too many tries. Give it a minute and try again.');
595        showErr('Couldn’t subscribe just now. Please try again.');
596      })
597      .catch(function () {
598        go.disabled = false; go.textContent = 'Subscribe';
599        showErr('Couldn’t subscribe just now. Please try again.');
600      });
601  });
602})();
603</script>
603
604</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.