Self-hosted service
YunYouJun/air-conditioner avatar
YunYouJun/air-conditioner

The default branch of this web air conditioner is the test deployment, and its only font is licensed for personal use

Yun Portable Air Conditioner - a small, portable air conditioner to keep you cool in summer.

3,459 stars457 forksTypeScriptMIT

At a glance

What is it?
YunYouJun/air-conditioner is a MIT-licensed browser air conditioner drawn in CSS rather than Canvas, last pushed to the dev branch on 2026-07-26. Its own documentation is unusually honest in places, listing a single disadvantage and an all-ticked roadmap, and unusually loose in others, with yarn commands in a pnpm project and a font licence that does not match the distribution model.
Who is it for?
This is a small, self-contained piece of web work that does one joke application very well, and it is a reasonable base to fork if you want a themed browser widget rather than a real utility. Two things to settle before you build on it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 15 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • YunYouJun/air-conditioner3,459 ★ · -7
Star history
Repository2026-09-282026-11-14
YunYouJun/air-conditioner3.5k3.5k

-7 Stars in the last 47 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The default branch is dev, which is also the test deployment

The README opens with a three-row table whose row labels are metaphorical.

The first is the bare machine, on the `main` branch, served at ac.yunyoujun.cn. The second is the test machine, on the `dev` branch, served at ac.yyj.moe. The third is the show home, which is a demo page that collects other people's installations.

So there are two live deployments and one gallery, and the naming convention maps branch to environment exactly.

The detail that matters is that the repository's default branch is `dev`, not `main`. A clone without arguments gives you the test deployment's source, not the production one. That is a deliberate arrangement for a project where the production build is the one people embed, and it is also a trap for a first-time reader who assumes the default branch is the release line.

The table also carries a link back to a blog post about the project's history, which is where the reasoning behind the split would live if you want it.

Nothing in the file warns you about the branch mapping. The table is the only place it appears.

The disadvantage list has exactly one item, and it is honest

The Features section is split into two lists, and the second one has a single entry.

The advantages are six lines: you can turn the air conditioner on anywhere, it is portable, it is low power because it is drawn with HTML and CSS rather than Canvas, it is silent, it is simple to operate, and it is easy to install.

The disadvantage is one line: there is no wind.

That is worth reading as a design decision rather than a joke. The low power claim follows directly from the rendering choice, and the two are stated together, so the author is describing what the artifact is rather than overselling it. A Canvas animation would give smoother fan motion and cost more battery on exactly the devices a portable widget is for.

The Todo list is also finished. Every entry is checked: the air conditioner itself, an energy consumption label, a temperature range of 16 to 31 degrees, wind styling in CSS, sound effects for the buttons and for the working noise, and support for the system's light and dark modes.

That last checkbox is the one that dates the project: automatic dark mode support implies a target of recent operating systems and browsers, and the temperature range is the only numeric specification anywhere in the file.

The install snippet hardcodes a 740 pixel height

The installation section is short, and the iframe is the whole of it:

html
<iframe height="740" src="https://ac.yunyoujun.cn"></iframe>

One line, no width, and a fixed height of 740 pixels in the attribute. There is no responsive example, no alternative height for mobile, and no note about what happens inside the frame on a small screen.

The copy around it says this is the quick way to install the air conditioner on your site, which is fair for a widget that is a fixed-size panel. A 740 pixel tall embedded frame is a tall element on any page, and how it behaves on a phone is left to the reader.

There is also a doorstep service section, which links a Hugo theme called Hugo-air-conditioner by another author. So there are at least two distribution paths beyond the iframe: the hosted widget and a theme that packages it for a static site generator.

The self-hosting path is a third. The Docker section names exactly two environment variables, `AC_NGINX_DOMAIN` and `AC_NGINX_PORT`, which is a small and legible surface: the domain to serve and the port to listen on, both for the nginx sitting in front of the application.

Turning ads off is a build-time constant, not a runtime setting

The development section has an environment variable section, and it contains one variable.

bash
cp .env.example .env
bash
# turn off ads
VITE_DISABLE_ADSENSE=true

The shipped example file sets the variable to an empty value, so the default build has the variable present but unset.

The `VITE_` prefix is the part worth understanding. Variables with that prefix are read at build time and substituted into the client bundle as literals. So `VITE_DISABLE_ADSENSE` is not a switch you flip on a running container; it is a constant baked into the JavaScript you deploy. Changing it means rebuilding.

That has a direct consequence for the self-hosting story. The Docker section advertises two runtime variables for the reverse proxy, but the ad behaviour is fixed when the image is built, and the image you pull is the one the author built. If you want ads off in a self-hosted copy, you need a build of your own, and the README does not say so.

It is a small thing and an honest feature, but it is the difference between a flag and a compile-time constant, and the file does not draw the line.

The development block says yarn in a project that declares pnpm

The Dev section is a single bash block, and it does not agree with the package manifest.

bash
# development preview
# yarn dev
yarn start
# http://localhost:3000/

# build the project
yarn build
# ./build

The commands are yarn. The repository root says otherwise: `packageManager` is set to pnpm at a pinned version, and there is a pnpm-lock.yaml and a pnpm-workspace.yaml. The `yarn dev` line is commented out, which suggests the author moved off that script and left the rest of the block behind.

The output comment is wrong too. The build command in package.json is `pnpm -r --filter './packages/*' run build`, a recursive workspace build filtered to the packages directory, so each package produces its own output rather than a single `./build` directory.

The scripts themselves are more coherent than the README. There are separate pairs for the React and Vue packages, another pair for the documentation site, and single entries for lint, test and typecheck. A `check` script runs lint, typecheck, test and build in sequence, which is the one command worth knowing if you fork this.

The toolchain around it is worth a sentence too: UnoCSS for styling, Vitest for unit tests, Playwright for browser tests, ESLint through a shared configuration, and tsdown for bundling.

The only font is licensed for personal use in a project meant to be embedded

The Ref section names two assets, and the first has a licence worth reading.

The digit font is Digital 7, from a font download site, and the README states its licence in plain words: free for personal use. The second is the air conditioner's working sound, taken from a sound effects site and attributed to the person who recorded it.

Now set that beside the distribution model. The README's own installation instructions are an iframe embed on someone else's website, plus a Docker image for self-hosting, plus a theme for a static site generator. An iframe on a commercial site is not personal use in any reading of that phrase.

The project is MIT licensed, and the font is a separate work with its own terms, so the MIT grant on the code does not reach it. Nothing in the file acknowledges the tension, and nothing says the font is substituted at runtime or replaced in a fork.

That does not make the project unusable. It makes it a decision for anyone shipping a fork publicly rather than for themselves, and it is exactly the sort of thing that is easier to notice now than after a design has shipped.

Packages publish under a different scope, and every dependency uses a catalog reference

Two details in the package manifest are about how this project is distributed rather than how it works.

The first is the scope. The repository is YunYouJun on GitHub, the author field names YunYouJun, and the repository URL points at that organisation. The release script publishes two filtered packages, one of them named `@yunlefun-home/air-conditioner-core`.

So the code that is published to a registry lives under a scope that matches neither the GitHub organisation nor the homepage domain, which is ac.yunyoujun.cn. For a fork, that is the first thing to change, because the existing name is taken and the relationship between the repository and the published package is not visible from either side.

The second is the dependency declaration. Every one of the ten devDependencies is written as `catalog:` rather than as a version. That is the pnpm catalog mechanism, where the actual versions live once in the workspace file and packages refer to them by name.

It is a clean way to keep one version of a tool across a monorepo, and it is why the manifest has no version numbers to audit. Anything reading this repository to check for a vulnerable dependency has to open pnpm-workspace.yaml as well.

The root package itself is marked private and never published. What ships is the two packages under packages/, which is where the React and Vue builds live.

Editorial conclusion

This is a small, self-contained piece of web work that does one joke application very well, and it is a reasonable base to fork if you want a themed browser widget rather than a real utility. Two things to settle before you build on it. The published packages use a scope that does not match the GitHub organisation, so check where your fork would land. And the digit font is documented as free for personal use, which does not obviously cover an iframe embedded on a public site, so read that licence before shipping one.

Frequently asked questions

How do I add the air-conditioner widget to my site?

With an iframe: `<iframe height="740" src="https://ac.yunyoujun.cn"></iframe>`. The height is fixed at 740 pixels in the documented snippet, and there is also a Hugo theme and a Docker image for self-hosting.

How do I turn off the ads in air-conditioner?

Set VITE_DISABLE_ADSENSE=true in a .env file copied from .env.example, which ships with the variable empty. The VITE_ prefix means the value is substituted at build time, so turning ads off in a self-hosted copy requires a rebuild.

Which branch of air-conditioner is the production one?

The main branch is the bare machine deployment at ac.yunyoujun.cn, and the dev branch is the test machine at ac.yyj.moe. The repository's default branch is dev, so an unqualified clone gives you the test deployment's source.

What are the disadvantages of air-conditioner?

One, listed as such in the README: there is no wind. The advantages listed are opening it anywhere, portability, low power from drawing with HTML and CSS rather than Canvas, silence, simple operation and easy installation.

How do I build air-conditioner locally?

The README shows a yarn block, but the manifest declares pnpm as the package manager at a pinned version, with a pnpm workspace and lockfile. The manifest's build script recurses over the packages directory, and a separate check script runs lint, typecheck, test and build.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. README
  5. YunYouJun/air-conditioner on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yunyoujun-air-conditioner.svg)](https://hysenlabs.com/projects/yunyoujun-air-conditioner)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/yunyoujun-air-conditioner.svg)](https://hysenlabs.com/projects/yunyoujun-air-conditioner)

Was this useful?