Files
GMW/design/core/03-spatial-system.md
T
asepharyana d59b59a7a7 feat: migrate frontend to Astro + expand AI moderation + backend admin/runtime config
Frontend:
- migrate from Vite to Astro (astro.config.mjs, pages/, layouts/)
- add admin panel, settings page, command palette, error boundary
- refactor App.tsx, MascotChatbot, Sidebar, Header, DashboardLayout
- update API client, WebSocket, auth, dashboard features

Backend:
- add admin module and config routes
- refactor middlewares, Redis connection, WebSocket server/bridge
- add runtime config loader

Discord Gateway:
- refactor AI moderation: circuit breaker, concurrency limiter, fallback processor
- add media analysis client, Seaxng search, user profile learner
- add new drizzle migration

Shared:
- extend database schema, add new config fields
2026-07-02 00:02:41 +07:00

9.8 KiB
Raw Blame History

Spatial System — The Architecture of Void

"Space is the breath of art." — Frank Lloyd Wright, arsitek organik.


🎯 Filosofi Spasial

Ruang dalam BETE bukan sekadar "tempat kosong" — ia adalah medium komunikasi visual. Jarak antarelemen menyampaikan hubungan semantik:

  • Dekat → Elemen terkait secara konseptual
  • Berjarak → Elemen independen atau batch berbeda
  • Terpisah jauh → Section baru, hierarki turun

Kita menggunakan 4px baseline grid untuk semua keputusan spasial.


📐 Grid System: 8px × 4px Hybrid

BETE menggunakan sistem 8px untuk layout kasar, 4px untuk fine-tuning.

:root {
  /* Base grid unit */
  --grid-unit: 4px;

  /* Spacing scale — exponential */
  --sp-0:   0px;
  --sp-0.5: calc(var(--grid-unit) * 1);   /* 4px  — micro spacing */
  --sp-1:   calc(var(--grid-unit) * 2);   /* 8px  — tight spacing */
  --sp-2:   calc(var(--grid-unit) * 3);   /* 12px — compact spacing */
  --sp-3:   calc(var(--grid-unit) * 4);   /* 16px — base spacing  ★ */
  --sp-4:   calc(var(--grid-unit) * 6);   /* 24px — relaxed spacing */
  --sp-5:   calc(var(--grid-unit) * 8);   /* 32px — section spacing */
  --sp-6:   calc(var(--grid-unit) * 12);  /* 48px — panel spacing */
  --sp-7:   calc(var(--grid-unit) * 16);  /* 64px — page spacing */
  --sp-8:   calc(var(--grid-unit) * 24);  /* 96px — hero spacing */
}

Logic di Balik Scale

Token px Konteks
--sp-0.5 4px Ikon-padding, badge spacing, dot indicators
--sp-1 8px Avatar-text gap, icon-button padding
--sp-2 12px Button padding, chip spacing, input padding
--sp-3 16px Base unit — card-padding, section margin, form gap
--sp-4 24px Card gap, panel padding, modal padding
--sp-5 32px Content area padding, desktop sidebar width
--sp-6 48px Page section gap, dashboards grid gap
--sp-7 64px Page padding desktop, hero spacing
--sp-8 96px Empty state height, large break sections

🔲 Layout Components

Page Layout (Desktop)

┌─────────────────────────────────────────────────────┐
│                      ┌── Header ──┐                  │ 56px
│                      └────────────┘                  │
│  ┌─ Sidebar ─┐  ┌────────── Main Content ──────────┐│
│  │           │  │  ┌─ Page Title ─────────────────┐ ││
│  │  icon grid │  │  │ Section Heading             │ ││
│  │  ────────  │  │  └─────────────────────────────┘ ││
│  │  nav-1     │  │                                   ││
│  │  nav-2     │  │  ┌────── Grid Area ─────────────┐ ││
│  │  nav-3     │  │  │  ┌── Card ──┐ ┌── Card ──┐  │ ││
│  │            │  │  │  │          │ │          │  │ ││
│  │  ────────  │  │  │  └──────────┘ └──────────┘  │ ││
│  │  mascot    │  │  │  ┌── Card ──┐ ┌── Card ──┐  │ ││
│  │            │  │  │  │          │ │          │  │ ││
│  └────────────┘  │  │  └──────────┘ └──────────┘  │ ││
│                  │  └───────────────────────────────┘ ││
│   w-64/          │      flex-1                        ││
│   w-16(icon)     │                                    ││
└─────────────────────────────────────────────────────┘

Vue 3 Component Mapping

// DashboardLayout.vue
<template>
  <div class="page-layout">
    <Sidebar :collapsed="sidebarCollapsed" />
    <main class="main-area">
      <Header />
      <div class="content-area">
        <slot />
      </div>
    </main>
  </div>
</template>
.page-layout {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto 1fr;
  min-height: 100vh;
}

.sidebar {
  grid-row: 1 / -1;
  width: 256px; /* w-64 */
  transition: width var(--dur-normal) var(--ease-out-quint);
}
.sidebar.collapsed {
  width: 64px; /* w-16 */
}

.main-area {
  display: flex;
  flex-direction: column;
  min-width: 0; /* Prevent grid blowout */
  overflow: hidden;
}

.content-area {
  flex: 1;
  overflow-y: auto;
  padding: var(--sp-5);
}

