Oracle3
Open-source trading engine and MCP server for prediction markets. Fee-aware no-arbitrage trading across related contracts, live on Kalshi, Polymarket and Solana or on paper.
Harga emas hari ini live per gram dalam Rupiah, USD dan 14 mata uang lain. Antam 1 gram Rp2.565.000 (8 Okt 2026), grafik sejak 2013, kalkulator zakat & investasi emas.
Translation pending
FinanceTypeScript · JavaScriptGitHub @taufiksolehOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Live gold price intelligence for Indonesian investors.
EmasKuy (emas is Indonesian for gold) is a real-time gold analysis terminal. It shows live gold prices per gram in rupiah or in any of 15 other currencies, and Antam, Galeri24 and UBS retail prices. It has daily history charts back to 2013, market analysis, gold calculators (investment, zakat, jewelry and savings target), a portfolio tracker and price alerts. It runs entirely in the browser, with no backend and no API keys, in Indonesian and English, and it can be installed as an app.Read the full README ↗Live gold price intelligence for Indonesian investors.
EmasKuy (emas is Indonesian for gold) is a real-time gold analysis terminal. It shows live gold prices per gram in rupiah or in any of 15 other currencies, and Antam, Galeri24 and UBS retail prices. It has daily history charts back to 2013, market analysis, gold calculators (investment, zakat, jewelry and savings target), a portfolio tracker and price alerts. It runs entirely in the browser, with no backend and no API keys, in Indonesian and English, and it can be installed as an app.
Live site: https://emaskuy.com (English: https://emaskuy.com/en)
/, /en)Ticker strip with live spot prices:
Hero price panel in the display currency and weight, with the 24h change, the range seen today and a 30-day sparkline. It shares to WhatsApp as a text message or an image card: square for chats, 4:5 for the Instagram feed, 9:16 for status and stories, or 9:16 for TikTok with the content kept clear of TikTok's buttons and caption. The Instagram feed and TikTok formats come with a plain caption and hashtags.
Currency and weight picker in the navbar, applied site-wide.
Interactive price chart (lightweight-charts): line, area or candles over 1H, 24H, 7D, 30D, 90D, 1Y and ALL (daily history back to 2013).
AI Insight: a daily market sentiment with three or four bullets and their sources. It shares as a WhatsApp message or an image card in the same four formats.
Price alerts above or below a target, in any currency and weight (up to 10).
Antam prices:
Antam is the state-owned miner PT Aneka Tambang, whose gold bars are Indonesia's most common retail reference price.
Market stats: 52-week high/low, 30-day volatility, a metals table and a quick converter.
/logam/perak, /en/metals/silver, …)Silver, platinum and palladium, each with:
/analisis, /en/analysis)/kalkulator, /en/calculator)/zakat): nisab 85 g of pure gold and 2.5 % after a lunar year. It covers stored or worn jewelry and shows progress toward the nisab./perhiasan, /jewelry): the gold value of jewelry by karat or purity, with an estimated resale price./target): how much to save each month to own a set amount of gold (dowry, umrah, education, …)./portofolio, /en/portfolio)/en; the language toggle moves to the same page in the other language.LIVE, CACHE and OFFLINE badges.| Area | Choice |
|---|---|
| Framework | React 19, TypeScript ~5.9 (strict), Vite 7 |
| Routing | react-router 7 (<BrowserRouter>); routes for both languages come from src/lib/routes.ts |
| Styling | Tailwind CSS 3.4 with CSS-variable design tokens (src/index.css) |
| Charts | lightweight-charts for the price chart; hand-drawn SVG elsewhere |
| Motion | framer-motion; GSAP + ScrollTrigger and Lenis on the Article and About pages |
| UI | The app's own primitives in src/components/ui-atoms/, Radix Dialog/Accordion/Slider in src/components/ui/, lucide-react icons, sonner toasts |
| Fonts | Space Grotesk, JetBrains Mono and Inter, self-hosted with @fontsource-variable |
| PWA | vite-plugin-pwa (Workbox) |
| SEO | Build-time prerender of every page in both languages (vite/seo-prerender.ts, src/seo/prerender.ts), sitemap and RSS |
| Tests | Vitest |
| Hosting | GitHub Pages via GitHub Actions, on emaskuy.com |
Prerequisites: Node.js 22 (as in CI; Vite 7 needs 20.19+ or 22.12+) and npm.
git clone https://github.com/taufiksoleh/emaskuy.git
cd emaskuy
npm ci
npm run dev # http://localhost:3000
No API key is needed: every data source is a public API called from the browser. Two optional features read environment variables (see .env.example; in production they come from repository variables):
VITE_NEWSLETTER_ACTION, VITE_NEWSLETTER_EMAIL_FIELD and VITE_NEWSLETTER_EXTRA post the form to Buttondown, Kit or MailerLite. Without them the form is hidden.VITE_CF_BEACON_TOKEN adds cookieless Cloudflare Web Analytics.| Script | What it does |
|---|---|
npm run dev | Dev server on port 3000 (makes the WebP image copies first) |
npm run build | Type-checks (tsc -b), bundles, and prerenders every page into dist/ |
npm run preview | Serves the production build locally |
npm test | Unit tests (Vitest) |
npm run lint | ESLint |
npm run validate:content | Checks the daily content files (see docs/content-pipeline.md) |
node scripts/verify-dist.mjs | Checks the built site: one title per page, canonical/hreflang/og tags, JSON-LD, sitemap, feeds |
The @ import alias points to src/.
| Source | Endpoint | Used for |
|---|---|---|
| gold-api.com | https://api.gold-api.com/price/{XAU,XAG,XPT,XPD} | Live spot prices in USD per troy ounce |
| Frankfurter | https://api.frankfurter.dev/v1/…?base=USD | ECB reference rates for 13 currencies over the last ten days (one request), and daily series per currency for the charts |
| NBP | https://api.nbp.pl/api/cenyzlota/… | Daily gold fixings in PLN per gram from the National Bank of Poland, back to 2013 |
All requests are plain fetch calls from the browser (9 s timeout, no keys).
useGoldPrice, refreshes the metals every 30 seconds and the rates every 30 minutes. Components read from it.gold-api.com (4 metals) ─┐ every 30 s
Frankfurter (rates) ─┴─▶ useGoldPrice ─▶ ticker, hero, alerts, calculators, portfolio, metal pages
NBP + Frankfurter (daily) ──▶ useHistory / useDisplayHistory ─▶ charts, stats
src/content/*.json ──▶ AI Insight, Antam prices (written daily by the content agent)
src/data/articles.ts ──▶ articles
price per weight = USD/oz ÷ 31.1034768 × grams per unit × units of currency per USD
SAR and AED use their official dollar pegs (3.75 and 3.6725); the other currencies use ECB reference rates.
Every fetch reports live, cached or offline. A failed request falls back to the last good response in localStorage (CACHE); with nothing cached the value shows OFFLINE. There are no placeholder prices.
| Data | Kind | Notes |
|---|---|---|
| Metal prices and exchange rates | Live | Every 30 s (rates every 30 min) |
| Price history | Live, derived | NBP fixings at the same day's rate |
AI Insight (src/content/ai-insight.json) | Static file | Written daily by an AI content agent outside this repo, through a pull request checked in CI (docs/content-pipeline.md); no model runs on the site |
Antam prices (src/content/antam.json) | Static file | Same agent: bar prices by size, buyback, optional Galeri24/UBS quotes and a daily history |
Articles (src/data/articles.ts) | Static | Bilingual; added by the agent or through commits |
Implemented in src/lib/calc.ts:
r_m = (1 + annual growth) ^ (1/12) - 1 monthly growth rate
price(m) = buy price × (1 + r_m) ^ m gold price at month m
grams += contribution ÷ (price(m) × (1 + spread)) grams bought that month
value(m) = grams × price(m) portfolio value at month m
The initial amount is invested at month 0 in both modes; DCA also invests the monthly amount every month. Growth is constant, the spread is charged on purchase only, the final value is marked at the chosen price basis, and inflation and taxes are ignored. The zakat, jewelry and target formulas are in src/lib/{zakat,jewelry,target}.ts.
There is no backend and no account. The app keeps these in localStorage:
| Key | Contents |
|---|---|
emaskuy.display.v1 | Display currency and weight |
emaskuy.lang | Language choice (the page language comes from the URL) |
emaskuy.theme | dark or light |
emaskuy.portfolio.v2 | Portfolio holdings |
emaskuy.portfolio.valuation | spot or buyback |
emaskuy.alerts.v2 | Price alerts |
emaskuy.ticks.{xau,xag,xpt,xpd} | Prices recorded in the last 24 h (charts) |
emaskuy.sessionbase | First prices seen, for the non-gold metals' change |
emaskuy.cache.* | Last good API responses and history, for offline use |
emaskuy.storage.v | Storage migration version |
Older keys (emaskuy.unit, emaskuy.alerts, emaskuy.portfolio) are migrated on first load and left in place.
Outside requests. Apart from the three APIs above, the browser only contacts:
The site uses no trackers or cookies.
.
├── .github/workflows/ # deploy-pages.yml (main → GitHub Pages), pr-check.yml (every PR)
├── docs/content-pipeline.md # contract for the daily content agent
├── scripts/ # images.mjs (WebP/OG copies), verify-dist.mjs, validate-content.mjs, content-schema.mjs, gen-icons.mjs
├── vite/seo-prerender.ts # build step that writes the prerendered pages
├── public/ # copied into dist/: CNAME, 404.html, icons, images, sw-notify.js
├── src/
│ ├── main.tsx # entry: router, storage migration, alert watcher
│ ├── App.tsx # providers, layout, routes for both languages
│ ├── pages/ # Home, Analysis, Article, Calculator + calculator/*, Portfolio, Metal, About, NotFound
│ ├── components/ # home/, analysis/, calculator/, portfolio/, share/, about/, ui-atoms/, ui/, Navbar, Footer, DisplayPicker
│ ├── hooks/ # useGoldPrice (poller), useHistory, useDisplay, usePriceAlerts, usePortfolio, …
│ ├── lib/ # api, money, history, routes, seo, i18n, calc, zakat, portfolio, alerts, antam, …
│ ├── seo/prerender.ts # prerendered HTML, sitemap, RSS
│ ├── content/ # antam.json, ai-insight.json (daily)
│ └── data/ # articles
├── index.html
└── vite.config.ts, tailwind.config.js, eslint.config.js, vitest.config.ts, tsconfig*.json
Pushes to main deploy to GitHub Pages through .github/workflows/deploy-pages.yml (also runnable by hand).
push to main ─▶ npm ci ─▶ validate:content ─▶ npm test ─▶ npm run build ─▶ verify-dist ─▶ GitHub Pages
public/CNAME (emaskuy.com) is copied into every build./kalkulator is served from kalkulator.html and /en/calculator from en/calculator.html, both with HTTP 200.public/404.html, which hands them to the app to show its "not found" page.index.html, or GitHub Pages would redirect /kalkulator to /kalkulator/; verify-dist checks this.base: '/' in vite.config.ts; the router basename and withBase() read Vite's BASE_URL.npm ci, npm run lint, npm run validate:content, npm test, npm run build and node scripts/verify-dist.mjs. Lint is reported but doesn't block (there is pre-existing lint debt); everything else must pass. It also runs for PRs stacked on another PR's branch.main deploys.Conventions:
src/ with the @/ alias.registerStrings({ key: { id, en } }) and read them with t('key') from useI18n().pathFor(), articlePath() or metalPath() from src/lib/routes.ts, so they stay in the current language.useGoldPrice(); show them with useDisplay() (the visitor's currency and weight).src/content/*.json follow a contract (docs/content-pipeline.md, scripts/content-schema.mjs). Change it only together with the content agent's prompt.EmasKuy content is for informational and educational purposes only — not financial advice. Data may be delayed. Past performance does not guarantee future results.