Skip to main content

Generative UI

Generative UI enables Nex-T1 to stream rich, interactive UI components alongside text responses. Instead of just returning data, the AI generates complete, ready-to-render React components for charts, tables, wallet views, and transaction interfaces.

What is Generative UI?

Generative UI is a paradigm where AI agents don’t just return text or JSON data—they stream fully-formed UI components directly to your frontend. When a user asks “Show me ETH price charts for the last 30 days,” Nex-T1:
  1. Fetches the data from on-chain sources
  2. Generates a complete chart component with the data
  3. Streams it to your frontend as a UI block
  4. Your app renders it instantly
This creates seamless, natural interactions where the AI understands not just what data to show, but how to visualize it.

Why Generative UI for Web3?

Context-Aware Visualizations

AI chooses the best visualization based on user intent:
  • Price queries → Line charts
  • Portfolio analysis → Holdings tables
  • Token comparisons → Comparison grids
  • Transaction history → Timeline views

Zero Manual Formatting

No need to parse API responses and format data:
  • Components arrive ready-to-render
  • Data is pre-formatted and validated
  • Styling matches component type
  • Responsive by default

Interactive Elements

Generate actionable UI components:
  • Swap forms with pre-filled data
  • Sign request dialogs
  • Transaction status trackers
  • Approval workflows

Streaming Updates

Real-time component delivery:
  • Progressive rendering as data arrives
  • Multiple components per response
  • Mixed text and UI content
  • Event-driven architecture

Architecture

How It Works

Component Generation Process

  1. Intent Recognition: Agent identifies what visualization is needed
  2. Data Fetching: Retrieves data from appropriate sources
  3. Schema Generation: Creates UI block following app/schemas/ui.py
  4. Streaming: Sends UI block as JSON event
  5. Frontend Rendering: Your app maps ui.kind to React component

Stream Event Types

The streaming API returns three types of events:
  • Text Events
  • UI Events
  • End Events
{
  "event": "text",
  "content": "Here's the ETH price chart...",
  "done": false
}
Regular text tokens for conversational responses.

Available UI Components

Chart Components

  • Price Chart
  • Volume Chart
  • Candlestick Chart
Component Type: price_chartProps:
{
  kind: 'price_chart';
  title?: string;
  symbol?: string;
  pair?: string;          // e.g., "ETH/USDC"
  currency?: string;      // e.g., "USD"
  range?: string;         // e.g., "30d", "1y"
  data: Array<{
    t: string;            // ISO timestamp
    price: number;
  }>;
}
Use Cases:
  • Token price history
  • Multi-timeframe analysis
  • Price comparison overlays
Example:
import { LineChart, Line, XAxis, YAxis, Tooltip } from 'recharts';

export function PriceChart({ title, symbol, data }: PriceChartProps) {
  return (
    <div className="chart-container">
      <h3>{title || `${symbol} Price Chart`}</h3>
      <LineChart width={600} height={300} data={data}>
        <XAxis dataKey="t" />
        <YAxis />
        <Tooltip />
        <Line type="monotone" dataKey="price" stroke="#8884d8" />
      </LineChart>
    </div>
  );
}

Table Components

  • Asset Table
  • Wallet Holdings
  • Wallet Balance
Component Type: asset_tableProps:
{
  kind: 'asset_table';
  title?: string;
  rows: Array<{
    symbol: string;
    name?: string;
    price?: number;
    change_24h_pct?: number;
    volume_24h?: number;
    logo_url?: string;
  }>;
}
Use Cases:
  • Token lists and rankings
  • Market comparisons
  • Watchlist displays
