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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/components/BaseHead.astro
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,51 @@ const gscVerification = import.meta.env.PUBLIC_GSC_VERIFICATION_TOKEN;
});
</script>

{/* Copy-to-clipboard for command blocks (#45): one delegated listener per
page, same pattern as the theme toggle. Each button carries its locale's
labels as data attributes, so this script stays locale-agnostic. Falls
back to a hidden textarea + execCommand for older Safari. */}
<script is:inline>
document.addEventListener("click", function (e) {
var b = e.target.closest("[data-copy]");
if (!b) return;
var pre = b.parentElement ? b.parentElement.querySelector("pre") : null;
if (!pre) return;
var text = pre.textContent || "";
var idle = b.getAttribute("data-copy-label") || "Copy";
var done = b.getAttribute("data-copied-label") || "Copied";
var timer = null;
function flash() {
b.textContent = done;
b.classList.add("copied");
clearTimeout(timer);
timer = setTimeout(function () {
b.textContent = idle;
b.classList.remove("copied");
}, 1500);
}
function legacy() {
var ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.position = "fixed";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.select();
try {
document.execCommand("copy");
flash();
} catch (err) {}
document.body.removeChild(ta);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(flash, legacy);
} else {
legacy();
}
});
</script>

{
analyticsToken && (
<script
Expand Down
34 changes: 34 additions & 0 deletions src/components/CodeBlock.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
import type { Locale } from "../i18n/config";
import { t } from "../i18n/ui";

/**
* A terminal command block with a one-tap Copy button (#45). Static markup
* only — the handler is a single delegated listener in BaseHead, the same
* pattern as the theme toggle. The button carries its labels as data
* attributes so the shared script stays locale-agnostic.
*/
interface Props {
code: string;
locale: Locale;
/** Extra classes for the <pre> — pages hook specific blocks (verify-command…). */
preClass?: string;
}

const { code, locale, preClass = "" } = Astro.props;
const chrome = t(locale);
---

<div class="code-block">
<button
class="copy-code-btn"
type="button"
data-copy
data-copy-label={chrome.code.copy}
data-copied-label={chrome.code.copied}
aria-label={chrome.code.copyAria}
>
{chrome.code.copy}
</button>
<pre class={preClass}><code>{code}</code></pre>
</div>
7 changes: 2 additions & 5 deletions src/components/pages/GuidePage.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import Base from "../../layouts/Base.astro";
import DocsSidebar from "../docs/DocsSidebar.astro";
import CodeBlock from "../CodeBlock.astro";
import { guides } from "../../i18n/guides/content";
import { laneHref, laneNeighbors, laneTitle } from "../../i18n/guides/lane";
import { SITE } from "../../i18n/config";
Expand Down Expand Up @@ -62,11 +63,7 @@ const jsonLd = [
{step.body.map((paragraph) => (
<p>{paragraph}</p>
))}
{step.code && (
<pre>
<code>{step.code}</code>
</pre>
)}
{step.code && <CodeBlock code={step.code} locale="en" />}
{step.shot && (
<figure class="shot-figure">
<img class="shot" src={step.shot} alt={step.shotCaption ?? step.title} loading="lazy" />
Expand Down
4 changes: 3 additions & 1 deletion src/components/pages/HomePage.astro
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ const fiqhHref =

<HonestBox
title={c.honest.title}
summaryBadge={c.honest.summaryBadge || c.honest.summaryBadgeAr || c.honest.summaryBadgeFr}
summaryBadge={
locale === "ar" ? c.honest.summaryBadgeAr : locale === "fr" ? c.honest.summaryBadgeFr : c.honest.summaryBadge
}
paragraphs={c.honest.body}
links={honestLinks}
/>
Expand Down
25 changes: 17 additions & 8 deletions src/components/pages/InstallPage.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import Base from "../../layouts/Base.astro";
import ForOperators from "../ForOperators.astro";
import CodeBlock from "../CodeBlock.astro";
import { readDataFile } from "../docs/nav";
import { install } from "../../i18n/pages/install";
import { alternatesFor, formatDate, localePath, type Locale } from "../../i18n/config";
Expand Down Expand Up @@ -70,6 +71,15 @@ const venvCommands = (os: "mac" | "win"): string => {
"keel versions",
].join("\n");
};

const fromSourceCommands = [
"git clone https://github.com/CodeGateSoftware/keel.git && cd keel",
"uv sync --all-extras --dev # any Python 3.11+ (the repo develops on 3.14)",
"cp .env.example .env # put the read-only CDP key/secret in it — market data only",
"uv run keel rules seed # register the rule families as candidates",
"uv run keel fetch # pull candle history for the default allowlist",
"uv run keel simulate --years 1 --skip-within-cap",
].join("\n");
---

<Base
Expand Down Expand Up @@ -125,7 +135,7 @@ const venvCommands = (os: "mac" | "win"): string => {
{target && <span class="direct-file">{target.name}</span>}
<div class="platform-steps">
<p class="shell-name">{card.shell}</p>
<pre><code>{card.codeComment + "\n" + venvCommands(os)}</code></pre>
<CodeBlock code={card.codeComment + "\n" + venvCommands(os)} locale={locale} />
</div>
</div>
);
Expand Down Expand Up @@ -173,7 +183,11 @@ const venvCommands = (os: "mac" | "win"): string => {

<h3>{c.unsigned.verifyTitle}</h3>
<p>{c.unsigned.verifyLead}</p>
<pre class="verify-command"><code>gh attestation verify &lt;the file you downloaded&gt; --repo CodeGateSoftware/keel</code></pre>
<CodeBlock
preClass="verify-command"
code="gh attestation verify <the file you downloaded> --repo CodeGateSoftware/keel"
locale={locale}
/>
<p><strong>{c.unsigned.verifyFail}</strong></p>

<h3>{c.unsigned.notMeaningTitle}</h3>
Expand Down Expand Up @@ -229,12 +243,7 @@ const venvCommands = (os: "mac" | "win"): string => {
<ul>
{c.fromSource.requirements.map((requirement) => <li>{requirement}</li>)}
</ul>
<pre><code>git clone https://github.com/CodeGateSoftware/keel.git && cd keel
uv sync --all-extras --dev # any Python 3.11+ (the repo develops on 3.14)
cp .env.example .env # put the read-only CDP key/secret in it — market data only
uv run keel rules seed # register the rule families as candidates
uv run keel fetch # pull candle history for the default allowlist
uv run keel simulate --years 1 --skip-within-cap</code></pre>
<CodeBlock code={fromSourceCommands} locale={locale} />
<h3>{c.fromSource.expectTitle}</h3>
<p>{c.fromSource.expect}</p>
<p>
Expand Down
8 changes: 5 additions & 3 deletions src/i18n/pages/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export const home: LocalizedPage<HomeContent> = {
},
honest: {
title: "The honest result, stated first",
summaryBadge: "In plain terms: Platform trading fees mean the reference rules currently do not beat simple dollar-cost averaging (DCA). We state this upfront so you evaluate the compliance machinery without false profit expectations.",
summaryBadge:
"In plain words: after the platform fees actually paid, none of keel's shipped rules is profitable and none beats simple dollar-cost averaging (DCA) — at best they touch break-even inside the venue's fee-free allowance.",
body: [
"No shipped rule family is net positive at the taker fee actually paid on Coinbase — about 1.2% on each side of a trade. In one measurement matrix, 0 of 90 configurations cleared; in another, 0 of 82. We then cross-verified a stochastic modelling note, re-deriving its mathematics against keel's real numbers, and it sharpened the why: the taker fee is the entire result. Inside the venue's fee-free monthly allowance, the reconstructed rules sit indistinguishably at break-even — a 14.9% win rate against a 14.88% break-even. One step outside it, break-even jumps to 29% and the same rules are decisively negative. The allowance rail is not a budget cap. It is the profitability boundary.",
"The point of this project is the enforcement machinery and the honest measurement of what runs through it, not a claim of profit. Every result is compared against a simple buy-every-period (DCA) benchmark, and the reference rules currently do not beat it after fees. We would rather you know that on the front page than discover it yourself. And we are working hard to improve the results of the algorithms and the strategies. We will report our progress here.",
Expand Down Expand Up @@ -116,7 +117,8 @@ export const home: LocalizedPage<HomeContent> = {
},
honest: {
title: "النتيجة الصادقة، نقولها أولًا",
summaryBadgeAr: "باختصار: رسوم التداول لدى المنصات تجعل القواعد المرجعية حاليًا لا تتفوق على الشراء الدوري المنتظم (DCA). ونحن ننشر هذا أولًا لتفحص آليات الامتثال بلا توقعات أرباح زائفة.",
summaryBadgeAr:
"بلغةٍ مبسّطة: بعد خصم رسوم المنصّة المدفوعة فعليًّا، لا تحقّق أيُّ قاعدةٍ من قواعد كيل المُصدَّرة ربحًا ولا تتفوّق على الشراء الدوري المنتظم (DCA) — وفي أحسن الأحوال تلامس نقطة التعادل داخل الحصّة الشهرية المعفاة من الرسوم.",
body: [
"لا تحقّق أيُّ عائلةٍ من القواعد المُصدَّرة ربحًا صافيًا عند رسوم الآخذ (taker) المدفوعة فعليًّا على منصّة Coinbase‏ (نحو 1.2٪ لكلِّ طرفٍ من الصفقة) — صفرٌ من 90 تهيئةً في إحدى مصفوفات القياس، وصفرٌ من 82 في أخرى. وقد تحقّقنا تحقّقًا مستقلًّا من مذكّرةِ نمذجةٍ عشوائية — إذ أعدنا اشتقاق رياضياتها على أرقام كيل الحقيقية — فجلَّت السبب: رسومُ الآخذ هي النتيجة كلُّها. فداخل الحصّة الشهرية المعفاة من الرسوم لدى المنصّة، تقف القواعد المُعاد بناؤها عند نقطة التعادل بلا فرقٍ يُذكر (نسبةُ ربحٍ 14.9٪ مقابل نقطة تعادلٍ عند 14.88٪)؛ وبخطوةٍ واحدةٍ خارجها تقفز نقطةُ التعادل إلى 29٪ فتصير القواعد نفسها سالبةً بوضوح. فسكةُ الحصّة ليست سقفًا للميزانية — بل هي حدُّ الربحية.",
"والغاية من هذا المشروع هي آلياتُ الإنفاذ والقياسُ الصادق لما يمرّ عبرها — لا ادّعاءُ الربح. فكلُّ نتيجةٍ تُقارَن بمؤشّرٍ مرجعيٍّ بسيط هو الشراء الدوري المنتظم (DCA)، والقواعد المرجعية لا تتفوّق عليه بعد خصم الرسوم. ونحن نفضّل أن تعرف ذلك من الصفحة الأولى على أن تكتشفه بنفسك. ونعمل جاهدين على تحسين نتائج الخوارزميات والاستراتيجيات — وسنوافيك بما نُحرزه أوّلًا بأوّل.",
Expand Down Expand Up @@ -176,7 +178,7 @@ export const home: LocalizedPage<HomeContent> = {
},
honest: {
title: "Le résultat honnête, annoncé d'emblée",
summaryBadgeFr: "En résumé : Les frais de plateforme font que les règles de référence ne battent pas le simple achat périodique (DCA). Nous le publions d'emblée pour que vous évaluiez la machinerie de conformité sans fausses promesses de gain.",
summaryBadgeFr: "En clair : une fois les frais de plateforme réellement payés déduits, aucune règle livrée de keel n'est rentable et aucune ne bat le simple achat périodique (DCA) — tout au plus elles touchent le point mort dans le quota mensuel sans frais.",
body: [
"Aucune famille de règles livrée ne dégage un résultat net positif aux frais de preneur (taker) réellement payés sur Coinbase (~1,2 % par sens) — 0 configuration sur 90 dans une matrice de mesure, 0 sur 82 dans une autre. Nous avons recoupé une note de modélisation stochastique — en refaisant ses calculs sur les chiffres réels de keel — et elle a précisé le pourquoi : les frais de preneur font tout le résultat. Dans le quota mensuel sans frais offert par la plateforme, les règles reconstruites se situent au point mort, à l'indiscernable près (14,9 % de trades gagnants pour un seuil d'équilibre de 14,88 %) ; un pas au-delà, ce seuil bondit à 29 % et les mêmes règles deviennent nettement perdantes. Le garde-fou de quota n'est pas un plafond budgétaire : c'est la frontière de la rentabilité.",
"Ce projet a pour objet la machinerie d'application, et la mesure honnête de ce qui la traverse — pas une promesse de gain. Chaque résultat est comparé à une référence simple, l'achat périodique (DCA), et les règles de référence ne la battent pas une fois les frais déduits. Nous préférons que vous l'appreniez dès la page d'accueil plutôt que de le découvrir par vous-même. Nous nous employons à améliorer les résultats des algorithmes et des stratégies, et nous rendrons compte de nos progrès.",
Expand Down
15 changes: 15 additions & 0 deletions src/i18n/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export const ui = {
seeReleases: "See releases on GitHub",
discussOnGitHub: "Discuss on GitHub",
},
code: {
copy: "Copy",
copied: "Copied ✓",
copyAria: "Copy the command to the clipboard",
},
tracks: {
plainLabel: "Plain English",
operatorsLabel: "For operators",
Expand Down Expand Up @@ -118,6 +123,11 @@ export const ui = {
seeReleases: "اطّلع على الإصدارات في GitHub",
discussOnGitHub: "ناقِش على GitHub",
},
code: {
copy: "نسخ",
copied: "تم النسخ ✓",
copyAria: "انسخ الأمر إلى الحافظة",
},
tracks: {
plainLabel: "بلغةٍ مبسّطة",
operatorsLabel: "للمشغّلين",
Expand Down Expand Up @@ -213,6 +223,11 @@ export const ui = {
seeReleases: "Voir les versions sur GitHub",
discussOnGitHub: "En discuter sur GitHub",
},
code: {
copy: "Copier",
copied: "Copié ✓",
copyAria: "Copier la commande dans le presse-papiers",
},
tracks: {
plainLabel: "En clair",
operatorsLabel: "Pour les opérateurs",
Expand Down
12 changes: 12 additions & 0 deletions src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,18 @@ section {
border-color: var(--accent);
}

/* Transient "Copied ✓" state after a successful copy (#45) */
.copy-code-btn.copied {
color: var(--accent);
border-color: var(--accent);
}

/* Wrapper around a command block and its Copy button (#45); min-width so
the pre can scroll inside grid/flex parents without widening the page. */
.code-block {
min-width: 0;
}

/* --------------------------------------------------------------------------
Cards & grids
-------------------------------------------------------------------------- */
Expand Down
Loading