kirarikirari.cafe
homeexplorecsslua
the handbook

make your corner exactly yours

everything you can change, in one place. start with a skin, drop into raw CSS when you want pixel control, then teach your desktop new tricks with Lua.

1getting started
claim a handle, open the desktop, find “edit my page”.
2skins & wallpaper
six built-in looks, or build your own palette and fonts.
3custom css
raw CSS on your public page. override anything.
4lua scripts
program your desktop with sandboxed Lua.
step one

getting started

kirari is a bio link that boots like a little desktop. everything you customize lives inside it, so there's no separate settings site.

  1. 1Claim a handle from the home page (a handle and password is all it takes, no email).
  2. 2You land on your desktop. Open the edit my page app from the icon grid or the start menu.
  3. 3Scroll that window. It goes from simple (name, links, avatar) all the way down to custom CSS.
  4. 4For desktop tricks, open the scripts app instead. That's where Lua lives.
Not sure what a page can become? browse explore. Every card is a real member page in its own skin.
the easy way

skins & wallpaper

the fastest restyle. In edit my page you can pick a skin, a page background pattern, drop shadows, color grades and effects, no code needed. Six skins ship built in; try them right here (this page reskins live):

want your own? the skin editor lets you set your palette, fonts and wallpaper, then export it as a code to share or reuse. Whatever skin you're on defines the color variables the CSS section below builds on.

Saved looks go further: near the top of edit my page you can snapshot your wholedesign (skin, fonts, layout, effects, background, music, even your custom CSS) into a named preset, keep up to 5, and swap between them in one click. Hit on any look to copy a shareable code, or paste someone else's to load theirs. Your bio, links and photos always stay yours.
power user

custom css

okay, the fun part. Scroll to the very bottom of edit my page and you'll find a plain text box labelled custom CSS. Anything you type in there gets stapled onto your public page, and it loads after the skin does, which is the whole trick: because it comes last, it wins. You can repaint, move, hide, or animate basically anything.

Two things worth burning into your memory before you touch it. One: it only affects your public page, never the dashboard, so refresh your real page in another tab to see changes. Two: if you paste something cursed and the page goes sideways, don't panic. Just empty the box and hit save. There's no way to permanently break it.

first, those "--" things

You'll see var(--accent) everywhere in the examples. Those are CSS variables: little named boxes your current skin fills in with a value. The skin sets --accent to its pink (or blue, or whatever), and you just say "give me the accent" instead of writing a hex code. Here's what each one is actually painting right now, on the skin you've got picked up top:

--accentyour main color: buttons, active links, highlights
--accent-2the secondary, for gradients and accents
A--on-accenttext that sits ON an accent fill (stays readable)
Aa--inkyour normal text color
Aa--ink-softthe muted text: captions, timestamps
--panelcard background
--panel-2a raised / inset surface
--linehairline borders and dividers
--radiushow round the corners are
Ki--font-displayheadings and your name
Ki--font-bodyparagraph text (you're reading it)
Ki--font-pixelthe tiny pixel labels
TIP
This is why you reuse the variables instead of hardcoding a color like #ff6fb5. The swatch above is live, so flip the skin dots in the top-right and watch every value change. Lean on the variables and your page follows along with every skin. Hardcode a hex and it'll clash the second you (or a visitor on a different skin) look at it.
where you're allowed to aim

Your page is built out of a few labelled chunks. Point at these ids and you're targeting a whole section without guessing at class names:

#s-cardthe whole profile card, the box itself
#s-topname, avatar, status block (the header)
#s-linksyour links list
#s-photosthe photo gallery
#s-notesthe guestbook
mainthe whole page column, everything at once
✦ dressing the card: a picture-frame border

A favourite from sites like rentry: wrap the whole card in an image border (a border-image), so the frame is a little pixel-art ribbon or lace instead of a plain line. Point at #s-card and give it two lines. The first makes room for the frame; the second paints your image into that room. One catch: the card already has its own border baked onto the element, so you add !importantto those two lines to win, and the border-image line must come after the border line (setting borderwipes a border-image, so order matters).

#s-card {
  /* line 1: 18px of see-through border = the frame's thickness */
  border: 18px solid transparent !important;
  /* line 2: your image, sliced 30% in from each edge, tiled 'round' */
  border-image: url(YOUR-IMAGE-URL) 30% / 20px / 5px round !important;
}

Read border-image as: the image, then how far in to slice each corner (30%), then how thick to draw it (20px), then how far to bleed past the edge (5px), then how to fill the sides. round tiles it neatly; try stretch or repeat for a different feel. Use a PNG with transparent corners and it reads like a real frame.

One thing worth knowing: a border-image is always painted as a square frame, because the spec gives it no way to follow border-radius. A framed card would read as pointy no matter how round you set it, because its own fill keeps painting out to the square corners underneath the frame. So as soon as a frame is on, we draw the card's face on a layer that sits inside the frame, where it rounds properly. Your frame is untouched. The one visible difference: if your frame is see-through (lace, ribbon, anything with holes), the holes now show the page behind the card instead of the card's own background.