Example:
export function AssetTable({ title, rows }: AssetTableProps) {
  return (
    <div className="asset-table">
      <h3>{title || 'Assets'}</h3>
      <table>
        <thead>
          <tr>
            <th>Token</th>
            <th>Price</th>
            <th>24h Change</th>
            <th>Volume</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((row) => (
            <tr key={row.symbol}>
              <td>
                {row.logo_url && <img src={row.logo_url} alt={row.symbol} />}
                {row.name} ({row.symbol})
              </td>
              <td>${row.price?.toFixed(2)}</td>
              <td className={row.change_24h_pct >= 0 ? 'positive' : 'negative'}>
                {row.change_24h_pct?.toFixed(2)}%
              </td>
              <td>${row.volume_24h?.toLocaleString()}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Wallet & Transaction Components

  • Wallet PnL
  • Sign Request
Component Type: wallet_pnlProps:
{
  kind: 'wallet_pnl';
  wallet_address: string;
  period?: string;         // e.g., "24h", "7d", "30d"
  pnl_usd?: number;
  pnl_pct?: number;
  timeseries?: Array<{
    t: string;
    pnl_usd: number;
  }>;
}
Use Cases:
  • Portfolio performance tracking
  • Profit/loss visualization
  • Historical performance analysis
Example:
export function WalletPnL({ wallet_address, period, pnl_usd, pnl_pct, timeseries }: WalletPnLProps) {
  const isPositive = pnl_usd >= 0;

  return (
    <div className="wallet-pnl">
      <h3>PnL for {wallet_address.slice(0, 6)}...{wallet_address.slice(-4)}</h3>
      <div className={`pnl-summary ${isPositive ? 'positive' : 'negative'}`}>
        <span className="pnl-amount">{isPositive ? '+' : ''}${pnl_usd?.toFixed(2)}</span>
        <span className="pnl-pct">{isPositive ? '+' : ''}{pnl_pct?.toFixed(2)}%</span>
        <span className="period">{period}</span>
      </div>
      {timeseries && (
        <LineChart width={400} height={200} data={timeseries}>
          <Line type="monotone" dataKey="pnl_usd" stroke={isPositive ? '#10b981' : '#ef4444'} />
        </LineChart>
      )}
    </div>
  );
}

NFT Components

  • NFT Grid
Component Type: nft_gridProps:
{
  kind: 'nft_grid';
  title?: string;
  items: Array<{
    token_id?: string;
    name?: string;
    collection?: string;
    image_url?: string;
    permalink?: string;     // Link to marketplace
  }>;
}
Use Cases:
  • NFT collection displays
  • Gallery views
  • Wallet NFT portfolios
Example:
export function NftGrid({ title, items }: NftGridProps) {
  return (
    <div className="nft-grid">
      <h3>{title || 'NFTs'}</h3>
      <div className="grid">
        {items.map((nft, idx) => (
          <a
            key={nft.token_id || idx}
            href={nft.permalink}
            target="_blank"
            rel="noopener noreferrer"
            className="nft-card"
          >
            {nft.image_url && (
              <img src={nft.image_url} alt={nft.name || 'NFT'} />
            )}
            <div className="nft-info">
              <div className="nft-name">{nft.name || `#${nft.token_id}`}</div>
              {nft.collection && (
                <div className="nft-collection">{nft.collection}</div>
              )}
            </div>
          </a>
        ))}
      </div>
    </div>
  );
}

Implementation Guide

1. Setting Up the Frontend

  • React Setup
  • Vue Setup
Install Dependencies:
npm install recharts @solana/wallet-adapter-react ethers
Create UIBlockRenderer:
// components/UIBlockRenderer.tsx
import React from 'react';
import { PriceChart } from './PriceChart';
import { VolumeChart } from './VolumeChart';
import { AssetTable } from './AssetTable';
import { WalletHoldings } from './WalletHoldings';
import { WalletBalance } from './WalletBalance';
import { WalletPnL } from './WalletPnL';
import { SignRequest } from './SignRequest';
import { NftGrid } from './NftGrid';
import { TokenLogo } from './TokenLogo';

export function UIBlockRenderer({ ui }: { ui: any }) {
  switch (ui.kind) {
    case 'price_chart':
      return <PriceChart {...ui} />;
    case 'volume_chart':
      return <VolumeChart {...ui} />;
    case 'asset_table':
      return <AssetTable {...ui} />;
    case 'wallet_holdings':
      return <WalletHoldings {...ui} />;
    case 'wallet_balance':
      return <WalletBalance {...ui} />;
    case 'wallet_pnl':
      return <WalletPnL {...ui} />;
    case 'sign_request':
      return <SignRequest {...ui} />;
    case 'nft_grid':
      return <NftGrid {...ui} />;
    case 'token_logo':
      return <TokenLogo {...ui} />;
    default:
      console.warn('Unknown UI block kind:', ui.kind);
      return null;
  }
}

2. Streaming UI Updates

  • POST Stream
  • SSE Stream
Using Fetch API:
type StreamEvent = {
  event: 'text' | 'ui' | 'end' | 'error';
  content?: string;
  ui?: any;
  done?: boolean;
};

export async function startChatStream(
  message: string,
  token: string,
  onEvent: (event: StreamEvent) => void
) {
  const response = await fetch('/api/v1/chatbot/chat/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
    body: JSON.stringify({ message }),
  });

  const reader = response.body?.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split('\n').filter(line => line.trim());

    for (const line of lines) {
      try {
        const event = JSON.parse(line);
        onEvent(event);
      } catch (e) {
        console.error('Failed to parse event:', line);
      }
    }
  }
}

3. Rendering Components Dynamically

Complete Chat Component:
// components/Chat.tsx
import { useState, useRef, useEffect } from 'react';
import { startChatStream } from '../lib/stream';
import { UIBlockRenderer } from './UIBlockRenderer';

type Message = {
  role: 'user' | 'assistant';
  content: string;
  ui?: any;
};

export function Chat() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [streaming, setStreaming] = useState(false);
  const messagesEndRef = useRef<HTMLDivElement>(null);

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  };

  useEffect(scrollToBottom, [messages]);

  const handleSend = async () => {
    if (!input.trim() || streaming) return;

    const userMessage: Message = { role: 'user', content: input };
    setMessages(prev => [...prev, userMessage]);
    setInput('');
    setStreaming(true);

    let assistantMessage: Message = { role: 'assistant', content: '' };
    const uiBlocks: any[] = [];

    try {
      await startChatStream(input, 'your-jwt-token', (event) => {
        if (event.event === 'text') {
          assistantMessage.content += event.content;
          setMessages(prev => [...prev.slice(0, -1), { ...assistantMessage }]);
        } else if (event.event === 'ui') {
          uiBlocks.push(event.ui);
          assistantMessage.ui = uiBlocks;
          setMessages(prev => [...prev.slice(0, -1), { ...assistantMessage }]);
        } else if (event.event === 'end') {
          setStreaming(false);
        }
      });

      setMessages(prev => [...prev, assistantMessage]);
    } catch (error) {
      console.error('Stream error:', error);
      setStreaming(false);
    }
  };

  return (
    <div className="chat-container">
      <div className="messages">
        {messages.map((msg, idx) => (
          <div key={idx} className={`message ${msg.role}`}>
            <div className="message-content">{msg.content}</div>
            {msg.ui && msg.ui.map((ui: any, uiIdx: number) => (
              <div key={uiIdx} className="ui-block">
                <UIBlockRenderer ui={ui} />
              </div>
            ))}
          </div>
        ))}
        <div ref={messagesEndRef} />
      </div>

      <div className="input-container">
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && handleSend()}
          placeholder="Ask about prices, portfolios, swaps..."
          disabled={streaming}
        />
        <button onClick={handleSend} disabled={streaming || !input.trim()}>
          {streaming ? 'Streaming...' : 'Send'}
        </button>
      </div>
    </div>
  );
}

