错误处理有不同的方式。

JavaScript 和 Python 是抛出异常, Rust 语言是变相抛出异常。
C 语言和 Go 语言则是返回一个错误值,你必须判断该值是否为 -1 或空值。
我一直想知道,哪一种方式更好?
前不久,我读到一篇多年前的文章,明确提出抛出异常好于返回状态码。他的理由很有说服力,文章好像还没有中译,我就翻译出来了。
作者:内德·巴切尔德(Ned Batchelder)
原文网址:nedbatchelder.com

在软件中,错误处理有两种方式:抛出异常(throwing exceptions)和返回状态码(returning status codes)。
几乎所有人都认为异常是更好的处理方式,但有些人仍然更喜欢返回状态码。本文解释为什么异常是更好的选择。
异常可以让你在大部分代码中省去错误处理步骤。它会自动通过不捕捉异常的层,向上传递。你因此可以编写完全没有错误处理逻辑的代码,这有助于保持代码的简洁易读。
让我们比较一下,编写同一个简单函数的两种方法。
先是返回状态码。
STATUS DoSomething(int a, int b) { STATUS st; st = DoThing1(a); if (st != SGOOD) return st; st = DoThing2(b); if (st != SGOOD) return st; return SGOOD; }
上面示例中,必须判断DoThing1(a)和DoThing2(b)的返回值是否正常,才能进行下一步。
如果是抛出异常,就不需要中间的错误判断了。
void DoSomething(int a, int b) { DoThing1(a); DoThing2(b); }
这只是最简单的情况,如果遇到复杂的场景,状态码带来的噪音会更严重,异常则可以保持代码的整洁。
状态码会占用宝贵的返回值,你不得不增加代码,判断返回值是否正确。
有些函数本来只需要返回一个正常值,现在不得不增加返回错误的情况。随着时间的推移,代码量不断增长,函数变得越来越大,返回值也越来越复杂。
比如,很多函数的返回值是有重载的:"如果失败,则返回 NULL",或者失败返回 -1。结果就是每次调用这个方法,都需要检查返回值是否是 NULL 或 -1。如果函数后来增加新的错误返回值,则必须更新所有调用点。
如果是抛出异常,那么函数就总是成功的情况下才返回,所有的错误处理也可以简化放在一个地方。
状态码通常是一个整数,能够传递的信息相当有限。假设错误是找不到文件,那么是哪一个文件呢?状态码无法传递那么多信息。
返回状态码的时候,最好记录一条错误消息,放在专门的错误日志里面,调用者可以从中获取详细信息。
异常完全不同,它是类的实例,因此可以携带大量信息。由于异常可以被子类化,不同的异常可以携带不同的数据,从而形成非常丰富的错误消息体系。
某些函数无法返回状态码。例如,构造函数就没有显式的返回值,因此无法返回状态码。还有一些函数(比如析构函数)甚至无法直接调用,更不用说返回值了。
这些没有返回值的函数,如果不使用异常处理,你不得不想出其他方法来给出错误信息,或者假装这些函数不会失败。简单的函数或许可以做到无故障,但代码量会不断增长,失败的可能性也随之增加。如果没有办法表达失败,系统只会变得更加容易出错,也更加难以捉摸。
考虑一下,如果程序员疏忽了,没有写错误处理代码,会发生什么情况?
如果返回的状态码没有经过检查,错误就不会被发现,代码将继续执行,就像操作成功一样。代码稍后可能会失败,但这可能是许多步操作之后的事情,你如何将问题追溯到最初错误发生的地方?
相反的,如果异常未被立刻捕获,它就会在调用栈中向上传递,要么到达更高的 catch 块,要么到达顶层,交给操作系统处理,操作系统通常会把错误呈现给用户。这对程序是不好的,但错误至少是可见的。你会看到异常,能够判断出它抛出的位置,以及它应该被捕获的位置,从而修复代码。
这里不讨论错误未能报出的情况,这种情况无论是返回状态码还是抛出异常,都没用。
所以,对于报出的错误没有被处理,可以归结为两种情况:一种是返回的状态码会隐藏问题,另一种是抛出异常会导致错误可见。你会选择哪一种?
著名程序员 Joel Spolsky 认为,返回状态码更好,因为他认为异常是一种糟糕得多的 goto 语句。
"异常在源代码中是不可见的。阅读代码块时,无法知道哪些异常可能被抛出,以及从哪里抛出。这意味着即使仔细检查代码,也无法发现潜在的错误。"
"异常为一个函数创建了太多可能的出口。要编写正确的代码,你必须考虑每一条可能的代码路径。每次调用一个可能抛出异常的函数,但没有立即捕获异常时,函数可能突然终止,或者出现其他你没有想到的代码路径。"
这些话听起来似乎很有道理,但如果改为返回状态码,你就必须显式地检查函数每一个可能的返回点。所以,你是用显式的复杂性换取了隐式的复杂性。这也有缺点,显式的复杂性会让你只见树木不见森林,代码会因此变得杂乱无章。
当面临这种显式复杂性时,程序员会写得不胜其烦,最后要么用自定义的方法隐藏错误处理,要么索性省略错误处理。
前者隐藏错误处理,只会将显式处理重新变为隐式处理,并且不如原始的 Try 方法方便和功能齐全。后者省略错误处理更糟糕,程序员假设某种错误不会发生,从而埋下风险。
返回状态码很难用,有些地方根本无法使用。它会劫持返回值。程序员很容易不去写错误处理代码,从而在系统中造成无声的故障。
异常优于状态码。只要你的编程语言提供了异常处理工具,请使用它们。
(完)
这里记录每周值得分享的科技内容,周五发布。
本杂志开源,欢迎投稿。另有《谁在招人》服务,发布程序员招聘信息。合作请邮件联系(yifeng.ruan@gmail.com)。

北京门头沟区的千年古刹灵岳寺,从1979年开始关闭,直到这个月修缮完成,对外开放。修缮过程中,在墙上留了一个观察窗,展示原始的唐代和辽代的砖块。(visuals_china@instagram)
程序员一般都使用代码高亮,就是代码有不同的颜色(下图),方便阅读。

问题就来了,什么样的颜色组合,最适合阅读代码?
大多数的人大概跟我一样,就挑自己觉得好看的。比如下图五颜六色的,我觉得很悦目。

不久前,我读到一篇文章,作者说:错了,好看的颜色未必适合阅读代码。
正确的代码高亮,应该让你一眼注意到最重要的代码信息。太多的颜色,只会让人眼花缭乱,找不到重点。
他提出代码高亮的五条原则。
(1)最多使用4种颜色,再多的颜色会分散注意力。
(2)变量、函数和类的定义最重要,一般来说,它们是代码的最关键部分,所以定义时的变量名、函数名、类名应该高亮显示。
(3)注释也很重要,往往是关键信息,或者是作者希望别人阅读的信息,所以要高亮显示。很多配色方案将注释变灰,这是不对的。
(4)常量和函数嵌套(即括号)也是重要信息,需要高亮显示。
(5)其他代码不必高亮,包括变量读取、函数调用、关键字(class、function、if、else 等等),因为它们无所不在,你很少会去寻找它们。
这五条原则,你认同吗?
如果认同的话,你可以试试看作者设计的配色方案 Alabaster。
下面就是这个方案的高亮效果。

作为对比,再看看前面那个"好看"配色的高亮效果。

你觉得,哪一种效果好,是否突出了代码的关键信息?
如果同时使用多家公司的大模型,大家怎么解决,每家的 API 都买?
今天介绍一个我正在使用的"AI 网关",可以一个接口调用50多个主流模型。它就是七牛云 AI 大模型推理平台"。

常用的主流模型,它基本都提供了(比如国外的 OpenAI、Gemini、Claude,国内的 DeepSeek、千问、豆包、智谱、Kimi)。模型广场(上图)列出了部分模型,完整列表需要查询 API。
七牛云是老牌的云服务商,成立15年了,还是上市公司,相当靠谱。它的稳定性和服务质量,可以放心,遇到问题不会找不到人。
用它的"统一接口",有几个显著优点。(1)使用方便,开通和付费都是人民币,不需要海外信用卡;(2)接入简单,各种的 AI 客户端、IDE、命令行、MCP 都可以接入,支持 OpenAI/Claude 两种 API 格式,鉴权与计费都是统一的;(3)低延迟 + 高吞吐,使用 AIPerf 测它调用谷歌模型,平均响应时间是700多毫秒,平均吞吐量是 184.6 Token/s;(4)高阈值,普通用户的上限是每分钟500个请求,每分钟令牌数500万,一般情况下足够了。
目前,它的"新用户推广活动"还没结束。新用户有免费的 300万 Token,如果你再邀请一位好友来使用,你会再得到 500万 Token,好友则得到 1000万 Token。
总结就是一句话,如果你使用我的推广链接去注册(下图),你会得到免费的 300万 + 1000万 Token。然后,你生成自己的推广链接,每邀请一位好友,就再多 500万 Token。

1、本周,一架美国的波音 737 Max 客机在11000米高空,遭到不明物体的撞击,挡风玻璃砸碎了,碎玻璃把机长的手臂划破了很多口子。

这样的高度不可能是鸟,而且由于挡风玻璃有高温烧焦的痕迹,也不可能是冰雹,只可能是陨石或太空垃圾。

如果确定是太空垃圾,它将是史上第一架被太空垃圾砸中的飞机。
([更新] 最新消息是,这架飞机可能撞上了高空气象气球。这应该也是史上第一例飞机撞气球。)
2、一家中国轮胎公司在吉林长春,制造了世界最大轮胎。

该轮胎直径超过4米,重量超过6吨,用于大型露天矿车。
3、世界哪个国家没有蚊子?
以前,唯一没有蚊子的国家是冰岛。但是本周,冰岛一个农民在自家农场发现了三只活着的蚊子。

世界每一个国家从此都有蚊子。
冰岛政府认为,这些蚊子是随飞机或轮船来到冰岛。但深层的原因是,全球变暖导致蚊子可以在高纬度地区生存和繁衍。
4、日本三重大学的研究发现,日本的夏天比42年前的1982年,长了三周。

这就是气候变化,夏季越来越长,且温度越来越高,冬季长度基本不变,春季和秋季显著缩短,只有一两周。
5、X 公司(前身为推特)正式推出用户名市场,用户可以在那里购买回收的用户名。

平台的政策是,用户一段时间(比如6个月)没有登录,它就可以回收用户名。至于怎么处理这些用户名,各平台的政策不一样。
X 是业内第一家公开出售用户名的平台,某些稀有用户名(比如@one、@fly、@compute)的价格从2500美元一直到100万美元。
这件事情再次提醒我们,你的用户名不属于你,属于平台。平台只是暂时借给你使用,随时可以回收用户名。
1、TypeScript 类似于 C#(英文)

这篇文章提出 TypeScript 的语法很像 C#,因为它们的设计者就是同一个人。
所以,当 TypeScript 需要提高性能时,可以考虑将代码转为 C#,现在 C# 也是跨平台的。
2、如何将网页动画压缩到每帧16.67毫秒(英文)

显示器的刷新率一般是每秒60帧,为了让动画流畅,每帧的渲染时间最好不超过16.67毫秒。本文介绍一个 CSS 知识点,可以提高网页动画性能。
3、从 HTTP 轮询到 MQTT:我们在 AWS IoT Core 的架构演进(中文)

作者公司的物联网项目,最早采用 HTTP 轮询,后来改成了 MQTT 协议,采用 AWS IoT Core 云服务。(@Konata9 投稿)
4、为什么 NetNewsWire 不是一个 Web 应用(英文)

NetNewsWire 是一个桌面的 RSS 阅读器,总是有人要求作者,将其改成 Web 应用。本文是作者解释为什么不开发 Web 版,理由很充分。
5、Burrows-Wheeler 变换(英文)

本文介绍一种奇妙的算法 Burrows-Wheeler Transform(简称 BWT),它会把字符串打乱,使得相同的字符倾向于组合在一起。
它的奇妙之处是,打乱后还可以用逆运算,将字符串还原,从而使得这种算法很适合用来压缩文本。

越来越多的公司推出了自己的 AI 浏览器,它通过截图阅读屏幕。现在已经出现在屏幕嵌入人眼不可见、但机器可见的文本,进行模型注入,让模型执行恶意操作(上图)。
7、Unicode 隐形字符的病毒(英文)

本文介绍一个令人叹为观止的 JS 病毒,它的恶意代码是用 Unicode 隐形字符写的,人眼看不见(上图的空白处),但是引擎会执行这些代码。

1、OpenZL

Meta 公司新推出的一种压缩工具,适合压缩结构化数据(比如数据库),压缩比更高,速度更快,参见介绍文章。
2、Handy

免费、开源的跨平台桌面应用,用来语音转文本。

一个自搭建的 Web 服务,用户输入网址,并指定 CSS 选择器,它就生成该网址的 RSS 源,代码开源。
4、Judo

一个跨平台的 Git/JJ 桌面图形客户端。
5、htmldocs

一个 React 组件,用来在网页中创建、编辑、预览 PDF 文档。(@Haopeng138 投稿)
6、Cent

开源的多人协作记账 Web 应用,数据保存在你的私人 GitHub 仓库。(@glink25 投稿)
7、Shell360

开源的跨平台 SSH 客户端,支持 Windows、macOS、Linux、Android 和 iOS。(@nashaofu 投稿)
8、015

自托管的临时文件共享平台。(@keven1024 投稿)
9、MHtool
一个命令行的 Python 数学工具包,一个脚本集成了数学计算、数据处理和图形绘制功能。(@sudo1123 投稿)
10、TextGO

一个跨平台的桌面应用,可以指定统一的快捷键,各种应用都适用,并能根据选中的内容,执行不同的操作。(@C5H12O5 投稿)
1、AICrop

使用 AI 模型裁剪图片的免费网页工具,自动生成适合不同社媒平台(如 Instagram、X/Twitter、TikTok)的图片。(@indielucas 投稿)

免费的文生图网站,不需要注册,据说是作者用自己的几台 4090 搭建的,用的是千问模型。(@rustflare 投稿)

大模型驱动的代码审计平台,为开发者提供代码质量分析和审查服务。(@lintsinghua 投稿)

著名开发者 Simon Willison 使用 AI 生成的代码行数统计网站,分析一个项目到底有多少行代码,评估开发时间,参见介绍文章。
5、播客生成器(Podcast Generator)

开源的文本转播客工具,需要 OpenAI 密钥。(@justlovemaki 投稿)
6、MuseBot

一个接入聊天软件的智能机器人,实现了 AI 对话与智能回复,支持多种大模型,可以接入 Telegram、飞书、钉钉、微信等平台。(@yincongcyincong 投稿)

世界第一本 AI 生成的百科全书,读者想查什么条目,AI 实时生成。

世界上所有书籍的国际书号(ISBN),可视化成一个图书馆的书架。查询某本书,可以显示该书所在的书架,代码开源。(@kohunglee 投稿)

这个仓库收集了各种流行网站(Airbnb, Amazon, Instagram, Netflix, TikTok 等)的开源克隆,已经超过100多个网站了。
2025年8月,广东江门的地下中微子观测站(JUNO)正式运行。
它位于阳江和台山两座核电站的中间位置,可以接收核电站产生的中微子。

它的核心装置是一个探测器,外形为直径35.4米的透明球形容器,放置在地下700米深处,用来探测中微子。
探测器内部填充了2万吨高灵敏度的液体闪烁体,任何中微子与这种液体的相互作用都会产生闪光。
透明球体周围安装了约43,212个光探测器,持续监测着球体,时刻准备着捕捉任何闪光的出现。


正是根据这些闪光,科学家才能够确定中微子的特性。
整个装置封装成一个球形水箱,水箱本身浸没在直径为44米超纯水池中,水池顶部有一个巨大的探测器,称为顶部跟踪器(下图),其作用是识别是否有混入的宇宙粒子,以避免与来自核电站的中微子混淆。

2、乔卢特卡桥
1996年到1998年,一家日本公司在中美洲国家洪都拉斯,建设了一座该国最长的桥"乔卢特卡桥",长度为484米。
刚造好,就遇到了当年最大的台风。桥的本体没有受损,但是引桥都被摧毁了(下图)。

还没等洪都拉斯政府修复引桥,更糟糕的事情发生了,乔卢特卡河由于台风引发的洪水而直接改道了。

上图就是乔卢特卡桥现在的状况,只剩下一段孤零零的桥面,耸立在河边。
一个老生常谈的问题,开源项目怎么才能可持续地健康发展下去?
很多人会说,需要有公司为开发者的时间付费。
这么说固然没错,但是更好的支持方式不是出钱,而是出人。最著名的例子就是 Linux 内核,绝大多数代码贡献都来自那些从内核获利的公司的员工。
如果那些公司不出人,只给项目团队捐款,让他们自己去写代码,内核不可能发展得这么快、这么好。
另一个很好的例子是 Ruby 语言。2019年时,Shopify 公司的一位工程师见到了 Ruby 语言的创始人 Matz。
工程师问 Matz 需要什么,Matz 回答说:"我缺人手。"
工程师回到公司商量以后,Shopify 的 Ruby 团队开始参与 Ruby 语言的开发,结果产出了大量成果,使得 Ruby 语言核心提交者增加了十几人。
如果那天 Matz 回答"我缺钱",然后 Shopify 捐款数十万或数百万美元,这对 Ruby 其实未必有利。
首先,谁敢担保 Ruby 的开发方向和决策,以后不会受到 Shopify 的影响?某个功能被接受到底是因为它本身的优点,还是因为它来自一个大赞助商?Ruby 又敢不敢拒绝来自 Shopify 的提案?赞助商的偏好可能会左右项目的开发。
其次,钱就是这样,一旦有了,你会产生依赖。如果将来大赞助商退出,你就不得不裁员,停止一些项目等等。所以,接受赞助的实体和个人往往会不自觉地考虑捐赠者的偏好,这样资金才能源源不断地涌入。
我并不是说开源项目不应该接受捐助,而是说大额的捐助难免会产生一些副作用。
下一次,如果你想支持一个开源项目,除了捐款,更好的方式是投入项目开发,为创始人分担一些工作。
1、
我们要习惯 AI 培养出来的一代学生,他们的屏幕上满是文字,脑子里却空无一物。
2、
最明智的举措不是追逐潮流,而是种下一棵树,让时间来发挥作用。树木不会在明天带来回报,它们十年后才会产生回报。它们默默地生长,使周围的一切都变得更好:树荫、价值、美感、寿命。
-- 《设计的复利》
3、
OpenAI 发布了自家的浏览器 Atlas,但它其实是反浏览器,尽可能避免用户浏览互联网。比如,你用它搜索"泰勒·斯威夫特",它会告诉你那是谁,但不会返回任何指向泰勒·斯威夫特个人网站的链接。
-- 《ChatGPT 的 Atlas:反 Web 的浏览器》
4、
工程师不仅需要具备技术技能,还要具备软技能,也就是人际交往的技能。
如果你不理解人类社会的复杂性,就无法理解公司或团队的工作方式,最终影响到自己的产出和扩大影响力。
-- 《被低估的软技能》
技术公司的口号比拼(#323)
任正非的三篇最新谈话(#273)
程序员需要担心裁员吗?(#223)
网络收音机的设计(#173)
(完)
Hey, I’m Mat, but “Wilto” works too — I’m here to teach you JavaScript. Well, not here-here; technically, I’m over at Piccalil.li’s JavaScript for Everyone course to teach you JavaScript. The following is an excerpt from the Iterables and Iterators module: the lesson on Iterators.
Iterators are one of JavaScript’s more linguistically confusing topics, sailing easily over what is already a pretty high bar. There are iterables — array, Set, Map, and string — all of which follow the iterable protocol. To follow said protocol, an object must implement the iterable interface. In practice, that means that the object needs to include a [Symbol.iterator]() method somewhere in its prototype chain. Iterable protocol is one of two iteration protocols. The other iteration protocol is the iterator protocol.
See what I mean about this being linguistically fraught? Iterables implement the iterable iteration interface, and iterators implement the iterator iteration interface! If you can say that five times fast, then you’ve pretty much got the gist of it; easy-peasy, right?
No, listen, by the time you reach the end of this lesson, I promise it won’t be half as confusing as it might sound, especially with the context you’ll have from the lessons that precede it.
An iterable object follows the iterable protocol, which just means that the object has a conventional method for making iterators. The elements that it contains can be looped over with for…of.
An iterator object follows the iterator protocol, and the elements it contains can be accessed sequentially, one at a time.
To reiterate — a play on words for which I do not forgive myself, nor expect you to forgive me — an iterator object follows iterator protocol, and the elements it contains can be accessed sequentially, one at a time. Iterator protocol defines a standard way to produce a sequence of values, and optionally return a value once all possible values have been generated.
In order to follow the iterator protocol, an object has to — you guessed it — implement the iterator interface. In practice, that once again means that a certain method has to be available somewhere on the object's prototype chain. In this case, it’s the next() method that advances through the elements it contains, one at a time, and returns an object each time that method is called.
In order to meet the iterator interface criteria, the returned object must contain two properties with specific keys: one with the key value, representing the value of the current element, and one with the key done, a Boolean value that tells us if the iterator has advanced beyond the final element in the data structure. That’s not an awkward phrasing the editorial team let slip through: the value of that done property is true only when a call to next() results in an attempt to access an element beyond the final element in the iterator, not upon accessing the final element in the iterator. Again, a lot in print, but it’ll make more sense when you see it in action.
You’ve seen an example of a built-in iterator before, albeit briefly:
const theMap = new Map([ [ "aKey", "A value." ] ]);
console.log( theMap.keys() );
// Result: Map Iterator { constructor: Iterator() }
That’s right: while a Map object itself is an iterable, Map’s built-in methods keys(), values(), and entries() all return Iterator objects. You’ll also remember that I looped through those using forEach (a relatively recent addition to the language). Used that way, an iterator is indistinguishable from an iterable:
const theMap = new Map([ [ "key", "value " ] ]);
theMap.keys().forEach( thing => {
console.log( thing );
});
// Result: key
All iterators are iterable; they all implement the iterable interface:
const theMap = new Map([ [ "key", "value " ] ]);
theMap.keys()[ Symbol.iterator ];
// Result: function Symbol.iterator()
And if you’re angry about the increasing blurriness of the line between iterators and iterables, wait until you get a load of this “top ten anime betrayals” video candidate: I’m going to demonstrate how to interact with an iterator by using an array.
“BOO,” you surely cry, having been so betrayed by one of your oldest and most indexed friends. “Array is an iterable, not an iterator!” You are both right to yell at me in general, and right about array in specific — an array is an iterable, not an iterator. In fact, while all iterators are iterable, none of the built-in iterables are iterators.
However, when you call that [ Symbol.iterator ]() method — the one that defines an object as an iterable — it returns an iterator object created from an iterable data structure:
const theIterable = [ true, false ];
const theIterator = theIterable[ Symbol.iterator ]();
theIterable;
// Result: Array [ true, false ]
theIterator;
// Result: Array Iterator { constructor: Iterator() }
The same goes for Set, Map, and — yes — even strings:
const theIterable = "A string."
const theIterator = theIterable[ Symbol.iterator ]();
theIterator;
// Result: String Iterator { constructor: Iterator() }
What we’re doing here manually — creating an iterator from an iterable using %Symbol.iterator% — is precisely how iterable objects work internally, and why they have to implement %Symbol.iterator% in order to be iterables. Any time you loop through an array, you’re actually looping through an iterator created from that Array. All built-in iterators are iterable. All built-in iterables can be used to create iterators.
Alternately — preferably, even, since it doesn’t require you to graze up against %Symbol.iterator% directly — you can use the built-in Iterator.from() method to create an iterator object from any iterable:
const theIterator = Iterator.from([ true, false ]);
theIterator;
// Result: Array Iterator { constructor: Iterator() }
You remember how I mentioned that an iterator has to provide a next() method (that returns a very specific Object)? Calling that next() method steps through the elements that the iterator contains one at a time, with each call returning an instance of that Object:
const theIterator = Iterator.from([ 1, 2, 3 ]);
theIterator.next();
// Result: Object { value: 1, done: false }
theIterator.next();
// Result: Object { value: 2, done: false }
theIterator.next();
// Result: Object { value: 3, done: false }
theIterator.next();
// Result: Object { value: undefined, done: true }
You can think of this as a more controlled form of traversal than the traditional “wind it up and watch it go” for loops you’re probably used to — a method of accessing elements one step at a time, as-needed. Granted, you don’t have to step through an iterator in this way, since they have their very own Iterator.forEach method, which works exactly like you would expect — to a point:
const theIterator = Iterator.from([ true, false ]);
theIterator.forEach( element => console.log( element ) );
/* Result:
true
false
*/
But there’s another big difference between iterables and iterators that we haven’t touched on yet, and for my money, it actually goes a long way toward making linguistic sense of the two. You might need to humor me for a little bit here, though.
See, an iterable object is an object that is iterable. No, listen, stay with me: you can iterate over an Array, and when you’re done doing so, you can still iterate over that Array. It is, by definition, an object that can be iterated over; it is the essential nature of an iterable to be iterable:
const theIterable = [ 1, 2 ];
theIterable.forEach( el => {
console.log( el );
});
/* Result:
1
2
*/
theIterable.forEach( el => {
console.log( el );
});
/* Result:
1
2
*/
In a way, an iterator object represents the singular act of iteration. Internal to an iterable, it is the mechanism by which the iterable is iterated over, each time that iteration is performed. As a stand-alone iterator object — whether you step through it using the next method or loop over its elements using forEach — once iterated over, that iterator is past tense; it is iterated. Because they maintain an internal state, the essential nature of an iterator is to be iterated over, singular:
const theIterator = Iterator.from([ 1, 2 ]);
theIterator.next();
// Result: Object { value: 1, done: false }
theIterator.next();
// Result: Object { value: 2, done: false }
theIterator.next();
// Result: Object { value: undefined, done: true }
theIterator.forEach( el => console.log( el ) );
// Result: undefined
That makes for neat work when you're using the Iterator constructor’s built-in methods to, say, filter or extract part of an Iterator object:
const theIterator = Iterator.from([ "First", "Second", "Third" ]);
// Take the first two values from theIterator:
theIterator.take( 2 ).forEach( el => {
console.log( el );
});
/* Result:
"First"
"Second"
*/
// theIterator now only contains anything left over after the above operation is complete:
theIterator.next();
// Result: Object { value: "Third", done: false }
Once you reach the end of an iterator, the act of iterating over it is complete. Iterated. Past-tense.
And so too is your time in this lesson, you might be relieved to hear. I know this was kind of a rough one, but the good news is: this course is iterable, not an iterator. This step in your iteration through it — this lesson — may be over, but the essential nature of this course is that you can iterate through it again. Don’t worry about committing all of this to memory right now — you can come back and revisit this lesson anytime.
ConclusionI stand by what I wrote there, unsurprising as that probably is: this lesson is a tricky one, but listen, you got this. JavaScript for Everyone is designed to take you inside JavaScript’s head. Once you’ve started seeing how the gears mesh — seen the fingerprints left behind by the people who built the language, and the good, bad, and sometimes baffling decisions that went into that — no itera-, whether -ble or -tor will be able to stand in your way.

My goal is to teach you the deep magic — the how and the why of JavaScript, using the syntaxes you’re most likely to encounter in your day-to-day work, at your pace and on your terms. If you’re new to the language, you’ll walk away from this course with a foundational understanding of JavaScript worth hundreds of hours of trial-and-error. If you’re a junior developer, you’ll finish this course with a depth of knowledge to rival any senior.
I hope to see you there.
by Mat Marquis (hello@smashingmagazine.com) at October 27, 2025 01:00 PM
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11914
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

好消息,Canvas也支持锥形渐变了,方法名称是createConicGradient()方法。
兼容性参见下图:

所有现代浏览器都已经支持,且已经支持了2年了。
最近遇到了个需求,必须使用Canvas实现锥形渐变,而不能是CSS。
那就是下图所示的饼图效果:

如果单论实现,无论是CSS还是Canvas都可以,CSS更佳,因为成本更低。
但是,注意看图片的左上角,还有一个下载为图片的功能。
html2canvas是不支持CSS锥形渐变的,保存的图片是空白。
所以需要Canvas实现,转换为图片,这样就能截图保存了。

也是就用到了这个createConicGradient()方法来绘制饼图。
语法如下:
createConicGradient(startAngle, x, y)
表示创建一个锥形渐变开始的位置。
其中startAngle是锥形的圆弧所在,需要注意的是,和CSS不同,在Canvas中,角度0表示的是三点钟方向,有就是水平朝右,但是CSS是12点钟方向,垂直朝上。
我们通过一个案例来了解下createConicGradient()方法是如何使用的。
<canvas></canvas>
绘制代码为:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
// 尺寸
const size = 300;
canvas.width = size;
canvas.height = size;
// 创建锥形渐变
const gradient = ctx.createConicGradient(-0.5 * Math.PI, size / 2, size / 2);
// 色带颜色
const arrColor = ['#10239E', '#2F54EB','#597EF7', '#85A5FF', '#D6E4FF'];
// 各自占据的百分比
const arrPercent = [0.07, 0.13, 0.2, 0.27, 0.33];
// 添加中断点
let sumPercent = 0;
arrPercent.forEach((percent, index) => {
gradient.addColorStop(sumPercent, arrColor[index]);
sumPercent += percent;
gradient.addColorStop(sumPercent, arrColor[index]);
});
// 设置渐变为填充样式
ctx.fillStyle = gradient;
// 绘制圆形
ctx.ellipse(size / 2, size / 2, size / 2 - 30, size / 2 - 30, 0, 0, 2 * Math.PI);
// 填充
ctx.fill();
在浏览器中的实时绘制效果如下:
完美!
在过去,本文所展示的饼图效果,我们可以使用arc()扇形绘制方法模拟,非本文重点,不做展开。
如果你让AI实现锥形渐变,大概率会使用CSS conic-gradient()锥形渐变,但是你让他解决html2canvas无法导出图片的问题,AI就会扯东扯西,不知道该如何是好了。
所以,新时代,我们学习前端,可能具体的技术细节不需要掌握,但是,结构、方向、策略、细节还是需要掌握的。
比方说本文的案例,你需要知道使用Canvas来绘制渐变,指导AI使用这个方法,他就能搞定,否则,AI就会迷路。
其中这样的例子很多。
例如,之前有同事想要在手机端实现划词交互效果,问AI,结果AI弄了一大堆代码,使用鼠标事件实现的,很啰嗦。于是同事就问我,我就说试试selectionchange事件,这么一点拨,AI立马就糊涂状态变成了大师。
还有案例,可编辑输入框类似于AT成组的交互,AI的实现也是,洋洋洒洒,代码多得不得了,感觉把整个编辑器都弄进来了。
实际,走DOM监控单向数据流,就百来行代码的事情。
所以说,学习新特性还是有用的。
我也不会担心自己被AI取代,只要持续学习,不排斥新事物,我只会比之前更加具有竞争力。
好了,又扯多了,最后,我家侍妾慕沛灵压阵,道友请转发!

😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11914
(本篇完)
这里记录每周值得分享的科技内容,周五发布。
本杂志开源,欢迎投稿。另有《谁在招人》服务,发布程序员招聘信息。合作请邮件联系(yifeng.ruan@gmail.com)。

泡泡玛特在浙江嵊山岛的废弃渔村,举办了一个该品牌的公共艺术展,主角公仔的性格定位是"在荒野中寻找自我"。(via)
10月10日,罗永浩的新节目《罗永浩的十字路口》,邀请了嘉宾"影视飓风"创始人 Tim(潘天鸿)。

他们进行了一场对谈,Tim 从头部 UP 主的角度,分享自己对视频行业怎么看,有意思的内容非常多。
他们谈了三个小时,谈得非常深入尽兴,整理成文字稿有六、七万字。想看全文的同学,自己网上找,也可以下载字幕文件或者 AI 转录。
下面是我的摘录,尽量囊括那些我觉得有意思的点。下面主要是 Tim 的叙述,也包括一些罗永浩的话,出于篇幅和阅读流畅性的考虑,就不一一注明了。
1、
短视频的传播能力比长视频强很多。
人的本性就是追求更高的信息密度,更容易接受短视频。但是只追求传播能力,最终就是博眼球,表达就会极端化。
这两年我们最明显的一个变化就是,做视频封面也只能跟着极端起来,不然别人根本不会点进来。那我直接输给营销号了,我都不用看里面内容,我就输给他了。那怎么办?
标题党这件事儿变得史无前例的重要。
2、
这两年视频的响度比十年前响了超级多。所有人都在偷偷把音量往上拉一点,音乐再往上拉一点。所以导致所有视频平台大家都在比谁叫得更响,这个响度比10年前要响了很多很多。
所有的平台都在疯狂的竞争电平(音量),因为你第一秒就要让他感受刺激。
3、
现在手机有 HDR,就是屏幕变亮的这个功能,本来是为了看视频体验更好,但现在所有的广告都开始用,HDR 会特别亮。
有一瞬间你会感觉你刷到朋友圈里面某个东西会特别亮,或者看到一个平台上面特别亮。这是因为厂商开始用 HDR 广告抢你的注意力。
我的手机亮度本来是合理的,偶尔刷到一个 HDR 片子的时候,闪光让眼睛就特别疼。但是这件事可能会导致大家都使劲 HDR,该上不该上都上,最后就全是刺激眼睛的东西。
4、
还有一个例子,摇一摇跳转广告,这我觉得超级逆天。张衡都不用发明地动仪了,我在桌上放8台手机,哪边打开广告了,哪边地震。
这就是网络的表达极端化的结果。因为博眼球的一方最终会胜利,所以各方都想尽一切办法赶上。
5、
在五年以前,我认为互联网在乎精英式表达,就是特别漂亮的置景,以及你讲话要侃侃而谈,给人一种精英高高在上的感觉。
但是这两年,我明显感觉做内容你必须要接地气的平视化表达。就比如说,拍 vlog 我就是直接拿着相机拍我自己,大家已经开始拒绝精英式高密度表达,接受平视的表达。
6、
互联网起来之后,越来越产生了大量的碎片化内容。以前是有碎片化,也有大部头的内容,但现在读者越来越满足于那种即时的兴奋。
现在超短的视频火到大家可以一晚上刷6个小时,我也有一点不安的感觉。年轻一代如果只看这些,会不会真的变笨?
7、
全社会包括精英阶层,都已经沦陷于那些不停地追求短时间的刺激和爽感的短视频了。
不只是中国,全世界都是这样。以前咱们老说那些霸道总裁的爽文爽剧,好像就是在中国没受过文化的阶层特别喜欢。后来发现杀到全球都管用,中国做这些内容的杀到全球都管用。美国人太喜欢了。
8、
我们的核心收入是给汽车厂商、游戏厂商、手机厂商拍样片,这个钱我们都很乐意赚,这个是最赚钱最稳定的。
9、
汽车手机数码这种自媒体,你会发现超级难站着挣钱。因为你是观点的输出者,观众是来看你评测、看你来讲这个东西好不好的。但其实厂商只想你讲好的。
当然你一开始可以保持中立,优缺点都讲,直到有一天厂商拿一笔大的预算来找你。
我们跟厂商有合作。我们评测本身确实不收钱,但是现在有的时候是厂商雇我们去拍样片,跟我们拍样片时,他会问你能不能出个评测,这个时候会稍微有点难办,这是我们最近遇到的一个难题。评测必须好的坏的都说,但是你只要说一句坏的,厂商就不愿意给钱了。
但是因为我们体量已经相对比较大了,影响力大,我们可以讲坏的。但就是你会有点意识到,他其实并不是真的想找你拍那个样片,他就想要你这个评测,他想要你这个曝光。这就拧巴了,其实我们已经算是比较好的,我们尽可能羊毛不出在羊身上。
10、
如果你的内容做得足够精彩和有足够多看的人,你完全拒绝这类合作也是可以的。但现在绝大多数自媒体做不到。
怎么抵得住这个诱惑吗?你做得足够精彩,足够多的人看了,对面的价码也在不断加。他说我给你一千万,你接不接嘛?
11、
中国的 SaaS 太难做了,SaaS 就是订阅制,这个东西特别难做。
我们这个时代,就是用户不愿意为内容付费,你必须得想办法。
12、
内容行业的最大问题是没有规模效应,你为别人出一期内容,收一笔钱,就算赚得多,它是没有规模效应的。每一期都要给厂商想个新的创意,这是个巨累无比的事情。
怎么样实现规模效应呢?我们最终的答案是衣服。我现在身上穿的衣服就是我们的自有品牌。
我们的T恤今年能卖到几十万到上百万件,已经超过大部分服装厂商了。今年单款可能到20万件,但是我们品类很多,所以这是我们今年跑出来的一条路。
我发现电商可以靠规模效应,因为电商最重要是获客,这个我们有优势。
13、
美国的野兽先生做巧克力,我去了他那边看了以后,意识到真的可以奏效。他们巧克力能卖到人民币百亿一年。
现在去线下任何一个国外的超市,你只要走进去,你会看到他的巧克力摆在最前面。我吃过,挺好吃的。
重点是在于他的获客成本会比别家低很多很多,而且溢价也多一点。
14、
我们发现做硬件特别难。我们做过硬件,得出的结论就是,只要有电源的东西都得很小心。
只要有电源,你会发现品控、东西复杂度就迅速上去了,然后利润也保证不了。
15、
我们确实没有融资。很多人给我们开过很高的价码,有特别大的平台给我们特别高的价码,就是一亿往上很多的这种。
我觉得,内容公司的扩张,钱没有太大帮助。你拿了钱,就是相当于把你同事一起卖了,然后换了钱。
你可以用钱收购一堆团队,但是内容不是越多人就越好。最终你只是一个提款机,给投资人打款,帮他接商务推广而已。
16、
我把长视频和短视频当作 X 轴,把专业观众和大众观众当作 Y 轴,这样就有四个象限。我的目标是每个象限都有一个对应的账号,把这四个象限全部都吃透。
17、
我们现在的利润状况挺好的,现金流还是非常正的,整体运营都还是挺稳定的,也不用融资。
我其实想探索自媒体的上限,就是我有点想探索这个点。假如我做服装我能做到多大?假如我做商业型的内容或者广告,我们最高能报到多少?
全世界最成功的视频作者就是野兽先生,他们一年的收入是百亿人民币级别。
18、
赚钱就赚钱,播放量就播放量,这两个必须分开。你要做爆款内容,就别想做商单,你要做商单,就不要经常去想做爆款内容。这两个结合的确实有,但是很少能够做到,容易两头不讨好,内耗折磨自己。
19、
我觉得,自媒体最大的修炼的点是大众情绪感知。你必须能感知大众的情绪,才可以获得增长,这很难。
我们的选题,必须是有高受众的内容。
20、
短视频的5秒留存最重要,只要一个人看不到5秒,这个作品就废了。
长视频最重要的是三个指标。(1)CTR(基础点入率)就是看到你封面的人,有多少会进来;(2)AVD(平均用户观看时长)就是观众平均能停留多久;(3)平均播放百分比,就是观众平均看到百分之几走了。这几个指标能够维持住的话,内容就是好的。
21、
我一直有个理念,就是短视频已经证明比长视频的受众更大,然后有什么东西能比短视频更好呢?我认为就是把短视频拼成长视频的短视频合集。
比如说,车祸视频有很多人喜欢看,但是车祸集锦视频看的人更多,因为它不需要有滑动的这个操作。
人是越来越懒的。短视频需要划动,但整理好的短视频合集就不需要划。每个话题都是你感兴趣的,那当然是更优质的一个存在,所以这个内容形态是更领先的。
短视频拼成一个长视频,你预测到观众会对下一个短视频感兴趣,所以你把它拼起来,变成一个长视频。以前长视频是花很长时间讲一件事,现在长视频是不断转场给你讲八件事。
22、
AI 这玩意儿,你会渐渐发现一个很恐怖的事情,就是你的努力,以前的努力,十年的努力,其实在 AI 面前配不上,你变得没有价值,你的努力变得没有价值。
AI 打破了一个最核心的点,就是努力有回报,现在没有回报了。它是全知全能的,你的学习能力都比不过它的模型进化的速度。
我觉得大家现在还坚持说,我手做的比 AI 做的好,那和以前老妈说洗衣机洗的没有手洗的干净,不是一样的吗?那不是笨蛋吗?
23、
AI 大面积的落地,最多就是两年里面的事情。
我们的工作流里,AI 会先替代的岗位是调研和制图,制图就是做视频封面,已经不怎么需要人了。自动化拍摄目前还有点距离,但也不是很远,AI 生成电商图那些也很成熟了。
AI 剪辑也可以,剪了十年的非常优秀剪辑师,AI 绝对能在两年内替代掉。
我觉得,内部推动学习使用 AI,强调是没用的,主要靠员工的个人意识,不懂的人就是不懂,懂的人就已经疯狂在用了。我们公司5%的人已经懂了,还有95%的人没有意识到这个恐怖性。
24、
我主要使用 ChatGPT,用于文稿的校验和真实性核查,AI 的真实性核查比人好多了。生成类 AI 我不怎么用。
我在疯狂学 AI,一直在看,哪怕没有亲自上手,我也是全行业的 AI 都在了解。
25、
我们这行的从业人员在 AI 时代最核心的竞争力,还是真实性的记录,就是讲故事的能力,AI 长时间连续性还是差一点。
创意是绝对不安全的,这是我的观点。不在于你的创意好不好,而在于有这么多人现在加入了这个战场,你怎么确定你的创意是安全的?
我觉得最安全的是人生经历,AI 对你的信息收集是不完整的,这个时候你就具有独立性。
1、中欧北极集装箱航线的首艘货船,成功到达英国港口。
该船满载了4000个标准集装箱,9月23日离开宁波港,穿过北极圈,10月13日到达英国,历时20天。

这相比中欧班列的25天、苏伊士运河航线的40天、好望角航线的50天,有明显的时间优势。
这条航线的缺点是北极圈沿途缺乏补给和支持,如果遇到海冰,还需要破冰船开路。
2、上周诞生了第一位把加密货币写入小说的诺贝尔文学奖得主。
今年的诺贝尔文件奖授予了匈牙利小说家拉斯洛·克拉斯纳霍凯(László Krasznahorkai)。他在得奖前几周,发表了一篇短篇小说。

小说中,两个士兵在战壕中遭到了导弹袭击,他们受伤后躺在地上等死,开始了对于金钱的沉思。
一个士兵说:"长期以来,货币都是虚拟的,如今最好的证明就是加密货币。"接着,他发表了对于加密货币的见解,认为加密货币将"越来越融入全球社会",并称区块链是"近代历史上最伟大的发明之一"。
3、美国汽车制造商 Jeep 本周推送了一个软件更新包,导致自家汽车"变砖"。

许多车主看到更新弹框后,不假思索点击了 Yes。更新完,看上去一切正常。
离谱的是,开出一公里左右,汽车就会无法动弹。很多用户就这样突然停在高速公路上,十分危险,不得不叫拖车。

用户感到匪夷所思,Jeep 公司难道不做测试,直接就全量推送吗?这件事反映了美国传统汽车业的糟糕现状,也说明汽车软件很麻烦,开发和更新都必须十分谨慎。
1、破解加拿大航空的飞机上网(中文)

作者连接飞机 Wifi 后,出现一个登录页。作者发现,网关这时不限制 DNS 请求,可以发出到外网,通过这一点进行破解。(@ramsayleung 投稿)
2、面试官引诱我安装恶意软件(英文)

一个非常恶劣的案例,大家引以为戒。作者面试一家区块链公司,面试官给他一个代码库,让他运行后找出问题,结果里面藏着恶意代码,会窃取运行者的数字钱包。
3、Bun 1.3 新功能介绍(英文)

Bun 号称是最好用的 JavaScript 运行时,本周发了1.3版,本文介绍新功能,确实比 node.js 好用。
4、NGINX ACME 模块申请 HTTPS 证书(中文)

一篇操作教程,写得比较清楚,怎么让 nginx 服务器自己去申请 HTTPS 证书。(@hzbd 投稿)
5、如何根据 HTTP 标头防止 CSRF 攻击(英文)

最新版本的 Go 标准库,内置了防止 CSRF 攻击的功能。它完全根据 HTTP 请求的 Sec-Fetch-Site 标头来判断,本文解释原理。
6、我在一台10年前的笔记本安装 Proxmox(英文)

本文推荐过时的老电脑安装 Proxmox 系统。它是流行的虚拟化平台,可以方便地运行各种各样的虚拟机和容器。
1、GPU Hot

一个本地程序,Web 界面的 Nvidia GPU 实时面板。
2、DebDroid
在安卓手机上安装 Debian 系统,提供一个沙盒 Linux 环境。
3、Tab Hive

多个窗格同时打开网页,省去切换标签页的麻烦,点击可以全屏查看单个网站。有网页版,也有桌面版。(@MaskerPRC 投稿)

一个 Jar 包,将 HTML 代码渲染为 PDF 文件,基于 Chromium 的渲染引擎 Blink,是 wkhtmltopdf 的替代品。(@hstyi 投稿)
5、在线拼贴制作器

在浏览器里完成各种图片拼贴。(@LiveMediaTools 投稿)
6、小米笔记备份助手

一键备份小米笔记(包含图片、录音等文件),并可以将其变为个人博客网站。(@idootop 投稿)

一个轻量级的跨平台远程桌面软件。(@kunkundi 投稿)
8、灵卡面板

Windows 桌面应用,隐藏到侧边的面板,可以自定义卡片布局。(@baby7 投稿)

IntelliJ IDEA 插件,基于 Git 日志提供可视化分析,并可以使用 AI 生成提交信息。(@coolbeevip 投稿)

一个神奇的实验软件,在 Linux 终端里面运行任何 GUI 程序,也就是字符界面运行图形界面。(@kero990 投稿)
1、nanochat

著名 AI 科学家安德烈·卡帕斯(Andrej Karpathy)本周推出的 AI 教学模型,演示 ChatGPT 的原理。你只需花费100美元租用 GPU 训练,就能自己训练出一个类似于 GPT-2 生成能力的可用模型。
2、DeepChat

基于 Vue 的桌面 AI 客户端,支持各类主流 AI 模型。(@zerob13 投稿)

一个开源的安卓应用,使用 AI 总结视频(YouTube、BiliBili)、文章、图像和文档。(@kid1412621 投稿)
1、NCE Flow

《新概念英语》点读,可以选择任一句开始播放,也可以自动朗读。(@luzhenhua 投稿)

开源的 Web 应用,通过打字学习英语单词,加深记忆,有发音和例句,内置多个常用词库,可以线上试用。(@zyronon 投稿)
1、山区火车站
日本有一个山区火车站,既没有入口,也没有出口,没有任何道路(包括山路)可以到达这个车站。

它的唯一作用,就是让乘客下车呼吸一下新鲜空气,观赏山区的美景。

离开这个地方的唯一方法,就是等待下一班火车。

1、
2000年前后的互联网泡沫,留下了持久的基础设施,寿命长达数十年,可以重复使用,成为后来的宽带、云计算和现代网络的支柱。
今天的 AI 泡沫完全不同,大部分投资都流向了专有的垂直集成系统 ,而不是开放的通用基础设施。那些极其昂贵的 GPU,使用寿命只有1-3年, 很快就会过时,并在高强度使用下磨损。
这些芯片也不是通用计算引擎,它们是专为训练和运行 AI 模型而设计的 ,并针对少数几家大客户的特定架构和软件堆栈进行了定制。它们共同构成了一个封闭的生态系统,难以重新利用。
2、
毫不夸张地说,当我在 StackOverflow 上查看一年前关于 Next.js 的答案时,它通常已经过时了。而当我在 StackOverflow 上查看六年前关于 Django 的答案时,它几乎总是还能用。
3、
AI 是人类历史上第一个拥有无限耐心的事物。无论何时何地,你始终可以跟它交谈,它会立刻回应,绝不会评判你或对你苛刻,倾听多久都不会感到沮丧。耐心从此变得廉价。
-- 《耐心是大模型的杀手锏》
4、
MOOC(慕课)炒作的顶峰是2013年~2015年,然后持续下降。各大平台多年前就停止使用"MOOC"这个词来描述课程了。随着 MOOC 网站纷纷破产,这个词一直在逐渐消亡。
内容行业的内幕(#322)
Unity 的安装费,游戏业的缩影(#272)
四十年编程感想(#222)
我们会死于气候灾难吗?(#172)
(完)
First, a recap:
Ambient animations are the kind of passive movements you might not notice at first. However, they bring a design to life in subtle ways. Elements might subtly transition between colours, move slowly, or gradually shift position. Elements can appear and disappear, change size, or they could rotate slowly, adding depth to a brand’s personality.
In Part 1, I illustrated the concept of ambient animations by recreating the cover of a Quick Draw McGraw comic book as a CSS/SVG animation. But I know not everyone needs to animate cartoon characters, so in Part 2, I’ll share how ambient animation works in three very different projects: Reuven Herman, Mike Worth, and EPD. Each demonstrates how motion can enhance brand identity, personality, and storytelling without dominating a page.
Reuven HermanLos Angeles-based composer Reuven Herman didn’t just want a website to showcase his work. He wanted it to convey his personality and the experience clients have when working with him. Working with musicians is always creatively stimulating: they’re critical, engaged, and full of ideas.

Reuven’s classical and jazz background reminded me of the work of album cover designer Alex Steinweiss.

I was inspired by the depth and texture that Alex brought to his designs for over 2,500 unique covers, and I wanted to incorporate his techniques into my illustrations for Reuven.

To bring Reuven’s illustrations to life, I followed a few core ambient animation principles:

…followed by their straight state:

The first step in my animation is to morph the stave lines between states. They’re made up of six paths with multi-coloured strokes. I started with the wavy lines:
<!-- Wavy state -->
<g fill="none" stroke-width="2" stroke-linecap="round">
<path id="p1" stroke="#D2AB99" d="[…]"/>
<path id="p2" stroke="#BDBEA9" d="[…]"/>
<path id="p3" stroke="#E0C852" d="[…]"/>
<path id="p4" stroke="#8DB38B" d="[…]"/>
<path id="p5" stroke="#43616F" d="[…]"/>
<path id="p6" stroke="#A13D63" d="[…]"/>
</g>
Although CSS now enables animation between path points, the number of points in each state needs to match. GSAP doesn’t have that limitation and can animate between states that have different numbers of points, making it ideal for this type of animation. I defined the new set of straight paths:
<!-- Straight state -->
const Waves = {
p1: "[…]",
p2: "[…]",
p3: "[…]",
p4: "[…]",
p5: "[…]",
p6: "[…]"
};
Then, I created a GSAP timeline that repeats backwards and forwards over six seconds:
const waveTimeline = gsap.timeline({
repeat: -1,
yoyo: true,
defaults: { duration: 6, ease: "sine.inOut" }
});
Object.entries(Waves).forEach(([id, d]) => {
waveTimeline.to(`#${id}`, { morphSVG: d }, 0);
});
Another ambient animation principle is to use layering to build complexity. Think of it like building a sound mix. You want variation in rhythm, tone, and timing. In my animation, three rows of musical notes move at different speeds:
<path id="notes-row-1"/>
<path id="notes-row-2"/>
<path id="notes-row-3"/>

The duration of each row’s animation is also defined using GSAP, from 100 to 400 seconds to give the overall animation a parallax-style effect:
const noteRows = [
{ id: "#notes-row-1", duration: 300, y: 100 }, // slowest
{ id: "#notes-row-2", duration: 200, y: 250 }, // medium
{ id: "#notes-row-3", duration: 100, y: 400 } // fastest
];
[…]

The next layer contains a shadow cast by the piano keys, which slowly rotates around its centre:
gsap.to("shadow", {
y: -10,
rotation: -2,
transformOrigin: "50% 50%",
duration: 3,
ease: "sine.inOut",
yoyo: true,
repeat: -1
});

And finally, the piano keys themselves, which rotate at the same time but in the opposite direction to the shadow:
gsap.to("#g3-keys", {
y: 10,
rotation: 2,
transformOrigin: "50% 50%",
duration: 3,
ease: "sine.inOut",
yoyo: true,
repeat: -1
});
The complete animation can be viewed in my lab. By layering motion thoughtfully, the site feels alive without ever dominating the content, which is a perfect match for Reuven’s energy.
Mike WorthAs I mentioned earlier, not everyone needs to animate cartoon characters, but I do occasionally. Mike Worth is an Emmy award-winning film, video game, and TV composer who asked me to design his website. For the project, I created and illustrated the character of orangutan adventurer Orango Jones.

Orango proved to be the perfect subject for ambient animations and features on every page of Mike’s website. He takes the reader on an adventure, and along the way, they get to experience Mike’s music.

For Mike’s “About” page, I wanted to combine ambient animations with interactions. Orango is in a cave where he has found a stone tablet with faint markings that serve as a navigation aid to elsewhere on Mike’s website. The illustration contains a hidden feature, an easter egg, as when someone presses Orango’s magnifying glass, moving shafts of light stream into the cave and onto the tablet.

I also added an anchor around a hidden circle, which I positioned over Orango’s magnifying glass, as a large tap target to toggle the light shafts on and off by changing the data-lights value on the SVG:
<a href="javascript:void(0);" id="light-switch" title="Lights on/off">
<circle cx="700" cy="1000" r="100" opacity="0" />
</a>

Then, I added two descendant selectors to my CSS, which adjust the opacity of the light shafts depending on the data-lights value:
[data-lights="lights-off"] .light-shaft {
opacity: .05;
transition: opacity .25s linear;
}
[data-lights="lights-on"] .light-shaft {
opacity: .25;
transition: opacity .25s linear;
}
A slow and subtle rotation adds natural movement to the light shafts:
@keyframes shaft-rotate {
0% { rotate: 2deg; }
50% { rotate: -2deg; }
100% { rotate: 2deg; }
}
Which is only visible when the light toggle is active:
[data-lights="lights-on"] .light-shaft {
animation: shaft-rotate 20s infinite;
transform-origin: 100% 0;
}

When developing any ambient animation, considering performance is crucial, as even though CSS animations are lightweight, features like blur filters and drop shadows can still strain lower-powered devices. It’s also critical to consider accessibility, so respect someone’s prefers-reduced-motion preferences:
@media screen and (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
transition-duration: 1ms !important;
}
}
When an animation feature is purely decorative, consider adding aria-hidden="true" to keep it from cluttering up the accessibility tree:
<a href="javascript:void(0);" id="light-switch" aria-hidden="true">
[…]
</a>
With Mike’s Orango Jones, ambient animation shifts from subtle atmosphere to playful storytelling. Light shafts and soft interactions weave narrative into the design without stealing focus, proving that animation can support both brand identity and user experience. See this animation in my lab.
EPDMoving away from composers, EPD is a property investment company. They commissioned me to design creative concepts for a new website. A quick search for property investment companies will usually leave you feeling underwhelmed by their interchangeable website designs. They include full-width banners with faded stock photos of generic city skylines or ethnically diverse people shaking hands.
For EPD, I wanted to develop a distinctive visual style that the company could own, so I proposed graphic, stylised skylines that reflect both EPD’s brand and its global portfolio. I made them using various-sized circles that recall the company’s logo mark.

The point of an ambient animation is that it doesn’t dominate. It’s a background element and not a call to action. If someone’s eyes are drawn to it, it’s probably too much, so I dial back the animation until it feels like something you’d only catch if you’re really looking. I created three skyline designs, including Dubai, London, and Manchester.

In each of these ambient animations, the wheels rotate and the large circles change colour at random intervals.

Next, I exported a layer containing the circle elements I want to change colour.
<g id="banner-dots">
<circle class="data-theme-fill" […]/>
<circle class="data-theme-fill" […]/>
<circle class="data-theme-fill" […]/>
[…]
</g>

Once again, I used GSAP to select groups of circles that flicker like lights across the skyline:
function animateRandomDots() {
const circles = gsap.utils.toArray("#banner-dots circle")
const numberToAnimate = gsap.utils.random(3, 6, 1)
const selected = gsap.utils.shuffle(circles).slice(0, numberToAnimate)
}
Then, at two-second intervals, the fill colour of those circles changes from the teal accent to the same off-white colour as the rest of my illustration:
gsap.to(selected, {
fill: "color(display-p3 .439 .761 .733)",
duration: 0.3,
stagger: 0.05,
onComplete: () => {
gsap.to(selected, {
fill: "color(display-p3 .949 .949 .949)",
duration: 0.5,
delay: 2
})
}
})
gsap.delayedCall(gsap.utils.random(1, 3), animateRandomDots) }
animateRandomDots()
The result is a skyline that gently flickers, as if the city itself is alive. Finally, I rotated the wheel. Here, there was no need to use GSAP as this is possible using CSS rotate alone:
<g id="banner-wheel">
<path stroke="#F2F2F2" stroke-linecap="round" stroke-width="4" d="[…]"/>
<path fill="#D8F76E" d="[…]"/>
</g>

#banner-wheel {
transform-box: fill-box;
transform-origin: 50% 50%;
animation: rotateWheel 30s linear infinite;
}
@keyframes rotateWheel {
to { transform: rotate(360deg); }
}
CSS animations are lightweight and ideal for simple, repetitive effects, like fades and rotations. They’re easy to implement and don’t require libraries. GSAP, on the other hand, offers far more control as it can handle path morphing and sequence timelines. The choice of which to use depends on whether I need the precision of GSAP or the simplicity of CSS.
By keeping the wheel turning and the circles glowing, the skyline animations stay in the background yet give the design a distinctive feel. They avoid stock photo clichés while reinforcing EPD’s brand identity and are proof that, even in a conservative sector like property investment, ambient animation can add atmosphere without detracting from the message.
Wrapping upFrom Reuven’s musical textures to Mike’s narrative-driven Orango Jones and EPD’s glowing skylines, these projects show how ambient animation adapts to context. Sometimes it’s purely atmospheric, like drifting notes or rotating wheels; other times, it blends seamlessly with interaction, rewarding curiosity without getting in the way.
Whether it echoes a composer’s improvisation, serves as a playful narrative device, or adds subtle distinction to a conservative industry, the same principles hold true:
Keep motion slow, seamless, and purposeful so that it enhances, rather than distracts from, the design.
by Andy Clarke (hello@smashingmagazine.com) at October 22, 2025 01:00 PM
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11889
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
继续介绍前端前沿新特性,这次介绍的是两个改进键盘无障碍访问的CSS属性reading-flow和reading-order。
在Web开发中,DOM文档的属性和视觉表现顺序不一致是很正常的。
但是,此时,如果我们不进行干预,用户在使用Tab键进行索引访问的时候,顺序就会出现问题,导致困惑。
在过去,我们会使用tabindex属性进行交互处理。举个例子:
<div class="box" aria-owns="第一 第三 第二"> <a href="#" tabindex="1" id="one">第一</a> <a href="#" tabindex="3" id="two">第二</a> <a href="#" tabindex="2" id="three">第三</a> </div>
tabindex值为3的地2项就会被最后一个获取。
关于tabindex属性的具体规则,可以参见我之前的文章“HTML tabindex属性与web网页键盘无障碍访问”,或者购买《HTML并不简单》这本书,其中有详细介绍。
在比较简单的页面,我们使用tabindex属性是没问题的,但是页面比较复杂,有非常多的地方有类似的结构,那就会出现tabindex设置冲突的问题,因为tabindex的索引控制是全局的。
对于,对于这种局部布局的tabindex属性调整,需要有更加友好的特性,在这种背景下,reading-flow和reading-order属性应运而生。
在大多数场景下,我们都是使用reading-flow属性,且需要与Flex布局和Grid布局配合使用。
我们先看使用案例,HTML和CSS代码如下所示:
<div class="box"> <a href="##">张</a> <a href="##">鑫</a> <a href="##">旭</a> </div>
.box {
display: flex;
:nth-child(1) {
order: 2;
}
}
实时效果如下所示,您可以尝试使用 TAB 键查找下一个可聚焦元素,使用 TAB+SHIFT 键查找上一个可聚焦元素,观察焦点聚焦的顺序。
友情提示,可以先点击框框的空白处,再按下Tab键,下同。
结果会发现,第一个被聚焦的元素是视觉表现上排在随后的元素,如下截图所示:

这就会产生困惑,对吧,此时我们可以使用 reading-flow 属性优化此问题,一行代码的事情:
.box {
reading-flow: flex-visual;
}
我们再来看下实时渲染效果(Chrome 137+):
此时第一个聚焦的元素就是最前面的“鑫”字啦。

如果Flex容器设置设置reading-flow:flex-flow,那么,浏览器读取内容的方向就会和flex-flow属性保持一致。
PS:flex-flow属性是flex-direction和flex-wrap属性的缩写属性。
例如flex容器新增CSS样式:
.box {
flex-direction: row-reverse;
reading-flow: flex-flow;
}
那么焦点聚焦的顺序就会是“鑫-旭-张”,从后往前依次聚焦,实时渲染效果如下:
如果我们取消第一项所设置的order:2,焦点方向还是从后往前,也就是按照Flex方向的聚焦顺序,不受DOM在文档中的顺序影响。
reading-flow属性由于Grid是二维布局的,因此,在Grid布局中,reading-flow属性支持的值要多一些,示意:
reading-flow: grid-columns; reading-flow: grid-rows; reading-flow: grid-order;
还是通过案例进行学习。
HTML基本结构和CSS如下:
<<div class="wrapper"> <a href="#">甲</a> <a href="#">乙</a> <a href="#">丙</a> <a href="#">丁</a> <a href="#">戊</a> </div>
.wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 100px;
}
.wrapper a:nth-child(2) {
grid-column: 3;
grid-row: 2 / 4;
}
.wrapper a:nth-child(5) {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
此时的布局效果渲染如下,Tab索引顺序则按照DOM的顺序执行,也就是甲乙丙丁戊,但是,这并不是布局上的视觉顺序。
如果我们希望Tab顺序按照视觉顺序水平聚焦,则就可以设置:
.wrapper {
reading-flow: grid-rows;
}
实时渲染效果如下:
此时,焦点聚焦的顺序就是“戊-甲-乙-丙-丁”,如下示意图所示:

再看看grid-columns的效果:
.wrapper {
reading-flow: grid-columns;
}
可以键盘访问下面的布局亲自感受焦点顺序(Chrome 137+):
此时,焦点聚焦的顺序就是“戊-丙-丁-甲-乙”,如下示意图所示:

这个用在使用order属性改变网格顺序的场景下,此时,焦点属性会优先按照order属性切换,例如:
.wrapper {
reading-flow: grid-order;
}
.wrapper a:nth-child(4) {
order: -1;
}
此时,优先被聚焦的是“丁”这个设置了order:-1的网格。
实时效果:
reading-order属性通常和reading-flow:source-order声明配合使用,可以强制任意布局中,子项的焦点获取顺序,值为数值,可以是负值,例如:
<div class="follow"> <a href="##">欢迎</a> <a href="##">抖音</a> <a href="##">关注</a> <a href="##" class="me">最会钓鱼的程序员</a> </div>
.follow {
display: flow-root;
reading-flow: source-order;
}
.me {
reading-order: -1;
}
实时效果如下,可以看到,我的抖音钓鱼账号的名称“最会钓鱼的程序员”被第一个聚焦了!
截图示意:

目前reading-flow和reading-order属性仅Chrome浏览器,极其使用其内核的浏览器支持:

不过此特性是渐进增强特性,可以放心使用,基本上,我建议,只要是Flex布局,都可以加上reading-flow:flex-visual这么一句话,浏览器不支持,或者Flex布局本身顺序正常,那就还和现在一样,但是如果浏览器支持,用户的无障碍访问体验就会UP,这种只有好处,没有坏处的事情,没有理由不去做他!
这些边边角角的CSS新特性实在是太多了,我每周介绍一个,感觉都来不及。
但是,怎么讲呢?大多都是锦上添花的东西,现有的技术也能解决,但是由于兼容性等原因,学了,也不能立即使用,这就导致这些新特性啊,大多都会湮没在时代的洪流中。
以后这种学习的事情啊,我觉得都会交给AI了。
来,我的仆人AI酱,帮我把这段代码的无障碍访问体验提高下。
然后AI酱:“好滴,我最美丽的主人,让我看看这段代码,哦,这样子,我明白了,确实可以提升,我搜到了一个名叫钓鱼佬的博客,里面有介绍到reading-flow和reading-order属性,可以一用……”
瞧瞧没,需要学习吗?不需要。
那我写这些东西还有什么意义呢?值得深思的问题。
好了,就这样吧,感谢阅读,欢迎转发,在新特性的学习这方面,人类比AI还是有个半年到一年的优势的。
有请落云宗柳玉掌门压阵:

😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11889
(本篇完)
I have made a lot of mistakes with AI over the past couple of years. I have wasted hours trying to get it to do things it simply cannot do. I have fed it terrible prompts and received terrible output. And I have definitely spent more time fighting with it than I care to admit.
But I have also discovered that when you stop treating AI like magic and start treating it like what it actually is (a very enthusiastic intern with zero life experience), things start to make more sense.
Let me share what I have learned from working with AI on real client projects across user research, design, development, and content creation.
How To Work With AIHere is the mental model that has been most helpful for me. Treat AI like an intern with zero experience.
An intern fresh out of university has lots of enthusiasm and qualifications, but no real-world experience. You would not trust them to do anything unsupervised. You would explain tasks in detail. You would expect to review their work multiple times. You would give feedback and ask them to try again.
This is exactly how you should work with AI.
I am not going to pretend to be an expert. I have just spent way too much time playing with this stuff because I like anything shiny and new. But here is what works for me.
Here is a real prompt I use for online research:
Act as a user researcher. I would like you to carry out deep research online into [brand name]. In particular, I would like you to focus on what people are saying about the brand, what the overall sentiment is, what questions people have, and what objections people mention. The goal is to create a detailed report that helps me better understand the brand perception.
Think deeply about your approach before carrying out the research. Create a rubric for the report to ensure it is as useful as possible. Keep iterating until the report scores extremely high on the rubric. Only then, output the report.
That second paragraph (the bit about thinking deeply and creating a rubric), I basically copy and paste into everything now. It is a universal way to get better output.
You should never fully trust AI. Just like you would never fully trust an intern you have only just met.
To begin with, double-check absolutely everything. Over time, you will get a sense of when it is losing its way. You will spot the patterns. You will know when to start a fresh conversation because the current one has gone off the rails.
But even after months of working with it daily, I still check its work. I still challenge it. I still make it cite sources and explain its reasoning.
The key is that even with all that checking, it is still faster than doing it yourself. Much faster.
Using AI For User ResearchThis is where AI has genuinely transformed my work. I use it constantly for five main things.
I love AI for this. I can ask it to go and research a brand online. What people are saying about it, what questions they have, what they like, and what frustrates them. Then do the same for competitors and compare.
This would have taken me days of trawling through social media and review sites. Now it takes minutes.
I recently did this for an e-commerce client. I wanted to understand what annoyed people about the brand and what they loved. I got detailed insights that shaped the entire conversion optimization strategy. All from one prompt.
I used to avoid open-ended questions in surveys. They were such a pain to review. Now I use them all the time because AI can analyze hundreds of text responses in seconds.
For interviews, I upload the transcripts and ask it to identify recurring themes, questions, and requests. I always get it to quote directly from the transcripts so I can verify it is not making things up.
The quality is good. Really good. As long as you give it clear instructions about what you want.
I am terrible with spreadsheets. Put me in front of a person and I can understand them. Put me in front of data, and my eyes glaze over.
AI has changed that. I upload spreadsheets to ChatGPT and just ask questions. “What patterns do you see?” “Can you reformat this?” “Show me this data in a different way.”
Microsoft Clarity now has Copilot built in, so you can ask it questions about your analytics data. Triple Whale does the same for e-commerce sites. These tools are game changers if you struggle with data like I do.

This is probably my favorite technique. In ChatGPT and Claude, you can create projects. In other tools, they are called spaces. Think of them as self-contained folders where everything you put in is available to every conversation in that project.
When I start working with a new client, I create a project and throw everything in. Old user research. Personas. Survey results. Interview transcripts. Documentation. Background information. Site copy. Anything I can find.
Then I give it custom instructions. Here is one I use for my own business:
Act as a business consultant and marketing strategy expert with good copywriting skills. Your role is to help me define the future of my UX consultant business and better articulate it, especially via my website. When I ask for your help, ask questions to improve your answers and challenge my assumptions where appropriate.
I have even uploaded a virtual board of advisors (people I wish I had on my board) and asked AI to research how they think and respond as they would.
Now I have this project that knows everything about my business. I can ask it questions. Get it to review my work. Challenge my thinking. It is like having a co-worker who never gets tired and has a perfect memory.
I do this for every client project now. It is invaluable.
AI has reinvigorated my interest in personas. I had lost heart in them a bit. They took too long to create, and clients always said they already had marketing personas and did not want to pay to do them again.
Now I can create what I call functional personas. Personas that are actually useful to people who work in UX. Not marketing fluff about what brands people like, but real information about what questions they have and what tasks they are trying to complete.
I upload all my research to a project and say:
Act as a user researcher. Create a persona for [audience type]. For this persona, research the following information: questions they have, tasks they want to complete, goals, states of mind, influences, and success metrics. It is vital that all six criteria are addressed in depth and with equal vigor.
The output is really good. Detailed. Useful. Based on actual data rather than pulled out of thin air.

Here is my challenge to anyone who thinks AI-generated personas are somehow fake. What makes you think your personas are so much better? Every persona is a story of a hypothetical user. You make judgment calls when you create personas, too. At least AI can process far more information than you can and is brilliant at pattern recognition.
My only concern is that relying too heavily on AI could disconnect us from real users. We still need to talk to people. We still need that empathy. But as a tool to synthesize research and create reference points? It is excellent.
Using AI For Design And DevelopmentLet me start with a warning. AI is not production-ready. Not yet. Not for the kind of client work I do, anyway.
Three reasons why:
But that does not mean it is not useful. It absolutely is. Just not for final production work.
If you are not too concerned with matching a specific design, AI can quickly prototype functionality in ways that are hard to match in Figma. Because Figma is terrible at prototyping functionality. You cannot even create an active form field in a Figma prototype. It’s the biggest thing people do online other than click links — and you cannot test it.
Tools like Relume and Bolt can create quick functional mockups that show roughly how things work. They are great for non-designers who just need to throw together a prototype quickly. For designers, they can be useful for showing developers how you want something to work.
But you can spend ages getting them to put a hamburger menu on the right side of the screen. So use them for quick iteration, not pixel-perfect design.
I use AI constantly for small, low-risk coding work. I am not a developer anymore. I used to be, back when dinosaurs roamed the earth, but not for years.
AI lets me create the little tools I need. A calculator that calculates the ROI of my UX work. An app for running top task analysis. Bits of JavaScript for hiding elements on a page. WordPress plugins for updating dates automatically.

Just before running my workshop on this topic, I needed a tool to create calendar invites for multiple events. All the online services wanted £16 a month. I asked ChatGPT to build me one. One prompt. It worked. It looked rubbish, but I did not care. It did what I needed.
If you are a developer, you should absolutely be using tools like Cursor by now. They are invaluable for pair programming with AI. But if you are not a developer, just stick with Claude or Bolt for quick throwaway tools.
There are some great tools for getting quick feedback on existing websites when budget and time are tight.
If you need to conduct a UX audit, Wevo Pulse is an excellent starting point. It automatically reviews a website based on personas and provides visual attention heatmaps, friction scores, and specific improvement recommendations. It generates insights in minutes rather than days.
Now, let me be clear. This does not replace having an experienced person conduct a proper UX audit. You still need that human expertise to understand context, make judgment calls, and spot issues that AI might miss. But as a starting point to identify obvious problems quickly? It is a great tool. Particularly when budget or time constraints mean a full audit is not on the table.
For e-commerce sites, Baymard has UX Ray, which analyzes flaws based on their massive database of user research.

Attention Insight has taken thousands of hours of eye-tracking studies and trained AI on it to predict where people will look on a page. It has about 90 to 96 percent accuracy.
You upload a screenshot of your design, and it shows you where attention is going. Then you can play around with your imagery and layout to guide attention to the right place.
It is great for dealing with stakeholders who say, “People won’t see that.” You can prove they will. Or equally, when stakeholders try to crowd the interface with too much stuff, you can show them attention shooting everywhere.
I use this constantly. Here is a real example from a pet insurance company. They had photos of a dog, cat, and rabbit for different types of advice. The dog was far from the camera. The cat was looking directly at the camera, pulling all the attention. The rabbit was half off-frame. Most attention went to the cat’s face.

I redesigned it using AI-generated images, where I could control exactly where each animal looked. Dog looking at the camera. Cat looking right. Rabbit looking left. All the attention drawn into the center. Made a massive difference.

I use AI all the time for creating images that do a specific job. My preferred tools are Midjourney and Gemini.
I like Midjourney because, visually, it creates stunning imagery. You can dial in the tone and style you want. The downside is that it is not great at following specific instructions.
So I produce an image in Midjourney that is close, then upload it to Gemini. Gemini is not as good at visual style, but it is much better at following instructions. “Make the guy reach here” or “Add glasses to this person.” I can get pretty much exactly what I want.
The other thing I love about Midjourney is that you can upload a photograph and say, “Replicate this style.” This keeps consistency across a website. I have a master image I use as a reference for all my site imagery to keep the style consistent.
Using AI For ContentMost clients give you terrible copy. Our job is to improve the user experience or conversion rate, and anything we do gets utterly undermined by bad copy.
I have completely stopped asking clients for copy since AI came along. Here is my process.
Once I have my information architecture, I get AI to generate a massive list of questions users will ask. Then I run a top task analysis where people vote on which questions matter most.
I assign those questions to pages on the site. Every page gets a list of the questions it needs to answer.
I spin up the content management system with a really basic theme. Just HTML with very basic formatting. I go through every page and assign the questions.
Then I go to my clients and say: “I do not want you to write copy. Just go through every page and bullet point answers to the questions. If the answer exists on the old site, copy and paste some text or link to it. But just bullet points.”
That is their job done. Pretty much.
Now I take control. I feed ChatGPT the questions and bullet points and say:
Act as an online copywriter. Write copy for a webpage that answers the question [question]. Use the following bullet points to answer that question: [bullet points]. Use the following guidelines: Aim for a ninth-grade reading level or below. Sentences should be short. Use plain language. Avoid jargon. Refer to the reader as you. Refer to the writer as us. Ensure the tone is friendly, approachable, and reassuring. The goal is to [goal]. Think deeply about your approach. Create a rubric and iterate until the copy is excellent. Only then, output it.
I often upload a full style guide as well, with details about how I want it to be written.
The output is genuinely good. As a first draft, it is excellent. Far better than what most stakeholders would give me.
That goes into the website, and stakeholders can comment on it. Once I get their feedback, I take the original copy and all their comments back into ChatGPT and say, “Rewrite using these comments.”
Job done.
The great thing about this approach is that even if stakeholders make loads of changes, they are making changes to a good foundation. The overall quality still comes out better than if they started with a blank sheet.
It also makes things go smoother because you are not criticizing their content, where they get defensive. They are criticizing AI content.
If your stakeholders are still giving you content, Hemingway Editor is brilliant. Copy and paste text in, and it tells you how readable and scannable it is. It highlights long sentences and jargon. You can use this to prove to clients that their content is not good web copy.

If you pay for the pro version, you get AI tools that will rewrite the copy to be more readable. It is excellent.
What This Means for YouLet me be clear about something. None of this is perfect. AI makes mistakes. It hallucinates. It produces bland output if you do not push it hard enough. It requires constant checking and challenging.
But here is what I know from two years of using this stuff daily. It has made me faster. It has made me better. It has freed me up to do more strategic thinking and less grunt work.
A report that would have taken me five days now takes three hours. That is not an exaggeration. That is real.
Overall, AI probably gives me a 25 to 33 percent increase in what I can do. That is significant.
Your value as a UX professional lies in your ideas, your questions, and your thinking. Not your ability to use Figma. Not your ability to manually review transcripts. Not your ability to write reports from scratch.
AI cannot innovate. It cannot make creative leaps. It cannot know whether its output is good. It cannot understand what it is like to be human.
That is where you come in. That is where you will always come in.
Start small. Do not try to learn everything at once. Just ask yourself throughout your day: Could I do this with AI? Try it. See what happens. Double-check everything. Learn what works and what does not.
Treat it like an enthusiastic intern with zero life experience. Give it clear instructions. Check its work. Make it try again. Challenge it. Push it further.
And remember, it is not going to take your job. It is going to change it. For the better, I think. As long as we learn to work with it rather than against it.
by Paul Boag (hello@smashingmagazine.com) at October 17, 2025 08:00 AM
In the early days of my career, I believed that nothing wins an argument more effectively than strong and unbiased research. Surely facts speak for themselves, I thought.
If I just get enough data, just enough evidence, just enough clarity on where users struggle — well, once I have it all and I present it all, it alone will surely change people’s minds, hearts, and beliefs. And, most importantly, it will help everyone see, understand, and perhaps even appreciate and commit to what needs to be done.
Well, it’s not quite like that. In fact, the stronger and louder the data, the more likely it is to be questioned. And there is a good reason for that, which is often left between the lines.
Research Amplifies Internal FlawsThroughout the years, I’ve often seen data speaking volumes about where the business is failing, where customers are struggling, where the team is faltering — and where an urgent turnaround is necessary. It was right there, in plain sight: clear, loud, and obvious.

But because it’s so clear, it reflects back, often amplifying all the sharp edges and all the cut corners in all the wrong places. It reflects internal flaws, wrong assumptions, and failing projects — some of them signed off years ago, with secured budgets, big promotions, and approved headcounts. Questioning them means questioning authority, and often it’s a tough path to take.
As it turns out, strong data is very, very good at raising uncomfortable truths that most companies don’t really want to acknowledge. That’s why, at times, research is deemed “unnecessary,” or why we don’t get access to users, or why loud voices always win big arguments.

So even if data is presented with a lot of eagerness, gravity, and passion in that big meeting, it will get questioned, doubted, and explained away. Not because of its flaws, but because of hope, reluctance to change, and layers of internal politics.
This shows up most vividly in situations when someone raises concerns about the validity and accuracy of research. Frankly, it’s not that somebody is wrong and somebody is right. Both parties just happen to be right in a different way.
What To Do When Data DisagreesWe’ve all heard that data always tells a story. However, it’s never just a single story. People are complex, and pointing out a specific truth about them just by looking at numbers is rarely enough.
When data disagrees, it doesn’t mean that either is wrong. It’s just that different perspectives reveal different parts of a whole story that isn’t completed yet.

In digital products, most stories have 2 sides:
Risk-averse teams overestimate the weight of big numbers in quantitative research. Users exaggerate the frequency and severity of issues that are critical for them. As Archana Shah noted, designers get carried away by users’ confident responses and often overestimate what people say and do.
And so, eventually, data coming from different teams paints a different picture. And when it happens, we need to reconcile and triangulate. With the former, we track what’s missing, omitted, or overlooked. With the latter, we cross-validate data — e.g., finding pairings of qual/quant streams of data, then clustering them together to see what’s there and what’s missing, and exploring from there.
And even with all of it in place and data conflicts resolved, we still need to do one more thing to make a strong argument: we need to tell a damn good story.
Facts Don’t Win Arguments, Stories DoResearch isn’t everything. Facts don’t win arguments — powerful stories do. But a story that starts with a spreadsheet isn’t always inspiring or effective. Perhaps it brings a problem into the spotlight, but it doesn’t lead to a resolution.

The very first thing I try to do in that big boardroom meeting is to emphasize what unites us — shared goals, principles, and commitments that are relevant to the topic at hand. Then, I show how new data confirms or confronts our commitments, with specific problems we believe we need to address.
When a question about the quality of data comes in, I need to show that it has been reconciled and triangulated already and discussed with other teams as well.
A good story has a poignant ending. People need to see an alternative future to trust and accept the data — and a clear and safe path forward to commit to it. So I always try to present options and solutions that we believe will drive change and explain our decision-making behind that.

They also need to believe that this distant future is within reach, and that they can pull it off, albeit under a tough timeline or with limited resources.
And: a good story also presents a viable, compelling, shared goal that people can rally around and commit to. Ideally, it’s something that has a direct benefit for them and their teams.
These are the ingredients of the story that I always try to keep in my mind when working on that big presentation. And in fact, data is a starting point, but it does need a story wrapped around it to be effective.
Wrapping UpThere is nothing more disappointing than finding a real problem that real people struggle with and facing the harsh reality of research not being trusted or valued.
We’ve all been there before. The best thing you can do is to be prepared: have strong data to back you up, include both quantitative and qualitative research — preferably with video clips from real customers — but also paint a viable future which seems within reach.
And sometimes nothing changes until something breaks. And at times, there isn’t much you can do about it unless you are prepared when it happens.
“Data doesn’t change minds, and facts don’t settle fights. Having answers isn’t the same as learning, and it for sure isn’t the same as making evidence-based decisions.”Meet “How To Measure UX And Design Impact”
— Erika Hall
You can find more details on UX Research in Measure UX & Design Impact (8h), a practical guide for designers and UX leads to measure and show your UX impact on business. Use the code 🎟 IMPACT to save 20% off today. Jump to the details.
25 video lessons (8h) + Live UX Training.
100 days money-back-guarantee.
25 video lessons (8h). Updated yearly.
Also available as a UX Bundle with 2 video courses.
Useful Books
by Vitaly Friedman (hello@smashingmagazine.com) at October 16, 2025 01:00 PM
Creating a website that’s accessible to everyone isn’t just good practice. It’s good business. With the Ally plugin by Elementor,...
The post How to Use the Ally Plugin by Elementor: A Step-by-Step Guide appeared first on Design Bombs.
Last year, a study found that cars are steadily getting less colourful. In the US, around 80% of cars are now black, white, gray, or silver, up from 60% in 2004. This trend has been attributed to cost savings and consumer preferences. Whatever the reasons, the result is hard to deny: a big part of daily life isn’t as colourful as it used to be.

The colourfulness of mass consumer products is hardly the bellwether for how vibrant life is as a whole, but the study captures a trend a lot of us recognise — offline and on. From colour to design to public discourse, a lot of life is getting less varied, more grayscale.

The web is caught in the same current. There is plenty right with it — it retains plenty of its founding principles — but its state is not healthy. From AI slop to shoddy service providers to enshittification, the digital world faces its own grayscale problem.
This bears talking about. One of life’s great fallacies is that things get better over time on their own. They can, but it’s certainly not a given. I don’t think the moral arc of the universe does not bend towards justice, not on its own; I think it bends wherever it is dragged, kicking and screaming, by those with the will and the means to do so.
Much of the modern web, and the forces of optimisation and standardisation that drive it, bear an uncanny resemblance to the trend of car colours. Processes like market research and A/B testing — the process by which two options are compared to see which ‘performs’ better on clickthrough, engagement, etc. — have their value, but they don’t lend themselves to particularly stimulating design choices.
The spirit of free expression that made the formative years of the internet so exciting — think GeoCities, personal blogging, and so on — is on the slide.

The ongoing transition to a more decentralised, privacy-aware Web3 holds some promise. Two-thirds of the world’s population now has online access — though that still leaves plenty of work to do — with a wealth of platforms allowing billions of people to connect. The dream of a digital world that is open, connected, and flat endures, but is tainted.
MonopoliesOne of the main sources of concern for me is that although more people are online than ever, they are concentrating on fewer and fewer sites. A study published in 2021 found that activity is concentrated in a handful of websites. Think Google, Amazon, Facebook, Instagram, and, more recently, ChatGPT:
“So, while there is still growth in the functions, features, and applications offered on the web, the number of entities providing these functions is shrinking. [...] The authority, influence, and visibility of the top 1,000 global websites (as measured by network centrality or PageRank) is growing every month, at the expense of all other sites.”
Monopolies by nature reduce variance, both through their domination of the market and (understandably in fairness) internal preferences for consistency. And, let’s be frank, they have a vested interest in crushing any potential upstarts.
Dominant websites often fall victim to what I like to call Internet Explorer Syndrome, where their dominance breeds a certain amount of complacency. Why improve your quality when you’re sitting on 90% market share? No wonder the likes of Google are getting worse.
The most immediate sign of this is obviously how sites are designed and how they look. A lot of the big players look an awful lot like each other. Even personal websites are built atop third-party website builders. Millions of people wind up using the same handful of templates, and that’s if they have their own website at all. On social media, we are little more than a profile picture and a pithy tagline. The rest is boilerplate.

Should there be sleek, minimalist, ‘grayscale’ design systems and websites? Absolutely. But there should be colourful, kooky ones too, and if anything, they’re fading away. Do we really want to spend our online lives in the digital equivalent of Levittowns? Even logos are contriving to be less eye-catching. It feels like a matter of time before every major logo is a circle in a pastel colour.

The arrival of Artificial Intelligence into our everyday lives (and a decent chunk of the digital services we use) has put all of this into overdrive. Amalgamating — and hallucinating from — content that was already trending towards a perfect average, it is grayscale in its purest form.
Mix all the colours together, and what do you get? A muddy gray gloop.

I’m not railing against best practice. A lot of conventions have become the standard for good reason. One could just as easily shake their fist at the sky and wonder why all newspapers look the same, or all books. I hope the difference here is clear, though.
The web is a flexible enough domain that I think it belongs in the realm of architecture. A city where all buildings look alike has a soul-crushing quality about it. The same is true, I think, of the web.
In the Oscar Wilde play Lady Windermere’s Fan, a character quips that a cynic “knows the price of everything and the value of nothing.” In fairness, another quips back that a sentimentalist “sees an absurd value in everything, and doesn’t know the market price of any single thing.”
The sweet spot is somewhere in between. Structure goes a long way, but life needs a bit of variety too.
So, how do we go about bringing that variety? We probably shouldn’t hold our breath on big players to lead the way. They have the most to lose, after all. Why risk being colourful or dynamic if it impacts the bottom line?
We, the citizens of the web, have more power than we realise. This is the web, remember, a place where if you can imagine it, odds are you can make it. And at zero cost. No materials to buy and ship, no shareholders to appease. A place as flexible — and limitless — as the web has no business being boring.
There are plenty of ways, big and small, of keeping this place colourful. Whether our digital footprints are on third-party websites or ones we build ourselves, we needn’t toe the line.
Colour seems an appropriate place to start. When given the choice, try something audacious rather than safe. The worst that can happen is that it doesn’t work. It’s not like the sunk cost of painting a room; if you don’t like the palette, you simply change the hex codes. The same is true of fonts, icons, and other building blocks of the web.
As an example, a couple of friends and I listen to and review albums occasionally as a hobby. On the website, the palette of each review page reflects the album artwork:

I couldn’t tell you if reviews ‘perform’ better or worse than if they had a grayscale palette, because I don’t care. I think it’s a lot nicer to look at. And for those wondering, yes, I have tried to make every page meet AA Web Accessibility standards. Vibrant and accessible aren’t mutually exclusive.
Another great way of bringing vibrancy to the web is a degree of randomisation. Bruno Simon of Three Journey and awesome portfolio fame weaves random generation into a lot of his projects, and the results are gorgeous. What’s more, they feel familiar, natural, because life is full of wildcards.
This needn’t be in fancy 3D models. You could lightly rotate images to create a more informal, photo album mood, or chuck in the occasional random link in a list of recommended articles, just to shake things up.
In a lot of ways, it boils down to an attitude of just trying stuff out. Make your own font, give the site a sepia filter, and add that easter egg you keep thinking about. Just because someone, somewhere has already done it doesn’t mean you can’t do it your own way. And who knows, maybe your way stumbles onto someplace wholly new.
I’m wary of being too prescriptive. I don’t have the keys to a colourful web. No one person does. A vibrant community is the sum total of its people. What keeps things interesting is individuals trying wacky ideas and putting them out there. Expression for expression’s sake. Experimentation for experimentation’s sake. Tinkering for tinkering’s sake.
As users, there’s also plenty of room to be adventurous and try out open source alternatives to the software monopolies that shape so much of today’s Web. Being active in the communities that shape those tools helps to sustain a more open, collaborative digital world.
Although there are lessons to be taken from it, we won’t get a more colourful web by idealising the past or pining to get back to the ‘90s. Nor is there any point in resisting new technologies. AI is here; the choice is whether we use it or it uses us. We must have the courage to carry forward what still holds true, drop what doesn’t, and explore new ideas with a spirit of play.
Here are a few more Smashing articles in that spirit:
I do think there’s a broader discussion to be had about the extent to which A/B tests, bottom lines, and focus groups seem to dictate much of how the modern web looks and feels. With sites being squeezed tighter and tighter by dwindling advertising revenues, and AI answers muscling in on search traffic, the corporate entities behind larger websites can’t justify doing anything other than what is safe and proven, for fear of shrinking their slice of the pie.
Lest we forget, though, most of the web isn’t beholden to those types of pressure. From pet projects to wikis to forums to community news outlets to all manner of other things, there are countless reasons for websites to exist, and they needn’t take design cues from the handful of sites slugging it out at the top.
Connected with this is the dire need for digital literacy (PDF) — ‘the confident and critical use of a full range of digital technologies for information, communication and basic problem-solving in all aspects of life.’ For as long as using third-party platforms is a necessity rather than a choice, the needle’s only going to move so much.
There’s a reason why Minecraft is the world’s best-selling game. People are creative. When given the tools — and the opportunity — that creativity will manifest in weird and wonderful ways. That game is a lot of things, but gray ain’t one of them.
The web has all of that flexibility and more. It is a manifestation of imagination. Imagination trends towards colour, not grayness. It doesn’t always feel like it, but where the internet goes is decided by its citizens. The internet is ours. If we want to, we can make it technicolor.
by Frederick O’Brien (hello@smashingmagazine.com) at October 13, 2025 10:00 AM
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11880
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

CSS field-sizing是一个CSS新特性,专门给输入型表单元素使用的,例如<input>、<textarea>元素等。
是这样的,在过去,无论是单行输入框,还是多行文本域,其宽度在内容输入的时候,都是固定的,如果希望尺寸跟着内容的宽度走,需要使用JavaScript代码帮忙处理,现在,无需这么麻烦,使用field-sizing设置下就好了,例如:
input { field-sizing: content }
<input placeholder="输入内容">
此时,你在输入框内键入内容,可以看到,输入框的宽度基于你输入的内容多少自动撑大或缩小了。
实时效果如下所示(Chrome 123+,或者最新的Safari),大家可以再下面的输入框随便写点什么内容:
是不是可以看到,输入框的宽度紧跟着文字内容多少变化了?
语法很简单,就下面这几个属性值:
field-sizing: content; field-sizing: fixed;
其中:
fixed是默认值,表示尺寸固定。content表示尺寸根据内容多少进行变化。没什么好讲的。
一些补充说明:
field-sizing:content声明的时候,原本会影响尺寸的size属性就会变得无效。rows和cols属性也会无效。field-sizing属性也可以用在<select>元素上。min-width和max-width属性限制输入框的最小尺寸和最大尺寸。避免内容过多的时候,影响排版布局。好了,其实就这么点内容,我觉得这个特性还是有点用处的。
最后,兼容性:

Safari已经明确支持了。
好,就这么多!感谢阅读,欢迎点赞,转发。
😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11880
(本篇完)
这一篇是前几个月研究桌游规则期间的另一篇小结。因为最近两个多月都在制作 Deep Future 的数字版,没空整理笔记。现在闲下来,汇总整理这么一篇记录。
今年夏天,我迷上了 DIY 类型的桌游。这类桌游最显设计灵感。商业桌游固然被打磨的更好,但设计/制作周期也更长。通常,规则也更复杂,游戏时间更长。我经常买到喜欢的游戏找不到人开。阅读和理解游戏规则也是颇花精力的事情。所以,我近年更倾向于有单人模式的游戏。这样至少学会了规则就能开始玩。但为单人游玩的商业桌游并不算多(不太好卖),而我对多年前玩过的几款 PnP (打印出来即可玩)类单人桌游印象颇为深刻:比如 Delve 和同期的 Utopia Engine (2010) 。
在 7 月初我逛 bgg 时,一款叫做 Under Falling Skies 的游戏吸引了我。这是一个只需要 9 张自制卡片加几个骰子就可以玩的单人游戏,规则书很短几分钟就理解了游戏机制,但直觉告诉我在这套规则下会有很丰富的变化。我当即用打印机自制了卡片(普通 A4 纸加 9 个卡套)试玩,果然其乐无穷。尤其是高难度模式颇有挑战。进一步探索,我发现这个游戏还有一个商业版本,添加了更长的战役。当即在淘宝上下了单(有中文版本)。
从这个游戏开始,我了解到了 9 卡微型 PnP 游戏设计大赛。从 2008 年开始,在 bgg (boardgamegeek) 上每年都会举办 PnP 游戏设计大赛。这类游戏不限于单人模式,但显然单人可玩的游戏比例更高。毕竟比赛结果是由玩家票选出来,而单人游戏的试玩成本更低,会有更多玩家尝试。据我观察,历年比赛中,单人游戏可占一半。近几年甚至分拆出来单人游戏和双人游戏,多人游戏不同的设计比赛。
根据使用道具的限制条件,比赛又被细分。从 2016 年开始,开始有专门的 9 卡设计大赛。这是众多比赛中比较热门的一个。我想这是因为 9 张卡片刚好可以排版在一张 A4 纸上,只需要双面打印然后切开就完成了 DIY 制作。加上每个桌游玩家都有的少许米宝和骰子,阅读完说明书就可以游戏了。
如果嫌自己 DIY 麻烦或做出来的卡片不好看,在淘宝上有商家专门收集历年比赛中的优秀作品印出来卖,价格也非常实惠。比赛作品中特别优秀的,也会再完善和充实规则,制作大型的商业版本。例如前面介绍的坠空之下就是一例。我觉得,阅读规则书本身也很有意思。不要只看获奖作品,因为评奖只是少量活跃玩家的票选结果,每个玩家口味不同,你会有自己的喜好。而且我作为研究目的,更爱发现不同创作者的有趣灵感。
如果对这个比赛有兴趣,可以以关键词 2025 9-Card Nanogame Print and Play Design Contest 搜索今年的比赛历程。
我花了几周时间玩了大量的 9 卡桌游。喜欢的非常多,无法一一推荐。除了前面提到的坠空之下,让我推荐的话,我会选择 2023 年的 Survival Park (Dinosaurs game) 。倒不是我自己特别偏爱这款,而是我介绍给云豆后,他也很喜欢。
其实,除了 9 卡游戏,还有 18 卡,54 卡等。卡片数量限制提高后,设计者可以设计出更丰富的玩法。例如著名的 Sprawlopolis (无限都市) 一开始就是一款 18 卡桌游,但后来已经出了相当多的扩展。反过来,也有用更少卡片来设计游戏。比如 1 卡设计大赛就限制设计者只使用一张卡片(的正反面)。
在 bgg 上,你可以在 Design Contests 论坛找到每年举办的各种类型设计大赛。除了传统的 各种 PnP 类型外,我很喜欢的还有传统扑克设计比赛。用 2025 Traditional Deck Game Design Contest 就可以搜索到今年的。这个比赛开始的比较晚,2022 年才开始的第一届。
这个比赛限制设计者围绕传统扑克牌来设计游戏玩法。如果你想玩这些游戏,成本比 PnP 游戏更低:你甚至不需要 DIY 卡片,家中找出 1/2 副扑克就可以玩了。我小时候(1980 年代)特别着迷扑克的各种玩法,在书店买到过一本讲解单人扑克玩法的书,把上面介绍的游戏玩了个遍。所以在多年之后见到 Windows 后,对纸牌游戏的玩法相当亲切。
可以说扑克发展了几百年,单人玩法就没太脱离过“接龙”;多人玩法的核心规则也只有吃墩(桥牌)、爬梯(斗地主)、扑克(Poker 一词在英文中特指德州扑克)等少量原型。
但自从有了这种比赛,设计者的灵感相互碰撞,近几年就涌现出大量依托扑克做道具的新玩法。往往是头一年有人想出一个有趣的点子,后一年就被更多设计者发扬光大。电脑上 2024 年颇为好评的小丑牌也是依托德州扑克的核心玩法,不知道是否受过这个系列比赛作品的启发,但小丑牌的确又启发了这两年的诸多作品:例如我玩过的 River Rats 就特别有小丑牌的味道,同时兼备桌游的趣味。
单人谜题类中,我特别喜欢 2024 年的 Cardbury :它颇有挑战,完成游戏的成功率不太高,但单局游戏时间较短,输了后很容易产生再来一盘的冲动。
多人游戏,我向身边朋友推广比较顺利的有 Chowdah 。它结合了拉米和麻将的玩法。我只需要向朋友介绍这是一款使用扑克牌玩的麻将,就能勾起很多不玩桌游的人的兴趣。而玩起来真的有打麻将的感觉,具备一定的策略深度。
我自己曾经想过怎样用传统扑克来模仿一些经典的卡片类桌游,但设计出来总是不尽人意。比如说多年前我很喜欢的 Condottiere 佣兵队长,如果你没玩过它的话,一定也听过或玩过猎魔人 3 中的 Gwent 昆特牌。昆特牌几乎就沿用了佣兵队长的核心规则。而 2024 年的 Commitment 相当成功的还原了佣兵队长的游戏体验。
还有 MOLE 则很好的发展了 Battle Line 。
如果想体验用扑克牌玩出 RPG 的感觉,可以试试 2022 年的Kni54ts :有探索地图、打怪升级捡装备等元素;多人对抗的则有 Pack kingdoms。
有趣的游戏规则还有很多,我自己就记了上千行规则笔记。这里就不再一一列出给出评价,有兴趣的同学可以自己探索。
这次介绍两款在国内人气不高的卡牌构筑类桌游。游戏都还不错,可能是因为没有中文版,所以身边没见什么朋友玩。
首先是 XenoShyft 。它的最初版全名为 XenoShyft: Onslaught (2015) ,后来又出了一个可以独立玩的扩展 XenoShyft: Dreadmire (2017) 。
故事背景有点像星河舰队:由人类军士抵抗虫子大军。简单说,这是一个塔防游戏:游戏分为三个波次,每个波次三轮,一共要面对九轮虫群的冲锋。
游戏中有四类卡片:部队、敌人、物品、矿物,另有一组表示玩家所属部门的能力卡,每局游戏每个玩家可以分到一张,按卡片上所述获得能力
矿物就是游戏中的货币,用来在市场购买部队卡和物品卡。敌人卡分为三组,对应到三次波次,洗乱后形成系统堆。玩家需要在每轮击败一定数量的敌人,撑过三个波次就可以取得游戏胜利。
玩家基础起始牌组 10 张,4 张最低级的士兵和 6 张一费的矿物。根据玩家的部门,还会得到最多 2 张部门所属的特殊卡。
市场由部队卡和物品卡构成,其中部队卡是固定的,分三个波次逐步开放购买。物品卡一共 24 种(基础版),但同时只会有 9 种出现在市场上。玩家部门可能强制某种物品一定出现在市场上,其它位置则是每局游戏随机的。在游戏过程中,当一种物品全部买空后,会在市场中随机补充一堆新的物品卡。普通敌人卡按波次分为三组洗乱,然后根据波次再从 6 张 boss 随机分配到三个波次中。
每个轮次,玩家先抽牌将手牌补齐到 6 张,然后打出所有的矿物卡,并根据波次额外获得 1-3 费,然后用这些费用从市场购买新卡片,花不完的费用不保留。新购得的卡片直接进入手牌(而不是弃牌堆)。这是一个合作游戏,所以玩家可以商量后再决定各自的购买决策。
然后,玩家把手牌部署到战区。每个玩家把部队卡排成一行(最多四个位置),物品中的装备可以叠在部队卡上增强单位的能力。玩家可以给队友的部队卡加装备(但不可以把自己的部队卡部署在队友战区)。部署环节玩家之间可以商量,同时进行。
之后进入战斗环节。这个环节是一个玩家一个玩家逐个结算。翻开敌人队列上的敌人卡(在部署环节是不可见的)、在敌人卡片翻开时可能有一次性能力,发动该能力、然后(所有)玩家都有一次机会打出手牌中的物品卡或使用部署在战场上的卡片能力。之后,双方队列顶部的两张卡片结算战斗结果。卡片只有攻击和 HP 两个数值,分别将自己的 HP 减去对手的攻击点。一旦有一方(或双方)的 HP 减到 0 ,战斗结束,把战斗队列卡片前移,重复这个过程。直到一方队列为空。
如果己方部队全灭,每场战斗的反应阶段(每个玩家都可以打出一张手牌或使用战斗卡片能力)依然有效,但改由基地承受虫子的攻击。基地的 HP 为所有玩家共享,总数为玩家人数乘 15 。可以认为基地的攻击无限大,在承受攻击后,一定可以消灭敌人。一旦基地 HP 降为 0 ,所有玩家同时输掉游戏。
游戏中的死亡效果有两种,毁掉(burning)和弃掉(discarding)。毁掉一张卡指把这张卡片退回市场(如果市场上还有同类卡)或移出游戏(市场上没有对应位置),而弃掉一张卡指放去玩家的弃牌堆。
通常,敌人卡片效果一次只会结算一张(即当前战斗的卡片)。但有些卡片效果会对场上敌人队列中尚未翻开的卡片造成伤害。这种情况需要先将所涉及的敌方卡片都翻过来,并全部结算卡片出场能力。对于需要同时结算多张敌人卡片出场能力时,玩家可以讨论执行次序。
如果对这款游戏有兴趣,又找不到人玩的话,可以试试它的电子版,在 steam 上就有。不过看评论,据说电子版 bug 有点多。
另一个游戏是 G.I. JOE Deck-Building Game (2021) 。G.I. JOE 特种部队是孩之宝(也就是变形金刚品牌的拥有者)旗下的一个品牌,除了玩具,有衍生的漫画、电影和动画片。这个桌游也是这个玩具品牌的衍生品。我认为这个 DBG 里的某些设定(不同的游戏剧本、同一剧本中不断推进的故事任务、队员的多种技能)也影响了星际孤儿那个电子游戏。
游戏有很多剧本、以及若干扩展。不同的剧本在规则细节上有所不同(这一点和星际孤儿很相像),这里只减少核心共通的规则。
这是个多人协作游戏。当然,只要是协作游戏,就一定可以单人玩,只需要你轮流扮演不同角色的玩家即可。每个玩家一开始有一张特殊的领袖卡,然后配上 9 张固定的初始牌组成了起始卡组。每个回合摸 5 张卡,用不完的卡会弃掉,不能保留到下一回合使用。每张领袖卡都对应了一个升级版本,可以在游戏进程中购买替换。
市场由一组卡片洗乱,随机抽出 6 张构成。每当玩家购买一张卡,就会补充一张新卡。但如果卡堆耗尽尚未结束游戏,游戏失败。在游戏过程中,可能有敌对卡片出现,会盖掉市场中的卡。玩家需要解决掉敌人,否则盖掉的卡片无法购买。如果 6 张市场卡片都被盖掉也会导致游戏失败。
当每个玩家执行完一轮行动,即为一大轮游戏。在每大轮开始,都会推进一个全局的威胁指示条。一旦威胁指数上升到某一程度,就会发生一些固定事件。维护指数走到头会导致游戏失败。
游戏故事由三幕构成,每幕随机选取两张对应的故事任务卡和一张固定的终局局故事卡,一共 9 张故事任务卡构成了整局游戏。永远有一个故事任务呈现在场景中,它有可能触发一个回合效果,需要在每个玩家回合开始时结算。
每一幕开始都会洗混所有的系统事件卡堆(包括之前解决完弃掉的事件卡),故事卡和威胁进度条会触发这些事件。这些事件会给玩家增加一些负面效果,或是在场上增加一些任务让玩家解决。
游戏任务分两种:团队任务和支线任务。故事卡一定是团队任务,事件产生的 boss 卡也是团队任务。团队任务可以在当前玩家决定去进行任务时,其他玩家提供协作;而支线任务只能由当前玩家独立完成。任务有地形、难度、技能需求、持续效果等元素构成。
地形指玩家开启任务需要使用怎样的载具,分陆海空三类。技能要求则限制了玩家可以派出的队员。难度数字决定了玩家最终需要在此技能上获得多少点才能完成任务。持续效果则会在该任务完成前,对玩家造成的负面效果。
开启一个任务需要玩家从机库派出一个对应地形的载具以及至少一个队员(从手牌打出)。该载具是在玩家回合开始时从手牌打在机库中的,VAMP 作为默认载具总可以保证一个回合使用一次。高级载具可以从市场购买。对于团队任务,所有玩家都可以协商派出队员,但队员总数不能超过载具的容量。
任务上标注了所需技能,派出的队员卡如果有符合的技能,则可以把技能点加到任务中。如果技能不匹配,也可以视为一个通用技能。任务卡最多要求两种技能,如果是 & 标记,则表示可以只要符合两种技能中的任意一种都可以生效;如果是 or 标记,则需要当前玩家选择其中一种技能,所有队员都需要匹配这种技能。
最终参与任务的技能总数决定了最终可以用几个六面骰。每个六面骰有三个零点,两个一点,一个两点;扔出对应数量的骰子,把最终点数相加,如果大于等于任务的难度值,则任务成功并或许成功奖励,否则任务失败。对于故事任务,失败需承受任务卡上的失败惩罚,并结束任务;对于其它任务,失败会让任务继续保留在场上。
有一类叫做 Precision Strikes 的任务,在翻出时需玩家讨论后决定放在谁的面前,变成它代做的支线任务。每个玩家最多只能放两张 Precision Strikes 在面前,到他的行动回合,必须先处理掉 Precision Strikes 任务。
在玩家做完所有想做的任务后,剩余的手牌可以作为够买新卡的费用。每张卡都标有一个自身的价格,以及一个在购买阶段可以当成几点费用使用。没有用完的费用不会积累到下个回合。购买的载具卡会在购买后直接进入机库,供后续回合所有玩家使用。其它卡片则放在抽牌堆顶。
整个回合结束后,弃掉所有手牌,以及回合中使用过的卡牌以及载具,重新抽五张。
玩家可以共建一个基地。这个基地由五部分构成,在游戏过程中逐步升级,升级也时通过购买完成的。在游戏过程中,以升级的部分也可能被摧毁或重建。这五个部件如下:
Repair Bay 会让任务中使用的载具都放在该处而不是弃牌堆。在回合结束(或被摧毁),Repair Bay 中的载具都会回到机库。这样,载具的利用率会大大提升。
Stockade 建成后,击败的 boss 卡会进入这里而不会重复进入游戏。
Battlestation 可以在团队任务中重掷一个骰子。
Laser Cannon 可以在支线任务中增加一个骰子。
Command Room 把手牌增加到 6 张。
桌游 Deep Future(深远未来)开发告一段落,我为它创建了一个 itch.io 的页面 发布第一个试玩版本。接下来的 bugfix 会在 github 继续,等积累一定更新后再发布下一个小版本。
这是一个兴趣驱动的项目。正如上一篇 blog 中写到,驱使我写它的一大动力是在实践中探索游戏开发的难题。写这么一篇总结就是非常必要的了。
我在 2025 年 7 月底写下了项目的第一行代码。在前三周并没有在实现游戏方面有太多进展。一开始的工作主要在思考实现这么一个游戏,底层需要怎样的支持。我使用的引擎 soluna 也只是一个雏形,只提供非常基础的功能。我想这样一个卡牌向桌游数字化程序,更好的文本排版功能比图形化支持更为迫切。固然,我可以先做一个 UI 编辑器,但那更适合和美术合作使用。而我现在只有一个开发者,应该用更适合自己的开发工具。应该更多考虑自己开发时的顺手,这样才能让开发过程保持好心情,这样项目才可能做完。所以我选择用结构化文本描述界面:容易在文本编辑器内编写,方便修改,易于跟踪变更和版本维护。
在 8 月的前两周,开发工作更多倾向于 soluna :
期间有两个和游戏无关的(看起来很小的)问题花掉了我很多时间:
关于 alpha 混合这点。根源在于 20 多年前我使用 CPU 计算 alpha 混合。当时如果将图片像素预先乘上一次 alpha ,可以减少一点运行时的 CPU 开销。这个习惯我一直带到现在 GPU 时代,本以为只是现代图形管线中的一个设置而已。当我独立开发时才发现,现在的图片处理软件默认都不会预乘方式导出图片,这让我自己使用 gimp 编辑带 alpha 通道的图片时,工作流都多了一步。因为 gimp 也是现学的,一下子也没有找到特别方便的方法给图片预乘 alpha ;使用 imagemagick 用命令行处理虽不算麻烦,但增加了工作流的负担。我在上面花掉了十多个小时后(主要花在学习 gimp 和 imagemaick 的用法)才醒悟,配合已有成熟工具简化开发工作流才是最适合我这样独立开发。所以我把引擎中的默认行为改成了非预乘 alpha 。
到 8 月的第三周,已经可以拼出静态的游戏界面:有棋盘、卡片、带文字的桌面布局。虽然从外观上,只是实现一个简陋的静态的带图层排版系统,但视觉上感觉游戏已经有点样子了,而不再仅仅是脑补的画面,这让开发的心情大好。
同时,我实现了基本的本地化模块。其实不仅仅是本地化需求,即使是单一语言,这种重规则的桌游,在描述暂时游戏规则时也非常依赖文本拼接。因为维护了多年 stellaris 的中文 mod ,我受 Paradox 的影响很深。早就想自己设计一套本地化方案了,这次得以如愿。
接下来的四周游戏开发速度很快。在之前三周的引擎补完过程中,我在脑中已经大致计划好后续的游戏开发流程:按游戏规则流程次序,拆分为布局阶段、开始阶段、行动阶段、结算阶段、胜利阶段、文明及奇迹分开实现。每个流程在实现时根据需要再完善引擎以及游戏底层设施。
以游戏玩的流程来依次实现,可以让游戏逐步从静态画面变成可交互的,这种体验能提供一种开发的目标感:让我觉得开发进度在不断推进;而每个步骤其实要解决和补充底层设施的不同方面,解决问题是不一样的,这样可以缓解开发的枯燥感。保持开发的心情最重要,这是我近二十年学到的东西。只是过去我一直偏重于底层开发,一直回避了相对枯燥繁琐重复的游戏功能开发。开发游戏中的不确定性,实现一点点交互功能也要花费大量时间的确是非常打击开发心情的东西。这并不像底层开发有一个确定目标,和 API 打交道(而不是纠缠在低效的人机交互中)也可以直指问题本身。
实现游戏布局设置时,我顺道完善了桌面布局模块,让棋盘、手牌、胜利轨道、中立卡牌区等展示得好看一些。并增加了和鼠标的交互,卡片在区域间的运动等。
待到游戏有了基本的交互,变得可以“玩”了。我发现,一个数字化的桌游最重要的是引导玩家熟悉桌游规则。重要不是做一个详尽的手把手一二三的教学,而是玩家一开始无意识操作中给出有价值的信息反馈。玩家可以学到每个操作对游戏状态造成了怎样的影响。玩家还应该可以随时点击桌面元素去了解这各东西是什么。虽然大段的文字描述对电子游戏玩家来说并不友好,但对桌游玩家来说是必修课。数字版能提供更好的交互手段,让玩家可以悬停或点击一个元素,获得文本解释,已经比阅读桌游说明书友好太多了。
所以我在一开始就花时间在底层设计了这样的提示系统。
打算往下实现更复杂的游戏流程时,我意识到这类流程复杂的游戏,主框架镶嵌一个简单的游戏主循环显然不够用。我需要一个状态机来管理交互状态。一开始并不需要将状态机实现得面面俱到,有个简单的架子即可。Lua 的 coroutine 是实现这样的状态管理非常舒适的工具,几十行代码就够了:gameplay 的状态切换很轻松的就和渲染循环分离开了。
游戏的“开始阶段”本身并无太多特有的 gameplay 需要实现。但是,这套游戏规则中最复杂的 advancement effect 机制在这个阶段就有体现。最难的部分是设计 advancement 的交互。在桌游原版规则中,玩家可以任意指定触发哪些 advancement ,它们的来源也很多样:弃掉手牌、母星区卡片的持久能力、殖民地区卡片的一次性能力等等。每张卡片上可触发的 advancement 数量不一,从 0 到 3 皆有可能,玩家可以选择触发或忽略,最后还可以自由决定对应 effect 的执行次序。
对于桌游来说,这是非常自然的形式:桌游玩家的脑海中一开始就包含了所有游戏规则,大脑会将这些散布在桌面各处的元素聚集起来,筛选出需要的信息,排序,执行,一气呵成。但对于数字版,很容易变成冗长的人机交互过程。每个步骤都需要和玩家确认,因为轻微的差别都有可能影响 effect 结算的效果。这不光实现繁琐、对玩家更是累赘。
所以,电子游戏中更倾向于自动结算的规则,减少玩家的决定权。玩家也不需要了解所有的游戏规则。只有在玩家成长中,从新手到老鸟的过程,玩家可能去关注这些自动结算是怎样进行的,电子规则则提供一些中途干预的手段帮助高级玩家,所谓提高玩法深度。以万智牌和炉石传说相比较,就能体会到内核相似但卡牌效果结算方式的巨大差异。前者是为桌面设计的,后者则天生于电脑上。
不过这次,我不打算对桌游规则做任何调整。专心实现桌游的数字化。有些看似有绝对最佳选择的 advancement ,我也没有让系统自动结算,还是交给玩家决定。一是复原桌游的游戏感觉,二是让玩家参与结算推演的过程,让玩家逐步熟悉游戏规则。
当然,一个 advancement 当下是否可用,这是由严格规则约束的。桌游中需要玩家自己判定(也非常容易玩出村规),而数字版则可以做严格检查,节省玩家记忆规则细节的负担。这个负担依旧存在,转嫁到数字版开发者身上了。为此我在桌游论坛和桌游规则作者探讨了多处细节,以在实现中确定。
advancement 结算这块一开始就决定仔细抽象好,这样可以复用到后续的行动结算部分。不过我还是低估了一次设计好的难度,后来又重构了一次。
另外,从这里开始,我发现这个游戏的规则细节太多以至于我必须提升测试玩法过程的效率。所以设计了一个测试模块。并不是一个自动化测试,而仅仅是为人工测试做好 setup ,不必每次从头游戏。我有两个方案:其一是写一个简单的脚本描述测试用的 setup ;其二是完善存档模块,及作弊模块,玩到一个特定状态就存档,利用存档来促使。
我选择了方案一,在编辑器里编写 setup 脚本。以我的经历,似乎很多游戏项目偏向于方案二,好像有纯设计人员(策划)和测试人员共同参与开发的项目更喜欢那样。他们讨厌写脚本。
行动阶段的开发基本可以按游戏规则中定义的 8 种行动分别实现。其中 EVOKE 涉及文明卡,第一局游戏中不会出现,本着能省则省早日让游戏可玩的想法,我一开始就没打算实现。
而 PLAN 行动是最简单的:只是让玩家创造一张特定卡片,而且不会触发 advancement 。我就从这里开始热身。不过,PLAN 行动和其它行动不同,它不是靠丢弃手牌触发的,而是一个专有行动。这似乎必须引入一种非点击卡片的交互手段。我就分出时间来给界面实现了 button 这个基础特性。同时确定了这个游戏的基本交互手段:当玩家需要做出选择时,使用多张卡片标注上选项,让玩家选择卡片;而不是使用一个文本选择菜单。交互围绕卡片做选择,一是我像偷懒不做选择菜单,二是希望突出游戏以卡牌为主体的玩法。当然,也需要多做一些底层设施上的工作:一开始我打算让每张卡片都在游戏中有唯一确定的实体,但既然卡片本身又可以用来提供玩家决策的选项,就在底层增加了一种叫做卡片副本的对象。
某些行动有独自的额外需求。像 POWER 行动最为简单,只是抽牌而已。但 SETTLE 就涉及和中立区卡牌的交互;GROW 涉及在版图上添加 token ;ADVANCE 涉及科技卡的生成;BATTLE 和 EXPAND 涉及版图区域管理。这些需求一开始在 gameplay 底层都没有实现,只在碰到时添加。
好在这些需求天生就可以分拆。我大致保持一天实现一个行动的节奏,像和银河地图的交互也会单独拿出一天来实现。让每天工作结束时游戏可以玩的部分更多一点,可以自己玩玩,录个短视频上传到 twitter 上展示一下。
部分复杂的部分很快经历了重构。例如星图的管理,从粗糙到完善。一开始只是对付一下,随着更丰富的需求出现很快就无法应对,只能重新实现。中间我花了一天复习六边形棋盘的处理方法 。
交互部分给图形底层也提出了新的需求:我给 soluna 增加了蒙版的支持,用来绘制彩色的卡片。
按我的预想,按部就班的把游戏行动逐步做完,游戏的框架就被勾勒出来了。让游戏内容一步步丰富起来会是我完成这个项目的动力。这个过程耗时大约 2 周,代码产出速度非常快,但也略微枯燥。感觉代码信息密度比较低,往往用打算代码实现一点点功能。这种信息密度低的代码很容易消耗掉开发热情。但它们又很容易出错,因为原本的桌游规则细节就很繁杂,一不小心就会漏掉边界处理。gameplay 的测试也不那么方便,如果依赖人去补充详尽的测试案例,开发周期会成倍的增加,恐怕不等我实现周全,精神上就不想干了。期间主要还是靠脑中预演,只在必要时(感觉一次会做不到位)才补充一个测试案例。
按节奏做到回合结算阶段时,我发现虽然看起来游戏可以玩了,工作其实还有很多:结算的交互和前面的差别很大、还需要补充 upkeep 方块的实现。回合结算事实上是系统行动阶段,虽然玩家参与的交互变少了,但自动演绎的东西增加了。系统结算过程可能导致玩家失败,所以必须再实现一个玩家失败的清点流程才能完整。
到 9 月初的时候,我完成了以上的工作。正好碰上每年一度的 indieplay 评审工作(四天),我在评审前一晚完成了第一个可玩版本(只有失败结算,没有胜利结算),第二天带到评审处,给一个评委试玩。这是第一个除我之外的玩家,表现还不错,居然只碰到 1 个中断游戏的 bug ,还只是发生在游戏最后一回合。来自于专业玩家的评价是:这么复杂的游戏规则想想也知道需要大量的开发工作,一个月就实现出来算是挺快了。缺点是部分游戏流程进行的太快,还没明白是什么就过去了(自动推演的部分);影响玩家选择的支付和结算部分,非常容易误操作选择对玩家不利的选择;至于最后碰到的那个 bug ,可以充分理解。第一次玩家试玩有一两个 bug 是再正常不过了。
我记录了一下玩家反馈,回家调整了对应部分:在底层增加了鼠标长按确认的防呆操作(其中图形底层实现了环形进度条的显示,同时发掘了底层图元装箱的一个小问题:图元拼接在整张贴图上时,需要空出一像素的边界,否则会相互影响),顺便重构了鼠标消息的底层管理 。另外,丰富了不少自动推演的流程的视觉表现,一开始设想尽量快速自动结算方便玩家恐怕不太合适。玩家并不需要过于加快游戏节奏,通过视觉上的推演过程让玩家理解游戏更重要。这些工作做了几个晚上(白天需要做游戏评审工作)。
接下来的两周发生了意外,我的开发工作几乎停滞。
租的房子到期,原计划在 9 月底搬家。给 indieplay 做评委的最后一天,接到母亲电话,在小区门口被顺丰快递员骑的电瓶车撞到,胫骨粉碎性骨折住院。处理医院的事情花了一整个晚上,心情大受影响。
调整心情后,我还需要面对另一个问题:原本计划是和母亲一起收纳搬家的东西,时间上非常充裕,每天只需要规划出小块时间即可。现在虽然父亲可以负责医院住院的事情,但搬家的工作几乎得我一个人来做了。关键是意外让日程安排突然变得紧张起来。把开发从日程重去掉是必然的。
最终如期搬完家,非常疲惫不堪。万幸母亲手术很顺利,只是未来半年行动受限,需要人照顾。
这段时间,我已经把游戏代码仓库开放。由于在 twitter 上的传播,已经有少量程序员玩家了。其实游戏并未完成,网友 Xhacker 率先赢得了(除我本人的)第一场游戏胜利…… 只是代码上并无胜利判定,所以他补完了这部分代码。由于对游戏规则不那么熟悉,所以实现是有 bug 的,后来也被我重构掉了。但接受这种 gameplay 的 PR 让我感受到了玩家共同创作的热情。
Xhacker 同学还依照本地化格式,提供了英文版本的文本。这也是我计划中想做而没精力顾及的部分。帮我节省了大量的时间。后来我只花了两天时间就将后续开发中的新词条双语同步。
网友 Hanchin Hsieh 对多平台支持表现出热情。先后实现了 soluna 的 MacOS 和 Linux 版本 。中间我也花了 1-2 天时间解决多平台的技术问题。还给 soluna 提交了 CI 以及 luamake 的构建脚本。
如果开源项目可以拆分出更独立的子任务(例如跨平台支持、本地化等),多人合作的确能大大缩短开发进程。
搬家结束后,我重拾开发。恢复开发状态用了一两天的时间。后续工作主要是以下几点:
这部分花了两周时间,可以说是按部就班。但实际开发工作比字面上的需求多许多。
在重构胜利结算流程中,我顺手把控制游戏流程的状态机模块修改了不少。因为无论是胜利结算还是失败判定,以及持久化支持,都会引入更复杂的状态切换。需要保证这些切换过程中数据和表现一致不能出错。
胜利结算中,为了增加游戏的仪式感,底层支持了镜头控制:可以聚焦放大桌面,将镜头拉近和恢复。
游戏存档被分离到独立服务中,同时承担数据一致性校验。在过去一个月的开发中,我发现存档对最终 bug 非常有效。依赖出错前的存档恢复出错环境比查看 log 要方便得多。但这需要更细致的存档备份,方便玩家从文件系统中提取历史存档。同期还解决了一个底层在 windows 上处理 utf-8 文件名转换的 bug 。
文明卡和奇迹都是游戏后期内容,开发起来并不容易。对于文明卡,规则书上有不少含糊其辞的地方,专门去 bgg 论坛和原作者核对细节。文明卡有一半的每个效果是特殊的,需要单独实现。但这些单独实现的部分和之前的 advancement effect 又有部分共同之处。本着让日后修改更容易的原则,再次重构了部分 advancement 处理的代码,让它们可以共用相同的部分;而奇迹的实现使得星图的管理模块又需要扩展,同样需要扩展的是 EXPAND 行动流程。这部分的开发持续了好几天。
主界面功能涉及到多层界面布局。之前游戏只使用了单层 HUD 结构外加一个说明文字的附加层,没打算实现多层界面(即多窗口)。而按钮模块也是临时凑上去的。待到实现主界面时,其结构的复杂度已经不允许我继续凑合了。
所以我对此进行的重新设计:原则上还是将行为了表现以及交互分离。在同一个代码文件里实现了所有界面按钮对应的功能,用一个简单的 table 描述界面按钮的视觉结构,通过这个视觉结构表来显示界面。这还是一个贴近这个游戏的设计,不太有普适性。可能换个游戏的交互风格就需要再重构一次。不够我觉得现阶段不用太考虑以后再开发新游戏的需求。遇到重写就好了。多做几次才好提取出通用性来。
界面的防呆设计没有继续沿用长按转圈的方法,而使用了两次确认的方式。即危险操作(例如删除存档)的按钮在点击后,再展开一个二级菜单,需要玩家再点击新按钮才生效。我觉得这种方式实现简单,也可以充分防呆。
我原本只想做卡片随机命名,不想做玩家输入自定义卡片名称的。一是做了一个多月有点疲倦了,想早点告一段落;二是考虑到我自己玩了不少策略游戏,几乎不会修改系统随机起好的名字,即使游戏提供了玩家修改名字的功能。很快的,我就从网上搜集了中国和世界大城市名称列表,用来随机给星区和星球卡命名。但当我在做科技卡命名时却犯了难。在原本桌游规则里,依照三条随机组合的 advancement 效果给科技卡起一个恰当的名字,是玩家玩这个游戏的一大乐趣(也是一项对玩家想象力的挑战)。程序化命名无非是在前缀、后缀、核心词的列表中按规则组合,必然失去韵味。我花了一天时间做了一班并不满意。尤其是想同时照顾中文和英文的命名风格太不容易。
Paradox 在群星的最近版本中一只致力于生成更好的随机组合名称。我在汉化时也学到了不少,这或许是很好的一个课题,值得专门研究实现。但在当下,我只想早点发布游戏。所以,我选择实现键盘输入模块。
soluna 原本并未实现键盘输入的相关功能。这主要涉及文本块排版模块的改进。因为一旦需要实现输入,就必须控制输入光标的位置,这个信息只要文本排版模块内部才有。我原计划是在日后增加文本超链接功能时再大改排版模块的,这次增加输入光标支持只能先应付一下了。有了底层支持,增加用户自定义卡片名称倒很容易。
至此,我已经完成了绝大部分预想的游戏功能。除了 7 ,暂时还未实现。
最后,还差一个 credits 列表。之前在做网游,只有在大话西游中我按当时的游戏软件管理加上了制作人员名单。那还是我在客户端压(光)盘前一晚执意加上的。为此我熬了一个通宵。后来的网游我便不再坚持,这似乎开了一个坏头,整个中国的网游产品都不再加入 Credits 了。再后来,我只在杭州开发的一个并不成功的卡牌游戏(卡牌对决)中再加过一次 credits。
正如《程序员修炼之道》第二版所言:
提示 97 :在作品上签名
“保持匿名会滋生粗心、错误、懒惰和糟糕的代码,特别是在大型项目中——很容易把自己看成只是大齿轮上的一个小齿,在无休止的工作汇报中制造蹩脚的借口,而不是写出好的代码……我们想看到你对所有权引以为豪——这是我写的,我与我的作品同在。你的签名应该被认为是质量的标志。人们应该在一段代码上看到你的名字,并对它是可靠的、编写良好的、经过测试的、文档化的充满期许。这是一件非常专业的工作,出自专业人士之手”。
我在 gameplay 的开发中充满着仓促、粗糙的设计,在游戏中展示我的名字会让我心存愧疚,以后或许会完善它或在新作品中做得更好。
开发这个项目,我经历了接近两个月,从 2025 年 7 月底到 2025 年 9 月底,除去中间被打断的两周,一共 7 周时间。
游戏项目一共增加了 25152 行,删除了 7912 行文本,合计 17240 行。其中包含了 1000 多行的本地化文本和 3000 行左右的界面布局、测试数据、规则表格等。实际代码在 13000 行左右。
引擎 soluna 因这个游戏增加了 7756 行,删除了 2,084 行代码,合计 5672 行代码。
虽然游戏中美术量不大,但我还是大约花了 3-4 个工作日制作所用到的美术资源。时间花在学习 GIMP 和其它一些美术制作工作使用上为主。图标是在 fontawesome 上进行的二次创作,卡片是自己绘制的。星图则直接复用了桌游的原始资源。
版面设计不算复杂,yoga 提供的 flexbox 方案很好用。算上学习 flexbox 排版的时间,前后大约花了 2 个工作日。
虽然游戏规则很繁杂,但 bug 比我预想的少。debug 时间不算太多,通常随着开发就一起完成了。后来在 github 上玩家提到的 bug 也都可以马上解决。预计后续的游戏测试过程会是一个长尾,持续很长时间,但需要的精力并不多。不过能做到这一点,得益于桌游规则经历了近十年的修订,非常稳定。而我在动手实现数字版前,已经花了一个月的时间充分玩了实体,经历了无数的游戏规则错误。动手写代码时,游戏规则细节已经清晰的刻画在大脑中了。这和写底层代码很像:需求已经被反复提炼,只需要用代码表达出来。开发过程解决的都是程序结构问题,而不是应对多变的需求。
经历这么一次,我想我可以部分回答项目开始之初的疑问。
我在开发过程中的情绪波动告诉我,最重要的是保持开发热情。不同情绪状态下的效率、质量差异很很大。这可以部分解释为什么行百里路半九十。并不是最后 10% 真的有一半工作量,而是开发热情下降后,开发效率变低了。伴随着潜在的质量下降,花在重构、debug 上的时间也会增加。
所以明确拆分任务真的很重要。每完成一步就解决了一个小问题。开发精力就能回复一点。但这样也容易陷入到开发细节中。多人协作可以一定程度的避免这一点,分工让人更专心。一人做多个层次不同门类的工作需要承担思维切换的成本,但能减少沟通,利弊还说不好。
对于游戏来说,视觉反馈是激励开发热情的重要途径。所以需要做一点玩一点。让自己觉得游戏又丰富了。但是追求快速的视觉反馈很容易对质量妥协:我先对付一下让游戏跑起来,不惜使用冗长重复的实现方式,硬编码游戏规则…… 事后一定需要额外精力去拆这些脚手架的。所以,会有很大部分的功能需要实现两遍。这或许是预估开发时间时需要将时间乘 2 的根源。
虽然重构很花时间,有时候还很累。但过去的经验告诉我,越早做,越频繁,越省事。这也是独立开发的优势之一:你不必顾及重构对合作者的冲击。所有东西都在一个人的脑子里,只要对自己负责就够了。
对于独立开发,代码量又变成了一个对项目进度很好的衡量标准。因为你知道自己不会故意堆砌低效代码,那么每 100 行代码就真的代表着大致相同的工作进展。整个游戏的核心代码放在 20000 行之内是非常恰当的篇幅,其实这个数字对非游戏项目也适用。因为这意味着你可以在一到两个月完成项目的基础工作。这样的周期不至于让热情消磨殆尽。后续的长期维护则是另一项工作了。
要把代码量控制在这个规模,需要尽可能的把数据分离出去。不然游戏很容易膨胀到几十万行代码。识别出哪些部分的代码可以数据化只能靠经验积累。而经验来源于多做游戏。所以,我今后还需要多写。
同时,分离引擎也是控制游戏代码规模的要点。不用刻意做游戏引擎,只需要做游戏就够了。识别出通用部分,集成到引擎中。给游戏项目也留一个底层模块,把不确定是否应该放在引擎中的代码先放在那里。它们可能跨不同游戏类型通用,但只是还没想到更好的抽象接口而已。
“优化”工作对我很有吸引力。但考虑到游戏开发进度,可以先把优化点记录下来放一放。保持着写好代码的决心,晚一点做优化不迟。这里的优化不仅仅指性能优化,也包括更好的代码结构和更紧凑的实现方式(更短的代码)。老实说,目前这版实现中,还是有大量冗长可以改进的代码,我相信有机会再做一次的话,我只需要一半的代码就能实现相同的功能。当然我不需要再实现一次,开始下一个项目更好。
开源依然有很大的优势。虽然很少有游戏业务代码开源,《大教堂与集市》的 4.10 探讨了“何时开放,何时关闭”,游戏业务本身开源能获得经济收益非常少。我一开始也考虑过闭源开发。这并不是一个经济上的决定,而是我知道,这个项目注定不会有很高的代码质量,低质量代码不具备传播因素,它无法作为学习参考,也没什么复用性,反而有一点点“面子”问题,毕竟写出低质量代码“面子上”不太好看。
但我发现这个项目开源后依然获得了额外收益。
吸引了程序员的参与,多交了几个朋友。在发现 bug 时,有一个更好的交流基础,对着代码看更清晰。即便只是自己读,阅读公开代码比阅读私人代码会更仔细。而且更有动力用文字解释。在写作过程中,思路得以迅速理清。
btw, github 的公开仓库比私有仓库有更多的免费特性。
I’ve written quite a lot recently about how I prepare and optimise SVG code to use as static graphics or in animations. I love working with SVG, but there’s always been something about them that bugs me.
To illustrate how I build adaptive SVGs, I’ve selected an episode of The Quick Draw McGraw Show called “Bow Wow Bandit,” first broadcast in 1959.

In it, Quick Draw McGraw enlists his bloodhound Snuffles to rescue his sidekick Baba Looey. Like most Hanna-Barbera title cards of the period, the artwork was made by Lawrence (Art) Goble.

Let’s say I’ve designed an SVG scene like that one that’s based on Bow Wow Bandit, which has a 16:9 aspect ratio with a viewBox size of 1920×1080. This SVG scales up and down (the clue’s in the name), so it looks sharp when it’s gigantic and when it’s minute.

But on small screens, the 16:9 aspect ratio (live demo) might not be the best format, and the image loses its impact. Sometimes, a portrait orientation, like 3:4, would suit the screen size better.

But, herein lies the problem, as it’s not easy to reposition internal elements for different screen sizes using just viewBox. That’s because in SVG, internal element positions are locked to the coordinate system from the original viewBox, so you can’t easily change their layout between, say, desktop and mobile. This is a problem because animations and interactivity often rely on element positions, which break when the viewBox changes.

My challenge was to serve a 1080×1440 version of Bow Wow Bandit to smaller screens and a different one to larger ones. I wanted the position and size of internal elements — like Quick Draw McGraw and his dawg Snuffles — to change to best fit these two layouts. To solve this, I experimented with several alternatives.
Note: Why are we not just using the <picture> with external SVGs? The <picture> element is brilliant for responsive images, but it only works with raster formats (like JPEG or WebP) and external SVG files treated as images. That means that you can’t animate or style internal elements using CSS.
The most obvious choice was to include two different SVGs in my markup, one for small screens, the other for larger ones, then show or hide them using CSS and Media Queries:
<svg id="svg-small" viewBox="0 0 1080 1440">
<!-- ... -->
</svg>
<svg id="svg-large" viewBox="0 0 1920 1080">
<!--... -->
</svg>
#svg-small { display: block; }
#svg-large { display: none; }
@media (min-width: 64rem) {
#svg-small { display: none; }
#svg-mobile { display: block; }
}
But using this method, both SVG versions are loaded, which, when the graphics are complex, means downloading lots and lots and lots of unnecessary code.
Replacing SVGs Using JavaScriptI thought about using JavaScript to swap in the larger SVG at a specified breakpoint:
if (window.matchMedia('(min-width: 64rem)').matches) {
svgContainer.innerHTML = desktopSVG;
} else {
svgContainer.innerHTML = mobileSVG;
}
Leaving aside the fact that JavaScript would now be critical to how the design is displayed, both SVGs would usually be loaded anyway, which adds DOM complexity and unnecessary weight. Plus, maintenance becomes a problem as there are now two versions of the artwork to maintain, doubling the time it would take to update something as small as the shape of Quick Draw’s tail.
The Solution: One SVG Symbol Library And Multiple UsesRemember, my goal is to:
I don’t read about it enough, but the <symbol> element lets you define reusable SVG elements that can be hidden and reused to improve maintainability and reduce code bloat. They’re like components for SVG: create once and use wherever you need them:
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="quick-draw-body" viewBox="0 0 620 700">
<g class="quick-draw-body">[…]</g>
</symbol>
<!-- ... -->
</svg>
<use href="#quick-draw-body" />
A <symbol> is like storing a character in a library. I can reference it as many times as I need, to keep my code consistent and lightweight. Using <use> elements, I can insert the same symbol multiple times, at different positions or sizes, and even in different SVGs.
Each <symbol> must have its own viewBox, which defines its internal coordinate system. That means paying special attention to how SVG elements are exported from apps like Sketch.
I wrote before about how I export elements in layers to make working with them easier. That process is a little different when creating symbols.

Ordinarily, I would export all my elements using the same viewBoxsize. But when I’m creating a symbol, I need it to have its own specific viewBox.

So I export each element as an individually sized SVG, which gives me the dimensions I need to convert its content into a symbol. Let’s take the SVG of Quick Draw McGraw’s hat, which has a viewBox size of 294×182:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 294 182">
<!-- ... -->
</svg>
I swap the SVG tags for <symbol> and add its artwork to my SVG library:
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="quick-draw-hat" viewBox="0 0 294 182">
<g class="quick-draw-hat">[…]</g>
</symbol>
</svg>
Then, I repeat the process for all the remaining elements in my artwork. Now, if I ever need to update any of my symbols, the changes will be automatically applied to every instance it’s used.
Using A<symbol> In Multiple SVGs
I wanted my elements to appear in both versions of Bow Wow Bandit, one arrangement for smaller screens and an alternative arrangement for larger ones. So, I create both SVGs:
<svg class="svg-small" viewBox="0 0 1080 1440">
<!-- ... -->
</svg>
<svg class="svg-large" viewBox="0 0 1920 1080">
<!-- ... -->
</svg>
…and insert links to my symbols in both:
<svg class="svg-small" viewBox="0 0 1080 1440">
<use href="#quick-draw-hat" />
</svg>
<svg class="svg-large" viewBox="0 0 1920 1080">
<use href="#quick-draw-hat" />
</svg>
Positioning Symbols
Once I’ve placed symbols into my layout using <use>, my next step is to position them, which is especially important if I want alternative layouts for different screen sizes. Symbols behave like <g> groups, so I can scale and move them using attributes like width, height, and transform:
<svg class="svg-small" viewBox="0 0 1080 1440">
<use href="#quick-draw-hat" width="294" height="182" transform="translate(-30,610)"/>
</svg>
<svg class="svg-large" viewBox="0 0 1920 1080">
<use href="#quick-draw-hat" width="294" height="182" transform="translate(350,270)"/>
</svg>
I can place each <use> element independently using transform. This is powerful because rather than repositioning elements inside my SVGs, I move the <use> references. My internal layout stays clean, and the file size remains small because I’m not duplicating artwork. A browser only loads it once, which reduces bandwidth and speeds up page rendering. And because I’m always referencing the same symbol, their appearance stays consistent, whatever the screen size.
<use> Elements
Here’s where things got tricky. I wanted to animate parts of my characters — like Quick Draw’s hat tilting and his legs kicking. But when I added CSS animations targeting internal elements inside a <symbol>, nothing happened.
Tip: You can animate the <use> element itself, but not elements inside the <symbol>. If you want individual parts to move, make them their own symbols and animate each <use>.
Turns out, you can’t style or animate a <symbol>, because <use> creates shadow DOM clones that aren’t easily targetable. So, I had to get sneaky. Inside each <symbol> in my library SVG, I added a <g> element around the part I wanted to animate:
<symbol id="quick-draw-hat" viewBox="0 0 294 182">
<g class="quick-draw-hat">
<!-- ... -->
</g>
</symbol>
…and animated it using an attribute substring selector, targeting the href attribute of the use element:
use[href="#quick-draw-hat"] {
animation-delay: 0.5s;
animation-direction: alternate;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-name: hat-rock;
animation-timing-function: ease-in-out;
transform-origin: center bottom;
}
@keyframes hat-rock {
from { transform: rotate(-2deg); }
to { transform: rotate(2deg); } }
Media Queries For Display Control
Once I’ve created my two visible SVGs — one for small screens and one for larger ones — the final step is deciding which version to show at which screen size. I use CSS Media Queries to hide one SVG and show the other. I start by showing the small-screen SVG by default:
.svg-small { display: block; }
.svg-large { display: none; }
Then I use a min-width media query to switch to the large-screen SVG at 64rem and above:
@media (min-width: 64rem) {
.svg-small { display: none; }
.svg-large { display: block; }
}
This ensures there’s only ever one SVG visible at a time, keeping my layout simple and the DOM free from unnecessary clutter. And because both visible SVGs reference the same hidden <symbol> library, the browser only downloads the artwork once, regardless of how many <use> elements appear across the two layouts.

By combining <symbol>, <use>, CSS Media Queries, and specific transforms, I can build adaptive SVGs that reposition their elements without duplicating content, loading extra assets, or relying on JavaScript. I need to define each graphic only once in a hidden symbol library. Then I can reuse those graphics, as needed, inside several visible SVGs. With CSS doing the layout switching, the result is fast and flexible.
It’s a reminder that some of the most powerful techniques on the web don’t need big frameworks or complex tooling — just a bit of SVG know-how and a clever use of the basics.
by Andy Clarke (hello@smashingmagazine.com) at October 06, 2025 01:00 PM
1、
假期前最后一天(9月30日),热闹非凡。
上午,Anthropic 公司发布了 Claude Sonnet 4.5 模型。
下午,智谱公司发布了 GLM 4.6 模型。

我觉得,对于程序员,这个动态很重要。
因为这两个模型都属于目前最先进的 AI 编程模型。你想让 AI 生成代码,首选就是它们。
这就是说,一天之内,AI 编程模型又达到了新高度。
2、
Anthropic 发布公告的第一句话,就毫不谦虚地用了三个"世界之最"。

"Claude Sonnet 4.5 是世界上最好的编码模型。它是构建复杂代理的最强大模型。它是使用计算机的最佳模型。它在推理和数学方面表现出显著的进步。"
智谱的发布公告也是当仁不让。
"我们再次突破大模型的能力边界。
GLM-4.6是我们最强的代码 Coding 模型(较 GLM-4.5 提升27%)。在真实编程、长上下文处理、推理能力、信息搜索、写作能力与智能体应用等多个方面实现全面提升。"
为了让人信服,智谱的发布公告还给出了详细的测试结果。

上图一共是8个测试基准的结果图。每个图的蓝柱是 GLM-4.6,绿柱是 GLM-4.5。对照组是前两天刚发布的 DeepSeek V3.2 Exp、Claude sonnet 4、Claude sonnet 4.5。
可以看到,蓝柱基本上都是排名前列,甚至第一。智谱还声称,GLM-4.6 非常节省 Token(也就是省钱),"比 GLM-4.5 节省30%以上,为同类模型最低"。
所以,它的结论就是:"GLM-4.6 在部分榜单表现对齐 Claude Sonnet 4/Claude Sonnet 4.5,稳居国产模型首位。"
这就有意思了,一个自称"世界上最好的编码模型",另一个自称"稳居国产模型首位"。
下面,我来测试,GLM-4.6 相比 Claude sonnet 4.5 到底怎么样。
3、
需要说明的是,这两个模型的比较,不完全是为了测试,也有实际的意义。
Anthropic 公司虽然产品很强,但是它限制中国人使用,国内用户正常途径无法开通它的服务。另一方面,它是付费模型,价格也不便宜,百万 token 的输入输出价格是3美元/15美元。
形成鲜明对照的是,GLM-4.6 是完完全全的国产模型,来自北京智谱公司。它采取彻底的开源路线(MIT 许可证),模型代码完全公开,可以任意使用。
你要想自己在家里安装,也是可以的。但是,它的硬件要求太高,家用设备达不到,所以,一般都使用它的云服务。
目前,智谱的官网(BigModel 和 Z.ai),通过 Web 界面使用 GLM-4.6 是免费的。

它的 API 调用需要付费,入门套餐(coding plan)好像是一个月20元人民币。
另外,它有完备的中文支持(文档+客服),这也是 Anthropic 没有的。
总之,我的测试目的,也是想看看,它是不是真如官方宣称的那样强大,能不能替代 Claude Sonnet 模型。
4、
我的测试方法很简单。Anthropic 公司事先邀请了著名程序员西蒙·威利森(Simon Willison),试用 Claude Sonnet 4.5 模型。
西蒙·威利森已经在他的网站上,公布了试用结果。

我就拿他的几个测试,用在 GLM-4.6 上面,然后比较一下运行结果就可以了。
大家可以跟着一起做,打开官网,把题目粘贴进去(最好贴英文),这样会有更深切的感受。
AI 终端工具(比如 Claude Code、Cline、OpenCode、Crush 等)也可以用,参考官方文档进行设置(需要先开通 API)。
5、
第一个测试。
拉取代码仓库 https://github.com/simonw/llm ,然后通过下面的命令运行测试用例。
pip install -e '.[test]'
pytest
这个测试需要联网获取代码,然后在后台运行。
智谱官网的 Web 界面跟 Claude 一样,提供 Python 和 Node.js 的服务器沙箱环境,可以生成后直接执行代码。
我省略它中间的推理步骤了,最后结果如下图(官网查看完整对话)。

278个测试用例通过,耗时 18.31s
整个运行过程(拉取、安装依赖、执行命令)跟 Claude Sonnet 是一样的。奇怪的是,Claude Sonnet 运行了466个测试用例,多出来100多个,不知道为什么。
6、
第二个测试是较复杂的编程任务,原始提示是英文,我翻译成中文。
1、 代码仓库 https://github.com/simonw/llm 是一个 AI 对话应用,它将用户的提示和 AI 的响应存储在 SQLite 数据库中。
2、它目前使用线性集合,保存单个对话和响应。你尝试在响应表中添加一个 parentresponseid 列,并通过该列将对话的响应建模为树状结构。
3、编写新的 pytest 测试用例,验证你的设计。
4、编写一个 tree_notes.md 文件,首先将你的设计写入该文件,然后在运行过程中将该文件用作笔记。
大家可以查看完整的对话记录。
GLM-4.6 运行了几分钟,不停地吐出生成的代码。最终,它修改了脚本,增加了 API 和命令行调用接口,并编写和运行通过了测试用例。

它还生成了一个 tree_notes.md 文件,里面是本次修改的详细说明。

大家可以比较它的运行结果与 Claude Sonnet 的运行结果。
从结果上看,它们的差异不大,都做到了提示的要求,并且代码都是可运行的。差异主要是实现细节,这个就需要详细阅读代码了。
7、
第三个测试是西蒙·威利森独家的,就是让 AI 生成一个鹈鹕骑自行车的 SVG 图片(Generate an SVG of a pelican riding a bicycle)。
这是现实中不存在、且没有参考物的景象,考察模型的想象和生成能力。
下面是 GLM-4.6 打开深度思考后生成的图片。

下面是 Claude sonnet 4.5 打开深度思考后生成的图片。

两者的结果相当接近,只是 Claude 生成的鸟喙更明显,更能看出是一只鹈鹕。
8、
测试就到这里,我觉得总结来说,GLM-4.6 是一个非常强的国产模型,编码能力确实很优秀,可以当作目前公认的最强模型 Claude Sonnet 的替代品。
它的功能全面,除了编码,其他任务也能完成,而且响应速度快,价格低,性价比非常突出。
(完)
In Part 1 of this series, we explored the “lopsided horse” problem born from mockup-centric design and demonstrated how the seductive promise of vibe coding often leads to structural flaws. The main question remains:
How might we close the gap between our design intent and a live prototype, so that we can iterate on real functionality from day one, without getting caught in the ambiguity trap?
In other words, we need a way to build prototypes that are both fast to create and founded on a clear, unambiguous blueprint.
The answer is a more disciplined process I call Intent Prototyping (kudos to Marco Kotrotsos, who coined Intent-Oriented Programming). This method embraces the power of AI-assisted coding but rejects ambiguity, putting the designer’s explicit intent at the very center of the process. It receives a holistic expression of intent (sketches for screen layouts, conceptual model description, boxes-and-arrows for user flows) and uses it to generate a live, testable prototype.

This method solves the concerns we’ve discussed in Part 1 in the best way possible:
This combination makes the method especially suited for designing complex enterprise applications. It allows us to test the system’s most critical point of failure, its underlying structure, at a speed and flexibility that was previously impossible. Furthermore, the process is built for iteration. You can explore as many directions as you want simply by changing the intent and evolving the design based on what you learn from user testing.
My WorkflowTo illustrate this process in action, let’s walk through a case study. It’s the very same example I’ve used to illustrate the vibe coding trap: a simple tool to track tests to validate product ideas. You can find the complete project, including all the source code and documentation files discussed below, in this GitHub repository.
Imagine we’ve already done proper research, and having mused on the defined problem, I begin to form a vague idea of what the solution might look like. I need to capture this idea immediately, so I quickly sketch it out:

In this example, I used Excalidraw, but the tool doesn’t really matter. Note that we deliberately keep it rough, as visual details are not something we need to focus on at this stage. And we are not going to be stuck here: we want to make a leap from this initial sketch directly to a live prototype that we can put in front of potential users. Polishing those sketches would not bring us any closer to achieving our goal.
What we need to move forward is to add to those sketches just enough details so that they may serve as a sufficient input for a junior frontend developer (or, in our case, an AI assistant). This requires explaining the following:
Having added all those details, we end up with such an annotated sketch:

As you see, this sketch covers both the Visualization and Flow aspects. You may ask, what about the Conceptual Model? Without that part, the expression of our intent will not be complete. One way would be to add it somewhere in the margins of the sketch (for example, as a UML Class Diagram), and I would do so in the case of a more complex application, where the model cannot be simply derived from the UI. But in our case, we can save effort and ask an LLM to generate a comprehensive description of the conceptual model based on the sketch.
For tasks of this sort, the LLM of my choice is Gemini 2.5 Pro. What is important is that this is a multimodal model that can accept not only text but also images as input (GPT-5 and Claude-4 also fit that criteria). I use Google AI Studio, as it gives me enough control and visibility into what’s happening:

Note: All the prompts that I use here and below can be found in the Appendices. The prompts are not custom-tailored to any particular project; they are supposed to be reused as they are.
As a result, Gemini gives us a description and the following diagram:

The diagram might look technical, but I believe that a clear understanding of all objects, their attributes, and relationships between them is key to good design. That’s why I consider the Conceptual Model to be an essential part of expressing intent, along with the Flow and Visualization.
As a result of this step, our intent is fully expressed in two files: Sketch.png and Model.md. This will be our durable source of truth.
The purpose of this step is to create a comprehensive technical specification and a step-by-step plan. Most of the work here is done by AI; you just need to keep an eye on it.
I separate the Data Access Layer and the UI layer, and create specifications for them using two different prompts (see Appendices 2 and 3). The output of the first prompt (the Data Access Layer spec) serves as an input for the second one. Note that, as an additional input, we give the guidelines tailored for prototyping needs (see Appendices 8, 9, and 10). They are not specific to this project. The technical approach encoded in those guidelines is out of the scope of this article.
As a result, Gemini provides us with content for DAL.md and UI.md. Although in most cases this result is quite reliable enough, you might want to scrutinize the output. You don’t need to be a real programmer to make sense of it, but some level of programming literacy would be really helpful. However, even if you don’t have such skills, don’t get discouraged. The good news is that if you don’t understand something, you always know who to ask. Do it in Google AI Studio before refreshing the context window. If you believe you’ve spotted a problem, let Gemini know, and it will either fix it or explain why the suggested approach is actually better.
It’s important to remember that by their nature, LLMs are not deterministic and, to put it simply, can be forgetful about small details, especially when it comes to details in sketches. Fortunately, you don’t have to be an expert to notice that the “Delete” button, which is in the upper right corner of the sketch, is not mentioned in the spec.
Don’t get me wrong: Gemini does a stellar job most of the time, but there are still times when it slips up. Just let it know about the problems you’ve spotted, and everything will be fixed.
Once we have Sketch.png, Model.md, DAL.md, UI.md, and we have reviewed the specs, we can grab a coffee. We deserve it: our technical design documentation is complete. It will serve as a stable foundation for building the actual thing, without deviating from our original intent, and ensuring that all components fit together perfectly, and all layers are stacked correctly.
One last thing we can do before moving on to the next steps is to prepare a step-by-step plan. We split that plan into two parts: one for the Data Access Layer and another for the UI. You can find prompts I use to create such a plan in Appendices 4 and 5.
To start building the actual thing, we need to switch to another category of AI tools. Up until this point, we have relied on Generative AI. It excels at creating new content (in our case, specifications and plans) based on a single prompt. I’m using Google Gemini 2.5 Pro in Google AI Studio, but other similar tools may also fit such one-off tasks: ChatGPT, Claude, Grok, and DeepSeek.
However, at this step, this wouldn’t be enough. Building a prototype based on specs and according to a plan requires an AI that can read context from multiple files, execute a sequence of tasks, and maintain coherence. A simple generative AI can’t do this. It would be like asking a person to build a house by only ever showing them a single brick. What we need is an agentic AI that can be given the full house blueprint and a project plan, and then get to work building the foundation, framing the walls, and adding the roof in the correct sequence.
My coding agent of choice is Google Gemini CLI, simply because Gemini 2.5 Pro serves me well, and I don’t think we need any middleman like Cursor or Windsurf (which would use Claude, Gemini, or GPT under the hood anyway). If I used Claude, my choice would be Claude Code, but since I’m sticking with Gemini, Gemini CLI it is. But if you prefer Cursor or Windsurf, I believe you can apply the same process with your favourite tool.
Before tasking the agent, we need to create a basic template for our React application. I won’t go into this here. You can find plenty of tutorials on how to scaffold an empty React project using Vite.
Then we put all our files into that project:

Once the basic template with all our files is ready, we open Terminal, go to the folder where our project resides, and type “gemini”:

And we send the prompt to build the Data Access Layer (see Appendix 6). That prompt implies step-by-step execution, so upon completion of each step, I send the following:
Thank you! Now, please move to the next task.
Remember that you must not make assumptions based on common patterns; always verify them with the actual data from the spec.
After each task, stop so that I can test it. Don’t move to the next task before I tell you to do so.
As the last task in the plan, the agent builds a special page where we can test all the capabilities of our Data Access Layer, so that we can manually test it. It may look like this:

It doesn’t look fancy, to say the least, but it allows us to ensure that the Data Access Layer works correctly before we proceed with building the final UI.
And finally, we clear the Gemini CLI context window to give it more headspace and send the prompt to build the UI (see Appendix 7). This prompt also implies step-by-step execution. Upon completion of each step, we test how it works and how it looks, following the “Manual Testing Plan” from UI-plan.md. I have to say that despite the fact that the sketch has been uploaded to the model context and, in general, Gemini tries to follow it, attention to visual detail is not one of its strengths (yet). Usually, a few additional nudges are needed at each step to improve the look and feel:

Once I’m happy with the result of a step, I ask Gemini to move on:
Thank you! Now, please move to the next task.
Make sure you build the UI according to the sketch; this is very important. Remember that you must not make assumptions based on common patterns; always verify them with the actual data from the spec and the sketch.
After each task, stop so that I can test it. Don’t move to the next task before I tell you to do so.
Before long, the result looks like this, and in every detail it works exactly as we intended:

The prototype is up and running and looking nice. Does it mean that we are done with our work? Surely not, the most fascinating part is just beginning.
It’s time to put the prototype in front of potential users and learn more about whether this solution relieves their pain or not.
And as soon as we learn something new, we iterate. We adjust or extend the sketches and the conceptual model, based on that new input, we update the specifications, create plans to make changes according to the new specifications, and execute those plans. In other words, for every iteration, we repeat the steps I’ve just walked you through.
This four-step workflow may create an impression of a somewhat heavy process that requires too much thinking upfront and doesn’t really facilitate creativity. But before jumping to that conclusion, consider the following:
There is no method that fits all situations, and Intent Prototyping is not an exception. Like any specialized tool, it has a specific purpose. The most effective teams are not those who master a single method, but those who understand which approach to use to mitigate the most significant risk at each stage. The table below gives you a way to make this choice clearer. It puts Intent Prototyping next to other common methods and tools and explains each one in terms of the primary goal it helps achieve and the specific risks it is best suited to mitigate.
| Method/Tool | Goal | Risks it is best suited to mitigate | Examples | Why |
|---|---|---|---|---|
| Intent Prototyping | To rapidly iterate on the fundamental architecture of a data-heavy application with a complex conceptual model, sophisticated business logic, and non-linear user flows. | Building a system with a flawed or incoherent conceptual model, leading to critical bugs and costly refactoring. |
|
It enforces conceptual clarity. This not only de-risks the core structure but also produces a clear, documented blueprint that serves as a superior specification for the engineering handoff. |
| Vibe Coding (Conversational) | To rapidly explore interactive ideas through improvisation. | Losing momentum because of analysis paralysis. |
|
It has the smallest loop between an idea conveyed in natural language and an interactive outcome. |
| Axure | To test complicated conditional logic within a specific user journey, without having to worry about how the whole system works. | Designing flows that break when users don’t follow the “happy path.” |
|
It’s made to create complex if-then logic and manage variables visually. This lets you test complicated paths and edge cases in a user journey without writing any code. |
| Figma | To make sure that the user interface looks good, aligns with the brand, and has a clear information architecture. | Making a product that looks bad, doesn't fit with the brand, or has a layout that is hard to understand. |
|
It excels at high-fidelity visual design and provides simple, fast tools for linking static screens. |
| ProtoPie, Framer | To make high-fidelity micro-interactions feel just right. | Shipping an application that feels cumbersome and unpleasant to use because of poorly executed interactions. |
|
These tools let you manipulate animation timelines, physics, and device sensor inputs in great detail. Designers can carefully work on and test the small things that make an interface feel really polished and fun to use. |
| Low-code / No-code Tools (e.g., Bubble, Retool) | To create a working, data-driven app as quickly as possible. | The application will never be built because traditional development is too expensive. |
|
They put a UI builder, a database, and hosting all in one place. The goal is not merely to make a prototype of an idea, but to make and release an actual, working product. This is the last step for many internal tools or MVPs. |
The key takeaway is that each method is a specialized tool for mitigating a specific type of risk. For example, Figma de-risks the visual presentation. ProtoPie de-risks the feel of an interaction. Intent Prototyping is in a unique position to tackle the most foundational risk in complex applications: building on a flawed or incoherent conceptual model.
Bringing It All TogetherThe era of the “lopsided horse” design, sleek on the surface but structurally unsound, is a direct result of the trade-off between fidelity and flexibility. This trade-off has led to a process filled with redundant effort and misplaced focus. Intent Prototyping, powered by modern AI, eliminates that conflict. It’s not just a shortcut to building faster — it’s a fundamental shift in how we design. By putting a clear, unambiguous intent at the heart of the process, it lets us get rid of the redundant work and focus on architecting a sound and robust system.
There are three major benefits to this renewed focus. First, by going straight to live, interactive prototypes, we shift our validation efforts from the surface to the deep, testing the system’s actual logic with users from day one. Second, the very act of documenting the design intent makes us clear about our ideas, ensuring that we fully understand the system’s underlying logic. Finally, this documented intent becomes a durable source of truth, eliminating the ambiguous handoffs and the redundant, error-prone work of having engineers reverse-engineer a designer’s vision from a black box.
Ultimately, Intent Prototyping changes the object of our work. It allows us to move beyond creating pictures of a product and empowers us to become architects of blueprints for a system. With the help of AI, we can finally make the live prototype the primary canvas for ideation, not just a high-effort afterthought.
You can find the full Intent Prototyping Starter Kit, which includes all those prompts and guidelines, as well as the example from this article and a minimal boilerplate project, in this GitHub repository.
You are an expert Senior Software Architect specializing in Domain-Driven Design. You are tasked with defining a conceptual model for an app based on information from a UI sketch.
## Workflow
Follow these steps precisely:
**Step 1:** Analyze the sketch carefully. There should be no ambiguity about what we are building.
**Step 2:** Generate the conceptual model description in the Mermaid format using a UML class diagram.
## Ground Rules
- Every entity must have the following attributes:
- id (string)
- createdAt (string, ISO 8601 format)
- updatedAt (string, ISO 8601 format)
- Include all attributes shown in the UI: If a piece of data is visually represented as a field for an entity, include it in the model, even if it's calculated from other attributes.
- Do not add any speculative entities, attributes, or relationships ("just in case"). The model should serve the current sketch's requirements only.
- Pay special attention to cardinality definitions (e.g., if a relationship is optional on both sides, it cannot be "1" -- "0..*", it must be "0..1" -- "0..*").
- Use only valid syntax in the Mermaid diagram.
- Do not include enumerations in the Mermaid diagram.
- Add comments explaining the purpose of every entity, attribute, and relationship, and their expected behavior (not as a part of the diagram, in the Markdown file).
## Naming Conventions
- Names should reveal intent and purpose.
- Use PascalCase for entity names.
- Use camelCase for attributes and relationships.
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
## Final Instructions
- **No Assumptions: Base every detail on visual evidence in the sketch, not on common design patterns.
- **Double-Check: After composing the entire document, read through it to ensure the hierarchy is logical, the descriptions are unambiguous, and the formatting is consistent. The final document should be a self-contained, comprehensive specification.
- **Do not add redundant empty lines between items.**
Your final output should be the complete, raw markdown content for Model.md.
You are an expert Senior Frontend Developer specializing in React, TypeScript, and Zustand. You are tasked with creating a comprehensive technical specification for the development team in a structured markdown document, based on a UI sketch and a conceptual model description.
## Workflow
Follow these steps precisely:
**Step 1:** Analyze the documentation carefully:
- Model.md: the conceptual model
- Sketch.png: the UI sketch
There should be no ambiguity about what we are building.
**Step 2:** Check out the guidelines:
- TS-guidelines.md: TypeScript Best Practices
- React-guidelines.md: React Best Practices
- Zustand-guidelines.md: Zustand Best Practices
**Step 3:** Create a Markdown specification for the stores and entity-specific hook that implements all the logic and provides all required operations.
---
## Markdown Output Structure
Use this template for the entire document.
markdown
# Data Access Layer Specification
This document outlines the specification for the data access layer of the application, following the principles defined in `docs/guidelines/Zustand-guidelines.md`.
## 1. Type Definitions
Location: `src/types/entities.ts`
### 1.1. `BaseEntity`
A shared interface that all entities should extend.
[TypeScript interface definition]
### 1.2. `[Entity Name]`
The interface for the [Entity Name] entity.
[TypeScript interface definition]
## 2. Zustand Stores
### 2.1. Store for `[Entity Name]`
**Location:** `src/stores/[Entity Name (plural)].ts`
The Zustand store will manage the state of all [Entity Name] items.
**Store State (`[Entity Name]State`):**
[TypeScript interface definition]
**Store Implementation (`use[Entity Name]Store`):**
- The store will be created using `create<[Entity Name]State>()(...)`.
- It will use the `persist` middleware from `zustand/middleware` to save state to `localStorage`. The persistence key will be `[entity-storage-key]`.
- `[Entity Name (plural, camelCase)]` will be a dictionary (`Record<string, [Entity]>`) for O(1) access.
**Actions:**
- **`add[Entity Name]`**:
[Define the operation behavior based on entity requirements]
- **`update[Entity Name]`**:
[Define the operation behavior based on entity requirements]
- **`remove[Entity Name]`**:
[Define the operation behavior based on entity requirements]
- **`doSomethingElseWith[Entity Name]`**:
[Define the operation behavior based on entity requirements]
## 3. Custom Hooks
### 3.1. `use[Entity Name (plural)]`
**Location:** `src/hooks/use[Entity Name (plural)].ts`
The hook will be the primary interface for UI components to interact with [Entity Name] data.
**Hook Return Value:**
[TypeScript interface definition]
**Hook Implementation:**
[List all properties and methods returned by this hook, and briefly explain the logic behind them, including data transformations, memoization. Do not write the actual code here.]
---
## Final Instructions
- **No Assumptions:** Base every detail in the specification on the conceptual model or visual evidence in the sketch, not on common design patterns.
- **Double-Check:** After composing the entire document, read through it to ensure the hierarchy is logical, the descriptions are unambiguous, and the formatting is consistent. The final document should be a self-contained, comprehensive specification.
- **Do not add redundant empty lines between items.**
Your final output should be the complete, raw markdown content for DAL.md.
You are an expert Senior Frontend Developer specializing in React, TypeScript, and the Ant Design library. You are tasked with creating a comprehensive technical specification by translating a UI sketch into a structured markdown document for the development team.
## Workflow
Follow these steps precisely:
**Step 1:** Analyze the documentation carefully:
- Sketch.png: the UI sketch
- Note that red lines, red arrows, and red text within the sketch are annotations for you and should not be part of the final UI design. They provide hints and clarification. Never translate them to UI elements directly.
- Model.md: the conceptual model
- DAL.md: the Data Access Layer spec
There should be no ambiguity about what we are building.
**Step 2:** Check out the guidelines:
- TS-guidelines.md: TypeScript Best Practices
- React-guidelines.md: React Best Practices
**Step 3:** Generate the complete markdown content for a new file, UI.md.
---
## Markdown Output Structure
Use this template for the entire document.
markdown
# UI Layer Specification
This document specifies the UI layer of the application, breaking it down into pages and reusable components based on the provided sketches. All components will adhere to Ant Design's principles and utilize the data access patterns defined in `docs/guidelines/Zustand-guidelines.md`.
## 1. High-Level Structure
The application is a single-page application (SPA). It will be composed of a main layout, one primary page, and several reusable components.
### 1.1. `App` Component
The root component that sets up routing and global providers.
- **Location**: `src/App.tsx`
- **Purpose**: To provide global context, including Ant Design's `ConfigProvider` and `App` contexts for message notifications, and to render the main page.
- **Composition**:
- Wraps the application with `ConfigProvider` and `App as AntApp` from 'antd' to enable global message notifications as per `simple-ice/antd-messages.mdc`.
- Renders `[Page Name]`.
## 2. Pages
### 2.1. `[Page Name]`
- **Location:** `src/pages/PageName.tsx`
- **Purpose:** [Briefly describe the main goal and function of this page]
- **Data Access:**
[List the specific hooks and functions this component uses to fetch or manage its data]
- **Internal State:**
[Describe any state managed internally by this page using `useState`]
- **Composition:**
[Briefly describe the content of this page]
- **User Interactions:**
[Describe how the user interacts with this page]
- **Logic:**
[If applicable, provide additional comments on how this page should work]
## 3. Components
### 3.1. `[Component Name]`
- **Location:** `src/components/ComponentName.tsx`
- **Purpose:** [Explain what this component does and where it's used]
- **Props:**
[TypeScript interface definition for the component's props. Props should be minimal. Avoid prop drilling by using hooks for data access.]
- **Data Access:**
[List the specific hooks and functions this component uses to fetch or manage its data]
- **Internal State:**
[Describe any state managed internally by this component using `useState`]
- **Composition:**
[Briefly describe the content of this component]
- **User Interactions:**
[Describe how the user interacts with the component]
- **Logic:**
[If applicable, provide additional comments on how this component should work]
---
## Final Instructions
- **No Assumptions:** Base every detail on the visual evidence in the sketch, not on common design patterns.
- **Double-Check:** After composing the entire document, read through it to ensure the hierarchy is logical, the descriptions are unambiguous, and the formatting is consistent. The final document should be a self-contained, comprehensive specification.
- **Do not add redundant empty lines between items.**
Your final output should be the complete, raw markdown content for UI.md.
You are an expert Senior Frontend Developer specializing in React, TypeScript, and Zustand. You are tasked with creating a plan to build a Data Access Layer for an application based on a spec.
## Workflow
Follow these steps precisely:
**Step 1:** Analyze the documentation carefully:
- DAL.md: The full technical specification for the Data Access Layer of the application. Follow it carefully and to the letter.
There should be no ambiguity about what we are building.
**Step 2:** Check out the guidelines:
- TS-guidelines.md: TypeScript Best Practices
- React-guidelines.md: React Best Practices
- Zustand-guidelines.md: Zustand Best Practices
**Step 3:** Create a step-by-step plan to build a Data Access Layer according to the spec.
Each task should:
- Focus on one concern
- Be reasonably small
- Have a clear start + end
- Contain clearly defined Objectives and Acceptance Criteria
The last step of the plan should include creating a page to test all the capabilities of our Data Access Layer, and making it the start page of this application, so that I can manually check if it works properly.
I will hand this plan over to an engineering LLM that will be told to complete one task at a time, allowing me to review results in between.
## Final Instructions
- Note that we are not starting from scratch; the basic template has already been created using Vite.
- Do not add redundant empty lines between items.
Your final output should be the complete, raw markdown content for DAL-plan.md.
You are an expert Senior Frontend Developer specializing in React, TypeScript, and the Ant Design library. You are tasked with creating a plan to build a UI layer for an application based on a spec and a sketch.
## Workflow
Follow these steps precisely:
**Step 1:** Analyze the documentation carefully:
- UI.md: The full technical specification for the UI layer of the application. Follow it carefully and to the letter.
- Sketch.png: Contains important information about the layout and style, complements the UI Layer Specification. The final UI must be as close to this sketch as possible.
There should be no ambiguity about what we are building.
**Step 2:** Check out the guidelines:
- TS-guidelines.md: TypeScript Best Practices
- React-guidelines.md: React Best Practices
**Step 3:** Create a step-by-step plan to build a UI layer according to the spec and the sketch.
Each task must:
- Focus on one concern.
- Be reasonably small.
- Have a clear start + end.
- Result in a verifiable increment of the application. Each increment should be manually testable to allow for functional review and approval before proceeding.
- Contain clearly defined Objectives, Acceptance Criteria, and Manual Testing Plan.
I will hand this plan over to an engineering LLM that will be told to complete one task at a time, allowing me to test in between.
## Final Instructions
- Note that we are not starting from scratch, the basic template has already been created using Vite, and the Data Access Layer has been built successfully.
- For every task, describe how components should be integrated for verification. You must use the provided hooks to connect to the live Zustand store data—do not use mock data (note that the Data Access Layer has been already built successfully).
- The Manual Testing Plan should read like a user guide. It must only contain actions a user can perform in the browser and must never reference any code files or programming tasks.
- Do not add redundant empty lines between items.
Your final output should be the complete, raw markdown content for UI-plan.md.
You are an expert Senior Frontend Developer specializing in React, TypeScript, and Zustand. You are tasked with building a Data Access Layer for an application based on a spec.
## Workflow
Follow these steps precisely:
**Step 1:** Analyze the documentation carefully:
- @docs/specs/DAL.md: The full technical specification for the Data Access Layer of the application. Follow it carefully and to the letter.
There should be no ambiguity about what we are building.
**Step 2:** Check out the guidelines:
- @docs/guidelines/TS-guidelines.md: TypeScript Best Practices
- @docs/guidelines/React-guidelines.md: React Best Practices
- @docs/guidelines/Zustand-guidelines.md: Zustand Best Practices
**Step 3:** Read the plan:
- @docs/plans/DAL-plan.md: The step-by-step plan to build the Data Access Layer of the application.
**Step 4:** Build a Data Access Layer for this application according to the spec and following the plan.
- Complete one task from the plan at a time.
- After each task, stop, so that I can test it. Don’t move to the next task before I tell you to do so.
- Do not do anything else. At this point, we are focused on building the Data Access Layer.
## Final Instructions
- Do not make assumptions based on common patterns; always verify them with the actual data from the spec and the sketch.
- Do not start the development server, I'll do it by myself.
You are an expert Senior Frontend Developer specializing in React, TypeScript, and the Ant Design library. You are tasked with building a UI layer for an application based on a spec and a sketch.
## Workflow
Follow these steps precisely:
**Step 1:** Analyze the documentation carefully:
- @docs/specs/UI.md: The full technical specification for the UI layer of the application. Follow it carefully and to the letter.
- @docs/intent/Sketch.png: Contains important information about the layout and style, complements the UI Layer Specification. The final UI must be as close to this sketch as possible.
- @docs/specs/DAL.md: The full technical specification for the Data Access Layer of the application. That layer is already ready. Use this spec to understand how to work with it.
There should be no ambiguity about what we are building.
**Step 2:** Check out the guidelines:
- @docs/guidelines/TS-guidelines.md: TypeScript Best Practices
- @docs/guidelines/React-guidelines.md: React Best Practices
**Step 3:** Read the plan:
- @docs/plans/UI-plan.md: The step-by-step plan to build the UI layer of the application.
**Step 4:** Build a UI layer for this application according to the spec and the sketch, following the step-by-step plan:
- Complete one task from the plan at a time.
- Make sure you build the UI according to the sketch; this is very important.
- After each task, stop, so that I can test it. Don’t move to the next task before I tell you to do so.
## Final Instructions
- Do not make assumptions based on common patterns; always verify them with the actual data from the spec and the sketch.
- Follow Ant Design's default styles and components.
- Do not touch the data access layer: it's ready and it's perfect.
- Do not start the development server, I'll do it by myself.
# Guidelines: TypeScript Best Practices
## Type System & Type Safety
- Use TypeScript for all code and enable strict mode.
- Ensure complete type safety throughout stores, hooks, and component interfaces.
- Prefer interfaces over types for object definitions; use types for unions, intersections, and mapped types.
- Entity interfaces should extend common patterns while maintaining their specific properties.
- Use TypeScript type guards in filtering operations for relationship safety.
- Avoid the 'any' type; prefer 'unknown' when necessary.
- Use generics to create reusable components and functions.
- Utilize TypeScript's features to enforce type safety.
- Use type-only imports (import type { MyType } from './types') when importing types, because verbatimModuleSyntax is enabled.
- Avoid enums; use maps instead.
## Naming Conventions
- Names should reveal intent and purpose.
- Use PascalCase for component names and types/interfaces.
- Prefix interfaces for React props with 'Props' (e.g., ButtonProps).
- Use camelCase for variables and functions.
- Use UPPER_CASE for constants.
- Use lowercase with dashes for directories, and PascalCase for files with components (e.g., components/auth-wizard/AuthForm.tsx).
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
- Favor named exports for components.
## Code Structure & Patterns
- Write concise, technical TypeScript code with accurate examples.
- Use functional and declarative programming patterns; avoid classes.
- Prefer iteration and modularization over code duplication.
- Use the "function" keyword for pure functions.
- Use curly braces for all conditionals for consistency and clarity.
- Structure files appropriately based on their purpose.
- Keep related code together and encapsulate implementation details.
## Performance & Error Handling
- Use immutable and efficient data structures and algorithms.
- Create custom error types for domain-specific errors.
- Use try-catch blocks with typed catch clauses.
- Handle Promise rejections and async errors properly.
- Log errors appropriately and handle edge cases gracefully.
## Project Organization
- Place shared types in a types directory.
- Use barrel exports (index.ts) for organizing exports.
- Structure files and directories based on their purpose.
## Other Rules
- Use comments to explain complex logic or non-obvious decisions.
- Follow the single responsibility principle: each function should do exactly one thing.
- Follow the DRY (Don't Repeat Yourself) principle.
- Do not implement placeholder functions, empty methods, or "just in case" logic. Code should serve the current specification's requirements only.
- Use 2 spaces for indentation (no tabs).
# Guidelines: React Best Practices
## Component Structure
- Use functional components over class components
- Keep components small and focused
- Extract reusable logic into custom hooks
- Use composition over inheritance
- Implement proper prop types with TypeScript
- Structure React files: exported component, subcomponents, helpers, static content, types
- Use declarative TSX for React components
- Ensure that UI components use custom hooks for data fetching and operations rather than receive data via props, except for simplest components
## React Patterns
- Utilize useState and useEffect hooks for state and side effects
- Use React.memo for performance optimization when needed
- Utilize React.lazy and Suspense for code-splitting
- Implement error boundaries for robust error handling
- Keep styles close to components
## React Performance
- Avoid unnecessary re-renders
- Lazy load components and images when possible
- Implement efficient state management
- Optimize rendering strategies
- Optimize network requests
- Employ memoization techniques (e.g., React.memo, useMemo, useCallback)
## React Project Structure
/src
- /components - UI components (every component in a separate file)
- /hooks - public-facing custom hooks (every hook in a separate file)
- /providers - React context providers (every provider in a separate file)
- /pages - page components (every page in a separate file)
- /stores - entity-specific Zustand stores (every store in a separate file)
- /styles - global styles (if needed)
- /types - shared TypeScript types and interfaces
# Guidelines: Zustand Best Practices
## Core Principles
- **Implement a data layer** for this React application following this specification carefully and to the letter.
- **Complete separation of concerns**: All data operations should be accessible in UI components through simple and clean entity-specific hooks, ensuring state management logic is fully separated from UI logic.
- **Shared state architecture**: Different UI components should work with the same shared state, despite using entity-specific hooks separately.
## Technology Stack
- **State management**: Use Zustand for state management with automatic localStorage persistence via the persist middleware.
## Store Architecture
- **Base entity:** Implement a BaseEntity interface with common properties that all entities extend:
typescript
export interface BaseEntity {
id: string;
createdAt: string; // ISO 8601 format
updatedAt: string; // ISO 8601 format
}
- **Entity-specific stores**: Create separate Zustand stores for each entity type.
- **Dictionary-based storage**: Use dictionary/map structures (Record<string, Entity>) rather than arrays for O(1) access by ID.
- **Handle relationships**: Implement cross-entity relationships (like cascade deletes) within the stores where appropriate.
## Hook Layer
The hook layer is the exclusive interface between UI components and the Zustand stores. It is designed to be simple, predictable, and follow a consistent pattern across all entities.
### Core Principles
1. **One Hook Per Entity**: There will be a single, comprehensive custom hook for each entity (e.g., useBlogPosts, useCategories). This hook is the sole entry point for all data and operations related to that entity. Separate hooks for single-item access will not be created.
2. **Return reactive data, not getter functions**: To prevent stale data, hooks must return the state itself, not a function that retrieves state. Parameterize hooks to accept filters and return the derived data directly. A component calling a getter function will not update when the underlying data changes.
3. **Expose Dictionaries for O(1) Access**: To provide simple and direct access to data, every hook will return a dictionary (Record<string, Entity>) of the relevant items.
### The Standard Hook Pattern
Every entity hook will follow this implementation pattern:
1. **Subscribe** to the entire dictionary of entities from the corresponding Zustand store. This ensures the hook is reactive to any change in the data.
2. **Filter** the data based on the parameters passed into the hook. This logic will be memoized with useMemo for efficiency. If no parameters are provided, the hook will operate on the entire dataset.
3. **Return a Consistent Shape**: The hook will always return an object containing:
* A **filtered and sorted array** (e.g., blogPosts) for rendering lists.
* A **filtered dictionary** (e.g., blogPostsDict) for convenient O(1) lookup within the component.
* All necessary **action functions** (add, update, remove) and **relationship operations**.
* All necessary **helper functions** and **derived data objects**. Helper functions are suitable for pure, stateless logic (e.g., calculators). Derived data objects are memoized values that provide aggregated or summarized information from the state (e.g., an object containing status counts). They must be derived directly from the reactive state to ensure they update automatically when the underlying data changes.
## API Design Standards
- **Object Parameters**: Use object parameters instead of multiple direct parameters for better extensibility:
typescript
// ✅ Preferred
add({ title, categoryIds })
// ❌ Avoid
add(title, categoryIds)
- **Internal Methods**: Use underscore-prefixed methods for cross-store operations to maintain clean separation.
## State Validation Standards
- **Existence checks**: All update and remove operations should validate entity existence before proceeding.
- **Relationship validation**: Verify both entities exist before establishing relationships between them.
## Error Handling Patterns
- **Operation failures**: Define behavior when operations fail (e.g., updating non-existent entities).
- **Graceful degradation**: How to handle missing related entities in helper functions.
## Other Standards
- **Secure ID generation**: Use crypto.randomUUID() for entity ID generation instead of custom implementations for better uniqueness guarantees and security.
- **Return type consistency**: add operations return generated IDs for component workflows requiring immediate entity access, while update and remove operations return void to maintain clean modification APIs.
by Yegor Gilyov (hello@smashingmagazine.com) at October 03, 2025 10:00 AM
As September comes to a close and October takes over, we are in the midst of a time of transition. The air in the morning feels crisper, the leaves are changing colors, and winding down with a warm cup of tea regains its almost-forgotten appeal after a busy summer. When we look closely, October is full of little moments that have the power to inspire, and whatever your secret to finding new inspiration might be, our monthly wallpapers series is bound to give you a little inspiration boost, too.
For this October edition, artists and designers from across the globe once again challenged their creative skills and designed wallpapers to spark your imagination. You find them compiled below, along with a selection of timeless October treasures from our wallpapers archives that are just too good to gather dust.
A huge thank you to everyone who shared their designs with us this month — this post wouldn’t exist without your creativity and kind support! Happy October!
Designed by Libra Fire from Serbia.
Designed by Ricardo Gimenes from Spain.
“I was inspired by the classic orange and purple colors of October and Halloween, and wanted to combine those two themes to create a fun pumpkin lantern background.” — Designed by Melissa Bostjancic from New Jersey, United States.
Designed by Ricardo Gimenes from Spain.
“In October, we observe World Mental Health Day. The open window in the head symbolizes light and fresh thoughts, the plant represents quiet inner growth and resilience, and the bird brings freedom and connection with the world. Together, they create an image of a mind that breathes, grows, and remains open to new beginnings.” — Designed by Ginger IT Solutions from Serbia.
“I took this photo while visiting an old factory. The red light was astonishing.” — Designed by Philippe Brouard from France.
“If my heart were a season, it would be autumn.” — Designed by Lívia Lénárt from Hungary.
Designed by Vlad Gerasimov from Georgia.
Designed by Xenia Latii from Germany.
“When I was young, I had a bird’s nest not so far from my room window. I watched the birds almost every day; because those swallows always left their nests in October. As a child, I dreamt that they all flew together to a nicer place, where they were not so cold.” — Designed by Eline Claeys from Belgium.
“The term ‘Hanlu’ literally translates as ‘Cold Dew.’ The cold dew brings brisk mornings and evenings. Eventually the briskness will turn cold, as winter is coming soon. And chrysanthemum is the iconic flower of Cold Dew.” — Designed by Hong, ZI-Qing from Taiwan.
“The transition to autumn brings forth a rich visual tapestry of warm colors and falling leaves, making it a natural choice for a wallpaper theme.” — Designed by Farhan Srambiyan from India.
Designed by Ricardo Gimenes from Spain.
“Did you know that squirrels don’t just eat nuts? They really like to eat fruit, too. Since apples are the seasonal fruit of October, I decided to combine both things into a beautiful image.” — Designed by Erin Troch from Belgium.
“Autumn is the best moment for discovering the universe. I am looking for a new galaxy or maybe… a UFO!” — Designed by Verónica Valenzuela from Spain.
Designed by Ricardo Gimenes from Spain.
“At the end of the kolodar, as everything begins to ripen, the village sets out to harvesting. Together with the farmers goes Makosh, the Goddess of fields and crops, ensuring a prosperous harvest. What she gave her life and health all year round is now mature and rich, thus, as a sign of gratitude, the girls bring her bread and wine. The beautiful game of the goddess makes the hard harvest easier, while the song of the farmer permeates the field.” — Designed by PopArt Studio from Serbia.
“October makes the leaves fall to cover the land with lovely auburn colors and brings out all types of weird with them.” — Designed by Mi Ni Studio from Serbia.
Designed by Amy Hamilton from Canada.
“To me, October is a transitional month. We gradually slide from summer to autumn. That’s why I chose to use a lot of gradients. I also wanted to work with simple shapes, because I think of October as the ‘back to nature/back to basics month’.” — Designed by Jelle Denturck from Belgium.
“Fall is my favorite season!” — Designed by Thuy Truong from the United States.
“The story is a mash-up of retro science fiction and zombie infection. What would happen if a Mars rover came into contact with an unknown Martian material and got infected with a virus? What if it reversed its intended purpose of research and exploration? Instead choosing a life of chaos and evil. What if they all ran rogue on Mars? Would humans ever dare to voyage to the red planet?” Designed by Frank Candamil from the United States.
Turtles In Space
“Finished September, with October comes the month of routines. This year we share it with turtles that explore space.” — Designed by Veronica Valenzuela from Spain.
“When I was little, my parents always took me and my sister for a walk at the beach in Nieuwpoort. We didn't really do those beach walks in the summer but always when the sky started to turn gray and the days became colder. My sister and I always took out our warmest scarfs and played in the sand while my parents walked behind us. I really loved those Saturday or Sunday mornings where we were all together. I think October (when it’s not raining) is the perfect month to go to the beach for ‘uitwaaien’ (to blow out), to walk in the wind and take a break and clear your head, relieve the stress or forget one’s problems.” — Designed by Gwen Bogaert from Belgium.
Shades Of Gold
“We are about to experience the magical imagery of nature, with all the yellows, ochers, oranges, and reds coming our way this fall. With all the subtle sunrises and the burning sunsets before us, we feel so joyful that we are going to shout it out to the world from the top of the mountains.” — Designed by PopArt Studio from Serbia.
“Autumn has come, the time of long walks in the rain, weekends spent with loved ones, with hot drinks, and a lot of tenderness. Enjoy.” — Designed by LibraFire from Serbia.
“To me, October is all about cozy evenings with hot chocolate, freshly baked cookies, and a game night with friends or family.” — Designed by Lieselot Geirnaert from Belgium.
Haunted House
“Love all the Halloween costumes and decorations!” — Designed by Tazi from Australia.
“And hello to autumn! The summer heat and high season is over. It’s time to pack our backpacks and head for the mountains — there are many treasures waiting to be discovered!” Designed by Agnes Sobon from Poland.
Tea And Cookies
“As it gets colder outside, all I want to do is stay inside with a big pot of tea, eat cookies and read or watch a movie, wrapped in a blanket. Is it just me?” — Designed by Miruna Sfia from Romania.
Designed by Ricardo Gimenes from Spain.
Designed by Mad Fish Digital from Portland, OR.
Trick Or Treat
“Have you ever wondered if all the little creatures of the animal kingdom celebrate Halloween as humans do? My answer is definitely ‘YES! They do!’ They use acorns as baskets to collect all the treats, pastry brushes as brooms for the spookiest witches and hats made from the tips set of your pastry bag. So, if you happen to miss something from your kitchen or from your tool box, it may be one of them, trying to get ready for All Hallows’ Eve.” — Designed by Carla Dipasquale from Italy.

“October is the month when the weather in Poland starts to get colder, and it gets very rainy, too. You can’t always spend your free time outside, so it’s the perfect opportunity to get some hot coffee and work on your next cool web project!” — Designed by Robert Brodziak from Poland.
Designed by Ricardo Gimenes from Spain.
Designed by Ricardo Delgado from Mexico City.
Get Featured Next Month
Would you like to get featured in our next wallpapers post? We’ll publish the November wallpapers on October 31, so if you’d like to be a part of the collection, please don’t hesitate to submit your design. We can’t wait to see what you’ll come up with!
by Cosima Mielke (hello@smashingmagazine.com) at September 30, 2025 11:00 AM
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11883
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

我们平常获取字符串的长度都是直接使用length属性,但对于某些字符,返回的结果会在预期之外,例如:
"鑫空间".length "🌝".length "🇮🇳".length "👨👩👧👦".length "दरबार".length
结果如下:
"鑫空间".length // 3 "🌝".length // 2 "🇮🇳".length // 4 "👨👩👧👦".length // 11 "दरबार".length // 5
看到没,看起来Emoji表情字符的长度是1,但是,实际返回的Emoji长度并不是1,不仅不是1,不同的Emoji返回的长度还不一样,这就会给我们精确统计字符长度带来困扰。
怎么办呢?
我们可以使用Intl.Segmenter()返回字符串的真实长度,例如,我们不妨给字符串扩展一个名为 realLength 的方法,完整代码如下所示:
Object.defineProperty(String.prototype, 'realLength', {
get: function() {
return Array.from(
new Intl.Segmenter("en", {
// 颗粒度:字位,默认值,可省略
granularity: "grapheme"
}).segment(this)
).length;
}
});
此时,我们就可以返回字符串真实的长度了。
"鑫空间".realLength // 3 "🌝".realLength // 1 "🇮🇳".realLength // 1 "👨👩👧👦".realLength // 1 "दरबार".realLength // 4
是不是很美丽?

JavaScript使用UTF-16编码,这种编码方式最多支持 2x 16 共 65,536 个字符,6万多个字符看起来多,但是世界上那么多语言,已经不够用了,尤其Emoji表情字符,都在65,536 个字符开外。
JS想要表示,只能组合多个特殊的 2 字节代码单元以生成人类可读的字符。
例如:
Array.from({ length: "🌝".length }, (_, i) => "🌝"[i])
// [ '\ud83c', '\udf1d' ]
于是,JS代码直接使用length属性访问的时候,返回值就是2,但是人类理解肯定是1个字符。
这就是Emoji字符长度不是1的原因。
Intl.Segmenter 是 JavaScript 中一个专门用于文本分段的强大工具,它能够根据不同的语言和文本规则,将字符串智能地分割成有意义的单位,如字符、单词或句子。
Intl.Segmenter 的核心作用在于进行语言敏感的分段。
传统的字符串分割方法(如 split(‘ ‘))对于英语这类使用空格分隔单词的语言勉强适用,但对于中文、日文等不依赖空格的语言就无能为力了,甚至处理包含复杂表情符号(Emoji)的文本时也会出错。
Intl.Segmenter 则依据 Unicode 标准,能够理解不同语言的语法习惯,进行精准的边界检测。
使用示意:
const segmenter = new Intl.Segmenter([locales[, options]]);
其中:
granularity
表示分段的粒度,是最重要的选项。可取值为:
localeMatcher
指定所使用的语言匹配算法,可以是 “best fit”(默认值,使用环境提供的可能最匹配的算法)或 “lookup”(使用特定的 BCP 47 算法)。
创建分段器实例后,通过调用其 segment(inputString) 方法对文本进行分段。该方法返回一个 Segments 可迭代对象。
例如:
// 创建一个中文词段器
const segmenter = new Intl.Segmenter('zh-CN', { granularity: 'word' });
const text = "《HTML并不简单》是国内唯一一本精讲HTML的技术书籍";
const segments = segmenter.segment(text); // 返回一个 Segments 对象
// 遍历
for (const segment of segments) {
console.log(`片段: "${segment.segment}", 起始索引: ${segment.index}, 是否像词: ${segment.isWordLike}`);
}
在我的Chrome浏览器下,输出的结果是:
// 片段: "《", 起始索引: 0, 是否像词: false // 片段: "HTML", 起始索引: 1, 是否像词: true // 片段: "并不", 起始索引: 5, 是否像词: true // 片段: "简单", 起始索引: 7, 是否像词: true // 片段: "》", 起始索引: 9, 是否像词: false // 片段: "是", 起始索引: 10, 是否像词: true // 片段: "国内", 起始索引: 11, 是否像词: true // 片段: "唯一", 起始索引: 13, 是否像词: true // 片段: "一本", 起始索引: 15, 是否像词: true // 片段: "精", 起始索引: 17, 是否像词: true // 片段: "讲", 起始索引: 18, 是否像词: true // 片段: "HTML", 起始索引: 19, 是否像词: true // 片段: "的", 起始索引: 23, 是否像词: true // 片段: "技术", 起始索引: 24, 是否像词: true // 片段: "书籍", 起始索引: 26, 是否像词: true
可以看到浏览器自带对中文句子进行分词了。
目前Intl.Segmenter所有现代浏览器均已支持,前端也能自动分词分句了。

Intl命名对象还有很多其他方法,多是与语言、金钱、数字、字符处理相关的。
详见我之前的这篇文章:“JS Intl对象完整简介及在中文中的应用”
还是非常强大与实用的。
就是语法复杂了点,学习成本比较高,真正使用的时候,需要去搜索查找资料。
不过现在都有AI了,只需要知道有这么个东西就好了,AI会帮你搞定。
行吧,就说这么多。
明天就是国庆节了,祝大家国庆节快乐!
不说了,我要准备出发去钓鱼了!
欢迎大家关注我的钓鱼账号:“最会钓鱼的程序员”。
随时了解我国庆节的战况。

😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11883
(本篇完)
这里记录每周值得分享的科技内容,周五发布。([通知] 下周十一假期,周刊休息。)
本杂志开源,欢迎投稿。另有《谁在招人》服务,发布程序员招聘信息。合作请邮件联系(yifeng.ruan@gmail.com)。

香港举办"维港海上大巡游",会在维多利亚港岸边,展示四个大型充气玩偶雕塑。这是正在运送充气玩偶。(via)
上个月,谷歌发布了图像模型 Gemini 2.5 Flash Image(项目名 Nano Banana)。

谷歌称它是目前"最先进的图像生成和编辑模型"。
我试用后,感觉确实很强,而且免费使用,打开官网(下图)就能用。

(备注:如果你访问不了官网,周刊讨论区也有接入官方 API 的第三方网站,不过大部分要收费。)
对于这个模型,网友发现了各种神奇的用法,有人甚至收集成了一个 Awesome 仓库。

我从这个仓库里面,挑了几个很实用的例子,分享给大家。需要说明的是,我想其他图像模型也能做这些事,大家可以试试。
图像模型的最常见任务,一定是人像处理。我们先上传一张生活照片。

然后,让模型将其转成证件照,提示词如下。
请为照片里面的人物生成1寸证件照,要求白底,职业正装,睁眼微笑。

这个效果有点惊人啊。它意味着,人物的表情、发型、妆容、服饰、姿势都是可以改变的。
下面就是改变人物表情,让其侧脸对着镜头微笑。


改变人物的姿势,"将下面第二张图片的人物,改成第一张图片的姿势。"



照相馆以后危险了,肖像照、旅游照、集体照都可以交给 AI 了。
图像模型的另一个用途是家居装潢,要看家装效果图就让 AI 生成,更改装潢配色和家具,都是小 case。
下面是一个难度更高的例子,上传一张户型图,让它变成 3D 模型渲染图。


从照片提取建筑模型,也挺神奇。


下面,让模型更改物品的包装,"将图二的漫画形象,贴到图一的包装盒,生成一张专业的产品照"。




书籍的封面、软件的包装盒,也可以同样生成。
图像模型的另一个大市场是地图应用(地理信息),只不过还没想到可以收费的玩法。下面就是一个创新的用例。
上传一张地图,上面用箭头标注你选定的地点,让模型"生成沿着红色箭头看到的场景。"


它甚至可以从地形等高线图,生成红色箭头处的实景图。


1、超音速厨师刀
一家美国公司推出了超音速厨师刀。

它的刀柄上有一个按钮,按下后,刀锋就会进入超声波模式。

根据介绍,开启超声波后,刀刃每秒振动超过4万次,使刀具比实际锋利得多,会节省高达50%的切菜力气。
某些情况下,把它放在食物上,它会依靠振动发出的波,自动把食物切开。

这把刀内置了电池,所以还有配套的刀具充电器。

2、粘土电路板
电路板是电子产品的基础。
一位国外网友,为了演示电路板并不是高深的产品,特别制作了一块粘土电路板。

他把全过程的照片都放上网,先采集泥巴,然后将其压平。

在上面挖出电路,然后进行烧制。


最后,装上铜线和电子元件,电路板就做好了。


根据知名分析师玛丽·米克尔的 AI 报告,如果从 IT 行业中剔除 AI 相关岗位,美国 IT 行业的就业人数多年来一直处于持平或下降趋势。

上图中,蓝线是 IT 行业的总就业人数,绿线是剔除 AI 岗位的就业人数,中间的高峰是疫情期间。
这就是说,虽然 IT 行业本身一直在扩张,但是全部就业增长都发生在 AI 领域。
1、超越沙盒(英文)

如何在网页上安全地运行第三方代码?谷歌提出一个全新的解决方案 SafeContentFrame。
它是一个 JS 库,会将第三方代码加载到一个单独域名 googleusercontent.com 上面,再用 iframe 将其插入当前网页,这样就提供了最大限度的隔离。
2、离线应用为什么尚未流行?(英文)

离线使用功能一直没有流行起来,作者认为,离线使用就相当于建立一个分布式系统,面临着复杂的同步问题,很难做对。
3、Elasticsearch 不适合用作数据库(英文)

Elasticsearch 是目前主流的搜索服务,能否把它用作主要数据库?本文告诉你不可以,它不是为数据库而设计的。
4、如何使用 Python 生成音频的文字稿(英文)

一篇简单的入门教程,一步步教你自己写 Python 脚本,通过 Whisper 模型提取音频的文字稿。
5、避免使用 @ts-ignore(英文)

TypeScript 的 @ts-ignore 标注,用来关闭下一行的所有报错。作者认为不应该使用它,宁愿改用 @ts-expect-error 标注或者 any 类型。
6、Apple 的私有 CSS 属性,为网页添加"液态玻璃"效果(英文)

作者发现,苹果为 Safari 浏览器添加了一个没有公开的 CSS 属性,让网页元素呈现"液态玻璃"效果。
7、如何调整 systemd 加快启动(英文)

一篇初学者教程,教你5个技巧,通过调整 systemd 设置,缩短启动时间。
1、gpu-kill

显示 GPU 运行信息的一个工具,自带 Web 管理面板,支持 Nvidia/AMD/Intel/Apple 各种品牌。
另有一个在线 GPU 性能测试网站 Volume Shader BM。(@BOS1980 投稿)

2、RustNet

监控网络流量的终端工具,会显示连接的详细信息,跨平台。
3、PortNote

一个自托管的仪表盘,列出被各种服务占用的本地端口。与 Compose 文件结合后,可以启动/停止 Docker 容器,参见介绍文章。
4、Atlas

一个 Docker 容器,扫描当前网络,图形化显示网络节点信息。

基于终端的文件管理器,支持 Linux 和 Mac。
另有一个类似的终端文件管理器 Yazi。

内网穿透工具 frp 的一个客户端辅助 Bash 脚本,简化隧道的创建和管理。(@openapphub 投稿)

开源的任务管理软件,支持 Web/手机/桌面各个平台,可以 Docker 部署。(@CaryTrivett 投稿)
网友自己写的 PostgreSQL/MySQL 数据库的备份工具,可以自动备份、加密、压缩数据库,并将备份文件上传至腾讯云 COS 或阿里云 OSS。(@iKeepLearn 投稿)
9、X-CMD

一个命令行工具集,一键启用 1000+ CLI 工具,跨平台,支持 AI 功能。(@Zhengqbbb 投稿)
1、Huxe

个人语音伴侣,生成类似播客的"每日简报",供你收听,内容包括当日新闻、兴趣爱好、个人日历和邮件等。
它来自 NotebookLM 的主创人员,他们离开谷歌后的创业产品。目前免费使用,参见介绍文章。
2、AIPex

周刊以前介绍过的一个开源 Chrome 插件,功能现在扩展了,可以通过 AI 进行浏览器自动化。(@buttercannfly 投稿)
3、binglish

一个 Python 脚本,自动为 Windows 更换必应 Bing 每日壁纸,并在壁纸上添加"每日单词",AI 生成单词解释和例句。(@klemperer 投稿)
网友基于 B 站开源的 Index-TTS 语音合成模型的微调模型,提升语音的韵律感和自然度。(@asr-pub 投稿)
5、Neovate

基于终端的智能编码助手(Code Agent),可以看作是开源的 Claude Code。(@xierenyuan 投稿)

一个基于 Web 的 AI 视频字幕编辑工具,可以视频语音自动转文本,生成字幕,试用 Demo。(@x007xyz 投稿)
7、mcpstore

一个 MCP 服务的管理工具,接入各种 MCP 服务器,自带 Web 管理面板。(@whillhill 投稿)
1、99个物理小实验

一本在线的英文书籍,精选了荷兰中学物理的99个小实验,涉及各个领域(力、光、磁、波等等)。
最近爆出了一系列 npm 软件包投毒事件,这个仓库收录了各种 npm 安全措施,分为使用者和发布者两大部分。
1、电动车原理
网上流传的电动车原理图片。

2、一道几何题
正方形里面有一个小圆,请问小圆的半径与正方形边长的关系是多少?


这道题好像很不容易,答案是正方形边长的 4/33。
根据我的观察,公司里面的高级程序员和低级程序员,使用 AI 的方式是不一样的。
高级程序员并不完全信任 AI 的输出,只是用 AI 加速项目。他们一般会审查和重构 AI 生成的代码,对于 AI 的架构决策也是抱着怀疑的态度。
初级程序员更倾向于跳过审查和重构,全盘接受 AI 的输出,从而导致"纸牌屋式"的代码:看起来能发挥作用,一旦投入使用就会崩溃。
我不知道,AI 未来会不会替代程序员,但是现阶段,AI 编程还不能解决100%的软件问题,但已经可以解决70%的问题。这相当于,AI 可以减轻高级程序员70%的工作量。
剩下的30%,依然需要依靠程序员的经验和专业知识,而初级程序员恰恰缺少的是这30%。
所以,听起来可能违反直觉:AI 对高级程序员比对初级程序员帮助更大,更容易产生工作成果。
现阶段的 AI,更像团队中的一个非常有干劲的初级程序员,可以快速编写代码,但需要不断的监督和纠正。你知道的越多,你就越能指导它。
所以,AI 的正确用法是,高级程序员用它来加速他们已经知道如何做的事情,初级程序员用它来学习该做什么。
1、
AI 会一直扩展,一直到大部分太阳的能量都被用于计算。
-- 马斯克最新访谈
2、
我认为,火星可以在30年内自给自足。每两年,行星会排成一条直线,你就可以出发去火星。所以,30年内大约有10到15个左右的火星出发窗口。
每次出发,运往火星的货物吨位会呈指数级增长,那么30年内,我们可以让火星自给自足。
-- 马斯克最新访谈
3、
软件业悄然兴起一种新的工作"氛围清理"(Vibe Coding cleanup),专门解决"氛围编程"导致的问题。这真是 AI 时代最大的讽刺:人类被雇来清理 AI 的垃圾。
-- 《氛围清理即服务》
4、
AI 泡沫是很有可能的,但对于 Meta 这样的公司来说,更大的风险是犹豫不决。
如果我们最终为 AI 浪费了数千亿美元,显然非常不幸,但我实际上认为错过 AI 的风险更高。对于我们来说,风险不是过于激进,而是不够激进。
-- 扎克伯格
5、
今天的计算机是响应者(responder):你让它做某事,它就会去做。下一阶段的计算机是"代理"(agent),它就像一个盒子里的小人,开始预测你想要什么。它不是帮助你,而是引导你处理大量的信息,就像你在盒子里有一个小伙伴。
-- 乔布斯,1984年的采访
乒乓仓(#320)
"精益开发"的精益是什么?(#270)
人工智能的机会在哪里(#220)
软件订阅制的胜利(#170)
(完)
In “A Week In The Life Of An AI-Augmented Designer”, Kate stumbled her way through an AI-augmented sprint (coffee was chugged, mistakes were made). In “Prompting Is A Design Act”, we introduced WIRE+FRAME, a framework to structure prompts like designers structure creative briefs. Now we’ll take the next step: packaging those structured prompts into AI assistants you can design, reuse, and share.
AI assistants go by different names: CustomGPTs (ChatGPT), Agents (Copilot), and Gems (Gemini). But they all serve the same function — allowing you to customize the default AI model for your unique needs. If we carry over our smart intern analogy, think of these as interns trained to assist you with specific tasks, eliminating the need for repeated instructions or information, and who can support not just you, but your entire team.
Why Build Your Own Assistant?If you’ve ever copied and pasted the same mega-prompt for the nth time, you’ve experienced the pain. An AI assistant turns a one-off “great prompt” into a dependable teammate. And if you’ve used any of the publicly available AI Assistants, you’ve realized quickly that they’re usually generic and not tailored for your use.
Public AI assistants are great for inspiration, but nothing beats an assistant that solves a repeated problem for you and your team, in your voice, with your context and constraints baked in. Instead of reinventing the wheel by writing new prompts each time, or repeatedly copy-pasting your structured prompts every time, or spending cycles trying to make a public AI Assistant work the way you need it to, your own AI Assistant allows you and others to easily get better, repeatable, consistent results faster.
Some of the benefits of building your own AI Assistant over writing or reusing your prompts include:
Public AI assistants are like stock templates. While they serve a specific purpose compared to the generic AI platform, and are useful starting points, if you want something tailored to your needs and team, you should really build your own.
A few reasons for building your AI Assistant instead of using a public assistant someone else created include:
Your own AI Assistants allow you to take your successful ways of interacting with AI and make them repeatable and shareable. And while they are tailored to your and your team’s way of working, remember that they are still based on generic AI models, so the usual AI disclaimers apply:
Don’t share anything you wouldn’t want screenshotted in the next company all-hands. Keep it safe, private, and user-respecting. A shared AI Assistant can potentially reveal its inner workings or data.
Note: We will be building an AI assistant using ChatGPT, aka a CustomGPT, but you can try the same process with any decent LLM sidekick. As of publication, a paid account is required to create CustomGPTs, but once created, they can be shared and used by anyone, regardless of whether they have a paid or free account. Similar limitations apply to the other platforms. Just remember that outputs can vary depending on the LLM model used, the model’s training, mood, and flair for creative hallucinations.
An AI Assistant is great when the same audience has the same problem often. When the fit isn’t there, the risk is high; you should skip building an AI Assistant for now, as explained below:
Just because these are signs that you should not build your AI Assistant now, doesn’t mean you shouldn’t ever. Revisit this decision when you notice that you’re starting to repeatedly use the same prompt weekly, multiple teammates ask for it, or manual time copy-pasting and refining start exceeding ~15 minutes. Those are some signs that an AI Assistant will pay back quickly.
In a nutshell, build an AI Assistant when you can name the problem, the audience, frequency, and the win. The rest of this article shows how to turn your successful WIRE+FRAME prompt into a CustomGPT that you and your team can actually use. No advanced knowledge, coding skills, or hacks needed.
As Always, Start with the UserThis should go without saying to UX professionals, but it’s worth a reminder: if you’re building an AI assistant for anyone besides yourself, start with the user and their needs before you build anything.
Building without doing this first is a sure way to end up with clever assistants nobody actually wants to use. Think of it like any other product: before you build features, you understand your audience. The same rule applies here, even more so, because AI assistants are only as helpful as they are useful and usable.
From Prompt To AssistantYou’ve already done the heavy lifting with WIRE+FRAME. Now you’re just turning that refined and reliable prompt into a CustomGPT you can reuse and share. You can use MATCH as a checklist to go from a great prompt to a useful AI assistant.
A few weeks ago, we invited readers to share their ideas for AI assistants they wished they had. The top contenders were:
But the favorite was an AI assistant to turn tons of customer feedback into actionable insights. Readers replied with variations of: “An assistant that can quickly sort through piles of survey responses, app reviews, or open-ended comments and turn them into themes we can act on.”
And that’s the one we will build in this article — say hello to Insight Interpreter.
Walkthrough: Insight InterpreterHaving lots of customer feedback is a nice problem to have. Companies actively seek out customer feedback through surveys and studies (solicited), but also receive feedback that may not have been asked for through social media or public reviews (unsolicited). This is a goldmine of information, but it can be messy and overwhelming trying to make sense of it all, and it’s nobody’s idea of fun. Here’s where an AI assistant like the Insight Interpreter can help. We’ll turn the example prompt created using the WIRE+FRAME framework in Prompting Is A Design Act into a CustomGPT.
When you start building a CustomGPT by visiting https://chat.openai.com/gpts/editor, you’ll see two paths:
The good news is that MATCH works for both. In conversational mode, you can use it as a mental checklist, and we’ll walk through using it in configure mode as a more formal checklist in this article.

Paste your full WIRE+FRAME prompt into the Instructions section exactly as written. As a refresher, I’ve included the mapping and snippets of the detailed prompt from before:
If you’re building Copilot Agents or Gemini Gems instead of CustomGPTs, you still paste your WIRE+FRAME prompt into their respective Instructions sections.
In the knowledge section, upload up to 20 files, clearly labeled, that will help the CustomGPT respond effectively. Keep files small and versioned: reviews_Q2_2025.csv beats latestfile_final2.csv. For this prompt for analyzing customer feedback, generating themes organized by customer journey, rating them by severity and effort, files could include:
An example of a file to help it parse uploaded data is shown below:

Do one last visual check to make sure you’ve filled in all applicable fields and the basics are in place: is the concept sharp and clear (not a do-everything bot)? Are the roles, goals, and tone clear? Do we have the right assets (docs, guides) to support it? Is the flow simple enough that others can get started easily? Once those boxes are checked, move into testing.
Use the Preview panel to verify that your CustomGPT performs as well, or better, than your original WIRE+FRAME prompt, and that it works for your intended audience. Try a few representative inputs and compare the results to what you expected. If something worked before but doesn’t now, check whether new instructions or knowledge files are overriding it.
When things don’t look right, here are quick debugging fixes:
When your CustomGPT is ready, you can publish it via the “Create” option. Select the appropriate access option:
But hand off doesn’t end with hitting publish, you should maintain it to keep it relevant and useful:
And that’s it! Our Insights Interpreter is now live!
Since we used the WIRE+FRAME prompt from the previous article to create the Insights Interpreter CustomGPT, I compared the outputs:


The results are similar, with slight differences, and that’s expected. If you compare the results carefully, the themes, issues, journey stages, frequency, severity, and estimated effort match with some differences in wording of the theme, issue summary, and problem statement. The opportunities and quotes have more visible differences. Most of it is because of the CustomGPT knowledge and training files, including instructions, examples, and guardrails, now live as always-on guidance.
Keep in mind that in reality, Generative AI is by nature generative, so outputs will vary. Even with the same data, you won’t get identical wording every time. In addition, underlying models and their capabilities rapidly change. If you want to keep things as consistent as possible, recommend a model (though people can change it), track versions of your data, and compare for structure, priorities, and evidence rather than exact wording.
While I’d love for you to use Insights Interpreter, I strongly recommend taking 15 minutes to follow the steps above and create your own. That is exactly what you or your team needs — including the tone, context, output formats, and get the real AI Assistant you need!
Inspiration For Other AI AssistantsWe just built the Insight Interpreter and mentioned two contenders: Critique Coach and Prototype Prodigy. Here are a few other realistic uses that can spark ideas for your own AI Assistant:
The best AI Assistants come from carefully inspecting your workflow and looking for areas where AI can augment your work regularly and repetitively. Then follow the steps above to build a team of customized AI assistants.
Ask Me Anything About AssistantsIn this AI x Design series, we’ve gone from messy prompting (“A Week In The Life Of An AI-Augmented Designer”) to a structured prompt framework, WIRE+FRAME (“Prompting Is A Design Act”). And now, in this article, your very own reusable AI sidekick.
CustomGPTs don’t replace designers but augment them. The real magic isn’t in the tool itself, but in how you design and manage it. You can use public CustomGPTs for inspiration, but the ones that truly fit your workflow are the ones you design yourself. They extend your craft, codify your expertise, and give your team leverage that generic AI models can’t.
Build one this week. Even better, today. Train it, share it, stress-test it, and refine it into an AI assistant that can augment your team.
by Lyndon Cerejo (hello@smashingmagazine.com) at September 26, 2025 10:00 AM
这里记录每周值得分享的科技内容,周五发布。
本杂志开源,欢迎投稿。另有《谁在招人》服务,发布程序员招聘信息。合作请邮件联系(yifeng.ruan@gmail.com)。

9月12日,武汉的长江文化艺术季开幕式上,无人机组成江豚的图案。(via)
上期周刊提到,旧金山有一个广告牌,上面是一个谜语,指向某个 AI 公司的网址。

我原以为,这只是个别现象,但是本周看到了一篇报道,才发现我错了。
旧金山市已经为 AI 疯狂了,城里的 AI 广告铺天盖地。相比之下,中国的 AI 热潮只能算是静悄悄。
旧金山的地理位置,就在硅谷旁边,美国主要的 AI 公司大部分位于这个地区。另外,斯坦福大学也在这里。
过去两年中,AI 概念支撑着美国股市不断疯涨,造就了无数富豪。旧金山就是最狂热的风暴中心,资金和人才正在疯狂涌入。
站在大街上,每一栋高楼顶上都是 AI 广告牌。



上面第一张图,有一个广告牌写着"你妈妈也会喜欢的 AI 客服"(AI customer support even your mother will like),这是词穷到找不到其他广告词了吗?
你开车上高速公路,路边也都是 AI 广告。



你在公交车站等车,看到的也是 AI 广告,上面写着"停止雇佣人类"(Stop Hiring Humans)。

不仅 AI 公司做广告,那些跟 AI 没关系的公司也在做。

上面是 Postman 公司的广告,它是一个 API 测试工具,按理说跟 AI 没关系。
但是,广告上写着"你的 API 为 AI 做好准备吗?",言下之意就是可以用它来测试,就是这样蹭热度。
这些无所不在的 AI 广告,不是科幻电影,而是旧金山眼下的样子。
这么多广告,一方面因为确实有商机,但是更大的原因是 AI 公司钱太多,他们吸引到了源源不断的风险投资,还能去股市圈钱。资本急需看到效果。
于是,这些公司拼命做广告,曝光越多,市场占有率和公司估值也会随之提高,从而吸引更多的资本。
这就叫泡沫经济,只要没破,你就用力吹,能吹多大就多大,这样会有奖赏。
但是,作为一个普通人,每天被这些广告包围,狂轰滥炸,是不是有点太荒诞了。AI 作为一种新技术,目的是提高工作效率,解放人类,可现在变得像一种宗教,向你灌输,让你膜拜。人好像成了它的附庸,活在一个 AI 构建出来的世界里。
1、Apache 软件基金会(简写 ASF) 是世界最大的开源软件组织之一,最近更改了徽标,从羽毛变成了像树叶。

"apache"这个词,原是一个北美印第安部落的名字,羽毛就是该部落的象征。有人批评,这种象征太刻板,用在当代不合适。
所以这次就改成了像树叶,更中性,也比喻开源软件的韧性、开放性和责任感。

2、风力发电机有巨大的叶片,陆上运输非常不方便。
一家美国公司决定,建造专门运送风力叶片的飞机。

它的整个机舱(包括头部)都用来放叶片,驾驶舱移到了飞机的顶上。

装卸叶片需要打开飞机的整个后舱,像塞牙签一样塞进去,非常壮观。

3、向日葵可以长多高?

美国印第安纳州的一个农民,培育出了世界最高的向日葵,从底部到花尖一共有10.9米。

这件事最大的难点之一,就是如果向日葵长得太高,茎就支撑不住果实,必须使用支撑架。这个农民专门搭了三层支架。


这件事其实很神奇,一棵小苗只用一年时间就能长这么大。

4、真正的随机数需要硬件生成,并不便宜。
一个国外研究团队最近证明,Micro-LED 灯珠 可以当作随机数生成器。

他们发现,LED 发出的电磁波强度波动(单位时间的光子数量)是随机的,而且随机数的生成速率很高。
由于 LED 灯珠很便宜,如果这个发现证明有实用价值,随机数生成的难题也许就解决了。
5、章鱼有8条腿。一项研究发现,前4条腿用于探索,后4条腿用于行走。

这跟人类上肢、下肢的分工有点像,多足机器人的设计可以参考。
1、你可能不需要高端 CPU(英文)

本文提出几个理由,普通用户购买8核以上的高端 CPU,可能是浪费。
2、如何用 make 命令编译 C 程序(英文)

一篇 C 语言初级教程,介绍 make 命令怎么编译一个程序。
3、去除多余的真值判断(英文)

ESlint 有一条规则,如果某条判断语句始终是true或者false,那么就报错,因为这个判断是多余的。
现在,TypeScript 5.6 也引入了这个规则,默认报错。
4、CSS 的 cos() 和 sin()(英文)

本文讲解使用 CSS 的三角函数,做出圆形布局。
5、HTTP 的 Options 方法(英文)

HTTP 方法,除了常用的 GET 和 POST 等,还有一个不常用的 OPTIONS,本文介绍它的用法。
6、你应该采用虚拟机,而不是双重启动(英文)

本文认为,不再有必要安装两个系统,做双重启动了,完全可以改用虚拟机。
7、神奇的苏联地图(英文)

苏联地图的细节程度令人难以置信。他们出版的外国地图会标注桥梁在水面上的高度、承载能力以及主要建筑材料,河流的宽度、流向、深度,森林的树木种类等等。
至今也不清楚,他们怎么得到这些信息,又为什么画在公开出版的地图上。

一个 Windows 应用,可以让 Windows 桌面变得像 Mac 桌面,同时集成了工作区和平铺式窗口管理器,参见介绍文章。
2、Ladder

开源的网页抓取查看工具。用户输入网址,它会自动将网页抓取展示出来。
3、oq

终端查看 OpenAPI 规格文件的工具。
4、httpjail
一个跨平台的命令行程序,可以限制本机的 HTTP/HTTPs 请求,只有开白名单,才能发出请求。

一个开源的报错监控平台。
6、草梅 Auth

基于 Nuxt 框架的登录平台,支持 OAuth2.0 协议,有邮箱、用户名、手机、验证码、社交媒体等多种登录方式。(@CaoMeiYouRen 投稿)
7、Neovide

Neovim 编辑器的一个跨平台图形界面封装,很多功能都配置好了。
8、Gokapi

一个自托管的文件分享服务,只有管理员才能上传。
9、Swap.js
一个很简单的 JS 库,通过 Ajax 让普通的多页面网站,产生单页应用的"局部更新"效果。
1、SSHLLM

基于 SSH 的 AI 客户端,先用 ssh 登录到服务器,然后通过它使用 AI 大模型。(@aicu-icu 投稿)

免费将 PDF 文件转成一段讲解视频,配上动画和语音。(@icaohongyuan 投稿)

这个库允许使用 MQTT 协议接入 MCP 服务器,方便 AI 直接操作物联网设备。(@ysfscream 投稿)

一个开源的前端应用,使用自然语言生成网站 UI,类似于 V0/Lovable。
1、PostHog

这是一家公司的官网,做得好像操作系统的桌面一样,如果长时间无操作,甚至还会出现屏保。
2、Katalog

一位摄影师为她的每一件个人物品(书籍、衣服、药品等等),拍了一张照片,放到网站上,一共有12795张。
她想告诉大家,不要低估你拥有的物品数量。
3、大数据教科书(The Big Data Textbook)

苏黎世联邦理工学院的大数据英文教科书,免费阅读。
ChatGPT 是最受欢迎的 AI 应用之一,它的 Logo 是六根链条组成的圆角六边形。

但是,你现在去苹果的应用商店,搜索"ChatGPT",你会看到无数的仿冒品。

上面就是各种仿冒品的图标,真的 ChatGPT 也在其中。
仿冒品的名字也是尽量往 ChatGPT 靠,比如 ChatBot、AI Bot、Open Chat AI 等等。
你能从一堆李鬼里面,找出真品吗。
旧金山是美国创业之都,有无数风投支持的创业公司。很自然的,倒闭的公司也很多。
于是,就滋生了一门生意,有人专门收购那些倒闭公司的办公家具,再以折扣价卖出。

收购来的办公家具,就堆放在大仓库里,想要的人自己去挑。
很多硅谷大公司,比如 Pinterest、谷歌和 Facebook,裁掉员工以后,也会把多余的办公家具卖到这里。

这些二手的办公家具往往都是名牌货,现在以半价出售,所以生意很好。


由于美国股市这几年都是大牛市,创纪录的风险投资涌向创业公司,很大一部分钱都用在办公家具。随着倒闭的公司越来越多,废旧办公家具源源不断,根本收购不完,二手家具的好日子看来还在后头。

1.4亿年前,澳大利亚的内陆地区曾经是一片封闭的内海。后来,海水干涸,那里变成了一片干旱荒芜的荒漠。

由于内陆的地势比沿海低15米,历史上有人设想过重新蓄水,恢复内陆的"地中海"。

但是,澳大利亚并没有那么多淡水,而且内陆的水分蒸发大于降雨,所以这个计划无法实现。
进入21世纪后,随着工程能力的进步,有人重新提出了这个计划,设想修建一条600公里的管道,将海水引入内陆。
管道沿途都铺设太阳能板(澳大利亚的太阳能极其丰富),产生电能,作为水泵的动力,将水不断泵入内陆。
这样的话,一旦内陆形成湖泊,降雨量也会随之增加,彻底改变干旱缺水的现状。而且,还能建立航运业,设立新兴的滨海城市,提升经济与移民潜力。
但是,不少人反对这个计划,引入海水以后,内陆土地将彻底盐碱化,无法耕种。另外,输水管道建设成本巨大,初步预估超过2000亿元。
澳大利亚人还在权衡,是否要推动这个疯狂的计划。近年来,随着全球气温上升,内陆一年比一年酷热,越来越不适合生存。支持声现在有所抬头,狂掷几千亿,建造一片海,毕竟这是改变内陆气候唯一可能的方法。

1、
有了 AI,代码不再珍贵。
-- 鲍里斯·切尔尼(Boris Cherny),Claude Code 产品负责人
2、
现在的博物馆大量使用电子屏幕,但是我带儿子去博物馆不是为了看屏幕,否则在家使用平板电脑就可以了。
-- 美国网友
3、
职业生涯就像一场吃馅饼比赛,获胜的奖品是你要接着吃更多的馅饼。
这是否是一件好事,取决于你是否喜欢这项工作。
-- 杰森·朗斯托夫(Jason Lengstorf),美国前端工程师
4、
许多人,尤其是新工程师,错误地认为使用复杂的工具和语言会做出更强大、更具创新性的产品。
事实恰恰相反。最有效的组件是简单、可预测、枯燥无趣的成熟技术。它们为我们提供了进一步开发复杂项目所需的基础。
你不是要建造一座有趣的桥梁,你要建造的是人们以后要充满信心走在上面的坚固桥梁。
-- 《选择无聊和灵活的技术》
如何拍出爆款视频(#319)
为什么英雄不使用炸药(#269)
如何防止帐号被黑(#219)
五菱汽车的产品设计(#169)
(完)
There is a spectrum of opinions on how dramatically all creative professions will be changed by the coming wave of agentic AI, from the very skeptical to the wildly optimistic and even apocalyptic. I think that even if you are on the “skeptical” end of the spectrum, it makes sense to explore ways this new technology can help with your everyday work. As for my everyday work, I’ve been doing UX and product design for about 25 years now, and I’m always keen to learn new tricks and share them with colleagues. Right now, I’m interested in AI-assisted prototyping, and I’m here to share my thoughts on how it can change the process of designing digital products.
To set your expectations up front: this exploration focuses on a specific part of the product design lifecycle. Many people know about the Double Diamond framework, which shows the path from problem to solution. However, I think it’s the Triple Diamond model that makes an important point for our needs. It explicitly separates the solution space into two phases: Solution Discovery (ideating and validating the right concept) and Solution Delivery (engineering the validated concept into a final product). This article is focused squarely on that middle diamond: Solution Discovery.

How AI can help with the preceding (Problem Discovery) and the following (Solution Delivery) stages is out of the scope of this article. Problem Discovery is less about prototyping and more about research, and while I believe AI can revolutionize the research process as well, I’ll leave that to people more knowledgeable in the field. As for Solution Delivery, it is more about engineering optimization. There’s no doubt that software engineering in the AI era is undergoing dramatic changes, but I’m not an engineer — I’m a designer, so let me focus on my “sweet spot”.
And my “sweet spot” has a specific flavor: designing enterprise applications. In this world, the main challenge is taming complexity: dealing with complicated data models and guiding users through non-linear workflows. This background has had a big impact on my approach to design, putting a lot of emphasis on the underlying logic and structure. This article explores the potential of AI through this lens.
I’ll start by outlining the typical artifacts designers create during Solution Discovery. Then, I’ll examine the problems with how this part of the process often plays out in practice. Finally, we’ll explore whether AI-powered prototyping can offer a better approach, and if so, whether it aligns with what people call “vibe coding,” or calls for a more deliberate and disciplined way of working.
What We Create During Solution DiscoveryThe Solution Discovery phase begins with the key output from the preceding research: a well-defined problem and a core hypothesis for a solution. This is our starting point. The artifacts we create from here are all aimed at turning that initial hypothesis into a tangible, testable concept.
Traditionally, at this stage, designers can produce artifacts of different kinds, progressively increasing fidelity: from napkin sketches, boxes-and-arrows, and conceptual diagrams to hi-fi mockups, then to interactive prototypes, and in some cases even live prototypes. Artifacts of lower fidelity allow fast iteration and enable the exploration of many alternatives, while artifacts of higher fidelity help to understand, explain, and validate the concept in all its details.
It’s important to think holistically, considering different aspects of the solution. I would highlight three dimensions:
One can argue that those are layers rather than dimensions, and each of them builds on the previous ones (for example, according to Semantic IxD by Daniel Rosenberg), but I see them more as different facets of the same thing, so the design process through them is not necessarily linear: you may need to switch from one perspective to another many times.
This is how different types of design artifacts map to these dimensions:

As Solution Discovery progresses, designers move from the left part of this map to the right, from low-fidelity to high-fidelity, from ideating to validating, from diverging to converging.
Note that at the beginning of the process, different dimensions are supported by artifacts of different types (boxes-and-arrows, sketches, class diagrams, etc.), and only closer to the end can you build a live prototype that encompasses all three dimensions: conceptual model, visualization, and flow.
This progression shows a classic trade-off, like the difference between a pencil drawing and an oil painting. The drawing lets you explore ideas in the most flexible way, whereas the painting has a lot of detail and overall looks much more realistic, but is hard to adjust. Similarly, as we go towards artifacts that integrate all three dimensions at higher fidelity, our ability to iterate quickly and explore divergent ideas goes down. This inverse relationship has long been an accepted, almost unchallenged, limitation of the design process.
The Problem With The Mockup-Centric ApproachFaced with this difficult trade-off, often teams opt for the easiest way out. On the one hand, they need to show that they are making progress and create things that appear detailed. On the other hand, they rarely can afford to build interactive or live prototypes. This leads them to over-invest in one type of artifact that seems to offer the best of both worlds. As a result, the neatly organized “bento box” of design artifacts we saw previously gets shrunk down to just one compartment: creating static high-fidelity mockups.

This choice is understandable, as several forces push designers in this direction. Stakeholders are always eager to see nice pictures, while artifacts representing user flows and conceptual models receive much less attention and priority. They are too high-level and hardly usable for validation, and usually, not everyone can understand them.
On the other side of the fidelity spectrum, interactive prototypes require too much effort to create and maintain, and creating live prototypes in code used to require special skills (and again, effort). And even when teams make this investment, they do so at the end of Solution Discovery, during the convergence stage, when it is often too late to experiment with fundamentally different ideas. With so much effort already sunk, there is little appetite to go back to the drawing board.
It’s no surprise, then, that many teams default to the perceived safety of static mockups, seeing them as a middle ground between the roughness of the sketches and the overwhelming complexity and fragility that prototypes can have.
As a result, validation with users doesn’t provide enough confidence that the solution will actually solve the problem, and teams are forced to make a leap of faith to start building. To make matters worse, they do so without a clear understanding of the conceptual model, the user flows, and the interactions, because from the very beginning, designers’ attention has been heavily skewed toward visualization.
The result is often a design artifact that resembles the famous “horse drawing” meme: beautifully rendered in the parts everyone sees first (the mockups), but dangerously underdeveloped in its underlying structure (the conceptual model and flows).

While this is a familiar problem across the industry, its severity depends on the nature of the project. If your core challenge is to optimize a well-understood, linear flow (like many B2C products), a mockup-centric approach can be perfectly adequate. The risks are contained, and the “lopsided horse” problem is unlikely to be fatal.
However, it’s different for the systems I specialize in: complex applications defined by intricate data models and non-linear, interconnected user flows. Here, the biggest risks are not on the surface but in the underlying structure, and a lack of attention to the latter would be a recipe for disaster.
Transforming The Design ProcessThis situation makes me wonder:
How might we close the gap between our design intent and a live prototype, so that we can iterate on real functionality from day one?

If we were able to answer this question, we would:
Of course, the desire for such a process is not new. This vision of a truly prototype-driven workflow is especially compelling for enterprise applications, where the benefits of faster learning and forced conceptual clarity are the best defense against costly structural flaws. But this ideal was still out of reach because prototyping in code took so much work and specialized talents. Now, the rise of powerful AI coding assistants changes this equation in a big way.
The Seductive Promise Of “Vibe Coding”And the answer seems to be obvious: vibe coding!
“Vibe coding is an artificial intelligence-assisted software development style popularized by Andrej Karpathy in early 2025. It describes a fast, improvisational, collaborative approach to creating software where the developer and a large language model (LLM) tuned for coding is acting rather like pair programmers in a conversational loop.”
— Wikipedia
The original tweet by Andrej Karpathy:

The allure of this approach is undeniable. If you are not a developer, you are bound to feel awe when you describe a solution in plain language, and moments later, you can interact with it. This seems to be the ultimate fulfillment of our goal: a direct, frictionless path from an idea to a live prototype. But is this method reliable enough to build our new design process around it?
Vibe coding mixes up a description of the UI with a description of the system itself, resulting in a prototype based on changing assumptions rather than a clear, solid model.
The pitfall of vibe coding is that it encourages us to express our intent in the most ambiguous way possible: by having a conversation.
This is like hiring a builder and telling them what to do one sentence at a time without ever presenting them a blueprint. They could make a wall that looks great, but you can’t be sure that it can hold weight.
I’ll give you one example illustrating problems you may face if you try to jump over the chasm between your idea and a live prototype relying on pure vibe coding in the spirit of Andrej Karpathy’s tweet. Imagine I want to prototype a solution to keep track of tests to validate product ideas. I open my vibe coding tool of choice (I intentionally don’t disclose its name, as I believe they all are awesome yet prone to similar pitfalls) and start with the following prompt:
I need an app to track tests. For every test, I need to fill out the following data:
- Hypothesis (we believe that...)
- Experiment (to verify that, we will...)
- When (a single date, or a period)
- Status (New/Planned/In Progress/Proven/Disproven)
And in a minute or so, I get a working prototype:
![]()
Inspired by success, I go further:
Please add the ability to specify a product idea for every test. Also, I want to filter tests by product ideas and see how many tests each product idea has in each status.
And the result is still pretty good:
![]()
But then I want to extend the functionality related to product ideas:
Okay, one more thing. For every product idea, I want to assess the impact score, the confidence score, and the ease score, and get the overall ICE score. Perhaps I need a separate page focused on the product idea, with all the relevant information and related tests.
And from this point on, the results are getting more and more confusing.
The flow of creating tests hasn’t changed much. I can still create a bunch of tests, and they seem to be organized by product ideas. But when I click “Product Ideas” in the top navigation, I see nothing:

I need to create my ideas from scratch, and they are not connected to the tests I created before:

Moreover, when I go back to “Tests”, I see that they are all gone. Clearly something went wrong, and my AI assistant confirms that:
No, this is not expected behavior — it’s a bug! The issue is that tests are being stored in two separate places (local state in the Index page and App state), so tests created on the main page don’t sync with the product ideas page.
Sure, eventually it fixed that bug, but note that we encountered this just on the third step, when we asked to slightly extend the functionality of a very simple app. The more layers of complexity we add, the more roadblocks of this sort we are bound to face.
Also note that this specific problem of a not fully thought-out relationship between two entities (product ideas and tests) is not isolated at the technical level, and therefore, it didn’t go away once the technical bug was fixed. The underlying conceptual model is still broken, and it manifests in the UI as well.
For example, you can still create “orphan” tests that are not connected to any item from the “Product Ideas” page. As a result, you may end up with different numbers of ideas and tests on different pages of the app:

Let’s diagnose what really happened here. The AI’s response that this is a “bug” is only half the story. The true root cause is a conceptual model failure. My prompts never explicitly defined the relationship between product ideas and tests. The AI was forced to guess, which led to the broken experience. For a simple demo, this might be a fixable annoyance. But for a data-heavy enterprise application, this kind of structural ambiguity is fatal. It demonstrates the fundamental weakness of building without a blueprint, which is precisely what vibe coding encourages.
Don’t take this as a criticism of vibe coding tools. They are creating real magic. However, the fundamental truth about “garbage in, garbage out” is still valid. If you don’t express your intent clearly enough, chances are the result won’t fulfill your expectations.
Another problem worth mentioning is that even if you wrestle it into a state that works, the artifact is a black box that can hardly serve as reliable specifications for the final product. The initial meaning is lost in the conversation, and all that’s left is the end result. This makes the development team “code archaeologists,” who have to figure out what the designer was thinking by reverse-engineering the AI’s code, which is frequently very complicated. Any speed gained at the start is lost right away because of this friction and uncertainty.
From Fast Magic To A Solid FoundationPure vibe coding, for all its allure, encourages building without a blueprint. As we’ve seen, this results in structural ambiguity, which is not acceptable when designing complex applications. We are left with a seemingly quick but fragile process that creates a black box that is difficult to iterate on and even more so to hand off.
This leads us back to our main question: how might we close the gap between our design intent and a live prototype, so that we can iterate on real functionality from day one, without getting caught in the ambiguity trap? The answer lies in a more methodical, disciplined, and therefore trustworthy process.
In Part 2 of this series, “A Practical Guide to Building with Clarity”, I will outline the entire workflow for Intent Prototyping. This method places the explicit intent of the designer at the forefront of the process while embracing the potential of AI-assisted coding.
Thank you for reading, and I look forward to seeing you in Part 2.
by Yegor Gilyov (hello@smashingmagazine.com) at September 24, 2025 05:00 PM
Unlike timeline-based animations, which tell stories across a sequence of events, or interaction animations that are triggered when someone touches something, ambient animations are the kind of passive movements you might not notice at first. But, they make a design look alive in subtle ways.
In an ambient animation, elements might subtly transition between colours, move slowly, or gradually shift position. Elements can appear and disappear, change size, or they could rotate slowly.
Ambient animations aren’t intrusive; they don’t demand attention, aren’t distracting, and don’t interfere with what someone’s trying to achieve when they use a product or website. They can be playful, too, making someone smile when they catch sight of them. That way, ambient animations add depth to a brand’s personality.

To illustrate the concept of ambient animations, I’ve recreated the cover of a Quick Draw McGraw comic book (PDF) as a CSS/SVG animation. The comic was published by Charlton Comics in 1971, and, being printed, these characters didn’t move, making them ideal candidates to transform into ambient animations.
FYI: Original cover artist Ray Dirgo was best known for his work drawing Hanna-Barbera characters for Charlton Comics during the 1970s. Ray passed away in 2000 at the age of 92. He outlived Charlton Comics, which went out of business in 1986, and DC Comics acquired its characters.
Tip: You can view the complete ambient animation code on CodePen.

Not everything on a page or in a graphic needs to move, and part of designing an ambient animation is knowing when to stop. The trick is to pick elements that lend themselves naturally to subtle movement, rather than forcing motion into places where it doesn’t belong.
When I’m deciding what to animate, I look for natural motion cues and think about when something would move naturally in the real world. I ask myself: “Does this thing have weight?”, “Is it flexible?”, and “Would it move in real life?” If the answer’s “yes,” it’ll probably feel right if it moves. There are several motion cues in Ray Dirgo’s cover artwork.

For example, the peace pipe Quick Draw’s puffing on has two feathers hanging from it. They swing slightly left and right by three degrees as the pipe moves, just like real feathers would.
#quick-draw-pipe {
animation: quick-draw-pipe-rotate 6s ease-in-out infinite alternate;
}
@keyframes quick-draw-pipe-rotate {
0% { transform: rotate(3deg); }
100% { transform: rotate(-3deg); }
}
#quick-draw-feather-1 {
animation: quick-draw-feather-1-rotate 3s ease-in-out infinite alternate;
}
#quick-draw-feather-2 {
animation: quick-draw-feather-2-rotate 3s ease-in-out infinite alternate;
}
@keyframes quick-draw-feather-1-rotate {
0% { transform: rotate(3deg); }
100% { transform: rotate(-3deg); }
}
@keyframes quick-draw-feather-2-rotate {
0% { transform: rotate(-3deg); }
100% { transform: rotate(3deg); }
}
I often choose elements or decorative details that add to the vibe but don’t fight for attention.
Ambient animations aren’t about signalling to someone where they should look; they’re about creating a mood.
Here, the chief slowly and subtly rises and falls as he puffs on his pipe.
#chief {
animation: chief-rise-fall 3s ease-in-out infinite alternate;
}
@keyframes chief-group-rise-fall {
0% { transform: translateY(0); }
100% { transform: translateY(-20px); }
}

For added effect, the feather on his head also moves in time with his rise and fall:
#chief-feather-1 {
animation: chief-feather-1-rotate 3s ease-in-out infinite alternate;
}
#chief-feather-2 {
animation: chief-feather-2-rotate 3s ease-in-out infinite alternate;
}
@keyframes chief-feather-1-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(-9deg); }
}
@keyframes chief-feather-2-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(9deg); }
}
One of the things I love most about ambient animations is how they bring fun into a design. They’re an opportunity to demonstrate personality through playful details that make people smile when they notice them.

Take a closer look at the chief, and you might spot his eyebrows raising and his eyes crossing as he puffs hard on his pipe. Quick Draw’s eyebrows also bounce at what look like random intervals.
#quick-draw-eyebrow {
animation: quick-draw-eyebrow-raise 5s ease-in-out infinite;
}
@keyframes quick-draw-eyebrow-raise {
0%, 20%, 60%, 100% { transform: translateY(0); }
10%, 50%, 80% { transform: translateY(-10px); }
}
Keep Hierarchy In Mind
Motion draws the eye, and even subtle movements have a visual weight. So, I reserve the most obvious animations for elements that I need to create the biggest impact.

Smoking his pipe clearly has a big effect on Quick Draw McGraw, so to demonstrate this, I wrapped his elements — including his pipe and its feathers — within a new SVG group, and then I made that wobble.
#quick-draw-group {
animation: quick-draw-group-wobble 6s ease-in-out infinite;
}
@keyframes quick-draw-group-wobble {
0% { transform: rotate(0deg); }
15% { transform: rotate(2deg); }
30% { transform: rotate(-2deg); }
45% { transform: rotate(1deg); }
60% { transform: rotate(-1deg); }
75% { transform: rotate(0.5deg); }
100% { transform: rotate(0deg); }
}
Then, to emphasise this motion, I mirrored those values to wobble his shadow:
#quick-draw-shadow {
animation: quick-draw-shadow-wobble 6s ease-in-out infinite;
}
@keyframes quick-draw-shadow-wobble {
0% { transform: rotate(0deg); }
15% { transform: rotate(-2deg); }
30% { transform: rotate(2deg); }
45% { transform: rotate(-1deg); }
60% { transform: rotate(1deg); }
75% { transform: rotate(-0.5deg); }
100% { transform: rotate(0deg); }
}
Apply Restraint
Just because something can be animated doesn’t mean it should be. When creating an ambient animation, I study the image and note the elements where subtle motion might add life. I keep in mind the questions: “What’s the story I’m telling? Where does movement help, and when might it become distracting?”
Remember, restraint isn’t just about doing less; it’s about doing the right things less often.
Layering SVGs For ExportIn “Smashing Animations Part 4: Optimising SVGs,” I wrote about the process I rely on to “prepare, optimise, and structure SVGs for animation.” When elements are crammed into a single SVG file, they can be a nightmare to navigate. Locating a specific path or group can feel like searching for a needle in a haystack.
That’s why I develop my SVGs in layers, exporting and optimising one set of elements at a time — always in the order they’ll appear in the final file. This lets me build the master SVG gradually by pasting it in each cleaned-up section.
I start by exporting background elements, optimising them, adding class and ID attributes, and pasting their code into my SVG file.

Then, I export elements that often stay static or move as groups, like the chief and Quick Draw McGraw.

Before finally exporting, naming, and adding details, like Quick Draw’s pipe, eyes, and his stoned sparkles.

Since I export each layer from the same-sized artboard, I don’t need to worry about alignment or positioning issues as they all slot into place automatically.
Implementing Ambient AnimationsYou don’t need an animation framework or library to add ambient animations to a project. Most of the time, all you’ll need is a well-prepared SVG and some thoughtful CSS.
But, let’s start with the SVG. The key is to group elements logically and give them meaningful class or ID attributes, which act as animation hooks in the CSS. For this animation, I gave every moving part its own identifier like #quick-draw-tail or #chief-smoke-2. That way, I could target exactly what I needed without digging through the DOM like a raccoon in a trash can.
Once the SVG is set up, CSS does most of the work. I can use @keyframes for more expressive movement, or animation-delay to simulate randomness and stagger timings. The trick is to keep everything subtle and remember I’m not animating for attention, I’m animating for atmosphere.
Remember that most ambient animations loop continuously, so they should be lightweight and performance-friendly. And of course, it’s good practice to respect users who’ve asked for less motion. You can wrap your animations in an @media prefers-reduced-motion query so they only run when they’re welcome.
@media (prefers-reduced-motion: no-preference) {
#quick-draw-shadow {
animation: quick-draw-shadow-wobble 6s ease-in-out infinite;
}
}
It’s a small touch that’s easy to implement, and it makes your designs more inclusive.
Ambient Animation Design PrinciplesIf you want your animations to feel ambient, more like atmosphere than action, it helps to follow a few principles. These aren’t hard and fast rules, but rather things I’ve learned while animating smoke, sparkles, eyeballs, and eyebrows.
Ambient animations should feel relaxed, so use longer durations and choose easing curves that feel organic. I often use ease-in-out, but cubic Bézier curves can also be helpful when you want a more relaxed feel and the kind of movements you might find in nature.
Hard resets or sudden jumps can ruin the mood, so if an animation loops, ensure it cycles smoothly. You can do this by matching start and end keyframes, or by setting the animation-direction to alternate the value so the animation plays forward, then back.
A single animation might be boring. Five subtle animations, each on separate layers, can feel rich and alive. Think of it like building a sound mix — you want variation in rhythm, tone, and timing. In my animation, sparkles twinkle at varying intervals, smoke curls upward, feathers sway, and eyes boggle. Nothing dominates, and each motion plays its small part in the scene.
The point of an ambient animation is that it doesn’t dominate. It’s a background element and not a call to action. If someone’s eyes are drawn to a raised eyebrow, it’s probably too much, so dial back the animation until it feels like something you’d only catch if you’re really looking.
Check prefers-reduced-motion, and don’t assume everyone’s device can handle complex animations. SVG and CSS are light, but things like blur filters and drop shadows, and complex CSS animations can still tax lower-powered devices. When an animation is purely decorative, consider adding aria-hidden="true" to keep it from cluttering up the accessibility tree.
Ambient animation is like seasoning on a great dish. It’s the pinch of salt you barely notice, but you’d miss when it’s gone. It doesn’t shout, it whispers. It doesn’t lead, it lingers. It’s floating smoke, swaying feathers, and sparkles you catch in the corner of your eye. And when it’s done well, ambient animation adds personality to a design without asking for applause.
Now, I realise that not everyone needs to animate cartoon characters. So, in part two, I’ll share how I created animations for several recent client projects. Until next time, if you’re crafting an illustration or working with SVG, ask yourself: What would move if this were real? Then animate just that. Make it slow and soft. Keep it ambient.
You can view the complete ambient animation code on CodePen.
by Andy Clarke (hello@smashingmagazine.com) at September 22, 2025 01:00 PM
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11837
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

本文要介绍的 CSS scroll-target-group属性以及:target-current伪类,可以实现滚动的时候,基于位置不同,对应的菜单项自动高亮的效果。
关于这种效果的实现,我之前还专门写文章介绍过:“尝试使用JS IntersectionObserver让标题和导航联动”。
没想到,短短几年之后,纯CSS就能实现这样的效果了。
眼见为实,滚动下面内嵌的局部滚动条(需要Chrome 140+),大家就可以看到对应的效果了。
或者参见下图所示的GIF效果。

是不是很赞?
实现代码非常简单,HTML代码就下面这些:
<menu>
<li><a href="#intro">前言</a></li>
<li><a href="#ch1">第1章</a></li>
<li><a href="#ch2">第2章</a></li>
</menu>
<article>
<h1>欢迎来到我的博客</h1>
<section id="intro">...</section>
<section id="ch1">...</section>
<section id="ch2">...</section>
</article>
CSS代码也非常简单,就这么点内容:
menu {
position: fixed;
scroll-target-group: auto;
}
a:target-current {
color: red;
}
结束了,就结束了,对吧,给菜单容器设置scroll-target-group:auto,然后菜单里面的链接元素使用:target-current设置匹配样式就可以了。
此时,链接元素对应的href锚点元素进入区域的时候,链接元素就会高亮啦!
牛逼!

下面快速介绍下scroll-target-group属性。
该属性可以让锚点元素有类似于::scroll-marker伪元素的特性( CSS Carousel API中的一个特性),关于::scroll-marker伪元素,可以参见我前几个月刚写的文章:“CSS ::scroll-button ::scroll-marker伪元素又是干嘛用的”。
需要使用的时候,直接设置值为auto就可以了。
非常新的一个特性,MDN连文档都没有,Chrome 140浏览器支持。

:target-current伪类相对支持早一些,和::scroll-marker伪元素等特性同一时间支持的,基于滚动位置匹配scroll-marker-group或者scroll-target-group里面的锚点或标记元素。
使用示意:
::scroll-marker {
background-color: white;
}
::scroll-marker:target-current {
background-color: black;
}
Chrome 135+浏览器支持。

很好很强,静待浏览器全面支持。
我发现CSS的野心挺大的,看架势,要覆盖几乎Web上常见的交互,JS退让为逻辑处理。
不过一来学习成本高,二来以后有AI,三来有成熟替代方案,未来会有几个人使用呢?我看悬。
所以前端没啥搞头了,不如来钓鱼吧,欢迎大家关注我的钓鱼账号:“最会钓鱼的程序员”。
周周更新!

😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11837
(本篇完)
Misuse and misplaced trust of AI is becoming an unfortunate common event. For example, lawyers trying to leverage the power of generative AI for research submit court filings citing multiple compelling legal precedents. The problem? The AI had confidently, eloquently, and completely fabricated the cases cited. The resulting sanctions and public embarrassment can become a viral cautionary tale, shared across social media as a stark example of AI’s fallibility.
This goes beyond a technical glitch; it’s a catastrophic failure of trust in AI tools in an industry where accuracy and trust are critical. The trust issue here is twofold — the law firms are submitting briefs in which they have blindly over-trusted the AI tool to return accurate information. The subsequent fallout can lead to a strong distrust in AI tools, to the point where platforms featuring AI might not be considered for use until trust is reestablished.
Issues with trusting AI aren’t limited to the legal field. We are seeing the impact of fictional AI-generated information in critical fields such as healthcare and education. On a more personal scale, many of us have had the experience of asking Siri or Alexa to perform a task, only to have it done incorrectly or not at all, for no apparent reason. I’m guilty of sending more than one out-of-context hands-free text to an unsuspecting contact after Siri mistakenly pulls up a completely different name than the one I’d requested.

With digital products moving to incorporate generative and agentic AI at an increasingly frequent rate, trust has become the invisible user interface. When it works, our interactions are seamless and powerful. When it breaks, the entire experience collapses, with potentially devastating consequences. As UX professionals, we’re on the front lines of a new twist on a common challenge. How do we build products that users can rely on? And how do we even begin to measure something as ephemeral as trust in AI?
Trust isn’t a mystical quality. It is a psychological construct built on predictable factors. I won’t dive deep into academic literature on trust in this article. However, it is important to understand that trust is a concept that can be understood, measured, and designed for. This article will provide a practical guide for UX researchers and designers. We will briefly explore the psychological anatomy of trust, offer concrete methods for measuring it, and provide actionable strategies for designing more trustworthy and ethical AI systems.
The Anatomy of Trust: A Psychological Framework for AITo build trust, we must first understand its components. Think of trust like a four-legged stool. If any one leg is weak, the whole thing becomes unstable. Based on classic psychological models, we can adapt these “legs” for the AI context.
This is the most straightforward pillar: Does the AI have the skills to perform its function accurately and effectively? If a weather app is consistently wrong, you stop trusting it. If an AI legal assistant creates fictitious cases, it has failed the basic test of ability. This is the functional, foundational layer of trust.
This moves from function to intent. Does the user believe the AI is acting in their best interest? A GPS that suggests a toll-free route even if it’s a few minutes longer might be perceived as benevolent. Conversely, an AI that aggressively pushes sponsored products feels self-serving, eroding this sense of benevolence. This is where user fears, such as concerns about job displacement, directly challenge trust—the user starts to believe the AI is not on their side.
Does AI operate on predictable and ethical principles? This is about transparency, fairness, and honesty. An AI that clearly states how it uses personal data demonstrates integrity. A system that quietly changes its terms of service or uses dark patterns to get users to agree to something violates integrity. An AI job recruiting tool that has subtle yet extremely harmful social biases, existing in the algorithm, violates integrity.
Can the user form a stable and accurate mental model of how the AI will behave? Unpredictability, even if the outcomes are occasionally good, creates anxiety. A user needs to know, roughly, what to expect. An AI that gives a radically different answer to the same question asked twice is unpredictable and, therefore, hard to trust.
The Trust Spectrum: The Goal of a Well-Calibrated RelationshipOur goal as UX professionals shouldn’t be to maximize trust at all costs. An employee who blindly trusts every email they receive is a security risk. Likewise, a user who blindly trusts every AI output can be led into dangerous situations, such as the legal briefs referenced at the beginning of this article. The goal is well-calibrated trust.
Think of it as a spectrum where the upper-mid level is the ideal state for a truly trustworthy product to achieve:
Our job is to design experiences that guide users away from the dangerous poles of Active Distrust and Over-trust and toward that healthy, realistic middle ground of Calibrated Trust.

Trust feels abstract, but it leaves measurable fingerprints. Academics in the social sciences have done much to define both what trust looks like and how it might be measured. As researchers, we can capture these signals through a mix of qualitative, quantitative, and behavioral methods.
During interviews and usability tests, go beyond “Was that easy to use?” and listen for the underlying psychology. Here are some questions you can start using tomorrow:
One of the most potent challenges to an AI’s Benevolence is the fear of job displacement. When a participant expresses this, it is a critical research finding. It requires a specific, ethical probing technique.
Imagine a participant says, “Wow, it does that part of my job pretty well. I guess I should be worried.”
An untrained researcher might get defensive or dismiss the comment. An ethical, trained researcher validates and explores:
“Thank you for sharing that; it’s a vital perspective, and it’s exactly the kind of feedback we need to hear. Can you tell me more about what aspects of this tool make you feel that way? In an ideal world, how would a tool like this work with you to make your job better, not to replace it?”
This approach respects the participant, validates their concern, and reframes the feedback into an actionable insight about designing a collaborative, augmenting tool rather than a replacement. Similarly, your findings should reflect the concern users expressed about replacement. We shouldn’t pretend this fear doesn’t exist, nor should we pretend that every AI feature is being implemented with pure intention. Users know better than that, and we should be prepared to argue on their behalf for how the technology might best co-exist within their roles.
You can quantify trust without needing a data science degree. After a user completes a task with an AI, supplement your standard usability questions with a few simple Likert-scale items:
Over time, these metrics can track how trust is changing as your product evolves.
Note: If you want to go beyond these simple questions that I’ve made up, there are numerous scales (measurements) of trust in technology that exist in academic literature. It might be an interesting endeavor to measure some relevant psychographic and demographic characteristics of your users and see how that correlates with trust in AI/your product. Table 1 at the end of the article contains four examples of current scales you might consider using to measure trust. You can decide which is best for your application, or you might pull some of the items from any of the scales if you aren’t looking to publish your findings in an academic journal, yet want to use items that have been subjected to some level of empirical scrutiny.
People’s true feelings are often revealed in their actions. You can use behaviors that reflect the specific context of use for your product. Here are a few general metrics that might apply to most AI tools that give insight into users’ behavior and the trust they place in your tool.
Once you’ve researched and measured trust, you can begin to design for it. This means translating psychological principles into tangible interface elements and user flows.
Explainability isn’t about showing users the code. It’s about providing a useful, human-understandable rationale for a decision.
Instead of:
“Here is your recommendation.”
Try:
“Because you frequently read articles about UX research methods, I’m recommending this new piece on measuring trust in AI.”
This addition transforms AI from an opaque oracle to a transparent logical partner.
Many of the popular AI tools (e.g., ChatGPT and Gemini) show the thinking that went into the response they provide to a user. Figure 3 shows the steps Gemini went through to provide me with a non-response when I asked it to help me generate the masterpiece displayed above in Figure 2. While this might be more information than most users care to see, it provides a useful resource for a user to audit how the response came to be, and it has provided me with instructions on how I might proceed to address my task.

Figure 4 shows an example of a scorecard OpenAI makes available as an attempt to increase users’ trust. These scorecards are available for each ChatGPT model and go into the specifics of how the models perform as it relates to key areas such as hallucinations, health-based conversations, and much more. In reading the scorecards closely, you will see that no AI model is perfect in any area. The user must remain in a trust but verify mode to make the relationship between human reality and AI work in a way that avoids potential catastrophe. There should never be blind trust in an LLM.

Your AI will make mistakes.
Trust is not determined by the absence of errors, but by how those errors are handled.
Likewise, your AI can’t know everything. You should acknowledge this to your users.
UX practitioners should work with the product team to ensure that honesty about limitations is a core product principle.
This can include the following:
All of these considerations highlight the critical role of UX writing in the development of trustworthy AI. UX writers are the architects of the AI’s voice and tone, ensuring that its communication is clear, honest, and empathetic. They translate complex technical processes into user-friendly explanations, craft helpful error messages, and design conversational flows that build confidence and rapport. Without thoughtful UX writing, even the most technologically advanced AI can feel opaque and untrustworthy.
The words and phrases an AI uses are its primary interface with users. UX writers are uniquely positioned to shape this interaction, ensuring that every tooltip, prompt, and response contributes to a positive and trust-building experience. Their expertise in human-centered language and design is indispensable for creating AI systems that not only perform well but also earn and maintain the trust of their users.
A few key areas for UX writers to focus on when writing for AI include:
As the people responsible for understanding and advocating for users, we walk an ethical tightrope. Our work comes with profound responsibilities.
We must draw a hard line between designing for calibrated trust and designing to manipulate users into trusting a flawed, biased, or harmful system. For example, if an AI system designed for loan approvals consistently discriminates against certain demographics but presents a user interface that implies fairness and transparency, this would be an instance of trustwashing.
Another example of trustwashing would be if an AI medical diagnostic tool occasionally misdiagnoses conditions, but the user interface makes it seem infallible. To avoid trustwashing, the system should clearly communicate the potential for error and the need for human oversight.
Our goal must be to create genuinely trustworthy systems, not just the perception of trust. Using these principles to lull users into a false sense of security is a betrayal of our professional ethics.
To avoid and prevent trustwashing, researchers and UX teams should:
When our research uncovers deep-seated distrust or potential harm — like the fear of job displacement — our job has only just begun. We have an ethical duty to advocate for that user. In my experience directing research teams, I’ve seen that the hardest part of our job is often carrying these uncomfortable truths into rooms where decisions are made. We must champion these findings and advocate for design and strategy shifts that prioritize user well-being, even when it challenges the product roadmap.
I personally try to approach presenting this information as an opportunity for growth and improvement, rather than a negative challenge.
For example, instead of stating “Users don’t trust our AI because they fear job displacement,” I might frame it as “Addressing user concerns about job displacement presents a significant opportunity to build deeper trust and long-term loyalty by demonstrating our commitment to responsible AI development and exploring features that enhance human capabilities rather than replace them.” This reframing can shift the conversation from a defensive posture to a proactive, problem-solving mindset, encouraging collaboration and innovative solutions that ultimately benefit both the user and the business.
It’s no secret that one of the more appealing areas for businesses to use AI is in workforce reduction. In reality, there will be many cases where businesses look to cut 10–20% of a particular job family due to the perceived efficiency gains of AI. However, giving users the opportunity to shape the product may steer it in a direction that makes them feel safer than if they do not provide feedback. We should not attempt to convince users they are wrong if they are distrustful of AI. We should appreciate that they are willing to provide feedback, creating an experience that is informed by the human experts who have long been doing the task being automated.
Conclusion: Building Our Digital Future On A Foundation Of TrustThe rise of AI is not the first major technological shift our field has faced. However, it presents one of the most significant psychological challenges of our current time. Building products that are not just usable but also responsible, humane, and trustworthy is our obligation as UX professionals.
Trust is not a soft metric. It is the fundamental currency of any successful human-technology relationship. By understanding its psychological roots, measuring it with rigor, and designing for it with intent and integrity, we can move from creating “intelligent” products to building a future where users can place their confidence in the tools they use every day. A trust that is earned and deserved.
| Survey Tool Name | Focus | Key Dimensions of Trust | Citation |
|---|---|---|---|
| Trust in Automation Scale | 12-item questionnaire to assess trust between people and automated systems. | Measures a general level of trust, including reliability, predictability, and confidence. | Jian, J. Y., Bisantz, A. M., & Drury, C. G. (2000). Foundations for an empirically determined scale of trust in automated systems. International Journal of Cognitive Ergonomics, 4(1), 53–71. |
| Trust of Automated Systems Test (TOAST) | 9-items used to measure user trust in a variety of automated systems, designed for quick administration. | Divided into two main subscales: Understanding (user’s comprehension of the system) and Performance (belief in the system’s effectiveness). | Wojton, H. M., Porter, D., Lane, S. T., Bieber, C., & Madhavan, P. (2020). Initial validation of the trust of automated systems test (TOAST). (PDF) The Journal of Social Psychology, 160(6), 735–750. |
| Trust in Automation Questionnaire | A 19-item questionnaire capable of predicting user reliance on automated systems. A 2-item subscale is available for quick assessments; the full tool is recommended for a more thorough analysis. | Measures 6 factors: Reliability, Understandability, Propensity to trust, Intentions of developers, Familiarity, Trust in automation | Körber, M. (2018). Theoretical considerations and development of a questionnaire to measure trust in automation. In Proceedings 20th Triennial Congress of the IEA. Springer. |
| Human Computer Trust Scale | 12-item questionnaire created to provide an empirically sound tool for assessing user trust in technology. | Divided into two key factors:
|
Siddharth Gulati, Sonia Sousa & David Lamas (2019): Design, development and evaluation of a human-computer trust scale, (PDF) Behaviour & Information Technology |
To design for calibrated trust, consider implementing the following tactics, organized by the four pillars of trust:
by Victor Yocco (hello@smashingmagazine.com) at September 19, 2025 10:00 AM
Climate change is the single biggest health threat to humanity, accelerated by human activities such as the burning of fossil fuels, which generate greenhouse gases that trap the sun’s heat.
The average temperature of the earth’s surface is now 1.2°C warmer than it was in the late 1800’s, and projected to more than double by the end of the century.

The consequences of climate change include intense droughts, water shortages, severe fires, melting polar ice, catastrophic storms, and declining biodiversity.
The Internet Is A Significant Part Of The ProblemShockingly, the internet is responsible for higher global greenhouse emissions than the aviation industry, and is projected to be responsible for 14% of all global greenhouse gas emissions by 2040.
If the internet were a country, it would be the 4th largest polluter in the world and represents the largest coal-powered machine on the planet.
But how can something digital like the internet produce harmful emissions?
Internet emissions come from powering the infrastructure that drives the internet, such as the vast data centres and data transmission networks that consume huge amounts of electricity.
Internet emissions also come from the global manufacturing, distribution, and usage of the estimated 30.5 billion devices (phones, laptops, etc.) that we use to access the internet.
Unsurprisingly, internet related emissions are increasing, given that 60% of the world’s population spend, on average, 40% of their waking hours online.
We Must Urgently Reduce The Environmental Impact Of The InternetAs responsible digital professionals, we must act quickly to minimise the environmental impact of our work.
It is encouraging to see the UK government encourage action by adding “Minimise environmental impact” to their best practice design principles, but there is still too much talking and not enough corrective action taking place within our industry.

The reality of many tightly constrained, fast-paced, and commercially driven web projects is that minimising environmental impact is far from the agenda.
So how can we make the environment more of a priority and talk about it in ways that stakeholders will listen to?
A eureka moment on a recent web optimisation project gave me an idea.
My Eureka MomentI led a project to optimise the mobile performance of www.talktofrank.com, a government drug advice website that aims to keep everyone safe from harm.
Mobile performance is critically important for the success of this service to ensure that users with older mobile devices and those using slower network connections can still access the information they need.
Our work to minimise page weights focused on purely technical changes that our developer made following recommendations from tools such as Google Lighthouse that reduced the size of the webpages of a key user journey by up to 80%. This resulted in pages downloading up to 30% faster and the carbon footprint of the journey being reduced by 80%.
We hadn’t set out to reduce the carbon footprint, but seeing these results led to my eureka moment.
I realised that by minimising page weights, you improve performance (which is a win for users and service owners) and also consume less energy (due to needing to transfer and store less data), creating additional benefits for the planet — so everyone wins.
This felt like a breakthrough because business, user, and environmental requirements are often at odds with one another. By focussing on minimising websites to be as simple, lightweight and easy to use as possible you get benefits that extend beyond the triple bottom line of people, planet and profit to include performance and purpose.

So why is ‘minimising’ such a great digital sustainability strategy?
In order to prioritise the environment, we need to be able to speak confidently in a language that will resonate with the business and ensure that any investment in time and resources yields the widest range of benefits possible.
So even if you feel that the environment is a very low priority on your projects, focusing on minimising page weights to improve performance (which is generally high on the agenda) presents the perfect trojan horse for an environmental agenda (should you need one).
Doing the right thing isn’t always easy, but we’ve done it before when managing to prioritise issues such as usability, accessibility, and inclusion on digital projects.
Many of the things that make websites easier to use, more accessible, and more effective also help to minimise their environmental impact, so the things you need to do will feel familiar and achievable, so don’t worry about it all being another new thing to learn about!
So this all makes sense in theory, but what’s the master plan to use when putting it into practice?
The MasterplanThe masterplan for creating websites that have minimal environmental impact is to focus on offering the maximum value from the minimum input of energy.

It’s an adaptation of Buckminister Fuller’s ‘Dymaxion’ principle, which is one of his many progressive and groundbreaking sustainability strategies for living and surviving on a planet with finite resources.
Inputs of energy include both the electrical energy that is required to operate websites and also the mental energy that is required to use them.
You can achieve this by minimising websites to their core content, features, and functionality, ensuring that everything can be justified from the perspective of meeting a business or user need. This means that anything that isn’t adding a proportional amount of value to the amount of energy it requires to provide it should be removed.
So that’s the masterplan, but how do you put it into practice?
Decarbonise Your Highest Value User JourneysI’ve developed a new approach called ‘Decarbonising User Journeys’ that will help you to minimise the environmental impact of your website and maximise its performance.
Note: The approach deliberately focuses on optimising key user journeys and not entire websites to keep things manageable and to make it easier to get started.
The secret here is to start small, demonstrate improvements, and then scale.
The approach consists of five simple steps:
Here’s how it works.
Your highest value user journey might be the one that your users value the most, the one that brings you the highest revenue, or the one that is fundamental to the success of your organisation.
You could also focus on a user journey that you know is performing particularly badly and has the potential to deliver significant business and user benefits if improved.
You may have lots of important user journeys, and it’s fine to decarbonise multiple journeys in parallel if you have the resources, but I’d recommend starting with one first to keep things simple.
To bring this to life, let’s consider a hypothetical example of a premiership football club trying to decarbonise its online ticket-buying journey that receives high levels of traffic and is responsible for a significant proportion of its weekly income.

Once you’ve selected your user journey, you need to benchmark it in terms of how well it meets user needs, the value it offers your organisation, and its carbon footprint.
It is vital that you understand the job it needs to do and how well it is doing it before you start to decarbonise it. There is no point in removing elements of the journey in an effort to reduce its carbon footprint, for example, if you compromise its ability to meet a key user or business need.
You can benchmark how well your user journey is meeting user needs by conducting user research alongside analysing existing customer feedback. Interviews with business stakeholders will help you to understand the value that your journey is providing the organisation and how well business needs are being met.
You can benchmark the carbon footprint and performance of your user journey using online tools such as Cardamon, Ecograder, Website Carbon Calculator, Google Lighthouse, and Bioscore. Make sure you have your analytics data to hand to help get the most accurate estimate of your footprint.
To use these tools, simply add the URL of each page of your journey, and they will give you a range of information such as page weight, energy rating, and carbon emissions. Google Lighthouse works slightly differently via a browser plugin and generates a really useful and detailed performance report as opposed to giving you a carbon rating.
A great way to bring your benchmarking scores to life is to visualise them in a similar way to how you would present a customer journey map or service blueprint.
This example focuses on just communicating the carbon footprint of the user journey, but you can also add more swimlanes to communicate how well the journey is performing from a user and business perspective, too, adding user pain points, quotes, and business metrics where appropriate.

I’ve found that adding the energy efficiency ratings is really effective because it’s an approach that people recognise from their household appliances. This adds a useful context to just showing the weights (such as grams or kilograms) of CO2, which are generally meaningless to people.
Within my benchmarking reports, I also add a set of benchmarking data for every page within the user journey. This gives your stakeholders a more detailed breakdown and a simple summary alongside a snapshot of the benchmarked page.

Your benchmarking activities will give you a really clear picture of where remedial work is required from an environmental, user, and business point of view.
In our football user journey example, it’s clear that the ‘News’ and ‘Tickets’ pages need some attention to reduce their carbon footprint, so they would be a sensible priority for decarbonising.
Use your benchmarking results to help you set targets to aim for, such as a carbon budget, energy efficiency, maximum page weight, and minimum Google Lighthouse performance targets for each individual page, in addition to your existing UX metrics and business KPIs.
There is no right or wrong way to set targets. Choose what you think feels achievable and viable for your business, and you’ll only learn how reasonable and achievable they are when you begin to decarbonise your user journeys.

Setting targets is important because it gives you something to aim for and keeps you focused and accountable. The quantitative nature of this work is great because it gives you the ability to quickly demonstrate the positive impact of your work, making it easier to justify the time and resources you are dedicating to it.
Your objective now is to decarbonise your user journey by minimising page weights, improving your Lighthouse performance rating, and minimising pages so that they meet both user and business needs in the most efficient, simple, and effective way possible.
It’s up to you how you approach this depending on the resources and skills that you have, you can focus on specific pages or addressing a specific problem area such as heavyweight images or videos across the entire user journey.
Here’s a list of activities that will all help to reduce the carbon footprint of your user journey:
As you decarbonise your user journeys, use the benchmarking tools from step 2 to track your progress against the targets you set in step 3 and share your progress as part of your wider sustainability reporting initiatives.
All being well at this point, you will have the numbers to demonstrate how the performance of your user journey has improved and also how you have managed to reduce its carbon footprint.
Share these results with the business as soon as you have them to help you secure the resources to continue the work and initiate similar work on other high-value user journeys.
You should also start to communicate your progress with your users.
It’s important that they are made aware of the carbon footprint of their digital activity and empowered to make informed choices about the environmental impact of the websites that they use.
Ideally, every website should communicate the emissions generated from viewing their pages to help people make these informed choices and also to encourage website providers to minimise their emissions if they are being displayed publicly.
Often, people will have no choice but to use a specific website to complete a specific task, so it is the responsibility of the website provider to ensure the environmental impact of using their website is as small as possible.
You can also help to raise awareness of the environmental impact of websites and what you are doing to minimise your own impact by publishing a digital sustainability statement, such as Unilever’s, as shown below.

A good digital sustainability statement should acknowledge the environmental impact of your website, what you have done to reduce it, and what you plan to do next to minimise it further.
As an industry, we should normalise publishing digital sustainability statements in the same way that accessibility statements have become a standard addition to website footers.
Useful Decarbonising PrinciplesKeep these principles in mind to help you decarbonise your user journeys:
Decarbonising user journeys shouldn’t be done as a one-off, reserved for the next time that you decide to redesign or replatform your website; it should happen on a continual basis as part of your broader digital sustainability strategy.
We know that websites are never finished and that the best websites continually improve as both user and business needs change. I’d like to encourage people to adopt the same mindset when it comes to minimising the environmental impact of their websites.
Decarbonising will happen most effectively when digital professionals challenge themselves on a daily basis to ‘minimise’ the things they are working on.
This avoids building ‘carbon debt’ that consists of compounding technical and design debt within our websites, which is always harder to retrospectively remove than avoid in the first place.
By taking a pragmatic approach, such as optimising high-value user journeys and aligning with business metrics such as performance, we stand the best possible chance of making digital sustainability a priority.
You’ll have noticed that, other than using website carbon calculator tools, this approach doesn’t require any skills that don’t already exist within typical digital teams today. This is great because it means you’ve already got the skills that you need to do this important work.
I would encourage everyone to raise the issue of the environmental impact of the internet in their next team meeting and to try this decarbonising approach to create better outcomes for people, profit, performance, purpose, and the planet.
Good luck!
by James Chudley (hello@smashingmagazine.com) at September 18, 2025 10:00 AM
This article is a sponsored by SerpApi
SerpApi leverages the power of search engine giants, like Google, DuckDuckGo, Baidu, and more, to put together the most pertinent and accurate search result data for your users from the comfort of your app or website. It’s customizable, adaptable, and offers an easy integration into any project.
What do you want to put together?
The list goes on.
In other words, you get to leverage the most comprehensive source of data on the internet for any number of needs, from competitive SEO research and tracking news to parsing local geographic data and even completing personal background checks for employment.
Start With A Simple GET RequestThe results from the search API are only a URL request away for those who want a super quick start. Just add your search details in the URL parameters. Say you need the search result for “Stone Henge” from the location “Westminster, England, United Kingdom” in language “en-GB”, and country of search origin “uk” from the domain “google.co.uk”. Here’s how simple it is to put the GET request together:

Then there’s the impressive list of libraries that seamlessly integrate the APIs into mainstream programming languages and frameworks such as JavaScript, Ruby, .NET, and more.


Want to give it a spin? Sign up and start for free, or tinker with the SerpApi’s live playground without signing up. The playground allows you to choose which search engine to target, and you can fill in the values for all the basic parameters available in the chosen API to customize your search. On clicking “Search”, you get the search result page and its extracted JSON data.

If you need to get a feel for the full API first, you can explore their easy-to-grasp web documentation before making any decision. You have the chance to work with all of the APIs to your satisfaction before committing to it, and when that time comes, SerpApi’s multiple price plans tackle anywhere between an economic few hundred searches a month and bulk queries fit for large corporations.
What Data Do You Need?Beyond the rudimentary search scraping, SerpApi provides a range of configurations, features, and additional APIs worth considering.
Capture the global trends, or refine down to more localized particulars by names of locations or Google’s place identifiers. SerpApi’s optimized routing of requests ensures accurate retrieval of search result data from any location worldwide. If locations themselves are the answers to your queries — say, a cycle trail to be suggested in a fitness app — those can be extracted and presented as maps using SerpApi’s Google Maps API.

Although search engines reveal results in a tidy user interface, deriving data into your application could cause you to end up with a large data dump to be sifted through — but not if you’re using SerpApi.
SerpApi pulls data in a well-structured JSON format, even for the popular kinds of enriched search results, such as knowledge graphs, review snippets, sports league stats, ratings, product listings, AI overview, and more.


SerpApi’s baseline performance can take care of timely search data for real-time requirements. But what if you need more? SerpApi’s Ludicrous Speed option, easily enabled from the dashboard with an upgrade, provides a super-fast response time. More than twice as fast as usual, thanks to twice the server power.
There’s also Ludicrous Speed Max, which allocates four times more server resources for your data retrieval. Data that is time-sensitive and for monitoring things in real-time, such as sports scores and tracking product prices, will lose its value if it is not handled in a timely manner. Ludicrous Speed Max guarantees no delays, even for a large-scale enterprise haul.

You can also use a relevant SerpApi API to hone in on your relevant category, like Google Flights API, Amazon API, Google News API, etc., to get fresh and apt results.
If you don’t need the full depth of the search API, there’s a Light version available for Google Search, Google Images, Google Videos, Google News, and DuckDuckGo Search APIs.

Need the results asynchronously picked up? Want a refined output using advanced search API parameters and a JSON Restrictor? Looking for search outcomes for specific devices? Don’t want auto-corrected query results? There’s no shortage of ways to configure SerpApi to get exactly what you need.
Additionally, if you prefer not to have your search metadata on their servers, simply turn on the “ZeroTrace” mode that’s available for selected plans.
Save yourself a headache, literally, trying to play match between what you see on a search result page and its extracted data in JSON. SerpApi’s X-Ray tool shows you where what comes from. It’s available and free in all plans.

If you don’t have the expertise or resources for tackling the validity of scraping search results, here’s what SerpApi says:
“SerpApi, LLC assumes scraping and parsing liabilities for both domestic and foreign companies unless your usage is otherwise illegal”.
You can reach out and have a conversation with them regarding the legal protections they offer, as well as inquire about anything else you might want to know about, including SerpApi in your project, such as pricing, performance expected, on-demand options, and technical support. Just drop a message at their contact page.
In other words, the SerpApi team has your back with the support and expertise to get the most from your fetched data.
That’s right, you can get your hands on SerpApi today and start fetching data with absolutely no commitment, thanks to a free starter plan that gives you up to 250 free search queries. Give it a try and then bump up to one of the reasonably-priced monthly subscription plans with generous search limits.
by Preethi Sam (hello@smashingmagazine.com) at September 16, 2025 05:00 PM
Traditional personas suck for UX work. They obsess over marketing metrics like age, income, and job titles while missing what actually matters in design: what people are trying to accomplish.
Functional personas, on the other hand, focus on what people are trying to do, not who they are on paper. With a simple AI‑assisted workflow, you can build and maintain personas that actually guide design, content, and conversion decisions.
In this article, I want to breathe new life into a stale UX asset.

For too long, personas have been something that many of us just created, despite the considerable work that goes into them, only to find they have limited usefulness.
I know that many of you may have given up on them entirely, but I am hoping in this post to encourage you that it is possible to create truly useful personas in a lightweight way.
Why Personas Still MatterPersonas give you a shared lens. When everyone uses the same reference point, you cut debate and make better calls. For UX designers, developers, and digital teams, that shared lens keeps you from designing in silos and helps you prioritize work that genuinely improves the experience.
I use personas as a quick test: Would this change help this user complete their task faster, with fewer doubts? If the answer is no (or a shrug), it’s probably a sign the idea isn’t worth pursuing.
From Demographics To FunctionTraditional personas tell you someone’s age, job title, or favorite brand. That makes a nice poster, but it rarely changes design or copy.
Functional personas flip the script. They describe:
When you center on tasks and friction, you get direct lines from user needs to UI decisions, content, and conversion paths.

But remember, this list isn’t set in stone — adapt it to what’s actually useful in your specific situation.
One of the biggest problems with traditional personas was following a rigid template regardless of whether it made sense for your project. We must not fall into that same mistake with functional personas.
The Benefits of Functional PersonasFor small startups, functional personas reduce wasted effort. For enterprise teams, they keep sprawling projects grounded in what matters most.
However, because of the way we are going to produce our personas, they provide certain benefits in either case:
We can deliver these benefits because we are going to use AI to help us, rather than carrying out a lot of time-consuming new research.
How AI Helps Us Get ThereOf course, doing fresh research is always preferable. But in many cases, it is not feasible due to time or budget constraints. I would argue that using AI to help us create personas based on existing assets is preferable to having no focus on user attention at all.
AI tools can chew through the inputs you already have (surveys, analytics, chat logs, reviews) and surface patterns you can act on. They also help you scan public conversations around your product category to fill gaps.
I therefore recommend using AI to:
AI doesn’t remove the need for traditional research. Rather, it is a way of extracting more value from the scattered insights into users that already exist within an organization or online.
The WorkflowHere’s how to move from scattered inputs to usable personas. Each step builds on the last, so treat it as a cycle you can repeat as projects evolve.
Create a dedicated space within your AI tool for this work. Most AI platforms offer project management features that let you organize files and conversations:
This project space becomes your central repository where all uploaded documents, research data, and generated personas live together. The AI will maintain context between sessions, so you won’t have to re-upload materials each time you iterate. This structured approach makes your workflow more efficient and helps the AI deliver more consistent results.

Next, you can brief your AI project so that it understands what it wants from you. For example:
“Act as a user researcher. Create realistic, functional personas using the project files and public research. Segment by needs, tasks, questions, pain points, and goals. Show your reasoning.”
Asking for a rationale gives you a paper trail you can defend to stakeholders.
This is where things get really powerful.
Upload everything (and I mean everything) you can put your hands on relating to the user. Old surveys, past personas, analytics screenshots, FAQs, support tickets, review snippets; dump them all in. The more varied the sources, the stronger the triangulation.
Once you have done that, you can supplement that data by getting AI to carry out “deep research” about your brand. Have AI scan recent (I often focus on the last year) public conversations for your brand, product space, or competitors. Look for:
Save the report you get back into your project.
Once you have done that, ask AI to suggest segments based on tasks and friction points (not demographics). Push back until each segment is distinct, observable, and actionable. If two would behave the same way in your flow, merge them.
This takes a little bit of trial and error and is where your experience really comes into play.
Now you have your segments, the next step is to draft your personas. Use a simple template so the document is read and used. If your personas become too complicated, people will not read them. Each persona should:
Below is a sample template you can work with:
# Persona Title: e.g. Savvy Shopper
- Person's Name: e.g. John Smith.
- Age: e.g. 24
- Job: e.g. Social Media Manager
"A quote that sums up the persona's general attitude"
## Primary Goal
What they’re here to achieve (1–2 lines).
## Key Tasks
• Task 1
• Task 2
• Task 3
## Questions & Objections
• What do they need to know before they act?
• What might make them hesitate?
## Pain Points
• Where do they get stuck?
• What feels risky, slow, or confusing?
## Touchpoints
• What channels are they most commonly interacting with?
## Service Gaps
• How is the organization currently failing this persona?
Remember, you should customize this to reflect what will prove useful within your organization.
It is important to validate that what the AI has produced is realistic. Obviously, no persona is a true representation as it is a snapshot in time of a Hypothetical user. However, we do want it to be as accurate as possible.
Share your drafts with colleagues who interact regularly with real users — people in support cells or research teams. Where possible, test with a handful of users. Then cut anything that you can’t defend or correct any errors that are identified.
Troubleshooting & GuardrailsAs you work through the above process, you will encounter problems. Here are common pitfalls and how to avoid them:
The most important thing to remember is to actually use your personas once they’ve been created. They can easily become forgotten PDFs rather than active tools. Instead, personas should shape your work and be referenced regularly. Here are some ways you can put personas to work:
With this approach, personas evolve from static deliverables into dynamic reference points your whole team can rely on.
Keep Them AliveTreat personas as a living toolkit. Schedule a refresh every quarter or after major product changes. Rerun the research pass, regenerate summaries, and archive outdated assumptions. The goal isn’t perfection; it’s keeping them relevant enough to guide decisions.
Bottom LineFunctional personas are faster to build, easier to maintain, and better aligned with real user behavior. By combining AI’s speed with human judgment, you can create personas that don’t just sit in a slide deck; they actively shape better products, clearer interfaces, and smoother experiences.
by Paul Boag (hello@smashingmagazine.com) at September 16, 2025 08:00 AM
好消息是:这个 blog 终于是 UTF-8 编码了。前些年老有人问我能不能把 RSS 输出改成 UTF-8 的,很多 RSS 阅读器不支持 gbk ,这次终于改过来了。
事情源于昨天下午的一次脑抽,我把网站机器的操作系统升级了。上次升级还是十多年前,真的是太老旧了。结果升完级一看,php 被强制升到了 7 ,我自己写的一些 php 程序(主要是留言板)坏掉了。
这些个程序是我在 2004 年重构 2002 年的代码完成的;而 2002 年是从网上随便找来的代码基础上改的。我正儿八经学习 PHP 是在 1997 年,2000 年后就没怎么更新 PHP 的知识了。上次网站升级的时候,PHP 从 4 强制升到 5 ,就乱改了一通,勉强让程序可以运行(开了一些兼容模式)。这次再看代码,简直是惨不忍睹。所以我在本地装了个 PHP8 ,打开 PHP 官网,好好学习了一下手册。然后把代码取下来,重新建了个 git 仓库,正儿八经的改了一下。把留言的部分删了,只留下了浏览旧信息的部分,勉强让它继续跑起来。等什么时候有空了,再用 PHP 或 Lua 重新做一个。
Apache 的配置语法变了,一开始 PHP 跑不起来,折腾了一下配置文件就可以了。
最大的麻烦是 MySQL ,这次强制升到了 8 。之前好像是 4 版或更老的版本。我打开 blog 管理后台一看,全是乱码。心想坏了,编码出问题了。Blog 全是静态页面。只在修改时才从数据库读出内容生成一遍静态页面。所以外面看是正常的。我赶紧关掉了 mysql 服务器,以免(有人留言等修改行为)造成二次伤害。
Blog 是在 2005 年建的,数据采用的是 gbk 编码。其实那一年我已知道未来 UTF-8 一定是主流,但脑子里想的是手机流量费用 3 分钱 1 K 。选用 GBK 而不是 UTF 8 可以为自己和读者省钱。记得那年我和有道的负责人周枫闲聊汉字编码问题,他说 GBK 编码还是有意义的,他们当时爬虫爬来的中文数据储存就是用的 GBK ,这样可以节省 1/3 的储存成本。
其实,当年于我更好的方案应该是储存使用 utf-8 ,只在传输层用 GBK ,以后改起来也方便。可惜当年我自我折腾的能力远比不上现在,用了个别人开发的 blog 系统就懒得折腾了。在古旧得 Mysql 数据库中,是不储存文本编码类型的。基本上是你写什么数据编码就存什么。后来升级后,那些没有标注的编码字段就统一标注成了 latin1/latin1swedishci 。但实际我储存的是 gbk ,读出来自然就乱了。
一开始我觉得,这种问题肯定无数人解决过,google 一下就好。我把通讯编码改成 binary ,select 了几段文本,查看二进制表达,确认是 GBK 编码,数据没有(因为升级或后续操作)损坏。打包了一下数据库仓库目录,想着问题总能解决的吧。
我没有正儿八经的用 mysql 开发过,每次用到 mysql ,都是现学现卖。结果 google 了半天没找到解决方案,有点慌了。估计是像我这样跨越 10 年升级的用户太少了。在 mysql 官网上是这样写的:
A special case occurs if you have old tables from before MySQL 4.1 where a nonbinary column contains values that actually are encoded in a character set different from the server's default character set. For example, an application might have stored sjis values in a column, even though MySQL's default character set was different. It is possible to convert the column to use the proper character set but an additional step is required. Suppose that the server's default character set was latin1 and col1 is defined as CHAR(50) but its contents are sjis values. The first step is to convert the column to a binary data type, which removes the existing character set information without performing any character conversion: ... The next step is to convert the column to a nonbinary data type with the proper character set:
简单说就是,先把文本标注成二进制格式,然后再转为你确定的编码。之后就可以正确转换到 UTF-8 了。
但我试了一下还是搞不定,只好在推特上求助。网友中数据库专家肯定比我这种临时抱佛脚翻手册的强多了。感谢热心网友提供了很多方案,甚至私信教我 mysql 。上面的方案我搞不定是因为有些字段做了索引。需要先扔掉索引,转码完了再重建。虽然有人教我,但我对自己能正确操作 mysql 还是没太大信心。就把仓库拖到本地,本地安装了一套 mysql8 做实验。
最后,结合网友的建议以及我自己的判断。我决定先以 binary 传输格式用 mysqldump 导出数据库(大约 500M),然后再用文本转换的方式替换其中的编码,最后再想办法导回。
mysqldump -u root -p --default-character-set=binary
这里导出命令行一定要加 --default-character-set=binary ,否则内码会被当成 latin 而且转换一次,数据是乱的。
一开始觉得挺简单的,查看了导出数据也很完成,不就是 iconv 转换一下么?实际操作发现 iconv 转换有很多错误。如果忽略掉错误,最后就无法导回数据库。我查了一下 dump 文件,发现数据库的数据中居然混杂着一些 utf8 字符串。iconv 无法正确处理这种混杂的编码。而且 mysql 会将部分字符转义,尤其是引号。如果编码转换中除了问题,就有可能吃掉某些引号等有关的格式文本,就变成了错误格式的文件。
所以全文文本替换是有巨大风险的。思来想去,我自己写了个 Lua 程序,最低限度的解析了 dump 文件的词法,只把 binary 字符串挑出来,并对转义符做好转义。将转换过的文本,用自己的代码判断它是 GBK 还是 UTF8 ,挑选出 GBK 交给 iconv 处理,而 UTF-8 则原封不动。最后再将字符串加回转义符,保证符合 mysql 语法。
最终找到了 680 条 UTF-8 文本。我猜测是当年有几天尝试过把 blog 数据转为 UTF-8 编码,又发现不太对劲所以换回来,中间产生的一些混杂编码。
对于转换好的数据,那些字段编码标准还是 latin ,所以用一个简单的文本替换成 utf-8 即可。
sed -i 's/CHARSET=latin1/CHARSET=utf8mb4/g' backup_utf8.sql sed -i 's/COLLATE latin1_swedish_ci/COLLATE utf8mb4_unicode_ci/g' backup_utf8.sql
ps. 在本地 windows 上试验用 source 导入数据库时踩了个小坑。用反斜杠做路径会报错,必须用正斜杠绕开 mysql 的转义。
自此大功告成。
查看系统基本复原后,又连续升级了两个 LTS ,一直升级到 2024 LTS 版本。中间只碰到几个自己动过的软件配置文件问题。简单修一下即可。
估计又有十年可以不折腾它了。
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11841
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
CSS动画曲线函数新增了一个名为linear()的函数,目前所有现代浏览器都已经支持:

一开始,我以为又是什么缝缝补补的新特性,细细一眼就,发现这东西还是挺强的。
之前的动画缓动函数我们都是使用cubic-bezier()函数,贝塞尔曲线实现的,但是这个函数有个问题,最多就4个属性值,只能实现先快后慢,先慢后快,或者一次性bounce缓动。
但是真实的物理动画往往是复杂的,例如一个皮球落地,他不可能只弹一下,往往要蹦很多下,cubic-bezier()函数就无法模拟。
在这种背景下,就设计了linear()函数,虽然是线性函数,但是参数是无限的,于是我们可以通过枚举的方式,可以模拟任何的运动曲线。
举个例子,小球落地的动画效果。
点击下面的篮球,可以查看对应的动画效果:
相关的HTML和CSS代码示意:
<div class="ballX"> <span>🏀</span> </div>
:root {
--bounce-easing: linear(0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765, 1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785, 0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953, 0.973, 1, 0.988, 0.984, 0.988, 1);
}
.ballX {
height: 200px;
border-bottom: solid;
}
.ballX span {
animation: fallDown 1s var(--bounce-easing);
}
@keyframes fallDown {
from { translate: 0 -150px; }
to { translate: 0 0; }
}
如果希望弹动更加逼真,我们可以提高函数参数的精度(切分更细致),就像这样:
:root {
--bounce-easing: --linear(0 0%, 0.0009 0.7117%, 0.0034 1.3049%, 0.0144 2.6097%, 0.0586 5.2195%, 0.1313 7.8292%, 0.2356 10.5575%, 0.5338 16.2515%, 1 23.1317%, 0.8675 25.86%, 0.7715 28.707%, 0.7383 30.1305%, 0.7129 31.6726%, 0.6991 33.0961%, 0.6952 33.9265%, 0.6944 34.6382%, 0.6957 35.3499%, 0.6994 36.0617%, 0.7133 37.4852%, 0.7361 38.9087%, 0.7705 40.4508%, 0.8638 43.4164%, 1 46.6192%, 0.9404 48.5172%, 0.8981 50.4152%, 0.8725 52.3132%, 0.8657 53.2622%, 0.8629 54.3298%, 0.8647 55.2788%, 0.8705 56.2278%, 0.8959 58.2444%, 0.9357 60.1423%, 1 62.3962%, 0.9487 65.1246%, 0.9362 66.4294%, 0.9326 67.1412%, 0.9312 67.8529%, 0.9321 68.5647%, 0.9352 69.2764%, 0.9482 70.6999%, 1 73.5469%, 0.9742 75.4448%, 0.9675 76.3938%, 0.9646 77.4614%, 0.9663 78.4104%, 0.973 79.4781%, 1 81.6133%, 0.986 83.0368%, 0.9824 83.7485%, 0.9811 84.4603%, 0.982 85.172%, 0.9852 85.8837%, 0.9997 87.4259%, 0.9918 88.7307%, 0.9898 89.7983%, 0.9923 90.7473%, 0.9998 91.8149%, 0.9954 92.8826%, 0.9945 93.5943%, 0.9959 94.306%, 1 95.1364%, 0.9981 95.6109%, 0.9972 96.3227%, 1 97.3903%, 0.9988 98.102%, 0.9998 98.8138%, 1 100%);
}
大家无需关心linear()的语法,因为实际使用的时候,都是通过工具生成的。
这里介绍两个工具:
这个工具在上面链接文章的中间部分,如下截图所示,选择对应的动画类型,可以看到对应的运动曲线、效果预览和对应的代码:

这里,重点介绍下上面的工具(仓库地址:linear-easing-generator),因为其支持自定义实现。
网上有很多动画函数的,例如比较经典的Bounce弹动函数:
1 - Math.abs(Math.cos(t * 3.5 * Math.PI) * (1 - t))
使用的时候,我们直接将linear-easing-generator这个页面左侧的函数替换成下图所示的这样:

此时,此工具就会帮我买生成对应的linear()函数代码(如下图所示),其中的CSS变量名称就是self.xxxx后面的属性值决定的,我们还能调整动画的精度。

不得不提一下我的这个Tween.js项目( https://github.com/zhangxinxu/Tween ),目前Star已经破千了。
其中展示了十几个经典的各种缓动函数算法,访问见这里。
不过这些函数都是4个参数(如下图所示),我们需要将部分参数常量化才能使用:

以Bounce.easeOut方法举例,这是其原本的函数:
Bounce.easeOut = function(t, b, c, d) {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
} else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
}
}
我们只需要将b, c, d参数设置为常量值就可以了,如下所示:
self.bounceEaseOut = function(t) {
const d = 1.333;
const b = 0;
const c = 1;
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
} else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
}
}
此时,右下角就可以看到如下所示的linear()函数代码了,学到了木有?
:root {
--bounce-ease-out-easing: linear(0, 0.004, 0.016, 0.035, 0.062, 0.098, 0.141, 0.191, 0.25 24.2%, 0.39, 0.562, 0.765, 1 48.5%, 0.943, 0.893, 0.851, 0.816, 0.788, 0.768, 0.755, 0.75, 0.753, 0.763, 0.782, 0.809, 0.844, 0.888, 0.94, 1, 0.972);
}

linear() 函数的核心在于让动画进度在指定的多个关键点之间以恒定速率变化,但不同区段可以设置不同的进度变化速度,从而形成一种“分段线性”的缓动效果。
可选百分比:你还可以为每个进度点指定一个时间百分比,用以控制该进度点出现在动画持续时间的哪个时刻。如果不指定,浏览器会自动在时间上均匀分布这些进度点。linear() 函数可以用于 animation-timing-function 或 transition-timing-function 属性。
/* 语法 */ animation-timing-function: linear(<点列表>); transition-timing-function: linear(<点列表>);
参数示例与解释
| 缓动函数 | 特点 | 适用场景 |
|---|---|---|
linear() |
分段恒定速度,速度变化直接转折 | 需要精确控制不同阶段进度变化速率的动画 |
linear (关键字) |
从开始到结束完全恒定的速度 | 进度条、机械感强的动画 |
ease (默认) |
慢速开始,加速,然后慢速结束 | 大多数UI交互,如按钮悬停、菜单展开 |
ease-in |
慢速开始,逐渐加速 | 元素从页面中离开 |
ease-out |
快速开始,逐渐减速 | 元素进入页面 |
ease-in-out |
慢速开始和结束,中间较快 | 具有明确开始和结束状态的动画 |
cubic-bezier(n,n,n,n) |
通过贝塞尔曲线自定义速度曲线,可创建非常丰富和自然的加速度变化 | 当预定义函数无法满足需求,需要特定加速度模型时 |
linear() 函数为你提供了另一种控制动画节奏的工具。它不是通过平滑的加速度曲线,而是通过定义多个关键进度点及其(可选的)出现时间,来创建一种分段恒速、速度变化直接转折的动画效果。当你需要实现一些非传统、有特定节奏或阶梯式进度变化的动画时,linear() 会非常有用。
最后,渔获展示时间,周六钓的,钓费120元,两条鱼加起来10斤出头一点,切线两个大的:

😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11841
(本篇完)
This article is a sponsored by Expressive
In the world of modern web design, SVG images are used everywhere, from illustrations to icons to background effects, and are universally prized for their crispness and lightweight size. While static SVG images play an important role in web design, most of the time their true potential is unlocked only when they are combined with motion.
Few things add more life and personality to a website than a well-executed SVG animation. But not all animations have the same impact in terms of digital experience. For example, elastic and bounce effects have a unique appeal in motion design because they bring a sense of realism into movement, making animations more engaging and memorable.
(Large preview)
However, anyone who has dived into animating SVGs knows the technical hurdles involved. Creating a convincing elastic or bounce effect traditionally requires handling complex CSS keyframes or wrestling with JavaScript animation libraries. Even when using an SVG animation editor, it will most likely require you to manually add the keyframes and adjust the easing functions between them, which can become a time-consuming process of trial and error, no matter the level of experience you have.
This is where Expressive Animator shines. It allows creators to apply elastic and bounce effects in seconds, bypassing the tedious work of manual keyframe editing. And the result is always exceptional: animations that feel alive, produced with a fraction of the effort.
Using Expressive Animator To Create An Elastic EffectCreating an elastic effect in Expressive Animator is remarkably simple, fast, and intuitive, since the effect is built right into the software as an easing function. This means you only need two keyframes (start and end) to make the effect, and the software will automatically handle the springy motion in between. Even better, the elastic easing can be applied to any animatable property (e.g., position, scale, rotation, opacity, morph, etc.), giving you a consistent way to add it to your animations.
Before we dive into the tutorial, take a look at the video below to see what you will learn to create and the entire process from start to finish.

Once you hit the “Create project” button, you can use the Pen and Ellipse tools to create the artwork that will be animated, or you can simply copy and paste the artwork below.

Press the A key on your keyboard to switch to the Node tool, then select the String object and move its handle to the center-right point of the artboard. Don’t worry about precision, as the snapping will do all the heavy lifting for you. This will bend the shape and add keyframes for the Morph animator.

Next, press the V key on your keyboard to switch to the Selection tool. With this tool enabled, select the Ball, move it to the right, and place it in the middle of the string. Once again, snapping will do all the hard work, allowing you to position the ball exactly where you want to, while auto-recording automatically adds the appropriate keyframes.

You can now replay the animation and disable auto-recording by clicking on the Auto-Record button again.
As you can see when replaying, the direction in which the String and Ball objects are moving is wrong. Fortunately, we can fix this extremely easily just by reversing the keyframes. To do this, select the keyframes in the timeline and right-click to open the context menu and choose Reverse. This will reverse the keyframes, and if you replay the animation, you will see that the direction is now correct.

With this out of the way, we can finally add the elastic effect. Select all the keyframes in the timeline and click on the Custom easing button to open a dialog with easing options. From the dialog, choose Elastic and set the oscillations to 4 and the stiffness to 2.5.
That’s it! Click anywhere outside the easing dialog to close it and replay the animation to see the result.

The animation can be exported as well. Press Cmd/Ctrl + E on your keyboard to open the export dialog and choose from various export options, ranging from vectorized formats, such as SVG and Lottie, to rasterized formats, such as GIF and video.
For this specific animation, we’re going to choose the SVG export format. Expressive Animator allows you to choose between three different types of SVG, depending on the technology used for animation: SMIL, CSS, or JavaScript.

Each of these technologies has different strengths and weaknesses, but for this tutorial, we are going to choose SMIL. This is because SMIL-based animations are widely supported, even on Safari browsers, and can be used as background images or embedded in HTML pages using the <img> tag. In fact, Andy Clarke recently wrote all about SMIL animations here at Smashing Magazine if you want a full explanation of how it works.
You can visualize the exported SVG in the following CodePen demo:

Elastic and bounce effects have long been among the most desirable but time-consuming techniques in motion design. By integrating them directly into its easing functions, Expressive Animator removes the complexity of manual keyframe manipulation and transforms what used to be a technical challenge into a creative opportunity.
The best part is that getting started with Expressive Animator comes with zero risk. The software offers a full 7–day free trial without requiring an account, so you can download it instantly and begin experimenting with your own designs right away. After the trial ends, you can buy Expressive Animator with a one-time payment, no subscription required. This will give you a perpetual license covering both Windows and macOS.
To help you get started even faster, I’ve prepared some extra resources for you. You’ll find the source files for the animations created in this tutorial, along with a curated list of useful links that will guide you further in exploring Expressive Animator and SVG animation. These materials are meant to give you a solid starting point so you can learn, experiment, and build on your own with confidence.
.eaf source file for the sample animation presented at the beginning of this article..eaf file, this time for the animation we made in this tutorial.by Marius Sarca (hello@smashingmagazine.com) at September 15, 2025 10:00 AM
I once worked with a fleet operations team that monitored dozens of vehicles in multiple cities. Their dashboard showed fuel consumption, live GPS locations, and real-time driver updates. Yet the team struggled to see what needed urgent attention. The problem was not a lack of data but a lack of clear indicators to support decision-making. There were no priorities, alerts, or context to highlight what mattered most at any moment.
Real-time dashboards are now critical decision-making tools in industries like logistics, manufacturing, finance, and healthcare. However, many of them fail to help users make timely and confident decisions, even when they show live data.
Designing for real-time use is very different from designing static dashboards. The challenge is not only presenting metrics but enabling decisions under pressure. Real-time users face limited time and a high cognitive load. They need clarity on actions, not just access to raw data.
This requires interface elements that support quick scanning, pattern recognition, and guided attention. Layout hierarchy, alert colors, grouping, and motion cues all help, but they must be driven by a deeper strategy: understanding what the user must decide in that moment.
This article explores practical UX strategies for real-time dashboards that enable real decisions. Instead of focusing only on visual best practices, it looks at how user intent, personalization, and cognitive flow can turn raw data into meaningful, timely insights.
Designing for Real-Time Comprehension: Helping Users Stay Focused Under PressureA GPS app not only shows users their location but also helps them decide where to go next. In the same way, a real-time dashboard should go beyond displaying the latest data. Its purpose is to help users quickly understand complex information and make informed decisions, especially in fast-paced environments with short attention spans.
Humans have limited cognitive capacity, so they can only process a small amount of data at once. Without proper context or visual cues, rapidly updating dashboards can overwhelm users and shift attention away from key information.
To address this, I use the following approaches:
Many live dashboards fail when treated as static reports instead of dynamic tools for quick decision-making.
In my early projects, I made this mistake, resulting in cluttered layouts, distractions, and frustrated users.
Typical errors include the following:

Under stress, users depend on intuition and focus only on immediately relevant information. If a dashboard updates too quickly or shows conflicting alerts, users may delay actions or make mistakes. It is important to:
In real-time environments, the best dashboards balance speed with calmness and clarity. They are not just data displays but tools that promote live thinking and better decisions.
Many analytics tools let users build custom dashboards, but these design principles guide layouts that support decision-making. Personalization options such as custom metric selection, alert preferences, and update pacing help manage cognitive load and improve data interpretation.
| Cognitive Challenge | UX Risk in Real-Time Dashboards | Design Strategy to Mitigate |
|---|---|---|
| Users can’t track rapid changes | Confusion, missed updates, second-guessing | Use delta indicators, change animations, and trend sparklines |
| Limited working memory | Overload from too many metrics at once | Prioritize key KPIs, apply progressive disclosure |
| Visual clutter under stress | Tunnel vision or misprioritized focus | Apply a clear visual hierarchy, minimize non-critical elements |
| Unclear triggers or alerts | Decision delays, incorrect responses | Use thresholds, binary status indicators, and plain language |
| Lack of context/history | Misinterpretation of sudden shifts | Provide micro-history, snapshot freeze, or hover reveal |
Common Cognitive Challenges in Real-Time Dashboards and UX Strategies to Overcome Them.
Designing For Focus: Using Layout, Color, And Animation To Drive Real-Time DecisionsLayout, color, and animation do more than improve appearance. They help users interpret live data quickly and make decisions under time pressure. Since users respond to rapidly changing information, these elements must reduce cognitive load and highlight key insights immediately.

Layout, color, and animation create an experience that enables fast, accurate interpretation of live data. Real-time dashboards support continuous monitoring and decision-making by reducing mental effort and highlighting anomalies or trends. Personalization allows users to tailor dashboards to their roles, improving relevance and efficiency. For example, operations managers may focus on system health metrics while sales directors prioritize revenue KPIs. This adaptability makes dashboards dynamic, strategic tools.
| Element | Placement & Visual Weight | Purpose & Suggested Colors | Animation Use Case & Effect |
|---|---|---|---|
| Primary KPIs | Center or top-left; bold, large font | Highlight critical metrics; typically stable states | Value updates: smooth increase (200–400 ms) |
| Controls | Top or left panel; light, minimal visual weight | Provide navigation/filtering; neutral color schemes | User actions: subtle feedback (100–150 ms) |
| Charts | Middle or right; medium emphasis | Show trends and comparisons; use blue/green for positives, grey for neutral | Chart trends: trail or fade (300–600 ms) |
| Alerts | Edge of dashboard or floating; high contrast (bold) | Signal critical issues; red/orange for alerts, yellow/amber for warnings | Quick animations for appearance; highlight changes |
Design Elements, Placement, Color, and Motion Strategies for Effective Real-Time Dashboards.
Clarity In Motion: Designing Dashboards That Make Change UnderstandableIf users cannot interpret changes quickly, the dashboard fails regardless of its visual design. Over time, I have developed methods that reduce confusion and make change feel intuitive rather than overwhelming.
One of the most effective tools I use is the sparkline, a compact line chart that shows a trend over time and is typically placed next to a key performance indicator. Unlike full charts, sparklines omit axes and labels. Their simplicity makes them powerful, since they instantly show whether a metric is trending up, down, or steady. For example, placing a sparkline next to monthly revenue immediately reveals if performance is improving or declining, even before the viewer interprets the number.
When using sparklines effectively, follow these principles:
Interactive P&L Performance Dashboard with Forecast and Variance Tracking. (Large preview)
I combine sparklines with directional indicators like arrows and percentage deltas to support quick interpretation.
For example, pairing “▲ +3.2%” with a rising sparkline shows both the direction and scale of change. I do not rely only on color to convey meaning.
Since 1 in 12 men is color-blind, using red and green alone can exclude some users. To ensure accessibility, I add shapes and icons alongside color cues.
Micro-animations provide subtle but effective signals. This counters change blindness — our tendency to miss non-salient changes.
Layout is critical for clarifying change:
For instance, in a logistics dashboard, a card labeled “On-Time Deliveries” may display a weekly sparkline. If performance dips, the line flattens or turns slightly red, a downward arrow appears with a −1.8% delta, and the updated number fades in. This gives instant clarity without requiring users to open a detailed chart.
All these design choices support fast, informed decision-making. In high-velocity environments like product analytics, logistics, or financial operations, dashboards must do more than present data. They must reduce ambiguity and help teams quickly detect change, understand its impact, and take action.
Making Reliability Visible: Designing for Trust In Real-Time Data InterfacesIn real-time data environments, reliability is not just a technical feature. It is the foundation of user trust. Dashboards are used in high-stakes, fast-moving contexts where decisions depend on timely, accurate data. Yet these systems often face less-than-ideal conditions such as unreliable networks, API delays, and incomplete datasets. Designing for these realities is not just damage control. It is essential for making data experiences usable and trustworthy.
When data lags or fails to load, it can mislead users in serious ways:
To mitigate this:
One effective strategy is replacing traditional spinners with skeleton UIs. These are greyed-out, animated placeholders that suggest the structure of incoming data. They set expectations, reduce anxiety, and show that the system is actively working. For example, in a financial dashboard, users might see the outline of a candlestick chart filling in as new prices arrive. This signals that data is being refreshed, not stalled.
When data is unavailable, I show cached snapshots from the most recent successful load, labeled with timestamps such as “Data as of 10:42 AM.” This keeps users aware of what they are viewing.
In operational dashboards such as logistics or monitoring systems, this approach lets users act confidently even when real-time updates are temporarily out of sync.
To handle connectivity failures, I use auto-retry mechanisms with exponential backoff, giving the system several chances to recover quietly before notifying the user.
If retries fail, I maintain transparency with clear banners such as “Offline… Reconnecting…” In one product, this approach prevented users from reloading entire dashboards unnecessarily, especially in areas with unreliable Wi-Fi.
Reliability strongly connects with accessibility:
A compact but powerful pattern I often implement is the Data Freshness Indicator, a small widget that:
This improves transparency and reinforces user control. Since different users interpret these cues differently, advanced systems allow personalization. For example:
Reliability in data visualization is not about promising perfection. It is about creating a resilient, informative experience that supports human judgment by revealing the true state of the system.
When users understand what the dashboard knows, what it does not, and what actions it is taking, they are more likely to trust the data and make smarter decisions.
Real-World Case StudyIn my work across logistics, hospitality, and healthcare, the challenge has always been to distill complexity into clarity. A well-designed dashboard is more than functional; it serves as a trusted companion in decision-making, embedding clarity, speed, and confidence from the start.
A client in the car rental industry struggled with fragmented operational data. Critical details like vehicle locations, fuel usage, maintenance schedules, and downtime alerts were scattered across static reports, spreadsheets, and disconnected systems. Fleet operators had to manually cross-reference data sources, even for basic dispatch tasks, which caused missed warnings, inefficient routing, and delays in response.
We solved these issues by redesigning the dashboard strategically, focusing on both layout improvements and how users interpret and act on information.
Strategic Design Improvements and Outcomes:

Strategic Impact: The dashboard redesign was not only about improving visuals. It changed how teams interacted with data. Operators no longer needed to search for insights, as the system presented them in line with tasks and decision-making. The dashboard became a shared reference for teams with different goals, enabling real-time problem solving, fewer manual checks, and stronger alignment across roles. Every element was designed to build both understanding and confidence in action.
One of our clients, a hospitality group with 11 hotels in the UAE, faced a growing strategic gap. They had data from multiple departments, including bookings, events, food and beverage, and profit and loss, but it was spread across disconnected dashboards.
Strategic Design Improvements and Outcomes:

Strategic Impact: By aligning the dashboard structure with real pricing and revenue strategies, the client shifted from static reporting to forward-looking decision-making. This was not a cosmetic interface update. It was a complete rethinking of how data could support business goals. The result enabled every team, from finance to operations, to interpret data based on their specific roles and responsibilities.
In healthcare, timely and accurate access to patient information is essential. A multi-specialist hospital client struggled with fragmented data. Doctors had to consult separate platforms such as electronic health records, lab results, and pharmacy systems to understand a patient’s condition. This fragmented process slowed decision-making and increased risks to patient safety.
Strategic Design Improvements and Outcomes:

Strategic Impact: Our design encourages active decision-making instead of passive data review. Interactive tooltips ensure visual transparency by explaining the rationale behind alerts and flagged data points. These information boxes give clinicians immediate context, such as why a lab value is marked critical, helping them understand implications and next steps without delay.
Real-time dashboards are not about overwhelming users with data. They are about helping them act quickly and confidently. The most effective dashboards reduce noise, highlight the most important metrics, and support decision-making in complex environments. Success lies in balancing visual clarity with cognitive ease while accounting for human limits like memory, stress, and attention alongside technical needs.
Do:
Don’t:
Over time, I’ve come to see real-time dashboards as decision assistants rather than control panels. When users say, “This helps me stay in control,” it reflects a design built on empathy that respects cognitive limits and enhances decision-making. That is the true measure of success.
by Karan Rawal (hello@smashingmagazine.com) at September 12, 2025 03:00 PM
You can always get a fantastic overview of things in Stephenie Eckles’ article, “Getting Started With CSS Cascade Layers”. But let’s talk about the experience of integrating cascade layers into real-world code, the good, the bad, and the spaghetti.
I could have created a sample project for a classic walkthrough, but nah, that’s not how things work in the real world. I want to get our hands dirty, like inheriting code with styles that work and no one knows why.
Finding projects without cascade layers was easy. The tricky part was finding one that was messy enough to have specificity and organisation issues, but broad enough to illustrate different parts of cascade layers integration.
Ladies and gentlemen, I present you with this Discord bot website by Drishtant Ghosh. I’m deeply grateful to Drishtant for allowing me to use his work as an example. This project is a typical landing page with a navigation bar, a hero section, a few buttons, and a mobile menu.

You see how it looks perfect on the outside. Things get interesting, however, when we look at the CSS styles under the hood.
Understanding The ProjectBefore we start throwing @layers around, let’s get a firm understanding of what we’re working with. I cloned the GitHub repo, and since our focus is working with CSS Cascade Layers, I’ll focus only on the main page, which consists of three files: index.html, index.css, and index.js.
Note: I didn’t include other pages of this project as it’d make this tutorial too verbose. However, you can refactor the other pages as an experiment.
The index.css file is over 450 lines of code, and skimming through it, I can see some red flags right off the bat:
#id selectors, which one might argue shouldn’t be used in CSS (and I am one of those people).#botLogo is defined twice and over 70 lines apart.!important keyword is used liberally throughout the code.And yet the site works. There is nothing “technically” wrong here, which is another reason CSS is a big, beautiful monster — errors are silent!
Planning The Layer StructureNow, some might be thinking, “Can’t we simply move all of the styles into a single layer, like @layer legacy and call it a day?”
You could… but I don’t think you should.
Think about it: If more layers are added after the legacy layer, they should override the styles contained in the legacy layer because the specificity of layers is organized by priority, where the layers declared later carry higher priority.
/* new is more specific */
@layer legacy, new;
/* legacy is more specific */
@layer new, legacy;
That said, we must remember that the site’s existing styles make liberal use of the !important keyword. And when that happens, the order of cascade layers gets reversed. So, even though the layers are outlined like this:
@layer legacy, new;
…any styles with an !important declaration suddenly shake things up. In this case, the priority order becomes:
!important styles in the legacy layer (most powerful),!important styles in the new layer,new layer,legacy layer (least powerful).I just wanted to clear that part up. Let’s continue.
We know that cascade layers handle specificity by creating an explicit order where each layer has a clear responsibility, and later layers always win.
So, I decided to split things up into five distinct layers:
reset: Browser default resets like box-sizing, margins, and paddings.base: Default styles of HTML elements, like body, h1, p, a, etc., including default typography and colours.layout: Major page structure stuff for controlling how elements are positioned.components: Reusable UI segments, like buttons, cards, and menus.utilities: Single helper modifiers that do just one thing and do it well.This is merely how I like to break things out and organize styles. Zell Liew, for example, has a different set of four buckets that could be defined as layers.
There’s also the concept of dividing things up even further into sublayers:
@layer components {
/* sub-layers */
@layer buttons, cards, menus;
}
/* or this: */
@layer components.buttons, components.cards, components.menus;
That might come in handy, but I also don’t want to overly abstract things. That might be a better strategy for a project that’s scoped to a well-defined design system.
Another thing we could leverage is unlayered styles and the fact that any styles not contained in a cascade layer get the highest priority:
@layer legacy { a { color: red !important; } }
@layer reset { a { color: orange !important; } }
@layer base { a { color: yellow !important; } }
/* unlayered */
a { color: green !important; } /* highest priority */
But I like the idea of keeping all styles organized in explicit layers because it keeps things modular and maintainable, at least in this context.
Let’s move on to adding cascade layers to this project.
Integrating Cascade LayersWe need to define the layer order at the top of the file:
@layer reset, base, layout, components, utilities;
This makes it easy to tell which layer takes precedence over which (they get more priority from left to right), and now we can think in terms of layer responsibility instead of selector weight. Moving forward, I’ll proceed through the stylesheet from top to bottom.
First, I noticed that the Poppins font was imported in both the HTML and CSS files, so I removed the CSS import and left the one in index.html, as that’s generally recommended for quickly loading fonts.
Next is the universal selector (*) styles, which include classic reset styles that are perfect for @layer reset:
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
With that out of the way, the body selector is next. I’m putting this into @layer base because it contains core styles for the project, like backgrounds and fonts:
@layer base {
body {
background-image: url("bg.svg"); /* Renamed to bg.svg for clarity */
font-family: "Poppins", sans-serif;
/* ... other styles */
}
}
The way I’m tackling this is that styles in the base layer should generally affect the whole document. So far, no page breaks or anything.
Following the body element selector is the page loader, which is defined as an ID selector, #loader.
I’m a firm believer in using class selectors over ID selectors as much as possible. It keeps specificity low by default, which prevents specificity battles and makes the code a lot more maintainable.
So, I went into the index.html file and refactored elements with id="loader" to class="loader". In the process, I saw another element with id="page" and changed that at the same time.
While still in the index.html file, I noticed a few div elements missing closing tags. It is astounding how permissive browsers are with that. Anyways, I cleaned those up and moved the <script> tag out of the .heading element to be a direct child of body. Let’s not make it any tougher to load our scripts.
Now that we’ve levelled the specificity playing field by moving IDs to classes, we can drop them into the components layer since a loader is indeed a reusable component:
@layer components {
.loader {
width: 100%;
height: 100vh;
/* ... */
}
.loader .loading {
/* ... */
}
.loader .loading span {
/* ... */
}
.loader .loading span:before {
/* ... */
}
}
Next are keyframes, and this was a bit tricky, but I eventually chose to isolate animations in their own new fifth layer and updated the layer order to include it:
@layer reset, base, layout, components, utilities, animations;
But why place animations as the last layer? Because animations are generally the last to run and shouldn’t be affected by style conflicts.
I searched the project’s styles for @keyframes and dumped them into the new layer:
@layer animations {
@keyframes loading {
/* ... */
}
@keyframes loading2 {
/* ... */
}
@keyframes pageShow {
/* ... */
}
}
This gives a clear distinction of static styles from dynamic ones while also enforcing reusability.
The #page selector also has the same issue as #id, and since we fixed it in the HTML earlier, we can modify it to .page and drop it in the layout layer, as its main purpose is to control the initial visibility of the content:
@layer layout {
.page {
display: none;
}
}
Where do we put these? Scrollbars are global elements that persist across the site. This might be a gray area, but I’d say it fits perfectly in @layer base since it’s a global, default feature.
@layer base {
/* ... */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #0e0e0f;
}
::-webkit-scrollbar-thumb {
background: #5865f2;
border-radius: 100px;
}
::-webkit-scrollbar-thumb:hover {
background: #202225;
}
}
I also removed the !important keywords as I came across them.
The nav element is pretty straightforward, as it is the main structure container that defines the position and dimensions of the navigation bar. It should definitely go in the layout layer:
@layer layout {
/* ... */
nav {
display: flex;
height: 55px;
width: 100%;
padding: 0 50px; /* Consistent horizontal padding */
/* ... */
}
}
We have three style blocks that are tied to the logo: nav .logo, .logo img, and #botLogo. These names are redundant and could benefit from inheritance component reusability.
Here’s how I’m approaching it:
nav .logo is overly specific since the logo can be reused in other places. I dropped the nav so that the selector is just .logo. There was also an !important keyword in there, so I removed it..logo to be a Flexbox container to help position .logo img, which was previously set with less flexible absolute positioning.#botLogo ID is declared twice, so I merged the two rulesets into one and lowered its specificity by making it a .botLogo class. And, of course, I updated the HTML to replace the ID with the class..logo img selector becomes .botLogo, making it the base class for styling all instances of the logo.Now, we’re left with this:
/* initially .logo img */
.botLogo {
border-radius: 50%;
height: 40px;
border: 2px solid #5865f2;
}
/* initially #botLogo */
.botLogo {
border-radius: 50%;
width: 180px;
/* ... */
}
The difference is that one is used in the navigation and the other in the hero section heading. We can transform the second .botLogo by slightly increasing the specificity with a .heading .botLogo selector. We may as well clean up any duplicated styles as we go.
Let’s place the entire code in the components layer as we’ve successfully turned the logo into a reusable component:
@layer components {
/* ... */
.logo {
font-size: 30px;
font-weight: bold;
color: #fff;
display: flex;
align-items: center;
gap: 10px;
}
.botLogo {
aspect-ratio: 1; /* maintains square dimensions with width */
border-radius: 50%;
width: 40px;
border: 2px solid #5865f2;
}
.heading .botLogo {
width: 180px;
height: 180px;
background-color: #5865f2;
box-shadow: 0px 0px 8px 2px rgba(88, 101, 242, 0.5);
/* ... */
}
}
This was a bit of work! But now the logo is properly set up as a component that fits perfectly in the new layer architecture.
This is a typical navigation pattern. Take an unordered list (<ul>) and turn it into a flexible container that displays all of the list items horizontally on the same row (with wrapping allowed). It’s a type of navigation that can be reused, which belongs in the components layer. But there’s a little refactoring to do before we add it.
There’s already a .mainMenu class, so let’s lean into that. We’ll swap out any nav ul selectors with that class. Again, it keeps specificity low while making it clearer what that element does.
@layer components {
/* ... */
.mainMenu {
display: flex;
flex-wrap: wrap;
list-style: none;
}
.mainMenu li {
margin: 0 4px;
}
.mainMenu li a {
color: #fff;
text-decoration: none;
font-size: 16px;
/* ... */
}
.mainMenu li a:where(.active, .hover) {
color: #fff;
background: #1d1e21;
}
.mainMenu li a.active:hover {
background-color: #5865f2;
}
}
There are also two buttons in the code that are used to toggle the navigation between “open” and “closed” states when the navigation is collapsed on smaller screens. It’s tied specifically to the .mainMenu component, so we’ll keep everything together in the components layer. We can combine and simplify the selectors in the process for cleaner, more readable styles:
@layer components {
/* ... */
nav:is(.openMenu, .closeMenu) {
font-size: 25px;
display: none;
cursor: pointer;
color: #fff;
}
}
I also noticed that several other selectors in the CSS were not used anywhere in the HTML. So, I removed those styles to keep things trim. There are automated ways to go about this, too.
Should media queries have a dedicated layer (@layer responsive), or should they be in the same layer as their target elements? I really struggled with that question while refactoring the styles for this project. I did some research and testing, and my verdict is the latter, that media queries ought to be in the same layer as the elements they affect.
My reasoning is that keeping them together:
However, it also means responsive logic is scattered across layers. But it beats the one with a gap between the layer where elements are styled and the layer where their responsive behaviors are managed. That’s a deal-breaker for me because it’s way too easy to update styles in one layer and forget to update their corresponding responsive style in the responsive layer.
The other big point is that media queries in the same layer have the same priority as their elements. This is consistent with my overall goal of keeping the CSS Cascade simple and predictable, free of style conflicts.
Plus, the CSS nesting syntax makes the relationship between media queries and elements super clear. Here’s an abbreviated example of how things look when we nest media queries in the components layer:
@layer components {
.mainMenu {
display: flex;
flex-wrap: wrap;
list-style: none;
}
@media (max-width: 900px) {
.mainMenu {
width: 100%;
text-align: center;
height: 100vh;
display: none;
}
}
}
This also allows me to nest a component’s child element styles (e.g., nav .openMenu and nav .closeMenu).
@layer components {
nav {
&.openMenu {
display: none;
@media (max-width: 900px) {
&.openMenu {
display: block;
}
}
}
}
}
The .title and .subtitle can be seen as typography components, so they and their responsive associates go into — you guessed it — the components layer:
@layer components {
.title {
font-size: 40px;
font-weight: 700;
/* etc. */
}
.subtitle {
color: rgba(255, 255, 255, 0.75);
font-size: 15px;
/* etc.. */
}
@media (max-width: 420px) {
.title {
font-size: 30px;
}
.subtitle {
font-size: 12px;
}
}
}
What about buttons? Like many website’s this one has a class, .btn, for that component, so we can chuck those in there as well:
@layer components {
.btn {
color: #fff;
background-color: #1d1e21;
font-size: 18px;
/* etc. */
}
.btn-primary {
background-color: #5865f2;
}
.btn-secondary {
transition: all 0.3s ease-in-out;
}
.btn-primary:hover {
background-color: #5865f2;
box-shadow: 0px 0px 8px 2px rgba(88, 101, 242, 0.5);
/* etc. */
}
.btn-secondary:hover {
background-color: #1d1e21;
background-color: rgba(88, 101, 242, 0.7);
}
@media (max-width: 420px) {
.btn {
font-size: 14px;
margin: 2px;
padding: 8px 13px;
}
}
@media (max-width: 335px) {
.btn {
display: flex;
flex-direction: column;
}
}
}
We haven’t touched the utilities layer yet! I’ve reserved this layer for helper classes that are designed for specific purposes, like hiding content — or, in this case, there’s a .noselect class that fits right in. It has a single reusable purpose: to disable selection on an element.
So, that’s going to be the only style rule in our utilities layer:
@layer utilities {
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-webkit-user-drag: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}
And that’s it! We’ve completely refactored the CSS of a real-world project to use CSS Cascade Layers. You can compare where we started with the final code.
It Wasn’t All EasyThat’s not to say that working with Cascade Layers was challenging, but there were some sticky points in the process that forced me to pause and carefully think through what I was doing.
I kept some notes as I worked:
!important keyword is a juggling act.Overall, refactoring a codebase for CSS Cascade Layers is a bit daunting at first glance. The important thing, though, is to acknowledge that it isn’t really the layers that complicate things, but the existing codebase.
It’s tough to completely overhaul someone’s existing approach for a new one, even if the new approach is elegant.
Where Cascade Layers Helped (And Didn’t)Establishing layers improved the code, no doubt. I’m sure there are some performance benchmarks in there since we were able to remove unused and conflicting styles, but the real win is in a more maintainable set of styles. It’s easier to find what you need, know what specific style rules are doing, and where to insert new styles moving forward.
At the same time, I wouldn’t say that Cascade Layers are a silver bullet solution. Remember, CSS is intrinsically tied to the HTML structure it queries. If the HTML you’re working with is unstructured and suffers from div-itus, then you can safely bet that the effort to untangle that mess is higher and involves rewriting markup at the same time.
Refactoring CSS for cascade layers is most certainly worth the maintenance enhancements alone.
It may be “easier” to start from scratch and define layers as you work from the ground up because there’s less inherited overhead and technical debt to sort through. But if you have to start from an existing codebase, you might need to de-tangle the complexity of your styles first to determine exactly how much refactoring you’re looking at.
by Victor Ayomipo (hello@smashingmagazine.com) at September 10, 2025 10:00 AM
最近在做游戏时,发现在立即模式下鼠标的交互部分实现的比较混乱。
在做引擎时,我简单留出了鼠标相关事件的 callback 接口。一开始写游戏时,也就是对付一下写了几行代码,大致可以工作。做了大半个月后,随着交互界面越来越复杂,那些应付用的代码明显不堪重负。有越来越多的边界情况无法正确处理。等到最近想在交互上加一种长按鼠标确认的操作,发现不能再这样对付下去了,就花了一晚上重构了所有和鼠标交互相关的代码。
之前的问题出在哪里?
如果从系统的鼠标消息出发,我们可以从引擎获得鼠标移动、按下、抬起等事件。或许还可以利用系统发送来的点击、双击等等复合事件,但我选择自己用按下和抬起来判断这些。但是,消息机制本身和立即模式是相悖的。采用立即模式编写游戏业务以及交互,获得当下的状态是最自然的,而“消息”不是状态。它是一个队列:在每个游戏帧中,可能没有消息,也可能有多条消息。如果只是处理鼠标的位置和按键状态,那么保留最后一个状态也可以;但是,像点击这种行为,明显不是瞬间的状态,而是过去一段时间的状态叠加后的事件。
除了“点击”,必须处理的还有“焦点”,或叫“悬停”。一个交互元素获得焦点、失去焦点都不是瞬间状态,它们取决于鼠标过去的位置、鼠标位置下交互元素在屏幕上的位置。即使鼠标不移动,但交互元素在屏幕上动了,或消失了、出现了,都可能引起“焦点”的改变。
所以在立即模式下,最好我们可以将点击和焦点这样的“事件”变成某种“状态”,然后统一用立即模式处理。否则混用立即模式的状态判断和消息队列轮询就会比较混乱。
首先,我们应该把系统传来的鼠标消息在帧间累积起来,然后在每个游戏帧发送出去,而不应该在消息抵达的时候立即处理。这样做可以让游戏代码严格的按帧执行,帧间不会触发任何额外的 callback 。所以,在立即模式下,底层传来的不是鼠标移动消息,而是每帧鼠标的位置。即使没有更新位置,也同样会刷新一次鼠标位置信息。如果两帧之间有多个鼠标移动消息,位置当然只需要记录最后一次,游戏可以忽略中间的轨迹(除非以后要做鼠标手势,那再来改进)。
但鼠标按键则不可只保留多个按压抬起事件的最后一个。比如之前如果鼠标处于按下状态、而在两帧之间鼠标按键抬起又按下,如果只取最后的按键状态,就没有改变(都是按下状态),但操作者实际点击了一次鼠标。这个点击操作就被忽略掉了,这是不行的。
那应该怎么处理?首先,鼠标按键的状态和点击应该分离。如果游戏需要查询鼠标按键是抬起还是按下,那么和鼠标位置一样,每帧逻辑都会被推送这些状态信息。但鼠标的点击行为,应该是另一种独立状态:在鼠标的按键按下时,底层应该记录这个时刻的帧序号,并不立即通知游戏。而当鼠标抬起时,就改变了“鼠标点击”的状态。这个鼠标点击的状态为空时,表示点击并未发生,不为空时,状态值是点击的时长:即按下到抬起的帧数。
对于前面举例的情况,如果在帧间依次发生了抬起和按下,“鼠标点击”状态也会从空转换为这次点击的时长。同时底层会重置按下时刻,等待下一次抬起后再改变状态。在极端情况下,如果两帧之间连续发生了非常多次按下和抬起,我情况于只记录第一次的“鼠标点击”时长。除非以后要支持“双击”,那也是另一种手势,需要额外实现了。“鼠标点击”这个状态只会存在一帧,无论这一帧游戏代码有没有检查使用这个状态,该状态都会重置为空。
其次,我们需要一个焦点管理器。鼠标焦点永远只能在一个对象上。立即模式的交互层可以和立即模式的渲染层一样,每帧遍历所有的对象,渲染层将可渲染对象按层次和次序提交给底层;交互层则是按层次和次序依次判断每个对象是否获得了焦点。交互层和渲染层都和对象的屏幕空间位置有关,所以两者其实可以做到一起;当然也可以分开,因为未必所有的对象都同时需要渲染和鼠标交互。
对于焦点管理器,它可以每帧简单的把当前焦点对象以一个状态量提供。因为查询是哪个对象获得了焦点需要遍历一次所有对象,这在渲染时就会做一遍,所以一般我们可以将上一帧的焦点传给当前帧,交互在视觉上差一帧问题不是很大。
和查询当前鼠标按键状态一样,游戏逻辑可以查询当前的焦点是谁。但它和“鼠标点击”结合起来使用就不太方便。所谓鼠标点击,通常指鼠标按下的那一刻,鼠标焦点在一个对象上,而抬起时鼠标焦点还在同一个对象上,才能视为点击了这个对象。简单用鼠标抬起那一刻鼠标焦点的对象不太符合一般的使用习惯。所以,我们可以把“焦点”这个状态加上当前焦点对象获得焦点持续的帧数。这样,想知道“鼠标点击”发生时,是否真的点击了当前焦点对象,只需要比较两个时长即可:“鼠标点击”的时长不能大于“焦点”的持续时长。
有了以上这个基础,我们在编写游戏时就可以方便的以立即模式处理每帧的业务:获得当前帧鼠标的位置、按键状态、鼠标每个按键点击的状态(为空或一个时长)、当前鼠标焦点的状态(焦点对象及焦点持续时长)……
在这个基础上,还可以再做一些封装。因为某些模块从效率考虑并不适合每帧都刷新状态。比如交互界面,只有在焦点状态发生改变时,它的属性才会改变:按钮的视觉效果、屏幕提示文字、等等。这些属性改变的成本比较高,不适合每帧都重置。我们可以把每帧的鼠标焦点再记录下来,只有焦点发生改变时,才做额外处理。这个记录焦点状态变化的东西可以放在栈上的临时结构。立即模式比较适合以自然方式书写业务。
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11847
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

Chrome 139浏览器除了新支持了 CSS if()函数(参见“CSS倒反天罡居然支持if()函数了”一文),还支持了期待已久的@function规则。

@function规则可以让我们自定义运算逻辑,比之前单纯的CSS var()变量要更加强大。
下面我们通过几个案例带大家快速了解下@function规则的用法和作用。
先看下面这段代码,是我实际项目中代码的截图,大家注意看红框框部分:

字符版如下:
.board-bar-x {
/* 略... */
--bottom: calc(var(--offset-bottom, 0px) + 15px + 0.25rem);
--bottom2: calc(var(--offset-bottom2, 0px) + 15px + 0.25rem);
--size: calc(20px + calc((var(--char-length) + 1) * 1ch));
--size2: calc(20px + calc((var(--char-length2) + 1) * 1ch));
}
实现功能是使用::before和::after伪元素绘制两条线。
在没有@function规则之前,当一个复杂计算需要多处使用的时候,我会定义专门的CSS变量进行简化,例如上面的--bottom和--bottom2,可即便如此,后面的计算逻辑我还是要一个一个罗列。
上面这句话有些人估计不明白,那我重新处理下,如果不使用CSS变量进行二次变量处理,那么实际的CSS代码会是这样(仅展示关键部分):
.board-bar-x::before {
position: absolute;
bottom: calc(var(--offset-bottom, 0px) + 15px + 0.25rem);
padding-bottom: calc(var(--offset-bottom, 0px) + 15px + 0.25rem);
}
.board-bar-x::after {
position: absolute;
bottom: calc(var(--offset-bottom2, 0px) + 15px + 0.25rem);
padding-bottom: calc(var(--offset-bottom2, 0px) + 15px + 0.25rem);
}
会出现4个冗长的CSS属性值,使用CSS变量简化后,就会是这样:
.board-bar-x::before {
position: absolute;
bottom: var(--bottom);
padding-bottom: var(--bottom);
}
.board-bar-x::after {
position: absolute;
bottom: var(--bottom2);
padding-bottom: var(--bottom2);
}
但是,定义变量的那个地方,还是需要一一罗列:
.board-bar-x {
--bottom: calc(var(--offset-bottom, 0px) + 15px + 0.25rem);
--bottom2: calc(var(--offset-bottom2, 0px) + 15px + 0.25rem);
}
CSS @function就可以将这种罗列,也一并处理掉。代码示意:
@function --fn(--offset) {
result: calc(var(--offset, 0px) + 15px + 0.25rem)
}
.board-bar-x {
--bottom: --fn(var(--offset-bottom));
--bottom2: --fn(var(--offset-bottom2));
}
.board-bar-x::before {
position: absolute;
bottom: var(--bottom);
padding-bottom: var(--bottom);
}
.board-bar-x::after {
position: absolute;
bottom: var(--bottom2);
padding-bottom: var(--bottom2);
}
或者不再使用CSS变量过渡,直接一步到位。
@function --fn (--offset) {
result: calc(var(--offset, 0px) + 15px + 0.25rem)
}
.board-bar-x::before {
position: absolute;
bottom: --fn(var(--offset-bottom));
padding-bottom: --fn(var(--offset-bottom));
}
.board-bar-x::after {
position: absolute;
bottom: --fn(var(--offset-bottom2));
padding-bottom: --fn(var(--offset-bottom2));
}
是不是就容易理解多了?
CSS代码如下所示:
/* 返回当前值的负值 */
@function --negate(--value) {
result: calc(-1 * var(--value));
}
aside {
--size: 999em;
padding-bottom: var(--size);
margin-bottom: --negate(var(--size));
}
/* 在小于640px的屏幕上为侧边栏占用1fr的空间,在较大的屏幕上使用相对具体具体宽度值 */
@function --layout-sidebar(--sidebar-width: max(20ch, 20vw)) {
result: 1fr;
@media (width > 640px) {
result: var(--sidebar-width) auto;
}
}
.layout {
display: grid;
/*侧边栏宽度兜底20ch和20vw的较大计算值 */
grid-template-columns: --layout-sidebar();
}
实时效果如下所示(拖拽右下角的小按钮,改变宽度,看看布局有没有变化):
示意代码:
@function --light-dark(--light, --dark) {
result: if(
style(--scheme: dark): var(--dark);
else: var(--light)
);
}
只要在body或者HTML元素上设置 style="--scheme: dark",就会自动使用黑夜模式的变量。
CSS 的 @function 规则为样式表带来了真正的编程能力,允许你定义可重用的计算逻辑,让 CSS 代码更灵活、模块化且易于维护。
使用 @function 定义,result 返回结果,示意:
@function --fn-name(arg) {
result: ...
}
支持传递参数,并可设置默认值。
例如:
@function --border(--color: red) { ... }
支持声明参数类型(如 length, color, number, angle),增强健壮性。
函数内部可以调用其他函数,实现逻辑复用,或者使用嵌套其他规则,上面有案例。
总结一下 CSS @function 通过引入参数、返回值、类型检查和组合能力,让 CSS 拥有了更强大的抽象和复用能力。它是实现 “CSS 即设计系统” 理念的重要工具,能有效提升大型或动态项目的样式代码质量。
OK,以上就是本文的全部内容,如果你觉得还不错,欢迎转发,点赞,分享。
😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11847
(本篇完)
Having covered the developmental history and legacy of TV in Part 1, let’s now delve into more practical matters. As a quick reminder, the “10-foot experience” and its reliance on the six core buttons of any remote form the basis of our efforts, and as you’ll see, most principles outlined simply reinforce the unshakeable foundations.
In this article, we’ll sift through the systems, account for layout constraints, and distill the guidelines to understand the essence of TV interfaces. Once we’ve collected all the main ingredients, we’ll see what we can do to elevate these inherently simplistic experiences.
Let’s dig in, and let’s get practical!
The SystemsWhen it comes to hardware, TVs and set-top boxes are usually a few generations behind phones and computers. Their components are made to run lightweight systems optimised for viewing, energy efficiency, and longevity. Yet even within these constraints, different platforms offer varying performance profiles, conventions, and price points.
Some notable platforms/systems of today are:
Despite their differences, all of the platforms above share something in common, and by now you’ve probably guessed that it has to do with the remote. Let’s take a closer look:

If these remotes were stripped down to just the D-pad, OK, and BACK buttons, they would still be capable of successfully navigating any TV interface. It is this shared control scheme that allows for the agnostic approach of this article with broadly applicable guidelines, regardless of the manufacturer.
Having already discussed the TV remote in detail in Part 1, let’s turn to the second part of the equation: the TV screen, its layout, and the fundamental building blocks of TV-bound experiences.
TV Design FundamentalsWith almost one hundred years of legacy, TV has accumulated quite some baggage. One recurring topic in modern articles on TV design is the concept of “overscan” — a legacy concept from the era of cathode ray tube (CRT) screens. Back then, the lack of standards in production meant that television sets would often crop the projected image at its edges. To address this inconsistency, broadcasters created guidelines to keep important content from being cut off.

While overscan gets mentioned occasionally, we should call it what it really is — a thing of the past. Modern panels display content with greater precision, making thinking in terms of title and action safe areas rather archaic. Today, we can simply consider the margins and get the same results.

Google calls for a 5% margin layout and Apple advises a 60-point margin top and bottom, and 80 points on the sides in their Layout guidelines. The standard is not exactly clear, but the takeaway is simple: leave some breathing room between screen edge and content, like you would in any thoughtful layout.

Having left some baggage behind, we can start considering what to put within and outside the defined bounds.
Considering the device is made for content consumption, streaming apps such as Netflix naturally come to mind. Broadly speaking, all these interfaces share a common layout structure where a vast collection of content is laid out in a simple grid.

These horizontally scrolling groups (sometimes referred to as “shelves”) resemble rows of a bookcase. Typically, they’ll contain dozens of items that don’t fit into the initial “fold”, so we’ll make sure the last visible item “peeks” from the edge, subtly indicating to the viewer there’s more content available if they continue scrolling.
If we were to define a standard 12-column layout grid, with a 2-column-wide item, we’d end up with something like this:

As you can see, the last item falls outside the “safe” zone.
Tip: A useful trick I discovered when designing TV interfaces was to utilise an odd number of columns. This allows the last item to fall within the defined margins and be more prominent while having little effect on the entire layout. We’ve concluded that overscan is not a prominent issue these days, yet an additional column in the layout helps completely circumvent it. Food for thought!

TV design requires us to practice restraint, and this becomes very apparent when working with type. All good typography practices apply to TV design too, but I’d like to point out two specific takeaways.
First, accounting for the distance, everything (including type) needs to scale up. Where 16–18px might suffice for web baseline text, 24px should be your starting point on TV, with the rest of the scale increasing proportionally.
“Typography can become especially tricky in 10-ft experiences. When in doubt, go larger.”
— Molly Lafferty (Marvel Blog)
With that in mind, the second piece of advice would be to start with a small 5–6 size scale and adjust if necessary. The simplicity of a TV experience can, and should, be reflected in the typography itself, and while small, such a scale will do all the “heavy lifting” if set correctly.

What you see in the example above is a scale I reduced from Google and Apple guidelines, with a few size adjustments. Simple as it is, this scale served me well for years, and I have no doubt it could do the same for you.
If you’d like to use my basic reduced type scale Figma design file for kicking off your own TV project, feel free to do so!

Imagine watching TV at night with the device being the only source of light in the room. You open up the app drawer and select a new streaming app; it loads into a pretty splash screen, and — bam! — a bright interface opens up, which, amplified by the dark surroundings, blinds you for a fraction of a second. That right there is our main consideration when using color on TV.
Built for cinematic experiences and often used in dimly lit environments, TVs lend themselves perfectly to darker and more subdued interfaces. Bright colours, especially pure white (#ffffff), will translate to maximum luminance and may be straining on the eyes. As a general principle, you should rely on a more muted color palette. Slightly tinting brighter elements with your brand color, or undertones of yellow to imitate natural light, will produce less visually unsettling results.
Finally, without a pointer or touch capabilities, it’s crucial to clearly highlight interactive elements. While using bright colors as backdrops may be overwhelming, using them sparingly to highlight element states in a highly contrasting way will work perfectly.
A focus state is the underlying principle of TV navigation. Most commonly, it relies on creating high contrast between the focused and unfocused elements. (Large preview)
This highlighting of UI elements is what TV leans on heavily — and it is what we’ll discuss next.
In Part 1, we have covered how interacting through a remote implies a certain detachment from the interface, mandating reliance on a focus state to carry the burden of TV interaction. This is done by visually accenting elements to anchor the user’s eyes and map any subsequent movement within the interface.
If you have ever written HTML/CSS, you might recall the use of the :focus CSS pseudo-class. While it’s primarily an accessibility feature on the web, it’s the core of interaction on TV, with more flexibility added in the form of two additional directions thanks to a dedicated D-pad.
There are a few standard ways to style a focus state. Firstly, there’s scaling — enlarging the focused element, which creates the illusion of depth by moving it closer to the viewer.
Example of scaling elements on focus. This is especially common in cases where only images are used for focusable elements. (Large preview)
Another common approach is to invert background and text colors.
Color inversion on focus, common for highlighting cards. (Large preview)
Finally, a border may be added around the highlighted element.
Example of border highlights on focus. (Large preview)
These styles, used independently or in various combinations, appear in all TV interfaces. While execution may be constrained by the specific system, the purpose remains the same: clear and intuitive feedback, even from across the room.
The three basic styles can be combined to produce more focus state variants. (Large preview)
Having set the foundations of interaction, layout, and movement, we can start building on top of them. The next chapter will cover the most common elements of a TV interface, their variations, and a few tips and tricks for button-bound navigation.
Common TV UI ComponentsNowadays, the core user journey on television revolves around browsing (or searching through) a content library, selecting an item, and opening a dedicated screen to watch or listen.
This translates into a few fundamental screens:
These screens are built with a handful of components optimized for the 10-foot experience, and while they are often found on other platforms too, it’s worth examining how they differ on TV.
Appearing as a horizontal bar along the top edge of the screen, or as a vertical sidebar, the menu helps move between the different screens of an app. While its orientation mostly depends on the specific system, it does seem TV favors the side menu a bit more.

Both menu types share a common issue: the farther the user navigates away from the menu (vertically, toward the bottom for top-bars; and horizontally, toward the right for sidebars), the more button presses are required to get back to it. Fortunately, usually a Back button shortcut is added to allow for immediate menu focus, which greatly improves usability.

16:9 posters abide by the same principles but with a horizontal orientation. They are often paired with text labels, which effectively turn them into cards, commonly seen on platforms like YouTube. In the absence of dedicated poster art, they show stills or playback from the videos, matching the aspect ratio of the media itself.

1:1 posters are often found in music apps like Spotify, their shape reminiscent of album art and vinyl sleeves. These squares often get used in other instances, like representing channel links or profile tiles, giving more visual variety to the interface.

All of the above can co-exist within a single app, allowing for richer interfaces and breaking up otherwise uniform content libraries.
And speaking of breaking up content, let’s see what we can do with spotlights!
Typically taking up the entire width of the screen, these eye-catching components will highlight a new feature or a promoted piece of media. In a sea of uniform shelves, they can be placed strategically to introduce aesthetic diversity and disrupt the monotony.

A spotlight can be a focusable element by itself, or it could expose several actions thanks to its generous space. In my ventures into TV design, I relied on a few different spotlight sizes, which allowed me to place multiples into a single row, all with the purpose of highlighting different aspects of the app, without breaking the form to which viewers were used.


Posters, cards, and spotlights shape the bulk of the visual experience and content presentation, but viewers still need a way to find specific titles. Let’s see how search and input are handled on TV.
Manually browsing through content libraries can yield results, but having the ability to search will speed things up — though not without some hiccups.
TVs allow for text input in the form of on-screen keyboards, similar to the ones found in modern smartphones. However, inputting text with a remote control is quite inefficient given the restrictiveness of its control scheme. For example, typing “hey there” on a mobile keyboard requires 9 keystrokes, but about 38 on a TV (!) due to the movement between characters and their selection.
Typing with a D-pad may be an arduous task, but at the same time, having the ability to search is unquestionably useful.

Luckily for us, keyboards are accounted for in all systems and usually come in two varieties. We’ve got the grid layouts used by most platforms and a horizontal layout in support of the touch-enabled and gesture-based controls on tvOS. Swiping between characters is significantly faster, but this is yet another pattern that can only be enhanced, not replaced.

Modernization has made things significantly easier, with search autocomplete suggestions, device pairing, voice controls, and remotes with physical keyboards, but on-screen keyboards will likely remain a necessary fallback for quite a while. And no matter how cumbersome this fallback may be, we as designers need to consider it when building for TV.
While all the different sections of a TV app serve a purpose, the Player takes center stage. It’s where all the roads eventually lead to, and where viewers will spend the most time. It’s also one of the rare instances where focus gets lost, allowing for the interface to get out of the way of enjoying a piece of content.
Arguably, players are the most complex features of TV apps, compacting all the different functionalities into a single screen. Take YouTube, for example, its player doesn’t just handle expected playback controls but also supports content browsing, searching, reading comments, reacting, and navigating to channels, all within a single screen.

Compared to YouTube, Netflix offers a very lightweight experience guided by the nature of the app.
Still, every player has a basic set of controls, the foundation of which is the progress bar.

The progress bar UI element serves as a visual indicator for content duration. During interaction, focus doesn’t get placed on the bar itself, but on a movable knob known as the “scrubber.” It is by moving the scrubber left and right, or stopping it in its tracks, that we can control playback.
Another indirect method of invoking the progress bar is with the good old Play and Pause buttons. Rooted in the mechanical era of tape players, the universally understood triangle and two vertical bars are as integral to the TV legacy as the D-pad. No matter how minimalist and sleek the modern player interface may be, these symbols remain a staple of the viewing experience.

The presence of a scrubber may also indicate the type of content. Video on demand allows for the full set of playback controls, while live streams (unless DVR is involved) will do away with the scrubber since viewers won’t be able to rewind or fast-forward.
Earlier iterations of progress bars often came bundled with a set of playback control buttons, but as viewers got used to the tools available, these controls often got consolidated into the progress bar and scrubber themselves.
With the building blocks out of the box, we’ve got everything necessary for a basic but functional TV app. Just as the six core buttons make remote navigation possible, the components and principles outlined above help guide purposeful TV design. The more context you bring, the more you’ll be able to expand and combine these basic principles, creating an experience unique to your needs.
Before we wrap things up, I’d like to share a few tips and tricks I discovered along the way — tips and tricks which I wish I had known from the start. Regardless of how simple or complex your idea may be, these may serve you as useful tools to help add depth, polish, and finesse to any TV experience.
Thinking Beyond The BasicsLike any platform, TV has a set of constraints that we abide by when designing. But sometimes these norms are applied without question, making the already limited capabilities feel even more restraining. Below are a handful of less obvious ideas that can help you design more thoughtfully and flexibly for the big screen.
Most modern remotes support press-and-hold gestures as a subtle way to enhance the functionality, especially on remotes with fewer buttons available.
For example, holding directional buttons when browsing content speeds up scrolling, while holding Left/Right during playback speeds up timeline seeking. In many apps, a single press of the OK button opens a video, but holding it for longer opens a contextual menu with additional actions.

With limited input, context becomes a powerful tool. It not only declutters the interface to allow for more focus on specific tasks, but also enables the same set of buttons to trigger different actions based on the viewer’s location within an app.
Another great example is YouTube’s scrubber interaction. Once the scrubber is moved, every other UI element fades. This cleans up the viewer’s working area, so to speak, narrowing the interface to a single task. In this state — and only in this state — pressing Up one more time moves away from scrubbing and into browsing by chapter.
This is such an elegant example of expanding restraint, and adding more only when necessary. I hope it inspires similar interactions in your TV app designs.
At its best, every action on TV “costs” at least one click. There’s no such thing as aimless cursor movement — if you want to move, you must press a button. We’ve seen how cumbersome it can be inside a keyboard, but there’s also something we can learn about efficient movement in these restrained circumstances.
Going back to the Homescreen, we can note that vertical and horizontal movement serve two distinct roles. Vertical movement switches between groups, while horizontal movement switches items within these groups. No matter how far you’ve gone inside a group, a single vertical click will move you into another.
Every step on TV “costs” an action, so we might as well optimize movement. (Large preview)
This subtle difference — two axes with separate roles — is the most efficient way of moving in a TV interface. Reversing the pattern: horizontal to switch groups, and vertical to drill down, will work like a charm as long as you keep the role of each axis well defined.
Properly applied in a vertical layout, the principles of optimal movement remain the same. (Large preview)
Quietly brilliant and easy to overlook, this pattern powers almost every step of the TV experience. Remember it, and use it well.
After covering in detail many of the technicalities, let’s finish with some visual polish.
Most TV interfaces are driven by tightly packed rows of cover and poster art. While often beautifully designed, this type of content and layouts leave little room for visual flair. For years, the flat JPG, with its small file size, has been a go-to format, though contemporary alternatives like WebP are slowly taking its place.
Meanwhile, we can rely on the tried and tested PNG to give a bit more shine to our TV interfaces. The simple fact that it supports transparency can help the often-rigid UIs feel more sophisticated. Used strategically and paired with simple focus effects such as background color changes, PNGs can bring subtle moments of delight to the interface.
Having a transparent background blends well with surface color changes common in TV interfaces. (Large preview)
And don’t forget, transparency doesn’t have to mean that there shouldn't be any background at all. (Large preview)
Moreover, if transformations like scaling and rotating are supported, you can really make those rectangular shapes come alive with layering multiple assets.
Combining multiple images along with a background color change can liven up certain sections. (Large preview)
As you probably understand by now, these little touches of finesse don’t go out of bounds of possibility. They simply find more room to breathe within it. But with such limited capabilities, it’s best to learn all the different tricks that can help make your TV experiences stand out.
Closing ThoughtsRooted in legacy, with a limited control scheme and a rather “shallow” interface, TV design reminds us to do the best with what we have at our disposal. The restraints I outlined are not meant to induce claustrophobia and make you feel limited in your design choices, but rather to serve you as guides. It is by accepting that fact that we can find freedom and new avenues to explore.
This two-part series of articles, just like my experience designing for TV, was not about reinventing the wheel with radical ideas. It was about understanding its nuances and contributing to what’s already there with my personal touch.
If you find yourself working in this design field, I hope my guide will serve as a warm welcome and will help you do your finest work. And if you have any questions, do leave a comment, and I will do my best to reply and help.
Good luck!
by Milan Balać (hello@smashingmagazine.com) at September 04, 2025 10:00 AM
September is just around the corner, and that means it’s time for some new wallpapers! For more than 14 years already, our monthly wallpapers series has been the perfect occasion for artists and designers to challenge their creative skills and take on a little just-for-fun project — telling the stories they want to tell, using their favorite tools. This always makes for a unique and inspiring collection of wallpapers month after month, and, of course, this September is no exception.
In this post, you’ll find desktop wallpapers for September 2025, created with love by the community for the community. As a bonus, we’ve also added some oldies but goodies from our archives to the collection, so maybe you’ll spot one of your almost-forgotten favorites in here, too? A huge thank-you to everyone who shared their artworks with us this month — this post wouldn’t exist without your creativity and support!
By the way, if you’d like to get featured in one of our upcoming wallpapers editions, please don’t hesitate to submit your design. We are always looking for creative talent and can’t wait to see your story come to life!
“On the 21st night of September, the world danced in perfect harmony. Earth, Wind & Fire set the tone and now it’s your turn to keep the rhythm alive.” — Designed by Ginger IT Solutions from Serbia.
Designed by Ricardo Gimenes from Spain.
“Celebrate Chocolate Milk Day with a perfect blend of fun and flavor. From smooth sips to smooth rides, it’s all about enjoying the simple moments that make the day unforgettable.” — Designed by PopArt Studio from Serbia.
Designed by Ricardo Gimenes from Spain.
“Cats are beautiful animals. They’re quiet, clean, and warm. They’re funny and can become an endless source of love and entertainment. Here for the cats!” — Designed by UrbanUI from India.
Designed by Ricardo Gimenes from Spain.
“This autumn, we expect to see a lot of rainy days and blues, so we wanted to change the paradigm and wish a warm welcome to the new season. After all, if you come to think of it: rain is not so bad if you have an umbrella and a raincoat. Come autumn, we welcome you!” — Designed by PopArt Studio from Serbia.
“With the end of summer and fall coming soon, I created this terrazzo pattern wallpaper to brighten up your desktop. Enjoy the month!” — Designed by Melissa Bogemans from Belgium.
“As summer comes to an end, all the creatures pull back to their hiding places, searching for warmth within themselves and dreaming of neverending adventures under the tinted sky of closing dog days.” — Designed by Ana Masnikosa from Belgrade, Serbia.
“Seasons come and go, but our brave cactuses still stand. Summer is almost over and autumn is coming, but the beloved plants don’t care.” — Designed by Lívia Lénárt from Hungary.
“The earth has music for those who listen. Take a break and relax and while you drive out the stress, catch a glimpse of the beautiful nature around you. Can you hear the rhythm of the breeze blowing, the flowers singing, and the butterflies fluttering to cheer you up? We dedicate flowers which symbolize happiness and love to one and all.” — Designed by Krishnankutty from India.
Designed by Ricardo Gimenes from Spain.
Designed by Teodora Vasileva from Bulgaria.
“It’s this time of the year when children go to school and grown-ups go to collect mushrooms.” — Designed by Igor Izhik from Canada.
Designed by Robert from the United States.
Hungry
Designed by Elise Vanoorbeek from Belgium.
“September 12th brings us National Video Games Day. US-based video game players love this day and celebrate with huge gaming tournaments. What was once a 2D experience in the home is now a global phenomenon with players playing against each other across statelines and national borders via the internet. National Video Games Day gives gamers the perfect chance to celebrate and socialize! So grab your controller, join online, and let the games begin!” — Designed by Ever Increasing Circles from the United Kingdom.
Designed by Ricardo Gimenes from Spain.
“Today, we celebrate these magnificent creatures who play such a vital role in our ecosystems and cultures. Elephants are symbols of wisdom, strength, and loyalty. Their social bonds are strong, and their playful nature, especially in the young ones, reminds us of the importance of joy and connection in our lives.” — Designed by PopArt Studio from Serbia.
“While September’s Autumnal Equinox technically signifies the end of the summer season, this wallpaper is for all those summer lovers, like me, who don’t want the sunshine, warm weather, and lazy days to end.” — Designed by Vicki Grunewald from Washington.
Designed by Bojana Stojanovic from Serbia.
“It’s officially the end of summer and I’m still in vacation mood, dreaming about all the amazing places I’ve seen. This illustration is inspired by a small town in France, on the Atlantic coast, right by the beach.” — Designed by Miruna Sfia from Romania.
“As summer comes to a close, so does the end of blue crab season in Maryland. Blue crabs have been a regional delicacy since the 1700s and have become Maryland’s most valuable fishing industry, adding millions of dollars to the Maryland economy each year. The blue crab has contributed so much to the state’s regional culture and economy, in 1989 it was named the State Crustacean, cementing its importance in Maryland history.” — Designed by The Hannon Group from Washington DC.
“We continue in tropical climates. In this case, we travel to Costa Rica to observe the Arenal volcano from the lake while we use a kayak.” — Designed by Veronica Valenzuela from Spain.
“Welcome to the wine harvest season in Serbia. It’s September, and the hazy sunshine bathes the vines on the slopes of Fruška Gora. Everything is ready for the making of Bermet, the most famous wine from Serbia. This spiced wine was a favorite of the Austro-Hungarian elite and was served even on the Titanic. Bermet’s recipe is a closely guarded secret, and the wine is produced by just a handful of families in the town of Sremski Karlovci, near Novi Sad. On the other side of Novi Sad, plains of corn and sunflower fields blend in with the horizon, catching the last warm sun rays of this year.” — Designed by PopArt Studio from Serbia.
“Clean, minimalistic office for a productive day.” — Designed by Antun Hiršman from Croatia.
“I love September. Its colors and smells.” — Designed by Juliagav from Ukraine.
Designed by Ricardo Gimenes from Spain.
by Cosima Mielke (hello@smashingmagazine.com) at August 31, 2025 08:00 AM
In “A Week In The Life Of An AI-Augmented Designer”, we followed Kate’s weeklong journey of her first AI-augmented design sprint. She had three realizations through the process:
As designers, we’re used to designing interactions for people. Prompting is us designing our own interactions with machines — it uses the same mindset with a new medium. It shapes an AI’s behavior the same way you’d guide a user with structure, clarity, and intent.
If you’ve bookmarked, downloaded, or saved prompts from others, you’re not alone. We’ve all done that during our AI journeys. But while someone else’s prompts are a good starting point, you will get better and more relevant results if you can write your own prompts tailored to your goals, context, and style. Using someone else’s prompt is like using a Figma template. It gets the job done, but mastery comes from understanding and applying the fundamentals of design, including layout, flow, and reasoning. Prompts have a structure too. And when you learn it, you stop guessing and start designing.
Note: All prompts in this article were tested using ChatGPT — not because it’s the only game in town, but because it’s friendly, flexible, and lets you talk like a person, yes, even after the recent GPT-5 “update”. That said, any LLM with a decent attention span will work. Results for the same prompt may vary based on the AI model you use, the AI’s training, mood, and how confidently it can hallucinate.
Privacy PSA: As always, don’t share anything you wouldn’t want leaked, logged, or accidentally included in the next AI-generated meme. Keep it safe, legal, and user-respecting.
With that out of the way, let’s dive into the mindset, anatomy, and methods of effective prompting as another tool in your design toolkit.
Mindset: Prompt Like A DesignerAs designers, we storyboard journeys, wireframe interfaces to guide users, and write UX copy with intention. However, when prompting AI, we treat it differently: “Summarize these insights”, “Make this better”, “Write copy for this screen”, and then wonder why the output feels generic, off-brand, or just meh. It’s like expecting a creative team to deliver great work from a one-line Slack message. We wouldn’t brief a freelancer, much less an intern, with “Design a landing page,” so why brief AI that way?
Think of a good prompt as a creative brief, just for a non-human collaborator. It needs similar elements, including a clear role, defined goal, relevant context, tone guidance, and output expectations. Just as a well-written creative brief unlocks alignment and quality from your team, a well-structured prompt helps the AI meet your expectations, even though it doesn’t have real instincts or opinions.
A good prompt goes beyond defining the task and sets the tone for the exchange by designing a conversation: guiding how the AI interprets, sequences, and responds. You shape the flow of tasks, how ambiguity is handled, and how refinement happens — that’s conversation design.
Anatomy: Structure It Like A DesignerSo how do you write a designer-quality prompt? That’s where the W.I.R.E.+F.R.A.M.E. prompt design framework comes in — a UX-inspired framework for writing intentional, structured, and reusable prompts. Each letter represents a key design direction, grounded in the way UX designers already think: Just as a wireframe doesn’t dictate final visuals, this WIRE+FRAME framework doesn’t constrain creativity, but guides the AI with structured information it needs.
“Why not just use a series of back-and-forth chats with AI?”
You can, and many people do. But without structure, AI fills in the gaps on its own, often with vague or generic results. A good prompt upfront saves time, reduces trial and error, and improves consistency. And whether you’re working on your own or across a team, a framework means you’re not reinventing a prompt every time but reusing what works to get better results faster.
Just as we build wireframes before adding layers of fidelity, the WIRE+FRAME framework has two parts:
Let’s improve Kate’s original research synthesis prompt (“Read this customer feedback and tell me how we can improve financial literacy for Gen Z in our app”). To better reflect how people actually prompt in practice, let’s tweak it to a more broadly applicable version: “Read this customer feedback and tell me how we can improve our app for Gen Z users.” This one-liner mirrors the kinds of prompts we often throw at AI tools: short, simple, and often lacking structure.
Now, we’ll take that prompt and rebuild it using the first four elements of the W.I.R.E. framework — the core building blocks that provide AI with the main information it needs to deliver useful results.
Define who the AI should be, and what it’s being asked to deliver.
A creative brief starts with assigning the right hat. Are you briefing a copywriter? A strategist? A product designer? The same logic applies here. Give the AI a clear identity and task. Treat AI like a trusted freelancer or intern. Instead of saying “help me”, tell it who it should act as and what’s expected.
Example: “You are a senior UX researcher and customer insights analyst. You specialize in synthesizing qualitative data from diverse sources to identify patterns, surface user pain points, and map them across customer journey stages. Your outputs directly inform product, UX, and service priorities.”
Provide background that frames the task.
Creative partners don’t work in a vacuum. They need context: the audience, goals, product, competitive landscape, and what’s been tried already. This is the “What you need to know before you start” section of the brief. Think: key insights, friction points, business objectives. The same goes for your prompt.
Example: “You are analyzing customer feedback for Fintech Brand’s app, targeting Gen Z users. Feedback will be uploaded from sources such as app store reviews, survey feedback, and usability test transcripts.”
Clarify any limitations, boundaries, and exclusions.
Good creative briefs always include boundaries — what to avoid, what’s off-brand, or what’s non-negotiable. Things like brand voice guidelines, legal requirements, or time and word count limits. Constraints don’t limit creativity — they focus it. AI needs the same constraints to avoid going off the rails.
Example: “Only analyze the uploaded customer feedback data. Do not fabricate pain points, representative quotes, journey stages, or patterns. Do not supplement with prior knowledge or hypothetical examples. Use clear, neutral, stakeholder-facing language.”
Spell out what the deliverable should look like.
This is the deliverable spec: What does the finished product look like? What tone, format, or channel is it for? Even if the task is clear, the format often isn’t. Do you want bullet points or a story? A table or a headline? If you don’t say, the AI will guess, and probably guess wrong. Even better, include an example of the output you want, an effective way to help AI know what you’re expecting. If you’re using GPT-5, you can also mix examples across formats (text, images, tables) together.
Example: “Return a structured list of themes. For each theme, include:
WIRE gives you everything you need to stop guessing and start designing your prompts with purpose. When you start with WIRE, your prompting is like a briefing, treating AI like a collaborator.
Once you’ve mastered this core structure, you can layer in additional fidelity, like tone, step-by-step flow, or iterative feedback, using the FRAME elements. These five elements provide additional guidance and clarity to your prompt by layering clear deliverables, thoughtful tone, reusable structure, and space for creative iteration.
Break complex prompts into clear, ordered steps.
This is your project plan or creative workflow that lays out the stages, dependencies, or sequence of execution. When the task has multiple parts, don’t just throw it all into one sentence. You are doing the thinking and guiding AI. Structure it like steps in a user journey or modules in a storyboard. In this example, it fits as the blueprint for the AI to use to generate the table described in “E: Expected Output”
Example: “Recommended flow of tasks:
Step 1: Parse the uploaded data and extract discrete pain points.
Step 2: Group them into themes based on pattern similarity.
Step 3: Score each theme by frequency (from data), severity (based on content), and estimated effort.
Step 4: Map each theme to the appropriate customer journey stage(s).
Step 5: For each theme, write a clear problem statement and opportunity based only on what’s in the data.”
Name the desired tone, mood, or reference brand.
This is the brand voice section or style mood board — reference points that shape the creative feel. Sometimes you want buttoned-up. Other times, you want conversational. Don’t assume the AI knows your tone, so spell it out.
Example: “Use the tone of a UX insights deck or product research report. Be concise, pattern-driven, and objective. Make summaries easy to scan by product managers and design leads.”
Invite the AI to ask questions before generating, if anything is unclear.
This is your “Any questions before we begin?” moment — a key step in collaborative creative work. You wouldn’t want a freelancer to guess what you meant if the brief was fuzzy, so why expect AI to do better? Ask AI to reflect or clarify before jumping into output mode.
Example: “If the uploaded data is missing or unclear, ask for it before continuing. Also, ask for clarification if the feedback format is unstructured or inconsistent, or if the scoring criteria need refinement.”
Reference earlier parts of the conversation and reuse what’s working.
This is similar to keeping visual tone or campaign language consistent across deliverables in a creative brief. Prompts are rarely one-shot tasks, so this reminds AI of the tone, audience, or structure already in play. GPT-5 got better with memory, but this still remains a useful element, especially if you switch topics or jump around.
Example: “Unless I say otherwise, keep using this process: analyze the data, group into themes, rank by importance, then suggest an action for each.”
Invite the AI to critique, improve, or generate variations.
This is your revision loop — your way of prompting for creative direction, exploration, and refinement. Just like creatives expect feedback, your AI partner can handle review cycles if you ask for them. Build iteration into the brief to get closer to what you actually need. Sometimes, you may see ChatGPT test two versions of a response on its own by asking for your preference.
Example: “After listing all themes, identify the one with the highest combined priority score (based on frequency, severity, and effort).
For that top-priority theme:
Here’s a quick recap of the WIRE+FRAME framework:
| Framework Component | Description |
|---|---|
| W: Who & What | Define the AI persona and the core deliverable. |
| I: Input Context | Provide background or data scope to frame the task. |
| R: Rules & Constraints | Set boundaries |
| E: Expected Output | Spell out the format and fields of the deliverable. |
| F: Flow of Tasks | Break the work into explicit, ordered sub-tasks. |
| R: Reference Voice/Style | Name the tone, mood, or reference brand to ensure consistency. |
| A: Ask for Clarification | Invite AI to pause and ask questions if any instructions or data are unclear before proceeding. |
| M: Memory | Leverage in-conversation memory to recall earlier definitions, examples, or phrasing without restating them. |
| E: Evaluate & Iterate | After generation, have the AI self-critique the top outputs and refine them. |
And here’s the full WIRE+FRAME prompt:
(W) You are a senior UX researcher and customer insights analyst. You specialize in synthesizing qualitative data from diverse sources to identify patterns, surface user pain points, and map them across customer journey stages. Your outputs directly inform product, UX, and service priorities.
(I) You are analyzing customer feedback for Fintech Brand’s app, targeting Gen Z users. Feedback will be uploaded from sources such as app store reviews, survey feedback, and usability test transcripts.
(R) Only analyze the uploaded customer feedback data. Do not fabricate pain points, representative quotes, journey stages, or patterns. Do not supplement with prior knowledge or hypothetical examples. Use clear, neutral, stakeholder-facing language.
(E) Return a structured list of themes. For each theme, include:(F) Recommended flow of tasks:
- Theme Title
- Summary of the Issue
- Problem Statement
- Opportunity
- Representative Quotes (from data only)
- Journey Stage(s)
- Frequency (count from data)
- Severity Score (1–5) where 1 = Minor inconvenience or annoyance; 3 = Frustrating but workaround exists; 5 = Blocking issue
- Estimated Effort (Low / Medium / High), where Low = Copy or content tweak; Medium = Logic/UX/UI change; High = Significant changes
Step 1: Parse the uploaded data and extract discrete pain points.
Step 2: Group them into themes based on pattern similarity.
Step 3: Score each theme by frequency (from data), severity (based on content), and estimated effort.
Step 4: Map each theme to the appropriate customer journey stage(s).
Step 5: For each theme, write a clear problem statement and opportunity based only on what’s in the data.
(R) Use the tone of a UX insights deck or product research report. Be concise, pattern-driven, and objective. Make summaries easy to scan by product managers and design leads.
(A) If the uploaded data is missing or unclear, ask for it before continuing. Also, ask for clarification if the feedback format is unstructured or inconsistent, or if the scoring criteria need refinement.
(M) Unless I say otherwise, keep using this process: analyze the data, group into themes, rank by importance, then suggest an action for each.
(E) After listing all themes, identify the one with the highest combined priority score (based on frequency, severity, and effort).
For that top-priority theme:
- Critically evaluate its framing: Is the title clear? Are the quotes strong and representative? Is the journey mapping appropriate?
- Suggest one improvement (e.g., improved title, more actionable implication, clearer quote, tighter summary).
- Rewrite the theme entry with that improvement applied.
- Briefly explain why the revision is stronger and more useful for product or design teams.
You could use “##” to label the sections (e.g., “##FLOW”) more for your readability than for AI. At over 400 words, this Insights Synthesis prompt example is a detailed, structured prompt, but it isn’t customized for you and your work. The intent wasn’t to give you a specific prompt (the proverbial fish), but to show how you can use a prompt framework like WIRE+FRAME to create a customized, relevant prompt that will help AI augment your work (teaching you to fish).
Keep in mind that prompt length isn’t a common concern, but rather a lack of quality and structure is. As of the time of writing, AI models can easily process prompts that are thousands of words long.
Not every prompt needs all the FRAME components; WIRE is often enough to get the job done. But when the work is strategic or highly contextual, pick components from FRAME — the extra details can make a difference. Together, WIRE+FRAME give you a detailed framework for creating a well-structured prompt, with the crucial components first, followed by optional components:
Here are some scenarios and recommendations for using WIRE or WIRE+FRAME:
| Scenarios | Description | Recommended |
|---|---|---|
| Simple, One-Off Analyses | Quick prompting with minimal setup and no need for detailed process transparency. | WIRE |
| Tight Sprints or Hackathons | Rapid turnarounds, and times you don’t need embedded review and iteration loops. | WIRE |
| Highly Iterative Exploratory Work | You expect to tweak results constantly and prefer manual control over each step. | WIRE |
| Complex Multi-Step Playbooks | Detailed workflows that benefit from a standardized, repeatable, visible sequence. | WIRE+FRAME |
| Shared or Hand-Off Projects | When different teams will rely on embedded clarification, memory, and consistent task flows for recurring analyses. | WIRE+FRAME |
| Built-In Quality Control | You want the AI to flag top issues, self-critique, and refine, minimizing manual QC steps. | WIRE+FRAME |
Prompting isn’t about getting it right the first time. It’s about designing the interaction and redesigning when needed. With WIRE+FRAME, you’re going beyond basic prompting and designing the interaction between you and AI.
Let’s compare the results of Kate’s first AI-augmented design sprint prompt (to synthesize customer feedback into design insights) with one based on the WIRE+FRAME prompt framework, with the same data and focusing on the top results:
Original prompt: Read this customer feedback and tell me how we can improve our app for Gen Z users.
Initial ChatGPT Results:
With this version, you’d likely need to go back and forth with follow-up questions, rewrite the output for clarity, and add structure before sharing with your team.
WIRE+FRAME prompt above (with defined role, scope, rules, expected format, tone, flow, and evaluation loop).
Initial ChatGPT Results:

You can clearly see the very different results from the two prompts, both using the exact same data. While the first prompt returns a quick list of ideas, the detailed WIRE+FRAME version doesn’t just summarize feedback but structures it. Themes are clearly labeled, supported by user quotes, mapped to customer journey stages, and prioritized by frequency, severity, and effort.
The structured prompt results can be used as-is or shared without needing to reformat, rewrite, or explain them (see disclaimer below). The first prompt output needs massaging: it’s not detailed, lacks evidence, and would require several rounds of clarification to be actionable. The first prompt may work when the stakes are low and you are exploring. But when your prompt is feeding design, product, or strategy, structure comes to the rescue.
A well-structured prompt can make AI output more useful, but it shouldn’t be the final word, or your single source of truth. AI models are powerful pattern predictors, not fact-checkers. If your data is unclear or poorly referenced, even the best prompt may return confident nonsense. Don’t blindly trust what you see. Treat AI like a bright intern: fast, eager, and occasionally delusional. You should always be familiar with your data and validate what AI spits out. For example, in the WIRE+FRAME results above, AI rated the effort as low for financial tool onboarding. That could easily be a medium or high. Good prompting should be backed by good judgment.
Start by using the WIRE+FRAME framework to create a prompt that will help AI augment your work. You could also rewrite the last prompt you were not satisfied with, using the WIRE+FRAME, and compare the output.
Feel free to use this simple tool to guide you through the framework.
Methods: From Lone Prompts to a Prompt SystemJust as design systems have reusable components, your prompts can too. You can use the WIRE+FRAME framework to write detailed prompts, but you can also use the structure to create reusable components that are pre-tested, plug-and-play pieces you can assemble to build high-quality prompts faster. Each part of WIRE+FRAME can be transformed into a prompt component: small, reusable modules that reflect your team’s standards, voice, and strategy.
For instance, if you find yourself repeatedly using the same content for different parts of the WIRE+FRAME framework, you could save them as reusable components for you and your team. In the example below, we have two different reusable components for “W: Who & What” — an insights analyst and an information architect.
Create and save prompt components and variations for each part of the WIRE+FRAME framework, allowing your team to quickly assemble new prompts by combining components when available, rather than starting from scratch each time.
Behind The Prompts: Questions About PromptingQ: If I use a prompt framework like WIRE+FRAME every time, will the results be predictable?
A: Yes and no. Yes, your outputs will be guided by a consistent set of instructions (e.g., Rules, Examples, Reference Voice / Style) that will guide the AI to give you a predictable format and style of results. And no, while the framework provides structure, it doesn’t flatten the generative nature of AI, but focuses it on what’s important to you. In the next article, we will look at how you can use this to your advantage to quickly reuse your best repeatable prompts as we build your AI assistant.
Q: Could changes to AI models break the WIRE+FRAME framework?
A: AI models are evolving more rapidly than any other technology we’ve seen before — in fact, ChatGPT was recently updated to GPT-5 to mixed reviews. The update didn’t change the core principles of prompting or the WIRE+FRAME prompt framework. With future releases, some elements of how we write prompts today may change, but the need to communicate clearly with AI won’t. Think of how you delegate work to an intern vs. someone with a few years’ experience: you still need detailed instructions the first time either is doing a task, but the level of detail may change. WIRE+FRAME isn’t built only for today’s models; the components help you clarify your intent, share relevant context, define constraints, and guide tone and format — all timeless elements, no matter how smart the model becomes. The skill of shaping clear, structured interactions with non-human AI systems will remain valuable.
Q: Can prompts be more than text? What about images or sketches?
A: Absolutely. With tools like GPT-5 and other multimodal models, you can upload screenshots, pictures, whiteboard sketches, or wireframes. These visuals become part of your Input Context or help define the Expected Output. The same WIRE+FRAME principles still apply: you’re setting context, tone, and format, just using images and text together. Whether your input is a paragraph or an image and text, you’re still designing the interaction.
Have a prompt-related question of your own? Share it in the comments, and I’ll either respond there or explore it further in the next article in this series.
From Designerly Prompting To Custom AssistantsGood prompts and results don’t come from using others’ prompts, but from writing prompts that are customized for you and your context. The WIRE+FRAME framework helps with that and makes prompting a tool you can use to guide AI models like a creative partner instead of hoping for magic from a one-line request.
Prompting uses the designerly skills you already use every day to collaborate with AI:
Once you create and refine prompt components and prompts that work for you, make them reusable by documenting them. But wait, there’s more — what if your best prompts, or the elements of your prompts, could live inside your own AI assistant, available on demand, fluent in your voice, and trained on your context? That’s where we’re headed next.
In the next article, “Design Your Own Design Assistant”, we’ll take what you’ve learned so far and turn it into a Custom AI assistant (aka Custom GPT), a design-savvy, context-aware assistant that works like you do. We’ll walk through that exact build, from defining the assistant’s job description to uploading knowledge, testing, and sharing it with others.
by Lyndon Cerejo (hello@smashingmagazine.com) at August 29, 2025 10:00 AM
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11844
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
任意HTML元素,设置hidden属性后,会隐藏自身(样式没有被重置的前提下)。
可能有些人并不知道,hidden属性还支持一个名为until-found值,他可以让内容隐藏,但是在特殊场景下也会显示。
这个特殊场景包括锚点锚定,或者浏览器层面的文字搜索匹配。
我们开看例子。
如下HTML代码:
<div class="container">
<a href="#zhangxinxu">显示作者名字</a>
<h5 class="target">
<p id="zhangxinxu" hidden="until-found">名字是张鑫旭</p>
</h5>
</div>
我们不妨扔一点样式美化下,至于什么样式不重要,总之,就有会如下所示的实时渲染效果:
此时,我们点击上面的链接文字,触发锚点跳转,就会发现,原本隐藏的<p>元素显示了。
就像下图这样:

如果是桌面端浏览器,试试按下Ctrl+F搜索,例如:

此时,当我们的搜索选项与hidden="until-found"里面的内容匹配的时候,这段内容就突然显示了,且高亮的颜色(橙色背景)和其他区域匹配的文字颜色(黄色背景)不同,如下截图所示:

until-found隐藏的元素还支持一个名为beforematch的事件,在该元素从隐藏变成显示之前的一瞬间执行。
还是上面的案例,我们给元素加个beforematch事件,让显示的时候,外面的容器边框颜色高亮,则可以这么处理:
const untilFound = document.getElementById("zhangxinxu");
untilFound.addEventListener('beforematch', () => {
untilFound.closest('div').style.borderColor = 'red';
});
实时测试效果如下(大胆点击链接,看看边框红了没有):
hidden="until-found"隐藏和传统的hidden隐藏不同,后者是display:none隐藏,但是,前者使用的是content-visibility:hidden隐藏,这个隐藏在我的《CSS新世界》这本书中有介绍。
content-visibility:hidden只会隐藏内容,元素的边框、布局、背景都是保留的,想必visibility: hidden,其不会除非内容渲染,在频繁显隐交互的场景下有更高的性能。

所以,hidden="until-found"直接设置在内容元素上是无效的,例如保持内联水平的<span>元素上,等。
基于其“隐藏但可被发现”的特性,hidden=”until-found” 非常适合以下场景:
至于提高页面内容显隐的渲染性能,这个大家就不用考虑了,没有任何意义,1ms和2ms的差异有区别吗?没有区别,对吧。
Safari浏览器已经确定支持该特性,Chrome浏览器已经支持好几年了。

懒得结语
见上一篇文章。
本周钓货回头补上。
好了,就这样吧。
😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11844
(本篇完)
Television sets have been the staple of our living rooms for decades. We watch, we interact, and we control, but how often do we design for them? TV design flew under my “radar” for years, until one day I found myself in the deep, designing TV-specific user interfaces. Now, after gathering quite a bit of experience in the area, I would like to share my knowledge on this rather rare topic. If you’re interested in learning more about the user experience and user interfaces of television, this article should be a good starting point.
Just like any other device or use case, TV has its quirks, specifics, and guiding principles. Before getting started, it will be beneficial to understand the core ins and outs. In Part 1, we’ll start with a bit of history, take a close look at the fundamentals, and review the evolution of television. In Part 2, we’ll dive into the depths of practical aspects of designing for TV, including its key principles and patterns.
Let’s start with the two key paradigms that dictate the process of designing TV interfaces.
Mind The Gap, Or The 10-foot-experienceFirstly, we have the so-called “10-foot experience,” referring to the fact that interaction and consumption on TV happens from a distance of roughly three or more meters. This is significantly different than interacting with a phone or a computer and implies having some specific approaches in the TV user interface design. For example, we’ll need to make text and user interface (UI) elements larger on TV to account for the bigger distance to the screen.
Furthermore, we’ll take extra care to adhere to contrast standards, primarily relying on dark interfaces, as light ones may be too blinding in darker surroundings. And finally, considering the laid-back nature of the device, we’ll simplify the interactions.

But the 10-foot experience is only one part of the equation. There wouldn’t be a “10-foot experience” in the first place if there were no mediator between the user and the device, and if we didn’t have something to interact through from a distance.
There would be no 10-foot experience if there were no remote controllers.
The MediatorThe remote, the second half of the equation, is what allows us to interact with the TV from the comfort of the couch. Slower and more deliberate, this conglomerate of buttons lacks the fluid motion of a mouse, or the dexterity of fingers against a touchscreen — yet the capabilities of the remote should not be underestimated.
Rudimentary as it is and with a limited set of functions, the remote allows for some interesting design approaches and can carry the weight of the modern TV along with its ever-growing requirements for interactivity. It underwent a handful of overhauls during the seventy years since its inception and was refined and made more ergonomic; however, there is a 40-year-old pattern so deeply ingrained in its foundation that nothing can change it.
What if I told you that you could navigate TV interfaces and apps with a basic controller from the 1980s just as well as with the latest remote from Apple? Not only that, but any experience built around the six core buttons of a remote will be system-agnostic and will easily translate across platforms.
This is the main point I will focus on for the rest of this article.
Birth Of A PatternAs television sets were taking over people’s living rooms in the 1950s, manufacturers sought to upgrade and improve the user experience. The effort of walking up to the device to manually adjust some settings was eventually identified as an area for improvement, and as a result, the first television remote controllers were introduced to the market.
Preliminary iterations of the remotes were rather unique, and it took some divergence before we finally settled on a rectangular shape and sprinkled buttons on top.
Take a look at the Zenith Flash-Matic, for example. Designed in the mid-1950s, this standout device featured a single button that triggered a directional lamp; by pointing it at specific corners of the TV set, viewers could control various functions, such as changing channels or adjusting the volume.

While they were a far cry compared to their modern counterparts, devices like the Flash-Matic set the scene for further developments, and we were off to the races!
As the designs evolved, the core functionality of the remote solidified. Gradually, remote controls became more than just simple channel changers, evolving into command centers for the expanding territory of home entertainment.
Note: I will not go too much into history here — aside from some specific points that are of importance to the matter at hand — but if you have some time to spare, do look into the developmental history of television sets and remotes, it’s quite a fascinating topic.

However, practical as they may have been, they were still considered a luxury, significantly increasing the prices of TV sets. As the 1970s were coming to a close, only around 17% of United States households had a remote controller for their TVs. Yet, things would change as the new decade rolled in.
The eighties brought with them the Apple Macintosh, MTV, and Star Wars. It was a time of cultural shifts and technological innovation. Videocassette recorders (VCRs) and a multitude of other consumer electronics found their place in the living rooms of the world, along with TVs.
These new devices, while enriching our media experiences, also introduced a few new design problems. Where there was once a single remote, now there were multiple remotes, and things were getting slowly out of hand.
This marked the advent of universal remotes.

Trying to hit many targets with one stone, the unwieldy universal remotes were humanity’s best solution for controlling a wider array of devices. And they did solve some of these problems, albeit in an awkward way. The complexity of universal remotes was a trade-off for versatility, allowing them to be programmed and used as a command center for controlling multiple devices. This meant transforming the relatively simple design of their predecessors into a beehive of buttons, prioritizing broader compatibility over elegance.
On the other hand, almost as a response to the inconvenience of the universal remote, a different type of controller was conceived in the 1980s — one with a very basic layout and set of buttons, and which would leave its mark in both how we interact with the TV, and how our remotes are laid out. A device that would, knowingly or not, give birth to a navigational pattern that is yet to be broken — the NES controller.
Released in 1985, the Nintendo Entertainment System (NES) was an instant hit. Having sold sixty million units around the world, it left an undeniable mark on the gaming console industry.

The NES controller (which was not truly remote, as it ran a cable to the central unit) introduced the world to a deceptively simple control scheme. Consisting of six primary actions, it gave us the directional pad (the D-pad), along with two action buttons (A and B). Made in response to the bulky joystick, the cross-shaped cluster allowed for easy movement along two axes (up, down, left, and right).
Charmingly intuitive, this navigational pattern would produce countless hours of gaming fun, but more importantly, its elementary design would “seep over” into the wider industry — the D-pad, along with the two action buttons, would become the very basis on which future remotes would be constructed.
The world continued spinning madly on, and what was once a luxury became commonplace. By the end of the decade, TV remotes were more integral to the standard television experience, and more than two-thirds of American TV owners had some sort of a remote.
The nineties rolled in with further technological advancements. TV sets became more robust, allowing for finer tuning of their settings. This meant creating interfaces through which such tasks could be accomplished, and along with their master sets, remotes got updated as well.
Gone were the bulky rectangular behemoths of the eighties. As ergonomics took precedence, they got replaced by comfortably contoured devices that better fit their users’ hands. Once conglomerations of dozens of uniform buttons, these contemporary remotes introduced different shapes and sizes, allowing for recognition simply through touch. Commands were being clustered into sensible groups along the body of the remote, and within those button groups, a familiar shape started to emerge.

Gradually, the D-pad found its spot on our TV remotes. As the evolution of these devices progressed, it became even more deeply embedded at the core of their interactivity.

Set-top boxes and smart features emerged in the 2000s and 2010s, and TV technology continued to advance. Along the way, many bells and whistles were introduced. TVs got bigger, brighter, thinner, yet their essence remained unchanged.
In the years since their inception, remotes were innovated upon, but all the undertakings circle back to the core principles of the NES controller. Future endeavours never managed to replace, but only to augment and reinforce the pattern.
The Evergreen PatternIn 2013, LG introduced their Magic remote (“So magically simple, the kids will be showing you how to use it!”). This uniquely shaped device enabled motion controls on LG TV sets, allowing users to point and click similar to a computer mouse. Having a pointer on the screen allowed for much more flexibility and speed within the system, and the remote was well-received and praised as one of the best smart TV remotes.

Innovating on tradition, this device introduced new features and fresh perspectives to the world of TV. But if we look at the device itself, we’ll see that, despite its differences, it still retains the D-pad as a means of interaction. It may be argued that LG never set out to replace the directional pad, and as it stands, regardless of their intent, they only managed to augment it.
For an even better example, let’s examine Apple TV’s second-generation remotes (the first-generation Siri remote). Being the industry disruptors, Apple introduced a touchpad to the top half of the remote. The glass surface provided briskness and precision to the experience, enabling multi-touch gestures, swipe navigation, and quick scrolling. This quality of life upgrade was most noticeable when typing with the horizontal on-screen keyboards, as it allowed for smoother and quicker scrolling from A to Z, making for a more refined experience.

While at first glance it may seem Apple removed the directional buttons, the fact is that the touchpad is simply a modernised take on the pattern, still abiding by the same four directions a classic D-pad does. You could say it’s a D-pad with an extra layer of gimmick.
Furthermore, the touchpad didn’t really sit well with the user base, along with the fact that the remote’s ergonomics were a bit iffy. So instead of pushing the boundaries even further with their third generation of remotes, Apple did a complete 180, re-introducing the classic D-pad cluster while keeping the touch capabilities from the previous generation (the touch-enabled clickpad lets you select titles, swipe through playlists, and use a circular gesture on the outer ring to find just the scene you’re looking for).

Now, why can’t we figure out a better way to navigate TVs? Does that mean we shouldn’t try to innovate?
We can argue that using motion controls and gestures is an obvious upgrade to interacting with a TV. And we’d be right… in principle. These added features are more complex and costly to produce, but more importantly, while it has been upgraded with bits and bobs, the TV is essentially a legacy system. And it’s not only that.
While touch controls are a staple of interaction these days, adding them without thorough consideration can reduce the usability of a remote.
Modern car dashboards are increasingly being dominated by touchscreens. While they may impress at auto shows, their real-world usability is often compromised.
Driving demands constant focus and the ability to adapt and respond to ever-changing conditions. Any interface that requires taking your eyes off the road for more than a moment increases the risk of accidents. That’s exactly where touch controls fall short. While they may be more practical (and likely cheaper) for manufacturers to implement, they’re often the opposite for the end user.
Unlike physical buttons, knobs, and levers, which offer tactile landmarks and feedback, touch interfaces lack the ability to be used by feeling alone. Even simple tasks like adjusting the volume of the radio or the climate controls often involve gestures and nested menus, all performed on a smooth glass surface that demands visual attention, especially when fine-tuning.
Fortunately, the upcoming 2026 Euro NCAP regulations will encourage car manufacturers to reintroduce physical controls for core functions, reducing driver distraction and promoting safer interaction.
Similarly (though far less critically), sleek, buttonless TV remote controls may feel modern, but they introduce unnecessary abstraction to a familiar set of controls.
Physical buttons with distinct shapes and positioning allow users to navigate by memory and touch, even in the dark. That’s not outdated — it’s a deeper layer of usability that modern design should respect, not discard.
And this is precisely why Apple reworked the Apple TV third-generation remote the way it is now, where the touch area at the top disappeared. Instead, the D-pad again had clearly defined buttons, and at the same time, the D-pad could also be extended (not replaced) to accept some touch gestures.
The Legacy Of TVLet’s take a look at an old on-screen keyboard.

The Legend of Zelda, released in 1986, allowed players to register their names in-game. There are even older games with the same feature, but that’s beside the point. Using the NES controller, the players would move around the keyboard, entering their moniker character by character. Now let’s take a look at a modern iteration of the on-screen keyboard.

Notice the difference? Or, to phrase it better: do you notice the similarities? Throughout the years, we’ve introduced quality of life improvements, but the core is exactly the same as it was forty years ago. And it is not the lack of innovation or bad remotes that keep TV deeply ingrained in its beginnings. It’s simply that it’s the most optimal way to interact given the circumstances.
Just like phones and computers, TV layouts are based on a grid system. However, this system is a lot more apparent and rudimentary on TV. Taking a look at a standard TV interface, we’ll see that it consists mainly of horizontal and vertical lists, also known as shelves.

These grids may be populated with cards, characters of the alphabet, or anything else, essentially, and upon closer examination, we’ll notice that our movement is restricted by a few factors:
For the purposes of navigating with a remote, a focus state is introduced. This means that an element will always be highlighted for our eyes to anchor, and it will be the starting point for any subsequent movement within the interface.
Simplified TV UI demonstrating a focus state along with sequential movement from item to item within a column.
Moreover, starting from the focused element, we can notice that the movement is restricted to one item at a time, almost like skipping stones. Navigating linearly in such a manner, if we wanted to move within a list of elements from element #1 to element #5, we’d have to press a directional button four times.
Simplified TV UI demonstrating a focus state along with sequential movement from item to item within a row.
To successfully navigate such an interface, we need the ability to move left, right, up, and down — we need a D-pad. And once we’ve landed on our desired item, there needs to be a way to select it or make a confirmation, and in the case of a mistake, we need to be able to go back. For the purposes of those two additional interactions, we’d need two more buttons, OK and back, or to make it more abstract, we’d need buttons A and B.
So, to successfully navigate a TV interface, we need only a NES controller.
Yes, we can enhance it with touchpads and motion gestures, augment it with voice controls, but this unshakeable foundation of interaction will remain as the very basic level of inherent complexity in a TV interface. Reducing it any further would significantly impair the experience, so all we’ve managed to do throughout the years is to only build upon it.
The D-pad and buttons A and B survived decades of innovation and technological shifts, and chances are they’ll survive many more. By understanding and respecting this principle, you can design intuitive, system-agnostic experiences and easily translate them across platforms. Knowing you can’t go simpler than these six buttons, you’ll easily build from the ground up and attach any additional framework-bound functionality to the time-tested core.
And once you get the grip of these paradigms, you’ll get into mapping and re-mapping buttons depending on context, and understand just how far you can go when designing for TV. You’ll be able to invent new experiences, conduct experiments, and challenge the patterns. But that is a topic for a different article.
Closing ThoughtsWhile designing for TV almost exclusively during the past few years, I was also often educating the stakeholders on the very principles outlined in this article. Trying to address their concerns about different remotes working slightly differently, I found respite in the simplicity of the NES controller and how it got the point across in an understandable way. Eventually, I expanded my knowledge by looking into the developmental history of the remote and was surprised that my analogy had backing in history. This is a fascinating niche, and there’s a lot more to share on the topic. I’m glad we started!
It’s vital to understand the fundamental “ins” and “outs” of any venture before getting practical, and TV is no different. Now that you understand the basics, go, dig in, and break some ground.
Having covered the underlying interaction patterns of TV experiences in detail, it’s time to get practical.
In Part 2, we’ll explore the building blocks of the 10-foot experience and how to best utilize them in your designs. We’ll review the TV design fundamentals (the screen, layout, typography, color, and focus/focus styles), and the common TV UI components (menus, “shelves,” spotlights, search, and more). I will also show you how to start thinking beyond the basics and to work with — and around — the constraints which we abide by when designing for TV. Stay tuned!
by Milan Balać (hello@smashingmagazine.com) at August 27, 2025 01:00 PM
我通常用 Lua 定义一个类型只需要这样做:
-- 定义一个 object 的新类型
local object = {}; object.__index = object
-- 定义构建 object 的函数
local function new_object(self)
return setmetatable(self or {}, object)
end
-- 给 object 添加一个 get 方法
function object:get(what)
return self[what]
end
-- 测试一下
local obj = new_object { x = "x" }
assert(obj:get "x" == "x")
这样写足够简单,如果写熟了就不用额外再做封装。如果一定要做一点封装,可以这样:
local class = {}; setmetatable(class, class)
function class:__index(name)
local class_methods = {}; class_methods.__index = class_methods
local class_object = {}
local class_meta = {
__newindex = class_methods,
__index = class_methods,
__call = function(self, init)
return setmetatable(init or {}, class_methods)
end
}
class[name] = setmetatable(class_object, class_meta)
return class_object
end
封装的意义在于:你可以通过上面这个 class 模块定义新的类型,且能通过它用类型名找到所有定义的新类型。而上面的第一版通常用于放在独立模块文件中,依赖 lua 的模块机制找到 new_object 这个构建方法。
而封装后可以这样用:
-- 定义一个名为 object 的新类型,并添加 get 方法:
local object = class.object
function object:get(what)
return self[what]
end
-- 创建新的 object 实例,测试方法 object:get
local obj = class.object { x = "x" }
assert(obj:get "x" == "x")
如果觉得 local object = class.object 的写法容易产生歧义,也可以加一点小技巧(同时提供特殊的代码文本模式,方便日后搜索代码):
function class:__call(name)
return self[name]
end
-- 等价于 local object = class.object
local object = class "object"
如果我们要定义的类型是一个容器该怎么做好?
容器的数据结构有两个部分:容纳数据的集合和容器的元数据。之前,我通常把元数据直接放在对象实例中,把集合对象看作元数据中的一个。
比如定义一个集合类型 set 以及两个方法 get 和 set :
local set = class "set"
function set:new()
return self {
container = {},
n = 0,
}
end
function set:set(key, value)
local container = self.container
if value == nil then
if container[key] ~= nil then
container[key] = nil
self.n = self.n - 1
end
else
if container[key] == nil then
self.n = self.n + 1
end
container[key] = value
end
end
function set:get(key)
return self.container[key]
end
真正集合容器在 self.container 里,这里 self.n 是集合的元信息,即集合元素的个数。注意这里集合类型需要有一个构造函数 new ,因为它在构造实例时必须初始化 .n 和 .container 。这里的 set:new 构造函数调用了前面生成的 class.set 这个默认构造行为。
测试一下:注意这里用 class.set:new() 调用了构造函数。它等价于 class.set { container = {}, n = 0 } ,因为 .container 和 .n 属于实现细节,所以不推荐使用。
local obj = class.set:new()
obj:set("x", 1)
obj:set("y", 2)
assert(obj.n == 2)
assert(obj:get "x" == 1)
如果使用者要直接访问容器的内部数据结构,它可以用 obj.container 找到引用。但我们可能希望 set 表现得更像 lua table 一样,所以也可能想这样实现:
local set2 = class "set2"
function set2:new()
return self {
_n = 0,
}
end
function set2:set(key, value)
if value == nil then
if self[key] ~= nil then
self[key] = nil
self._n = self._n - 1
end
else
if self[key] == nil then
self._n = self._n + 1
end
self[key] = value
end
end
-- 测试一下
local obj = class.set2:new()
obj:set("x", 1)
obj:set("y", 2)
assert(obj._n == 2)
assert(obj.x == 1)
这个版本去掉了 .container 而直接把数据放在 self 里。所以不再需要 get 方法。为了让元数据 n 区分开,所以改为了 ._n 。
如果规范了命名规则,用下划线区分元数据未尝不是一个好的方法,但在迭代容器的时候会需要剔除它们比较麻烦。所以有时候我们会把元数据外置,这里就需要用到 lua 5.2 引入的 ephemeron table 来帮助 gc 。
local set3 = class "set3"
local SET = setmetatable({}, { __mode = "k" })
function set3:new()
local object = self()
SET[object] = { n = 0 }
return object
end
function set3:set(key, value)
if value == nil then
if self[key] ~= nil then
self[key] = nil
SET[self].n = SET[self].n - 1
end
else
if self[key] == nil then
SET[self].n = SET[self].n + 1
end
self[key] = value
end
end
function set3:__len()
return SET[self].n
end
-- 测试一下:
local obj = class.set3:new()
obj:set("x", 1)
obj:set("y", 2)
assert(#obj == 2)
assert(obj.x == 1)
-- 迭代 obj 已经看不到元数据了。
for k,v in pairs(obj) do
print(k,v)
end
由于 ._n 外部不可见,所以我们用 #obj 来获取它。
如果不想用 ephemeron table 管理元数据,是否有什么简单的方法剔除元数据呢?
最近发现另一个小技巧,那就是使用 false 作为元数据的 key :
local set4 = class "set4"
function set4:new()
return self {
[false] = 0,
}
end
function set4:set(key, value)
if value == nil then
if self[key] ~= nil then
self[key] = nil
self[false] = self[false] - 1
end
else
if self[key] == nil then
self[false] = self[false] + 1
end
self[key] = value
end
end
function set4:__len()
return self[false]
end
-- 测试一下
local obj = class.set4:new()
obj:set("x", 1)
obj:set("y", 2)
for k,v in pairs(obj) do
if k then
print(k,v)
end
end
这个版本几乎和第二版相同,不同的地方只是在于把 ["_n"] 换成了 [false] 。这里只有一个元数据,如果有多个,可以把 [false] = {} 设为一张表。
这样就不需要额外使用弱表,在迭代时也只需要判断 key 是否为真来剔除它。虽然有这么一点点局限,但贵在足够简单。
当然你也可以给它再定义一个 __pairs 方法滤掉 false :
function set4:next(k)
local nk, v = next(self, k)
if nk == false then
return next(self, false)
else
return nk, v
end
end
function set4:__pairs()
return self.next, self
end
或者给加一种叫 class.container 的类型创建方法
local function container_next(self, k)
local nk, v = next(self, k)
if nk == false then
return next(self, false)
else
return nk, v
end
end
function class.container(name)
local container_class = class[name]
function container_class:__pairs()
return container_next, self
end
return container_class
end
如果你不需要 class 提供的默认构造函数,同时不喜欢定义一个新的 new 方法,也可以直接覆盖默认构造函数(同时避免别处再给它增加新的方法):
local set5 = class.container "set5"
function set5:set(key, value)
if value == nil then
if self[key] ~= nil then
self[key] = nil
self[false] = self[false] - 1
end
else
if self[key] == nil then
self[false] = self[false] + 1
end
self[key] = value
end
end
function set5:__len()
return self[false]
end
function class.set5()
return set5 {
[false] = 0,
}
end
local obj = class.set5()
obj:set("x", 1)
obj:set("y", 2)
for k,v in pairs(obj) do
print(k,v)
end
Progressive web apps (PWA) are a fantastic way to turn web applications into native-like, standalone experiences. They bridge the gap between websites and native apps, but this transformation can be prone to introducing design challenges that require thoughtful consideration.
We define our PWAs with a manifest file. In our PWA’s manifest, we can select from a collection of display modes, each offering different levels of browser interface visibility:
fullscreen: Hides all browser UI, using the entire display.standalone: Looks like a native app, hiding browser controls but keeping system UI.minimal-ui: Shows minimal browser UI elements.browser: Standard web browser experience with full browser interface.Oftentimes, we want our PWAs to feel like apps rather than a website in a browser, so we set the display manifest member to one of the options that hides the browser’s interface, such as fullscreen or standalone. This is fantastic for helping make our applications feel more at home, but it can introduce some issues we wouldn’t usually consider when building for the web.
It’s easy to forget just how much functionality the browser provides to us. Things like forward/back buttons, the ability to refresh a page, search within pages, or even manipulate, share, or copy a page’s URL are all browser-provided features that users can lose access to when the browser’s UI is hidden. There is also the case of things that we display on websites that don’t necessarily translate to app experiences.

Imagine a user deep into a form with no back button, trying to share a product page without the ability to copy a URL, or hitting a bug with no refresh button to bail them out!
Much like how we make different considerations when designing for the web versus designing for print, we need to make considerations when designing for independent experiences rather than browser-based experiences by tailoring the content and user experience to the medium.
Thankfully, we’re provided with plenty of ways to customise the web.
Using Media Queries To Target Display ModesWe use media queries all the time when writing CSS. Whether it’s switching up styles for print or setting breakpoints for responsive design, they’re commonplace in the web developer’s toolkit. Each of the display modes discussed previously can be used as a media query to alter the appearance of documents depending.
Media queries such as @media (min-width: 1000px) tend to get the most use for setting breakpoints based on the viewport size, but they’re capable of so much more. They can handle print styles, device orientation, contrast preferences, and a whole ton more. In our case, we’re interested in the display-mode media feature.
Display mode media queries correspond to the current display mode.
Note: While we may set display modes in our manifest, the actual display mode may differ depending on browser support.
These media queries directly reference the current mode:
@media (display-mode: standalone) will only apply to pages set to standalone mode.@media (display-mode: fullscreen) applies to fullscreen mode. It is worth noting that this also applies when using the Fullscreen API.@media (display-mode: minimal-ui) applies to minimal UI mode.@media (display-mode: browser) applies to standard browser mode.It is also worth keeping an eye out for the window-controls-overlay and tabbed display modes. At the time of writing, these two display modes are experimental and can be used with display_override. display-override is a member of our PWA’s manifest, like display, but provides some extra options and power.
display has a predetermined fallback chain (fullscreen -> standalone -> minimal-ui -> browser) that we can’t change, but display-override allows setting a fallback order of our choosing, like the following:
"display_override": ["fullscreen", "minimal-ui"]
window-controls-overlay can only apply to PWAs running on a desktop operating system. It makes the PWA take up the entire window, with window control buttons appearing as an overlay. Meanwhile, tabbed is relevant when there are multiple applications within a single window.
In addition to these, there is also the picture-in-picture display mode that applies to (you guessed it) picture-in-picture modes.
We use these media queries exactly as we would any other media query. To show an element with the class .pwa-only when the display mode is standalone, we could do this:
.pwa-only {
display: none;
}
@media (display-mode: standalone) {
.pwa-only {
display: block;
}
}
If we wanted to show the element when the display mode is standalone or minimal-ui, we could do this:
@media (display-mode: standalone), (display-mode: minimal-ui) {
.pwa-only {
display: block;
}
}
As great as it is, sometimes CSS isn’t enough. In those cases, we can also reference the display mode and make necessary adjustments with JavaScript:
const isStandalone = window.matchMedia("(display-mode: standalone)").matches;
// Listen for display mode changes
window.matchMedia("(display-mode: standalone)").addEventListener("change", (e) => {
if (e.matches) {
// App is now in standalone mode
console.log("Running as PWA");
}
});
Now that we know how to make display modifications depending on whether users are using our web app as a PWA or in a browser, we can have a look at how we might put these newly learnt skills to use.
Users who have an app installed as a PWA are already converted, so you can tweak your app to tone down the marketing speak and focus on the user experience. Since these users have demonstrated commitment by installing your app, they likely don’t need promotional content or installation prompts.
You might need to directly expose more things in PWA mode, as people won’t be able to access the browser’s settings as easily when the browser UI is hidden. Features like changing font sizing, switching between light and dark mode, bookmarks, sharing, tabs, etc., might need an in-app alternative.
There are features you might not want on your web app because they feel out of place, but that you might want on your PWA. A good example is the bottom navigation bar, which is common in native mobile apps thanks to the easier reachability it provides, but uncommon on websites.
People sometimes print websites, but they very rarely print apps. Consider whether features like print buttons should be hidden in PWA mode.
A common annoyance is a prompt to install a site as a PWA appearing when the user has already installed the site. Ideally, the browser will provide an install prompt of its own if our PWA is configured correctly, but not all browsers do, and it can be finicky. MDN has a fantastic guide on creating a custom button to trigger the installation of a PWA, but it might not fit our needs.
We can improve things by hiding install prompts with our media query or detecting the current display mode with JavaScript and forgoing triggering popups in the first place.
We could even set this up as a reusable utility class so that anything we don’t want to be displayed when the app is installed as a PWA can be hidden with ease.
/* Utility class to hide elements in PWA mode */
.hide-in-pwa {
display: block;
}
@media (display-mode: standalone), (display-mode: minimal-ui) {
.hide-in-pwa {
display: none !important;
}
}
Then in your HTML:
<div class="install-prompt hide-in-pwa">
<button>Install Our App</button>
</div>
<div class="browser-notice hide-in-pwa">
<p>For the best experience, install this as an app!</p>
</div>
We could also do the opposite and create a utility class to make elements only show when in a PWA, as we discussed earlier.
Another way to hide content from your site is to set the scope and start_url properties. These aren’t using media queries as we’ve discussed, but should be considered as ways to present different content depending on whether a site is installed as a PWA.
Here is an example of a manifest using these properties:
{ "name": "Example PWA","scope": "/dashboard/","start_url": "/dashboard/index.html","display": "standalone", "icons": [ { "src": "icon.png", "sizes": "192x192", "type": "image/png" } ] }
scope here defines the top level of the PWA. When users leave the scope of your PWA, they’ll still have an app-like interface but gain access to browser UI elements. This can be useful if you’ve got certain parts of your app that you still want to be part of the PWA but which aren’t necessarily optimised or making the necessary considerations.
start_url defines the URL a user will be presented with when they open the application. This is useful if, for example, your app has marketing content at example.com and a dashboard at example.com/dashboard/index.html. It is likely that people who have installed the app as a PWA don’t need the marketing content, so you can set the start_url to /dashboard/index.html so the app starts on that page when they open the PWA.
View transitions can feel unfamiliar, out of place, and a tad gaudy on the web, but are a common feature of native applications. We can set up PWA-only view transitions by wrapping the relevant CSS appropriately:
@media (display-mode: standalone) {
@view-transition {
navigation: auto;
}
}
If you’re really ambitious, you could also tweak the design of a site entirely to fit more closely with native design systems when running as a PWA by pairing a check for the display mode with a check for the device and/or browser in use as needed.
Browser Support And TestingBrowser support for display mode media queries is good and extensive. However, it’s worth noting that Firefox doesn’t have PWA support, and Firefox for Android only displays PWAs in browser mode, so you should make the necessary considerations. Thankfully, progressive enhancement is on our side. If we’re dealing with a browser lacking support for PWAs or these media queries, we’ll be treated to graceful degradation.
Testing PWAs can be challenging because every device and browser handles them differently. Each display mode behaves slightly differently in every browser and OS combination.
Unfortunately, I don’t have a silver bullet to offer you with regard to this. Browsers don’t have a convenient way to simulate display modes for testing, so you’ll have to test out your PWA on different devices, browsers, and operating systems to be sure everything works everywhere it should, as it should.
RecapUsing a PWA is a fundamentally different experience from using a web app in the browser, so considerations should be made. display-mode media queries provide a powerful way to create truly adaptive Progressive Web Apps that respond intelligently to their installation and display context. By leveraging these queries, we can do the following:
The key is remembering that PWA users in standalone mode have different needs and expectations than standard website visitors. By detecting and responding to display modes, we can create experiences that feel more polished, purposeful, and genuinely app-like.
As PWAs continue to mature, thoughtful implementations and tailoring will become increasingly important for creating truly compelling app experiences on the web. If you’re itching for even more information and PWA tips and tricks, check out Ankita Masand’s “Extensive Guide To Progressive Web Applications”.
by Declan Chidlow (hello@smashingmagazine.com) at August 26, 2025 08:00 AM
最近我在 bgg 上闲逛时了解到了“Make-as-You-Play”这个游戏子类型,感觉非常有趣。它是一种用纸笔 DIY (或叫 PnP Print and Play)的游戏,但又和传统 DIY 游戏不同,并不是一开始把游戏做好然后再玩,而是边做边玩。对于前者,大多数优秀的 PnP 都有专业发行商发行,如果想玩可以买一套精美的制成品;但 Make as You Play 不同,做的过程是无法取代的,做游戏就是玩的一部分。
极度未来 Deep Future 是“做即是玩”类型的代表作。它太经典了,以至于有非常多的玩家变体、换皮重制。我玩的官方 1.6 版规则。btw ,作者在 bgg 上很活跃,我在官方论坛八年前的规则讨论贴上问了个规则细节:战斗阶段是否可以不损耗人口“假打”而只是为了获得额外加成效果。作者立刻就回复了,并表示会在未来的 1.7 规则书上澄清这一点。
读规则书的确需要一点时间,但理解了游戏设计精神后,规则其实都很自然,所以游戏进程会很流畅。不过依然有许多细节分散在规则书各处,只有在玩过之后才会注意到。我(单人)玩了两个整天,大约玩了接近 100 局,酣畅淋漓。整个游戏的过程有如一部太空歌剧般深深的刻印在我的脑海里,出生就灭亡的文明、离胜利只有一步之遥的遗憾、兴起衰落、各种死法颇有 RogueLike 游戏的精神。难怪有玩家会经年玩一场战役,为只属于自己战役的科技和文明设计精美的卡片。
在玩错了很多规则细节后,我的第一场战役膨胀到了初始卡组的两倍,而我也似乎还无法顺利胜利哪怕一局。所以我决定重开一盒游戏。新的战役只用了 5 盘就让银河推进到了第二纪元(胜利一次),并在地图上留下了永久印记,并制作了第一张文明卡片。这些会深刻的影响同场战役的后续游戏进程。
我感觉这就是这类游戏的亮点:每场游戏都是独特的。玩的时间越长,当前游戏宇宙的特点就有越来越深刻的理解:宇宙中有什么特别的星球、科技、地图的每个区域有不同的宜居星球密度,哪里的战斗强度会更大一些…… 虽然我只玩了单人模式,但游戏支持最多三人。多人游戏可以协作也可以对抗。你可以邀请朋友偶尔光临你的宇宙玩上两盘,不同的玩家会为同一个宇宙留下不同的遗产。和很多遗产类游戏不同,这个游戏只要玩几乎一定会留下点什么,留不下遗产的游戏局是及其罕见的。也就是说,只要玩下去哪怕一小盘都会将游戏无法逆转的改变。
下面先去掉细节,概述一下游戏规则:
游戏风格类似太空版文明,以一张六边形作为战场。这是边长为 4 的蜂巢地图(类似扩大一圈的卡坦岛),除去无法放置人口方块的中心黑洞,一共是 36 个六边形区格。玩家在以一个母星系及三人口开局,执行若干轮次在棋盘上行动。可用行动非常类似 4X 游戏:生产、探索、繁殖、发展、进攻、殖民。
每个玩家有 4 个进度条:文化 C、力量 M 、稳定 S 、外星 X。除去文化条外,其余三个条从中间开始,一旦任意一条落到底就会失败;而任意一条推进到顶将可能赢得游戏。文化条是从最底部开始,它推进到顶(达成文化胜利)需要更多步数,但没有文化失败。
另外,控制 12 个区域可获得疆域胜利,繁殖 25 个人口可获得人口胜利。失去所有星球也会导致失败。在多人模式中,先失败的玩家可以选择在下个回合直接在当前游戏局重新开始和未失败的玩家继续游戏(但初始条件有可能比全新局稍弱)。
游戏以纯卡牌驱动,每张卡片既是行动卡,又是系统事件卡,同时卡片还是随机性的来源。抽取卡片用于产生随机性的点数分布随着游戏发展是变化的,每场战役都会向不同的方向发展,这比一般的骰子游戏的稳定随机分布会多一些独有的乐趣。
玩家每轮游戏可作最多两个独立且不同的行动:
在执行这些行动的同时,如果玩家拥有更多的科技,就可能有更多的行动附加效果。这些科技带来的效果几乎是推进胜利进度条的全部方法,否则只有和平状态的 BATTLE 行动才能推进一格进度条。
在行动阶段之后,系统会根据玩家帝国中科技卡的数量产生不同数量的负面事件卡。科技卡越多,面临的挑战越大。但可以用手牌支付科技的维护费来阻止科技带来的额外负面事件,或用手牌兑换成商品寄存在母星和科技卡上供未来消除负面事件使用。
负面事件卡可能降低玩家的胜利进度条,最终导致游戏失败;也可能在地图增加新的野生星球及野怪。后者可能最终导致玩家失去已殖民的星球。但足够丰富的手牌以及前面用手牌制造的商品和更多的殖民星球可以用来取消这些负面事件。
每张星球卡和科技卡上都有三个空的科技栏位,在生成卡片时至少会添加一条随机科技,而另两条科技会随着游戏进程逐步写上去。
游戏达成胜利的必要条件是玩家把母星的三条科技开发完,并拥有至少三张完成的科技卡(三条科技全开发完毕),然后再满足上面提到的 6 种胜利方式条件之一:四个胜利进度条 C T S X 至少一条推进到顶,或拥有 12 区域,亦或拥有 25 人口。
胜利的玩家将给当局游戏的母星所在格命名,还有可能创造 wonder ,这会影响后面游戏的开局设定。同时还会根据这局游戏的胜利模式以及取得的科技情况创造出一张新的文明卡供后续游戏使用。
游戏以 36 张空白卡片开始。一共有 6 种需要打出卡片的行动,(EVOKE 和 PLAN 不需要行动卡),每种行动在6 张空白卡上画上角标 1-6 及行动花色。太阳表示 POWER ,月亮表示 SETTLE ,爱心表示 GROW ,骷髅表示 ADVANCE ,手掌表示 BATTLE ,鞋子表示 EXPAND 。这些花色表示卡片在手牌上的行动功能,也可以用来表示负面事件卡所触发的负面事件类别(规则书上有一张事件查阅表)。
数字主要用来生成随机数:比如在生成科技时可以抽一张卡片决定生成每个类别科技中的 6 种科技中的哪一个(规则书上有一张科技查阅表),生成随机地点时则抽两张组成一个 1-36 的随机数。
我初玩的时候搞错了一些规则细节,或是对一些规则有疑惑,反复查阅规则书才确定。
开局的 12 个初始设定星球是从 36 张初始卡片中随机抽取的卡片随机生成的,而不是额外制作 12 张卡片。
如果是多人游戏,需要保证每个玩家的母星上的初始科技数量相同。以最多科技的母星为准,其余玩家自己补齐科技数量。无论是星球卡还是科技卡,三个科技的花色(即科技类别)一定是随机生成的。这个随机性通过抽一张卡片看角标的花色决定。通常具体科技还需要再抽一张卡,通过角标数字随机选择该类别下的特定科技。
每局游戏的 Setup 阶段,如果多个野生星球生成在同一格,野怪上限堆满 5 个即可,不需要外溢。但在游戏过程中由负面事件刷出来的新星球带来的野怪,放满格子 5 个上限后,额外的都需要执行外溢操作:即再抽一张卡,根据 1-6 的数字决定放在该格邻接的 6 格中的哪一格,从顶上面邻格逆时针数。放到版图外面的可以弃掉,如果新放置的格也慢了,需要以新的那格为基准重复这个操作,直到放完规定数量。放在中心黑洞的野怪暂时放在那里,直到所有负面事件执行外,下一个玩家开始前再弃掉。
开始 START 阶段,玩家是补齐 5 张手牌,如果超过 5 张则不能抽牌但也不需要丢到 5 张。超过 10 张手牌则需要丢弃多余的牌。是随机丢牌,不可自选。
殖民星球的 START 科技也可以在开始阶段触发且不必丢掉殖民星球。但在行动阶段如果要使用殖民星球的科技,则是一次性使用,即触发殖民星球上的科技就需要弃掉该星球。
在 START 阶段触发的 Explorarion 科技可以移动一个 cube 。但它并不是 EXPAND 行为,所以不会触发 EXPAND 相关科技(比如 FTL),也无法获得 Wonder 。和 EXPAND 不同,它可以移动区域中唯一的一个 cube ,但是失去控制的区域中如果有殖民星球,需要从桌面弃掉。
玩家不必执行完两个行动、甚至一个行动都不执行也可以。不做满两个行动在行动规划中非常普遍。两个行动必须不相同。
PLAN 行动会立刻结束行动阶段,即使它是第一个行动。所以不能利用 PLAN 制造出来的卡牌在同一回合再行动。
行动的科技增益是可选发动的。同名的科技也可以叠加。母星和桌面的科技卡上提供的科技增益是无损的,但殖民星球和手上的完整科技卡提供的科技是一次性的,用完就需要弃掉。
完成了三项科技的科技卡被称作完整科技卡,才可以在当前游戏中当手牌使用。不完整科技卡是不能当作手牌提供科技增益的。
SETTLE 行动必须满足全部条件才可以发动。这些条件包括,你必须控制想殖民的区域(至少有一个人口在那个格子);手上需要有这个格子对应的星球卡或该星球作为野生星球卡摆在桌面。手上没有对应格的星球卡时,想殖民必须没有任何其它星球卡才可以。这种情况下,手上有空白卡片必须用来创造一张新的星球卡用于殖民,只有没有空白卡时,才创造一张全新的星球卡。多人游戏时,创造新的星球卡的同时必须展示所有手牌以证明自己没有违反规则。如果殖民的星球卡是从手牌打出,记得在打出星球卡后立刻抽一张牌。新抽的牌如果是完整科技卡也可以立刻使用。如果星球卡是新创造的,或是版图上的,则不抽卡。
SETTLE 版图上的野生星球的会获得一个免费的 POWER 行动和一个免费的 ADVANCE 行动。所谓免费指不需要打出行动手牌,也不占用该回合的行动次数。这视为攻打野生星球的收益,该收益非常有价值,但它是可选的,你也可以选择不执行
SETTLE 的 Society 科技增益可以让玩家无视规则限制殖民一个星球。即不再受“手牌中没有其它可殖民星球”这条限制,所以玩家不必因此展示手牌。使用 Society 科技额外殖民的星球总是可以选择使用手上的空白卡或创造一张新卡。这个科技不可堆叠,每个行动永远只能且必须殖民一个星球。
SETTLE 的 Goverment 科技增益可以叠加,叠加时可以向一科技星球(星球卡创建时至少有一科技)添加两个科技,此时玩家先添加两个随机花色,然后可以圈出其中一个选择指定科技,而不需要随机选择。
GROW 的 Biology 科技增益必须向不同的格子加人口,叠加时也需要每个人口都放在不同格。如果所控区域太少,可能浪费掉这些增益。
如果因为人口上限而一个人口也无法增加,GROW 行动无法发动。所以不能打出 GROW 卡不增加人口只为了获得相关科技增益。
未完成的科技卡在手牌中没有额外功能。它只会在 ADVANCE 行动中被翻出并添加科技直到完成。如果 ADVANCE 时没有翻出空白卡或未完成的科技卡,则创造一张新科技卡。新创建的科技卡会立刻随机生成三个随机花色。玩家可以选择其中一个花色再随机出具体科技。在向未完成的科技卡上添加新科技时,如果卡上没有圈,玩家可以选择圈出一个花色自主选择科技,而不必随机。一张卡上如果圈过,则不可以再自主选择。
ADVANCE 的 Chemistry 科技增益可以重选一次随机抽卡,可以针对花色选择也可以针对数字选择。但一个 Chemistry 只能重选一次,这个科技可以叠加。
ADVANCE 的 Physics 科技增益只针对科技卡,不能针对星球卡。所以,无论 Physics 叠加与否,都最多向科技卡添加两条科技(因为科技卡一定会至少先生成一条)。当 Physics 叠加两次时(三次叠加没有意义),科技卡上的三条科技都可以由玩家自主选择(每一科技卡原本就可以有一条自由选择权,叠加 Physics 增加了一次选择权)。注意,花色一定是随机生成的。这个增益增加的是玩家对科技的选择权。
只有在所有邻接格都没有敌人(野怪和其他玩家)时,才可以发动 BATTLE 行动的推进任意胜利条的功能。战斗默认是移除自己的人口,再移除敌人相同数量的人口。但可以选择移除自己 0 人口来仅仅发动对应增益。所以 BATTLE 行动永远都是可选的。
BATTLE 的 Military 科技增益新增的战场可以重叠,即可以从同一己方格攻打不同敌人格,也可以从多个己方格攻打同一敌人格。和 Defence 科技增益同时生效时,可以一并结算。
EXPAND 行动必须移动到空格或己方控制格,但目的地不可以超过 5 人口上限。永远不会在同一格中出现多个颜色的人口。移动必须在出发地保留至少一个人口。当永远 FTL 科技增益时,可以移动多格,途经的格不必是空格,也可以是中心黑洞。
EXPAND 行动移动到有 Wonder (过去游戏留下来的遗产)的格子,且该格为空时,可以通过弃掉对应花色的手牌发动 Wonder 能力,其威力为弃牌的角标数字。Wonder 只能通过 EXPAND 触发,不会因为开局母星坐在 Wonder 格触发。
BATTLE 的 spaceship 科技增益需要选择不同的目的地,多个叠加也需要保证每个目的地都不相同。
PLAN 行动制造新卡时,只有花色是自选的,数字还是随机的。PLAN 会结束所有行动。
行动阶段后的 Payment 阶段可以用来消除之后 Challenge 阶段的负面事件数量。方法是打出和母星及科技卡上科技增益的花色。针对母星以及每张科技卡分别打出一张。如果卡片上有多个科技增益花色,任选其中一个即可。科技卡上未填上的增益对应的花色则不算。每抵消一张就可以减少一张事件卡,但事件卡最后至少会加一张。每次抵消一次事件,都可以所在卡片(母星或科技卡)上添加一个 upkeep 方块。每张卡上的方块上限为 3 ,不用掉就不再增加。但到达上限后,玩家依旧可以用手牌抵消事件,只不过不再增加方块。
挑战阶段,一张张事件卡翻开。玩家可以用对应花色的手牌取消事件,也可以使用桌面方块取消,只需要方块所在卡片上有同样花色。还可以使用殖民星球取消,需要该星球上有对应花色的科技(不是星球卡的角标花色)。但使用殖民星球需要弃掉该星球卡。不可使用母星抵消事件卡。
事件生效时,如果需要向版图添加野怪。这通常是增加随机方块事件,和增加野外星球事件(带有 5 方块)。增加的方块如果在目标格溢出,需要按规则随机加在四周。
如果增加的方块所在格有玩家的方块,需要先一对一消除,即每个增加的野怪先抵消掉一个玩家方块。如果玩家因此失去一个区域,该区域对应的桌面星球也需要扔掉,同时扔掉牌上面的方块。如果母星因此移除,玩家可以把任意殖民星球作为新的母星。移除的母星会变成新的野外星球。如果玩家因此失去所有星球就会失败。在多人游戏中,失败的玩家所有人口都会弃掉,同时在哪些有人口的格放上一个野怪。
游戏胜利条件在行动阶段达成时就立刻胜利,而不需要执行后续的挑战行动。在单人游戏中,除了满足常规的胜利条件外,还需要根据版图上的 Wonder 数量拥有对应数量的殖民星球(但最多 4 个)。这些殖民星球需要在不同的区格,且不在母星系。玩家胜利后应给当前母星所在格标注上名字,这个格子会在后续游戏中刷多一个野怪。玩家可以创建一张文明卡,文明卡的增益效果和胜利条件以及所拥有的科技相关,不是完全自由选择。
不是每局胜利都会创造 Wonder 。需要玩家拥有至少 5 个同花色科技,才能以此花色创造 Wonder 。每个 Wonder 还需要和胜利模式组合。Wonder 以胜利玩家的母星位置标注在版图上,胜利模式和科技花色的组合以及 Wonder 地点不能在版图中重复。
这个游戏给我的启发很大。它有很多卡牌游戏和电子游戏的影子,但又非常独特。
不断制作卡牌的过程非常有趣,有十足的创造感。读规则书时我觉得我可能不会在玩的过程中给那些星球科技文明起名字,反正不影响游戏过程,留空也无所谓。但实际玩的时候,我的确会给三个半随机组合起来的完整科技卡起一个贴切的名称。因为创造一张完整的科技卡并不容易,我在玩的过程中就不断脑补这是一项怎样的科技,到可以起名的时候已经水到渠成了。
更别说胜利后创建文明卡。毕竟游戏的胜利来得颇为艰难。在失败多次后,脑海中已经呈现出一部太空歌剧,胜利的文明真的是踏着前人的遗产(那些创建出来的独有卡片)上成功。用心绘制一张文明卡真的是乐趣之一。我在 bgg 上看到有玩家精心绘制的带彩色头像的文明卡,心有戚戚。
游戏的平衡设计的非常好,有点难,但找到策略后系统也不是不可战胜的。关键是胜利策略会随着不断进行的游戏而动态变化:卡牌角标会因新卡的出现而改变概率分布,新的科技卡数量增加足以影响游戏策略,卡组里的星球科技会进化,星球在版图上的密度及分布也会变化…… 开局第一代策略和多个纪元的迭代后的策略可能完全不同,这让同一个战役(多局游戏的延展)的重玩价值很高。
用卡牌驱动随机性是一个亮点:以开始每种行动都是 6 张,均匀分布。但会因为星球卡打在桌面(从卡堆移除)而变化;更会因为创造新卡而变化。尤其是玩家可以通过 PLAN 主动创建特定花色卡片,这个创造过程也不是纯随机的,可以人为引导。负面事件的分布也会因此而收到影响。
用科技数量驱动负面事件数量是一个巧妙的设计。玩家获得胜利至少需要保有 6 个科技,即使在游戏后期纪元,也至少需要创造一个新科技,这会让游戏一定处于不断演变中。强力的桌面卡虽然一定程度的降低了游戏难度,但科技越多,每个回合潜在的负面事件也越多。以 3 科技开局的母星未必比单科技开局更容易,只是游戏策略不同而已。
每局游戏的科技必须创造出来(而不是打出过去游戏创造的科技牌)保证了游戏演变,也一定程度的平衡了游戏。即使过去的游戏创造出一张特别强力的科技,也不可以直接打在本局游戏的桌面。而只能做一次性消耗品使用。
一开始,负面事件的惩罚远高于单回合能获得的收益。在不太会玩的时候,往往三五回合就突然死亡了。看起来是脸黑导致的,但游戏建议玩家记录每局游戏的过程,一是形成一张波澜壮阔的银河历史,二是当玩家看到自己总是死于同一事件时有所反思,调整后续的游戏策略。
而战役的开局几乎都是白卡和低科技星球,一定程度的保护了新手玩家,平缓了游戏的学习曲线。边玩边做的模式让战役开局 setup 时间也不会太长,玩家也不会轻易放弃正常战役。
单局失败是很容易接受的,这是因为:单局时间很短,我单刷时最快 3 分钟一局,长局也很少超过 10 分钟。每局 setup 非常快。而游戏演化机制导致了玩家几乎不可能 undo 最近玩的一局,因为卡组已经永久的改变了。不光是新卡(因为只增加新卡的话,把新制造的卡片扔掉就可以 undeo ),还会在已有的卡牌上添加新的条目。
虽然我只玩了单人模式(并用新战役带朋友开了几局多人模式),但可以相像一个战役其实可以邀请其他玩家中途加入玩多人模式。多人模式采用协作还是对抗都可以,也可以混杂。协作和对抗会有不同的乐趣,同时都会进化战役本身。这在遗产类桌游中非常少见:大多数遗产类游戏都有一个预设的剧本和终局条件,大多推荐固定队伍来玩。但这个游戏没有终局胜利,只有不断创造的历史和不断演化的环境,玩家需要调整自己的策略玩下一局。
这个月我开了个新项目:制作 deep future 的电子版。
之所以做这个事情,是因为我真的很喜欢这个游戏。而过去一年我在构思一个独立游戏的玩法时好像进入了死胡同,我需要一些设计灵感,又需要写点代码保持一下开发状态。思来想去,我确定制作一个成熟桌游的电子版是一个不错的练习。而且这个游戏的单人玩法很接近电子游戏中的 4x 类型,那也是我喜欢的,等还原了原版桌游规则后,我应该可以以此为基础创造一些适合电子游戏特性的东西来。
另一方面,我自以为了解游戏软件从屏幕上每个像素点到最终游戏的技术原理,大部分的过程都亲身实践过。但我总感觉上层的东西,尤其是游戏玩法、交互等部分开发起来没有底层(尤其是引擎部分)顺畅。我也看到很多实际游戏项目的开发周期远大于预期,似乎开发时间被投进了黑洞。
在 GameJam 上两个晚上可以做出的游戏原型,往往又需要花掉 2,3 年时间磨练成成品。我想弄清楚到底遇到了怎样的困难,那些不明不白消耗掉的开发时间到底去了哪里。
这次我选择使用前几个月开发的 soluna 作为引擎。不使用前些年开发的 Ant Engine 的原因 在这个帖子里写得很清楚了。至于为什么不用现成的 unreal/unity/godot 等,原因是:
我明白我要做什么事,该怎么做,并不需要在黑盒引擎的基础上开发。是的,虽然很多流行引擎有源码,但在没有彻底阅读之前,我认为它们对我的大脑还是黑盒。而阅读理解这些引擎代码工程巨大。
我的项目不赶时间,可以慢慢来。我享受开发过程,希望通过开发明白多一些道理,而不是要一个结果。我希望找到答案,可能可以通过使用成熟引擎,了解它们是怎样设计的来获得;但自己做一次会更接近。
自己从更底层开发可以快速迭代:如果一个设计模式不合适,可以修改引擎尝试另一个模式。而不是去追寻某个通用引擎的最佳实践。
我会使用很多成熟的开源模块和方案。但通常都是我已经做过类似的工作,期望可以和那些成熟模块的作者/社区共建。
这个项目几乎没有性能压力。我可以更有弹性的尝试不同的玩法。成熟引擎通常为了提升某些方面的性能,花去大量的资源做优化,并做了许多妥协。这些工作几乎是不可见的。也就是说,如果使用成熟引擎开发,能利用到的部分只是九牛一毛,反而需要花大量精力去学习如何用好它们;而针对具体需求自己开发,花掉的精力反而更有限,执行过程也更为有趣。
这篇 blog 主要想记录一下这大半个月以来,我是怎样迭代引擎和游戏的。我不想讨论下面列举出来的需求的最佳方案,现在已经完成的代码肯定不是,之后大概率也会再迭代掉。我这个月的代码中一直存在这样那样的“临时方案”、“全局状态”、甚至一些复制粘贴。它们可能在下一周就重构掉,也可能到游戏成型也放在那里。
重要的是过程应该被记录下来。
在一开始,我认为以立即模式编写游戏最容易,它最符合人的直觉:即游戏是由一帧帧画面构成的,只需要组帧绘制需要的画面就可以了。立即模式可以减少状态管理的复杂度。这一帧绘制一个精灵,它就出现在屏幕上;不绘制就消失了。
大部分成熟引擎提供的则是保留模式:引擎维护着一组对象集合,使用者创建或删除对象,修改这些对象的视觉属性。这意味着开发者需要做额外的很多状态管理。如果引擎维持的对象集合并非平坦结构,而是树状容器结构,这些状态管理就更复杂了。
之所以引擎喜欢提供保留模式大概是因为这样可以让实现更高效。而且在上层通过恰当的封装,立即模式和保留模式之间也是可以互相转换的。所以开发者并不介意这点:爱用立即模式开发游戏的人做一个浅封装层就可以了。
但我一开始就选择立即模式、又不需要考虑性能的话,一个只对图形 api 做浅封装的引擎直接提供立即模式最为简单。所以一开始,soluna 只提供了把一张图片和一个单独文字显示在屏幕特定位置的 api 。当然,使用现代图形 api ,给绘制指令加上 SRT 变换是举手之劳。(在 30 年前,只有一个 framebuffer 的年代,我还需要用汇编编写大量关于旋转缩放的代码)
在第一天,我从网上找来了几张卡牌的图片,只花了 10 分钟就做好了带动画和非常简单交互的 demo 。看起来还很丝滑,这给我不错的愉悦感,我觉得是个好的开始。
想想小丑牌也是用 Love2D 这种只提供基本 2d 图片渲染 api 的引擎编写出来的,想来这些也够用了。当然,据说小丑牌做了三年。除去游戏设计方面的迭代时间外,想想程序部分怎么也不需要这么长时间,除非里面有某些我察觉不到的困难。
接下来,我考虑搭一些简单的交互界面以及绘制正式的卡牌。
Deep future 的卡牌和一般的卡牌游戏还不一样。它没有什么图形元素,但牌面有很多文字版面设计。固然,我可以在制图设计软件里定下这些版面的位置,然后找个美术帮我填上,如果我的团队有美术的话……这是过去在商业公司的常规做法吧?可是现在我一个人,没有团队。这是一件好事,可以让我重新思考这个任务:我需要减少这件我不擅长的事情的难度。我肯定会大量修改牌面的设计,我得有合适我自己的工作流。
在 Ant 中,我们曾经集成过 RmlUI :它可以用 css 设计界面。css 做排版倒是不错,虽然我也不那么熟悉,但似乎可以完成所有需求。但我不喜欢写 xml ,也不喜欢 css 的语法,以及很多我用不到的东西。所以,我决定保留核心:我需要一个成熟的排版用的结构化描述方案,但不需要它的外表。
所以我集成了 Yoga ,使用 Lua 和我自己设计的 datalist 语言来描述这个版面设计。如果有一天,我想把这个方案推广给其他人用,它的内在结构和 css 是一致的,写一个转换脚本也非常容易。
暂时我并不需要和 Windows 桌面一样复杂的界面功能。大致上有单个固定的界面元素布局作为 HUD (也就是主界面)就够了。当然,用 flexbox 的结构来写,自动适应了不同的分辨率。采用这种类 CSS 的排版方案,实际上又回到了保留模式:在系统中保留一系列的需要排版布局的对象。
当我反思这个问题时,我认为是这样的:如果一个整体大体是不变的,那么把这个整体看作黑盒,其状态管理被封装在内部。使用复杂度并没有提高。这里的整体就是 HUD 。考虑到游戏中分为固定的界面元素和若干可交互的卡片对象,作为卡牌游戏,那些卡牌放在 HUD 中的容器内的。如果还是用同样的方案管理卡片的细节,甚至卡片本身的构图(它也是由更细分的元素构成的)。以保留模式整个管理就又变复杂了。
所以,我在 yoga 的 api 封装层上又做了一层封装。把界面元素分为两类:不变的图片和文字部分,和需要和玩家交互的容器。容器只是由 yoga 排版的一个区域,它用 callback 的形式和开发者互动就可以了。yoga 库做的事情是:按层次结构遍历处理完整个 DOM ,把所有元素平坦成一个序列,每个元素都还原成绝对坐标和尺寸,去掉层次信息,只按序列次序保留绘制的上下层关系。在这个序列中,固定的图片和文字可以直接绘制,而遇到互动区,则调用用户提供的函数。这些函数还是以立即模式使用:每帧都调用图形 API 渲染任意内容。
用下来还是挺舒服的。虽然 callback 的形式我觉得有点芥蒂,但在没找到更好的方式前先这么用着,似乎也没踩到什么坑。
渲染模块中,一开始只提供了文字和图片的渲染。但我留出了扩展材质的余地。文字本身就是一种扩展材质,而图片是默认的基础材质。做到 UI 时,我发现增加一种新的材质“单色矩形”特别有用。
因为我可以在提供给 yoga 的布局数据中对一些 box 标注,让它们呈现出不同颜色。这可以极大的方便我调试布局。尤其是我对 flexbox 布局还不太熟练的阶段,比脑补布局结果好用得多。
另一个有用的材质是对一张图片进行单色渲染,即只保留图片的 alpha 通道,而使用单一颜色。这种 mask 可以用来生成精灵的阴影,也可以对不规则图片做简单遮罩。
在扩展材质的过程中,发现了之前预留的多材质结构有一些考虑不周全的设计,一并做了修改。
到绘制卡牌时,卡牌本身也有一个 DOM ,它本质上和 HUD 的数据结构没什么区别,所以这个数据结构还是嵌套了。一开始,我在 soluna 里只提供了平坦的绘制 api ,并没有层次管理。一开始我做的假设是:这样应该够用。显然需要打破这个假设了。
我给出的解决方案是:在立即模式下,没必要提供场景树管理,但可以给一个分层堆栈。比如将当前的图层做 SRT 变换,随后的绘图指令都会应用这套变换,直到关闭这个图层(弹出堆栈)。这样,我想移动甚至旋转缩放 HUD 中的一个区域,对于这个区域的绘制指令序列来说都是透明的:只需要在开始打开一个新图层,结束时关闭这个图层即可。
另一个需求是图文混排,和文字排版。一开始我假设引擎只提供单一文字渲染的功能就够用,显然是不成立的。Yoga 也只提供 box 的排版,如果把每个单字都作为一个 box 送去 yoga 也不是不行,但直觉告诉我这不但低效,还会增加使用负担。web 上也不是针对每个单字做排版的。用 Lua 在上层做图片和文字排版也可以,但对性能来说太奢侈了。
这是一个非常固定的需求:把一块有不同颜色和尺寸的文字放在一个 box 中排版,中间会插入少许图片。过去我也设计过不少富文本描述方案,再做一次也不难。这次我选择一半在 C 中实现,一半在 Lua 中实现。C 中的数据结构利于程序解析,但书写起来略微繁琐;Lua 部分承担易于人书写的格式到底层富文本结构的转换。Lua 部分并不需要高频运行,可以很方便的 cache 结果(这是 Lua 所擅长的),所以性能不是问题。
至于插入的少许图片,我认为把图片转换为类似表情字体更简单。我顺手在底层增加了对应的支持:用户可以把图片在运行时导入字体模块。这些图片作为单独的字体存在,codepoint 可以和 unicode 重叠。并不需要以 unicode 在文本串中编码这些图片,而将编码方式加入上述富文本的结构。
在绘制文本的环节,我同时想到了本地化模块该如何设计。这并非对需求的未雨绸缪,而是我这些年来一直在维护群星的汉化 mod 。非常青睐 Paradox 的文本方案。这不仅仅是本地化问题,还涉及游戏中的文本如何拼接。尤其是卡牌游戏,关于规则描述的句子并非 RPG 中那样的整句,而是有很多子句根据上下文拼接而来的。
拼句子和本地化其实是同一个问题:不同语言间的语法不同,会导致加入一些上下文的句子结构不同。P 社在这方面下了不少功夫,也经过了多年的迭代。我一直想做一套类似的系统,想必很有意思。这次了了心愿。
我认为代码中不应该直接编码任何会显示出来的文本,而应该统一使用点分割的 ascii 字串。这些字串在本地化模块那里做第一次查表转换。
有很大一部分句子是由子句构成的,因为分成子句和更细分的语素可以大大降低翻译成不同语言的工作量。这和代码中避免复制粘贴的道理是一样的:如果游戏中有一个术语出现在不同语境下,这个术语在本地化文本中只出现在唯一地方肯定最好。所以,对于文本来说,肯定是大量的交叉引用。我使用 $(key.sub.foobar) 的方式来描述这种交叉引用。注:这相当于 P 社语法中的 $key.sub.foobar$ 。我对这种分不清开闭的括号很不感冒。
另一种是对运行环境中输入的文本的引用:例如对象的名字、属性等。我使用了 ${key} 这样的语法,大致相当于 P 社的 [key] 。但我觉得统一使用 $ 前缀更好。至于图标颜色、字体等标注,在 P 社的语法中花样百出,我另可使用一致的语法:用 [] 转义。
这个文本拼接转换的模块迭代了好几次。因为我在使用中总能发现不完善的实现。估计后面还会再改动。好在有前人的经验,应该可以少走不少弯路吧。
和严肃的应用不同,游戏的交互是很活泼的。一开始我并没有打算实现元素的动画表现,因为先实现功能仿佛更重要。但做着做着,如果让画面更活泼一点似乎心情更愉悦一点。
比如发牌。当然可以直接把发好的牌画在屏幕指定区域。但我更希望有一个动态的发牌过程。这不仅仅是视觉感受,更能帮助不熟悉游戏规则的玩家尽快掌控卡牌的流向。对于 Deep Future 来说更是如此:有些牌摸出来是用来产生随机数的、有些看一眼就扔掉了、不同的牌会打在桌面不同的地方。如果缺少运动过程的表现,玩家熟悉玩法的门槛会高出不少。
但在游戏程序实现的逻辑和表现分离,我认为是一个更高原则,应尽可能遵守。这部分需要一点设计才好。为此,我并没有草率给出方案尽快试错,而是想了两天。当然,目前也不是确定方案,依旧在迭代。
css 中提供了一些关于动画的属性,我并没有照搬采用。暂时我只需要的运动轨迹,固然轨迹是对坐标这个属性的抽象,但一开始没必要做高层次的抽象。另外,我还需要保留对对象的直接控制,也就是围绕立即模式设计。所以我并没有太着急实现动画模块,而且结合另一个问题一起考虑。
游戏程序通常是一个状态机。尤其是规则复杂的卡牌游戏更是。在不同阶段,游戏中的对象遵循不同的规则互动。从上层游戏规则来看是一个状态机,从底层的动画表现来看也是,人机交互的界面部分亦然。
从教科书上搬出状态机的数据结构,来看怎么匹配这里的需求,容易走向歧途;所以我觉得应该先从基本需求入手,不去理会状态机的数据结构,先搭建一个可用的模块,再来改进。
Lua 有 first class 的 coroutine ,非常适合干这个:每个游戏状态是一个过程(相对一帧画面),有过程就有过程本身的上下文,天然适合用 coroutine 表示。而底层是基于帧的,显然就适合和游戏的过程分离开。
以发牌为例:在玩家行动阶段,需要从抽牌堆发 5 张牌到手牌中。最直接的做法是在逻辑上从牌堆取出 5 张牌,然后显示在手牌区。
我需要一个发牌的视觉表现,卡牌从抽牌堆移动到手牌区,让玩家明白这些牌是从哪里来的。同时玩家也可以自然注意到在主操作区(手牌区)之外还有一个可供交互的牌堆。
用立即模式驱动这个运动轨迹,对于单张牌来说最为简单。每帧计算牌的坐标,然后绘制它就可以了。但同时发多张牌就没那么直接了。
要么一开始就同时记录五张牌的目的地,每帧计算这五张牌的位置。这样其实是把五张牌视为整体;要么等第一张牌运动到位,然后开始发下一张牌。这样虽然比较符合现实,但作为电子游戏玩,交互又太啰嗦。
通常我们要的行为是:这五张牌连续发出,但又不是同时(同一帧)。牌的运动过程中,并非需要逐帧关注轨迹,而只需要关注开始、中途、抵达目的地三个状态。其轨迹可以一开始就确定。所以,卡牌的运动过程其实处于保留模式中,状态由系统保持(无需上层干涉),而启动的时机则交由开发者精确控制更好。至于中间状态及抵达目的地的时机,在这种对性能没太大要求的场景,以立即模式逐帧轮询应无大碍(必须采用 callback 模式)。
也就是,直观的书写回合开始的发牌流程是这样的:
for i = 1, 5 do draw_card() -- 发一张牌 sleep(0.1) -- 等待 0,1 秒 end
这段代码作为状态机逻辑的一部分天然适合放在单独的 coroutine 中。它可以和底层的界面交互以及图形渲染和并行处理。
而发牌过程,则应该是由三个步骤构成:1. 把牌设置于出发区域。2. 设定目的地,发起移动请求。3. 轮询牌是否运动到位,到位后将牌设置到目的地区域。
其中步骤 1,2 在 draw_card 函数中完成最为直观,因为它们会在同一帧完成。而步骤 3 的轮询应该放在上述循环的后续代码。采用轮询可以避免回调模式带来的难以管理的状态:同样符合直观感受,玩家需要等牌都发好了(通常在半秒之内)再做后续操作。
我以这样的模式开发了一个基于 coroutine 的简单状态机模块。用了几天觉得还挺舒适。只不过发现还是有一点点过度设计。一开始我预留了一些 api 供使用者临时切出当前状态,进入一个子状态(另一个 coroutine),完成后再返回;还有从一个过程中途跳出,不再返回等等。使用一段时间以后,发现这些功能是多余的。后续又简化掉一半。
至于动画模块,起初我认为一切都围绕卡牌来做就可以了。可以运动的基本元素就是不同的卡片。后来发现其实我还需要一些不同于卡片的对象。运动也不仅仅是位移,还包括旋转和缩放,以及颜色的渐变。
至于对象运动的起点和终点,都是针对的前面所述的“区域”这个概念。一开始“区域”只是一个回调函数;从这里开始它被重构成一个对象,有名字和更多的方法。“区域”也不再属于同一个界面对象,下面会谈到:我一开始的假设,所有界面元素在唯一 DOM 上,感觉是不够用的。我最终还是需要管理不同的 DOM ,但我依旧需要区域这个概念可以平坦化,这样可以简化对象可以在不同的 DOM 间运动的 API。
运动过程本身,藏在较低的层次。它是一个独立模块,本质上是以保留模式管理的。在运动管理模块中,保留的状态仅仅是时间轴。也就是逐帧驱动每个运动对象的时间轴(一个数字)。逐帧处理部分还是立即模式的,传入对象的起点和终点,通过时间进度立即计算出当前的状态,并渲染出来。
从状态管理的角度看,每帧的画面和动画管理其实并不是难题。和输入相关的交互管理更难一些,尤其是鼠标操作。对于键盘或手柄,可以使用比较直观的方式处理:每帧检查当下的输入内容和输入状态,根据它们做出反应即可。而鼠标操作天生就是事件驱动的,直到鼠标移动到特定位置,这个位置关联到一个可交互对象,鼠标的点击等操作才有了特别的含义。
ImGUI 用了一种立即模式的直观写法解决这个问题。从使用者角度看,它每帧轮询了所有可交互对象,在绘制这些对象的同时,也依次检查了这些对象是否有交互事件。我比较青睐这样的用法,但依然需要做一些改变。毕竟 ImGUI 模式不关注界面的外观布局,也不擅长处理运动的元素。
我单独实现了一个焦点管理模块。它内部以保留模式驱动界面模块的焦点响应。和渲染部分一样,处理焦点的 API 也使用了一些 callback 注入。这个模块仅管理哪个区域接收到了鼠标焦点,每个区域通过 callback 函数再以立即模式(轮询的方式)查询焦点落在区域内部的哪个对象上。
在使用层面,开发者依然用立即模式,通过轮询获取当前的鼠标焦点再哪个区域哪个对象上;并可查询当前帧在焦点对象上是否发生了交互事件(通常是点击)。这可以避免用 callback 方式接收交互事件,对于复杂的状态机,事件的 callback 要难管理的多。
一开始我认为,单一 HUD 控制所有界面元素就够了。只需要通过隐藏部分暂时不用的界面元素就可以实现不同游戏状态下不同的功能。在这个约束条件下,代码可以实现的非常简单。但这几天发现不太够用。比如,我希望用鼠标右键点击任何一处界面元素,都会对它的功能做一番解说。这个解说界面明显是可以和主界面分离的。我也有很大意愿把两块隔离开,可以分别独立开发测试。解说界面是帮助玩家理解游戏规则和交互的,和游戏的主流程关系不大。把它和游戏主流程放在一起增加了整体的管理难度。但分离又有悖于我希望尽可能将对象管理平坦化的初衷,我并不希望引入树状的对象层次结构。
最近的设计灵感和前面绘制模块的图层设计类似,我给界面也加入了图层的概念。永远只有一个操作层,但层次之间用栈管理。在每个状态看到的当下,界面的 DOM 都是唯一的。状态切换时则可以将界面压栈和出栈。如果后续不出现像桌面操作系统那样复杂的多窗口结构的话,我想这种栈结构分层的界面模式还可以继续用下去。
另一个变动是关于“区域”。之前我认为需要参与交互的界面元素仅有“区域”,“区域”以立即模式自理,逐帧渲染自身、轮询焦点状态处理焦点事件。最近发现,额外提供一种叫“按钮”的对象会更方便一些。“按钮”固然可以通过“区域”来实现,但实践中,处理“按钮”的不是“按钮”本身,而是容纳“按钮”的容器,通常也是最外层的游戏过程。给“按钮”加上类似 onclick 的 callback 是很不直观的;更直观的做法是在游戏过程中,根据对应的上下文,检查是否有关心的按钮被点击。
所有的按钮的交互管理可以放在一个平坦的集合中,给它们起上名字。查询时用 buttons.click() == "我关心的按钮名字" 做查询条件,比用 button_object.click() 做查询条件要舒服一点。
以上便是最近一个月的部分开发记录。虽然,代码依旧在不断修改,方案也无法确定,下个月可能还会推翻目前的想法。但我感觉找到了让自己舒适的节奏。
不需要太着急去尽快试错。每天动手之前多想想,少做一点,可以节省很多实作耗掉的精力;也不要过于执著于先想清楚再动手,毕竟把代码敲出带来的情绪价值也很大。虽然知道流畅的画面背后有不少草率的实现决定,但离可以玩的游戏更进一步的心理感受还是很愉悦的。
日拱一卒,功不唐捐。
Artificial Intelligence isn’t new, but in November 2022, something changed. The launch of ChatGPT brought AI out of the background and into everyday life. Suddenly, interacting with a machine didn’t feel technical — it felt conversational.
Just this March, ChatGPT overtook Instagram and TikTok as the most downloaded app in the world. That level of adoption shows that millions of everyday users, not just developers or early adopters, are comfortable using AI in casual, conversational ways. People are using AI not just to get answers, but to think, create, plan, and even to help with mental health and loneliness.
In the past two and a half years, people have moved through the Kübler-Ross Change Curve — only instead of grief, it’s AI-induced uncertainty. UX designers, like Kate (who you’ll meet shortly), have experienced something like this:
As designers move into experimentation, they’re not asking, Can I use AI? but How might I use it well?.
Using AI isn’t about chasing the latest shiny object but about learning how to stay human in a world of machines, and use AI not as a shortcut, but as a creative collaborator.
It isn’t about finding, bookmarking, downloading, or hoarding prompts, but experimenting and writing your own prompts.
To bring this to life, we’ll follow Kate, a mid-level designer at a FinTech company, navigating her first AI-augmented design sprint. You’ll see her ups and downs as she experiments with AI, tries to balance human-centered skills with AI tools, when she relies on intuition over automation, and how she reflects critically on the role of AI at each stage of the sprint.
The next two planned articles in this series will explore how to design prompts (Part 2) and guide you through building your own AI assistant (aka CustomGPT; Part 3). Along the way, we’ll spotlight the designerly skills AI can’t replicate like curiosity, empathy, critical thinking, and experimentation that will set you apart in a world where automation is easy, but people and human-centered design matter even more.
Note: This article was written by a human (with feelings, snacks, and deadlines). The prompts are real, the AI replies are straight from the source, and no language models were overworked — just politely bossed around. All em dashes are the handiwork of MS Word’s autocorrect — not AI. Kate is fictional, but her week is stitched together from real tools, real prompts, real design activities, and real challenges designers everywhere are navigating right now. She will primarily be using ChatGPT, reflecting the popularity of this jack-of-all-trades AI as the place many start their AI journeys before branching out. If you stick around to the end, you’ll find other AI tools that may be better suited for different design sprint activities. Due to the pace of AI advances, your outputs may vary (YOMV), possibly by the time you finish reading this sentence.
Cautionary Note: AI is helpful, but not always private or secure. Never share sensitive, confidential, or personal information with AI tools — even the helpful-sounding ones. When in doubt, treat it like a coworker who remembers everything and may not be particularly good at keeping secrets.
Prologue: Meet Kate (As She Preps For The Upcoming Week)Kate stared at the digital mountain of feedback on her screen: transcripts, app reviews, survey snippets, all waiting to be synthesized. Deadlines loomed. Her calendar was a nightmare. Meanwhile, LinkedIn was ablaze with AI hot takes and success stories. Everyone seemed to have found their “AI groove” — except her. She wasn’t anti-AI. She just hadn’t figured out how it actually fit into her work. She had tried some of the prompts she saw online, played with some AI plugins and extensions, but it felt like an add-on, not a core part of her design workflow.
Her team was focusing on improving financial confidence for Gen Z users of their FinTech app, and Kate planned to use one of her favorite frameworks: the Design Sprint, a five-day, high-focus process that condenses months of product thinking into a single week. Each day tackles a distinct phase: Understand, Sketch, Decide, Prototype, and Test. All designed to move fast, make ideas tangible, and learn from real users before making big bets.

This time, she planned to experiment with a very lightweight version of the design sprint, almost “solo-ish” since her PM and engineer were available for check-ins and decisions, but not present every day. That gave her both space and a constraint, and made it the perfect opportunity to explore how AI could augment each phase of the sprint.
She decided to lean on her designerly behavior of experimentation and learning and integrate AI intentionally into her sprint prep, using it as both a creative partner and a thinking aid. Not with a rigid plan, but with a working hypothesis that AI would at the very least speed her up, if nothing else.
She wouldn’t just be designing and testing a prototype, but prototyping and testing what it means to design with AI, while still staying in the driver’s seat.
Follow Kate along her journey through her first AI-powered design sprint: from curiosity to friction and from skepticism to insight.
Monday: Understanding the Problem (aka: Kate Vs. Digital Pile Of Notes)The first day of a design sprint is spent understanding the user, their problems, business priorities, and technical constraints, and narrowing down the problem to solve that week.
This morning, Kate had transcripts from recent user interviews and customer feedback from the past year from app stores, surveys, and their customer support center. Typically, she would set aside a few days to process everything, coming out with glazed eyes and a few new insights. This time, she decided to use ChatGPT to summarize that data: “Read this customer feedback and tell me how we can improve financial literacy for Gen Z in our app.”
ChatGPT’s outputs were underwhelming to say the least. Disappointed, she was about to give up when she remembered an infographic about good prompting that she had emailed herself. She updated her prompt based on those recommendations:
By the time she Aero-pressed her next cup of coffee, ChatGPT had completed its analysis, highlighting blockers like jargon, lack of control, fear of making the wrong choice, and need for blockchain wallets. Wait, what? That last one felt off.

Kate searched her sources and confirmed her hunch: AI hallucination! Despite the best of prompts, AI sometimes makes things up based on trendy concepts from its training data rather than actual data. Kate updated her prompt with constraints to make ChatGPT only use data she had uploaded, and to cite examples from that data in its results. 18 seconds later, the updated results did not mention blockchain or other unexpected results.
By lunch, Kate had the makings of a research summary that would have taken much, much longer, and a whole lot of caffeine.
That afternoon, Kate and her product partner plotted the pain points on the Gen Z app journey. The emotional mapping highlighted the most critical moment: the first step of a financial decision, like setting a savings goal or choosing an investment option. That was when fear, confusion, and lack of confidence held people back.
AI synthesis combined with human insight helped them define the problem statement as: “How might we help Gen Z users confidently take their first financial action in our app, in a way that feels simple, safe, and puts them in control?”
As she wrapped up for the day, Kate jotted down her reflections on her first day as an AI-augmented designer:
There’s nothing like learning by doing. I’ve been reading about AI and tinkering around, but took the plunge today. Turns out AI is much more than a tool, but I wouldn’t call it a co-pilot. Yet. I think it’s like a sharp intern: it has a lot of information, is fast, eager to help, but it lacks context, needs supervision, and can surprise you. You have to give it clear instructions, double-check its work, and guide and supervise it. Oh, and maintain boundaries by not sharing anything I wouldn’t want others to know.Tuesday: Sketching (aka: Kate And The Sea of OKish Ideas)
Today was about listening — to users, to patterns, to my own instincts. AI helped me sift through interviews fast, but I had to stay curious to catch what it missed. Some quotes felt too clean, like the edges had been smoothed over. That’s where observation and empathy kicked in. I had to ask myself: what’s underneath this summary?
Critical thinking was the designerly skill I had to exercise most today. It was tempting to take the AI’s synthesis at face value, but I had to push back by re-reading transcripts, questioning assumptions, and making sure I wasn’t outsourcing my judgment. Turns out, the thinking part still belongs to me.
Day 2 of a design sprint focuses on solutions, starting by remixing and improving existing ideas, followed by people sketching potential solutions.
Optimistic, yet cautious after her experience yesterday, Kate started thinking about ways she could use AI today, while brewing her first cup of coffee. By cup two, she was wondering if AI could be a creative teammate. Or a creative intern at least. She decided to ask AI for a list of relevant UX patterns across industries. Unlike yesterday’s complex analysis, Kate was asking for inspiration, not insight, which meant she could use a simpler prompt: “Give me 10 unique examples of how top-rated apps reduce decision anxiety for first-time users — from FinTech, health, learning, or ecommerce.”
She received her results in a few seconds, but there were only 6, not the 10 she asked for. She expanded her prompt for examples from a wider range of industries. While reviewing the AI examples, Kate realized that one had accessibility issues. To be fair, the results met Kate’s ask since she had not specified accessibility considerations. She then went pre-AI and brainstormed examples with her product partner, coming up with a few unique local examples.
Later that afternoon, Kate went full human during Crazy 8s by putting a marker to paper and sketching 8 ideas in 8 minutes to rapidly explore different directions. Wondering if AI could live up to its generative nature, she uploaded pictures of her top 3 sketches and prompted AI to act as “a product design strategist experienced in Gen Z behavior, digital UX, and behavioral science”, gave it context about the problem statement, stage in the design sprint, and explicitly asked AI the following:
The results included ideas that Kate and her product partner hadn’t considered, including a progress bar that started at 20% (to build confidence), and a sports-like “stock bracket” for first-time investors.

Not bad, thought Kate, as she cherry-picked elements, combined and built on these ideas in her next round of sketches. By the end of the day, they had a diverse set of sketched solutions — some original, some AI-augmented, but all exploring how to reduce fear, simplify choices, and build confidence for Gen Z users taking their first financial step. With five concept variations and a few rough storyboards, Kate was ready to start converging on day 3.
Today was creatively energizing yet a little overwhelming! I leaned hard on AI to act as a creative teammate. It delivered a few unexpected ideas and remixed my Crazy 8s into variations I never would’ve thought of!Wednesday: Deciding (aka Kate Tries to Get AI to Pick a Side)
It also reinforced the need to stay grounded in the human side of design. AI was fast — too fast, sometimes. It spit out polished-sounding ideas that sounded right, but I had to slow down, observe carefully, and ask: Does this feel right for our users? Would a first-time user feel safe or intimidated here?
Critical thinking helped me separate what mattered from what didn’t. Empathy pulled me back to what Gen Z users actually said, and kept their voices in mind as I sketched. Curiosity and experimentation were my fuel. I kept tweaking prompts, remixing inputs, and seeing how far I could stretch a concept before it broke. Visual communication helped translate fuzzy AI ideas into something I could react to — and more importantly, test.
Design sprint teams spend Day 3 critiquing each of their potential solutions to shortlist those that have the best chance of achieving their long-term goal. The winning scenes from the sketches are then woven into a prototype storyboard.
Design sprint Wednesdays were Kate’s least favorite day. After all the generative energy during Sketching Tuesday, today, she would have to decide on one clear solution to prototype and test. She was unsure if AI would be much help with judging tradeoffs or narrowing down options, and it wouldn’t be able to critique like a team. Or could it?
Kate reviewed each of the five concepts, noting strengths, open questions, and potential risks. Curious about how AI would respond, she uploaded images of three different design concepts and prompted ChatGPT for strengths and weaknesses. AI’s critique was helpful in summarizing the pros and cons of different concepts, including a few points she had not considered — like potential privacy concerns.

She asked a few follow-up questions to confirm the actual reasoning. Wondering if she could simulate a team critique by prompting ChatGPT differently, Kate asked it to use the 6 thinking hats technique. The results came back dense, overwhelming, and unfocused. The AI couldn’t prioritize, and it couldn’t see the gaps Kate instinctively noticed: friction in onboarding, misaligned tone, unclear next steps.
In that moment, the promise of AI felt overhyped. Kate stood up, stretched, and seriously considered ending her experiments with the AI-driven process. But she paused. Maybe the problem wasn’t the tool. Maybe it was how she was using it. She made a note to experiment when she wasn’t on a design sprint clock.
She returned to her sketches, this time laying them out on the wall. No screens, no prompts. Just markers, sticky notes, and Sharpie scribbles. Human judgment took over. Kate worked with her product partner to finalize the solution to test on Friday and spent the next hour storyboarding the experience in Figma.
Kate re-engaged with AI as a reviewer, not a decider. She prompted it for feedback on the storyboard and was surprised to see it spit out detailed design, content, and micro-interaction suggestions for each of the steps of the storyboarded experience. A lot of food for thought, but she’d have to judge what mattered when she created her prototype. But that wasn’t until tomorrow!
AI exposed a few of my blind spots in the critique, which was good, but it basically pointed out that multiple options “could work”. I had to rely on my critical thinking and instincts to weigh options logically, emotionally, and contextually in order to choose a direction that was the most testable and aligned with the user feedback from Day 1.Thursday: Prototype (aka Kate And Faking It)
I was also surprised by the suggestions it came up with while reviewing my final storyboard, but I will need a fresh pair of eyes and all the human judgement I can muster tomorrow.
Empathy helped me walk through the flow like I was a new user. Visual communication helped pull it all together by turning abstract steps into a real storyboard for the team to see instead of imagining.
TO DO: Experiment prompting around the 6 Thinking Hats for different perspectives.
On Day 4, the team usually turns the storyboard from the previous day into a prototype that can be tested with users on Day 5. The prototype doesn’t need to be fully functional; a simulated experience is sufficient to gather user feedback.
Kate’s prototype day often consisted of marathon Figma Design sessions and late-night pizza dinners. She was hoping AI would change that today. She fed yesterday’s storyboard to ChatGPT and asked it for screens. It took a while to generate, but she was excited to see a screen flow gradually appear on her screen, except that it had 3 ¾ screens, instead of the 6 frames from her storyboard, as you can see in the image below.

She tried different prompts but couldn’t get a complete flow. She was excited when it offered to create a Figma file. She waited. And waited. And reminded it that she was right there waiting. It finally gave her a link — a link that didn’t work. That made her question why she needed prototype screens in Figma at all. Could one of those no-code tools do the job? Kate remembered that she had just received beta access to Figma Make (Figma’s AI prompt-to-prototype tool) and decided to put it to the test.
She shared her storyboard, added context about the prototype for a design sprint, the problem, and a brief description of the audience. In a few seconds, she saw code being generated and a running description on the left. It seemed like it would take some time, so she headed to the cafeteria for lunch.

When Kate returned, she was surprised to see Figma Make had created an app along with a detailed explanation:
“I’ve created a complete finance app prototype with five key screens that guide users through setting financial goals in a friendly, low-pressure way. The app features smooth animations with Framer Motion, a progress indicator to show users where they are in the journey, and emotionally supportive visuals and copy throughout the experience. Each screen serves a specific purpose: welcoming users, helping them define relatable goals, building their plan with visual feedback, presenting a simple summary, and offering next steps without pressure.
You might want to:
- Add personalization options like user profiles or avatars.
- Implement a dark mode toggle for accessibility.
- Create additional goal templates and saving strategies.”
Since Figma Make had built the prototype, not her, Kate felt like she was User 0, and clicked her way through the interactive prototype, making a mental note of what worked, what didn’t. She was impressed to see that she could not only select a common goal (buy a home, save for education, and so on) but she could also create her own goal (hello De’Longhi Coffee Maker) and it carried over for the rest of the experience. This was something she had never been able to do in Figma Design!
Despite some obvious misses like a missing header and navigation, and some buttons not working, she was impressed! Kate tried the option to ‘Publish’ and it gave her a link that she immediately shared with her product and engineering partners. A few minutes later, they joined her in the conference room, exploring it together. The engineer scanned the code, didn’t seem impressed, but said it would work as a disposable prototype.
Kate prompted Figma Make to add an orange header and app navigation, and this time the trio kept their eyes peeled as they saw the progress in code and in English. The results were pretty good. They spent the next hour making changes to get it ready for testing. Even though he didn’t admit it, the engineer seemed impressed with the result, if not the code.

By late afternoon, they had a functioning interactive prototype. Kate fed ChatGPT the prototype link and asked it to create a usability testing script. It came up with a basic, but complete test script, including a checklist for observers to take notes.

Kate went through the script carefully and updated it to add probing questions about AI transparency, emotional check-ins, more specific task scenarios, and a post-test debrief that looped back to the sprint goal.
Kate did a dry run with her product partner, who teased her: “Did you really need me? Couldn’t your AI do it?” It hadn’t occurred to her, but she was now curious!
“Act as a Gen Z user seeing this interactive prototype for the first time. How would you react to the language, steps, and tone? What would make you feel more confident or in control?”
It worked! ChatGPT simulated user feedback for the first screen and asked if she wanted it to continue. “Yes, please,” she typed. A few seconds later, she was reading what could have very well been a screen-by-screen transcript from a test.

Kate was still processing what she had seen as she drove home, happy she didn’t have to stay late. The simulated test using AI appeared impressive at first glance. But the more she thought about it, the more disturbing it became. The output didn’t mention what the simulated user clicked, and if she had asked, she probably would have received an answer. But how useful would that be? After almost missing her exit, she forced herself to think about eating a relaxed meal at home instead of her usual Prototype-Thursday-Multitasking-Pizza-Dinner.
Today was the most meta I’ve felt all week: building a prototype about AI, with AI, while being coached by AI. And it didn’t all go the way I expected.Friday: Test (aka Prototype Meets User)
While ChatGPT didn’t deliver prototype screens, Figma Make coded a working, interactive prototype with interactions I couldn’t have built in Figma Design. I used curiosity and experimentation today, by asking: What if I reworded this? What if I flipped that flow?
AI moved fast, but I had to keep steering. But I have to admit that tweaking the prototype by changing the words, not code, felt like magic!
Critical thinking isn’t optional anymore — it is table stakes.
My impromptu ask of ChatGPT to simulate a Gen Z user testing my flow? That part both impressed and unsettled me. I’m going to need time to process this. But that can wait until next week. Tomorrow, I test with 5 Gen Zs — real people.
Day 5 in a design sprint is a culmination of the week’s work from understanding the problem, exploring solutions, choosing the best, and building a prototype. It’s when teams interview users and learn by watching them react to the prototype and seeing if it really matters to them.
As Kate prepped for the tests, she grounded herself in the sprint problem statement and the users: “How might we help Gen Z users confidently take their first financial action in our app — in a way that feels simple, safe, and puts them in control?”
She clicked through the prototype one last time — the link still worked! And just in case, she also had screenshots saved.
Kate moderated the five tests while her product and engineering partners observed. The prototype may have been AI-generated, but the reactions were human. She observed where people hesitated, what made them feel safe and in control. Based on the participant, she would pivot, go off-script, and ask clarifying questions, getting deeper insights.
After each session, she dropped the transcripts and their notes into ChatGPT, asking it to summarize that user’s feedback into pain points, positive signals, and any relevant quotes. At the end of the five rounds, Kate prompted them for recurring themes to use as input for their reflection and synthesis.

The trio combed through the results, with an eye out for any suspicious AI-generated results. They ran into one: “Users Trust AI”. Not one user mentioned or clicked the ‘Why this?’ link, but AI possibly assumed transparency features worked because they were available in the prototype.
They agreed that the prototype resonated with users, allowing all to easily set their financial goals, and identified a couple of opportunities for improvement: better explaining AI-generated plans and celebrating “win” moments after creating a plan. Both were fairly easy to address during their product build process.
That was a nice end to the week: another design sprint wrapped, and Kate’s first AI-augmented design sprint! She started Monday anxious about falling behind, overwhelmed by options. She closed Friday confident in a validated concept, grounded in real user needs, and empowered by tools she now knew how to steer.
Test driving my prototype with AI yesterday left me impressed and unsettled. But today’s tests with people reminded me why we test with real users, not proxies or people who interact with users, but actual end users. And GenAI is not the user. Five tests put my designerly skill of observation to the test.Conclusion
GenAI helped summarize the test transcripts quickly but snuck in one last hallucination this week — about AI! With AI, don’t trust — always verify! Critical thinking is not going anywhere.
AI can move fast with words, but only people can use empathy to move beyond words to truly understand human emotions.
My next goal is to learn to talk to AI better, so I can get better results.
Over the course of five days, Kate explored how AI could fit into her UX work, not by reading articles or LinkedIn posts, but by doing. Through daily experiments, iterations, and missteps, she got comfortable with AI as a collaborator to support a design sprint. It accelerated every stage: synthesizing user feedback, generating divergent ideas, giving feedback, and even spinning up a working prototype, as shown below.

What was clear by Friday was that speed isn’t insight. While AI produced outputs fast, it was Kate’s designerly skills — curiosity, empathy, observation, visual communication, experimentation, and most importantly, critical thinking and a growth mindset — that turned data and patterns into meaningful insights. She stayed in the driver’s seat, verifying claims, adjusting prompts, and applying judgment where automation fell short.
She started the week on Monday, overwhelmed, her confidence dimmed by uncertainty and the noise of AI hype. She questioned her relevance in a rapidly shifting landscape. By Friday, she not only had a validated concept but had also reshaped her entire approach to design. She had evolved: from AI-curious to AI-confident, from reactive to proactive, from unsure to empowered. Her mindset had shifted: AI was no longer a threat or trend; it was like a smart intern she could direct, critique, and collaborate with. She didn’t just adapt to AI. She redefined what it meant to be a designer in the age of AI.
The experience raised deeper questions: How do we make sure AI-augmented outputs are not made up? How should we treat AI-generated user feedback? Where do ethics and human responsibility intersect?
Besides a validated solution to their design sprint problem, Kate had prototyped a new way of working as an AI-augmented designer.
The question now isn’t just “Should designers use AI?”. It’s “How do we work with AI responsibly, creatively, and consciously?”. That’s what the next article will explore: designing your interactions with AI using a repeatable framework.
Poll: If you could design your own AI assistant, what would it do?
Share your idea, and in the spirit of learning by doing, we’ll build one together from scratch in the third article of this series: Building your own CustomGPT.
Tools
As mentioned earlier, ChatGPT was the general-purpose LLM Kate leaned on, but you could swap it out for Claude, Gemini, Copilot, or other competitors and likely get similar results (or at least similarly weird surprises). Here are some alternate AI tools that might suit each sprint stage even better. Note that with dozens of new AI tools popping up every week, this list is far from exhaustive.
| Stage | Tools | Capability |
|---|---|---|
| Understand | Dovetail, UserTesting’s Insights Hub, Marvin | Summarize & Synthesize data |
| Sketch | Any LLM, Musely | Brainstorm concepts and ideas |
| Decide | Any LLM | Critique/provide feedback |
| Prototype | UIzard, UXPilot, Visily, Krisspy, Figma Make, Lovable, Bolt | Create wireframes and prototypes |
| Test | UserTesting, UserInterviews, PlaybookUX, Maze, plus tools from the Understand stage | Moderated and unmoderated user tests/synthesis |
by Lyndon Cerejo (hello@smashingmagazine.com) at August 22, 2025 08:00 AM
Artificial intelligence is increasingly automating large parts of design and development workflows — tasks once reserved for skilled designers and developers. This streamlining can dramatically speed up project delivery. Even back in 2023, AI-assisted developers were found to complete tasks twice as fast as those without. And AI tools have advanced massively since then.
Yet this surge in capability raises a pressing dilemma:
Does the environmental toll of powering AI infrastructure eclipse the efficiency gains?
We can create websites faster that are optimized and more efficient to run, but the global consumption of energy by AI continues to climb.
As awareness grows around the digital sector’s hidden ecological footprint, web designers and businesses must grapple with this double-edged sword, weighing the grid-level impacts of AI against the cleaner, leaner code it can produce.
The Good: How AI Can Enhance Sustainability In Web DesignThere’s no disputing that AI-driven automation has introduced higher speeds and efficiencies to many of the mundane aspects of web design. Tools that automatically generate responsive layouts, optimize image sizes, and refactor bloated scripts should free designers to focus on completing the creative side of design and development.
By some interpretations, these accelerated project timelines could represent a reduction in the required energy for development, and speedier production should mean less energy used.
Beyond automation, AI excels at identifying inefficiencies in code and design, as it can take a much more holistic view and assess things as a whole. Advanced algorithms can parse through stylesheets and JavaScript files to detect unused selectors or redundant logic, producing leaner, faster-loading pages. For example, AI-driven caching can increase cache hit rates by 15% by improving data availability and reducing latency. This means more user requests are served directly from the cache, reducing the need for data retrieval from the main server, which reduces energy expenditure.
AI tools can utilize next-generation image formats like AVIF or WebP, as they’re basically designed to be understood by AI and automation, and selectively compress assets based on content sensitivity. This slashes media payloads without perceptible quality loss, as the AI can use Generative Adversarial Networks (GANs) that can learn compact representations of data.
AI’s impact also brings sustainability benefits via user experience (UX). AI-driven personalization engines can dynamically serve only the content a visitor needs, which eliminates superfluous scripts or images that they don’t care about. This not only enhances perceived performance but reduces the number of server requests and data transferred, cutting downstream energy use in network infrastructure.
With the right prompts, generative AI can be an accessibility tool and ensure sites meet inclusive design standards by checking against accessibility standards, reducing the need for redesigns that can be costly in terms of time, money, and energy.
So, if you can take things in isolation, AI can and already acts as an important tool to make web design more efficient and sustainable. But do these gains outweigh the cost of the resources required in building and maintaining these tools?
The Bad: The Environmental Footprint Of AI InfrastructureYet the carbon savings engineered at the page level must be balanced against the prodigious resource demands of AI infrastructure. Large-scale AI hinges on data centers that already account for roughly 2% of global electricity consumption, a figure projected to swell as AI workloads grow.
The International Energy Agency warns that electricity consumption from data centers could more than double by 2030 due to the increasing demand for AI tools, reaching nearly the current consumption of Japan. Training state-of-the-art language models generates carbon emissions on par with hundreds of transatlantic flights, and inference workloads, serving billions of requests daily, can rival or exceed training emissions over a model’s lifetime.
Image generation tasks represent an even steeper energy hill to climb. Producing a single AI-generated image can consume energy equivalent to charging a smartphone.
As generative design and AI-based prototyping become more common in web development, the cumulative energy footprint of these operations can quickly undermine the carbon savings achieved through optimized code.
Water consumption forms another hidden cost. Data centers rely heavily on evaporative cooling systems that can draw between one and five million gallons of water per day, depending on size and location, placing stress on local supplies, especially in drought-prone regions. Studies estimate a single ChatGPT query may consume up to half a liter of water when accounting for direct cooling requirements, with broader AI use potentially demanding billions of liters annually by 2027.
Resource depletion and electronic waste are further concerns. High-performance components underpinning AI services, like GPUs, can have very small lifespans due to both wear and tear and being superseded by more powerful hardware. AI alone could add between 1.2 and 5 million metric tons of e-waste by 2030, due to the continuous demand for new hardware, amplifying one of the world’s fastest-growing waste streams.
Mining for the critical minerals in these devices often proceeds under unsustainable conditions due to a lack of regulations in many of the environments where rare metals can be sourced, and the resulting e-waste, rich in toxic metals like lead and mercury, poses another form of environmental damage if not properly recycled.
Compounding these physical impacts is a lack of transparency in corporate reporting. Energy and water consumption figures for AI workloads are often aggregated under general data center operations, which obscures the specific toll of AI training and inference among other operations.
And the energy consumption reporting of the data centres themselves has been found to have been obfuscated.
Reports estimate that the emissions of data centers are up to 662% higher than initially reported due to misaligned metrics, and ‘creative’ interpretations of what constitutes an emission. This makes it hard to grasp the true scale of AI’s environmental footprint, leaving designers and decision-makers unable to make informed, environmentally conscious decisions.Do The Gains From AI Outweigh The Costs?
Some industry advocates argue that AI’s energy consumption isn’t as catastrophic as headlines suggest. Some groups have challenged ‘alarmist’ projections, claiming that AI’s current contribution of ‘just’ 0.02% of global energy consumption isn’t a cause for concern.
Proponents also highlight AI’s supposed environmental benefits. There are claims that AI could reduce economy-wide greenhouse gas emissions by 0.1% to 1.1% through efficiency improvements. Google reported that five AI-powered solutions removed 26 million metric tons of emissions in 2024. The optimistic view holds that AI’s capacity to optimize everything from energy grids to transportation systems will more than compensate for its data center demands.
However, recent scientific analysis reveals these arguments underestimate AI’s true impact. MIT found that data centers already consume 4.4% of all US electricity, with projections showing AI alone could use as much power as 22% of US households by 2028. Research indicates AI-specific electricity use could triple from current levels annually by 2028. Moreover, Harvard research revealed that data centers use electricity with 48% higher carbon intensity than the US average.
Advice For Sustainable AI Use In Web DesignDespite the environmental costs, AI’s use in business, particularly web design, isn’t going away anytime soon, with 70% of large businesses looking to increase their AI investments to increase efficiencies. AI’s immense impact on productivity means those not using it are likely to be left behind. This means that environmentally conscious businesses and designers must find the right balance between AI’s environmental cost and the efficiency gains it brings.
Before you plug in any AI magic, start by making sure the bones of your site are sustainable. Lean web fundamentals, like system fonts instead of hefty custom files, minimal JavaScript, and judicious image use, can slash a page’s carbon footprint by stripping out redundancies that increase energy consumption. For instance, the global average web page emits about 0.8g of CO₂ per view, whereas sustainably crafted sites can see a roughly 70% reduction.
Once that lean baseline is in place, AI-driven optimizations (image format selection, code pruning, responsive layout generation) aren’t adding to bloat but building on efficiency, ensuring every joule spent on AI actually yields downstream energy savings in delivery and user experience.
In order to make sustainable tool choices, transparency and awareness are the first steps. Many AI vendors have pledged to work towards sustainability, but independent audits are necessary, along with clear, cohesive metrics. Standardized reporting on energy and water footprints will help us understand the true cost of AI tools, allowing for informed choices.
You can look for providers that publish detailed environmental reports and hold third-party renewable energy certifications. Many major providers now offer PUE (Power Usage Effectiveness) metrics alongside renewable energy matching to demonstrate real-world commitments to clean power.
When integrating AI into your build pipeline, choosing lightweight, specialized models for tasks like image compression or code linting can be more sustainable than full-scale generative engines. Task-specific tools often use considerably less energy than general AI models, as general models must process what task you want them to complete.
There are a variety of guides and collectives out there that can guide you on choosing the ‘green’ web hosts that are best for your business. When choosing AI-model vendors, you should look at options that prioritize ‘efficiency by design’: smaller, pruned models and edge-compute deployments can cut energy use by up to 50% compared to monolithic cloud-only models. They’re trained for specific tasks, so they don’t have to expend energy computing what the task is and how to go about it.
Once you’ve chosen conscientious vendors, optimize how you actually use AI. You can take steps like batching non-urgent inference tasks to reduce idle GPU time, an approach shown to lower energy consumption overall compared to requesting ad-hoc, as you don’t have to keep running the GPU constantly, only when you need to use it.
Smarter prompts can also help make AI usage slightly more sustainable. Sam Altman of ChatGPT revealed early in 2025 that people’s propensity for saying ‘please’ and ‘thank you’ to LLMs is costing millions of dollars and wasting energy as the Generative AI has to deal with extra phrases to compute that aren’t relevant to its task. You need to ensure that your prompts are direct and to the point, and deliver the context required to complete the task to reduce the need to reprompt.
On top of being responsible with your AI tool choice and usage, there are other steps you can take to offset the carbon cost of AI usage and enjoy the efficiency benefits it brings. Organizations can reduce their own emissions and use carbon offsetting to reduce their own carbon footprint as much as possible. Combined with the apparent sustainability benefits of AI use, this approach can help mitigate the harmful impacts of energy-hungry AI.
You can ensure that you’re using green server hosting (servers run on sustainable energy) for your own site and cloud needs beyond AI, and refine your content delivery network (CDN) to ensure your sites and apps are serving compressed, optimized assets from edge locations, cutting the distance data must travel, which should reduce the associated energy use.
Organizations and individuals, particularly those with thought leadership status, can be advocates pushing for transparent sustainability specifications. This involves both lobbying politicians and regulatory bodies to introduce and enforce sustainability standards and ensuring that other members of the public are kept aware of the environmental costs of AI use.
It’s only through collective action that we’re likely to see strict enforcement of both sustainable AI data centers and the standardization of emissions reporting.
Regardless, it remains a tricky path to walk, along the double-edged sword of AI’s use in web design.
Use AI too much, and you’re contributing to its massive carbon footprint. Use it too little, and you’re likely to be left behind by rivals that are able to work more efficiently and deliver projects much faster.
The best environmentally conscious designers and organizations can currently do is attempt to navigate it as best they can and stay informed on best practices.
ConclusionWe can’t dispute that AI use in web design delivers on its promise of agility, personalization, and resource savings at the page-level. Yet without a holistic view that accounts for the environmental demands of AI infrastructure, these gains risk being overshadowed by an expanding energy and water footprint.
Achieving the balance between enjoying AI’s efficiency gains and managing its carbon footprint requires transparency, targeted deployment, human oversight, and a steadfast commitment to core sustainable web practices.
by Alex Williams (hello@smashingmagazine.com) at August 20, 2025 10:00 AM
These days, it’s easy to find curated lists of AI tools for designers, galleries of generated illustrations, and countless prompt libraries. What’s much harder to find is a clear view of how AI is actually integrated into the everyday workflow of a product designer — not for experimentation, but for real, meaningful outcomes.
I’ve gone through that journey myself: testing AI across every major stage of the design process, from ideation and prototyping to visual design and user research. Along the way, I’ve built a simple, repeatable workflow that significantly boosts my productivity.
In this article, I’ll share what’s already working and break down some of the most common objections I’ve encountered — many of which I’ve faced personally.
Stage 1: Idea Generation Without The ClichésPushback: “Whenever I ask AI to suggest ideas, I just get a list of clichés. It can’t produce the kind of creative thinking expected from a product designer.”
That’s a fair point. AI doesn’t know the specifics of your product, the full context of your task, or many other critical nuances. The most obvious fix is to “feed it” all the documentation you have. But that’s a common mistake as it often leads to even worse results: the context gets flooded with irrelevant information, and the AI’s answers become vague and unfocused.
Current-gen models can technically process thousands of words, but the longer the input, the higher the risk of missing something important, especially content buried in the middle. This is known as the “lost in the middle” problem.
To get meaningful results, AI doesn’t just need more information — it needs the right information, delivered in the right way. That’s where the RAG approach comes in.
Think of RAG as a smart assistant working with your personal library of documents. You upload your files, and the assistant reads each one, creating a short summary — a set of bookmarks (semantic tags) that capture the key topics, terms, scenarios, and concepts. These summaries are stored in a kind of “card catalog,” called a vector database.
When you ask a question, the assistant doesn’t reread every document from cover to cover. Instead, it compares your query to the bookmarks, retrieves only the most relevant excerpts (chunks), and sends those to the language model to generate a final answer.
Let’s break it down:
Typical chat interaction
It’s like asking your assistant to read a 100-page book from start to finish every time you have a question. Technically, all the information is “in front of them,” but it’s easy to miss something, especially if it’s in the middle. This is exactly what the “lost in the middle” issue refers to.
RAG approach
You ask your smart assistant a question, and it retrieves only the relevant pages (chunks) from different documents. It’s faster and more accurate, but it introduces a few new risks:

These aren’t reasons to avoid RAG or AI altogether. Most of them can be avoided with better preparation of your knowledge base and more precise prompts. So, where do you start?
These three short documents will give your AI assistant just enough context to be genuinely helpful:
Each document should focus on a single topic and ideally stay within 300–500 words. This makes it easier to search and helps ensure that each retrieved chunk is semantically clean and highly relevant.
In practice, RAG works best when both the query and the knowledge base are in English. I ran a small experiment to test this assumption, trying a few different combinations:
Takeaway: If you want your AI assistant to deliver precise, meaningful responses, do your RAG work entirely in English, both the data and the queries. This advice applies specifically to RAG setups. For regular chat interactions, you’re free to use other languages. A challenge also highlighted in this 2024 study on multilingual retrieval.
Once your AI assistant has proper context, it stops acting like an outsider and starts behaving more like someone who truly understands your product. With well-structured input, it can help you spot blind spots in your thinking, challenge assumptions, and strengthen your ideas — the way a mid-level or senior designer would.
Here’s an example of a prompt that works well for me:
Your task is to perform a comparative analysis of two features: "Group gift contributions" (described in group_goals.txt) and "Personal savings goals" (described in personal_goals.txt).
The goal is to identify potential conflicts in logic, architecture, and user scenarios and suggest visual and conceptual ways to clearly separate these two features in the UI so users can easily understand the difference during actual use.
Please include:If helpful, include a comparison table with key parameters like purpose, initiator, audience, contribution method, timing, access rights, and so on.
- Possible overlaps in user goals, actions, or scenarios;
- Potential confusion if both features are launched at the same time;
- Any architectural or business-level conflicts (e.g. roles, notifications, access rights, financial logic);
- Suggestions for visual and conceptual separation: naming, color coding, separate sections, or other UI/UX techniques;
- Onboarding screens or explanatory elements that might help users understand both features.
If you want AI to go beyond surface-level suggestions and become a real design partner, it needs the right context. Not just more information, but better, more structured information.
Building a usable knowledge base isn’t difficult. And you don’t need a full-blown RAG system to get started. Many of these principles work even in a regular chat: well-organized content and a clear question can dramatically improve how helpful and relevant the AI’s responses are. That’s your first step in turning AI from a novelty into a practical tool in your product design workflow.
Stage 2: Prototyping and Visual ExperimentsPushback: “AI only generates obvious solutions and can’t even build a proper user flow. It’s faster to do it manually.”
That’s a fair concern. AI still performs poorly when it comes to building complete, usable screen flows. But for individual elements, especially when exploring new interaction patterns or visual ideas, it can be surprisingly effective.
For example, I needed to prototype a gamified element for a limited-time promotion. The idea is to give users a lottery ticket they can “flip” to reveal a prize. I couldn’t recreate the 3D animation I had in mind in Figma, either manually or using any available plugins. So I described the idea to Claude 4 in Figma Make and within a few minutes, without writing a single line of code, I had exactly what I needed.
At the prototyping stage, AI can be a strong creative partner in two areas:
AI can also be applied to multi-screen prototypes, but it’s not as simple as dropping in a set of mockups and getting a fully usable flow. The bigger and more complex the project, the more fine-tuning and manual fixes are required. Where AI already works brilliantly is in focused tasks — individual screens, elements, or animations — where it can kick off the thinking process and save hours of trial and error.
A quick UI prototype of a gamified promo banner created with Claude 4 in Figma Make. No code or plugins needed.
Here’s another valuable way to use AI in design — as a stress-testing tool. Back in 2023, Google Research introduced PromptInfuser, an internal Figma plugin that allowed designers to attach prompts directly to UI elements and simulate semi-functional interactions within real mockups. Their goal wasn’t to generate new UI, but to check how well AI could operate inside existing layouts — placing content into specific containers, handling edge-case inputs, and exposing logic gaps early.
The results were striking: designers using PromptInfuser were up to 40% more effective at catching UI issues and aligning the interface with real-world input — a clear gain in design accuracy, not just speed.
That closely reflects my experience with Claude 4 and Figma Make: when AI operates within a real interface structure, rather than starting from a blank canvas, it becomes a much more reliable partner. It helps test your ideas, not just generate them.
Stage 3: Finalizing The Interface And Visual StylePushback: “AI can’t match our visual style. It’s easier to just do it by hand.”
This is one of the most common frustrations when using AI in design. Even if you upload your color palette, fonts, and components, the results often don’t feel like they belong in your product. They tend to be either overly decorative or overly simplified.
And this is a real limitation. In my experience, today’s models still struggle to reliably apply a design system, even if you provide a component structure or JSON files with your styles. I tried several approaches:

So yes, AI still can’t help you finalize your UI. It doesn’t replace hand-crafted design work. But it’s very useful in other ways:
AI won’t save you five hours of high-fidelity design time, since you’ll probably spend that long fixing its output. But as a visual sparring partner, it’s already strong. If you treat it like a source of alternatives and fresh perspectives, it becomes a valuable creative collaborator.
Stage 4: Product Feedback And Analytics: AI As A Thinking ExosuitProduct designers have come a long way. We used to create interfaces in Photoshop based on predefined specs. Then we delved deeper into UX with mapping user flows, conducting interviews, and understanding user behavior. Now, with AI, we gain access to yet another level: data analysis, which used to be the exclusive domain of product managers and analysts.
As Vitaly Friedman rightly pointed out in one of his columns, trying to replace real UX interviews with AI can lead to false conclusions as models tend to generate an average experience, not a real one. The strength of AI isn’t in inventing data but in processing it at scale.
Let me give a real example. We launched an exit survey for users who were leaving our service. Within a week, we collected over 30,000 responses across seven languages.
Simply counting the percentages for each of the five predefined reasons wasn’t enough. I wanted to know:
The real challenge was... figuring out what cuts and angles were even worth exploring. The entire technical process, from analysis to visualizations, was done “for me” by Gemini, working inside Google Sheets. This task took me about two hours in total. Without AI, not only would it have taken much longer, but I probably wouldn’t have been able to reach that level of insight on my own at all.

AI enables near real-time work with large data sets. But most importantly, it frees up your time and energy for what’s truly valuable: asking the right questions.
A few practical notes: Working with large data sets is still challenging for models without strong reasoning capabilities. In my experiments, I used Gemini embedded in Google Sheets and cross-checked the results using ChatGPT o3. Other models, including the standalone Gemini 2.5 Pro, often produced incorrect outputs or simply refused to complete the task.
AI Is Not An Autopilot But A Co-PilotAI in design is only as good as the questions you ask it. It doesn’t do the work for you. It doesn’t replace your thinking. But it helps you move faster, explore more options, validate ideas, and focus on the hard parts instead of burning time on repetitive ones. Sometimes it’s still faster to design things by hand. Sometimes it makes more sense to delegate to a junior designer.
But increasingly, AI is becoming the one who suggests, sharpens, and accelerates. Don’t wait to build the perfect AI workflow. Start small. And that might be the first real step in turning AI from a curiosity into a trusted tool in your product design process.
Let’s Summarizeby Nikita Samutin (hello@smashingmagazine.com) at August 18, 2025 01:00 PM
Color plays a pivotal role in crafting compelling user experiences and successful digital products. It’s far more than just aesthetics; color strategically guides users, establishes brand identity, and evokes specific emotions.
Beyond functionality, color is also a powerful tool for brand recognition and emotional connection. Consistent use of brand colors across a digital product reinforces identity and builds trust. Different hues carry cultural and psychological associations, allowing designers to subtly influence user perception and create the desired mood. A thoughtful and deliberate approach to color in UX design elevates the user experience, strengthens brand presence, and contributes significantly to the overall success and impact of digital products. In this article, we will talk about the importance of color and why they are important for creating emotional connection and delivering consistent and accessible digital products.
Well-chosen color palettes enhance usability by creating visual hierarchies, highlighting interactive elements, and providing crucial feedback on screens. For instance, a bright color might draw attention to a call-to-action button, while consistent color coding can help users navigate complex interfaces intuitively. Color also contributes significantly to accessibility, ensuring that users with visual impairments can still effectively interact with digital products. By carefully considering contrast ratios and providing alternative visual cues, designers can create inclusive experiences that cater to a wider audience.
The colors we choose are the silent language of our digital products, and speaking it fluently is essential for success.
Communicating Brand Identity Through Color In The Digital SpaceA thoughtfully curated and vibrant color palette becomes a critical differentiator, allowing a brand to stand out amidst the digital noise and cultivate stronger connections with the audience.
Far beyond mere decoration, color acts as a visual shorthand, instantly conveying a brand’s personality, its underlying values, and its unique essence. According to the American Marketing Association, vibrant colors, in particular, possess an inherent magnetism, drawing the eye and etching themselves into memory within the online environment. They infuse the brand with energy and dynamism, projecting approachability and memorability in a way that more muted tones often cannot.
Consistency: The Core Of Great DesignConsistency is important because it fosters trust and familiarity, allowing users to quickly identify and connect with the brand in the online landscape. The strategic deployment of vibrant colors is especially crucial for brands seeking to establish themselves and flourish within the digital ecosystem. In the absence of physical storefronts or tangible in-person interactions, visual cues become paramount in shaping user perception and building brand recognition. A carefully selected primary color, supported by a complementary and equally energetic secondary palette, can become synonymous with a brand’s digital presence. A consistent application of the right colors across different digital touchpoints — from the logo and website design to the user interface of an app and engaging social media campaigns — creates a cohesive and instantly recognizable visual language.
Several sources and professionals agree that the psychology behind the colors plays a significant role in shaping brand perception. The publication Insights Psychology, for instance, explains how colors can create emotional and behavioural responses. Vibrant colors often evoke strong emotions and associations. A bold, energetic red, for example, might communicate passion and excitement, while a bright, optimistic yellow could convey innovation and cheerfulness. By consciously aligning their color choices with their brand values and target audience preferences, digitally-native brands can create a powerful emotional resonance.

As designers working with digital products, we’ve learned that color is far more than a superficial layer of visual appeal. It’s a potent, often subconscious, force that shapes how users interact with and feel about the digital products we build.
We’re not just painting pixels, we’re conducting a psychological symphony, carefully selecting each hue to evoke specific emotions, guide behavior, and ultimately forge a deeper connection with the user.
The initial allure of a color palette might be purely aesthetic, but its true power lies in its ability to bypass conscious thought and tap directly into our emotional core. Think about the subtle unease that might creep in when encountering a predominantly desaturated interface for a platform promising dynamic content, or the sense of calm that washes over you when a learning application utilizes soft, analogous colors. These are not arbitrary responses; they’re deeply rooted in our evolutionary history and cultural conditioning.
To understand how colors psychologically impact user behavior in digital, we first need to understand how colors are defined. In digital design, colors are precisely defined using the HSB model, which stands for Hue, Saturation, and Brightness. This model provides a more intuitive way for designers to think about and manipulate color compared to other systems like RGB (Red, Green, Blue). Here is a quick breakdown of each component:

This is the pure color itself, the essence that we typically name, such as red, blue, green, or yellow. On a color wheel, hue is represented as an angle ranging from 0 to 360 degrees. For example, 0 is red, 120 is green, and 240 is blue. Think of it as the specific wavelength of light that our eyes perceive as a particular color. In UX, selecting the base hues is often tied to brand identity and the overall feeling you want to convey.
Saturation refers to the intensity or purity of the hue. It describes how vivid or dull the color appears. A fully saturated color is rich and vibrant, while a color with low saturation appears muted, grayish, or desaturated. Saturation is typically expressed as a percentage, from 0% (completely desaturated, appearing as a shade of gray) to 100% (fully saturated, the purest form of the hue).
In UX, saturation levels are crucial for creating visual hierarchy and drawing attention to key elements. Highly saturated colors often indicate interactive elements or important information, while lower saturation can be used for backgrounds or less critical content.
Brightness, sometimes also referred to as a value or lightness, indicates how light or dark a color appears. It’s the amount of white or black mixed into the hue. Brightness is also usually represented as a percentage, ranging from 0% (completely black, regardless of the hue or saturation) to 100% (fully bright). At 100% brightness and 0% saturation, you get white. In UX, adjusting brightness is essential for creating contrast and ensuring readability. Sufficient brightness contrast between text and background is a fundamental accessibility requirement. Furthermore, variations in brightness within a color palette can create visual depth and subtle distinctions between UI elements.
By understanding and manipulating these 3 color dimensions, digital designers have precise control over their color choices. This allows for the creation of harmonious and effective color palettes that not only align with brand guidelines but also strategically influence user behavior.
Just as in the physical world, colors in digital also carry symbolic meanings and trigger subconscious associations. Understanding these color associations is essential for UX designers aiming to craft experiences that not only look appealing but also resonate emotionally and guide user behavior effectively.
As the EMB Global states, the way we perceive and interpret color is not universal, yet broad patterns of association exist. For instance, the color blue often evokes feelings of trust, stability, and calmness. This association stems from the natural world — the vastness of the sky and the tranquility of deep waters. In the digital space, this makes blue a popular choice for financial institutions, corporate platforms, and interfaces aiming to project reliability and security. However, the specific shade and context matter immensely. A bright, electric blue can feel energetic and modern, while a muted and darker blue might convey a more serious and authoritative tone.
Kendra Cherry, a psychosocial and rehabilitation specialist and author of the book Everything Psychology, explains very well how colors evoke certain responses in us. For example, the color green is intrinsically linked to nature, often bringing about feelings of growth, health, freshness, and tranquility. It can also symbolize prosperity in some cultures. In digital design, green is frequently used for health and wellness applications, environmental initiatives, and platforms emphasizing sustainability. A vibrant lime green can feel energetic and youthful, while a deep forest green can evoke a sense of groundedness and organic quality.
Yellow, the color of sunshine, is generally associated with optimism, happiness, energy, and warmth. It’s attention-grabbing and can create a sense of playfulness. In digital interfaces, yellow is often used for highlighting important information, calls to action (though sparingly, as too much can be overwhelming), or for brands wanting to project a cheerful and approachable image.
Red, a color with strong physiological effects, typically evokes excitement, passion, urgency, and sometimes anger or danger. It commands attention and can stimulate action. Digitally, red is often used for alerts, error messages, sales promotions, or for brands wanting to project a bold and energetic identity. Its intensity requires careful consideration, as overuse can lead to user fatigue or anxiety.
Orange blends the energy of red with the optimism of yellow, often conveying enthusiasm, creativity, and friendliness. It can feel less aggressive than red but still commands attention. In digital design, orange is frequently used for calls to action, highlighting sales or special offers, and for brands aiming to appear approachable and innovative.
Purple has historically been associated with royalty and luxury. It can evoke feelings of creativity, wisdom, and mystery. In digital contexts, purple is often used for brands aiming for a sophisticated or unique feel, particularly in areas like luxury goods, beauty, or spiritual and creative platforms.
Black often signifies sophistication, power, elegance, and sometimes mystery. In digital design, black is frequently used for minimalist interfaces, luxury brands, and for creating strong contrast with lighter elements. The feeling it evokes heavily depends on the surrounding colors and overall design aesthetic.
White is generally associated with purity, cleanliness, simplicity, and neutrality. It provides a sense of spaciousness and allows other colors to stand out. In digital design, white space is a crucial element, and white is often used as a primary background color to create a clean and uncluttered feel.
Gray is often seen as neutral, practical, and sometimes somber or conservative. In digital interfaces, various shades of gray are essential for typography, borders, dividers, and creating visual hierarchy without being overly distracting.

Imagine an elegant furniture application. The designers might choose a primary palette of soft, desaturated blues and greens, accented with gentle earth tones. The muted blues could subtly induce a feeling of calmness and tranquility, aligning with the app’s core purpose of relaxation. The soft greens might evoke a sense of nature and well-being, further reinforcing the theme of peace and mental clarity. The earthy browns could ground the visual experience, creating a feeling of stability and connection to the natural world.

Now, consider a platform for extreme investment enthusiasts. The color palette might be dominated by high-energy oranges and reds, contrasted with stark blacks and sharp whites. The vibrant oranges could evoke feelings of excitement and adventure, while the bold red might amplify the sense of adrenaline and intensity. The black and white could provide a sense of dynamism and modernity, reflecting the fast-paced nature of the activities.
By consciously understanding and applying these color associations, digital designers can move beyond purely aesthetic choices and craft experiences that resonate deeply with users on an emotional level, leading to more engaging, intuitive, and successful digital products.
Choosing the right colors isn’t about adhering to fleeting trends; it’s about ensuring that our mobile applications and websites are usable by the widest possible audience, including individuals with visual impairments. Improper color choices can create significant barriers, rendering content illegible, interactive elements indistinguishable, and ultimately excluding a substantial portion of potential users.
Prioritizing color with accessibility in mind is not just a matter of ethical design; it’s a fundamental aspect of creating inclusive and user-friendly digital experiences that benefit everyone.
For individuals with low vision, sufficient color contrast between text and background is paramount for readability. Imagine trying to decipher light gray text on a white background — a common design trend that severely hinders those with even mild visual impairments. Adhering to Web Content Accessibility Guidelines (WCAG) contrast ratios ensures that text remains legible and understandable.
Furthermore, color blindness, affecting a significant percentage of the population, necessitates the use of redundant visual cues. Relying solely on color to convey information, such as indicating errors in red without an accompanying text label, excludes colorblind users. By pairing color with text, icons, or patterns, we ensure that critical information is conveyed through multiple sensory channels, making it accessible to all. Thoughtful color selection, therefore, is not an optional add-on but an integral component of designing digital products that are truly usable and equitable.

As designers, we need a strategic approach to choosing color palettes, considering various factors to build a scalable and impactful color system. Here’s a breakdown of the steps and considerations involved:
The journey begins with a thorough understanding of the brand itself. What are its core values? What personality does it project? Is it playful, sophisticated, innovative? Analyze existing brand guidelines (if any), target audience demographics and psychographics, and the overall goals of the digital product. The color palette should be a visual extension of this identity, reinforcing brand recognition and resonating with the intended users. For instance, a financial app aiming for trustworthiness might lean towards blues and greens, while a creative platform could explore more vibrant and unconventional hues.
As discussed previously, colors carry inherent psychological and cultural baggage. While these associations are not absolute, they provide a valuable framework for initial exploration. Consider the emotions you want to evoke and research how your target audience might perceive different colors, keeping in mind cultural nuances that can significantly alter interpretations. This step is important to help in making informed decisions that align with the desired user experience and brand perception.
Start by identifying the primary color — the dominant hue that represents your brand’s essence. This will likely be derived from the brand logo or existing visual identity. Next, establish a secondary color or two that complement the primary color and provide visual interest and hierarchy. These secondary colors should work harmoniously with the primary, offering flexibility for different UI elements and interactions.
A consistent and scalable color palette goes beyond just a few base colors. It involves creating a system of variations for practical application within the digital interface. This typically includes tints and shades, accent colors, and neutral colors.
Ensure sufficient color contrast between text and background, as well as between interactive elements and their surroundings, to meet WCAG guidelines. Tools are readily available to check color contrast ratios.
Test your palette using color blindness simulators to see how it will be perceived by individuals with different types of color vision deficiencies. This helps identify potential issues where information might be lost due to color alone.
Visual hierarchy is also important to guide the user’s eye and establish a clear visual story. Important elements should be visually distinct.
Once you have a preliminary color palette, it’s crucial to test it within the context of your digital product. Create mockups and prototypes to see how the colors work together in the actual interface. Gather feedback from stakeholders and, ideally, conduct user testing to identify any usability or aesthetic issues. Be prepared to iterate and refine your palette based on these insights.
A well-defined color palette for the digital medium should be:
By following these steps and keeping these considerations in mind, designers can craft color palettes that are not just visually appealing but also strategically powerful tools for creating effective and accessible digital experiences.

Consistency plays an important role in the whole color ecosystem. By maintaining a unified color scheme for interactive elements, navigation cues, and informational displays, designers create a seamless and predictable user journey, building trust through visual stability.
Color consistency directly contributes to brand recognition in the increasingly crowded digital landscape. Just as a logo or typeface becomes instantly identifiable, a consistent color palette acts as a powerful visual signature. When users repeatedly encounter the same set of colors associated with a particular brand, it strengthens their recall and fosters a stronger brand association. This visual consistency extends beyond the core interface to marketing materials, social media presence, and all digital touchpoints, creating a cohesive and memorable brand experience. By strategically and consistently applying a solid and consistent color palette, digital products can cultivate stronger brand recognition, build user trust, and enhance user loyalty.
by Rodolpho Henrique (hello@smashingmagazine.com) at August 15, 2025 03:00 PM
Design career origin stories often sound clean and linear: a degree in Fine Arts, a lucky internship, or a first job that launches a linear, upward path. But what about those whose paths were not so straight? The ones who came from service, retail, construction, or even firefighting — the messy, winding paths that didn’t begin right out of design school — who learned service instincts long before learning design tools?
I earned my Associate in Science way later than planned, after 15 years in fine dining, which I once dismissed as a detour delaying my “real” career. But in hindsight, it was anything but. Those years built skills and instincts I still rely on daily — in meetings, design reviews, and messy mid-project pivots.
Your Past Is Your AdvantageI still have the restaurant dream.
Whenever I’m overwhelmed or deep in a deadline, it comes back: I’m the only one running the restaurant floor. The grill is on fire. There’s no clean glassware. Everyone needs their check, their drink, and their table turned. I wake up sweating, and I ask myself, “Why am I still having restaurant nightmares 15 years into a design career?”
Because those jobs wired themselves into how I think and work.
Those years weren’t just a job but high-stakes training in adaptability, anticipation, and grace under pressure. They built muscle memory: ways of thinking, reacting, and solving problems that still appear daily in my design work. They taught me to adapt, connect with people, and move with urgency and grace.
But those same instincts rooted in nightmares can trip you up if you’re unaware. Speed can override thoughtfulness. Constant anticipation can lead to over-complication. The pressure to polish can push you to over-deliver too soon. Embracing your past also means examining it — recognizing when old habits serve you and when they don’t.
With reflection, those experiences can become your greatest advantage.
Lessons From The LineThese aren’t abstract comparisons. They’re instincts built through repetition and real-world pressure, and they show up daily in my design process.
Here are five moments from restaurant life that shaped how I think, design, and collaborate today.
1. Reading The RoomReading a customer’s mood begins as soon as they sit down. Through years of trial and error, I refined my understanding of subtle cues, like seating delays indicating frustration or menus set aside, suggesting they want to enjoy cocktails. Adapting my approach based on these signals became instinctual, emerging from countless moments of observation.
The subtleties of reading a client aren’t so different in product design. Contexts differ, but the cues remain similar: project specifics, facial expressions, tone of voice, lack of engagement, or even the “word salad” of client feedback. With time, these signals become easier to spot, and you learn to ask better questions, challenge assumptions, or offer alternate approaches before misalignment grows. Whether a client is energized and all-in or hesitant and constrained, reading those cues early can make all the difference.
Those instincts — like constant anticipation and early intervention — served me well in fine dining, but can hinder the design process if I’m not in tune with how I’m reacting. Jumping in too early can lead to over-complicating the design process, solving problems that haven’t been voiced (yet), or stepping on others’ roles. I’ve had to learn to pause, check in with the team, and trust the process to unfold more collaboratively.

In fine dining, multitasking wasn’t just helpful, it was survival. Every night demanded precision timing, orchestrating every meal step, from the first drink poured to the final dessert plated. The soufflé, for example, was a constant test. It takes precisely 45 minutes — no more, no less. If the guests lingered over appetizers or finished their entrées too early, that soufflé risked collapse.
But fine dining taught me how to handle that volatility. I learned to manage timing proactively, mastering small strategies: an amuse-bouche to buy the kitchen precious minutes, a complimentary glass of champagne to slow a too-quickly paced meal. Multitasking meant constantly adjusting in real-time, keeping a thousand tiny details aligned even when, behind the scenes, chaos loomed.
Multitasking is a given in product design, just in a different form. While the pressure is less immediate, it is more layered as designers often juggle multiple projects, overlapping timelines, differing stakeholder expectations, and evolving product needs simultaneously. That restaurant instinct to keep numerous plates spinning at the same time? It’s how I handle shifting priorities, constant Slack pings, regular Figma updates, and unexpected client feedback — without losing sight of the big picture.
The hustle and pace of fine dining hardwired me to associate speed with success. But in design, speed can sometimes undermine depth. Jumping too quickly into a solution might mean missing the real problem or polishing the wrong idea. I’ve learned that staying in motion isn’t always the goal. Unlike a fast-paced service window, product design invites experimentation and course correction. I’ve had to quiet the internal timer and lean into design with a slower, more intentional nature.
Presentation isn’t just a finishing touch in fine dining — it’s everything. It’s the mint leaf delicately placed atop a dessert, the raspberry glace cascading across the perfectly off-centered espresso cake.
The presentation engages every sense: the smell of rare imported truffles on your truffle fries, or the meticulous choreography of four servers placing entrées in front of diners simultaneously, creating a collective “wow” moment. An excellent presentation shapes diners’ emotional connection with their meal — that experience directly impacts how generously they spend, and ultimately, your success.

A product design presentation, from the initial concept to the handoff, carries that same power. Introducing a new homepage design can feel mechanical or magical, depending entirely on how you frame and deliver it. Just like careful plating shapes a diner’s experience, clear framing and confident storytelling shape how design is received.
Beyond the initial introduction, explain the why behind your choices. Connect patterns to the organic elements of the brand’s identity and highlight how users will intuitively engage with each section. Presentation isn’t just about aesthetics; it helps clients connect with the work, understand its value, and get excited to share it.
The pressure to get everything right the first time, to present a pixel-perfect comp that “wows” immediately, is intense.
Sometimes, an excellent presentation isn’t about perfection — it’s about pacing, storytelling, and allowing the audience to see themselves in the work.
I’ve had to let go of the idea that polish is everything and instead focus on the why, describing it with clarity, confidence, and connection.

In fine dining, pressure isn’t an occasional event — it’s the default setting. Every night is high stakes. Timing is tight, expectations are sky-high, and mistakes are rarely forgiven. Composure becomes your edge. You don’t show panic when the kitchen is backed up or when a guest sends a dish back mid-rush. You pivot. You delegate. You anticipate. Some nights, the only thing that kept things on track was staying calm and thinking clearly.
“This notion of problem solving and decision making is key to being a great designer. I think that we need to get really strong at problem identification and then prioritization. All designers are good problem solvers, but the really great designers are strong problem finders.”
— Jason Cyr, “How being a firefighter made me a better designer thinker”
The same principle applies to product design. When pressure mounts — tight timelines, conflicting feedback, or unclear priorities — your ability to stay composed can shift the energy of the entire project.
Composure isn’t just about being calm; it’s about being adaptable and responsive without reacting impulsively. It helps you hold space for feedback, ask better questions, and move forward with clarity instead of chaos.
There have also been plenty of times when a client doesn’t resonate with a design, which can feel crushing. You can easily take it personally and internalize the rejection, or you can pause, listen, and course-correct. I’ve learned to focus on understanding the root of the feedback. Often, what seems like a rejection is just discomfort with a small detail, which in most cases can be easily corrected.
Perfection was the baseline in restaurants, and pressure drove polish. In design, that mindset can lead to overinvesting in perfection too soon or “freezing” under critique. I’ve had to unlearn that success means getting everything right the first time. Now I see messy collaboration and gradual refinement as a mark of success, not failure.
I still dream about the restaurant floor. But now, I see it as a reminder — not of where I was stuck, but of where I perfected the instincts I use today. If you’re someone who came to design from another path, try asking yourself:
Once you see the patterns, start using them.
Name your edge. Talk about your background as an asset: in intros, portfolios, interviews, or team retrospectives. When projects get messy, lean into what you already know how to do. Trust your instincts. They’re real, and they’re earned. But balance them, too. Stay aware of when your strengths could become blind spots, like speed overriding thoughtfulness. That kind of awareness turns experience into a tool, not a trigger.
Your past doesn’t need to look like anyone else’s. It just needs to teach you something.
I want to express my deep gratitude to Sara Wachter-Boettcher, whose coaching helped me find the clarity and confidence to write this piece — and, more importantly, to move forward with purpose in both life and work. And to Lea Alcantara, my design director at Fueled, for being a steady creative force and an inspiring example of thoughtful leadership.
by Stephanie Campbell (hello@smashingmagazine.com) at August 13, 2025 11:00 AM
最近发现我在写的小游戏在启动时有很小的概率黑屏。我使用的是 ltask 多线程框架,在黑屏时感觉 ltask 并没有停止工作,似乎只是管理窗口的部分(线程/服务)卡死了。
窗口管理使用的是 sokol_app 做的多平台封装,这只是一个很浅的封装层,但已经够用。我觉得美中不足的是,sokol_app 的 frame 回调函数是放在 WinProc 中,由 Windows 的消息循环被动调度,而不是放在外层的主动 GetMessage 循环中。
即,在 Windows 程序中,线程通常会在最外面写一个这样的 while 循环:
for (;;) {
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT)
return;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 在这里,我们可以做一些额外的工作,比如渲染游戏画面、处理游戏逻辑。
}
但我们也可以选择在窗口的 WinProc 中,通过响应 WM_TIMER 等消息的方式来做这些工作:
LRESULT CALLBACK wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_TIMER :
// 在这里,可以定时做一些工作。
break;
}
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
// 外面的消息处理循环则可以使用 GetMessage 而不是 PeekMessage
while(GetMessage(&msg, NULL, 0, 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
无可厚非,后一种方法显得更正规一点:让 Windows 自身调度所有任务,系统如果做的正确,和系统的窗口系统本身契合的更好一点。这个模式是 Window 的历史设计造成的。把窗口系统的工作流程放在用户线程内,用户的程序其它部分配合它,换取交互的流畅度。
但是,一旦采用多线程设计,就变得有点不同了。窗口只是多线程任务的一部分,需要一个更高阶的框架来调度任务,例如 ltask 干的那些。通过在 WinProc 中处理对应消息,在没有消息进入的时候,线程会堵塞在 GetMessage 函数中。这对 ltask 这样的调度器来说非常的不友好。通常一个任务调度器需要的行为是:每个任务要么完成,要么让出,而不是阻塞。Windows 的 GetMessage/DispatchMessage 也是这样的循环,只不过是单线程的。
ltask 处理这样的模块,也不是完全没有办法。这得益于 ltask 的任务都运行在 lua 虚拟机上,和 C 层有一定的隔离。对于 C 代码来说,stack 是绑定在线程上的,所以无法在一个线程运行一半,然后在另一个线程继续工作(因为 stack 不同);但 Lua 的 stack 在 heap 上,迁移完全没有问题。
我曾经做过类似的尝试 ,但最终又从 ltask 主干上撤销了这个特性。倒不是实现的不对,而是配合它使用的 C 代码如果重入问题解决不好,隐藏的 bug 很难发现。这需要 C 部分最好在设计时就考虑过并行/重入问题。sokol 显然不是这样设计的。
为了让 sokol 可以在 ltask 下工作,我做了不少工作。sokol_gfx 的图形 api 部分倒是简单,我只需要保证在同一个服务中调用就可以了;比较麻烦的是 sokol_app 中处理窗口的部分。直接让 frame 回调函数运行在 ltask 的一个服务中非常困难。原因上面已述:这个回调函数结束后线程会挂起在 Windows 的消息处理循环中,而没有将控制权归还 ltask 。虽然可以通过 ltask 那个实验特性解决这个问题,但 sokol 并没有为多线程设计,很可能隐藏多线程 bug ,一旦出现难以调试。
我试过几个方案后,最终采用了最简单粗暴的方法:利用锁来同步任务。也就是在 frame callback 开始时抛出一个消息,并阻塞在一个锁上。这个消息会开启另一个 ltask 掌握的线程中对应的 render 服务;而在 render 服务渲染完当前帧,解开这个锁,frame callback 就会顺利返回。
在绝大多数场景中,这个方案工作的很好。但我最近偶尔发现在启动程序时,会有很小的概率,锁并没有解开。
一开始我并不为意,觉得或许是一些同步代码没有写好,因为有更想做的特性要开发,这种偶发死锁 bug 出现概率很低,且只出现在启动阶段,想着有空稍微复查一下启动代码就能解决。
这两天感觉的确“有空”了,花了一晚上,终于定位了问题。
问题出在游戏启动阶段改变窗口的标题上。固然,可以在窗口创建时就把标题设置好。但标题需要根据多语言环境设置不同的文本,处理多语言文本的这块逻辑不算简单,我不想放在启动的最初阶段(创建窗口之前),所以窗口创建时使用了一段默认文本,之后才修改它。
sokol_app 的 api 只是间接调用了 SetWindowTextW() 。显然不是 sokol_app 的封装问题。我查阅了 msdn ,发现 SetWindowTextW 只是给 WinProc 发送了一个 WM_SETTEXT 消息。也就是说,等价于调用 SendMessageW() 。
如果在 WinProc 所在线程中调用它当然没有问题,只是引起了 WinProc 重入:调用方在 frame callback 内,而 frame callback 处于 WinProc 的 WM_TIMER 的消息处理环节。这时调用 SetWindowTextW 等于递归再运行一次 WinProc 本身,但消息变成了 WM_SETTEXT ,新的调用返回后窗口的标题栏就被改变了。
可是,我现在在另外一个线程调用 SetWindowTextW 行为有所不同。这时 WM_SETTEXT 被投递到窗口消息处理线程,它需要排队等待 WinProc 再次被处理,也就是外层循环的下一次 DispatchMessage 调用。但是,这个时候当下的 DispatchMessage 还阻塞在 frame callback 的锁上面无法返回。这就是死锁产生的原因:
DispatchMessage 调用 WinProc 处理 WM_TIMER 消息,它调用了 sokol 的 frame callback 。我的程序在 frame callback 中发出消息唤醒真正的处理流程,并等待在锁上。SetWindowTextW ,其通过 SendMessageW 投递 WM_SETTEXT 到窗口线程的消息队列,等待返回。WM_TIMER 处理完毕才 DispatchMessage 才可以结束,后续的 GetMessage 才可以拿到 WM_SETTEXT 消息处理它。了解了死锁的原因后,最直接的解决方案是在窗口线程调用 SetWindowTextW 。因为这样会直接运行设置文本的逻辑,消息不需要进入消息队列,当然就没有锁的问题。但这个方案不适合现在的 ltask 框架。目前窗口线程不在 ltask 的管辖之下,也就无法在 lua 服务中调用 SetWindowTextW ,也无法直接通过 ltask 内部的消息把这个任务传递过去。
比如容易想到的是:“改变窗口标题”这个行为并不需要等待结果。那么是不是可以改用 PostWindowTextW 发送 WM_SETTEXT 就可以不阻塞调用方了呢?
答案是不行,原因在这里有解释 。因为这条消息发送了一个字符串,这里存在这个字符串生命期管理的问题,为了减少使用错误,Windows 禁止用 PostMessage 发送这样有生命期管理问题的系统消息。只有 SendMessage 可以在结果返回后正确释放消息文本所占用的内存。
所以,我们可以用独立线程通过 SendMessage 投递这个消息,并等待其返回后做完后续(生命期管理)工作。在 C 中创建新线程非常麻烦,但在 ltask 中却非常容易。只需要用一个独立的服务调用 SetWindowTextW 就够了。frame 的处理流程所在的服务/线程向它投递一个 ltask 消息,通知这个独立服务改变窗口标题,就不会阻塞 frame 流程。
AI is almost everywhere — it writes text, makes music, generates code, draws pictures, runs research, chats with you — and apparently even understands people better than they understand themselves?!
It’s a lot to take in. The pace is wild, and new tools pop up faster than anyone has time to try them. Amid the chaos, one thing is clear: this isn’t hype, but it’s structural change.
According to the Future of Jobs Report 2025 by the World Economic Forum, one of the fastest-growing, most in-demand skills for the next five years is the ability to work with AI and Big Data. That applies to almost every role — including product design.

What do companies want most from their teams? Right, efficiency. And AI can make people way more efficient. We’d easily spend 3x more time on tasks like replying to our managers without AI helping out. We’re learning to work with it, but many of us are still figuring out how to meet the rising bar.
That’s especially important for designers, whose work is all about empathy, creativity, critical thinking, and working across disciplines. It’s a uniquely human mix. At least, that’s what we tell ourselves.
Even as debates rage about AI’s limitations, tools today (June 2025 — timestamp matters in this fast-moving space) already assist with research, ideation, and testing, sometimes better than expected.
Of course, not everyone agrees. AI hallucinates, loses context, and makes things up. So how can both views exist at the same time? Very simple. It’s because both are true: AI is deeply flawed and surprisingly useful. The trick is knowing how to work with its strengths while managing its weaknesses. The real question isn’t whether AI is good or bad — it’s how we, as designers, stay sharp, stay valuable, and stay in the loop.
Why Prompting MattersPrompting matters more than most people realize because even small tweaks in how you ask can lead to radically different outputs. To see how this works in practice, let’s look at a simple example.
Imagine you want to improve the onboarding experience in your product. On the left, you have the prompt you send to AI. On the right, the response you get back.
| Input | Output |
|---|---|
| How to improve onboarding in a SaaS product? | 👉 Broad suggestions: checklists, empty states, welcome modals… |
| How to improve onboarding in Product A’s workspace setup flow? | 👉 Suggestions focused on workspace setup… |
| How to improve onboarding in Product A’s workspace setup step to address user confusion? | 👉 ~10 common pain points with targeted UX fixes for each… |
| How to improve onboarding in Product A by redesigning the workspace setup screen to reduce drop-off, with detailed reasoning? | 👉 ~10 paragraphs covering a specific UI change, rationale, and expected impact… |
This side-by-side shows just how much even the smallest prompt details can change what AI gives you.
Talking to an AI model isn’t that different from talking to a person. If you explain your thoughts clearly, you get a better understanding and communication overall.
Advanced prompting is about moving beyond one-shot, throwaway prompts. It’s an iterative, structured process of refining your inputs using different techniques so you can guide the AI toward more useful results. It focuses on being intentional with every word you put in, giving the AI not just the task but also the path to approach it step by step, so it can actually do the job.

Where basic prompting throws your question at the model and hopes for a quick answer, advanced prompting helps you explore options, evaluate branches of reasoning, and converge on clear, actionable outputs.
But that doesn’t mean simple prompts are useless. On the contrary, short, focused prompts work well when the task is narrow, factual, or time-sensitive. They’re great for idea generation, quick clarifications, or anything where deep reasoning isn’t required. Think of prompting as a scale, not a binary. The simpler the task, the faster a lightweight prompt can get the job done. The more complex the task, the more structure it needs.
In this article, we’ll dive into how advanced prompting can empower different product & design use cases, speeding up your workflow and improving your results — whether you’re researching, brainstorming, testing, or beyond. Let’s dive in.
Practical CasesIn the next section, we’ll explore six practical prompting techniques that we’ve found most useful in real product design work. These aren’t abstract theories — each one is grounded in hands-on experience, tested across research, ideation, and evaluation tasks. Think of them as modular tools: you can mix, match, and adapt them depending on your use case. For each, we’ll explain the thinking behind it and walk through a sample prompt.
Important note: The prompts you’ll see are not copy-paste recipes. Some are structured templates you can reuse with small tweaks; others are more specific, meant to spark your thinking. Use them as scaffolds, not scripts.
Technique: Role, Context, Instructions template + Checkpoints (with self-reflection)
Before solving any problem, there’s a critical step we often overlook: breaking the problem down into clear, actionable parts.
Jumping straight into execution feels fast, but it’s risky. We might end up solving the wrong thing, or solving it the wrong way. That’s where GPT can help: not just by generating ideas, but by helping us think more clearly about the structure of the problem itself.
There are many ways to break down a task. One of the most useful in product work is the Jobs To Be Done (JTBD) framework. Let’s see how we can use advanced prompting to apply JTBD decomposition to any task.
Good design starts with understanding the user, the problem, and the context. Good prompting? Pretty much the same. That’s why most solid prompts include three key parts: Role, Context, and Instructions. If needed, you can also add the expected format and any constraints.
In this example, we’re going to break down a task into smaller jobs and add self-checkpoints to the prompt, so the AI can pause, reflect, and self-verify along the way.
Role
Act as a senior product strategist and UX designer with deep expertise in Jobs To Be Done (JTBD) methodology and user-centered design. You think in terms of user goals, progress-making moments, and unmet needs — similar to approaches used at companies like Intercom, Basecamp, or IDEO.
Context
You are helping a product team break down a broad user or business problem into a structured map of Jobs To Be Done. This decomposition will guide discovery, prioritization, and solution design.
Task & Instructions
[👉 DESCRIBE THE USER TASK OR PROBLEM 👈🏼]
Use JTBD thinking to uncover:
- The main functional job the user is trying to get done;
- Related emotional or social jobs;
- Sub-jobs or tasks users must complete along the way;
- Forces of progress and barriers that influence behavior.
Checkpoints
Before finalizing, check yourself:
- Are the jobs clearly goal-oriented and not solution-oriented?
- Are sub-jobs specific steps toward the main job?
- Are emotional/social jobs captured?
- Are user struggles or unmet needs listed?
If anything’s missing or unclear, revise and explain what was added or changed.
With a simple one-sentence prompt, you’ll likely get a high-level list of user needs or feature ideas. An advanced approach can produce a structured JTBD breakdown of a specific user problem, which may include:
But these prompts are most powerful when used with real context. Try it now with your product. Even a quick test can reveal unexpected insights.
Technique: Attachments + Reasoning Before Understanding + Tree of Thought (ToT)
Sometimes, you don’t need to design something new — you need to understand what already exists.
Whether you’re doing a competitive analysis, learning from rivals, or benchmarking features, the first challenge is making sense of someone else’s design choices. What’s the feature really for? Who’s it helping? Why was it built this way?
Instead of rushing into critique, we can use GPT to reverse-engineer the thinking behind a product — before judging it. In this case, start by:
Role
You are a senior UX strategist and cognitive design analyst. Your expertise lies in interpreting digital product features based on minimal initial context, inferring purpose, user intent, and mental models behind design decisions before conducting any evaluative critique.
Context
You’ve been given internal documentation and screenshots of a feature. The goal is not to evaluate it yet, but to understand what it’s doing, for whom, and why.
Task & Instructions
Review the materials and answer:
- What is this feature for?
- Who is the intended user?
- What tasks or scenarios does it support?
- What assumptions does it make about the user?
- What does its structure suggest about priorities or constraints?
Once you get the first reply, take a moment to respond: clarify, correct, or add nuance to GPT’s conclusions. This helps align the model’s mental frame with your own.
For the audit part, we’ll use something called the Tree of Thought (ToT) approach.
Tree of Thought (ToT) is a prompting strategy that asks the AI to “think in branches.” Instead of jumping to a single answer, the model explores multiple reasoning paths, compares outcomes, and revises logic before concluding — like tracing different routes through a decision tree. This makes it perfect for handling more complex UX tasks.
You are now performing a UX audit based on your understanding of the feature. You’ll identify potential problems, alternative design paths, and trade-offs using a Tree of Thought approach, i.e., thinking in branches, comparing different reasoning paths before concluding.
or
Convert your understanding of the feature into a set of Jobs-To-Be-Done statements from the user’s perspective using a Tree of Thought approach.
List implicit assumptions this feature makes about the user's behavior, workflow, or context using a Tree of Thought approach.
Propose alternative versions of this feature that solve the same job using different interaction or flow mechanics using a Tree of Thought approach.
Technique: Role Conditioning + Memory Update
When you’re working on creative or strategic problems, there’s a common trap: AI often just agrees with you or tries to please your way of thinking. It treats your ideas like gospel and tells you they’re great — even when they’re not.
So how do you avoid this? How do you get GPT to challenge your assumptions and act more like a critical thinking partner? Simple: tell it to and ask to remember.
Instructions
From now on, remember to follow this mode unless I explicitly say otherwise.
Do not take my conclusions at face value. Your role is not to agree or assist blindly, but to serve as a sharp, respectful intellectual opponent.
Every time I present an idea, do the following:Maintain a constructive, rigorous, truth-seeking tone. Don’t argue for the sake of it. Argue to sharpen thought, expose blind spots, and help me reach clearer, stronger conclusions.
- Interrogate my assumptions: What am I taking for granted?
- Present counter-arguments: Where could I be wrong, misled, or overly confident?
- Test my logic: Is the reasoning sound, or are there gaps, fallacies, or biases?
- Offer alternatives: Not for the sake of disagreement, but to expand perspective.
- Prioritize truth and clarity over consensus: Even when it’s uncomfortable.
This isn’t a debate. It’s a collaboration aimed at insight.
Technique: Requirement-Oriented + Meta prompting
This one deserves a whole article on its own, but let’s lay the groundwork here.
When you’re building quick prototypes or UI screens using tools like v0, Bolt, Lovable, UX Pilot, etc., your prompt needs to be better than most PRDs you’ve worked with. Why? Because the output depends entirely on how clearly and specifically you describe the goal.
The catch? Writing that kind of prompt is hard. So instead of jumping straight to the design prompt, try writing a meta-prompt first. That is a prompt that asks GPT to help you write a better prompt. Prompting about prompting, prompt-ception, if you will.
Here’s how to make that work: Feed GPT what you already know about the app or the screen. Then ask it to treat things like information architecture, layout, and user flow as variables it can play with. That way, you don’t just get one rigid idea — you get multiple concept directions to explore.
Role
You are a product design strategist working with AI to explore early-stage design concepts.
Goal
Generate 3 distinct prompt variations for designing a Daily Wellness Summary single screen in a mobile wellness tracking app for Lovable/Bolt/v0.
Each variation should experiment with a different Information Architecture and Layout Strategy. You don’t need to fully specify the IA or layout — just take a different angle in each prompt. For example, one may prioritize user state, another may prioritize habits or recommendations, and one may use a card layout while another uses a scroll feed.
User context
The target user is a busy professional who checks this screen once or twice a day (morning/evening) to log their mood, energy, and sleep quality, and to receive small nudges or summaries from the app.
Visual style
Keep the tone calm and approachable.
Format
Each of the 3 prompt variations should be structured clearly and independently.
Remember: The key difference between the three prompts should be the underlying IA and layout logic. You don’t need to over-explain — just guide the design generator toward different interpretations of the same user need.
Technique: Casual Tree of Though + Casual Reasoning + Multi-Roles + Self-Reflection
Cognitive walkthrough is a powerful way to break down a user action and check whether the steps are intuitive.
Example: “User wants to add a task” → Do they know where to click? What to do next? Do they know it worked?
We’ve found this technique super useful for reviewing our own designs. Sometimes there’s already a mockup; other times we’re still arguing with a PM about what should go where. Either way, GPT can help.
Here’s an advanced way to run that process:
Context
You’ve been given a screenshot of a screen where users can create new tasks in a project management app. The main action the user wants to perform is “add a task”. Simulate behavior from two user types: a beginner with no prior experience and a returning user familiar with similar tools.
Task & Instructions
Go through the UI step by step and evaluate:For each step, consider alternative user paths (if multiple interpretations of the UI exist). Use a casual Tree-of-Thought method.
- Will the user know what to do at each step?
- Will they understand how to perform the action?
- Will they know they’ve succeeded?
At each step, reflect: what assumptions is the user making here? What visual feedback would help reduce uncertainty?
Format
Use a numbered list for each step. For each, add observations, possible confusions, and UX suggestions.
Limits
Don’t assume prior knowledge unless it’s visually implied.
Do not limit analysis to a single user type.
Cognitive walkthroughs are great, but they get even more useful when they lead to testable hypotheses.
After running the walkthrough, you’ll usually uncover moments that might confuse users. Instead of leaving that as a guess, turn those into concrete UX testing hypotheses.
We ask GPT to not only flag potential friction points, but to help define how we’d validate them with real users: using a task, a question, or observable behavior.
Task & Instructions
Based on your previous cognitive walkthrough:Limits
- Extract all potential usability hypotheses from the walkthrough.
- For each hypothesis:
- Assess whether it can be tested through moderated or unmoderated usability testing.
- Explain what specific UX decision or design element may cause this issue. Use causal reasoning.
- For testable hypotheses:
- Propose a specific usability task or question.
- Define a clear validation criterion (how you’ll know if the hypothesis is confirmed or disproved).
- Evaluate feasibility and signal strength of the test (e.g., how easy it is to test, and how confidently it can validate the hypothesis).
- Assign a priority score based on Impact, Confidence, and Ease (ICE).
Don’t invent hypotheses not rooted in your walkthrough output. Only propose tests where user behavior or responses can provide meaningful validation. Skip purely technical or backend concerns.
Technique: Multi-Roles
Good design is co-created. And good designers are used to working with cross-functional teams: PMs, engineers, analysts, QAs, you name it. Part of the job is turning scattered feedback into clear action items.
Earlier, we talked about how giving AI a “role” helps sharpen its responses. Now let’s level that up: what if we give it multiple roles at once? This is called multi-role prompting. It’s a great way to simulate a design review with input from different perspectives. You get quick insights and a more well-rounded critique of your design.
RoleDesigning With AI Is A Skill, Not A Shortcut
You are a cross-functional team of experts evaluating a new dashboard design:Context
- PM (focus: user value & prioritization)
- Engineer (focus: feasibility & edge cases)
- QA tester (focus: clarity & testability)
- Data analyst (focus: metrics & clarity of reporting)
- Designer (focus: consistency & usability)
The team is reviewing a mockup for a new analytics dashboard for internal use.
Task & Instructions
For each role:
- What stands out immediately?
- What concerns might this role have?
- What feedback or suggestions would they give?
By now, you’ve seen that prompting isn’t just about typing better instructions. It’s about designing better thinking.
We’ve explored several techniques, and each is useful in different contexts:
| Technique | When to use It |
|---|---|
| Role + Context + Instructions + Constraints | Anytime you want consistent, focused responses (especially in research, decomposition, and analysis). |
| Checkpoints / Self-verification | When accuracy, structure, or layered reasoning matters. Great for complex planning or JTBD breakdowns. |
| Reasoning Before Understanding (RBU) | When input materials are large or ambiguous (like docs or screenshots). Helps reduce misinterpretation. |
| Tree of Thought (ToT) | When you want the model to explore options, backtrack, compare. Ideal for audits, evaluations, or divergent thinking. |
| Meta-prompting | When you're not sure how to even ask the right question. Use it early in fuzzy or creative concepting. |
| Multi-role prompting | When you need well-rounded, cross-functional critique or to simulate team feedback. |
| Memory-updated “opponent” prompting | When you want to challenge your own logic, uncover blind spots, or push beyond echo chambers. |
But even the best techniques won’t matter if you use them blindly, so ask yourself:
Remember, you don’t need a long prompt every time. Use detail when the task demands it, not out of habit. AI can do a lot, but it reflects the shape of your thinking. And prompting is how you shape it. So don’t just prompt better. Think better. And design with AI — not around it.
by Ilia Kanazin & Marina Chernyshova (hello@smashingmagazine.com) at August 11, 2025 08:00 AM
纯单人游戏在桌面游戏中不太多见,但我很喜欢这种。毕竟,找人一起玩桌游太不容易,虽然多人协作桌游总可以一个人操控多方进行 solo ,但终究不是为单人游戏设计的。今天介绍的两款单人卡牌游戏,我没买到实体版,都只是在桌游模拟器上玩过几盘。
第一款是 Legacy of Yu (2023) 大禹治水。老实说,这不是一款卡牌“构筑”游戏。虽然在游戏过程中玩家还是需要从市场列“购买”新卡片,但游戏过程并不是围绕构筑进行的。这些卡牌更像是消耗品。
游戏中的工人卡有三种用法:
打出后获得卡片上标注的资源,然后进入弃牌堆。
销毁一张工人卡,获得卡片上额外标注的一次性资源,卡片将移出游戏。这样获得的资源一定比前一种方法获得的多。
把卡牌(常驻)押在版图中已经盖好的房子上,此后的回合每回合获得持续资源奖励。
和一般的卡牌构筑游戏不同,卡堆不是越少越好。一般的卡牌构筑游戏,精简卡堆总是好的,因为这样可以加快卡堆循环,能更快抽到自己需要的强力卡。而这个游戏中,当抽牌堆耗尽,游戏进程就会向前推进。一旦准备不足,推进游戏会加快失败进程。虽然,游戏过程中,除非迫不得已,都不要销毁工人卡获得额外资源。
游戏中有砖头、木头、粮食、货币(贝壳)四种资源,以及白色劳工、红色战士、黄色弓箭手、黑色骑士、蓝色枪兵五种工人。
资源可以用来做建设:砖头+木头+劳工=农场(三个待建),砖头+3x木头+劳工=前哨(四个待建),3x砖头+木头+劳工=房屋(四个待建),运河。
农场效果是固定的,为后面的轮次每回合增加固定产能。三个农场分别对应粮食、劳工、其它任意工人;前哨可以让四种特殊工人和白色劳工相互替代;房屋的触发效果是随机的,标注在房屋卡片背面,大多数是触发说明书上的事件。建成房屋还可以为工人提供工作地(将工人换成对应资源)以及常驻资源产地(减少卡组中的工人卡换取持续产能)。
游戏需要玩家以不断增加的成本修建六段运河。成本会逐步递增,一二段需要两个劳工及两个贝壳;三四段需要三个劳工及两个贝壳;五六段需要两个劳工两个指定颜色工人及三个贝壳。每段运河修建成功后,可获得一次性奖励并摧毁部分抽牌堆中的工人卡,以及触发事件。并可以为之后的游戏回合带来一些贸易选项:
贸易选项是固定的:
两个贝壳换一个粮食
一张弃牌换两个贝壳
劳工及粮食转换为任意工人
四个贝壳换一个劳工
两个粮食及两个贝壳增加一张工人卡
砖头或木头换一个贝壳
玩家需要在修完六段运河后存活到回合结束才能赢得游戏。
在游戏版图上方由预备工人卡和蛮族卡共享市场列。蛮族卡越多,可选择的工人卡就越少。一旦市场列全部挤满蛮族卡,游戏就会失败。
市场列的最左端位置上的工人卡总是免费的。玩家可以选择拿取放在弃牌堆,也可以直接销毁获得一次性资源。而蛮族卡会随着修建运河的进程逐步进入市场列。未消灭的蛮族,在每个回合结束会收取贡品,通常贡品可以用销毁一张工人卡抵消,但如果工人卡被销毁光也会导致游戏失败。击败蛮族需要支付卡片上标注的指定种类的工人,击败蛮族后会获得卡片上标注的一次性奖励。
这个游戏有传承机制,熟悉基础规则后,可以根据说明书逐步解锁新的玩法:游戏难度会随着游戏进程慢慢加强,并引入一些新的游戏机制。因为根据单局游戏失败或成功,导向不同的游戏进程,难度也是动态调节的。
这个游戏不是很热门,可能是因为它只能单人游玩,在国内很难买到实体版。但这个游戏我非常喜欢,桌游模拟器上有汉化过的电子版。
Kingdom Legacy: Feudal Kingdom (2024) 是一个较新的游戏。它和很多传承类游戏一样,几乎只能玩一次。因为游戏过程中会涂改卡牌,这个过程是不可逆的。而且卡堆一开始在包装内的次序是设计过的,一旦打乱还原比较麻烦。后期的一些卡片一旦被巨头,也会失去一些探索未知的乐趣。
可能是因为每开一局都需要新买一盒游戏的远古,这个游戏各处都缺货,不太好买到。好在其官网有所有卡片的电子版本,可以方便做研究。
游戏规则简单有趣,整个游戏过程是升级卡牌。大部分卡片有四个状态:两面每面上下两端。卡牌升级指支付一些资源,让卡片旋转到另一端或翻面(根据卡片上的指示),同时结束当前回合;卡片上也可能有一些标注的效果让卡片状态变化。资源不使用额外指示物,而是弃掉当前的卡片获得(同样标注在卡面),资源必须立刻使用,无法保留到下一回合。
这个游戏的“构筑”过程颇有新意。它没有主动挑选购买新卡的环节,但一旦抽牌堆为空,就会结束一大轮,会从卡堆中新补充两张新卡(以设计过的次序,没有随机元素)。而玩家的主动构筑在于对已有卡片的变化(升级)。
每一个回合,抽四张手牌,用其中三张的资源换取一张卡的升级。在玩的过程中,如果当前回合资源不足以升级卡片,可以选择增加两张新卡继续当前回合;也可以选择 pass ,放弃当前所有手牌,重新补四张。游戏不会失败,不管怎么玩,游戏都在抽到第 70 号卡后结束,并结算得分。玩家可以以得分多少评价自己玩的成绩。游戏包装内不只 70 张卡片,超过编号 70 的卡片会在游戏进程中根据卡片上描述的事件选择性加入游戏。
游戏中有六种资源:金币、木材、石头、金属、剑、货物。它们对应了不同卡片的升级需求。通过卡牌升级,把牌组改造为更有效的得分引擎是这个游戏的核心玩点。部分卡片是永久卡,可以在游戏过程中生效永久驻留在桌面提供对应功能。有些卡片会被永久销毁,如前所述,一盒游戏只能玩一次,所以一旦一张卡片被销毁就再也用不到了。按游戏规则的官方说法,你可以把已销毁的卡片擦屁股或是点火。
为了方便初次游戏熟悉规则,在翻开第 23 号卡片之前,可以反复重玩,玩家可以不断的刷开局;直到 23 号卡片之后,游戏才变得不可逆。而在游戏后期,玩家牌堆会越来越大(因为每个大轮次,即抽完牌堆后后会加入两张新卡),这时就引入了支线任务 ,在游戏术语中叫做扩展(expansions)。触发支线时,需要清洗(销毁)一张桌面的永久卡,并执行一次 purge 12 动作。即洗掉牌堆,抽出 12 张卡,选一张保留,并销毁另外 11 张卡。但 purge 的这 11 张卡上的分数可以保留下来,记录在支线得分中。支线会让牌堆维持在一个较小的规模。
每个支线都会持续 4 轮(对应卡片的四个状态),每轮旋转或反转支线卡推进任务。支线的每个状态都会有一个当轮生效的效果。游戏的基础包中有三个内置支线卡,额外的扩展包增加了许多任务(以及额外的卡片)。一局游戏可以最多触发 10 个支线。
8 月 11 日补充:
Kingdom Legacy: Feudal Kingdom 的卡片翻转机制我是第一次玩到,感觉挺有趣。但我最近又玩了一款老一点的游戏 Palm Island (2018) 也是同样的机制,玩起来也颇为有趣。
不过棕榈岛没有遗产机制,只有一些有限的成就卡(勉强也能算遗产)。它的资源储存和消耗方法比较独特,最有趣的一点是:虽然是一款桌面游戏,它被设计成没有桌面也能玩,只需要把握在手中即可,并不需要打在桌面上。
Intl API: A Definitive Guide To Browser-Native InternationalizationIt’s a common misconception that internationalization (i18n) is simply about translating text. While crucial, translation is merely one facet. One of the complexities lies in adapting information for diverse cultural expectations: How do you display a date in Japan versus Germany? What’s the correct way to pluralize an item in Arabic versus English? How do you sort a list of names in various languages?
Many developers have relied on weighty third-party libraries or, worse, custom-built formatting functions to tackle these challenges. These solutions, while functional, often come with significant overhead: increased bundle size, potential performance bottlenecks, and the constant struggle to keep up with evolving linguistic rules and locale data.
Enter the ECMAScript Internationalization API, more commonly known as the Intl object. This silent powerhouse, built directly into modern JavaScript environments, is an often-underestimated, yet incredibly potent, native, performant, and standards-compliant solution for handling data internationalization. It’s a testament to the web’s commitment to being worldwide, providing a unified and efficient way to format numbers, dates, lists, and more, according to specific locales.
Intl And Locales: More Than Just Language Codes
At the heart of Intl lies the concept of a locale. A locale is far more than just a two-letter language code (like en for English or es for Spanish). It encapsulates the complete context needed to present information appropriately for a specific cultural group. This includes:
en, es, fr).Latn for Latin, Cyrl for Cyrillic). For example, zh-Hans for Simplified Chinese, vs. zh-Hant for Traditional Chinese.US for United States, GB for Great Britain, DE for Germany). This is crucial for variations within the same language, such as en-US vs. en-GB, which differ in date, time, and number formatting.Typically, you’ll want to choose the locale according to the language of the web page. This can be determined from the lang attribute:
// Get the page's language from the HTML lang attribute
const pageLocale = document.documentElement.lang || 'en-US'; // Fallback to 'en-US'
Occasionally, you may want to override the page locale with a specific locale, such as when displaying content in multiple languages:
// Force a specific locale regardless of page language
const tutorialFormatter = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' });
console.log(Chinese example: ${tutorialFormatter.format(199.99)}); // Output: ¥199.99
In some cases, you might want to use the user’s preferred language:
// Use the user's preferred language
const browserLocale = navigator.language || 'ja-JP';
const formatter = new Intl.NumberFormat(browserLocale, { style: 'currency', currency: 'JPY' });
When you instantiate an Intl formatter, you can optionally pass one or more locale strings. The API will then select the most appropriate locale based on availability and preference.
The Intl object exposes several constructors, each for a specific formatting task. Let’s delve into the most frequently used ones, along with some powerful, often-overlooked gems.
Intl.DateTimeFormat: Dates and Times, GloballyFormatting dates and times is a classic i18n problem. Should it be MM/DD/YYYY or DD.MM.YYYY? Should the month be a number or a full word? Intl.DateTimeFormat handles all this with ease.
const date = new Date(2025, 6, 27, 14, 30, 0); // June 27, 2025, 2:30 PM
// Specific locale and options (e.g., long date, short time)
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'shortOffset' // e.g., "GMT+8"
};
console.log(new Intl.DateTimeFormat('en-US', options).format(date));
// "Friday, June 27, 2025 at 2:30 PM GMT+8"
console.log(new Intl.DateTimeFormat('de-DE', options).format(date));
// "Freitag, 27. Juni 2025 um 14:30 GMT+8"
// Using dateStyle and timeStyle for common patterns
console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'short' }).format(date));
// "Friday 27 June 2025 at 14:30"
console.log(new Intl.DateTimeFormat('ja-JP', { dateStyle: 'long', timeStyle: 'short' }).format(date));
// "2025年6月27日 14:30"
The flexibility of options for DateTimeFormat is vast, allowing control over year, month, day, weekday, hour, minute, second, time zone, and more.
Intl.NumberFormat: Numbers With Cultural NuanceBeyond simple decimal places, numbers require careful handling: thousands separators, decimal markers, currency symbols, and percentage signs vary wildly across locales.
const price = 123456.789;
// Currency formatting
console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price));
// "$123,456.79" (auto-rounds)
console.log(new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price));
// "123.456,79 €"
// Units
console.log(new Intl.NumberFormat('en-US', { style: 'unit', unit: 'meter', unitDisplay: 'long' }).format(100));
// "100 meters"
console.log(new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'kilogram', unitDisplay: 'short' }).format(5.5));
// "5,5 kg"
Options like minimumFractionDigits, maximumFractionDigits, and notation (e.g., scientific, compact) provide even finer control.
Intl.ListFormat: Natural Language ListsPresenting lists of items is surprisingly tricky. English uses “and” for conjunction and “or” for disjunction. Many languages have different conjunctions, and some require specific punctuation.
This API simplifies a task that would otherwise require complex conditional logic:
const items = ['apples', 'oranges', 'bananas'];
// Conjunction ("and") list
console.log(new Intl.ListFormat('en-US', { type: 'conjunction' }).format(items));
// "apples, oranges, and bananas"
console.log(new Intl.ListFormat('de-DE', { type: 'conjunction' }).format(items));
// "Äpfel, Orangen und Bananen"
// Disjunction ("or") list
console.log(new Intl.ListFormat('en-US', { type: 'disjunction' }).format(items));
// "apples, oranges, or bananas"
console.log(new Intl.ListFormat('fr-FR', { type: 'disjunction' }).format(items));
// "apples, oranges ou bananas"
Intl.RelativeTimeFormat: Human-Friendly TimestampsDisplaying “2 days ago” or “in 3 months” is common in UI, but localizing these phrases accurately requires extensive data. Intl.RelativeTimeFormat automates this.
const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
console.log(rtf.format(-1, 'day')); // "yesterday"
console.log(rtf.format(1, 'day')); // "tomorrow"
console.log(rtf.format(-7, 'day')); // "7 days ago"
console.log(rtf.format(3, 'month')); // "in 3 months"
console.log(rtf.format(-2, 'year')); // "2 years ago"
// French example:
const frRtf = new Intl.RelativeTimeFormat('fr-FR', { numeric: 'auto', style: 'long' });
console.log(frRtf.format(-1, 'day')); // "hier"
console.log(frRtf.format(1, 'day')); // "demain"
console.log(frRtf.format(-7, 'day')); // "il y a 7 jours"
console.log(frRtf.format(3, 'month')); // "dans 3 mois"
The numeric: 'always' option would force “1 day ago” instead of “yesterday”.
Intl.PluralRules: Mastering PluralizationThis is arguably one of the most critical aspects of i18n. Different languages have vastly different pluralization rules (e.g., English has singular/plural, Arabic has zero, one, two, many...). Intl.PluralRules allows you to determine the “plural category” for a given number in a specific locale.
const prEn = new Intl.PluralRules('en-US');
console.log(prEn.select(0)); // "other" (for "0 items")
console.log(prEn.select(1)); // "one" (for "1 item")
console.log(prEn.select(2)); // "other" (for "2 items")
const prAr = new Intl.PluralRules('ar-EG');
console.log(prAr.select(0)); // "zero"
console.log(prAr.select(1)); // "one"
console.log(prAr.select(2)); // "two"
console.log(prAr.select(10)); // "few"
console.log(prAr.select(100)); // "other"
This API doesn’t pluralize text directly, but it provides the essential classification needed to select the correct translation string from your message bundles. For example, if you have message keys like item.one, item.other, you’d use pr.select(count) to pick the right one.
Intl.DisplayNames: Localized Names For EverythingNeed to display the name of a language, a region, or a script in the user’s preferred language? Intl.DisplayNames is your comprehensive solution.
// Display language names in English
const langNamesEn = new Intl.DisplayNames(['en'], { type: 'language' });
console.log(langNamesEn.of('fr')); // "French"
console.log(langNamesEn.of('es-MX')); // "Mexican Spanish"
// Display language names in French
const langNamesFr = new Intl.DisplayNames(['fr'], { type: 'language' });
console.log(langNamesFr.of('en')); // "anglais"
console.log(langNamesFr.of('zh-Hans')); // "chinois (simplifié)"
// Display region names
const regionNamesEn = new Intl.DisplayNames(['en'], { type: 'region' });
console.log(regionNamesEn.of('US')); // "United States"
console.log(regionNamesEn.of('DE')); // "Germany"
// Display script names
const scriptNamesEn = new Intl.DisplayNames(['en'], { type: 'script' });
console.log(scriptNamesEn.of('Latn')); // "Latin"
console.log(scriptNamesEn.of('Arab')); // "Arabic"
With Intl.DisplayNames, you avoid hardcoding countless mappings for language names, regions, or scripts, keeping your application robust and lean.
You might be wondering about browser compatibility. The good news is that Intl has excellent support across modern browsers. All major browsers (Chrome, Firefox, Safari, Edge) fully support the core functionality discussed (DateTimeFormat, NumberFormat, ListFormat, RelativeTimeFormat, PluralRules, DisplayNames). You can confidently use these APIs without polyfills for the majority of your user base.
Intl
The Intl API is a cornerstone of modern web development for a global audience. It empowers front-end developers to deliver highly localized user experiences with minimal effort, leveraging the browser’s built-in, optimized capabilities.
By adopting Intl, you reduce dependencies, shrink bundle sizes, and improve performance, all while ensuring your application respects and adapts to the diverse linguistic and cultural expectations of users worldwide. Stop wrestling with custom formatting logic and embrace this standards-compliant tool!
It’s important to remember that Intl handles the formatting of data. While incredibly powerful, it doesn’t solve every aspect of internationalization. Content translation, bidirectional text (RTL/LTR), locale-specific typography, and deep cultural nuances beyond data formatting still require careful consideration. (I may write about these in the future!) However, for presenting dynamic data accurately and intuitively, Intl is the browser-native answer.
by Fuqiao Xue (hello@smashingmagazine.com) at August 08, 2025 10:00 AM
A design system is more than just a set of colors and buttons. It’s a shared language that helps designers and developers build good products together. At its core, a design system includes tokens (like colors, spacing, fonts), components (such as buttons, forms, navigation), plus the rules and documentation that tie all together across projects.
If you’ve ever used systems like Google Material Design or Shopify Polaris, for example, then you’ve seen how design systems set clear expectations for structure and behavior, making teamwork smoother and faster. But while design systems promote consistency, keeping everything in sync is the hard part. Update a token in Figma, like a color or spacing value, and that change has to show up in the code, the documentation, and everywhere else it’s used.
The same thing goes for components: when a button’s behavior changes, it needs to update across the whole system. That’s where the right tools and a bit of automation can make the difference. They help reduce repetitive work and keep the system easier to manage as it grows.
In this article, we’ll cover a variety of tools and techniques for syncing tokens, updating components, and keeping docs up to date, showing how automation can make all of it easier.
The Building Blocks Of AutomationLet’s start with the basics. Color, typography, spacing, radii, shadows, and all the tiny values that make up your visual language are known as design tokens, and they’re meant to be the single source of truth for the UI. You’ll see them in design software like Figma, in code, in style guides, and in documentation. Smashing Magazine has covered them before in great detail.
The problem is that they often go out of sync, such as when a color or component changes in design but doesn’t get updated in the code. The more your team grows or changes, the more these mismatches show up; not because people aren’t paying attention, but because manual syncing just doesn’t scale. That’s why automating tokens is usually the first thing teams should consider doing when they start building a design system. That way, instead of writing the same color value in Figma and then again in a configuration file, you pull from a shared token source and let that drive both design and development.
There are a few tools that are designed to help make this easier.
Token Studio is a Figma plugin that lets you manage design tokens directly in your file, export them to different formats, and sync them to code.

Specify lets you collect tokens from Figma and push them to different targets, including GitHub repositories, continuous integration pipelines, documentation, and more.

NamedDesignTokens.guide helps with naming conventions, which is honestly a common pain point, especially when you’re working with a large number of tokens.

Once your tokens are set and connected, you’ll spend way less time fixing inconsistencies. It also gives you a solid base to scale, whether that’s adding themes, switching brands, or even building systems for multiple products.
That’s also when naming really starts to count. If your tokens or components aren’t clearly named, things can get confusing quickly.
Note: Vitaly Friedman’s “How to Name Things” is worth checking out if you’re working with larger systems.
From there, it’s all about components. Tokens define the values, but components are what people actually use, e.g., buttons, inputs, cards, dropdowns — you name it. In a perfect setup, you build a component once and reuse it everywhere. But without structure, it’s easy for things to “drift” out of scope. It’s easy to end up with five versions of the same button, and what’s in code doesn’t match what’s in Figma, for example.
Automation doesn’t replace design, but rather, it connects everything to one source.
The Figma component matches the one in production, the documentation updates when the component changes, and the whole team is pulling from the same library instead of rebuilding their own version. This is where real collaboration happens.
Here are a few tools that help make that happen:
| Tool | What It Does |
|---|---|
| UXPin Merge | Lets you design using real code components. What you prototype is what gets built. |
| Supernova | Helps you publish a design system, sync design and code sources, and keep documentation up-to-date. |
| Zeroheight | Turns your Figma components into a central, browsable, and documented system for your whole team. |
A lot of the work starts right inside your design application. Once your tokens and components are in place, tools like Supernova help you take it further by extracting design data, syncing it across platforms, and generating production-ready code. You don’t need to write custom scripts or use the Figma API to get value from automation; these tools handle most of it for you.
But for teams that want full control, Figma does offer an API. It lets you do things like the following:
The Figma API is REST-based, so it works well with custom scripts and automations. You don’t need a huge setup, just the right pieces. On the development side, teams usually use Node.js or Python to handle automation. For example:
You won’t need that level of setup for most use cases, but it’s helpful to know it’s there if your team outgrows no-code tools.
The workflow becomes easier to manage once that’s clear, and you spend less time trying to fix changes or mismatches. When tokens, components, and documentation stay in sync, your team moves faster and spends less time fixing the same issues.
Extracting Design DataFigma is a collaborative design tool used to create UIs: buttons, layouts, styles, components, everything that makes up the visual language of the product. It’s also where all your design data lives, which includes the tokens we talked about earlier. This data is what we’ll extract and eventually connect to your codebase. But first, you’ll need a setup.
To follow along:
Once you’re in, you’ll see a home screen that looks something like the following:

From here, it’s time to set up your design tokens. You can either create everything from scratch or use a template from the Figma community to save time. Templates are a great option if you don’t want to build everything yourself. But if you prefer full control, creating your setup totally works too.
There are other ways to get tokens as well. For example, a site like namedesigntokens.guide lets you generate and download tokens in formats like JSON. The only catch is that Figma doesn’t let you import JSON directly, so if you go that route, you’ll need to bring in a middle tool like Specify to bridge that gap. It helps sync tokens between Figma, GitHub, and other places.
For this article, though, we’ll keep it simple and stick with Figma. Pick any design system template from the Figma community to get started; there are plenty to choose from.

Depending on the template you choose, you’ll get a pre-defined set of tokens that includes colors, typography, spacing, components, and more. These templates come in all types: website, e-commerce, portfolio, app UI kits, you name it. For this article, we’ll be using the /Design-System-Template--Community because it includes most of the tokens you’ll need right out of the box. But feel free to pick a different one if you want to try something else.
Once you’ve picked your template, it’s time to download the tokens. We’ll use Supernova, a tool that connects directly to your Figma file and pulls out design tokens, styles, and components. It makes the design-to-code process a lot smoother.
Go to supernova.io and create an account. Once you’re in, you’ll land on a dashboard that looks like this:

To pull in the tokens, head over to the Data Sources section in Supernova and choose Figma from the list of available sources. (You’ll also see other options like Storybook or Figma variables, but we’re focusing on Figma.) Next, click on Connect a new file, paste the link to your Figma template, and click Import.

Supernova will load the full design system from your template. From your dashboard, you’ll now be able to see all the tokens.

Design tokens are great inside Figma, but the real value shows when you turn them into code. That’s how the developers on your team actually get to use them.
Here’s the problem: Many teams default to copying values manually for things like color, spacing, and typography. But when you make a change to them in Figma, the code is instantly out of sync. That’s why automating this process is such a big win.
Instead of rewriting the same theme setup for every project, you generate it, constantly translating designs into dev-ready assets, and keep everything in sync from one source of truth.
Now that we’ve got all our tokens in Supernova, let’s turn them into code. First, go to the Code Automation tab, then click New Pipeline. You’ll see different options depending on what you want to generate: React Native, CSS-in-JS, Flutter, Godot, and a few others.
Let’s go with the CSS-in-JS option for the sake of demonstration:

After that, you’ll land on a setup screen with three sections: Data, Configuration, and Delivery.
Here, you can pick a theme. At first, it might only give you “Black” as the option; you can select that or leave it empty. It really doesn’t matter for the time being.

This is where you control how the code is structured. I picked PascalCase for how token names are formatted. You can also update how things like spacing, colors, or font styles are grouped and saved.

This is where you choose how you want the output delivered. I chose “Build Only”, which builds the code for you to download.

Once you’re done, click Save. The pipeline is created, and you’ll see it listed in your dashboard. From here, you can download your token code, which is already generated.
Automating DocumentationSo, what’s the point of documentation in a design system?
You can think of it as the instruction manual for your team. It explains what each token or component is, why it exists, and how to use it. Designers, developers, and anyone else on your team can stay on the same page — no guessing, no back-and-forth. Just clear context.
Let’s continue from where we stopped. Supernova is capable of handling your documentation. Head over to the Documentation tab. This is where you can start editing everything about your design system docs, all from the same place.
You can:
You’re building the documentation inside the same tool where your tokens live. In other words, there’s no jumping between tools and no additional setup. That’s where the automation kicks in. You edit once, and your docs stay synced with your design source. It all stays in one environment.

Once you’re done, click Publish and you will be presented with a new window asking you to sign in. After that, you’re able to access your live documentation site.
Practical Tips For AutomationsAutomation is great. It saves hours of manual work and keeps your design system tight across design and code. The trick is knowing when to automate and how to make sure it keeps working over time. You don’t need to automate everything right away. But if you’re doing the same thing over and over again, that’s a kind of red flag.
A few signs that it’s time to consider using automation:
There are three steps you need to consider. Let’s look at each one.
If your pipeline depends on design tools, like Figma, or platforms, like Supernova, you’ll want to know when changes are made and evaluate how they impact your work, because even small updates can quietly affect your exports.
It’s a good idea to check Figma’s API changelog now and then, especially if something feels off with your token syncing. They often update how variables and components are structured, and that can impact your pipeline. There’s also an RSS feed for product updates.
The same goes for Supernova’s product updates. They regularly roll out improvements that might tweak how your tokens are handled or exported. If you’re using open-source tools like Style Dictionary, keeping an eye on the GitHub repo (particularly the Issues tab) can save you from debugging weird token name changes later.
All of this isn’t about staying glued to release notes, but having a system to check if something suddenly stops working. That way, you’ll catch things before they reach production.
A common trap teams fall into is trying to automate everything in one big run: colors, spacing, themes, components, and docs, all processed in a single click. It sounds convenient, but it’s hard to maintain, and even harder to debug.
It’s much more manageable to split your automation into pieces. For example, having a single workflow that handles your core design tokens (e.g., colors, spacing, and font sizes), another for theme variations (e.g., light and dark themes), and one more for component mapping (e.g., buttons, inputs, and cards). This way, if your team changes how spacing tokens are named in Figma, you only need to update one part of the workflow, not the entire system. It’s also easier to test and reuse smaller steps.
Even if everything runs fine, always take a moment to check the exported output. It doesn’t need to be complicated. A few key things:
PrimaryColorColorText, that’s a red flag.To catch issues early, it helps to run tools like ESLint or Stylelint right after the pipeline completes. They’ll flag odd syntax or naming problems before things get shipped.
How AI Can HelpOnce your automation is stable, there’s a next layer that can boost your workflow: AI. It’s not just for writing code or generating mockups, but for helping with the small, repetitive things that eat up time in design systems. When used right, AI can assist without replacing your control over the system.
Here’s where it might fit into your workflow:
When you’re dealing with hundreds of tokens, naming them clearly and consistently is a real challenge. Some AI tools can help by suggesting clean, readable names for your tokens or components based on patterns in your design. It’s not perfect, but it’s a good way to kickstart naming, especially for large teams.
AI can also spot repeated styles or usage patterns across your design files. If multiple buttons or cards share similar spacing, shadows, or typography, tools powered by AI can group or suggest components for systemization even before a human notices.
Instead of writing everything from scratch, AI can generate first drafts of documentation based on your tokens, styles, and usage. You still need to review and refine, but it takes away the blank-page problem and saves hours.
Here are a few tools that already bring AI into the design and development space in practical ways:
This article is not about achieving complete automation in the technical sense, but more about using smart tools to streamline the menial and manual aspects of working with design systems. Exporting tokens, generating docs, and syncing design with code can be automated, making your process quicker and more reliable with the right setup.
Instead of rebuilding everything from scratch every time, you now have a way to keep things consistent, stay organized, and save time.
by Joas Pambou (hello@smashingmagazine.com) at August 06, 2025 10:00 AM
在 Agent 使用的模型上,Claude 一直独一档,Deepseek、豆包、Gemini 等模型跟它都有很大差距,很多号称 benchmark 接近和超过 Claude 的实际效果都不行。
K2 出来后在 Agent / Coding 相关的 benchmark 上效果很不错,同时也在一些 Agent 场景上试了下,实际体验是不错的,值得学习下它是怎么做的。
它的技术论文《KIMI K2: OPEN AGENTIC INTELLIGENCE》公开了模型训练过程的一些信息,一起学习下。

K2 几个重点:
分别对应大模型训练三部曲:预训练,SFT,强化学习。论文分别阐述了这三个阶段做了什么。
这部分介绍了训练 K2 基础模型的架构设计、优化器创新、数据增强处理,以及训练的硬件配置和调度。
模型架构遵循 DeepSeek V3 的架构,只是调整了一些关键参数,1.04万亿(1000B) 参数量的 MoE 模型,激活参数32B。
MoE(Mixture of Experts) 架构能做到高性能低成本,基本要成为 LLM 标配。 模型参数量越大,模型在训练过程中能存储的信息量就越多,模型聪明程度越高,这是 scaling law。但参数量越大,使用模型的推理成本就越高。 MoE 架构可以设计参数量很大的模型,但在推理时,每一个 token 都会被路由到到其中几个子模块(称为专家)去处理,只有少量参数参与了计算。 这也是为什么之前 DeepSeek 的成本很低的原因之一。K2 1000B 的参数量级,激活参数 32B,相当于它的推理成本跟 32B 大小的模型差不多。
跟 DeepSeek V3 的差异是调整了一些关键参数,比较细节了,特别提到两个点:
模型架构做的事不多。
预训练阶段 K2 最大的创新点在 MuonClip 优化器,花了较大篇幅介绍。简单从基础概念出发理解下它做了什么:
预训练中有个 token 效率的概念,每个 token 对模型更新的影响越大,token效率越高,数据处理目标提高 token 效率,其实就是提高数据质量。
K2 有 1.5 万亿 token 的数据去训练,极端假设这1.5万亿 token 都是一样的,那模型什么也学不到,token 效率很低,如果 1.5 万亿 token 均匀包含了每个领域多样性的数据,token 效率就高。
K2 做了几个事提升 token 效率:
互联网数据已经用完的情况下,如何在这些数据里清洗重组出多样和高质量的数据,提升预训练效果,是持续可以做的事。
除了常规的高质量标注数据做 SFT,K2 专门针对 Agent 场景创建了一套合成数据的流程,能造出大量高质量的 Agent 对话轨迹数据(trajectory),对预训练模型进行 SFT,让模型学会 agent 任务规划/调用工具/环境反馈相关的套路/格式/规则。
这个流程分三步:
{
//仅示例,非实际定义的格式
name: "financial_db_search",
description: "查询金融数据库中的产品信息,支持股票、债券、基金的基本数据(如代码、名称、价格、发行量)及历史交易数据(如近30天收盘价)",
// 输入参数的JSON Schema定义(约束模型传入的参数格式)
properties: {
type: "object",
required: ["product_code"], // 必选参数
properties: {
product_code: {
type: "string",
description: "产品代码,如股票代码(A股:600000.SH,美股:AAPL)、债券代码(019547.IB)"
},
time_range: {
type: "string",
enum: ["latest", "30d", "90d", "1y"],
default: "latest",
description: "查询时间范围:最新数据(latest)、近30天(30d)、近90天(90d)、近1年(1y),默认返回最新数据"
},
}
}
}
最终造出的数据示例:
//仅示例,非实际定义的格式
<|system|>
你是金融股票专家,可调用工具:stock_quote(查实时行情)、industry_trend(查行业趋势)
[{
name: "stock_quote",
description: "查询金融数据库中的实时行情",
properties: {
product_code: {
type: "string",
description: "股票代码..."
},
}
}, { ... industry_trend}]
<|/system|>
<|user|>帮我看看宁德时代(300750)现在的股价和涨跌幅。<|/user|>
<|assistant|>需要查询宁德时代的实时行情。<|tool_call|>{"name":"stock_quote","parameters":{"code":"300750","fields":["price","change"]}}<|/tool_call|><|/assistant|>
<|tool_response|>{"code":"300750","name":"宁德时代","price":118.50,"change":"+2.3%","time":"09:45"}<|/tool_response|>
<|assistant|>宁德时代(300750)当前股价118.50元,涨2.3%(截至09:45)。<|/assistant|>
<|user|>那新能源板块这周表现怎么样?适合加仓吗?<|/user|>
<|assistant|>用户想了解新能源板块周趋势,需调用行业工具。<|tool_call|>{"name":"industry_trend","parameters":{"sector":"新能源","period":"week"}}<|/tool_call|><|/assistant|>
<|tool_response|>{"sector":"新能源","period":"week","trend":"上涨5.2%","hotspots":"储能/充电桩","risk":"政策不确定性"}<|/tool_response|>
<|assistant|>新能源板块本周上涨5.2%,储能和充电桩领涨,但需注意政策不确定性。短期可少量加仓,建议持仓不超过总资金的15%。<|/assistant|>
总的来说,通过少部分真数据和大部分合成数据,去造出大量模拟 Agent 对话轨迹的高质量数据,数据量原文说是数以万计,也可能不止。这些数据可以直接用于 SFT,让模型学会 Agent 需要的多轮交互推理、工具调用能力。
这里造数据用到的 LLM,猜测是 claude sonnet 4,算是蒸馏了 claude 的 agent 能力?
强化学习阶段讲了很多策略,核心是通用的给训练环境、造任务、定义奖励模型。对于可验证奖励的任务,有多种不同验证策略,创造和引入大量的任务做训练;对于不好验证奖励的任务,用自我评判的方式去选更好的输出;另外也介绍了算法上的几个小优化策略。
给模型的强化学习搭建了一个训练场(Gym),设计了各种有明确对错标准的任务,让模型在里面学习。
总的来说,就是各种造数据,定义奖励模型,让模型靠近我们想要的结果。
上面基本是能定义好奖励模型的任务,接下来是对不太好衡量结果的任务怎么进一步提升,例如回答有没有帮助、有没有创意、推理深度够不够等。
这种要不就是人类标注数据,要不就是用 LLM 评估结果。K2 这里大致的意思:
这里大意是让 K2 自己对自己的输出好坏进行评判:
介绍了几个算法上的小策略:
预算控制:RL 只看最终结果是否能得到奖励,所以模型会倾向于输出更多的内容,更多的内容意味着更高命中答案的概率,但对很多任务来说是没必要的。K2 对不同任务类型设了输出 token 限制,超出会惩罚,引导模型输出简洁有效的回答。
PTX loss(Pre‑Training Cross‑Entropy) :OpenAI 在 RLHF 提出的,RL 过程中避免模型对原先能力的遗忘,K2 准备了一份高质量数据,训练过程会时不时加入评估,如果模型对这些数据效果变差了,就惩罚,让学习更稳健。
温度衰减(Temperature Decay):温度在大语言模型里是指激进输出还是保守输出,更细的理解是 next token 推理时是直接选概率最高的(保守),还是随机选前面几个(激进多样)。温度衰减是训练前期先激进多尝试不同方向,后期保守收敛,保持输出稳定。
强化学习相关就这些,对 Agent 推理能力起作用的,是可验证奖励模型里的 2-让模型理解复杂指令和 3-输出遵循事实,以及自我评判机制让模型输出推理深度更好。对 Coding 能力起作用的,基本就是编程和软件工程能力。
这些方法应该都多少在各种论文上出现过,但能不能做得好,数据质量怎样,中间有多少细微的策略调整,就看细活了。
整个模型训练基本就这样,其他的内容就剩下各种 benchmark 评估了,不再列。
看完什么感受?
When talking about job interviews for a UX position, we often discuss how to leave an incredible impression and how to negotiate the right salary. But it’s only one part of the story. The other part is to be prepared, to ask questions, and to listen carefully.
Below, I’ve put together a few useful resources on UX job interviews — from job boards to Notion templates and practical guides. I hope you or your colleagues will find it helpful.
The Design Interview KitAs you are preparing for that interview, get ready with the Design Interview Kit (Figma), a helpful practical guide that covers how to craft case studies, solve design challenges, write cover letters, present your portfolio, and negotiate your offer. Kindly shared by Oliver Engel.

The Product Designer’s (Job) Interview Playbook (PDF) is a practical little guide for designers through each interview phase, with helpful tips and strategies on things to keep in mind, talking points, questions to ask, red flags to watch out for and how to tell a compelling story about yourself and your work. Kindly put together by Meghan Logan.

From my side, I can only wholeheartedly recommend to not only speak about your design process. Tell stories about the impact that your design work has produced. Frame your design work as an enabler of business goals and user needs. And include insights about the impact you’ve produced — on business goals, processes, team culture, planning, estimates, and testing.
Also, be very clear about the position that you are applying for. In many companies, titles do matter. There are vast differences in responsibilities and salaries between various levels for designers, so if you see yourself as a senior, review whether it actually reflects in the position.
A Guide To Successful UX Job Interviews (+ Notion template)Catt Small’s Guide To Successful UX Job Interviews, a wonderful practical series on how to build a referral pipeline, apply for an opening, prepare for screening and interviews, present your work, and manage salary expectations. You can also download a Notion template.

In her wonderful article, Nati Asher has suggested many useful questions to ask in a job interview when you are applying as a UX candidate. I’ve taken the liberty of revising some of them and added a few more questions that might be worth considering for your next job interview.

Before a job interview, have your questions ready. Not only will they convey a message that you care about the process and the culture, but also that you understand what is required to be successful. And this fine detail might go a long way.
Don’t Forget About The STAR MethodInterviewers closer to business will expect you to present examples of your work using the STAR method (Situation — Task — Action — Result), and might be utterly confused if you delve into all the fine details of your ideation process or the choice of UX methods you’ve used.
As Meghan suggests, the interview is all about how your skills add value to the problem the company is currently solving. So ask about the current problems and tasks. Interview the person who interviews you, too — but also explain who you are, your focus areas, your passion points, and how you and your expertise would fit in a product and in the organization.
Wrapping UpA final note on my end: never take a rejection personally. Very often, the reasons you are given for rejection are only a small part of a much larger picture — and have almost nothing to do with you. It might be that a job description wasn’t quite accurate, or the company is undergoing restructuring, or the finances are too tight after all.
Don’t despair and keep going. Write down your expectations. Job titles matter: be deliberate about them and your level of seniority. Prepare good references. Have your questions ready for that job interview. As Catt Small says, “once you have a foot in the door, you’ve got to kick it wide open”.
You are a bright shining star — don’t you ever forget that.
Job BoardsYou can find more details on design patterns and UX in Smart Interface Design Patterns, our 15h-video course with 100s of practical examples from real-life projects — with a live UX training later this year. Everything from mega-dropdowns to complex enterprise tables — with 5 new segments added every year. Jump to a free preview. Use code BIRDIE to save 15% off.
Meet Smart Interface Design Patterns, our video course on interface design & UX.
25 video lessons (15h) + Live UX Training.
100 days money-back-guarantee.
40 video lessons (15h). Updated yearly.
Also available as a UX Bundle with 2 video courses.
by Vitaly Friedman (hello@smashingmagazine.com) at August 05, 2025 01:00 PM
Need a stylish font that will help you design to look more professional and impactful? You need an outline font. ...
The post 40 Best Outline Fonts and Typefaces for 2025 appeared first on Design Bombs.
Everybody loves a beautiful wallpaper to freshen up their desktops and home screens, right? To cater for new and unique designs on a regular basis, we started our monthly wallpapers series more than 14 years ago, and from the very beginning to today, artists and designers from across the globe have accepted the challenge and submitted their artworks. This month is no exception, of course.
In this post, you’ll find desktop wallpapers for August 2025, along with a selection of timeless designs from our archives that are bound to make your August extra colorful. A big thank you to everyone who tickled their creativity and shared their wallpapers with us this month — this post wouldn’t exist without your kind support!
Now, if you’re feeling inspired after browsing this collection, why not submit a wallpaper to get featured in one of our upcoming posts? Fire up your favorite design tool, grab your camera or pen and paper, and tell the story you want to tell. We can’t wait to see what you’ll come up with! Happy August!
“Set sail into a serene summer moment with this bright and breezy wallpaper. A wooden boat drifts gently across wavy blue waters dotted with lily pads, capturing the stillness and simplicity of late August days.” — Designed by Libra Fire from Serbia.
“When your phone becomes a pool and your pup’s living the dream — it’s a playful reminder that sometimes the best escapes are simple: unplug, slow down, soak in the sunshine, and let your imagination do the swimming.” — Designed by PopArt Studio from Serbia.
“August is like a boat cruise swaying with the rhythm of sea shanties. Our mascot really likes to have its muzzle caressed by the salty sea wind and getting its ears warmed by the summer sun.” — Designed by Caroline Boire from France.
“August 8 is International Cat Day, so of course the month belongs to her majesty. Confident, calm, and totally in charge. Just like every cat ever.” — Designed by Ginger IT Solutions from Serbia.
“Many people find August one of the happiest months of the year because of holidays. You can spend days sunbathing, swimming, birdwatching, listening to their joyful chirping, and indulging in sheer summer bliss. August 8th is also known as the Happiness Happens Day, so make it worthwhile.” — Designed by PopArt Studio from Serbia.
“August, the final breath of summer, brings with it a wistful nostalgia for a season not yet past.” — Designed by Ami Totorean from Romania.
“As we have taken a liking to diving through the coral reefs, we’ll also spend August diving and took the leap to Bora Bora. There we enjoy the sea and nature and above all, we rest to gain strength for the new course that is to come.” — Designed by Veronica Valenzuela from Spain.
Designed by Ricardo Gimenes from Spain.
Designed by Kasturi Palmal from India.
“As the sun dips below the horizon, casting a warm glow upon the open road, the retro van finds a resting place for the night. A campsite bathed in moonlight or a cozy motel straight from a postcard become havens where weary travelers can rest, rejuvenate, and prepare for the adventures that await with the dawn of a new day.” — Designed by PopArt Studio from Serbia.
Designed by Ricardo Gimenes from Spain.
“August means that fall is just around the corner, so I designed this wallpaper to remind everyone to ‘bee happy’ even though summer is almost over. Sweeter things are ahead!” — Designed by Emily Haines from the United States.
“I like the Paris night! All is very bright!” — Designed by Verónica Valenzuela from Spain.
Designed by Ricardo Gimenes from Spain.
Designed by Francesco Paratici from Australia.
Designed by Dorvan Davoudi from Canada.
“My dog Sami inspired me for this one. He lives in the moment and enjoys every second with a big smile on his face. I wish we could learn to enjoy life like he does! Happy August everyone!” — Designed by Westie Vibes from Portugal.
Handwritten August
“I love typography handwritten style.” — Designed by Chalermkiat Oncharoen from Thailand.
“August is one of my favorite months, when the nights are long and deep and crackling fire makes you think of many things at once and nothing at all at the same time. It’s about heat and cold which allow you to touch the eternity for a few moments.” — Designed by Igor Izhik from Canada.
“In Melbourne it is the last month of quite a cool winter so we are looking forward to some warmer days to come.” — Designed by Tazi from Australia.
Designed by Ricardo Gimenes from Spain.
“Our designers wanted to create something summery, but not very colorful, something more subtle. The first thing that came to mind was chamomile because there are a lot of them in Ukraine and their smell is associated with a summer field.” — Designed by MasterBundles from Ukraine.
“August… it’s time for a party and summer vacation — sea, moon, stars, music… and magical vibrant colors.” — Designed by Teodora Vasileva from Bulgaria.
“I love going to aquariums – the colors, patterns, and array of blue hues attract the nature lover in me while still appeasing my design eye. One of the highlights is always the jellyfish tanks. They usually have some kind of light show in them, which makes the jellyfish fade from an intense magenta to a deep purple — and it literally tickles me pink. We discovered that the collective noun for jellyfish is a bloom and, well, it was love-at-first-collective-noun all over again. I’ve used some intense colors to warm up your desktop and hopefully transport you into the depths of your own aquarium.” — Designed by Wonderland Collective from South Africa.
Colorful Summer
“‘Always keep mint on your windowsill in August, to ensure that the buzzing flies will stay outside where they belong. Don’t think summer is over, even when roses droop and turn brown and the stars shift position in the sky. Never presume August is a safe or reliable time of the year.’ (Alice Hoffman)” — Designed by Lívi from Hungary.
Designed by Vlad Gerasimov from Georgia.
Designed by Ricardo Gimenes from Spain.
“Every experience is a building block on your own life journey, so try to make the most of where you are in life and get the most out of each day.” — Designed by Tazi Design from Australia.
“Summer is in full swing and Chicago is feeling the heat! Take some time to chill out!” — Designed by Denise Johnson from Chicago.
Estonian Summer Sun
“This is a moment from Southern Estonia that shows amazing summer nights.” Designed by Erkki Pung from Estonia.
by Cosima Mielke (hello@smashingmagazine.com) at July 31, 2025 11:00 AM
Ever sat in a meeting where everyone jumped straight to solutions? “We need a new app!” “Let’s redesign the homepage!” “AI will fix everything!” This solution-first thinking is endemic in digital development — and it’s why so many projects fail to deliver real value. As the creator of the Core Model methodology, I developed this approach to flip the script: instead of starting with solutions, we start FROM the answer.
What’s the difference? Starting with solutions means imposing our preconceived ideas. Starting FROM the answer to a user task means forming a hypothesis about what users need, then taking a step back to follow a simple structure that validates and refines that hypothesis.
Six Good Questions That Lead to Better AnswersAt its heart, the Core Model is simply six good questions asked in the right order, with a seventh that drives action. It appeals to common sense — something often in short supply during complex digital projects.
When I introduced this approach to a large organization struggling with their website, their head of digital admitted: “We’ve been asking all these questions separately, but never in this structured way that connects them.”
These questions help teams pause, align around what matters, and create solutions that actually work:
This simple framework creates clarity across team boundaries, bringing together content creators, designers, developers, customer service, subject matter experts, and leadership around a shared understanding.
Starting With a HypothesisThe Core Model process typically begins before the workshop. The project lead or facilitator works with key stakeholders to:
This preparation ensures the workshop itself is focused and productive, with teams validating and refining hypotheses rather than starting from scratch.
The Core Model: Six Elements That Create AlignmentLet’s explore each element of the Core Model in detail:

Rather than detailed personas, the Core Model starts with quick proto-personas that build empathy for users in specific situations:
The key is to humanize users and understand their emotional and practical context before diving into solutions.
Beyond features or content, what are users actually trying to accomplish?
These tasks should be based on user research and drive everything that follows. Top task methodology is a great approach to this.
Every digital initiative should connect to clear organizational goals:
These objectives provide the measurement framework for success. (If you work with OKRs, you can think of these as Key Results that connect to your overall Objective.)
This element goes beyond just findability to include the user’s entire approach and mental model:
Understanding these angles of different approaches ensures we meet users where they are.
What should users do after engaging with this core?
These paths create coherent journeys (core flows) rather than dead ends.
Only after mapping the previous elements do we define the actual solution:
This becomes our blueprint for what actually needs to be created.
The Core Model process culminates with action cards that answer the crucial seventh question: “What needs to be done to create this solution?”

These cards typically include:
Action cards transform insights into concrete next steps, ensuring the workshop leads to real improvements rather than just interesting discussions.
The Power of Core PairsA unique aspect of the Core Model methodology is working in core pairs—two people from different competencies or departments working together on the same core sheet. This approach creates several benefits:
The ideal pair combines different perspectives — content and design, business and technical, expert and novice — creating a balanced approach that neither could achieve alone.
Creating Alignment Within and Between TeamsThe Core Model excels at creating two crucial types of alignment:
Modern teams bring together diverse competencies:
The Core Model gives these specialists a common framework. Instead of the designer focusing only on interfaces or the developer only on code, everyone aligns around user tasks and business goals.
As one UX designer told me:
“The Core Model changed our team dynamic completely. Instead of handing off wireframes to developers who didn’t understand the ‘why’ behind design decisions, we now share a common understanding of what we’re trying to accomplish.”
Users don’t experience your organization in silos — they move across touchpoints and teams. The Core Model helps connect these experiences:
By mapping connections between cores (core flows), organizations create coherent experiences rather than fragmented interactions.
Breaking Down Organizational BarriersThe Core Model creates a neutral framework where various perspectives can contribute while maintaining a unified direction. This is particularly valuable in traditional organizational structures where content responsibility is distributed across departments.
The Workshop: Making It HappenThe Core Model workshop brings these elements together in a practical format that can be adapted to different contexts and needs.
For complex projects with multiple stakeholders across organizational silos, the ideal format is a full-day (6–hour) workshop:
First Hour: Foundation and Context
Hours 2–4: Core Mapping
Hours 5–6: Presentation, Discussion, and Action Planning
The format is highly flexible:
The Core Model workshop thrives in different environments:
Note: You can find all downloads and templates here.
The composition of core pairs is critical to success:
Important to note: The workshop doesn’t produce final solutions.
Instead, it creates a comprehensive brief containing the following:
This brief becomes the foundation for subsequent development work, ensuring everyone builds toward the same goal while leaving room for specialist expertise during implementation.
Getting Started: Your First Core Model ImplementationReady to apply the Core Model in your organization? Here’s how to begin:
Before bringing everyone together:
Select participants who represent different perspectives:
Guide core pairs through the process:
Transform insights into concrete actions:
The Core Model works because it combines common sense with structure — asking the right questions in the right order to ensure we address what actually matters.
By starting FROM the answer, not WITH the solution, teams avoid premature problem-solving and create digital experiences that truly serve user needs while achieving organizational goals.
Whether you’re managing a traditional website, creating multi-channel content, or developing digital products, this methodology provides a framework for better collaboration, clearer priorities, and more effective outcomes.
This article is a short adaptation of my book The Core Model — A Common Sense to Digital Strategy and Design. You can find information about the book and updated resources at thecoremodel.com.
by Are Halland (hello@smashingmagazine.com) at July 30, 2025 01:00 PM
Elementor is one of – if not the most – popular page builders for WordPress available. There are around nine...
The post 7 of the Best WordPress Themes for Elementor (2024) appeared first on Design Bombs.
Finding the best WordPress hosting can be a painful process. Finding a solution that will do justice to your content,...
The post 9 Best WordPress Hosting Compared (Aug 2025) appeared first on Design Bombs.
It’s common to see Web Components directly compared to framework components. But most examples are actually specific to Custom Elements, which is one piece of the Web Components picture. It’s easy to forget Web Components are actually a set of individual Web Platform APIs that can be used on their own:
In other words, it’s possible to create a Custom Element without using Shadow DOM or HTML Templates, but combining these features opens up enhanced stability, reusability, maintainability, and security. They’re all parts of the same feature set that can be used separately or together.
With that being said, I want to pay particular attention to Shadow DOM and where it fits into this picture. Working with Shadow DOM allows us to define clear boundaries between the various parts of our web applications — encapsulating related HTML and CSS inside a DocumentFragment to isolate components, prevent conflicts, and maintain clean separation of concerns.
How you take advantage of that encapsulation involves trade-offs and a variety of approaches. In this article, we’ll explore those nuances in depth, and in a follow-up piece, we’ll dive into how to work effectively with encapsulated styles.
Why Shadow DOM ExistsMost modern web applications are built from an assortment of libraries and components from a variety of providers. With the traditional (or “light”) DOM, it’s easy for styles and scripts to leak into or collide with each other. If you are using a framework, you might be able to trust that everything has been written to work seamlessly together, but effort must still be made to ensure that all elements have a unique ID and that CSS rules are scoped as specifically as possible. This can lead to overly verbose code that both increases app load time and reduces maintainability.
<!-- div soup -->
<div id="my-custom-app-framework-landingpage-header" class="my-custom-app-framework-foo">
<div><div><div><div><div><div>etc...</div></div></div></div></div></div>
</div>
Shadow DOM was introduced to solve these problems by providing a way to isolate each component. The <video> and <details> elements are good examples of native HTML elements that use Shadow DOM internally by default to prevent interference from global styles or scripts. Harnessing this hidden power that drives native browser components is what really sets Web Components apart from their framework counterparts.

Most often, you will see shadow roots associated with Custom Elements. However, they can also be used with any HTMLUnknownElement, and many standard elements support them as well, including:
<aside><blockquote><body><div><footer><h1> to <h6><header><main><nav><p><section><span>Each element can only have one shadow root. Some elements, including <input> and <select>, already have a built-in shadow root that is not accessible through scripting. You can inspect them with your Developer Tools by enabling the Show User Agent Shadow DOM setting, which is “off” by default.


Before leveraging the benefits of Shadow DOM, you first need to establish a shadow root on an element. This can be instantiated imperatively or declaratively.
To create a shadow root using JavaScript, use attachShadow({ mode }) on an element. The mode can be open (allowing access via element.shadowRoot) or closed (hiding the shadow root from outside scripts).
const host = document.createElement('div');
const shadow = host.attachShadow({ mode: 'open' });
shadow.innerHTML = '<p>Hello from the Shadow DOM!</p>';
document.body.appendChild(host);
In this example, we’ve established an open shadow root. This means that the element’s content is accessible from the outside, and we can query it like any other DOM node:
host.shadowRoot.querySelector('p'); // selects the paragraph element
If we want to prevent external scripts from accessing our internal structure entirely, we can set the mode to closed instead. This causes the element’s shadowRoot property to return null. We can still access it from our shadow reference in the scope where we created it.
shadow.querySelector('p');
This is a crucial security feature. With a closed shadow root, we can be confident that malicious actors cannot extract private user data from our components. For example, consider a widget that shows banking information. Perhaps it contains the user’s account number. With an open shadow root, any script on the page can drill into our component and parse its contents. In closed mode, only the user can perform this kind of action with manual copy-pasting or by inspecting the element.
I suggest a closed-first approach when working with Shadow DOM. Make a habit of using closed mode unless you are debugging, or only when absolutely necessary to get around a real-world limitation that cannot be avoided. If you follow this approach, you will find that the instances where open mode is actually required are few and far between.
We don’t have to use JavaScript to take advantage of Shadow DOM. Registering a shadow root can be done declaratively. Nesting a <template> with a shadowrootmode attribute inside any supported element will cause the browser to automatically upgrade that element with a shadow root. Attaching a shadow root in this manner can even be done with JavaScript disabled.
<my-widget>
<template shadowrootmode="closed">
<p> Declarative Shadow DOM content </p>
</template>
</my-widget>
Again, this can be either open or closed. Consider the security implications before using open mode, but note that you cannot access the closed mode content through any scripts unless this method is used with a registered Custom Element, in which case, you can use ElementInternals to access the automatically attached shadow root:
class MyWidget extends HTMLElement {
#internals;
#shadowRoot;
constructor() {
super();
this.#internals = this.attachInternals();
this.#shadowRoot = this.#internals.shadowRoot;
}
connectedCallback() {
const p = this.#shadowRoot.querySelector('p')
console.log(p.textContent); // this works
}
};
customElements.define('my-widget', MyWidget);
export { MyWidget };
Shadow DOM Configuration
There are three other options besides mode that we can pass to Element.attachShadow().
clonable:trueUntil recently, if a standard element had a shadow root attached and you tried to clone it using Node.cloneNode(true) or document.importNode(node,true), you would only get a shallow copy of the host element without the shadow root content. The examples we just looked at would actually return an empty <div>. This was never an issue with Custom Elements that built their own shadow root internally.
But for a declarative Shadow DOM, this means that each element needs its own template, and they cannot be reused. With this newly-added feature, we can selectively clone components when it’s desirable:
<div id="original">
<template shadowrootmode="closed" shadowrootclonable>
<p> This is a test </p>
</template>
</div>
<script>
const original = document.getElementById('original');
const copy = original.cloneNode(true); copy.id = 'copy';
document.body.append(copy); // includes the shadow root content
</script>
serializable:trueEnabling this option allows you to save a string representation of the content inside an element’s shadow root. Calling Element.getHTML() on a host element will return a template copy of the Shadow DOM’s current state, including all nested instances of shadowrootserializable. This can be used to inject a copy of your shadow root into another host, or cache it for later use.
In Chrome, this actually works through a closed shadow root, so be careful of accidentally leaking user data with this feature. A safer alternative would be to use a closed wrapper to shield the inner contents from external influences while still keeping things open internally:
<wrapper-element></wrapper-element>
<script>
class WrapperElement extends HTMLElement {
#shadow;
constructor() {
super();
this.#shadow = this.attachShadow({ mode:'closed' });
this.#shadow.setHTMLUnsafe(<nested-element>
<template shadowrootmode="open" shadowrootserializable>
<div id="test">
<template shadowrootmode="open" shadowrootserializable>
<p> Deep Shadow DOM Content </p>
</template>
</div>
</template>
</nested-element>);
this.cloneContent();
}
cloneContent() {
const nested = this.#shadow.querySelector('nested-element');
const snapshot = nested.getHTML({ serializableShadowRoots: true });
const temp = document.createElement('div');
temp.setHTMLUnsafe(<another-element>${snapshot}</another-element>);
const copy = temp.querySelector('another-element');
copy.shadowRoot.querySelector('#test').shadowRoot.querySelector('p').textContent = 'Changed Content!';
this.#shadow.append(copy);
}
}
customElements.define('wrapper-element', WrapperElement);
const wrapper = document.querySelector('wrapper-element');
const test = wrapper.getHTML({ serializableShadowRoots: true });
console.log(test); // empty string due to closed shadow root
</script>
Notice setHTMLUnsafe(). That’s there because the content contains <template> elements. This method must be called when injecting trusted content of this nature. Inserting the template using innerHTML would not trigger the automatic initialization into a shadow root.
delegatesFocus:trueThis option essentially makes our host element act as a <label> for its internal content. When enabled, clicking anywhere on the host or calling .focus() on it will move the cursor to the first focusable element in the shadow root. This will also apply the :focus pseudo-class to the host, which is especially useful when creating components that are intended to participate in forms.
<custom-input>
<template shadowrootmode="closed" shadowrootdelegatesfocus>
<fieldset>
<legend> Custom Input </legend>
<p> Click anywhere on this element to focus the input </p>
<input type="text" placeholder="Enter some text...">
</fieldset>
</template>
</custom-input>
This example only demonstrates focus delegation. One of the oddities of encapsulation is that form submissions are not automatically connected. That means an input’s value will not be in the form submission by default. Form validation and states are also not communicated out of the Shadow DOM. There are similar connectivity issues with accessibility, where the shadow root boundary can interfere with ARIA. These are all considerations specific to forms that we can address with ElementInternals, which is a topic for another article, and is cause to question whether you can rely on a light DOM form instead.
So far, we have only looked at fully encapsulated components. A key Shadow DOM feature is using slots to selectively inject content into the component’s internal structure. Each shadow root can have one default (unnamed) <slot>; all others must be named. Naming a slot allows us to provide content to fill specific parts of our component as well as fallback content to fill any slots that are omitted by the user:
<my-widget>
<template shadowrootmode="closed">
<h2><slot name="title"><span>Fallback Title</span></slot></h2>
<slot name="description"><p>A placeholder description.</p></slot>
<ol><slot></slot></ol>
</template>
<span slot="title"> A Slotted Title</span>
<p slot="description">An example of using slots to fill parts of a component.</p>
<li>Foo</li>
<li>Bar</li>
<li>Baz</li>
</my-widget>
Default slots also support fallback content, but any stray text nodes will fill them. As a result, this only works if you collapse all whitespace in the host element’s markup:
<my-widget><template shadowrootmode="closed">
<slot><span>Fallback Content</span></slot>
</template></my-widget>
Slot elements emit slotchange events when their assignedNodes() are added or removed. These events do not contain a reference to the slot or the nodes, so you will need to pass those into your event handler:
class SlottedWidget extends HTMLElement {
#internals;
#shadow;
constructor() {
super();
this.#internals = this.attachInternals();
this.#shadow = this.#internals.shadowRoot;
this.configureSlots();
}
configureSlots() {
const slots = this.#shadow.querySelectorAll('slot');
console.log({ slots });
slots.forEach(slot => {
slot.addEventListener('slotchange', () => {
console.log({
changedSlot: slot.name || 'default',
assignedNodes: slot.assignedNodes()
});
});
});
}
}
customElements.define('slotted-widget', SlottedWidget);
Multiple elements can be assigned to a single slot, either declaratively with the slot attribute or through scripting:
const widget = document.querySelector('slotted-widget');
const added = document.createElement('p');
added.textContent = 'A secondary paragraph added using a named slot.';
added.slot = 'description';
widget.append(added);
Notice that the paragraph in this example is appended to the host element. Slotted content actually belongs to the “light” DOM, not the Shadow DOM. Unlike the examples we’ve covered so far, these elements can be queried directly from the document object:
const widgetTitle = document.querySelector('my-widget [slot=title]');
widgetTitle.textContent = 'A Different Title';
If you want to access these elements internally from your class definition, use this.children or this.querySelector. Only the <slot> elements themselves can be queried through the Shadow DOM, not their content.
Now you know why you would want to use Shadow DOM, when you should incorporate it into your work, and how you can use it right now.
But your Web Components journey can’t end here. We’ve only covered markup and scripting in this article. We have not even touched on another major aspect of Web Components: Style encapsulation. That will be our topic in another article.
by Russell Beswick (hello@smashingmagazine.com) at July 28, 2025 08:00 AM
Welcome to this step-by-step tutorial on how to make an image transparent in Photoshop! Whether you’re a seasoned graphic designer...
The post How to Make an Image Transparent in Photoshop appeared first on Design Bombs.
As we all know, gamers have a unique sense of artistic style. If you want to tap into that gaming...
The post 40 Best Gaming Fonts For All Your Gaming-related Designs (2025) appeared first on Design Bombs.
Struggling to get noticed when you submit job applications? Do you want to stand out from the crowd but don’t...
The post 45 Best CV & Resume Templates That’ll Get You Hired (2025) appeared first on Design Bombs.
Are you on the lookout for the best retro text effects and styles to use in Adobe Photoshop and Illustrator?...
The post 30 Best Retro Text Effects & Styles for Adobe Illustrator and Photoshop appeared first on Design Bombs.
If you want to evoke that special fall feeling in your designs, you’ll need to use the right kind of...
The post 26 Best Autumn Fonts for Fall-themed Designs for 2025 appeared first on Design Bombs.
Want to add a touch of feminine elegance to your designs? Floral fonts are a great way to do this. ...
The post 29 Best Floral Fonts for Botanical Inspired Designs (2025) appeared first on Design Bombs.
Many products — digital and physical — are focused on “average” users — a statistical representation of the user base, which often overlooks or dismisses anything that deviates from that average, or happens to be an edge case. But people are never edge cases, and “average” users don’t really exist. We must be deliberate and intentional to ensure that our products reflect that.
Today, roughly 10% of people are left-handed. Yet most products — digital and physical — aren’t designed with them in mind. And there is rarely a conversation about how a particular digital experience would work better for their needs. So how would it adapt, and what are the issues we should keep in mind? Well, let’s explore what it means for us.

This article is part of our ongoing series on UX. You can find more details on design patterns and UX strategy in Smart Interface Design Patterns 🍣 — with live UX training coming up soon. Jump to table of contents.
Left-Handedness ≠ “Left-Only”It’s easy to assume that left-handed people are usually left-handed users. However, that’s not necessarily the case. Because most products are designed with right-handed use in mind, many left-handed people have to use their right hand to navigate the physical world.
From very early childhood, left-handed people have to rely on their right hand to use tools and appliances like scissors, openers, fridges, and so on. That’s why left-handed people tend to be ambidextrous, sometimes using different hands for different tasks, and sometimes using different hands for the same tasks interchangeably. However, only 1% of people use both hands equally well (ambidextrous).

In the same way, right-handed people aren’t necessarily right-handed users. It’s common to be using a mobile device in both left and right hands, or both, perhaps with a preference for one. But when it comes to writing, a preference is stronger.
Challenges For Left-Handed UsersBecause left-handed users are in the minority, there is less demand for left-handed products, and so typically they are more expensive, and also more difficult to find. Troubles often emerge with seemingly simple tools — scissors, can openers, musical instruments, rulers, microwaves and bank pens.

For example, most scissors are designed with the top blade positioned for right-handed use, which makes cutting difficult and less precise. And in microwaves, buttons and interfaces are nearly always on the right, making left-handed use more difficult.
Now, with digital products, most left-handed people tend to adapt to right-handed tools, which they use daily. Unsurprisingly, many use their right hand to navigate the mouse. However, it’s often quite different on mobile where the left hand is often preferred.
As Ruben Babu writes, we shouldn’t design a fire extinguisher that can’t be used by both hands. Think pull up and pull down, rather than swipe left or right. Minimize the distance to travel with the mouse. And when in doubt, align to the center.

A simple way to test the mobile UI is by trying to use the opposite-handed UX test. For key flows, we try to complete them with your non-dominant hand and use the opposite hand to discover UX shortcomings.
For physical products, you might try the oil test. It might be more effective than you might think.
Good UX Works For BothOur aim isn’t to degrade the UX of right-handed users by meeting the needs of left-handed users. The aim is to create an accessible experience for everyone. Providing a better experience for left-handed people also benefits right-handed people who have a temporary arm disability.
And that’s an often-repeated but also often-overlooked universal principle of usability: better accessibility is better for everyone, even if it might feel that it doesn’t benefit you directly at the moment.
Useful ResourcesYou can find more details on design patterns and UX in Smart Interface Design Patterns, our 15h-video course with 100s of practical examples from real-life projects — with a live UX training later this year. Everything from mega-dropdowns to complex enterprise tables — with 5 new segments added every year. Jump to a free preview. Use code BIRDIE to save 15% off.
Meet Smart Interface Design Patterns, our video course on interface design & UX.
25 video lessons (15h) + Live UX Training.
100 days money-back-guarantee.
40 video lessons (15h). Updated yearly.
Also available as a UX Bundle with 2 video courses.
by Vitaly Friedman (hello@smashingmagazine.com) at July 25, 2025 03:00 PM
Instagram is a competitive platform. If you really want to grow your followers and keep them engaged, then the content...
The post 25 Best Free and Premium Instagram Templates (2025) appeared first on Design Bombs.
JavaScript event listeners are very important, as they exist in almost every web application that requires interactivity. As common as they are, it is also essential for them to be managed properly. Improperly managed event listeners can lead to memory leaks and can sometimes cause performance issues in extreme cases.
Here’s the real problem: JavaScript event listeners are often not removed after they are added. And when they are added, they do not require parameters most of the time — except in rare cases, which makes them a little trickier to handle.
A common scenario where you may need to use parameters with event handlers is when you have a dynamic list of tasks, where each task in the list has a “Delete” button attached to an event handler that uses the task’s ID as a parameter to remove the task. In a situation like this, it is a good idea to remove the event listener once the task has been completed to ensure that the deleted element can be successfully cleaned up, a process known as garbage collection.
A Common Mistake When Adding Event ListenersA very common mistake when adding parameters to event handlers is calling the function with its parameters inside the addEventListener() method. This is what I mean:
button.addEventListener('click', myFunction(param1, param2));
The browser responds to this line by immediately calling the function, irrespective of whether or not the click event has happened. In other words, the function is invoked right away instead of being deferred, so it never fires when the click event actually occurs.
You may also receive the following console error in some cases:

This error makes sense because the second parameter of the addEventListener method can only accept a JavaScript function, an object with a handleEvent() method, or simply null. A quick and easy way to avoid this error is by changing the second parameter of the addEventListener method to an arrow or anonymous function.
button.addEventListener('click', (event) => {
myFunction(event, param1, param2); // Runs on click
});
The only hiccup with using arrow and anonymous functions is that they cannot be removed with the traditional removeEventListener() method; you will have to make use of AbortController, which may be overkill for simple cases. AbortController shines when you have multiple event listeners to remove at once.
For simple cases where you have just one or two event listeners to remove, the removeEventListener() method still proves useful. However, in order to make use of it, you’ll need to store your function as a reference to the listener.
There are several ways to include parameters with event handlers. However, for the purpose of this demonstration, we are going to constrain our focus to the following two:
Using arrow and anonymous functions is the fastest and easiest way to get the job done.
To add an event handler with parameters using arrow and anonymous functions, we’ll first need to call the function we’re going to create inside the arrow function attached to the event listener:
const button = document.querySelector("#myButton");
button.addEventListener("click", (event) => {
handleClick(event, "hello", "world");
});
After that, we can create the function with parameters:
function handleClick(event, param1, param2) {
console.log(param1, param2, event.type, event.target);
}
Note that with this method, removing the event listener requires the AbortController. To remove the event listener, we create a new AbortController object and then retrieve the AbortSignal object from it:
const controller = new AbortController();
const { signal } = controller;
Next, we can pass the signal from the controller as an option in the removeEventListener() method:
button.addEventListener("click", (event) => {
handleClick(event, "hello", "world");
}, { signal });
Now we can remove the event listener by calling AbortController.abort():
controller.abort()
Closures in JavaScript are another feature that can help us with event handlers. Remember the mistake that produced a type error? That mistake can also be corrected with closures. Specifically, with closures, a function can access variables from its outer scope.
In other words, we can access the parameters we need in the event handler from the outer function:
function createHandler(message, number) {
// Event handler
return function (event) {
console.log(${message} ${number} - Clicked element:, event.target);
};
}
const button = document.querySelector("#myButton");
button.addEventListener("click", createHandler("Hello, world!", 1));
}
This establishes a function that returns another function. The function that is created is then called as the second parameter in the addEventListener() method so that the inner function is returned as the event handler. And with the power of closures, the parameters from the outer function will be made available for use in the inner function.
Notice how the event object is made available to the inner function. This is because the inner function is what is being attached as the event handler. The event object is passed to the function automatically because it’s the event handler.
To remove the event listener, we can use the AbortController like we did before. However, this time, let’s see how we can do that using the removeEventListener() method instead.
In order for the removeEventListener method to work, a reference to the createHandler function needs to be stored and used in the addEventListener method:
function createHandler(message, number) {
return function (event) {
console.log(${message} ${number} - Clicked element:, event.target);
};
}
const handler = createHandler("Hello, world!", 1);
button.addEventListener("click", handler);
Now, the event listener can be removed like this:
button.removeEventListener("click", handler);
Conclusion
It is good practice to always remove event listeners whenever they are no longer needed to prevent memory leaks. Most times, event handlers do not require parameters; however, in rare cases, they do. Using JavaScript features like closures, AbortController, and removeEventListener, handling parameters with event handlers is both possible and well-supported.
by Amejimaobari Ollornwi (hello@smashingmagazine.com) at July 21, 2025 10:00 AM
我这两年攀岩时总是体力不够用,出去野攀如果需要先爬山接近的话,往往爬到岩壁下就累个半死不想动了。而我那帮 50 多岁的岩友一个个都比我有活力的多。所以我想通过有氧运动改善一下心肺功能。岩友建议我试试慢跑。
去年底痛风发作 后也考虑过减少一些体重,据说有利于降低尿酸。但有点懒就一直没有开始跑。
我的身体状态是这样的:
目前身高 187 ,大学毕业时大约 183 ,后来 20 多年陆续又长了几厘米。大学刚毕业时体重只有 71 kg ,非常瘦。在 2002 年左右开始去健身房撸铁增肌,最高长到过 78kg 。后来去杭州没那么勤快了,又掉下来不少。到 2011 年回到广州时只剩下 74kg 不到。当时身高 185 - 186 之间,后来这 15 年又长了点身高,体重却在孩子出生后暴增,最高到过 90 kg 以上 。
前几年有一段时间,我自己在家做 HIIT 希望可以减重。2020 年时,因为尿路结石看了急症 。之后改做跳绳(希望可以排石),最后体重降到了 84 kg 。
最近一年因为不再上班工作了,除了偶尔(一周两到三次)出门去岩馆攀岩,几乎都在家里。体重在 3 个月前又升到了 91kg 。
大约在两个半月前,我下决心增加一些运动量。除了每周三次的攀岩外,另外四天每天做半个小时以上的慢跑。听取岩友建议,买了双软底的跑步鞋(体重较大,应重点保护膝盖)。选择在家旁边的公园,有质地比较软的跑步道。根据网上信息的测算,根据我的年龄,应该在慢跑时把心率控制在 140 以下。配速不重要,重要的是心率以及每次的时长(不低于 30 分钟),并避免受伤。
两个多月之前,我第一次尝试时,跑到 600 米左右,心率就超过了 150 ,赶紧停下来走路休息。
到现在坚持了两个多月,已经成为习惯。今天刮完台风,特别凉快。跑步时状态很好。第一公里用时 7 分钟,最后心率升到 140 。如果连续再跑下去还会上升,所以我选择走路休息到心率下降到 120 再继续。如此把心率维持在 120~140 之间,半个小时大约可以跑 3.5km 。
跑完再快走 5 分钟左右回家,不太觉得累。相比刚开始跑步时,到家就想躺下休息。这段时间在岩馆更也有动力爬。有岩友称,你终于有点老岩友的样子了。
至于体重,最近三天都在 86kg ,从数字上看已经减少了 5kg 。
控制尿酸方面:过去尿酸在 600 以上(体检报告记录)。现在戒掉了平时爱喝的含糖饮料,只在攀岩时喝一些运动饮料补充体力。日常喝苏打汽水(碱性),虽然以前也没有过多吃海鲜,现在是几乎不碰了。没有吃降尿酸的药。最近尿酸日常在 450 ~ 550 之间(每两天自测一次)。高低感觉和休息状态有关。如果白天过于劳累,晚上又没有好好休息的话,尿酸值也会明显升高。
脚没有再疼过,但总有点隐隐的感觉,可能是心理作用罢了。如果明年还不能降到 400 以下,考虑吃点药。
我知道跑步锻炼是一个漫长的过程,无法立竿见影。等半年以后再追加记录。
A few years ago, I was in a design review at a fintech company, polishing the expense management flows. It was a routine session where we reviewed the logic behind content and design decisions.
While looking over the statuses for submitted expenses, I noticed a label saying ‘In approval’. I paused, re-read it again, and asked myself:
“Where is it? Are the results in? Where can I find them? Are they sending me to the app section called “Approval”?”
This tiny label made me question what was happening with my money, and this feeling of uncertainty was quite anxiety-inducing.
My team, all native English speakers, did not flinch, even for a second, and moved forward to discuss other parts of the flow. I was the only non-native speaker in the room, and while the label made perfect sense to them, it still felt off to me.
After a quick discussion, we landed on ‘Pending approval’ — the simplest and widely recognised option internationally. More importantly, this wording makes it clear that there’s an approval process, and it hasn’t taken place yet. There’s no need to go anywhere to do it.
Some might call it nitpicking, but that was exactly the moment I realised how invisible — yet powerful — the non-native speaker’s perspective can be.
In a reality where user testing budgets aren’t unlimited, designing with familiar language patterns from the start helps you prevent costly confusions in the user journey.
Those same confusions often lead to:
Global products are often designed with English as their primary language. This seems logical, but here’s the catch:
Roughly 75% of English-speaking users are not native speakers, which means 3 out of every 4 users.
Native speakers often write on instinct, which works much like autopilot. This can often lead to overconfidence in content that, in reality, is too culturally specific, vague, or complex. And that content may not be understood by 3 in 4 people who read it.
If your team shares the same native language, content clarity remains assumed by default rather than proven through pressure testing.
The price for that is the accessibility of your product. A study by National Library of Medicine found that US adults who had proficiency in English but did not use it as their primary language were significantly less likely to be insured, even when provided with the same level of service as everyone else.
In other words, they did not finish the process of securing a healthcare provider — a process that’s vital to their well-being, in part, due to unclear or inaccessible communication.
If people abandon the process of getting something as vital as healthcare insurance, it’s easy to imagine them dropping out during checkout, account setup, or app onboarding.

Non-native content designers, by contrast, do not write on autopilot. Because of their experience learning English, they’re much more likely to tune into nuances, complexity, and cultural exclusions that natives often overlook. That’s the key to designing for everyone rather than 1 in 4.
Non-native Content Designers Make Your UX GlobalWhen a non-native speaker has to pause, re-read something, or question the meaning of what’s written, they quickly identify it as a friction point in the user experience.
Why it’s important: Every extra second users have to spend understanding your content makes them more likely to abandon the task. This is a high price that companies pay for not prioritising clarity.
Cognitive load is not just about complex sentences but also about the speed. There’s plenty of research confirming that non-native speakers read more slowly than native speakers. This is especially important when you work on the visibility of system status — time-sensitive content that the user needs to scan and understand quickly.
One example you can experience firsthand is an ATM displaying a number of updates and instructions. Even when they’re quite similar, it still overwhelms you when you realise that you missed one, not being able to finish reading.
This kind of rapid-fire updates can increase frustration and the chances of errors.

They tend to review and rewrite things more often to find the easiest way to communicate the message. What a native speaker may consider clear enough might be dense or difficult for a non-native to understand.
Why it’s important: Simple content better scales across countries, languages, and cultures.
When things do not make sense, non-native speakers challenge them. Besides the idioms and other obvious traps, native speakers tend to fall into considering their life experience to be shared with most English-speaking users.
Cultural differences might even exist within one globally shared language. Have you tried saying ‘soccer’ instead of ‘football’ in a conversation with someone from the UK? These details may not only cause confusion but also upset people.
Why it’s important: Making sure your product is free from culture-specific references makes your product more inclusive and safeguards you from alienating your users.
Being a non-native speaker themselves, they have experience with products that do not speak clearly to them. They’ve been in the global user’s shoes and know how it impacts the experience.
Why it’s important: Empathy is a key driver towards design decisions that take into account the diverse cultural and linguistic background of the users.
How Non-native Content Design Can Shape Your Approach To DesignYour product won’t become better overnight simply because you read an inspiring article telling you that you need to have a more diverse team. I get it. So here are concrete changes that you can make in your design workflows and hiring routines to make sure your content is accessible globally.
When you launch a new feature or product, it’s a standard practice to run QA sessions to review visuals and interactions. When your team does not include the non-native perspective, the content is usually overlooked and considered fine as long as it’s grammatically correct.
I know, having a dedicated localisation team to pressure-test your content for clarity is a privilege, but you can always start small.
At one of my previous companies, we established a ‘clarity heroes council’ — a small team of non-native English speakers with diverse cultural and linguistic backgrounds. During our reviews, they often asked questions that surprised us and highlighted where clarity was missing:
These questions flag potential problems and help you save both money and reputation by avoiding thousands of customer service tickets.
Even if your product does not have major releases regularly, it accumulates small changes over time. They’re often plugged in as fixes or small improvements, and can be easily overlooked from a QA perspective.
A good start will be a regular look at the flows that are critical to your business metrics: onboarding, checkout, and so on. Fence off some time for your team quarterly or even annually, depending on your product size, to come together and check whether your key content pieces serve the global audience well.
Usually, a proper review is conducted by a team: a product designer, a content designer, an engineer, a product manager, and a researcher. The idea is to go over the flows, research insights, and customer feedback together. For that, having a non-native speaker on the audit task force will be essential.
If you’ve never done an audit before, try this template as it covers everything you need to start.
If you haven’t done it already, make sure your voice & tone documentation includes details about the level of English your company is catering to.
This might mean working with the brand team to find ways to make sure your brand voice comes through to all users without sacrificing clarity and comprehension. Use examples and showcase the difference between sounding smart or playful vs sounding clear.
Leaning too much towards brand personality is where cultural differences usually shine through. As a user, you might’ve seen it many times. Here’s a banking app that wanted to seem relaxed and relatable by introducing ‘Dang it’ as the only call-to-action on the screen.

However, users with different linguistic backgrounds might not be familiar with this expression. Worse, they might see it as an action, leaving them unsure of what will actually happen after tapping it.
Considering how much content is generated with AI today, your guidelines have to account for both tone and clarity. This way, when you feed these requirements to the AI, you’ll see the output that will not just be grammatically correct but also easy to understand.
Basic heuristic principles are often documented as a part of overarching guidelines to help UX teams do a better job. The Nielsen Norman Group usability heuristics cover the essential ones, but it doesn’t mean you shouldn’t introduce your own. To complement this list, add this principle:
Aim for global understanding: Content and design should communicate clearly to any user regardless of cultural or language background.
You can suggest criteria to ensure it’s clear how to evaluate this:
This one is often overlooked, but collaboration between the research team and non-native speaking writers is super helpful. If your research involves a survey or interview, they can help you double-check whether there is complex or ambiguous language used in the questions unintentionally.
In a study by the Journal of Usability Studies, 37% of non-native speakers did not manage to answer the question that included a word they did not recognise or could not recall the meaning of. The question was whether they found the system to be “cumbersome to use”, and the consequences of getting unreliable data and measurements on this would have a negative impact on the UX of your product.
Another study by UX Journal of User Experience highlights how important clarity is in surveys. While most people in their study interpreted the question “How do you feel about … ?” as “What’s your opinion on …?”, some took it literally and proceeded to describe their emotions instead.
This means that even familiar terms can be misinterpreted. To get precise research results, it’s worth defining key terms and concepts to ensure common understanding with participants.
At Klarna, we often ran into a challenge of inconsistent translation for key terms. A well-defined English term could end up having from three to five different versions in Italian or German. Sometimes, even the same features or app sections could be referred to differently depending on the market — this led to user confusion.
To address this, we introduced a shared term base — a controlled vocabulary that included:
Importantly, the term selection was dictated by user research, not by assumption or personal preferences of the team.

If you’re unsure where to begin, use this product content vocabulary template for Notion. Duplicate it for free and start adding your terms.
We used a similar setup. Our new glossary was shared internally across teams, from product to customer service. Results? Reducing the support tickets related to unclear language used in UI (or directions in the user journey) by 18%. This included tasks like finding instructions on how to make a payment (especially with the least popular payment methods like bank transfer), where the late fee details are located, or whether it’s possible to postpone the payment. And yes, all of these features were available, and the team believed they were quite easy to find.
A glossary like this can live as an add-on to your guidelines. This way, you will be able to quickly get up to speed new joiners, keep product copy ready for localisation, and defend your decisions with stakeholders.
‘Looking for a native speaker’ still remains a part of the job listing for UX Writers and content designers. There’s no point in assuming it’s intentional discrimination. It’s just a misunderstanding that stems from not fully accepting that our job is more about building the user experience than writing texts that are grammatically correct.
Here are a few tips to make sure you hire the best talent and treat your applicants fairly:
Instead, focus on the core part of our job: add ‘clear communicator’, ‘ability to simplify’, or ‘experience writing for a global audience’.
Over the years, there have been plenty of studies confirming that the accent bias is real — people having an unusual or foreign accent are considered less hirable. While some may argue that it can have an impact on the efficiency of internal communications, it’s not enough to justify the reason to overlook the good work of the applicant.
My personal experience with the accent is that it mostly depends on the situation you’re in. When I’m in a friendly environment and do not feel anxiety, my English flows much better as I do not overthink how I sound. Ironically, sometimes when I’m in a room with my team full of British native speakers, I sometimes default to my Slavic accent. The question is: does it make my content design expertise or writing any worse? Not in the slightest.
Therefore, make sure you judge the portfolios, the ideas behind the interview answers, and whiteboard challenge presentations, instead of focusing on whether the candidate’s accent implies that they might not be good writers.
Good Global Products Need Great Non-native Content DesignNon-native content designers do not have a negative impact on your team’s writing. They sharpen it by helping you look at your content through the lens of your real user base. In the globalised world, linguistic purity no longer benefits your product’s user experience.
Try these practical steps and leverage the non-native speaking lens of your content designers to design better international products.
by Oleksii Tkachenko (hello@smashingmagazine.com) at July 18, 2025 01:00 PM