Skip to content

toozuuu/ecoux-agent

Repository files navigation

EcoUX Agent (Angular + SCSS)

Adaptive “green UX” for the web. This Angular starter auto-tunes your UI for eco / balanced / pro modes based on real runtime signals (network quality, motion preference, device capability, brief CPU load). Ships with optional gadgets like mode-aware preloading, Speculation Rules toggling, and a smart image component.


✨ Features

  • Context sensing: prefers-reduced-motion, navigator.connection, deviceMemory, hardwareConcurrency, light “busy” hints.
  • Policy engine: maps signals → eco | balanced | pro, sets body[data-ux-mode].
  • Directive: modeClass adds BEM-style classes (card card--eco|balanced|pro) to any element.
  • Tokens: SCSS variables per mode (animation, elevation, image density).
  • (Optional) Routing preloading: preload aggressively in pro, opt-in in balanced, never in eco.
  • (Optional) Speculation Rules: inject <script type="speculationrules">…</script> in pro only.
  • (Optional) Smart images: \<smart-img> picks src/srcset, loading strategy, and priority per mode.
  • SSR-ready with browser-safety guards.

🧱 Tech Stack

  • Angular 17+ (standalone APIs, Signals)
  • SCSS
  • Vite dev server (default in Angular 17+)
  • Optional SSR (Angular server rendering)

📦 Getting Started

# 1) Clone
git clone https://github.com/<you>/ecoux-agent.git
cd ecoux-agent

# 2) Install deps
npm i

# 3) Run (SPA mode)
npm run start
# opens http://localhost:4200

Run with SSR (optional)

# Dev SSR + hydrate on client
npm run dev:ssr

If you prefer the classic zone-based mode, leave it as-is. If you want zoneless, see Zoned vs Zoneless below.


🗂️ Project Structure

src/
├─ index.html
├─ main.ts                     # client entry (SPA or CSR hydration)
├─ main.server.ts              # server entry (SSR only)
├─ styles.scss                 # global, imports styles/_modes.scss
├─ styles/_modes.scss          # CSS variables for eco/balanced/pro
└─ app/
   ├─ app.config.ts            # routes + (optional) preloading strategy
   ├─ app.component.*          # demo + mode badge
   ├─ core/
   │  ├─ runtime-context.service.ts   # reads env signals (browser-guarded)
   │  ├─ ux-agent.service.ts          # policy -> mode + sets body[data-ux-mode]
   │  ├─ mode-preloading.strategy.ts  # (optional) route preloading per mode
   │  └─ speculation.service.ts       # (optional) Speculation Rules toggler
   ├─ shared/
   │  ├─ mode-class.directive.ts      # adds base/base--mode classes
   │  └─ smart-img.component.ts       # (optional) mode-aware images
   └─ features/
      └─ demo/
         └─ demo-card/
            ├─ demo-card.component.ts
            ├─ demo-card.component.html
            └─ demo-card.component.scss

🧠 How It Works

  1. Sense (client only): RuntimeContextService samples motion preference, connection, memory/cores, and brief long tasks.
  2. Decide: UxAgentService maps signals → eco | balanced | pro and writes body[data-ux-mode].
  3. Style: your SCSS reads the mode via CSS vars or modeClass (card--eco|balanced|pro).
  4. Enhance (optional): router preloading, Speculation Rules, and smart image priorities adapt to mode.

🧪 Quick Demo

In any template:

<section modeClass [base]="'card'">
  <h3>EcoUX Demo</h3>
  <p>Mode: <strong>{{ ux.mode() }}</strong> ({{ ux.reason() }})</p>

  <!-- Optional smart image -->
  <smart-img
    [alt]="'Hero'"
    [sources]="{
      '1':  '/assets/hero@1x.jpg',
      '1.5':'/assets/hero@1_5x.jpg',
      '2':  '/assets/hero@2x.jpg'
    }">
  </smart-img>
</section>

SCSS:

.card { border-radius: 16px; padding: 16px; background: #fff; transition: box-shadow var(--anim-ms) ease; }
.card--eco { box-shadow: 0 4px 8px rgba(0,0,0,.06); }
.card--balanced { box-shadow: 0 12px 20px rgba(0,0,0,.08); }
.card--pro { box-shadow: 0 24px 36px rgba(0,0,0,.10); }

Global tokens (styles/_modes.scss):

:root { --anim-ms: 180ms; --elev: 16px; --img-density: 1; }

body[data-ux-mode="eco"] {
  --anim-ms: 0ms; --elev: 4px; --img-density: .75;
  scroll-behavior: auto;
  * { animation: none !important; transition-duration: 0ms !important; }
}

body[data-ux-mode="pro"] { --anim-ms: 140ms; --elev: 24px; --img-density: 1.5; }

🛣️ Optional Enhancements

  • Mode-aware route preloading: see core/mode-preloading.strategy.ts and enable in app.config.ts with withPreloading(ModePreloadingStrategy). Use data: { preload: true|false } per route.
  • Speculation Rules: injects/removes a <script type="speculationrules"> tag in pro. Activate by injecting SpeculationService once (e.g., in AppComponent).
  • Smart images: use \<smart-img> for mode-aware srcset, loading, and fetchpriority.

🧩 Zoned vs. Zoneless

This starter supports both. Choose one and be consistent.

✅ Zone-based (default; most compatible)

  • Client (main.ts) must import:

    import 'zone.js'; // first
  • Server (main.server.ts) must import:

    import 'zone.js/node'; // first
  • Do not provide zoneless change detection.

⚡ Zoneless (Angular 17+/18+)

  • Do not import Zone.js anywhere.

  • Client:

    import { provideZonelessChangeDetection } from '@angular/core'; // or provideExperimental... on v17
  • Server:

    import { provideServerRendering } from '@angular/platform-server';
  • Third-party libs that rely on zones may need adjustments.

If you see Error: NG0908: Angular requires Zone.js, you’re mixing modes. Use one approach only.


🧯 Troubleshooting (copy/paste fixes)

  • ReferenceError: matchMedia is not defined (SSR/tests) Guard browser APIs with isPlatformBrowser(inject(PLATFORM_ID)) and run setup in afterNextRender(...).

  • Attribute modeClass is not allowed here Ensure modeClass directive is standalone: true and imported in the component’s imports: [].

  • TS18046: c is of type unknown Type tokens:

    const tokens: string[] = Array.from(el.classList);
  • TS2307: Cannot find module './features/...demo-card.component' File missing or wrong path/export. Confirm the file exists and exports DemoCardComponent. In lazy routes, don’t include .ts extension.

  • Unresolved variable or type ux Inject the service in the component class that owns the template:

    ux = inject(UxAgentService);
  • NG0908: Angular requires Zone.js Import 'zone.js' (client) and 'zone.js/node' (server) or switch to pure zoneless (and remove both imports).

  • Vite cache weirdness after file adds Delete node_modules/.vite and restart.


🧪 Scripts

{
  "scripts": {
    "start": "ng serve",
    "build": "ng build",
    "dev:ssr": "ng run ecoux-agent:serve-ssr",
    "build:ssr": "ng run ecoux-agent:build-ssr",
    "prerender": "ng run ecoux-agent:prerender"
  }
}

Names may differ if your app name in angular.json isn’t ecoux-agent.


🔐 Notes

  • Never ship API keys in the browser. If you later add an AI policy, call it via a tiny backend proxy.
  • All browser-only logic is feature-detected and wrapped to be SSR-safe.

🤝 Contributing

Issues and PRs welcome! Please:

  • Keep browser-only code guarded (isPlatformBrowser).
  • Add unit tests for policy changes where possible.
  • Update README if you change public APIs (modeClass, smart-img).

📄 License

MIT


📬 Support

If you get stuck, open an issue with:

  • Angular version (ng version)
  • Your main.ts, main.server.ts
  • The exact error + stack trace

Happy shipping! 🚀

About

Adaptive “green UX” for Angular: auto-switches eco / balanced / pro modes from real runtime signals (network, device, motion). Ships with SCSS tokens, modeClass directive, SSR-safe sensing, mode-aware preloading, Speculation Rules, and smart images.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors