diff --git a/.github/workflows/broadcast.yaml b/.github/workflows/broadcast.yaml index 1deec28..4d7e17a 100644 --- a/.github/workflows/broadcast.yaml +++ b/.github/workflows/broadcast.yaml @@ -1,47 +1,36 @@ name: Broadcast - on: workflow_dispatch: inputs: + event: + description: "Event directory slug, e.g. 45 or sfd-1404" + type: string + required: true environment: + description: "Destination" type: choice - description: 'choose the environment' + options: [test, production] + default: test required: true - default: 'test' - options: - - 'test' - - 'production' - +permissions: + contents: read jobs: broadcast: runs-on: ubuntu-latest environment: broadcast - steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - fetch-depth: 2 - - - name: prepare - run: | - pip install -r ./tools/requirements.txt - - if [ "${{ github.event.inputs.environment }}" == "test" ]; then - echo "setting up test environment" - echo "CHAT_ID=${{ vars.TEST_CHAT_ID }}" >> $GITHUB_ENV - echo "TEST=1" >> $GITHUB_ENV - else - echo "setting up production environment" - echo "CHAT_ID=${{ vars.CHAT_ID }}" >> $GITHUB_ENV - echo "TEST=0" >> $GITHUB_ENV - fi - - - name: notify - run: ./tools/notify.sh - env: - TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} - CHAT_ID: ${{ env.CHAT_ID }} - MASTODON_TOKEN: ${{ secrets.MASTODON_TOKEN }} - MASTODON_URL: ${{ vars.MASTODON_URL }} - TEST: ${{ env.TEST }} + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.13" + - run: python -m pip install -r tools/requirements.txt + - name: Notify + env: + EVENT: ${{ inputs.event }} + TEST: ${{ inputs.environment == 'test' && '1' || '0' }} + CHAT_ID: ${{ inputs.environment == 'test' && vars.TEST_CHAT_ID || vars.CHAT_ID }} + MEDIA_BASE_URL: ${{ vars.MEDIA_BASE_URL }} + TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} + MASTODON_TOKEN: ${{ secrets.MASTODON_TOKEN }} + MASTODON_URL: ${{ vars.MASTODON_URL }} + run: bash tools/notify.sh diff --git a/.github/workflows/hugo.yaml b/.github/workflows/hugo.yaml index e85449f..6a1deaf 100644 --- a/.github/workflows/hugo.yaml +++ b/.github/workflows/hugo.yaml @@ -1,70 +1,87 @@ -name: Deploy birlug.github.io +name: Deploy BirLUG on: push: - branches: - - master - paths-ignore: - - 'README.md' + branches: [master] + paths-ignore: [README.md, DESIGN.md, SYSTEM-DESIGN.md] + pull_request: + workflow_dispatch: + schedule: + - cron: "17 2 * * *" permissions: contents: read - pages: write - id-token: write concurrency: - group: "pages" - cancel-in-progress: false - -defaults: - run: - shell: bash + group: "pages-${{ github.ref }}" + cancel-in-progress: true jobs: build: runs-on: ubuntu-latest + permissions: + contents: read + pages: read + defaults: + run: + working-directory: site env: - HUGO_VERSION: 0.124.0 + HUGO_VERSION: 0.167.0 + HUGO_PARAMS_MEDIABASEURL: ${{ vars.MEDIA_BASE_URL }} steps: - - name: Install Hugo CLI - run: | - wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ - && sudo dpkg -i ${{ runner.temp }}/hugo.deb - - name: Install Dart Sass - run: sudo snap install dart-sass - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: - submodules: recursive - fetch-depth: 0 - - name: Setup Pages + path: site + - name: Read Pages URL id: pages - uses: actions/configure-pages@v4 - - name: Install Node.js dependencies - run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" - - name: Build with Hugo + uses: actions/configure-pages@v6 + - name: Checkout media + uses: actions/checkout@v7 + with: + repository: birlug/media + ref: main + path: birlug-media + token: ${{ secrets.MEDIA_READ_TOKEN || github.token }} + - uses: actions/setup-python@v7 + with: + python-version: "3.13" + - uses: actions/setup-node@v7 + with: + node-version: "24" + - name: Install tools + run: | + python -m pip install -r tools/requirements.txt + npm ci --ignore-scripts + npm run icons + archive="hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + curl --fail --location --retry 3 "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/${archive}" -o "/tmp/${archive}" + curl --fail --location --retry 3 "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_checksums.txt" -o /tmp/hugo-checksums.txt + (cd /tmp && grep " ${archive}$" hugo-checksums.txt | sha256sum --check -) + tar -xzf "/tmp/${archive}" -C /tmp hugo + - name: Validate and build env: - HUGO_ENVIRONMENT: production - HUGO_ENV: production + PAGES_BASE_URL: ${{ steps.pages.outputs.base_url }} run: | - hugo \ - --gc \ - --minify \ - --baseURL "${{ steps.pages.outputs.base_url }}/" - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + test -d ../birlug-media/public/media + cp ../birlug-media/public/catalog.json data/media.json + python tools/content.py + /tmp/hugo --baseURL "${PAGES_BASE_URL%/}/" --gc --minify --panicOnWarning --cleanDestinationDir + python tools/check_site.py public --base-url "${PAGES_BASE_URL%/}/" + - uses: actions/upload-pages-artifact@v5 + if: github.event_name != 'pull_request' with: - path: ./public + path: site/public deploy: + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + needs: build + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build steps: - - name: Deploy to GitHub Pages + - uses: actions/deploy-pages@v5 id: deployment - uses: actions/deploy-pages@v4 - - diff --git a/.gitignore b/.gitignore index 8d9ac89..6d241a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,14 @@ /public .hugo_build.lock /resources +/.venv +/__pycache__ +/tools/__pycache__ +/data/events.json +/static/calendar.ics +/static/events/*/calendar.ics +/assets/events/ +/static/media/ +/node_modules/ +/assets/icons/ui.svg +/LUG Website.html diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..a3ec948 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,292 @@ +# LUG Website — Design System + +> A dark, text-first reading room for a Linux users group: charcoal canvas, grey type, and one phosphor-green signal. + +**Theme:** dark only · **Direction:** RTL (Persian, `lang="fa"`, `dir="rtl"`) · **Style lineage:** Zen-style vertical sidebar + a Supabase-inspired monochrome palette, applied as a classic, unboxed layout. + +--- + +## 1. Principles + +1. **Text first, no boxes.** Content sits directly on the canvas. Hierarchy comes from size, weight, color and whitespace — not from cards, panels or bordered containers. +2. **One color.** The page is ~99% grayscale. Green is rationed: the logo, the active nav item, small mono labels, and primary actions. One green focal point per region. +3. **Terminal-native details.** Mono path crumbs (`~/lug/sessions`), `#24` session numbers, `$` prompts in code — a quiet nod to the shell, never a costume. +4. **Calm weight.** Only 400 and 500. Emphasis comes from size or green, never bold. +5. **Readable Persian.** Generous line-height (≈2.0 for body), no letter-spacing on Persian script, mono reserved for Latin/code fragments. + +--- + +## 2. Color tokens + +| Token | Value | Role | +|---|---|---| +| `--color-canvas` | `#121212` | Page background. The only background. | +| `--color-surface` | `#242424` | Image/avatar/logo placeholders, subtle fills. Not for containers. | +| `--color-text` | `#FAFAFA` | Primary text, titles, links in content. | +| `--color-text-2` | `#B4B4B4` | Secondary text, body copy, inactive nav. | +| `--color-text-3` | `#898989` | Metadata, captions, dates, labels. | +| `--color-line` | `#2E2E2E` | Pill-filter outlines, hairlines (use sparingly). | +| `--color-line-strong` | `#393939` | Input borders, ghost-button borders. | +| `--color-accent` | `#3ECF8E` | Phosphor green — primary actions, active states, logo, mono labels. | +| `--color-accent-alt` | `#00C573` | Optional cooler green for inline links. | +| `--color-on-accent` | `#121212` | Text on green fills. | + +**Contrast (on `#121212`):** `#FAFAFA` ≈ 18:1 · `#B4B4B4` ≈ 9.5:1 · `#898989` ≈ 5.6:1 · `#3ECF8E` ≈ 10:1. All pass WCAG AA for body text. + +**Rule:** text on a green fill is always `#121212` — white on `#3ECF8E` is ≈ 1.9:1 and fails. + +**Never:** a second hue, gradients, shadows, or green as a large background. + +--- + +## 3. Typography + +| Family | Use | Weights | Source | +|---|---|---|---| +| **Vazirmatn** | All UI and content (Persian + Latin) | 400, 500 | Self-hosted SIL OFL WOFF2 | +| **Source Code Pro** derived **BirLUG Mono** | Paths, session numbers, times, code | 400, 500 | Self-hosted SIL OFL Latin subset; renamed to respect Adobe reserved font name | + +```html + + +``` + +### Type scale + +| Role | Size | Weight | Line height | Color | Example | +|---|---|---|---|---|---| +| Page title (h1) | 36px | 500 | 1.4 | text | جلسات | +| Feature title | 30px | 500 | 1.5 | text | Next-session title, active challenge | +| Section (h2) | 22px | 500 | 1.5 | text | آرشیو جلسات | +| Post title | 22px | 500 | 1.7 | text | Blog list item | +| Item title | 17px | 400–500 | 1.8 | text | Session row | +| Body | 16px | 400 | 2.1 | text-2 | Paragraphs | +| Body small / nav | 15px | 400 | 2.0 | text-2 | Sidebar links, lists | +| UI / buttons | 14px | 500 | 1.5 | — | Pills, filters | +| Meta / caption | 13px | 400 | 1.8 | text-3 | Dates, presenter | +| Mono label | 13px | 400 | 1.5 | accent or text-3 | `#24`, `~/lug/blog` | + +**Persian rules** +- No `letter-spacing` on Persian text: it breaks letter joining. +- Use the zero-width non-joiner (ZWNJ, U+200C) correctly: `جلسه‌ی`, `ارائه‌دهندگان`. +- Mark Latin/code islands with `dir="ltr"` (handles, paths, inputs for flags or emails). +- Persian digits in prose (`۲۴`) are fine. Use Latin digits inside mono/code (`17:00`, `#24`). + +--- + +## 4. Spacing, radius, layout + +**Base unit:** 8px. Scale: 4 · 8 · 12 · 16 · 20 · 24 · 32 · 48 · 56 · 64 · 72. + +| Context | Value | +|---|---| +| Page padding | 48px top/bottom, 40px sides (16px on phones) | +| Sidebar ↔ content gap | 72px (48px when stacked) | +| Between sections | 56–64px | +| Heading → content | 12–20px | +| List rows | 10–12px vertical padding, no dividers | + +**Radius vocabulary (only these):** +- `9999px` for buttons, filter pills and status dots +- `8px` for inputs and the image popup (`--radius-input`, `--radius-dialog`) +- `6px` for prepared photo images, gallery tiles and album previews (`--radius-media`) +- `50%` for avatars +- `0` remains valid for code and original puzzle imagery; never round/crop away clues + +**Layout** + +``` +┌───────────────────────────────────────────────┐ +│ [content column, max 760px] │ [sidebar 200–260px] │ ← RTL: sidebar on the right +└───────────────────────────────────────────────┘ +``` + +- Root: `display:flex; flex-wrap:wrap; gap:48px 72px`. +- Sidebar: `flex: 1 1 200px; max-width: 260px`. It is not sticky and has no background or border. +- Content: `flex: 999 1 520px; min-width: 0; max-width: 760px` (860px when a gallery is present). +- **Stacked layout (<880px):** the sidebar stacks above the content and everything stays single-column. + +--- + +## 5. Components + +### 5.1 Sidebar +- **Wordmark:** `BirLUG` with Iconify `lucide:terminal`, self-hosted mono 20px/500, colored green. The Birjand subtitle sits below it at 13px in text-3. +- **Nav items:** 15px in text-2, `padding: 10px 16px 10px 0`, with hover changing the color to text. +- **Active item:** a 6px green dot plus 10px gap, text color, weight 500. The dot is the only indicator — no background. +- **Order:** جلسات · گالری · چالش‌ها · بلاگ · موضوعات · ارائه‌دهندگان · حمایت مالی +- **Footer:** a "جامعه" label above the Telegram · Matrix · GitHub · RSS links, then کد رفتاری · درباره‌ی ما. All at 13px in text-2. + +### 5.2 Primary button (pill) +```css +background: var(--color-accent); color: #121212; +border-radius: 9999px; padding: 12px 22px; +font: 500 14px Vazirmatn; text-decoration: none; +``` +Use one per region, for Register, Submit answer, Sponsor, or Propose a talk. Labels end with `←` (forward in RTL). + +### 5.3 Ghost button (pill) +```css +background: transparent; color: #FAFAFA; +border: 1px solid #393939; border-radius: 9999px; padding: 11px 22px; +font: 400 14px Vazirmatn; +``` +On hover the border becomes `#4D4D4D` and the background `rgba(255,255,255,.04)`. + +### 5.4 Filter pills (topic / category / range) +- **Idle:** transparent background, a 1px `#2E2E2E` border, text-2, `8px 16px` padding, min-height 40px. +- **Selected:** a green fill with a green border, `#121212` text, weight 500. +- **Group:** `display:flex; flex-wrap:wrap; gap:8px` with `role="group"` and an `aria-label`. + +### 5.5 Text input +```css +background: #121212; border: 1px solid #393939; border-radius: 8px; +height: 44–46px; padding: 0 12px; color: #FAFAFA; +``` +- Focus: the border turns green, with no glow. +- Every input has a `