4. State Management

  • React Context
  • Zustand Store
// contexts/ChatContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';

type ChatContextType = {
  messages: Message[];
  addMessage: (message: Message) => void;
  updateLastMessage: (updater: (msg: Message) => Message) => void;
  clearMessages: () => void;
};

const ChatContext = createContext<ChatContextType | null>(null);

export function ChatProvider({ children }: { children: ReactNode }) {
  const [messages, setMessages] = useState<Message[]>([]);

  const addMessage = (message: Message) => {
    setMessages(prev => [...prev, message]);
  };

  const updateLastMessage = (updater: (msg: Message) => Message) => {
    setMessages(prev => {
      const last = prev[prev.length - 1];
      return [...prev.slice(0, -1), updater(last)];
    });
  };

  const clearMessages = () => setMessages([]);

  return (
    <ChatContext.Provider value={{ messages, addMessage, updateLastMessage, clearMessages }}>
      {children}
    </ChatContext.Provider>
  );
}

export const useChat = () => {
  const context = useContext(ChatContext);
  if (!context) throw new Error('useChat must be used within ChatProvider');
  return context;
};

Examples

Simple Chart Generation

User Query: “Show me ETH price for the last 7 days” Stream Response:
// Event 1: Text
{
  "event": "text",
  "content": "Here's the ETH price chart for the last 7 days:",
  "done": false
}

