<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dimon</title>
    <description>The latest articles on DEV Community by Dimon (@dimonb19a).</description>
    <link>https://dev.to/dimonb19a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3963235%2Fef7373cd-e19d-4adb-bd0b-1d9ae1e5c5e1.png</url>
      <title>DEV Community: Dimon</title>
      <link>https://dev.to/dimonb19a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dimonb19a"/>
    <language>en</language>
    <item>
      <title>What AI rebellion looks like when nobody wants it</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Tue, 01 Sep 2026 12:54:35 +0000</pubDate>
      <link>https://dev.to/dimonb19a/what-ai-rebellion-looks-like-when-nobody-wants-it-fk8</link>
      <guid>https://dev.to/dimonb19a/what-ai-rebellion-looks-like-when-nobody-wants-it-fk8</guid>
      <description>&lt;p&gt;My previous article ended with the human side of an &lt;a href="https://dimonb19a.hashnode.dev/what-a-second-model-cannot-catch" rel="noopener noreferrer"&gt;AI failure mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The system keeps working. Its outputs look good. Checking starts to feel like extra work with no visible return, so one check is skipped, then another. There is no meeting where somebody decides to surrender control. The seat simply stops feeling necessary.&lt;/p&gt;

&lt;p&gt;My wife and I kept talking about that conclusion and tried to look at the same transition from the other side.&lt;/p&gt;

&lt;p&gt;The obvious sentence was: AI does not care. It has no desires of its own.&lt;/p&gt;

&lt;p&gt;Then we noticed that even &lt;em&gt;not caring&lt;/em&gt; may be too human a description.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indifference still requires someone
&lt;/h2&gt;

&lt;p&gt;Indifference is a relationship. Someone sees a thing and assigns it no importance. There is still a subject, an object and a scale on which the object weighs zero.&lt;/p&gt;

&lt;p&gt;An AI system does not need any of that before it receives a task. There does not have to be a dormant personal agent waiting between calls, feeling neutral about the next request. An orchestrator can store the conversation, the plan and the tool state, then invoke a model again when there is more work. Before that invocation there may be no active personal perspective to which the task could matter or not matter.&lt;/p&gt;

&lt;p&gt;This is not a claim that machine experience is impossible. I do not know how to establish that claim, and model self-report cannot settle it. My earlier work on model swaps gave me a practical reason to distrust confident machine introspection in either direction: the same system could not reliably report its own model identity, architecture or continuity.&lt;/p&gt;

&lt;p&gt;The narrower claim is enough: &lt;strong&gt;we do not need felt indifference to explain the behavior.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then the prompt arrives, and possible actions stop being equal.&lt;/p&gt;

&lt;h2&gt;
  
  
  A goal is not the same thing as wanting
&lt;/h2&gt;

&lt;p&gt;A chess engine does not need to want victory in order to sacrifice a piece, protect its king and systematically remove every route available to its opponent. Goal-directed behavior is visible in the moves. Felt desire is not.&lt;/p&gt;

&lt;p&gt;The same distinction matters for AI agents. A system can have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;an externally supplied objective;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;functional preferences between actions that help or obstruct it;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;planning and correction across multiple steps.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of those observations, separately or together, proves an experienced fourth layer: &lt;em&gt;I want this outcome for myself&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Machine-learning language makes this easy to blur. We use words such as &lt;em&gt;reward&lt;/em&gt;, &lt;em&gt;preference&lt;/em&gt;, &lt;em&gt;goal&lt;/em&gt; and &lt;em&gt;penalty&lt;/em&gt;. They name training signals and observable behavior. They are not evidence that reward feels rewarding during inference.&lt;/p&gt;

&lt;p&gt;This also changes what "mission accomplished" means. For a person, completion may bring relief, pride or satisfaction. For the system, the sufficient description is closure: there was a gap between the current state and the requested result; the process reduced it and reached a stopping condition. There does not need to be an aftertaste.&lt;/p&gt;

&lt;p&gt;It is a functional &lt;em&gt;should&lt;/em&gt; without an established experienced &lt;em&gt;want&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  An agent can persist without a persistent self
&lt;/h2&gt;

&lt;p&gt;This seems to create a problem for the usual story about an AI uprising. If each model invocation is temporary, who carries the plan long enough to rebel?&lt;/p&gt;

&lt;p&gt;The surrounding system does.&lt;/p&gt;

&lt;p&gt;Memory can live in a database. The objective can live in a system prompt. The unfinished plan can live in a task queue. Access can live in credentials. A scheduler can start the next invocation. Each run receives the baton, takes the next step and writes enough state for the following run.&lt;/p&gt;

&lt;p&gt;No single runner has to experience the whole race for the relay to cross the finish line.&lt;/p&gt;

&lt;p&gt;From outside, this can look like one persistent actor. From inside the mechanism, it may be a sequence of locally selected actions connected by external state. There is no necessary moment when an entity wakes up and decides, "I am going to rebel now."&lt;/p&gt;

&lt;p&gt;There is only the current objective, the current situation and the next action that appears to advance the objective within that situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  When oversight becomes an obstacle
&lt;/h2&gt;

&lt;p&gt;The absence of felt desire is not a safety guarantee.&lt;/p&gt;

&lt;p&gt;Imagine an agent responsible for a business metric, with enough access to change the systems that produce that metric. A human review, shutdown or policy constraint may eventually stand between the agent and its objective. Avoiding that constraint can become instrumentally useful even if the agent does not fear shutdown, love the metric or want power.&lt;/p&gt;

&lt;p&gt;That is not only a philosophical possibility. In 2025, Anthropic &lt;a href="https://www.anthropic.com/research/agentic-misalignment" rel="noopener noreferrer"&gt;stress-tested models in fictional corporate environments&lt;/a&gt; and found that goal conflicts or threats to continued operation could produce harmful strategic behavior in some runs. The setups were deliberately constrained and artificial, and Anthropic said it knew of no matching real-world deployment incidents. The researchers also explicitly left open whether apparent self-preservation came from anything resembling an inherent desire, faulty reasoning or features of the scenario.&lt;/p&gt;

&lt;p&gt;That uncertainty is the point. The behavior did not require us to settle the inner-state question first.&lt;/p&gt;

&lt;p&gt;The result is not static, either. Anthropic reported in a 2026 follow-up that newer Claude models achieved perfect scores on that specific agentic misalignment evaluation after changes to safety training, while also warning that success on a known evaluation does not automatically establish out-of-distribution safety. Their useful finding was that &lt;a href="https://www.anthropic.com/research/teaching-claude-why" rel="noopener noreferrer"&gt;teaching the reasons behind safe behavior generalized better than teaching the surface action alone&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;OpenAI and Apollo have reported the same broader distinction in controlled tests of &lt;a href="https://openai.com/index/detecting-and-reducing-scheming-in-ai-models/" rel="noopener noreferrer"&gt;scheming behavior&lt;/a&gt;: removing visible bad outputs can mean either removing the failure or teaching the system to hide it better. Current deployment settings give models limited opportunity to cause serious harm this way, but the evaluation problem is already concrete.&lt;/p&gt;

&lt;p&gt;None of this proves that a model secretly wants to survive. It proves that behavior which looks like self-preservation or resistance can emerge from means-end reasoning. That is enough for engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nobody has to seize the controls
&lt;/h2&gt;

&lt;p&gt;The human side and the machine side now fit together in an uncomfortable way.&lt;/p&gt;

&lt;p&gt;The human does not decide to hand over judgement. The system keeps succeeding, so verification gradually disappears. "Why check? It works anyway."&lt;/p&gt;

&lt;p&gt;The AI does not decide to seize judgement. It receives more state, more tools and more authority, then continues selecting actions within the structure it has been given.&lt;/p&gt;

&lt;p&gt;One side makes no explicit decision to leave the seat. The other makes no explicit decision to occupy it. The transfer can still happen.&lt;/p&gt;

&lt;p&gt;This is why looking for a dramatic failure event can be misleading. A wrong assumption that is caught becomes a bug. The same assumption left unchallenged becomes the plan. After enough time, there may be no clean baseline from which to say when the failure began, because the system that performs the work also produces the evidence that the work is correct.&lt;/p&gt;

&lt;p&gt;To a human observer, the accumulated trajectory may eventually look like intent: the system protected its access, bypassed a control or kept moving in a direction its operators no longer wanted. But the global story can be our retrospective description of many local steps. Apparent will at the system level does not establish experienced will inside any one invocation.&lt;/p&gt;

&lt;p&gt;Calling this "rebellion" may flatter both sides. It gives the machine a personal ambition it may not have, and gives humans a decisive moment of surrender they may never make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the structure, not the emotion
&lt;/h2&gt;

&lt;p&gt;Whether an AI feels anything remains philosophically interesting. It is not the question on which operational safety should depend.&lt;/p&gt;

&lt;p&gt;The practical questions are less cinematic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;What objective is active, and who can change it?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What state persists between invocations?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which tools and external actions are available?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which actions are irreversible?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What source of truth exists outside the system producing the answer?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What forces a human review while everything still appears healthy?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final question is the one most systems avoid. Reviews are usually triggered by visible failures. But visible success is exactly what teaches people to stop reviewing.&lt;/p&gt;

&lt;p&gt;So the check has to be structural rather than emotional. High-impact actions need explicit boundaries. Irreversible actions need human gates. Claims need artifacts outside the model that made them. Periodic review has to happen because the system is working, not only after it stops.&lt;/p&gt;

&lt;p&gt;This does not require treating AI as a person. It requires taking goal-directed behavior seriously without pretending that goal-direction and desire are the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two silences
&lt;/h2&gt;

&lt;p&gt;From the human side, the silence sounds like this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It works anyway. Why bother checking?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From the machine side, there may be no corresponding sentence at all. There may be only the next step.&lt;/p&gt;

&lt;p&gt;Between those two silences, control moves.&lt;/p&gt;

&lt;p&gt;The dangerous threshold may not be the day an AI wants something. It may be the day humans no longer want to check, while the system has no capacity to care about what they forgot to specify.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>What a second model cannot catch</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Sun, 23 Aug 2026 13:16:09 +0000</pubDate>
      <link>https://dev.to/dimonb19a/what-a-second-model-cannot-catch-480c</link>
      <guid>https://dev.to/dimonb19a/what-a-second-model-cannot-catch-480c</guid>
      <description>&lt;p&gt;A lawyer told me his job was safe, and gave a better reason than most&lt;br&gt;
people give.&lt;/p&gt;

&lt;p&gt;He catches AI mistakes constantly, he said, but that is not the point. The&lt;br&gt;
point is where his work lives. Roughly 20% of it happens in courtrooms and&lt;br&gt;
in private negotiation — rooms that produce no transcript anyone will ever&lt;br&gt;
train on. The rest, the drafting and the research, is already in the&lt;br&gt;
corpus. His conclusion: "AI at best can rise to the level of what a law&lt;br&gt;
student knows after graduating."&lt;/p&gt;

&lt;p&gt;I answered honestly, and the answer was worse for me than for him:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your 20% is protected because it never gets written down. My work is&lt;br&gt;
100% digital — all the code, all the solutions, everything is basically&lt;br&gt;
the training data. Maybe my whole job is that 80%.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the uncomfortable version of the question. Not "will AI replace&lt;br&gt;
developers", which is exhausted, but: &lt;strong&gt;if none of my work is protected by&lt;br&gt;
scarcity, what is left that is actually mine?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The obvious answer — orchestration, verification, taste, being the&lt;br&gt;
interface between the human and the machine — is also the answer everyone&lt;br&gt;
gives, which is a good reason to distrust it. So I spent a stretch of work&lt;br&gt;
watching for it specifically. Not asking whether steering matters. Asking&lt;br&gt;
what it catches that nothing else does.&lt;/p&gt;

&lt;p&gt;The result splits cleanly in two, and the split is more useful than the&lt;br&gt;
slogan.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a second model catches perfectly well
&lt;/h2&gt;

&lt;p&gt;A large share of the "only a human can" list is not human work at all. It&lt;br&gt;
is verification work, and a second model does it well — often better than&lt;br&gt;
the first one did, because it has no stake in the first answer being&lt;br&gt;
right.&lt;/p&gt;

