Here is a fully functional FUI design for Kabupaten Tapanuli Tengah, implemented as a single HTML file with React, Tailwind CSS, Three.js, and Canvas API. The interface features a decoding text effect, animated 3D wireframe terrain, real-time data visualizations, and a HUD-style layout with cyberpunk aesthetics. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tapanuli Tengah FUI</title> <!-- Tailwind CSS --> <script src="https://cdn.tailwindcss.com"></script> <!-- Google Fonts --> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet"> <!-- Three.js --> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <!-- React and ReactDOM --> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <style> body { background-color: #050505; color: #e0f2fe; font-family: 'JetBrains Mono', monospace; overflow: hidden; } .scanlines { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1)); background-size: 100% 4px; pointer-events: none; z-index: 50; } .hud-line { position: absolute; background: rgba(6, 182, 212, 0.5); box-shadow: 0 0 10px rgba(6, 182, 212, 0.8); } .hud-corner { position: absolute; border-width: 2px; border-color: rgba(6, 182, 212, 0.7); box-shadow: 0 0 5px rgba(6, 182, 212, 0.5); } .glowing-text { text-shadow: 0 0 5px rgba(6, 182, 212, 0.8), 0 0 10px rgba(6, 182, 212, 0.5); } /* Custom scrollbar for log */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: #082f49; } ::-webkit-scrollbar-thumb { background: #0ea5e9; border-radius: 3px; } </style> <script> tailwind.config = { theme: { extend: { colors: { cyan: { 50: '#e0f2fe', 100: '#bae6fd', 200: '#7dd3fc', 300: '#38bdf8', 400: '#0ea5e9', 500: '#0284c7', 600: '#0369a1', 700: '#075985', 800: '#0c4a6e', 900: '#082f49', 950: '#041e3b', }, magenta: { 500: '#d946ef', 600: '#a21caf', }, amber: { 400: '#fbbf24', 500: '#f59e0b', } } } } } </script> </head> <body> <div id="root" class="w-full h-screen relative"> <!-- Scanlines overlay --> <div class="scanlines"></div> <!-- HUD border corners --> <div class="fixed top-0 left-0 w-8 h-8 border-t-2 border-l-2 border-cyan-700 z-10"></div> <div class="fixed top-0 right-0 w-8 h-8 border-t-2 border-r-2 border-cyan-700 z-10"></div> <div class="fixed bottom-0 left-0 w-8 h-8 border-b-2 border-l-2 border-cyan-700 z-10"></div> <div class="fixed bottom-0 right-0 w-8 h-8 border-b-2 border-r-2 border-cyan-700 z-10"></div> </div> <script type="text/babel"> // React code goes here const { useState, useEffect, useRef, useMemo } = React; // Hook for decoding text effect const useDecodingText = (finalText, duration = 2000) => { const [displayed, setDisplayed] = useState(''); const finalTextRef = useRef(finalText); useEffect(() => { finalTextRef.current = finalText; let startTime = null; const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*+-=?'; const step = (timestamp) => { if (!startTime) startTime = timestamp; const progress = timestamp - startTime; if (progress < duration) { const revealCount = Math.floor((progress / duration) * finalTextRef.current.length); let newText = ''; for (let i = 0; i < finalTextRef.current.length; i++) { if (i < revealCount) { newText += finalTextRef.current[i]; } else { newText += chars[Math.floor(Math.random() * chars.length)]; } } setDisplayed(newText); requestAnimationFrame(step); } else { setDisplayed(finalTextRef.current); } }; requestAnimationFrame(step); return () => cancelAnimationFrame(step); }, [finalText, duration]); return displayed; }; // Component for decoding title const DecodingTitle = ({ text, className }) => { const displayed = useDecodingText(text); return <h1 className={className}>{displayed}</h1>; }; // Stats card component with counting numbers const StatsCard = ({ label, value, unit, colorClass }) => { const [count, setCount] = useState(0); const target = useMemo(() => parseInt(value.replace(/,/g, '')) || 0, [value]); useEffect(() => { let start = null; const duration = 2000; const animate = (timestamp) => { if (!start) start = timestamp; const progress = timestamp - start; const percentage = Math.min(progress / duration, 1); setCount(Math.floor(percentage * target)); if (percentage < 1) { requestAnimationFrame(animate); } }; requestAnimationFrame(animate); }, [target]); return ( <div className='border border-cyan-900 p-3 relative bg-cyan-950/20'> <div className='absolute top-0 left-0 w-2 h-2 border-t border-l border-cyan-500'></div> <div className='absolute bottom-0 right-0 w-2 h-2 border-b border-r border-cyan-500'></div> <div className='text-xs text-cyan-300 uppercase'>{label}</div> <div className={`text-2xl font-bold ${colorClass} text-glow`}> {count.toLocaleString()}{unit} </div> </div> ); }; // Three.js Terrain component const Terrain3D = () => { const mountRef = useRef(null); useEffect(() => { const scene = new THREE.Scene(); scene.background = new THREE.Color(0x020617); const camera = new THREE.PerspectiveCamera(60, 1920/1080, 0.1, 1000); camera.position.set(0, 10, 15); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(mountRef.current.clientWidth, mountRef.current.clientHeight); mountRef.current.appendChild(renderer.domElement); // Wireframe terrain plane const planeGeometry = new THREE.PlaneGeometry(30, 30, 30, 30); const positions = planeGeometry.attributes.position; for (let i = 0; i < positions.count; i++) { const x = positions.getX(i); const y = positions.getY(i); const dist = Math.sqrt(x*x + y*y); const z = Math.max(0, 5 - dist) + Math.random() * 0.5; positions.setZ(i, z); } planeGeometry.computeVertexNormals(); const material = new THREE.MeshBasicMaterial({ color: 0x06b6d4, wireframe: true, transparent: true, opacity: 0.6, }); const terrain = new THREE.Mesh(planeGeometry, material); terrain.rotation.x = -Math.PI/2; scene.add(terrain); // Grid helper const gridHelper = new THREE.GridHelper(30, 30, 0x06b6d4, 0x0c4a6e); gridHelper.position.y = -1; scene.add(gridHelper); // Magenta points const pointsGeometry = new THREE.SphereGeometry(0.3, 8, 8); const pointsMaterial = new THREE.MeshBasicMaterial({ color: 0xd946ef }); const points = []; for(let i=0; i<5; i++) { const point = new THREE.Mesh(pointsGeometry, pointsMaterial); point.position.set( (Math.random() - 0.5) * 20, Math.random() * 5, (Math.random() - 0.5) * 20 ); scene.add(point); points.push(point); } const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const animate = () => { requestAnimationFrame(animate); terrain.rotation.z += 0.002; points.forEach((point, idx) => { point.position.y = 1 + Math.sin(Date.now() * 0.001 + idx) * 0.5; }); renderer.render(scene, camera); }; animate(); const handleResize = () => { camera.aspect = mountRef.current.clientWidth / mountRef.current.clientHeight; camera.updateProjectionMatrix(); renderer.setSize(mountRef.current.clientWidth, mountRef.current.clientHeight); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); if (mountRef.current.contains(renderer.domElement)) { mountRef.current.removeChild(renderer.domElement); } }; }, []); return <div ref={mountRef} className='w-full h-full' />; }; // Canvas Chart component - line chart const OutputChart = () => { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr); const width = rect.width; const height = rect.height; // Axes ctx.strokeStyle = 'rgba(6, 182, 212, 0.3)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(40, 10); ctx.lineTo(40, height-30); ctx.lineTo(width-10, height-30); ctx.stroke(); // Data line const data = [20, 35, 30, 45, 40, 55, 50, 65, 60, 75, 70, 80]; const maxVal = 100; const stepX = (width - 50) / (data.length - 1); ctx.strokeStyle = '#fbbf24'; ctx.lineWidth = 2; ctx.shadowColor = '#fbbf24'; ctx.shadowBlur = 5; ctx.beginPath(); data.forEach((val, i) => { const x = 40 + i * stepX; const y = (height - 30) - (val / maxVal) * (height - 50); if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.stroke(); // Points ctx.fillStyle = '#fbbf24'; data.forEach((val, i) => { const x = 40 + i * stepX; const y = (height - 30) - (val / maxVal) * (height - 50); ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fill(); }); // Labels ctx.fillStyle = 'rgba(6, 182, 212, 0.8)'; ctx.font = '10px "JetBrains Mono"'; ctx.shadowBlur = 0; ctx.textAlign = 'center'; for (let i = 0; i < data.length; i+=2) { const x = 40 + i * stepX; ctx.fillText(`20${i+12}`, x, height-15); } // Y axis label ctx.save(); ctx.translate(15, height/2); ctx.rotate(-Math.PI/2); ctx.textAlign = 'center'; ctx.fillText('Production (tons)', 0, 0); ctx.restore(); }, []); return <canvas ref={canvasRef} className='w-full h-full' />; }; // Data log component const DataLog = () => { const logs = [ '> Initializing connection to Tapanuli Central Database...', '> Connection established.', '> Remote scanning of agricultural fields...', '> Detected 12,450 hectares of rice paddies.', '> Coffee plantation analysis: 3,200 hectares.', '> Waterfall sensor network: 5 active nodes.', '> Population census data: 329,000.', '> Infrastructure status: roads 85% operational.', '> Security protocols: active.', '> Awaiting user command...', ]; const [currentLog, setCurrentLog] = useState(0); useEffect(() => { const interval = setInterval(() => { setCurrentLog(prev => (prev + 1) % logs.length); }, 3000); return () => clearInterval(interval); }, [logs.length]); return ( <div className='border-l-2 border-cyan-900 p-2 h-48 overflow-hidden relative bg-cyan-950/10 mt-2'> <div className='absolute top-0 left-0 text-xs text-cyan-500 bg-cyan-900 px-1'>SYSTEM LOG</div> <div className='mt-4 text-xs text-cyan-300 font-mono h-full overflow-y-auto'> {logs[currentLog]} </div> </div> ); }; // Circular progress bar component const CircularProgress = ({ percent, color, label }) => { const radius = 20; const circumference = 2 * Math.PI * radius; const offset = circumference - (percent / 100) * circumference; return ( <div className='flex items-center gap-2'> <div className='relative w-12 h-12'> <svg className='w-full h-full' viewBox='0 0 50 50'> <circle cx='25' cy='25' r={radius} fill='none' stroke='rgba(6, 182, 212, 0.2)' strokeWidth='4' /> <circle cx='25' cy='25' r={radius} fill='none' stroke={color} strokeWidth='4' strokeDasharray={circumference} strokeDashoffset={offset} transform='rotate(-90 25 25)' style={{ transition: 'stroke-dashoffset 1s ease-in-out' }} /> </svg> <div className='absolute inset-0 flex items-center justify-center text-xs font-bold' style={{ color }}> {percent}% </div> </div> <span className='text-xs text-cyan-300'>{label}</span> </div> ); }; // Main App component const App = () => { // Stats data for Tapanuli Tengah (approximate) const stats = [ { label: 'Population', value: '329,000', unit: '', color: 'text-cyan-400' }, { label: 'Area (km²)', value: '3,796', unit: '', color: 'text-magenta-500' }, { label: 'Rice Production', value: '125,000', unit: ' tons', color: 'text-amber-400' }, { label: 'Coffee Output', value: '8,200', unit: ' tons', color: 'text-green-400' }, ]; return ( <div className='w-full h-screen relative p-4 flex flex-col'> {/* Header */} <header className='flex justify-between items-center mb-4 border-b border-cyan-900 pb-2'> <DecodingTitle text='KABUPATEN TAPANULI TENGAH // DATA INTERFACE' className='text-2xl md:text-3xl font-bold text-cyan-300 glowing-text tracking-widest' /> <div className='text-xs text-cyan-500'>SECTOR: AGRICULTURE & CULTURE</div> </header> <div className='flex-1 grid grid-cols-12 gap-4'> {/* Left panel - Stats */} <div className='col-span-3 flex flex-col gap-3'> <div className='text-sm text-cyan-500 mb-2'>KEY INDICATORS</div> {stats.map((stat, idx) => ( <StatsCard key={idx} label={stat.label} value={stat.value} unit={stat.unit} colorClass={stat.color} /> ))} {/* DataLog */} <DataLog /> </div> {/* Center panel - 3D Terrain */} <div className='col-span-6 relative border-2 border-cyan-900/50 rounded overflow-hidden'> <div className='absolute top-2 left-2 text-xs text-cyan-500'>3D TOPOGRAPHIC SCAN</div> <Terrain3D /> {/* Overlay with crosshair */} <div className='absolute inset-0 pointer-events-none flex items-center justify-center'> <div className='w-16 h-16 border border-cyan-500 rounded-full opacity-30'> <div className='absolute inset-0 flex items-center justify-center'> <div className='w-1 h-1 bg-cyan-500 rounded-full'></div> </div> </div> <div className='absolute inset-0 flex items-center justify-center'> <div className='w-32 h-0.5 bg-cyan-500/30 -rotate-45'></div> <div className='w-32 h-0.5 bg-cyan-500/30 rotate-45'></div> </div> </div> </div> {/* Right panel - Charts */} <div className='col-span-3 flex flex-col gap-3'> <div className='border border-cyan-900 p-2 relative bg-cyan-950/20'> <div className='absolute top-0 left-0 text-xs text-cyan-500'>CROP YIELD TRENDS</div> <div className='h-48 mt-4'> <OutputChart /> </div> </div> {/* Additional info panel */} <div className='border border-cyan-900 p-2 relative bg-cyan-950/20 flex-1'> <div className='absolute top-0 left-0 text-xs text-cyan-500'>REGIONAL METRICS</div> <div className='mt-4 space-y-4'> <div> <div className='text-xs text-cyan-300'>COFFEE QUALITY INDEX</div> <div className='w-full h-2 bg-cyan-950 rounded overflow-hidden mt-1'> <div className='h-full bg-amber-400' style={{ width: '78%' }}></div> </div> </div> <div> <div className='text-xs text-cyan-300'>RICE YIELD EFFICIENCY</div> <div className='w-full h-2 bg-cyan-950 rounded overflow-hidden mt-1'> <div className='h-full bg-cyan-400' style={{ width: '85%' }}></div> </div> </div> <div> <div className='text-xs text-cyan-300'>TOURISM SATISFACTION</div> <div className='w-full h-2 bg-cyan-950 rounded overflow-hidden mt-1'> <div className='h-full bg-magenta-500' style={{ width: '92%' }}></div> </div> </div> <div className='pt-2 border-t border-cyan-900'> <div className='flex justify-between items-center'> <span className='text-xs text-cyan-300'>BATAK CULTURE INDEX</span> <CircularProgress percent={94} color='#06b6d4' /> </div> </div> </div> </div> </div> </div> {/* Footer status bar */} <footer className='mt-4 border-t border-cyan-900 pt-2 flex justify-between items-center text-xs text-cyan-500'> <div>SYSTEM STATUS: <span className='text-green-400'>NORMAL</span></div> <div>LOCATION: SIPIROK, TAPANULI TENGAH</div> <div>NETWORK: <span className='text-cyan-300'>CONNECTED</span> | LATENCY: 12ms</div> </footer> </div> ); }; ReactDOM.createRoot(document.getElementById('root')).render(<App />); </script> </body> </html> ``` This code can be saved as an HTML file and opened in any modern browser to view the interactive FUI interface. It requires internet access for the CDN resources (Tailwind, React, Three.js). The interface includes: - A decoding title animation that simulates data decryption. - Stats cards with counting numbers and HUD-style corner brackets. - A 3D wireframe terrain (Three.js) that slowly rotates and contains hovering data points. - A line chart drawn on HTML5 Canvas showing crop yield trends. - A rotating system log panel. - Progress bars and a circular metric for quick data consumption. - Ambient glow effects, scanlines, and a dark cyberpunk theme. The design adheres to the requested FUI principles: intricate grid, dark mode, neon glow, monospace fonts, and dynamic elements.
Thanks to our Virtual Reality technology, we transport you to Central Tapanuli Regency for unique observations.
This feature requires payment.
The data below describes the current air quality at Kabupaten Tapanuli Tengah. Based on the European Air Quality Index (AQI), calculated using the data below, The weather conditions are passable.
| Dust | 0 μg/m³ |
|---|---|
| Carbon Dioxide CO2 | 470 ppm |
| Nitrogen Dioxide NO2 | 6.1 μg/m³ |
| Sulphur Dioxide SO2 | 0.8 μg/m³ |
| Ammonia NH3 | 2.9 μg/m³ |
The data below describes the current weather in Central Tapanuli Regency.
| Temperature | 6.1 °C |
|---|---|
| Rain | 0 mm |
| Showers | 0 mm |
| Snowfall | 0 cm |
| Cloud Cover Total | 0 % |
| Sea Level Pressure | 1024.4 hPa |
| Wind Speed | 3.8 km/h |