// Event 2: UI Block
{
  "event": "ui",
  "ui": {
    "kind": "price_chart",
    "symbol": "ETH",
    "range": "7d",
    "currency": "USD",
    "data": [
      {"t": "2024-01-01T00:00:00Z", "price": 2200},
      {"t": "2024-01-02T00:00:00Z", "price": 2250},
      {"t": "2024-01-03T00:00:00Z", "price": 2180},
      {"t": "2024-01-04T00:00:00Z", "price": 2300},
      {"t": "2024-01-05T00:00:00Z", "price": 2350},
      {"t": "2024-01-06T00:00:00Z", "price": 2280},
      {"t": "2024-01-07T00:00:00Z", "price": 2400}
    ]
  },
  "done": false
}

// Event 3: End
{
  "event": "end",
  "done": true
}
Rendered Output: A complete line chart showing ETH price trend.

Complex Dashboard

User Query: “Show me a full analysis of my wallet 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb” Stream Response: Multiple UI blocks in sequence:
  1. Wallet Balance Card: Total portfolio value
  2. Holdings Table: Breakdown by asset
  3. PnL Chart: Performance over time
  4. Recent Transactions: Last 10 transactions
// The chat component automatically renders each UI block as it arrives
<div className="wallet-dashboard">
  <WalletBalance {...balanceUI} />
  <WalletHoldings {...holdingsUI} />
  <WalletPnL {...pnlUI} />
  <AssetTable {...transactionsUI} />
</div>

Interactive Swap Interface

User Query: “Swap 1 ETH for USDC” Stream Response:
{
  "event": "ui",
  "ui": {
    "kind": "sign_request",
    "chain": "ethereum",
    "wallet_address": "0x742d35...",
    "title": "Swap 1 ETH → USDC",
    "payload": {
      "transaction": {
        "to": "0x...",
        "data": "0x...",
        "value": "1000000000000000000",
        "gasLimit": "200000"
      }
    },
    "disclaimer": "Review all transaction details carefully before signing."
  }
}
Rendered: Interactive swap form with sign button that connects to user’s wallet.

Real-Time Price Updates

User Query: “Monitor BTC and ETH prices” Implementation with Continuous Streaming:
function PriceMonitor() {
  const [prices, setPrices] = useState<Record<string, number>>({});

  useEffect(() => {
    const cleanup = startChatStream(
      "Monitor BTC and ETH prices",
      token,
      (event) => {
        if (event.event === 'ui' && event.ui.kind === 'price_chart') {
          const latestPrice = event.ui.data[event.ui.data.length - 1].price;
          setPrices(prev => ({
            ...prev,
            [event.ui.symbol]: latestPrice,
          }));
        }
      }
    );

    return cleanup;
  }, []);

  return (
    <div className="price-monitor">
      {Object.entries(prices).map(([symbol, price]) => (
        <div key={symbol} className="price-card">
          <span className="symbol">{symbol}</span>
          <span className="price">${price.toFixed(2)}</span>
        </div>
      ))}
    </div>
  );
}

Best Practices

Performance Optimization

  • Component Memoization
  • Lazy Loading
  • Virtual Scrolling
import { memo } from 'react';

export const PriceChart = memo(({ symbol, data }: PriceChartProps) => {
  // Component implementation
}, (prevProps, nextProps) => {
  // Custom comparison: only re-render if data actually changed
  return prevProps.symbol === nextProps.symbol &&
         JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data);
});

Error Handling

export function UIBlockRenderer({ ui }: { ui: any }) {
  try {
    // Validate UI block structure
    if (!ui || !ui.kind) {
      console.error('Invalid UI block:', ui);
      return <ErrorBoundary>Invalid component data</ErrorBoundary>;
    }

    // Render based on kind
    switch (ui.kind) {
      case 'price_chart':
        if (!ui.data || !Array.isArray(ui.data)) {
          return <ErrorBoundary>Invalid chart data</ErrorBoundary>;
        }
        return <PriceChart {...ui} />;

      case 'sign_request':
        if (!ui.chain || !ui.wallet_address || !ui.payload) {
          return <ErrorBoundary>Incomplete sign request</ErrorBoundary>;
        }
        return <SignRequest {...ui} />;

      default:
        console.warn('Unknown UI kind:', ui.kind);
        return null;
    }
  } catch (error) {
    console.error('UI rendering error:', error);
    return <ErrorBoundary>Failed to render component</ErrorBoundary>;
  }
}

// Error Boundary Component
class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <div className="error">Something went wrong rendering this component.</div>;
    }
    return this.props.children;
  }
}

Accessibility

export function PriceChart({ symbol, data }: PriceChartProps) {
  return (
    <div
      className="price-chart"
      role="img"
      aria-label={`Price chart for ${symbol}`}
    >
      <h3 id={`chart-${symbol}`}>{symbol} Price</h3>
      <LineChart
        width={600}
        height={300}
        data={data}
        aria-labelledby={`chart-${symbol}`}
      >
        <XAxis dataKey="t" />
        <YAxis />
        <Tooltip />
        <Line
          type="monotone"
          dataKey="price"
          stroke="#8884d8"
          aria-label="Price line"
        />
      </LineChart>

      {/* Provide text alternative for screen readers */}
      <div className="sr-only">
        {symbol} price data: {data.map(d => `${d.t}: $${d.price}`).join(', ')}
      </div>
    </div>
  );
}

Mobile Responsiveness

import { useState, useEffect } from 'react';

function useIsMobile() {
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const checkMobile = () => setIsMobile(window.innerWidth < 768);
    checkMobile();
    window.addEventListener('resize', checkMobile);
    return () => window.removeEventListener('resize', checkMobile);
  }, []);

  return isMobile;
}

export function PriceChart({ symbol, data }: PriceChartProps) {
  const isMobile = useIsMobile();

  return (
    <div className="price-chart">
      <LineChart
        width={isMobile ? 350 : 600}
        height={isMobile ? 200 : 300}
        data={data}
      >
        <XAxis
          dataKey="t"
          angle={isMobile ? -45 : 0}
          textAnchor={isMobile ? 'end' : 'middle'}
        />
        <YAxis />
        <Tooltip />
        <Line type="monotone" dataKey="price" stroke="#8884d8" />
      </LineChart>
    </div>
  );
}
/* Responsive styles */
.price-chart {
  width: 100%;
  max-width: 600px;
  margin: 1rem auto;
}

@media (max-width: 768px) {
  .price-chart {
    max-width: 100%;
    padding: 0 1rem;
  }

  .chart-container {
    overflow-x: auto;
  }
}

.asset-table {
  overflow-x: auto;
}

.asset-table table {
  min-width: 600px;
}

@media (max-width: 768px) {
  .asset-table table {
    min-width: 100%;
    font-size: 0.875rem;
  }
}

Security Considerations

Critical Security Rules for Sign Requests:
  1. Never Auto-Execute: Always require explicit user confirmation
  2. Display Full Details: Show all transaction parameters in plain language
  3. Validate Addresses: Verify address format and checksum
  4. Network Matching: Ensure connected wallet matches requested chain
  5. Throttle Requests: Debounce consecutive sign_request blocks to prevent spam
  6. Warn on High Values: Alert users for transactions above configured thresholds
  7. Audit Logging: Log all signature requests and outcomes
Example Security Implementation:
export function SignRequest({ chain, wallet_address, payload, disclaimer }: SignRequestProps) {
  const [userConfirmed, setUserConfirmed] = useState(false);
  const [addressValidated, setAddressValidated] = useState(false);

  useEffect(() => {
    // Validate address format
    const isValid = chain === 'ethereum'
      ? ethers.isAddress(wallet_address)
      : validateSolanaAddress(wallet_address);
    setAddressValidated(isValid);
  }, [chain, wallet_address]);

  const handleSign = async () => {
    if (!userConfirmed || !addressValidated) {
      alert('Please confirm all details before signing');
      return;
    }

    // Additional checks
    if (payload.transaction?.value) {
      const valueInEth = ethers.formatEther(payload.transaction.value);
      if (parseFloat(valueInEth) > 10) {
        const confirmed = confirm(`Warning: High value transaction (${valueInEth} ETH). Proceed?`);
        if (!confirmed) return;
      }
    }

    // Proceed with signing...
  };

  return (
    <div className="sign-request">
      {!addressValidated && (
        <div className="error">Invalid address format</div>
      )}

      <label>
        <input
          type="checkbox"
          checked={userConfirmed}
          onChange={(e) => setUserConfirmed(e.target.checked)}
        />
        I have reviewed all details and wish to proceed
      </label>

      {disclaimer && <div className="disclaimer">{disclaimer}</div>}

      <button
        onClick={handleSign}
        disabled={!userConfirmed || !addressValidated}
      >
        Sign & Execute
      </button>
    </div>
  );
}

TypeScript Types

For type safety, generate TypeScript types from the backend schemas:
// types/ui.ts

export type UIBlock =
  | PriceChartUI
  | VolumeChartUI
  | AssetTableUI
  | WalletHoldingsUI
  | WalletBalanceUI
  | WalletPnLUI
  | SignRequestUI
  | NftGridUI
  | TokenLogoUI;

export interface PriceChartUI {
  kind: 'price_chart';
  title?: string;
  symbol?: string;
  pair?: string;
  currency?: string;
  range?: string;
  data: Array<{ t: string; price: number }>;
}

export interface VolumeChartUI {
  kind: 'volume_chart';
  title?: string;
  symbol?: string;
  pair?: string;
  data: Array<{ t: string; volume: number }>;
}

export interface AssetTableUI {
  kind: 'asset_table';
  title?: string;
  rows: Array<{
    symbol: string;
    name?: string;
    price?: number;
    change_24h_pct?: number;
    volume_24h?: number;
    logo_url?: string;
  }>;
}

export interface WalletHoldingsUI {
  kind: 'wallet_holdings';
  wallet_address: string;
  chain?: string;
  rows: Array<{
    symbol: string;
    amount: number;
    usd_value?: number;
    logo_url?: string;
  }>;
}

export interface WalletBalanceUI {
  kind: 'wallet_balance';
  wallet_address: string;
  chain?: string;
  total_usd?: number;
  breakdown: Array<{
    symbol: string;
    amount: number;
    usd_value?: number;
    logo_url?: string;
  }>;
}

export interface WalletPnLUI {
  kind: 'wallet_pnl';
  wallet_address: string;
  period?: string;
  pnl_usd?: number;
  pnl_pct?: number;
  timeseries?: Array<{
    t: string;
    pnl_usd: number;
  }>;
}

export interface SignRequestUI {
  kind: 'sign_request';
  chain: string;
  wallet_address: string;
  title?: string;
  message?: string;
  payload: any;
  disclaimer?: string;
}

export interface NftGridUI {
  kind: 'nft_grid';
  title?: string;
  items: Array<{
    token_id?: string;
    name?: string;
    collection?: string;
    image_url?: string;
    permalink?: string;
  }>;
}

export interface TokenLogoUI {
  kind: 'token_logo';
  symbol: string;
  logo_url: string;
  name?: string;
  size?: 'sm' | 'md' | 'lg';
}

Next Steps

Need Help? Join our developer community or check out the example React implementation in examples/frontend/react/ in the Nex-T1 repository.