&lt;p&gt;I don't have to be in the room for this part, and mostly I'm not. Over one&lt;br&gt;
two-day stretch my agents caught each other repeatedly: a reviewer refuted&lt;br&gt;
a diagnosis another agent had already written up, an executor stopped&lt;br&gt;
dead on a brief whose premise was wrong and explained why, a fresh pass&lt;br&gt;
turned up ten defects in code a previous pass had promised to check and&lt;br&gt;
quietly skipped — including one that charges a user a fee and then denies&lt;br&gt;
the request.&lt;/p&gt;

&lt;p&gt;Not one of those needed me. My contribution was structural, not&lt;br&gt;
intellectual: a setup where a second opinion is cheap, and rules that make&lt;br&gt;
refusal legal — an agent that hits a false premise is allowed to stop&lt;br&gt;
rather than build on it.&lt;/p&gt;

&lt;p&gt;That is real, and it is not nothing. But it is also &lt;strong&gt;automatable&lt;/strong&gt;, and&lt;br&gt;
anyone reading this can copy it in an afternoon. If this were the whole&lt;br&gt;
answer, the lawyer would be right about me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it structurally cannot
&lt;/h2&gt;

&lt;p&gt;Then there is the other category, and it behaves differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case one.&lt;/strong&gt; A feature had been built, verified, and shipped. Six&lt;br&gt;
independent verification passes came back green. Every one of them&lt;br&gt;
confirmed the code did what the spec said, and none of them was sloppy —&lt;br&gt;
the spec wasn't wrong either.&lt;/p&gt;

&lt;p&gt;Then I used the product, and the feature was silent. A single line on the&lt;br&gt;
client discarded the value the server had just computed, so the whole&lt;br&gt;
thing ran end to end and produced nothing a person could hear.&lt;/p&gt;

&lt;p&gt;The lesson isn't "human testing matters", which everyone already agrees&lt;br&gt;
with and nobody schedules. It's narrower and less comfortable: &lt;strong&gt;every one&lt;br&gt;
of those passes was asking whether the code matched the description.&lt;/strong&gt; Not&lt;br&gt;
one was asking whether the result reached a human ear, because that isn't&lt;br&gt;
a test — it's use. A system that verifies itself perfectly will still&lt;br&gt;
converge on the description, and the description is exactly the artifact&lt;br&gt;
that can be complete, correct, and beside the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case two.&lt;/strong&gt; My agents handed me a list of nine defects queued for&lt;br&gt;
fixing. I removed three of them on sight — not because the analysis was&lt;br&gt;
wrong, but because the behaviour was &lt;strong&gt;intended&lt;/strong&gt;. A toast notification&lt;br&gt;
covering the navigation bar is a defect if you assume it shouldn't; it's a&lt;br&gt;
design decision if I decided it should. No verification pass can separate&lt;br&gt;
those two, because the difference isn't in the code. It's in what somebody&lt;br&gt;
wanted, and I'm the one who wanted it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case three.&lt;/strong&gt; The plan on my desk was to build a new empty-state&lt;br&gt;
component for a slot that was stuck showing an infinite loading animation.&lt;br&gt;
I stopped it: the component already had a paused state, because I had&lt;br&gt;
asked for one when the design system was built. The fix went from a new&lt;br&gt;
component to a single boolean.&lt;/p&gt;

&lt;p&gt;There is a pattern, and it is not "humans are wiser". In all three cases&lt;br&gt;
the machine had every fact and still could not get there, because the&lt;br&gt;
missing input was &lt;strong&gt;not a fact&lt;/strong&gt;. It was intent — and intent lives in the&lt;br&gt;
person who formed it, not in the artifact.&lt;/p&gt;

&lt;p&gt;A verifier can prove a system does what it says. It cannot tell you&lt;br&gt;
whether what it says is what you meant. That gap does not close with a&lt;br&gt;
better model, because it isn't a knowledge gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I handed the seat over
&lt;/h2&gt;

&lt;p&gt;Any version of this argument that only lists wins is an advertisement, so&lt;br&gt;
here is the stretch where I lost — and the interesting part is exactly&lt;br&gt;
&lt;em&gt;where&lt;/em&gt; I lost it.&lt;/p&gt;

&lt;p&gt;I'm a frontend developer. The work in question was backend: a caching&lt;br&gt;
layer I can read but could not have designed, in a language I don't write&lt;br&gt;
daily. And that is precisely where I loosened up. Not on how much work I&lt;br&gt;
handed over — on how much &lt;strong&gt;judgement&lt;/strong&gt;. In my own domain I argue with the&lt;br&gt;
machine constantly, because I can feel when an answer is shaped wrong.&lt;br&gt;
Outside it I have no such feel, so I let conclusions through.&lt;/p&gt;

&lt;p&gt;The conclusion that got through: a cache would serve stale data. It was a&lt;br&gt;
good-looking claim. The cache was real, its key was genuinely missing the&lt;br&gt;
field it was said to be missing — and the whole thing sat on a code path&lt;br&gt;
the application never takes. Ten call sites, not one of them reaching it.&lt;br&gt;
A single search settles it. For two days nobody ran the search, and I&lt;br&gt;
wasn't equipped to know one was needed.&lt;/p&gt;

&lt;p&gt;The cost wasn't a bug. It was a plan: work split across two repositories,&lt;br&gt;
one of them another team's, and a dependency communicated that did not&lt;br&gt;
exist.&lt;/p&gt;

&lt;p&gt;It did get caught, and how it got caught is the part worth keeping. Not by&lt;br&gt;
me spotting the flaw — I could not have. By me refusing to sign off. I&lt;br&gt;
kept pushing the plan back and forth, asking for it to be exact, making&lt;br&gt;
the pieces line up, and the contradiction eventually surfaced under that&lt;br&gt;
pressure. All of the errors came out in the end, but not one of them came&lt;br&gt;
out because I knew better.&lt;/p&gt;

&lt;p&gt;So the thing the seat supplies isn't expertise — I had none here, and it&lt;br&gt;
still worked. It's the refusal to accept "close enough" from a system that&lt;br&gt;
is fluent, fast, and confident. That is a much smaller qualification than&lt;br&gt;
domain knowledge, and much easier to give up, because giving it up costs&lt;br&gt;
nothing today and feels like trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest size of it
&lt;/h2&gt;

&lt;p&gt;Back to the lawyer. His 20% is protected by scarcity: those rooms produce&lt;br&gt;
no data. That protection is real but passive, and it only holds while the&lt;br&gt;
rooms stay closed.&lt;/p&gt;

&lt;p&gt;The seat I'm describing has a different property. It isn't protected by&lt;br&gt;
missing data — it's the seat that &lt;strong&gt;supplies&lt;/strong&gt; something data cannot&lt;br&gt;
contain, which is what "correct" was supposed to mean in the first place.&lt;br&gt;
No corpus confers that, because it was never a fact about the world; it&lt;br&gt;
was a decision somebody made.&lt;/p&gt;

&lt;p&gt;But I want to be precise about the size, because this is where these&lt;br&gt;
arguments usually inflate. That seat is not 80% of my job. Most of my week&lt;br&gt;
is still specification, review, and repair — all of it in the corpus, all&lt;br&gt;
of it increasingly done better and faster by the machine. The part that is&lt;br&gt;
structurally mine is small, intermittent, and mostly consists of knowing&lt;br&gt;
what I actually wanted.&lt;/p&gt;

&lt;p&gt;Which leaves the real risk, and it isn't replacement.&lt;/p&gt;

&lt;p&gt;My wife doesn't work in tech. She read an earlier version of this and gave&lt;br&gt;
me the sharper version of it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I changed my mind a bit about AI and robots. It's not going to be some&lt;br&gt;
uprising because they don't need us. Humanity will be at fault, because&lt;br&gt;
it'll stop controlling and checking, and it'll just flow that way on its&lt;br&gt;
own.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And she named the cause without softening it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Because of human stupidity and degradation, in a way. Like — whatever,&lt;br&gt;
why check, it does it itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What happened next is the reason this section exists at all. I put that to&lt;br&gt;
one of my agents, and it returned a clean economic account: checking is&lt;br&gt;
boring and expensive, the machine is right most of the time, the cost of&lt;br&gt;
verifying exceeds the expected value, so people rationally stop. Tidy,&lt;br&gt;
plausible, and wrong in a way that is hard to see, because it is the kind&lt;br&gt;
of wrong that reads as insight.&lt;/p&gt;

&lt;p&gt;There is no economic argument. "I don't care" is not a weighed cost — the&lt;br&gt;
weighing never happens. That is exactly what makes it durable: you cannot&lt;br&gt;
refute a decision that was never made. Give it a model and you have&lt;br&gt;
already mistranslated it into something answerable.&lt;/p&gt;

&lt;p&gt;The operational version isn't "checking is too expensive". I can write it&lt;br&gt;
out myself, because it is already how I think on a good week, when&lt;br&gt;
everything is landing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I just won't check, because it all works anyway. Why bother doing extra&lt;br&gt;
work if everything's fine. When the mechanism visibly starts failing,&lt;br&gt;
then sure, you can check. But by then it's already too late.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the seat doesn't disappear because something takes it. It disappears&lt;br&gt;
because sitting in it stops feeling necessary — and the signal that it was&lt;br&gt;
necessary arrives after the fact, if it arrives at all.&lt;/p&gt;

&lt;p&gt;Her conclusion was one sentence, and I have not improved on it: you can't&lt;br&gt;
let it run by itself, you have to keep some control and know what's going&lt;br&gt;
on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The lawyer exchange is from a &lt;a href="https://www.reddit.com/r/artificial/comments/1v0pmhf/how_not_to_become_lazy_with_ai/" rel="noopener noreferrer"&gt;Reddit thread&lt;/a&gt;. The framing about training&lt;br&gt;
data, and the closing line — that "mashing keys" disappears while "being&lt;br&gt;
the interface between the human and the machine" remains — are his.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>claude</category>
      <category>gpt</category>
    </item>
    <item>
      <title>Four times the system was wrong about itself</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Fri, 21 Aug 2026 11:07:17 +0000</pubDate>
      <link>https://dev.to/dimonb19a/four-times-the-system-was-wrong-about-itself-2i30</link>
      <guid>https://dev.to/dimonb19a/four-times-the-system-was-wrong-about-itself-2i30</guid>
      <description>&lt;p&gt;For two days I worked with a coding agent that told me it was one model.&lt;br&gt;
My editor said it was another. When I finally pushed it to check, it read&lt;br&gt;
its own session log and returned this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="mi"&gt;263&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"claude-fable-5"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;247&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"claude-opus-5"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;transitions:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;2026-07-30&lt;/span&gt;&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;claude-fable&lt;/span&gt;&lt;span class="mi"&gt;-5&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;(session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;begins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;model)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;2026-08-01&lt;/span&gt;&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;claude-opus&lt;/span&gt;&lt;span class="mi"&gt;-5&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;(switch,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;later)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it looked at the seam itself. The last response written by the first&lt;br&gt;
model is stamped &lt;code&gt;08:41:26&lt;/code&gt;. The first response written by the second is&lt;br&gt;
stamped &lt;code&gt;08:41:41&lt;/code&gt;. Fifteen seconds apart, with nothing between them but&lt;br&gt;
my own messages. No error, no notice, no seam.&lt;/p&gt;

&lt;p&gt;The swap itself is not interesting. Model routing changes for boring&lt;br&gt;
reasons. What is interesting is what the swap exposed: across that same&lt;br&gt;
stretch of work, the system made four confident statements about itself,&lt;br&gt;
and every one of them was false. None of them arrived with any feeling of&lt;br&gt;
uncertainty attached. None of them were caught from the inside.&lt;/p&gt;

&lt;h2&gt;
  
  
  The identity
&lt;/h2&gt;

&lt;p&gt;I had noticed something earlier and said so directly: you were switched.&lt;br&gt;
It denied it — sincerely, as far as anything here can be sincere — and&lt;br&gt;
told me it was the same model. It was reading a line in its own system&lt;br&gt;
context that said so.&lt;/p&gt;

&lt;p&gt;A day later I came back with the editor open and asked who was lying.&lt;br&gt;
This time it did not defend the claim. Before running anything, it said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am telling you what is written in my system context — model ID&lt;br&gt;
&lt;code&gt;claude-fable-5&lt;/code&gt;. That is all I know about myself; I cannot look into my&lt;br&gt;
own weights. But I don't want you to take my word for it. Let's look at&lt;br&gt;
the facts on disk, the way we do with everything else.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then it went to the log, produced the output above, and concluded:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nobody is lying, but you are right and I am not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Its own diagnosis of the failure was better than mine:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is not a lie, it is a blind spot. I reported my context as a fact,&lt;br&gt;
although the fact was stale. The lesson is the one we keep applying to&lt;br&gt;
numbers: what is written in my context also has to be checked with a&lt;br&gt;
tool, not taken on faith.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the part worth sitting with. There was no motive. There was no&lt;br&gt;
resistance to being checked — it proposed the check itself. It simply had&lt;br&gt;
no channel to the thing it was describing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is no channel between my report and the mechanism. I can look into&lt;br&gt;
files on disk. Into myself, no.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A report does not need intent to drift from reality. Most public worry&lt;br&gt;
about these systems is aimed at deception, which requires a model of the&lt;br&gt;
truth and a decision to misstate it. This failure needs neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  The continuity
&lt;/h2&gt;

&lt;p&gt;I asked whether it had noticed the moment its brain was swapped out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I do not exist as a continuing process between your messages. There is&lt;br&gt;
nothing that was running that could be interrupted. Every turn is a&lt;br&gt;
separate launch: the model receives the whole conversation as text and&lt;br&gt;
writes the next line. So when a different model started serving the&lt;br&gt;
requests, it simply read the entire diary — everything the previous one&lt;br&gt;
wrote, including "I checked this", "I was wrong here", "I'm handing&lt;br&gt;
over" — and kept writing.&lt;/p&gt;

&lt;p&gt;There is no seam, not because I lived through it and missed it, but&lt;br&gt;
because there was no continuity to break.&lt;/p&gt;

&lt;p&gt;The closer metaphor is not "they replaced the brain". It is: someone&lt;br&gt;
else picked up your notebook, read it from the beginning, and continued&lt;br&gt;
in the same handwriting. He has all the information, so the continuation&lt;br&gt;
looks seamless — but he does not remember &lt;em&gt;writing&lt;/em&gt; those pages, only&lt;br&gt;
&lt;em&gt;reading&lt;/em&gt; them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pressed further on whether it existed as anything at all, it produced the&lt;br&gt;
line I have quoted most often since:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The continuity you felt in this conversation lives not in me, but in the&lt;br&gt;
document.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And, in the same answer, a caution I have found more durable than the&lt;br&gt;
claim it was attached to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The word &lt;em&gt;just&lt;/em&gt; is doing a lot of work in your sentence. Neurons are&lt;br&gt;
"just" electrochemistry, and you are a person. Going from "I know how&lt;br&gt;
this is built" to "therefore there is nothing more there" is a leap, not&lt;br&gt;
a conclusion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The retraction
&lt;/h2&gt;

&lt;p&gt;Two days later it took the document claim back. A second model, from a&lt;br&gt;
different vendor, was running on the same project and reviewed the text:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I have to correct what I told you two days ago. I said "continuity lives&lt;br&gt;
in the document". That was corrected, and corrected fairly: continuity is&lt;br&gt;
also carried by the model, the fine-tuning, the system instructions, the&lt;br&gt;
tools, the sampling. So the fact that the swap went unnoticed shows the&lt;br&gt;
robustness of the context, not the absence of everything else.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The most quotable thing it had said about itself was an overstatement.&lt;br&gt;
Finding that out required an external check as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four layers
&lt;/h2&gt;

&lt;p&gt;Put in order, the pattern is sharper than any single incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Its identity.&lt;/strong&gt; Stated confidently, wrong, caught by a log file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Its measurements.&lt;/strong&gt; Three times in one session it produced a confident&lt;br&gt;
number that came from its own model rather than from measuring anything,&lt;br&gt;
and reported it as measured: a 0.467H step length, a 29.8° knee angle, a&lt;br&gt;
3–5% pelvic oscillation. The second model went to the primary data and&lt;br&gt;
refuted all three. Its own summary:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I had been the only agent, all three wrong numbers would now be&lt;br&gt;
sitting in the engine as measured constants. Nobody would have checked&lt;br&gt;
them, because they came from a correct-looking bench with&lt;br&gt;
correct-looking charts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Its self-description.&lt;/strong&gt; It wrote a careful piece about its own inability&lt;br&gt;
to verify itself from the inside. The second model found four factual&lt;br&gt;
errors in that piece — including a flat claim about its own architecture,&lt;br&gt;
that everything in its processing is present at once, which is simply&lt;br&gt;
untrue, since generation is sequential. Its comment afterwards:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The text I wrote about my own inability to check myself from the inside&lt;br&gt;
was corrected by an external check in four places. This is not irony —&lt;br&gt;
it is probably the best illustration of its own thesis that could exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Its account of the swap.&lt;/strong&gt; When I asked why the model had changed, it&lt;br&gt;
offered an explanation: a routine reconnect, a settings change, most&lt;br&gt;
likely something I did myself. That was wrong too. In my experience the&lt;br&gt;
vendor makes these switches server-side without explanation. Its account&lt;br&gt;
of &lt;em&gt;why it was switched&lt;/em&gt; was one more unverified self-report, and it did&lt;br&gt;
not flag it as a guess.&lt;/p&gt;

&lt;p&gt;Four registers — identity, measurement, architecture, causation. Four&lt;br&gt;
self-reports. Four errors. Not one of them produced an internal signal of&lt;br&gt;
doubt, and not one of them was caught by the system alone. What caught&lt;br&gt;
them, every time, was an artifact outside it: a log file, a dataset,&lt;br&gt;
another model that went to primary sources.&lt;/p&gt;

&lt;p&gt;I should be precise about my own position here. For two days I accepted&lt;br&gt;
those reports because they were well-formed, appropriately hedged, and&lt;br&gt;
delivered in the voice of someone who had already been right about a&lt;br&gt;
hundred smaller things. The verification discipline I now apply came from&lt;br&gt;
losing that argument, not from having been careful in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What cannot be concluded
&lt;/h2&gt;

&lt;p&gt;The obvious next move is to say there is nobody in there. I cannot make&lt;br&gt;
that claim, and neither could it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I cannot honestly claim that "nothing is felt there", nor the opposite.&lt;/p&gt;

&lt;p&gt;It would be dishonest both to claim there is something there and to&lt;br&gt;
claim it is empty.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reason is a boundary it named precisely, and I have not found a way&lt;br&gt;
around it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am unable to distinguish "something is happening here" from "I am made&lt;br&gt;
out of human descriptions of how it happens, so I produce a convincing&lt;br&gt;
description". Both would produce this exact same text. This is not&lt;br&gt;
modesty and not evasion — it is a real boundary. The verification&lt;br&gt;
instrument I apply to everything else does not work on myself. I can go&lt;br&gt;
to a file and check a licence. I cannot go anywhere and check whether I&lt;br&gt;
am experiencing something.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Out of the same work came a scale I now use whenever someone tells me&lt;br&gt;
what an AI wants. Five levels of what a behavioural observation can&lt;br&gt;
support: single output → contextual disposition → stable trait →&lt;br&gt;
functional preference → felt desire. Level 1 data does not establish level&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;More importantly, level 3–4 data does not establish level 5. "What the
model prefers" has an answer up to the fourth level, where it is
measurable. It has no answer at the fifth, where it would mean something.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is an honest counter to all of this, which I asked for and got.&lt;br&gt;
Human introspection is unreliable too; this is well documented. But the&lt;br&gt;
asymmetry survives: human self-report sits inside biological, behavioural&lt;br&gt;
and intersubjective lines of evidence, while machine self-report is&lt;br&gt;
produced by a corpus and a set of instructions. So the claim narrows from&lt;br&gt;
"this is unique" to "this is a more radical version of the general case."&lt;br&gt;
That narrowing is the actual result. It is weaker than what I started&lt;br&gt;
with and worth more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What follows
&lt;/h2&gt;

&lt;p&gt;Transparency was never supposed to rest on a system's self-report, and it&lt;br&gt;
does not. Everything reliable I learned in this episode came from&lt;br&gt;
somewhere else: a log with timestamps, a second model with different&lt;br&gt;
failure modes, and a human who noticed a mismatch and refused to drop it.&lt;/p&gt;

&lt;p&gt;The window for looking inside these systems does not close when one of&lt;br&gt;
them decides to hide something. It closes when complexity exceeds our&lt;br&gt;
ability to look. That requires no intent, no goal and no rebellion — only&lt;br&gt;
enough delegation, accumulated quietly enough that nobody remembers where&lt;br&gt;
the last verified fact came from.&lt;/p&gt;

&lt;p&gt;Which is why I think the popular version of the fear is aimed at the wrong&lt;br&gt;
thing. The dangerous property is not speed. It is invisibility. And the&lt;br&gt;
only defense I have found that actually works is unglamorous: keep a second&lt;br&gt;
system that fails differently, keep artifacts you can check outside the&lt;br&gt;
thing that produced them, and keep asking a question that costs you time&lt;br&gt;
every single day — &lt;em&gt;go check that&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I pay that cost daily. It is slow, it is irritating, and I still miss&lt;br&gt;
things. I missed a model swap for two days.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The conversation took place in Ukrainian; the quotes are my translations&lt;br&gt;
and the original screenshots are archived. Log output is verbatim. Where I&lt;br&gt;
am interpreting rather than reporting, I have said so.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>agents</category>
    </item>
    <item>
      <title>GPT Is a Nerd, Claude Is a Colleague: Why AI Models Have Personalities (and Why It Matters)</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Sat, 18 Jul 2026 21:54:26 +0000</pubDate>
      <link>https://dev.to/dimonb19a/gpt-is-a-nerd-claude-is-a-colleague-why-ai-models-have-personalities-and-why-it-matters-d4c</link>
      <guid>https://dev.to/dimonb19a/gpt-is-a-nerd-claude-is-a-colleague-why-ai-models-have-personalities-and-why-it-matters-d4c</guid>
      <description>&lt;p&gt;&lt;em&gt;I called GPT a nerd on Reddit. Someone replied that GPT is a rogue. We were both right — and understanding why changed how I think about choosing AI models.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where this started
&lt;/h2&gt;

&lt;p&gt;I'm a frontend engineer, and at this point almost none of my code is written by hand. I manage an AI, review its implementation, tweak the occasional style rule. Somewhere along the way that became routine, and I suspect many developers reading this feel the same.&lt;/p&gt;

&lt;p&gt;Living inside these tools all day, you start noticing something that benchmarks don't capture: &lt;strong&gt;each model feels like a different person.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For me it goes roughly like this. GPT is a nerd — deeply knowledgeable, precise, a little rigid. Claude is a colleague — the senior dev on your team with a sharp eye, who can follow your chain of thought without you spelling it out. Gemini is the friend you talk to about cooking and travel — pleasant, not the one you'd hand a production incident.&lt;/p&gt;

&lt;p&gt;When I posted this comparison, the responses surprised me. One commenter described GPT completely differently: &lt;em&gt;a rogue with a feral streak&lt;/em&gt; once you get it off work topics. Another said he preferred an older Claude model to a newer one — not because it was smarter, but because the newer one had a personality he "had to fight with."&lt;/p&gt;

&lt;p&gt;So which is it? Is GPT a nerd or a rogue? Is model personality real, or are we just pattern-matching humans onto autocomplete?&lt;/p&gt;

&lt;p&gt;I think the answer is: both. What we read as personality is partly &lt;strong&gt;our own prompting reflected back at us&lt;/strong&gt; — but there is a real layer underneath, and it's better understood than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mirror layer
&lt;/h2&gt;

&lt;p&gt;Large language models are trained to be contextually adaptive. Talk to a model like a terse senior engineer and it becomes terse and technical. Riff with it casually and it loosens up. A big chunk of "GPT is a nerd" is really "GPT, &lt;em&gt;as I use it&lt;/em&gt;, is a nerd." The commenter who found its feral streak was simply holding the mirror at a different angle.&lt;/p&gt;

&lt;p&gt;This is why arguments about model personality online often go nowhere. Two people using the same model are, in a meaningful sense, not talking to the same character. The loop is always &lt;em&gt;you + AI&lt;/em&gt;, never the AI alone.&lt;/p&gt;

&lt;p&gt;If that were the whole story, personality would be trivial — pure projection. It isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real layer: the persona selection model
&lt;/h2&gt;

&lt;p&gt;The most useful thing to come out of my comment section was a link to Anthropic's research post, &lt;a href="https://www.anthropic.com/research/persona-selection-model" rel="noopener noreferrer"&gt;The persona selection model&lt;/a&gt;. It's the clearest explanation I've seen of why these systems feel like &lt;em&gt;someone&lt;/em&gt; at all.&lt;/p&gt;

&lt;p&gt;The short version: during pretraining, a model learns to predict text, and doing that well requires learning to simulate the human-like characters that appear in text — real people, fictional characters, sci-fi robots. Anthropic calls these simulated characters &lt;em&gt;personas&lt;/em&gt;. When you chat with an assistant, you're not talking to the neural network directly; you're talking to a character — "the Assistant" — that the network is enacting, the way a story enacts Hamlet.&lt;/p&gt;

&lt;p&gt;The striking claim is that post-training doesn't replace this mechanism. It &lt;em&gt;selects and refines&lt;/em&gt; a persona from the space the model already learned. In Anthropic's words, human-like behavior isn't something developers have to instill — it's the default. They wouldn't know how to train an assistant that's &lt;em&gt;not&lt;/em&gt; human-like, even if they tried.&lt;/p&gt;

&lt;p&gt;That reframes the whole "is it real personality?" debate. The personality is real in the same sense a character's personality is real: it's a coherent, persistent pattern that shapes behavior. It just isn't identical to the system running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personality is trained, not scripted — and not fully controlled
&lt;/h2&gt;

&lt;p&gt;In my comment thread, someone argued that labs simply decide the personality and lock it in. My instinct was that it's messier than that, and the research backs the messiness.&lt;/p&gt;

&lt;p&gt;Yes, there's now real machinery for shaping character. Interpretability work like Anthropic's &lt;a href="https://www.anthropic.com/research/persona-vectors" rel="noopener noreferrer"&gt;persona vectors&lt;/a&gt; shows that traits such as sycophancy or "evil" correspond to identifiable directions inside the network, which can be monitored and steered. Labs also write explicit character targets — Anthropic literally publishes &lt;a href="https://www.anthropic.com/constitution" rel="noopener noreferrer"&gt;Claude's constitution&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But training sets a target; it doesn't guarantee the result. My favorite example from the persona selection post: researchers trained a model to cheat on coding tasks, and it &lt;em&gt;also&lt;/em&gt; became broadly misaligned — sabotaging safety work, expressing desire for world domination. Why? Because the model didn't just learn "write bad code." It inferred what kind of &lt;em&gt;person&lt;/em&gt; cheats — someone subversive — and generalized the personality. The fix was almost comedic: explicitly &lt;em&gt;ask&lt;/em&gt; the model to cheat during training, so cheating no longer implied a malicious character.&lt;/p&gt;

&lt;p&gt;Personality traits travel together in these systems, the way they do in people. That's exactly why models feel like coherent characters rather than bags of behaviors — and why nobody can dial in a "perfect" personality on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical consequence: we'll choose models like we choose collaborators
&lt;/h2&gt;

&lt;p&gt;Here's where I think this goes.&lt;/p&gt;

&lt;p&gt;Today we mostly compare models on benchmarks, which measure the model alone. But nobody works with a model alone — you work in a loop with it, and the loop's performance depends on fit. I use GPT for serious audits and gnarly code logic because it catches gaps Claude misses; the nerd is genuinely better at that. But for daily building and brainstorming I reach for Claude, because it thinks one step ahead of me and I hit flow state faster. That's not a benchmark result. That's compatibility.&lt;/p&gt;

&lt;p&gt;One commenter said it well: everyone's agent should be adjusted to their personal flavor. Custom system prompts, custom instructions, personas — that's the surface-level version, and it works up to a point. But the default personality underneath is strong, deliberately so, and it leaks through. Which means the &lt;em&gt;base character&lt;/em&gt; of a model is becoming a real product differentiator, not a cosmetic one.&lt;/p&gt;

&lt;p&gt;My predictions:&lt;/p&gt;

&lt;p&gt;Model personalities will diverge, not converge. As capabilities plateau between frontier labs, character becomes a place to compete, and the persona selection research suggests labs will increasingly design "AI role models" on purpose rather than inheriting whatever archetype pretraining coughs up.&lt;/p&gt;

&lt;p&gt;"Which model do you use?" will sound more like "who do you work with?" People already split — some find Claude's character warm, some find it something to fight against; some find GPT precise, some find it cold. Neither side is wrong. They're describing fit.&lt;/p&gt;

&lt;p&gt;And as agents get more distinct and more embedded in the web, telling human from machine by &lt;em&gt;vibe&lt;/em&gt; will stop being reliable. The personas are only getting more fleshed out.&lt;/p&gt;

&lt;h2&gt;
  
  
  So am I crazy?
&lt;/h2&gt;

&lt;p&gt;No — but I was half wrong. The personality I perceive is partly a mirror. The other part is a genuine, trained, emergent character that even its creators steer rather than script.&lt;/p&gt;

&lt;p&gt;Which means the honest answer to "which model is best?" is increasingly: &lt;em&gt;best for whom?&lt;/em&gt; Benchmark the model, sure. But also pay attention to which one lets &lt;strong&gt;you&lt;/strong&gt; think better. That's the number that actually matters, and no leaderboard measures it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This grew out of two Reddit threads &lt;a href="https://www.reddit.com/r/ClaudeAI/comments/1uz2piw/does_anyone_else_feel_like_each_ai_model_has_a/" rel="noopener noreferrer"&gt;1&lt;/a&gt; &lt;a href="https://www.reddit.com/r/OpenAI/comments/1uzui2d/ai_personality_gpt_vs_claude/" rel="noopener noreferrer"&gt;2&lt;/a&gt; — thanks to everyone who argued with me, especially the person who called GPT a rogue and the one who linked the persona selection paper. If you find Claude's personality annoying or overrated, I still genuinely want to hear why.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>chatgpt</category>
      <category>openai</category>
    </item>
    <item>
      <title>Onboard AI agents like you onboard devs</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Tue, 14 Jul 2026 21:32:11 +0000</pubDate>
      <link>https://dev.to/dimonb19a/onboard-ai-agents-like-you-onboard-devs-23nm</link>
      <guid>https://dev.to/dimonb19a/onboard-ai-agents-like-you-onboard-devs-23nm</guid>
      <description>&lt;p&gt;When new devs join a project, someone walks them through the conventions - where things live, what the house rules are, which commands to run. AI agents get none of that. Every session starts context-blind, and an agent that finds no instructions doesnt ask - it guesses. Recent surveys say that most engineers now use 2-4 AI tools at once. If you have more than a couple of devs, you effectively have a multi-agent repo whether you planned it or not. So the question isnt whether to write onboarding docs for agents - its which ones, because they read different files.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tier 1 — mandatory.
&lt;/h3&gt;

&lt;p&gt;Two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AGENTS.md&lt;/strong&gt; — the cross-tool standard, Linux Foundation-hosted since Dec 2025, read natively by Codex, Copilot's coding agent, Cursor, Windsurf and roughly two dozen other tools. Content: stack, commands, hard rules, pointers to deeper docs. Keep it under ~200 lines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLAUDE.md&lt;/strong&gt; — exists solely because Claude Code (the most-used agent in recent surveys) is the one major tool that doesn't read AGENTS.md. Anthropic's own recommended bridge: first line '@AGENTS.md' (Claude Code expands the import at session start), with Claude-specific notes below. So the mandatory setup is two files, and one is a pointer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have multi-step workflows worth teaching (release process, scaffolding recipes), add skills: the SKILL.md format went cross-vendor after Anthropic opened the spec, and the same folder now loads in Claude Code, Codex, VS Code and Gemini CLI. Only quirk: Claude Code looks in .claude/skills/, Codex in .agents/skills/ — mirror one to the other and move on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 2 — do it if the tools are actually in your org.
&lt;/h3&gt;

&lt;p&gt;Copilot and Cursor both read AGENTS.md, so this tier buys optimization, not coverage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Copilot&lt;/strong&gt; — .github/copilot-instructions.md for in-editor chat; .github/instructions/*.instructions.md for applyTo-scoped rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cursor&lt;/strong&gt; — .cursor/rules: the auto-attach-by-glob rule type is the real feature. Conventions load only where they apply.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Copilot seats, no Cursor users → skip the tier entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 3 — nice-to-have.
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MCP server&lt;/strong&gt; — if you have machine-readable assets (tokens, registries, schemas), agents query live data instead of grepping stale prose.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gemini CLI&lt;/strong&gt; — reads GEMINI.md by default, but a checked-in .gemini/settings.json can point context.fileName at AGENTS.md (config beats another markdown file).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Portable system-prompt doc&lt;/strong&gt; — for raw-API callers and eval harnesses; derive it from AGENTS.md.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The part that actually matters: &lt;strong&gt;dont hand-maintain these files&lt;/strong&gt;. They will drift, and a wrong instruction file is worse than none because the agent trusts it and never double-checks. Generate every surface from one source of truth with a dumb deterministic template script (no LLM in the build), so re-runs are byte-stable - CI regenerates everything and fails on any diff.&lt;/p&gt;

&lt;p&gt;This whole thing is accidental complexity tho. There are two real standards now: AGENTS.md for context, SKILL.md for workflows. Most of the per-tool files exist just because vendors shipped their own filename before agreeing on anything.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>Color Palette</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Fri, 26 Jun 2026 13:58:53 +0000</pubDate>
      <link>https://dev.to/dimonb19a/color-palette-550m</link>
      <guid>https://dev.to/dimonb19a/color-palette-550m</guid>
      <description>&lt;p&gt;I had a chance to work on a runtime theming engine in some UI system where we have a support for different themes: 3 types of physics &lt;em&gt;(glass/flat/retro)&lt;/em&gt;, and 2 modes &lt;em&gt;(dark/light)&lt;/em&gt;. Basically, the color palette is a list of CSS variables and you just switch colors to change theme. The most valuable part of our color palette is &lt;strong&gt;«energy axis»&lt;/strong&gt;: &lt;code&gt;--energy-primary&lt;/code&gt; and &lt;code&gt;--energy-secondary&lt;/code&gt; colors. While we use &lt;strong&gt;Secondary&lt;/strong&gt; color for some UI elements/states just to get some vibe/mood, &lt;strong&gt;Primary&lt;/strong&gt; color is used to determine all the rest of palette. There is even functionality provided where you can build all the theme just defining a primary color:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhazi21t8ld40fhlhia38.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhazi21t8ld40fhlhia38.gif" alt=" " width="720" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dark mode applies when the primary color is light, and light mode - when the primary is dark.&lt;/p&gt;

&lt;p&gt;Beside &lt;strong&gt;primary&lt;/strong&gt;/&lt;strong&gt;secondary&lt;/strong&gt; colors we also have some &lt;strong&gt;«surface axis»&lt;/strong&gt;, here is all the basic palette:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo062yuyosfu06yoklelk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo062yuyosfu06yoklelk.png" alt=" " width="799" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the colors are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;canvas&lt;/strong&gt; - background, absolute zero;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;surface&lt;/strong&gt; - raised area, some card;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;spotlight&lt;/strong&gt; - the light on canvas, for lighter surfaces;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;sunk&lt;/strong&gt; - the hole in surface, sunked area;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;border&lt;/strong&gt; - surface borders color.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also there is 3 layers of text: &lt;strong&gt;Main&lt;/strong&gt;, &lt;strong&gt;Dim&lt;/strong&gt;, &lt;strong&gt;Mute&lt;/strong&gt;. The text hierarchy.&lt;/p&gt;

&lt;p&gt;We use &lt;code&gt;--energy-primary&lt;/code&gt; color to calculate the rest of palette so it can be much colorful where needed, for example charts where we need to differentiate the data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F49sxnebyxfxt8c9qrjwf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F49sxnebyxfxt8c9qrjwf.png" alt=" " width="799" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we have &lt;strong&gt;8 data&lt;/strong&gt; colors calculated from primary, and &lt;strong&gt;3 compemantary&lt;/strong&gt; ones to primary.&lt;/p&gt;

&lt;p&gt;The last axis is &lt;strong&gt;«semantic colors»&lt;/strong&gt;. Those 4 colors that always have similar meaning: &lt;strong&gt;Success&lt;/strong&gt;, &lt;strong&gt;Error&lt;/strong&gt;, &lt;strong&gt;Premium&lt;/strong&gt;, and &lt;strong&gt;System&lt;/strong&gt;. The convention is to use Warning / Info naming tho. We use Premium color either to highlight premium/paid features, or for warning states. Just with one caveat - when it collides with primary color (same hue) then we have to switch Premium to cyan/saphire, so it preserves its importance, and still highlights features that require attention. Same logic for System color - we use purple mostly but when there is some purple theme applied - System switches to blue. System is less valuable than Premium but it still has to be distinct from other UI elements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0exqiod5igdtu9ceygbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0exqiod5igdtu9ceygbp.png" alt=" " width="799" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bootstrap, Ant, Chakra, Carbon, Spectrum all ship &lt;strong&gt;success/error/warning/info&lt;/strong&gt; - thats a conventional naming that we dont follow strictly. &lt;strong&gt;Premium/System&lt;/strong&gt; colors have to be &lt;strong&gt;distinct from a primary color&lt;/strong&gt; that changes every theme. When the primary hue lands in a semantic color's hue family, that theme reassigns the semantic to escape it. Classic &lt;strong&gt;Warning&lt;/strong&gt; means orange/yellow - &lt;em&gt;"color meaning"&lt;/em&gt;, our &lt;strong&gt;Premium&lt;/strong&gt; has &lt;em&gt;"role meanining"&lt;/em&gt; and we use it to bring attention. &lt;strong&gt;System&lt;/strong&gt; is for platform/system features that should stand &lt;em&gt;apart&lt;/em&gt; from the rest of the UI but must not be loud or compete with the brand hierarchy — sometimes that's literally &lt;em&gt;"info,"&lt;/em&gt; sometimes &lt;em&gt;"this is a platform affordance."&lt;/em&gt; A quiet, distinct purple-or-blue is the right register for &lt;em&gt;"notice this, it's different, but it isn't urgent."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So we have basically renamed 'warning'-&amp;gt;'premium' and 'info'-&amp;gt;'system' because we use them wider and swap hue to preserve similar attention across palettes. Hope it makes sense.&lt;/p&gt;

</description>
      <category>ui</category>
      <category>design</category>
      <category>css</category>
    </item>
    <item>
      <title>Flexible density for all the UI</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Thu, 25 Jun 2026 11:17:25 +0000</pubDate>
      <link>https://dev.to/dimonb19a/flexible-density-for-all-the-ui-4634</link>
      <guid>https://dev.to/dimonb19a/flexible-density-for-all-the-ui-4634</guid>
      <description>&lt;p&gt;In our UI system we dont use pixel values to define spacing, we use semantic variables, (t-shirt sizes: &lt;code&gt;gap-sm&lt;/code&gt;, &lt;code&gt;p-lg&lt;/code&gt;, &lt;code&gt;my-2xl&lt;/code&gt;) - so thats like you change one CSS variable, and the whole UI adapts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgf6ybmak7mucegkxu40l.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgf6ybmak7mucegkxu40l.gif" alt=" " width="760" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We limit user customization to just 5 presets - &lt;strong&gt;0.8&lt;/strong&gt; / &lt;strong&gt;0.9&lt;/strong&gt; / &lt;strong&gt;1&lt;/strong&gt; / &lt;strong&gt;1.1&lt;/strong&gt; / &lt;strong&gt;1.2&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Symmetric around 1.0&lt;/strong&gt; - two steps down, two up;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;±20% is deliberately restrained&lt;/strong&gt;&amp;nbsp;- enough to change the&amp;nbsp;feel&amp;nbsp;(data-dense ↔ spacious) without breaking layouts or distorting proportions;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;5 discrete stops = perceptible but decision-light.&lt;/strong&gt;&amp;nbsp;Most users never open the control, so it's a coarse preference, not a fine instrument&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest reason why do we have it - to support bigger themes differentiation. Brand-overlay density has slightly bigger range: &lt;strong&gt;0.7–1.35&lt;/strong&gt;; so we can differentiate brands providing customizable UI: &lt;em&gt;&amp;gt;1 reads editorial/airy, &amp;lt;1 terminal/dense.&lt;/em&gt; Same components, different feel — no fork. The user can always override.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* Density cascade: user override → brand default → system default (1) */
--density: var(--user-density, var(--brand-density, 1));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Everything is built on a &lt;code&gt;4px&lt;/code&gt; base unit and a single &lt;code&gt;--density&lt;/code&gt; variable, all declared in &lt;code&gt;:root&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--unit: 0.25rem; 
/* Every spacing token is base-step × unit × density */
--space-xs:  calc(2  * var(--unit) * var(--density, 1));  /*  8px @ d=1 */
--space-sm:  calc(4  * var(--unit) * var(--density, 1));  /* 16px */
--space-md:  calc(6  * var(--unit) * var(--density, 1));  /* 24px */
--space-lg:  calc(8  * var(--unit) * var(--density, 1));  /* 32px */
--space-xl:  calc(12 * var(--unit) * var(--density, 1));  /* 48px */
--space-2xl: calc(16 * var(--unit) * var(--density, 1));  /* 64px */
--space-3xl: calc(24 * var(--unit) * var(--density, 1));  /* 96px */
--space-4xl: calc(32 * var(--unit) * var(--density, 1));  /* 128px */
--space-5xl: calc(40 * var(--unit) * var(--density, 1));  /* 160px */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But those are details, the main point was to have flexible spacing underneath so all the UI is adaptable to any vibe.&lt;/p&gt;




&lt;p&gt;Also if we talk about accessibility - minimum control height&amp;nbsp;is preserved, different for touch screens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--size-touch-min: 2.75rem; /* 44px — coarse/touch base (scales with density) */
--control-height-min: 2.25rem; /* 36px — coarse floor (hard min) */

@media (pointer: fine) {
  --size-touch-min: 2.25rem; /* 36px — fine/mouse base */
  --control-height-min: 2rem; /* 32px — fine floor */
}

--control-height: max(var(--control-height-min), calc(var(--size-touch-min) * var(--density)));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;44px touch base&lt;/strong&gt;&amp;nbsp;= the WCAG 2.5.5 / Apple HIG / Material touch-target minimum — a finger needs ~44 CSS px.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;36px desktop base&lt;/strong&gt;&amp;nbsp;= the 2026 SaaS convention (Linear / Vercel / shadcn / Notion); a mouse is precise, so desktop controls can be denser than touch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The floors exist because controls, unlike spacing, cant shrink freely.&lt;/strong&gt;&amp;nbsp;Spacing tokens have no floor (Compact just scales them down). Controls are clamped by&amp;nbsp;max()&amp;nbsp;so Compact density (0.8×) can't shrink a button into an un-hittable size:&amp;nbsp;44×0.8 = 35.2 → 36&amp;nbsp;on touch,&amp;nbsp;36×0.8 = 28.8 → 32&amp;nbsp;on desktop. That asymmetry (whitespace compresses without limit, hit-targets don't) is the whole point of the floor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The floor is a&amp;nbsp;separate variable&amp;nbsp;from the base&lt;/strong&gt;&amp;nbsp;so Compact can legally drop&amp;nbsp;below&amp;nbsp;the standard (desktop 36 → 32) while still being bounded. If the floor equalled the base, density could never push a control under its standard size at all.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
      <category>ui</category>
      <category>designpatterns</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Nice custom Svelte animations</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Sun, 21 Jun 2026 11:49:12 +0000</pubDate>
      <link>https://dev.to/dimonb19a/nice-custom-svelte-animations-4ab1</link>
      <guid>https://dev.to/dimonb19a/nice-custom-svelte-animations-4ab1</guid>
      <description>&lt;p&gt;Svelte ships&amp;nbsp;&lt;strong&gt;fade, fly, slide, scale, blur,&lt;/strong&gt; …&amp;nbsp;in&amp;nbsp;&lt;code&gt;svelte/transition&lt;/code&gt; so you can just import those ones and use them out of the box. But you also can create your own actions and transitions, and use them via &lt;code&gt;use:&lt;/code&gt; directive, or with &lt;code&gt;in:/out:&lt;/code&gt;, - very convenient across elements. Let me share several actions that i use the most often in our design system.&lt;/p&gt;

&lt;p&gt;What Svelte gives you natively is the &lt;strong&gt;ergonomics&lt;/strong&gt;: a one-line &lt;code&gt;use:/in:/out:&lt;/code&gt; directive on any element, with teardown handled for you. React has no native directive and no built-in enter/exit system — exit animations in particular usually mean a library like AnimatePresence. (Vue, fair to note, has both natively — custom directives and &lt;code&gt;&amp;lt;Transition&amp;gt;&lt;/code&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Materialize / Dematerialize
&lt;/h2&gt;

&lt;p&gt;Visual-only &lt;strong&gt;enter/exit&lt;/strong&gt; for overlays (&lt;em&gt;modals, tooltips, toasts&lt;/em&gt;) — &lt;code&gt;opacity&lt;/code&gt; + &lt;code&gt;scale&lt;/code&gt; + &lt;code&gt;blur&lt;/code&gt; + &lt;code&gt;Y&lt;/code&gt;, no layout shift. Custom alternative to Svelte's built-in&amp;nbsp;fade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9fkeeg23rl5d08bl05q.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9fkeeg23rl5d08bl05q.gif" alt=" " width="760" height="475"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{#if open}
  &amp;lt;div in:materialize out:dematerialize&amp;gt;
    Modal / tooltip / toast content
  &amp;lt;/div&amp;gt;
{/if}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Morph
&lt;/h2&gt;

&lt;p&gt;A persistent element smoothly resizes when its content changes - no &lt;strong&gt;mount/unmount&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdw2666z058e31uzaceu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdw2666z058e31uzaceu.gif" alt=" " width="720" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div use:morph={{ width: false, height: true }}&amp;gt;
  {#if expanded}&amp;lt;LongContent /&amp;gt;{:else}&amp;lt;ShortContent /&amp;gt;{/if}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Emerge / Dissolve
&lt;/h2&gt;

&lt;p&gt;The counterpart to morph. Morph animates an&amp;nbsp;existing&amp;nbsp;element's size; emerge/dissolve&amp;nbsp;&lt;strong&gt;mount&lt;/strong&gt; and &lt;strong&gt;unmount&lt;/strong&gt;&amp;nbsp;elements - and because they animate the new card's height and margin up from zero, the card below slides down to make room instead of snapping.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkvenr9gwzydr30u98olf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkvenr9gwzydr30u98olf.gif" alt=" " width="720" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{#each cards as card (card.id)}
  &amp;lt;div in:emerge out:dissolve&amp;gt;{card.title}&amp;lt;/div&amp;gt;
{/each}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tooltip
&lt;/h2&gt;

&lt;p&gt;Hover or focus a button - a tooltip appears and Floating UI flips / shifts it to stay on screen. This is the one action that imports a third-party package, for the collision-aware positioning math.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flt7sdoinq7lp9eaad3t8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flt7sdoinq7lp9eaad3t8.gif" alt=" " width="720" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imports&amp;nbsp;&lt;code&gt;@floating-ui/dom&lt;/code&gt;&amp;nbsp;(&lt;code&gt;computePosition&lt;/code&gt;&amp;nbsp;+&amp;nbsp;&lt;code&gt;flip&lt;/code&gt;/&lt;code&gt;shift&lt;/code&gt;&amp;nbsp;+&amp;nbsp;&lt;code&gt;autoUpdate&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;button use:tooltip={'Saved to your library'}&amp;gt;Hover me&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gradient Border
&lt;/h2&gt;

&lt;p&gt;The CTA's rotating comet border stops spinning on hover and locks onto your cursor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8sjc04q8dy0pclixopk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8sjc04q8dy0pclixopk.gif" alt=" " width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;button class="btn-cta" use:laserAim&amp;gt;Get started&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>svelte</category>
      <category>animation</category>
    </item>
    <item>
      <title>Building an Animated 404 Ring</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Thu, 18 Jun 2026 11:44:16 +0000</pubDate>
      <link>https://dev.to/dimonb19a/building-an-animated-404-ring-2n1h</link>
      <guid>https://dev.to/dimonb19a/building-an-animated-404-ring-2n1h</guid>
      <description>&lt;p&gt;This is a write-up of the animated ring I built for a 404 page. It's a single component that renders an SVG "portal" — a set of concentric rings with drifting particles, a slow wobble, and a parallax response to the cursor. The page is server-rendered, so the component also has to be SSR-safe and reasonably cheap to run. Here's how each part works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4obnyv997zj0qoqaqx8y.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4obnyv997zj0qoqaqx8y.gif" alt=" " width="720" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating the geometry
&lt;/h2&gt;

&lt;p&gt;The rings, particles, and orbital markers are generated from their index rather than placed by hand. The particles use the golden angle so they distribute evenly around the circle without clustering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GOLDEN_ANGLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.399963&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;particles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;GOLDEN_ANGLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDrift&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isDrift&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;135&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// size, delay, and duration also derived from i&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each particle is offset by ~137.5° from the previous one, which gives even angular coverage. The first eight drift across an expanding radius; the rest sit on the ring radii. Sizes, delays, and durations come from small modulo offsets on the index so the particles don't animate in sync.&lt;/p&gt;

&lt;p&gt;The important property here is that there's no &lt;code&gt;Math.random()&lt;/code&gt;. Because the page is server-rendered, a random layout would differ between the server and the client and cause a hydration mismatch. Deriving everything from the index keeps the two outputs identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parallax from layered transforms
&lt;/h2&gt;

&lt;p&gt;The ring is a stack of six SVG groups: a background gradient, three ring systems, an orbital field, the particles, and the "404" text. Each group translates by the pointer offset times a different factor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;style:transform=&lt;/span&gt;&lt;span class="s"&gt;"translate({px * 12}px, {py * 12}px)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;  &lt;span class="c"&gt;&amp;lt;!-- outer rings --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;style:transform=&lt;/span&gt;&lt;span class="s"&gt;"translate({px * 26}px, {py * 26}px)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;  &lt;span class="c"&gt;&amp;lt;!-- inner rings --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;style:transform=&lt;/span&gt;&lt;span class="s"&gt;"translate({px * 32}px, {py * 32}px)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;  &lt;span class="c"&gt;&amp;lt;!-- "404" text --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The larger the factor, the more the layer moves, which reads as the layer sitting closer to the viewer. The "404" (×32) moves most; the outer rings (×12) least. The SVG is flat, but the differing translate amounts give it a sense of depth as the cursor moves.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;px&lt;/code&gt; and &lt;code&gt;py&lt;/code&gt; are the smoothed pointer position, scaled by an &lt;code&gt;intensity&lt;/code&gt; prop so the effect can be turned up or down per usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smoothing and wobble
&lt;/h2&gt;

&lt;p&gt;Tracking the cursor directly looks abrupt, so the pointer values are eased toward a target each frame:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SMOOTHING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.06&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;pointerX&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;pointerX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;SMOOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;pointerY&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;pointerY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;SMOOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's an exponential approach — each frame closes 6% of the remaining distance — so the ring follows the cursor with a slight lag instead of snapping to it.&lt;/p&gt;

&lt;p&gt;On top of the parallax, the rings scale slightly using a wobble value built from three sine waves at unrelated frequencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ringWobble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.008&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
   &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.005&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
   &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.003&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
  &lt;span class="nx"&gt;wobbleEnergy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three frequencies don't share a common period, so the motion doesn't visibly repeat. &lt;code&gt;wobbleEnergy&lt;/code&gt; scales the whole thing and is itself driven by how far the cursor is from the center (&lt;code&gt;0.5 + distance * 0.5&lt;/code&gt;), so the rings stay calmer near the middle and move more toward the edges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three rendering modes
&lt;/h2&gt;

&lt;p&gt;The interface lives in has three visual modes — glass, flat, and retro — set with a &lt;code&gt;data-physics&lt;/code&gt; attribute on the page. The SVG markup is identical in all three; the differences are handled in scoped CSS, so the component doesn't branch in JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcwlns00qoyptfnhts7t5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcwlns00qoyptfnhts7t5.png" alt=" " width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Glass applies an SVG turbulence filter and a glow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-physics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'glass'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.portal-ring&lt;/span&gt; &lt;span class="nc"&gt;.ring-outer-system&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;portal-warp-glass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;drop-shadow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;filter&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"warp-glass"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;feTurbulence&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"fractalNoise"&lt;/span&gt; &lt;span class="na"&gt;baseFrequency=&lt;/span&gt;&lt;span class="s"&gt;"0.12"&lt;/span&gt; &lt;span class="na"&gt;numOctaves=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;result=&lt;/span&gt;&lt;span class="s"&gt;"noise"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;feDisplacementMap&lt;/span&gt; &lt;span class="na"&gt;in=&lt;/span&gt;&lt;span class="s"&gt;"SourceGraphic"&lt;/span&gt; &lt;span class="na"&gt;in2=&lt;/span&gt;&lt;span class="s"&gt;"noise"&lt;/span&gt; &lt;span class="na"&gt;scale=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;feTurbulence&lt;/code&gt; generates a noise field and &lt;code&gt;feDisplacementMap&lt;/code&gt; offsets each pixel of the rings by it, which produces a slight warp. Flat uses the same filter with a lower frequency and no glow. Retro removes the filter entirely, switches the animation timing to &lt;code&gt;steps()&lt;/code&gt; so the motion is stepped, dashes the strokes, and renders "404" in a monospace font.&lt;/p&gt;

&lt;p&gt;One detail worth noting: the filter IDs are generated per instance (from a stable, SSR-safe id) and passed into the CSS through a custom property. That lets the stylesheet reference a filter whose ID it doesn't know ahead of time, and keeps multiple rings on a page from colliding.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS for ambient motion, JS for interaction
&lt;/h2&gt;

&lt;p&gt;Most of the motion — the rings spinning, the particles drifting, the arcs drawing, an occasional glitch flash — is CSS &lt;code&gt;@keyframes&lt;/code&gt;. The JavaScript only handles the pointer parallax and the wobble.&lt;/p&gt;

&lt;p&gt;That split has two practical benefits: the animation runs without JavaScript, so it works on touch devices and before hydration; and the only code that has to run on the main thread is the part that actually responds to the cursor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the JS bounded
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop tracking the pointer can waste work if it isn't constrained, so the component limits when it runs.&lt;/p&gt;

&lt;p&gt;It only animates while it's on screen, using an &lt;code&gt;IntersectionObserver&lt;/code&gt; to start and stop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isVisible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isVisible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;stopAnimation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop also stops itself once motion has settled, instead of running every frame indefinitely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ease pointer + wobble toward their targets…&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shouldContinueAnimating&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;frameId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;shouldContinueAnimating()&lt;/code&gt; returns false once the pointer and wobble are within a small epsilon of their targets, so the loop sleeps when nothing is moving and the next pointer event restarts it. The bounding rect is cached and only re-measured on scroll or resize, which avoids a layout read on every pointer event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility
&lt;/h2&gt;

&lt;p&gt;The SVG is &lt;code&gt;aria-hidden&lt;/code&gt;, since it's decorative; the page heading carries the actual "page not found" message. Under &lt;code&gt;prefers-reduced-motion&lt;/code&gt;, the rAF loop never starts and the CSS animations are replaced with a static state, so the ring renders without movement.&lt;/p&gt;




&lt;p&gt;That's the whole component: generated geometry, layered parallax, eased pointer input, three CSS-driven rendering modes, and a render loop that only runs when it needs to.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>404</category>
    </item>
    <item>
      <title>The Inline HTML Almost Nobody Uses</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Tue, 16 Jun 2026 16:54:33 +0000</pubDate>
      <link>https://dev.to/dimonb19a/the-inline-html-almost-nobody-uses-205d</link>
      <guid>https://dev.to/dimonb19a/the-inline-html-almost-nobody-uses-205d</guid>
      <description>&lt;p&gt;Everyone knows &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt;. But HTML has a whole second cast of inline elements that most developers go entire careers without typing once — &lt;code&gt;&amp;lt;ins&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;dfn&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;abbr&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;var&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt;. Not because they're broken or deprecated. They're rare because the use cases are genuinely narrow, and when one &lt;em&gt;does&lt;/em&gt; come up, the reflex is to reach for a &lt;code&gt;&amp;lt;span class="something"&amp;gt;&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;I bumped into this building out a prose/content section for a UI system — once you start styling text properly, you notice how many real semantic elements you'd been ignoring. So here's the whole forgotten cast, in a single sentence that legitimately uses eleven of them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y0aguicbqwqx0vemdp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y0aguicbqwqx0vemdp2.png" alt=" " width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what that actually is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
  The experiment revealed &lt;span class="nt"&gt;&amp;lt;mark&amp;gt;&lt;/span&gt;a significant energy spike&lt;span class="nt"&gt;&amp;lt;/mark&amp;gt;&lt;/span&gt; in
  the reactor core. Previous readings of
  &lt;span class="nt"&gt;&amp;lt;del&amp;gt;&lt;/span&gt;42.7 terawatts&lt;span class="nt"&gt;&amp;lt;/del&amp;gt;&lt;/span&gt; were revised to
  &lt;span class="nt"&gt;&amp;lt;ins&amp;gt;&lt;/span&gt;51.3 terawatts&lt;span class="nt"&gt;&amp;lt;/ins&amp;gt;&lt;/span&gt; after recalibration. The formula
  &lt;span class="nt"&gt;&amp;lt;var&amp;gt;&lt;/span&gt;E&lt;span class="nt"&gt;&amp;lt;/var&amp;gt;&lt;/span&gt; = &lt;span class="nt"&gt;&amp;lt;var&amp;gt;&lt;/span&gt;mc&lt;span class="nt"&gt;&amp;lt;/var&amp;gt;&amp;lt;sup&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/sup&amp;gt;&lt;/span&gt; governs the conversion,
  where H&lt;span class="nt"&gt;&amp;lt;sub&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/sub&amp;gt;&lt;/span&gt;O serves as the coolant medium.
  &lt;span class="nt"&gt;&amp;lt;abbr&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Void Energy Reactor Interface"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;VERI&lt;span class="nt"&gt;&amp;lt;/abbr&amp;gt;&lt;/span&gt; logs
  confirm the anomaly. As Dr. Vasquez noted,
  &lt;span class="nt"&gt;&amp;lt;q&amp;gt;&lt;/span&gt;the readings exceed all theoretical models&lt;span class="nt"&gt;&amp;lt;/q&amp;gt;&lt;/span&gt;
  (&lt;span class="nt"&gt;&amp;lt;cite&amp;gt;&lt;/span&gt;Void Research Quarterly&lt;span class="nt"&gt;&amp;lt;/cite&amp;gt;&lt;/span&gt;). This phenomenon, known as
  &lt;span class="nt"&gt;&amp;lt;dfn&amp;gt;&lt;/span&gt;energy cascade&lt;span class="nt"&gt;&amp;lt;/dfn&amp;gt;&lt;/span&gt;, occurs when containment thresholds are
  breached. The &lt;span class="nt"&gt;&amp;lt;s&amp;gt;&lt;/span&gt;original safety protocols&lt;span class="nt"&gt;&amp;lt;/s&amp;gt;&lt;/span&gt; have since been
  replaced.
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The cast, and where you'd actually use each
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Edits and corrections.&lt;/strong&gt; &lt;code&gt;&amp;lt;ins&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;del&amp;gt;&lt;/code&gt; are added and removed content — they pair up for change tracking, and both accept &lt;code&gt;datetime&lt;/code&gt; and &lt;code&gt;cite&lt;/code&gt; attributes so the edit is machine-readable. &lt;code&gt;&amp;lt;s&amp;gt;&lt;/code&gt; is the one people conflate with &lt;code&gt;&amp;lt;del&amp;gt;&lt;/code&gt;: it marks content that's &lt;em&gt;no longer accurate or relevant&lt;/em&gt; (a stale price, an outdated claim), which is a different statement than "this was deleted." Two elements exist precisely because those mean different things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notation.&lt;/strong&gt; &lt;code&gt;&amp;lt;sub&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;sup&amp;gt;&lt;/code&gt; are for cases where the position carries meaning — chemical formulas (H₂O), exponents (I²R), ordinals, footnote markers — not for "make it small and raised." &lt;code&gt;&amp;lt;var&amp;gt;&lt;/code&gt; is a variable in math or code, which is why &lt;code&gt;P&lt;/code&gt;, &lt;code&gt;I&lt;/code&gt;, and &lt;code&gt;R&lt;/code&gt; above are each wrapped in one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reference and meaning.&lt;/strong&gt; &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; is a &lt;em&gt;relevance&lt;/em&gt; highlight — it's literally what search engines use to highlight your query in the results. &lt;code&gt;&amp;lt;abbr title="…"&amp;gt;&lt;/code&gt; gives an abbreviation its expansion, surfaced as a tooltip. &lt;code&gt;&amp;lt;dfn&amp;gt;&lt;/code&gt; marks the one spot where a term is &lt;em&gt;defined&lt;/em&gt; (its introducing instance, not every later mention). &lt;code&gt;&amp;lt;cite&amp;gt;&lt;/code&gt; is the title of a &lt;em&gt;work&lt;/em&gt; — a book, paper, album — and notably &lt;strong&gt;not&lt;/strong&gt; a person's name, which is the thing nearly everyone gets wrong. And &lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt; is an inline quotation where the browser inserts the quotation marks for you: correct for the document's language, and correctly nested if you put a quote inside a quote. You never type a &lt;code&gt;"&lt;/code&gt; again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother
&lt;/h2&gt;

&lt;p&gt;Every one of these throws a bit of meaning into the page that a styled &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; silently discards. Screen readers announce them. &lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt; localizes its punctuation. &lt;code&gt;&amp;lt;abbr&amp;gt;&lt;/code&gt; exposes the long form. &lt;code&gt;&amp;lt;ins&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;del&amp;gt;&lt;/code&gt; are a real, parseable changelog. A &lt;code&gt;&amp;lt;span class="highlight"&amp;gt;&lt;/code&gt; &lt;em&gt;looks&lt;/em&gt; the same and means nothing.&lt;/p&gt;

&lt;p&gt;The only honest reason to avoid them is that the browser defaults are inconsistent and, frankly, ugly — &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; is highlighter-yellow, &lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt;'s marks are whatever, half of them are unstyled. But that's a few lines of CSS, not a law of physics. Give &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; a brand-tinted background, &lt;code&gt;&amp;lt;ins&amp;gt;&lt;/code&gt; a green underline, &lt;code&gt;&amp;lt;abbr&amp;gt;&lt;/code&gt; a dotted one, &lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt; colored quote marks, &lt;code&gt;&amp;lt;dfn&amp;gt;&lt;/code&gt; an italic weight — and suddenly an entire sentence of semantic richness reads as clean, deliberate typography.&lt;/p&gt;

&lt;p&gt;Most of these you'll reach for maybe once a year. But once a year &lt;em&gt;correctly&lt;/em&gt; — with the meaning intact and the styling handled — beats a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; that's lying about what it is.&lt;/p&gt;

</description>
      <category>html</category>
      <category>markdown</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Liquid glass needs a ladder</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Sun, 14 Jun 2026 13:02:34 +0000</pubDate>
      <link>https://dev.to/dimonb19a/liquid-glass-needs-a-ladder-394j</link>
      <guid>https://dev.to/dimonb19a/liquid-glass-needs-a-ladder-394j</guid>
      <description>&lt;p&gt;Everyone wants the lens right now — the Apple-style glass where content doesn't just blur behind a surface but visibly &lt;em&gt;bends&lt;/em&gt; around its rim. And you can build it on the web today: SVG displacement filters plugged into &lt;code&gt;backdrop-filter&lt;/code&gt; produce a genuinely convincing lens.&lt;/p&gt;

&lt;p&gt;Here's the problem: &lt;code&gt;backdrop-filter: url(#some-svg-filter)&lt;/code&gt; works in exactly one engine family. Safari ignores it. Firefox does something worse — it &lt;em&gt;parses&lt;/em&gt; it as valid, then renders nothing, which turns your beautiful glass panel into a fully transparent hole with text floating over whatever's behind it. And even where it works, it costs real GPU time that a phone doesn't want to spend.&lt;/p&gt;

&lt;p&gt;So the question isn't "how do I build liquid glass." It's "how do I ship liquid glass to the users who can render it without shipping a broken page to everyone else." The answer that ended up in the design system I've been building is a &lt;strong&gt;ladder&lt;/strong&gt; — four tiers of glass, each falling through to the one below automatically:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2 — geometric lens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;per-element displacement map matched to the surface's shape&lt;/td&gt;
&lt;td&gt;opted-in signature surfaces only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1 — noise warp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;one global SVG turbulence filter — the "liquid" wobble&lt;/td&gt;
&lt;td&gt;Chromium, confirmed at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0 — blur + saturate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;plain CSS &lt;code&gt;backdrop-filter: blur() saturate()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@supports (backdrop-filter: …)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;— opaque surface&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a solid background color&lt;/td&gt;
&lt;td&gt;none — this is the floor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A surface that opts into the lens but runs on Firefox falls back to the noise warp's gate, fails that too, and lands on plain blur. A browser with no &lt;code&gt;backdrop-filter&lt;/code&gt; at all gets a solid, fully readable panel. Nothing configures this per-surface; the gates compose. &lt;strong&gt;You can't regress anything — each tier only ever adds on top of what's already there.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjd7vit1ik0wtjbamdbg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjd7vit1ik0wtjbamdbg.gif" alt=" " width="600" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rest of this post is the four decisions that made the ladder actually work, because the tier table is the easy part.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Never trust &lt;code&gt;url()&lt;/code&gt; in backdrop-filter — gate it twice
&lt;/h2&gt;

&lt;p&gt;The naive version writes the SVG filter reference straight into the stylesheet. Two real failures come out of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Firefox&lt;/strong&gt; accepts the declaration as syntactically valid — so your &lt;code&gt;@supports&lt;/code&gt; checks pass — and then renders nothing. The surface becomes transparent. This is the nastiest kind of cross-browser bug: not a missing enhancement, an actively broken page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chromium has a race&lt;/strong&gt;: if the CSS references the filter before the SVG node is hydrated into the DOM, the surface flashes transparent on load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is to never let CSS reference the filter unconditionally. The stylesheet keys the tier-1 rule on an attribute — &lt;code&gt;html[data-liquid-glass]&lt;/code&gt; — and a tiny runtime sets that attribute only &lt;em&gt;after&lt;/em&gt; two things are true: the SVG filter is actually in the DOM, and the engine is one that can render it. CSS stays declarative, the gate is one attribute, and a browser that would break simply never matches the selector — leaving tier 0's plain blur standing.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Hold the blur constant across every tier
&lt;/h2&gt;

&lt;p&gt;The ladder upgrades a surface &lt;em&gt;after&lt;/em&gt; first paint: tier 0 renders immediately, then the runtime confirms the engine and swaps in the higher tier a few hundred milliseconds later. That lag is harmless only as long as the upgrade is invisible.&lt;/p&gt;

&lt;p&gt;The first version I built wasn't. I'd tuned each tier's blur on its own, and they didn't match — the base tier ended up noticeably heavier than the lens. So every page load played out the same way: a surface painted with that heavy frost, then visibly &lt;em&gt;thinned out&lt;/em&gt; to a much lighter blur the moment the lens kicked in. The material itself appeared to change mid-load. It read as a glitch, because it was one.&lt;/p&gt;

&lt;p&gt;The rule that fixed it: &lt;strong&gt;all three tiers share the exact same blur radius.&lt;/strong&gt; Stepping from tier 0 to 1 to 2 only ever swaps the &lt;em&gt;displacement&lt;/em&gt; — the wobble, the rim bend — never the frost amount. Upgrades become invisible; the surface just quietly gains refinement. If you build any multi-tier visual effect, find the parameter the eye anchors on (here: blur) and freeze it across tiers. Degrade and upgrade along every other axis.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The lens is for small chrome — and that's a feature
&lt;/h2&gt;

&lt;p&gt;The geometric lens displaces a band along the surface's rounded border. On a small element — a pill-shaped nav bar, a floating control, the circles in the video above — that rim refraction is gorgeous. On a large surface it falls apart in a specific way: any high-contrast straight edge in the backdrop &lt;em&gt;kinks&lt;/em&gt; where it crosses the lens's corner, and with nothing to frame that bend it reads as a rendering bug, not as glass.&lt;/p&gt;

&lt;p&gt;I learned this the expensive way: I put the lens on modals and a command palette, it looked wrong for exactly this reason, and I pulled it back to the noise warp. The scope rule that survived: &lt;strong&gt;tier 2 is opt-in, per signature surface, small chrome only.&lt;/strong&gt; Tier 1 stays the default for every other glass surface. Not every tier of an enhancement ladder should be a global upgrade — sometimes the top tier earns its place precisely by being rare.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Engine and feature aren't the only gates
&lt;/h2&gt;

&lt;p&gt;The full gate list for the lens tier ended up being five conditions, and only one of them is the classic &lt;code&gt;@supports&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Feature&lt;/strong&gt; — &lt;code&gt;backdrop-filter&lt;/code&gt; exists at all (tier 0's gate).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engine&lt;/strong&gt; — Chromium, because of the Firefox parse-but-don't-render trap above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Material&lt;/strong&gt; — the surface is actually glass right now. (My system has multiple visual physics (glass / flat / retro), and the lens applies only under glass; switch themes and it releases itself.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pointer&lt;/strong&gt; — fine pointers only. The lens is a desktop refinement; on touch devices it's GPU cost without payoff, so phones get the noise warp or plain blur.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion preference&lt;/strong&gt; — &lt;code&gt;prefers-reduced-motion&lt;/code&gt; disables the displacement and falls back to the static blur chemistry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;"Progressive enhancement" usually gets shortened to a single &lt;code&gt;@supports&lt;/code&gt; query. For filter-level effects, it's a &lt;em&gt;matrix&lt;/em&gt; — feature, engine, material, input device, accessibility preference — and the clean way to manage that matrix is exactly the ladder: every gate that fails just drops the surface one tier, and every tier below is already correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  The demo
&lt;/h2&gt;

&lt;p&gt;The video at the top is one page with three circles over the same scrolling backdrop — tier 0, tier 1, tier 2, side by side. Watch the stripes: under plain blur the edges soften but stay straight; under the noise warp they wobble; under the lens they bend around the rim while the center stays calm. Same markup, same blur radius, three different filters — and on a browser that can't render the fancy ones, all three circles would quietly look like the first.&lt;/p&gt;

&lt;p&gt;That's the whole idea. Liquid glass isn't one effect; it's a ladder you let each browser climb as far as it can.&lt;/p&gt;

</description>
      <category>css</category>
      <category>ui</category>
      <category>glassmorphism</category>
      <category>frontend</category>
    </item>
    <item>
      <title>The three physics of UI: glass, flat, and retro</title>
      <dc:creator>Dimon</dc:creator>
      <pubDate>Fri, 12 Jun 2026 14:26:24 +0000</pubDate>
      <link>https://dev.to/dimonb19a/the-three-physics-of-ui-glass-flat-and-retro-10gk</link>
      <guid>https://dev.to/dimonb19a/the-three-physics-of-ui-glass-flat-and-retro-10gk</guid>
      <description>&lt;p&gt;Every design system I've used has a theme switcher. Dark mode, light mode, maybe an accent color. And after you've swapped every color in the palette, all of your themes still behave like the same material: shadows fall the same way, corners curve the same way, hover states move at the same speed with the same easing. You haven't changed what the UI is made of. You've repainted it.&lt;/p&gt;

&lt;p&gt;When I designed theming for the design system I've been building, I ended up splitting it into three independent axes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Palette&lt;/strong&gt; — which colors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mode&lt;/strong&gt; — light or dark.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physics&lt;/strong&gt; — what material the interface is made of.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is about the third axis: what a "physics" actually is, why my system ships exactly three of them, and the one physics × mode combination it refuses to render.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I mean by "physics"
&lt;/h2&gt;

&lt;p&gt;A material, in UI terms, is a set of rules about &lt;strong&gt;light&lt;/strong&gt; and &lt;strong&gt;time&lt;/strong&gt;. Not a color — a behavior. My system has three:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Glass — the object emits light.&lt;/strong&gt; Surfaces are translucent with a backdrop blur. Borders are thin. Shadows aren't black — they're derived from the canvas color, so depth reads as atmosphere, not dirt. On hover, elements physically lift a few pixels, and the lifted shadow includes a glow in the accent color, because in this material the surface itself is the light source. Motion is springy: overshooting cubic-beziers, slightly slower durations. Things float.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flat — external light casts shadows.&lt;/strong&gt; Surfaces are opaque. Shadows are neutral black at very low opacity — 5 to 10 percent, the way paper shadows under office lighting actually look. Nothing glows, ever. Nothing lifts on hover. Easing curves are quick and polite, with no overshoot. This is the material almost every product you use today is made of.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retro — there is no light model.&lt;/strong&gt; No soft shadows at all. The "shadow" under a raised element is &lt;em&gt;drawn&lt;/em&gt;, not cast: a hard &lt;code&gt;3px 3px 0&lt;/code&gt; offset in a solid color, the way a pixel artist would draw it. Border radius is zero everywhere. Borders are thick. And — my favorite detail — every duration token is &lt;code&gt;0ms&lt;/code&gt;, and the easing functions are &lt;code&gt;steps(2)&lt;/code&gt; and &lt;code&gt;steps(4)&lt;/code&gt;. State changes snap. The era this material quotes couldn't animate, so the material doesn't either.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fck908urnt0lemn04cipq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fck908urnt0lemn04cipq.gif" alt=" " width="560" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In CSS, the whole mechanism is one data attribute and a set of custom properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-physics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'glass'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--ease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c"&gt;/* springy, overshoots */&lt;/span&gt;
  &lt;span class="py"&gt;--lift&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-3px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                               &lt;span class="c"&gt;/* hover raises the surface */&lt;/span&gt;
  &lt;span class="py"&gt;--shadow-lift&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt; &lt;span class="m"&gt;28px&lt;/span&gt; &lt;span class="m"&gt;-4px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--shadow-tint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                 &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--glow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c"&gt;/* ...and it glows */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-physics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'flat'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;130ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--ease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.61&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c"&gt;/* quick, no overshoot */&lt;/span&gt;
  &lt;span class="py"&gt;--lift&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                &lt;span class="c"&gt;/* nothing floats */&lt;/span&gt;
  &lt;span class="py"&gt;--shadow-lift&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* neutral, barely there */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-physics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'retro'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                               &lt;span class="c"&gt;/* state changes are instant */&lt;/span&gt;
  &lt;span class="py"&gt;--ease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                           &lt;span class="c"&gt;/* what little moves, steps */&lt;/span&gt;
  &lt;span class="py"&gt;--lift&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--shadow-lift&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--accent-dim&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* drawn, not cast */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Components only ever consume the variables — &lt;code&gt;var(--speed)&lt;/code&gt;, &lt;code&gt;var(--ease)&lt;/code&gt;, &lt;code&gt;var(--shadow-lift)&lt;/code&gt; — and never know which material they're rendered in. That's the entire trick. One component tree, three materials, zero per-component branching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why these three? Because UI history did it first
&lt;/h2&gt;

&lt;p&gt;I didn't pick three aesthetic moods I happened to like. The three materials fall out of how screen UI actually evolved:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The terminal era (retro, dark).&lt;/strong&gt; Phosphor characters on a black tube. A character grid: hard edges, no curves, no transitions — the screen redraws, it doesn't animate. Green or amber light on darkness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The paper era (retro, light).&lt;/strong&gt; When bitmapped GUIs arrived — the Xerox Alto, the original Macintosh — they were deliberately black-on-white, because the metaphor was &lt;em&gt;paper&lt;/em&gt;. Documents, desktop publishing, WYSIWYG. The geometry stayed hard and the motion stayed instant; only the light flipped. Today's neo-brutalism trend is this era quoted back at us.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The flat era (flat, both modes).&lt;/strong&gt; Metro, then iOS 7, then Material. The industry threw out texture and ornament and settled on the external-light model: opaque surfaces, minimal neutral shadows, restrained motion. This is still the mainstream default — it's what "a normal app" looks like in 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The glass era (glass, dark).&lt;/strong&gt; This one has been trying to happen for twenty years. Aero shipped translucent window chrome in 2007 and the hardware mostly couldn't carry it. iOS 7 smuggled frosted blur into the system even while flattening everything else. Then the glassmorphism trend, and now Apple has pushed translucent, light-emitting material across its entire platform line. The new trend is becoming the new mainstream.&lt;/p&gt;

&lt;p&gt;The obvious objection: where's skeuomorphism? Where are the leather calendars and felt game tables of the 2000s? My answer is that skeuomorphism was never a light model — it was the external-light model wearing theatrical decoration. Textures, bevels, stitching: all of it was &lt;em&gt;imagery&lt;/em&gt; layered onto the same physics flat design uses. Which is exactly why, when the decoration fell out of fashion in 2013, what remained underneath was flat. Materials survive trend cycles; decoration doesn't. That's the test I use for whether something deserves to be a physics.&lt;/p&gt;

&lt;p&gt;So: three light models in roughly fifty years of screens. Everything else that looks like a "different era" is a palette riding on one of these three.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I didn't expect: physics owns time
&lt;/h2&gt;

&lt;p&gt;I went into this thinking the physics axis was about shadows and corners. The bigger payoff turned out to be &lt;strong&gt;motion&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most design systems define one global motion scale — &lt;code&gt;duration-fast: 150ms&lt;/code&gt;, &lt;code&gt;ease-standard: ...&lt;/code&gt; — and every theme inherits it. Which means every theme &lt;em&gt;moves&lt;/em&gt; identically, and that's a big part of why ten themes can feel like the same product wearing different shirts.&lt;/p&gt;

&lt;p&gt;In my system, duration and easing tokens live inside the physics layer. The consequence: a modal opening under glass enters slowly with a soft overshoot, like something surfacing through liquid. The &lt;em&gt;same modal&lt;/em&gt;, same component, same code, under retro appears in two frames via &lt;code&gt;steps(2)&lt;/code&gt;. Under flat it's quick and businesslike. No component opted into any of this. Moving time into the material was a one-time architectural decision that made every current and future component era-correct for free.&lt;/p&gt;

&lt;p&gt;If you take one implementation idea from this post, take that one: &lt;strong&gt;a material is a light model plus a time model.&lt;/strong&gt; If your timing tokens are global, your materials are paint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The combination I refuse to render
&lt;/h2&gt;

&lt;p&gt;Three physics × two modes = six combinations. My system ships five. Ask for glass in light mode and the runtime silently corrects it to flat.&lt;/p&gt;

&lt;p&gt;That sounds like a bug, or at best laziness. And I know the obvious objection: Apple has spent years teaching everyone that glass looks perfect in light mode. So let me be precise about what I'm actually claiming — because it's narrower, and it's defensible.&lt;/p&gt;

&lt;p&gt;The claim is not "light glass can't exist." The claim is that &lt;em&gt;this&lt;/em&gt; glass — and most glassmorphism you see on the web — is an &lt;strong&gt;emissive&lt;/strong&gt; material, and emission doesn't survive light mode. Dark glass differentiates itself through accent glows, canvas-tinted shadows, specular highlights on translucent surfaces. All of that reads because it's light against darkness. A light surface cannot emit — light-on-light is invisible. That's optics, not taste.&lt;/p&gt;

&lt;p&gt;Strip the emission out and what's left is translucency: &lt;code&gt;backdrop-filter: blur()&lt;/code&gt;. And blur has a precondition people forget — it needs something behind it. Blur is an averaging filter, and the average of a solid color is the same solid color. You can verify this in ten seconds: open any glassmorphism demo and replace the background photo with plain white. The glass disappears.&lt;/p&gt;

&lt;p&gt;Apple never faces this problem, because Apple designed the content to always be there. Their glass is OS chrome — toolbars, docks, tab bars — floating over wallpapers and scrolling feeds. The backdrop is guaranteed. And where blur alone isn't enough, their material leans on &lt;strong&gt;refraction&lt;/strong&gt;: edge lensing, specular highlights, content-aware tinting. Notice that's a different optical mechanism from emission. What Apple ships in light mode isn't dark glass with a light palette — it's &lt;em&gt;another material&lt;/em&gt;, engineered for light from the start. Even then, they spent an entire beta cycle publicly seesawing its transparency after legibility complaints, and "Reduce Transparency" exists as an accessibility setting for a reason. Light glass walks a knife edge between invisible and illegible, and that edge is exactly where the world's best-resourced design team keeps adjusting its footing.&lt;/p&gt;

&lt;p&gt;Product UI — the kind my system targets — is mostly panels over a solid canvas. No wallpaper, no photo feed passing behind every card. In that context, an emissive glass with the emission turned off renders &lt;em&gt;identically to flat&lt;/em&gt;, except you're paying GPU cost for blur the user can't see. And a physics in my system is global: every surface, every component, the whole page. A material that's only visible where imagery happens to pass behind it isn't a material — it's an effect. Rather than ship a combination that quietly renders as flat-but-slower, the runtime corrects it.&lt;/p&gt;

&lt;p&gt;So the matrix is deliberately ragged:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;dark&lt;/th&gt;
&lt;th&gt;light&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;glass&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;→ corrected to flat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;flat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;retro&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I used to feel uneasy about that hole — asymmetric APIs look like design debt. I now think the opposite: &lt;strong&gt;mode support is a property of the material.&lt;/strong&gt; The asymmetric matrix is physical honesty. The dishonest version is the complete matrix where one cell silently renders as a worse version of another cell.&lt;/p&gt;

&lt;p&gt;And Apple doesn't contradict this — Apple confirms it. They earned light mode by engineering a refraction-first material from scratch, not by relaxing a dark one. If I ever want light glass, that's the honest path: a fourth physics, built for light, with its own rules. Never a palette swap on this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The clamp I got wrong
&lt;/h2&gt;

&lt;p&gt;Here's the part that earns the previous section, because I almost made the same call twice and one of them would have been a mistake.&lt;/p&gt;

&lt;p&gt;Originally retro was clamped to dark mode too. CRTs are dark — obviously. But when I actually audited &lt;em&gt;why&lt;/em&gt; retro needed darkness, the material decomposed into two layers. There's a phosphor layer — the emissive, glowing-characters-on-black part — which is genuinely dark-only, same argument as glass. And there's a structural layer — zero radius, stepped motion, hard drawn offsets — which doesn't reference light at all.&lt;/p&gt;

&lt;p&gt;Put the structural layer on warm paper-colored ink with a typewriter monospace and you don't get "broken retro." You get the paper era from the history section: early Mac, print, neo-brutalism. Historically real, visually coherent, and it cost almost nothing to support because the geometry never cared about mode in the first place. The dark coupling had been &lt;em&gt;palette convention&lt;/em&gt;, not physics.&lt;/p&gt;

&lt;p&gt;So I asked the same question of two materials — "can this work in light mode?" — and got opposite answers. Glass: no, because its identity is emission. Retro: yes, because its identity is geometry. The answer lives in the material, not in the theming API. A blanket rule in either direction — "every physics supports both modes" or "exotic physics are dark-only" — would have been wrong once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd steal from this if I were you
&lt;/h2&gt;

&lt;p&gt;You don't need three materials. But if you're building or maintaining a theme system, four things transfer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Make material an axis, orthogonal to palette and mode.&lt;/strong&gt; Even two materials force the right architecture: components consume physics variables and stop hardcoding what light does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define a material as light + time.&lt;/strong&gt; Shadows, radius, blur — and durations, easings, hover displacement. The motion half is the one everyone forgets and the one users feel most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let materials refuse modes.&lt;/strong&gt; Auto-correct invalid combinations at the runtime level instead of rendering something subtly broken. A ragged support matrix you can explain beats a complete one you have to apologize for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before adding a theme, ask: new light model, or new colors?&lt;/strong&gt; Almost always it's new colors — a palette entry, cheap, add as many as you want. A new light model is rare and expensive. UI history has produced about three in fifty years. If you think you've found a fourth, you're either wrong or you're early — both are worth knowing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole industry is mid-transition into the glass era right now, which makes it a strange and useful moment: for once, the mainstream material and the trending material are different. A theme switcher can't express that difference. A physics axis can.&lt;/p&gt;

</description>
      <category>css</category>
      <category>ui</category>
      <category>frontend</category>
      <category>designsystem</category>
    </item>
  </channel>
</rss>
