Skip to content

Instantly share code, notes, and snippets.

@andrewmd5
Created November 6, 2025 10:16
Show Gist options
  • Select an option

  • Save andrewmd5/f0be891c8a2b8c2ab3ad31517f423740 to your computer and use it in GitHub Desktop.

Select an option

Save andrewmd5/f0be891c8a2b8c2ab3ad31517f423740 to your computer and use it in GitHub Desktop.
hako exampl
// Modern Finance Tracking App
// Real-time portfolio tracking with dark/light mode
// Initial stock data
const initialStocks = {
AAPL: {
symbol: "AAPL",
name: "Apple Inc.",
price: 182.52,
change: 2.34,
percent: 1.3,
shares: 50,
},
GOOGL: {
symbol: "GOOGL",
name: "Alphabet Inc.",
price: 139.78,
change: -1.23,
percent: -0.87,
shares: 20,
},
MSFT: {
symbol: "MSFT",
name: "Microsoft Corp.",
price: 378.91,
change: 5.67,
percent: 1.52,
shares: 30,
},
AMZN: {
symbol: "AMZN",
name: "Amazon.com Inc.",
price: 145.32,
change: 3.21,
percent: 2.26,
shares: 25,
},
TSLA: {
symbol: "TSLA",
name: "Tesla Inc.",
price: 243.84,
change: -4.56,
percent: -1.84,
shares: 15,
},
};
// Theme definitions
const themes = {
dark: {
bg: "#0a0a0a",
surface: "#1a1a1a",
card: "#242424",
cardHover: "#2a2a2a",
border: "#333333",
borderLight: "#404040",
text: "#ffffff",
textSecondary: "#999999",
textMuted: "#666666",
accent: "#4a9eff",
success: "#10b981",
danger: "#ef4444",
warning: "#f59e0b",
highlight: "#8b5cf6",
iconColor: "#ffffff",
iconMuted: "#999999",
},
light: {
bg: "#f8f9fa",
surface: "#ffffff",
card: "#ffffff",
cardHover: "#f3f4f6",
border: "#e5e7eb",
borderLight: "#d1d5db",
text: "#111827",
textSecondary: "#6b7280",
textMuted: "#9ca3af",
accent: "#3b82f6",
success: "#10b981",
danger: "#ef4444",
warning: "#f59e0b",
highlight: "#8b5cf6",
iconColor: "#374151",
iconMuted: "#9ca3af",
},
};
export function render() {
const [darkMode, setDarkMode] = useState(true);
const [selectedView, setSelectedView] = useState("overview");
const [stocks, setStocks] = useState(initialStocks);
const [cashBalance, setCashBalance] = useState(15000);
const [refreshCount, setRefreshCount] = useState(0);
const [showSettings, setShowSettings] = useState(false);
const [showMarketTooltip, setShowMarketTooltip] = useState(false);
const [autoRefresh, setAutoRefresh] = useState(true);
const [notifications, setNotifications] = useState(true);
const [timeoutStarted, setTimeoutStarted] = useState(false);
const theme = darkMode ? themes.dark : themes.light;
// Automatically update stock prices every 5 seconds if enabled
if (autoRefresh && !timeoutStarted) {
setTimeoutStarted(true);
setTimeout(() => {
UpdatePrices();
setTimeoutStarted(false); // Reset so next render can set another timeout
}, 1000);
}
// Update stock prices
const UpdatePrices = () => {
setStocks((prevStocks) => {
const newStocks = {};
for (let symbol in prevStocks) {
const stock = prevStocks[symbol];
const movement = (Math.random() - 0.5) * 4;
const priceChange = movement;
const newPrice = stock.price + priceChange;
newStocks[symbol] = {
...stock,
price: newPrice,
change: stock.change + priceChange,
percent: ((stock.change + priceChange) / stock.price) * 100,
};
}
return newStocks;
});
setRefreshCount((c) => c + 1);
};
// Calculate portfolio metrics
const stocksValue = Object.values(stocks).reduce(
(sum, stock) => sum + stock.price * stock.shares,
0,
);
const portfolioValue = stocksValue + cashBalance;
const dayChange = Object.values(stocks).reduce(
(sum, stock) => sum + stock.change * stock.shares,
0,
);
const dayChangePercent = (dayChange / portfolioValue) * 100;
return VStack(
{
spacing: 0,
width: "fill",
height: "fill",
background: theme.bg,
},
[
// Header
HStack(
{
background: theme.surface,
padding: 16,
spacing: 16,
height: 64,
width: "fill",
alignment: "center",
border: { width: { bottom: 2 }, color: theme.accent },
},
[
HStack({ spacing: 12, alignment: "center" }, [
VStack(
{
background: theme.accent,
width: 36,
height: 36,
cornerRadius: 8,
alignment: "center",
border: { width: 2, color: theme.highlight },
},
[Text("FT", { fontSize: 18, color: "white" })],
),
VStack({ spacing: 2 }, [
Text("FinanceTracker", { fontSize: 18, color: theme.text }),
Text("Professional", { fontSize: 11, color: theme.accent }),
]),
]),
Spacer(),
HStack({ spacing: 12 }, [
// Market status with hover tooltip
ZStack([
// Market status with hover tooltip
Button(
{
onClick: () => setShowMarketTooltip(!showMarketTooltip),
background: theme.card,
hoverBackground: theme.cardHover,
padding: 8,
cornerRadius: 20,
border: { width: 1, color: theme.success },
},
[
HStack({ spacing: 8 }, [
VStack({
background: theme.success,
width: 8,
height: 8,
cornerRadius: 4,
}),
Text("Markets Open", { fontSize: 12, color: theme.text }),
]),
// Tooltip as a sibling to button content, not nested in HStack
Tooltip(
{
attachment: "below",
background: darkMode ? "#333333" : "#374151",
padding: 10,
cornerRadius: 6,
border: { width: 1, color: theme.border },
},
[
VStack({ spacing: 4 }, [
Text("NYSE Trading Hours", {
fontSize: 12,
color: "white",
}),
Text("9:30 AM - 4:00 PM EST", {
fontSize: 11,
color: "#cccccc",
}),
Divider({ color: "#555555", thickness: 1 }),
Text(
"Last update: " + new Date().toLocaleTimeString(),
{ fontSize: 10, color: "#aaaaaa" },
),
]),
],
),
],
),
]),
// Refresh button
Button(
{
onClick: UpdatePrices,
background: theme.card,
hoverBackground: theme.cardHover,
padding: 10,
cornerRadius: 8,
border: { width: 1, color: theme.border },
},
Icon("refresh", { size: 18, fill: theme.iconColor }),
),
// Settings button
Button(
{
onClick: () => setShowSettings(!showSettings),
background: showSettings ? theme.accent : theme.card,
hoverBackground: showSettings ? theme.accent : theme.cardHover,
padding: 10,
cornerRadius: 8,
border: {
width: 1,
color: showSettings ? theme.highlight : theme.border,
},
},
Icon("settings", {
size: 18,
fill: showSettings ? "white" : theme.iconColor,
}),
),
]),
],
),
HStack({ spacing: 0, width: "fill", height: "fill" }, [
// Sidebar
VStack(
{
width: 280,
background: theme.surface,
padding: 20,
spacing: 16,
height: "fill",
border: { width: { right: 1 }, color: theme.border },
},
[
// Portfolio summary card
VStack(
{
background: theme.accent,
padding: 20,
cornerRadius: 12,
spacing: 12,
width: "fill",
border: { width: 2, color: theme.highlight },
},
[
Text("Portfolio Value", { fontSize: 13, color: "#ffffffcc" }),
Text(`$${portfolioValue.toFixed(2)}`, {
fontSize: 28,
color: "white",
}),
HStack({ spacing: 8 }, [
VStack(
{
background: dayChange >= 0 ? theme.success : theme.danger,
padding: 4,
cornerRadius: 4,
},
[
Text(dayChange >= 0 ? "↑" : "↓", {
fontSize: 12,
color: "white",
}),
],
),
Text(`$${Math.abs(dayChange).toFixed(2)}`, {
fontSize: 14,
color: "white",
}),
Text(
`(${dayChangePercent >= 0 ? "+" : ""}${dayChangePercent.toFixed(2)}%)`,
{ fontSize: 12, color: "#ffffffaa" },
),
]),
],
),
// Navigation
VStack({ spacing: 6, width: "fill" }, [
NavButton(
"Overview",
"overview",
"dashboard",
selectedView,
setSelectedView,
theme,
),
NavButton(
"Portfolio",
"portfolio",
"chart",
selectedView,
setSelectedView,
theme,
),
NavButton(
"Activity",
"activity",
"refresh",
selectedView,
setSelectedView,
theme,
),
]),
Spacer(),
// Quick stats
VStack(
{
background: theme.card,
padding: 16,
cornerRadius: 10,
spacing: 12,
width: "fill",
border: { width: 1, color: theme.border },
},
[
StatRow("Cash Balance", `$${cashBalance.toFixed(2)}`, theme),
StatRow("Stocks Value", `$${stocksValue.toFixed(2)}`, theme),
StatRow(
"Day Change",
`${dayChangePercent >= 0 ? "+" : ""}${dayChangePercent.toFixed(2)}%`,
theme,
dayChangePercent >= 0 ? theme.success : theme.danger,
),
],
),
],
),
// Main content
VStack(
{
background: theme.bg,
width: "fill",
height: "fill",
},
[
ScrollView(
{
width: "fill",
height: "fill",
axis: "vertical",
},
[
VStack(
{
padding: 24,
spacing: 20,
width: "fill",
},
[
showSettings
? RenderSettings(
theme,
darkMode,
setDarkMode,
autoRefresh,
setAutoRefresh,
notifications,
setNotifications,
setShowSettings,
)
: selectedView === "overview"
? RenderOverview(
theme,
stocks,
portfolioValue,
cashBalance,
dayChange,
)
: selectedView === "portfolio"
? RenderPortfolio(theme, stocks)
: selectedView === "activity"
? RenderActivity(theme)
: null,
],
),
],
),
],
),
]),
],
);
}
function NavButton(label, id, icon, selectedView, setSelectedView, theme) {
const isSelected = selectedView === id;
return Button(
{
onClick: () => setSelectedView(id),
background: isSelected ? theme.accent : theme.card,
hoverBackground: isSelected ? theme.accent : theme.cardHover,
width: "fill",
padding: 12,
cornerRadius: 8,
border: {
width: isSelected ? 2 : 1,
color: isSelected ? theme.highlight : theme.border,
},
},
HStack({ spacing: 10, alignment: "center" }, [
Icon(icon, { size: 18, fill: isSelected ? "white" : theme.iconColor }),
Text(label, { fontSize: 14, color: isSelected ? "white" : theme.text }),
]),
);
}
function StatRow(label, value, theme, valueColor) {
return HStack({ width: "fill" }, [
Text(label, { fontSize: 12, color: theme.textMuted }),
Spacer(),
Text(value, { fontSize: 12, color: valueColor || theme.text }),
]);
}
function RenderOverview(theme, stocks, portfolioValue, cashBalance, dayChange) {
return VStack({ spacing: 20, width: "fill" }, [
Text("Dashboard Overview", { fontSize: 24, color: theme.text }),
// Top cards
HStack({ spacing: 16, width: "fill" }, [
MetricCard(
"Total Assets",
`$${portfolioValue.toFixed(2)}`,
"+12.5%",
theme,
theme.accent,
),
MetricCard(
"Today's Gain",
`$${dayChange.toFixed(2)}`,
`${dayChange >= 0 ? "+" : ""}${((dayChange / portfolioValue) * 100).toFixed(2)}%`,
theme,
dayChange >= 0 ? theme.success : theme.danger,
),
MetricCard(
"Cash Available",
`$${cashBalance.toFixed(2)}`,
"Ready to invest",
theme,
theme.warning,
),
]),
// Stocks table
VStack(
{
background: theme.card,
padding: 20,
cornerRadius: 12,
spacing: 16,
width: "fill",
border: { width: 1, color: theme.border },
},
[
HStack({ width: "fill" }, [
Text("Your Holdings", { fontSize: 18, color: theme.text }),
Spacer(),
Text(`${Object.keys(stocks).length} positions`, {
fontSize: 12,
color: theme.textMuted,
}),
]),
VStack({ spacing: 8, width: "fill" }, [
// Header
HStack(
{
padding: 8,
width: "fill",
background: theme.surface,
cornerRadius: 6,
border: { width: 1, color: theme.border },
},
[
Text("Symbol", { fontSize: 12, color: theme.textMuted }),
Spacer(),
Text("Price", { fontSize: 12, color: theme.textMuted }),
Spacer(),
Text("Change", { fontSize: 12, color: theme.textMuted }),
Spacer(),
Text("Holdings", { fontSize: 12, color: theme.textMuted }),
],
),
// Stock rows
...Object.values(stocks).map((stock) => StockRow(stock, theme)),
]),
],
),
]);
}
function MetricCard(title, value, subtitle, theme, accentColor) {
return VStack(
{
background: theme.card,
padding: 20,
cornerRadius: 10,
spacing: 8,
width: "fill",
border: {
width: { left: 4, top: 1, right: 1, bottom: 1 },
color: accentColor,
},
},
[
Text(title, { fontSize: 12, color: theme.textMuted }),
Text(value, { fontSize: 20, color: theme.text }),
Text(subtitle, { fontSize: 11, color: accentColor }),
],
);
}
function StockRow(stock, theme) {
const changeColor = stock.change >= 0 ? theme.success : theme.danger;
return HStack(
{
padding: 12,
width: "fill",
background: theme.surface,
cornerRadius: 6,
border: { width: 1, color: theme.border },
},
[
VStack({ spacing: 2, width: 100 }, [
Text(stock.symbol, { fontSize: 14, color: theme.text }),
Text(stock.shares + " shares", {
fontSize: 10,
color: theme.textMuted,
}),
]),
Spacer(),
Text(`$${stock.price.toFixed(2)}`, { fontSize: 14, color: theme.text }),
Spacer(),
HStack({ spacing: 6, width: 120 }, [
Text(`${stock.change >= 0 ? "+" : ""}${stock.change.toFixed(2)}`, {
fontSize: 13,
color: changeColor,
}),
VStack(
{
background: changeColor,
padding: 3,
cornerRadius: 4,
},
[
Text(
`${stock.percent >= 0 ? "+" : ""}${stock.percent.toFixed(1)}%`,
{ fontSize: 10, color: "white" },
),
],
),
]),
Spacer(),
Text(`$${(stock.price * stock.shares).toFixed(2)}`, {
fontSize: 14,
color: theme.text,
}),
],
);
}
function RenderPortfolio(theme, stocks) {
const totalValue = Object.values(stocks).reduce(
(sum, s) => sum + s.price * s.shares,
0,
);
return VStack({ spacing: 20, width: "fill" }, [
Text("Portfolio Breakdown", { fontSize: 24, color: theme.text }),
HStack({ spacing: 16, width: "fill" }, [
// Allocation chart placeholder
VStack(
{
background: theme.card,
padding: 24,
cornerRadius: 12,
spacing: 16,
width: "fill",
height: 300,
alignment: "center",
border: { width: 1, color: theme.border },
},
[
Icon("chart-big", { size: 64, fill: theme.iconMuted }),
Text("Portfolio Allocation Chart", {
fontSize: 14,
color: theme.textMuted,
}),
],
),
// Holdings list
VStack(
{
background: theme.card,
padding: 20,
cornerRadius: 12,
spacing: 12,
width: 300,
border: { width: 1, color: theme.border },
},
[
Text("Holdings", { fontSize: 16, color: theme.text }),
Divider({ color: theme.border }),
...Object.values(stocks).map((stock) =>
HStack({ width: "fill", padding: 8 }, [
VStack({
background: theme.accent,
width: 4,
height: 30,
}),
HStack({ width: "fill", padding: 8 }, [
VStack({ spacing: 2 }, [
Text(stock.symbol, { fontSize: 13, color: theme.text }),
Text(
`${(((stock.price * stock.shares) / totalValue) * 100).toFixed(1)}%`,
{ fontSize: 10, color: theme.textMuted },
),
]),
Spacer(),
Text(`$${(stock.price * stock.shares).toFixed(2)}`, {
fontSize: 13,
color: theme.text,
}),
]),
]),
),
],
),
]),
]);
}
function RenderActivity(theme) {
const activities = [
{ type: "buy", symbol: "AAPL", amount: 1825.2, time: "2 hours ago" },
{ type: "sell", symbol: "TSLA", amount: 1250.0, time: "1 day ago" },
{ type: "dividend", symbol: "MSFT", amount: 45.0, time: "3 days ago" },
{ type: "buy", symbol: "GOOGL", amount: 2795.6, time: "1 week ago" },
];
return VStack({ spacing: 20, width: "fill" }, [
Text("Recent Activity", { fontSize: 24, color: theme.text }),
VStack(
{
background: theme.card,
padding: 20,
cornerRadius: 12,
spacing: 12,
width: "fill",
border: { width: 1, color: theme.border },
},
[
...activities.map((activity) =>
HStack(
{
padding: 12,
width: "fill",
background: theme.surface,
cornerRadius: 8,
border: { width: 1, color: theme.border },
},
[
VStack(
{
background:
activity.type === "buy"
? theme.success
: activity.type === "sell"
? theme.danger
: theme.warning,
width: 40,
height: 40,
cornerRadius: 8,
alignment: "center",
},
[
Icon(
activity.type === "buy"
? "plus"
: activity.type === "sell"
? "minus"
: "info",
{ size: 20, fill: "white" },
),
],
),
VStack({ spacing: 2, width: "fill", padding: 8 }, [
Text(`${activity.type.toUpperCase()} ${activity.symbol}`, {
fontSize: 14,
color: theme.text,
}),
Text(activity.time, { fontSize: 11, color: theme.textMuted }),
]),
Text(`$${activity.amount.toFixed(2)}`, {
fontSize: 14,
color: theme.text,
}),
],
),
),
],
),
]);
}
function RenderSettings(theme, darkMode, setDarkMode, autoRefresh, setAutoRefresh, notifications, setNotifications, setShowSettings) {
return VStack({ spacing: 20, width: "fill" }, [
HStack({ width: "fill", alignment: "center" }, [
Text("Settings", { fontSize: 24, color: theme.text }),
Spacer(),
Button(
{
onClick: () => setShowSettings(false),
background: theme.card,
hoverBackground: theme.cardHover,
padding: 8,
cornerRadius: 6,
border: { width: 1, color: theme.border },
},
Icon("close", { size: 16, fill: theme.iconColor }),
),
]),
VStack(
{
background: theme.card,
padding: 20,
cornerRadius: 12,
spacing: 16,
width: "fill",
border: { width: 1, color: theme.border },
},
[
SettingRow("Dark Mode", "Use dark theme", darkMode, setDarkMode, theme),
SettingRow("Notifications", "Get price alerts", notifications, setNotifications, theme),
SettingRow(
"Auto-refresh",
"Update prices automatically",
autoRefresh,
setAutoRefresh,
theme,
),
],
),
]);
}
function SettingRow(title, subtitle, value, setValue, theme) {
return HStack({ padding: 12, width: "fill" }, [
VStack({ spacing: 4, width: "fill" }, [
Text(title, { fontSize: 14, color: theme.text }),
Text(subtitle, { fontSize: 11, color: theme.textMuted }),
]),
Button(
{
onClick: () => setValue(!value),
background: value ? theme.success : theme.border,
padding: 8,
cornerRadius: 6,
border: { width: 1, color: value ? theme.success : theme.border },
},
Text(value ? "ON" : "OFF", {
fontSize: 11,
color: value ? "white" : theme.text,
}),
),
]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment