个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Stretchy Liquid Glass (iOS style) for MacOS and Websites
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Stretchy Liquid Glass (iOS style) for macOS and websites.
AppKit buttons for macOS 26 that swell when pressed, follow your drag, stretch along the pull,
and wobble back — plus a dependency-free web version you can try in a browser.
If this made you smile, a ⭐ helps others find it.
macOS 26's NSGlassEffectView gives you the Liquid Glass material. It does not give you the feel — the
way glass in a polished app gives under your finger. JellyGlass adds that feel, and writes down what it took
to get it working inside AppKit's glass containers (it is less obvious than it sounds — see
How it works).
The numbers are not guessed: they were measured, frame by frame, from a screen recording of a production app (MEASUREMENTS.md). So was the lens of the system glass that the web version copies (HOW_IT_WORKS.md §10).
In the browser (any modern browser, no install): https://54degree5.github.io/JellyGlass/
Download the demo app (macOS 26+, Apple silicon and Intel): latest release.
It is ad-hoc signed, not notarized: unzip, then right-click → Open (or xattr -dr com.apple.quarantine JellyGlassDemo.app).
Or build it (macOS 26+, Xcode 26 / Swift 6.2+):
git clone https://github.com/54degree5/JellyGlass.git
cd JellyGlass
swift run -c release JellyGlassDemo
Drag any button. Keys: K on top ⇄ merge · S spring (jelly / wobble / snappy) · A stretch
(standard / wide / heavy) · C glass style.
// Package.swift
.package(url: "https://github.com/54degree5/JellyGlass.git", branch: "main")
import JellyGlass
let mic = JellyGlassButton(size: CGSize(width: 44, height: 44), symbols: ["mic"])
mic.spring = .jelly // .jelly (default) · .wobble · .snappy
mic.stretch = .standard // .standard · .wide · .heavy
mic.liftsOnPress = true // false: melt into neighbours instead
mic.look = .transparent // default: clear refractive glass, the scene behind stays visible; .system = AppKit's frosted material
// Optional: group buttons so neighbours share one glass surface and merge when `liftsOnPress` is false.
let group = NSGlassEffectContainerView()
group.spacing = 14
let holder = NSView(frame: group.bounds)
holder.addSubview(mic)
group.contentView = holder
Any view works as content: JellyGlassButton(size:contentView:). The button deforms its glass and its content.
With d the cursor's distance in points from where the button was pressed:
| formula | at d = 38 / 80 / 185 / 800 pt | |
|---|---|---|
body travel t(d) | 6.5 · ln(1 + d / 28) | 5.6 / 8.8 / 13.2 / 22 pt |
stretch e(d) | 0.105 · ln(1 + d / 50) · d² / (d² + 40²) | 0.03 / 0.08 / 0.16 / 0.30 |
Beyond ~200 pt the recorded body travel plateaus (it contains a single long flick), but by hand the app keeps stretching, so the model deliberately keeps growing — slowly, without a ceiling (see the shaded area below).
Any size. The numbers above come from a 44 pt circle. For other sizes the absolute change follows them, not the
ratio: along the pull the factor is 1 + e·√(44/L), across it 1 − (1 − 1/(1+e))·(44/L), where L is the button's length
on that axis. A 44 pt circle behaves exactly as measured; a 108 pt pill pulled down keeps its width (−2 pt instead of
−10 pt); a 360 pt bar pulled sideways grows by ~9 % instead of ~30 %. Press swell scales the same way. (The AppKit glass
margin grows with the width too, so long buttons are never clipped.)
Pulled along an axis the shape scales ×(1+e) along it and ÷(1+e) across it. Pulled diagonally the
axial part fades out and the whole button grows 1 + 0.6·e instead. Springs (stiffness / damping):
| preset | held | after release |
|---|---|---|
jelly | 1500 / 70 | 420 / 12 — swings past rest once or twice |
wobble | 1500 / 70 | 240 / 6 |
snappy | 3600 / 120 | 3600 / 120 — back in ~60 ms |
The short version of what is not in any documentation:
NSGlassEffectView is made larger than the button (a 90 pt margin) and its silhouette is set with
the private _setPath: — the resting capsule, scaled and shifted every frame — and the container is asked
to re-layout each frame so it re-reads the path. The icons live in the content view with their own spring._interactionState = 1 lifts a glass out of its group and draws it on top of its neighbours
(a highlight, no merging). That is the default "on top" behaviour; leave it off and the button melts
into neighbours instead.spacing / 2, but they
start leaning toward each other already below spacing. Resting gap ≥ spacing avoids pointed tips at rest.Full write-up with the layer tree and the dead ends: docs/HOW_IT_WORKS.md.
docs/index.html is a single file, no dependencies. Open it and, after a moment, a cursor starts
pressing the buttons and wandering with them (press anything to take over; ▶ demo brings it back).
The glass is real, not a CSS imitation: a WebGL2 fragment shader draws the scene behind the buttons and draws it again through each button's capsule. Near the rim the sample point is moved along the edge normal by a profile measured from the real macOS 26 glass (the outer rim shows the inside of the glass flipped, then comes a short magnified band, then the glass is 1:1), with a hair of dispersion, a light blur and a thin bright contour that picks up the colour under it (how it was measured). Each capsule follows its button's jelly motion every frame, so the refraction stretches and wobbles with it (120 fps on a retina MacBook). The physics is the same as the AppKit version: same formulas, same springs.
Any shape, any length: docs/shapes.html runs the same physics on a circle, a
pill, a 560 px bar, a squircle, a star, a heart, a hexagon, a triangle, a plus sign and a wobbly blob. A shape is just a
signed-distance function in the shader; refraction, rim and shadow come from its gradient, so any closed shape works.
In AppKit the private _setPath: takes any CGPath.
Two honest limits: the glass refracts its own scene (a shader cannot see the DOM, so it works because the background
is drawn by the same shader), and without WebGL2 the page falls back to a frosted CSS glass. Making it refract arbitrary
page content needs a page capture or Chromium's SVG backdrop-filter; contributions welcome.
_setPath: and _interactionState are private AppKit selectors, resolved at runtime
(they exist on macOS 26), and the clear look edits the inputs of the private glassBackground backdrop filter. Fine for tools and experiments, not for the App Store. If a selector goes missing
the call is skipped and the button degrades to icon animation only.NSGlassEffectView.JellyGlass { … })Issues and PRs welcome — especially measurements from other apps (record the screen, drop the file in an issue), and anything that makes the AppKit side less private-API-dependent.
MIT — see LICENSE.