diff --git a/app/app.config.ts b/app/app.config.ts index 6c01951..dc62412 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -20,7 +20,7 @@ export default defineAppConfig({ ], }, gratewizard: { - url: '/gratewizard?popup', + url: 'https://gratewizard.ml', popup: { width: 420, height: 720, diff --git a/app/components/book/BookPlayer.vue b/app/components/book/BookPlayer.vue index b025d93..c3f6280 100644 --- a/app/components/book/BookPlayer.vue +++ b/app/components/book/BookPlayer.vue @@ -564,6 +564,7 @@ onUnmounted(() => { align-items: center; width: 100%; flex: 1; + min-height: 0; } /* ═══════════════════════════════════════ @@ -574,6 +575,7 @@ onUnmounted(() => { max-width: 52rem; padding: 0 1rem; gap: 0; + min-height: 0; } /* ─── Top bar ─── */ @@ -729,11 +731,13 @@ onUnmounted(() => { /* ─── Scroll mode overrides ─── */ .reader-viewport--scroll { - overflow-y: auto; + overflow: hidden auto; + min-height: 0; } .reader-columns--scroll { height: auto; column-fill: unset; + column-width: unset !important; transition: none; } diff --git a/app/components/gratewizard/GwCRA.vue b/app/components/gratewizard/GwCRA.vue deleted file mode 100644 index 3937d6c..0000000 --- a/app/components/gratewizard/GwCRA.vue +++ /dev/null @@ -1,194 +0,0 @@ - - - diff --git a/app/components/gratewizard/GwCRS.vue b/app/components/gratewizard/GwCRS.vue deleted file mode 100644 index 12fa7c4..0000000 --- a/app/components/gratewizard/GwCRS.vue +++ /dev/null @@ -1,208 +0,0 @@ - - - diff --git a/app/components/gratewizard/GwMN.vue b/app/components/gratewizard/GwMN.vue deleted file mode 100644 index 22d3fcc..0000000 --- a/app/components/gratewizard/GwMN.vue +++ /dev/null @@ -1,272 +0,0 @@ - - - diff --git a/app/components/gratewizard/GwMap.client.vue b/app/components/gratewizard/GwMap.client.vue deleted file mode 100644 index f58d10a..0000000 --- a/app/components/gratewizard/GwMap.client.vue +++ /dev/null @@ -1,146 +0,0 @@ - - - - - diff --git a/app/components/gratewizard/GwPerimeterList.vue b/app/components/gratewizard/GwPerimeterList.vue deleted file mode 100644 index 07fa0ed..0000000 --- a/app/components/gratewizard/GwPerimeterList.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/app/components/gratewizard/GwRelations.vue b/app/components/gratewizard/GwRelations.vue deleted file mode 100644 index 138b7c0..0000000 --- a/app/components/gratewizard/GwRelations.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - diff --git a/app/components/gratewizard/GwTabs.vue b/app/components/gratewizard/GwTabs.vue deleted file mode 100644 index 3b42022..0000000 --- a/app/components/gratewizard/GwTabs.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - diff --git a/app/components/player/PlayerPersistent.vue b/app/components/player/PlayerPersistent.vue index 93eaa18..463995f 100644 --- a/app/components/player/PlayerPersistent.vue +++ b/app/components/player/PlayerPersistent.vue @@ -5,106 +5,107 @@ ref="widgetRef" class="mini-player" > - +
- -
- - - + +
+
+

{{ store.currentSong.title }}

+

{{ store.currentSong.artist }}

+
+
+ + + +
- -
+ +
-
- +
+ {{ store.formattedCurrentTime }} + {{ store.formattedDuration }}
- -
- -
- - -
- - {{ store.formattedCurrentTime }} / {{ store.formattedDuration }} - + +
+ +
+ + +
+ + +
+ + +
+
{{ store.currentSong.lyrics }}
-
+
- -
- -
- + +
+ +
+ - -
- -
-
- {{ store.currentSong.title }} + {{ store.currentSong.title }} - +
@@ -123,7 +124,7 @@ const widgetRef = ref() const isExpanded = ref(false) let previousVolume = 0.8 -const circumference = 2 * Math.PI * 18 // r=18 +const circumference = 2 * Math.PI * 16 const volumeIcon = computed(() => { if (store.volume === 0) return 'i-lucide-volume-x' @@ -151,11 +152,6 @@ function toggleExpanded() { isExpanded.value = !isExpanded.value } -function onPillClick() { - isExpanded.value = !isExpanded.value -} - -// Close expanded panel on click outside onClickOutside(widgetRef, () => { if (isExpanded.value) isExpanded.value = false }) @@ -163,168 +159,247 @@ onClickOutside(widgetRef, () => { diff --git a/app/composables/useCesiumProfiles.ts b/app/composables/useCesiumProfiles.ts deleted file mode 100644 index 1eb7409..0000000 --- a/app/composables/useCesiumProfiles.ts +++ /dev/null @@ -1,97 +0,0 @@ -const CESIUM_PODS = [ - 'https://g1.data.brussels.ovh/user/profile/_search', - 'https://g1.data.le-sou.org/user/profile/_search', - 'https://g1.data.e-is.pro/user/profile/_search', -]; -const BATCH_SIZE = 500; - -export type GeoMember = { - pubkey: string; - title: string; - city: string; - lat: number; - lon: number; -}; - -/** Find the first Cesium+ pod that responds successfully. */ -async function findWorkingPod(): Promise { - for (const url of CESIUM_PODS) { - try { - const res = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ size: 0, query: { match_all: {} } }), - }); - if (res.ok) return url; - } catch { - // try next pod - } - } - throw new Error('Aucun pod Cesium+ disponible'); -} - -/** - * Fetch Cesium+ profiles for a given list of v1 pubkeys. - * Uses Elasticsearch `ids` query with batches of 500, filtered to geolocated profiles only. - * Pass `null` to skip fetching (e.g. while pubkeys are still loading). - */ -export function useCesiumProfiles(v1Pubkeys: Ref) { - const geoMembers = ref([]); - const loading = ref(true); - const error = ref(null); - - watch(v1Pubkeys, async (pubkeys) => { - if (pubkeys === null) return; - - loading.value = true; - error.value = null; - - try { - const podUrl = await findWorkingPod(); - const allMembers: GeoMember[] = []; - - for (let i = 0; i < pubkeys.length; i += BATCH_SIZE) { - const batch = pubkeys.slice(i, i + BATCH_SIZE); - const res = await fetch(podUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - size: batch.length, - _source: ['title', 'city', 'geoPoint'], - query: { - bool: { - must: [ - { ids: { values: batch } }, - { exists: { field: 'geoPoint' } }, - ], - }, - }, - }), - }); - - if (!res.ok) throw new Error(`Cesium+ HTTP ${res.status}`); - const json = await res.json(); - - for (const hit of json.hits?.hits ?? []) { - const s = hit._source; - if (!s?.geoPoint?.lat || !s?.geoPoint?.lon) continue; - allMembers.push({ - pubkey: hit._id, - title: s.title || '', - city: s.city || '', - lat: s.geoPoint.lat, - lon: s.geoPoint.lon, - }); - } - } - - geoMembers.value = allMembers; - } catch (e: any) { - error.value = e.message; - } finally { - loading.value = false; - } - }, { immediate: true }); - - return { geoMembers, loading, error }; -} diff --git a/app/composables/useGrateWizard.ts b/app/composables/useGrateWizard.ts index 2e043e4..8f7c8f3 100644 --- a/app/composables/useGrateWizard.ts +++ b/app/composables/useGrateWizard.ts @@ -8,7 +8,7 @@ export function useGrateWizard() { const win = window.open( url, 'grateWizard', - `width=${popup.width},height=${popup.height},left=${left},top=${top},scrollbars=yes,resizable=yes`, + `width=${popup.width},height=${popup.height},left=${left},top=${top},menubar=no,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes`, ) if (win) e?.preventDefault() } diff --git a/app/composables/useSavedPerimeters.ts b/app/composables/useSavedPerimeters.ts deleted file mode 100644 index f090fc5..0000000 --- a/app/composables/useSavedPerimeters.ts +++ /dev/null @@ -1,24 +0,0 @@ -export type SavedPerimeter = { - name: string; - polygon: [number, number][]; - createdAt: string; -}; - -const STORAGE_KEY = 'gw-saved-perimeters'; - -export function useSavedPerimeters() { - const perimeters = useLocalStorage(STORAGE_KEY, []); - - function savePerimeter(name: string, polygon: [number, number][]) { - perimeters.value = [ - ...perimeters.value.filter((p) => p.name !== name), - { name, polygon, createdAt: new Date().toISOString() }, - ]; - } - - function deletePerimeter(name: string) { - perimeters.value = perimeters.value.filter((p) => p.name !== name); - } - - return { perimeters, savePerimeter, deletePerimeter }; -} diff --git a/app/pages/gratewizard.vue b/app/pages/gratewizard.vue index e339d89..e1d17d3 100644 --- a/app/pages/gratewizard.vue +++ b/app/pages/gratewizard.vue @@ -1,15 +1,5 @@ - - diff --git a/app/services/duniter/index.ts b/app/services/duniter/index.ts deleted file mode 100644 index 0411d7f..0000000 --- a/app/services/duniter/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Changer cette ligne pour switcher v1 → v2 après mars 2026 : -import { v1Adapter as duniter } from './v1'; -export { duniter }; -export type { DuniterAdapter, MonetaryData } from './types'; diff --git a/app/services/duniter/types.ts b/app/services/duniter/types.ts deleted file mode 100644 index 69f91d8..0000000 --- a/app/services/duniter/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type MonetaryData = { - monetaryMass: string; - membersCount: number; - amount: string; - timestamp: string; - blockNumber: number; - udBlockNumbers: number[]; -}; - -export interface DuniterAdapter { - fetchMonetary(): Promise; - fetchMemberPubkeys(): Promise; - fetchMemberJoinBlocks(pubkeys: string[]): Promise>; -} diff --git a/app/services/duniter/v1.ts b/app/services/duniter/v1.ts deleted file mode 100644 index fbb4a96..0000000 --- a/app/services/duniter/v1.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { DuniterAdapter, MonetaryData } from './types'; - -const BMA_URL = 'https://g1.duniter.org'; - -async function bmaGet(path: string): Promise { - const res = await fetch(`${BMA_URL}${path}`); - if (!res.ok) throw new Error(`BMA ${path}: ${res.status}`); - return res.json(); -} - -const joinBlockCache = new Map(); - -export const v1Adapter: DuniterAdapter = { - async fetchMonetary(): Promise { - const [current, udBlocks] = await Promise.all([ - bmaGet<{ - monetaryMass: number; - membersCount: number; - number: number; - medianTime: number; - }>('/blockchain/current'), - bmaGet<{ result: { blocks: number[] } }>('/blockchain/with/ud'), - ]); - - const udBlockNumbers = udBlocks.result.blocks; - const lastUdBlock = udBlockNumbers[udBlockNumbers.length - 1]; - const udBlock = await bmaGet<{ dividend: number }>(`/blockchain/block/${lastUdBlock}`); - - return { - monetaryMass: String(current.monetaryMass), - membersCount: current.membersCount, - amount: String(udBlock.dividend), - timestamp: new Date(current.medianTime * 1000).toISOString(), - blockNumber: current.number, - udBlockNumbers, - }; - }, - - async fetchMemberPubkeys(): Promise { - const data = await bmaGet<{ results: { pubkey: string }[] }>('/wot/members'); - return data.results.map((m) => m.pubkey); - }, - - async fetchMemberJoinBlocks(pubkeys: string[]): Promise> { - const result = new Map(); - const toFetch: string[] = []; - - for (const pk of pubkeys) { - const cached = joinBlockCache.get(pk); - if (cached !== undefined) { - result.set(pk, cached); - } else { - toFetch.push(pk); - } - } - - const CONCURRENT = 10; - for (let i = 0; i < toFetch.length; i += CONCURRENT) { - const batch = toFetch.slice(i, i + CONCURRENT); - await Promise.all( - batch.map(async (pk) => { - try { - const data = await bmaGet<{ - results: { uids: { meta: { timestamp: string } }[] }[]; - }>(`/wot/lookup/${encodeURIComponent(pk)}`); - const ts = data.results?.[0]?.uids?.[0]?.meta?.timestamp; - if (ts) { - const blockNum = parseInt(ts.split('-')[0], 10); - if (!isNaN(blockNum)) { - joinBlockCache.set(pk, blockNum); - result.set(pk, blockNum); - } - } - } catch { - // Skip members we can't look up - } - }) - ); - } - - return result; - }, -}; diff --git a/app/services/duniter/v2.ts b/app/services/duniter/v2.ts deleted file mode 100644 index 1648dbf..0000000 --- a/app/services/duniter/v2.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { ss58ToV1Pubkey } from '~/utils/ss58'; -import type { DuniterAdapter, MonetaryData } from './types'; - -const SQUID_URL = 'https://gt-squid.axiom-team.fr/v1/graphql'; - -async function gql(query: string): Promise { - const res = await fetch(SQUID_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query }), - }); - const json = await res.json(); - if (json.errors) throw new Error(json.errors[0].message); - return json.data; -} - -export const v2Adapter: DuniterAdapter = { - async fetchMonetary(): Promise { - const data = await gql<{ - universalDividends: { nodes: (Omit & Record)[] }; - }>(`{ - universalDividends(first: 1, orderBy: BLOCK_NUMBER_DESC) { - nodes { monetaryMass membersCount amount timestamp blockNumber } - } - }`); - return { ...data.universalDividends.nodes[0], udBlockNumbers: [] }; - }, - - async fetchMemberPubkeys(): Promise { - const accountIds: string[] = []; - let offset = 0; - const pageSize = 1000; - - while (true) { - const data = await gql<{ - identities: { nodes: { accountId: string }[] }; - }>(`{ - identities(first: ${pageSize}, offset: ${offset}, filter: { isMember: { equalTo: true } }) { - nodes { accountId } - } - }`); - - const nodes = data.identities.nodes; - for (const node of nodes) { - accountIds.push(node.accountId); - } - - if (nodes.length < pageSize) break; - offset += pageSize; - } - - // Convert SS58 accountIds to Cesium+ v1 base58 pubkeys - const pubkeys: string[] = []; - for (const id of accountIds) { - try { - pubkeys.push(ss58ToV1Pubkey(id)); - } catch { - // Skip invalid addresses - } - } - return pubkeys; - }, - - async fetchMemberJoinBlocks(_pubkeys: string[]): Promise> { - // TODO: implement using squid GraphQL after v2 migration - return new Map(); - }, -}; diff --git a/app/utils/gratewizard.ts b/app/utils/gratewizard.ts deleted file mode 100644 index f477eca..0000000 --- a/app/utils/gratewizard.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** Ray-casting algorithm to test if a point is inside a polygon */ -export function pointInPolygon(lat: number, lng: number, polygon: [number, number][]): boolean { - let inside = false; - for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { - const [yi, xi] = polygon[i]; - const [yj, xj] = polygon[j]; - if ((yi > lat) !== (yj > lat) && lng < ((xj - xi) * (lat - yi)) / (yj - yi) + xi) { - inside = !inside; - } - } - return inside; -} - -/** Format a number in French locale */ -export const fr = (n: number, decimals = 2) => - n.toLocaleString('fr-FR', { minimumFractionDigits: decimals, maximumFractionDigits: decimals }); - -export type CurrencyUnit = 'DU' | 'G1'; - -/** Format a G1 value in the given unit, with k/M suffix */ -export function formatValue(g1Value: number, unit: CurrencyUnit, duDaily: number): string { - const val = unit === 'DU' ? g1Value / duDaily : g1Value; - const suffix = unit === 'DU' ? 'DU' : '\u011e1'; - if (val >= 1_000_000) return fr(val / 1_000_000) + ' M' + suffix; - if (val >= 1_000) return fr(val / 1_000) + ' k' + suffix; - return fr(val) + ' ' + suffix; -} - -/** Binary-search count of udBlocks entries >= joinBlock (udBlocks is sorted ascending). */ -export function countUdSince(udBlocks: number[], joinBlock: number): number { - let lo = 0, hi = udBlocks.length; - while (lo < hi) { - const mid = (lo + hi) >> 1; - if (udBlocks[mid] < joinBlock) lo = mid + 1; - else hi = mid; - } - return udBlocks.length - lo; -} - -/** Date to ISO-like string (yyyy-mm-dd) */ -export const dateToString = (date: Date) => - date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2); - -/** Number of days between a date and today */ -export const getDays = (date: string | undefined) => { - if (!date) return 0; - const d = new Date(date); - const today = new Date(); - return Math.floor(Math.abs(d.getTime() - today.getTime()) / (1000 * 3600 * 24)); -}; - -/** Seniority ratio between two dates (days from today) */ -export const getRatio = (date1: string | undefined, date2: string | undefined) => { - return getDays(date1) / Math.max(getDays(date2), 1); -}; - -export const Block0Date = '2017-03-08'; - -export type Friend = { - name: string; - date: string; -}; - -export type TableFriend = Friend & { - [key: string]: string | number; - displayName: string; - displayDate: string; - du: number; -}; diff --git a/app/utils/ss58.ts b/app/utils/ss58.ts deleted file mode 100644 index 776c1e1..0000000 --- a/app/utils/ss58.ts +++ /dev/null @@ -1,109 +0,0 @@ -const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; - -const ALPHABET_MAP = new Map(); -for (let i = 0; i < BASE58_ALPHABET.length; i++) { - ALPHABET_MAP.set(BASE58_ALPHABET[i], i); -} - -export function base58Decode(str: string): Uint8Array { - if (str.length === 0) return new Uint8Array(0); - - // Count leading '1's (zero bytes) - let leadingZeros = 0; - while (leadingZeros < str.length && str[leadingZeros] === '1') { - leadingZeros++; - } - - // Decode base58 to big integer (stored as byte array) - const size = Math.ceil(str.length * 733 / 1000) + 1; // log(58) / log(256) - const bytes = new Uint8Array(size); - - for (let i = leadingZeros; i < str.length; i++) { - const val = ALPHABET_MAP.get(str[i]); - if (val === undefined) throw new Error(`Invalid base58 character: ${str[i]}`); - - let carry = val; - for (let j = size - 1; j >= 0; j--) { - carry += 256 * bytes[j]; - bytes[j] = carry % 256; - carry = Math.floor(carry / 256); - } - } - - // Skip leading zeros in the decoded bytes - let start = 0; - while (start < size && bytes[start] === 0) { - start++; - } - - const result = new Uint8Array(leadingZeros + (size - start)); - // Leading zeros from '1' characters - for (let i = 0; i < leadingZeros; i++) { - result[i] = 0; - } - // Decoded bytes - for (let i = start; i < size; i++) { - result[leadingZeros + (i - start)] = bytes[i]; - } - - return result; -} - -export function base58Encode(bytes: Uint8Array): string { - if (bytes.length === 0) return ''; - - // Count leading zero bytes - let leadingZeros = 0; - while (leadingZeros < bytes.length && bytes[leadingZeros] === 0) { - leadingZeros++; - } - - // Encode to base58 - const size = Math.ceil(bytes.length * 138 / 100) + 1; // log(256) / log(58) - const digits = new Uint8Array(size); - - for (let i = leadingZeros; i < bytes.length; i++) { - let carry = bytes[i]; - for (let j = size - 1; j >= 0; j--) { - carry += 256 * digits[j]; - digits[j] = carry % 58; - carry = Math.floor(carry / 58); - } - } - - // Skip leading zeros in base58 output - let start = 0; - while (start < size && digits[start] === 0) { - start++; - } - - let result = '1'.repeat(leadingZeros); - for (let i = start; i < size; i++) { - result += BASE58_ALPHABET[digits[i]]; - } - - return result; -} - -/** - * Convert an SS58 address to a base58-encoded raw pubkey (Cesium+ v1 format). - * - * SS58 layout: [prefix (1 or 2 bytes)] [32 bytes pubkey] [2 bytes checksum] - * - If first byte has bit 6 set (& 0x40), prefix is 2 bytes - * - Otherwise prefix is 1 byte - */ -export function ss58ToV1Pubkey(ss58: string): string { - const raw = base58Decode(ss58); - - // Determine prefix length - const prefixLen = (raw[0] & 0x40) ? 2 : 1; - - // Extract 32-byte pubkey (skip prefix, drop 2-byte checksum) - const pubkey = raw.slice(prefixLen, prefixLen + 32); - - if (pubkey.length !== 32) { - throw new Error(`Invalid SS58 address: expected 32-byte pubkey, got ${pubkey.length}`); - } - - return base58Encode(pubkey); -} diff --git a/media/Paroles Chansons.pdf b/media/Paroles Chansons.pdf new file mode 100644 index 0000000..9162bcc Binary files /dev/null and b/media/Paroles Chansons.pdf differ diff --git a/package.json b/package.json index 2cb26f3..d33bf08 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,12 @@ "postinstall": "nuxt prepare" }, "dependencies": { - "@geoman-io/leaflet-geoman-free": "^2.19.2", "@nuxt/content": "^3.11.2", "@nuxt/image": "^2.0.0", "@pinia/nuxt": "^0.11.3", "@unocss/nuxt": "^66.6.0", "@vueuse/nuxt": "^14.2.1", "better-sqlite3": "^12.6.2", - "leaflet": "^1.9.4", - "leaflet.markercluster": "^1.5.3", "nuxt": "^4.3.1", "vue": "^3.5.28", "vue-router": "^4.6.4", @@ -27,7 +24,6 @@ "devDependencies": { "@iconify-json/lucide": "^1.2.91", "@iconify-json/ph": "^1.2.2", - "@types/leaflet": "^1.9.21", "typescript": "^5.9.3", "unocss": "^66.6.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b617cb..e123a0d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@geoman-io/leaflet-geoman-free': - specifier: ^2.19.2 - version: 2.19.2(leaflet@1.9.4) '@nuxt/content': specifier: ^3.11.2 version: 3.11.2(better-sqlite3@12.6.2)(magicast@0.5.2) @@ -29,12 +26,6 @@ importers: better-sqlite3: specifier: ^12.6.2 version: 12.6.2 - leaflet: - specifier: ^1.9.4 - version: 1.9.4 - leaflet.markercluster: - specifier: ^1.5.3 - version: 1.5.3(leaflet@1.9.4) nuxt: specifier: ^4.3.1 version: 4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2) @@ -54,9 +45,6 @@ importers: '@iconify-json/ph': specifier: ^1.2.2 version: 1.2.2 - '@types/leaflet': - specifier: ^1.9.21 - version: 1.9.21 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -400,12 +388,6 @@ packages: '@fastify/accept-negotiator@2.0.1': resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} - '@geoman-io/leaflet-geoman-free@2.19.2': - resolution: {integrity: sha512-FYqLCFjCWLc1c5vel83i2ON77zPugH9qfxzLxTt+SiFiMgHjO1dSS59qz23aLLQ0hRWTQdycnxXGNmT+4OC9sg==} - engines: {node: '>=18.0.0'} - peerDependencies: - leaflet: ^1.2.0 - '@iconify-json/lucide@1.2.91': resolution: {integrity: sha512-8fuRiK+HiNRgCKMspn9UPsDpBw0TqVTIY0LOiDbMnFxOBwAulMXIl+SVOtp4LzxNvCXB5ofYffiiFIFDitqo7w==} @@ -1493,51 +1475,6 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@turf/bbox@7.3.4': - resolution: {integrity: sha512-D5ErVWtfQbEPh11yzI69uxqrcJmbPU/9Y59f1uTapgwAwQHQztDWgsYpnL3ns8r1GmPWLP8sGJLVTIk2TZSiYA==} - - '@turf/boolean-contains@7.3.4': - resolution: {integrity: sha512-AJMGbtC6HiXgHvq0RNlTfsDB58Qf9Js45MP/APbhGTH4AiLZ8VMDISywVFNd7qN6oppNlDd3xApVR28+ti8bNg==} - - '@turf/boolean-point-in-polygon@7.3.4': - resolution: {integrity: sha512-v/4hfyY90Vz9cDgs2GwjQf+Lft8o7mNCLJOTz/iv8SHAIgMMX0czEoIaNVOJr7tBqPqwin1CGwsncrkf5C9n8Q==} - - '@turf/boolean-point-on-line@7.3.4': - resolution: {integrity: sha512-70gm5x6YQOZKcw0b/O4jjMwVWnFj+Zb6TXozLgZFDZShc8pgTQtZku7K+HKZ7Eya+7usHIB4IimZauomOMa+iw==} - - '@turf/distance@7.3.4': - resolution: {integrity: sha512-9drWgd46uHPPyzgrcRQLgSvdS/SjVlQ6ZIBoRQagS5P2kSjUbcOXHIMeOSPwfxwlKhEtobLyr+IiR2ns1TfF8w==} - - '@turf/geojson-rbush@7.3.4': - resolution: {integrity: sha512-aDG/5mMCgKduqBwZ3XpLOdlE2hizV3fM+5dHCWyrBepCQLeM/QRvvpBDCdQKDWKpoIBmrGGYDNiOofnf3QmGhg==} - - '@turf/helpers@7.3.4': - resolution: {integrity: sha512-U/S5qyqgx3WTvg4twaH0WxF3EixoTCfDsmk98g1E3/5e2YKp7JKYZdz0vivsS5/UZLJeZDEElOSFH4pUgp+l7g==} - - '@turf/invariant@7.3.4': - resolution: {integrity: sha512-88Eo4va4rce9sNZs6XiMJowWkikM3cS2TBhaCKlU+GFHdNf8PFEpiU42VDU8q5tOF6/fu21Rvlke5odgOGW4AQ==} - - '@turf/kinks@7.3.4': - resolution: {integrity: sha512-LZTKELWxvXl0vc9ZxVgi0v07fO9+2FrZOam2B10fz/eGjy3oKNazU5gjggbnc499wEIcJS4hN+VyjQZrmsJAdQ==} - - '@turf/line-intersect@7.3.4': - resolution: {integrity: sha512-XygbTvHa6A+v6l2ZKYtS8AAWxwmrPxKxfBbdH75uED1JvdytSLWYTKGlcU3soxd9sYb4x/g9sDvRIVyU6Lucrg==} - - '@turf/line-segment@7.3.4': - resolution: {integrity: sha512-UeISzf/JHoWEY5yeoyvKwA5epWcvJMCpCwbIMolvfTC5pp+IVozjHPVCRvRWuzmbmAvetcW0unL5bjqi0ADmuQ==} - - '@turf/line-split@7.3.4': - resolution: {integrity: sha512-l1zmCSUnGsiN4gf22Aw91a2VnYs5DZS67FdkYqKgr+wPEAL/gpQgIBBWSTmhwY8zb3NEqty+f/gMEe8EJAWYng==} - - '@turf/meta@7.3.4': - resolution: {integrity: sha512-tlmw9/Hs1p2n0uoHVm1w3ugw1I6L8jv9YZrcdQa4SH5FX5UY0ATrKeIvfA55FlL//PGuYppJp+eyg/0eb4goqw==} - - '@turf/nearest-point-on-line@7.3.4': - resolution: {integrity: sha512-DQrP3lRju83rIXFN68tUEpc7ki/eRwdwBkK2CTT4RAcyCxbcH2NGJPQv8dYiww/Ar77u1WLVn+aINXZH904dWw==} - - '@turf/truncate@7.3.4': - resolution: {integrity: sha512-VPXdae9+RLLM19FMrJgt7QANBikm7DxPbfp/dXgzE4Ca7v+mJ4T1fYc7gCZDaqOrWMccHKbvv4iSuW7YZWdIIA==} - '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -1553,18 +1490,12 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/geojson@7946.0.16': - resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/leaflet@1.9.21': - resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==} - '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -2020,9 +1951,6 @@ packages: resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} - bignumber.js@9.3.1: - resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -3064,14 +2992,6 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} - leaflet.markercluster@1.5.3: - resolution: {integrity: sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==} - peerDependencies: - leaflet: ^1.3.1 - - leaflet@1.9.4: - resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -3612,12 +3532,6 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - point-in-polygon-hao@1.2.4: - resolution: {integrity: sha512-x2pcvXeqhRHlNRdhLs/tgFapAbSSe86wa/eqmj1G6pWftbEs5aVRJhRGM6FYSUERKu0PjekJzMq0gsI2XyiclQ==} - - polyclip-ts@0.16.8: - resolution: {integrity: sha512-JPtKbDRuPEuAjuTdhR62Gph7Is2BS1Szx69CFOO3g71lpJDFo78k4tFyi+qFOMVPePEzdSKkpGU3NBXPHHjvKQ==} - possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3833,9 +3747,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quickselect@2.0.0: - resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==} - radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} @@ -3846,9 +3757,6 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - rbush@3.0.1: - resolution: {integrity: sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==} - rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -3966,9 +3874,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - robust-predicates@3.0.2: - resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup-plugin-visualizer@6.0.5: resolution: {integrity: sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==} engines: {node: '>=18'} @@ -4141,9 +4046,6 @@ packages: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} - splaytree-ts@1.0.2: - resolution: {integrity: sha512-0kGecIZNIReCSiznK3uheYB8sbstLjCZLiwcQwbmLhgHJj2gz6OnSPkVzJQCMnmEz1BQ4gPK59ylhBoEWOhGNA==} - srvx@0.11.5: resolution: {integrity: sha512-MbQgu/gbLcXjg1bhUhPXXOpeMfmDMTGSKPWeht5acXnlQNldD925eS4+bIH/qESecSkP71dU3Fmvunlai1+yzw==} engines: {node: '>=20.16.0'} @@ -4228,9 +4130,6 @@ packages: engines: {node: '>=16'} hasBin: true - sweepline-intersections@1.5.0: - resolution: {integrity: sha512-AoVmx72QHpKtItPu72TzFL+kcYjd67BPLDoR0LarIk+xyaRg+pDTMFXndIEvZf9xEKnJv6JdhgRMnocoG0D3AQ==} - system-architecture@0.1.0: resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} engines: {node: '>=18'} @@ -4292,9 +4191,6 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinyqueue@2.0.3: - resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==} - to-buffer@1.2.2: resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} engines: {node: '>= 0.4'} @@ -5143,16 +5039,6 @@ snapshots: '@fastify/accept-negotiator@2.0.1': optional: true - '@geoman-io/leaflet-geoman-free@2.19.2(leaflet@1.9.4)': - dependencies: - '@turf/boolean-contains': 7.3.4 - '@turf/kinks': 7.3.4 - '@turf/line-intersect': 7.3.4 - '@turf/line-split': 7.3.4 - leaflet: 1.9.4 - lodash: 4.17.23 - polyclip-ts: 0.16.8 - '@iconify-json/lucide@1.2.91': dependencies: '@iconify/types': 2.0.0 @@ -6232,123 +6118,6 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@turf/bbox@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@turf/meta': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/boolean-contains@7.3.4': - dependencies: - '@turf/bbox': 7.3.4 - '@turf/boolean-point-in-polygon': 7.3.4 - '@turf/boolean-point-on-line': 7.3.4 - '@turf/helpers': 7.3.4 - '@turf/invariant': 7.3.4 - '@turf/line-split': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/boolean-point-in-polygon@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@turf/invariant': 7.3.4 - '@types/geojson': 7946.0.16 - point-in-polygon-hao: 1.2.4 - tslib: 2.8.1 - - '@turf/boolean-point-on-line@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@turf/invariant': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/distance@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@turf/invariant': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/geojson-rbush@7.3.4': - dependencies: - '@turf/bbox': 7.3.4 - '@turf/helpers': 7.3.4 - '@turf/meta': 7.3.4 - '@types/geojson': 7946.0.16 - rbush: 3.0.1 - tslib: 2.8.1 - - '@turf/helpers@7.3.4': - dependencies: - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/invariant@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/kinks@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/line-intersect@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@types/geojson': 7946.0.16 - sweepline-intersections: 1.5.0 - tslib: 2.8.1 - - '@turf/line-segment@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@turf/invariant': 7.3.4 - '@turf/meta': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/line-split@7.3.4': - dependencies: - '@turf/bbox': 7.3.4 - '@turf/geojson-rbush': 7.3.4 - '@turf/helpers': 7.3.4 - '@turf/invariant': 7.3.4 - '@turf/line-intersect': 7.3.4 - '@turf/line-segment': 7.3.4 - '@turf/meta': 7.3.4 - '@turf/nearest-point-on-line': 7.3.4 - '@turf/truncate': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/meta@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/nearest-point-on-line@7.3.4': - dependencies: - '@turf/distance': 7.3.4 - '@turf/helpers': 7.3.4 - '@turf/invariant': 7.3.4 - '@turf/meta': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - - '@turf/truncate@7.3.4': - dependencies: - '@turf/helpers': 7.3.4 - '@turf/meta': 7.3.4 - '@types/geojson': 7946.0.16 - tslib: 2.8.1 - '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -6370,18 +6139,12 @@ snapshots: '@types/estree@1.0.8': {} - '@types/geojson@7946.0.16': {} - '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 '@types/json-schema@7.0.15': {} - '@types/leaflet@1.9.21': - dependencies: - '@types/geojson': 7946.0.16 - '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -7030,8 +6793,6 @@ snapshots: bindings: 1.5.0 prebuild-install: 7.1.3 - bignumber.js@9.3.1: {} - bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -8154,12 +7915,6 @@ snapshots: dependencies: readable-stream: 2.3.8 - leaflet.markercluster@1.5.3(leaflet@1.9.4): - dependencies: - leaflet: 1.9.4 - - leaflet@1.9.4: {} - lilconfig@3.1.3: {} listhen@1.9.0: @@ -9131,15 +8886,6 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 - point-in-polygon-hao@1.2.4: - dependencies: - robust-predicates: 3.0.2 - - polyclip-ts@0.16.8: - dependencies: - bignumber.js: 9.3.1 - splaytree-ts: 1.0.2 - possible-typed-array-names@1.1.0: {} postcss-calc@10.1.1(postcss@8.5.6): @@ -9345,8 +9091,6 @@ snapshots: queue-microtask@1.2.3: {} - quickselect@2.0.0: {} - radix3@1.1.2: {} randombytes@2.1.0: @@ -9355,10 +9099,6 @@ snapshots: range-parser@1.2.1: {} - rbush@3.0.1: - dependencies: - quickselect: 2.0.0 - rc9@2.1.2: dependencies: defu: 6.1.4 @@ -9554,8 +9294,6 @@ snapshots: rfdc@1.4.1: {} - robust-predicates@3.0.2: {} - rollup-plugin-visualizer@6.0.5(rollup@4.57.1): dependencies: open: 8.4.2 @@ -9797,8 +9535,6 @@ snapshots: speakingurl@14.0.1: {} - splaytree-ts@1.0.2: {} - srvx@0.11.5: {} standard-as-callback@2.1.0: {} @@ -9887,10 +9623,6 @@ snapshots: picocolors: 1.1.1 sax: 1.4.4 - sweepline-intersections@1.5.0: - dependencies: - tinyqueue: 2.0.3 - system-architecture@0.1.0: {} tagged-tag@1.0.0: {} @@ -9960,8 +9692,6 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinyqueue@2.0.3: {} - to-buffer@1.2.2: dependencies: isarray: 2.0.5 @@ -9984,7 +9714,8 @@ snapshots: trough@2.2.0: {} - tslib@2.8.1: {} + tslib@2.8.1: + optional: true tunnel-agent@0.6.0: dependencies: diff --git a/site/site.yml b/site/site.yml index 4e1c978..8c592bd 100644 --- a/site/site.yml +++ b/site/site.yml @@ -17,7 +17,7 @@ footer: - label: Mentions légales to: /mentions-legales gratewizard: - url: /gratewizard?popup + url: https://gratewizard.ml popup: width: 420 height: 720 diff --git a/uno.config.ts b/uno.config.ts index 533d6fb..2af0d2d 100644 --- a/uno.config.ts +++ b/uno.config.ts @@ -74,7 +74,7 @@ export default defineConfig({ shortcuts: { 'btn-primary': 'inline-flex items-center justify-center px-6 py-3 rounded-lg bg-primary text-white font-display font-semibold tracking-wide transition-all duration-200 hover:bg-primary-600 hover:scale-105 active:scale-95', 'btn-accent': 'inline-flex items-center justify-center px-6 py-3 rounded-lg bg-accent text-surface-bg font-display font-semibold tracking-wide transition-all duration-200 hover:bg-accent-600 hover:scale-105 active:scale-95', - 'btn-ghost': 'inline-flex items-center justify-center px-4 py-2 rounded-lg text-white/70 font-sans transition-all duration-200 hover:bg-white/10 hover:text-white', + 'btn-ghost': 'inline-flex items-center justify-center px-4 py-2 rounded-lg border-none text-white/70 font-sans transition-all duration-200 hover:bg-white/10 hover:text-white', 'card-surface': 'rounded-xl bg-surface border border-white/8 p-6 transition-all duration-300 hover:border-primary/30 hover:shadow-lg hover:shadow-primary/5', 'text-gradient': 'bg-gradient-to-r from-primary-300 to-accent bg-clip-text text-transparent', 'text-muted': 'text-white/60',