✦ tweening: making things move

"Tweening" is the old animator's word for the frames between two poses. You draw the start and the end, and the computer fills in the middle so it slides smoothly instead of snapping. CSS does exactly this, and it's honestly the thing that makes a page feel aliveinstead of a flat wall of links. There are two ways in.

1. transitions (the easy 90%)

A transition says "when this property changes, don't jump, glide there over some time." You define a normal look, then a second look for a state like :hover, and the transition line handles the in-between. Hover this box:

lift me
hover the card ↑
#s-links a {
  /* property   how long   the curve */
  transition: transform .25s cubic-bezier(.2,.7,.2,1),
              box-shadow .25s ease;
}
#s-links a:hover {
  transform: translateY(-6px);              /* float up 6px */
  box-shadow: 0 14px 30px -12px rgba(0,0,0,.4);
}

Read the transition line as three parts: what to animate (transform), how long (.25s), and the curve, the personality of the movement. List several, comma-separated, to tween more than one thing at once. A tiny gotcha that trips everyone up: the transitionlives on the resting rule, not the :hover one. Put it on hover only and it'll ease in but snap back.

the curve matters more than you'd think

That last value, the timing function, is the difference between "cheap" and "expensive" feeling. It decides whether the motion starts slow, ends slow, or overshoots. Same distance, same duration, watch how different they feel:

linear
ease
ease-in-out
cubic-bezier(spring)
all four cross the same gap in the same time

linear is robotic and you almost never want it. ease-in-outis the safe, pretty default. And a cubic-bezier with a number above 1 (like the last one) overshoots and springs back. Great for something playful, too much for everything.

2. keyframes (things that move on their own)

Transitions need a trigger, like a hover or a click. When you want something to move forever with no input (a glow that breathes, an avatar that floats), you write an @keyframesblock: named poses at 0%, 50%,100%, and CSS tweens between them on a loop. These two run on their own:

no hover needed, these loop
/* the floating square */
@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-9px); }
}
#s-top img {                 /* your avatar */
  animation: bob 2.4s ease-in-out infinite;
}

/* the breathing ring */
@keyframes halo {
  0%   { box-shadow: 0 0 0 0   var(--accent); }
  70%  { box-shadow: 0 0 0 12px transparent; }
  100% { box-shadow: 0 0 0 0   transparent; }
}
#s-top img { animation: halo 1.8s ease-out infinite; }

The animation shorthand is: name, duration, curve, then infinite to loop forever (or a number like 3to run it three times). One kindness to your visitors: some people get motion-sick from looping animation, so it's polite to switch heavy stuff off for them:

@media (prefers-reduced-motion: reduce) {
  #s-top img { animation: none; }
}
putting it together

Here's a "living link" that uses both. It rests calm, then on hover it glides up, glows in your accent, and its little arrow slides over. Nothing here is hardcoded, so it matches whatever skin you're wearing:

#s-links a {
  transition: transform .2s ease, color .2s ease, text-shadow .2s ease;
}
#s-links a:hover {
  transform: translateX(4px);
  color: var(--accent);
  text-shadow: 0 0 10px color-mix(in srgb, var(--accent) 60%, transparent);
}
quick copy-paste, no reading required
/* space out your name */
#s-top h1 { letter-spacing: 3px; }

/* rounder cards everywhere */
:root { --radius: 24px; }

/* a soft gradient behind the whole page */
main {
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--accent) 12%, transparent),
    transparent 60%);
}

/* hide the guestbook entirely */
#s-notes { display: none; }
Stuck? Open your public page, right-click something, and hit Inspect. Your browser's dev tools show you the exact element and let you try CSS live before you paste it in the box.
power user

lua scripts

open the scripts app for a real Lua 5.3 editor. Scripts run in your browser only, so they never execute for visitors. They're hard-sandboxed: no files, no network, no load(), and a runaway loop hits an instruction cap and stops instead of freezing the tab. The only door out of the sandbox is the kirari table.

the kirari api
kirari.toast(text)little popup on the desktop
kirari.say(text)the pet speaks (≤80 chars)
kirari.mood(text)set your status line
kirari.note(text)spawn a sticky note
kirari.skin(id)wear a skin: "sugar", "kuro", "noir", "cyber", "spooky"…
kirari.open(app)open a window: "chess", "album", "market"…
kirari.wallpaper(hex)solid desktop color, e.g. "#1a0f24"
kirari.every(n, fn)run fn every n seconds (n≥1, max 5 timers)
print(...)write to the console pane
a live clock in your status
kirari.every(30, function()
  kirari.mood("it is " .. os.date("%H:%M") .. " somewhere (here)")
end)
a skin disco (runs until you hit stop)
local skins = {"sugar","kuro","noir","cyber","spooky"}
local i = 1
kirari.every(4, function()
  kirari.skin(skins[i])
  i = i % #skins + 1
end)
Timers keep running until you press stop or close the scripts window. Up to 5 timers at once.
that's the whole toolbox. go make a mess.← back to kirari.cafe