🔳 Border Radius Scale

--rd-none:  0px;
--rd-xs:    4px;    /* Checkbox, toggle, small indicators */
--rd-sm:    6px;    /* Input, button small, badges */
--rd-md:    8px;    /* Button default, card, modal */
--rd-lg:    12px;   /* Card elevated, sheets, panels */
--rd-xl:    16px;   /* Dialog, bottom sheet */
--rd-2xl:   20px;   /* Full-width cards on mobile */
--rd-full:  9999px; /* Pill, chip, avatar */

Radius Decision Tree

Elemen interaktif?
├── Ya ─→ butuh affordance visual?
│   ├── Ya, utama (button, card clickable) → --rd-md (8px)
│   └── Tidak (chip, tag, avatar) → --rd-full (pill)
│
└── Tidak → container?
    ├── Dialog/modal → --rd-xl (16px)
    ├── Card dalam grid → --rd-lg (12px)
    └── Sheet/panel → --rd-xl atau --rd-2xl

🥞 Z-Index Registry (Formal)

:root {
  --z-base:      0;
  --z-dropdown:  10;
  --z-sticky:    20;
  --z-header:    30;
  --z-sidebar:   40;
  --z-overlay:   50;    /* Mobile sidebar, backdrop */
  --z-modal:     60;    /* Dialog, confirm */
  --z-popover:   70;    /* Tooltip, popover, dropdown menu */
  --z-toast:     80;    /* Toast notification */
  --z-mascot:    100;   /* Mascot chatbot — highest */
}

/* Implementation */
.header      { z-index: var(--z-header); }
.sidebar     { z-index: var(--z-sidebar); }
.modal       { z-index: var(--z-modal); }
.toast       { z-index: var(--z-toast); }
.mascot-chat { z-index: var(--z-mascot); }

Stacking Order

Layer        Value     Elemen
─────────────────────────────────────────────
Background   -1        ParticleBackground
Base          0        Layout, cards, text
Dropdown     10        Select options, context menu
Sticky       20        Sticky section headers
Header       30        Sticky page header
Sidebar      40        Desktop sidebar
Overlay      50        Backdrop, mobile drawer
Modal        60        Confirm dialog, modal
Popover      70        Tooltip, dropdown
Toast        80        Toast notifications
Mascot      100        Chatbot floating panel

📱 Responsive Breakpoints

/* CSS Custom Properties for breakpoints */
:root {
  --bp-sm:  640px;
  --bp-md:  768px;
  --bp-lg:  1024px;
  --bp-xl:  1280px;
  --bp-2xl: 1536px;
}

/* Container max-width */
--container-sm:   640px;
--container-md:   768px;
--container-lg:   1024px;
--container-xl:   1280px;

Layout Behavior per Breakpoint

Breakpoint Sidebar Content Padding Grid Columns
<640px Bottom tab (56px) --sp-3 (16px) 1
640768px Bottom tab --sp-4 (24px) 1
7681024px Icon-only (64px) --sp-4 (24px) 12
10241280px Full (256px) --sp-5 (32px) 23
1280px+ Full (256px) --sp-5 (32px) 24

📦 Common Layout Patterns

Card Grid

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--sp-4);
}

Two-Column Detail

.two-column {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4);
}

@media (max-width: 768px) {
  .two-column {
    grid-template-columns: 1fr;
  }
}

Sidebar + Content (Live Panel)

.live-layout {
  display: grid;
  grid-template-columns: 1fr 320px;  /* Content 1fr, sidebar fixed */
  gap: var(--sp-4);
}

@media (max-width: 1024px) {
  .live-layout {
    grid-template-columns: 1fr;  /* Stack on smaller screens */
  }
}

.live-sidebar {
  position: sticky;
  top: calc(56px + var(--sp-4)); /* Below header */
  max-height: calc(100vh - 56px - var(--sp-4) * 2);
  overflow-y: auto;
}

🧠 Container Queries (Modern Approach)

Untuk komponen yang reusable di berbagai konteks:

.card-grid-component {
  container-type: inline-size;
  container-name: card-grid;
}

@container card-grid (max-width: 400px) {
  .card-item {
    grid-template-columns: 1fr;  /* Single column di container kecil */
  }
}

@container card-grid (min-width: 401px) {
  .card-item {
    grid-template-columns: 1fr 1fr;
  }
}

⚠️ Anti-Patterns Spasial

Margin collapse tanpa sengaja

/* ❌ JANGAN — flex gap jauh lebih aman */
.card + .card { margin-top: 16px; }  /* Rawan collapse */

/* ✅ Gunakan gap */
.card-grid { display: flex; flex-direction: column; gap: var(--sp-4); }

Padding tidak konsisten

/* ❌ JANGAN — setiap file punya padding sendiri */
.page-a { padding: 20px; }
.page-b { padding: 24px; }

/* ✅ Gunakan spacing token */
.page { padding: var(--sp-5); }

Grid blowout (min-width tanpa min-width: 0)

/* ❌ JANGAN — grid item dengan teks panjang mendorong layout */
.grid-item { overflow: visible; }

/* ✅ Cegah blowout */
.grid-item { min-width: 0; overflow: hidden; }

"Ruang adalah kanvas tempat ingatan menari — setiap piksel memiliki tempatnya." ❄️🩵