Jauge offset 62, onglet mensuel renommé, déduction 66% sur boutons libres, champs label noir/vert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
25fe91ea93
commit
b8048d24f6
5 changed files with 68 additions and 6 deletions
52
CLAUDE.md
Normal file
52
CLAUDE.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project overview
|
||||||
|
|
||||||
|
Static PHP site for the Index NGO donation/support campaign (`soutenir.index.ngo`). No build step — PHP files are served directly. The site has two language versions: French (`index.php`) and English (`en/index.php`).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Content flow
|
||||||
|
|
||||||
|
All editorial content is managed via a **Kirby CMS** running in a separate repo (`/Users/adrienpayet/Documents/code/en-cours/index/main/`). The static site fetches content from the Kirby API and caches it locally:
|
||||||
|
|
||||||
|
```
|
||||||
|
Kirby CMS (main repo) → /support-content?lang=fr → includes/cache.php → index.php
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`includes/config.php`** — API URL, token, cache TTL constants
|
||||||
|
- **`includes/cache.php`** — `getContent(lang)` function: serves cache if fresh (<5 min), otherwise fetches from Kirby API via curl, falls back to stale cache
|
||||||
|
- **`cache/`** — stores `content.fr.json` and `content.en.json`
|
||||||
|
|
||||||
|
### Kirby backend (separate repo)
|
||||||
|
|
||||||
|
- **Blueprint**: `/Users/adrienpayet/Documents/code/en-cours/index/main/site/blueprints/pages/support.yml` — defines all editable fields for the support page
|
||||||
|
- **API route**: `/Users/adrienpayet/Documents/code/en-cours/index/main/site/config/routes/support-content.php` — builds and returns the JSON response consumed by this site; uses `->inline()` on writer fields
|
||||||
|
|
||||||
|
When adding a new dynamic field: add it to the blueprint, expose it in the API route, then consume `$data['fieldName']` in the PHP templates.
|
||||||
|
|
||||||
|
### Donation gauge
|
||||||
|
|
||||||
|
- **`api/donorbox-proxy.php`** — server-side proxy to Donorbox API (Basic Auth). Paginates through all active plans to get `recurring_donors_count`. Caches result in `api/cache/donorbox_data.json` (5 min).
|
||||||
|
- **`assets/js/donorbox-gauge.js`** — calls the proxy, adds `RECURRING_DONORS_OFFSET` to the real count, updates the gauge CSS variable `--pourcent` and the displayed number. Also sets up 5-minute auto-refresh.
|
||||||
|
|
||||||
|
`RECURRING_DONORS_OFFSET` is defined at the top of `donorbox-gauge.js` and represents historic/offline supporters added to the live Donorbox count.
|
||||||
|
|
||||||
|
### Donation pad
|
||||||
|
|
||||||
|
**`assets/js/donation.js`** — IIFE that:
|
||||||
|
- Dynamically generates all donation buttons (amounts, after-tax calculations) on `DOMContentLoaded`
|
||||||
|
- Contains `TRANSLATIONS` object for FR/EN static strings in the donation pad
|
||||||
|
- Generates Donorbox redirect URLs with UTM passthrough
|
||||||
|
- Handles monthly/one-off tab switching
|
||||||
|
|
||||||
|
Static strings in the donation pad (e.g. "Avec déduction fiscale de 66 %") live in the `TRANSLATIONS` object in this file, not in Kirby.
|
||||||
|
|
||||||
|
## Key conventions
|
||||||
|
|
||||||
|
- **Writer fields in Kirby**: use `->inline()` in the API route to get inline HTML without block wrappers. For multiline text (e.g. a title with a `<br>`), use a `writer` field with `nodes: false` and `buttons: false` — this preserves line breaks. In the API route, use `->inline()` which converts newlines to `<br>` tags; do **not** use `nl2br()`.
|
||||||
|
- **`heroHeading` field**: rendered with `<?= $data['heroHeading'] ?>` (no `htmlspecialchars`) because it contains trusted HTML from the writer field.
|
||||||
|
- **Language**: `document.documentElement.lang` drives JS translations. FR page sets `lang="fr"`, EN page sets `lang="en"`.
|
||||||
|
- **Cache invalidation**: delete files in `cache/` to force a fresh fetch from Kirby.
|
||||||
|
|
@ -14,11 +14,13 @@
|
||||||
afterTax: 'Soit {amount} € après impôts',
|
afterTax: 'Soit {amount} € après impôts',
|
||||||
perMonth: '€/mois',
|
perMonth: '€/mois',
|
||||||
withTaxReduction: 'Avec 66 % de déduction fiscale',
|
withTaxReduction: 'Avec 66 % de déduction fiscale',
|
||||||
|
chooseAmount: 'Choisissez votre montant',
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
afterTax: 'That is {amount} € after tax',
|
afterTax: 'That is {amount} € after tax',
|
||||||
perMonth: '€/month',
|
perMonth: '€/month',
|
||||||
withTaxReduction: 'With 66 % tax deduction',
|
withTaxReduction: 'With 66 % tax deduction',
|
||||||
|
chooseAmount: 'Choose your amount',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -115,6 +117,10 @@
|
||||||
window.open(generateDonorboxUrl(amount, false), '_blank');
|
window.open(generateDonorboxUrl(amount, false), '_blank');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
button.innerHTML = `
|
||||||
|
<p class="bold">${translate('chooseAmount')}</p>
|
||||||
|
<p class="small">${translate('withTaxReduction')}</p>
|
||||||
|
`;
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
window.open(generateDonorboxUrl(null, false), '_blank');
|
window.open(generateDonorboxUrl(null, false), '_blank');
|
||||||
});
|
});
|
||||||
|
|
@ -144,6 +150,10 @@
|
||||||
window.open(generateDonorboxUrl(amount, true), '_blank');
|
window.open(generateDonorboxUrl(amount, true), '_blank');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
button.innerHTML = `
|
||||||
|
<p class="bold">${translate('chooseAmount')}</p>
|
||||||
|
<p class="small">${translate('withTaxReduction')}</p>
|
||||||
|
`;
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
window.open(generateDonorboxUrl(null, true), '_blank');
|
window.open(generateDonorboxUrl(null, true), '_blank');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const DONORBOX_CONFIG = {
|
||||||
proxyUrl: '/api/donorbox-proxy.php',
|
proxyUrl: '/api/donorbox-proxy.php',
|
||||||
};
|
};
|
||||||
|
|
||||||
const RECURRING_DONORS_OFFSET = 98;
|
const RECURRING_DONORS_OFFSET = 62;
|
||||||
const GOAL_SUPPORTERS = 500;
|
const GOAL_SUPPORTERS = 500;
|
||||||
|
|
||||||
async function fetchDonorboxData() {
|
async function fetchDonorboxData() {
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ $data = getContent('en');
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="hero-heading">
|
<p class="hero-heading">
|
||||||
<?= htmlspecialchars($data['heroObjectiveLabel'] ?? '') ?>
|
<?= htmlspecialchars($data['gaugeBlackLabel'] ?? '') ?>
|
||||||
<?php if (!empty($data['heroObjectiveDate'])): ?><br /><strong><?= htmlspecialchars($data['heroObjectiveDate']) ?></strong><?php endif; ?>
|
<?php if (!empty($data['gaugeGreenLabel'])): ?><br /><strong><?= htmlspecialchars($data['gaugeGreenLabel']) ?></strong><?php endif; ?>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ $data = getContent('fr');
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="hero-heading">
|
<p class="hero-heading">
|
||||||
<?= htmlspecialchars($data['heroObjectiveLabel'] ?? '') ?>
|
<?= htmlspecialchars($data['gaugeBlackLabel'] ?? '') ?>
|
||||||
<?php if (!empty($data['heroObjectiveDate'])): ?><br /><strong><?= htmlspecialchars($data['heroObjectiveDate']) ?></strong><?php endif; ?>
|
<?php if (!empty($data['gaugeGreenLabel'])): ?><br /><strong><?= htmlspecialchars($data['gaugeGreenLabel']) ?></strong><?php endif; ?>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -131,7 +131,7 @@ $data = getContent('fr');
|
||||||
<div class="col-right">
|
<div class="col-right">
|
||||||
<section id="section__donation">
|
<section id="section__donation">
|
||||||
<nav class="nav--tabs">
|
<nav class="nav--tabs">
|
||||||
<button class="nav--tabs__btn is-selected" data-tab="monthly">Je donne tous les mois</button>
|
<button class="nav--tabs__btn is-selected" data-tab="monthly">Je deviens Soutien</button>
|
||||||
<button class="nav--tabs__btn" data-tab="one-off">Je donne une fois</button>
|
<button class="nav--tabs__btn" data-tab="one-off">Je donne une fois</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue