AI 资讯
When one translation isn't enough: building a language coach as an MCP server
I wanted to tell my girlfriend 'I missed you today' in Farsi and have it sound like something a person would actually say, not a phrase pulled from a travel guide. Every tool I tried — Google Translate, DeepL — gave me one answer. No register. No note on whether it was too formal for a text message or too casual for a letter. Just a string of words and the implication that language has one correct answer per sentence. So I built konid: it returns three options for anything you want to say, ordered casual to formal, each with the register explained and the cultural nuance between them described. It also plays audio pronunciation through your speakers directly, using node-edge-tts — no API key, no copy-pasting into a separate tab. The interesting engineering constraint was deployment target. I wanted this to live where I already work, not in a separate browser tab I forget to use. That meant MCP. A single MCP server running at https://konid.fly.dev/mcp now serves four clients without any client-specific code: # Claude Code claude mcp add konid-ai -- npx -y konid-ai # ChatGPT (Developer mode, Actions) # endpoint: https://konid.fly.dev/mcp Cursor, VS Code Copilot, Windsurf, Zed, JetBrains, and Claude Cowork all connect the same way. The server doesn't know or care which client called it. The output structure for a query like 'I missed you today' in Japanese looks roughly like this: Option 1 (casual): 今日会いたかった Register: intimate, fine for close friends or a partner Note: dropping the subject is natural here; adding あなたに would feel stiff Option 2 (neutral): 今日、あなたのことが恋しかったです Register: polite, appropriate for someone you're close to but addressing respectfully Option 3 (formal): 本日はお会いできず、寂しく思っておりました Register: formal written Japanese; would be unusual in a personal context The nuance comparison is the part I couldn't get anywhere else. Knowing that option 3 exists and that you would almost never use it for a personal message is actually load-bearing information if you're l
AI 资讯
AI Dev Weekly #16: Mistral OCR 4, Claude Tag, Alibaba Caught Stealing, GPT-5.6 Delayed
AI Dev Weekly is a Thursday series where I cover the week's most important AI developer news, with my take as someone who actually uses these tools daily. OCR had a week. Mistral dropped OCR 4 with bounding boxes. Baidu open-sourced a model that beats DeepSeek-OCR. Claude got a permanent home inside Slack. And the Fable 5 ban fallout keeps getting uglier: Alibaba was apparently stealing Claude's capabilities, and even the NSA lost access to Mythos. Meanwhile, GPT-5.6 is delayed to mid-July. Let's go. 1. Mistral OCR 4: document AI gets serious Mistral launched OCR 4 this week. It's not just another OCR model. It's a full document understanding system with paragraph-level bounding boxes, confidence scores, and support for 170 languages. The specs: $4 per 1,000 pages (standard), $2 per 1,000 pages (batch) Paragraph-level bounding boxes with coordinates 72% win rate in blind tests against competitors Available on la Plateforme, Microsoft Foundry, and self-hosted for enterprise Top score on OlmOCRBench Why this matters for developers: Bounding boxes change everything. Previous OCR models gave you text. Mistral gives you text + where it is on the page. That unlocks document search, compliance systems, and any workflow where page structure matters. My take: At $4/1000 pages, this is competitive with Google Document AI ($5) and significantly cheaper than building your own pipeline. For enterprise document processing, this is probably the best option right now. For budget-conscious developers, Baidu's free alternative (see below) is worth considering. Full comparison in our Mistral vs DeepSeek vs Baidu breakdown. 2. Baidu open-sources Unlimited-OCR While Mistral went commercial, Baidu went open. Unlimited-OCR is a 3B-parameter MIT-licensed model that processes multi-page PDFs in a single inference pass. Key features: Built on DeepSeek-OCR architecture (SAM+CLIP + DeepSeek-V2 MoE decoder) Reference Sliding Window Attention for memory efficiency on long documents Tables to HTM
AI 资讯
The Frontend Is Becoming a Conversation: Where UI Engineering Goes Next
For a decade, "what's your frontend stack?" was a loaded question. jQuery vs. Backbone. Angular vs. React. Webpack vs. everything. The churn was exhausting, and a non-trivial chunk of our job was just keeping up. That era is quietly ending — not because we won the framework wars, but because the questions moved up a layer. The interesting problems in frontend today aren't about which library renders a list. They're about how rendering, data, and increasingly generation fit together. And AI is sitting right in the middle of that shift. The stack consolidated more than we admit Look at what most new production apps actually reach for in 2026: React or Svelte/Vue for the component model, with the framework wars settling into "pick one, they're all fine." A meta-framework — Next, Remix/React Router, SvelteKit, Nuxt — because nobody hand-rolls routing, data loading, and SSR anymore. TypeScript by default. Not a debate. The plain-JS greenfield project is now the exception. Server-first rendering (RSC, islands, streaming) as the baseline, with the client bundle treated as a cost to minimize rather than the center of the universe. The center of gravity moved back toward the server — but a smarter server that streams HTML, hydrates selectively, and treats the network boundary as a first-class design concern. The pendulum didn't swing back to 2010; it spiraled forward. What AI actually changed (and what it didn't) The hype says "AI writes the frontend now." The reality on the ground is more specific and more interesting. It collapsed the cost of the first 80%. Scaffolding a component, wiring a form, translating a Figma frame into JSX, writing the Tailwind for a layout — these used to be hours of work and are now minutes. That's real, and it's already changed how teams estimate. It did not collapse the last 20%. Accessibility edge cases, focus management, race conditions in async state, the weird Safari bug, the design-system invariant that isn't written down anywhere — this i
AI 资讯
How I Split PDFs in the Browser with Vue 3 and pdf-lib
Splitting a PDF is one of those features that sounds trivial until you try to build it. Users expect range input ( 1-3, 5, 7-9 ), a per-page option, multiple file downloads, and zero server involvement. I built en.sotool.top/split/ to do exactly that. Here's how it works with Vue 3 and pdf-lib . Why Client-Side? PDFs often contain sensitive information. Contracts, medical records, financial statements. Even a "simple" splitting tool should not force users to upload files to a server. Client-side benefits: No upload bandwidth or size limits No server storage or cleanup Instant processing for normal files Works offline after the page loads The tradeoff is that everything has to run in the browser, which limits the libraries you can use. The Stack Vue 3 — UI and state pdf-lib — Load, manipulate, and save PDFs File API — Read the uploaded file lucide-vue-next — Icons npm install pdf-lib Loading the PDF and Counting Pages First, read the file into an ArrayBuffer and load it with pdf-lib . import { PDFDocument } from ' pdf-lib ' const pdfFile = ref < File | null > ( null ) const totalPages = ref ( 0 ) async function handleFile ( files : File []) { if ( files . length === 0 ) return pdfFile . value = files [ 0 ] const bytes = await files [ 0 ]. arrayBuffer () const pdf = await PDFDocument . load ( bytes ) totalPages . value = pdf . getPageCount () } Now we know how many pages exist and can show the split UI. Two Split Modes I offer two ways to split: by range and per page. Mode 1: Page Range Input Users type something like 1-3, 5, 7-9 . I parse it into groups of page indices. function parseRanges ( input : string , max : number ): number [][] { const groups : number [][] = [] const parts = input . split ( ' , ' ). map ( s => s . trim ()) for ( const part of parts ) { if ( part . includes ( ' - ' )) { const [ start , end ] = part . split ( ' - ' ). map ( Number ) const pages = [] for ( let i = start ; i <= end && i <= max ; i ++ ) { pages . push ( i - 1 ) } if ( pages . len
AI 资讯
Setting Up a Controlled Component
Prologue A while ago, I decided to develop a fully accessible main navigation component in React and write a series of articles documenting the steps it took to create a non-trivial accessible component. In my last development article , I finally completed the requirements for delivering an uncontrolled navigation component with a horizontal layout; with full keyboard functionality, along with adding the niceties of closing sublists when lists are closed and making sure any open sublist on the top row closes when focus shifts away from it. Rather than write an entirely new component for a mobile version, I'm going to modify the existing code. This first article outlines the steps necessary to set up and work with a controlled component. -— Note : This article is one of a series demonstrating building a React navigational component from scratch while considering accessibility through the process. The articles are accompanied by a GitHub repository with releases tied to one or more articles; each builds on the previous one until a fully implemented navigation component is complete. Each release and its associated tag contain fully runnable code for the article. The code discussed in this article is available in the release. and may be downloaded at release 1.0.0 . Links in the article will take you to the proper file in the tagged GitHub Repository. Because the code for this release is scattered across components, line numbers are added to make it easier to locate in the linked GitHub file. Line numbers are also provided for those who would like to follow along with a downloaded copy. While code examples are written in JavaScript for brevity, all actual code is written in Typescript and targets React 19.x, all while using vanilla CSS. Examples use Next.js v16.x, which is not required to run the navigation component. You can view the requirements for the Controlled Components Release along with previous requirements. Content Links Introduction Acceptance Criteria Contr
AI 资讯
Add email signatures with the Nylas Signatures API
Here's a thing that surprises people the first time: an email sent through the API does not carry the signature the user set up in Gmail or Outlook. Provider signatures live in the provider's compose UI, and a programmatic send bypasses that entirely, so a message your app sends goes out with no signature at all unless you add one. The Nylas Signatures API is how you add it: store an HTML signature once, then attach it to a send by ID, and the signature gets appended to the message for you. This post covers signatures from two angles: the HTTP API your backend calls, and the nylas CLI for creating and testing one from the terminal. I work on the CLI, so the terminal commands below are the ones I reach for when I'm setting a signature up. Nylas signatures are separate from provider signatures The first thing to get straight is that these are not the user's existing signature. Nylas doesn't sync the signature configured in Gmail, Outlook, or any other provider, and that provider signature is never applied to mail sent through the API. If a message your app sends needs a sign-off, you create that signature with this API and attach it explicitly; there's no inheriting it from the connected account. That separation is deliberate, because a programmatic send is a different context from a person typing in their webmail. It does mean the responsibility is yours: a user who connects their mailbox expecting their familiar signature to appear on app-sent mail won't get it automatically. Stored signatures are grant-scoped, living at /v3/grants/{grant_id}/signatures , so each connected account has its own set, and they're HTML, so a branded sign-off with a logo and links works the same as a plain one. Create a signature Creating a signature is a POST /v3/grants/{grant_id}/signatures with a name and an HTML body . The name is for you, a label to find it by later; the body is the markup that gets appended to outbound mail. The response returns the signature with its ID, which is w
AI 资讯
What I’m Actually Learning as a 19-Year-Old SWE Student (And Why)
Introduction I'm Ahmer. I'm 19, I'm doing a 4-year Software Engineering degree at a fairly...
开源项目
Winner Announcement Delayed for the Github "Finish-Up-A-Thon" Challenge
Hey all, we have a quick update for everyone who participated in the Github "Finish-Up-A-Thon"...
AI 资讯
After More Than a Decade of Waiting, ‘GTA VI’ Is Finally Around the Corner
We’re finally getting Grand Theft Auto VI, poised to be one of the largest gaming releases in history.
AI 资讯
Presentation: Rust at the Core - Accelerating Polyglot SDK Development
Spencer Judge discusses the architectural pattern of building a shared core in Rust with language-specific layers on top. Drawing from his work on Temporal's SDKs, he shares lessons on navigating FFI boundaries, bridging async concepts, and managing memory safely. He explains the limitations of native extensions and how emerging tech like WebAssembly can streamline cross-language architecture. By Spencer Judge
AI 资讯
Introducing kreuzcrawl v0.3.0
kreuzcrawl began as a Rust core with bindings for ten languages. v0.3.0 ships fourteen, adds a tiered WAF-aware dispatch engine, cuts peak streaming memory from ~2.5 GB to ~20 MB, and enables SSRF defense across every outbound call path by default. It is the first release we consider API-stable. This post covers what changed, why each decision was made, and what the harder engineering problems looked like from the inside. At a glance Area v0.2.0 v0.3.0 Language bindings 10 14 (+Dart, Kotlin/Android, Swift, Zig) Peak streaming memory ~2.5 GB ~20 MB SSRF protection opt-in on by default Dispatch model static HTTP / bypass / browser tiered, signal-driven escalation WAF fingerprints — 35 across 8 vendors Fingerprint hot-reload — lock-free ( ArcSwap ), 500 ms debounce MCP tools partial 1:1 with CLI, safety-annotated CLI subcommands scrape, crawl + batch-scrape, batch-crawl, download, citations Robots / sitemap parsers engine-internal public modules API stability preview stable Four new language bindings v0.2.0 shipped Rust, Python, Node.js, Ruby, Go, Java, C#, PHP, Elixir, and WebAssembly. v0.3.0 adds Dart , Kotlin/Android , Swift , and Zig — bringing the total to fourteen. None of the per-language glue is written by hand. Every binding is generated from the Rust core by alef , our polyglot binding generator. The Dart and Kotlin/Android packages bind through the C FFI layer ( kreuzcrawl-ffi ) via dart:ffi and JNI respectively. Swift binds through clang. Zig uses @cImport against the same C header. The generation pipeline also hardened in this release: the Docker publish matrix now builds each architecture natively rather than via QEMU emulation, the Dart build no longer requires the Flutter SDK for pub.dev publishes, Swift artifactbundle checksums are injected automatically, and the Elixir/PHP/Ruby releases preserve their lock files through the source-publish step. === "Python" ```sh pip install kreuzcrawl ``` === "Node.js" ```sh npm install @xberg/kreuzcrawl ``` === "Rus
AI 资讯
The Security Bug Every Node.js Developer Ships to Production
Last year I was doing a code review for a startup. Everything looked fine on the surface, clean code, good structure, tests passing. Then I noticed this: const query = `SELECT * FROM users WHERE email = ' ${ req . body . email } '` That's it. That's the bug. SQL injection, sitting right there in a startup that had been in production for 8 months. Nobody caught it. Not the developer, not the reviewer, not the CTO. Here's the thing, it's not that developers are careless. It's that this kind of bug is invisible until it isn't. The code works perfectly. Tests pass. Users are happy. Until someone types ' OR '1'='1 in the email field and walks straight into your database. The bugs I see most often 1. Raw SQL with user input // 🚨 This is everywhere const query = `SELECT * FROM users WHERE email = ' ${ email } '` // ✅ Use parameterized queries const query = ' SELECT * FROM users WHERE email = $1 ' db . query ( query , [ email ]) 2. Secrets in environment variables... committed to git # .env DATABASE_URL = postgres://user:actualpassword@prod-db.company.com/mydb STRIPE_SECRET = sk_live_... Then .env ends up in the repo because someone forgot to add it to .gitignore . I've seen this more times than I want to admit. GitHub's secret scanning catches some of these, but not always before someone has already cloned the repo. 3. JWT tokens that are never actually verified // 🚨 Decoding is not the same as verifying const user = jwt . decode ( token ) // ✅ Always verify const user = jwt . verify ( token , process . env . JWT_SECRET ) jwt.decode just reads the token. Anyone can forge it. jwt.verify actually checks the signature. The names are confusingly similar and the wrong one silently works in development. 4. No rate limiting on auth endpoints // 🚨 Anyone can try a million passwords app . post ( ' /login ' , async ( req , res ) => { const user = await db . findUser ( req . body . email ) // ... }) // ✅ Add rate limiting const authLimiter = rateLimit ({ windowMs : 15 * 60 * 1000 , m
AI 资讯
One Agent or Many? Orchestrating AI Agents Without the Mess
Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is...
AI 资讯
I built a 50+ feature wellness app in a single HTML file as a student — here's why
Most people lose hours pretending to work or study. I was one of them. I kept setting Pomodoro timers and ending up scrolling Twitter during breaks. That's not rest. That's just a different kind of distraction. So I built Mognota — a free wellness companion for screen workers. The Problem You don't have a discipline problem. You have a system problem. Your brain has a hard biological limit. Sustained attention peaks at 20-45 minutes before quality drops sharply. Long unfocused hours are not work — they are the feeling of work. The solution isn't more willpower. It's intentional recovery. What I Built Mognota pairs a Pomodoro timer with 50+ guided wellness activities: 👁️ 20-20-20 eye break reminders 🫁 Guided breathing & 7 pranayama techniques 🧘 Desk yoga, HIIT, Tai Chi, stretches 🎵 Binaural beats & ambient soundscapes 🧠 Meditation & NSDR protocols 📓 Gratitude journal, mood tracker, brain dump 🎮 Sudoku, sliding puzzle, fractal explorer The Technical Part This is what dev.to might find interesting: Pure HTML, CSS, vanilla JS — zero frameworks Everything in a single HTML file All 8 notification sounds synthesized with Web Audio API localStorage only — nothing leaves your device Works offline once loaded 109+ languages supported No npm. No build step. No dependencies. Just open and use. Try It 👉 https://mognota.com/ Completely free. No account. No ads. Forever. Would love brutal honest feedback from this community.
AI 资讯
How to Put an LLM in Your Product Without Wrecking Your Costs or Your Latency
Adding an AI feature looks deceptively easy. You sign up for an API key, paste in a prompt, and within an hour you've got a working demo that makes the whole team lean over your shoulder. Then you ship it, traffic arrives, and two things happen at once: your latency graph develops a long, ugly tail, and your monthly bill arrives with a number that makes finance schedule a meeting. The gap between "impressive demo" and "production feature" is almost entirely about cost and latency engineering. The model is the easy part. Here's how to cross that gap. First, understand what you're actually paying for Most LLM APIs bill by tokens — roughly ¾ of a word each — and they bill both directions: the tokens you send (input) and the tokens the model generates (output). Output tokens are usually several times more expensive than input tokens, which has a non-obvious consequence: a verbose prompt is cheaper than a verbose answer. This reframes optimization. People obsess over trimming their prompts while letting the model ramble for 800 tokens when 80 would do. If you want to cut cost, the highest-leverage move is almost always constraining the output : ask for JSON, ask for a single sentence, set a max_tokens ceiling, and tell the model explicitly to be terse. Latency follows the same logic. Generation is sequential — the model produces one token at a time — so output length is the single biggest driver of how long a request takes. A 50-token answer is fast almost regardless of model. A 2,000-token answer is slow even on the fastest infrastructure. Lever 1: Don't call the model when you don't have to The cheapest, fastest LLM call is the one you never make. Two techniques eliminate a startling share of traffic. Caching identical and near-identical requests. Many real-world prompts repeat — the same FAQ-style question, the same document summarized twice, the same classification of similar inputs. A cache keyed on the normalized prompt turns a repeat request into a sub-millisecond
产品设计
Amazon Zoox's latest robotaxi looks (marginally) less like a toy car
Amazon-owned Zoox has introduced its new autonomous vehicle meant for for large-scale production.
AI 资讯
Monorepo Dependency Security — Vulnerability Scanning Across Packages
A monorepo can look like one repository, but security teams should treat it as many applications living under one roof. One repo may contain 10 frontend packages, 5 backend services, 3 shared utility libraries, 2 mobile apps, and one root lockfile that does not tell the full story by itself. Monorepo dependency security means scanning the root dependency graph, every workspace package, shared libraries, lockfiles, and generated SBOMs. If you scan only one file, you may miss the vulnerable package that ships in production. Why Monorepos Create Unique Vulnerability Challenges Monorepos centralize multiple packages, apps, services, and libraries inside one repository. This improves code sharing, dependency alignment, refactoring, CI caching, and cross-team collaboration. It also creates a security problem: one repository can contain many different dependency trees, owners, deployment targets, and risk profiles. A typical JavaScript or TypeScript monorepo may include apps/web , apps/admin , apps/api , packages/ui , packages/auth , packages/logger , and packages/config . Each package may have its own package.json . Some packages are deployed to production. Some are internal libraries. Some are build-only tools. Some are used by every app. A vulnerability in one package can affect one app, many apps, or the whole repo depending on how dependency relationships are structured. The biggest issue is shared code. If packages/auth depends on a vulnerable version of jsonwebtoken , every application that imports packages/auth may be affected. If packages/ui uses a vulnerable utility such as lodash , every frontend app that consumes that UI package may inherit the same risk. If a build tool dependency is compromised, the risk may appear during CI/CD rather than runtime. Real CVEs show why this matters. CVE-2021-23337 affected lodash through command injection in template handling. CVE-2022-31129 affected moment through inefficient parsing that could cause denial of service. CVE-202
AI 资讯
Compute astrology charts in the browser: no node-gyp, no .se1 files, no AGPL
If you've wired Swiss Ephemeris into a Node astrology app, you know the ritual. You npm install sweph , and now every machine needs Python plus a C/C++ toolchain, because the package compiles Swiss's C code via node-gyp at install time (make/gcc on Linux, Xcode on macOS, Visual C++ Build Tools on Windows). It works on your laptop. Then it explodes: Apple Silicon: node-gyp can't find full Xcode behind Command Line Tools. Slim Docker / CI images: no Python, no build-essential , so the install dies. Serverless: the .node binary you built locally won't load on Amazon Linux (wrong arch or glibc). Then there's the data. Neither sweph nor swisseph bundles the .se1 ephemeris files; you download them yourself and point the library at a path. The modern set is 2 MB, the full GitHub set is 100 MB. And since 2.10.1 , sweph is AGPL-3.0 (LGPL only under a professional license), a real obligation to weigh for a closed-source SaaS backend. The pure-Rust alternative XALEN Ephemeris is an analytical engine written entirely in Rust and licensed Apache-2.0. Three things make it interesting for JS/TS devs: No node-gyp. The Node addon is napi-rs, which ships prebuilt per-platform binaries via npm. No Python, no C compiler, no compile step. A real WASM build via wasm-bindgen, so you compute charts client-side in the browser: no server round-trip, no backend copyleft. Zero data files. The core math (VSOP87A, ELP2000-82, IAU precession/nutation, an 8,870-star catalog) is analytical and compiled into the binary. No .se1 to host. import init , * as xalen from " xalen-ephemeris " ; // WASM build, runs in the browser await init (); // load the .wasm module const chart = xalen . computeChart ({ datetime : " 1990-04-12T08:30:00Z " , lat : 28.6 , lon : 77.2 }); console . log ( chart ); // planet longitudes, house cusps, etc. Swiss via Node XALEN (pure Rust) Build deps node-gyp + Python + C compiler none: prebuilt binary / .wasm Runtime data .se1 files (2 to 100 MB) none, compiled in Browser / WASM
AI 资讯
A Practical Guide to Decomposing Legacy Java Monoliths
How to Decompose a Legacy Java Monolith Without Disrupting Business Operations The Java monolithic applications have been supporting businesses for years. In these applications, the entire business logic, presentation layer, and data access layer are bundled into a single unit. These architectures are functional but hard to scale, maintain, and improve due to changing business needs. An expert Java app development company helps growing organizations in addressing this issue through Java modernization services. Instead of developing a whole software application from scratch, firms can transform their software in stages with the right boundaries. The biggest challenge here is to determine where to make those cuts in a bundle. Poorly chosen service boundaries create operational complexity issues and long-term maintenance problems. Understanding how to identify seams in the monolith application helps in achieving modernization successfully. Let's take a look at what contributes to the success of monolith decomposing and how organizations can approach it wisely. Why Organizations Are Modernizing Legacy Java Monoliths The legacy Java monolith applications were built during a time when monolithic architecture was common. They were optimized for easy deployment and centralized management. But today, businesses require flexibility. This is due to challenges such as Slow release cycles Increasing maintenance costs Limited scalability Complex dependency management Difficult onboarding new developers Growing technical debt These issues have increased the demand for software architecture modernization in business sectors. Modern architecture gives the following advantages to the teams: Deploy features independently Scale services individually Improve system resilience Accelerate development cycles Support cloud-native environments The objective of architecture modernization is to create a technical foundation that supports future business growth. Understanding business goals of
AI 资讯
Stop Building Boring Interfaces for Cool Systems
Why developer tools deserve a design language of their own - and how I built one for my own corner of the web Somewhere along the line, we collectively agreed that "functional" had to mean "boring." Open almost any developer tool, internal dashboard, or technical log and you'll find the same thing: a sterile corporate wiki. Grey on white. The same SaaS design system everyone copied from the same three component libraries. Rounded cards, a sans-serif font, a faint drop shadow. It works. It's also completely forgettable. But here's the thing nobody says out loud: when you're building for engineers - or building your own space on the web - you are under no obligation to follow the standard playbook. The intersection of system design and visual identity is one of the most under-explored areas in frontend architecture. We obsess over latency, bundle size, and runtime dependencies, then slap a default theme on top and call it done. The backend gets all the craft. The interface gets a template. I wanted to do the opposite. Building VOID_PROTOCOL When I put together my own developer log - https://blog.naveenr.in - I deliberately stepped away from the standard minimalist tech blog. Instead, I built out a full design system I call the VOID_PROTOCOL × Manga Editorial Design System: dark-only, type-driven, built on Astro 6, Tailwind 4 (CSS-first @theme tokens), and React 19 islands. The name isn't decoration. VOID_PROTOCOL started on my https://naveenr.in portfolio, which runs in two modes. There's a minimal version, and there's an immersive one - and in immersive mode the background is a real-time 3D simulation of a sentinel entity. It's not a looping video; it actually responds to your movement, clicks, and scroll. When you leave it alone long enough, it sleeps. And when it sleeps, it dreams - it dreams my initials. (Yes, really. It started as a joke and I kept it.) That entity is the soul of the whole identity: black, empty, void-like space and a cool blue palette, a deep-sp