mnswpr/eslint.config.js
Ayo f019afd7c8 feat: reusable, time-windowed leaderboard (@cozy-games/leaderboard)
Extract the leaderboard into a generic, backend-agnostic package and add
rolling Today/Week/Month/All-Time windows, a web component, and local-first
development against the Firestore emulator.

Package (leaderboard/):
- LeaderBoardService: backend-agnostic core via a storage-adapter seam, with
  Firebase and Supabase adapters (Supabase client injected, no added dep)
- Rolling time windows (last 24h / 7d / 30d) with hover tooltips; top-N by score
- <cozy-leaderboard> web component built on web-component-base: compose the UI
  in HTML, configure the backend once in JS
- Every user-facing string is configurable (labels, tooltips, empty/loading/
  error messages, anonymous name) so i18n lives in the app; README + CONFIGURATION

App (mnswpr.com):
- Compose the board declaratively in index.html via <cozy-leaderboard>
- Nickname + randomized greeting bar; score submission through the element
- Legends: the current all-time leaders frozen into a static /legends page
- Firebase config and leaderboard namespace via Vite env vars; emulator-first
  local dev (VITE_FIRESTORE_EMULATOR)

Firebase schema-as-code:
- firebase.json, .firebaserc, firestore.rules (public reads, create-only scores,
  no client updates/deletes, namespace-generalized), empty indexes (rolling
  windows need none)
- prod (mw-*) vs dev/test (mw-test-*) separation by collection namespace
- emulator config, seed script, and docs (firebase-leaderboards.md,
  leaderboard-env-migration.md, AYO.md)

Utils/tests: UTC date-bucket helper (retained as metadata) with Vitest coverage.
2026-07-03 13:39:44 +02:00

59 lines
1.5 KiB
JavaScript

// @ts-check
import js from '@eslint/js'
import css from '@eslint/css'
import globals from 'globals'
import { defineConfig, globalIgnores } from 'eslint/config'
import stylistic from '@stylistic/eslint-plugin'
export default defineConfig([
{
files: ['**/*.css'],
plugins: {
css
},
languageOptions: {
tolerant: true
},
language: 'css/css',
rules: {
'css/no-duplicate-imports': 'error',
'css/no-empty-blocks': 'error',
'css/no-invalid-at-rules': 'error',
'css/no-invalid-properties': 'error'
}
},
{
files: ['**/*.{js,mjs,cjs}'],
plugins: {
js, '@stylistic': stylistic
},
extends: ['js/recommended'],
languageOptions: {
globals: globals.browser
},
rules: {
'@stylistic/indent': ['error', 2],
'@stylistic/quotes': ['error', 'single'],
'@stylistic/semi': ['error', 'never'],
'@stylistic/comma-dangle': ['error', 'never'] ,
'@stylistic/block-spacing': 'error',
'@stylistic/array-bracket-spacing': ['error', 'never'],
'@stylistic/object-curly-spacing': ['error', 'always'],
'@stylistic/key-spacing': ['error', {
'beforeColon': false
}],
'@stylistic/array-bracket-newline': ['error', 'consistent'],
'@stylistic/object-curly-newline': ['error', {
'consistent': true
}]
}
},
{
files: ['scripts/**/*.js'],
languageOptions: {
globals: globals.node
}
},
globalIgnores(['**/dist'])
])